The push-to-talk path was fully implemented but completely unwired:
`VoiceController.pttDown()` / `pttUp()` and `PushToTalk.bind(...)` all
existed as dead code because no production caller ever bound a hotkey.
Three changes plug the gap end-to-end:
- `WizardState.pttHotkey` — persisted PTT shortcut under a separate
UserDefaults key (`Jarvis.pttHotkey`) from the summon-Jarvis hotkey.
Ships unbound (nil): CLAUDE.md hotkey-hygiene forbids pre-binding
because Cmd+Shift+J and Option+Space collide with too many tools.
- `AppDelegate.rebindPTTFromState` — pulls the persisted shortcut and
`inputMonitoringGranted` flag, then calls `PushToTalk.bind(...)`.
Called from three sites: installVoice tail (after `pushToTalk` is
constructed), the foreground TCC re-grant path (so a global monitor
is acquired under fresh TCC), and the Settings Save/Clear handlers.
- `SettingsView.pttHotkeySection` — same Save/Clear pattern as the
summon hotkey, fed by a separate `onPTTHotkeyChanged` callback to
avoid coupling the two paths.
Audio-graph isolation: PTT.bind installs `NSEvent.addGlobalMonitorFor
Events` callbacks only; the press path routes through
`VoiceController.pttDown`, which uses the audio graph the wake-word DAG
already owns. So this lane is safe to land in parallel with the Voice
CRIT branch (`fix/voice-crit-v0.1`) — no audio-graph surgery.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
SHELL-06 v1 requirement: surface a HUD banner when global key monitoring
silently no-ops on TCC denial, and clear it when the user grants access.
Settings already exposed the state; the HUD path was missing.
Three changes:
- `BannerContent.inputMonitoringDenied` is now `nonDismissible: true`.
The user must not be able to click past a denial that breaks global
hotkeys — the silent-no-op failure mode is exactly what this banner
exists to prevent.
- `HUDBannerCoordinator.dismiss(id:)` clears a banner by id, bypassing
the `nonDismissible` flag, without poisoning `dismissedThisLaunch`.
The condition can recur (user revokes again), so we need to be able
to re-enqueue after a programmatic clear.
- `AppDelegate.reprobeInputMonitoring` runs on
`NSApplication.didBecomeActiveNotification`. Was-denied → now-granted
dismisses the banner and rebinds the global hotkey under fresh TCC
(the original bind silently fell back to local-only). Was-granted →
now-denied re-enqueues the banner.
`IOHIDCheckAccess` reads the cached TCC decision without prompting, so
this is safe to call on every foreground hop.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
When Jarvis launches in Debug builds, the HUD, Status panel, and DevOverlay
all now open visible by default — eliminating the three-menu-click discovery
gap to the operator's primary observability surface.
Behavior controlled by `AppDelegate.startupSurfacesMode`:
- `.diagnostic` (Debug default) — HUD + Status + DevOverlay all visible.
- `.minimal` (Release default) — HUD only.
Positioning: HUD stays centered; Status panel goes top-right, DevOverlay
goes bottom-right of `NSScreen.main.visibleFrame`. DevOverlay opens to the
Logs tab (index 3) so the operator sees activity stream immediately
instead of a blank Agent tab.
Skip conditions: wizard open (don't stack panels behind the wizard) and
XCTest hosts (don't pop panels during unit-test runs).
Plumbing:
- `DevOverlayView.init` gains `initialTab: Int = 0` parameter; selection
becomes `@State` driven so the first tab can be picked at construction.
- `DevOverlayWindow.init` forwards `initialTab` and exposes
`setFrameOrigin(_:)` + `frameSize` so AppDelegate can position the
panel without KVC-poking at the private `panel` field.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>