// Line diff for "pull" commands (live store text vs the local files). Pure, no dependency: longest common subsequence // over lines, printed like a unified diff without hunks (" " same, "-" only local, "+" only on the store). export function lineDiff(before: string, after: string): string[] { const a = before.split('\n'), b = after.split('\n'); if (a.length * b.length > 4_000_000) return [...a.map(l => `-${l}`), ...b.map(l => `+${l}`)]; const n = a.length, m = b.length; const lcs: Uint32Array[] = Array.from({ length: n + 1 }, () => new Uint32Array(m + 1)); for (let i = n - 1; i >= 0; i--) for (let j = m - 1; j >= 0; j--) lcs[i]![j] = a[i] === b[j] ? lcs[i + 1]![j + 1]! + 1 : Math.max(lcs[i + 1]![j]!, lcs[i]![j + 1]!); const out: string[] = []; let i = 0, j = 0; while (i < n && j < m) { if (a[i] === b[j]) { out.push(` ${a[i]}`); i++; j++; } else if (lcs[i + 1]![j]! >= lcs[i]![j + 1]!) out.push(`-${a[i++]}`); else out.push(`+${b[j++]}`); } while (i < n) out.push(`-${a[i++]}`); while (j < m) out.push(`+${b[j++]}`); return out; } /** Only the changed lines with `context` unchanged lines around them ("..." between gaps). Empty when equal. */ export function compactDiff(before: string, after: string, context = 2): string[] { const d = lineDiff(before, after); if (!d.some(l => l[0] !== ' ')) return []; const keep = new Set(); d.forEach((l, i) => { if (l[0] !== ' ') for (let k = i - context; k <= i + context; k++) keep.add(k); }); const out: string[] = []; let last = -1; d.forEach((l, i) => { if (!keep.has(i)) return; if (last >= 0 && i > last + 1) out.push('...'); out.push(l); last = i; }); return out; }