From 5b1fcb0ff09c5db6076a3e41d08e6e192cf13bc4 Mon Sep 17 00:00:00 2001 From: Jinwoo Hong <73622457+Jinwoo-H@users.noreply.github.com> Date: Fri, 10 Jul 2026 22:56:33 -0700 Subject: [PATCH] Fix task workspace handoff after switching workspaces (#8226) Co-authored-by: Orca --- .../src/lib/worktree-creation-flow.test.ts | 59 +++++++++++++++++++ .../src/lib/worktree-creation-flow.ts | 17 ++++-- 2 files changed, 71 insertions(+), 5 deletions(-) diff --git a/src/renderer/src/lib/worktree-creation-flow.test.ts b/src/renderer/src/lib/worktree-creation-flow.test.ts index 1ceebd2093f..f614c679c7e 100644 --- a/src/renderer/src/lib/worktree-creation-flow.test.ts +++ b/src/renderer/src/lib/worktree-creation-flow.test.ts @@ -504,6 +504,65 @@ describe('staged background worktree creation', () => { }) }) + it('reveals the completed workspace after the user switches to another workspace', async () => { + let resolveCreate!: (result: { worktree: { id: string; repoId: string } }) => void + store.createWorktree.mockReturnValueOnce( + new Promise((resolve) => { + resolveCreate = resolve + }) + ) + + const started = continueBackgroundWorktreeCreation('creation-1', makeRequest(), { + revealCreationSurface: false + }) + + expect(started).toBe(true) + await vi.waitFor(() => expect(store.createWorktree).toHaveBeenCalledTimes(1)) + // Why: selecting a real workspace clears only the pending surface pointer; + // completion should still finish the task-launch handoff once it is ready. + store.activePendingCreationId = null + resolveCreate({ worktree: { id: 'wt-1', repoId: 'repo-1' } }) + await flushAsyncWorktreeCreation() + + expect(activateAndRevealWorktree).toHaveBeenCalledWith('wt-1', { + sidebarRevealBehavior: 'auto' + }) + expect(ensureWorktreeHasInitialTerminal).not.toHaveBeenCalled() + expect(store.removePendingWorktreeCreation).toHaveBeenCalledWith('creation-1', { + cleanupVm: false + }) + }) + + it('does not reveal a workspace cancelled during post-create trust preflight', async () => { + let resolveTrust!: () => void + const markTrusted = vi.fn( + () => + new Promise((resolve) => { + resolveTrust = resolve + }) + ) + globalThis.window = { api: { agentTrust: { markTrusted } } } as never + store.repos = [{ id: 'repo-1', connectionId: null }] + store.createWorktree.mockResolvedValueOnce({ + worktree: { id: 'wt-1', repoId: 'repo-1', path: '/repo/wt-1' } + }) + + const started = continueBackgroundWorktreeCreation( + 'creation-1', + makeRequest({ agent: 'codex' }), + { revealCreationSurface: false } + ) + + expect(started).toBe(true) + await vi.waitFor(() => expect(markTrusted).toHaveBeenCalledTimes(1)) + delete store.pendingWorktreeCreations['creation-1'] + store.activePendingCreationId = null + resolveTrust() + await vi.waitFor(() => expect(ensureWorktreeHasInitialTerminal).toHaveBeenCalledTimes(1)) + + expect(activateAndRevealWorktree).not.toHaveBeenCalled() + }) + // Why: one-click "Start workspace from issue" commonly backgrounds, so the // user-moved-on path is the common delivery for the repo's issue command; it // must thread through as the 5th positional arg, not be dropped to undefined. diff --git a/src/renderer/src/lib/worktree-creation-flow.ts b/src/renderer/src/lib/worktree-creation-flow.ts index 9af1dc61ebe..6bdac108c23 100644 --- a/src/renderer/src/lib/worktree-creation-flow.ts +++ b/src/renderer/src/lib/worktree-creation-flow.ts @@ -214,13 +214,20 @@ async function executeWorktreeCreation( await preflightAgentTrust(preparedRequest, worktree.path, repoConnectionId) } - // `createWorktree` already inserted the real worktree row. Whether we steal - // the view depends on whether the user is still watching this creation. - const stillActive = isPendingCreationSurfaceVisible(creationId) + // `createWorktree` already inserted the real worktree row. Leaving for an app + // view keeps the create in the background, while selecting another workspace + // means the user still expects this task-launch handoff when it becomes ready; + // the entry guard prevents a late trust preflight from reviving a cancelled create. + const completionState = useAppStore.getState() + const shouldActivateOnCompletion = + completionState.pendingWorktreeCreations[creationId] !== undefined && + (isPendingCreationSurfaceVisible(creationId) || + (completionState.activeView === 'terminal' && + completionState.activePendingCreationId === null)) let activation: ActivateAndRevealResult | false = false let primaryTabId: string | null - if (stillActive) { + if (shouldActivateOnCompletion) { activation = activateAndRevealWorktree(worktree.id, { sidebarRevealBehavior: 'auto', ...(result.setup ? { setup: result.setup } : {}), @@ -254,7 +261,7 @@ async function executeWorktreeCreation( startup: preparedRequest.startupPlan }) } - if (stillActive && !preparedRequest.suppressTerminalFocusOnCompletion) { + if (shouldActivateOnCompletion && !preparedRequest.suppressTerminalFocusOnCompletion) { queueNewWorkspaceTerminalFocus(worktree.id, activation) }