From 3e4150684518a82e9fede5ce35e94cd30594d082 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sat, 7 Nov 2026 23:16:53 +0000 Subject: [PATCH 1/2] fix(web): Stop recording still works when the preview page fails to load The preview camera button doubles as "Stop recording", but it was disabled whenever the page failed to load or the desktop overlay was missing, so an active recording could not be stopped; the button now stays enabled while a recording is running, since stopping needs neither a loaded page nor the overlay. Co-Authored-By: Claude Opus 5.5 --- .../preview/PreviewView.browser.test.tsx | 27 ++++++++++++++++++- .../src/components/preview/PreviewView.tsx | 2 +- 2 files changed, 27 insertions(+), 2 deletions(-) diff --git a/apps/web/src/components/preview/PreviewView.browser.test.tsx b/apps/web/src/components/preview/PreviewView.browser.test.tsx index a73f88be9fdf5..88e54c615a607 100644 --- a/apps/web/src/components/preview/PreviewView.browser.test.tsx +++ b/apps/web/src/components/preview/PreviewView.browser.test.tsx @@ -31,11 +31,13 @@ const mocks = vi.hoisted(() => ({ pictureInPicture: false, showEmptyState: false, loading: false, + loadFailed: false, nativeServerTab: false, serverEpoch: null as string | null, servers: [] as PreviewableServer[], recordingTabIds: new Set(), recordingRuntimeTabId: null as string | null, + stopBrowserRecording: vi.fn(async (_runtimeTabId: string) => null), recordVisitForThread: vi.fn(), })); @@ -117,7 +119,15 @@ vi.mock(import("~/previewStateStore"), async (original) => ({ threadId: "thread-1", tabId: "tab-1", runtime: mocks.nativeServerTab ? "server" : "desktop", - navStatus: { _tag: "Success", url: "http://example.com/", title: "Example" }, + navStatus: mocks.loadFailed + ? { + _tag: "LoadFailed", + url: "http://example.com/", + title: "", + code: -102, + description: "ERR_CONNECTION_REFUSED", + } + : { _tag: "Success", url: "http://example.com/", title: "Example" }, canGoBack: false, canGoForward: false, updatedAt: "2026-07-13T00:00:00.000Z", @@ -135,6 +145,7 @@ vi.mock(import("~/browser/browserRecording"), async (original) => ({ ...(await original()), findActiveBrowserRecordingRuntimeTabId: () => mocks.recordingRuntimeTabId, useActiveBrowserRecordingTabIds: () => mocks.recordingTabIds, + stopBrowserRecording: mocks.stopBrowserRecording, })); vi.mock(import("./useDiscoveredLocalServers"), async (original) => ({ ...(await original()), @@ -202,6 +213,7 @@ function setup() { mocks.pictureInPicture = false; mocks.showEmptyState = false; mocks.loading = false; + mocks.loadFailed = false; mocks.nativeServerTab = false; mocks.serverEpoch = null; mocks.servers = []; @@ -330,6 +342,19 @@ describe("PreviewView", () => { expect(listeners.size).toBe(0); }); + it("stops an active recording after the page fails to load", async () => { + setup(); + mocks.loadFailed = true; + mocks.recordingTabIds = new Set([TEST_RUNTIME_TAB_ID]); + await using _screen = await render(view()); + + const stop = page.getByRole("button", { name: "Stop recording" }); + await expect.element(stop).toBeEnabled(); + await userEvent.click(stop); + + expect(mocks.stopBrowserRecording).toHaveBeenCalledWith(TEST_RUNTIME_TAB_ID); + }); + it("focuses the address bar each time the focus-url shortcut fires", async () => { setup(); await using _screen = await render(view()); diff --git a/apps/web/src/components/preview/PreviewView.tsx b/apps/web/src/components/preview/PreviewView.tsx index 253a4fc14e568..6718915466615 100644 --- a/apps/web/src/components/preview/PreviewView.tsx +++ b/apps/web/src/components/preview/PreviewView.tsx @@ -918,7 +918,7 @@ export function PreviewView({ onSubmit={(next) => void handleSubmitUrl(next)} onOpenInBrowser={tabId ? handleOpenInBrowser : undefined} onCapture={previewBridge && tabId && !isServerTab ? handleCapture : undefined} - captureDisabled={!desktopOverlay || isUnreachable} + captureDisabled={recordingRuntimeTabId === null && (!desktopOverlay || isUnreachable)} recording={recordingRuntimeTabId !== null} onPictureInPicture={ tabId && (isServerTab || previewBridge) ? handlePictureInPicture : undefined From 8abb11fc511e932adc62206a76cf37071337a6c1 Mon Sep 17 00:00:00 2001 From: Victor Vogel Date: Sat, 14 Nov 2026 18:57:48 +0000 Subject: [PATCH 2/2] test(web): the failed-page recording case fails the desktop overlay's load too The preview now reads the desktop overlay's status first, so the case only reaches a failed page when the overlay reports it. --- .../preview/PreviewView.browser.test.tsx | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/apps/web/src/components/preview/PreviewView.browser.test.tsx b/apps/web/src/components/preview/PreviewView.browser.test.tsx index 4d8c558860705..f3c86edcddd1b 100644 --- a/apps/web/src/components/preview/PreviewView.browser.test.tsx +++ b/apps/web/src/components/preview/PreviewView.browser.test.tsx @@ -53,6 +53,13 @@ const STUB_BROWSER_DEFAULTS = { profiles: BUILT_IN_BROWSER_PROFILES, profileId: DEFAULT_BROWSER_PROFILE_ID, }; +const LOAD_FAILED = { + _tag: "LoadFailed", + url: "http://example.com/", + title: "", + code: -102, + description: "ERR_CONNECTION_REFUSED", +} as const; // The desktop bridge only exists inside Electron; the view reads it once at import. vi.mock(import("./previewBridge"), async (original) => ({ @@ -107,9 +114,11 @@ vi.mock(import("~/previewStateStore"), async (original) => ({ desktopByTabId: { "tab-1": { hasWebContents: true, - navStatus: mocks.idleDesktopOverlay - ? { _tag: "Idle" } - : { _tag: "Success", url: "http://example.com/", title: "Example" }, + navStatus: mocks.loadFailed + ? LOAD_FAILED + : mocks.idleDesktopOverlay + ? { _tag: "Idle" } + : { _tag: "Success", url: "http://example.com/", title: "Example" }, canGoBack: false, canGoForward: false, loading: mocks.loading, @@ -142,13 +151,7 @@ vi.mock(import("~/previewStateStore"), async (original) => ({ tabId: "tab-1", runtime: mocks.nativeServerTab || mocks.streamedServerTab ? "server" : "desktop", navStatus: mocks.loadFailed - ? { - _tag: "LoadFailed", - url: "http://example.com/", - title: "", - code: -102, - description: "ERR_CONNECTION_REFUSED", - } + ? LOAD_FAILED : { _tag: "Success", url: "http://example.com/", title: "Example" }, canGoBack: false, canGoForward: false,