Ctrl K
ComunidadGuías…

src/dashboard/diff.js

Rama master · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
/**
 * Minimal line diff — pure functions, zero dependency (no diff library).
 *
 * Used by the dashboard's "show what will change before you save" gate on
 * dangerous writes (runtime-config / accounts.json / tunables). We render the
 * additions/deletions in a confirm modal so an operator sees the exact change
 * before it commits.
 *
 * INLINE MIRROR: index.html inlines an equivalent copy (single-file constraint);
 * src/dashboard/check-inline-sync.js keeps them in sync. Edit both.
 *
 * Algorithm: classic LCS (longest common subsequence) over lines, then walk the
 * DP table to emit a unified add/del/context sequence. O(n*m) — fine for config
 * blobs (tens–hundreds of lines), never fed megabytes.
 */

// Normalize any value to an array of display lines. Objects → pretty JSON.
export function toLines(value) {
  if (value == null) return [];
  const text = typeof value === 'string' ? value : JSON.stringify(value, null, 2);
  return text.split('\n');
}

// Compute a line diff. Returns [{ op:'ctx'|'add'|'del', text }] in order.
export function lineDiff(before, after) {
  const a = toLines(before);
  const b = toLines(after);
  const n = a.length, m = b.length;
  // LCS DP table: lcs[i][j] = LCS length of a[i:] and b[j:].
  const lcs = Array.from({ length: n + 1 }, () => new Array(m + 1).fill(0));
  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 = [];
  let i = 0, j = 0;
  while (i < n && j < m) {
    if (a[i] === b[j]) { out.push({ op: 'ctx', text: a[i] }); i++; j++; }
    else if (lcs[i + 1][j] >= lcs[i][j + 1]) { out.push({ op: 'del', text: a[i] }); i++; }
    else { out.push({ op: 'add', text: b[j] }); j++; }
  }
  while (i < n) { out.push({ op: 'del', text: a[i] }); i++; }
  while (j < m) { out.push({ op: 'add', text: b[j] }); j++; }
  return out;
}

// Convenience: is there any real change? (used to skip the modal on no-op saves)
export function hasChange(before, after) {
  return lineDiff(before, after).some(d => d.op !== 'ctx');
}

// Summary counts for a one-line "+X / -Y" header.
export function diffStat(before, after) {
  let add = 0, del = 0;
  for (const d of lineDiff(before, after)) {
    if (d.op === 'add') add++;
    else if (d.op === 'del') del++;
  }
  return { add, del };
}