Skip to content

Double-click custom titlebar to zoom or minimize - #2130

Merged
austinywang merged 1 commit into
mainfrom
issue-2128-titlebar-double-click-zoom
Mar 25, 2026
Merged

austinywang merged 1 commit into
mainfrom
issue-2128-titlebar-double-click-zoom

Conversation

@austinywang

@austinywang austinywang commented Mar 25, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • forward double-clicks on the custom titlebar surfaces to the standard macOS titlebar action
  • respect the user's AppleActionOnDoubleClick / legacy AppleMiniaturizeOnDoubleClick preference when deciding between zoom, minimize, or no action
  • cover the preference mapping with unit tests

Fixes #2128.

Testing

  • Built a tagged Debug app with ./scripts/reload.sh --tag issue2128-doubleclick
  • Opened the tagged app for manual verification
  • Did not run local automated tests because this repo's policy is to run them in GitHub Actions / VM

Demo Video

  • Video URL or attachment: not included from CLI environment

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

Summary by cubic

Double-clicking the custom titlebar now performs the standard macOS action (zoom, minimize, or none) based on the user’s setting, and works in minimal mode. Fixes #2128.

  • Bug Fixes
    • Route double-clicks via AppleActionOnDoubleClick and legacy AppleMiniaturizeOnDoubleClick, defaulting to zoom when unset.
    • Added TitlebarDoubleClickMonitorView and applied it to the drag handle and the minimal-mode top strip.
    • Added unit tests covering preference mapping (fill→zoom, minimize, no action, legacy key, default).

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

Summary by CodeRabbit

  • New Features

    • Enhanced titlebar and sidebar double-click interactions to properly respond to macOS system preferences for window actions (minimize, zoom).
    • Improved event detection in titlebar area with support for executing system-preferred window behaviors.
  • Tests

    • Added test coverage for titlebar double-click preference resolution and action handling.

@vercel

vercel Bot commented Mar 25, 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 Mar 25, 2026 9:04am

@austinywang

Copy link
Copy Markdown
Contributor Author

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

1 similar comment
@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Mar 25, 2026

Copy link
Copy Markdown

@austinywang Sure! I'll start reviewing the PR now.

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai

coderabbitai Bot commented Mar 25, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

This PR implements macOS standard double-click title bar functionality to toggle window zoom/minimize states. It adds event monitoring to the title bar and sidebar areas, resolves system preferences to determine the appropriate action, and integrates the behavior across multiple view components.

Changes

Cohort / File(s) Summary
Core double-click handler and event monitoring
Sources/WindowDragHandleView.swift
Replaced boolean return type with StandardTitlebarDoubleClickAction enum (cases: .zoom, .miniaturize, .none). Extracted preference parsing into resolvedStandardTitlebarDoubleClickAction() that maps AppleActionOnDoubleClick and AppleMiniaturizeOnDoubleClick system preferences. Added new TitlebarDoubleClickMonitorView NSViewRepresentable that installs a local event monitor to detect double-clicks and suppress further handling when action is resolved.
UI integration
Sources/ContentView.swift, Sources/WorkspaceContentView.swift
Integrated TitlebarDoubleClickMonitorView() as background views to the main titlebar and sidebar drag handle in ContentView. Added WorkspaceTitlebarInteractionMetrics.minimalModeTopStripHeight constant and conditionally display monitor view in minimal-mode titlebar overlay when workspace input is active.
Test coverage
cmuxTests/GhosttyConfigTests.swift
Added TitlebarDoubleClickPreferenceTests class with five test cases verifying mapping of macOS system preferences ("Fill" → .zoom, "Minimize" → .miniaturize, "No Action" → .none), legacy preference fallback, and default zoom behavior when preference is missing.

Sequence Diagram

