/** * Line diff — one copy, for every surface that shows what changed. * * WHY THIS FILE EXISTS. The same LCS walk was written out three times: * privately in `useAssist.ts`, and again inside `HistoryPanel.vue` and * `VersionHistorySection.vue`. The three were character-identical apart from * quote style — nobody had diverged them on purpose, they were simply copied * because `computeDiff` was never exported. Milestone 323 needed a fourth * consumer (a rule's edit history), and a fourth copy is the cost #3207 * records: a fix or an improvement now has to be found in N places by someone * who does not know N. */ export interface DiffLine { type: "equal" | "delete" | "insert"; text: string; } /** * Diff `a` against `b`, line by line. * * `delete` lines come from `a`, `insert` lines from `b` — so the caller * decides which side reads as "before" by which argument it passes. Every * caller here passes the CURRENT text as `a` and the older text as `b`, so a * deletion is what the old version had and an insertion is what replaced it. * * O(m·n) in time and memory: fine for a note or a rule, and deliberately not * generalised further, since nothing here diffs a file of thousands of lines. */ export function computeDiff(a: string, b: string): DiffLine[] { const aLines = a.split("\n"); const bLines = b.split("\n"); const m = aLines.length, n = bLines.length; const dp: number[][] = Array.from({ length: m + 1 }, () => new Array(n + 1).fill(0)); for (let i = m - 1; i >= 0; i--) for (let j = n - 1; j >= 0; j--) dp[i][j] = aLines[i] === bLines[j] ? dp[i + 1][j + 1] + 1 : Math.max(dp[i + 1][j], dp[i][j + 1]); const result: DiffLine[] = []; let i = 0, j = 0; while (i < m && j < n) { if (aLines[i] === bLines[j]) { result.push({ type: "equal", text: aLines[i++] }); j++; } else if (dp[i + 1][j] >= dp[i][j + 1]) result.push({ type: "delete", text: aLines[i++] }); else result.push({ type: "insert", text: bLines[j++] }); } while (i < m) result.push({ type: "delete", text: aLines[i++] }); while (j < n) result.push({ type: "insert", text: bLines[j++] }); return result; }