| name | macos-blueprint |
| description | Use the macOS app blueprint to shape new native menu bar apps under ~/apps/macos. Use when deciding a menu bar app's data source, whether it needs the credentials/networking/oauth/icon-render modules, its distribution path (Developer ID direct vs Mac App Store), how the menu and settings are structured, or whether an idea needs native work beyond the menu bar (sidebar/full window). Pairs with the `macos` CLI skill for commands. |
macOS menu bar app blueprint
Architectural decisions for apps built on ~/blueprints/macos. For commands, use the macos skill.
The shape every app shares
- SwiftPM, three targets:
__App__Core (portable logic, tested via a CLI), __app__cli
(headless), __App__ (thin AppKit+SwiftUI UI). Put logic in Core; keep the app target thin.
- AppKit
NSStatusItem + native NSMenu with SwiftUI rows hosted in NSMenuItem.view. Not
MenuBarExtra — that can't draw live icons or show multiple items, and boxes you in.
- Modern Observation (
@Observable/@State/@Bindable), injected UserDefaults, no
ObservableObject.
- Developer-ID-direct distribution: notarized zip → Sparkle auto-update → Homebrew cask.
Decide the data source
| App idea | Data | Modules to enable |
|---|
| Static/computed (clock, system stat, timer) | none / local compute | core only |
| Reads a public API (weather, prices, status) | URLSession + cache | networking |
| Reads an authed API (GitHub, a SaaS) | API + stored token | networking + credentials (+ oauth if OAuth) |
| Local prefs/state only | UserDefaults (built in) | core only |
| Live indicator in the menu bar (progress, count) | any of the above | add icon-render |
macos new <slug> --api enables networking + oauth + credentials up front. Otherwise add a
module later with macos modules update <slug> <module>.
Credentials
If the app stores a token/key, use the credentials module's TokenStore (Keychain in release,
file in debug — so dev and agents never hit a Keychain prompt). Never roll your own SecItem calls;
never log tokens. For OAuth, pair oauth (PKCE + loopback callback) with credentials.
Menu & settings
- Menu content is a
MenuDescriptor value tree — add sections/entries there, not ad-hoc in the
controller. Keep it pure so it stays unit-testable.
- Settings is a tabbed
Settings scene; add a pane per concern, reuse SettingsSection /
PreferenceToggleRow. Persist via SettingsStore (injected UserDefaults).
Distribution
- Default: Developer ID direct. Best for open source — no sandbox restrictions, Sparkle
auto-update,
brew install --cask. This is fully built.
- Mac App Store: designed in
references/app-store-connect.md, lands in v1.1. Set
distribution: "mas" in app.config.json when ready; it requires app-sandbox entitlements and a
different upload path. Choose MAS only if you need its discovery/billing; it costs you the
non-sandbox freedoms.
When the menu bar isn't enough
A full window with a sidebar (the eventual "proper app" track) is additive: the same Core + the same
Settings scene, plus a real WindowGroup with NavigationSplitView. Anything needing private
entitlements (FamilyControls, screen recording, accessibility automation) is a meaningfully larger
native effort — flag it early; it's not a menu-bar-afternoon.
Don't
- Don't reach for
MenuBarExtra, ObservableObject, or @AppStorage — they're off-pattern here.
- Don't add dependencies casually; the baseline is Foundation + Sparkle.
- Don't put identity/distribution facts anywhere but
app.config.json.