diff --git a/apps/web/src/components/AppSidebarLayout.tsx b/apps/web/src/components/AppSidebarLayout.tsx index 97c0bd44eb96..0cf965c4bd22 100644 --- a/apps/web/src/components/AppSidebarLayout.tsx +++ b/apps/web/src/components/AppSidebarLayout.tsx @@ -35,6 +35,7 @@ import LegacyThreadSidebar from "./LegacySidebar"; import ThreadSidebar from "./Sidebar"; import { SettingsSidebarNav } from "./settings/SettingsSidebarNav"; import { SidebarChromeHeader } from "./sidebar/SidebarChrome"; +import { MainAppLocationTracker } from "./sidebar/mainAppLocation"; import { useSidebarStageBackdropVariant } from "./SidebarStageBackdrop"; import { useProjects } from "../state/entities"; import { @@ -321,6 +322,7 @@ export function AppSidebarLayout({ children }: { children: ReactNode }) { {children} + ); diff --git a/apps/web/src/components/sidebar/SidebarChrome.tsx b/apps/web/src/components/sidebar/SidebarChrome.tsx index a37aba27b8de..af4bd8a1f5cb 100644 --- a/apps/web/src/components/sidebar/SidebarChrome.tsx +++ b/apps/web/src/components/sidebar/SidebarChrome.tsx @@ -1,7 +1,7 @@ import { ArrowLeftIcon, ChartNoAxesColumnIcon, SettingsIcon } from "lucide-react"; import type { ReactNode } from "react"; import { memo, useCallback } from "react"; -import { Link, useCanGoBack, useLocation, useNavigate } from "@tanstack/react-router"; +import { Link, useLocation, useNavigate } from "@tanstack/react-router"; import { useEnvironmentIdentificationMode } from "../../hooks/useSettings"; import { cn } from "../../lib/utils"; @@ -24,6 +24,7 @@ import { } from "../ui/sidebar"; import { Tooltip, TooltipPopup, TooltipTrigger } from "../ui/tooltip"; import { readPullRequestListPreferences } from "../pullRequest/pullRequestListPreferences"; +import { isSidebarUtilityPage, useNavigateToMainApp } from "./mainAppLocation"; import { SidebarThreadUndoNotice } from "./SidebarThreadUndoNotice"; import { SidebarProviderUpdatePill } from "./SidebarProviderUpdatePill"; import { SidebarUpdateArchitectureWarning, SidebarUpdatePill } from "./SidebarUpdatePill"; @@ -127,19 +128,10 @@ function SidebarUtilityItem({ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { const navigate = useNavigate(); - const canGoBack = useCanGoBack(); + const navigateToMainApp = useNavigateToMainApp(); const { isMobile, setOpenMobile } = useSidebar(); - const currentFooterPage = useLocation({ - select: (location) => - /^\/settings(?:\/|$)/.test(location.pathname) - ? "settings" - : /^\/projects\/[^/]+\/?$/.test(location.pathname) - ? "project-settings" - : location.pathname === "/usage" - ? "usage" - : location.pathname === "/pull-requests" - ? "pull-requests" - : null, + const isOnUtilityPage = useLocation({ + select: (location) => isSidebarUtilityPage(location.pathname), }); const { environments } = useEnvironments(); // The page reads every connected server, so one of them offering pull requests is enough for @@ -173,16 +165,12 @@ export const SidebarUtilityMenu = memo(function SidebarUtilityMenu() { const handleBackClick = useCallback(() => { closeMobileSidebar(); - if (canGoBack) { - window.history.back(); - return; - } - void navigate({ to: "/" }); - }, [canGoBack, closeMobileSidebar, navigate]); + void navigateToMainApp(); + }, [closeMobileSidebar, navigateToMainApp]); return ( - {currentFooterPage ? ( + {isOnUtilityPage ? ( diff --git a/apps/web/src/components/sidebar/mainAppLocation.ts b/apps/web/src/components/sidebar/mainAppLocation.ts new file mode 100644 index 000000000000..fcbf2f489d33 --- /dev/null +++ b/apps/web/src/components/sidebar/mainAppLocation.ts @@ -0,0 +1,36 @@ +import { useLocation, useNavigate } from "@tanstack/react-router"; +import { useCallback, useEffect } from "react"; + +// Settings, Usage, and Pull Requests replace the sidebar utility row with a +// Back button. Everything else is the main app. Legacy `/projects/` links +// redirect into settings, so they count too and are never remembered. +export function isSidebarUtilityPage(pathname: string) { + return ( + pathname === "/settings" || + pathname.startsWith("/settings/") || + pathname.startsWith("/projects/") || + pathname === "/usage" || + pathname === "/pull-requests" + ); +} + +let mainAppHref: string | null = null; + +// Mount once in the app shell. Records the latest main app URL so Back can +// return there no matter how many utility pages were visited since. +export function MainAppLocationTracker() { + const href = useLocation({ + select: (location) => (isSidebarUtilityPage(location.pathname) ? null : location.href), + }); + useEffect(() => { + if (href !== null) mainAppHref = href; + }, [href]); + return null; +} + +// Leaves a utility page for the last main app URL, or the thread list when +// the app was opened directly on a utility page. +export function useNavigateToMainApp() { + const navigate = useNavigate(); + return useCallback(() => navigate({ href: mainAppHref ?? "/" }), [navigate]); +} diff --git a/apps/web/src/routes/settings.tsx b/apps/web/src/routes/settings.tsx index 5fc6d63f20f6..d529148e17b9 100644 --- a/apps/web/src/routes/settings.tsx +++ b/apps/web/src/routes/settings.tsx @@ -1,18 +1,12 @@ -import { - Outlet, - createFileRoute, - redirect, - useCanGoBack, - useLocation, - useNavigate, -} from "@tanstack/react-router"; -import { useCallback, useEffect, useState, type ReactNode } from "react"; +import { Outlet, createFileRoute, redirect, useLocation } from "@tanstack/react-router"; +import { useEffect, useState, type ReactNode } from "react"; import { RotateCcwIcon } from "lucide-react"; import { Button } from "../components/ui/button"; import { useSettingsRestore } from "../components/settings/SettingsPanels"; import { SettingsBreadcrumb } from "../components/settings/SettingsBreadcrumb"; import { SidebarInset } from "../components/ui/sidebar"; +import { useNavigateToMainApp } from "../components/sidebar/mainAppLocation"; import { WorkspacePageHeader } from "../components/WorkspacePageHeader"; import { isElectron } from "../env"; import { @@ -117,17 +111,9 @@ function SettingsScopeBoundary({ pathname, children }: { pathname: string; child function SettingsContentLayout() { const location = useLocation(); - const navigate = useNavigate(); - const canGoBack = useCanGoBack(); + const navigateToMainApp = useNavigateToMainApp(); const { search } = useSettingsScope(); const [restoreSignal, setRestoreSignal] = useState(0); - const navigateBackWithinApp = useCallback(() => { - if (canGoBack) { - window.history.back(); - return; - } - void navigate({ to: "/" }); - }, [canGoBack, navigate]); useEffect(() => { const onKeyDown = (event: KeyboardEvent) => { @@ -140,7 +126,7 @@ function SettingsContentLayout() { activeElement.blur(); } - navigateBackWithinApp(); + void navigateToMainApp(); } }; @@ -148,7 +134,7 @@ function SettingsContentLayout() { return () => { window.removeEventListener("keydown", onKeyDown); }; - }, [navigateBackWithinApp]); + }, [navigateToMainApp]); return (