# CHUNK #3 — Writing Surface **Duration:** ~3 days · **Prereqs:** CHUNK #2 (storage spine, autosave) · **Follows:** CHUNK #2 · **Unblocks:** CHUNK #6 (export), #7 (spellcheck), #9 (AI suggestions render over this editor) **Purpose:** Build the actual writing experience: the TipTap 3 editor with the full node schema, the document tree, the font system, focus/typewriter modes, live stats, and Markdown import/export — performing well enough that a 100k-word project opens in under 2 seconds and typing never lags. --- ## Steps ### 1. Editor package (`packages/editor`) 1. `packages/editor/src/schema.ts` — TipTap extensions, all registered once: - Base: `Document`, `Paragraph`, `Text`, `Heading` (1–3), `Blockquote`, `HorizontalRule`, `HardBreak`, `Bold`, `Italic`, `Strike`, `Code`, `CodeBlock`. - **`SceneBreak`** (node) — a centered `* * *` divider, draggable, with a NodeView; splits scenes for stats and AI targeting. - **`Footnote`** (inline + a `FootnoteArea` block at doc end) — v1 simple: number, reference, definition. - **`CharacterMention`** (inline atom) — rendered as a styled chip linking to a character entity (inserted by autocomplete in Chunk 4; the node type lands here). - Markdown I/O: `prosemirror-markdown` with rules for scene breaks (`***`), mentions (`@Name`), footnotes. 2. `packages/editor/src/Editor.tsx` — the headless wrapper: - `createEditor({ doc, onUpdate, decorations })` returning `{ view, state, dispatch, applyTransaction }`. - **Decoration pipeline:** `useDecorations(view, sources[])` — merges decorations from spellcheck (Chunk 7), AI suggestions (Chunk 9), and mentions into one `DecorationSet`, memoized, recomputed only on relevant transactions. This is the seam later chunks plug into — build it now, even with zero sources. - `applyEdit(range, before, after)` — the transaction used by "Accept suggestion". 3. `packages/editor/src/perf.ts` — `measureTypingLatency()` (rAF-based) + a dev overlay showing ms/keystroke. ### 2. Writing view (`apps/web/src/features/write/`) 1. `WritingView.tsx` — layout: document tree (left, from Chunk 2), editor (center), context rail (right: current chapter's linked characters/notes, collapsible). 2. **Toolbar** — floating (appears on selection) + docked (top): headings, bold/italic/strike, blockquote, scene break, footnote, mention, undo/redo. Slash menu (`/`) for block insertion. 3. **Keyboard map** (`packages/editor/src/keys.ts`): `Ctrl/Cmd+B/I/K`, `Ctrl+/` scene break, `Alt+↑/↓` chapter nav, `F2` focus mode. 4. **Focus mode** — dims everything but the current paragraph band; **Typewriter mode** — auto-scrolls the active line to a fixed vertical position. Both toggle from toolbar + settings. 5. **Stats bar** (footer): word count (chapter/book/project), session words, reading time; per-chapter counts in the tree. 6. **Font system** (`packages/ui/src/fonts.ts`): bundle via `@fontsource` — **EB Garamond**, **Source Serif 4**, **Inter**, **Space Grotesk**, **JetBrains Mono** (+ weights). Settings controls: family, size (14–22 px), leading (1.3–2.0), measure (50–90 ch), margins. Apply via CSS variables on the editor root; persist in settings store. 7. **Markdown I/O:** `exportMarkdown(doc)` / `importMarkdown(text)` in `packages/editor/src/markdown.ts`. Import dialog in the tree ("Import .md…") with a preview + conflict check; per-chapter and whole-project export (feeds Chunk 6). ### 3. Performance work 1. **Lazy chapters:** only the open chapter's doc is in the editor; switching chapters swaps state without unmounting the view. 2. **Virtualize the tree** for books with 500+ chapters (`@tanstack/react-virtual`). 3. Add a Playwright perf test (`apps/web/e2e/perf.spec.ts`): seed a 100k-word doc (script generates 40k paragraphs), assert: open < 2 s; 20 keystrokes median latency < 16 ms (using the dev overlay's API); tree scroll at 60 fps. ### 4. Polish 1. Placeholder states (empty book, empty chapter), save indicator already wired (Chunk 2). 2. Accessibility: visible focus rings, `aria` on toolbar/tree, full keyboard operation of the tree. 3. `docs/EDITOR.md` — node reference (for humans) + "how to add a node type" (for agents). --- ## Acceptance criteria (all must Pass) - [ ] Type a full chapter: no dropped keystrokes; latency overlay < 16 ms median on a 100k-doc. - [ ] 100k-word seeded project opens < 2 s (Playwright perf test in CI). - [ ] Scene breaks insert via `***` and slash menu; stats split by scene. - [ ] Footnotes render and export to Markdown correctly. - [ ] Font/size/leading/measure changes apply live and persist across reload. - [ ] Focus + typewriter modes work and are keyboard-toggleable. - [ ] Import a 50-chapter `.md` file → tree + docs correct; re-export round-trips (diff of re-imported Markdown is empty modulo whitespace). - [ ] Decoration pipeline: register a fake decoration source → renders; (Chunk 9 will replace it with real AI suggestions.) - [ ] A11y pass: toolbar fully keyboard-operable; contrast ≥ 4.5:1 in all four themes. ## Definition of done An author can sit down and *write* — fast, beautiful, with the story structure around them — and the editor exposes a stable decoration/edit API that spellcheck and AI plug into without touching core code. ## Risks & fallbacks - **TipTap v3 API churn** → pin the exact v3 release; wrap all v3-specific calls in `packages/editor` so the app never imports TipTap directly. - **Markdown round-trip loss** (footnotes/mentions) → keep a canonical ProseMirror-JSON store; Markdown is an *exchange* format, not the source of truth (document this in `docs/EDITOR.md`). - **Perf test flakiness in CI** → run perf specs on a dedicated runner with 3 runs, take the median; fail only on >2× regression.