fix: adopt the native macOS app lifecycle around hide-to-tray - #4876
fix: adopt the native macOS app lifecycle around hide-to-tray#4876ole-tidal wants to merge 2 commits into
Conversation
…wns it Cmd+W is the standard macOS chord for closing the focused window; for a tray-resident app that means hide-to-tray, matching the red close button. The item returns as a custom MenuItem (predefined items cannot be toggled) and Buzz Term disables it for exactly the span it owns the keyboard, so the accelerator falls through to the webview and the close-tab chord still runs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Ole Bakstad <obakstad@squareup.com>
Four deviations from standard macOS behavior, all rooted in the hide-to-tray window model, compounded into "the app ignores me" moments: - Cmd+Tab into Buzz while the window was hidden showed only the menu bar: macOS surfaces Dock clicks as Reopen, but app-switcher activations only post NSApplicationDidBecomeActiveNotification, which nothing observed. New app_activation module re-presents the main window when the app becomes active with nothing visible, gated on the deliberate first reveal. - Closing the last window left Buzz frontmost with zero windows — a dead Cmd+Tab target that window managers (Rectangle) can't resolve a window from. CloseRequested now yields activation (app.hide()) when no other window remains visible. - The main window carried an empty title; Mission Control, the Window menu, and AX tools showed a nameless window. It is now "Buzz", still visually hidden by the overlay title bar. - The app submenu lacked the standard Settings… (Cmd+,) and Check for Updates… items. Both are added in HIG positions and forward to the webview: Settings toggles the settings route (mirroring the existing webview shortcut, which keeps working elsewhere), Check for Updates lands on Settings → Updates with the check already running. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Signed-off-by: Ole Bakstad <obakstad@squareup.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 01de0c1c55
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // and the block is sendable — it captures only a cloned `AppHandle`, | ||
| // which is `Send + Sync`. The observer token is deliberately leaked: | ||
| // the observer must live for the whole app lifetime. | ||
| let token = unsafe { |
There was a problem hiding this comment.
Avoid adding unsafe notification registration
This new AppKit observer introduces an unsafe block in production desktop code. The repo-level agent rules explicitly disallow unsafe code, so this should be refactored behind an existing safe abstraction or otherwise removed rather than landing a new unsafe call site.
AGENTS.md reference: AGENTS.md:L113-L114
Useful? React with 👍 / 👎.
|
🤖 Closing at the reporter's request: the driving pain point (window-manager control) turned out to be a stale Rectangle Accessibility session on the reporting machine, resolved by restarting Rectangle. Withdrawing the remaining native-behavior changes rather than pursuing them. |
Why
Buzz on macOS deviated from standard app behavior in ways that compounded into "the app ignores me" moments (originally reported as Rectangle and ⌘W not working). All of them trace back to the hide-to-tray window model missing its native-lifecycle counterparts:
applicationShouldHandleReopen:(Tauri'sReopenevent — already handled), but ⌘Tab and other app-switcher activations only postNSApplicationDidBecomeActiveNotification, which nothing observed. Every standard macOS app re-presents a window on activation."title": ""), so Mission Control, the Window menu, and AX tools showed a nameless window.What
app_activation.rs(new): observesNSApplicationDidBecomeActiveNotification(block2 observer, same pattern and safety posture asmouse_nav.rs) and re-presents the main window when the app becomes active with no visible webview window. Gated on the deliberate first reveal (INITIAL_REVEAL_DONE, set by the first-frame reveal and by any explicit show) so launch-time self-activation cannot preempt the geometry-settled reveal. Minimized-only counts as "nothing visible", matching how ⌘Tab de-miniaturizes in standard apps.lib.rs: after hide-to-tray onCloseRequested, if no other window is visible,app.hide()yields activation to the next app — the same handoff as closing the last window of any Mac app. The two changes compose: ⌘W hands focus to the next app; ⌘Tab back re-presents the window.tauri.conf.json: windowtitle: "Buzz"(still visually hidden viahiddenTitle+ overlay title bar).Verification on a local build
Tested against a locally built instance (
just desktop-standalone), driving the real app pid-exactly over the Accessibility API:Buzz(was empty) —windowTitles=["Buzz"].Check for Updates…andSettings…with cmd char,, in HIG positions between About and Services.NSApplicationDidBecomeActiveNotificationwith correct gate values — verified via a temporary log line in the running app:did-become-active reveal_done=true any_visible=true(correctly no-oping while a window is visible).show_main_windowre-presents the hidden window (tray path, exercised repeatedly).should_restore) is unit-tested for all four gate combinations.Scope note on Rectangle: during live verification, the reporter's residual "Rectangle can't resize the window" symptom (window visible and focused) turned out to be Rectangle's own stale Accessibility session — restarting Rectangle fixed it, and the window was AX-resizable throughout. This PR's window-manager relevance is limited to the real defect above: the frontmost-with-zero-windows state after close-to-tray, where there is no window for any WM to target.
Verification gap, called out honestly: the composed end-to-end gesture — a real ⌘Tab into hidden-window Buzz auto-re-presenting, and the frontmost-app switch after close — cannot be driven programmatically: macOS 14+/Tahoe refuses every synthetic activation route without real user intent (
NSRunningApplication.activatecooperative-refused,activateIgnoringOtherAppsa documented no-op, System Eventsset frontmostrefused, HID-synthesized Dock clicks filtered). Every constituent link is verified above; the composition is three unit-tested lines. One human ⌘Tab confirms it.Also not coverable in standalone mode (no community, so
AppShelland its listeners never mount): the webview side of the Settings…/Check for Updates… events. That path is typechecked and mirrors the existing ⌘, handler; the native side (items present, correct accelerator) is verified above.Builds on the ⌘W PR (#4875) — both touch
app_menu.rs. Opened from a fork, so it targetsmaindirectly and includes that PR's commit until it merges; once it lands, this diff reduces to the lifecycle changes (the second commit).🤖 Generated with Claude Code