Skip to content

Fix #3856: honor focusPaneOnFirstClick for minimal-mode chrome and workspace sidebar - #3881

Merged
austinywang merged 2 commits into
mainfrom
issue-3856-focus-pane-first-click-chrome
May 12, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-3856-focus-pane-first-click-chrome

Conversation

@austinywang

@austinywang austinywang commented May 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #3856.

This PR keeps PaneFirstClickFocusSettings as the single source of truth for inactive first-click behavior. It adds failing regression coverage first, then gates the minimal-mode sidebar controls, PDF chrome hosting view, and SwiftUI workspace sidebar first-mouse boundary so inactive first clicks only activate cmux when app.focusPaneOnFirstClick is false.

Local tests/build were not run per repo and task instructions; CI is the verification path before the required tagged reload.


Note

Medium Risk
Changes macOS first-mouse hit-testing/acceptance for several UI surfaces, which can subtly affect click/focus behavior across the app. Adds broad UI-event-driven test coverage, reducing risk but still sensitive to edge cases in AppKit/SwiftUI integration.

Overview
Ensures inactive-window first clicks consistently respect PaneFirstClickFocusSettings (aka focusPaneOnFirstClick) across minimal-mode titlebar controls, PDF preview chrome, and the SwiftUI workspace sidebar.

Adds a SwiftUI/AppKit boundary “first-mouse gate” overlay (FirstMouseGatedHostingOverlay) on the sidebar to capture the initial click when the window is inactive and the setting is disabled, preventing accidental workspace switches.

Updates/expands regression tests to cover these behaviors, including an integration-style sidebar harness that locates rows via accessibility IDs and dispatches real mouse events; also adjusts existing PDF chrome tests to set the setting explicitly.

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


Summary by cubic

Fixes #3856 by honoring the inactive first‑click policy across the workspace sidebar, minimal‑mode controls, and PDF preview chrome. When PaneFirstClickFocusSettings is off, the first click on an inactive window only activates the app and does not switch panes or workspaces.

  • Bug Fixes
    • Added FirstMouseGatedHostingView, FirstMouseGatedPassThroughHostingView, and FirstMouseGatedHostingOverlay; overlay (accessibility‑hidden) is applied to VerticalTabsSidebar to capture inactive first clicks at the SwiftUI↔AppKit boundary.
    • Gated acceptsFirstMouse in MinimalModeSidebarControlActionView and FilePreviewPDFChromeHostingView with PaneFirstClickFocusSettings.isEnabled().
    • Expanded tests with a runtime sidebar harness using accessibility IDs and real mouse events; verifies active‑window clicks switch workspaces, inactive first clicks are intercepted by a FirstMouseGated* view when the setting is off, and updated PDF chrome tests to respect the setting.

Written for commit 9cf33d8. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • First-click handling updated so inactive-window clicks respect the user focus setting across sidebars, previews, and minimal controls.
  • Bug Fixes

    • Prevents unintended activation/selection when clicking inactive panes by gating first-mouse behavior.
  • Tests

    • Added end-to-end and unit tests covering first-click behavior for active/inactive window scenarios.

Review Change Stack

@vercel

vercel Bot commented May 12, 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 12, 2026 2:41am
cmux-staging Building Building Preview, Comment May 12, 2026 2:41am

@coderabbitai

coderabbitai Bot commented May 12, 2026 •

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Adds gated-first-mouse hosting views and an overlay for the workspace sidebar, updates two control overrides to respect PaneFirstClickFocusSettings, and expands tests with a sidebar harness that simulates active vs inactive first-click behavior.

Changes

First-Click Focus Gating for Sidebar and Controls

