Skip to content

fix(web): sidebar follows a worktree thread's live branch - #8126

Draft
sethwebster wants to merge 1 commit into
pingdotgg:mainfrom
sethwebster:upstream/sidebar-worktree-branch-label
Draft

fix(web): sidebar follows a worktree thread's live branch#8126
sethwebster wants to merge 1 commit into
pingdotgg:mainfrom
sethwebster:upstream/sidebar-worktree-branch-label

Conversation

@sethwebster

@sethwebster sethwebster commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

What Changed

Sidebar rows resolve their branch label through a new resolveSidebarThreadBranchLabel, which adopts the live gitStatus.refName for worktree threads and falls back to the stamped thread.branch on detached HEAD. Local-checkout rows are unchanged.

Applied to the three places that previously read thread.branch directly: the thread row label, the hover tooltip, and the search-result row (which shares that tooltip).

+91 / −4 across three files, one of them the test.

Why

Correction (under revision). An earlier version of this description claimed nothing reconciles thread.branch against git. That is wrong. followWorktreeBranchDrift (apps/server/src/orchestration/Layers/CheckpointReactor.ts:566) already adopts the checked-out branch at turn end, skipping detached HEAD, temporary branches and shared worktrees. See Deviations — this PR is being reworked and should not be reviewed yet.

Sidebar rows render the persisted thread.branch. The thread view resolves the same value through resolveBranchToolbarValue, which prefers the live gitStatus.refName. Because the server only follows drift at turn completion, the two disagree in the window between a mid-turn git checkout and the end of that turn, and indefinitely for a checkout never followed by a turn.

So the moment a worktree's checked-out branch diverges from the stamp, the two views disagree: the toolbar follows reality while the sidebar keeps showing a branch the thread has already left.

A worktree belongs to exactly one thread, so whichever ref is checked out there is that thread's branch. The shared local checkout is the opposite — its ref belongs to whichever thread last switched it — so those rows keep the branch of record and let the existing mismatch warning explain the difference.

The rows already subscribe to vcsEnvironment.status for that warning (Sidebar.tsx:792 and :1622), so reading the live ref costs no additional query.

Scope and non-goals

In scope: the web sidebar's branch display for worktree threads.

Explicit non-goals:

  • Does not persist anything. thread.branch stays stale in projection_threads, so mobile's thread list (apps/mobile/src/features/threads/thread-list-items.tsx:465) and PR matching still read the old value. Fixing that needs a server-side branch-update event, which [Bug]: Worktree threads lose their PR badge once the agent switches branches inside the worktree #4831 noted does not exist.
  • Does not make anything refresh faster. There is no .git watcher in apps/server/src; local status is only re-read on an in-app git action, an explicit vcsRefreshStatus, or the end-of-turn CheckpointReactor. The 30s loop refreshes remote status only. This PR makes the sidebar agree with the thread view, not update sooner — so it does not fix [Bug]: Branch name in session screen is not reactive #7409, whose repro is a bare git checkout in the terminal panel.
  • Does not touch local-checkout rows.

Related work

Decisions

  • Display-only rather than server-side reconciliation. Persisting the live ref would fix every surface at once, but it needs a new event and a rule for the shared-checkout case. Kept small on purpose, per CONTRIBUTING.
  • Worktree threads only. Adopting live refName on the shared local checkout would relabel every row in a project whenever one thread switched branches.
  • Fall back to the stamp, never to nothing. Detached HEAD reports a null ref; the row keeps its last known branch instead of going blank.

Implementation checklist

  • Add resolveSidebarThreadBranchLabel with worktree/local split and detached-HEAD fallback
  • Use it for the thread row label, the tooltip, and the search-result row
  • Unit tests for all five branches of the resolution
  • Before/after screenshots

