1c9b531996
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.
53 lines
2.8 KiB
Markdown
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).
|