From e8fd793303aef17f1fad7e7025fce9997bb554f4 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 7 May 2026 19:59:03 -0700 Subject: [PATCH 1/2] Cover terminal top-row hit ownership 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. --- cmuxTests/TerminalAndGhosttyTests.swift | 48 +++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/cmuxTests/TerminalAndGhosttyTests.swift b/cmuxTests/TerminalAndGhosttyTests.swift index c143a41971ae..b37ee8e5a9ae 100644 --- a/cmuxTests/TerminalAndGhosttyTests.swift +++ b/cmuxTests/TerminalAndGhosttyTests.swift @@ -2257,6 +2257,54 @@ final class WindowTerminalHostViewTests: XCTestCase { ) } + func testHostViewKeepsTerminalTopRowClickableWhenTabStripRegionOverlapsContent() { + let window = NSWindow( + contentRect: NSRect(x: 0, y: 0, width: 420, height: 260), + styleMask: [.titled, .closable], + backing: .buffered, + defer: false + ) + defer { window.orderOut(nil) } + guard let contentView = window.contentView, + let container = contentView.superview else { + XCTFail("Expected window content container") + return + } + + let hostFrame = container.convert(contentView.bounds, from: contentView) + let host = WindowTerminalHostView(frame: hostFrame) + host.autoresizingMask = [.width, .height] + + let terminalFrame = host.bounds.insetBy(dx: 0, dy: 32) + let hostedView = makeHostedTerminalView(frame: terminalFrame) + host.addSubview(hostedView) + container.addSubview(host, positioned: .above, relativeTo: contentView) + + let tabStripOverlap: CGFloat = 2 + let terminalTopInContent = contentView.convert(hostedView.frame, from: host).maxY + let tabStrip = FakeTabBarBackgroundNSView( + frame: NSRect( + x: 0, + y: terminalTopInContent - tabStripOverlap, + width: contentView.bounds.width, + height: 44 + ) + ) + tabStrip.autoresizingMask = [.width, .minYMargin] + contentView.addSubview(tabStrip) + + let pointInHostedView = NSPoint(x: hostedView.bounds.midX, y: hostedView.bounds.maxY - 0.5) + let pointInWindow = hostedView.convert(pointInHostedView, to: nil) + let pointInHost = host.convert(pointInWindow, from: nil) + let event = makeMouseDownEvent(at: pointInWindow, window: window) + + assertHitFallsInsideHostedTerminal( + host.performHitTest(at: pointInHost, currentEvent: event), + hostedView: hostedView, + message: "The absolute top row of terminal content should own mouse-down hit-testing even if chrome hit regions overlap it" + ) + } + func testHostViewPassesThroughWhenNoTerminalSubviewIsHit() { let host = WindowTerminalHostView(frame: NSRect(x: 0, y: 0, width: 200, height: 120)) From c829a2e8a39e0fab171ea11b7f327b2221f05d08 Mon Sep 17 00:00:00 2001 From: austinpower1258 Date: Thu, 7 May 2026 19:59:41 -0700 Subject: [PATCH 2/2] Keep terminal content above tab-strip hit regions 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. --- Sources/TerminalWindowPortal.swift | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/Sources/TerminalWindowPortal.swift b/Sources/TerminalWindowPortal.swift index c0ba5066e47e..589c97bc38b6 100644 --- a/Sources/TerminalWindowPortal.swift +++ b/Sources/TerminalWindowPortal.swift @@ -217,7 +217,20 @@ final class WindowTerminalHostView: NSView { in: self, eventType: eventType ) else { return false } - return decision.result + guard decision.result else { return false } + return hostedTerminalHitView(at: point) == nil + } + + private func hostedTerminalHitView(at point: NSPoint) -> NSView? { + for subview in subviews.reversed() { + guard let hostedView = subview as? GhosttySurfaceScrollView, + !hostedView.isHidden, + hostedView.alphaValue > 0, + hostedView.frame.contains(point) else { continue } + + return hostedView.hitTest(point) ?? hostedView + } + return nil } private func shouldPassThroughToChrome(at point: NSPoint, eventType: NSEvent.EventType?) -> Bool {