Layer / File(s) Summary
First-mouse gating infrastructure
Sources/App/CmuxMainWindow.swift
Introduces FirstMouseGatedHostingView, FirstMouseGatedPassThroughHostingView, and FirstMouseGatedHostingOverlay. These types override hitTest to intercept clicks and acceptsFirstMouse to conditionally suppress first-mouse events based on window key state and PaneFirstClickFocusSettings.
UI integration and control gating
Sources/ContentView.swift, Sources/Panels/FilePreviewPanel.swift, Sources/Update/MinimalModeSidebarControls.swift
Sidebar workspace rows gain a FirstMouseGatedHostingOverlay that intercepts clicks on inactive windows. PDF chrome and minimal-mode toolbar controls change acceptsFirstMouse from always returning true to returning PaneFirstClickFocusSettings.isEnabled().
Test infrastructure and coverage
cmuxTests/InactivePaneFirstClickFocusTests.swift, cmuxTests/WindowAndDragTests.swift
Adds SidebarFirstClickHarness test infrastructure with accessibility-based click dispatch, mouse-event construction, and element traversal helpers. Tests verify acceptsFirstMouse behavior for the two updated controls and confirm that workspace sidebar clicks behave correctly when the window is active versus inactive. Updates existing chrome test to enable the setting during test execution.

Sequence Diagram

sequenceDiagram
  participant User
  participant HostingOverlay as FirstMouseGatedHostingOverlay
  participant PassThrough as FirstMouseGatedPassThroughHostingView
  participant HostingView as FirstMouseGatedHostingView
  participant Setting as PaneFirstClickFocusSettings
  
  User->>PassThrough: mouse click on inactive window
  PassThrough->>HostingView: hitTest(_:)
  HostingView->>Setting: isEnabled() + window.isKeyWindow
  Setting-->>HostingView: shouldCaptureInactiveFirstMouse result
  HostingView-->>PassThrough: capture decision
  alt Setting enabled or window active
    PassThrough-->>PassThrough: return self (capture)
  else Setting disabled and window inactive
    PassThrough-->>User: return nil (pass through)
  end
  PassThrough->>PassThrough: acceptsFirstMouse(for:)
  PassThrough-->>User: return setting.isEnabled()
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • manaflow-ai/cmux#3102: Adds or moves PaneFirstClickFocusSettings symbol used by the gating logic.
  • manaflow-ai/cmux#3194: Related AppKit hit-testing changes for hosting views and pass-through behavior.
  • manaflow-ai/cmux#1796: Earlier work on the first-click-to-focus feature and gated acceptsFirstMouse implementations.

Poem

🐰 A soft tap on an idle pane,

The gate holds back the noisy bane,
One click wakes, the next will act,
Settings hush the eager tact,
A quiet sidebar — peace regained.


Important

