Skip to content

Add optional single-click focus for inactive panes - #1796

Merged
lawrencecchen merged 3 commits into
mainfrom
task-single-click-inactive-pane-focus
Mar 19, 2026
Merged

lawrencecchen merged 3 commits into
mainfrom
task-single-click-inactive-pane-focus

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Mar 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a settings toggle for one-click inactive-pane focus when returning to cmux from another app
  • opt terminal and browser panes into first-click delivery, and forward inactive markdown clicks so the pane can focus on the first click too
  • add regression tests that go red until the setting-backed first-click behavior exists

Testing

  • ./scripts/setup.sh
  • ./scripts/reload.sh --tag single-click-pane-focus (build succeeded)

Issues


Summary by cubic

Adds an optional one-click focus for inactive panes so a single click activates the window and focuses the clicked pane. Closes #1794.

  • New Features

    • New Settings toggle: “Focus Pane on First Click” with localized title and on/off subtitles (EN/JA).
    • Terminal and browser panes now accept first click when enabled; markdown forwards the first click to the underlying target and focuses immediately.
    • Added unit tests for accepts-first-click behavior across terminal, browser, and markdown panes.
  • Migration

    • Off by default. Enable in Settings > App: “Focus Pane on First Click”.

Written for commit 673246e. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added "Focus Pane on First Click" setting in App preferences with a toggle and dynamic subtitle; inactive panes can be focused on first click when enabled.
  • Localization

    • Added localized strings for the new setting and its on/off subtitles (English and Japanese).
  • Tests

    • Added tests covering first-click focus behavior across pane types.

@vercel

vercel Bot commented Mar 19, 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 19, 2026 8:01am

@greptile-apps greptile-apps Bot left a comment

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.

Your free trial has ended. If you'd like to continue receiving code reviews, you can add a payment method here.

@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.
To continue using code reviews, add credits to your account and enable them for code reviews in your settings.

@coderabbitai

coderabbitai Bot commented Mar 19, 2026 •

Copy link
Copy Markdown

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1efd2874-fec1-45f9-9578-e9b23e9f57f8

📥 Commits

Reviewing files that changed from the base of the PR and between 13ed3ac and 673246e.

📒 Files selected for processing (3)
  • Sources/Panels/MarkdownPanelView.swift
  • Sources/cmuxApp.swift
  • cmuxTests/InactivePaneFirstClickFocusTests.swift

📝 Walkthrough

Walkthrough

Implements an optional single-click pane focus when cmux is inactive: adds a persisted setting and localized strings, updates terminal/browser/markdown views to accept first-mouse when enabled, forwards markdown pointer events as needed, and adds tests and Xcode project entries.

Changes

Cohort / File(s) Summary
Xcode project
GhosttyTabs.xcodeproj/project.pbxproj
Registered new test file cmuxTests/InactivePaneFirstClickFocusTests.swift in the project and test target.
Localization
Resources/Localizable.xcstrings
Added three keys: settings.app.paneFirstClickFocus, settings.app.paneFirstClickFocus.subtitleOff, settings.app.paneFirstClickFocus.subtitleOn with en/ja strings.
Settings / App UI
Sources/cmuxApp.swift
Added PaneFirstClickFocusSettings (UserDefaults key + helper), an @AppStorage toggle, settings subtitle logic, UI row for the toggle, and reset behavior.
Terminal & Browser views
Sources/GhosttyTerminalView.swift, Sources/Panels/CmuxWebView.swift
Added acceptsFirstMouse(for:) overrides that return PaneFirstClickFocusSettings.isEnabled() to control first-click focus behavior.
Markdown pointer observer
Sources/Panels/MarkdownPanelView.swift
Updated MarkdownPanelPointerObserverView: conditional hitTest/acceptsFirstMouse, tightened shouldHandle(_:), added mouse event forwarding with forwardedTarget(for:) and stored weak forwarded target.
Tests
cmuxTests/InactivePaneFirstClickFocusTests.swift
Added InactivePaneFirstClickFocusTests verifying acceptsFirstMouse(for:) behavior for GhosttyNSView, CmuxWebView, and MarkdownPanelPointerObserverView with the feature flag on/off.

