Skip to content

Fix #5099: titlebar band swallows right-sidebar button clicks#5102

Merged
austinywang merged 1 commit into
mainfrom
issue-5099-titlebar-band-hit-region
Jun 1, 2026
Merged

Fix #5099: titlebar band swallows right-sidebar button clicks#5102
austinywang merged 1 commit into
mainfrom
issue-5099-titlebar-band-hit-region

Conversation

@austinywang
Copy link
Copy Markdown
Contributor

@austinywang austinywang commented Jun 1, 2026

Fixes #5099.

Supersedes #5101. That PR (now merged) removed the titlebarInteractiveControl() reparenting and added registry-based first-mouse — both fine hardening, but they were the wrong layer: the buttons stayed dead. This PR fixes the actual cause.

Symptom

Right-sidebar top buttons (Files / Search / Feed / Vault, plus close + open-as-pane) render normally but dead-click — no hover, no press, no panel switch. The keyboard toggle still works, which isolates it to buttons not receiving mouse events, not the RightSidebarMode action path.

Root cause (regression from #5017, ca73d65cf)

Before #5017 the workspace titlebar was an overlay placed "only over terminal content, not the sidebar." #5017 ("Polish sidebar extensions and titlebar chrome") changed it into a full-width workspaceTitlebarBand layered at zIndex(100) over the content/sidebar layout. Its customTitlebar is:

.frame(maxWidth: .infinity)
.contentShape(Rectangle())   // full-width, hit-capturing

The right-sidebar mode bar lives inside the titlebar-height strip but below that band, so the band wins the SwiftUI hit-test across the entire top row and absorbs every click/hover on those buttons.

The left sidebar's titlebar controls live in the AppKit titlebar accessory (above the band), so only the right sidebar regressed. The keyboard path never goes through hit-testing, so it kept working — exactly the reported behavior.

How it was confirmed (empirical, on a tagged dev build)

  • An NSView.hitTest probe on the content host resolved right-sidebar button clicks (winPt x≈799–993, top strip) to the hosting view itself — i.e. no NSView subview (drag handle, portal, etc.) claimed them.
  • A probe inside the mode-button action (rightSidebar.modeButton.tap) and the button's onHover both fired zero times on clicks/hover — the full-width SwiftUI band in front was consuming the events before SwiftUI routed them to the buttons.

Fix

Confine the band's interactive surface to the area left of the right sidebar, restoring the pre-#5017 "not over the sidebar" intent:

customTitlebar(appearance: appearance)
    .padding(.trailing, rightSidebarVisible ? rightSidebarWidth : 0)

The title text is left-aligned, so the trailing inset hides nothing; window-drag/double-click over the terminal-area titlebar is preserved; and the right-sidebar mode bar now receives its own clicks. Only the trailing (right-sidebar) edge needs ceding — the left sidebar's controls are in the titlebar accessory above the band.

Tests

This is a SwiftUI hit-test / z-order layering bug in the full-size-content titlebar band; it depends on the real NSWindow titlebar compositing and is not cleanly reproducible in a headless unit test, so I'm not adding a synthetic one rather than faking a passing test. Verified manually on a tagged dev build (the mode-button action fires on click after the change).

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.

Summary by CodeRabbit

  • Bug Fixes
    • Fixed an issue where right sidebar controls were not responding to clicks and hover events when the right sidebar was visible.

…5099)

Right-sidebar top buttons (Files/Search/Feed/Vault, plus close/open-as-pane)
rendered normally but dead-clicked — no hover, no press, no panel switch —
while the keyboard toggle kept working.

