From 3727afe7dff2249d4408a0e7cb84b913b333ba44 Mon Sep 17 00:00:00 2001 From: badcuban <108198679+badcuban@users.noreply.github.com> Date: Thu, 3 Sep 2026 01:57:45 -0400 Subject: [PATCH 1/2] fix(web): hide the browser preview button on the web build The header showed the globe button in any browser, including phones, but the preview is a Chromium webview that only the desktop app can host. Tapping it opened a panel that could only say so. The button and panel now require the desktop app as well as a project thread. The agent's automation mount stays on the web build as before: it reads the bridge at effect time and refuses to connect without one. --- apps/web/src/components/ChatView.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/ChatView.tsx b/apps/web/src/components/ChatView.tsx index d4570079..85edfe3d 100644 --- a/apps/web/src/components/ChatView.tsx +++ b/apps/web/src/components/ChatView.tsx @@ -2700,8 +2700,10 @@ export default function ChatView(props: ChatViewProps) { const splitChatFraction = useBrowserPanelStore((store) => store.splitChatFraction); const setSplitChatFraction = useBrowserPanelStore((store) => store.setSplitChatFraction); const browserExpanded = useBrowserPanelStore((store) => store.expanded); + // The preview is a Chromium webview, which only the desktop app can host: a + // web build has no browser to toggle, so it gets no button and no panel. // General chats have no project and therefore no dev server to look at. - const browserAvailable = !isGeneralChatThread; + const browserAvailable = isElectron && !isGeneralChatThread; const browserOpen = browserAvailable && browserPanelState.open; const handleToggleBrowser = useCallback(() => { @@ -6970,7 +6972,10 @@ export default function ChatView(props: ChatViewProps) { {/* The agent's end of the browser is mounted with the thread, not with the panel: a closed panel is a closed panel, not the absence of a browser, and a request for the browser opens it. */} - {browserAvailable && routeThreadRef !== null ? ( + {!isGeneralChatThread && routeThreadRef !== null ? ( + // Mounted on the web build too: the host reads the desktop bridge at + // effect time and refuses to connect without one, so it costs nothing + // there and keeps working if the preload attaches late. // The project is passed alongside the thread because a local draft // thread has no shell to look it up from, and browser approvals are // recorded per project. From 402f908d7f9e265165fcdf1722af8c34b5d7f2fa Mon Sep 17 00:00:00 2001 From: badcuban <108198679+badcuban@users.noreply.github.com> Date: Sat, 5 Sep 2026 01:15:42 -0400 Subject: [PATCH 2/2] test(web): wait for Claude settings to open before clicking --- .../settings/SettingsPanels.browser.tsx | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/apps/web/src/components/settings/SettingsPanels.browser.tsx b/apps/web/src/components/settings/SettingsPanels.browser.tsx index 763502e0..b2277199 100644 --- a/apps/web/src/components/settings/SettingsPanels.browser.tsx +++ b/apps/web/src/components/settings/SettingsPanels.browser.tsx @@ -1884,9 +1884,18 @@ describe("GeneralSettingsPanel observability", () => { await expect.element(page.getByText(/Credential configured ยท Claude Max/)).toBeInTheDocument(); await page.getByLabelText("Toggle Claude details").click(); - await expect.element(page.getByText("Chat configured")).toBeInTheDocument(); - await expect.element(page.getByText("Usage verified")).toBeInTheDocument(); - await page.getByText("Advanced: headless chat token").click(); + await expect.element(page.getByText("Chat configured")).toBeVisible(); + await expect.element(page.getByText("Usage verified")).toBeVisible(); + const advancedTokenLabel = page.getByText("Advanced: headless chat token"); + await expect.element(advancedTokenLabel).toBeVisible(); + const advancedTokenToggle = advancedTokenLabel.element().closest("summary"); + // The summary can have stable bounds while its parent is still revealing it. + // Wait for that height transition before sending a pointer click. + const detailsPanel = advancedTokenToggle?.closest('[data-slot="collapsible-panel"]'); + if (!advancedTokenToggle || !detailsPanel) + throw new Error("Claude details panel did not render"); + await Promise.all(detailsPanel.getAnimations().map((animation) => animation.finished)); + await page.elementLocator(advancedTokenToggle).click(); await expect.element(page.getByText(/Optional for remote or headless chat/)).toBeVisible(); });