Pre-merge checks failed

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

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
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 (13 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly identifies the issue being fixed (#3856) and summarizes the main change: ensuring the focusPaneOnFirstClick setting is honored for minimal-mode chrome and the workspace sidebar.
Linked Issues check ✅ Passed The PR comprehensively addresses issue #3856 by implementing both suggested parts: gating acceptsFirstMouse in MinimalModeSidebarControlActionView and FilePreviewPDFChromeHostingView, and introducing FirstMouseGatedHostingView/Overlay helpers to gate SwiftUI sidebar content at the AppKit boundary.
Out of Scope Changes check ✅ Passed All changes directly support fixing issue #3856. File changes include the two gating implementations for minimal-mode and PDF chrome, the new SwiftUI/AppKit boundary helpers for the sidebar, and comprehensive test coverage with no unrelated modifications.
Cmux Swift Actor Isolation ✅ Passed No Swift 6 actor isolation issues. New UI view classes follow proper patterns for MainActor-bound types. No Sendable violations, no background context access issues, no implicit MainActor value types.
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization added. New production code: simple AppKit overrides for hitTest/acceptsFirstMouse and NSViewRepresentable composition. No semaphores, waits, sleeps, or locks introduced.
Cmux No Hacky Sleeps ✅ Passed Check applies to non-Swift runtime changes only. PR contains Swift-only modifications for first-click focus policy.
Cmux Swift Concurrency ✅ Passed No legacy async patterns. Changes are pure synchronous UI-layer AppKit/SwiftUI code (allowed) and XCTest. Zero DispatchQueue, Combine, completion handlers, or Tasks introduced.
Cmux Swift File And Package Boundaries ✅ Passed Adds 35 lines of focused AppKit/SwiftUI bridge helpers to 129-line file. Incidental changes to existing files. No oversized files, no >250-line additions, cohesive window management code.
Cmux Swift Logging ✅ Passed No swift-logging.md violations. The PR adds first-mouse gating code with no print, NSLog, debugPrint, dump, file/stdout logging, Logger isolation issues, or sensitive data in logging.
Cmux Swiftui State Layout ✅ Passed No violations of SwiftUI state layout rules detected. New AppKit bridge classes are stateless with no problematic state patterns.
Cmux Architecture Rethink ✅ Passed No timing patterns, mutable state, or split lifecycle. All acceptsFirstMouse gates on PaneFirstClickFocusSettings. Meets guidelines for correctness fixes with clear ownership.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed No new standalone auxiliary windows. CmuxMainWindow is the main app window (allowed case). FirstMouseGated* are view subclasses, not windows. Other changes modify existing acceptsFirstMouse only.
Description check ✅ Passed PR description provides comprehensive summary, testing approach, and implementation details, though missing demo video link and not all checklist items marked.
✨ 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-3856-focus-pane-first-click-chrome

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 12, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes #3856 by making the inactive first-click policy consistent across the workspace sidebar, minimal-mode sidebar controls, and PDF preview chrome — gating action delivery on PaneFirstClickFocusSettings.isEnabled(). It introduces a SwiftUI/AppKit overlay (FirstMouseGatedHostingOverlay) built on FirstMouseGatedPassThroughHostingView that sits on top of VerticalTabsSidebar; when the window is inactive and the setting is off the overlay wins the hit-test and returns false from acceptsFirstMouse, so AppKit activates the window without dispatching the action.

  • New hit-test gate (CmuxMainWindow.swift): FirstMouseGatedHostingView and its PassThrough subclass correctly convert the incoming hit-test point from superview-space before checking bounds, and return self/nil according to window key-state and the setting.
  • acceptsFirstMouse gating (FilePreviewPanel.swift, MinimalModeSidebarControls.swift): Both views changed from unconditional true to PaneFirstClickFocusSettings.isEnabled(), bringing them in line with the single source of truth.
  • Test expansion (InactivePaneFirstClickFocusTests.swift): Adds unit tests for the two newly gated views and a sidebar integration harness that discovers the target workspace row via accessibility identifier and verifies the overlay is the hit view in the disabled path.

Confidence Score: 5/5

The change is safe to merge. Hit-test coordinate conversion is correct, the overlay is correctly scoped to the sidebar, and the three acceptsFirstMouse gates are consistent with PaneFirstClickFocusSettings.

All three production surfaces (sidebar overlay, PDF chrome, minimal-mode controls) now read from the same setting, the coordinate-space conversion in shouldCaptureInactiveFirstMouse correctly converts from superview space before the bounds check, and the split into FirstMouseGatedHostingView / FirstMouseGatedPassThroughHostingView eliminates the mutable-flag bad state. No incorrect data paths or activation regressions were found in the production code.

No files require special attention.

Important Files Changed

Filename Overview
Sources/App/CmuxMainWindow.swift Adds FirstMouseGatedHostingView, FirstMouseGatedPassThroughHostingView, and FirstMouseGatedHostingOverlay. Hit-test coordinate conversion is correct (superview-space to local bounds), and acceptsFirstMouse correctly mirrors PaneFirstClickFocusSettings. No issues found.
Sources/ContentView.swift Applies FirstMouseGatedHostingOverlay as an accessibility-hidden overlay on VerticalTabsSidebar. Overlay is correctly maxWidth/maxHeight expanded. No production issues found.
Sources/Panels/FilePreviewPanel.swift acceptsFirstMouse changed from unconditional true to PaneFirstClickFocusSettings.isEnabled() — consistent with the rest of the policy. Change is correct and minimal.
Sources/Update/MinimalModeSidebarControls.swift Same acceptsFirstMouse gating as FilePreviewPanel.swift. One-line change is correct.
cmuxTests/InactivePaneFirstClickFocusTests.swift Adds unit tests for the two newly gated views and a full sidebar integration harness. The disabled-path test is well-structured. The enabled inactive path test asserts acceptedFirstMouse == true for a SwiftUI-internal hit view, which may not hold since most SwiftUI-generated NSViews return false from acceptsFirstMouse by default — test-only scaffolding.
cmuxTests/WindowAndDragTests.swift Adds proper UserDefaults save/restore around testChromeHostsAcceptFirstMouse so the setting is honoured in the test environment. Correct and isolated.

Sequence Diagram

sequenceDiagram
    participant User as User click
    participant AppKit as AppKit hit-test
    participant Overlay as FirstMouseGatedPassThroughHostingView
    participant Sidebar as Sidebar NSViews
    participant TM as TabManager

    User->>AppKit: leftMouseDown (window inactive)
    AppKit->>Overlay: hitTest(point)

    alt focusPaneOnFirstClick disabled
        Overlay->>Overlay: shouldCaptureInactiveFirstMouse → true
        Overlay-->>AppKit: return self
        AppKit->>Overlay: acceptsFirstMouse → false
        AppKit->>AppKit: activate window only (no action dispatch)
        Note over TM: workspace unchanged
    else focusPaneOnFirstClick enabled
        Overlay->>Overlay: shouldCaptureInactiveFirstMouse → false
        Overlay-->>AppKit: return nil
        AppKit->>Sidebar: hitTest(point) → underlying view
        AppKit->>Sidebar: dispatch mouseDown
        Sidebar->>TM: selectWorkspace(target)
    end
Loading

Reviews (5): Last reviewed commit: "Honor inactive first-click policy in sid..." | Re-trigger Greptile

Comment thread Sources/App/CmuxMainWindow.swift Outdated
Comment on lines +51 to +55
private func shouldCaptureInactiveFirstMouse(at point: NSPoint) -> Bool {
window?.isKeyWindow != true &&
!PaneFirstClickFocusSettings.isEnabled() &&
bounds.contains(point)
}

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.

P1 Coordinate-space mismatch may silently disable the inactive first-click gate

hitTest(_:) receives point in the superview's coordinate system (per NSView docs), but bounds is in the receiver's own coordinate system. The two coincide only when frame.origin == (0, 0). For a SwiftUI overlay that can be placed at a non-zero y-origin — e.g., after ignoresSafeArea, safe-area insets, or any layout shift on the ZStack — bounds.contains(point) returns false for points that actually fall inside the view, causing the check to miss the gate and fall through to passesThroughWhenNotGating = true, silently delivering inactive first clicks to sidebar rows. The fix is to check against the frame (frame.contains(point)) or convert first (bounds.contains(convert(point, from: superview))).

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.

I verified this one against AppKit's hit-testing contract: NSView.hitTest(_:) receives the point in the receiver's coordinate space, so bounds.contains(point) is the correct check here. I added a source comment documenting that invariant.

— Claude Code

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.

Updated after re-checking the AppKit hitTest(_:) docs. The current code now converts from the superview into local bounds before checking containment.

— Claude Code

Comment thread Sources/App/CmuxMainWindow.swift Outdated
Comment on lines +31 to +56
var passesThroughWhenNotGating = false

override var intrinsicContentSize: NSSize {
NSSize(width: NSView.noIntrinsicMetric, height: NSView.noIntrinsicMetric)
}

override func hitTest(_ point: NSPoint) -> NSView? {
if shouldCaptureInactiveFirstMouse(at: point) {
return self
}
if passesThroughWhenNotGating {
return nil
}
return super.hitTest(point)
}

override func acceptsFirstMouse(for event: NSEvent?) -> Bool {
PaneFirstClickFocusSettings.isEnabled()
}

private func shouldCaptureInactiveFirstMouse(at point: NSPoint) -> Bool {
window?.isKeyWindow != true &&
!PaneFirstClickFocusSettings.isEnabled() &&
bounds.contains(point)
}
}

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 passesThroughWhenNotGating mutable flag leaves a bad default state representable

passesThroughWhenNotGating defaults to false, but the only consumer always immediately sets it to true. In the false state (the default before makeNSView/updateNSView assigns it), the view wraps EmptyView, so super.hitTest returns self for any point in bounds, silently intercepting every click. This matches the architectural-rethink rule's concern about new mutable flags that create a second owner for behavior already owned by a single path. The simplest resolution would be a custom initializer that takes passesThroughWhenNotGating as a constructor argument, or splitting into two distinct types so the bad state is unexpressible.

Rule Used: Flag Swift fixes that patch symptoms while leaving... (source)

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 by splitting the pass-through overlay into a dedicated FirstMouseGatedPassThroughHostingView subclass. The mutable passesThroughWhenNotGating flag is gone, so the bad default state is no longer representable.

— Claude Code

Comment on lines +161 to +169
modifierFlags: [],
timestamp: ProcessInfo.processInfo.systemUptime,
windowNumber: window.windowNumber,
context: nil,
eventNumber: 1,
clickCount: 1,
pressure: 1
))
let hitView = try XCTUnwrap(host.hitTest(targetRowPoint))

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 Workspace-selection assertions are vacuously true

