From 7e1d8c5e70a691807e1e3bd8e7a8f98ed76c1808 Mon Sep 17 00:00:00 2001 From: Frexxty Date: Sat, 12 Sep 2026 22:24:38 +0300 Subject: [PATCH 1/2] fix(web): prevent duplicate diff file ids --- apps/web/src/components/DiffPanel.tsx | 19 +++++++++---------- apps/web/src/lib/diffRendering.test.ts | 20 ++++++++++++++++++++ apps/web/src/lib/diffRendering.ts | 10 ++++++++++ 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index d764b9c6a9e2..095f66d11a44 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -35,7 +35,7 @@ import { useLocalStorage } from "../hooks/useLocalStorage"; import { useTheme } from "../hooks/useTheme"; import { buildFileDiffContentVersion, - buildFileDiffIdentityKey, + buildFileDiffIdentityKeys, getDiffCollapseIconClassName, getDiffLineStat, getRenderablePatch, @@ -416,15 +416,14 @@ export default function DiffPanel({ }), ); }, [renderablePatch]); - const renderableFileEntries = useMemo( - () => - renderableFiles.map((fileDiff) => ({ - fileDiff, - fileKey: buildFileDiffIdentityKey(fileDiff), - fileVersion: buildFileDiffContentVersion(fileDiff), - })), - [renderableFiles], - ); + const renderableFileEntries = useMemo(() => { + const fileKeys = buildFileDiffIdentityKeys(renderableFiles); + return renderableFiles.map((fileDiff, index) => ({ + fileDiff, + fileKey: fileKeys[index]!, + fileVersion: buildFileDiffContentVersion(fileDiff), + })); + }, [renderableFiles]); const codeViewFiles = useMemo( () => renderableFileEntries.map(({ fileDiff, fileKey, fileVersion }) => { diff --git a/apps/web/src/lib/diffRendering.test.ts b/apps/web/src/lib/diffRendering.test.ts index 6ba83cc7a9a4..98a64e38ec35 100644 --- a/apps/web/src/lib/diffRendering.test.ts +++ b/apps/web/src/lib/diffRendering.test.ts @@ -2,6 +2,7 @@ import { describe, expect, it } from "vite-plus/test"; import { buildFileDiffContentVersion, buildFileDiffIdentityKey, + buildFileDiffIdentityKeys, buildFileDiffRenderKey, buildPatchCacheKey, getDiffLineStat, @@ -148,6 +149,25 @@ describe("diff file reconciliation", () => { buildFileDiffContentVersion(beforeChanged), ); }); + + it("disambiguates repeated file identities without dropping either diff", () => { + const filePatch = [ + "diff --git a/scripts/push-release.sh b/scripts/push-release.sh", + "--- a/scripts/push-release.sh", + "+++ b/scripts/push-release.sh", + "@@ -1 +1 @@", + "-before", + "+after", + ].join("\n"); + const parsed = getRenderablePatch(`${filePatch}\n${filePatch}`); + expect(parsed?.kind).toBe("files"); + if (parsed?.kind !== "files") return; + + const keys = buildFileDiffIdentityKeys(parsed.files); + expect(parsed.files).toHaveLength(2); + expect(new Set(keys).size).toBe(2); + expect(keys[0]).toBe(buildFileDiffIdentityKey(parsed.files[0]!)); + }); }); describe("getDiffLineStat", () => { diff --git a/apps/web/src/lib/diffRendering.ts b/apps/web/src/lib/diffRendering.ts index 90c456aa9da1..0c21f627896d 100644 --- a/apps/web/src/lib/diffRendering.ts +++ b/apps/web/src/lib/diffRendering.ts @@ -167,6 +167,16 @@ export function buildFileDiffIdentityKey(fileDiff: FileDiffMetadata): string { return `${resolveFileDiffPreviousPath(fileDiff)}\u0000${resolveFileDiffPath(fileDiff)}`; } +export function buildFileDiffIdentityKeys(files: ReadonlyArray): string[] { + const occurrences = new Map(); + return files.map((file) => { + const identity = buildFileDiffIdentityKey(file); + const occurrence = occurrences.get(identity) ?? 0; + occurrences.set(identity, occurrence + 1); + return occurrence === 0 ? identity : `${identity}\u0000${occurrence}`; + }); +} + export function buildFileDiffRenderKey(fileDiff: FileDiffMetadata): string { const cacheKey = fileDiff.cacheKey; if (!cacheKey) return `${fileDiff.prevName ?? "none"}:${fileDiff.name}`; From 4ede6cfc04bc9d0d91b787d740b9a5f158b23f2b Mon Sep 17 00:00:00 2001 From: Frexxty Date: Sat, 12 Sep 2026 22:29:15 +0300 Subject: [PATCH 2/2] fix(web): preserve duplicate diff interactions --- apps/web/src/components/DiffPanel.tsx | 19 +++++++++---------- .../components/diffs/AnnotatableCodeView.tsx | 4 ++-- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/apps/web/src/components/DiffPanel.tsx b/apps/web/src/components/DiffPanel.tsx index 095f66d11a44..73a4e36c1f43 100644 --- a/apps/web/src/components/DiffPanel.tsx +++ b/apps/web/src/components/DiffPanel.tsx @@ -955,14 +955,10 @@ export default function DiffPanel({ (node): node is HTMLElement => node instanceof HTMLElement && node.hasAttribute("data-diffs-header"), ); - const headerFilePath = header - ?.querySelector("[data-title]") - ?.textContent?.trim(); - if (!headerFilePath) return; - const file = codeViewFiles.find( - (candidate) => candidate.filePath === headerFilePath, - ); - if (file) toggleDiffFileCollapsed(file.fileKey); + const fileKey = + header?.querySelector("[data-diff-file-key]")?.dataset + .diffFileKey; + if (fileKey) toggleDiffFileCollapsed(fileKey); }} > ( - + renderHeaderFilenameSuffix={(fileDiff, fileKey) => ( + <> + + + )} renderHeaderPrefix={(fileDiff, fileKey, collapsed) => { const filePath = resolveFileDiffPath(fileDiff); diff --git a/apps/web/src/components/diffs/AnnotatableCodeView.tsx b/apps/web/src/components/diffs/AnnotatableCodeView.tsx index b8ace2340557..3ead8a454bf6 100644 --- a/apps/web/src/components/diffs/AnnotatableCodeView.tsx +++ b/apps/web/src/components/diffs/AnnotatableCodeView.tsx @@ -86,7 +86,7 @@ interface AnnotatableCodeViewProps { options: StyledDiffCodeViewOptions; viewerRef?: Ref; className?: string; - renderHeaderFilenameSuffix: (fileDiff: FileDiffMetadata) => ReactNode; + renderHeaderFilenameSuffix: (fileDiff: FileDiffMetadata, fileKey: string) => ReactNode; renderHeaderPrefix: ( fileDiff: FileDiffMetadata, fileKey: string, @@ -255,7 +255,7 @@ export function AnnotatableCodeView({ onGutterUtilityClick: beginComment, }} renderHeaderFilenameSuffix={(item) => - item.type === "diff" ? renderHeaderFilenameSuffix(item.fileDiff) : null + item.type === "diff" ? renderHeaderFilenameSuffix(item.fileDiff, item.id) : null } renderHeaderPrefix={(item) => item.type === "diff"