Skip to content

Fix terminal top-row click routing - #3720

Merged
austinywang merged 2 commits into
mainfrom
issue-3709-terminal-top-rows-not-clickable
May 8, 2026
Merged

austinywang merged 2 commits into
mainfrom
issue-3709-terminal-top-rows-not-clickable

Conversation

@austinywang

@austinywang austinywang commented May 8, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • add a regression covering terminal top-row hit ownership when tab-strip chrome regions overlap terminal pixels
  • keep Bonsplit tab-strip pass-through from swallowing hits that resolve inside a visible hosted terminal surface

Testing

  • Not run locally: xcodebuild-based test execution is intentionally skipped per repo policy; CI should run the test matrix.

Fixes #3709


Note

Medium Risk
Touches pointer hit-testing/routing in WindowTerminalHostView, which can subtly affect mouse interactions across chrome/terminal boundaries. Scoped change with targeted regression coverage lowers the risk but UI event routing regressions are still possible.

Overview
Fixes click routing when the Bonsplit tab strip’s pass-through region overlaps terminal pixels by only passing through to the pane tab bar if no visible GhosttySurfaceScrollView is hit.

Adds a regression test ensuring a mouse-down on the terminal’s absolute top row is delivered to the hosted terminal even when a fake tab strip overlaps that area.

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


Summary by cubic

Keep the terminal’s top row clickable even when tab-strip hit regions overlap terminal pixels. Fixes #3709 by routing hits to the visible hosted terminal before any chrome pass-through.

  • Bug Fixes
    • In WindowTerminalHostView, only pass through to chrome when the point does not hit a visible hosted terminal view (GhosttySurfaceScrollView).
    • Keep Bonsplit tab-strip pass-through for real chrome, but it no longer swallows terminal-surface hits.
    • Added a unit regression that models a small tab-strip overlap and asserts the terminal’s absolute top row owns mouse-down events.

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

Summary by CodeRabbit

  • Bug Fixes

    • Fixed an issue where clicks could incorrectly pass through overlapping UI chrome into terminal content; terminal top-row and other click areas remain interactive when UI elements overlap.
  • Tests

    • Added a regression test to ensure hit-testing keeps terminal content clickable when the tab-strip region overlaps.

The terminal portal could defer top-row clicks to chrome when a tab-strip hit region overlapped the rendered terminal surface. Add a regression that models that overlap through the existing AppKit hit-test seam before changing routing policy.

Constraint: Do not run XCUITests locally; this uses the unit-test hit-test seam instead.
Confidence: high
Scope-risk: narrow
Directive: Keep this test focused on terminal-surface ownership rather than source-code shape.
Tested: Not run locally by policy; intended to fail before the fix.
Not-tested: Local xcodebuild-based test execution.
@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 3:09am
cmux-staging Building Building Preview, Comment May 8, 2026 3:09am

@coderabbitai

coderabbitai Bot commented May 8, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Gates tab-bar pass-through on whether a portal-hosted terminal surface is hit by adding hostedTerminalHitView(at:) and requiring no hosted hit before allowing shouldPassThroughToPaneTabBar to return true. Adds a regression test verifying the terminal top row remains clickable when overlapped by the tab strip.

Changes

Terminal Hit-Testing Pass-Through

Layer / File(s) Summary
Hosted Terminal Hit-Test Helper
Sources/TerminalWindowPortal.swift
New hostedTerminalHitView(at:) scans portal-hosted GhosttySurfaceScrollView subviews in reverse z-order and returns the hit hosted terminal view (or its hitTest result) for visible/non-hidden/non-zero-alpha surfaces; otherwise returns nil.
Pass-Through Decision Logic
Sources/TerminalWindowPortal.swift
shouldPassThroughToPaneTabBar(at:eventType:) now returns true only when the Bonsplit pass-through decision is true AND hostedTerminalHitView(at:) returns nil.
Regression Test
cmuxTests/TerminalAndGhosttyTests.swift
Added testHostViewKeepsTerminalTopRowClickableWhenTabStripRegionOverlapsContent confirming terminal top-row hit-testing remains owned by the terminal when the tab-strip overlaps content.