No click event is ever dispatched into the AppKit event system in this test — the NSApp.sendEvent path is inside an if hitView.acceptsFirstMouse(for: downEvent) block that the test has already asserted to be false. The final XCTAssertEqual(tabManager.selectedTabId, initialWorkspace.id) assertions therefore just verify the initial setup, not that an accepted click would have been blocked from switching workspaces. The test name promises end-to-end "does not switch workspace" coverage that isn't delivered; a future regression that lets clicks through would still pass all four assertions here.

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 by expanding the sidebar coverage to prove both branches. The enabled-path test dispatches an accepted inactive first click and verifies the workspace switches; the disabled-path test verifies the click is rejected and the selection stays on the original workspace.

— Claude Code

@austinywang
austinywang force-pushed the issue-3856-focus-pane-first-click-chrome branch 2 times, most recently from 28d07d9 to 9a438cb Compare May 12, 2026 01:24
Comment on lines +226 to +227
XCTAssertTrue(try dispatchInactiveFirstClick(in: harness))
XCTAssertEqual(harness.tabManager.selectedTabId, harness.targetWorkspace.id)

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.

P1 Enabled-path integration test likely always fails in CI

dispatchInactiveFirstClick gates on hitView.acceptsFirstMouse(for: downEvent) before dispatching any event (line 201). When the setting is enabled, FirstMouseGatedPassThroughHostingView.hitTest returns nil and the hit-test descends into SwiftUI's internal NSView hierarchy. SwiftUI-generated views for list rows and buttons do not override acceptsFirstMouse, so it returns the NSView default of false. The guard short-circuits, events are never sent, and dispatchInactiveFirstClick returns false. XCTAssertTrue therefore fails and line 227's workspace-switch assertion is never reached.