Verification

  • vp test run apps/web/src/components/Sidebar.logic.test.ts — 110 passed, 5 new
  • tsgo --noEmit on apps/web — clean
  • vp lint --report-unused-disable-directives on the three changed files — clean
  • vp fmt --check on the three changed files — clean
  • Visual confirmation in a running client — isolated dev server against a seeded fixture; server logs confirm the status subscription resolving branch: agent/renamed-during-run for the worktree thread and branch: main for the local one

UI Changes

A worktree thread stamped t3code/rework-branch-toolbar whose worktree was switched to agent/renamed-during-run, next to a local-checkout thread on main as a control. Same fixture, same viewport; only the build differs.

Before After
sidebar before sidebar after

Before, the worktree row shows the stale stamp. After, it shows the branch the worktree is actually on. The local-checkout row reads main in both, confirming shared checkouts are untouched.

Security and compatibility

No contract, schema, or server change; no new network calls; no auth or trust-boundary surface. Purely which of two already-present client values is rendered. Fully backward compatible — a client without this change simply keeps showing the stamped branch.

Deviations

Adversarial review found three defects in this change as written. Recorded here rather than silently amended:

  1. False premise. resolveSidebarThreadBranchLabel's comment claims a worktree belongs to exactly one thread. It does not — new-thread-on-branch (Sidebar.tsx:3109) carries worktreePath into a second thread and useHandleNewThread.ts:403 persists it. The server guards this with an explicit worktreeIsShared check; this change has no equivalent, so two threads sharing a worktree would both be relabeled from one thread's checkout.
  2. Missing temporary-branch guard. The server skips isTemporaryWorktreeBranch (packages/shared/src/git.ts:107) so it does not race the first-turn auto-rename. This change adopts any non-null ref, so a row can briefly render a t3/<hex> placeholder.
  3. Row incoherence. Only the label follows the live ref. The PR badge, worktree tooltip, copy-branch, and new-thread-on-branch still key off thread.branch. Worse, resolveDisplayedThreadPr requires refName === thread.branch, so a drifted row now prints branch B while hiding branch B's PR badge.

Scope is being reconsidered in light of the existing server-side drift-follow.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes
  • I included a video for animation/interaction changes — n/a, no motion

Made with Claude Opus 5 in the Claude Code harness.

Note

Fix sidebar to show a worktree thread's live branch instead of stamped branch

  • Adds resolveSidebarThreadBranchLabel in Sidebar.logic.ts to compute the displayed branch label. For dedicated worktrees it uses the current git ref (gitStatus.refName) when available, falling back to the stamped thread.branch; for shared local checkouts it uses thread.branch.
  • Updates Sidebar.tsx so SidebarThreadRow, SidebarSearchResultRow, and SidebarThreadTooltip render the computed branchLabel instead of always using thread.branch. The tooltip hides the branch row when the label is null.
  • Adds tests in Sidebar.logic.test.ts covering worktree with switched branch, detached HEAD, no stamped branch, shared checkout with differing ref, and no branch.
  • Risk: threads in a dedicated worktree whose live ref differs from the stamped branch now show the live ref; any code expecting the sidebar to always display thread.branch will see the new value.

Macroscope summarized 7dca192.

Plan item: resolve the sidebar's branch label from live git status for
worktree threads.

Sidebar rows render the persisted `thread.branch`, which nothing ever
reconciles against git. The thread view resolves the same value through
`resolveBranchToolbarValue`, which prefers the live `gitStatus.refName`.
So once a worktree's checked-out branch diverges from the stamp, the two
views disagree and the sidebar keeps showing a branch the thread left.

A worktree belongs to exactly one thread, so its checked-out ref is that
thread's branch — `resolveSidebarThreadBranchLabel` adopts it for
worktree rows and falls back to the stamp on detached HEAD. The shared
local checkout is left alone: its ref belongs to whichever thread last
switched it, so adopting it would relabel every row in the project.

The rows already subscribe to `vcsEnvironment.status` for the mismatch
warning, so the live ref costs no extra query.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Aug 24, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 28e327ee-15e4-4f45-82ca-e5645e5709ef

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Branch name in session screen is not reactive

1 participant