Plan Artifact · Not Code Execution

Roughdraft for HTML — MVP Parity Plan

A goal-sliced implementation plan for shipping HTML-document review at MVP parity with the existing Markdown product, executed as a sequenced Claude Code loop with Dab as orchestrator/verifier.

Repo · roughdraft @ 78af393 Feasibility · MVP achievable Stance · low-churn DOM, not byte-preserving Loop · /goal-N · wake on done
Read me first. This document is a plan, not a build log. No production code has been written. Each goal card defines an exact /goal prompt for Claude Code, the tests it must run, and what Dab independently verifies before sending the next goal.
§ 01

Executive decision #

MVP parity for HTML is feasible inside the existing Roughdraft architecture. We will ship it as a sequenced Claude Code loop, not one giant prompt.

Why goal-sliced, not one prompt

  • Each goal fits comfortably in one Claude Code context window with room for tests.
  • Dab can independently verify between goals, so a regression stops the loop early.
  • Failure isolation: a broken goal does not poison later context.
  • Each goal produces a green test gate as durable proof of progress.

The architectural bet

  • Reuse the existing seam: @roughdraft/rfm-style index over annotated source files; storage backend interface; MCP tools; TipTap as the visual editor.
  • Accept low-churn DOM serialization only for edited HTML — not byte-preserving.
  • Persist review state inside HTML using semantic tags + data-rd-* attributes so the file remains a valid HTML document on disk.
  • Treat script, style, pre, code, textarea contents as literal zones the reviewer cannot mutate and the editor will not execute.
§ 02

MVP parity definition #

Parity is measured against current Roughdraft features verified in the repo at 78af393. Anything not on the must-have list is explicitly deferred.

Must have for MVP · parity

  • Open a local .html file via CLI / server endpoint.
  • Render it in the TipTap-based review surface (read-mode default).
  • Add inline comments on a selection; threaded replies; resolve.
  • Add inline suggestions: insertion, deletion, substitution.
  • Accept / reject each suggestion from the review rail.
  • Persist annotations inside the HTML file as semantic tags + data attrs.
  • Raw HTML source view (CodeMirror, lang-html) with save.
  • File watch / optimistic concurrency / conflict surface, mirroring the Markdown path.
  • Done Reviewing button → SSE / MCP handoff, mirroring current Markdown flow.
  • MCP tools: get index, get pending, watch, reply, resolve — for HTML.
  • localStorage demo/remote mode works for at least one sample HTML doc.
  • Sanitization: editor never executes script/style from reviewed docs.
  • pnpm check, pnpm test:e2e, pnpm test:smoke all green on an HTML fixture flow.

Explicitly deferred · post-MVP

  • Byte-preserving HTML round-trip for edited documents.
  • CSS/asset resolution, live preview of complex layouts beyond plain semantic HTML.
  • Iframe / shadow DOM / web component fidelity.
  • Form field state, JavaScript-driven HTML, SPA fragments.
  • Image / SVG inline annotation regions.
  • Multi-file site review (single document at a time).
  • HTML-specific style suggestions (semantic tag swaps, attribute edits).
  • Performance work on very large (>1MB) HTML files.
  • Migration of existing .md reviews to HTML; both formats coexist behind the storage seam.

Annotation schema (on-disk, inside the HTML)

Annotations live in the document as valid HTML. Goal 0 finalizes the exact attribute set; this is the working draft used throughout planning.

<!-- comment anchor: a mark wraps the commented range -->
<mark data-rd-comment-ids="c_8f2">customer health score</mark>

<!-- comment body: stored as a custom element near the anchor or in a footer block -->
<rd-comment
   data-rd-id="c_8f2"
   data-rd-by="ananth"
   data-rd-at="2026-05-23T14:02:11Z"
   data-rd-re=""
   data-rd-status="open">
  Define this — is it NRR-based or product-usage-based?
</rd-comment>

<!-- suggestion: insertion / deletion / substitution pair -->
<ins data-rd-change-id="s_31a">quarterly</ins>
<del data-rd-change-id="s_31a">monthly</del>
<span data-rd-change-kind="substitution"
      data-rd-change-id="s_31a"></span>
