Report
Date: 2026-02-22 Enquiries completed: 6/6
Executive Summary¶
A SwiftUI MenuBarExtra app is the right approach for giving Picobot an always-on macOS presence. The API is stable (macOS 13+), the resource footprint is minimal (~10-30 MB RAM), and the thin-client architecture means the Swift codebase stays small (~500-1000 lines) while the FastAPI server retains all business logic. The recommended communication pattern is SSE for real-time event streaming plus REST for queries and commands. Native notifications via UNUserNotificationCenter with actionable buttons cover the sub-agent completion use case. Distribution via DMG + Sparkle auto-updates + Homebrew cask provides a professional pipeline without App Store review.
Theme 1: The MenuBarExtra API is Production-Ready with Known Workarounds¶
MenuBarExtra, introduced in macOS 13 Ventura, provides two styles: .menu (standard dropdown, limited to text/buttons/dividers) and .window (arbitrary SwiftUI content in a popover). The .window style is the right choice for Picobot's monitoring popover.
Key limitations that need workarounds: - SettingsLink does not work reliably in MenuBarExtra context. Solution: hidden 1x1 window providing SwiftUI environment context, plus activation policy toggling. - No first-party API for programmatic show/hide of the popover. Solution: MenuBarExtraAccess library (orchetect/MenuBarExtraAccess). - Scene declaration order matters on macOS 15+. Solution: declare Hidden Window first, MenuBarExtra second, Settings last.
Third-party libraries that fill gaps: - FluidMenuBarExtra: animated resizing, fade-out, screen-edge repositioning - MenuBarExtraAccess: programmatic popover control via Bool binding
Assessment: These are ergonomic gaps, not blockers. The workarounds are well-documented and stable.
Theme 2: The Thin-Client Architecture Maps Well to Existing Infrastructure¶
Picobot already has a FastAPI web server with REST endpoints. The menubar app should be a thin client that consumes data from this server.
Recommended communication stack:
| Channel | Protocol | Purpose |
|---|---|---|
| Event stream | SSE (Server-Sent Events) | Real-time agent status changes, sub-agent completions, errors |
| Queries | REST (GET) | Full agent details, configuration, history |
| Commands | REST (POST) | Stop agent, clear notifications, trigger actions |
Why SSE over WebSocket: The data flow is primarily server-to-client (monitoring). SSE provides automatic reconnection (built into the spec), works over standard HTTP, and has mature Swift client libraries (LDSwiftEventSource, mattt/EventSource). WebSocket's bidirectional capability is unnecessary since commands use REST.
FastAPI SSE endpoint: Use StreamingResponse with media_type="text/event-stream" or the sse-starlette library for EventSourceResponse.
Fallback: REST polling at 2-second intervals if SSE connection fails.
Theme 3: Native Notifications are Straightforward for the Core Use Case¶
The UNUserNotificationCenter framework provides everything needed for sub-agent completion notifications:
Two notification categories:
1. AGENT_COMPLETION: Actions = View Results, Open Dashboard, Dismiss
2. AGENT_FAILURE: Actions = View Details, Open Dashboard
Integration flow: SSE event received -> create UNNotificationRequest -> deliver via UNUserNotificationCenter -> user taps action -> delegate routes to appropriate handler.
Skip rich notification content extensions: Standard banners with action buttons are sufficient. Custom notification UI (UNNotificationContentExtension) adds significant complexity and has documented macOS-specific issues for marginal UX benefit.
Fallback for denied permissions: If the user declines notification permissions, show status changes in the menu bar icon (dynamic SF Symbol) and in the popover UI.
Theme 4: Distribution is Complex but Well-Understood¶
The distribution pipeline has multiple components, each with documented pitfalls:
Code signing: Requires Apple Developer Program ($99/year) and Developer ID Application certificate. Hardened runtime required for notarization.
Notarization: Use xcrun notarytool submit --wait with credentials stored in Keychain. Staple the ticket with xcrun stapler staple. Typically takes 2-15 minutes.
Sparkle auto-updates: The de facto standard for macOS outside-App-Store updates. Integrates via SPM. Key pitfall: skip App Sandbox to avoid XPC service entitlement issues. Use EdDSA (ed25519) for update signing. Host appcast XML and DMGs on GitHub Releases.
Homebrew cask: Create a tap repository (homebrew-picobot) with a cask formula pointing to GitHub Releases. Users install with brew install --cask picobot/picobot/picobot-menubar.
Critical decision: skip App Sandbox. Sandboxing causes the most Sparkle integration pain (XPC entitlements, filesystem restrictions, socket access). Since the app is distributed outside the App Store, sandboxing is not required. Hardened runtime alone satisfies notarization requirements.
Launch at login: SMAppService (macOS 13+) provides a simple register/unregister API. Always read status from SMAppService, not local storage.
Theme 5: SwiftUI is the Right Choice Over Alternatives¶
The counter-perspective analysis evaluated five alternatives:
| Alternative | Verdict | Reason |
|---|---|---|
| Electron | Rejected | 200-500 MB RAM for an always-on utility is disqualifying |
| Tauri | Viable but unnecessary | Adds Rust complexity without benefit for macOS-only tool |
| Web-only | Complementary | Cannot provide always-on presence or native notifications |
| CLI notifications | Phase 1 stopgap | Validates the concept before investing in SwiftUI |
| rumps/xbar (Python) | MVP option | Cannot achieve popover dashboard UX |
The case for SwiftUI: For an always-on menubar utility, resource efficiency matters more than cross-platform reach. SwiftUI provides native feel, minimal RAM footprint, proper notification integration, and launch-at-login support. The thin-client architecture keeps the Swift codebase small (most logic in FastAPI), making the maintenance burden proportional to the benefit.
The case against SwiftUI: Adds a separate language (Swift) and toolchain (Xcode) to a Python project. Requires Apple Developer Program membership. macOS-only. However, these are one-time setup costs, and the ongoing maintenance of a thin client is minimal.
Theme 6: Reference Implementations Validate the Pattern¶
Multiple Ollama menu bar apps demonstrate the exact architecture Picobot needs: SwiftUI MenuBarExtra with .window style popover, communicating with a local HTTP API, providing status monitoring and quick interactions. These are real-world, community-built applications proving that the pattern works.
Key architectural patterns from reference implementations:
- @StateObject for data model, @EnvironmentObject for view sharing
- @NSApplicationDelegateAdaptor for notification delegate integration
- LSUIElement = YES for agent app mode
- Custom NSImage for menu bar icons (with size normalization to 18pt height)
Recommendation for Picobot¶
Phased Implementation¶
Phase 0: Validate (1 day)
Add osascript notification calls to Picobot's daemon for sub-agent completions. Validate that notifications add value to the workflow.
Phase 1: SwiftUI Menubar App MVP (1 week)
- MenuBarExtra with .window style
- Dynamic SF Symbol icon reflecting daemon status
- Popover: daemon status, active agents, recent completions
- REST polling (2-second interval) to existing FastAPI endpoints
- Native notifications for sub-agent completion/failure
- Launch at login toggle
Phase 2: Real-Time Events (3-5 days)
- Add SSE endpoint to FastAPI server (/api/events)
- Replace REST polling with SSE in the Swift app
- Event-driven notifications instead of polling-based
Phase 3: Distribution (2-4 days) - Code signing + notarization pipeline (GitHub Actions) - Sparkle auto-update integration - Homebrew cask formula - DMG packaging
Architecture Diagram¶
+-------------------+ SSE events +------------------+
| Picobot Daemon | ------------------> | SwiftUI |
| (Python) | | MenuBarExtra |
| | <--- REST queries --| (.window style) |
| FastAPI Server | <--- REST commands -| |
| localhost:8000 | | UNNotification |
+-------------------+ +------------------+
|
macOS Notification
Center
Key Technical Decisions¶
- macOS 13+ minimum — Required for MenuBarExtra
- No App Sandbox — Hardened runtime only, avoids Sparkle XPC issues
- SSE + REST — SSE for events, REST for queries/commands
- Standard notifications — No rich content extensions
- SPM only — No CocoaPods/Carthage
- GitHub Releases — Host DMGs and Sparkle appcast
- LDSwiftEventSource or mattt/EventSource — SSE client library
Open Questions¶
-
Should the menubar app bundle the
picobot daemonprocess? Starting/stopping the daemon from the menubar (like macLlama starts Ollama) would improve UX but couples the Python and Swift codebases. -
What specific SSE event types should the FastAPI server emit? Needs design: agent_started, agent_completed, agent_failed, budget_warning, daemon_status_changed, etc.
-
Should the popover show a mini version of the web dashboard (HTML in WKWebView) or pure SwiftUI views? Pure SwiftUI is more native but requires reimplementing dashboard views. WKWebView could reuse HTMX templates but feels less native.
-
How should the app discover the FastAPI server? Hardcoded localhost:8000, configurable URL in settings, or Bonjour/mDNS service discovery?
-
Is the $99/year Apple Developer Program cost justified for this project's scope? Required for code signing and notarization. Without it, users must right-click > Open to bypass Gatekeeper on first launch.
-
Should the Xcode project live in the same git repository as Picobot or a separate one? Same repo simplifies versioning but mixes Python and Swift toolchains. Separate repo isolates concerns but complicates releases.