Files
vscode-cowriting-plugin-con…/specs/coauthoring-interactive-review.md
T

37 KiB
Raw Blame History

status
status
graduated

Solution Design: Interactive Track-Changes Review in the Markdown Preview (F10)

Author(s) Ben Stull (with Claude)
Reviewers / approvers Ben Stull
Status draft
Version v0.1.0
Source artifacts Feature benstull/vscode-cowriting-plugin#29 (F10, type/feature, priority/P1) · Epic #1 (closed) · Capture session vscode-cowriting-plugin-0028 (2026-06-11) · Brainstorming session vscode-cowriting-plugin-0029 · Builds on (all shipped): F3 #6 (live attribution), F4 #12 (propose/accept), F6 #17/#19 (baseline + diff view), F7 #21/#22 (rendered preview + intra-diagram mermaid), F9 #27 (authorship preview) · Parent specs (graduated): coauthoring-inner-loop.md, coauthoring-attribution.md, coauthoring-propose-accept.md, coauthoring-diff-view.md, coauthoring-rendered-preview.md, 2026-06-11-authorship-preview-design.md · Lineage: ben.stull/rfc-app#48

Change log

Date Version Change By
2026-06-11 v0.1.0 Initial draft — brainstorming session 0029 (from the capture in session 0028, which followed the session-0027 ideation where the operator found the F6 two-pane diff + F3 attribution overlap confusing). Ben Stull + Claude

1. Business Context

1.1 Executive Summary

The plugin shipped its review signals as several overlapping surfaces that collide on the same text: F6's two-pane red/green raw diff (ctrl+alt+d), F3's in-editor attribution decorations (green human border / blue Claude fill), F4's in-editor amber proposal threads, and F7/F9's rendered preview (two modes). In manual testing the operator opened the F6 diff and saw F3's blue attribution bleeding into it at the same time — "I don't understand this."

F10 resolves this into one mental model: write on the left, review on the right. The editor becomes a clean, zero-annotation markdown pane. The rendered preview becomes the single review surface, with an annotations on/off toggle. With annotations on, the preview paints every change since the F6 baseline in one visual language — green = human-authored, blue = LLM-authored, strikethrough = deleted — and lets the human accept (✓) or reject (✗) Claude's pending proposals, and only those, right inside the preview. F10 is mostly assembly of shipped features (F3/F4/F6/F7/F9) plus three new pieces: strip the editor decorations, collapse F9's two preview modes into the one toggle, and make the preview interactive.

1.2 Background