sequenceDiagram
    actor User
    participant Monitor as TitlebarDoubleClickMonitorView
    participant Handler as WindowDragHandleView
    participant Prefs as System Preferences
    participant Window as NSWindow

    User->>Monitor: Double-click titlebar
    Monitor->>Monitor: Detect left mouse down in bounds
    Monitor->>Handler: performStandardTitlebarDoubleClick(window)
    Handler->>Prefs: Read AppleActionOnDoubleClick
    Handler->>Prefs: Read AppleMiniaturizeOnDoubleClick
    Handler->>Handler: resolvedStandardTitlebarDoubleClickAction()
    Handler-->>Monitor: Returns StandardTitlebarDoubleClickAction
    alt Action is .zoom
        Monitor->>Window: performZoom(nil)
    else Action is .miniaturize
        Monitor->>Window: miniaturize(nil)
    else Action is .none
        Monitor->>Monitor: No action taken
    end
    Monitor->>Monitor: Suppress further event handling if action resolved
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐰 Tap, tap upon the titlebar bright,
The window dances left and right,
A zoom, a shrink, whatever's true,
The system knows just what to do!
Double-click's the way, hooray! ✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main change: implementing double-click functionality on the custom titlebar to zoom or minimize the window.
Description check ✅ Passed The description covers the Summary, Testing, Demo Video, Review Trigger, and Checklist sections from the template. Testing details and checklist items are provided, though demo video is explicitly noted as unavailable.
Linked Issues check ✅ Passed All coding requirements from issue #2128 are met: double-click handling on titlebar, respect for AppleActionOnDoubleClick/AppleMiniaturizeOnDoubleClick preferences, and comprehensive unit tests covering preference mapping.
Out of Scope Changes check ✅ Passed All changes are scoped to implementing issue #2128: new titlebar double-click monitoring, preference resolution logic, unit tests, and integration into existing titlebar components. No unrelated changes detected.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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-2128-titlebar-double-click-zoom

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.

@cubic-dev-ai

cubic-dev-ai Bot commented Mar 25, 2026

Copy link
Copy Markdown

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

@austinywang I have started the AI code review. It will take a few minutes to complete.

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai coderabbitai 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@Sources/ContentView.swift`:
- Line 2492: The TitlebarDoubleClickMonitorView is being added on top of regions
that already include WindowDragHandleView causing the double-click action to
fire twice; remove the duplicate dispatcher by ensuring each hit region uses
only one path—either remove the overlaying
.background(TitlebarDoubleClickMonitorView()) where WindowDragHandleView() is
present, or modify TitlebarDoubleClickMonitorView to detect and no-op when a
WindowDragHandleView is active (or add a boolean parameter to
TitlebarDoubleClickMonitorView to disable forwarding in those areas); update
calls to TitlebarDoubleClickMonitorView() and WindowDragHandleView() so only one
dispatcher is attached per titlebar region.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a7f6d632-fb97-490e-a18d-ea8bb97c7b35

📥 Commits

Reviewing files that changed from the base of the PR and between da70f3f and c60d452.

📒 Files selected for processing (4)
  • Sources/ContentView.swift
  • Sources/WindowDragHandleView.swift
  • Sources/WorkspaceContentView.swift
  • cmuxTests/GhosttyConfigTests.swift

Comment thread Sources/ContentView.swift
.frame(height: titlebarPadding)
.frame(maxWidth: .infinity)
.contentShape(Rectangle())
.background(TitlebarDoubleClickMonitorView())

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Prevent duplicate titlebar double-click dispatch

On Line 2492 and Line 8614, TitlebarDoubleClickMonitorView() is layered onto regions that already use WindowDragHandleView(). Both paths invoke the standard titlebar double-click action, which can fire twice per gesture (notably causing zoom to toggle and immediately revert).

Suggested fix
-        .background(TitlebarDoubleClickMonitorView())
-                        .background(TitlebarDoubleClickMonitorView())

If you still need monitor-based forwarding, keep a single dispatcher path (either monitor or drag-handle) for each hit region.

Also applies to: 8614-8614

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/ContentView.swift` at line 2492, The TitlebarDoubleClickMonitorView
is being added on top of regions that already include WindowDragHandleView
causing the double-click action to fire twice; remove the duplicate dispatcher
by ensuring each hit region uses only one path—either remove the overlaying
.background(TitlebarDoubleClickMonitorView()) where WindowDragHandleView() is
present, or modify TitlebarDoubleClickMonitorView to detect and no-op when a
WindowDragHandleView is active (or add a boolean parameter to
TitlebarDoubleClickMonitorView to disable forwarding in those areas); update
calls to TitlebarDoubleClickMonitorView() and WindowDragHandleView() so only one
dispatcher is attached per titlebar region.