§ 03

Goal prompt architecture #

Every /goal-N prompt is structured the same way so Claude Code can pattern-match on what's required, and so Dab can grade the output deterministically.

Reusable goal template

/goal-N — <short title> CONTEXT One paragraph: what the user is trying to ship, why this goal exists, and what was completed in the previous goal that you can rely on. OBJECTIVE A single concrete sentence describing the end state of this goal. IN SCOPE - bullet list of files/areas you may touch - explicit list of tests that must exist or be updated OUT OF SCOPE - features that are tempting but belong to a later goal - refactors not required for this goal's acceptance CONSTRAINTS - architectural rules to honor (storage seam, MCP shape, schema) - "do not break the Markdown path" guarantees DONE CRITERIA - bullet list of observable outcomes - exact test commands that must pass REQUIRED EVIDENCE - diffs / file paths changed - command outputs (with exit code) - for UI goals: short browser flow you exercised in dev STOP CONDITIONS - if you cannot resolve X, stop and report; do not push beyond scope - if a test you did not change starts failing, stop and report - if you find an unrecorded assumption, stop and ask TESTS YOU MUST RUN BEFORE REPORTING DONE pnpm check pnpm test:e2e (when UI/route/editor touched) pnpm test:smoke (when UI/route/editor/file-backend touched) <plus targeted unit tests added by this goal> RETURN FORMAT 1. Summary: 3–6 bullets, "did X, observed Y". 2. Files changed: path — one-line purpose. 3. Commands run: exact command + exit code + relevant tail. 4. Manual checks (if UI): URLs hit, flow steps, observed result. 5. Open questions / deferred items, if any.

Why this shape

  • Single objective keeps each context window focused.
  • Required evidence blocks "should work" reports — Dab grades on artifacts.
  • Stop conditions tell Claude when not to push through; cheaper than rollback.
  • Test commands listed verbatim so Claude can't "interpret" them away.
§ 04

Sequential goals (0 → 7) #

Each goal is independently shippable: it ends with a green test gate, a runnable artifact, or both. Dab does not send Goal N+1 until Goal N's evidence has been verified locally.

GOAL · 0

Plan, spec, and fixtures — no production code

