@@ -8,10 +8,11 @@ import { Store } from './store'
88import { registerStateTransport } from './transport-electron'
99import { PRPoller } from './pr-poller'
1010import { WorktreesFSM } from './worktrees-fsm'
11+ import { WorktreeDeletionFSM } from './worktree-deletion-fsm'
1112import { PanesFSM , stripTransientTabFields } from './panes-fsm'
1213import { ActivityDeriver } from './activity-deriver'
1314import type { TerminalTab , WorkspacePane } from '../shared/state/terminals'
14- import { listWorktrees , listBranches , continueWorktree , removeWorktree , isWorktreeDirty , defaultWorktreeDir , getChangedFiles , getFileDiff , getBranchCommits , getCommitDiff , getMainWorktreeStatus , prepareMainForMerge , mergeWorktreeLocally , getBranchSha , previewMergeConflicts , getBranchDiffStats , listAllFiles , readWorktreeFile , runWorktreeScript , type MergeStrategy } from './worktree'
15+ import { listWorktrees , listBranches , continueWorktree , isWorktreeDirty , defaultWorktreeDir , getChangedFiles , getFileDiff , getBranchCommits , getCommitDiff , getMainWorktreeStatus , prepareMainForMerge , mergeWorktreeLocally , getBranchSha , previewMergeConflicts , getBranchDiffStats , listAllFiles , readWorktreeFile , type MergeStrategy } from './worktree'
1516import { getPRStatus , testToken , starRepo , unstarRepo , isRepoStarred } from './github'
1617import { AVAILABLE_EDITORS , DEFAULT_EDITOR_ID , openInEditor } from './editor'
1718import { setSecret , hasSecret , deleteSecret } from './secrets'
@@ -191,6 +192,11 @@ const worktreesFSM = new WorktreesFSM(store, {
191192 }
192193} )
193194
195+ const worktreeDeletionFSM = new WorktreeDeletionFSM ( store , {
196+ getGlobalTeardownCmd : ( ) => config . worktreeTeardownCommand || '' ,
197+ worktreesFSM
198+ } )
199+
194200const activityDeriver = new ActivityDeriver ( store )
195201
196202/** Install Claude Code hooks into any worktree that's missing them, but only
@@ -343,7 +349,9 @@ function registerIpcHandlers(): void {
343349 removeMeta ?: { prNumber ?: number ; prState ?: PRState }
344350 ) => {
345351 if ( ! repoRoot ) throw new Error ( 'No repo root provided' )
346- // Drop any locally-merged flag for the branch at this path
352+ // Drop any locally-merged flag for the branch at this path. We still
353+ // need the worktree record for its branch name *before* kicking off
354+ // the async deletion.
347355 const trees = await listWorktrees ( repoRoot )
348356 const wt = trees . find ( ( t ) => t . path === path )
349357 if ( wt && config . locallyMerged && wt . branch && config . locallyMerged [ wt . branch ] ) {
@@ -358,18 +366,22 @@ function registerIpcHandlers(): void {
358366 prNumber : removeMeta ?. prNumber ,
359367 prState : removeMeta ?. prState
360368 } )
361- const teardownRepoCfg = loadRepoConfig ( repoRoot )
362- const teardownCmd = teardownRepoCfg . teardownCommand || config . worktreeTeardownCommand || ''
363- if ( teardownCmd ) {
364- await runWorktreeScript ( 'teardown' , teardownCmd , {
365- worktreePath : path ,
366- branch : wt ?. branch || '' ,
367- repoRoot
368- } )
369- }
370- const result = await removeWorktree ( repoRoot , path , force )
371- void worktreesFSM . refreshList ( )
372- return result
369+ // Fire-and-forget: the WorktreeDeletionFSM runs the teardown script
370+ // and git worktree remove in the background, streaming progress
371+ // through the store. Returns immediately so the renderer can animate
372+ // the deletion card instead of freezing on the row.
373+ worktreeDeletionFSM . enqueue ( {
374+ repoRoot,
375+ path,
376+ branch : wt ?. branch || '' ,
377+ force
378+ } )
379+ return { queued : true }
380+ } )
381+
382+ ipcMain . handle ( 'worktree:dismissPendingDeletion' , ( _ , path : string ) => {
383+ worktreeDeletionFSM . dismiss ( path )
384+ return true
373385 } )
374386
375387 ipcMain . handle ( 'worktree:dir' , async ( _ , repoRoot : string ) => {
0 commit comments