spec(f7.1): intra-diagram mermaid diffing design + ship note (#22)
Refine the F7 whole-diagram "changed" badge into a node/edge-level diff inside flowchart and sequence diagrams against the F6 baseline. Parsed-graph diff re-emitted with mermaid's own styling directives (classDef/class/linkStyle for flowcharts; rect-tinted runs for sequence), removed elements ghosted in place, layout reflow accepted, all else (other types, parse failure) falling back to the v1 badge. New §11 + INV-29..31. Shipped via PR #28 (session 0027). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -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 `<pre class="mermaid">`). |
|
||||
| **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 `class <ids> cwAdded` for nodes and `linkStyle <indices> 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 `<pre class="mermaid">AUGMENTED</pre>`
|
||||
+ 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.
|
||||
|
||||
Reference in New Issue
Block a user