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 (