diff --git a/apps/web/components/diff/use-expandable-diff.ts b/apps/web/components/diff/use-expandable-diff.ts index 6365a15d06..84567952ea 100644 --- a/apps/web/components/diff/use-expandable-diff.ts +++ b/apps/web/components/diff/use-expandable-diff.ts @@ -114,11 +114,14 @@ export function useExpandableDiff({ const [error, setError] = useState(null); // Reset cached content when inputs change so stale data is never rendered. + // Including fileDiffMetadata ensures expansion content is invalidated when + // the diff changes (e.g., file modified while Diff panel is open), because + // @pierre/diffs uses oldLines/newLines for rendering when present. useEffect(() => { requestVersionRef.current += 1; setLoadedContent(null); setError(null); - }, [sessionId, filePath, baseRef]); + }, [sessionId, filePath, baseRef, fileDiffMetadata]); const loadContent = useCallback(async () => { if (!sessionId || !enableExpansion || loadedContent || isLoading) return; diff --git a/apps/web/e2e/tests/git/diff-update.spec.ts b/apps/web/e2e/tests/git/diff-update.spec.ts index 1e32508b62..488035d0df 100644 --- a/apps/web/e2e/tests/git/diff-update.spec.ts +++ b/apps/web/e2e/tests/git/diff-update.spec.ts @@ -158,6 +158,54 @@ test.describe("Diff update on file change", () => { timeout: 15_000, }); }); + + test("diff panel auto-updates without re-opening when file changes", async ({ + testPage, + apiClient, + seedData, + }) => { + // This test verifies the Diff [file] panel reactively updates when the + // underlying file changes, WITHOUT the user re-clicking the file. + const { session } = await seedDiffUpdateTask(testPage, apiClient, seedData); + await openChangesTab(testPage); + await openFileDiff(testPage, "diff_update_test.txt"); + + // Verify initial diff content + const diffsContainer = getDiffsContainer(testPage); + await expect(diffsContainer).toBeVisible({ timeout: 15_000 }); + await expect(diffsContainer.getByText("FIRST_MODIFICATION", { exact: true })).toBeVisible({ + timeout: 15_000, + }); + + // Switch to chat, trigger the second modification + await session.clickSessionChatTab(); + await session.sendMessage("/e2e:diff-update-modify"); + await expect( + session.chat.getByText("diff-update-modify complete", { exact: false }), + ).toBeVisible({ timeout: 45_000 }); + + // DO NOT re-open the file diff via Changes tab. Instead, click the diff + // panel tab directly — the panel is still mounted, just not the active tab. + const diffTab = testPage.locator(".dv-default-tab", { hasText: "diff_update_test.txt" }); + await expect(diffTab).toBeVisible({ timeout: 10_000 }); + await diffTab.click(); + + // The diff should auto-update with the new content (no re-click needed). + const updatedDiffsContainer = getDiffsContainer(testPage); + await expect(updatedDiffsContainer).toBeVisible({ timeout: 15_000 }); + await expect( + updatedDiffsContainer.getByText("SECOND_MODIFICATION", { exact: true }), + ).toBeVisible({ timeout: 30_000 }); + + // Verify old content is gone + await expect( + updatedDiffsContainer.getByText("FIRST_MODIFICATION", { exact: true }), + ).toHaveCount(0); + + await expect(updatedDiffsContainer.getByText("ALSO_CHANGED", { exact: true })).toBeVisible({ + timeout: 15_000, + }); + }); }); test.describe("Untracked file diff update", () => {