Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Sources/TerminalWindowPortal.swift
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ final class WindowTerminalHostView: NSView {
let eventType = routingContext.eventType

if routingContext.allowsPortalPointerHitTesting {
if shouldPassThroughToTitlebar(at: point) {
if shouldPassThroughToTitlebar(at: point),
hostedTerminalHitView(at: point) == nil {
clearActiveDividerCursor(restoreArrow: false)
return nil
}
Expand Down
39 changes: 39 additions & 0 deletions cmuxTests/TerminalAndGhosttyTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -3802,6 +3802,45 @@ final class WindowTerminalHostViewTests: XCTestCase {
)
}

func testHostViewKeepsTerminalTopRowClickableInsideTitlebarBand() {
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 hostedView = makeHostedTerminalView(frame: host.bounds)
host.addSubview(hostedView)
container.addSubview(host, positioned: .above, relativeTo: contentView)

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)

XCTAssertGreaterThanOrEqual(
pointInWindow.y,
BonsplitTabBarPassThrough.titlebarInteractionBandMinY(in: window),
"The regression point must exercise the fixed-height titlebar pass-through band"
)
assertHitFallsInsideHostedTerminal(
host.performHitTest(at: pointInHost, currentEvent: event),
hostedView: hostedView,
message: "Minimal-mode terminal content that extends into the titlebar band should keep receiving top-row mouse-downs"
)

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 Guard assertion does not exit on failure

XCTAssertGreaterThanOrEqual records a failure but does not stop execution. If the window geometry unexpectedly places pointInWindow.y below titlebarInteractionBandMinY, the guard assertion fires but assertHitFallsInsideHostedTerminal is still evaluated. Because the hosted terminal fills the entire host.bounds, a click anywhere on it would route to the terminal even without the fix — so the second assertion would likely pass on the old code as well, making the regression test give a false sense of coverage while only the guard shows as failing. Adding a return (or using XCTSkipIf) after the guard assertion ensures the test aborts rather than silently exercising the wrong scenario.

Suggested change
XCTAssertGreaterThanOrEqual(
pointInWindow.y,
BonsplitTabBarPassThrough.titlebarInteractionBandMinY(in: window),
"The regression point must exercise the fixed-height titlebar pass-through band"
)
assertHitFallsInsideHostedTerminal(
host.performHitTest(at: pointInHost, currentEvent: event),
hostedView: hostedView,
message: "Minimal-mode terminal content that extends into the titlebar band should keep receiving top-row mouse-downs"
)
guard pointInWindow.y >= BonsplitTabBarPassThrough.titlebarInteractionBandMinY(in: window) else {
XCTFail("The regression point must exercise the fixed-height titlebar pass-through band")
return
}
assertHitFallsInsideHostedTerminal(

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 in b6198ed by converting the titlebar-band preconditions to guard/return checks in the new focused test file, so the assertions cannot continue on the wrong geometry.

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

Superseded in the current test file: the titlebar-band geometry check now uses Swift Testing , so the test exits before evaluating the hit assertion if the point is outside the fixed band.

— Claude Code

}

func testHostViewPassesThroughWhenNoTerminalSubviewIsHit() {
let host = WindowTerminalHostView(frame: NSRect(x: 0, y: 0, width: 200, height: 120))

Expand Down
Loading