The inner loop shipped F2F5 (threads · attribution · propose/accept · cross-rung). F6 added the baseline + a native two-pane diff toggle (#17), broadened to any file (#19). F7 added the rendered track-changes preview (#21) with full mermaid support, refined by F7.1 intra-diagram mermaid diffing (#22). F9 added an Authorship mode to that preview (#27), switched by a segmented [ Track changes | Authorship ] header control — and F9 explicitly chose two separate modes, never combined (F9 INV-26).

The friction the operator hit is the multiplicity of surfaces, not any one of them: the two-pane raw diff is low-altitude for prose; the in-editor decorations clutter the writing surface (the human authored most of the document, so attribution paints everywhere); and there is no single place to answer "what changed, who changed it, and do I keep the LLM's edits?" Capture session 0028 filed Feature #29 locking the product vision (markdown-only; clean editor; preview as the single review surface). This spec is the Solution Design for it.

1.3 Business Actors / Roles

  • Coauthor (human) — the writer/engineer (PP-1); F10's sole user.
  • Coauthor (machine) — Claude via @cline/sdk; not a user of F10, but its proposals are what the human reviews in the preview, and its landings advance the F6 baseline.

1.4 Problem Statement

A markdown writer reviewing a coauthoring session has no single, intuitive surface that answers "what changed since the last coauthoring moment, who changed it, and do I keep Claude's edits?" The signal is fragmented across a raw two-pane diff, in-editor decorations, and a two-mode preview that shows changes or authorship but never both, and never lets the human act on Claude's proposals from inside the rendered view.

1.5 Pain Points

  • Surface collision — F6's raw diff and F3's attribution paint the same text simultaneously and read as noise ("I don't understand this").
  • Cluttered editor — F3 attribution + F4 proposal threads decorate the writing pane; since the human authored most of the document, attribution paints nearly everywhere.
  • Two-pane raw diff is low-altitude for prose — you read markdown source, not the rendered document.
  • Changes XOR authorship — F9's preview shows what changed (track-changes) or who authored (authorship) but never both in one glance, and neither lets you accept/reject from the rendered view.

1.6 Targeted Business Outcomes

One clean model: write on the left, review on the right. The editor is distraction-free (zero annotations). The preview, with annotations on, shows every change since the baseline in one visual language and lets the human keep or revert Claude's proposals without leaving that pane — so "what did the LLM change, and do I want it?" is answered in place, in the rendered document, not in a raw diff or a cluttered editor.

1.7 Scope (business)

In scope: a clean (zero-annotation) markdown editor; the rendered preview as the single review surface; an annotations on/off toggle (off = clean rendered markdown, built-in-preview parity; on = the combined annotated view); the unified per-author + strikethrough annotation language (green human / blue LLM / struck deletions, reconstructed from the F6 baseline); interactive accept/reject of Claude's pending proposals from inside the preview (✓/✗); deprecating F6's two-pane view (command + keybinding hidden) and stripping the editor's F3/F4 decorations; unit + host-E2E coverage; manual webview smoke.

Out of scope (deferred, not forgotten): non-markdown review (F6's any-file reach #19 is dropped as a user goal — see §6.7); the repo rename to vscode-markdown-cowriting-plugin (the markdown-only identity is affirmed, but the rename — Gitea repo, app.json, content repo, session-history paths — is a separate deliberate gesture); preview→source scroll-sync; export / print / copy-as-clean; editing in the preview beyond the ✓/✗ proposal controls; intra-emphasis sentinel hardening (the F9 deferred refinement).

Non-goals (firm): any change to the sidecar persistence, the F4 propose/accept model (proposals stay pending-by-default — F10 surfaces and acts on them, it does not change how Claude's edits enter the system), the cross-rung contract, or SCHEMA_VERSION; a general-purpose markdown previewer; a WYSIWYG editor; combining anything with the LLM/network/credential surface (INV-8 untouched).

1.8 Assumptions · Constraints · Dependencies

  • Anchor: Feature #29 (F10). Mostly assembly of shipped features:
    • F6 baseline (#17/#19) — kept as the data layer ("what changed since when"; auto-advances at every machine landing, INV-18); F6's two-pane view is deprecated as a user surface (command/keybinding hidden, code kept).
    • F7 render/diff engine (#21/#22) — the rendered preview, block/word diff, strikethrough deletions, atomic code/mermaid, and F7.1 intra-diagram mermaid diff already exist and are reused.
    • F3 attribution (#6) — kept as the data layer (spansFor — who authored each span); its in-editor decorations are removed.
    • F4 propose/accept (#12) — the accept/reject logic (ProposalController.accept/rejectapplyAgentEdit, the INV-9 WorkspaceEdit seam) already exists; F10 moves the controls into the preview and removes the in-editor proposal threads.
    • F9 authorship preview (#27) — its two preview modes collapse into the one annotations on/off toggle; its PUA-sentinel author-coloring technique is reused inside the new combined render; its standalone renderAuthorship / segmented control are superseded (removed).
  • Constraint: the preview is a sealed webview (strict CSP, local assets, no network — F7 INV-21). Interactive ✓/✗ controls post messages to the extension host, which applies/discards proposals via the F4 path; the webview never edits the document directly.
  • Constraint: deletions aren't in the live buffer — they are reconstructed by diffing the buffer against the F6 baseline (the F7 engine already does this).
  • No LLM, no network, no new credential surface (INV-8 untouched); nothing persisted changes (.threads/, the contract, SCHEMA_VERSION all untouched).

1.9 Business Use Cases

  • BUC-1 (unified review) Mid-session the writer opens the preview and sees the rendered chapter with their own additions in green, their deletions struck, and Claude's pending proposals as blue blocks carrying ✓/✗ — one glance answers "what changed, who, and do I keep Claude's edits?"
  • BUC-2 (act in place) The writer clicks ✓ on a Claude proposal in the preview; the proposal lands in the document, the baseline advances, and the blue block becomes ordinary (unmarked) text — without leaving the preview or touching the editor.
  • BUC-3 (clean writing) The writer works in the editor, which shows no coauthoring decorations at all — a plain markdown surface — and flips annotations off in the preview to read the document clean (built-in-preview parity) when they want to.

2. Solution Proposal

The editor is stripped of all coauthoring decorations (F3 attribution tint, F4 proposal threads), and F6's two-pane diff command/keybinding are hidden (when:false); the F6 baseline store and onDidChangeBaseline event remain as the data layer.

The preview's per-panel mode collapses from F9's "changes" | "authorship" to a single "on" | "off" annotations toggle (default on):

  • Off — plain markdown-it render of the current buffer, no marks (parity with VS Code's built-in markdown preview).
  • On — a combined annotated render produced by one new pure, vscode-free engine, renderReview(baselineText, currentText, authorSpans, proposals), that overlays two axes in a single pass:
    1. Changes since the F6 baseline — the F7 block + word diff of baselineText vs currentText: additions and deletions, author-colored via F3 authorSpans (human → green, agent → blue) using F9's PUA-sentinel technique; deletions rendered struck (reconstructed from the baseline).
    2. Pending Claude proposals — each F4 pending proposal rendered inline at its resolved anchor as a blue block (the replaced text struck, the proposed text shown) carrying a data-proposal-id and ✓/✗ buttons.

The webview becomes interactive: clicking ✓/✗ posts {type: "accept" | "reject", proposalId} to the host, which calls ProposalController.accept / reject — accept runs the existing applyAgentEdit WorkspaceEdit seam and advances the baseline (INV-18), reject discards the proposal; either way the host re-renders. The webview never mutates the document (INV-20/21 hold). Everything downstream of (baseline, current, spans, proposals) → HTML is a pure function (INV-33), unit-testable with no vscode and no webview.

Because proposals are now preview-only, a small status-bar indicator ("N Claude proposals — open review", clickable to open the preview) signals pending proposals when no preview is open (§6.5 PUC-6).


3. Product Personas

  • PP-1 Inner-loop coauthor — the human markdown writer/engineer (as F2F9); the only persona F10 serves.

4. Product Use Cases

  • PUC-1 (clean editor) Opening / editing a markdown document shows a plain editor with no coauthoring decorations (no F3 tint, no F4 threads, no F6 diff). The writing surface is distraction-free.
  • PUC-2 (toggle annotations) In the preview, a header switch flips annotations on (combined annotated render) / off (clean rendered markdown). Default on. Mode is remembered per panel.
  • PUC-3 (see who changed what) With annotations on, the writer's additions since the baseline render green, their deletions render struck, and Claude's pending proposals render as blue blocks. One visual language.
  • PUC-4 (accept a proposal) The writer clicks on a blue proposal → it lands in the document via the F4 seam → the baseline advances → the block re-renders as ordinary (unmarked) text. The editor reflects the new text, still with no decorations.
  • PUC-5 (reject a proposal) The writer clicks on a blue proposal → the proposal is discarded → the block disappears on re-render → the document is unchanged.
  • PUC-6 (proposal while no preview open) Claude proposes while no preview is open → a status-bar item ("N Claude proposals — open review") appears; clicking it opens the preview where the proposals are reviewable. (No forced auto-open — the writer stays in control of focus.)
  • PUC-7 (graceful edges) Non-markdown active editor → the open command warns and opens nothing (markdown-only). A malformed markdown/mermaid block → an inline error chip for that block, the rest renders. No baseline yet → render current doc with no change-marks (everything is "since opened") + a header note; proposals (if any) still render with ✓/✗.

5. UX Layout

A webview in the editor column beside the source (ViewColumn.Beside). The source editor stays fully editable but is now visually clean — no coauthoring decorations. The preview is read-only rendered output except for the ✓/✗ proposal controls, which post intent (never edit the doc directly). With annotations on:

  • Human additions since baseline — soft green background (cw-by-human over <ins>), themeable.
  • LLM-authored text (already landed, attributed by F3) — soft blue (cw-by-claude).
  • Deletions — struck-through, muted (<del class="cw-del">), reconstructed from the baseline.
  • Pending Claude proposal — a blue proposal block: the text it would replace shown struck, the proposed text shown, and a compact ✓ / ✗ control pair in the block's corner (data-proposal-id on the block).
  • Atomic blocks (code / mermaid) — diffed whole (INV-23) with F7.1 intra-diagram mermaid styling for changed flowchart/sequence diagrams; an overlapping author span yields a block-level author badge (F9 INV-27).
  • A compact header bar: the annotations on/off switch; in the on state, a Since <epoch> label (reusing F6's epoch text) + a +N M · P proposals summary and the legend (● You · ● Claude · ✓/✗ = accept/reject Claude).

Colors derive from the active VS Code theme via webview CSS variables (light / dark / high-contrast); proposal-block and ✓/✗ button styling are bespoke but theme-variable-driven.


6. Technical Design

6.1 Invariants

Parent invariants INV-1..INV-31 carry over except where F10 supersedes them. F10 adds:

  • INV-32 (single review surface; clean editor) The rendered preview is the only annotated review surface. The editor carries zero coauthoring decorations: F3 attribution decorations, F4 in-editor proposal threads, and the F6 two-pane diff are all removed from / hidden in the editor. (F3 spansFor, F4 accept/reject logic, and the F6 baseline store remain as data layers.)
  • INV-33 (combined pure render) The "on" render unifies two axes — changes-since-baseline (author-colored additions + struck deletions) and interactive pending proposals — in one pass of a pure, vscode-free, DOM-free function renderReview(baselineText, currentText, authorSpans, proposals): string: same inputs → identical HTML (extends INV-22). The "off" render is the plain markdown-it of the current buffer.
  • INV-34 (✓/✗ act only on pending Claude proposals, via the F4 seam) Interactive controls appear only on pending Claude proposals (blocks carrying data-proposal-id); human (green) changes carry no controls. ✓/✗ post intent to the host; all document mutation goes through the existing F4 ProposalController.accept/rejectapplyAgentEdit (WorkspaceEdit) seam (INV-9). The webview never mutates the document, sidecar, or baseline (INV-20/21 hold).

Supersession (explicit): F10 reverses F9 INV-26 ("authorship mode is baseline-independent and never combined with the diff"). F10 combines the authorship axis with the change axis by design; F9's standalone authorship mode and segmented control are removed. F9 INV-27 (atomic fences get a block-level author badge) and INV-28 (pure authorship render) are absorbed into INV-33 (the combined render keeps fences atomic and stays pure).

6.2 High-level architecture

Three reused data layers + one new pure render path + an interactive webview.

flowchart LR
  edit["source editor\n(clean — no decorations)"] -- onDidChangeTextDocument (debounced) --> ctl
  base["F6 baseline\n(DiffViewController, INV-18)"] -- onDidChangeBaseline --> ctl["trackChangesPreview\n(vscode layer)"]
  attr["F3 AttributionController\nspansFor(doc)"] -- author spans --> ctl
  prop["F4 ProposalController\npending proposals"] -- onDidChangeProposals --> ctl
  ctl -- "(baseline, current, spans, proposals)" --> model["renderReview\n(pure, vscode-free)\nblock+word diff · author sentinels · proposal blocks"]
  model -- annotated HTML --> ctl
  ctl -- postMessage{html, mode, summary} --> wv["webview\n(sealed CSP, local assets)\nmermaid.run() · ✓/✗ buttons"]
  wv -- "postMessage{accept|reject, proposalId}" --> ctl
  ctl -- "accept/reject" --> prop
  prop -- "applyAgentEdit (WorkspaceEdit)" --> doc[(document)]
  prop -- accept --> base
  • trackChangesModel.ts (pure, vscode-free — INV-33) gains renderReview(baselineText, currentText, authorSpans, proposals): string. Internals: (1) the existing F7 diffBlocks over baseline/current → block ops; (2) for changed/added prose, the existing word-level <ins>/<del>, then author-color each <ins> (and live text) by intersecting authorSpans via the F9 PUA-sentinel technique (refactored out of renderAuthorship into a shared colorByAuthor helper); (3) inject proposal blocks: for each proposal, resolve its anchor offset in currentText, emit a <div class="cw-proposal" data-proposal-id="…"> containing the struck replaced-text and the proposed text + a <span class="cw-actions">✓ ✗</span> placeholder rendered by the webview; (4) atomic code/mermaid unchanged (F7.1 reused). Off-mode calls a thin renderPlain(currentText) (markdown-it only).
  • trackChangesPreview.ts (vscode layer) — mode: Map<key, "on"|"off"> (default "on", replacing F9's "changes"|"authorship"); gains a ProposalController dependency and subscribes to a F4 onDidChangeProposals event (additive, mirroring F6's onDidChangeBaseline) alongside the existing edit + baseline triggers; on the new {accept|reject, proposalId} inbound message, calls ProposalController.accept/reject then re-renders; owns the status-bar item (PUC-6).
  • Webview asset (media/preview.ts + .css) — header on/off switch (replacing the segmented control); inbound click handler on .cw-actions buttons → postMessage({accept|reject, proposalId}) (nonce-gated inline script, CSP-safe); proposal-block + ✓/✗ button CSS; reuses the existing mermaid.run() swap. Stays sealed (INV-21).
  • F3 AttributionController — remove the render() setDecorations calls and dispose the decoration types; keep spansFor. Retire/hide cowriting.toggleAttribution.
  • F4 ProposalController — remove the in-editor proposal-thread decorations and the in-editor accept/reject codelens/commands' UI; keep accept/reject/propose. Add listProposals(document): ProposalView[] (anchor offset + replacement + id) and onDidChangeProposals for the preview.
  • F6 DiffViewController — unchanged code; cowriting.toggleDiffView + ctrl+alt+d set when:false in package.json.

6.3 Data model & ownership

No new persisted artifact (INV-20). F10 holds only in-memory webview panels keyed by document URI + the per-panel on/off mode + the status-bar item. The baseline is owned by F6; proposals by F4 (sidecar); attribution by F3. The only on-the-wire models are transient:

// host → webview (transient HTML payload)
type RenderMsg = {
  type: "render"; mode: "on" | "off"; html: string;
  epoch?: string; summary?: { added: number; removed: number; proposals: number };
};
// webview → host (intent only — INV-34)
type ActionMsg =
  | { type: "setMode"; mode: "on" | "off" }
  | { type: "accept"; proposalId: string }
  | { type: "reject"; proposalId: string };

// internal to renderReview (illustrative — not persisted)
type ProposalView = { id: string; anchorStart: number; anchorEnd: number; replacement: string };

6.4 Interfaces & contracts

  • trackChangesModel (vscode-free): renderReview(baselineText: string, currentText: string, authorSpans: AuthorSpan[], proposals: ProposalView[]): string — annotated HTML for the on-state body; deterministic (INV-33). renderPlain(currentText: string): string — off-state body. Exports colorByAuthor(html-or-blocks, spans) (the salvaged F9 sentinel helper) for unit tests. Removes the public renderAuthorship (superseded); renderTrackChanges is retained internally / folded into renderReview.
  • TrackChangesPreviewController (vscode layer): show(document), refresh(document) (debounced), setMode(uriString, "on"|"off") (test seam), getLastModel(uriString) (test seam — the last computed block/proposal model, so host E2E asserts marks without reading webview DOM). Constructor now takes AttributionController and ProposalController.
  • AttributionController (additive removal): render() no longer applies editor decorations; spansFor(document): AuthorSpan[] unchanged.
  • ProposalController (additive): listProposals(document): ProposalView[]; readonly onDidChangeProposals: vscode.Event<{ uri: string }> fired on propose/accept/reject. accept/reject/applyAgentEdit unchanged.
  • DiffViewController (F6) — unchanged; only its package.json command/keybinding when flips to false.
  • Commands / keybindings (package.json): cowriting.showTrackChangesPreview ("Cowriting: Open Review Preview", ctrl+alt+r, when: editorLangId == markdown) — retitled, kept. cowriting.toggleDiffView + ctrl+alt+d, cowriting.toggleAttribution, and the in-editor cowriting.acceptProposal / cowriting.rejectProposal palette entries → when:false (hidden; the seams stay for tests/programmatic use). Status-bar item registered in extension.ts.

6.5 PerProduct-Use-Case design

  • PUC-1 (clean editor): delete AttributionController.render's setDecorations + decoration-type creation; remove F4's in-editor proposal decorations. Editor shows plain text.
  • PUC-2 (toggle): header switch → {type:"setMode", mode} → controller updates the per-panel map → re-render. Off = renderPlain; on = renderReview.
  • PUC-3 (who changed what): renderReview overlays the F7 baseline diff (author-colored via F9 sentinels) + proposal blocks.
  • PUC-4 (accept): {type:"accept", id}ProposalController.accept(state, proposal)applyAgentEdit WorkspaceEdit (INV-9) → baseline advances (INV-18) → onDidChangeBaseline + onDidChangeProposals → re-render (block now ordinary text).
  • PUC-5 (reject): {type:"reject", id}ProposalController.reject → proposal removed from sidecar → onDidChangeProposals → re-render (block gone); document untouched.
  • PUC-6 (proposal while no preview): on onDidChangeProposals, if no panel is open for the doc, show/update a status-bar item with the pending count, command = cowriting.showTrackChangesPreview. Hidden when count = 0 or a panel is open.
  • PUC-7 (edges): non-markdown → showWarningMessage, no panel. Per-block try/catch → error chip (F7). No baseline → render with no change-marks + a note; proposals still render.

6.6 Non-functional requirements & cross-cutting concerns

Render cost is O(document) on a debounced edit / proposal change — fine at inner-loop scale (large-doc debounce/idle cap is the F7 tunable, not v1 critical). The webview stays sealed (INV-21): local assets, strict CSP with a per-load nonce, no network; mermaid lives only in the webview bundle. The ✓/✗ buttons are host-rendered HTML with data-proposal-id; the nonce'd inline script only reads those ids and posts intent (no eval, no remote). No telemetry, no LLM, no credentials, nothing persisted.

6.7 Key decisions & alternatives considered

Decision Chosen Alternatives rejected
Where Claude's reviewable changes come from Pending F4 proposals surfaced in the preview — keep propose-by-default; ✓ = F4 accept (apply + advance baseline), ✗ = F4 reject. Apply-then-review (LLM edits land in the buffer; ✓/✗ pin/revert per span) — reverses F4 INV-10/11 and needs new per-span baseline machinery. Both axes overlaid (landed-LLM authorship + pending proposals + human diff) — richest but most rendering complexity; deferred. (Operator decision, session 0029.)
Editor decoration scope Fully clean — strip F3 attribution and F4 proposal threads; proposals are preview-only. Strip F3 only, keep amber threads — editor isn't truly "zero annotations"; two proposal surfaces to keep in sync. (Operator decision, session 0029.)
F6 two-pane diff Hide command + ctrl+alt+d (when:false); keep the controller + baseline store. Delete the view code now — larger, less reversible diff; a later cleanup PR can remove the dead view. (Operator decision, session 0029.)
F9's two modes Collapse into one on/off toggle; on = combined render; off = plain markdown. Remove renderAuthorship + segmented control. Keep three modes (changes / authorship / combined) — re-introduces the very surface-multiplicity F10 removes.
Combined render approach One pure renderReview overlaying diff + author sentinels + proposal blocks (extends INV-22). Compose two HTML passes in the webview — pushes logic into the sealed sandbox, loses unit-testability (INV-33).
Proposal-while-no-preview Status-bar indicator (clickable to open). Force auto-open the preview — hijacks focus, fights "write left, review right." (Deferred decision — cheap to flip to auto-open.)
Deletion coloring Neutral struck (cw-del), per the issue's "strikethrough = deleted". Color the strikethrough by who deleted — adds a fourth signal for marginal value.
Non-markdown review Dropped as a goal (markdown-only product). Keep F6's any-file reach — contradicts the locked product vision; rename to a markdown-specific identity is the eventual corollary (deferred).

6.8 Testing strategy

  • Unit (vitest, vscode-free): renderReview over fixtures — human addition (green <ins>), human deletion (struck cw-del), a proposal insert (blue cw-proposal block + data-proposal-id + ✓/✗ markup), a proposal replace (struck old + proposed new in one block), mixed human+proposal in a paragraph, an atomic code fence + a changed mermaid fence (F7.1 augmentation intact, no inner sentinels), off-mode = plain markdown (renderPlain equals markdown-it of source), determinism (same inputs → identical HTML). colorByAuthor helper unit tests (salvaged F9 cases). Proposal-anchor resolution edge: an unresolvable fingerprint → the proposal is rendered as a trailing block, never dropped silently.
  • Host E2E (@vscode/test-electron, no LLM, extends the F7/F9 suite): open a markdown fixture → cowriting.showTrackChangesPreview → assert panel open + getLastModel (opened baseline → no change-marks). Type → an added block, author = human. proposeAgentEdit seam → getLastModel shows a proposal op with id; simulate {type:"accept", id} → proposal applied, baseline advanced, block now unchanged/ordinary; {type:"reject", id} → proposal gone, document text unchanged. Assert the editor has no decorations (the F3 decoration types are not created / applied). Assert toggleDiffView is not in the active command set (or its when is false). Non-markdown doc → command warns, no panel. Status-bar item appears on a proposal with no panel open. Webview DOM / real button clicks are not E2E-asserted (sealed sandbox) — manual smoke.
  • Live smoke (manual — docs/MANUAL-SMOKE-F10.md): open a markdown doc; confirm the editor is clean (no tint/threads); edit prose (green ins / struck del); ask Claude to edit a selection → a blue proposal block with ✓/✗ appears in the preview; click ✓ (lands, mark clears) and ✗ on another (block vanishes, doc unchanged); toggle annotations off (clean render) / on; verify the status-bar indicator with no preview open; verify light/dark theming and that git status shows nothing.

6.9 Failure modes, rollback & flags

markdown-it/mermaid throwing on a block → inline error chip (F7), preview still renders. A proposal whose fingerprint no longer resolves → rendered as a trailing "unanchored proposal" block with ✓/✗ (never silently dropped — INV-34 must remain actionable). Webview disposed → controller drops the panel + clears the status-bar item; re-run the command to reopen. No feature flag — the preview is a pure read-only view (INV-20); not opening it is the off state. Rollback is reverting the PR with zero data migration (nothing persisted; the F6 baseline, F4 sidecar, F3 attribution data are untouched, so the hidden/removed UI can be restored). The clean-editor change is mostly deletion of decoration calls — trivially reversible.


7. Delivery Plan

7.1 Approach / strategy

One planning-and-executing session (F10 = #29), plan written just-in-time from this spec — the F2F9 precedent. Host-E2E tier (a VS Code extension has no browser/deploy stage); no LLM in CI. The webview's visual rendering and real button clicks are verified by the manual smoke; the automated seams are the pure render model and the host's accept/reject wiring.

7.2 Slicing plan

  • SLICE-1 — Clean editor. Strip F3 attribution decorations (AttributionController.render) + F4 in-editor proposal threads; hide toggleDiffView/ctrl+alt+d + toggleAttribution (when:false). Host E2E: no editor decorations. (Mostly deletion.)
  • SLICE-2 — Combined render engine. renderReview + renderPlain in trackChangesModel.ts; salvage colorByAuthor from renderAuthorship; remove the public renderAuthorship; proposal-block emission + anchor resolution; vitest suite (§6.8). Pure, vscode-free (INV-33).
  • SLICE-3 — Interactive controller + webview. ProposalController.listProposals
    • onDidChangeProposals; wire ProposalController + AttributionController into TrackChangesPreviewController; collapse mode to on/off; inbound accept/reject/setMode handling + re-render; webview on/off switch + ✓/✗ click→postMessage + proposal/button CSS; status-bar indicator (PUC-6).
  • SLICE-4 — Tests & docs. Host E2E (open / toggle / propose→accept→reject / clean-editor / hidden-F6 / status-bar) + docs/MANUAL-SMOKE-F10.md + README F10 section (and a note that F6/F9 user surfaces are superseded).

E2E are first-class plan tasks (handbook §9/§4); this app's required tier is host E2E (the F2F9 precedent).

7.3 Rollout / launch plan

Non-shippable (no marketplace publish). "Done" = #29 acceptance: a clean editor; the preview is the single review surface with an annotations on/off toggle; on-state paints green human / blue LLM / struck deletions and renders Claude's pending proposals with working ✓/✗ that land/discard via the F4 seam; unit + host E2E green; live smoke performed once. The repo rename remains deferred.

7.4 Risks & mitigations

Risk Mitigation
Proposals being preview-only hides pending work when no preview is open Status-bar indicator (PUC-6); README documents "review happens in the preview"; auto-open is a cheap fallback if it proves insufficient
Overlaying author sentinels onto the diff's <ins>/<del> perturbs markdown-it inline parsing (F9's known edge) Per-block try/catch → error chip (no hard failure); fences atomic; covered by the common-case tests; intra-emphasis hardening stays deferred
Proposal anchor can't be resolved at render time Render as a trailing "unanchored proposal" block (never dropped); unit-tested
Removing F4's in-editor threads loses a familiar affordance The ✓/✗ move to the preview (the single review surface by design); status-bar signals presence
Two reversed decisions (F9 INV-26; F6 any-file #19) confuse future readers This spec records the supersession explicitly (§6.1, §6.7); README updated
Webview interactivity widens the attack surface Nonce-gated inline script reads only data-proposal-id and posts intent; CSP unchanged; host validates the id against live proposals before acting (INV-34)

8. Traceability matrix

Requirement (#29) Use case Design Slice
Editor carries no coauthoring decorations PUC-1 INV-32, §6.2 SLICE-1
Preview annotations on/off toggle (off = clean render) PUC-2 §6.2, §6.4 SLICE-3
One language: green human / blue LLM / struck deleted PUC-3 INV-33, §6.2 (renderReview) SLICE-2
✓/✗ on Claude's changes only; ✓ keeps, ✗ reverts PUC-4/5 INV-34, §6.5 (F4 seam) SLICE-2/3
Accept/reject updates doc + preview consistently PUC-4/5 §6.5, INV-18 reuse SLICE-3
Deletions reconstructed from the F6 baseline PUC-3 F7 reuse, §6.2 SLICE-2
Deprecate F6 as a view; keep baseline data layer §6.2, §6.7 SLICE-1
Markdown-only; no sidecar/contract/SCHEMA change §1.7, INV-20 all
Sealed webview, no network/LLM INV-21, §6.6 SLICE-3
Unit + host E2E, no LLM in CI §6.8 SLICE-1..4

9. Open Questions & Decisions log

  • RESOLVED (session 0029, operator): Claude's reviewable changes = pending F4 proposals surfaced in the preview (propose-by-default kept; ✓ = F4 accept + baseline advance, ✗ = F4 reject); editor decoration scope = fully clean (strip F3 attribution and F4 proposal threads — proposals preview-only); F6 two-pane diff = hidden (when:false, code/baseline kept).
  • RESOLVED (this spec, autonomous — cheap to revisit): proposal-while-no-preview → status-bar indicator (not forced auto-open); F9's dead renderAuthorship / segmented control → removed (the PUA-sentinel coloring salvaged into a shared colorByAuthor helper); deletions → neutral strikethrough.
  • SUPERSEDED: F9 INV-26 ("authorship never combined with the diff") — F10 combines them by design; F9's authorship mode + segmented control are removed. F6's any-file review reach (#19) is dropped as a user goal (markdown-only).
  • OPEN → later: the repo rename to vscode-markdown-cowriting-plugin (deliberate, deferred); preview→source scroll-sync; intra-emphasis sentinel-safety hardening; whether to eventually overlay landed-LLM authorship (F9's old axis) in the on-state too (the "both axes" option, deferred); deleting F6's two-pane view code in a later cleanup PR.

10. Glossary & References

  • Review preview — the rendered markdown preview as the single review surface: clean (off) or annotated (on). Annotations on/off — the per-panel toggle replacing F9's two modes. Combined renderrenderReview's single-pass overlay of changes-since-baseline (author-colored + struck) and pending proposals. Pending proposal — an F4 propose-by-default edit, not yet in the buffer, rendered blue with ✓/✗. Baseline / epoch / advance — as F6 (coauthoring-diff-view.md). spansFor — F3's author-span data layer (coauthoring-attribution.md). PUA sentinels / colorByAuthor — F9's author-coloring technique (2026-06-11-authorship-preview-design.md), salvaged here.
  • Feature #29 (F10) · Epic #1 · builds on F3 #6, F4 #12, F6 #17/#19, F7 #21/#22, F9 #27 · parent specs coauthoring-inner-loop.md, coauthoring-attribution.md, coauthoring-propose-accept.md, coauthoring-diff-view.md, coauthoring-rendered-preview.md, 2026-06-11-authorship-preview-design.md · capture session 0028 · lineage ben.stull/rfc-app#48.