From b4ec1b9ab56231fe719bee06089bf0ebc6f8eeb4 Mon Sep 17 00:00:00 2001 From: danielhanchen Date: Tue, 19 May 2026 21:14:50 +0000 Subject: [PATCH 1/2] studio/frontend: clear stale thread/compare search after sidebar navigation The sidebar's "delete active chat", "New Chat", and Unsloth-home-logo all call `navigate({to: "/chat", search: {new: nonce}})`. TanStack Router merges search params by default, so passing only `{new}` leaves the prior `thread=` (or `compare=`) in the URL. The recovery useEffect in chat-page.tsx only fires when `search.thread` changes, which it doesn't here, so the in-tab address bar stays stale until a hard reload kicks in. Visible impact: deleting the currently-open chat correctly removes the row from the sidebar, but the URL still references the deleted id. A copied share link is broken; opening a new tab to the same URL trips the "Chat not found" recovery toast. Fix: pass `thread: undefined, compare: undefined` alongside `new` so the merged search ends up as just `{new: nonce}`. This is the documented TanStack Router idiom for removing keys. Probe + repro: scripts/r6_sidebar_delete_chat_probe.py and scripts/r6_repro_active_delete_url.py. --- studio/frontend/src/components/app-sidebar.tsx | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/studio/frontend/src/components/app-sidebar.tsx b/studio/frontend/src/components/app-sidebar.tsx index aac5f8f8a8..daf3b1e788 100644 --- a/studio/frontend/src/components/app-sidebar.tsx +++ b/studio/frontend/src/components/app-sidebar.tsx @@ -249,9 +249,14 @@ export function AppSidebar() { async function handleDeleteThread(item: Parameters[0]) { await deleteChatItem(item, activeThreadId, (view) => { + // Clear `thread`/`compare` explicitly: TanStack Router merges + // search params by default, so passing only `{new}` leaves the + // deleted thread id in the URL. The recovery useEffect in + // chat-page only fires on hard reload, which means the in-tab + // address bar stays stale until then. navigate({ to: "/chat", - search: { new: view.newThreadNonce }, + search: { new: view.newThreadNonce, thread: undefined, compare: undefined }, }); }); } @@ -362,7 +367,7 @@ export function AppSidebar() { closeMobileIfOpen(); void navigate({ to: "/chat", - search: { new: createNavigationNonce() }, + search: { new: createNavigationNonce(), thread: undefined, compare: undefined }, }); }} className="flex items-center gap-[6px] select-none" @@ -440,7 +445,10 @@ export function AppSidebar() { onClick={() => { if (chatDisabled) return; setActiveThreadId(null); - navigate({ to: "/chat", search: { new: createNavigationNonce() } }); + navigate({ + to: "/chat", + search: { new: createNavigationNonce(), thread: undefined, compare: undefined }, + }); closeMobileIfOpen(); }} /> From f973657b3f4192c63815402b5159519a549b8874 Mon Sep 17 00:00:00 2001 From: danielhanchen Date: Tue, 19 May 2026 21:35:59 +0000 Subject: [PATCH 2/2] studio/frontend: switch to TanStack Router function-form search Address gemini review on #5633: use `search: () => ({...})` instead of merging with explicit `undefined`. The function form is the documented idiom for replacing (not merging) search state, and is clearer about intent. Also clear `activeThreadId` and close mobile sidebar in the delete-handler callback to match the other two sites. --- .../frontend/src/components/app-sidebar.tsx | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/studio/frontend/src/components/app-sidebar.tsx b/studio/frontend/src/components/app-sidebar.tsx index daf3b1e788..2676c60fc5 100644 --- a/studio/frontend/src/components/app-sidebar.tsx +++ b/studio/frontend/src/components/app-sidebar.tsx @@ -249,15 +249,16 @@ export function AppSidebar() { async function handleDeleteThread(item: Parameters[0]) { await deleteChatItem(item, activeThreadId, (view) => { - // Clear `thread`/`compare` explicitly: TanStack Router merges - // search params by default, so passing only `{new}` leaves the - // deleted thread id in the URL. The recovery useEffect in - // chat-page only fires on hard reload, which means the in-tab - // address bar stays stale until then. - navigate({ + // Function-form search replaces (not merges) so the deleted + // thread / compare id can't survive in the URL. The recovery + // useEffect in chat-page only fires on hard reload, which is + // why the in-tab address bar stays stale otherwise. + setActiveThreadId(null); + void navigate({ to: "/chat", - search: { new: view.newThreadNonce, thread: undefined, compare: undefined }, + search: () => ({ new: view.newThreadNonce }), }); + closeMobileIfOpen(); }); } @@ -367,7 +368,7 @@ export function AppSidebar() { closeMobileIfOpen(); void navigate({ to: "/chat", - search: { new: createNavigationNonce(), thread: undefined, compare: undefined }, + search: () => ({ new: createNavigationNonce() }), }); }} className="flex items-center gap-[6px] select-none" @@ -447,7 +448,7 @@ export function AppSidebar() { setActiveThreadId(null); navigate({ to: "/chat", - search: { new: createNavigationNonce(), thread: undefined, compare: undefined }, + search: () => ({ new: createNavigationNonce() }), }); closeMobileIfOpen(); }}