Files
paseo/packages/app/src/stores/navigation-active-workspace-store/navigation.ts
T
Mohamed BoudraandGitHub d3e8f77914 Keep composer autocomplete visible after route hops (#1851)
* fix(app): keep composer autocomplete visible after route hops

Workspace callers now express only the target workspace. The navigation helper chooses whether to pop to a mounted host route or fall back, and active-workspace tracking ignores stale hidden route params while app-wide routes are foregrounded.

* test(app): address workspace navigation review

* fix(app): parse decoded legacy workspace routes

* fix(app): ignore decoded legacy tab routes

* test(app): use canonical offline workspace route
2026-07-01 21:55:17 +02:00

105 lines
3.3 KiB
TypeScript

import type { Agent, WorkspaceDescriptor } from "@/stores/session-store";
import { pickAttentionAgent } from "@/utils/agent-attention";
import {
buildHostWorkspaceRoute,
decodeWorkspaceIdFromPathSegment,
parseHostWorkspaceRouteFromPathname,
} from "@/utils/host-routes";
import {
normalizeWorkspaceOpaqueId,
resolveWorkspaceMapKeyByIdentity,
} from "@/utils/workspace-identity";
import type { ActiveWorkspaceSelection } from "@/stores/last-workspace-selection";
export interface RouteSelectionInput {
pathname: string;
params: {
serverId?: string | string[];
workspaceId?: string | string[];
};
}
export interface NavigateToWorkspaceDeps {
getSessionWorkspaces: (serverId: string) => Map<string, WorkspaceDescriptor> | null | undefined;
getSessionAgents: (serverId: string) => Iterable<Agent>;
openWorkspaceAgentTab: (workspaceKey: string, agentId: string) => void;
rememberLastWorkspace: (selection: ActiveWorkspaceSelection) => void;
navigateToRoute: (route: string) => void;
}
export interface NavigateToLastWorkspaceDeps extends NavigateToWorkspaceDeps {
getLastWorkspaceSelection: () => ActiveWorkspaceSelection | null;
}
function getParamValue(value: string | string[] | undefined): string {
if (typeof value === "string") {
return value.trim();
}
if (Array.isArray(value)) {
const firstValue = value[0];
return typeof firstValue === "string" ? firstValue.trim() : "";
}
return "";
}
function parseWorkspaceSelectionFromRouteParams(params: {
serverId?: string | string[];
workspaceId?: string | string[];
}): ActiveWorkspaceSelection | null {
const serverId = getParamValue(params.serverId);
const workspaceValue = getParamValue(params.workspaceId);
const workspaceId = workspaceValue ? decodeWorkspaceIdFromPathSegment(workspaceValue) : null;
if (!serverId || !workspaceId) {
return null;
}
return { serverId, workspaceId };
}
export function parseActiveWorkspaceSelection(
input: RouteSelectionInput,
): ActiveWorkspaceSelection | null {
const routeSelection = parseHostWorkspaceRouteFromPathname(input.pathname);
if (routeSelection) {
return routeSelection;
}
if (input.pathname !== "/" && input.pathname !== "") {
return null;
}
return parseWorkspaceSelectionFromRouteParams(input.params);
}
export function navigateToWorkspace(
serverId: string,
workspaceId: string,
deps: NavigateToWorkspaceDeps,
): void {
const workspaces = deps.getSessionWorkspaces(serverId);
const resolvedWorkspaceId = resolveWorkspaceMapKeyByIdentity({
workspaces,
workspaceId,
});
const workspaceAgents = resolvedWorkspaceId
? Array.from(deps.getSessionAgents(serverId)).filter(
(agent) => normalizeWorkspaceOpaqueId(agent.workspaceId) === resolvedWorkspaceId,
)
: [];
const attentionAgentId = pickAttentionAgent(workspaceAgents);
if (attentionAgentId && resolvedWorkspaceId) {
deps.openWorkspaceAgentTab(`${serverId}:${resolvedWorkspaceId}`, attentionAgentId);
}
deps.rememberLastWorkspace({ serverId, workspaceId });
deps.navigateToRoute(buildHostWorkspaceRoute(serverId, workspaceId));
}
export function navigateToLastWorkspace(deps: NavigateToLastWorkspaceDeps): boolean {
const selection = deps.getLastWorkspaceSelection();
if (!selection) {
return false;
}
navigateToWorkspace(selection.serverId, selection.workspaceId, deps);
return true;
}