Skip to content

fix(sender): stop the status menu from orphaning a click-eating window#111

Merged
swellweb merged 1 commit into
swellweb:mainfrom
DrDavidL:codex/fix-menubar-orphan-deadzone
Jun 15, 2026
Merged

fix(sender): stop the status menu from orphaning a click-eating window#111
swellweb merged 1 commit into
swellweb:mainfrom
DrDavidL:codex/fix-menubar-orphan-deadzone

Conversation

@DrDavidL

Copy link
Copy Markdown
Contributor

Problem

Opening the menu bar icon's menu and then selecting an item left a persistent "dead zone" below the icon — a rectangle where the cursor was visible but hover/clicks never landed (links wouldn't highlight, menu bar unclickable). It cleared only by quitting the app, and reproduced reliably after clicking an option (not on plain open/close).

A system window dump while the dead zone was present revealed the culprit:

owner=TargetBridge  layer=101 (menu/popup)  alpha=0.00  w=974 h=272

A TargetBridge-owned, invisible, menu-layer window sitting at the menu's exact footprint — the menu's backing window had faded out but was never closed, so it kept swallowing clicks.

Cause

Each @objc menu-item handler ran synchronously while the menu was still dismissing. Activating the app, ordering windows front (showMainWindow), or mutating observed session state (addSession/stopAll) mid-dismissal interrupted the menu window's fade-out: alpha reached 0 but the window was never removed.

A secondary contributor: the controller reassigned item.menu = makeMenu() on every service.objectWillChange (which fires constantly during a session), so the menu object could be swapped while open/tracking.

Fix

  • Assign one NSMenu for the status item's lifetime; repopulate items lazily via NSMenuDelegate.menuNeedsUpdate(_:) instead of swapping the menu object.
  • Defer each menu-item handler to the next runloop tick so the menu fully tears down before the action runs.

Testing

Built locally (xcodebuild … TBDisplaySender). Verified on-device: opened the menu and exercised Show main window, Add session, and Stop all repeatedly during an active session — no dead zone appears, and the orphaned layer-101 alpha-0 window no longer shows in the window dump.

🤖 Generated with Claude Code

Clicking the menu bar icon's menu and then selecting an item left a
persistent "dead zone" below the icon: a rectangle where the cursor was
visible but hover/clicks never landed, cleared only by quitting the app.
A window dump while the zone was present showed a TargetBridge-owned,
layer-101 (menu/popup), alpha-0 window at the menu's exact 974x272
footprint — the menu's backing window had faded out but was never closed.

Two changes remove the orphaning:

- Assign one NSMenu for the status item's lifetime and repopulate it
  lazily via NSMenuDelegate.menuNeedsUpdate(_:), instead of reassigning
  item.menu = makeMenu() on every service.objectWillChange. Swapping the
  menu object while it is open/tracking can strand its window.

- Defer each menu-item handler to the next runloop tick. The handlers ran
  synchronously while the menu was still dismissing; activating the app,
  ordering windows front, or mutating observed session state mid-dismissal
  interrupted the fade-out so the window reached alpha 0 but was never
  removed. Deferring lets the menu fully tear down first. This was the
  primary cause — the dead zone only appeared after selecting an item,
  not on plain open/close.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@swellweb swellweb merged commit 92e812c into swellweb:main Jun 15, 2026
3 checks passed
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.

2 participants