Skip to content

Commit d7fc08f

Browse files
fix(web): an unrelated tab settling no longer replays stale titles
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
1 parent ad2d75c commit d7fc08f

2 files changed

Lines changed: 31 additions & 8 deletions

File tree

‎apps/web/src/browserHistoryStore.test.ts‎

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -111,17 +111,17 @@ const threadRef = {
111111

112112
let settledAt = 0;
113113

114-
function settle(url: string, title: string, tabId = "tab_a"): void {
114+
function settle(url: string, title: string, tabId = "tab_a", ref = threadRef): void {
115115
settledAt += 1;
116116
const snapshot: PreviewSessionSnapshot = {
117-
threadId: threadRef.threadId,
117+
threadId: ref.threadId,
118118
tabId,
119119
navStatus: { _tag: "Success", url, title },
120120
canGoBack: false,
121121
canGoForward: false,
122122
updatedAt: new Date(settledAt).toISOString(),
123123
};
124-
applyPreviewServerSnapshot(threadRef, snapshot);
124+
applyPreviewServerSnapshot(ref, snapshot);
125125
}
126126

127127
function resetStores(): () => void {
@@ -162,6 +162,22 @@ describe("useBrowserHistoryStore", () => {
162162
expect(useBrowserHistoryStore.getState().byProjectKey["proj-a"]?.[0]?.title).toBe("New");
163163
});
164164

165+
it("keeps another thread's newer title when an unrelated tab settles", () => {
166+
const otherThreadRef = { ...threadRef, threadId: ThreadId.make("thread-2") };
167+
const store = useBrowserHistoryStore.getState();
168+
store.registerThreadProject(threadRef, "proj-a");
169+
store.registerThreadProject(otherThreadRef, "proj-a");
170+
recordVisitForThread(threadRef, "http://a.test/", 1);
171+
settle("http://a.test/", "Old", "tab_a", threadRef);
172+
settle("http://a.test/", "New", "tab_a", otherThreadRef);
173+
settle("http://b.test/", "Unrelated", "tab_b", threadRef);
174+
expect(
175+
useBrowserHistoryStore
176+
.getState()
177+
.byProjectKey["proj-a"]?.find((e) => e.url === "http://a.test/")?.title,
178+
).toBe("New");
179+
});
180+
165181
it("sets a title against a settled url that differs from the stored one only by a trailing slash", () => {
166182
useBrowserHistoryStore.getState().registerThreadProject(threadRef, "proj-a");
167183
recordVisitForThread(threadRef, "http://a.test/community", 1);

‎apps/web/src/browserHistoryStore.ts‎

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -336,16 +336,22 @@ export function recordVisitForThread(ref: ScopedThreadRef, url: string, at?: num
336336
* Titles only update entries already in the thread's project history, so they
337337
* are applied both when a page settles and when its visit is recorded.
338338
*/
339-
function recordPreviewTitles(ref: ScopedThreadRef, preview: ThreadPreviewState): void {
339+
function recordPreviewTitles(
340+
ref: ScopedThreadRef,
341+
preview: ThreadPreviewState,
342+
previousSessions?: ThreadPreviewState["sessions"],
343+
): void {
340344
const state = useBrowserHistoryStore.getState();
341345
const projectKey = state.projectKeyByThreadKey[scopedThreadKey(ref)];
342346
if (!projectKey || !state.byProjectKey[projectKey]?.length) return;
343347
const connection = readPreparedConnection(ref.environmentId);
344348
const environmentHostname = connection ? new URL(connection.httpBaseUrl).hostname : null;
345349
// Tabs can share a URL; applying oldest first lets the newest settle win.
346-
const sessions = Object.values(preview.sessions).toSorted((a, b) =>
347-
a.updatedAt.localeCompare(b.updatedAt),
348-
);
350+
// Unchanged sessions are skipped so a stale title cannot overwrite a newer
351+
// one another thread in the same project recorded since.
352+
const sessions = Object.values(preview.sessions)
353+
.filter((session) => previousSessions?.[session.tabId] !== session)
354+
.toSorted((a, b) => a.updatedAt.localeCompare(b.updatedAt));
349355
for (const session of sessions) {
350356
const status = session.navStatus;
351357
if (status._tag !== "Success" || !status.title) continue;
@@ -365,8 +371,9 @@ export function startPreviewTitleRecording(): () => void {
365371
threadKey,
366372
appAtomRegistry.subscribe(atom, (next) => {
367373
if (next.sessions === previousSessions) return;
374+
const changedSince = previousSessions;
368375
previousSessions = next.sessions;
369-
recordPreviewTitles(threadRef, next);
376+
recordPreviewTitles(threadRef, next, changedSince);
370377
}),
371378
);
372379
}

0 commit comments

Comments
 (0)