From 6aab02e6a52b5bc3ac89c37013d4c06891c22bfb Mon Sep 17 00:00:00 2001 From: Shakker Date: Tue, 11 Aug 2026 15:23:23 +0200 Subject: [PATCH] fix: align browser history tab metadata --- .../browser-panel-controller-input.test.ts | 52 +++++++++++++++++++ .../browser/browser-panel-controller.ts | 13 +++++ 2 files changed, 65 insertions(+) diff --git a/ui/src/components/browser/browser-panel-controller-input.test.ts b/ui/src/components/browser/browser-panel-controller-input.test.ts index 9b818e33f5d1..a7a94c842654 100644 --- a/ui/src/components/browser/browser-panel-controller-input.test.ts +++ b/ui/src/components/browser/browser-panel-controller-input.test.ts @@ -656,6 +656,58 @@ describe("BrowserPanelController capture and input ownership", () => { expect(inspections[0]?.[1]).toMatchObject({ body: { targetId: "tab-a" } }); }); + it("reconciles selected tab metadata from the captured history document", async () => { + vi.useFakeTimers(); + stubScreenshotMedia(); + const currentUrl = "https://example.test/current"; + const previousUrl = "https://example.test/previous"; + const { client, request } = createBrowserClient(async (envelope) => { + if (envelope.path === "/act") { + const fn = String(envelope.body?.fn ?? ""); + return fn.includes("history.go") + ? { result: true } + : createBrowserPanelTestMetrics(previousUrl, "Previous"); + } + if (envelope.path === "/screenshot") { + return { path: "/fresh.png", targetId: "raw-a", url: previousUrl }; + } + throw new Error(`Unexpected browser route: ${envelope.path}`); + }); + const controller = createBrowserPanelTestController(client, "tab-a", currentUrl); + controller.tabs = [ + { id: "tab-a", targetId: "raw-a", title: "Current", url: currentUrl }, + { + id: "tab-b", + targetId: "raw-b", + title: "Background", + url: "https://example.test/background", + }, + ]; + + controller.goHistory(-1); + await flushBrowserResponses(); + await vi.advanceTimersByTimeAsync(350); + await flushBrowserResponses(); + await vi.runAllTimersAsync(); + await flushBrowserResponses(); + + expect(controller.tabs).toEqual([ + { id: "tab-a", targetId: "raw-a", title: "Previous", url: previousUrl }, + { + id: "tab-b", + targetId: "raw-b", + title: "Background", + url: "https://example.test/background", + }, + ]); + expect(controller.urlDraft).toBe(previousUrl); + expect( + request.mock.calls.filter(([, envelope]) => { + return (envelope as BrowserRequestEnvelope).path === "/tabs"; + }), + ).toEqual([]); + }); + it("never forwards a queued wheel action to a newly selected tab", async () => { vi.useFakeTimers(); const { client, request } = createBrowserClient(async (envelope) => { diff --git a/ui/src/components/browser/browser-panel-controller.ts b/ui/src/components/browser/browser-panel-controller.ts index 7022532021f1..dc7497f96d7b 100644 --- a/ui/src/components/browser/browser-panel-controller.ts +++ b/ui/src/components/browser/browser-panel-controller.ts @@ -223,6 +223,19 @@ export class BrowserPanelController implements ReactiveController { shot.url && observedMetrics?.url && shot.url !== observedMetrics.url ? null : observedMetrics; + const tabIndex = this.tabs.findIndex((tab) => tab.id === targetId); + const tab = this.tabs[tabIndex]; + if (tab) { + // Tab snapshots can lag history and in-page navigation. Keep the active + // tab's stable identity aligned with the document this capture owns. + const url = metrics?.url || shot.url || tab.url; + const title = metrics ? metrics.title : tab.title; + if (url !== tab.url || title !== tab.title) { + const tabs = [...this.tabs]; + tabs[tabIndex] = { ...tab, title, url }; + this.setState("tabs", tabs); + } + } this.setState("view", { targetId, dataUrl, image, url: shot.url, metrics }); if (!this.urlDraftEditing && shot.url) { this.setState("urlDraft", shot.url);