- + {showBackToProjects ? : null} @@ -306,6 +324,8 @@ function ProjectSettingsBody({ client, onReload: handleReload, isHostGone, + onBackToProjects, + showBackToProjects, })} ); @@ -322,6 +342,8 @@ interface RenderContentInput { client: DaemonClient; onReload: () => void; isHostGone: boolean; + onBackToProjects: () => void; + showBackToProjects: boolean; } function renderContent({ @@ -335,6 +357,8 @@ function renderContent({ client, onReload, isHostGone, + onBackToProjects, + showBackToProjects, }: RenderContentInput) { if (readQuery.isLoading) { return ( @@ -353,7 +377,12 @@ function renderContent({ } if (isHostGone) { - return ; + return ( + + ); } if (!loadedConfig) { diff --git a/packages/app/src/screens/projects-screen.test.tsx b/packages/app/src/screens/projects-screen.test.tsx index ffb10d7f9..2dc170958 100644 --- a/packages/app/src/screens/projects-screen.test.tsx +++ b/packages/app/src/screens/projects-screen.test.tsx @@ -7,7 +7,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { ProjectHostEntry, ProjectSummary, WorkspaceSummary } from "@/utils/projects"; import type { ProjectHostError, UseProjectsResult } from "@/hooks/use-projects"; -const { theme, projectsState, navigate } = vi.hoisted(() => ({ +const { theme, projectsState, push } = vi.hoisted(() => ({ theme: { spacing: { 0: 0, 1: 4, "1.5": 6, 2: 8, 3: 12, 4: 16, 6: 24, 8: 32 }, iconSize: { sm: 14, md: 20 }, @@ -37,7 +37,7 @@ const { theme, projectsState, navigate } = vi.hoisted(() => ({ refetch: vi.fn(), } as UseProjectsResult, }, - navigate: vi.fn(), + push: vi.fn(), })); vi.mock("react-native", () => { @@ -125,7 +125,7 @@ vi.mock("lucide-react-native", () => { }); vi.mock("expo-router", () => ({ - router: { navigate }, + router: { push }, })); vi.mock("react-i18next", () => ({ @@ -281,7 +281,7 @@ describe("ProjectsScreen", () => { document.body.appendChild(container); root = createRoot(container); setProjectsState({}); - navigate.mockReset(); + push.mockReset(); }); afterEach(() => { @@ -334,8 +334,8 @@ describe("ProjectsScreen", () => { row.dispatchEvent(new window.MouseEvent("click", { bubbles: true })); }); - expect(navigate).toHaveBeenCalledTimes(1); - expect(navigate).toHaveBeenCalledWith("/settings/hosts/host-a/projects/project-a"); + expect(push).toHaveBeenCalledTimes(1); + expect(push).toHaveBeenCalledWith("/settings/hosts/host-a/projects/project-a"); }); it("does not render a kebab menu on the row", () => { diff --git a/packages/app/src/screens/projects-screen.tsx b/packages/app/src/screens/projects-screen.tsx index 81e1da742..146307475 100644 --- a/packages/app/src/screens/projects-screen.tsx +++ b/packages/app/src/screens/projects-screen.tsx @@ -1,6 +1,5 @@ import { useCallback, useMemo } from "react"; import { Pressable, Text, View, type PressableStateCallbackType } from "react-native"; -import { router } from "expo-router"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { ChevronRight } from "lucide-react-native"; import { useTranslation } from "react-i18next"; @@ -9,7 +8,7 @@ import { LoadingSpinner } from "@/components/ui/loading-spinner"; import { useProjects, type ProjectHostError } from "@/hooks/use-projects"; import { useProjectIcons } from "@/projects/icons"; import { settingsStyles } from "@/styles/settings"; -import { buildProjectSettingsRoute } from "@/utils/host-routes"; +import { openProjectSettings } from "@/navigation/settings-navigation"; import type { ProjectHostEntry, ProjectSummary } from "@/utils/projects"; interface ProjectsScreenProps { @@ -112,7 +111,7 @@ function ProjectRow({ project, host, isFirst, iconDataUri }: ProjectRowProps) { const { viewKey } = project; const { projectName } = host; const handleNavigate = useCallback(() => { - router.navigate(buildProjectSettingsRoute(host.serverId, host.projectId)); + openProjectSettings(host.serverId, host.projectId); }, [host.projectId, host.serverId]); const rowStyle = useCallback( diff --git a/packages/app/src/screens/settings-screen.tsx b/packages/app/src/screens/settings-screen.tsx index 8b27b5ab8..9bcd60071 100644 --- a/packages/app/src/screens/settings-screen.tsx +++ b/packages/app/src/screens/settings-screen.tsx @@ -121,27 +121,18 @@ import { useEnableBuiltInDaemonOption, } from "@/desktop/hooks/use-enable-built-in-daemon-option"; import { - buildOpenProjectRoute, buildSettingsHostSectionRoute, buildSettingsSectionRoute, type HostSectionSlug, type SettingsSectionSlug, } from "@/utils/host-routes"; -import { - navigateToLastWorkspace, - useLastWorkspaceSelection, -} from "@/stores/navigation-active-workspace-store"; +import { useLastWorkspaceSelection } from "@/stores/navigation-active-workspace-store"; +import { returnFromSettings, type SettingsView } from "@/navigation/settings-navigation"; // --------------------------------------------------------------------------- // View model // --------------------------------------------------------------------------- -export type SettingsView = - | { kind: "root" } - | { kind: "section"; section: SettingsSectionSlug } - | { kind: "host"; serverId: string; section: HostSectionSlug } - | { kind: "project"; serverId: string; projectId: string }; - interface SidebarSectionItem { id: SettingsSectionSlug; labelKey: string; @@ -1367,29 +1358,13 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti } }, [isCompactLayout, router]); - const handleBackToRoot = useCallback(() => { - if (router.canGoBack()) { - router.back(); - } else { - router.replace("/settings"); - } - }, [router]); - - const detailProjectServerId = view.kind === "project" ? view.serverId : null; const handleBackFromDetail = useCallback(() => { - if (detailProjectServerId) { - router.navigate(buildSettingsHostSectionRoute(detailProjectServerId, "projects")); - return; - } - handleBackToRoot(); - }, [detailProjectServerId, handleBackToRoot, router]); + returnFromSettings(view); + }, [view]); const handleBackToWorkspace = useCallback(() => { - if (navigateToLastWorkspace()) { - return; - } - router.replace(buildOpenProjectRoute()); - }, [router]); + returnFromSettings({ kind: "root" }); + }, []); const detailHeader = ((): { title: string; @@ -1417,7 +1392,14 @@ export default function SettingsScreen({ view, openAddHostIntent = null }: Setti return renderHostSettingsContent(view, handleHostRemoved); } if (view.kind === "project") { - return ; + return ( + + ); } if (view.kind === "section") { switch (view.section) {