Data Display

SourceDiffViewer

Lightweight unified-patch viewer for source-code and operational diffs with file headers, hunk headers, line state styling, and bounded rendering.

import { SourceDiffViewer } from '@lostgradient/cinder';
diffsourcecode
01

Overview

Reference usage

Lightweight unified-patch viewer for source-code and operational diffs with file headers, hunk headers, line state styling, and bounded rendering.

See the editor/Cinder surface boundary. SourceDiffViewer owns dependency-light source patches; use the editor DiffViewer for Markdown review and editor-runtime workflows.

Usage

svelte
<script lang="ts">
  import { SourceDiffViewer } from '@lostgradient/cinder';

  const patch = `diff --git a/src/generated.txt b/src/generated.txt
--- a/src/generated.txt
+++ b/src/generated.txt
@@ -1,6 +1,6 @@
 unchanged
-old one
+new one
-old two
+new two
 unchanged
`;
</script>

<SourceDiffViewer {patch} maxLines={4} />

Use SourceDiffViewer for source-code patches, agent workspace patches, Git output, and other operational unified diffs. Use DiffViewer from @lostgradient/editor for Markdown document review, front-matter handling, Markdown normalization, and prose-oriented word-level changes.

Annotation hooks

SourceDiffViewer exposes optional, dependency-light selection and file hooks so a caller can build comment/annotation UI without SourceDiffViewer ever importing @lostgradient/editor or Markdown/Milkdown/ProseMirror. These hooks are the Cinder half of the diff-review contract; the Editor package's DiffReview/DiffReviewComments consume them, but this component itself has no idea either exists.

svelte
<script lang="ts">
  import { SourceDiffViewer, type SourceDiffAnnotationSelection } from '@lostgradient/cinder';

  const patch = `diff --git a/src/cache.ts b/src/cache.ts
--- a/src/cache.ts
+++ b/src/cache.ts
@@ -1,2 +1,2 @@
-cache.set(key, result);
+cache.set(key, result, { ttl: 60 });
 return result;
`;

  let selection: SourceDiffAnnotationSelection | null = $state(null);
</script>

<SourceDiffViewer
  {patch}
  annotationSelection={selection}
  onAnnotationSelectionChange={(next) => (selection = next)}
/>
  • onFilesChange(files) reports a descriptor — occurrence, paths, label, hunk/changed-line counts, and whether the file's content was entirely cut by maxLines — for every file the parser recognized, including a fully truncated one that never appears in the rendered output.
  • activeFileOccurrence renders exactly one recognized file; omit it (the default) to render every file, matching prior behavior. A fully truncated active file still renders its header and an explicit "Lines unavailable at the current display limit." message.
  • fileAnnotation renders alongside each file's header regardless of the other annotation hooks, receiving that file's descriptor. lineAnnotation renders next to each commentable row/side's add-comment control, so — like the control itself — it only appears when onAnnotationSelectionChange is also supplied.
  • annotationSelection + onAnnotationSelectionChange are a controlled pair, separate from ordinary diff navigation: a line's add-comment button or Shift-click selects/extends and commits immediately; a focused control's Enter starts a keyboard selection, Shift+Arrow extends it, Escape cancels and returns focus to the origin, and Enter commits. An extension that would cross a file, hunk, or side boundary is rejected — the origin never moves — with the reason announced in an instance-scoped status region.
  • ref exposes focusFile(fileOccurrence) and focusAnchor(anchor), each returning { status: 'focused' | 'unavailable' } and operating only within that component instance.

Every hook is optional and additive: with none supplied, rendering is byte-for-byte the same as before this feature existed, and two instances on the same page never share selection state, focus, or DOM ids.

Contract note: this repository's check-prop-conventions gate bans a non-native-passthrough lowercase on* prop, so the cross-package diff-review contract's onfileschange/onannotationselectionchange spelling is translated here to onFilesChange/onAnnotationSelectionChange. Every field name and payload shape is otherwise exactly as specified.

Guidance

Use When

  • Rendering source-code unified patches from agent output, Git operations, workspace changes, or review systems.
  • Showing operational patch output where file and hunk structure matters more than Markdown front matter or word-level prose review.

