Complete the server-backed storage layer for PubScript so the app can run against a real Fastify + SQLite backend instead of only local IndexedDB. Server (@pubscript/server): - db.ts: better-sqlite3 schema + migrations (users, shares, projects, books, chapters, entities, boards, ai_sessions, audit); WAL mode; idempotent admin bootstrap via PUBSCRIPT_ADMIN_EMAIL/PASSWORD. - auth.ts: argon2 hashing, JWT-ish session tokens, guards, auth routes. - routes.ts: REST CRUD for projects/books/chapters/entities/boards/ai_sessions with multi-tenant isolation and optimistic concurrency (baseRev). - ws.ts: Fastify websocket hub for autosave + presence events. - mapping.ts: row <-> core type conversion. Core (@pubscript/core): - Zod domain schemas (projects, books, chapters, entities, boards, ai_sessions) exported from index. - storage-local.ts (IndexedDB/Dexie) and storage-server.ts (REST) adapters implementing the StorageAdapter contract. Tests & tooling: - storage.contract.test.ts: parameterized over both adapters; wired as `pnpm contract` and added to CI. - seed.ts (`pnpm --filter server seed`): creates demo project "The Glass Harbor" — 3 chapters of prose, 4 linked characters, 1 world location, 6-node board. - Dockerfile + compose wiring for the server runtime. Acceptance: seed project opens with 3 chapters, 4 linked characters, 1 board.
6.4 KiB
CHUNK #1 — Scaffold & Foundations
Status: ✅ COMPLETE — verified green (typecheck, test, lint, build, and docker build + runtime health check all pass). Duration: ~1 day · Prereqs: none (this chunk creates the repo) · Follows: — · Unblocks: CHUNK #2
Purpose: Stand up the entire monorepo with strict TypeScript, the app shell, the theming system, the packages/core contracts, a Fastify server skeleton, and a green CI pipeline. When this chunk is done, pnpm dev runs a themed, navigable app shell and pnpm test is green — everything later chunks build on.
Steps
1. Workspace & tooling
cdto the repo root and run:pnpm init→ set"name": "pubscript","packageManager": "pnpm@latest".- Create
pnpm-workspace.yaml:packages: - "apps/*" - "packages/*" - "server"
- Root
package.jsonscripts:dev,build,test,lint,typecheck,contract(each fanning out to workspaces viapnpm -r run <name>). tsconfig.base.jsonwith"strict": true,"noUncheckedIndexedAccess": true,"exactOptionalPropertyTypes": true,"moduleResolution": "bundler","target": "ES2022". Every package extends it.- ESLint (flat config,
typescript-eslintrecommended +react-hooks) and Prettier at root;.editorconfig;.gitignore(node_modules, dist, .env, *.sqlite, desktop/target). vitest.config.tsat root (workspace mode) sopnpm testruns every package.
2. apps/web — the app
- Scaffold: Vite 6 + React 19. Add
react-routerv7,zustandv5, Tailwind v4 (CSS-first config via@tailwindcss/vite). - Create the app shell in
apps/web/src/:AppShell.tsx— left sidebar (Projects / Characters / World / Plot / Whiteboard / AI / Settings), top bar (project title, save status, search), main content outlet.CommandPalette.tsx—Ctrl/Cmd+Kpalette (jump to chapter, entity, setting, action).- Routes:
/projects,/projects/:id/write,/projects/:id/characters,/world,/plot,/whiteboard,/ai,/settings.
- Settings store (
apps/web/src/state/settings.ts, zustand + persist): theme id, accent, font family/size/leading/measure, density, autosave interval, per-role AI defaults. Persisted to localStorage (web) / Tauri store (desktop) behind oneSettingsBackendinterface.
3. packages/ui — design system
- Theme engine (
packages/ui/src/theme.ts): CSS-variable token map. Implement the four themes — Ink (dark, default), Parchment (warm light), Nocturne (deep indigo), Terminal (monochrome mono) — as token objects;applyTheme(id)setsdocument.documentElement.dataset.theme; accent color and density are runtime overrides. - Component kit (typed, Tailwind-styled, no external UI framework):
Button,Input,Select,Modal,Drawer,Tooltip,Tabs,Badge,EmptyState,Spinner,Toast. packages/ui/src/AppShell.css— base typography, focus rings, scrollbars, reduced-motion support.
4. packages/core — shared contracts
packages/core/src/types.ts— every domain type from PLAN.md §3.3 (User, Project, Book, Chapter, Character, WorldItem, PlotBeat, TimelineEvent, Board, Note, AISession, Suggestion) as strict TS interfaces + aRole/Permissionunion.packages/core/src/storage.ts— theStorageAdapterinterface exactly as in PLAN.md §3.4, plusStoreEventunion (doc-changed,entity-changed,board-changed,ai-session-changed,presence).packages/core/src/ids.ts— ULID generator + validator.packages/core/src/time.ts— UTC ISO helpers.packages/core/src/tokens.ts—countTokens(text)(wrapgpt-tokenizer, lazy-loaded) +allocateBudget({system, context, history, output, total})returning per-slot token caps. Unit-test both.
5. server/ — Fastify skeleton
server/package.json(ESM):fastify,@fastify/static,@fastify/websocket,@fastify/cookie,better-sqlite3,argon2,zod.server/src/index.ts— boots Fastify, servesapps/web/diststatically (so one container serves app + API), exposes:GET /api/health→{ ok, version, db: 'up' }GET /api/version
server/src/config.ts— env-driven:PUBSCRIPT_DB(path, default./data/pubscript.sqlite),PORT,PUBSCRIPT_ORIGIN(CORS),PUBSCRIPT_ADMIN_EMAIL(bootstrap admin),PUBSCRIPT_TOKEN_TTL.
6. CI — .github/workflows/ci.yml
On push/PR: pnpm install --frozen-lockfile → pnpm lint → pnpm typecheck → pnpm test → pnpm build (web + server) → docker build -f docker/Dockerfile . (smoke). Post the test summary as an artifact.
7. docker/
docker/Dockerfile— multi-stage:node:22-alpinebuild stage (pnpm, build web + server) → slim runtime stage serving the built app + running the API. Non-root user.HEALTHCHECKhitting/api/health.docker/compose.yaml— servicepubscript, port 8080, volume./data:/app/data,restart: unless-stopped.
8. Docs & conventions
docs/CONVENTIONS.md— commit style, file layout rules, "code againstpackages/core, never against a backend" rule.README.mdat root — 10-line quickstart:pnpm i && pnpm dev,docker compose up, where things live.
Acceptance criteria (all must pass)
pnpm i && pnpm buildsucceeds with zero TS errors understrict.pnpm dev→ app shell loads at/, sidebar + top bar render, all 8 routes navigate,Ctrl+Kpalette opens and jumps to a route.- Switching theme (Ink → Parchment → Nocturne → Terminal) restyles the whole shell instantly; choice survives a reload.
pnpm testgreen: token budget math, ULID, theme token map, settings persistence.docker compose up→curl localhost:8080/api/healthreturns{"ok":true,...}and the app shell loads in the browser.- CI run is green end-to-end (lint, typecheck, test, build, docker).
Definition of done
Repo is a working, themed, CI-green monorepo. Any later chunk can assume: strict types exist in packages/core, the shell renders, settings persist, and docker compose up serves the app.
Risks & fallbacks
- Tailwind v4 + React 19 friction → if a component misbehaves, pin exact versions from the Vite 6 template and record them in
docs/CONVENTIONS.md. - better-sqlite3 native build in Docker → use
node:22-alpinewithpython3 make g++in the build stage; if it still fails, fall back tonode:22-slim(Debian) — record the choice in an ADR.