diff --git a/frontend/src/components/app-sidebar.tsx b/frontend/src/components/app-sidebar.tsx index 3118201c..d4a15d2e 100644 --- a/frontend/src/components/app-sidebar.tsx +++ b/frontend/src/components/app-sidebar.tsx @@ -18,9 +18,10 @@ import { UserRound, Users, } from "lucide-react"; -import { usePathname, useRouter } from "next/navigation"; +import { usePathname } from "next/navigation"; import { useEffect, useRef } from "react"; import type { Ref } from "react"; +import { persistLoginSessionReturn } from "@/lib/chat-session-url"; import { Sidebar, SidebarContent, @@ -102,7 +103,7 @@ export function AppSidebar({ sessionControls, onAccountMenuOpenChange, onNewChat, - onOpenReports, + onOpenReports: _onOpenReports, onSelectSession, onSelectChart, onAddChart, @@ -113,7 +114,6 @@ export function AppSidebar({ onOpenLogout, }: AppSidebarProps) { const { isMobile, setOpen, setOpenMobile, state, viewport } = useSidebar(); - const router = useRouter(); const pathname = usePathname(); const firstSessionRef = useRef(null); const historyHeadingRef = useRef(null); @@ -149,18 +149,23 @@ export function AppSidebar({ if (isMobile) setOpenMobile(false); } + function leaveChat(path: "/chart" | "/ephemeris" | "/reports") { + persistLoginSessionReturn(); + window.location.assign(path); + } + function handleOpenReports() { - onOpenReports(); + leaveChat("/reports"); if (isMobile) setOpenMobile(false); } function handleOpenChart() { - router.push("/chart"); + leaveChat("/chart"); if (isMobile) setOpenMobile(false); } function handleOpenEphemeris() { - router.push("/ephemeris"); + leaveChat("/ephemeris"); if (isMobile) setOpenMobile(false); } diff --git a/frontend/tests/chat-navigation-a11y-contract.test.ts b/frontend/tests/chat-navigation-a11y-contract.test.ts index f4e78b78..a49c9a2a 100644 --- a/frontend/tests/chat-navigation-a11y-contract.test.ts +++ b/frontend/tests/chat-navigation-a11y-contract.test.ts @@ -27,15 +27,15 @@ test("in-app destinations navigate client-side so the chat survives the round tr assert.match(pageSource, /const router = useRouter\(\)/); // 原值: 4 条 membershipHref 软跳 /membership - // 新值: 账单入口改 openAccountDialog("billing");报告仍 router.push - // 原因: 删除 /membership 整页,对话状态不丢 + // 新值: 账单入口改 openAccountDialog("billing");离开对话的星盘/星历/报告走 document load + // 原因: 对话用 history.pushState 写 ?c=,App Router 的 router.push 无法离开首页 assert.match(pageSource, /onOpenReports=\{\(\) => router\.push\("\/reports"\)\}/); assert.match(pageSource, /onOpenBilling=\{\(\) => openAccountDialog\("billing", \{ source: "account-menu" \}\)\}/); assert.match(pageSource, /openAccountDialog\("billing", \{ returnTarget: event\.currentTarget, source: "credits" \}\)/); assert.match(pageSource, /openAccountDialog\("billing", \{ source: "insufficient-credits" \}\)/); assert.match(pageSource, /if \(response\.status === 402\) openAccountDialog\("billing", \{ source: "insufficient-credits" \}\)/); - // And: none of them fall back to a document load. + // And: membership stays in-page; leaving chat uses a document load in the sidebar. assert.doesNotMatch(pageSource, /window\.location\.assign\("\/reports"\)/); assert.doesNotMatch(pageSource, /window\.location\.assign\(membershipHref\(/); }); diff --git a/frontend/tests/sidebar-contract.test.ts b/frontend/tests/sidebar-contract.test.ts index 261de1da..adee2e65 100644 --- a/frontend/tests/sidebar-contract.test.ts +++ b/frontend/tests/sidebar-contract.test.ts @@ -185,6 +185,18 @@ test("keeps the sidebar brand row free of a duplicate collapse trigger", () => { assert.doesNotMatch(appSidebar, /SidebarTrigger/); }); +test("leaves the chat document for chart, ephemeris, and reports", () => { + const appSidebar = readProjectFile("src/components/app-sidebar.tsx"); + // 对话首页用 history.pushState 维护 ?c=,App Router 的 router.push 无法离开。 + assert.match(appSidebar, /persistLoginSessionReturn\(\);\s*window\.location\.assign\(path\)/); + assert.match(appSidebar, /leaveChat\("\/chart"\)/); + assert.match(appSidebar, /leaveChat\("\/ephemeris"\)/); + assert.match(appSidebar, /leaveChat\("\/reports"\)/); + assert.doesNotMatch(appSidebar, /router\.push\("\/chart"\)/); + assert.doesNotMatch(appSidebar, /router\.push\("\/ephemeris"\)/); + assert.doesNotMatch(appSidebar, /useRouter/); +}); + test("uses one collapsed history action instead of icon-only session rows", () => { const appSidebar = readProjectFile("src/components/app-sidebar.tsx"); assert.match(appSidebar, /MessageSquareText/);