fix(app): archiving a worktree no longer holds its dialog open (BRA-2199)
Summary
Archive… held its confirm dialog open with a spinner until the box answered. For a repo with no archive script, the box removes the worktree before it answers (dropWorktree: stop services, git worktree remove, stop sessions), and for a monorepo checkout with node_modules that takes 5 to 30 seconds or more. Archiving several finished worktrees in a row meant waiting on each one.
Archive… still asks first, but pressing Archive now closes the dialog at once. The worktree shows Archiving… right away and goes when its box gets to it. It uses the same per-box queue that Remove worktree already uses, one removal at a time per box.
-
lib/remove-worktree.ts:queueRemovalnow takes the kind ("archive" | "remove"). For an archive it gives today's toasts:- Archived NAME / "Branch … is kept." when there is no script.
- Archiving NAME / "Its archive script is running…" when there is a script. Archived NAME follows when
worktree.removedarrives, as before. - A warning, Couldn't archive NAME / "NAME has uncommitted changes, so it was left as it is. Commit them first, or use Remove worktree to discard them."
keepsWorkis the one copy of git's "it would lose work" check. It is shared withuse-bulk.ts. -
lib/removing.ts:inTurn(box, job)is the per-box line, built on the existingoneAtATime. It replaces the queue's hand-rolledwaiting/drain. -
components/sidebar/actions.tsx:archiveWorktree'srunjust queues. The sidebar's ⋯ and right-click, zen's worktree menu and the launcher's "Archive this worktree…" all go through it. -
views/worktrees/use-bulk.ts: bulk Archive skips a worktree that is already on its way out, so its box never gets a second DELETE for it. Codex's adversarial review raised this. -
lib/mock.ts:?mock=1&slowremove=MSmakes a mock box take that long to remove a worktree with no archive script, answering once it is gone.
One behaviour change: an error is now a toast instead of text inside the dialog, so the dialog no longer offers a retry. Archive… can be pressed again.
Not changed: bulk Archive N… in the Worktrees table (archive-dialog.tsx). Its dialog stays open on purpose, showing each row's outcome, so it still waits on each box. Moving it onto the queue would be a redesign of that dialog, so it is left for a separate change.
Evidence
Driven with the verify skill's new worktree-archive scenario: the production build on the mock fixtures with slowremove=4000 and the list sidebar, in Chromium. It right-clicks a row, chooses Archive…, presses Archive, and times how long the dialog takes to close.
Before (origin/main app code, same slow mock): the dialog spun for 4638 ms, the full length of the box's removal.

After: the dialog closed in 149–308 ms, which is its close animation, across four archives.
| Archive pressed on gpu/ci-flake, then gpu/judge-v2 at once: both marked, the dialog gone | ~4.2 s later gpu answers: Archived ci-flake, judge-v2 still waits its turn |
|---|---|
![]() | ![]() |
Archive script (devl/qa-deck, pnpm db:drop) | Uncommitted work (devl/checkout-fix): kept, with a warning |
|---|---|
![]() | ![]() |
The scenario's steps:
- the dialog closed 149 ms after Archive was pressed (gpu/ci-flake, no script)
- the dialog closed 276 ms after Archive was pressed (gpu/judge-v2, queued behind it)
- Archived ci-flake toast 4239 ms after Archive
- Archived judge-v2 toast 8663 ms after the first Archive (one at a time on gpu)
- the dialog closed 295 ms after Archive was pressed (devl/qa-deck, archive script)
- the dialog closed 308 ms after Archive was pressed (devl/checkout-fix, uncommitted)
OK
The mock can't show a real box's teardown time. The fix is app-side and doesn't depend on it.
Checks: pnpm typecheck:plugins, check:titles, check:type, check:grid, check:csp, check:themes, pnpm test (565 pass), npx tsc -p e2e, pnpm build, and (cd docs-site && pnpm build) all pass. In the browser, e2e/worktree-remove.spec.ts passes, including a new test: Archive with slowremove, the dialog hidden within 1 s, Archiving…, the toast, and the dirty case. e2e/publish-failed.spec.ts passes too. There is no Go change.
Unit tests: removing.test.ts checks that a box takes its removals one at a time and that another box's don't wait.
The verify skill now has features/worktree-archive.md, a row in features/README.md, and scripts/scenarios/worktree-archive.mjs. There is also a changelog bullet under Unreleased.
Worth a separate fix: why the box is slow
Without an archive script, dropWorktree calls Locations.RemoveWorktree, which runs git worktree remove. git deletes the folder itself, one file at a time, before the HTTP answer goes back, and a node_modules tree has hundreds of thousands of files. The box could instead rename the folder aside on the same filesystem, run git worktree prune, answer and publish worktree.removed, and delete the renamed folder in the background. Removals would then be quick everywhere, the Worktrees table's bulk Archive and the CLI included.
🤖 Generated with Claude Code



