diff --git a/apps/web/src/components/diffs/codeViewContextExpansion.browser.test.ts b/apps/web/src/components/diffs/codeViewContextExpansion.browser.test.ts new file mode 100644 index 0000000000000..0abdbd9322dc7 --- /dev/null +++ b/apps/web/src/components/diffs/codeViewContextExpansion.browser.test.ts @@ -0,0 +1,59 @@ +import { CodeView, DEFAULT_THEMES, parsePatchFiles, preloadHighlighter } from "@pierre/diffs"; +import { expect, it, vi } from "vite-plus/test"; + +const OLD_LINES = Array.from({ length: 60 }, (_, index) => `line ${index + 1}`); +const NEW_LINES = OLD_LINES.with(0, "changed 1").with(59, "changed 60"); +const PATCH = `diff --git a/notes.txt b/notes.txt +--- a/notes.txt ++++ b/notes.txt +@@ -1,4 +1,4 @@ +-line 1 ++changed 1 + line 2 + line 3 + line 4 +@@ -57,4 +57,4 @@ + line 57 + line 58 + line 59 +-line 60 ++changed 60 +`; + +function parsePartialDiff() { + return parsePatchFiles(PATCH, { cacheKeyPrefix: "notes" })[0]!.files[0]!; +} + +// A diff refresh re-parses an unchanged patch into a new object with the same cacheKey. +// Upstream keeps rendering the old object and then discards the file contents loaded for it. +it("expands hidden context after the host re-supplies an unchanged partial diff", async () => { + await preloadHighlighter({ themes: Object.values(DEFAULT_THEMES), langs: ["text"] }); + const root = document.createElement("div"); + root.style.cssText = "width: 800px; height: 600px; overflow: auto;"; + document.body.append(root); + const view = new CodeView({ + diffStyle: "unified", + loadDiffFiles: async () => ({ + oldFile: { name: "notes.txt", contents: `${OLD_LINES.join("\n")}\n` }, + newFile: { name: "notes.txt", contents: `${NEW_LINES.join("\n")}\n` }, + }), + }); + using _view = { + [Symbol.dispose]: () => { + view.cleanUp(); + root.remove(); + }, + }; + view.setup(root); + const shadowRoot = () => root.querySelector("diffs-container")?.shadowRoot; + const lines = () => + Array.from(shadowRoot()?.querySelectorAll("[data-line]") ?? [], (line) => line.textContent); + + view.setItems([{ id: "notes", type: "diff", fileDiff: parsePartialDiff(), version: 1 }]); + await vi.waitFor(() => expect(lines()).toContain("line 57")); + view.setItems([{ id: "notes", type: "diff", fileDiff: parsePartialDiff(), version: 2 }]); + + shadowRoot()!.querySelector("[data-expand-button]")!.click(); + + await vi.waitFor(() => expect(lines()).toContain("line 30")); +}); diff --git a/patches/@pierre__diffs@1.7.0-beta.3.patch b/patches/@pierre__diffs@1.7.0-beta.3.patch index 30ad06bd76e93..8ae33350d70d2 100644 --- a/patches/@pierre__diffs@1.7.0-beta.3.patch +++ b/patches/@pierre__diffs@1.7.0-beta.3.patch @@ -218,10 +218,20 @@ index 734ff9862cdfe65b8082599c6c221c4886436e01..f18798a3d56dd0749550b5aff62a06fb } catch (error) { if (disableErrorHandling) throw error; diff --git a/dist/components/VirtualizedFileDiff.js b/dist/components/VirtualizedFileDiff.js -index 984f049ff3694dd353fa6637b0f09fc7dce74992..8f659c06b70ba603f725cccdcaebe89c21b00f6b 100644 +index 984f049ff3694dd353fa6637b0f09fc7dce74992..4d6599905be0fe7b5b5c1a225c0b3ff8d810c72a 100644 --- a/dist/components/VirtualizedFileDiff.js +++ b/dist/components/VirtualizedFileDiff.js -@@ -626,7 +626,7 @@ var VirtualizedFileDiff = class extends FileDiff { +@@ -264,7 +264,8 @@ var VirtualizedFileDiff = class extends FileDiff { + const { pendingExpansions, pendingHydratedDiff, options: { collapsed = false } } = this; + if (pendingHydratedDiff != null) { + this.pendingHydratedDiff = void 0; +- if (pendingHydratedDiff.expectedDiff === fileDiff) { ++ // T3 patch: a host may re-supply an unchanged diff as a new object; it is still this diff. ++ if (areDiffTargetsEqual(pendingHydratedDiff.expectedDiff, fileDiff)) { + Object.assign(fileDiff, pendingHydratedDiff.nextDiff); + this.setHydratedState(pendingHydratedDiff.files); + this.installHydratedDiff(fileDiff); +@@ -626,7 +627,7 @@ var VirtualizedFileDiff = class extends FileDiff { rerender() { if (!this.enabled || this.getInputDiff() == null && this.additionFile == null && this.deletionFile == null) return; const workingDiff = this.getWorkingDiff(); @@ -230,7 +240,7 @@ index 984f049ff3694dd353fa6637b0f09fc7dce74992..8f659c06b70ba603f725cccdcaebe89c if (this.isSimpleMode()) this.pendingRender = void 0; this.forceRenderOverride = true; if (this.isSimpleMode() && this.requestRender()) return; -@@ -790,7 +790,7 @@ var VirtualizedFileDiff = class extends FileDiff { +@@ -790,7 +791,7 @@ var VirtualizedFileDiff = class extends FileDiff { const previousRenderedDiff = this.getRenderedDiff(); const { pendingRender } = this; const previousLayoutDiff = pendingRender?.diff ?? previousRenderedDiff; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 72442ea5f6440..efd4f84496318 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -361,7 +361,7 @@ patchedDependencies: '@ff-labs/fff-node@0.11.0': cc71fcec015f4d39bc96ae2e2bf6b0ca678124c722c66a7841efce9d34b5983c '@legendapp/list@3.7.2': 3c68607b8bb7a531873bcbe0b197d163ef1fdb4d4cf52a4ddf9d2b3804671658 '@muse-code/sdk@1.4.6': 7b495b607fc5d76b444c7c4d58821e839726a30e6f3c53564f0d59f2d5b42101 - '@pierre/diffs@1.7.0-beta.3': b927b5049250ce93163cd44b03d9d22d16b484a8aae74ae1664973c6aa8060e4 + '@pierre/diffs@1.7.0-beta.3': aea143f4a2e59744371219c4e7ae675ec739c9ba16f1732e2f11748b496ea05e '@react-native-ai/apple@0.12.0': 2d09870c2848d185cb05b53ed823a46e12dba519324d8dd8e584e28731990f9d '@react-native-menu/menu@2.0.0': 6872ba71b9847c5a0210b1b896923dadc3860faedfe28d9f062bcedfe786ef1d '@react-navigation/native-stack@7.21.0': 990d575444c4af2ce8218b3b7a8280fe244c03abbdb32b822a2e862f791dbf42 @@ -549,7 +549,7 @@ importers: version: 0.3.0 '@pierre/diffs': specifier: 1.7.0-beta.3 - version: 1.7.0-beta.3(patch_hash=b927b5049250ce93163cd44b03d9d22d16b484a8aae74ae1664973c6aa8060e4)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 1.7.0-beta.3(patch_hash=aea143f4a2e59744371219c4e7ae675ec739c9ba16f1732e2f11748b496ea05e)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@pierre/highlights': specifier: 'catalog:' version: 0.5.0 @@ -1061,7 +1061,7 @@ importers: version: 1.8.0 '@pierre/diffs': specifier: 1.7.0-beta.3 - version: 1.7.0-beta.3(patch_hash=b927b5049250ce93163cd44b03d9d22d16b484a8aae74ae1664973c6aa8060e4)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 1.7.0-beta.3(patch_hash=aea143f4a2e59744371219c4e7ae675ec739c9ba16f1732e2f11748b496ea05e)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@pierre/trees': specifier: 1.0.0-beta.6 version: 1.0.0-beta.6(@pierre/theme@2.0.0)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)(shiki@4.2.0) @@ -1480,7 +1480,7 @@ importers: dependencies: '@pierre/diffs': specifier: 1.7.0-beta.3 - version: 1.7.0-beta.3(patch_hash=b927b5049250ce93163cd44b03d9d22d16b484a8aae74ae1664973c6aa8060e4)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) + version: 1.7.0-beta.3(patch_hash=aea143f4a2e59744371219c4e7ae675ec739c9ba16f1732e2f11748b496ea05e)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0) '@t3tools/contracts': specifier: workspace:* version: link:../contracts @@ -15392,7 +15392,7 @@ snapshots: tslib: 2.8.1 webcrypto-core: 1.9.2 - '@pierre/diffs@1.7.0-beta.3(patch_hash=b927b5049250ce93163cd44b03d9d22d16b484a8aae74ae1664973c6aa8060e4)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': + '@pierre/diffs@1.7.0-beta.3(patch_hash=aea143f4a2e59744371219c4e7ae675ec739c9ba16f1732e2f11748b496ea05e)(@shikijs/themes@4.2.0)(react-dom@19.3.0(react@19.3.0))(react@19.3.0)': dependencies: '@pierre/highlights': 0.5.0 '@pierre/patches': 0.0.1