From 411cef0008fa8e1e5251a8aae2cc6a215f700a9c Mon Sep 17 00:00:00 2001 From: Alex Southwell Date: Tue, 6 Oct 2026 20:22:39 +1100 Subject: [PATCH] fix(desktop): prevent browser screenshot filename collisions (#14784) --- apps/desktop/src/preview/Manager.test.ts | 34 ++++++++++++++++++++++++ apps/desktop/src/preview/Manager.ts | 2 +- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/preview/Manager.test.ts b/apps/desktop/src/preview/Manager.test.ts index 5211d59804f163ef0bd651168fdce0dd94f58db9..bf14a1981f13c72e89881f9b283008760e735d24 100644 --- a/apps/desktop/src/preview/Manager.test.ts +++ b/apps/desktop/src/preview/Manager.test.ts @@ -2367,6 +2367,40 @@ describe("PreviewManager", () => { ), ); + effectIt.effect("preserves both screenshots of the same site in the same millisecond", () => + withManager((manager) => + Effect.gen(function* () { + const firstPng = Buffer.from("first-preview-png"); + const secondPng = Buffer.from("second-preview-png"); + const pngImage = (png: Buffer) => ({ + toPNG: () => png, + toJPEG: () => png, + getSize: () => ({ width: 1280, height: 720 }), + }); + const capturePage = vi + .fn<() => Promise>>() + .mockResolvedValueOnce(pngImage(firstPng)) + .mockResolvedValueOnce(pngImage(secondPng)); + fromId.mockReturnValue(makeTestPreviewWebContents(capturePage)); + yield* manager.createTab("tab_1"); + yield* manager.registerWebview("tab_1", 42); + // it.effect keeps TestClock fixed until explicitly advanced. + const first = yield* manager.captureScreenshot("tab_1"); + const second = yield* manager.captureScreenshot("tab_1"); + + expect(first.createdAt).toBe(second.createdAt); + expect(first.id).not.toBe(second.id); + expect(first.path).not.toBe(second.path); + expect(first.id).toMatch(/^browser-screenshot-example-com-[a-z0-9]+-[0-9a-f]{8}$/); + expect(second.id).toMatch(/^browser-screenshot-example-com-[a-z0-9]+-[0-9a-f]{8}$/); + expect(writeFile.mock.calls).toEqual([ + [first.path, firstPng], + [second.path, secondPng], + ]); + }), + ), + ); + effectIt.effect("keeps every recorded guest unthrottled until its frame capture stops", () => withManager((manager) => Effect.gen(function* () { diff --git a/apps/desktop/src/preview/Manager.ts b/apps/desktop/src/preview/Manager.ts index bc34daee4adfa0743afa550bee9ed0cdfee2aea3..10bb0d299d3024295f792f4be5b348bb3c84fe41 100644 --- a/apps/desktop/src/preview/Manager.ts +++ b/apps/desktop/src/preview/Manager.ts @@ -2974,7 +2974,7 @@ const makeNativeOperations = Effect.fn("PreviewManager.makeOperations")(function wc, ), ]); - const id = `browser-screenshot-${artifactSiteSlug(wc.getURL())}-${millis.toString(36)}`; + const id = `browser-screenshot-${artifactSiteSlug(wc.getURL())}-${millis.toString(36)}-${NodeCrypto.randomUUID().slice(0, 8)}`; const artifactPath = path.join(resolvedArtifactDirectory, `${id}.png`); const data = image.toPNG(); yield* fileSystem.makeDirectory(resolvedArtifactDirectory, { recursive: true }).pipe( -- 2.54.0