The harness correctly proves the disabled path (overlay blocks the click). To prove the enabled path, the test needs a way to deliver the event that doesn't depend on acceptsFirstMouse; for example, calling window.makeKeyAndOrderFront(nil) first (making the window key) to simulate an already-active window, or driving the click directly through NSApp.sendEvent at the window level bypassing the acceptsFirstMouse gate.

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 by changing the enabled/control path into an active-window row click. That validates the discovered row target without depending on SwiftUI internal views accepting first mouse; the inactive disabled test remains focused on the first-mouse gate.

— Claude Code

window: window,
keyWindow: keyWindow,
host: host,
targetRowPoint: NSPoint(x: 48, y: frame.height - 88)

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.

P1 Hardcoded pixel coordinate is fragile in a headless test environment

NSPoint(x: 48, y: frame.height - 88) = (48, 272) assumes the second workspace row lands at exactly that Y position after layout. displayIfNeeded() + layoutSubtreeIfNeeded() on a bare NSWindow without a real screen do not guarantee that SwiftUI's layout pass completes to the pixel-perfect position expected here. If the layout doesn't settle, host.hitTest returns the wrong view (or nil), making try XCTUnwrap fail in the enabled case or silently hit the wrong row in both cases.

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 by removing the hardcoded pixel coordinate. The harness now finds the target workspace row through its sidebarWorkspace. accessibility identifier and uses the element's accessibility frame center.

