From 00327f58a83f9dbbf5d3e3d904b5e3e02fe1cb94 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 29 May 2026 11:29:44 +0000 Subject: [PATCH 1/2] Fix diff line stats display Co-authored-by: cooper --- .../widget/summaries/diff-line-stats.test.tsx | 56 +++++++++++++++ .../widget/summaries/diff-line-stats.tsx | 69 +++++++++++++++++++ .../summaries/full-file-diff-editor.tsx | 13 +++- .../components/widget/summaries/hunk-pill.tsx | 42 ++++------- 4 files changed, 152 insertions(+), 28 deletions(-) create mode 100644 apps/native/src/components/widget/summaries/diff-line-stats.test.tsx create mode 100644 apps/native/src/components/widget/summaries/diff-line-stats.tsx 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 From d32f60b60e10d21ae8befa18a3f2471f518030e9 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 29 May 2026 11:34:18 +0000 Subject: [PATCH 2/2] Update diff UI snapshots Co-authored-by: cooper --- .../summaries/__snapshots__/diff-section.stories.tsx.snap | 4 ++-- .../__snapshots__/full-file-diff-editor.stories.tsx.snap | 8 ++++---- .../summaries/__snapshots__/hunk-pill.stories.tsx.snap | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) 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`] = `"
No diff available
"`; -exports[`Multiple Files 1`] = `"
modules/darwin/packages.nix
+2
modules/home/shell.nix
+5
flake.nix
+1
"`; +exports[`Multiple Files 1`] = `"
modules/darwin/packages.nix
+2
modules/home/shell.nix
+5
flake.nix
+1
"`; -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.nix
Add ripgrep, fd, jqEnable flakes
"`; +exports[`Collapsed 1`] = `"
configuration.nix
+4Add ripgrep, fd, jqEnable flakes
"`; -exports[`Loading 1`] = `"
configuration.nix
Add ripgrep, fd, jq
Loading...
"`; +exports[`Loading 1`] = `"
configuration.nix
+3Add ripgrep, fd, jq
Loading...
"`; -exports[`Multiple Hunks 1`] = `"
configuration.nix
Add 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"`;