Fix cold workspace URL sidebar navigation
Directly opening a workspace URL created a different React Navigation state shape than entering from /. Expo Router parsed the URL as a workspace layout route with a nested index child whose route.path and params pointed at the original workspace. Later sidebar navigation used router.dismissTo for another workspace. React Navigation's POP_TO reused the existing workspace route by name and updated the parent params, but preserved the nested child state. The result was a hybrid state: parent workspace params for B, child index path for A. Expo Router serialized the focused child path, so the address bar stayed on A and sidebar clicks looked like no-ops. The fix is to stop using workspace/[workspaceId]/_layout.tsx as the actual screen. That layout did not render a Slot, so it only added an extra navigation level for Expo Router to preserve. Move the workspace UI, bootstrap boundary, open-intent consumption, and retained WorkspaceDeck into the leaf index route, making /h/:serverId/workspace/:workspaceId one real rendered route. Keep workspace switches on navigateToWorkspace/router.dismissTo. This is not another replace workaround: replace previously created duplicate mounted workspace shells. The route-shape fix removes the stale nested child state that made dismissTo fail after cold deep links. Also route archive/new-agent and notification flows through the centralized workspace navigation path so they do not bypass the same route contract. Regression coverage: cold-load workspace A, click workspace B in the sidebar, and assert the URL changes to B. Verified with workspace-navigation-regression.spec.ts, settings-navigation.spec.ts, npm run typecheck, and npm run lint.
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
import { useState, useCallback, useEffect, useMemo, type ReactElement } from "react";
|
||||
import { useRouter } from "expo-router";
|
||||
import AsyncStorage from "@react-native-async-storage/async-storage";
|
||||
import {
|
||||
type CheckoutGitActionStatus,
|
||||
@@ -14,9 +13,10 @@ import {
|
||||
useCheckoutPrStatusQuery,
|
||||
} from "@/hooks/use-checkout-pr-status-query";
|
||||
import { buildGitActions, type GitActions } from "@/components/git-actions-policy";
|
||||
import { buildNewAgentRoute, resolveNewAgentWorkingDir } from "@/utils/new-agent-routing";
|
||||
import { resolveNewAgentWorkingDir } from "@/utils/new-agent-routing";
|
||||
import { openExternalUrl } from "@/utils/open-external-url";
|
||||
import { useToast } from "@/contexts/toast-context";
|
||||
import { navigateToWorkspace } from "@/hooks/use-workspace-navigation";
|
||||
|
||||
export type { GitActionId, GitAction, GitActions } from "@/components/git-actions-policy";
|
||||
|
||||
@@ -225,7 +225,6 @@ interface UseGitActionsResult {
|
||||
}
|
||||
|
||||
export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): UseGitActionsResult {
|
||||
const router = useRouter();
|
||||
const toast = useToast();
|
||||
const [postShipArchiveSuggested, setPostShipArchiveSuggested] = useState(false);
|
||||
const [shipDefault, setShipDefault] = useState<"merge" | "pr">("merge");
|
||||
@@ -432,13 +431,13 @@ export function useGitActions({ serverId, cwd, icons }: UseGitActionsInput): Use
|
||||
const targetWorkingDir = resolveNewAgentWorkingDir(cwd, status ?? null);
|
||||
void runArchiveWorktree({ serverId, cwd, worktreePath })
|
||||
.then(() => {
|
||||
router.replace(buildNewAgentRoute(serverId, targetWorkingDir));
|
||||
navigateToWorkspace(serverId, targetWorkingDir);
|
||||
return;
|
||||
})
|
||||
.catch((err) => {
|
||||
toastActionError(err, "Failed to archive worktree");
|
||||
});
|
||||
}, [cwd, router, runArchiveWorktree, serverId, status, toast, toastActionError]);
|
||||
}, [cwd, runArchiveWorktree, serverId, status, toast, toastActionError]);
|
||||
|
||||
const baseRefLabel = useMemo(() => formatBaseRefLabel(baseRef), [baseRef]);
|
||||
const derived = deriveGitActionsState({
|
||||
|
||||
Reference in New Issue
Block a user