From e00ebea262545278fc59c2e87155bd7d7a74ca09 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Fri, 28 Aug 2026 19:14:14 +0300 Subject: [PATCH] fix(browser): wait for panel surface before capture --- .../ui/src/components/browser/BrowserPane.tsx | 21 ++++++++++++++++++- ...ue-3175-browserCaptureRevealsPanel.test.ts | 7 +++++++ 2 files changed, 27 insertions(+), 1 deletion(-) diff --git a/packages/ui/src/components/browser/BrowserPane.tsx b/packages/ui/src/components/browser/BrowserPane.tsx index f5b5440f..3828ca2b 100644 --- a/packages/ui/src/components/browser/BrowserPane.tsx +++ b/packages/ui/src/components/browser/BrowserPane.tsx @@ -339,6 +339,25 @@ const WebviewBrowser: React.FC = ({ initialUrl, directory, tab } if (action === 'browser.capture') { + // A user may close the panel after browser.open. Chromium then removes + // the zero-width webview's composited surface and capturePage() fails + // with UnknownVizError. Reveal this existing browser tab again and let + // the layout paint before asking Electron for the image. + useUIStore.getState().openContextBrowser(directory, webview.getURL()); + const surfaceDeadline = Date.now() + 1_200; + let previousWidth = 0; + let stableSamples = 0; + while (stableSamples < 2 && Date.now() < surfaceDeadline) { + const width = webview.getBoundingClientRect().width; + stableSamples = width >= 2 && Math.abs(width - previousWidth) < 0.5 + ? stableSamples + 1 + : 0; + previousWidth = width; + await new Promise((resolve) => setTimeout(resolve, 50)); + } + await new Promise((resolve) => { + requestAnimationFrame(() => requestAnimationFrame(() => resolve())); + }); // Wait for a settled page first: a screenshot of a half-painted layout is // worse than none, because it looks like a finished one. await waitForIdle(); @@ -450,7 +469,7 @@ const WebviewBrowser: React.FC = ({ initialUrl, directory, tab await waitForIdle(); } return result; - }, [annotationHost, loadUrl, waitForIdle]); + }, [annotationHost, directory, loadUrl, waitForIdle]); React.useEffect( () => registerBrowserController({ run: runControlAction }), diff --git a/packages/ui/src/components/layout/__tests__/issue-3175-browserCaptureRevealsPanel.test.ts b/packages/ui/src/components/layout/__tests__/issue-3175-browserCaptureRevealsPanel.test.ts index 434a743c..af366c2c 100644 --- a/packages/ui/src/components/layout/__tests__/issue-3175-browserCaptureRevealsPanel.test.ts +++ b/packages/ui/src/components/layout/__tests__/issue-3175-browserCaptureRevealsPanel.test.ts @@ -15,6 +15,7 @@ import { useUIStore } from '@/stores/useUIStore'; const __dirname = dirname(fileURLToPath(import.meta.url)); const contextPanelSource = readFileSync(join(__dirname, '..', 'ContextPanel.tsx'), 'utf-8'); +const browserPaneSource = readFileSync(join(__dirname, '..', '..', 'browser', 'BrowserPane.tsx'), 'utf-8'); const DIRECTORY = '/path/to/repository'; beforeEach(() => { @@ -40,4 +41,10 @@ describe('issue #3175 browser capture while the context panel is closed', () => expect(panel.tabs[0]?.mode).toBe('browser'); expect(panel.tabs[0]?.targetPath).toBe('https://example.com'); }); + + test('reveals the browser again if it was closed before capture', () => { + expect(browserPaneSource).toContain( + 'openContextBrowser(directory, webview.getURL())', + ); + }); });