Avoid When

  • Comparing two Markdown documents with normalization, front-matter, and revert affordances — use DiffViewer from @lostgradient/editor.
  • Showing syntax-highlighted code samples rather than patch output — use code-block instead.
Live preview

Basic source diff viewer

src/example.ts
@@ -1,4 +1,5 @@
Context line 1: export function greet(name: string) {
Removed line 2: return `Hello, ${name}.`;
Added line 2: const normalizedName = name.trim();
Added line 3: return `Hello, ${normalizedName}.`;
Context old line 3, new line 4: }

02

When to use

Use when
  • Rendering source-code unified patches from agent output, Git operations, workspace changes, or review systems.
  • Showing operational patch output where file and hunk structure matters more than Markdown front matter or word-level prose review.
Avoid when
  • Comparing two Markdown documents with normalization, front-matter, and revert affordances — use `DiffViewer` from `@lostgradient/editor`.
  • Showing syntax-highlighted code samples rather than patch output. Use Code Block instead
03

Examples

Basic source diff viewer

Unified patch output with file headers, hunk headers, and source line states.

Bounded source diff viewer

Large patches can cap rendered diff rows while preserving the total count.

04

Props

Props for source-diff-viewer
Name
Type
Default
Description
patch required text Unified patch text to parse and render.
ariaLabel text Accessible label for the diff region.
maxLines number 1000 Maximum number of diff rows to render before truncating.
lineNumbers boolean true Whether old and new line-number gutters are rendered.
empty snippet Rendered when the patch is empty or contains no displayable diff rows. Falls back to a default "No patch lines to display." message — matching the empty snippet the chart/command families expose.
onFilesChange (files: SourceDiffFileDescriptor[]) => void Called with a descriptor for every recognized file whenever the parsed patch changes. Named onFilesChange (camelCase) rather than the lowercase onfileschange the cross-package diff-review contract uses elsewhere: this repository's check-prop-conventions gate structurally bans a non-native-passthrough lowercase on* prop, so the callback casing is translated to this repository's house style while every field name and the payload shape stay exactly as specified.
activeFileOccurrence number Controlled zero-based file occurrence to render exclusively. Absent (the default) renders every file, matching prior behavior.
fileAnnotation snippet Rendered next to each file's header, alongside its file-level annotation controls.
lineAnnotation snippet Rendered next to each commentable row's add-comment control. Like that control, it only appears when onAnnotationSelectionChange is also supplied — pass both to render per-line annotation markers.
annotationSelection { fileOccurrence: number; hunkOccurrence: number; side: SourceDiffAnnotationSide; /** Inclusive, one-based. */ startLine: number; /** Inclusive, one…
Show full type { fileOccurrence: number; hunkOccurrence: number; side: SourceDiffAnnotationSide; /** Inclusive, one-based. */ startLine: number; /** Inclusive, one-based. */ endLine: number; /** Always `'raw-source'`: source patches never claim normalized coordinates. */ coordinateSpace: 'raw-source'; selectedText: string; /** Up to three same-side lines immediately before `startLine`, in document order. */ contextBefore: string[]; /** Up to three same-side lines immediately after `endLine`, in document order. */ contextAfter: string[]; oldPath: string | null; newPath: string | null; }
Controlled current annotation selection. Distinct from ordinary diff navigation: setting or clearing it never affects which rows are shown.
onAnnotationSelectionChange (selection: SourceDiffAnnotationSelection | null) => void Called when the user commits, extends, or clears an annotation selection. See onFilesChange above for why this repository spells the contract's onannotationselectionchange as camelCase.
ref bindable { /** Focuses the labeled header of the given file occurrence, if rendered. */ focusFile: (fileOccurrence: number) => SourceDiffViewerFocusResult; /** Foc…
Show full type { /** Focuses the labeled header of the given file occurrence, if rendered. */ focusFile: (fileOccurrence: number) => SourceDiffViewerFocusResult; /** Focuses the line control for the given anchor, if its row is rendered. */ focusAnchor: ( anchor: Pick<SourceDiffAnnotationSelection, 'fileOccurrence' | 'hunkOccurrence' | 'side'> & { startLine: number; }, ) => SourceDiffViewerFocusResult; }
Programmatic handle for focusFile/focusAnchor.