spec only
Objective
Lock the HTML annotation schema, fixtures, and write the implementation spec under .context/. No packages/* code yet.
In scope
.context/html-mvp-plan.md (detailed spec), .context/fixtures/html/ (3–5 sample docs: plain article, doc with code blocks, doc with tables, doc with annotations round-trip example, malformed input).
Out of scope
Any change under packages/.
Files touched
Only .context/** and docs/adr/<next>-html-annotation-format.md.
Acceptance
  • Spec includes final attribute names and resolution semantics.
  • Each fixture has an inline comment explaining what it stresses.
  • ADR explains why we don't pursue byte-preserving round-trip in MVP.
Tests
pnpm check (no code changed, must still pass).
Dab verifies
Reads spec + ADR in Roughdraft; confirms fixtures parse as valid HTML in a browser; OKs schema before Goal 1.
Show /goal-0 prompt
/goal-0 — HTML MVP spec, fixtures, ADR CONTEXT We're shipping MVP parity of Roughdraft for HTML documents. This goal is plan-only: no production code. We need a frozen schema and fixtures before the implementation goals start. OBJECTIVE Produce: (1) .context/html-mvp-plan.md spec, (2) fixtures under .context/fixtures/html/, (3) docs/adr/<next>-html-annotation-format.md. IN SCOPE - Finalize on-disk annotation schema (mark[data-rd-comment-ids], rd-comment[data-rd-id/by/at/re/status], ins/del[data-rd-change-id], span[data-rd-change-kind]). - 5 fixtures: plain article, code-heavy doc, table doc, fully annotated round-trip doc, malformed input. - Decide naming: keep @roughdraft/rfm and expose HTML functions, OR create @roughdraft/rhfm. Recommend one with reasoning. OUT OF SCOPE - Any change under packages/. - Implementation of parser/emitter. CONSTRAINTS - Annotations must survive a browser opening the raw HTML file without errors (use custom element rd-comment, valid attrs). - script/style/pre/code/textarea are literal zones in the spec. DONE CRITERIA - Spec, ADR, fixtures committed (or in working tree) under listed paths. - pnpm check passes. RETURN FORMAT Standard template. Include schema table and the recommended package naming decision with one paragraph of reasoning.
GOAL · 1

HTML review format package (parser/emitter/index)

core lib
Objective
Stand up the HTML annotation library: parse annotated HTML, build a review index, append replies, mark resolved, validate. Pure functions; no DOM-in-app dependency.
In scope
Either (a) extend packages/rfm/src/index.ts with HTML-specific entry points, or (b) introduce packages/rhfm. Decision came from Goal 0.
Out of scope
App/server/MCP wiring, TipTap bridge, CodeMirror.
API surface
parseHtmlReview(input) → { ast, index }  ·  emitHtmlReview(ast) → string  ·  extractReviewIndex(html) → ReviewIndex  ·  appendReply(html, threadId, reply)  ·  markResolved(html, threadId)  ·  validate(html) → Issue[]
Acceptance
  • Unit tests for each entry point, table-driven over fixtures.
  • Round-trip property test: parse(emit(parse(x))) ≡ parse(x) on annotated fixtures.
  • Validate catches: duplicate data-rd-id, dangling change pairs, malformed status.
Tests Claude runs
pnpm --filter @roughdraft/<pkg> test · pnpm check
Dab verifies
Spot-checks one fixture by hand: parse → mutate index → emit → re-parse, ensure identity.
Show /goal-1 prompt
/goal-1 — HTML review format library CONTEXT Goal 0 produced the spec, ADR, and fixtures. Now implement the pure-function library for parsing and emitting annotated HTML. This must not depend on the React app or the server. OBJECTIVE Ship parseHtmlReview / emitHtmlReview / extractReviewIndex / appendReply / markResolved / validate with unit tests. IN SCOPE - The package chosen in Goal 0 (extend rfm or new rhfm). - Tests under that package using the .context/fixtures/html/ files. OUT OF SCOPE - Server endpoints, MCP tools, TipTap, CodeMirror, app storage. CONSTRAINTS - Must not break @roughdraft/rfm Markdown behavior. Confirm by running existing rfm tests. - Use a permissive HTML parser; preserve unknown attributes; do not re-format whitespace inside literal zones. - All public functions are pure; no fs/network. DONE CRITERIA - New tests pass. - Existing rfm tests still pass. - pnpm check passes. RETURN FORMAT Standard template. Include test names and counts.
GOAL · 2

Server & CLI accept .html documents

backend seam
Objective
Teach the server, CLI open path, and storage seam to handle .html as a first-class document while keeping .md behavior intact.
In scope
packages/server/src/index.ts (extend or add /api/document generic; keep /api/markdown-file as compatibility wrapper), extension routing, file-watch wiring, optimistic concurrency for .html.
Out of scope
MCP tools, TipTap, raw editor.
Files touched
packages/server/src/index.ts · packages/server/src/mcp.ts (read only) · packages/app/src/storage.ts (read/test seam) · server tests
Acceptance
  • Open .html via CLI returns parsed document + index.
  • Save round-trip writes valid HTML; If-Match-style concurrency works.
  • File-watch fires on external edit and emits SSE to a connected client (verified by a unit/integration test, even if UI not wired yet).
  • All existing Markdown server tests still pass.
Tests Claude runs
pnpm --filter @roughdraft/server test · pnpm check
Dab verifies
Curl-based smoke: open a fixture, write, observe conflict on stale ETag.
Show /goal-2 prompt
/goal-2 — Server + storage seam for .html CONTEXT Library from Goal 1 is in place. Now extend the server and storage seam so .html documents can be opened, watched, saved, and conflict- detected, mirroring .md behavior. OBJECTIVE .html documents flow through the same lifecycle as .md from the server's perspective. Markdown path remains untouched in behavior. IN SCOPE - Endpoint surface (decide: extend /api/markdown-file to be generic, or add /api/document — recommend, then implement). - Storage seam extension to recognize .html. - File watcher. - Integration tests for open/read/write/watch/conflict on .html. OUT OF SCOPE - MCP tools (Goal 6). - UI changes beyond what's required for the storage seam tests. CONSTRAINTS - Do not change semantics of /api/markdown-file. If renamed, leave a compatibility wrapper. - No new third-party deps unless justified. DONE CRITERIA - New server tests pass. - Existing server tests still pass. - pnpm check passes. RETURN FORMAT Standard template. Include endpoint shape (paths, request/response) and a curl walkthrough you actually ran.
GOAL · 3

App storage + raw HTML source editor

app shell
Objective
Wire the app's storage layer to load and save HTML through the server, and add a CodeMirror raw HTML editor (lang-html) with save-on-blur / save button parity.
In scope
packages/app/src/storage.ts (extend getMarkdownFile / saveMarkdownFile / watchMarkdownFile or add document-typed siblings), new CodeMirror source view, route to open .html.
Out of scope
TipTap rich-editor for HTML (Goal 4), MCP (Goal 6).
Acceptance
  • Open a fixture .html in the app; raw view renders correctly with syntax highlighting.
  • Edit + save round-trips through server; reload preserves changes.
  • External edit triggers reload notice; conflict UI surfaces on stale write.
  • Markdown path still loads/saves identically.
Tests Claude runs
pnpm --filter @roughdraft/app test · pnpm check · browser dev run with screenshot evidence.
Dab verifies
Opens dev server, loads HTML fixture, edits raw view, observes save + reload survives.
Show /goal-3 prompt
/goal-3 — App storage + raw HTML source editor CONTEXT Server speaks .html now (Goal 2). Bring the app's storage layer and raw-source editor onto the .html path. OBJECTIVE A user can open a .html fixture in the app, edit it in raw view, save, and reload preserves the change. External edit shows a banner. IN SCOPE - Extend packages/app/src/storage.ts to handle .html. Keep .md behavior identical. - CodeMirror raw HTML view with lang-html. - Route / open-file plumbing for .html. OUT OF SCOPE - TipTap rich editor for HTML (Goal 4). - Annotation rendering or comments rail (Goal 5). CONSTRAINTS - Do not introduce a separate storage abstraction; reuse the seam. - Local-first / demo (localStorage) mode must still work for .md. DONE CRITERIA - Existing Markdown UI flows unaffected. - New tests for HTML storage pass. - Browser flow verified: open / edit raw / save / reload / external edit shows banner. - pnpm check, pnpm test:smoke pass. RETURN FORMAT Standard template. Include screenshot path(s) or recording of the browser flow.
GOAL · 4

TipTap HTML annotation bridge

editor
Objective
Parse annotated HTML into TipTap, render comments and suggestions as marks, support add-comment, add-suggestion (ins/del/substitution), accept/reject — analogous to the current critic-markup bridge for Markdown.
In scope
New module packages/app/src/html-review-bridge/ mirroring the shape of packages/app/src/critic-markup/index.ts and reusing packages/app/src/editor-extensions.ts mark types where possible.
Out of scope
Review rail / Done Reviewing handoff (Goal 5), MCP (Goal 6).
Acceptance
  • Round-trip: HTML → TipTap doc → HTML preserves annotations and content semantics.
  • Add comment on selection produces correct on-disk markup.
  • Add substitution suggestion produces matched ins/del with shared id.
  • Accept turns ins → plain text and removes del; reject removes ins, restores del.
  • Unit tests cover each transform; literal zones untouched.
Tests Claude runs
pnpm --filter @roughdraft/app test · pnpm check
Dab verifies
Manually adds a comment and a substitution in browser; opens raw view; confirms on-disk shape matches schema.
Show /goal-4 prompt
/goal-4 — TipTap HTML annotation bridge CONTEXT The app can open and save raw .html (Goal 3). Now make TipTap a first-class renderer for annotated HTML, analogous to the existing Markdown / CriticMarkup bridge. OBJECTIVE Annotated HTML round-trips through TipTap with comments and suggestions rendered as marks; user can add/accept/reject. IN SCOPE - New html-review-bridge module under packages/app/src/. - Reuse comment / change mark types in editor-extensions.ts where they fit; add HTML-specific mark types only when needed. - Unit tests using fixtures from Goal 0/1. OUT OF SCOPE - Review rail visuals beyond what's needed for the bridge to work. - Done Reviewing SSE flow (Goal 5). - MCP (Goal 6). CONSTRAINTS - Editor must NEVER execute script/style from reviewed HTML. - Literal zones (script/style/pre/code/textarea) are atomic blocks: no inline annotations inside; comments allowed on the block itself. - Markdown path must remain bit-for-bit unchanged in behavior. DONE CRITERIA - Bridge unit tests pass. - Markdown editor unaffected. - pnpm check passes; pnpm test:e2e passes if any e2e touched. RETURN FORMAT Standard template. Include a before/after of one fixture showing the annotation transforms.
GOAL · 5

Review rail + Done Reviewing end-to-end

flow
Objective
Wire the existing review rail to the HTML bridge, then complete the Done Reviewing handoff (SSE event + MCP signal) so the entire single-user review flow works on HTML.
In scope
Review rail components, SSE event for HTML doc completion, end-to-end browser test.
Out of scope
MCP tools (Goal 6) — only the SSE-side handoff is required here.
Acceptance
  • Comments and suggestions show in the rail with correct counts and statuses.
  • Resolve, reply, accept, reject all update the on-disk HTML.
  • Done Reviewing emits the SSE event a downstream consumer can subscribe to.
  • pnpm test:e2e and pnpm test:smoke include the HTML happy path.
Tests Claude runs
pnpm check · pnpm test:e2e · pnpm test:smoke · browser flow.
Dab verifies
Opens a fixture, adds 1 comment + 1 substitution + 1 deletion, resolves one, accepts one, rejects one, clicks Done Reviewing, observes event.
Show /goal-5 prompt
/goal-5 — Review rail + Done Reviewing for HTML CONTEXT TipTap can now render and mutate annotated HTML (Goal 4). Wire the existing review rail + Done Reviewing flow onto the HTML path. OBJECTIVE Single-user HTML review flow works end to end in the browser, and test:e2e + test:smoke cover it. IN SCOPE - Rail rendering for HTML annotations. - Resolve / reply / accept / reject mapped to bridge operations. - Done Reviewing → SSE event for HTML documents. - At least one Playwright/smoke test driving the HTML happy path. OUT OF SCOPE - MCP tools (Goal 6). - Visual redesign of the rail. CONSTRAINTS - Reuse rail components; do not fork. - Do not regress the Markdown happy path in e2e/smoke. DONE CRITERIA - pnpm check, pnpm test:e2e, pnpm test:smoke all pass. - Manual browser flow recorded in evidence. RETURN FORMAT Standard template. Include the e2e/smoke spec names added or modified.
GOAL · 6

MCP parity for HTML

MCP
Objective
Bring MCP tools to parity for HTML: get index, get pending, watch, reply, resolve. Mutations must be safe under optimistic concurrency.
In scope
packages/server/src/mcp.ts, importing HTML helpers from the Goal 1 package. Tool schemas mirror current Markdown tools 1:1.
Out of scope
Multi-doc workflows, server-driven AI suggestion application beyond what already exists for Markdown.
Acceptance
  • MCP tools enumerate HTML threads identically to Markdown threads (shape match).
  • Reply / resolve operations write valid HTML and bump the file etag.
  • Concurrent write produces a clean conflict error, not corruption.
  • MCP integration tests cover the happy path and the conflict path.
Tests Claude runs
pnpm --filter @roughdraft/server test · pnpm check · local sample flow with a connected MCP client.
Dab verifies
Runs the sample MCP flow against a fixture; confirms reply lands on disk and rail updates after watch fires.
Show /goal-6 prompt
/goal-6 — MCP parity for HTML CONTEXT HTML review works in the app and persists in valid annotated HTML (Goals 1–5). Now expose it to MCP clients with the same surface area used for Markdown. OBJECTIVE MCP tools reach parity: get index, get pending, watch, reply, resolve — for HTML documents, sharing helpers with Markdown where possible. IN SCOPE - packages/server/src/mcp.ts changes. - New MCP integration tests for the HTML path. - A short sample script in scripts/ or docs/ that drives the tools against a fixture, used in the evidence. OUT OF SCOPE - New MCP tools not in the current Markdown set. - Multi-document review. CONSTRAINTS - Tool names and shapes must mirror Markdown; document type is a field or inferred from extension — pick one and justify. - Mutations must use optimistic concurrency; on conflict, surface the standard error shape. - No regression to Markdown MCP tools. DONE CRITERIA - Tests pass: MCP integration, server, and pnpm check. - Sample script run logs in evidence. RETURN FORMAT Standard template. Include the tool signatures with deltas vs. the Markdown counterparts.
GOAL · 7

Full MVP e2e gate + runbook

ship gate
Objective
Run the entire MVP gate, fix the long tail of small breakage, and produce a runbook describing how to open, review, and hand off an HTML document, plus a remaining-gaps list.
In scope
Test fixes, doc updates, runbook in docs/, final pass of pnpm check / pnpm test:e2e / pnpm test:smoke.
Out of scope
Net-new features. If a gap is found that isn't a regression, document it in the gaps list and stop.
Acceptance
  • All three gates green on a clean checkout.
  • Runbook validated by Dab following it from scratch.
  • Remaining-gaps list lists every deferred item (linked to §02).
Tests Claude runs
Full gate from §05 plus the manual flow.
Dab verifies
Runs the runbook end-to-end; signs off MVP.
Show /goal-7 prompt
/goal-7 — MVP e2e gate + runbook CONTEXT Goals 0–6 complete. Now drive the full MVP gate, fix the small tail, and produce a runbook + remaining-gaps list. OBJECTIVE Green gate (pnpm check, pnpm test:e2e, pnpm test:smoke) plus a runbook that Dab can execute from a clean checkout. IN SCOPE - Stabilizing fixes only. - docs/runbook-html-mvp.md. - docs/gaps-html-mvp.md aligned with the deferred list in the plan. OUT OF SCOPE - New features. - Refactors not required to stabilize. CONSTRAINTS - If a non-regression gap appears, stop and document it; do not expand scope. DONE CRITERIA - Gate green on this machine. - Runbook walks Dab through the canonical flow including MCP. - Gaps list complete. RETURN FORMAT Standard template. Include final command outputs (tails with exit codes) and the runbook link.
§ 05

End-to-end test gate #

No goal reports done without running these. Goals that don't touch UI/routes can skip test:e2e and test:smoke — Claude must justify the skip in evidence; Dab decides whether the justification holds.

# Mandatory for every goal
pnpm check                # lint + test:selectors + unit + build

# Required when UI / route / editor / file-backend / workflow touched
pnpm test:e2e             # Playwright e2e suites
pnpm test:smoke           # browser smoke (not covered by pnpm check)

# Manual browser flow for HTML MVP (Goal 5 onward)
$roughdraft_cmd start
$roughdraft_cmd open "$repo/.context/fixtures/html/article.html"

# Inside the app:
# 1. Select a phrase, add a comment.
# 2. Select a different phrase, add a substitution suggestion.
# 3. Reply to your own comment, then resolve it.
# 4. Accept the suggestion. Reload — change persists.
# 5. Run the MCP sample script — it lists pending threads, replies, resolves.
# 6. Click "Done Reviewing" — observe SSE event in the subscriber.
§ 06

Wake-up loop — Dab as orchestrator #

Dab supervises a tmux pane running Claude Code. When a goal reports done, Dab does not trust the self-report — it independently runs the gate and inspects evidence before sending the next goal.

Loop state machine

idletmux pane open, no active goal
send /goal-Npaste prompt from §04 verbatim
watchpoll tmux output for "Summary:" + evidence block
verifyDab runs the gate locally, reads diffs, exercises flow
decisionpass → next goal · partial → repair prompt · fail → stop & report
advancesend /goal-(N+1) immediately; do not idle

Rules Dab enforces

  • No advancing on self-report alone. Dab re-runs pnpm check + relevant gate.
  • Evidence must include exact commands + exit codes; missing → repair prompt.
  • UI/editor goals require a screenshot or recorded flow, not "tests passed".
  • If the same goal fails twice with the same root cause, escalate to Ananth, do not third-try.
  • Dab wakes itself the moment a goal lands — no idle minutes between goals.

Repair prompt template

/goal-N-repair PROBLEM <exact failure Dab observed: command output, missing evidence, regressed test name, or unmet acceptance bullet>. EXPECTATION Restate the unmet bullet from the goal's acceptance criteria. SCOPE Repair only. Do not start work on the next goal. Do not expand scope to refactors unless they are the direct cause. EVIDENCE REQUIRED Re-run the test that failed and paste its output. Re-run pnpm check. If UI: re-run the manual browser flow and report observations.
§ 07

Risk controls #

Risk Why it bites HTML harder than Markdown Control
Round-trip churn Any DOM serializer reorders attributes, normalizes whitespace, collapses empty tags. A diff-heavy save erodes trust. MVP stance: low-churn DOM only for edited docs. Unedited regions kept byte-identical via a "preserve unedited fragments" pass. ADR records the deferral of full byte-preserving round-trip.
Sanitization Reviewed HTML may contain script, on-handlers, hostile style. Editor parses with scripts disabled; on-handlers stripped at render time only (still present on disk, not executed); CSP for the editor frame; never eval; sanitized preview.
Block-spanning suggestions A substitution that crosses block boundaries breaks paired ins/del semantics. Suggestions are clamped to the smallest block that contains the selection; cross-block edits fall back to two coupled single-block edits sharing one change id. Validator flags orphan pairs.
Literal zones script, style, pre, code, textarea contents are not HTML. These nodes are atomic in TipTap: no inline marks inside, only block-level comments on the node itself. Parser preserves contents verbatim.
MCP mutation safety External agents writing via MCP can race the in-app editor. Every write goes through optimistic concurrency (etag/version). On conflict, MCP returns a typed error; the editor surfaces a reload banner.
Schema drift If Goal 0's schema is wrong, later goals carry the cost. Schema is locked in an ADR; any change requires a one-paragraph amendment and a migration test on the fixtures.
§ 08

Confidence & timeline #

Confidence

MVP parity is achievable; the dominant variable is editor fidelity inside literal zones and on big real-world documents.

Library / server / storage

TipTap bridge & round-trip

MCP parity

e2e / smoke green on first integration

Expected timeline (calendar, single-track Claude)

Goal 0~½ day · spec, ADR, fixtures
Goal 11 day · parser/emitter/index + tests
Goal 21 day · server + storage seam
Goal 3½–1 day · raw editor + storage
Goal 41½ days · TipTap bridge — biggest risk
Goal 51 day · rail + Done Reviewing e2e
Goal 6½–1 day · MCP parity
Goal 7½ day · gate + runbook

Total wall-clock: ~6–7 working days if no goal slips its scope. Add ~1 day buffer for the TipTap bridge.

What "done" means in one paragraph

A user opens a local .html file in the worktree CLI, sees it rendered in TipTap, selects text and adds a comment, adds a substitution suggestion, replies to the comment, resolves it, accepts the suggestion, clicks Done Reviewing, and the SSE event arrives at a connected MCP client which has already been listing pending threads through MCP tools — all while pnpm check, pnpm test:e2e, and pnpm test:smoke stay green and the Markdown path remains bit-for-bit unchanged in behavior.