Skip to content
Kappa
Diff Viewer
@dicehub/kappav0.4.2

Diff Viewer

Review unified text patches with line numbers, change counts, and inline emphasis.

Application settings+1−1
app.yaml
BeforeAfterContent
@@ -1,4 +1,4 @@
11
Unchanged: service: background-jobs
2
Removed: retryLimit: 3
2
Added: retryLimit: 5
33
Unchanged: timeout: 30000
44
Unchanged: concurrency: 8
<script setup>
import { DiffViewer } from "@dicehub/kappa/components/diff-viewer";

const patch = `--- a/app.yaml
+++ b/app.yaml
@@ -1,4 +1,4 @@
 service: background-jobs
-retryLimit: 3
+retryLimit: 5
 timeout: 30000
 concurrency: 8`;
</script>

<template>
  <DiffViewer :patch="patch" label="Application settings" />
</template>

Installation

Diff Viewer is part of Kappa. Import the shared styles and theme once in your application.

import "@dicehub/kappa/styles/kappa.css";
import "@dicehub/kappa/styles/theme-kappa.css";
import { DiffViewer } from "@dicehub/kappa";

A dedicated entry point also exports the parser and public types.

import { DiffViewer, parseUnifiedDiff } from "@dicehub/kappa/components/diff-viewer";

Usage

Pass a complete unified patch with file headers and hunk ranges. The viewer is read-only; it does not load files, generate patches, or apply changes. Copy returns the original patch, including its headers and line endings.

Text Git patches, multiple files, multiple hunks, creation, deletion, and no-newline markers are supported. Binary patches, combined merge diffs, and metadata-only changes are unsupported. Quoted Git paths remain as supplied.

Changed characters use a shared prefix and suffix within paired replacement lines. This emphasis is language independent; syntax highlighting and virtual scrolling are not included. Load a selected file or bounded patch for large reviews.

Examples

Split view

Compare Before and After columns. Adjacent removal and addition blocks pair by line order; unmatched lines leave an empty cell.

Application settings+1−1
app.yaml
BeforeAfterContent
@@ -1,4 +1,4 @@
11
Unchanged: service: background-jobs
2
Removed: retryLimit: 3
2
Added: retryLimit: 5
33
Unchanged: timeout: 30000
44
Unchanged: concurrency: 8
<script setup lang="ts">
import { ref } from "vue";
import { DiffViewer } from "@dicehub/kappa/components/diff-viewer";
import { Button } from "@dicehub/kappa/components/button";

const split = ref(false);
const patch = `--- a/app.yaml
+++ b/app.yaml
@@ -1,4 +1,4 @@
 service: background-jobs
-retryLimit: 3
+retryLimit: 5
 timeout: 30000
 concurrency: 8`;
</script>

<template>
  <Button size="sm" :aria-pressed="split" @click="split = !split">
    Split view
  </Button>
  <DiffViewer
    :patch="patch"
    label="Application settings"
    :view="split ? 'split' : 'unified'"
  />
</template>

Multiple files

File headers separate each change. The title shows total counts, and each file shows its own counts.

Service configuration+3−1
app.yaml+1−1
BeforeAfterContent
@@ -1,4 +1,4 @@
11
Unchanged: service: background-jobs
2
Removed: retryLimit: 3
2
Added: retryLimit: 5
33
Unchanged: timeout: 30000
44
Unchanged: concurrency: 8
notifications.yaml+2−0
BeforeAfterContent
@@ -0,0 +1,2 @@
1
Added: enabled: true
2
Added: channels: [email, webhook]
<script setup lang="ts">
import { DiffViewer } from "@dicehub/kappa/components/diff-viewer";

const patch = `--- a/app.yaml
+++ b/app.yaml
@@ -1,4 +1,4 @@
 service: background-jobs
-retryLimit: 3
+retryLimit: 5
 timeout: 30000
 concurrency: 8
diff --git a/notifications.yaml b/notifications.yaml
new file mode 100644
--- /dev/null
+++ b/notifications.yaml
@@ -0,0 +1,2 @@
+enabled: true
+channels: [email, webhook]`;
</script>

<template>
  <DiffViewer :patch="patch" label="Service configuration" />
</template>

Empty and invalid patches

Empty text shows no changes. Invalid or unsupported input shows a clear message and no partial diff. Change the patch prop to update the view.

Patch state+1−1
app.yaml
BeforeAfterContent
@@ -1,4 +1,4 @@
11
Unchanged: service: background-jobs
2
Removed: retryLimit: 3
2
Added: retryLimit: 5
33
Unchanged: timeout: 30000
44
Unchanged: concurrency: 8
<script setup lang="ts">
import { ref } from "vue";
import { DiffViewer } from "@dicehub/kappa/components/diff-viewer";
import { Button } from "@dicehub/kappa/components/button";

const validPatch = `--- a/app.yaml
+++ b/app.yaml
@@ -1,4 +1,4 @@
 service: background-jobs
-retryLimit: 3
+retryLimit: 5
 timeout: 30000
 concurrency: 8`;
