From 22d2509d0e6fb05c737864e568f0990ef5e63d46 Mon Sep 17 00:00:00 2001 From: James Magahern Date: Sun, 15 Feb 2026 22:57:10 -0800 Subject: [PATCH] url and history --- web/src/App.tsx | 64 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 63 insertions(+), 1 deletion(-) diff --git a/web/src/App.tsx b/web/src/App.tsx index 8c2650b..e03b9e0 100644 --- a/web/src/App.tsx +++ b/web/src/App.tsx @@ -48,6 +48,32 @@ type ContextMenuState = { y: number; }; +function readSidebarSelectionFromUrl(): SidebarSelection | null { + if (typeof window === "undefined") return null; + const params = new URLSearchParams(window.location.search); + const chatId = params.get("chat")?.trim(); + if (chatId) { + return { kind: "chat", id: chatId }; + } + const searchId = params.get("search")?.trim(); + if (searchId) { + return { kind: "search", id: searchId }; + } + return null; +} + +function buildWorkspaceUrl(selection: SidebarSelection | null) { + if (typeof window === "undefined") return "/"; + const params = new URLSearchParams(window.location.search); + params.delete("chat"); + params.delete("search"); + if (selection) { + params.set(selection.kind === "chat" ? "chat" : "search", selection.id); + } + const query = params.toString(); + return `${window.location.pathname}${query ? `?${query}` : ""}`; +} + const PROVIDER_FALLBACK_MODELS: Record = { openai: ["gpt-4.1-mini"], anthropic: ["claude-3-5-sonnet-latest"], @@ -302,6 +328,8 @@ export default function App() { const searchRunCounterRef = useRef(0); const [contextMenu, setContextMenu] = useState(null); const [isMobileSidebarOpen, setIsMobileSidebarOpen] = useState(false); + const initialRouteSelectionRef = useRef(readSidebarSelectionFromUrl()); + const hasSyncedSelectionHistoryRef = useRef(false); useEffect(() => { if (typeof document === "undefined") return; @@ -417,9 +445,43 @@ export default function App() { useEffect(() => { if (!isAuthenticated) return; - void Promise.all([refreshCollections(), refreshModels()]); + const preferredSelection = initialRouteSelectionRef.current; + initialRouteSelectionRef.current = null; + void Promise.all([refreshCollections(preferredSelection ?? undefined), refreshModels()]); }, [isAuthenticated]); + useEffect(() => { + const onPopState = () => { + setContextMenu(null); + setDraftKind(null); + setSelectedItem(readSidebarSelectionFromUrl()); + setIsMobileSidebarOpen(false); + }; + window.addEventListener("popstate", onPopState); + return () => window.removeEventListener("popstate", onPopState); + }, []); + + useEffect(() => { + if (!isAuthenticated) { + hasSyncedSelectionHistoryRef.current = false; + return; + } + const current = `${window.location.pathname}${window.location.search}`; + const next = buildWorkspaceUrl(selectedItem); + + if (!hasSyncedSelectionHistoryRef.current) { + hasSyncedSelectionHistoryRef.current = true; + if (current !== next) { + window.history.replaceState({}, "", next); + } + return; + } + + if (current !== next) { + window.history.pushState({}, "", next); + } + }, [isAuthenticated, selectedItem]); + const providerModelOptions = useMemo(() => getModelOptions(modelCatalog, provider), [modelCatalog, provider]); useEffect(() => {