From 3f3d942aab2eacb20a4865963286e2701c01eb64 Mon Sep 17 00:00:00 2001 From: Bohdan Triapitsyn Date: Fri, 28 Aug 2026 14:06:43 +0300 Subject: [PATCH] fix(browser): reveal panel for agent captures Fixes #3175 --- .../ui/src/components/layout/ContextPanel.tsx | 9 ++-- ...ue-3175-browserCaptureRevealsPanel.test.ts | 43 +++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) create mode 100644 packages/ui/src/components/layout/__tests__/issue-3175-browserCaptureRevealsPanel.test.ts diff --git a/packages/ui/src/components/layout/ContextPanel.tsx b/packages/ui/src/components/layout/ContextPanel.tsx index cd22371e..7e05d80a 100644 --- a/packages/ui/src/components/layout/ContextPanel.tsx +++ b/packages/ui/src/components/layout/ContextPanel.tsx @@ -452,13 +452,12 @@ export const ContextPanel: React.FC = () => { // Lets an agent's browser.open create the tab it needs when none is open yet. // Registered from the panel because opening a tab is panel state, not - // something the browser view itself can do before it exists. Background on - // purpose: an agent working a page must not pop the panel open (or steal - // the active surface) under the user — the tab mounts invisibly, and the - // rail is where the user opens it when curious. + // something the browser view itself can do before it exists. Reveal the + // panel so Electron gives the webview a composited surface; capturePage() + // cannot capture the zero-width webview inside a closed panel. React.useEffect(() => { if (!effectiveDirectory) return; - return registerBrowserOpener((url) => openContextBrowser(effectiveDirectory, url, { reveal: false })); + return registerBrowserOpener((url) => openContextBrowser(effectiveDirectory, url)); }, [effectiveDirectory, openContextBrowser]); const reorderContextPanelTabs = useUIStore((state) => state.reorderContextPanelTabs); const setSelectedFilePath = useFilesViewTabsStore((state) => state.setSelectedPath); 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 new file mode 100644 index 00000000..434a743c --- /dev/null +++ b/packages/ui/src/components/layout/__tests__/issue-3175-browserCaptureRevealsPanel.test.ts @@ -0,0 +1,43 @@ +/** + * Regression coverage for https://github.com/openchamber/openchamber/issues/3175 + * + * A full ContextPanel mount is not available in bun test because its import + * graph includes a Vite worker URL. This test follows the source-level guard + * pattern used by the neighboring ContextPanel regression tests and exercises + * the real store behavior that the registered opener delegates to. + */ +import { beforeEach, describe, expect, test } from 'bun:test'; +import { readFileSync } from 'node:fs'; +import { dirname, join } from 'node:path'; +import { fileURLToPath } from 'node:url'; + +import { useUIStore } from '@/stores/useUIStore'; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const contextPanelSource = readFileSync(join(__dirname, '..', 'ContextPanel.tsx'), 'utf-8'); +const DIRECTORY = '/path/to/repository'; + +beforeEach(() => { + useUIStore.setState({ contextPanelByDirectory: {}, contextRailOrder: [] }); +}); + +describe('issue #3175 browser capture while the context panel is closed', () => { + test('registers the agent browser opener without suppressing panel reveal', () => { + expect(contextPanelSource).toContain( + 'registerBrowserOpener((url) => openContextBrowser(effectiveDirectory, url))', + ); + expect(contextPanelSource).not.toContain( + 'openContextBrowser(effectiveDirectory, url, { reveal: false })', + ); + }); + + test('opening the agent browser gives its webview a visible panel surface', () => { + useUIStore.getState().openContextBrowser(DIRECTORY, 'https://example.com'); + + const panel = useUIStore.getState().contextPanelByDirectory[DIRECTORY]; + expect(panel.isOpen).toBe(true); + expect(panel.tabs).toHaveLength(1); + expect(panel.tabs[0]?.mode).toBe('browser'); + expect(panel.tabs[0]?.targetPath).toBe('https://example.com'); + }); +});