From 6d0abb71074f31cc279d6069cb1d48dd42084c5f Mon Sep 17 00:00:00 2001 From: Lakshmi Tanmay Date: Mon, 2 Nov 2026 21:24:39 +0530 Subject: [PATCH 1/4] fix(web): keep incognito visits out of project browser history --- apps/web/src/browserHistoryStore.test.ts | 134 ++++++++++++++---- apps/web/src/browserHistoryStore.ts | 27 +++- .../src/components/preview/PreviewView.tsx | 18 +-- .../components/preview/openDiscoveredPort.ts | 2 +- .../preview/openTerminalLinkInPreview.ts | 2 +- 5 files changed, 135 insertions(+), 48 deletions(-) diff --git a/apps/web/src/browserHistoryStore.test.ts b/apps/web/src/browserHistoryStore.test.ts index c68d3334b6f2..c5a08a612dc3 100644 --- a/apps/web/src/browserHistoryStore.test.ts +++ b/apps/web/src/browserHistoryStore.test.ts @@ -2,7 +2,9 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vite-plus/test" import { scopeProjectRef } from "@t3tools/client-runtime/environment"; import { + DEFAULT_BROWSER_PROFILE_ID, EnvironmentId, + INCOGNITO_BROWSER_PROFILE_ID, type PreviewSessionSnapshot, ProjectId, ThreadId, @@ -157,11 +159,18 @@ const threadRef = { let settledAt = 0; -function settle(url: string, title: string, tabId = "tab_a", ref = threadRef): void { +function settle( + url: string, + title: string, + tabId = "tab_a", + ref = threadRef, + profileId?: string, +): void { settledAt += 1; const snapshot: PreviewSessionSnapshot = { threadId: ref.threadId, tabId, + ...(profileId === undefined ? {} : { profileId }), navStatus: { _tag: "Success", url, title }, canGoBack: false, canGoForward: false, @@ -179,20 +188,83 @@ function resetStores(): () => void { describe("useBrowserHistoryStore", () => { beforeEach(resetStores); + it.each([false, true])( + "excludes incognito visits from saved history with project already registered: %s", + async (registered) => { + if (registered) useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); + recordVisitForThread( + threadRef, + "https://private.example/unique", + INCOGNITO_BROWSER_PROFILE_ID, + 1, + ); + expect(useBrowserHistoryStore.getState().pendingVisitsByThreadKey).toEqual({}); + useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); + for (const [index, profileId] of [undefined, DEFAULT_BROWSER_PROFILE_ID, "work"].entries()) { + recordVisitForThread(threadRef, `https://public.example/${index}`, profileId, index + 2); + } + expect( + useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.map((entry) => entry.url), + ).toEqual([ + "https://public.example/2", + "https://public.example/1", + "https://public.example/0", + ]); + const { storage, name } = useBrowserHistoryStore.persist.getOptions(); + const saved = JSON.stringify(await storage!.getItem(name!)); + expect(saved).not.toContain("private.example"); + expect(saved).toContain("public.example"); + await useBrowserHistoryStore.persist.rehydrate(); + expect( + useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.map((entry) => entry.url), + ).toEqual([ + "https://public.example/2", + "https://public.example/1", + "https://public.example/0", + ]); + }, + ); + + it.each([false, true])( + "excludes incognito titles from regular history with page settled before visit: %s", + async (settledBeforeVisit) => { + useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); + if (!settledBeforeVisit) { + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); + settle("http://a.test/", "Public page"); + } + settle( + "http://a.test/", + "Private page", + "private-tab", + threadRef, + INCOGNITO_BROWSER_PROFILE_ID, + ); + if (settledBeforeVisit) recordVisitForThread(threadRef, "http://a.test/", undefined, 1); + expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]?.title).toBe( + settledBeforeVisit ? undefined : "Public page", + ); + const { storage, name } = useBrowserHistoryStore.persist.getOptions(); + expect(JSON.stringify(await storage!.getItem(name!))).not.toContain("Private page"); + settle("http://a.test/", "Work page", "work-tab", threadRef, "work"); + expect(JSON.stringify(await storage!.getItem(name!))).toContain("Work page"); + }, + ); + it("titles only pages already in history when they settle", () => { useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); settle("http://a.test/", "Should not create"); expect(useBrowserHistoryStore.getState().byProjectKey).toEqual({}); - recordVisitForThread(threadRef, "http://a.test/#/settings", 1); + recordVisitForThread(threadRef, "http://a.test/#/settings", undefined, 1); settle("http://a.test/#/settings", "My App"); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]?.title).toBe("My App"); }); it("titles a visit recorded after its page already settled", () => { useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); - recordVisitForThread(threadRef, "http://other.test/", 1); + recordVisitForThread(threadRef, "http://other.test/", undefined, 1); settle("http://a.test/", "My App"); - recordVisitForThread(threadRef, "http://a.test/", 2); + recordVisitForThread(threadRef, "http://a.test/", undefined, 2); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]).toMatchObject({ url: "http://a.test/", title: "My App", @@ -201,7 +273,7 @@ describe("useBrowserHistoryStore", () => { it("keeps the newest title when another tab shows the same url", () => { useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); - recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); settle("http://a.test/", "Old", "tab_a"); settle("http://a.test/", "Old", "tab_b"); settle("http://a.test/", "New", "tab_a"); @@ -213,7 +285,7 @@ describe("useBrowserHistoryStore", () => { const store = useBrowserHistoryStore.getState(); store.registerThreadProject(threadRef, "proj-a"); store.registerThreadProject(otherThreadRef, "proj-a"); - recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); settle("http://a.test/", "Old", "tab_a", threadRef); settle("http://a.test/", "New", "tab_a", otherThreadRef); settle("http://b.test/", "Unrelated", "tab_b", threadRef); @@ -229,10 +301,10 @@ describe("useBrowserHistoryStore", () => { const store = useBrowserHistoryStore.getState(); store.registerThreadProject(threadRef, "proj-a"); store.registerThreadProject(otherThreadRef, "proj-a"); - recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); settle("http://a.test/", "Old", "tab_a", threadRef); settle("http://a.test/", "New", "tab_a", otherThreadRef); - recordVisitForThread(threadRef, "http://b.test/", 2); + recordVisitForThread(threadRef, "http://b.test/", undefined, 2); expect( useBrowserHistoryStore .getState() @@ -245,7 +317,7 @@ describe("useBrowserHistoryStore", () => { const store = useBrowserHistoryStore.getState(); store.registerThreadProject(threadRef, "proj-a"); store.registerThreadProject(otherThreadRef, "proj-a"); - recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); settle("http://a.test/", "Old", "tab_a", threadRef); settle("http://a.test/", "New", "tab_a", otherThreadRef); // A resize or a list reconcile replaces the session without a new page load. @@ -258,16 +330,16 @@ describe("useBrowserHistoryStore", () => { const store = useBrowserHistoryStore.getState(); store.registerThreadProject(threadRef, "proj-a"); store.registerThreadProject(otherThreadRef, "proj-a"); - recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); settle("http://a.test/", "Old", "tab_a", threadRef); settle("http://a.test/", "New", "tab_a", otherThreadRef); - recordVisitForThread(threadRef, "http://a.test/", 2); + recordVisitForThread(threadRef, "http://a.test/", undefined, 2); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]?.title).toBe("New"); }); it("sets a title against a settled url that differs from the stored one only by a trailing slash", () => { useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); - recordVisitForThread(threadRef, "http://a.test/community", 1); + recordVisitForThread(threadRef, "http://a.test/community", undefined, 1); settle("http://a.test/community/", "Community"); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]).toMatchObject({ url: "http://a.test/community", @@ -275,7 +347,7 @@ describe("useBrowserHistoryStore", () => { }); useBrowserHistoryStore.setState({ byProjectKey: {} }); - recordVisitForThread(threadRef, "http://a.test/community/", 1); + recordVisitForThread(threadRef, "http://a.test/community/", undefined, 1); settle("http://a.test/community", "Community"); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]).toMatchObject({ url: "http://a.test/community/", @@ -285,7 +357,7 @@ describe("useBrowserHistoryStore", () => { it("matches a requested localhost URL to the resolved environment host", () => { useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); - recordVisitForThread(threadRef, "http://localhost:5173/app", 1); + recordVisitForThread(threadRef, "http://localhost:5173/app", undefined, 1); readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://192.168.64.2:3773" }); settle("http://192.168.64.2:5173/app", "Local App"); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]?.title).toBe("Local App"); @@ -294,16 +366,16 @@ describe("useBrowserHistoryStore", () => { it("deduplicates loopback aliases and the resolved environment host", () => { readPreparedConnection.mockReturnValue({ httpBaseUrl: "http://192.168.64.2:3773" }); useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); - recordVisitForThread(threadRef, "http://localhost:5173/app", 1); - recordVisitForThread(threadRef, "http://127.0.0.1:5173/app", 2); - recordVisitForThread(threadRef, "http://192.168.64.2:5173/app", 3); + recordVisitForThread(threadRef, "http://localhost:5173/app", undefined, 1); + recordVisitForThread(threadRef, "http://127.0.0.1:5173/app", undefined, 2); + recordVisitForThread(threadRef, "http://192.168.64.2:5173/app", undefined, 3); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]).toEqual([ { url: "http://localhost:5173/app", lastVisitedAt: 3 }, ]); useBrowserHistoryStore.setState({ byProjectKey: {} }); - recordVisitForThread(threadRef, "http://192.168.64.2:5173/app", 4); - recordVisitForThread(threadRef, "http://localhost:5173/app", 5); + recordVisitForThread(threadRef, "http://192.168.64.2:5173/app", undefined, 4); + recordVisitForThread(threadRef, "http://localhost:5173/app", undefined, 5); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]).toEqual([ { url: "http://localhost:5173/app", lastVisitedAt: 5 }, ]); @@ -325,7 +397,7 @@ describe("thread project resolution", () => { entities.projects = [repoProject]; entities.shellProjectId = "project-1"; - recordVisitForThread(threadRef, "http://localhost:5173/", 1); + recordVisitForThread(threadRef, "http://localhost:5173/", undefined, 1); settle("http://localhost:5173/", "Dev App"); expect(useBrowserHistoryStore.getState().byProjectKey).toEqual({ @@ -343,7 +415,7 @@ describe("thread project resolution", () => { threadId: threadRef.threadId, }); - recordVisitForThread(threadRef, "http://localhost:5173/", 1); + recordVisitForThread(threadRef, "http://localhost:5173/", undefined, 1); expect(useBrowserHistoryStore.getState().byProjectKey).toEqual({ "env-1:/repo": [{ url: "http://localhost:5173/", lastVisitedAt: 1 }], @@ -362,7 +434,7 @@ describe("thread project resolution", () => { }; }); - recordVisitForThread(threadRef, "http://localhost:5173/", 1); + recordVisitForThread(threadRef, "http://localhost:5173/", undefined, 1); expect(useBrowserHistoryStore.getState().projectKeyByThreadKey).toEqual({}); settingsHydration.finish(); @@ -374,10 +446,10 @@ describe("thread project resolution", () => { }); it("lands visits queued before the thread's project loaded with the next visit", () => { - recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); entities.projects = [repoProject]; entities.shellProjectId = "project-1"; - recordVisitForThread(threadRef, "http://b.test/", 2); + recordVisitForThread(threadRef, "http://b.test/", undefined, 2); expect( useBrowserHistoryStore.getState().byProjectKey["env-1:/repo"]?.map((entry) => entry.url), @@ -389,8 +461,8 @@ describe("pendingVisitsByThreadKey", () => { beforeEach(resetStores); it("queues a visit recorded before registration and drains it in order on registration", () => { - recordVisitForThread(threadRef, "http://a.test/", 1); - recordVisitForThread(threadRef, "http://b.test/", 2); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); + recordVisitForThread(threadRef, "http://b.test/", undefined, 2); expect(useBrowserHistoryStore.getState().byProjectKey).toEqual({}); useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.map((e) => e.url)).toEqual([ @@ -408,8 +480,8 @@ describe("pendingVisitsByThreadKey", () => { threadId: ThreadId.make("thread-2"), }; useBrowserHistoryStore.getState().registerThreadProject(otherThreadRef, "proj-a"); - recordVisitForThread(otherThreadRef, "http://newer.test/", 2000); - recordVisitForThread(threadRef, "http://older.test/", 1000); + recordVisitForThread(otherThreadRef, "http://newer.test/", undefined, 2000); + recordVisitForThread(threadRef, "http://older.test/", undefined, 1000); useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); const entries = useBrowserHistoryStore.getState().byProjectKey["proj-a"]; @@ -422,7 +494,7 @@ describe("titles before registration", () => { beforeEach(resetStores); it("applies a settled page's title once the matching visit drains", () => { - recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); settle("http://a.test/", "My App"); expect(useBrowserHistoryStore.getState().byProjectKey).toEqual({}); @@ -434,10 +506,10 @@ describe("titles before registration", () => { it("keeps another thread's title when a queued visit to the same url drains", () => { const otherThreadRef = { ...threadRef, threadId: ThreadId.make("thread-2") }; - recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); settle("http://a.test/", "Old", "tab_a", threadRef); useBrowserHistoryStore.getState().registerThreadProject(otherThreadRef, "proj-a"); - recordVisitForThread(otherThreadRef, "http://a.test/", 2); + recordVisitForThread(otherThreadRef, "http://a.test/", undefined, 2); settle("http://a.test/", "New", "tab_a", otherThreadRef); useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); diff --git a/apps/web/src/browserHistoryStore.ts b/apps/web/src/browserHistoryStore.ts index fe1982f742ea..91290d64f548 100644 --- a/apps/web/src/browserHistoryStore.ts +++ b/apps/web/src/browserHistoryStore.ts @@ -3,11 +3,12 @@ import { scopedThreadKey, scopeProjectRef, } from "@t3tools/client-runtime/environment"; -import type { - EnvironmentId, - PreviewSessionSnapshot, - ScopedProjectRef, - ScopedThreadRef, +import { + INCOGNITO_BROWSER_PROFILE_ID, + type EnvironmentId, + type PreviewSessionSnapshot, + type ScopedProjectRef, + type ScopedThreadRef, } from "@t3tools/contracts"; import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; @@ -398,7 +399,13 @@ function resolveThreadProjectKey(ref: ScopedThreadRef): string | undefined { return projectKey; } -export function recordVisitForThread(ref: ScopedThreadRef, url: string, at?: number): void { +export function recordVisitForThread( + ref: ScopedThreadRef, + url: string, + profileId?: string, + at?: number, +): void { + if (profileId === INCOGNITO_BROWSER_PROFILE_ID) return; const threadKey = scopedThreadKey(ref); const projectKey = resolveThreadProjectKey(ref); const state = useBrowserHistoryStore.getState(); @@ -468,7 +475,13 @@ function recordPreviewTitles( ); for (const session of sessions) { const status = session.navStatus; - if (status._tag !== "Success" || !status.title || !include(session, status.url)) continue; + if ( + session.profileId === INCOGNITO_BROWSER_PROFILE_ID || + status._tag !== "Success" || + !status.title || + !include(session, status.url) + ) + continue; state.setTitleForUrl(projectKey, status.url, status.title, environmentHostname); } } diff --git a/apps/web/src/components/preview/PreviewView.tsx b/apps/web/src/components/preview/PreviewView.tsx index 253a4fc14e56..834f492c1211 100644 --- a/apps/web/src/components/preview/PreviewView.tsx +++ b/apps/web/src/components/preview/PreviewView.tsx @@ -239,15 +239,15 @@ export function PreviewView({ const navigateToResolvedUrl = async (resolvedUrl: string) => { if (isServerTab && serverSurfaceRef.current) { - if (serverInputDisabled) return false; + if (serverInputDisabled) return null; serverSurfaceRef.current.navigate(resolvedUrl); rememberPreviewUrl(threadRef, resolvedUrl); - return true; + return snapshot; } if (runtimeTabId && previewBridge) { await previewBridge.navigate(runtimeTabId, resolvedUrl); rememberPreviewUrl(threadRef, resolvedUrl); - return true; + return snapshot; } const result = await openPreviewSession({ openPreview: open, threadRef, url: resolvedUrl }); if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { @@ -260,14 +260,15 @@ export function PreviewView({ }); } } - return result._tag === "Success"; + return result._tag === "Success" ? result.value : null; }; const handleSubmitUrl = async (next: string) => { try { const resolved = resolveAddressBarInput(next, resolveSearchUrlTemplate(getClientSettings())); - if (await navigateToResolvedUrl(resolved)) { - recordVisitForThread(threadRef, resolved); + const navigated = await navigateToResolvedUrl(resolved); + if (navigated) { + recordVisitForThread(threadRef, resolved, navigated.profileId); } } catch (error) { toastManager.add({ @@ -288,8 +289,9 @@ export function PreviewView({ isServerTab || !previewBridge ? normalizePreviewUrl(next) : resolveDiscoveredServerUrl(threadRef.environmentId, next); - if (await navigateToResolvedUrl(resolved)) { - recordVisitForThread(threadRef, next); + const navigated = await navigateToResolvedUrl(resolved); + if (navigated) { + recordVisitForThread(threadRef, next, navigated.profileId); } } catch (error) { toastManager.add({ diff --git a/apps/web/src/components/preview/openDiscoveredPort.ts b/apps/web/src/components/preview/openDiscoveredPort.ts index 687ad87cbac4..c77bf3020543 100644 --- a/apps/web/src/components/preview/openDiscoveredPort.ts +++ b/apps/web/src/components/preview/openDiscoveredPort.ts @@ -26,7 +26,7 @@ export async function openDiscoveredPort(input: { url: resolvedUrl, }); return mapAtomCommandResult(result, (snapshot) => { - recordVisitForThread(input.threadRef, input.port.url); + recordVisitForThread(input.threadRef, input.port.url, snapshot.profileId); useRightPanelStore.getState().openBrowser(input.threadRef, snapshot.tabId); }); } diff --git a/apps/web/src/components/preview/openTerminalLinkInPreview.ts b/apps/web/src/components/preview/openTerminalLinkInPreview.ts index d160c1ee7ea6..84e432a31a82 100644 --- a/apps/web/src/components/preview/openTerminalLinkInPreview.ts +++ b/apps/web/src/components/preview/openTerminalLinkInPreview.ts @@ -80,7 +80,7 @@ export async function openTerminalLinkInPreview( input.fallbackToBrowser(); return; } - recordVisitForThread(input.threadRef, input.url); + recordVisitForThread(input.threadRef, input.url, result.value.profileId); applyPreviewServerSnapshot(input.threadRef, result.value); useRightPanelStore.getState().openBrowser(input.threadRef, result.value.tabId); } From 6aec78f93aaadb8ad0a9f0843118281954549eab Mon Sep 17 00:00:00 2001 From: Lakshmi Tanmay Date: Mon, 2 Nov 2026 22:18:56 +0530 Subject: [PATCH 2/4] fix(web): preserve queued regular browser visits before incognito --- apps/web/src/browserHistoryStore.test.ts | 26 ++++++++++++++++-------- apps/web/src/browserHistoryStore.ts | 2 +- 2 files changed, 18 insertions(+), 10 deletions(-) diff --git a/apps/web/src/browserHistoryStore.test.ts b/apps/web/src/browserHistoryStore.test.ts index c5a08a612dc3..696cb5c538c4 100644 --- a/apps/web/src/browserHistoryStore.test.ts +++ b/apps/web/src/browserHistoryStore.test.ts @@ -445,16 +445,24 @@ describe("thread project resolution", () => { expect(useBrowserHistoryStore.getState().pendingVisitsByThreadKey).toEqual({}); }); - it("lands visits queued before the thread's project loaded with the next visit", () => { - recordVisitForThread(threadRef, "http://a.test/", undefined, 1); - entities.projects = [repoProject]; - entities.shellProjectId = "project-1"; - recordVisitForThread(threadRef, "http://b.test/", undefined, 2); + it.each([undefined, INCOGNITO_BROWSER_PROFILE_ID])( + "lands queued regular visits when the next visit uses profile %s", + (profileId) => { + recordVisitForThread(threadRef, "http://a.test/", undefined, 1); + entities.projects = [repoProject]; + entities.shellProjectId = "project-1"; + recordVisitForThread(threadRef, "http://b.test/", profileId, 2); - expect( - useBrowserHistoryStore.getState().byProjectKey["env-1:/repo"]?.map((entry) => entry.url), - ).toEqual(["http://b.test/", "http://a.test/"]); - }); + expect( + useBrowserHistoryStore.getState().byProjectKey["env-1:/repo"]?.map((entry) => entry.url), + ).toEqual( + profileId === INCOGNITO_BROWSER_PROFILE_ID + ? ["http://a.test/"] + : ["http://b.test/", "http://a.test/"], + ); + expect(useBrowserHistoryStore.getState().pendingVisitsByThreadKey).toEqual({}); + }, + ); }); describe("pendingVisitsByThreadKey", () => { diff --git a/apps/web/src/browserHistoryStore.ts b/apps/web/src/browserHistoryStore.ts index 91290d64f548..f1689a9adfe4 100644 --- a/apps/web/src/browserHistoryStore.ts +++ b/apps/web/src/browserHistoryStore.ts @@ -405,9 +405,9 @@ export function recordVisitForThread( profileId?: string, at?: number, ): void { - if (profileId === INCOGNITO_BROWSER_PROFILE_ID) return; const threadKey = scopedThreadKey(ref); const projectKey = resolveThreadProjectKey(ref); + if (profileId === INCOGNITO_BROWSER_PROFILE_ID) return; const state = useBrowserHistoryStore.getState(); const visitAt = at ?? Date.now(); const connection = readPreparedConnection(ref.environmentId); From 6461b7d67266555cbc29f87e6008c18cdc7ac590 Mon Sep 17 00:00:00 2001 From: Lakshmi Tanmay Date: Mon, 2 Nov 2026 22:48:56 +0530 Subject: [PATCH 3/4] chore: rerun CI after unrelated editor initialization failure From a87fb44ddcd8d9d580aa648d8ff0b2f8a33e59b5 Mon Sep 17 00:00:00 2001 From: maria-rcks Date: Sat, 7 Nov 2026 14:14:45 +0000 Subject: [PATCH 4/4] fix(web): record visits with the opened preview profile --- apps/web/src/browser/openFileInPreview.ts | 7 ++++--- apps/web/src/browser/useOpenLink.ts | 2 +- apps/web/src/components/ChatMarkdown.tsx | 2 +- apps/web/src/components/preview/PreviewView.tsx | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/apps/web/src/browser/openFileInPreview.ts b/apps/web/src/browser/openFileInPreview.ts index a997ee2b552e..f551887501d8 100644 --- a/apps/web/src/browser/openFileInPreview.ts +++ b/apps/web/src/browser/openFileInPreview.ts @@ -62,7 +62,7 @@ export async function openUrlInPreview(input: { readonly openPreview: OpenPreviewMutation; readonly profileId?: PreviewOpenInput["profileId"]; readonly background?: boolean; -}): Promise> { +}): Promise> { // Read before any await: a side chat pane that unmounts meanwhile drops its host mapping. const threadRef = rightPanelHostRef(input.threadRef); const defaults = await resolveBrowserDefaults().catch( @@ -100,10 +100,11 @@ export async function openUrlInPreview(input: { ) { setActivePreviewTab(threadRef, previousActiveTabId); } - return; + return snapshot; } applyPreviewServerSnapshot(threadRef, snapshot); useRightPanelStore.getState().openBrowser(threadRef, snapshot.tabId); + return snapshot; }); } @@ -119,7 +120,7 @@ export async function openFileInPreview(input: { readonly openPreview: OpenPreviewMutation; }): Promise< AtomCommandResult< - void, + PreviewSessionSnapshot, AssetError | PreviewError | BrowserPreviewUnavailableError | BrowserSettingsReadError > > { diff --git a/apps/web/src/browser/useOpenLink.ts b/apps/web/src/browser/useOpenLink.ts index 73d880632e93..883f55df11ce 100644 --- a/apps/web/src/browser/useOpenLink.ts +++ b/apps/web/src/browser/useOpenLink.ts @@ -42,7 +42,7 @@ export function useOpenLink(threadRef: ScopedThreadRef | null | undefined): ( const result = await openUrlInPreview({ threadRef: targetThreadRef, url, openPreview }); if (isAtomCommandInterrupted(result)) return; if (result._tag === "Success") { - recordVisitForThread(targetThreadRef, url); + recordVisitForThread(targetThreadRef, url, result.value.profileId); return; } const failure = squashAtomCommandFailure(result); diff --git a/apps/web/src/components/ChatMarkdown.tsx b/apps/web/src/components/ChatMarkdown.tsx index 5e3c7ca96fd3..eb2c6fe2ded1 100644 --- a/apps/web/src/components/ChatMarkdown.tsx +++ b/apps/web/src/components/ChatMarkdown.tsx @@ -712,7 +712,7 @@ function useChatMarkdownState({ ); } return openUrlInPreview({ threadRef, url, openPreview }).then((result) => { - if (result._tag === "Success") recordVisitForThread(threadRef, url); + if (result._tag === "Success") recordVisitForThread(threadRef, url, result.value.profileId); else if (!isAtomCommandInterrupted(result)) { const error = squashAtomCommandFailure(result); if (isBrowserSettingsReadError(error)) { diff --git a/apps/web/src/components/preview/PreviewView.tsx b/apps/web/src/components/preview/PreviewView.tsx index 834f492c1211..7ac2001d8853 100644 --- a/apps/web/src/components/preview/PreviewView.tsx +++ b/apps/web/src/components/preview/PreviewView.tsx @@ -242,12 +242,12 @@ export function PreviewView({ if (serverInputDisabled) return null; serverSurfaceRef.current.navigate(resolvedUrl); rememberPreviewUrl(threadRef, resolvedUrl); - return snapshot; + return { profileId: snapshot?.profileId }; } if (runtimeTabId && previewBridge) { await previewBridge.navigate(runtimeTabId, resolvedUrl); rememberPreviewUrl(threadRef, resolvedUrl); - return snapshot; + return { profileId: snapshot?.profileId }; } const result = await openPreviewSession({ openPreview: open, threadRef, url: resolvedUrl }); if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) {