Skip to content

Fix right sidebar titlebar double-click - #3750

Merged
austinywang merged 9 commits into
mainfrom
issue-3746-right-sidebar-titlebar-doubleclick
May 9, 2026
Merged

austinywang merged 9 commits into
mainfrom
issue-3746-right-sidebar-titlebar-doubleclick

Conversation

@austinywang

@austinywang austinywang commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3746.

Bug

The right sidebar mode bar is custom titlebar chrome, but empty space in the Files / Find / Vault row only registered the minimal hit region and did not opt into the standard titlebar double-click/drag interaction path. Double-clicking empty space there did nothing while other titlebar regions zoomed/minimized normally.

Change

  • Added a layout-neutral titlebarDoubleClickRegion() SwiftUI modifier that layers WindowDragHandleView and TitlebarDoubleClickMonitorView behind an existing chrome view.
  • Applied it to RightSidebarPanelView.modeBar while keeping MinimalModeTitlebarControlHitRegionView.
  • Added an XCUnit regression that mounts the real RightSidebarPanelView, sends a double-click through NSApp.sendEvent, and asserts the window titlebar action is invoked.

Manual Results

Before: baseline build issue-3746-right-sidebar-titlebar-doubleclick-baseline left window bounds unchanged (526,181,460,360) after double-clicking empty space between Files and Find.
After: tagged build issue-3746-right-sidebar-titlebar-doubleclick builds and launches; the right-sidebar mode bar remains correctly sized at the top, empty space routes to WindowDragHandleView in the debug log, and Files / Find / Vault button clicks still switch modes. Automation-generated double-clicks report clickCount=1, so the actual clickCount=2 titlebar action is covered by the XCUnit regression and CI.

Files Changed

  • Sources/WindowDragHandleView.swift
  • Sources/RightSidebarPanelView.swift
  • cmuxTests/WindowAndDragTests.swift

Test Plan

  • CI: run the new WindowDragHandleHitTests.testRightSidebarModeBarEmptySpaceDoubleClickPerformsTitlebarAction.
  • Manual: open right sidebar, double-click empty space in the mode bar, verify zoom/minimize follows the system titlebar preference.
  • Manual: click Files / Find / Vault and verify tab switching still works.
  • Manual: drag empty mode-bar space and verify it behaves like titlebar chrome.

Note

Medium Risk
Adjusts custom titlebar hit-testing/monitoring to change which clicks are intercepted, which could subtly affect drag/double-click behavior in other titlebar-adjacent regions. Includes a new integration-style XCTest to reduce regression risk.

Overview
Fixes the right sidebar mode bar’s empty titlebar space so it participates in standard macOS titlebar interactions (drag + double-click zoom/minimize) by layering WindowDragHandleView behind the mode buttons and adding TitlebarDoubleClickMonitorView to the bar background.

Updates the double-click monitor to defer to minimal-mode registered control hit regions only when no co-located drag handle would capture the click, and tags drag-handle views with a stable identifier to support this lookup.

Adds an XCTest regression that hosts the real RightSidebarPanelView, synthesizes a double-click into an actually-capturable empty point, and asserts the window’s standard titlebar action is invoked.

Reviewed by Cursor Bugbot for commit b8f65cd. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Restores standard macOS titlebar double‑click and drag in the right sidebar mode bar. Empty space between Files/Find/Vault now zooms or minimizes per system setting and stays draggable. Fixes #3746.

  • Bug Fixes
    • Use a WindowDragHandleView sibling behind the mode bar buttons and a TitlebarDoubleClickMonitorView background; keep MinimalModeTitlebarControlHitRegionView.
    • Have the double‑click monitor defer to registered controls only when no colocated drag handle would capture the point, so empty chrome triggers the system action and buttons keep their behavior.
    • Add a deterministic regression test that finds a real empty titlebar point via the drag‑handle capture predicate, sends a double‑click with NSApp.sendEvent, and verifies the window performs the standard titlebar action.

Written for commit b8f65cd. Summary will update on new commits.

Summary by CodeRabbit

  • New Features
    • Right sidebar mode bar now responds to double‑clicks on empty titlebar space, invoking window titlebar actions (e.g., zoom).
  • Tests
    • Added comprehensive test coverage verifying right sidebar titlebar double‑click behavior.

The right sidebar mode bar is a custom titlebar region, so empty space in it should dispatch the same zoom/minimize action as other titlebar strips. This regression test mounts the real SwiftUI sidebar in an AppKit window and sends a double-click through NSApp so CI exercises the hit-testing path instead of a source-text assertion.