— Claude Code

@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 9a438cb. Configure here.

Comment thread Sources/App/CmuxMainWindow.swift
@austinywang
austinywang force-pushed the issue-3856-focus-pane-first-click-chrome branch 2 times, most recently from 1db4c1f to 42fe0c0 Compare May 12, 2026 01:35
@austinywang
austinywang force-pushed the issue-3856-focus-pane-first-click-chrome branch from 42fe0c0 to 4161427 Compare May 12, 2026 01:38
coderabbitai[bot]
coderabbitai Bot previously requested changes May 12, 2026

@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: 2

🤖 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.

Inline comments:
In `@cmuxTests/InactivePaneFirstClickFocusTests.swift`:
- Around line 136-200: The test mounts VerticalTabsSidebar directly into an
NSHostingView in makeSidebarFirstClickHarness, bypassing the production
first-mouse gate/overlay; replace the raw NSHostingView usage with the
production first-mouse gated hosting wrapper (i.e., instantiate the
FirstMouseGatedHostingView or the app's equivalent gated hosting helper with
rootView: sidebar) so the test exercises the same overlay boundary as production
and verifies first-click behavior for VerticalTabsSidebar.
- Around line 283-300: The test currently returns early in
dispatchInactiveFirstClick when hitView.acceptsFirstMouse(for: downEvent) is
false, so it never exercises the inactive-click activation path; change
dispatchInactiveFirstClick (and the duplicate at the other range) to always
drive the inactive-click sequence: if acceptsFirstMouse is false, still send the
click event (use sendClick(at:in:) with the constructed downEvent/point) so the
window activation path runs, then assert the window became key
(window.isKeyWindow or appropriate API) while verifying selectedTabId remains
the same; keep the existing behavior when acceptsFirstMouse is true (send click
and return true), but ensure the test asserts window activation and unchanged
selectedTabId after the click in both branches.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 59e128c1-80a7-4c2a-b9be-69adb05a20e2

📥 Commits

Reviewing files that changed from the base of the PR and between 0ce2a24 and 4161427.

📒 Files selected for processing (6)
  • Sources/App/CmuxMainWindow.swift
  • Sources/ContentView.swift
  • Sources/Panels/FilePreviewPanel.swift
  • Sources/Update/MinimalModeSidebarControls.swift
  • cmuxTests/InactivePaneFirstClickFocusTests.swift
  • cmuxTests/WindowAndDragTests.swift

Comment thread cmuxTests/InactivePaneFirstClickFocusTests.swift
Comment thread cmuxTests/InactivePaneFirstClickFocusTests.swift Outdated
@austinywang
austinywang force-pushed the issue-3856-focus-pane-first-click-chrome branch from 4161427 to 1f4566c Compare May 12, 2026 01:45
@austinywang
austinywang force-pushed the issue-3856-focus-pane-first-click-chrome branch from 1f4566c to 89be965 Compare May 12, 2026 01:48
@austinywang
austinywang force-pushed the issue-3856-focus-pane-first-click-chrome branch from 89be965 to 0826e48 Compare May 12, 2026 01:53
coderabbitai[bot]
coderabbitai Bot previously requested changes May 12, 2026

