diff --git a/specs/coauthoring-rendered-preview.md b/specs/coauthoring-rendered-preview.md index c8cbd03..84c7803 100644 --- a/specs/coauthoring-rendered-preview.md +++ b/specs/coauthoring-rendered-preview.md @@ -449,9 +449,12 @@ mermaid diff) remains open. - **RESOLVED (this spec):** open command `cowriting.showTrackChangesPreview` (`ctrl+alt+r`, markdown-only); test seam = pure model + `getLastModel` (webview DOM not E2E-asserted); additive F6 `onDidChangeBaseline` event. -- **OPEN → later:** intra-diagram mermaid diffing (**#22**); preview→source - scroll-sync; non-markdown rendered views; per-author coloring in the preview; - whether F7 should eventually subsume F6's prose path. +- **RESOLVED (session 0027, §11):** intra-diagram mermaid diffing (**#22**) — + parsed-graph diff, flowchart + sequence this increment, removed elements + ghosted in place, layout reflow accepted; all else falls back to the v1 badge. +- **OPEN → later:** preview→source scroll-sync; non-markdown rendered views; + per-author coloring in the preview; whether F7 should eventually subsume F6's + prose path; further mermaid diagram types for #22 (class/state/ER/…). - **Deferred decisions (autonomous calls for operator review):** **F7 coexists with F6** rather than replacing it (markdown-only vs any-file); the `ctrl+alt+r` keybinding; webview DOM/mermaid rendering verified by manual smoke rather than @@ -469,3 +472,134 @@ mermaid diff) remains open. parent specs `coauthoring-diff-view.md`, `coauthoring-inner-loop.md`, `coauthoring-attribution.md`, `coauthoring-propose-accept.md` · lineage `ben.stull/rfc-app#48`. + +--- + +## 11. F7.1 — Intra-diagram mermaid diffing (#22) + +> **Status:** shipped (session 0027, 2026-06-11; PR #28). Addendum to F7 (#21): +> the v1 whole-diagram "changed" badge (PUC-5, INV-23) is refined into a +> **node/edge-level diff inside the diagram** for the two most common diagram +> types. Anchor: task `benstull/vscode-cowriting-plugin#22` (`type/task`). Builds +> on the §6 render engine; reuses the F6 baseline as "before". No new persistence +> (INV-20 holds), no webview-security change (INV-21 holds), host stays pure & +> deterministic (INV-22 holds). + +### 11.1 What changes + +When a **`changed` mermaid block** of a **supported diagram type** is diffed +against the F6 baseline, instead of rendering the new diagram whole with a single +"changed" badge, F7.1 renders the new diagram **with its individual elements +colored by what changed**: + +- **flowchart** (`graph` / `flowchart`) — added / removed / changed **nodes and + edges**; +- **sequence** (`sequenceDiagram`) — added / removed / changed **participants and + messages**. + +Every other case is **unchanged from v1**: a wholly `added` / `removed` mermaid +block, a `changed` block of any **other** type (class, state, ER, gantt, …), and +**any parse failure** all keep the v1 whole-block badge. This is a pure +refinement of one branch of `renderOp` — nothing else in F7 moves. + +### 11.2 The three forks, resolved + +| Fork | Decision | Why | +| --- | --- | --- | +| **Diff level** — source-text vs parsed-graph vs SVG | **Parsed-graph.** Parse the diagram source into a typed element model (nodes/edges or participants/messages), diff that, then **re-emit the new diagram source augmented with mermaid's own styling directives** so mermaid renders a colored diagram. | Source-text diff shows a *text* diff in a *rendered* view (defeats F7). SVG diff is layout-brittle and has no stable semantic mapping (§6.7 already rejected it). Parsed-graph keeps the host **pure** (parse + diff + string-augment — fully unit-testable, INV-22) and the **webview unchanged** (it still just runs `mermaid.run()` over `
`). | +| **Visual marking of removed elements** | **Ghost in place.** Removed nodes/edges/messages are **re-injected** into the emitted diagram, styled faded/dashed, so the deletion is visible in its original context. | The point of an intra-diagram diff is to *see* what left, where it was. A caption-only list loses position. Accepted cost: ghosts add to layout reflow. | +| **Diagram types this increment** | **Flowchart + sequence.** All other types fall back to the v1 badge. | The two most common types. Each later type is an additive follow-up on #22 (same dispatch seam). | + +**Layout reflow (the fourth open question):** **accepted, not fought.** The +augmented new diagram is laid out fresh by mermaid; we do **not** attempt to pin +node positions to the baseline layout (mermaid exposes no stable layout pinning; +position-matching is a large unscoped effort). Ghosting keeps removed elements +present so the diff still reads; we do not promise the before/after diagrams are +spatially aligned. + +### 11.3 Styling-hook asymmetry (a real mermaid constraint) + +Flowcharts and sequence diagrams give very different styling surfaces, so the two +emitters differ: + +- **Flowchart** — crisp per-element styling. Emit `classDef cwAdded/cwChanged/cwRemoved …` + once, then `classcwAdded` for nodes and `linkStyle stroke:…` for + edges (edges are addressed by their **declaration-order index**, which the parser + tracks). Ghost removed nodes/edges are appended to the source with the `cwRemoved` + class / a dashed `linkStyle`. +- **Sequence** — mermaid has **no per-message color directive**; its only + per-message styling hook is the **`rect rgb(r,g,b) … end`** background block. So + the sequence emitter rebuilds the message stream (ghosted-removed messages + re-inserted at their baseline position) and wraps each added / changed / removed + message in a one-message `rect` tinted green / amber / grey. Participants are + re-emitted (removed ones re-declared so they still appear). + +Colors are **fixed, theme-neutral** values baked into the emitted source (mermaid +source can't read VS Code CSS variables): added ≈ green (`#2ea043`), changed ≈ +amber (`#d29922`), removed ≈ muted grey + dashed (`#808080`), each chosen to read +on both light and dark mermaid themes. A small **legend** (`+ added · ~ changed · +− removed`) is shown beneath a diffed diagram (host-emitted markup, not part of +the mermaid source). + +### 11.4 Architecture & seam + +A new **pure, vscode-free, DOM-free** host module tree (INV-22), dispatched from +the existing `changed`+atomic+mermaid branch of `renderOp`: + +- **`src/mermaidDiff.ts`** — `diffMermaid(beforeSrc, currentSrc): MermaidDiffResult` — + detects the diagram type (`detectDiagramType`); routes to the flowchart or + sequence differ; returns `{ kind: "augmented"; source }` on success or + `{ kind: "fallback" }` for unsupported types / parse failure. Wraps the differ + in try/catch so **any** parser surprise degrades to the v1 badge (never throws — + the §6.9 error-chip philosophy). Owns the shared `CW_COLORS` palette. +- **`src/mermaidFlowchartDiff.ts`** — `parseFlowchart` (nodes by id, edges by + declaration order) + `diffFlowchart` (emits the augmented source). +- **`src/mermaidSequenceDiff.ts`** — `parseSequence` (participants + statements) + + `diffSequence` (LCS over statements via jsdiff `diffArrays`; `rect` tints + ghost + re-insertions). + +`renderOp`'s changed-mermaid branch (`src/trackChangesModel.ts`) extracts the +fence body, calls `diffMermaid`; on `augmented` it emits ` AUGMENTED` ++ the legend and drops the single-badge; on `fallback` it does exactly what it did +before. **The webview (`media/preview.ts`) needs no change** — the styling rides +inside the mermaid source. `media/preview.css` gains the legend swatch styles only. + +### 11.5 Invariants (continuing the project sequence; F8 took 24–25, F9 26–28) + +- **INV-29 (supported-type intra-diff)** A `changed` mermaid block whose type is + **flowchart or sequence** is diffed at element granularity (node/edge resp. + participant/message) against the F6 baseline and re-emitted as a single mermaid + diagram whose elements are styled by change kind. It remains **one rendered + diagram**, never a split/word-diff (INV-23's atomicity is *refined* here, not + abandoned — the block is still rendered whole, just self-colored). +- **INV-30 (graceful fallback is total)** Any unsupported diagram type, any + wholly added/removed mermaid block, and **any** parse/emit failure fall back to + the exact v1 whole-block badge. Intra-diagram diffing **never** produces an + error chip or a broken diagram where v1 would have rendered. +- **INV-31 (ghost completeness)** Every element present in the baseline but absent + from the current diagram appears in the rendered diff as a faded/dashed ghost in + its baseline position; no removed element silently vanishes. +- **INV-22/-20/-21 preserved** The differ is pure & deterministic (same inputs → + identical augmented source); F7.1 persists nothing and reads only the existing + F6 baseline; the webview stays sealed and unchanged (styling travels in-source). + +### 11.6 Testing (as shipped) + +- **Unit (vitest, host, no DOM/LLM):** dispatcher + type detection + (`mermaidDiff.test.ts`); flowchart parser + node/edge diff + emission + (`mermaidFlowchartDiff.test.ts`); sequence parser + participant/message diff + + `rect` emission (`mermaidSequenceDiff.test.ts`); `renderTrackChanges` augments a + changed flowchart/sequence and falls back for unsupported types + (`trackChangesModel.test.ts`). Determinism asserted on each differ. +- **Host E2E:** a changed flowchart in the fixture doc → `getLastModel` shows the + mermaid op is `changed`+atomic and the emitted HTML (via a `renderHtmlFor` test + seam) carries the augmenting directives for the added node. Webview DOM rendering + stays manual-smoke (`docs/MANUAL-SMOKE-F7.1.md`). +- **Counts at ship:** 189 unit + 38/5 host E2E green; typecheck clean. + +### 11.7 Out of scope (still deferred) + +Class / state / ER / gantt / other diagram types (additive #22 follow-ups, same +seam); pinning layout so before/after align spatially; intra-label word-diffing +inside a single changed node's text; animating the transition. These are +explicitly **not** in this increment.