Constraint: Local tests are intentionally not run in this workflow; CI owns test execution.

Confidence: medium

Scope-risk: narrow

Directive: Keep this test on the runtime event path; do not replace it with structural source assertions.

Tested: Not run locally per instruction.

Not-tested: CI has not run yet.
The right sidebar mode bar is part of the custom titlebar chrome but only registered the minimal-mode hit region, so empty-space double-clicks never reached the standard macOS titlebar action. A small SwiftUI modifier now owns the WindowDragHandleView plus TitlebarDoubleClickMonitorView pairing, and the right sidebar mode bar opts into that same interaction path.

Constraint: Do not run xcodebuild directly; final verification must use the tagged reload script.

Rejected: Add a one-off ZStack directly in RightSidebarPanelView | repeats the existing two-layer pattern and makes future titlebar chrome easier to miss.

Confidence: medium

Scope-risk: narrow

Directive: Use titlebarDoubleClickRegion() for future custom titlebar strips with empty draggable space.

Tested: git diff --check

Not-tested: Local unit/UI tests were not run per instruction; CI must execute the regression test.
The base branch advanced with tab-bar click-zone and CI updates after the local fix commits were created. Merging origin/main now keeps the PR branch testable against the same infrastructure and click-routing code that CI will use.

Constraint: Preserve the red/green regression and fix commits below this merge.

Confidence: high

Scope-risk: moderate

Directive: Do not squash away the two underlying issue-3746 commits before verifying the regression-test story in the PR.

Tested: git status --short --branch

Not-tested: Local tests/build not run after merge; CI and final reload remain pending.
Dogfooding the first helper version showed the ZStack wrapper could let the WindowDragHandleView representable influence the surrounding SwiftUI layout. Making the drag handle a background keeps the interaction layer bounded to the content's established frame while retaining the same empty-space hit path.

Constraint: Right sidebar chrome height must remain owned by rightSidebarChromeBar().

Rejected: Add a fixed frame inside the helper | would bake one caller's sizing into a generic titlebar modifier.

Confidence: medium

Scope-risk: narrow

Directive: Keep titlebarDoubleClickRegion() layout-neutral; callers own size before applying it.

Tested: git diff --check

Not-tested: Local tests not run per instruction; tagged reload/manual dogfood is next.
@vercel

vercel Bot commented May 8, 2026 •

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment May 8, 2026 11:24pm
cmux-staging Building Building Preview, Comment May 8, 2026 11:24pm

@coderabbitai

coderabbitai Bot commented May 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a stable drag-handle identifier and deferment helpers, updates the titlebar double-click monitor to defer when a registered drag handle would capture the click, embeds the drag-handle + monitor into the right-sidebar mode bar, and adds tests that synthesize double-clicks asserting zoom/miniaturize behavior.

Changes

Titlebar Double-Click Region for Right Sidebar

Layer / File(s) Summary
Identifier & DraggableView Setup
Sources/WindowDragHandleView.swift
WindowDragHandleView.DraggableView now assigns WindowDragHandleView.viewIdentifier to its identifier in both initializer paths.
Deferment Logic Helpers
Sources/WindowDragHandleView.swift
Adds private helpers that walk the view hierarchy to detect whether a registered drag-handle view would capture a click and combine that with minimal-mode titlebar control hit detection to decide whether deferment is required.
Monitor Deferment Integration
Sources/WindowDragHandleView.swift
TitlebarDoubleClickMonitorView's left-mouse-down local monitor now consults the new deferment decision; deferred clicks clear lastClick and are returned unconsumed.
Debug Logging
Sources/WindowDragHandleView.swift
Adds a #if DEBUG log recording the result of handleTitlebarDoubleClick.
Right Sidebar Integration
Sources/RightSidebarPanelView.swift
modeBar now composes a ZStack with WindowDragHandleView and adds TitlebarDoubleClickMonitorView to the header background chain so double-clicks in empty mode-bar space invoke window actions.
Test Infrastructure
cmuxTests/WindowAndDragTests.swift
Adds RecordingTitlebarActionWindow, firstSubview(in:matching:), and firstCapturableTitlebarPoint(in:window:) helpers to locate the installed drag-handle and compute a capturable point.
Mode Bar Double-Click Test
cmuxTests/WindowAndDragTests.swift
Adds testRightSidebarModeBarEmptySpaceDoubleClickPerformsTitlebarAction which synthesizes a left double-click over an empty mode-bar point and asserts zoom/miniaturize call counts.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • manaflow-ai/cmux#3150: Modifies WindowDragHandleView and titlebar double-click handling; related identifier and monitor changes.
  • manaflow-ai/cmux#2130: Adds titlebar double-click region and related hit-test/defer logic similar to this PR.
  • manaflow-ai/cmux#620: Changes drag-handle hit-resolution helpers and titlebar hit detection paths.

