From 8f4fd62d6382c33e57ca5fd1245e1c063f288e2b Mon Sep 17 00:00:00 2001 From: kumarabhirup Date: Fri, 6 Mar 2026 21:29:03 -0800 Subject: [PATCH] fix(workspace): prevent URL sync from wiping params on hydration render The URL sync effect and hydration effect run in the same React render cycle. Since React state updates (setActivePath) are batched, the URL sync effect still saw activePath=null and pushed "/", stripping all query params. This caused an alternating refresh bug where odd refreshes showed the homepage and even refreshes worked correctly. Skip the URL sync effect for one render after hydration completes, giving React state time to update before the effect writes the URL. --- apps/web/app/workspace/workspace-content.tsx | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/apps/web/app/workspace/workspace-content.tsx b/apps/web/app/workspace/workspace-content.tsx index 0cdcd94b392..cbe4ac66392 100644 --- a/apps/web/app/workspace/workspace-content.tsx +++ b/apps/web/app/workspace/workspace-content.tsx @@ -365,6 +365,10 @@ function WorkspacePageInner() { const searchParams = useSearchParams(); const router = useRouter(); const initialPathHandled = useRef(false); + // Counts how many renders have happened since hydration completed. + // The URL sync effect skips render 0 (the same render where hydration ran) + // because React state (activePath, etc.) hasn't updated yet. + const rendersSinceHydration = useRef(-1); const lastPushedQs = useRef(null); // Chat panel ref for session management @@ -1165,6 +1169,13 @@ function WorkspacePageInner() { useEffect(() => { if (!initialPathHandled.current) return; + // Skip the render where hydration just ran — React state (activePath, etc.) + // hasn't updated yet, so we'd compute empty params and wipe the URL. + if (rendersSinceHydration.current === 0) { + rendersSinceHydration.current = 1; + return; + } + const current = new URLSearchParams(window.location.search); const params = new URLSearchParams(); @@ -1235,6 +1246,7 @@ function WorkspacePageInner() { useEffect(() => { if (initialPathHandled.current || treeLoading || tree.length === 0) return; + rendersSinceHydration.current = 0; const urlState = parseUrlState(searchParams); if (urlState.path) {