Root cause (regression from #5017, ca73d65): the workspace titlebar was
changed from an overlay scoped "only over terminal content, not the sidebar"
into a full-width `workspaceTitlebarBand` layered at `zIndex(100)` over the
content/sidebar layout. Its `customTitlebar` is
`.frame(maxWidth: .infinity).contentShape(Rectangle())` — a full-width,
hit-capturing strip. The right-sidebar mode bar lives inside the
titlebar-height strip but below that band, so the band won the SwiftUI
hit-test across the whole top row and absorbed every click/hover on those
buttons. The left sidebar's titlebar controls live in the AppKit titlebar
accessory (above the band), so only the right sidebar regressed; the keyboard
path never goes through hit-testing, so it was unaffected.

Confirmed empirically on a tagged dev build: an `NSView.hitTest` probe on the
content host resolved right-sidebar button clicks to the hosting view (no
NSView subview claimed them), and a probe in the mode-button action never
fired — the SwiftUI band in front was consuming the hits.

Fix: confine the band's interactive surface to the area left of the right
sidebar via `.padding(.trailing, rightSidebarWidth)`, restoring the original
"not over the sidebar" intent so the right-sidebar mode bar receives its own
clicks again. The title text is left-aligned and unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 1, 2026 11:02am
cmux-staging Building Building Preview, Comment Jun 1, 2026 11:02am

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Review Change Stack

Caution

Review failed

Pull request was closed or merged during review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 027e279f-6d11-432a-bfc7-4377fadbafcb

📥 Commits

Reviewing files that changed from the base of the PR and between 588aa9d and 49ec477.

📒 Files selected for processing (1)
  • Sources/ContentView.swift

📝 Walkthrough

Walkthrough

The workspaceTitlebarBand interactive surface in the workspace view now includes conditional trailing padding when the right sidebar is visible. This prevents the full-width drag/double-click titlebar hit-test area from intercepting and swallowing mouse clicks on the right-sidebar mode bar buttons (Files, Search, Feed, Vault).

Changes

Titlebar Hit-Test Adjustment

Layer / File(s) Summary
Titlebar Band Trailing Padding
Sources/ContentView.swift
The workspaceTitlebarBand now applies trailing padding based on rightSidebarVisible and rightSidebarWidth, ensuring the titlebar's interactive surface does not overlap the right-sidebar mode buttons.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related PRs

  • manaflow-ai/cmux#5101: Modifies titlebar hit-testing registration to ensure right-sidebar buttons receive mouse events, directly complementing this PR's layout-padding fix for the same click-routing issue.
  • manaflow-ai/cmux#5036: Adjusts titlebar overlay padding to avoid overlapping the left sidebar, using a parallel approach to prevent hit-test interference with sidebar controls.
  • manaflow-ai/cmux#5089: Modifies custom titlebar padding logic to prevent the titlebar's hit-test region from overlapping the sidebar, employing similar hit-test exclusion principles.

Poem

🐰 The titlebar once stole every click,
But padding saves the sidebar's trick—
No more lost taps on Files or Feed,
The right-side buttons get their deed! ✨


Caution

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

  • Ignore

❌ Failed checks (1 error, 1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Cmux Swift Blocking Runtime ❌ Error PR introduces Task.sleep(nanoseconds: 150_000_000) in production ContentView.swift for workspace handoff timeout, violating swift-blocking-runtime rules against Task.sleep outside test code. Replace Task.sleep timeout with callback/notification-based handoff completion signal or use a dedicated timer abstraction instead of blocking sleep.
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift @Concurrent ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (15 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: addressing the titlebar band that was blocking right-sidebar button clicks, which matches the core change.
Description check ✅ Passed The description provides detailed context (symptom, root cause, confirmation method, fix explanation), but is missing the testing and checklist sections from the template.
Linked Issues check ✅ Passed The PR fully addresses issue #5099 by fixing the titlebar band's hit-test area to stop overlapping the right-sidebar buttons, restoring mouse-event handling while preserving other titlebar behaviors.
Out of Scope Changes check ✅ Passed The change is narrowly scoped to confining the workspaceTitlebarBand hit-test region away from the right sidebar via trailing padding—directly addressing the regression without unrelated alterations.
Cmux Swift Actor Isolation ✅ Passed PR adds only a padding modifier to a SwiftUI View in ContentView. No actor isolation violations, implicit MainActor issues, or mutable shared state introduced.
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift code (Sources/ContentView.swift). Rule applies to TypeScript, JavaScript, shell only; Swift covered by separate rule. No existing runtime code worsened.
Cmux Algorithmic Complexity ✅ Passed The change adds a conditional padding modifier accessing only scalar properties with O(1) lookup; no collection scans, filtering, or sorting occurs.
Cmux Swift Concurrency ✅ Passed The PR adds only a SwiftUI .padding modifier to fix a hit-testing issue; no legacy async patterns introduced.
Cmux Swift File And Package Boundaries ✅ Passed Initial repository import of established application. Specific PR change was a focused 12-line bug fix to workspaceTitlebarBand with padding adjustment.
Cmux Swift Logging ✅ Passed PR only adds padding and explanatory comments to fix titlebar hit-test behavior; no prohibited logging patterns (print, debugPrint, dump, NSLog) or secrets exposed in code or comments.
Cmux User-Facing Error Privacy ✅ Passed PR adds only a SwiftUI padding modifier to fix titlebar-sidebar hit-testing; no user-facing errors, alerts, or sensitive data exposed; includes developer-only code comments.
Cmux Full Internationalization ✅ Passed PR adds layout fix with developer comments only; no user-facing text added or changed, so internationalization rules do not apply.
Cmux Swiftui State Layout ✅ Passed Only a conditional padding modifier is added based on existing computed properties; no new state patterns or violations introduced.
Cmux Architecture Rethink ✅ Passed Fix adds conditional trailing padding using existing state. No timing repairs, mutable flags, observers, or split lifecycle ownership introduced. Small, correct layout fix with clear invariant.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds padding to existing workspaceTitlebarBand view; no standalone windows (NSWindow, NSPanel, NSWindowController, Window/WindowGroup) created or materially changed.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5099-titlebar-band-hit-region

Warning

Review ran into problems

🔥 Problems

Git: Failed to clone repository. Please run the @coderabbitai full review command to re-trigger a full review. If the issue persists, set path_filters to include or exclude specific files.


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 and usage tips.

@austinywang austinywang merged commit f4ff9de into main Jun 1, 2026
17 of 19 checks passed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Jun 1, 2026

Caution

Failed to replace (edit) comment. This is likely due to insufficient permissions or the comment being deleted.

Error details
{"name":"HttpError","status":500,"request":{"method":"PATCH","url":"https://api.github.com/repos/manaflow-ai/cmux/issues/comments/4591947850","headers":{"accept":"application/vnd.github.v3+json","user-agent":"octokit.js/0.0.0-development octokit-core.js/7.0.6 Node.js/24","authorization":"token [REDACTED]","content-type":"application/json; charset=utf-8"},"body":{"body":"<!-- This is an auto-generated comment: summarize by coderabbit.ai -->\n<!-- review_stack_entry_start -->\n\n[![Review Change Stack](https://storage.googleapis.com/coderabbit_public_assets/review-stack-in-coderabbit-ui.svg)](https://app.coderabbit.ai/change-stack/manaflow-ai/cmux/pull/5102?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)\n\n<!-- review_stack_entry_end -->\n<!-- This is an auto-generated comment: review in progress by coderabbit.ai -->\n\n> [!NOTE]\n> Currently processing new changes in this PR. This may take a few minutes, please wait...\n> \n> <details>\n> <summary>⚙️ Run configuration</summary>\n> \n> **Configuration used**: Path: .coderabbit.yaml\n> \n> **Review profile**: ASSERTIVE\n> \n> **Plan**: Pro\n> \n> **Run ID**: `027e279f-6d11-432a-bfc7-4377fadbafcb`\n> \n> </details>\n> \n> <details>\n> <summary>📥 Commits</summary>\n> \n> Reviewing files that changed from the base of the PR and between 588aa9dd8381982a13326259eb0ff4be58084a24 and 49ec477a7cd31663766d2134bcc47c337fc50d96.\n> \n> </details>\n> \n> <details>\n> <summary>📒 Files selected for processing (1)</summary>\n> \n> * `Sources/ContentView.swift`\n> \n> </details>\n> \n> ```ascii\n>  ____________________________________\n> < Zero-day? Zero chance on my watch. >\n>  ------------------------------------\n>   \\\n>    \\   \\\n>         \\ /\\\n>         ( )\n>       .( o ).\n> ```\n\n<!-- end of auto-generated comment: review in progress by coderabbit.ai -->\n\n<!-- finishing_touch_checkbox_start -->\n\n<details>\n<summary>✨ Finishing Touches</summary>\n\n<details>\n<summary>📝 Generate docstrings</summary>\n\n- [ ] <!-- {\"checkboxId\": \"7962f53c-55bc-4827-bfbf-6a18da830691\"} --> Create stacked PR\n- [ ] <!-- {\"checkboxId\": \"3e1879ae-f29b-4d0d-8e06-d12b7ba33d98\"} --> Commit on current branch\n\n</details>\n<details>\n<summary>🧪 Generate unit tests (beta)</summary>\n\n- [ ] <!-- {\"checkboxId\": \"f47ac10b-58cc-4372-a567-0e02b2c3d479\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Create PR with unit tests\n- [ ] <!-- {\"checkboxId\": \"6ba7b810-9dad-11d1-80b4-00c04fd430c8\", \"radioGroupId\": \"utg-output-choice-group-unknown_comment_id\"} -->   Commit unit tests in branch `issue-5099-titlebar-band-hit-region`\n\n</details>\n\n</details>\n\n<!-- finishing_touch_checkbox_end -->\n<!-- tips_start -->\n\n---\n\nThanks for using [CodeRabbit](https://coderabbit.ai?utm_source=oss&utm_medium=github&utm_campaign=manaflow-ai/cmux&utm_content=5102)! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.\n\n<details>\n<summary>❤️ Share</summary>\n\n- [X](https://twitter.com/intent/tweet?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A&url=https%3A//coderabbit.ai)\n- [Mastodon](https://mastodon.social/share?text=I%20just%20used%20%40coderabbitai%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20the%20proprietary%20code.%20Check%20it%20out%3A%20https%3A%2F%2Fcoderabbit.ai)\n- [Reddit](https://www.reddit.com/submit?title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&text=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code.%20Check%20it%20out%3A%20https%3A//coderabbit.ai)\n- [LinkedIn](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fcoderabbit.ai&mini=true&title=Great%20tool%20for%20code%20review%20-%20CodeRabbit&summary=I%20just%20used%20CodeRabbit%20for%20my%20code%20review%2C%20and%20it%27s%20fantastic%21%20It%27s%20free%20for%20OSS%20and%20offers%20a%20free%20trial%20for%20proprietary%20code)\n\n</details>\n\n\n<sub>Comment `@coderabbitai help` to get the list of available commands and usage tips.</sub>\n\n<!-- tips_end -->"},"request":{"retryCount":3,"signal":{},"retries":3,"retryAfter":16}}}

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented Jun 1, 2026

Greptile Summary

This PR fixes the right-sidebar button dead-click regression introduced in #5017 by confining the customTitlebar's interactive hit surface (.contentShape(Rectangle())) to the area left of the right sidebar via a trailing padding inset.

  • Adds .padding(.trailing, rightSidebarVisible ? rightSidebarWidth : 0) to the customTitlebar overlay in workspaceTitlebarBand, so the full-width band no longer wins SwiftUI hit-tests over the right-sidebar mode bar (Files / Search / Feed / Vault, close, open-as-pane).
  • The left sidebar's titlebar controls live in the AppKit titlebar accessory above this band and are unaffected; only the trailing edge needs to be ceded to the right sidebar.

Confidence Score: 4/5

Safe to merge; the targeted trailing-padding inset correctly restores right-sidebar hit-testing without touching any other interaction surface.

The fix is minimal and correctly scoped — it pads only the customTitlebar overlay, leaving the outer Color.clear band (which has no hit surface) and all other overlays untouched. The two remaining issues are a redundant ternary that can be simplified and a speculative animation-desync edge case when the right sidebar is toggled inside an animated context.

Sources/ContentView.swift — specifically the animation suppression gap between the sidebar panel's .transaction { $0.animation = nil } and the new trailing padding modifier.

Important Files Changed

Filename Overview
Sources/ContentView.swift Single-line targeted fix for the titlebar hit-test swallow; minor redundant ternary and a potential animation-desync edge case with the sidebar panel's .transaction suppression.

Sequence Diagram

sequenceDiagram
    participant User
    participant WorkspaceTitlebarBand as workspaceTitlebarBand (zIndex 100)
    participant CustomTitlebar as customTitlebar (.contentShape(Rectangle()))
    participant RightSidebarModeBar as RightSidebarModeBar (Files/Search/Feed/Vault)

    Note over WorkspaceTitlebarBand,RightSidebarModeBar: Before fix (#5017 regression)
    User->>WorkspaceTitlebarBand: Mouse click over right sidebar area
    WorkspaceTitlebarBand->>CustomTitlebar: Hit-test, full-width .contentShape wins
    CustomTitlebar-->>RightSidebarModeBar: Event swallowed, buttons dead

    Note over WorkspaceTitlebarBand,RightSidebarModeBar: After fix (this PR)
    User->>WorkspaceTitlebarBand: Mouse click over right sidebar area
    WorkspaceTitlebarBand->>CustomTitlebar: .padding(.trailing, rightSidebarWidth) confines interactive surface
    CustomTitlebar-->>RightSidebarModeBar: Event passes through to mode bar buttons
Loading

Reviews (1): Last reviewed commit: "fix: stop titlebar band from swallowing ..." | Re-trigger Greptile

Comment thread Sources/ContentView.swift
// not the sidebar" intent. The left sidebar's titlebar controls live in
// the AppKit titlebar accessory (above this band), so only the trailing
// (right-sidebar) edge needs to be ceded here.
.padding(.trailing, rightSidebarVisible ? rightSidebarWidth : 0)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The ternary is redundant: rightSidebarWidth is already computed as rightSidebarVisible ? fileExplorerWidth : 0, so rightSidebarVisible ? rightSidebarWidth : 0 always equals rightSidebarWidth. Using rightSidebarWidth directly also keeps this line consistent with how the same property is used elsewhere (e.g. .frame(width: rightSidebarWidth) on the panel).

Suggested change
.padding(.trailing, rightSidebarVisible ? rightSidebarWidth : 0)
.padding(.trailing, rightSidebarWidth)

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch — simplified to .padding(.trailing, rightSidebarWidth) in e386c2f, since rightSidebarWidth already evaluates to 0 when the sidebar is hidden.

— Claude Code

Comment thread Sources/ContentView.swift
// not the sidebar" intent. The left sidebar's titlebar controls live in
// the AppKit titlebar accessory (above this band), so only the trailing
// (right-sidebar) edge needs to be ceded here.
.padding(.trailing, rightSidebarVisible ? rightSidebarWidth : 0)
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Animation desync on sidebar toggle

rightSidebarPanel carries .transaction { $0.animation = nil } (line 2240), so it snaps immediately when fileExplorerState.isVisible changes. The new .padding(.trailing, …) on the titlebar band has no matching animation suppression. If any ancestor view wraps the visibility toggle in a withAnimation, the sidebar panel disappears/appears instantaneously while the titlebar band slowly closes/opens its trailing gap — briefly leaving an uncovered strip (sidebar gone, band still padded) or a covered strip (sidebar present, band not yet padded). Adding .transaction { $0.animation = nil } on the same modifier or on the workspaceTitlebarBand call site would keep both surfaces in sync.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in e386c2f: added .animation(nil, value: rightSidebarWidth) on the band's trailing inset so it snaps in lockstep with the sidebar panel (which uses .transaction { $0.animation = nil }). The hit-region edge can no longer animate out of step and momentarily re-cover the mode bar during toggle.

— Claude Code

austinywang added a commit that referenced this pull request Jun 1, 2026
Address review feedback on #5102:
- `rightSidebarVisible ? rightSidebarWidth : 0` is redundant — `rightSidebarWidth`
  already evaluates to 0 when the sidebar is hidden. Use it directly.
- The right sidebar panel snaps without animation; match that on the titlebar
  band's trailing inset (`.animation(nil, value: rightSidebarWidth)`) so the
  hit-region edge can't animate out of step with the panel during toggle and
  briefly re-cover the mode bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
austinywang added a commit that referenced this pull request Jun 1, 2026
…ollowup

Follow-up to #5102: simplify titlebar inset + fix sidebar-toggle animation desync
pull Bot pushed a commit to macintoshUserchg/cmux_windows that referenced this pull request Jun 1, 2026
Address review feedback on manaflow-ai#5102:
- `rightSidebarVisible ? rightSidebarWidth : 0` is redundant — `rightSidebarWidth`
  already evaluates to 0 when the sidebar is hidden. Use it directly.
- The right sidebar panel snaps without animation; match that on the titlebar
  band's trailing inset (`.animation(nil, value: rightSidebarWidth)`) so the
  hit-region edge can't animate out of step with the panel during toggle and
  briefly re-cover the mode bar.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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.

Right sidebar top buttons (Files/Search/Feed/Vault) no longer clickable — dead clicks (keyboard toggle still works)

1 participant