Files
PubScript/CHUNK#1.md
T

6.3 KiB

CHUNK #1 — Scaffold & Foundations

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

  1. cd to the repo root and run:
    • pnpm init → set "name": "pubscript", "packageManager": "pnpm@latest".
    • Create pnpm-workspace.yaml:
      packages:
        - "apps/*"
        - "packages/*"
        - "server"
      
  2. Root package.json scripts: dev, build, test, lint, typecheck, contract (each fanning out to workspaces via pnpm -r run <name>).
  3. tsconfig.base.json with "strict": true, "noUncheckedIndexedAccess": true, "exactOptionalPropertyTypes": true, "moduleResolution": "bundler", "target": "ES2022". Every package extends it.
  4. ESLint (flat config, typescript-eslint recommended + react-hooks) and Prettier at root; .editorconfig; .gitignore (node_modules, dist, .env, *.sqlite, desktop/target).
  5. vitest.config.ts at root (workspace mode) so pnpm test runs every package.

2. apps/web — the app

  1. Scaffold: Vite 6 + React 19. Add react-router v7, zustand v5, Tailwind v4 (CSS-first config via @tailwindcss/vite).
  2. 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+K palette (jump to chapter, entity, setting, action).
    • Routes: /projects, /projects/:id/write, /projects/:id/characters, /world, /plot, /whiteboard, /ai, /settings.
  3. 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 one SettingsBackend interface.

3. packages/ui — design system

  1. 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) sets document.documentElement.dataset.theme; accent color and density are runtime overrides.
  2. Component kit (typed, Tailwind-styled, no external UI framework): Button, Input, Select, Modal, Drawer, Tooltip, Tabs, Badge, EmptyState, Spinner, Toast.
  3. packages/ui/src/AppShell.css — base typography, focus rings, scrollbars, reduced-motion support.

4. packages/core — shared contracts

  1. 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 + a Role/Permission union.
  2. packages/core/src/storage.ts — the StorageAdapter interface exactly as in PLAN.md §3.4, plus StoreEvent union (doc-changed, entity-changed, board-changed, ai-session-changed, presence).
  3. packages/core/src/ids.ts — ULID generator + validator. packages/core/src/time.ts — UTC ISO helpers.
  4. packages/core/src/tokens.ts — countTokens(text) (wrap gpt-tokenizer, lazy-loaded) + allocateBudget({system, context, history, output, total}) returning per-slot token caps. Unit-test both.

5. server/ — Fastify skeleton

  1. server/package.json (ESM): fastify, @fastify/static, @fastify/websocket, @fastify/cookie, better-sqlite3, argon2, zod.
  2. server/src/index.ts — boots Fastify, serves apps/web/dist statically (so one container serves app + API), exposes:
    • GET /api/health → { ok, version, db: 'up' }
    • GET /api/version
  3. 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-alpine build stage (pnpm, build web + server) → slim runtime stage serving the built app + running the API. Non-root user. HEALTHCHECK hitting /api/health.
  • docker/compose.yaml — service pubscript, port 8080, volume ./data:/app/data, restart: unless-stopped.

8. Docs & conventions

  • docs/CONVENTIONS.md — commit style, file layout rules, "code against packages/core, never against a backend" rule.
  • README.md at root — 10-line quickstart: pnpm i && pnpm dev, docker compose up, where things live.

Acceptance criteria (all must pass)

  • pnpm i && pnpm build succeeds with zero TS errors under strict.
  • pnpm dev → app shell loads at /, sidebar + top bar render, all 8 routes navigate, Ctrl+K palette opens and jumps to a route.
  • Switching theme (Ink → Parchment → Nocturne → Terminal) restyles the whole shell instantly; choice survives a reload.
  • pnpm test green: token budget math, ULID, theme token map, settings persistence.
  • docker compose up → curl localhost:8080/api/health returns {"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-alpine with python3 make g++ in the build stage; if it still fails, fall back to node:22-slim (Debian) — record the choice in an ADR.