diff --git a/packages/app/e2e/worktree-restore.spec.ts b/packages/app/e2e/worktree-restore.spec.ts index ff596e2cc2..70aeb66d61 100644 --- a/packages/app/e2e/worktree-restore.spec.ts +++ b/packages/app/e2e/worktree-restore.spec.ts @@ -1,9 +1,16 @@ import { randomUUID } from "node:crypto"; +import { execFileSync } from "node:child_process"; import { existsSync } from "node:fs"; import { rename } from "node:fs/promises"; import type { Page } from "@playwright/test"; +import { buildHostWorkspaceRoute } from "@/utils/host-routes"; import { expect, test } from "./fixtures"; import { gotoAppShell } from "./helpers/app"; +import { + expectWorkspaceBranch, + openChangesPanel, + switchBranchFromChangesPanel, +} from "./helpers/branch-switcher"; import { createIdleAgent, expectSessionRowNotArchived, @@ -81,6 +88,19 @@ test.describe("Worktree restore", () => { return seeded; } + async function openArchivedAgentBeforeWorkspaceHydration(page: Page, prefix: string) { + const seeded = await createArchivedMissingWorktree(prefix); + const workspaceRoute = buildHostWorkspaceRoute(getServerId(), seeded.worktree.workspaceId); + const openAgent = encodeURIComponent(`agent:${seeded.agent.id}`); + + await page.goto(`${workspaceRoute}?open=${openAgent}`); + await expect(page.getByText("Workspace archived", { exact: true })).toBeVisible({ + timeout: 30_000, + }); + await expect(page.getByTestId("workspace-recovery-action")).toHaveText("Restore"); + return seeded; + } + test.afterEach(async () => { for (const directory of createdWorktreeDirectories) { await archiveWorkspaceFromDaemon(worktreeClient, directory).catch(() => undefined); @@ -156,7 +176,10 @@ test.describe("Worktree restore", () => { }); test("explicit Restore shows loading and opens the recreated workspace", async ({ page }) => { - const { agent, worktree } = await openArchivedWorkspaceFromHistory(page, "restore-success"); + const { agent, worktree } = await openArchivedAgentBeforeWorkspaceHydration( + page, + "restore-success", + ); await page.getByTestId("workspace-recovery-action").click(); @@ -173,6 +196,22 @@ test.describe("Worktree restore", () => { ).toBeVisible({ timeout: 30_000 }); await expect(page.getByTestId("workspace-recovery-action")).toHaveCount(0); expect(await fetchAgentArchivedAt(client, agent.id)).toBeNull(); + + const switchedBranch = `restored-live-${randomUUID().slice(0, 8)}`; + execFileSync("git", ["branch", switchedBranch], { + cwd: tempRepo.path, + stdio: "pipe", + }); + await openChangesPanel(page); + await expectWorkspaceBranch(page, worktree.workspaceName); + await switchBranchFromChangesPanel(page, { + from: worktree.workspaceName, + to: switchedBranch, + }); + await expectWorkspaceBranch(page, switchedBranch); + await expect( + page.getByTestId("workspace-header-title").filter({ visible: true }).first(), + ).toHaveText(switchedBranch, { timeout: 30_000 }); }); test("restore failure stays visible and permits a successful retry", async ({ page }) => { diff --git a/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/index.tsx b/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/index.tsx index 0621d2f63a..c02f427087 100644 --- a/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/index.tsx +++ b/packages/app/src/app/h/[serverId]/workspace/[workspaceId]/index.tsx @@ -111,7 +111,7 @@ function HostWorkspaceRouteContent() { const hasHydratedWorkspaces = useHasHydratedWorkspaces(serverId); const workspaceExists = useWorkspaceExists(serverId, workspaceId); const isOpenIntentWaitingForWorkspace = Boolean( - openValue && hasHydratedWorkspaces && !workspaceExists, + openValue && (!hasHydratedWorkspaces || !workspaceExists), ); useEffect(() => { if (!serverId || !workspaceId) { diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index 35b4ea3990..2717f63420 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -4012,9 +4012,11 @@ export class Session { private async restoreWorkspaceAndEmit(workspaceId: string): Promise { await this.workspaceRecovery.restore(workspaceId); - await this.emitWorkspaceUpdatesForWorkspaceIds([workspaceId], { - skipReconcile: true, - }); + const workspace = await this.workspaceRegistry.get(workspaceId); + if (!workspace) { + throw new Error(`Recovered workspace record not found: ${workspaceId}`); + } + await this.workspaceGitObserver.warmGitData(workspace); } private async restoreOwningWorkspaceForLegacyAgentRefresh(agentId: string): Promise {