fix: label worktree rows by the branch they are for, not the folder name - #24
Merged
Conversation
The main worktree vanished from `ch list worktree` and the TUI dashboard whenever its clone dir had a non-default branch checked out. Each row was labelled with whatever HEAD happened to be on, so a geomonitor clone sitting on docs/rbac-epic showed up as "docs/rbac-epic" — "main" never appeared, and nothing distinguished the main worktree from the branch it was holding. The CLI compounded it by dropping the head-state entirely. The fix separates addressing from display. Ref stays the worktree's identity — the folder-derived key every operation feeds back — while a new resolveDisplay decides the label from authoritative sources rather than reconstructing it from the folder name. git's live branch is the ground truth of what is checked out. The branch a worktree is *for* — its original — is the configured default branch for the main clone dir, or the branch a running session recorded in @codeherd_branch for any other worktree. A row is only shown as diverged, "<original> (on <live>)", when HEAD has actually left that original; the main clone on a feature branch therefore stays "main (on docs/rbac-epic)" and remains spottable. A worktree with no known original — a non-main worktree with no session — is never treated as diverged: it shows exactly what git reports. This is what keeps the folder name out of the label. Rendering the folder identity is what produced "chore-cron-rework (on chore/restore-cron-rework)" for a worktree simply sitting on its own branch, whose directory name happens not to match it. The session is a refinement, never a dependency: the common case is a worktree with no session, and it must resolve from git alone. Both surfaces now render through one FormatBranchLabel, so the CLI listing and the TUI agree on how a diverged or detached worktree reads. Display resolution moves after the session join in List, because a non-main worktree's original branch lives on its session, not in the git worktree entry. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The main worktree vanished from
ch list worktreeand the TUI dashboard whenever its clone dir had a non-default branch checked out. Each row was labelled with whatever HEAD happened to be on, so a geomonitor clone sitting ondocs/rbac-epicshowed up asdocs/rbac-epic—mainnever appeared, and nothing distinguished the main worktree from the branch it was holding. The CLI compounded it by dropping the head-state entirely.Fix
Separate addressing from display.
Refstays the worktree's identity — the folder-derived key every operation feeds back — while a newresolveDisplaydecides the label from authoritative sources rather than reconstructing it from the folder name:default_branchfor the main clone dir, or the branch a running session recorded in@codeherd_branchfor any other worktree.<original> (on <live>), only when HEAD has actually left that original — so the main clone on a feature branch staysmain (on docs/rbac-epic)and remains spottable.A worktree with no known original (a non-main worktree with no session) is never treated as diverged: it shows exactly what git reports. This keeps the folder name out of the label — rendering the folder identity is what produced
chore-cron-rework (on chore/restore-cron-rework)for a worktree simply sitting on its own branch whose directory name happens not to match it. The session is a refinement, never a dependency: the common case is a worktree with no session, and it resolves from git alone.Both surfaces now render through one
FormatBranchLabel, so the CLI listing and the TUI agree on how a diverged or detached worktree reads. Display resolution moves after the session join inList, because a non-main worktree's original branch lives on its session, not in the git worktree entry.Tests
FormatBranchLabel.buildItems → delegate.TestListWorktree_noSession_showsLiveBranch(primary path) andTestListWorktree_sessionDivergence_showsRecordedBranch(real shell session).make checkpasses: 85.5% coverage, integration green, lint 0 issues, build OK.🤖 Generated with Claude Code