Files
paseo/packages/app/src/screens/new-workspace-initial-context.ts
T
Mohamed BoudraandGitHub 99eca19a76 Keep selected projects when switching hosts (#2700)
* fix(app): preserve project selection across hosts

Rendered project identity and persisted equivalence were conflated after duplicate-project handling. Keep them separate so host changes resolve selectable equivalents deterministically without resetting legacy sidebar identity.

* fix(app): restore selected clone after host round trip

Keep manual origin and current destination placements separately so switching back restores the chosen checkout while archive gaps preserve the active host selection.

* fix(app): key status icons by project view

* fix(app): preserve origin during archive gaps
2026-07-31 15:42:08 +02:00

253 lines
7.4 KiB
TypeScript

import {
canCreateWorkspaceForHostProject,
filterWorkspaceProjectsForHost,
resolveHostProjectCandidate,
type HostProjectListItem,
} from "@/projects/host-projects";
import type { HostRuntimeConnectionStatus } from "@/runtime/host-runtime";
export interface NewWorkspaceInitialServerInput {
allServerIds: readonly string[];
routeServerId: string | null | undefined;
lastActiveProject: HostProjectListItem | null;
projects: readonly HostProjectListItem[];
hostConnectionStatusByServerId: ReadonlyMap<string, HostRuntimeConnectionStatus>;
workspaceMultiplicityByServerId: ReadonlyMap<string, boolean>;
}
function knownServerId(serverIds: ReadonlySet<string>, serverId: string | null | undefined) {
const normalized = serverId?.trim() ?? "";
return normalized && serverIds.has(normalized) ? normalized : null;
}
function supportsAllProjects(
workspaceMultiplicityByServerId: ReadonlyMap<string, boolean>,
serverId: string,
) {
return workspaceMultiplicityByServerId.get(serverId) === true;
}
function canUseProjectForServer(input: {
project: HostProjectListItem;
projects: readonly HostProjectListItem[];
serverId: string;
workspaceMultiplicityByServerId: ReadonlyMap<string, boolean>;
}) {
const allowAllProjects = supportsAllProjects(
input.workspaceMultiplicityByServerId,
input.serverId,
);
const selectableProjects = filterWorkspaceProjectsForHost({
projects: input.projects,
serverId: input.serverId,
allowAllProjects,
});
const project =
resolveHostProjectCandidate({
candidate: input.project,
projects: selectableProjects,
serverId: input.serverId,
}) ?? input.project;
return canCreateWorkspaceForHostProject({
project,
serverId: input.serverId,
allowAllProjects,
});
}
function findLastActiveProjectServerId(input: {
serverIds: ReadonlySet<string>;
lastActiveProject: HostProjectListItem | null;
projects: readonly HostProjectListItem[];
workspaceMultiplicityByServerId: ReadonlyMap<string, boolean>;
}) {
if (!input.lastActiveProject) {
return null;
}
for (const host of input.lastActiveProject.hosts) {
if (!input.serverIds.has(host.serverId)) {
continue;
}
if (
canUseProjectForServer({
project: input.lastActiveProject,
projects: input.projects,
serverId: host.serverId,
workspaceMultiplicityByServerId: input.workspaceMultiplicityByServerId,
})
) {
return host.serverId;
}
}
return null;
}
function hasSelectableProject(input: {
projects: readonly HostProjectListItem[];
serverId: string;
workspaceMultiplicityByServerId: ReadonlyMap<string, boolean>;
}) {
return input.projects.some((project) =>
canUseProjectForServer({
project,
projects: input.projects,
serverId: input.serverId,
workspaceMultiplicityByServerId: input.workspaceMultiplicityByServerId,
}),
);
}
function isOnline(
statuses: ReadonlyMap<string, HostRuntimeConnectionStatus>,
serverId: string,
): boolean {
return statuses.get(serverId) === "online";
}
function isKnownUnreachable(
statuses: ReadonlyMap<string, HostRuntimeConnectionStatus>,
serverId: string,
): boolean {
const status = statuses.get(serverId);
return status === "offline" || status === "error";
}
export function resolveNewWorkspaceInitialServerId(input: NewWorkspaceInitialServerInput): string {
const serverIds = new Set(input.allServerIds);
const routeServerId = knownServerId(serverIds, input.routeServerId);
if (routeServerId) {
return routeServerId;
}
const onlineServerIds = input.allServerIds.filter((serverId) =>
isOnline(input.hostConnectionStatusByServerId, serverId),
);
const onlineServerIdsWithProjects = onlineServerIds.filter((serverId) =>
hasSelectableProject({
projects: input.projects,
serverId,
workspaceMultiplicityByServerId: input.workspaceMultiplicityByServerId,
}),
);
const serverIdsWithProjects = input.allServerIds.filter((serverId) =>
hasSelectableProject({
projects: input.projects,
serverId,
workspaceMultiplicityByServerId: input.workspaceMultiplicityByServerId,
}),
);
const lastActiveProjectServerId = findLastActiveProjectServerId({
serverIds,
lastActiveProject: input.lastActiveProject,
projects: input.projects,
workspaceMultiplicityByServerId: input.workspaceMultiplicityByServerId,
});
if (
lastActiveProjectServerId &&
isOnline(input.hostConnectionStatusByServerId, lastActiveProjectServerId)
) {
return lastActiveProjectServerId;
}
if (onlineServerIdsWithProjects.length > 0) {
return onlineServerIdsWithProjects[0] ?? "";
}
if (onlineServerIds.length === 1) {
return onlineServerIds[0] ?? "";
}
if (onlineServerIds.length > 0) {
return onlineServerIds[0] ?? "";
}
const reachableServerIdsWithProjects = serverIdsWithProjects.filter(
(serverId) => !isKnownUnreachable(input.hostConnectionStatusByServerId, serverId),
);
if (reachableServerIdsWithProjects.length === 1) {
return reachableServerIdsWithProjects[0] ?? "";
}
if (lastActiveProjectServerId) {
return lastActiveProjectServerId;
}
if (serverIdsWithProjects.length === 1) {
return serverIdsWithProjects[0] ?? "";
}
return input.allServerIds[0] ?? "";
}
export function resolveNewWorkspaceAutomaticServerId(
input: NewWorkspaceInitialServerInput & {
currentServerId: string | null | undefined;
nextServerId: string | null | undefined;
},
): string {
const serverIds = new Set(input.allServerIds);
const currentServerId = knownServerId(serverIds, input.currentServerId);
const nextServerId = knownServerId(serverIds, input.nextServerId) ?? input.allServerIds[0] ?? "";
if (!currentServerId || currentServerId === nextServerId) {
return nextServerId;
}
if (
isOnline(input.hostConnectionStatusByServerId, nextServerId) &&
!isOnline(input.hostConnectionStatusByServerId, currentServerId)
) {
return nextServerId;
}
const routeServerId = knownServerId(serverIds, input.routeServerId);
if (routeServerId === nextServerId) {
return nextServerId;
}
const lastActiveProjectServerId = findLastActiveProjectServerId({
serverIds,
lastActiveProject: input.lastActiveProject,
projects: input.projects,
workspaceMultiplicityByServerId: input.workspaceMultiplicityByServerId,
});
const hasOnlineServer = input.allServerIds.some((serverId) =>
isOnline(input.hostConnectionStatusByServerId, serverId),
);
if (
lastActiveProjectServerId === nextServerId &&
(isOnline(input.hostConnectionStatusByServerId, nextServerId) || !hasOnlineServer)
) {
return nextServerId;
}
const currentHasProject = hasSelectableProject({
projects: input.projects,
serverId: currentServerId,
workspaceMultiplicityByServerId: input.workspaceMultiplicityByServerId,
});
const nextHasProject = hasSelectableProject({
projects: input.projects,
serverId: nextServerId,
workspaceMultiplicityByServerId: input.workspaceMultiplicityByServerId,
});
if (
isKnownUnreachable(input.hostConnectionStatusByServerId, currentServerId) &&
nextHasProject &&
!isKnownUnreachable(input.hostConnectionStatusByServerId, nextServerId)
) {
return nextServerId;
}
if (
!currentHasProject &&
nextHasProject &&
(!isOnline(input.hostConnectionStatusByServerId, currentServerId) ||
isOnline(input.hostConnectionStatusByServerId, nextServerId))
) {
return nextServerId;
}
return currentServerId;
}