From e641472b07ba990322384939cb455d6f3d3bc2f7 Mon Sep 17 00:00:00 2001 From: Mohamed Boudra Date: Sat, 11 Apr 2026 09:51:33 +0000 Subject: [PATCH] fix(app): use history.replaceState to strip ?open since Expo Router skips query-only changes Expo Router's findDivergentState ignores search params, so router.replace with the same pathname but without ?open is a no-op. Use window.history.replaceState on web to directly strip the query param, and track intent consumption in component state so WorkspaceScreen renders once the tab is prepared. Co-Authored-By: Claude Opus 4.6 --- .../workspace/[workspaceId]/_layout.tsx | 46 +++++++++++-------- 1 file changed, 26 insertions(+), 20 deletions(-) diff --git a/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/_layout.tsx b/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/_layout.tsx index 6d0830f56..82c6ddb45 100644 --- a/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/_layout.tsx +++ b/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/_layout.tsx @@ -1,14 +1,9 @@ -import { useEffect, useRef } from "react"; -import { - useGlobalSearchParams, - useLocalSearchParams, - useRootNavigationState, - useRouter, -} from "expo-router"; +import { useEffect, useRef, useState } from "react"; +import { useGlobalSearchParams, useLocalSearchParams, useRootNavigationState } from "expo-router"; +import { Platform } from "react-native"; import type { WorkspaceTabTarget } from "@/stores/workspace-tabs-store"; import { WorkspaceScreen } from "@/screens/workspace/workspace-screen"; import { - buildHostWorkspaceRoute, decodeWorkspaceIdFromPathSegment, parseWorkspaceOpenIntent, type WorkspaceOpenIntent, @@ -40,9 +35,9 @@ function getOpenIntentTarget(openIntent: WorkspaceOpenIntent): WorkspaceTabTarge } export default function HostWorkspaceLayout() { - const router = useRouter(); const rootNavigationState = useRootNavigationState(); const consumedIntentRef = useRef(null); + const [intentConsumed, setIntentConsumed] = useState(false); const params = useLocalSearchParams<{ serverId?: string | string[]; workspaceId?: string | string[]; @@ -72,19 +67,30 @@ export default function HostWorkspaceLayout() { consumedIntentRef.current = consumptionKey; const openIntent = parseWorkspaceOpenIntent(openValue); - const route = openIntent - ? prepareWorkspaceTab({ - serverId, - workspaceId, - target: getOpenIntentTarget(openIntent), - pin: openIntent.kind === "agent", - }) - : buildHostWorkspaceRoute(serverId, workspaceId); + if (openIntent) { + prepareWorkspaceTab({ + serverId, + workspaceId, + target: getOpenIntentTarget(openIntent), + pin: openIntent.kind === "agent", + }); + } - router.replace(route as any); - }, [openValue, rootNavigationState?.key, router, serverId, workspaceId]); + // Expo Router's replace ignores query-param-only changes (findDivergentState + // skips search params). Strip ?open from the browser URL directly so the + // address bar reflects the clean workspace route. + if (Platform.OS === "web" && typeof window !== "undefined") { + const url = new URL(window.location.href); + if (url.searchParams.has("open")) { + url.searchParams.delete("open"); + window.history.replaceState(null, "", url.toString()); + } + } - if (openValue) { + setIntentConsumed(true); + }, [openValue, rootNavigationState?.key, serverId, workspaceId]); + + if (openValue && !intentConsumed) { return null; }