const patch = ref(validPatch);
</script>

<template>
  <Button size="sm" @click="patch = ''">Empty patch</Button>
  <Button size="sm" @click="patch = 'incomplete patch'">Invalid patch</Button>
  <Button size="sm" @click="patch = validPatch">Valid patch</Button>
  <DiffViewer :patch="patch" label="Patch state" />
</template>

Compact review

Hide line numbers, inline emphasis, or the copy control when the surrounding workflow supplies them.

Compact review+1−1
app.yaml
Content
@@ -1,4 +1,4 @@
Unchanged: service: background-jobs
Removed: retryLimit: 3
Added: retryLimit: 5
Unchanged: timeout: 30000
Unchanged: concurrency: 8
<script setup lang="ts">
import { DiffViewer } from "@dicehub/kappa/components/diff-viewer";

const patch = `--- a/app.yaml
+++ b/app.yaml
@@ -1,4 +1,4 @@
 service: background-jobs
-retryLimit: 3
+retryLimit: 5
 timeout: 30000
 concurrency: 8`;
</script>

<template>
  <DiffViewer
    :patch="patch"
    label="Compact review"
    :line-numbers="false"
    :inline-changes="false"
    :copyable="false"
  />
</template>

Long lines and safe text

Long lines scroll inside the viewer. Source markup remains plain text. Set the maximum height with the component CSS variable.

Report export+2−1
report.ts
BeforeAfterContent
@@ -1,2 +1,3 @@
1
Removed: export const fields = ["jobId", "status"];
1
Added: export const fields = ["jobId", "status", "createdAt", "completedAt", "durationMs", "attemptCount", "correlationId"];
22
Unchanged: export const format = "csv";
3
Added: export const title = '<img src=x onerror="alert(1)">';
<script setup lang="ts">
import { DiffViewer } from "@dicehub/kappa/components/diff-viewer";

const patch = `--- a/report.ts
+++ b/report.ts
@@ -1,2 +1,3 @@
-export const fields = ["jobId", "status"];
+export const fields = ["jobId", "status", "createdAt", "completedAt", "durationMs", "attemptCount", "correlationId"];
 export const format = "csv";
+export const title = '<img src=x onerror="alert(1)">';`;
</script>

<template>
  <DiffViewer :patch="patch" label="Report export" class="review" />
</template>

<style scoped>
.review { --kappa-diff-viewer-max-height: 18rem; }
</style>

Parser

Use parseUnifiedDiff(patch) for counts or application summaries. It returns a DiffDocument with files, hunks, lines, and total counts. Malformed counts, incomplete hunks, overlapping ranges, and unsupported formats throw an Error. The component converts these errors into its invalid state.

import { parseUnifiedDiff } from "@dicehub/kappa/components/diff-viewer";

const patch = `--- a/app.yaml
+++ b/app.yaml
@@ -1,4 +1,4 @@
 service: background-jobs
-retryLimit: 3
+retryLimit: 5
 timeout: 30000
 concurrency: 8`;

try {
  const { files, additions, deletions } = parseUnifiedDiff(patch);
  console.log(files.length, additions, deletions);
} catch (error) {
  // A malformed or unsupported patch throws before any partial result is returned.
  console.error(error.message);
}

Accessibility

Each file uses a labeled table. Added and removed lines have signs and text labels as well as color. The scroll region is keyboard focusable and keeps a visible focus outline. Source code uses left-to-right text in both page directions.

The copy control follows Ark UI Clipboard behavior through Kappa Clipboard Text. Copy requires browser clipboard access. An accessible status reports the copied state. Kappa supplies the static diff layout because Ark UI has no diff primitive.

Use labels to translate empty, invalid, copy, copied, before, after, content, added, removed, unchanged, and noNewline. Use label for the title.

API Reference

PropTypeDefaultDescription
patchstringrequiredComplete unified text patch; empty text means no changes.
labelstring"File changes"Visible title and accessible name.
view"unified" | "split""unified"Unified rows or paired Before/After columns.
lineNumbersbooleantrueShow original and new line numbers.
inlineChangesbooleantrueEmphasize changed characters in equal-length replacement blocks.
copyablebooleantrueShow the Ark UI copy control for a valid patch.
labelsPartial<DiffViewerLabels>English labelsTranslate copy, state, column, and change labels.

DiffViewer and DiffViewer.Root are equivalent. DiffViewerRoot, parseUnifiedDiff, DIFF_VIEWER_DEFAULT_LABELS, and the DiffDocument, DiffFile, DiffHunk, DiffLine, DiffLineKind, DiffViewerProps, DiffViewerView, and DiffViewerLabels types are public exports.

HTML attributes and listeners pass to the root section. The root exposes data-slot="diff-viewer", data-state="ready|empty|invalid", and data-view="unified|split". Set --kappa-diff-viewer-max-height to change the default 32rem scroll height. Added and removed surfaces use the shared success and danger theme tokens in both modes. Inline emphasis mixes the corresponding text token at 18% over its surface.