Poem

🐇 In sidebar light the rabbit spies,
An empty stripe where double-click lies,
Drag-handle waits and defers with care,
A double tap — the window's in the air,
Zoom or tuck — the rabbit cheers!


Caution

Pre-merge checks failed

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

  • Ignore

❌ Failed checks (1 error, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Actor Isolation ❌ Error RightSidebarKeyboardFocusView (NSView subclass) introduced without @MainActor despite accessing AppDelegate.shared and main-thread-only NSView methods. Violates isolation pattern. Add @MainActor to RightSidebarKeyboardFocusView class declaration to match WindowObservingView pattern used elsewhere for NSView subclasses accessing main-thread services.
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.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix right sidebar titlebar double-click' clearly and concisely summarizes the main change in the changeset.
Linked Issues check ✅ Passed All coding objectives from issue #3746 are met: double-click behavior restored [#3746], drag interaction preserved [#3746], button behavior maintained [#3746], two-layer pattern applied [#3746], and regression test added [#3746].
Out of Scope Changes check ✅ Passed All changes directly address #3746 requirements: RightSidebarPanelView layout changes, WindowDragHandleView identifier additions, and regression test implementation are all in scope.
Cmux Swift Blocking Runtime ✅ Passed No new blocking/timing-based synchronization in production code. Changes are SwiftUI view layers, helper functions, and test scaffolding. All NSLock usage found is pre-existing.
Cmux No Hacky Sleeps ✅ Passed Check does not apply: all PR changes are Swift code. The rule covers only TypeScript, JavaScript, shell, and non-Swift runtime scripts; Swift timing is covered separately.
Cmux Swift Concurrency ✅ Passed No legacy async patterns introduced. Changes involve SwiftUI layout, event handling, and tests only.
Cmux Swift @Concurrent ✅ Passed No async functions, @concurrent annotations, or nonisolated async added. All new code is synchronous UI and AppKit helpers with no concurrent annotation rule violations.
Cmux Swift File And Package Boundaries ✅ Passed Adds 61 lines to 1567-line WindowDragHandleView (under 250-line threshold). Focused AppKit bridge code fixing #3746. No new responsibilities. Matches allowed "focused bug fix" case.
Cmux Swift Logging ✅ Passed All logging additions comply with swift-logging.md. Only addition is cmuxDebugLog guarded with #if DEBUG. No violations found.
Cmux Swiftui State Layout ✅ Passed No violations: no new @ObservableObject/@published, GeometryReader, lazy-list store refs, or mutations. WindowDragHandleView is NSViewRepresentable. ForEach passes value snapshots.
Cmux Architecture Rethink ✅ Passed Applies established pattern with clear ownership. No timing repairs, locks, observers, or mutable state. Safe correctness fix with documented coordination logic.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No new production windows created. Changes modify existing views and event handling. Test-only RecordingTitlebarActionWindow fixture is exempt per rule exceptions.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering the bug, change, testing approach, and manual verification.
✨ 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-3746-right-sidebar-titlebar-doubleclick

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.

@greptile-apps

greptile-apps Bot commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes the right sidebar mode bar so that empty titlebar space correctly participates in macOS drag and double-click (zoom/minimize) interactions, without breaking the Files/Find/Vault button clicks that share the same bar.

  • RightSidebarPanelView.modeBar is refactored from a flat HStack to a ZStack, with WindowDragHandleView layered behind the button row so empty space routes to the drag handle while buttons remain in front.
  • WindowDragHandleView gains a stable viewIdentifier and two new private helpers that, on a qualifying click, walk the window view tree to distinguish button-registered hit regions from true empty chrome before deciding whether to fire the titlebar action.
  • WindowAndDragTests adds a geometry-driven regression test that locates a real empty titlebar point via the drag-handle capture predicate, synthesizes a clickCount: 2 event through NSApp.sendEvent, and asserts synchronously — resolving previously-flagged timing and hardcoded-coordinate issues.

Confidence Score: 5/5

Safe to merge — the change is narrowly scoped to the right sidebar mode bar and its hit-testing path, the regression test exercises the real view hierarchy, and the existing button click paths are unaffected.

All changed logic is confined to a narrow path (clicks within the mode bar bounds that also pass a registered-control check). The new helpers add no new mutable state, no actor isolation concerns, and no blocking primitives. The synchronous NSApp.sendEvent regression test gives good coverage of the core double-click dispatch path.

Sources/WindowDragHandleView.swift — the new view-tree walk starts from window.contentView rather than a narrower anchor; worth tightening if the window hierarchy grows.

Important Files Changed

Filename Overview
Sources/WindowDragHandleView.swift Adds a stable viewIdentifier to DraggableView and introduces two new private helpers — titlebarDoubleClickMonitorHasCapturingDragHandle (recursive view-tree walk from window.contentView) and titlebarDoubleClickMonitorShouldDeferToRegisteredControl — to distinguish button-registered click regions from true empty-chrome regions before firing the titlebar action. The walk is scoped to qualifying clicks only but starts from the root.
Sources/RightSidebarPanelView.swift Wraps the mode bar's HStack in a ZStack with WindowDragHandleView layered behind the buttons, and adds TitlebarDoubleClickMonitorView as a background. Change is layout-neutral and correctly keeps MinimalModeTitlebarControlHitRegionView for button registration.
cmuxTests/WindowAndDragTests.swift Adds RecordingTitlebarActionWindow and a geometry-driven helper (firstCapturableTitlebarPoint) to locate real empty titlebar space, then drives a clickCount: 2 event through NSApp.sendEvent and asserts synchronously. Previously-flagged timing and hardcoded-coordinate issues are resolved.

Sequence Diagram

sequenceDiagram
    participant User
    participant ZStack as ZStack (modeBar)
    participant ModeBtn as ModeBarButton
    participant DragHandle as WindowDragHandleView
    participant Monitor as TitlebarDoubleClickMonitorView (local monitor)
    participant HitCheck as isMinimalModeTitlebarControlHit
    participant WalkFn as titlebarDoubleClickMonitorHasCapturingDragHandle
    participant Window as NSWindow

    User->>ZStack: leftMouseDown on empty space
    ZStack->>DragHandle: hit-test (foreground passes through)
    DragHandle-->>Window: performDrag / handleDoubleClick

    User->>ZStack: leftMouseDown on ModeBarButton
    ZStack->>ModeBtn: hit-test (button captured)
    ModeBtn-->>Monitor: event forwarded to local monitor
    Monitor->>HitCheck: isMinimalModeTitlebarControlHit?
    HitCheck-->>Monitor: true
    Monitor->>WalkFn: titlebarDoubleClickMonitorHasCapturingDragHandle(contentView)
    WalkFn-->>Monitor: true (drag handle found at point)
    Monitor-->>Monitor: defer to registered control → pass event through
    ModeBtn-->>User: mode switch action fires
Loading

Reviews (6): Last reviewed commit: "Keep titlebar strip fix current with mai..." | Re-trigger Greptile

Comment thread cmuxTests/WindowAndDragTests.swift Outdated
window.displayIfNeeded()
hostingView.layoutSubtreeIfNeeded()

let emptyModeBarPoint = NSPoint(x: 690, y: 242)

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 Fragile absolute coordinate anchored to current layout

NSPoint(x: 690, y: 242) is the only thing keeping the test pointed at "empty space." The window is 720×260 with titlebarHeight: 36, so this places the click 30 px from the right edge and 18 px from the top — both of which can shift silently if a mode button is added, padding changes, or the titlebarHeight parameter changes. When that happens, the point either lands on a button (causing isMinimalModeTitlebarControlHit to return true and bail the monitor) or falls outside the view bounds, and zoomCallCount stays 0. The test would then fail without pointing at a layout regression — or, worse, the point could land in a new empty region that still passes, masking a real geometry change. Deriving the target point from the actual laid-out view geometry would make the assertion track the real empty-space region robustly.

Comment thread cmuxTests/WindowAndDragTests.swift Outdated
}

