diff --git a/apps/web/src/browserHistoryStore.test.ts b/apps/web/src/browserHistoryStore.test.ts index c68d3334b6f2..bc0aa9a29d25 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 { + type BrowserProfileId, EnvironmentId, + INCOGNITO_BROWSER_PROFILE_ID, type PreviewSessionSnapshot, ProjectId, ThreadId, @@ -157,7 +159,13 @@ 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?: BrowserProfileId, +): void { settledAt += 1; const snapshot: PreviewSessionSnapshot = { threadId: ref.threadId, @@ -166,6 +174,7 @@ function settle(url: string, title: string, tabId = "tab_a", ref = threadRef): v canGoBack: false, canGoForward: false, updatedAt: new Date(settledAt).toISOString(), + ...(profileId ? { profileId } : {}), }; applyPreviewServerSnapshot(ref, snapshot); } @@ -208,6 +217,17 @@ describe("useBrowserHistoryStore", () => { expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]?.title).toBe("New"); }); + it("never titles history from an Incognito page, whichever settles first", () => { + useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a"); + settle("http://a.test/", "Private A", "tab_incognito", threadRef, INCOGNITO_BROWSER_PROFILE_ID); + recordVisitForThread(threadRef, "http://a.test/", 1); + recordVisitForThread(threadRef, "http://b.test/", 2); + settle("http://b.test/", "Private B", "tab_incognito", threadRef, INCOGNITO_BROWSER_PROFILE_ID); + expect( + useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.map((entry) => entry.title), + ).toEqual([undefined, undefined]); + }); + it("keeps another thread's newer title when an unrelated tab settles", () => { const otherThreadRef = { ...threadRef, threadId: ThreadId.make("thread-2") }; const store = useBrowserHistoryStore.getState(); diff --git a/apps/web/src/browserHistoryStore.ts b/apps/web/src/browserHistoryStore.ts index fe1982f742ea..d5ccb0fcd621 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 { + type EnvironmentId, + INCOGNITO_BROWSER_PROFILE_ID, + type PreviewSessionSnapshot, + type ScopedProjectRef, + type ScopedThreadRef, } from "@t3tools/contracts"; import { create } from "zustand"; import { createJSONStorage, persist } from "zustand/middleware"; @@ -468,6 +469,7 @@ function recordPreviewTitles( ); for (const session of sessions) { const status = session.navStatus; + if (session.profileId === INCOGNITO_BROWSER_PROFILE_ID) continue; if (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 5629352f38c8..25ba198469ea 100644 --- a/apps/web/src/components/preview/PreviewView.tsx +++ b/apps/web/src/components/preview/PreviewView.tsx @@ -10,6 +10,7 @@ import { import { AuthOrchestrationOperateScope, DEFAULT_BROWSER_PROFILE_ID, + INCOGNITO_BROWSER_PROFILE_ID, FILL_PREVIEW_VIEWPORT, type PreviewAnnotationPayload, type ScopedThreadRef, @@ -248,18 +249,19 @@ export function PreviewView({ runtimeTabId ? (state.byTabId[runtimeTabId]?.rect ?? null) : null, ); + /** Resolves to the profile of the tab that loaded the URL, or null when nothing opened. */ 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 activeProfileId; } if (runtimeTabId && previewBridge) { await previewBridge.navigate(runtimeTabId, resolvedUrl); rememberPreviewUrl(threadRef, resolvedUrl); focusPreviewPage(runtimeTabId); - return true; + return activeProfileId; } const result = await openPreviewSession({ openPreview: open, threadRef, url: resolvedUrl }); if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { @@ -272,13 +274,19 @@ export function PreviewView({ }); } } - return result._tag === "Success"; + return result._tag === "Success" + ? (result.value.profileId ?? DEFAULT_BROWSER_PROFILE_ID) + : null; }; + // Incognito visits stay out of the project's persisted history. + const isHistoryProfile = (profileId: string | null) => + profileId !== null && profileId !== INCOGNITO_BROWSER_PROFILE_ID; + const handleSubmitUrl = async (next: string) => { try { const resolved = resolveAddressBarInput(next, resolveSearchUrlTemplate(getClientSettings())); - if (await navigateToResolvedUrl(resolved)) { + if (isHistoryProfile(await navigateToResolvedUrl(resolved))) { recordVisitForThread(threadRef, resolved); } } catch (error) { @@ -300,7 +308,7 @@ export function PreviewView({ isServerTab || !previewBridge ? normalizePreviewUrl(next) : resolveDiscoveredServerUrl(threadRef.environmentId, next); - if (await navigateToResolvedUrl(resolved)) { + if (isHistoryProfile(await navigateToResolvedUrl(resolved))) { recordVisitForThread(threadRef, next); } } catch (error) {