#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).
Slice 3 of dev-overlay-end-to-end. Adds a fourth DevOverlay tab that
tails every log record from every Jarvis subsystem in real time —
agent, voice, vision, memory, MCP, bus, replay, system — without
polling files or relying on Console.app.
How it works:
1. `LogBroadcaster` (new) — process-wide actor with a 500-line ring
buffer + subscriber set. Borrowed shape from
OrchestratorEventBroadcaster so the project's three broadcasters
share a mental model. New subscribers replay the buffer in order,
then go live.
2. `BroadcastLogHandler` (new) — swift-log LogHandler conformance.
Formats each log call into a `LogLine` and schedules an unstructured
`Task { await LogBroadcaster.shared.publish(...) }` so the logging
caller never blocks on actor isolation. Synchronous handler contract
preserved; ordering is best-effort under heavy load.
3. `LoggingBootstrap.make(label:)` now returns
`MultiplexLogHandler([file, os, broadcast])` — the new handler is
purely additive. File rotation + os.Logger streaming unchanged.
4. `DevOverlayLogsView` (new) — fourth tab in the overlay. Subscribes
to `LogBroadcaster.shared` on appear, caps at 1000 lines, renders
monospaced. Per-channel filter (Menu w/ checkboxes), per-level
filter (debug → critical), substring search, Pause/Resume button
(Cmd+P), auto-scroll-to-bottom while not paused. Color-coded by
level (warn=orange, error/critical=red).
Tests:
- 5 new LogBroadcasterTests covering publish/cap/replay/live/handler.
- swift test --package-path packages/Logging → 22/22 pass.
- swift test --package-path packages/DevOverlay → 14/14 pass.
- bash scripts/check-app-builds.sh → PASS.
- 18/18 boundary gates green.
Redaction note: file handler still routes through Redact.apply for
disk writes; the broadcaster sees raw messages because the DevOverlay
is a developer-only in-process surface. If this ever becomes
user-facing, route through Redact before publishing.
The DevOverlay panel has been silently rendering DevSnapshot.initial since
Phase 4 because three independent breaks left the data path open-circuit
(see .planning/audit-2026-05-12/devoverlay-diagnosis.md for the full trace).
Slice 2 closes all three:
1. AppDelegate constructs `DevSnapshotEmitter` in `installAgent` and
stores it on the delegate. Provider/model is set from configStore
so the header row shows the active provider rather than a blank.
2. The `.devOverlay` broadcaster subscriber that previously drained to
`/dev/null` now calls `emitter.apply(event)` for every event. This
produces real DevSnapshots that flow through the bounded output
channel (capacity 32, .dropOldest — AGENT-10 observational seam).
3. `DevOverlayWindow` now accepts the live emitter + BootHealthOrchestrator
+ a HudState reader closure. It instantiates its own DevOverlayBridge
and attaches the bridge to `emitter.output` so viewModel.apply is
actually called as snapshots arrive.
Also reshapes the SwiftUI surface as a TabView with three observational
panes (Agent, Tools, Health). The Health pane is read-only — the Status…
menu remains the place to trigger re-probes. The Logs pane lands in
Slice 3 (live log stream).
ViewModel extends with `bootHealth` + `hudStateLabel` slices, each with
its own single-writer mutator and `private(set)` enforcement.
Verification:
- swift test --package-path packages/DevOverlay → 14/14 pass
- swift test --package-path packages/AgentCore --filter DevSnapshotEmitterTests → 9/9 pass
- bash scripts/check-app-builds.sh → PASS
- 18/18 boundary gates green
Adds README.md to each of the 13 SPM packages (AgentCore, Bus, Config,
DevOverlay, Harness, Keychain, Logging, MCP, Memory, Replay, Shell, Vision,
Voice). Each covers purpose, key public types, deps, consumers, invariants
(pulled from CLAUDE.md + grep-gates + audit findings), test stack, and
notable files. Memory README explicitly marks the package functionally OFF
until vec0.dylib + Ollama models land.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`public var snapshot` allowed any caller to write directly, defeating
the AGENT-10 invariant "Single write path: the DevSnapshot channel
subscriber Task." `apply(_:)` was a convention-only chokepoint with no
type teeth.
Change to `public private(set) var snapshot` so reads stay open for the
SwiftUI view but writes route exclusively through `apply(_:)` and
`reset()`. A future plan that wires a menu-bar "force refresh" command
can no longer reach for the var directly and silently break the
single-write-path invariant.
Existing tests (VM1–VM4) still pass — they all already used `apply(_:)`.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
`@State` is for view-owned, value-typed local state. Wrapping an
injected `@Observable` reference type with `State(wrappedValue:)`
captures the initial instance and ignores subsequent inits — if a
parent view ever passes a different `DevOverlayViewModel`, the view
keeps observing the old one. Today the construction path goes through
`DevOverlayWindow` (single instance per app), so the bug was dormant;
any future use that re-instantiates the view would silently desync.
Replace `@State public var viewModel` with `public let viewModel`.
The Observation framework triggers re-renders on the view's read of
`viewModel.snapshot.*` automatically — no wrapper needed.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>