From 6dfef2b2df1c6f900bcd0570c1c811c5a7beb7c3 Mon Sep 17 00:00:00 2001 From: Jinjing <6427696+AmethystLiang@users.noreply.github.com> Date: Sun, 16 Aug 2026 14:39:00 -0700 Subject: [PATCH] Fix PR diff viewer cache isolation and list key collisions - Changed list key generation from string concatenation to JSON serialization to avoid collisions with actual content keys - Added host-aware scoping to diff view caches so local and remote execution don't share entries - Optimized virtualizer keys to use lightweight revision counter instead of full serialized signature --- .../checks/details-list-keys.ts | 3 ++- .../files/combined-diff-viewer.tsx | 20 ++++++++++++++----- src/renderer/src/i18n/locales/es.json | 2 +- 3 files changed, 18 insertions(+), 7 deletions(-) diff --git a/src/renderer/src/components/pull-request-page/checks/details-list-keys.ts b/src/renderer/src/components/pull-request-page/checks/details-list-keys.ts index 068d6a9c077..66f10c98689 100644 --- a/src/renderer/src/components/pull-request-page/checks/details-list-keys.ts +++ b/src/renderer/src/components/pull-request-page/checks/details-list-keys.ts @@ -13,6 +13,7 @@ export function assignUniqueListKeys( const base = contentKey(item) const seen = occurrences.get(base) ?? 0 occurrences.set(base, seen + 1) - return { item, key: seen === 0 ? base : `${base}#${seen}` } + // JSON-encoded pair, not `base#seen`: a separator suffix would collide with a real content key. + return { item, key: JSON.stringify([base, seen]) } }) } diff --git a/src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx b/src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx index 7e7c2920136..8a50aabcf26 100644 --- a/src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx +++ b/src/renderer/src/components/pull-request-page/files/combined-diff-viewer.tsx @@ -27,6 +27,7 @@ import { type PRFilesCombinedDiffViewerProps } from '@/components/github/pr-file-diff-mapping' import { githubRepoIdentityKey } from '../../../../../shared/github/repository-identity-key' +import { LOCAL_EXECUTION_HOST_ID } from '../../../../../shared/execution-host' import { prFilesDiffScrollTopCache, prFilesDiffViewStateCache } from '../cache/files-diff-view' import { PRFilesDiffToolbar } from './toolbar' import { addPullRequestLineComment } from './line-comment' @@ -80,27 +81,35 @@ export function PRFilesCombinedDiffViewer({ () => buildInlineReviewComments(comments, repoId, prNumber), [comments, prNumber, repoId] ) + // Why: section contents are fetched through sourceContext, so local and remote hosts must not share cache entries. + const sourceScope = sourceContext?.hostId ?? LOCAL_EXECUTION_HOST_ID const entrySignature = useMemo( () => JSON.stringify({ repoId, prNumber, prRepo: prRepo ? githubRepoIdentityKey(prRepo) : null, + sourceScope, headSha: headSha ?? null, baseSha: baseSha ?? null, files: diffEntrySignature }), - [baseSha, diffEntrySignature, headSha, prNumber, prRepo, repoId] + [baseSha, diffEntrySignature, headSha, prNumber, prRepo, repoId, sourceScope] ) const viewStateKey = useMemo( - () => [repoId || repoPath, prNumber, prRepo ? githubRepoIdentityKey(prRepo) : ''].join('\0'), - [prNumber, prRepo, repoId, repoPath] + () => + [repoId || repoPath, prNumber, prRepo ? githubRepoIdentityKey(prRepo) : '', sourceScope].join( + '\0' + ), + [prNumber, prRepo, repoId, repoPath, sourceScope] ) const [sections, setSections] = useState([]) const [sideBySide, setSideBySide] = useState(false) const [fileTreeCollapsed, setFileTreeCollapsed] = useState(false) const [sectionHeights, setSectionHeights] = useState>({}) const [activeTreeSectionKey, setActiveTreeSectionKey] = useState(null) + // Why: short virtualizer-key token; entrySignature serializes every file and would be copied into every item key. + const [entryRevision, setEntryRevision] = useState(0) const scrollContainerRef = useRef(null) const pendingRestoreScrollTopRef = useRef(null) const loadedIndicesRef = useRef>(new Set()) @@ -117,6 +126,7 @@ export function PRFilesCombinedDiffViewer({ useEffect(() => { // Why: bump generation so stale async diff loads from the previous view can't patch the restored sections. generationRef.current += 1 + setEntryRevision((revision) => revision + 1) const cached = prFilesDiffViewStateCache.get(viewStateKey) if (cached && cached.entrySignature === entrySignature) { const restoredSections = cached.sections @@ -210,8 +220,8 @@ export function PRFilesCombinedDiffViewer({ getItemKey: (index) => { const section = sections[index] return section - ? `${section.key}:${section.collapsed ? 'collapsed' : 'expanded'}:${entrySignature}` - : `${index}:${entrySignature}` + ? `${section.key}:${section.collapsed ? 'collapsed' : 'expanded'}:${entryRevision}` + : `${index}:${entryRevision}` } }) diff --git a/src/renderer/src/i18n/locales/es.json b/src/renderer/src/i18n/locales/es.json index 5c825005fb1..c3c6768dbd9 100644 --- a/src/renderer/src/i18n/locales/es.json +++ b/src/renderer/src/i18n/locales/es.json @@ -1538,7 +1538,7 @@ "dd5d9a4f17": "actualizado {{value0}}", "71a3c0f9d2": "Iniciar espacio de trabajo", "filesUnavailable": "Couldn't load changed files.", - "filesRetry": "Retry", + "filesRetry": "Reintentar", "77482513f8": "Cerrar", "2f5195c6a0": "Reabrir", "4b8ae7303f": "No se pudo cargar la diferencia.",