@greptile-apps

greptile-apps Bot commented Mar 25, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds standard macOS double-click-titlebar behaviour (zoom / minimize / no-op, driven by AppleActionOnDoubleClick and the legacy AppleMiniaturizeOnDoubleClick preference) to the app's custom titlebar surfaces, fixing #2128.

  • Architecture – The preference-reading logic is cleanly extracted into a pure resolvedStandardTitlebarDoubleClickAction(globalDefaults:) function, making it straightforwardly unit-testable without any mocking or swizzling. performStandardTitlebarDoubleClick wraps it with the actual side-effects.
  • Reliability – TitlebarDoubleClickMonitorView installs an NSEvent local monitor that fires before the responder-chain dispatch. This is the right tool here: it handles cases where the view hierarchy's hit-test doesn't route the click to DraggableView.mouseDown (e.g. sibling-capture, SwiftUI layout state). DraggableView.mouseDown is retained as a fallback for any window where the monitor view is not yet in the hierarchy.
  • Event semantics – For the .none/No Action preference the monitor still consumes the second click (returns nil), correctly preventing the event from falling through to window-drag logic, matching native titlebar behaviour. The change from Bool-returning to StandardTitlebarDoubleClickAction?-returning also fixes a pre-existing bug where "none" preference double-clicks would initiate an unintended window drag.
  • Placement – Three call sites are covered: the main custom-titlebar strip (ContentView), the VerticalTabsSidebar drag handle, and the minimal-mode top strip in WorkspaceContentView. The isWorkspaceInputActive guard in the minimal-mode overlay is intentional: the coordinator deinit removes the underlying monitor when the view leaves the hierarchy.
  • Test gap – There are five new unit tests covering the happy paths. One edge case is not yet covered: an unrecognised AppleActionOnDoubleClick string that should fall through and honour AppleMiniaturizeOnDoubleClick (the default: break branch).

Confidence Score: 5/5

  • Safe to merge; the implementation is correct and well-structured with no identified logic or runtime bugs.
  • The refactoring is clean, the local-event-monitor approach is the right mechanism for reliable pre-dispatch interception, event semantics are correct for all preference values including .none, and there is no double-fire risk. The only gap is a single missing unit-test edge case (default: break + legacy fallback), which is a P2 suggestion and does not block merge.
  • No files require special attention.

Important Files Changed

