Files
paseo/packages/app/src/projects/project-remove.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

106 lines
3.1 KiB
TypeScript

import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
import { useSessionStore } from "@/stores/session-store";
import { selectHostFeature } from "@/runtime/host-features";
interface ProjectRemoveHost {
serverId: string;
projectId: string;
}
export interface ProjectRemoveProject {
hosts: readonly ProjectRemoveHost[];
}
export interface ProjectRemoveTarget {
serverId: string;
projectId: string;
}
export type ProjectRemoveReadiness =
| { kind: "ready"; targets: ProjectRemoveTarget[] }
| { kind: "needs_host_update"; serverIds: string[] };
export type ProjectRemoveOutcome =
| { kind: "removed"; serverIds: string[] }
| { kind: "host_disconnected"; serverIds: string[] }
| { kind: "failed"; serverIds: string[] };
type ProjectRemoveClient = Pick<DaemonClient, "removeProject">;
export function getProjectRemoveReadiness(input: {
project: ProjectRemoveProject;
supportsProjectRemove: (serverId: string) => boolean;
}): ProjectRemoveReadiness {
const unsupportedServerIds: string[] = [];
const targets: ProjectRemoveTarget[] = [];
for (const host of input.project.hosts) {
if (!input.supportsProjectRemove(host.serverId)) {
unsupportedServerIds.push(host.serverId);
continue;
}
targets.push({
serverId: host.serverId,
projectId: host.projectId,
});
}
if (unsupportedServerIds.length > 0) {
return { kind: "needs_host_update", serverIds: unsupportedServerIds };
}
return { kind: "ready", targets };
}
export function getCurrentProjectRemoveReadiness(
project: ProjectRemoveProject,
): ProjectRemoveReadiness {
const sessionState = useSessionStore.getState();
return getProjectRemoveReadiness({
project,
supportsProjectRemove: (serverId) => selectHostFeature(sessionState, serverId, "projectRemove"),
});
}
export async function removeProjectFromHosts(input: {
targets: readonly ProjectRemoveTarget[];
getClient: (serverId: string) => ProjectRemoveClient | null;
}): Promise<ProjectRemoveOutcome> {
const clients: Array<{ serverId: string; projectId: string; client: ProjectRemoveClient }> = [];
const disconnectedServerIds: string[] = [];
for (const target of input.targets) {
const client = input.getClient(target.serverId);
if (!client) {
disconnectedServerIds.push(target.serverId);
continue;
}
clients.push({ serverId: target.serverId, projectId: target.projectId, client });
}
if (disconnectedServerIds.length > 0) {
return { kind: "host_disconnected", serverIds: disconnectedServerIds };
}
const results = await Promise.allSettled(
clients.map(async ({ client, projectId }) => {
await client.removeProject(projectId);
}),
);
const failedServerIds: string[] = [];
for (const [index, result] of results.entries()) {
if (result.status === "rejected") {
const failed = clients[index];
if (failed) {
failedServerIds.push(failed.serverId);
}
}
}
if (failedServerIds.length > 0) {
return { kind: "failed", serverIds: failedServerIds };
}
return { kind: "removed", serverIds: clients.map((entry) => entry.serverId) };
}