#89: new AppDelegate.startupSurfacesMode enum (.minimal/.diagnostic). Debug defaults .diagnostic; Release defaults .minimal. openStartupSurfacesIfDiagnostic() runs at tail of bootHealthTask (skipped under XCTest or when wizard is open). Status panel top-right + Dev Overlay bottom-right of NSScreen.main.visibleFrame. DevOverlay opens to Logs tab.
#88: BannerContent.inputMonitoringDenied is now nonDismissible: true. New HUDBannerCoordinator.dismiss(id:) clears a banner programmatically without writing dismissedThisLaunch. AppDelegate.reprobeInputMonitoring fires on NSApplication.didBecomeActiveNotification; rebinds the global hotkey on re-grant. Uses IOHIDCheckAccess (query-only, no prompt) per CLAUDE.md guidance. 3 new coordinator tests.
#87: WizardState.pttHotkey persisted under Jarvis.pttHotkey (independent of summon hotkey). Ships unbound per CLAUDE.md hotkey hygiene. AppDelegate.rebindPTTFromState() called from installVoice, foreground TCC re-grant, Settings Save/Clear. SettingsView extended with PTT hotkey section. Audio-graph isolation: NSEvent monitors only, no audio-graph surgery — safe alongside fix/voice-crit-v0.1. 3 new WizardState tests.
DevOverlay 14 + Voice 26 tests pass; all 17 grep boundary gates green (incl. single-writer-hudstate + no-modal-presentation).
The wizard had two persistence holes that made the user's quit-and-reopen
workflow surface the same stage(s) again:
1. Hotkey was in-memory only. WizardState.hotkey was @Published but
never written to disk. Quit and reopen → state defaults to nil →
firstUnresolvedStage returns .hotkey → wizard re-asks even though
the user already bound one a moment ago.
2. Input Monitoring grant detection used the in-memory
inputMonitoringProbed flag (also session-scoped), not the actual
system grant state. Quit and reopen after granting → flag resets to
false → wizard returns to TCC stage even though TCC remembers the
grant.
Fixes:
- WizardState.refresh() now reads from system sources of truth on every
invocation:
apiKeyStored ← Keychain (already correct)
inputMonitoringGranted ← IOHIDCheckAccess (new — query-only, no prompt)
hotkey ← UserDefaults 'Jarvis.hotkey' (JSON-encoded)
- firstUnresolvedStage gates TCC on !inputMonitoringGranted (the actual
grant) instead of !inputMonitoringProbed (the session UI flag).
- WizardState.hotkey has a didSet that persists to UserDefaults; the
Stage 3 'Save Hotkey' button assigning to state.hotkey now hits disk
too. Set to nil → key removed.
- HIDAccessProbe protocol gains isListenEventAccessGranted(), backed by
IOHIDCheckAccess in production. Tests can inject deterministic grant
state without TCC side effects.
Verified: check-app-builds clean. After scripts/dev-reset.sh --yes,
launching → wizard at apiKey. Entering key + granting Input Monitoring
+ binding hotkey + Quit + relaunch (fresh process) should land past
all three stages because each is recomputed from system state.