From 4bca6e10e82b297ae9b9c390db2fa1fe4788e74f Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Tue, 12 May 2026 09:40:37 +1000 Subject: [PATCH 1/4] fix(desktop): filter /dev/null from diff file labels For new/deleted files, git diffs use /dev/null as the absent path. getDiffFileLabel was showing labels like "/dev/null -> path/to/file". Treat /dev/null as an absent path so only the real file path is shown. Co-Authored-By: Claude Opus 4.6 (1M context) Signed-off-by: Matt Toohey --- desktop/src/features/messages/lib/parseDiff.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/desktop/src/features/messages/lib/parseDiff.ts b/desktop/src/features/messages/lib/parseDiff.ts index a44dc62ec7..b65c1e63cd 100644 --- a/desktop/src/features/messages/lib/parseDiff.ts +++ b/desktop/src/features/messages/lib/parseDiff.ts @@ -37,11 +37,14 @@ export function getDiffFileLabel( file: FileData, fallbackFilePath?: string, ): string { - if (file.oldPath && file.newPath && file.oldPath !== file.newPath) { - return `${file.oldPath} -> ${file.newPath}`; + const oldPath = file.oldPath === "/dev/null" ? undefined : file.oldPath; + const newPath = file.newPath === "/dev/null" ? undefined : file.newPath; + + if (oldPath && newPath && oldPath !== newPath) { + return `${oldPath} -> ${newPath}`; } - return file.newPath || file.oldPath || fallbackFilePath || "diff"; + return newPath || oldPath || fallbackFilePath || "diff"; } export function countDiffFileChanges(file: FileData) { From e3c9dfcfee05744d4651068eb1ef713ecaf00a06 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Fri, 10 Jul 2026 17:16:53 +1000 Subject: [PATCH 2/4] fix(desktop): keep New file/Deleted badge visible in single-file diffs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The /dev/null label fix made getDiffFileLabel return the bare file path for new/deleted files, which now matches fallbackFilePath in single-file diffs — so showFileHeader skipped the per-file header entirely, hiding the New file/Deleted type badge and the +/- change counts. Show the header whenever the diff type is notable (add/delete/rename/ copy), while still collapsing it for plain modifications where it would only repeat the path shown in the card header. Co-Authored-By: Claude Fable 5 Signed-off-by: Matt Toohey --- desktop/src/features/messages/ui/DiffViewer.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/desktop/src/features/messages/ui/DiffViewer.tsx b/desktop/src/features/messages/ui/DiffViewer.tsx index 239fd02110..3c1723ce72 100644 --- a/desktop/src/features/messages/ui/DiffViewer.tsx +++ b/desktop/src/features/messages/ui/DiffViewer.tsx @@ -79,10 +79,13 @@ export function DiffViewer({
{files.map((file) => { const label = getDiffFileLabel(file, fallbackFilePath); - const showFileHeader = - files.length > 1 || !fallbackFilePath || label !== fallbackFilePath; const { additions, deletions } = countDiffFileChanges(file); const diffType = normalizeDiffType(file.type); + const showFileHeader = + files.length > 1 || + !fallbackFilePath || + label !== fallbackFilePath || + diffType !== "modify"; const fileKey = [ file.oldPath || "", file.newPath || "", From 0683e35d86bbbcffe11532bbdccdee68a6bc10c1 Mon Sep 17 00:00:00 2001 From: Matt Toohey Date: Fri, 10 Jul 2026 17:23:23 +1000 Subject: [PATCH 3/4] fix(desktop): surface diff type badge in card title when file header is collapsed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of forcing the per-file header open just to show the New file/Deleted badge (previous fix), keep the header collapsed when it would only repeat the card title, and show the badge in the diff card's title bar instead — both in the inline DiffMessage card and the expanded dialog. DIFF_TYPE_LABELS and the header-visibility check move into parseDiff.ts as shared helpers, and a new getDiffTitleBadge returns the badge label only for single-file diffs whose collapsed header has a notable change type — so the badge never renders in both places. Co-Authored-By: Claude Fable 5 Signed-off-by: Matt Toohey --- .../src/features/messages/lib/parseDiff.ts | 41 +++++++++++++++++++ .../src/features/messages/ui/DiffMessage.tsx | 11 +++++ .../messages/ui/DiffMessageExpanded.tsx | 13 +++++- .../src/features/messages/ui/DiffViewer.tsx | 20 ++++----- 4 files changed, 71 insertions(+), 14 deletions(-) diff --git a/desktop/src/features/messages/lib/parseDiff.ts b/desktop/src/features/messages/lib/parseDiff.ts index b65c1e63cd..c797dc8904 100644 --- a/desktop/src/features/messages/lib/parseDiff.ts +++ b/desktop/src/features/messages/lib/parseDiff.ts @@ -33,6 +33,14 @@ export function parseUnifiedDiff(content: string): ParsedDiffResult { } } +export const DIFF_TYPE_LABELS: Record = { + add: "New file", + copy: "Copied", + delete: "Deleted", + modify: "Modified", + rename: "Renamed", +}; + export function getDiffFileLabel( file: FileData, fallbackFilePath?: string, @@ -47,6 +55,39 @@ export function getDiffFileLabel( return newPath || oldPath || fallbackFilePath || "diff"; } +export function shouldShowDiffFileHeader( + label: string, + fileCount: number, + fallbackFilePath?: string, +): boolean { + return fileCount > 1 || !fallbackFilePath || label !== fallbackFilePath; +} + +/** + * Badge for the diff card's title bar. Set only when the diff is a single + * file whose per-file header is collapsed (its label just repeats the card + * title) and whose change type is notable — so "New file"/"Deleted" isn't + * lost with the header. + */ +export function getDiffTitleBadge( + content: string, + fallbackFilePath?: string, +): string | undefined { + const { files } = parseUnifiedDiff(content); + if (files.length !== 1) { + return undefined; + } + + const file = files[0]; + const label = getDiffFileLabel(file, fallbackFilePath); + if (shouldShowDiffFileHeader(label, files.length, fallbackFilePath)) { + return undefined; + } + + const diffType = normalizeDiffType(file.type); + return diffType === "modify" ? undefined : DIFF_TYPE_LABELS[diffType]; +} + export function countDiffFileChanges(file: FileData) { let additions = 0; let deletions = 0; diff --git a/desktop/src/features/messages/ui/DiffMessage.tsx b/desktop/src/features/messages/ui/DiffMessage.tsx index 9013ae5d69..444f2d1faa 100644 --- a/desktop/src/features/messages/ui/DiffMessage.tsx +++ b/desktop/src/features/messages/ui/DiffMessage.tsx @@ -1,6 +1,7 @@ import * as React from "react"; import { FileDiff, Maximize2 } from "lucide-react"; +import { getDiffTitleBadge } from "@/features/messages/lib/parseDiff"; import { isSafeUrl } from "@/shared/lib/url"; import { Button } from "@/shared/ui/button"; import { useSmoothCorners } from "@/shared/ui/smoothCorners"; @@ -39,6 +40,11 @@ export default function DiffMessage({ const safeRepoUrl = isSafeUrl(repoUrl) ? repoUrl : undefined; + const titleBadge = React.useMemo( + () => getDiffTitleBadge(content, filePath), + [content, filePath], + ); + const commitUrl = safeRepoUrl && commitSha ? `${safeRepoUrl}/commit/${commitSha}` : undefined; @@ -54,6 +60,11 @@ export default function DiffMessage({ {filePath ?? "diff"} + {titleBadge && ( + + {titleBadge} + + )} {shortSha && ( {commitUrl ? ( diff --git a/desktop/src/features/messages/ui/DiffMessageExpanded.tsx b/desktop/src/features/messages/ui/DiffMessageExpanded.tsx index 15ce1703e4..eb30447f72 100644 --- a/desktop/src/features/messages/ui/DiffMessageExpanded.tsx +++ b/desktop/src/features/messages/ui/DiffMessageExpanded.tsx @@ -1,6 +1,7 @@ import { Rows3, SplitSquareVertical } from "lucide-react"; -import { useState } from "react"; +import { useMemo, useState } from "react"; +import { getDiffTitleBadge } from "@/features/messages/lib/parseDiff"; import { DiffViewer } from "@/features/messages/ui/DiffViewer"; import { Button } from "@/shared/ui/button"; import { @@ -23,6 +24,11 @@ export default function DiffMessageExpanded({ }: DiffMessageExpandedProps) { const [viewType, setViewType] = useState<"split" | "unified">("unified"); + const titleBadge = useMemo( + () => getDiffTitleBadge(content, filePath), + [content, filePath], + ); + return ( { @@ -36,6 +42,11 @@ export default function DiffMessageExpanded({ {filePath ?? "Diff Viewer"} + {titleBadge && ( + + {titleBadge} + + )}
- - Expand diff - - )} + + )} + {onExpand && ( + + + + + Expand diff + + )} +
{description && ( diff --git a/desktop/src/features/messages/ui/DiffMessageExpanded.tsx b/desktop/src/features/messages/ui/DiffMessageExpanded.tsx index eb30447f72..5c533bb4e5 100644 --- a/desktop/src/features/messages/ui/DiffMessageExpanded.tsx +++ b/desktop/src/features/messages/ui/DiffMessageExpanded.tsx @@ -39,7 +39,7 @@ export default function DiffMessageExpanded({
- + {filePath ?? "Diff Viewer"} {titleBadge && ( @@ -47,7 +47,7 @@ export default function DiffMessageExpanded({ {titleBadge} )} -
+