Filename Overview
Sources/WindowDragHandleView.swift Core refactor: extracts pure resolvedStandardTitlebarDoubleClickAction for testability, adds StandardTitlebarDoubleClickAction enum, adds new preference values ("fill", "miniaturize", "no action"), and introduces TitlebarDoubleClickMonitorView using a local event monitor. Logic is sound and the monitor correctly fires before dispatch so it cannot double-fire with DraggableView.mouseDown.
Sources/ContentView.swift Adds TitlebarDoubleClickMonitorView as .background() in two places: the main custom-titlebar strip and the VerticalTabsSidebar drag-handle strip. Both placements are in distinct screen regions so there is no risk of the two monitors both matching the same click.
Sources/WorkspaceContentView.swift Adds a conditionally-shown TitlebarDoubleClickMonitorView overlay (30 pt, matching Bonsplit's tab bar height) only in minimal mode and only when isWorkspaceInputActive. The guard is intentional: when the window isn't active the overlay is not in the hierarchy, the coordinator deinits, and the underlying monitor is removed cleanly.
cmuxTests/GhosttyConfigTests.swift Good unit-test coverage for the five main preference paths. Missing one edge-case: an unrecognised AppleActionOnDoubleClick value that should fall through and honour AppleMiniaturizeOnDoubleClick.

Sequence Diagram

sequenceDiagram
    participant User
    participant NSApp
    participant Monitor as TitlebarDoubleClickMonitorView<br/>(local event monitor)
    participant DraggableView as DraggableView.mouseDown
    participant Action as performStandardTitlebarDoubleClick

    User->>NSApp: leftMouseDown (clickCount=2)
    NSApp->>Monitor: local monitor fires (before dispatch)
    alt click NOT in monitor view bounds
        Monitor-->>NSApp: return event (pass through)
        NSApp->>DraggableView: dispatch to hit-test winner
        DraggableView->>Action: performStandardTitlebarDoubleClick
        Action-->>DraggableView: .zoom / .miniaturize / .none / nil
        DraggableView-->>NSApp: return (drag suppressed for non-nil)
    else click IS in monitor view bounds
        Monitor->>Action: performStandardTitlebarDoubleClick
        Action->>NSApp: window.zoom(nil) or window.miniaturize(nil)
        Action-->>Monitor: .zoom / .miniaturize / .none (non-nil)
        Monitor-->>NSApp: return nil (event consumed)
        Note over DraggableView: mouseDown never called
    end
Loading

Reviews (1): Last reviewed commit: "Fix titlebar double-click zoom handling" | Re-trigger Greptile

Comment on lines +1289 to +1296
func testFallsBackToLegacyMiniaturizePreference() {
XCTAssertEqual(
resolvedStandardTitlebarDoubleClickAction(globalDefaults: [
"AppleMiniaturizeOnDoubleClick": true,
]),
.miniaturize
)
}

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 Missing edge-case test: unknown AppleActionOnDoubleClick falls through to legacy key

The existing testFallsBackToLegacyMiniaturizePreference test only exercises the path where AppleActionOnDoubleClick is absent. There is a distinct untested path where the key is present but holds an unrecognised value (the default: break branch), and the code should then fall through to honour AppleMiniaturizeOnDoubleClick.

A minimal addition would be:

func testFallsBackToLegacyMiniaturizeWhenActionIsUnknown() {
    XCTAssertEqual(
        resolvedStandardTitlebarDoubleClickAction(globalDefaults: [
            "AppleActionOnDoubleClick": "SomeFutureValue",
            "AppleMiniaturizeOnDoubleClick": true,
        ]),
        .miniaturize
    )
}

Without this test the default: break branch in resolvedStandardTitlebarDoubleClickAction has no coverage, so a future refactor could accidentally break that fallback silently.

@austinywang
austinywang merged commit 71a64a1 into main Mar 25, 2026
15 checks passed
@austinywang
austinywang deleted the issue-2128-titlebar-double-click-zoom branch March 25, 2026 09:15

@cubic-dev-ai cubic-dev-ai 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.

1 issue found across 4 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/WindowDragHandleView.swift">

<violation number="1" location="Sources/WindowDragHandleView.swift:495">
P2: The `action == nil` check incorrectly evaluates to `false` when the action is `.none`, swallowing the event and preventing double-click-to-drag.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

guard view.bounds.contains(point) else { return event }

let action = performStandardTitlebarDoubleClick(window: window)
return action == nil ? event : nil

@cubic-dev-ai cubic-dev-ai Bot Mar 25, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2: The action == nil check incorrectly evaluates to false when the action is .none, swallowing the event and preventing double-click-to-drag.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/WindowDragHandleView.swift, line 495:

<comment>The `action == nil` check incorrectly evaluates to `false` when the action is `.none`, swallowing the event and preventing double-click-to-drag.</comment>

<file context>
@@ -440,3 +457,48 @@ struct WindowDragHandleView: NSViewRepresentable {
+            guard view.bounds.contains(point) else { return event }
+
+            let action = performStandardTitlebarDoubleClick(window: window)
+            return action == nil ? event : nil
+        }
+
</file context>
Suggested change
return action == nil ? event : nil
if let action, action != .none { return nil }
return event
Fix with Cubic

This branch was successfully deployed

1 active deployment
Preview — c60d4526 Deployed Mar 25, 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.

Double-click title bar should toggle maximize/restore window

1 participant