F7.1: intra-diagram mermaid diffing (#22) #28

Merged
benstull merged 10 commits from f7.1-intra-diagram-mermaid-diff into main 2026-06-11 23:36:58 +00:00
2 changed files with 84 additions and 1 deletions
Showing only changes of commit 96931f245d - Show all commits
+60 -1
View File
@@ -1,4 +1,63 @@
// stub, replaced in Tasks 4-5 /**
* mermaidSequenceDiff — F7.1 (#22). Pure sequence-diagram parser + diff/emit.
* Mermaid sequence diagrams have NO per-message color directive; the only
* per-message hook is the `rect rgb(...) … end` background block. So the emitter
* rebuilds the message stream (ghosted-removed messages re-inserted at their
* baseline position) wrapping each added/changed/removed message in a one-message
* tinted rect, and re-declares removed participants (INV-29/31). Deterministic;
* no vscode, no DOM.
*/
import { diffArrays } from "diff";
import { CW_COLORS } from "./mermaidDiff";
export interface SeqDiagram {
header: string;
participants: string[];
/** Message/other statement lines, verbatim & trimmed, in order. */
statements: string[];
}
// `A->>B: text`, plus ->, -->, -->>, -x, --x, -), --) variants. Captures from/to.
const MSG = /^([A-Za-z0-9_]+)\s*(-{1,2}(?:>>?|x|\)))\s*([A-Za-z0-9_]+)\s*:/;
const PARTICIPANT = /^(?:participant|actor)\s+([A-Za-z0-9_]+)/;
export function parseSequence(source: string): SeqDiagram {
const lines = source.split(/\r?\n/);
let header = "";
const declared: string[] = [];
const seen = new Set<string>();
const statements: string[] = [];
const addP = (p: string) => {
if (!seen.has(p)) {
seen.add(p);
declared.push(p);
}
};
for (const rawLine of lines) {
const line = rawLine.trim();
if (line === "" || line.startsWith("%%")) continue;
if (!header && /^sequenceDiagram\b/.test(line)) {
header = line;
continue;
}
const pm = line.match(PARTICIPANT);
if (pm) {
addP(pm[1]);
continue;
}
const mm = line.match(MSG);
if (mm) {
addP(mm[1]);
addP(mm[3]);
}
statements.push(line);
}
return { header: header || "sequenceDiagram", participants: declared, statements };
}
// Replaced in Task 5 with the real diff/emit.
export function diffSequence(_beforeSrc: string, currentSrc: string): string { export function diffSequence(_beforeSrc: string, currentSrc: string): string {
void diffArrays;
void CW_COLORS;
return currentSrc; return currentSrc;
} }
+24
View File
@@ -0,0 +1,24 @@
import { describe, it, expect } from "vitest";
import { parseSequence } from "../src/mermaidSequenceDiff";
describe("parseSequence", () => {
it("captures explicit participants in order", () => {
const d = parseSequence("sequenceDiagram\n participant A\n participant B\n A->>B: hi");
expect(d.participants).toEqual(["A", "B"]);
});
it("captures message statements verbatim (trimmed)", () => {
const d = parseSequence("sequenceDiagram\n A->>B: hi\n B-->>A: bye");
expect(d.statements).toEqual(["A->>B: hi", "B-->>A: bye"]);
});
it("infers participants from messages when not declared", () => {
const d = parseSequence("sequenceDiagram\n A->>B: hi\n B->>C: on");
expect(d.participants).toEqual(["A", "B", "C"]);
});
it("ignores comments and blank lines", () => {
const d = parseSequence("sequenceDiagram\n%% note\n\n A->>B: hi");
expect(d.statements).toEqual(["A->>B: hi"]);
});
});