NSApp.sendEvent(event)
RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05))

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 Timing-based assertion window may be too short on slow CI runners

RunLoop.main.run(until: Date(timeIntervalSinceNow: 0.05)) assumes the zoom action completes within 50 ms. NSApp.sendEvent dispatches synchronously through local monitors, so RecordingTitlebarActionWindow.zoom should be called before sendEvent returns — the spin loop appears unnecessary. But if any internal path defers the action asynchronously (e.g., a future refactor wraps handleTitlebarDoubleClick in a DispatchQueue.main.async), the 50 ms window may silently become too short on a loaded CI runner, yielding a false "zoomCallCount == 0" without a clear synchronization error. Removing the spin or waiting on an explicit signal would make the test deterministic rather than timing-dependent.

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.

Already addressed in the current branch head: the regression test no longer uses a RunLoop/timing window. It sends the synthetic event through NSApp.sendEvent and asserts the recording window call counts synchronously.

— Claude Code

The regression should prove the right sidebar exposes a real empty titlebar hit region without depending on a hardcoded pixel or a timing window. Tagging the drag-handle view lets the test locate the laid-out helper and ask the same capture predicate used by the production hit path for a valid empty point.

Constraint: Local tests are intentionally not run for this branch; CI owns XCTest execution.

Rejected: Keep the absolute test point | fragile if the right sidebar mode bar layout changes.

