5.7 KiB
5.7 KiB
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)
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 + aFootnoteAreablock 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-markdownwith rules for scene breaks (***), mentions (@Name), footnotes.
- Base:
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 oneDecorationSet, 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".
packages/editor/src/perf.ts—measureTypingLatency()(rAF-based) + a dev overlay showing ms/keystroke.
2. Writing view (apps/web/src/features/write/)
WritingView.tsx— layout: document tree (left, from Chunk 2), editor (center), context rail (right: current chapter's linked characters/notes, collapsible).- Toolbar — floating (appears on selection) + docked (top): headings, bold/italic/strike, blockquote, scene break, footnote, mention, undo/redo. Slash menu (
/) for block insertion. - Keyboard map (
packages/editor/src/keys.ts):Ctrl/Cmd+B/I/K,Ctrl+/scene break,Alt+↑/↓chapter nav,F2focus mode. - 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.
- Stats bar (footer): word count (chapter/book/project), session words, reading time; per-chapter counts in the tree.
- 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. - Markdown I/O:
exportMarkdown(doc)/importMarkdown(text)inpackages/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
- Lazy chapters: only the open chapter's doc is in the editor; switching chapters swaps state without unmounting the view.
- Virtualize the tree for books with 500+ chapters (
@tanstack/react-virtual). - 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
- Placeholder states (empty book, empty chapter), save indicator already wired (Chunk 2).
- Accessibility: visible focus rings,
ariaon toolbar/tree, full keyboard operation of the tree. 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
.mdfile → 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/editorso 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.