Files
paseo/packages/app/src/utils/notification-routing.ts
T
Mohamed Boudra 5e5fc97798 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.
2026-05-06 15:13:53 +07:00

38 lines
1.1 KiB
TypeScript

import type { Href } from "expo-router";
import { buildHostAgentDetailRoute, buildHostRootRoute } from "@/utils/host-routes";
type NotificationData = Record<string, unknown> | null | undefined;
type NotificationRoute = Extract<Href, string>;
function readNonEmptyString(data: NotificationData, key: string): string | null {
const value = data?.[key];
if (typeof value !== "string") {
return null;
}
const trimmed = value.trim();
return trimmed.length > 0 ? trimmed : null;
}
export function resolveNotificationTarget(data: NotificationData): {
serverId: string | null;
agentId: string | null;
workspaceId: string | null;
} {
return {
serverId: readNonEmptyString(data, "serverId"),
agentId: readNonEmptyString(data, "agentId"),
workspaceId: readNonEmptyString(data, "workspaceId"),
};
}
export function buildNotificationRoute(data: NotificationData): NotificationRoute {
const { serverId, agentId } = resolveNotificationTarget(data);
if (serverId && agentId) {
return buildHostAgentDetailRoute(serverId, agentId);
}
if (serverId) {
return buildHostRootRoute(serverId);
}
return "/" as const;
}