Possibly Related PRs

  • manaflow-ai/cmux#3194: Related — also modifies WindowTerminalHostView hit-testing/pass-through logic.
  • manaflow-ai/cmux#3299: Related — overlaps on portal pass-through and hit-test routing changes for terminal pane drop targets.
  • manaflow-ai/cmux#1213: Related — touches Ghostty hosted-surface overlay and hit/overlay behavior that interacts with this change.

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐰 I hopped through view layers, soft and spry,
Found clicks that hid beneath the UI sky.
A helper sniffed the surface, true and neat,
Now top-row clicks return to shell and seat. 🎩

🚥 Pre-merge checks | ✅ 13 | ❌ 1

❌ Failed checks (1 warning)

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.
✅ Passed checks (13 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Fix terminal top-row click routing' clearly and concisely summarizes the main change: fixing click/hit-testing routing for the terminal's top row.
Linked Issues check ✅ Passed The PR's code changes directly address issue #3709: adding a regression test for top-row click routing and modifying hit-testing logic in WindowTerminalHostView to prevent the tab-strip from swallowing hits to visible hosted terminals.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the terminal top-row click routing issue. The helper function, hit-testing logic modification, and regression test are all directly related to the stated objective.
Cmux Swift Actor Isolation ✅ Passed Production code adds private helper to NSView subclass. No new types or isolation structures. All operations UI-bound, consistent with NSView's implicit MainActor. Test changes exempt.
Cmux Swift Blocking Runtime ✅ Passed No blocking synchronization patterns found. Production changes use deterministic hit-testing logic only. Test uses standard scaffolding. No semaphores, waits, sleeps, asyncAfter, or locks.
Cmux No Hacky Sleeps ✅ Passed Only Swift files modified. Check excludes Swift code (per .github/review-bot-rules/runtime-no-hacky-sleeps.md scope). Check applies to TypeScript, JavaScript, shell scripts only.
Cmux Swift Concurrency ✅ Passed The PR introduces no legacy async patterns. Changes are purely synchronous hit-testing logic (helper function and test) with no DispatchQueue, Combine, Task, or completion-handler additions.
Cmux Swift @Concurrent ✅ Passed Changes introduce only synchronous functions with no async/await, actor isolation, or concurrency issues. Hit-testing logic remains appropriate for UI-bound work.
Cmux Swift File And Package Boundaries ✅ Passed Focused bug fix: +14 lines to existing TerminalWindowPortal.swift adding UI/hit-testing glue. No mixed responsibilities. Matches allowed case for focused bug fixes in large files.
Cmux Swift Logging ✅ Passed No Swift logging violations found. Production code changes contain no print, debugPrint, dump, NSLog, or ad hoc logging calls. Test additions comply with rules.
Cmux Swiftui State Layout ✅ Passed AppKit-only changes (NSView hit-testing). No @Published/@Observable/@StateObject/@EnvironmentObject. No GeometryReader. No state mutations. Allowed AppKit bridge view exception.
Cmux Architecture Rethink ✅ Passed Targeted hit-testing fix with pure helper function. No timing patterns, mutable state, caches, or observers. Clear ownership and invariant.
Description check ✅ Passed The PR description covers the summary, testing approach, and fixes linked issue #3709 with risk context.

✏️ 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-3709-terminal-top-rows-not-clickable

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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 a hit-test routing bug (#3709) where the Bonsplit tab-strip pass-through was swallowing click events on the terminal's top row when tab-strip chrome regions geometrically overlapped the terminal surface. It gates pass-through on there being no visible GhosttySurfaceScrollView at the hit point.

  • Bug fix in shouldPassThroughToPaneTabBar: after the BonsplitTabBarPassThrough decision approves pass-through, a second check (hostedTerminalHitView) walks subviews in reverse z-order and returns the first visible GhosttySurfaceScrollView whose frame contains the point; if one is found, pass-through is suppressed and the terminal wins.
  • Regression test added: builds a window with a fake tab strip overlapping the terminal by 2 px and asserts that a mouse-down event at the terminal's topmost half-pixel resolves inside the hosted terminal view, not nil.

Confidence Score: 5/5

Safe to merge; the change is narrowly scoped to one pass-through guard and has a targeted regression test covering the exact failure geometry.

The fix adds a single, well-bounded helper that only fires after the existing BonsplitTabBarPassThrough decision already says pass through. No existing pass-through paths are widened, the subview walk mirrors the same reverse-z-order pattern used elsewhere in the file, and the ?? hostedView fallback correctly handles a transparent GhosttySurfaceScrollView.hitTest. The regression test exercises the precise 2-px overlap scenario that triggered the bug.

No files require special attention.

Important Files Changed

Filename Overview
Sources/TerminalWindowPortal.swift Adds hostedTerminalHitView(at:) helper and threads it through shouldPassThroughToPaneTabBar to prevent chrome pass-through when a visible terminal surface occupies the hit point. Logic is minimal and consistent with existing coordinate-space conventions in the file.
cmuxTests/TerminalAndGhosttyTests.swift Adds testHostViewKeepsTerminalTopRowClickableWhenTabStripRegionOverlapsContent regression test. Setup accurately mirrors the problematic geometry, coordinate conversions are correct, and the assertion uses the existing assertHitFallsInsideHostedTerminal helper.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[hitTest called with point] --> B{isPointerEvent?}
    B -- No --> Z[super.hitTest / nil]
    B -- Yes --> C{shouldPassThroughToTitlebar?}
    C -- Yes --> N1[return nil — titlebar wins]
    C -- No --> D{shouldPassThroughToPaneTabBar?}
    D --> E{BonsplitTabBarPassThrough decision.result?}
    E -- No --> CONT[continue to sidebar/divider checks]
    E -- Yes --> F{hostedTerminalHitView returns non-nil?}
    F -- Yes --> G[return false — terminal wins, pass-through suppressed]
    F -- No --> H[return true — tab strip wins, return nil]
    G --> CONT
    CONT --> I{shouldPassThroughToSidebarResizer?}
    I -- Yes --> N3[return nil]
    I -- No --> J{splitDividerCursorKind?}
    J -- Yes --> N4[return nil]
    J -- No --> K[super.hitTest → terminal subview]
Loading

Reviews (2): Last reviewed commit: "Keep terminal content above tab-strip hi..." | Re-trigger Greptile

Comment thread Sources/TerminalWindowPortal.swift Outdated

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

Comment thread Sources/TerminalWindowPortal.swift Outdated
The terminal portal now treats visible hosted terminal surfaces as the source of truth for terminal content hit ownership. Bonsplit tab-strip pass-through still handles real chrome, but it cannot swallow points that resolve inside a hosted terminal surface.

Constraint: Top-row terminal clicks must reach Ghostty even when a chrome hit region overlaps terminal pixels.
Rejected: Add a fixed Y offset below the tab strip | brittle across titlebar, font, and scale changes.
Confidence: high
Scope-risk: narrow
Directive: Do not reintroduce tab-strip pass-through before checking hosted terminal ownership.
Tested: Added unit regression in WindowTerminalHostViewTests; local xcodebuild execution skipped by policy.
Not-tested: Local XCUITests; CI will run the project test matrix.

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

@austinywang
austinywang merged commit 3d6cdf1 into main May 8, 2026
24 checks passed
@AlexPoirier1

Copy link
Copy Markdown

@austinywang This is still an issue for me even with this fix. Should I open a new issue?

@Jimmy-b36

Copy link
Copy Markdown

This PR also didn't fix it for me

@AlexPoirier1

Copy link
Copy Markdown

Hey as an FYI, I noticed that the issue only still exists in "compact" mode. So a workaround for me was to turn "compact" mode off

@LielAmar

LielAmar commented Jun 10, 2026 •

Copy link
Copy Markdown

Still happening on the latest version (0.64.14) only when using "compact" mode

This branch was successfully deployed

1 active deployment
Preview – cmux — c829a2e8 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.

Top rows of terminal surfaces are not clickable

4 participants