Engineering breakdown of the build. Epics map 1:1 to the PRD/brief milestones (M0–M7).
Source of truth: PRD.md (spec) + PROMPT.md (implementation brief).
App lives in EscapeVelocity-native-tauri/.
Status legend: ⬜ not started · 🟡 in progress · ✅ done · ⏸️ blocked/deferred · 🟢 verified on-device
| Epic | Milestone | Theme | Status |
|---|---|---|---|
| Epic 0 | M0 | Project shell & reuse foundation | 🟢 |
| Epic 1 | M1 | Compile loop (Tectonic → PDF.js) · checkpoint | 🟢 |
| Epic 2 | M2 | Generation contract (latexgen) |
🟢 |
| Epic 3 | M3 | Structured editor | 🟢 |
| Epic 4 | M4 | LaTeX source pane + SyncTeX | 🟢 |
| Epic 5 | M5 | PageSetting inspector | 🟢 |
| Epic 6 | M6 | Export + KDP preflight · checkpoint | 🟢 |
| Epic 7 | M7 | Import + templates (Phase 2 start) | 🟢 |
Locked decisions (from the interview): Stack A (Tauri 2 + React + Vite + TS); licensing deferred → clean-room Zed aesthetic, no vendored Zed source; block editor = TipTap/ProseMirror custom schema; LaTeX = embedded tectonic crate, pre-bundled for offline; flowing-text books only (v1); latexgen emits a node-id ↔ .tex source map; generated .tex read-only, Raw-LaTeX editable/verbatim; all trim/bleed/margin geometry in one data-driven presets file; Rust = rust-analyzer LSP live + clippy/tests at each gate.
DoD: app launches; dark with Zed-derived tokens; three empty panes; palette opens; THIRD_PARTY.md records sources. Status: 🟢 (verified on-device — the shell renders in the native window during M1).
- ✅ Scaffold Tauri 2 + React + Vite + TypeScript (strict)
- ✅ Restructure to the PRD §8.4 component map (
theme/,panes/,commands/,command-palette/,workspace/;src-tauri/{compile,latexgen,project,import,export}) - ✅ Gates:
tscstrict clean ·cargo clippyclean · native debug binary links - 🟢 Verified on-device:
npm run tauri devrenders the shell in the native window
- ✅ Dark-first CSS token set (surfaces, text, accent, status, syntax)
- ✅ Light theme override + runtime switch + persistence
- ✅
THIRD_PARTY.mdclean-room/aesthetic posture (licensing deferred)
- ✅ Resizable/collapsible structure | editor | preview (react-resizable-panels)
- ✅ Persisted layout (
autoSaveId) - ✅ Title bar (drag region, macOS overlay traffic-light inset) + status strip
- ✅ Editor Structured/LaTeX view toggle (stub)
- ✅ Action registry (id → command)
- ✅ Data-driven, platform-aware keymap dispatch (chord normalization,
mod= ⌘/Ctrl) - ✅ Command palette + fuzzy matcher + keyboard nav
- ✅ Default commands (palette toggle, dock toggles, view toggle, theme toggle)
DoD: a real PDF page renders in-app; changing the hardcoded .tex and recompiling updates the preview. ← checkpoint: confirm before M2. Status: 🟢 (DoD met and verified on-device; some refinements deferred — see ⏸️ below.)
- ✅ Add the pinned
tectoniccrate (=0.16.9) tosrc-tauri - ✅ Resolve macOS build deps (brew: harfbuzz/graphite2/freetype/libpng/fontconfig + keg-only icu4c;
.cargo/config.tomlsetsPKG_CONFIG_PATH) - ✅ Fully offline compilation: cache-only mode (
only_cached) over a pre-warmed cache.npm run prewarm(examples/prewarm_cache.rs) buildsvendor/tectonic-cache(covers the generator's sizes/packages); it's bundled as a Tauri resource and seeded into a writable per-user cache on first launch. Verified: a distinct doc compiles with the network blocked (dead proxy) and zero files fetched. (gitignored/regenerated; raw-LaTeX needing un-prewarmed packages is the known exception.) - ✅ Rust smoke test (
tests/compile_smoke.rs): representativememoir→ valid PDF, offline - 🟡 Verify the bundled-resource path in a packaged
tauri build(engine offline proven; packaged-app seeding pending a release build)
- ✅
compile::latex_to_pdf(source) -> PDF bytesvia the embedded engine - ✅ Tauri command
compile_latex(source)→ bytes (ArrayBuffer), run on a blocking thread - ⏸️ SyncTeX output +
raw_log/synctex_path(planned with M4) - 🟡 Debounced (~600 ms) recompile ✅ (frontend); true engine cancellation deferred (currently supersede-by-runId)
- ✅ Representative
memoirfixture at 6×9 (chapter opening, body, two pages) - ✅ Editable LaTeX view + "Compile" command/button (⌘↵)
- ✅
pdfjs-dist; PDF delivered as bytes over IPC, rendered one canvas per page - ✅ Pages at true trim size, fit-to-width (scroll navigation; explicit page nav/zoom deferred)
- ✅ Status strip wired to real compile state + page count (flags odd counts for KDP)
- ✅ Editing the source + recompile visibly updates the preview (chapter-title change confirmed)
- ✅
cargo clippyclean ·tscstrict clean · smoke test green · screenshots captured - 🟢 Verified on-device (native window: live page renders, recompile updates) — checkpoint: confirm before M2
DoD: editing a sample document.json changes the compiled preview; golden tests pass. Status: 🟢 (DoD met + verified on-device.)
- ✅
document.jsonnode tree (latexgen/model.rs): containersfront_matter/body/back_matter,part/chapter/section; blocksparagraph/heading/block_quote/verse/figure/scene_break/pull_quote/raw_latex; inlinetext/emphasis/strong/small_caps/link/footnote(footnotes modeled inline) - ✅
settings.jsonPageSetting model (trim, margins, body, paragraph, chapter, running_heads, output_preset) - ✅ Shared TS mirror (
src/model/types.ts, hand-kept; Rust authoritative)
- ✅ Preamble from
settings.json(\documentclass{memoir},\setstocksize/\settrimmedsize(+\settrimsfor bleed),\linespread,microtypeprotrusion, margins, widow/orphan penalties, chapter style, page styles) - ✅ Body from
document.json(chapter/section, quotation, verse, figure+caption, scene-break ornament, inline footnote,raw_latexverbatim) with LaTeX escaping - ✅ Readable, commented output (
% --- Chapter: … ---) - ⏸️
fontspec+ bundled fonts → M5 (PageSetting font selector); drop-cap/ornament chapter styles → M5 - ⏸️ node-id ↔
.texsource map → M4 (with SyncTeX, its consumer)
- ✅ One data-driven presets module (
latexgen/presets.rs): KDP 6×9/5×8/5.5×8.5/8.5×11 + KDP gutter-by-page-count table
- ✅ Golden test (BLESS pattern, dep-free)
tests/latexgen_golden.rs; determinism test;generated_sample_compilesend-to-end (generated LaTeX compiles offline to PDF) - ✅ Frontend wired to generate-from-document (
generate_latex/default_settingscommands); Structured view editsdocument.json, LaTeX view shows generated source read-only - 🟢 Verified on-device: sample document generates + compiles + renders in the native window
DoD: a user types a chapter visually and sees it typeset; never touches code. Status: 🟢 (DoD met + verified on-device.)
- ✅ Custom TipTap schema (
src/editor/schema.ts): flat semantic blocks (inline*) — chapterTitle, heading(2/3), paragraph, blockQuote, pullQuote, verse, rawLatex, sceneBreak; marks bold↔strong, italic↔emphasis, smallCaps; footnote inline atom - ✅ Two-way serializer (
serialize.ts): chapter titles group following blocks; verified lossless (footnote + front/back matter survive a round-trip; programmatic + slash-menu edits re-serialize correctly)
- ✅ Style application via keyboard shortcuts (on each node) and a slash menu (
SlashMenu.tsx, filter + ↑/↓/↵) - ✅ Semantic-only (no font/size/spacing pickers) — appearance owned by settings
- ⏸️ Figure insert (needs asset handling) → later; footnote/link interactive editing → later (footnote round-trips; link renders as text)
- ✅ Edits → serialize →
setDocument→latexgen→ debounced recompile (supersede-by-runId) - ✅ Outline pane reflects chapters/headings (read-only)
- ⏸️ Drag-to-reorder; spell-check; word/page count → later
- ✅ Browser: editor renders sample, round-trip lossless, slash menu applies + serializes
- 🟢 On-device: visual editing + live typeset preview together (full loop), no code touched
DoD: click a preview page → editor jumps; raw block edits survive a round-trip. Status: 🟢 (DoD met + verified.)
- ✅ CM6 +
stexLaTeX highlighting + Zed theme (src/editor/LatexPane.tsx), read-only - ✅ Source is generated → the pane is read-only by design; raw-LaTeX is edited in the structured editor (round-trips losslessly). Editable raw regions in the source pane → later (needs the node source map)
- ✅ Enable SyncTeX in Tectonic; capture + gunzip; parse (
src-tauri/src/compile/synctex.rs, fraction-based) — Rust unit test - ✅ Preview click → source line (
synctex_inverse); source click → preview page (synctex_forward); jump signals wired through the store - ⏸️ Compose with the node-id source map (jumps land on the
.texline; line→node mapping later)
- ✅
raw_latexround-trips verbatim via the structured editor (verified in M3)
- ✅ Browser: LaTeX pane renders generated source with highlighting;
requestSourceJumpscrolls/highlights the target line; Rust SyncTeX unit test; native regression (compile + preview intact)
DoD: switching trim to 6×9 and changing body font visibly re-typesets the whole book. Status: 🟢 (DoD met; verified.)
- ✅ GUI controls bound to
settings.json(src/inspector/), in a tabbed left dock (Structure ↔ Settings): output preset, trim readout, bleed; font, size, leading, justify, hyphenate, microtype; paragraph style + no-first-indent; inside/outside/top/bottom margins; running heads. Command Open PageSetting Inspector (⌘,) - ✅ Fonts now work —
fontspecby OTF filename over the bundled fonts (EB Garamond default, + Libertinus / TeX Gyre Pagella / Latin Modern / Computer Modern);latexgenemits\setmainfont[...]with bold/italic variants - ⏸️ Chapter-style drop-cap/ornament controls, measure, front/back + auto-ToC → later
- ✅ Each change →
setSettings→ regenerate preamble → debounced recompile; KDP 6×9 default + preset switching (switchPreset→ Rustdefault_settings) - ⏸️ Gutter auto-suggest from live page count (data-driven
kdp_gutter_inexists; not yet wired to the compiled page count)
- ✅ Browser: inspector renders all sections; font/size/toggle changes drive
settings. Rust: golden re-blessed withfontspec;generated_sample_compilespasses offline with EB Garamond. clippy clean; native app runs.
DoD: exported PDF opens with fonts embedded, correct page size, even page count. ← checkpoint. Status: 🟢 (DoD met + verified.)
- ✅ Even page count (auto blank verso via recompile), correct page size (trim; bleed geometry emitted by
latexgen), embedded/subset fonts (XeTeX), no crop marks —src-tauri/src/export/ - ✅ Export the
.texsource (Export LaTeX…); native save dialog (dialog plugin) - ⏸️ Assets bundle + image DPI/transparency flattening (no images yet); bleed verified end-to-end → later
- ✅ Checks (with concrete fixes, no silent structural fixes): trim vs preset, gutter adequacy by page count, font embedding, even page count, image DPI, crop marks/transparency
- ✅ Unit tests (
tests/export_test.rs): even-page export + no preflight failures for the sample
- ⏸️ Map Tectonic warnings/errors to plain language + show-raw-log + link to node → later (currently the top-level Tectonic error string)
- ✅ Export of the sample book verified: 6 pages (5→6, blank verso added), opens at 6×9, fonts embedded; preflight all-pass. Full import→export demo lands with import in M7
DoD (Phase 2 start): import a manuscript with a review step; full presets; templates; scaffolding. Status: 🟢 (import + presets + templates done; some Phase-2 items deferred.)
- ✅ Markdown import (
src/import/markdown.ts, marked) with a review step (ImportDialog, ⌘⇧I / title-bar): paste or open a.mdfile → structure summary → replace (editor reloads viadocVersion).#→chapter,##/###→heading, block quotes,---→scene break, bold/italic/links/code - ⏸️ DOCX import (mammoth) with style-mapping review → later
- ✅ Full KDP set + IngramSpark (data-driven in
presets.rs)
- ✅ House-style starter library (Literary 5×8, Nonfiction 6×9, Memoir 5.5×8.5) applied from the inspector (
apply_template) - ⏸️ Save/share custom templates → later
- ⏸️ Standard front/back-matter scaffolding + auto-ToC → later (Phase 2)
- ✅ Open-licensed fonts shipped (EB Garamond / Libertinus / TeX Gyre Pagella / Latin Modern — from the Tectonic bundle, M5)
- ✅ Markdown import verified end-to-end (parse → review → editor reload → outline + marks); native launch clean
A non-technical user can import a manuscript, choose a KDP 6×9 preset, see a live page-accurate preview, and export a PDF that passes KDP's Print Previewer on first upload — while a technical user can open the LaTeX pane, read clean generated source, and tweak via Raw-LaTeX blocks.