Rejected: Keep a short RunLoop spin | NSApp.sendEvent dispatches this path synchronously and the wait can introduce timing noise.

Confidence: high

Scope-risk: narrow

Tested: git diff --check

Not-tested: Local XCTest execution per workflow policy

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 1e7bed2. Configure here.

Comment thread Sources/RightSidebarPanelView.swift Outdated
Cursor flagged that the right-sidebar minimal-mode host registration made the monitor layer defer across the whole mode bar. The monitor now treats a registered point as empty chrome when the colocated drag handle would capture that same point, so foreground controls still win while the monitor remains a real double-click safety net.

Constraint: The right sidebar keeps its existing minimal-mode hit-region registration for drag/control routing.

Rejected: Remove the monitor from the right sidebar helper | this would satisfy current behavior but drop the two-layer titlebar pattern requested for the fix.

Rejected: Ignore the registered-control guard entirely | button double-clicks could be consumed by the titlebar monitor.

Confidence: high

Scope-risk: narrow

Tested: git diff --check

Not-tested: Local XCTest execution per workflow policy
The right-sidebar strip installed its drag handle as a SwiftUI background, which could participate in probing but did not reliably become the AppKit mouseDown receiver in the live hierarchy. Using a layout-neutral overlay matches the intended event order: foreground buttons still win through windowDragHandleShouldCaptureHit, while true empty chrome is claimed by the drag handle.

Constraint: The helper must not affect mode-bar sizing after the earlier ZStack regression.

Rejected: Return to a ZStack wrapper | it previously expanded the chrome layout.

Rejected: Keep the drag handle as a background | live hit testing still leaves empty strip clicks without drag/double-click behavior.

Confidence: high

Scope-risk: narrow

Tested: git diff --check

Not-tested: Local XCTest execution per workflow policy
Dogfooding showed the layout-neutral helper left the empty right-sidebar strip as a SwiftUI hosting hit instead of a real AppKit mouse-down target. The strip now uses the same proven shape as the main custom titlebar: a WindowDragHandleView sibling behind the tab buttons, with the double-click monitor layered on the fixed chrome container.

Constraint: The right-sidebar chrome height remains owned by rightSidebarChromeBar().

Rejected: Handle drag from the local monitor | window.performDrag returns immediately when invoked from the monitor and subsequent drag events still fall into file-drop routing.

Rejected: Keep the generic titlebarDoubleClickRegion helper | its background/overlay variants were layout-neutral but did not reliably receive live mouse-down delivery here.

Confidence: high

Scope-risk: narrow

Tested: ./scripts/reload.sh --tag issue-3746-right-sidebar-titlebar-doubleclick --launch

Tested: Manual drag from empty space to the right of Vault moved the window and logged titlebar.dragHandle.mouseDown.

Tested: Manual double-click in the same empty space zoomed the window and logged titlebar.monitor.doubleClick result=performed(...zoom).

Tested: Manual clicks on Find and Vault switched right-sidebar modes.

Not-tested: Local XCTest execution per workflow policy
origin/main moved while the right-sidebar titlebar strip fix was being re-verified, so the PR branch was merged forward before CI iteration. There were no conflicts; this keeps the review branch testing against the current app code.

Constraint: iterate-pr sync requires the PR branch to include the latest base branch before acting on CI.

Confidence: high

Scope-risk: moderate

Tested: git merge origin/main completed without conflicts

Not-tested: Local tests not run per workflow policy
@austinywang
austinywang merged commit 7e2d376 into main May 9, 2026
26 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — b8f65cd8 Deployed May 8, 2026 by vercel[bot]
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 tab strip (Files/Find/Vault) doesn't support title-bar double-click to zoom/minimize

1 participant