Skip to content

feat(worktrees): merge & clean up, health badges, archive-before-remove snapshots#40

Merged
danielss-dev merged 2 commits into
mainfrom
feat/worktree-lifecycle
Jul 7, 2026
Merged

feat(worktrees): merge & clean up, health badges, archive-before-remove snapshots#40
danielss-dev merged 2 commits into
mainfrom
feat/worktree-lifecycle

Conversation

@danielss-dev

Copy link
Copy Markdown
Owner

Summary

The second half of the agent-worktree loop — retiring a worktree safely once its work lands (improvements.md W1–W3, from the 2026-07-07 worktree research pass):

  • W1 — Merge & clean up as one action. Repo::integrate_worktree_branch lands a worktree branch on its base as squash (default) / merge-commit / fast-forward, running in whichever worktree holds the base (tracked-changes-only dirty guard, conflict auto-abort) or as a pure ref fast-forward when the base isn't checked out anywhere. WorktreeMergeDialog previews the exact git commands, has an editable base picker (the detection heuristic can name a sibling when several worktrees share a fork point), and optionally removes the worktree + deletes the branch in the same motion.
  • W2 — Dirty-aware cleanup + merged detection. Repo::worktree_health classifies each worktree (merged via a containment scan across all local branches, ahead-of-base, can-fast-forward, unpushed). The overview badges merged / unpushed / unmerged (lock + prune reasons as tooltips), and grows Clean up (N) — confirm-listed removal of clean, fully-merged worktrees incl. branch deletion — and Prune stale.
  • W3 — Archive-before-remove. Every worktree removal first snapshots HEAD + staged + unstaged + untracked into refs/strand/archive/<slug>/<secs> (throwaway GIT_INDEX_FILE, real index untouched). Restore puts the original directory and branch back when they're free, else falls back detached; a collapsible "Archived snapshots" strip offers Restore / Delete.
  • Fix (separate commit): all 12 dialogs re-arm their mountedRef in the effect body — StrictMode's dev remount left the ref permanently false, freezing any dialog on its busy label the first time a submit errored.

Also rides along: repo_worktree_add/remove/prune now route through run_blocking.

Test plan

  • cargo test -p strand-core — 102 passed (+5 worktree: health with sibling branches at the fork, ff/merge/squash integration incl. dirty-base refusal and untracked bystanders, archive round-trip incl. identity restore and second-restore fallback)
  • cargo clippy -p strand-core -p strand-tauri -- -D warnings — clean
  • pnpm --filter ./ui exec tsc --noEmit — clean
  • pnpm --filter ./ui exec vitest run — 200 passed
  • Manual pass in tauri dev against a playground repo with merged / dirty / unmerged sibling worktrees: badges, Clean up, merge dialog (incl. frozen-busy repro → fixed, dirty-base inline error), force-remove → restore round-trip with branch + directory identity back.

🤖 Generated with Claude Code

…ve snapshots

The second half of the agent-worktree loop: retiring a worktree safely
once its work lands (improvements.md W1-W3).

Engine (worktree.rs):
- worktree_health: detected base, ahead-of-base, can-fast-forward,
  upstream/unpushed, and merged-detection via a containment scan across
  all local branches (detect_base_branch alone names a sibling when
  several worktrees sit at one fork point and would hide "merged").
- integrate_worktree_branch: squash / merge-commit / ff into the base,
  run in whichever worktree holds it (tracked-changes-only dirty guard,
  conflict auto-abort) or as a pure ref fast-forward when unheld.
- Archive snapshots: archive_worktree_state captures HEAD + staged +
  unstaged + untracked into refs/strand/archive/<slug>/<secs> via a
  throwaway GIT_INDEX_FILE; restore puts the original directory and
  branch back when free (subject records the branch, body the path),
  else falls back detached; list/delete counterparts. prunable reason
  parsed alongside locked.

IPC: six new repo_worktree_* commands; add/remove/prune now route
through run_blocking (they wait on subprocesses).

UI (Worktrees overview): merged/unpushed/unmerged badges with
lock/prune reasons as tooltips, merged hero metric, Clean up (N) for
clean+merged worktrees, Prune stale button, WorktreeMergeDialog with
exact-command preview + editable base picker, removeWorktree archives
first on every path, and a restorable "Archived snapshots" strip.

+5 engine tests (health w/ siblings, ff/merge/squash, archive
round-trip incl. identity restore and untracked bystanders).
…e busy state

Every dialog guards post-await setState with a mountedRef, but armed it
only via the useRef initializer with a cleanup-only effect. StrictMode's
dev remount reuses the same ref object, so the simulated unmount left it
permanently false on a mounted dialog: the first submit error skipped
both setError and the finally setBusy(false), freezing the dialog on its
busy label (found as "Merging..." stuck in WorktreeMergeDialog; the same
latent bug sat in eleven other dialogs). Re-arm the ref in the effect
body. Documented in docs/learnings.md; dev-only, production builds do
not double-mount.
@danielss-dev danielss-dev merged commit 07ecff1 into main Jul 7, 2026
1 of 2 checks passed
@danielss-dev danielss-dev deleted the feat/worktree-lifecycle branch July 7, 2026 21:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant