Recreate deleted worktrees from their branch on restore
Archiving a Paseo-owned worktree deletes its directory, so reopening its agent previously dead-ended even after the workspace record came back — there was no folder to return to. Restore now recreates the worktree at its original path from the kept branch (the branch is never deleted on archive), re-points the same workspace record, and clears archivedAt — preserving the workspaceId so the agent stays linked. The directory is recreated before the un-archive so the reconciler can't re-archive it for a missing directory. A deleted or already-checked-out branch surfaces a typed failure and leaves the workspace archived. Gated behind a worktreeRestore capability flag; older hosts get an explicit "update your host" state instead of a misleading failure.
This commit is contained in:
@@ -23,6 +23,14 @@ export function renderWorkspaceRouteGate(input: {
|
||||
return <WorkspaceConnecting hostName={input.state.hostName} />;
|
||||
case "restoring":
|
||||
return <WorkspaceRestoring hostName={input.state.hostName} />;
|
||||
case "needsHostUpgrade":
|
||||
return (
|
||||
<WorkspaceEmptyState
|
||||
titleKey="workspace.route.needsHostUpgrade"
|
||||
hostName={input.state.hostName}
|
||||
onDismiss={input.actions.onDismissMissingWorkspace}
|
||||
/>
|
||||
);
|
||||
case "unreachable":
|
||||
return (
|
||||
<WorkspaceUnreachable
|
||||
@@ -33,9 +41,11 @@ export function renderWorkspaceRouteGate(input: {
|
||||
);
|
||||
case "missing":
|
||||
return (
|
||||
<WorkspaceMissing
|
||||
<WorkspaceEmptyState
|
||||
titleKey={
|
||||
input.state.restoreFailed ? "workspace.route.restoreFailed" : "workspace.route.missing"
|
||||
}
|
||||
hostName={input.state.hostName}
|
||||
restoreFailed={input.state.restoreFailed}
|
||||
onDismiss={input.actions.onDismissMissingWorkspace}
|
||||
/>
|
||||
);
|
||||
@@ -142,13 +152,16 @@ function WorkspaceUnreachable({
|
||||
);
|
||||
}
|
||||
|
||||
function WorkspaceMissing({
|
||||
function WorkspaceEmptyState({
|
||||
titleKey,
|
||||
hostName,
|
||||
restoreFailed,
|
||||
onDismiss,
|
||||
}: {
|
||||
titleKey:
|
||||
| "workspace.route.missing"
|
||||
| "workspace.route.restoreFailed"
|
||||
| "workspace.route.needsHostUpgrade";
|
||||
hostName: string;
|
||||
restoreFailed: boolean;
|
||||
onDismiss: () => void;
|
||||
}) {
|
||||
const { t } = useTranslation();
|
||||
@@ -156,9 +169,7 @@ function WorkspaceMissing({
|
||||
return (
|
||||
<View style={styles.emptyState}>
|
||||
<View style={styles.textStack}>
|
||||
<Text style={styles.title}>
|
||||
{restoreFailed ? t("workspace.route.restoreFailed") : t("workspace.route.missing")}
|
||||
</Text>
|
||||
<Text style={styles.title}>{t(titleKey)}</Text>
|
||||
<Text style={styles.description}>{hostName}</Text>
|
||||
</View>
|
||||
<View style={styles.actions}>
|
||||
|
||||
@@ -127,6 +127,19 @@ describe("resolveWorkspaceRouteState", () => {
|
||||
).toEqual({ kind: "restoring", hostName: "Laptop" });
|
||||
});
|
||||
|
||||
it("returns needsHostUpgrade when the daemon lacks the restore capability", () => {
|
||||
expect(
|
||||
resolveWorkspaceRouteState({
|
||||
hostName: "Laptop",
|
||||
connectionStatus: "online",
|
||||
lastError: null,
|
||||
workspace: null,
|
||||
hasHydratedWorkspaces: true,
|
||||
restoreStatus: "needs-host-upgrade",
|
||||
}),
|
||||
).toEqual({ kind: "needsHostUpgrade", hostName: "Laptop" });
|
||||
});
|
||||
|
||||
it("falls back to a restore-failed missing state once the restore times out", () => {
|
||||
expect(
|
||||
resolveWorkspaceRouteState({
|
||||
|
||||
@@ -17,6 +17,7 @@ export type WorkspaceRouteState =
|
||||
}
|
||||
| { kind: "loading"; hostName: string }
|
||||
| { kind: "restoring"; hostName: string }
|
||||
| { kind: "needsHostUpgrade"; hostName: string }
|
||||
| { kind: "missing"; hostName: string; restoreFailed: boolean };
|
||||
|
||||
export function resolveWorkspaceRouteState(input: {
|
||||
@@ -25,7 +26,7 @@ export function resolveWorkspaceRouteState(input: {
|
||||
lastError: string | null;
|
||||
workspace: WorkspaceDescriptor | null;
|
||||
hasHydratedWorkspaces: boolean;
|
||||
restoreStatus: "restoring" | "failed" | null;
|
||||
restoreStatus: "restoring" | "failed" | "needs-host-upgrade" | null;
|
||||
}): WorkspaceRouteState {
|
||||
if (input.workspace) {
|
||||
if (input.connectionStatus === "online") {
|
||||
@@ -45,6 +46,10 @@ export function resolveWorkspaceRouteState(input: {
|
||||
return { kind: "restoring", hostName: input.hostName };
|
||||
}
|
||||
|
||||
if (input.restoreStatus === "needs-host-upgrade") {
|
||||
return { kind: "needsHostUpgrade", hostName: input.hostName };
|
||||
}
|
||||
|
||||
if (input.hasHydratedWorkspaces) {
|
||||
return {
|
||||
kind: "missing",
|
||||
|
||||
Reference in New Issue
Block a user