@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 current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/InactivePaneFirstClickFocusTests.swift`:
- Around line 329-348: Add a new test that verifies the enabled-path for
inactive-window first-click on the sidebar: create a test (e.g.,
testWorkspaceSidebarInactiveFirstClickSwitchesWorkspaceWhenSettingEnabled) that
sets UserDefaults.standard.set(true, forKey: settingsKey), creates the harness
via makeSidebarFirstClickHarness(), calls dispatchInactiveFirstClick(in:
harness), and asserts that result.acceptedFirstMouse is true, the
hitViewClassName does not contain "FirstMouseGated", and
harness.tabManager.selectedTabId equals harness.targetWorkspace.id (and is not
the initialWorkspace.id) to ensure the enabled behavior actually switches
workspaces.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: f6008581-8494-435b-8082-83b197236a11

📥 Commits

Reviewing files that changed from the base of the PR and between 4161427 and 0826e48.

📒 Files selected for processing (6)
  • Sources/App/CmuxMainWindow.swift
  • Sources/ContentView.swift
  • Sources/Panels/FilePreviewPanel.swift
  • Sources/Update/MinimalModeSidebarControls.swift
  • cmuxTests/InactivePaneFirstClickFocusTests.swift
  • cmuxTests/WindowAndDragTests.swift

Comment thread cmuxTests/InactivePaneFirstClickFocusTests.swift
Add failing behavior coverage for the first-click focus policy where chrome and SwiftUI sidebar surfaces bypass PaneFirstClickFocusSettings. The tests mirror the existing pane body assertions and exercise the workspace sidebar through a hosted runtime path instead of checking source text.

The sidebar regression discovers the target row through its accessibility identifier, proves the coordinate with an active-window click, then verifies that an inactive first click with focusPaneOnFirstClick disabled is rejected before the workspace selection changes.

Constraint: Do not run local tests; CI owns unit and UI verification for this repo.

Rejected: Source-shape assertions for hardcoded acceptsFirstMouse | project policy requires runtime behavior tests.

Confidence: medium

Scope-risk: narrow

Tested: git diff --check

Not-tested: Local XCTest execution, by repo and user instruction.
Route minimal-mode controls, PDF chrome, and the SwiftUI workspace sidebar through PaneFirstClickFocusSettings. The sidebar gets a single AppKit hosting gate that captures inactive first clicks when the setting is off and otherwise passes through to normal SwiftUI hit testing.

The pass-through overlay is a dedicated subclass instead of mutable configuration, so the default hosting gate cannot be left in a bad pass-through state. The gate converts hit-test points from the superview into local bounds before deciding whether to capture.

Constraint: focusPaneOnFirstClick is the existing source of truth for pane first-click behavior.

Rejected: Guard each workspace row action | duplicates the policy across SwiftUI action sites and misses future sidebar controls.

Confidence: medium

Scope-risk: moderate

Directive: Keep first-mouse policy at AppKit boundaries; do not add per-row workarounds for inactive-window activation.

Tested: git diff --check; source scan for acceptsFirstMouse overrides; file length check for touched budgeted files.

Not-tested: Local XCTest/build execution, by repo and user instruction; CI will run on the PR.
@austinywang
austinywang force-pushed the issue-3856-focus-pane-first-click-chrome branch from 0826e48 to 9cf33d8 Compare May 12, 2026 02:04
@austinywang
austinywang dismissed stale reviews from coderabbitai[bot] and coderabbitai[bot] May 12, 2026 02:09

Stale CodeRabbit change request on an older commit. Requested enabled-path regression was added in the current tests-only commit and the thread is resolved.

@austinywang
austinywang merged commit 3ed5af9 into main May 12, 2026
27 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — 9cf33d88 Deployed May 12, 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.

focusPaneOnFirstClick is ignored by minimal-mode toolbar and the workspace sidebar

1 participant