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

56 lines
2.6 KiB
Markdown

# 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**:
```yaml
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
```text
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).