Sequence Diagram

sequenceDiagram
    actor User
    participant App as cmux App
    participant Settings as PaneFirstClickFocusSettings
    participant View as Pane View (Terminal/Browser/Markdown)
    participant System as AppKit Event System

    User->>System: mouseDown on inactive window
    System->>App: deliver event to window
    System->>View: calls acceptsFirstMouse(for:)
    View->>Settings: isEnabled()?
    alt enabled
        Settings-->>View: true
        View-->>System: accepts first-mouse
        System->>View: deliver mouseDown
        View->>App: becomeFirstResponder / focus pane (1 click)
    else disabled
        Settings-->>View: false
        View-->>System: reject first-mouse
        System->>App: window becomes key (no pane focus)
        User->>View: second click required
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 I found a click that skips a chore,
One hop to focus, not two anymore.
I nuzzle panes with nimble paws,
One tap, one leap — no extra pause.
Hooray for windows, sleek and spry! 🥕

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely describes the main feature added: optional single-click focus for inactive panes, matching the primary objective in the linked issue.
Description check ✅ Passed The description includes Summary and Testing sections covering what changed and how it was tested, though Demo Video and complete Checklist sections are not fully filled.
Linked Issues check ✅ Passed The PR implements all key requirements from issue #1794: a user-toggleable settings option, first-click focus for terminal and browser panes, markdown pane click forwarding, and preserved default behavior [#1794].
Out of Scope Changes check ✅ Passed All changes are directly scoped to implement optional first-click focus: settings UI, view overrides, localization strings, and regression tests. No unrelated modifications detected.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ 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 task-single-click-inactive-pane-focus
📝 Coding Plan
  • Generate coding plan for human review comments

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.

@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

🧹 Nitpick comments (2)
cmuxTests/InactivePaneFirstClickFocusTests.swift (1)

25-47: Consider adding negative case tests for completeness.

The tests verify that acceptsFirstMouse(for:) returns true when the setting is enabled, which correctly validates runtime behavior. For more robust coverage, consider also testing that the views return false (or the platform default) when the setting is disabled.

