midi-harbor/specs/014-mac-app-store-mode/data-model.md
2026-09-28 13:59:10 -05:00

2.6 KiB

Data model: Mac App Store mode

The daemon's configuration, the contract and the domain types are unchanged. Two small things are new, both held by the app rather than the daemon.

Window state at quit

Whether the window was open when Midi Harbor last quit entirely (FR-A10).

  • Where: window.yaml in the configuration directory, which the sandbox puts in the container: ~/Library/Containers/com.mrgeckosmedia.MidiHarbor/Data/Library/Application Support/midi-harbor/window.yaml. A separate file, because the daemon owns config.yaml and rewrites it from what it holds.

  • Shape:

    open: true
    
  • Written: when Midi Harbor quits entirely, atomically as the configuration is (temporary file, flush, rename). Hiding the window writes nothing; only the state at quit matters.

  • Read: at launch. A missing or unreadable file reads as open, so a first launch at login, or one after a crash, shows the window rather than hiding the app.

  • Used: only for a launch at login. Launched any other way, the window opens.

Login item

The app's registration to start at login. macOS owns it; the app never stores it.

SMAppService.Status Switch shows Turning it on
enabled on
requiresApproval waiting, with where to approve it
notRegistered off register()
notFound off register(), which decides (R-099)

A register() or unregister() that fails shows its error beside the switch, and the status is read again. The first launch in App Store mode offers it once; whether it has been offered is kept in window.yaml as login_offered: true.

Daemon lifecycle, as the app sees it

app starts ──► socket answers? ──yes──► attached (did not start it)
                     │no
                     ▼
               start helper ──► running ◄──┐
                     │ exits with failure  │ after the supervisor's delay
                     └─────────────────────┘
running ──quit entirely──► SIGTERM, wait for exit ──► app exits
attached ──quit entirely──► StopDaemon, wait for the socket to close ──► app exits
running or attached ──window crashes──► daemon keeps running; next launch attaches

An attached daemon is one the app did not start, left by a window that crashed. The app stops it on Quit all the same, since in App Store mode nothing else would. The sandbox forbids signalling a process the app did not start, so it asks through the contract's StopDaemon (R-095).