Files
electric-cherry/src/lib/ui/components/DiffViewer.svelte
T
joshandClaude Opus 4.7 47051dc79a Repaint theme blue with larger typography
Swap the cherry-red primary for the logo's blue, bump body 13→15px
and h1 32→40px, and add semantic tokens (--bg-*, --accent-*, --text-*)
so future palette shifts are a single-file edit. Cherry-red survives
only for danger states and the brand cherry mark.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-09-12 12:29:02 -04:00

113 lines
3.6 KiB
Svelte

<script lang="ts">
interface Props {
before: string | null;
after: string | null;
verb: string;
relPath: string;
}
let { before, after, verb, relPath }: Props = $props();
interface Row {
kind: 'ctx' | 'add' | 'del';
text: string;
}
let rows = $derived(computeRows(before, after));
function computeRows(a: string | null, b: string | null): Row[] {
const alines = (a ?? '').split(/\r?\n/);
const blines = (b ?? '').split(/\r?\n/);
const asetBase: Row[] = alines.map((t) => ({ kind: 'del' as const, text: t }));
const bsetBase: Row[] = blines.map((t) => ({ kind: 'add' as const, text: t }));
if (a === null) return bsetBase;
if (b === null) return asetBase;
let i = 0;
while (i < alines.length && i < blines.length && alines[i] === blines[i]) i++;
let j = 0;
while (
j < alines.length - i &&
j < blines.length - i &&
alines[alines.length - 1 - j] === blines[blines.length - 1 - j]
)
j++;
const prefix = alines.slice(0, i).map((t) => ({ kind: 'ctx' as const, text: t }));
const suffix = alines.slice(alines.length - j).map((t) => ({ kind: 'ctx' as const, text: t }));
const midA = alines.slice(i, alines.length - j).map((t) => ({ kind: 'del' as const, text: t }));
const midB = blines.slice(i, blines.length - j).map((t) => ({ kind: 'add' as const, text: t }));
return [...prefix, ...midA, ...midB, ...suffix];
}
</script>
<div class="diff">
<header>
<span class="verb verb-{verb}">{verb}</span>
<span class="path">{relPath}</span>
</header>
<pre>
{#each rows as r, idx (idx)}<span
class="ln ln-{r.kind}">{r.kind === 'add' ? '+' : r.kind === 'del' ? '-' : ' '} {r.text}
</span>{/each}</pre>
</div>
<style>
.diff {
background: var(--bg-page);
border: 1px solid var(--border);
border-radius: var(--r-sm);
margin: 10px 0;
overflow: hidden;
}
header {
display: flex;
gap: 12px;
padding: 10px 14px;
background: var(--bg-nav);
border-bottom: 1px solid var(--border);
font-size: var(--text-sm);
align-items: center;
}
.verb {
padding: 3px 10px;
border-radius: var(--r-sm);
font-family: var(--font-mono);
font-size: var(--text-xs);
text-transform: uppercase;
letter-spacing: 0.8px;
border: 1px solid transparent;
}
.verb-added {
background: rgba(124, 212, 255, 0.10);
border-color: rgba(124, 212, 255, 0.3);
color: var(--spark-500);
}
.verb-updated {
background: rgba(124, 192, 245, 0.10);
border-color: rgba(124, 192, 245, 0.28);
color: var(--cream);
}
.verb-removed {
background: rgba(255, 48, 80, 0.1);
border-color: rgba(255, 48, 80, 0.3);
color: var(--danger-500);
}
.verb-held {
background: rgba(255, 176, 32, 0.1);
border-color: rgba(255, 176, 32, 0.3);
color: var(--amber-500);
}
.path { color: var(--ink-100); font-family: var(--font-mono); font-size: var(--text-mono); }
pre {
margin: 0;
padding: 14px 16px;
font-family: var(--font-mono);
font-size: var(--text-mono);
line-height: var(--leading-mono);
color: var(--ink-300);
max-height: 540px;
overflow: auto;
}
.ln { display: block; }
.ln-add { background: rgba(124, 212, 255, 0.08); color: var(--spark-400); }
.ln-del { background: rgba(255, 48, 80, 0.08); color: #ff8a9a; }
</style>