fix: tone down branch-mismatch banner#4416
Conversation
The "You're on a different branch" warning fired the moment a mismatched
thread was opened, wasn't dismissible, and treated routine branch
switching as an anomaly. Reworked per user feedback:
- Copy is now a one-line fragment: "Branch changed — was <branch>", info
variant with a git-branch icon. The consequence ("sending will
continue on the current branch") lives in the chip tooltip.
- Banner only renders on send intent (composer focused or has draft
content); passive browsing shows nothing beyond the existing
branch-picker tint. Once revealed it stays mounted for the current
mismatch so it doesn't flicker on blur.
- "Move thread here" is gone — sending already rebinds the thread.
The single action is "Restore branch", with a confirm dialog only
when the working tree has uncommitted changes.
- Dismissible, keyed on threadId + both branches, session-scoped for
now (server-persisted cross-device ack is a planned follow-up).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
📝 WalkthroughWalkthroughBranch mismatch handling now includes deterministic keys, session-scoped dismissal, focus-aware banner visibility, a single restore action, and confirmation before switching branches when uncommitted working-tree changes exist. ChangesBranch mismatch handling
Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: Sequence Diagram(s)sequenceDiagram
participant Composer
participant ChatView
participant AlertDialog
participant BranchCheckout
Composer->>ChatView: Select Restore branch
ChatView->>AlertDialog: Request confirmation for working-tree changes
AlertDialog->>ChatView: Confirm restore
ChatView->>BranchCheckout: Switch to thread branch
Poem
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 2 potential issues.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit f64a69a. Configure here.
ApprovabilityVerdict: Approved UX refinement that reduces branch-mismatch banner visibility to only when the user is drafting a message. Changes are self-contained, well-tested, and don't alter core branch detection or switching functionality. You can customize Macroscope's approvability policy. Learn more. |
Cursor Bugbot caught two real gating bugs: - ChatView autofocuses the composer on every thread open, so "composer focused" was always true and the banner showed during passive reading anyway. Draft content is now the only intent signal. - revealedBranchMismatchKey was never cleared, so a mismatch that resolved and later recurred bypassed the gate. The key now resets whenever the active mismatch changes or resolves. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
apps/web/src/components/ChatView.tsx (1)
3835-3846: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueConsider extracting
composerHasDraftContentintoChatView.logic.ts.Every other predicate in this feature (
branchMismatchKey,shouldShowBranchMismatchBanner) is a pure, independently-tested helper inChatView.logic.ts. This "has draft content" check is left inline in the component, breaking that pattern and making it untestable without mounting the component.♻️ Suggested extraction
+// ChatView.logic.ts +export function composerDraftHasContent( + draft: { prompt: string; images: unknown[]; terminalContexts: unknown[]; elementContexts: unknown[]; previewAnnotations: unknown[]; reviewComments: unknown[] } | undefined, +): boolean { + return Boolean( + draft && + (draft.prompt.trim().length > 0 || + draft.images.length > 0 || + draft.terminalContexts.length > 0 || + draft.elementContexts.length > 0 || + draft.previewAnnotations.length > 0 || + draft.reviewComments.length > 0), + ); +}- const composerHasDraftContent = useComposerDraftStore((store) => { - const draft = store.getComposerDraft(composerDraftTarget); - return Boolean( - draft && - (draft.prompt.trim().length > 0 || - draft.images.length > 0 || - draft.terminalContexts.length > 0 || - draft.elementContexts.length > 0 || - draft.previewAnnotations.length > 0 || - draft.reviewComments.length > 0), - ); - }); + const composerHasDraftContent = useComposerDraftStore((store) => + composerDraftHasContent(store.getComposerDraft(composerDraftTarget)), + );🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@apps/web/src/components/ChatView.tsx` around lines 3835 - 3846, Extract the draft-content predicate from the composerHasDraftContent selector in ChatView into a pure helper in ChatView.logic.ts, alongside branchMismatchKey and shouldShowBranchMismatchBanner. Have the selector delegate to that helper while preserving all existing prompt, image, terminal-context, element-context, preview-annotation, and review-comment checks, and make the helper independently testable.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@apps/web/src/components/ChatView.tsx`:
- Around line 3835-3846: Extract the draft-content predicate from the
composerHasDraftContent selector in ChatView into a pure helper in
ChatView.logic.ts, alongside branchMismatchKey and
shouldShowBranchMismatchBanner. Have the selector delegate to that helper while
preserving all existing prompt, image, terminal-context, element-context,
preview-annotation, and review-comment checks, and make the helper independently
testable.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 85e733f5-156d-4036-8819-8f410da8678b
📒 Files selected for processing (3)
apps/web/src/components/ChatView.logic.test.tsapps/web/src/components/ChatView.logic.tsapps/web/src/components/ChatView.tsx
2 upstream commits (pingdotgg#4414, pingdotgg#4416). No conflicts.

What Changed
Reworks the "You're on a different branch" composer banner added in #2284, which fired on thread open, wasn't dismissible, and alarmed users doing routine branch switching (user report).
Branch changed — was <branch>, info variant, git-branch icon. No description block; the consequence ("sending will continue on the current branch") lives in the branch chip's tooltip. The current branch is never named — it's already visible in the branch picker below.resolveThreadMetadataUpdateForNextTurn, so the button duplicated the default outcome. The remaining action is Restore branch (checkout the thread's branch).threadId + threadBranch + currentBranchmismatch for the session. If the checkout later moves to a different branch, the banner is eligible again.Mismatch detection (
resolveLocalCheckoutBranchMismatch) is unchanged, as is the branch-picker tint/popover from #2284.Design doc with mockups and the reasoning (including why we deliberately did not auto-sync
thread.branchon turn completion): https://3vj3y2v9zb4u.postplan.devWhy
The mismatch is valid but the old presentation punished the most common innocent flow: finish issue A, checkout a branch for issue B, reopen thread A just to read context → big non-dismissible amber warning at thread-open time, when the only actual risk exists at send time.
Follow-up (separate PR)
Server-persisted dismissal ack (
{threadBranch, checkoutBranch}on thread metadata) so dismissals hold across devices; session-scoped for now.Testing
ChatView.logic.test.ts)bun run typecheck,bun run lint, fullapps/webunit suite (1495 tests) all passChecklist
🤖 Generated with Claude Code
Note
Low Risk
Composer UX and when the banner renders; restore still uses existing git checkout and thread metadata RPCs, with no change to mismatch detection itself.
Overview
Reworks the local checkout vs thread branch composer banner so it no longer appears on every thread open.
Intent gating: New
shouldShowBranchMismatchBannerlogic only shows the notice when the composer has draft content (or after it was already shown for the same mismatch, so clearing the draft does not flicker it away). Passive reading stays quiet aside from existing branch-picker cues.Copy and actions: The banner switches from a warning with two repair buttons to an info line (
Branch changed — was <thread branch>) with tooltip context. Move thread here is removed; Restore branch checks out the thread’s branch (with an AlertDialog when the working tree is dirty).Dismissal: Per-mismatch keys (
threadId:threadBranch:currentBranch) can be dismissed for the session via a module-level set inChatView.logic.ts, with unit tests for gating, keys, and dismissal.Reviewed by Cursor Bugbot for commit b40c0f1. Bugbot is set up for automated code reviews on this repo. Configure here.
Note
Reduce branch-mismatch banner intrusiveness in ChatView
Setin ChatView.logic.ts.AlertDialogconfirmation prompt appears before switching branches.GitBranchIcon.Macroscope summarized b40c0f1.
Summary by CodeRabbit
New Features
Bug Fixes