Skip to content

feat: order worktree list by session and pr state#289

Open
hugocasa wants to merge 1 commit into
mainfrom
feat/worktree-list-ordering
Open

feat: order worktree list by session and pr state#289
hugocasa wants to merge 1 commit into
mainfrom
feat/worktree-list-ordering

Conversation

@hugocasa

Copy link
Copy Markdown
Contributor

Summary

Worktree lists were sorted alphabetically by branch, which buried the worktrees you actually care about. They now rank by relevance: open worktrees first, then closed ones ordered by how much attention their PR still needs.

The order is:

  1. Open worktrees (a live tmux session)
  2. Closed, with an open PR
  3. Closed, with a closed/merged PR
  4. Closed, with no PR

Within each group the previous alphabetical-by-branch order is kept as a tiebreaker.

Three judgment calls worth flagging for review:

  • "Open" means a live tmux session (mux), which is what the sidebar's closed badge already keys off — so the ordering matches the label rendered next to it.
  • Worktrees mid-creation count as open. They have no session yet, so a literal reading would fling every newly created worktree to the bottom until it boots. The UI already declines to dim them, so the sort follows that.
  • An open worktree stays on top even if its PR is merged. PR state only breaks ties among closed worktrees.

Ordering was already server-side (a single sort in buildWorktreeSnapshots; the frontend only filters and nests), so this is one shared comparator rather than a per-surface change.

Changes

  • Add compareWorktreeOrder / WorktreeOrderFields to backend/src/domain/policies.ts — a pure comparator over the normalized fields ordering depends on (branch, open, prStates).
  • Use it in buildWorktreeSnapshots (snapshot-service.ts), replacing the alphabetical sort. This drives the frontend sidebar.
  • Use it in the CLI's wt list (bin/src/worktree-commands.ts), which sorted alphabetically on its own and would otherwise have disagreed with the sidebar. It builds rows from disk rather than from the snapshot, so it now reads the cached PR file per worktree via readWorktreePrs — one extra local read, no network.
  • Tests for the comparator, the snapshot ordering, and an update to the one CLI test that hard-coded the old alphabetical order.

Not changed

In a stacked worktree the parent's rank still determines where the whole subtree sits, so a closed parent drags its open children down with it. That falls out of the existing baseBranch nesting in buildWorktreeListRows, not the sort, and is left as-is.

Test plan

  • bun run test — full suite passes (838 tests)
  • bun run --cwd backend check — typechecks
  • In the sidebar, confirm open worktrees sort above all closed ones, and that closed ones with an open PR sit above merged/closed-PR ones, with no-PR worktrees last
  • Create a new worktree and confirm it does not jump to the bottom of the list while its session starts
  • webmux wt list — confirm the CLI order matches the sidebar

Generated with Claude Code

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
webmux 33ba804 Commit Preview URL

Branch Preview URL
Jul 16 2026, 10:04 AM

@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@hugocasa
hugocasa marked this pull request as ready for review July 16, 2026 10:31
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude encountered an error —— View job


I'll analyze this and get back to you.

@hugocasa
hugocasa marked this pull request as draft July 16, 2026 10:58
@hugocasa
hugocasa marked this pull request as ready for review July 16, 2026 10:58
@claude

claude Bot commented Jul 16, 2026

Copy link
Copy Markdown

Claude finished @hugocasa's task in 1m 54s —— View job


Code Review — PR #289

Verdict: LGTM. Clean, well-scoped change. The comparator is a single shared source of truth, the ordering fields are normalized identically on both surfaces, and the test coverage matches the behavior described. No blocking issues found. A couple of minor notes below.

  • Read the diff and relevant files
  • Review comparator logic (compareWorktreeOrder)
  • Review snapshot service integration
  • Review CLI integration
  • Review tests

What I verified

  • Comparator is well-formed. compareWorktreeOrder (backend/src/domain/policies.ts:83) is a proper total order: rank difference first, branch.localeCompare tiebreak, so it's transitive and deterministic — no reliance on sort stability.
  • The ordering actually reaches the UI. buildWorktreeListRows (frontend/src/lib/worktree-list.ts:91) only nests/filters and appends roots and children in input order, so the server-side sort in buildWorktreeSnapshots drives the sidebar. The claim in the PR body holds.
  • prStates normalization matches PrEntry["state"] ("open" | "closed" | "merged", model.ts:180), and readWorktreePrs returns [] on any read/parse failure (fs.ts:347), so a missing/corrupt PR cache degrades gracefully to rank 3 rather than throwing in the CLI path.
  • open = mux || creation !== null (snapshot-service.ts:110) correctly keeps mid-creation worktrees at the top, matching the sidebar's non-dimming behavior.

Minor notes (non-blocking)

  1. CLI vs. sidebar parity for mid-creation worktrees. On the sidebar, open includes creation !== null, but the CLI's isOpen is purely openWindows.has(...) (worktree-commands.ts:694). A worktree still booting has no tmux window, so wt list will rank it as closed while the sidebar ranks it open. This is a transient race and the CLI is a point-in-time snapshot, so it's a fair judgment call — worth a one-line note in the PR body's "judgment calls" list since AGENTS.md emphasizes surface parity.

  2. Test ordering of closed-vs-merged is incidental. In snapshot-service.test.ts, feature/closed-closed-pr is expected before feature/closed-merged-pr, and in domain-policies.test.ts closed-pr before merged-pr. Both share rank 2 (closed and merged are one group), so this ordering comes entirely from the localeCompare branch-name tiebreak, not from any closed-above-merged rule. The tests pass, but a reader might infer a distinction that doesn't exist — a brief comment noting they're the same rank would prevent a future "why did merged move above closed?" confusion.

Nothing here needs to change to merge. Nice, focused PR with good test coverage.

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