Dismiss menu bar popover when opening Settings#481
Merged
Conversation
The MenuBarExtra(.window) popover stayed visible on top of the Settings window when the user clicked Settings from the menu bar, obscuring part of the Settings UI. SwiftUI's \\.dismiss action does not work for MenuBarExtra(.window), so capture the popover's host NSWindow via an NSViewRepresentable (the same viewDidMoveToWindow hook MenuBarPresentationObserver uses) and orderOut it before opening the Settings window. Fixes FuJacob#455
Contributor
Author
Xcode re-introduced its auto-signing artifact (DEVELOPMENT_TEAM in per-config build settings, no DevelopmentTeam in TargetAttributes) the last time the project was opened locally. Resetting the file to upstream/main and re-running xcodegen leaves only the legitimate MenuBarPopoverDismisser.swift rows in the diff. Addresses Greptile P1 on FuJacob#481.
Calling orderOut on the popover window did not clear the NSStatusBarButton's pressed-state appearance, leaving the menu bar icon looking stuck in the "open" state after Settings appeared. The system only clears that highlight on its own click-toggle path, so do it explicitly: walk NSApp.windows for our NSStatusBarButton (public AppKit, scoped to our process) and call highlight(false) once the popover has been ordered out.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
MenuBarExtra(.window)popover stayed visible on top of the Settings window after clicking Settings from the menu bar, partially obscuring the Settings UI. SwiftUI's@Environment(\.dismiss)does not work insideMenuBarExtra(.window), so this captures the popover's hostNSWindowthrough the sameviewDidMoveToWindowhookMenuBarPresentationObserveralready uses, and callsorderOut(nil)on it before invokingonOpenSettings.The existing inline note at
MenuBarView.swift:60already acknowledged this gap — it explained thatLinkdismisses the popover for free butButtondoes not. This PR closes that gap for the Settings button.Fixes #455
Validation
Tested manually: opened the menu bar dropdown, clicked Settings, and confirmed the popover dismisses cleanly before the Settings window appears — no overlap, no lingering popover. The fix is awkward to capture as a still image since the bug is "popover stays" / fix is "popover goes away"; a short screen recording is the best evidence and can be added in a follow-up comment if reviewers want it.
swiftlint lint --quietnot run locally (binary not installed on this machine); CI will gate.Linked issues
Fixes #455
Risk / rollout notes
popoverDismisser.dismiss(); the app menu / shortcut paths into the Settings window still go throughSettingsCoordinator.showSettings()directly.weak, and the binder skips capture whenwindowis nil (the teardown phase), so no stale reference can outlive the popover instance.NSViewRepresentableinstead of\.dismiss: SwiftUI'sDismissActionis documented for "current presentation" but does not bridge to the AppKit window that backsMenuBarExtra(.window). The existingMenuBarPresentationObserveralready proves theviewDidMoveToWindowroute is the reliable way to reach that window from SwiftUI; this PR reuses the same pattern.Greptile Summary
This PR fixes a bug where the
MenuBarExtra(.window)popover remained visible on top of the Settings window after clicking the Settings button, because SwiftUI's@Environment(\\.dismiss)does not bridge to the AppKit window backing aMenuBarExtra.MenuBarPopoverDismisser, a@MainActor ObservableObjectthat captures the popover's hostNSWindowvia aweakreference using the sameviewDidMoveToWindowhook already used byMenuBarPresentationObserver, and exposes adismiss()method that callsresignKey()+orderOut(nil)+ a DFS walk ofNSApp.windowsto unhighlight theNSStatusBarButton.MenuBarPopoverDismisserBinder, an invisibleNSViewRepresentableattached as a.backgroundmodifier, which forwards the real host window to the dismisser.MenuBarViewnow callspopoverDismisser.dismiss()before invokingonOpenSettings(), eliminating the popover-over-Settings overlap.Confidence Score: 5/5
Safe to merge once the local signing-identity artifact in project.pbxproj (flagged in a prior review) is reverted.
The new dismissal logic is narrowly scoped to the Settings button click path, uses weak references correctly to avoid retain cycles, and mirrors the proven pattern already in MenuBarPresentationObserver. The dismiss() → onOpenSettings() call order is synchronous and AppKit-safe. No data flow, persistence, or auth paths are touched.
Cotabby.xcodeproj/project.pbxproj — the DevelopmentTeam signing change flagged in the previous review thread still needs to be reverted before merging.
Important Files Changed
Sequence Diagram
sequenceDiagram participant User participant MenuBarView participant MenuBarPopoverDismisserBinder participant WindowBindingView participant MenuBarPopoverDismisser participant NSWindow_popover as NSWindow (popover) participant SettingsCoordinator Note over MenuBarView,WindowBindingView: On menu bar open MenuBarView->>MenuBarPopoverDismisserBinder: .background(binder) MenuBarPopoverDismisserBinder->>WindowBindingView: makeNSView / updateNSView WindowBindingView->>WindowBindingView: viewDidMoveToWindow() WindowBindingView->>MenuBarPopoverDismisser: "hostWindow = window" Note over User,SettingsCoordinator: User clicks Settings User->>MenuBarView: tap Settings button MenuBarView->>MenuBarPopoverDismisser: dismiss() MenuBarPopoverDismisser->>NSWindow_popover: resignKey() MenuBarPopoverDismisser->>NSWindow_popover: orderOut(nil) MenuBarPopoverDismisser->>MenuBarPopoverDismisser: unhighlightStatusBarButton() MenuBarView->>SettingsCoordinator: onOpenSettings() SettingsCoordinator-->>User: Settings window appears (no popover overlap)Reviews (3): Last reviewed commit: "Unhighlight menu bar status item when di..." | Re-trigger Greptile