Files
PubScript/docs/CONVENTIONS.md
T
Schwitzz 1c9b531996 Implement CHUNK #2: server layer, core schemas/adapters, contract tests, seed
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.
2026-10-24 18:31:20 -05:00

53 lines
2.8 KiB
Markdown

# PubScript Conventions
Guidelines every chunk follows so a human and a strictly-instructed coding agent stay in lockstep.
## Language & tooling
- **TypeScript `strict`** everywhere, with `noUncheckedIndexedAccess` and `exactOptionalPropertyTypes` on. No `any` in new code.
- **pnpm workspaces** with a committed lockfile. Never run `pnpm install` without `--frozen-lockfile` in CI.
- **ESM throughout** (`"type": "module"`). Import sibling modules with a `.js` extension even though the source is `.ts` (bundler module resolution).
- **Prettier** (100-col, double quotes) + **ESLint flat config** (`typescript-eslint` recommended, `no-explicit-any` off, unused-vars on).
## File layout
```
PubScript/
├── PLAN.md ← single source of truth for *what* and *why*
├── CHUNK#n.md ← executable work orders (this chunk's steps)
├── apps/web/ ← React 19 + Vite app (runs in browser AND Tauri)
├── packages/
│ ├── core/ ← domain types, IDs, time, token budget, StorageAdapter contract
│ ├── ui/ ← design tokens, theme engine, component kit
│ ├── editor/ boards/ exporters/ ai/ ← later chunks
├── server/ ← Fastify + SQLite: auth, multi-tenant, WS, admin, AI proxy
├── desktop/ ← Tauri 2 shell (later chunk)
├── docker/ ← Dockerfile + compose
└── docs/ ← ADRs, data model, role prompts, parity matrix
```
## The golden rule: code against `packages/core`, never against a backend
Every feature codes against the **`StorageAdapter` contract** in `packages/core/src/storage.ts`, never against `ServerAdapter` or `LocalAdapter` directly. This is what makes web, desktop, and local modes interchangeable and lets a single contract-test suite enforce parity.
## Domain conventions
- **IDs are ULIDs** (sortable, collision-free). See `packages/core/src/ids.ts`.
- **Timestamps are UTC ISO-8601** strings. See `packages/core/src/time.ts`.
- **All domain types live in `packages/core/src/types.ts`** and are shared by app *and* server. Do not redefine them locally.
## Testing
- **Vitest** for unit tests colocated as `src/**/*.test.ts(x)`.
- **Contract suite** (`packages/core` `contract` script) runs the same assertions against every `StorageAdapter` implementation.
- Acceptance criteria in each `CHUNK#n.md` are the gate to the next chunk.
## Commits
Conventional Commits (`feat:`, `fix:`, `chore:`, `docs:`, `refactor:`, `test:`, `build:`). One logical change per commit; reference the chunk in the body when relevant.
## Risks & fallbacks
- Tailwind v4 + React 19 friction → pin exact versions and record them here.
- `better-sqlite3` native build in Docker → `node:22-alpine` with `python3 make g++` in the build stage; fall back to `node:22-slim` if needed (record in an ADR).