diff --git a/apps/native/src/components/widget/summaries/__snapshots__/diff-section.stories.tsx.snap b/apps/native/src/components/widget/summaries/__snapshots__/diff-section.stories.tsx.snap
index 2d7ad5af8..d653a75c8 100644
--- a/apps/native/src/components/widget/summaries/__snapshots__/diff-section.stories.tsx.snap
+++ b/apps/native/src/components/widget/summaries/__snapshots__/diff-section.stories.tsx.snap
@@ -2,6 +2,6 @@
exports[`Empty 1`] = `"
"`;
-exports[`Multiple Files 1`] = `"modules/darwin/packages.nix+2
"`;
+exports[`Multiple Files 1`] = `"modules/darwin/packages.nix+2
"`;
-exports[`Single File 1`] = `"modules/darwin/packages.nix+2
"`;
+exports[`Single File 1`] = `"modules/darwin/packages.nix+2
"`;
diff --git a/apps/native/src/components/widget/summaries/__snapshots__/full-file-diff-editor.stories.tsx.snap b/apps/native/src/components/widget/summaries/__snapshots__/full-file-diff-editor.stories.tsx.snap
index 31b1df028..72332d02f 100644
--- a/apps/native/src/components/widget/summaries/__snapshots__/full-file-diff-editor.stories.tsx.snap
+++ b/apps/native/src/components/widget/summaries/__snapshots__/full-file-diff-editor.stories.tsx.snap
@@ -1,11 +1,11 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`Collapsed 1`] = `"configuration.nixAdd ripgrep, fd, jqEnable flakes
"`;
+exports[`Collapsed 1`] = `"configuration.nix+4Add ripgrep, fd, jqEnable flakes
"`;
-exports[`Loading 1`] = `"configuration.nixAdd ripgrep, fd, jq
"`;
+exports[`Loading 1`] = `"configuration.nix+3Add ripgrep, fd, jq
"`;
-exports[`Multiple Hunks 1`] = `"configuration.nixAdd ripgrep, fd, jqEnable flakes
"`;
+exports[`Multiple Hunks 1`] = `"configuration.nix+4Add ripgrep, fd, jqEnable flakes
"`;
-exports[`Removed 1`] = `"modules/home/old-shell.nix "`;
+exports[`Removed 1`] = `"modules/home/old-shell.nix-8
"`;
exports[`Single Hunk 1`] = `""`;
diff --git a/apps/native/src/components/widget/summaries/__snapshots__/hunk-pill.stories.tsx.snap b/apps/native/src/components/widget/summaries/__snapshots__/hunk-pill.stories.tsx.snap
index 533a2600a..5509f6694 100644
--- a/apps/native/src/components/widget/summaries/__snapshots__/hunk-pill.stories.tsx.snap
+++ b/apps/native/src/components/widget/summaries/__snapshots__/hunk-pill.stories.tsx.snap
@@ -1,9 +1,9 @@
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
-exports[`Additions Only 1`] = `"+3"`;
+exports[`Additions Only 1`] = `"+3"`;
-exports[`Deletions Only 1`] = `"-3"`;
+exports[`Deletions Only 1`] = `"-3"`;
-exports[`Mixed 1`] = `"+2 -1"`;
+exports[`Mixed 1`] = `"+2-1"`;
exports[`With Summary Title 1`] = `"Add vim and git"`;
diff --git a/apps/native/src/components/widget/summaries/diff-line-stats.test.tsx b/apps/native/src/components/widget/summaries/diff-line-stats.test.tsx
new file mode 100644
index 000000000..617d4204d
--- /dev/null
+++ b/apps/native/src/components/widget/summaries/diff-line-stats.test.tsx
@@ -0,0 +1,56 @@
+import { describe, expect, it } from "vitest";
+import { countDiffLineStats, sumDiffLineStats } from "./diff-line-stats";
+
+describe("diff line stats", () => {
+ it("counts added and removed hunk lines without counting file headers", () => {
+ const diff = `diff --git a/configuration.nix b/configuration.nix
+--- a/configuration.nix
++++ b/configuration.nix
+@@ -1,4 +1,6 @@
+ context
+-old
++new
++another
+ unchanged`;
+
+ expect(countDiffLineStats(diff)).toEqual({ added: 2, removed: 1 });
+ });
+
+ it("counts new and removed files", () => {
+ const newFile = `diff --git a/new.nix b/new.nix
+--- /dev/null
++++ b/new.nix
+@@ -0,0 +1,3 @@
++{
++ programs.git.enable = true;
++}`;
+
+ const removedFile = `diff --git a/old.nix b/old.nix
+--- a/old.nix
++++ /dev/null
+@@ -1,3 +0,0 @@
+-{
+- programs.fish.enable = true;
+-}`;
+
+ expect(countDiffLineStats(newFile)).toEqual({ added: 3, removed: 0 });
+ expect(countDiffLineStats(removedFile)).toEqual({ added: 0, removed: 3 });
+ });
+
+ it("counts hunk content that resembles diff headers", () => {
+ const diff = `@@ -1,2 +1,2 @@
+---- actual removed content
++++ actual added content`;
+
+ expect(countDiffLineStats(diff)).toEqual({ added: 1, removed: 1 });
+ });
+
+ it("sums stats across hunks", () => {
+ const changes = [
+ { diff: "@@ -1 +1 @@\n-old\n+new" },
+ { diff: "@@ -5,0 +6,2 @@\n+one\n+two" },
+ ];
+
+ expect(sumDiffLineStats(changes)).toEqual({ added: 3, removed: 1 });
+ });
+});
diff --git a/apps/native/src/components/widget/summaries/diff-line-stats.tsx b/apps/native/src/components/widget/summaries/diff-line-stats.tsx
new file mode 100644
index 000000000..6b4ce9005
--- /dev/null
+++ b/apps/native/src/components/widget/summaries/diff-line-stats.tsx
@@ -0,0 +1,69 @@
+import { cn } from "@/lib/utils";
+
+export type DiffLineStats = {
+ added: number;
+ removed: number;
+};
+
+export function countDiffLineStats(diff: string): DiffLineStats {
+ let added = 0;
+ let removed = 0;
+ let inHunk = false;
+
+ for (const line of diff.split("\n")) {
+ if (line.startsWith("diff --git ")) {
+ inHunk = false;
+ continue;
+ }
+
+ if (line.startsWith("@@")) {
+ inHunk = true;
+ continue;
+ }
+
+ if (!inHunk) continue;
+
+ if (line.startsWith("+")) added++;
+ else if (line.startsWith("-")) removed++;
+ }
+
+ return { added, removed };
+}
+
+export function sumDiffLineStats(changes: Array<{ diff: string }>): DiffLineStats {
+ return changes.reduce(
+ (total, change) => {
+ const stats = countDiffLineStats(change.diff);
+ total.added += stats.added;
+ total.removed += stats.removed;
+ return total;
+ },
+ { added: 0, removed: 0 },
+ );
+}
+
+interface DiffLineStatsBadgeProps {
+ stats: DiffLineStats;
+ className?: string;
+}
+
+export function DiffLineStatsBadge({ stats, className }: DiffLineStatsBadgeProps) {
+ if (stats.added === 0 && stats.removed === 0) return null;
+
+ return (
+
+ {stats.added > 0 && (
+ +{stats.added}
+ )}
+ {stats.removed > 0 && (
+ -{stats.removed}
+ )}
+
+ );
+}
diff --git a/apps/native/src/components/widget/summaries/full-file-diff-editor.tsx b/apps/native/src/components/widget/summaries/full-file-diff-editor.tsx
index f9d606fca..2cb5ca512 100644
--- a/apps/native/src/components/widget/summaries/full-file-diff-editor.tsx
+++ b/apps/native/src/components/widget/summaries/full-file-diff-editor.tsx
@@ -7,6 +7,7 @@ import { HunkPill } from "./hunk-pill";
import { DiffView } from "./diff-view";
import { monaco } from "./monaco-setup";
import { FileView } from "./file-view";
+import { DiffLineStatsBadge, sumDiffLineStats } from "./diff-line-stats";
interface FullFileDiffEditorProps {
filename: string;
@@ -62,6 +63,7 @@ export function FullFileDiffEditor({ filename, changes, contents, isOpen, onOpen
};
const changeType = displayChange.changeType;
+ const fileStats = sumDiffLineStats(changes);
return (
+
{changes.map((c, i) => (
- focusChange(i)} />
+ 1}
+ onClick={() => focusChange(i)}
+ />
))}
>
}
diff --git a/apps/native/src/components/widget/summaries/hunk-pill.tsx b/apps/native/src/components/widget/summaries/hunk-pill.tsx
index 7eb591ac5..c508cf4b3 100644
--- a/apps/native/src/components/widget/summaries/hunk-pill.tsx
+++ b/apps/native/src/components/widget/summaries/hunk-pill.tsx
@@ -1,38 +1,26 @@
import { Badge } from "@/components/ui/badge";
import type { ChangeWithRichType } from "@/components/widget/utils";
import { useWidgetStore } from "@/stores/widget-store";
-
-function getDiffBody(diff: string): string[] {
- const lines = diff.split("\n");
- const hunkStart = lines.findIndex((l) => l.startsWith("@@"));
- return hunkStart >= 0 ? lines.slice(hunkStart + 1) : [];
-}
-
-function countAddedRemoved(diff: string): { added: number; removed: number } {
- const body = getDiffBody(diff);
- let added = 0;
- let removed = 0;
- for (const line of body) {
- if (line.startsWith("+") && !line.startsWith("+++")) added++;
- else if (line.startsWith("-") && !line.startsWith("---")) removed++;
- }
- return { added, removed };
-}
+import { countDiffLineStats, DiffLineStatsBadge } from "./diff-line-stats";
interface HunkPillProps {
change: ChangeWithRichType;
+ showCounts?: boolean;
onClick: () => void;
}
-// Badge shown in a file header for a single change: displays the summary title if available, otherwise +N/-M counts. Clicking scrolls the diff editor to that hunk.
-export function HunkPill({ change, onClick }: HunkPillProps) {
+// Clicking a hunk pill opens the file and scrolls the diff editor to that hunk.
+export function HunkPill({ change, showCounts = true, onClick }: HunkPillProps) {
const changeMap = useWidgetStore((s) => s.changeMap);
let summaryTitle: string | null = null;
if (changeMap) {
for (const group of changeMap.groups) {
const match = group.changes.find((c) => c.hash === change.hash);
- if (match) { summaryTitle = match.title; break; }
+ if (match) {
+ summaryTitle = match.title;
+ break;
+ }
}
if (!summaryTitle) {
const match = changeMap.singles.find((c) => c.hash === change.hash);
@@ -40,12 +28,12 @@ export function HunkPill({ change, onClick }: HunkPillProps) {
}
}
- const { added, removed } = countAddedRemoved(change.diff);
- const showCounts = change.changeType === "edited" || change.changeType === "renamed";
- const label = summaryTitle
- ?? (showCounts ? [added && `+${added}`, removed && `-${removed}`].filter(Boolean).join(" ") : null);
+ const stats = countDiffLineStats(change.diff);
+ const label = summaryTitle;
- if (!label) return null;
+ if (!label && (!showCounts || (stats.added === 0 && stats.removed === 0))) {
+ return null;
+ }
return (
- {label}
+ {label ?? }
);
}
\ No newline at end of file