diff --git a/apps/web/src/browserHistoryStore.test.ts b/apps/web/src/browserHistoryStore.test.ts index 554faf086d5d4..7c548da3350d4 100644 --- a/apps/web/src/browserHistoryStore.test.ts +++ b/apps/web/src/browserHistoryStore.test.ts @@ -194,6 +194,19 @@ describe("useBrowserHistoryStore", () => { ).toBe("New"); }); + it("keeps another thread's newer title when this thread's unchanged tab is re-sent", () => { + const otherThreadRef = { ...threadRef, threadId: ThreadId.make("thread-2") }; + const store = useBrowserHistoryStore.getState(); + store.registerThreadProject(threadRef, "proj-a"); + store.registerThreadProject(otherThreadRef, "proj-a"); + recordVisitForThread(threadRef, "http://a.test/", 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. + settle("http://a.test/", "Old", "tab_a", threadRef); + expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]?.title).toBe("New"); + }); + it("keeps another thread's newer title when this thread revisits the same url", () => { 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 33a9b0dc52390..3b1b1c12ff680 100644 --- a/apps/web/src/browserHistoryStore.ts +++ b/apps/web/src/browserHistoryStore.ts @@ -336,10 +336,9 @@ export function recordVisitForThread(ref: ScopedThreadRef, url: string, at?: num } /** - * Titles only update entries already in history, so they apply on settle and on - * visit. History is shared across a project's threads and settled sessions stay - * retained, so a visit only fills untitled entries: replaying this thread's - * older settle would clobber a newer title. Settles apply what just changed. + * History is shared across a project's threads and settled sessions are retained, + * so replaying an old settle would clobber another thread's newer title. Visits + * only fill untitled entries; settles apply only pages that loaded or retitled. */ function recordPreviewTitles( ref: ScopedThreadRef, @@ -368,9 +367,15 @@ function recordPreviewTitles( "urls" in only ? new Set(only.urls.map(titleKey).filter((key) => key !== null && !titled.has(key))) : null; + // Resizes and list reconciles replace a session without loading a new page. + const settledAgain = (session: PreviewSessionSnapshot, previous?: PreviewSessionSnapshot) => + previous?.navStatus._tag === "Success" && + session.navStatus._tag === "Success" && + previous.navStatus.url === session.navStatus.url && + previous.navStatus.title === session.navStatus.title; const include = (session: PreviewSessionSnapshot, url: string) => "changedSince" in only - ? only.changedSince[session.tabId] !== session + ? !settledAgain(session, only.changedSince[session.tabId]) : (visitKeys?.has(titleKey(url)) ?? false); // Tabs can share a URL; applying oldest first lets the newest settle win. const sessions = Object.values(preview.sessions).toSorted((a, b) =>