💡 Example negative case test
func testTerminalViewRejectsFirstMouseWhenSettingDisabled() {
    UserDefaults.standard.set(false, forKey: settingsKey)
    
    let view = GhosttyNSView(frame: .zero)
    
    XCTAssertFalse(view.acceptsFirstMouse(for: nil))
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@cmuxTests/InactivePaneFirstClickFocusTests.swift` around lines 25 - 47, Add
negative-case tests mirroring the existing positives: for each of
testTerminalViewAcceptsFirstMouseWhenSettingEnabled,
testBrowserViewAcceptsFirstMouseWhenSettingEnabled, and
testMarkdownPointerObserverAcceptsFirstMouseWhenSettingEnabled set
UserDefaults.standard.set(false, forKey: settingsKey) and assert the view's
acceptsFirstMouse(for:) returns false (use GhosttyNSView, CmuxWebView with
WKWebViewConfiguration(), and MarkdownPanelPointerObserverView respectively) so
behavior is verified when the setting is disabled.
Sources/Panels/MarkdownPanelView.swift (1)

382-390: The isHidden toggle during hit-test is a valid pattern but consider edge cases.

Temporarily hiding self to perform hit-testing on the underlying content is a reasonable approach. The defer ensures isHidden is restored even if the hit-test throws.

One edge case: if the window's content view is nil (line 384 guard), the function returns nil and no forwarding occurs. This is safe but could silently drop the mouse event. Consider whether a fallback or debug log would help diagnose issues.

💡 Optional: Add debug logging for nil content view
 private func forwardedTarget(for event: NSEvent) -> NSView? {
-    guard let window,
-          let contentView = window.contentView else { return nil }
+    guard let window,
+          let contentView = window.contentView else {
+#if DEBUG
+        dlog("markdown.pointer.forwardedTarget window=\(window != nil ? 1 : 0) contentView=nil")
+#endif
+        return nil
+    }
     isHidden = true
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Sources/Panels/MarkdownPanelView.swift` around lines 382 - 390, The guard in
forwardedTarget(for:) silently returns nil when window.contentView is nil; add a
debug-only log before returning to surface this edge case for diagnostics.
Update the function forwardedTarget(for event: NSEvent) to detect the nil
contentView case (the existing guard) and emit a concise debug message (e.g.,
NSLog or os_log inside a `#if` DEBUG block) indicating the window or event
context, then return nil as before; leave the isHidden toggle and defer
unchanged.
🤖 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/cmuxApp.swift`:
- Around line 3821-3822: resetAllSettings() currently doesn't reset the new
AppStorage-backed property paneFirstClickFocusEnabled; update resetAllSettings()
to explicitly set paneFirstClickFocusEnabled =
PaneFirstClickFocusSettings.defaultEnabled so the persisted toggle is restored
to its default when users choose "Reset All Settings" (ensure you modify the
resetAllSettings function where other settings are restored).

---

Nitpick comments:
In `@cmuxTests/InactivePaneFirstClickFocusTests.swift`:
- Around line 25-47: Add negative-case tests mirroring the existing positives:
for each of testTerminalViewAcceptsFirstMouseWhenSettingEnabled,
testBrowserViewAcceptsFirstMouseWhenSettingEnabled, and
testMarkdownPointerObserverAcceptsFirstMouseWhenSettingEnabled set
UserDefaults.standard.set(false, forKey: settingsKey) and assert the view's
acceptsFirstMouse(for:) returns false (use GhosttyNSView, CmuxWebView with
WKWebViewConfiguration(), and MarkdownPanelPointerObserverView respectively) so
behavior is verified when the setting is disabled.

In `@Sources/Panels/MarkdownPanelView.swift`:
- Around line 382-390: The guard in forwardedTarget(for:) silently returns nil
when window.contentView is nil; add a debug-only log before returning to surface
this edge case for diagnostics. Update the function forwardedTarget(for event:
NSEvent) to detect the nil contentView case (the existing guard) and emit a
concise debug message (e.g., NSLog or os_log inside a `#if` DEBUG block)
indicating the window or event context, then return nil as before; leave the
isHidden toggle and defer unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 631d9b05-9140-4259-b290-af875123f895

📥 Commits

Reviewing files that changed from the base of the PR and between 5cab7c4 and 13ed3ac.

📒 Files selected for processing (7)
  • GhosttyTabs.xcodeproj/project.pbxproj
  • Resources/Localizable.xcstrings
  • Sources/GhosttyTerminalView.swift
  • Sources/Panels/CmuxWebView.swift
  • Sources/Panels/MarkdownPanelView.swift
  • Sources/cmuxApp.swift
  • cmuxTests/InactivePaneFirstClickFocusTests.swift

Comment thread Sources/cmuxApp.swift

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

No issues found across 7 files

@lawrencecchen
lawrencecchen merged commit 7cbac35 into main Mar 19, 2026
10 of 14 checks passed
@lawrencecchen
lawrencecchen deleted the task-single-click-inactive-pane-focus branch March 19, 2026 08:01
bn-l pushed a commit to bn-l/cmux that referenced this pull request Apr 3, 2026
* Add failing first-click pane focus tests

* Enable optional first-click focus for inactive panes

* Address PR review follow-ups

---------

Co-authored-by: Lawrence Chen <lawrencecchen@users.noreply.github.com>

This branch was successfully deployed

1 active deployment
Preview — 673246eb Deployed Mar 19, 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.

Add optional single-click pane focus when returning from another app

1 participant