From edb3b7e44674f1e04e34303be23c034915a2f3c5 Mon Sep 17 00:00:00 2001 From: chengyongru Date: Fri, 7 Aug 2026 14:56:23 +0800 Subject: [PATCH] fix(webui): preserve newly created topic route --- webui/src/App.tsx | 16 +++++++++++++--- webui/src/tests/app-layout.test.tsx | 17 +++++++++++++++++ 2 files changed, 30 insertions(+), 3 deletions(-) diff --git a/webui/src/App.tsx b/webui/src/App.tsx index bbcb0ae9d..7bbd54fc8 100644 --- a/webui/src/App.tsx +++ b/webui/src/App.tsx @@ -1004,6 +1004,7 @@ function Shell({ useState>({}); const runningChatIdsRef = useRef>(new Set()); const activeChatIdRef = useRef(null); + const pendingCreatedSessionKeyRef = useRef(null); const hostSidebarPreviewCloseTimerRef = useRef(null); const effectiveRuntimeSurface = settingsSnapshot?.surface ?? settingsSnapshot?.runtime_surface ?? runtimeSurface; @@ -1181,8 +1182,15 @@ function Shell({ }, [loading, sessions]); useEffect(() => { - if (loading || !activeKey) return; - if (sessions.some((session) => session.key === activeKey)) return; + if (loading) return; + const pendingCreatedKey = pendingCreatedSessionKeyRef.current; + if (pendingCreatedKey && sessions.some((session) => session.key === pendingCreatedKey)) { + pendingCreatedSessionKeyRef.current = null; + } + if (!activeKey || sessions.some((session) => session.key === activeKey)) return; + // WebKit can commit the route before useSessions' optimistic insert. + // Keep that just-created destination valid until the session list catches up. + if (pendingCreatedKey === activeKey) return; const currentRoute = readShellRoute(); navigate( currentRoute.view === "chat" @@ -1366,9 +1374,11 @@ function Shell({ try { const scope = workspaceScope ?? activeWorkspaceScope; const chatId = await createChat(scope); + const key = `websocket:${chatId}`; + pendingCreatedSessionKeyRef.current = key; navigate({ view: "chat", - activeKey: `websocket:${chatId}`, + activeKey: key, settingsSection: "overview", }); setMobileSidebarOpen(false); diff --git a/webui/src/tests/app-layout.test.tsx b/webui/src/tests/app-layout.test.tsx index 8ce7d818d..2b7dc7e2f 100644 --- a/webui/src/tests/app-layout.test.tsx +++ b/webui/src/tests/app-layout.test.tsx @@ -367,6 +367,23 @@ describe("App layout", () => { ); }); + it("keeps a just-created topic route while the session list catches up", async () => { + render(); + + await waitFor(() => expect(connectSpy).toHaveBeenCalled()); + fireEvent.change(screen.getByRole("textbox", { name: "Message input" }), { + target: { value: "/model" }, + }); + fireEvent.click(screen.getByRole("button", { name: "Send message" })); + + await waitFor(() => expect(createChatSpy).toHaveBeenCalledTimes(1)); + await waitFor(() => + expect(window.location.hash).toBe( + `#/chat/${encodeURIComponent("websocket:chat-1")}`, + ), + ); + }); + it("restores the Settings route after a restart fallback hash", async () => { localStorage.setItem("nanobot-webui.restartStartedAt", String(Date.now())); localStorage.setItem("nanobot-webui.restartRoute", "#/settings?section=channels");