Fix duplicate workspace shell navigation

This commit is contained in:
Mohamed Boudra
2026-05-05 21:55:57 +07:00
parent 60531b5e1b
commit 02e5b56408
12 changed files with 96 additions and 62 deletions

View File

@@ -36,6 +36,7 @@ import {
workspaceDeckEntryLocator, workspaceDeckEntryLocator,
expectWorkspaceDeckEntryCount, expectWorkspaceDeckEntryCount,
} from "./helpers/workspace-ui"; } from "./helpers/workspace-ui";
import { clickSettingsBackToWorkspace } from "./helpers/settings";
const LOADING_WORKSPACE_TEXT_PATTERN = /Loading workspace/i; const LOADING_WORKSPACE_TEXT_PATTERN = /Loading workspace/i;
@@ -75,6 +76,18 @@ async function expectNoLoadingPane(page: Page): Promise<void> {
await expect(page.getByText(LOADING_WORKSPACE_TEXT_PATTERN)).toHaveCount(0); await expect(page.getByText(LOADING_WORKSPACE_TEXT_PATTERN)).toHaveCount(0);
} }
async function getVisibleDraftTabCount(page: Page): Promise<number> {
return page.locator('[data-testid^="workspace-tab-draft"]').filter({ visible: true }).count();
}
async function closeFirstVisibleDraftTab(page: Page): Promise<void> {
const closeButton = page.locator('[data-testid^="workspace-draft-close-"]').filter({
visible: true,
});
await expect(closeButton.first()).toBeVisible({ timeout: 30_000 });
await closeButton.first().click();
}
async function installDaemonWebSocketGate(page: Page, daemonPort: string) { async function installDaemonWebSocketGate(page: Page, daemonPort: string) {
let acceptingConnections = true; let acceptingConnections = true;
const activeSockets = new Set<WebSocketRoute>(); const activeSockets = new Set<WebSocketRoute>();
@@ -133,6 +146,25 @@ async function installDaemonWebSocketGate(page: Page, daemonPort: string) {
test.describe("Workspace navigation regression", () => { test.describe("Workspace navigation regression", () => {
test.describe.configure({ timeout: 240_000 }); test.describe.configure({ timeout: 240_000 });
test("keeps one replacement draft after returning from settings and closing the last tab", async ({
page,
withWorkspace,
}) => {
const workspace = await withWorkspace({ prefix: "workspace-settings-back-tab-" });
await workspace.navigateTo();
await expect.poll(() => getVisibleDraftTabCount(page), { timeout: 30_000 }).toBe(1);
await openSettings(page);
await clickSettingsBackToWorkspace(page);
await expect(page).toHaveURL(/\/workspace\//, { timeout: 30_000 });
await expect.poll(() => getVisibleDraftTabCount(page), { timeout: 30_000 }).toBe(1);
await closeFirstVisibleDraftTab(page);
await expect.poll(() => getVisibleDraftTabCount(page), { timeout: 30_000 }).toBe(1);
});
test("keeps the workspace rendered while reconnecting to the host", async ({ page }) => { test("keeps the workspace rendered while reconnecting to the host", async ({ page }) => {
const serverId = process.env.E2E_SERVER_ID; const serverId = process.env.E2E_SERVER_ID;
const daemonPort = process.env.E2E_DAEMON_PORT; const daemonPort = process.env.E2E_DAEMON_PORT;

View File

@@ -1,12 +1,12 @@
import { useEffect, useRef } from "react"; import { useEffect, useRef } from "react";
import { useLocalSearchParams, useRouter, type Href } from "expo-router"; import { useLocalSearchParams, usePathname, useRouter, type Href } from "expo-router";
import { HostRouteBootstrapBoundary } from "@/components/host-route-bootstrap-boundary"; import { HostRouteBootstrapBoundary } from "@/components/host-route-bootstrap-boundary";
import { useSessionStore } from "@/stores/session-store"; import { useSessionStore } from "@/stores/session-store";
import { useResolveWorkspaceIdByCwd } from "@/stores/session-store-hooks"; import { useResolveWorkspaceIdByCwd } from "@/stores/session-store-hooks";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime"; import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import { buildHostRootRoute } from "@/utils/host-routes"; import { buildHostRootRoute } from "@/utils/host-routes";
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution"; import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
import { prepareWorkspaceTab } from "@/utils/workspace-navigation"; import { navigateToPreparedWorkspaceTab } from "@/utils/workspace-navigation";
export default function HostAgentReadyRoute() { export default function HostAgentReadyRoute() {
return ( return (
@@ -18,6 +18,7 @@ export default function HostAgentReadyRoute() {
function HostAgentReadyRouteContent() { function HostAgentReadyRouteContent() {
const router = useRouter(); const router = useRouter();
const pathname = usePathname();
const params = useLocalSearchParams<{ const params = useLocalSearchParams<{
serverId?: string; serverId?: string;
agentId?: string; agentId?: string;
@@ -50,15 +51,15 @@ function HostAgentReadyRouteContent() {
if (resolvedWorkspaceId) { if (resolvedWorkspaceId) {
redirectedRef.current = true; redirectedRef.current = true;
router.replace( navigateToPreparedWorkspaceTab({
prepareWorkspaceTab({ serverId,
serverId, workspaceId: resolvedWorkspaceId,
workspaceId: resolvedWorkspaceId, target: { kind: "agent", agentId },
target: { kind: "agent", agentId }, navigationMethod: "replace",
}) as Href, currentPathname: pathname,
); });
} }
}, [agentId, resolvedWorkspaceId, router, serverId]); }, [agentId, pathname, resolvedWorkspaceId, router, serverId]);
useEffect(() => { useEffect(() => {
if (redirectedRef.current) { if (redirectedRef.current) {
@@ -102,13 +103,13 @@ function HostAgentReadyRouteContent() {
} }
redirectedRef.current = true; redirectedRef.current = true;
if (workspaceId) { if (workspaceId) {
router.replace( navigateToPreparedWorkspaceTab({
prepareWorkspaceTab({ serverId,
serverId, workspaceId,
workspaceId, target: { kind: "agent", agentId },
target: { kind: "agent", agentId }, navigationMethod: "replace",
}) as Href, currentPathname: pathname,
); });
return; return;
} }
router.replace(buildHostRootRoute(serverId)); router.replace(buildHostRootRoute(serverId));
@@ -125,7 +126,7 @@ function HostAgentReadyRouteContent() {
return () => { return () => {
cancelled = true; cancelled = true;
}; };
}, [agentId, client, hasHydratedWorkspaces, isConnected, router, serverId]); }, [agentId, client, hasHydratedWorkspaces, isConnected, pathname, router, serverId]);
return null; return null;
} }

View File

@@ -21,7 +21,7 @@ import { Archive } from "lucide-react-native";
import { getProviderIcon } from "@/components/provider-icons"; import { getProviderIcon } from "@/components/provider-icons";
import { buildHostAgentDetailRoute } from "@/utils/host-routes"; import { buildHostAgentDetailRoute } from "@/utils/host-routes";
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution"; import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
import { prepareWorkspaceTab } from "@/utils/workspace-navigation"; import { navigateToPreparedWorkspaceTab } from "@/utils/workspace-navigation";
import type { Agent } from "@/stores/session-store"; import type { Agent } from "@/stores/session-store";
import { useArchiveAgent } from "@/hooks/use-archive-agent"; import { useArchiveAgent } from "@/hooks/use-archive-agent";
@@ -324,13 +324,12 @@ export function AgentList({
} }
rememberArchivedAgentDetail(agent); rememberArchivedAgentDetail(agent);
const route = prepareWorkspaceTab({ navigateToPreparedWorkspaceTab({
serverId, serverId,
workspaceId, workspaceId,
target: { kind: "agent", agentId }, target: { kind: "agent", agentId },
pin: Boolean(agent.archivedAt), pin: Boolean(agent.archivedAt),
}); });
router.navigate(route);
}, },
[isActionSheetVisible, onAgentSelect], [isActionSheetVisible, onAgentSelect],
); );

View File

@@ -21,7 +21,6 @@ import {
import { StyleSheet, withUnistyles } from "react-native-unistyles"; import { StyleSheet, withUnistyles } from "react-native-unistyles";
import { useIsCompactFormFactor } from "@/constants/layout"; import { useIsCompactFormFactor } from "@/constants/layout";
import { useMutation } from "@tanstack/react-query"; import { useMutation } from "@tanstack/react-query";
import { useRouter } from "expo-router";
import Animated, { import Animated, {
FadeIn, FadeIn,
FadeOut, FadeOut,
@@ -83,7 +82,7 @@ import {
import { MAX_CONTENT_WIDTH } from "@/constants/layout"; import { MAX_CONTENT_WIDTH } from "@/constants/layout";
import { normalizeInlinePathTarget } from "@/utils/inline-path"; import { normalizeInlinePathTarget } from "@/utils/inline-path";
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution"; import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
import { prepareWorkspaceTab } from "@/utils/workspace-navigation"; import { navigateToPreparedWorkspaceTab } from "@/utils/workspace-navigation";
import { useStableEvent } from "@/hooks/use-stable-event"; import { useStableEvent } from "@/hooks/use-stable-event";
import { import {
getWorkingIndicatorDotStrength, getWorkingIndicatorDotStrength,
@@ -135,7 +134,6 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
ref, ref,
) { ) {
const viewportRef = useRef<StreamViewportHandle | null>(null); const viewportRef = useRef<StreamViewportHandle | null>(null);
const router = useRouter();
const isMobile = useIsCompactFormFactor(); const isMobile = useIsCompactFormFactor();
const streamRenderStrategy = useMemo( const streamRenderStrategy = useMemo(
() => () =>
@@ -213,12 +211,11 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
} }
if (workspaceId) { if (workspaceId) {
const route = prepareWorkspaceTab({ navigateToPreparedWorkspaceTab({
serverId: resolvedServerId, serverId: resolvedServerId,
workspaceId, workspaceId,
target: { kind: "file", path: normalized.file }, target: { kind: "file", path: normalized.file },
}); });
router.navigate(route);
} }
return; return;
} }
@@ -247,7 +244,6 @@ const AgentStreamViewComponent = forwardRef<AgentStreamViewHandle, AgentStreamVi
onOpenWorkspaceFile, onOpenWorkspaceFile,
requestDirectoryListing, requestDirectoryListing,
resolvedServerId, resolvedServerId,
router,
setExplorerTabForCheckout, setExplorerTabForCheckout,
openWorkspaceFile, openWorkspaceFile,
workspaceId, workspaceId,

View File

@@ -19,7 +19,7 @@ import { useKeyboardShortcutOverrides } from "@/hooks/use-keyboard-shortcut-over
import { getShortcutOs } from "@/utils/shortcut-platform"; import { getShortcutOs } from "@/utils/shortcut-platform";
import { getIsElectronRuntime } from "@/constants/layout"; import { getIsElectronRuntime } from "@/constants/layout";
import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution"; import { resolveWorkspaceIdByExecutionDirectory } from "@/utils/workspace-execution";
import { prepareWorkspaceTab } from "@/utils/workspace-navigation"; import { navigateToPreparedWorkspaceTab } from "@/utils/workspace-navigation";
import { focusWithRetries } from "@/utils/web-focus"; import { focusWithRetries } from "@/utils/web-focus";
import { useActiveServerId } from "@/hooks/use-active-server-id"; import { useActiveServerId } from "@/hooks/use-active-server-id";
@@ -123,7 +123,7 @@ function resolveActionShortcutKeys(
} }
export function useCommandCenter() { export function useCommandCenter() {
const _pathname = usePathname(); const pathname = usePathname();
const routeActiveServerId = useActiveServerId(); const routeActiveServerId = useActiveServerId();
const { overrides } = useKeyboardShortcutOverrides(); const { overrides } = useKeyboardShortcutOverrides();
const open = useKeyboardShortcutsStore((s) => s.commandCenterOpen); const open = useKeyboardShortcutsStore((s) => s.commandCenterOpen);
@@ -212,14 +212,14 @@ export function useCommandCenter() {
router.navigate(buildHostAgentDetailRoute(agent.serverId, agent.id) as Href); router.navigate(buildHostAgentDetailRoute(agent.serverId, agent.id) as Href);
return; return;
} }
const route = prepareWorkspaceTab({ navigateToPreparedWorkspaceTab({
serverId: agent.serverId, serverId: agent.serverId,
workspaceId, workspaceId,
target: { kind: "agent", agentId: agent.id }, target: { kind: "agent", agentId: agent.id },
currentPathname: pathname,
}); });
router.navigate(route);
}, },
[setOpen], [pathname, setOpen],
); );
const openProjectPicker = useOpenProjectPicker(activeServerId); const openProjectPicker = useOpenProjectPicker(activeServerId);

View File

@@ -1,5 +1,9 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
vi.hoisted(() => {
(globalThis as typeof globalThis & { __DEV__?: boolean }).__DEV__ = false;
});
vi.mock("@react-native-async-storage/async-storage", () => { vi.mock("@react-native-async-storage/async-storage", () => {
const storage = new Map<string, string>(); const storage = new Map<string, string>();
return { return {
@@ -19,10 +23,8 @@ const { replaceRoute } = vi.hoisted(() => ({
replaceRoute: vi.fn(), replaceRoute: vi.fn(),
})); }));
vi.mock("expo-router", () => ({ vi.mock("@/hooks/use-workspace-navigation", () => ({
router: { navigateToWorkspace: replaceRoute,
replace: replaceRoute,
},
})); }));
import { openProjectDirectly } from "@/hooks/use-open-project"; import { openProjectDirectly } from "@/hooks/use-open-project";
@@ -89,7 +91,7 @@ describe("openProjectDirectly", () => {
mergeWorkspaces: useSessionStore.getState().mergeWorkspaces, mergeWorkspaces: useSessionStore.getState().mergeWorkspaces,
setHasHydratedWorkspaces: useSessionStore.getState().setHasHydratedWorkspaces, setHasHydratedWorkspaces: useSessionStore.getState().setHasHydratedWorkspaces,
openDraftTab: createOpenDraftTab(), openDraftTab: createOpenDraftTab(),
replaceRoute, navigateToWorkspace: replaceRoute,
}); });
expect(result).toBe(true); expect(result).toBe(true);
@@ -115,7 +117,7 @@ describe("openProjectDirectly", () => {
const tabs = collectAllTabs(layout.root); const tabs = collectAllTabs(layout.root);
expect(tabs).toHaveLength(1); expect(tabs).toHaveLength(1);
expect(tabs[0]?.target.kind).toBe("draft"); expect(tabs[0]?.target.kind).toBe("draft");
expect(replaceRoute).toHaveBeenCalledWith("/h/server-1/workspace/1"); expect(replaceRoute).toHaveBeenCalledWith("server-1", "1");
}); });
it("does not navigate or seed tabs when openProject fails", async () => { it("does not navigate or seed tabs when openProject fails", async () => {
@@ -133,7 +135,7 @@ describe("openProjectDirectly", () => {
mergeWorkspaces: useSessionStore.getState().mergeWorkspaces, mergeWorkspaces: useSessionStore.getState().mergeWorkspaces,
setHasHydratedWorkspaces: useSessionStore.getState().setHasHydratedWorkspaces, setHasHydratedWorkspaces: useSessionStore.getState().setHasHydratedWorkspaces,
openDraftTab: createOpenDraftTab(), openDraftTab: createOpenDraftTab(),
replaceRoute, navigateToWorkspace: replaceRoute,
}); });
expect(result).toBe(false); expect(result).toBe(false);

View File

@@ -1,5 +1,4 @@
import { useCallback } from "react"; import { useCallback } from "react";
import { router, type Href } from "expo-router";
import type { DaemonClient } from "@server/client/daemon-client"; import type { DaemonClient } from "@server/client/daemon-client";
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime"; import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
import { import {
@@ -12,7 +11,7 @@ import {
useWorkspaceLayoutStore, useWorkspaceLayoutStore,
} from "@/stores/workspace-layout-store"; } from "@/stores/workspace-layout-store";
import { generateDraftId } from "@/stores/draft-keys"; import { generateDraftId } from "@/stores/draft-keys";
import { buildHostWorkspaceRoute } from "@/utils/host-routes"; import { navigateToWorkspace } from "@/hooks/use-workspace-navigation";
interface OpenProjectDirectlyInput { interface OpenProjectDirectlyInput {
serverId: string; serverId: string;
@@ -22,7 +21,7 @@ interface OpenProjectDirectlyInput {
mergeWorkspaces: (serverId: string, workspaces: Iterable<WorkspaceDescriptor>) => void; mergeWorkspaces: (serverId: string, workspaces: Iterable<WorkspaceDescriptor>) => void;
setHasHydratedWorkspaces: (serverId: string, hydrated: boolean) => void; setHasHydratedWorkspaces: (serverId: string, hydrated: boolean) => void;
openDraftTab: (workspaceKey: string) => string | null; openDraftTab: (workspaceKey: string) => string | null;
replaceRoute: (route: string) => void; navigateToWorkspace: (serverId: string, workspaceId: string) => void;
} }
export async function openProjectDirectly(input: OpenProjectDirectlyInput): Promise<boolean> { export async function openProjectDirectly(input: OpenProjectDirectlyInput): Promise<boolean> {
@@ -50,7 +49,7 @@ export async function openProjectDirectly(input: OpenProjectDirectlyInput): Prom
} }
input.openDraftTab(workspaceKey); input.openDraftTab(workspaceKey);
input.replaceRoute(buildHostWorkspaceRoute(normalizedServerId, workspace.id)); input.navigateToWorkspace(normalizedServerId, workspace.id);
return true; return true;
} }
@@ -75,9 +74,7 @@ export function useOpenProject(serverId: string | null): (path: string) => Promi
kind: "draft", kind: "draft",
draftId: generateDraftId(), draftId: generateDraftId(),
}), }),
replaceRoute: (route) => { navigateToWorkspace,
router.replace(route as Href);
},
}); });
}, },
[client, isConnected, mergeWorkspaces, normalizedServerId, setHasHydratedWorkspaces], [client, isConnected, mergeWorkspaces, normalizedServerId, setHasHydratedWorkspaces],

View File

@@ -1,12 +1,12 @@
import { beforeEach, describe, expect, it, vi } from "vitest"; import { beforeEach, describe, expect, it, vi } from "vitest";
const { navigateMock } = vi.hoisted(() => ({ const { dismissToMock } = vi.hoisted(() => ({
navigateMock: vi.fn(), dismissToMock: vi.fn(),
})); }));
vi.mock("expo-router", () => ({ vi.mock("expo-router", () => ({
router: { router: {
navigate: navigateMock, dismissTo: dismissToMock,
}, },
})); }));
@@ -19,11 +19,11 @@ import {
describe("navigateToWorkspace", () => { describe("navigateToWorkspace", () => {
beforeEach(() => { beforeEach(() => {
navigateMock.mockReset(); dismissToMock.mockReset();
syncNavigationActiveWorkspace({ current: null }); syncNavigationActiveWorkspace({ current: null });
}); });
it("uses router navigation from a non-workspace route even when active selection is stale", () => { it("dismisses to the workspace route from a non-workspace route even when active selection is stale", () => {
activateNavigationWorkspaceSelection({ activateNavigationWorkspaceSelection({
serverId: "server-1", serverId: "server-1",
workspaceId: "workspace-a", workspaceId: "workspace-a",
@@ -33,7 +33,7 @@ describe("navigateToWorkspace", () => {
currentPathname: "/h/server-1/sessions", currentPathname: "/h/server-1/sessions",
}); });
expect(navigateMock).toHaveBeenCalledWith("/h/server-1/workspace/workspace-b"); expect(dismissToMock).toHaveBeenCalledWith("/h/server-1/workspace/workspace-b");
expect(getNavigationActiveWorkspaceSelection()).toEqual({ expect(getNavigationActiveWorkspaceSelection()).toEqual({
serverId: "server-1", serverId: "server-1",
workspaceId: "workspace-a", workspaceId: "workspace-a",
@@ -50,7 +50,7 @@ describe("navigateToWorkspace", () => {
currentPathname: "/h/server-1/workspace/workspace-a", currentPathname: "/h/server-1/workspace/workspace-a",
}); });
expect(navigateMock).not.toHaveBeenCalled(); expect(dismissToMock).not.toHaveBeenCalled();
expect(getNavigationActiveWorkspaceSelection()).toEqual({ expect(getNavigationActiveWorkspaceSelection()).toEqual({
serverId: "server-1", serverId: "server-1",
workspaceId: "workspace-b", workspaceId: "workspace-b",

View File

@@ -19,7 +19,7 @@ function shouldUseRetainedWorkspaceSwitch(input: {
} }
if (input.currentPathname == null) { if (input.currentPathname == null) {
return true; return false;
} }
return parseHostWorkspaceRouteFromPathname(input.currentPathname) !== null; return parseHostWorkspaceRouteFromPathname(input.currentPathname) !== null;
@@ -49,7 +49,7 @@ export function navigateToWorkspace(
} }
const href = buildHostWorkspaceRoute(serverId, workspaceId); const href = buildHostWorkspaceRoute(serverId, workspaceId);
router.navigate(href); router.dismissTo(href);
} }
export function useWorkspaceNavigation() { export function useWorkspaceNavigation() {

View File

@@ -78,13 +78,13 @@ import { useIsCompactFormFactor } from "@/constants/layout";
import { useLocalDaemonServerId } from "@/hooks/use-is-local-daemon"; import { useLocalDaemonServerId } from "@/hooks/use-is-local-daemon";
import { import {
buildHostOpenProjectRoute, buildHostOpenProjectRoute,
buildHostWorkspaceRoute,
buildProjectsSettingsRoute, buildProjectsSettingsRoute,
buildSettingsHostRoute, buildSettingsHostRoute,
buildSettingsSectionRoute, buildSettingsSectionRoute,
type SettingsSectionSlug, type SettingsSectionSlug,
} from "@/utils/host-routes"; } from "@/utils/host-routes";
import { getLastNavigationWorkspaceRouteSelection } from "@/stores/navigation-active-workspace-store"; import { getLastNavigationWorkspaceRouteSelection } from "@/stores/navigation-active-workspace-store";
import { navigateToWorkspace } from "@/hooks/use-workspace-navigation";
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// View model // View model
@@ -963,9 +963,7 @@ export default function SettingsScreen({ view }: SettingsScreenProps) {
const handleBackToWorkspace = useCallback(() => { const handleBackToWorkspace = useCallback(() => {
const lastWorkspaceRoute = getLastNavigationWorkspaceRouteSelection(); const lastWorkspaceRoute = getLastNavigationWorkspaceRouteSelection();
if (lastWorkspaceRoute) { if (lastWorkspaceRoute) {
router.replace( navigateToWorkspace(lastWorkspaceRoute.serverId, lastWorkspaceRoute.workspaceId);
buildHostWorkspaceRoute(lastWorkspaceRoute.serverId, lastWorkspaceRoute.workspaceId),
);
return; return;
} }
if (anyOnlineServerId) { if (anyOnlineServerId) {

View File

@@ -8,6 +8,7 @@ const { platformState, routerMock } = vi.hoisted(() => ({
routerMock: { routerMock: {
back: vi.fn(), back: vi.fn(),
canGoBack: vi.fn(() => false), canGoBack: vi.fn(() => false),
dismissTo: vi.fn(),
navigate: vi.fn(), navigate: vi.fn(),
replace: vi.fn(), replace: vi.fn(),
}, },
@@ -60,6 +61,7 @@ describe("prepareWorkspaceTab", () => {
routerMock.back.mockReset(); routerMock.back.mockReset();
routerMock.canGoBack.mockReset(); routerMock.canGoBack.mockReset();
routerMock.canGoBack.mockReturnValue(false); routerMock.canGoBack.mockReturnValue(false);
routerMock.dismissTo.mockReset();
routerMock.navigate.mockReset(); routerMock.navigate.mockReset();
routerMock.replace.mockReset(); routerMock.replace.mockReset();
syncNavigationActiveWorkspace({ current: null }); syncNavigationActiveWorkspace({ current: null });
@@ -106,6 +108,7 @@ describe("prepareWorkspaceTab", () => {
expect(route).toBe("/h/server-1/workspace/b64_L3JlcG8vd29ya3RyZWU"); expect(route).toBe("/h/server-1/workspace/b64_L3JlcG8vd29ya3RyZWU");
expect(routerMock.back).toHaveBeenCalledOnce(); expect(routerMock.back).toHaveBeenCalledOnce();
expect(routerMock.dismissTo).not.toHaveBeenCalled();
expect(routerMock.replace).not.toHaveBeenCalled(); expect(routerMock.replace).not.toHaveBeenCalled();
vi.runAllTimers(); vi.runAllTimers();

View File

@@ -1,5 +1,6 @@
import { router, type Href } from "expo-router"; import { router } from "expo-router";
import { isNative } from "@/constants/platform"; import { isNative } from "@/constants/platform";
import { navigateToWorkspace } from "@/hooks/use-workspace-navigation";
import { import {
activateNavigationWorkspaceSelection, activateNavigationWorkspaceSelection,
getLastNavigationWorkspaceRouteSelection, getLastNavigationWorkspaceRouteSelection,
@@ -22,6 +23,7 @@ interface PrepareWorkspaceTabInput {
interface NavigateToPreparedWorkspaceTabInput extends PrepareWorkspaceTabInput { interface NavigateToPreparedWorkspaceTabInput extends PrepareWorkspaceTabInput {
navigationMethod?: "navigate" | "replace"; navigationMethod?: "navigate" | "replace";
currentPathname?: string | null;
} }
function getPreparedTarget(target: WorkspaceTabTarget): WorkspaceTabTarget { function getPreparedTarget(target: WorkspaceTabTarget): WorkspaceTabTarget {
@@ -65,9 +67,13 @@ export function navigateToPreparedWorkspaceTab(input: NavigateToPreparedWorkspac
}, 0); }, 0);
return route; return route;
} }
router.replace(route as Href); navigateToWorkspace(input.serverId, input.workspaceId, {
currentPathname: input.currentPathname,
});
} else { } else {
router.navigate(route as Href); navigateToWorkspace(input.serverId, input.workspaceId, {
currentPathname: input.currentPathname,
});
} }
return route; return route;
} }