vercel-labs / vercel-labs/native
Menu-bar apps: window close-policy (hide instead of quit) and tray commands for app show/quit
Nobody has claimed this yet.
- Dominant language
- Zig
- Stars
- 7.7k
- Forks
- 314
- Avg merge
- 5h
- Merged PRs (30d)
- 13
Description
Version: native 0.5.3 (commit 57bf56b) · macOS
We're building a menu-bar music player (internet-radio client: status_item_fn tray, audio via fx.playAudio). Two lifecycle behaviors of that app class currently have no seam, and both need the same kind of host-side support. Filing together since they're two halves of one pattern.
1. Closing the last window always quits the app
windowWillClose in the AppKit host emits shutdown when windows.count == 0. For a media app, the red close button killing playback is wrong — the expected shape is Spotify-like: close hides the window, the audio and the tray keep running, and a Dock-icon click brings the window back. There's currently no policy hook: no windowShouldClose on the delegate and no manifest option.
Proposal: a declarative policy on ShellWindow / WindowDescriptor, e.g.
.close_policy = .quit (default) | .hide_app | .hide_window
.hide_app mapping cleanly onto [NSApp hide:] (Dock click restores for free, Cmd+Q/SIGTERM/AppleScript quit still terminate normally).
Our local patch (running fine for a while now) is a windowShouldClose: on NativeSdkWindowDelegate that hides the app when a status item is installed and the closing window is "main":
- (BOOL)windowShouldClose:(NSWindow *)sender {
(void)sender;
if (self.host.statusItem == nil) return YES;
NSString *label = self.host.windowLabels[@(self.windowId)];
if ([label isEqualToString:@"main"]) {
[NSApp hide:nil];
return NO;
}
return YES;
}
2. Tray items can't open (unhide) or quit the app
Once close-hides exists, the tray needs "Open player" and "Quit" rows — but tray commands dispatch into update, and the model has no channel to [NSApp unhide:] / [NSApp activate...] / [NSApp terminate:]. (The status_item docs example even shows .command = "app.quit", but nothing handles that name.)
Proposal: well-known built-in tray commands the host handles before (or instead of) forwarding — e.g. "app.show" → unhide + activate, "app.quit" → terminate — mirroring what the docs already imply.
Our local patch reserves item ids in trayMenuItemClicked::
if (menuItem.tag == 100) { /* "Open player" */
[NSApp unhide:nil];
[NSApp activateIgnoringOtherApps:YES];
} else if (menuItem.tag == 101) { /* "Quit" */
[NSApp terminate:nil];
return;
}
Both patches together give the full menu-bar-app close/reopen/quit loop; we'd love to drop them for first-class options. Happy to send PRs if the proposed shapes look right.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with the AppKit host's NativeSdkWindowDelegate and its windowWillClose/windowShouldClose behavior, then trace ShellWindow or WindowDescriptor and the trayMenuItemClicked: entry point. Compare the status_item documentation example with the host command dispatch. Done means a declared close policy and built-in show/quit commands support the described hide, reopen, and termination flows without breaking default quitting.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- macos, objective-c, zig
- Domain
- desktop
- Issue type
- Feature
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 45/100