Files
PubScript/CHUNK#3.md
T

5.7 KiB
Raw Blame History

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.