develop
14
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
795c3bf5c9 |
feat(app): v0.1 UX surfaces — startup panels + InputMonitoring banner + PTT (#87, #88, #89, #156)
#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). |
||
|
|
79955b67b7 |
feat(round-4): non-dismissible banners for critical subsystem failures
Slice 2 of Round 4. With Slice 1 the probe layer now reports severity;
this slice routes severity to the banner system so the user CANNOT
silently dismiss a "your memory is dead, Jarvis will lie to you" warning.
- `BannerContent.nonDismissible: Bool` (default false).
- `HUDBanner` hides the dismiss X when `content.nonDismissible == true`.
- `HUDBannerCoordinator.dismissCurrent()` no-ops on non-dismissible
banners; the banner stays on screen until programmatically cleared
(`clear()` on app quit, or a re-probe finding the subsystem healthy
enqueues a different banner that pre-empts it).
- Two new presets:
* `bootHealthCritical(subsystem:reason:)` — priority 1, non-dismissible.
Title format: "Memory: critical failure". Body promises that the
agent will be warned not to rely on this subsystem (Slice 3).
* `bootHealthLoud(subsystem:reason:)` — priority 3, dismissible.
- Existing `bootHealthFailed(...)` preset kept as backwards-compat
fallback (no current call sites after this slice).
- `AppDelegate.enqueueFailedBootHealthBanners` rewritten to iterate
every non-ok subsystem and route by severity:
.critical → bootHealthCritical + `[system] CRITICAL` log line
.loud → bootHealthLoud
.soft → no banner (Status panel only)
Previously only `.failed` cases enqueued banners, which silently
dropped `.degraded` (the Toby case) entirely.
Two new tests in HUDBannerCoordinatorTests cover the new behavior:
* test_nonDismissibleBannerIgnoresDismiss
* test_criticalBannerPreemptsLowerPriorityCurrent
(Note: `xcodebuild test -only-testing JarvisAppTests` is pre-existing-
broken on this checkout due to onnxruntime static-link duplication —
confirmed by `git stash` before this slice. The test code compiles
cleanly via the regular App build; SPM-target tests for the
AgentCore-side severity model are green.)
Boundary gates 18/18 green. `swift test --package-path packages/AgentCore
--filter BootHealthOrchestratorTests` → 13/13.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
687b80b3f0 |
feat(round-2): boot-phase health probes — NOT FAKED, structured logs
After all install Tasks join, runs one live probe per subsystem and
emits structured `BootHealth: subsystem=X state=Y latencyMs=Z evidence="..."`
log lines. Any subsystem reporting .failed enqueues a non-modal banner
via HUDBannerCoordinator (priority 3 — sits between hotkey-bind-failed
and ollama-url-rejected). The snapshot is stored on bootHealthOrchestrator
so Round 3's Status menu can read + re-probe.
Anti-fake-status invariant is type-level: ProbeStatus is a sum type with
.ok / .degraded(reason:) / .failed(reason:) / .unknown(reason:) — every
non-ok case carries a reason string, so a snapshot can never report
state=unknown without explaining why. A probe that can't execute MUST
return .unknown — never .ok with a fake evidence line.
New types in packages/AgentCore/Sources/AgentCore/BootHealth.swift:
- ProbeStatus, ProbeOutcome, BootHealthProbe protocol
- SubsystemHealth (Codable for Round 3 "Copy report"), BootHealthSnapshot
- BootHealthOrchestrator actor — parallel TaskGroup fan-out,
registration-order-preserved snapshot, injectable clock for tests
Eight concrete probes in App/Boot/BootHealthProbes.swift:
- MemoryBootHealthProbe — MemoryStatsStoreAdapter.getMemoryStats round-trip
- AnthropicBootHealthProbe — Keychain structural check (no billable
network call at boot; Round 3 panel offers explicit network re-probe)
- OllamaBootHealthProbe — GET /api/tags, 3s timeout, asserts qwen3.6 +
qwen2.5-coder + nomic-embed-text all present
- VoiceBootHealthProbe — AudioGraphOwner state + mic TCC
- VisionBootHealthProbe — camera TCC + DiscoverySession (read-only,
does NOT prompt TCC per RESEARCH §Example 2)
- MCPBootHealthProbe — runtime.client.registeredToolNames count
- ReplayBootHealthProbe — replay.sqlite presence + parent-dir writability
- WebviewBootHealthProbe — WebviewBridge.handshakeState (.armed = live)
- DormantSubsystemProbe — placeholder for subsystems whose actor was nil
at probe-registration; reports .unknown honestly rather than skipping
AppDelegate wiring (step 16, runs after selfKnowledgeInstallTask which
transitively awaits every other install Task):
- bootHealthOrchestrator: BootHealthOrchestrator stored property
- bootHealthTask: Task<Void, Never>? cancelled in applicationWillTerminate
- runBootHealth() async: registers probes (idempotent — re-probes from
Round 3 reuse the existing registry), runs, logs, enqueues banners
- emitBootHealthLog / enqueueFailedBootHealthBanners helpers
Tests (packages/AgentCore/Tests/AgentCoreTests/BootHealthOrchestratorTests.swift):
- 8/8 passing (210ms wall — 3×200ms parallel probes prove TaskGroup
fan-out works; serial would be 600ms+)
- Coverage: runAll invokes every probe, snapshot preserves registration
order, parallel execution timing, lastSnapshot persistence, .failed
propagation, .unknown not coerced to .ok, deterministic clock
injection for latencyMs, JSON round-trip for all 4 ProbeStatus variants
Verification before commit:
- swift test --package-path packages/AgentCore --filter
BootHealthOrchestratorTests: 8/8 PASS (0.210s)
- swift test --package-path packages/Memory --filter
MemoryExtractorTests: 11/11 PASS
- bash scripts/check-app-builds.sh: PASS
- 18/18 boundary gates: PASS
Dogfooding next: cold-launch + grep system.log for `BootHealth:
subsystem=` lines; force a failure (e.g. kill Ollama) and verify
banner appears. Status menu (Round 3) is next session task.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
683be70ae6 |
fix(hud,menu): summon brings HUD to front + Settings menu opens wizard
Two surgical UX fixes before the broader audit: 1. JarvisHUDPanel.summon() used orderFrontRegardless() which doesn't activate the app or make the panel key. With another app frontmost (the common case for a global-hotkey summon), the panel ordered to front but stayed BEHIND the active app's windows. User saw 'hotkey dismisses but won't bring forward' — actually summon was firing, the window just wasn't visible above the active app's chrome. Fix: NSApp.activate(ignoringOtherApps: true) + makeKeyAndOrderFront(nil). Also tightened isSummoned to require BOTH .isVisible AND .isKeyWindow, so the toggle correctly distinguishes 'visible-but-occluded' (→ summon) from 'visible-and-frontmost' (→ dismiss). 2. Settings… menu item was hardcoded .isEnabled = false with 'Coming soon' tooltip. Now wired to the wizard (firstLaunch: false) so the user can change API key, re-grant permissions, or rebind the hotkey from one place — those are the canonical settings until a dedicated panel ships. |
||
|
|
dfc237db0c |
feat(07-06): installMemory + installVision + TS bus protocol 2.1.0 (Task 1)
- AppDelegate gains installMemory() and installVision() mirroring installVoice's six-step pattern. Memory installs BEFORE voice (memory ready before first turn); vision AFTER voice (camera lifecycle independent). - MemoryStore + MemoryExtractionOrchestrator + MemoryExtractionCoordinator constructed; AppDelegateMemoryReplaySink bridges Memory.MemoryReplaySink -> Replay.ReplayLog.record(_:for:). Coordinator wiring deferred until AgentOrchestrator lands in AppDelegate (documented in SUMMARY). - CameraCapture + PresenceMonitor instantiated; the SINGLE PresenceSignalBus reference (owned by PresenceMonitor) flows via AppDelegateContextBuilderAdapter to ContextBuilder.installPresence and via hudStateCoordinator?.attachPresence to the HUD coordinator. VISION-03 boundary preserved. - VisionRouter constructed with T1=Ollama, T2=Ollama (sidecar deferred), T3= Anthropic (key from Keychain); the runTurn dispatcher branch is deferred until AgentOrchestrator wires up. - DisablePresence menu-bar toggle wired alongside MuteWakeWord. - BannerContent.cameraDenied + cameraRevoked presets; cameraDegradationTask watches CameraCapture.degradationStream and enqueues banners. - HudStateCoordinator.attachPresence is a generic AsyncSequence subscriber preserving HUD-08 single-writer invariant (presence is record-only). - ContextBuilder.installPresence(_:) is a static no-op drain (deferred wiring for the future per-turn system-prompt enricher). - TS BUS_PROTOCOL_VERSION 2.0.0 -> 2.1.0; sessionHistory + TurnRow added to the BusOutbound discriminator union with full decode arm. Round-trip test bumped accordingly. scripts/check-bus-protocol-version.sh now passes. |
||
|
|
06312a7012 |
feat(06-05): VoiceController state machine + barge-in routing (VOICE-14)
- Add VoiceController actor: wakeWord→listening→thinking→speaking→idle FSM - Add VoiceState.swift: ListeningSource (.wakeWord | .ptt) + VoiceState enum - Add VoiceInterfaces.swift: VoiceOrchestratorInterface, VoiceTTSInterface, VoiceBannerInterface, BusOutboundEmitter, VoiceHudIntent (source of truth) - Add Control/AudioLevelEmitter: RingBuffer RMS at 30 Hz via BusOutboundEmitter - Add Control/PushToTalk: NSEvent.addGlobalMonitorForEvents (VOICE-13) - Add Control/MuteWakeWord: UserDefaults persistence, WakeWordDAG.pause/resume - Update App/HUD/HudStateIntent.swift: typealias VoiceHudIntent = Voice.VoiceHudIntent - Add VoiceControllerTests (V1-V4) + BargeInTests (B1-B4): all 8 pass - VOICE-14: single cancelAndSubmit call site in bargeIn() with 200ms debounce - T-06-05-03: transcript never passed to logger |
||
|
|
1c250fd771 |
fix(hud): layer-clear belt-and-braces + enable WebInspector in Debug
Diagnosing the white-HUD-on-summon: the WKWebView and its container view had `wantsLayer = true` on the container only, with no explicit layer.backgroundColor. macOS 26's WebKit may be backing the hosting layer with an opaque-white surface even when `setDrawsBackground:` is honored at the WebView level. Setting both layer backgrounds to `NSColor.clear.cgColor` is the documented belt-and-braces: - `webView.wantsLayer = true; webView.layer?.backgroundColor = .clear` - `container.layer?.backgroundColor = .clear` Also enables `webView.isInspectable = true` (macOS 13.3+) so right-click → "Inspect Element" works in Debug builds. This is the canonical macOS diagnostic for "WKWebView shows wrong color" — without it, "white square" can mean transparency-not-honored OR JS-bundle-not-mounted OR R3F-canvas-not-painting, and we can't tell which. The deployment target is macOS 13.0, so the `isInspectable` setter is behind a `#available(macOS 13.3, *)` availability guard. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
8ebdf76980 |
fix(hud): wrap text in wizard + banner instead of tail-truncation
Both the first-launch wizard's TCC stage and the HUD banner panel were
showing tail-truncated body text (Grant Permissions descriptions ending
in "...", Limited hotkey mode body cut at "The hotke..."). Two layered
SwiftUI layout bugs:
1. The body Text inside row HStacks (`VStack(title+body) + Spacer +
button`) didn't have `.fixedSize(horizontal: false, vertical: true)`,
so when the trailing button was wide ("Denied — Open System Settings")
SwiftUI's HStack layout compressed Text and chose tail-truncation
over multi-line wrap.
2. The wizard's outer frame was `560×440`, leaving only ~318h for
stageBody after header(64) + dividers(2) + footer(56). The TCC stage
with 4 permission rows needed ≥520h. The vertical squeeze cascaded
into more aggressive Text compression and clipped the bottom Back row.
Fixes:
- WizardView: bump frame to 600×640.
- WizardStageTCCView: `.fixedSize(horizontal: false, vertical: true)` on
description + each row's body. `.frame(maxWidth: .infinity, alignment:
.leading)` on inner row VStacks so they receive the available width
before the trailing button claims it. Spacer minLength bumped to 16
for breathing room between body text and button.
- HUDBanner: same `.fixedSize` + `.frame(maxWidth:)` pair on the title
+body VStack. The 360pt-wide banner needed it most because
`HUDBannerPanel.positionTopTrailing` re-reads the SwiftUI fittingSize
on host — without wrapping, the panel was sized to a single-line body
and forced all subsequent body text to truncate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
cabc27299a |
feat(03-05): AppDelegate wires HudStateCoordinator + bridge; lint activated; W1-W6 + B1-B3 tests
- App/HUD/HudStateBridge.swift (new): @MainActor public func busHudState(
from: HudState) -> Bus.HudState. RawValue round-trip with assertionFailure
on drift, .idle fallback in Release. Option (b) from the plan — avoids
name-collision between App.HudState and Bus.HudState via module-qualified
Bus.HudState in the helper's scope.
- App/AppDelegate.swift:
* New stored properties: hudStateCoordinator, dormantAgentContinuation,
dormantVoiceContinuation, dormantConfirmContinuation (all internal so
@testable import Jarvis can assert W5 retention), webviewEntryFilename
(internal test seam for W6).
* installBus(): constructs HudStateCoordinator with emit closure that
bridges App.HudState -> Bus.HudState via busHudState() and dispatches
to bridge.send(.hudState(...)) on the MainActor. Creates three dormant
AsyncStream producers (Phase 4/5/6 replace them) and retains their
continuations on self so coordinator.{agent,voice,confirm}Task stay
live. markReady() fires from onHandshakeArmed so the HUD promotes
.booting -> .idle as soon as the webview is responsive.
* Load target: bus-harness.html -> index.html (via
webviewEntryFilename property so W6 can assert post-install value).
XCTest-host short-circuit from 02-03 deviation #5 preserved, updated
to reference the property-driven filename.
- scripts/check-single-writer-hudstate.sh: allowlist extended to
{HudStateCoordinator.swift, HudStateBridge.swift, AppDelegate.swift} +
{BusOutbound.swift, Protocol.swift}. Rationale comment updated: Phase 4+
drives the coordinator via its three input AsyncStreams, not new
.hudState(...) call sites. Already wired as a preBuildScript by Task 1.
- App/Tests/AppTests/HudStateCoordinatorBusWiringTests.swift (new):
* W1 test_coordinatorConstructedInInstallBus — delegate.hudStateCoordinator
non-nil after applicationWillFinishLaunching.
* W2 test_emitClosureBridgesToBusSend — FakeJSEvaluator observes the
closure producing a decoded BusOutbound.hudState(.thinking) after
coordinator.markReady() + agent.yield(.thinking). Proves the exact
closure shape AppDelegate installs routes rawValues end-to-end.
* W3 test_markReadyCalledOnHandshakeArmed — coordinator pinned at .booting
pre-handshake, promotes to .idle after bridge.handleHelloAck.
* W4 test_appHudStateRawValueMatchesBusHudState — table-driven bidirectional
rawValue parity over HudState.allCases and Bus.HudState.allCases. Catches
drift before busHudState()'s assertion fires at runtime.
* W5 test_dormantStreamsHeldByDelegate — all three continuations non-nil.
* W6 test_installBusLoadsIndexNotHarness — webviewEntryFilename == "index"
post-installBus.
- App/Tests/AppTests/WebviewBundleLoadTests.swift (new):
* B1 test_indexHtmlInRepoWebviewDir — structural (#filePath traversal to
repo root), catches missing committed index.html.
* B3 test_indexHtmlReferencesRelativeAssets — content-grep for "./assets/"
present + "src=\"/assets/" absent. Catches Vite misconfig or absolute-
path drift.
* B-extra test_assetsDirectoryGitignored_documentedInSmokeScript — doc-only,
asserts the split of responsibility between XCTest (committed index.html)
and scripts/smoke-test-hud.sh (built-app assets/*.js).
Tests are repo-structural / evaluator-observation only; xcodebuild test
launch still blocked on Xcode 26 RunningBoard error 5 per 02-03 precedent.
Verification is xcodebuild build (succeeds) + scripts/smoke-test-hud.sh
(next task). This follows the 02-03 SUMMARY deviation #5 pattern.
xcodebuild build -configuration Debug: BUILD SUCCEEDED.
bash scripts/check-single-writer-hudstate.sh: PASS (allowlist active).
packages/Bus swift test: 47/47 green.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
79092263bb |
feat(03-01): HudStateCoordinator single-writer + intents + lint
Implements HUD-08 precedence ladder as the sole Swift-side writer of HudState. Three producer streams (agent, voice, confirmation) feed a MainActor-isolated resolver; one emit closure is the only path out. - HudStateIntent.swift: AgentHudIntent/VoiceHudIntent/ConfirmHudIntent Sendable enums. SystemHudIntent intentionally omitted — markReady() is a one-shot direct call, not a stream. - HudStateCoordinator.swift: @MainActor final class. Ladder per HUD-08 verbatim: awaitingConfirmation > speaking > listening > thinking > idle > booting > reconfiguring. Ready-gate tweak on the .booting step (RESEARCH Open Q #4 pre-authorized) so boot-time silent inputs don't prematurely resolve to .idle. - Bus-agnostic by design: takes @escaping @MainActor (HudState) -> Void emit closure so Plan 03-05 can wire it to WebviewBridge without this plan importing Bus or WebKit. - Weak-self capture + deinit cancel on three for-await loops prevents the T-03-03 leak. - HudStateCoordinatorTests.swift: 19 methods covering every precedence pair, booting gate, idempotent emit, cleared-confirm restore, weak-self no-leak, interleaved producers. - scripts/check-single-writer-hudstate.sh: executable; exits 0 on current codebase; exits 1 on planted .hudState() construction outside the allowlist. Pre-build wiring deferred to Plan 03-05. |
||
|
|
20e8647af4 |
feat(02-03): wire bus into AppDelegate + expose HUD webView + bundle harness
- JarvisHUDPanel.webView: private → public. Surgical one-line change per RESEARCH open question #4 — keeps the dep graph App → Bus, not inverted. - AppDelegate.installBus(): new launch-chain step 4.5 (after installHUDPanel / installBannerPanel). Constructs WebviewBridge around panel.webView, wires alertPresenter → TCCAlertService.presentHardBlock + terminate, wires onHandshakeArmed → onBusArmed test hook, installs BridgeNavigationDelegate so WKNavigationDelegate.webView(_:didFinish:) triggers startHandshake, and loads bus-harness.html via Bundle.main.url(forResource:withExtension:subdirectory:). Missing harness is a hard-block (terminates) in production; silently skips under XCTest so unit tests assert bridge construction. - AppDelegate.toggleHUD(): gates on handshakeState == .armed. Unarmed path enqueues BannerContent(id: "hud-not-ready", priority: 2) rather than popping an empty window. - AppDelegate.onHandshakeMismatch + onBusArmed: @MainActor injectable closures for test hooks. Production default calls NSApp.terminate(nil) after the TCCAlertService modal returns. - App/Resources/webview/bus-harness.html: bundle-vendored harness loaded from Jarvis.app/Contents/Resources/webview/bus-harness.html. Plan 04 adds the parity check that keeps this in sync with webview/bus-harness.html. - project.yml: adds Bus package dep, adds App/Resources/webview as a blue-folder reference (type: folder under sources:) so the subdirectory survives into the bundle. Excludes App/Resources/** from the flat source path to avoid double-including. xcodegen regenerates clean. - AppDelegateBusWiringTests: 6 new test cases covering installBus construction, toggleHUD armed/unarmed gating, onHandshakeArmed hook firing, mismatch terminate hook, and Bundle.main harness lookup. Verified: - xcodegen generate + xcodebuild -scheme Jarvis build: BUILD SUCCEEDED. - packages/Bus SPM tests: 47 passed / 0 failed. - bus-harness.html + Bus_Bus.bundle/Injection.js both ship in the .app. - grep -rn '.evaluateJavaScript(' App/ packages/Bus/Sources/: 0 matches. Note: xcodebuild test -only-testing:JarvisAppTests fails at test-host launch with dyld 'different Team IDs' on Xcode 26 + macOS 26 Debug — this is a pre-existing repo-level issue documented in .planning/debug/xctest-launch-runningboard-error-5.md and tracked in 01-HUMAN-UAT.md. Out of scope for Plan 02-03; SPM-level coverage is complete via BatcherTests + WebviewBridgeOutboundTests. |
||
|
|
9d50e1cb56 |
fix(01-WR-10): guard WKWebView setDrawsBackground KVC with responds(to:)
Phase 1 REVIEW.md WR-10: `JarvisHUDPanel.init` reached into WKWebView's
private ObjC API via `setValue(false, forKey: "drawsBackground")` to
force a transparent backing (UI-SPEC Surface 7 line 562). If a future
WebKit build renames the setter, `NSUndefinedKeyException` propagates
out of the panel initializer and the app crashes at launch — instead
of degrading to an opaque HUD.
App/HUD/JarvisHUDPanel.swift
- Probe `responds(to: NSSelectorFromString("setDrawsBackground:"))`
before calling setValue. If the selector is missing, skip the KVC
call; the HUD renders with an opaque backing (visible regression)
rather than crashing at launch.
|
||
|
|
9fc053511c |
fix(01-WR-05): cancellable 300ms banner drain, close race windows
Phase 1 REVIEW.md WR-05: the 300ms drain after `dismissCurrent()` was
a fire-and-forget `DispatchQueue.main.asyncAfter`. Two races fell out
of that:
1. `clear()` during the drain window — pending drain still fires,
banner pops up while app is quitting.
2. Higher-priority banner enqueued during the drain — `current` is
nil for 300ms, so `enqueue` calls `showBanner` immediately, then
the drained banner fires afterwards and clobbers it.
App/HUD/HUDBannerCoordinator.swift
- Add `private var drainWorkItem: DispatchWorkItem?` — cancellable
handle on the drain.
- `dismissCurrent()`: cancel any prior drain before scheduling a new
one (belt + suspenders — prevents two drains from racing).
- `enqueue()`: when `current == nil`, cancel any pending drain before
calling `showBanner`. Prevents the clobber race.
- `clear()`: cancel the pending drain. Prevents pop-up-at-quit.
- Drain closure re-checks `current != nil` at fire time for defense
in depth.
App/Tests/AppTests/HUDBannerCoordinatorTests.swift
- `test_clearCancelsPendingDrain` — enqueue two, dismiss, clear,
wait 500ms, assert currentBanner is nil.
- `test_enqueueDuringDrainCancelsDrain` — enqueue/enqueue/dismiss/
enqueue-higher-priority, assert higher-priority wins immediately
AND survives past the 300ms drain window.
|
||
|
|
122153d544 |
feat(01-03): add JarvisHUDPanel, HUDBannerPanel+Coordinator, AppDelegate entitlement hard-block
- JarvisHUDPanel: borderless 720×720 NSPanel, .statusBar level, .canJoinAllSpaces/.fullScreenAuxiliary/.transient collectionBehavior, transparent WKWebView (drawsBackground=false via KVO), cancelOperation(_:) + performClose(_:) overrides for Escape/⌘W dismiss, Reduce Transparency fallback to windowBackgroundColor@95%, fresh-center on summon via NSScreen.screenWithMouse() - HUDBannerPanel: .floating non-activating 360pt banner panel, canBecomeKey/canBecomeMain both false (never steals focus), top-trailing positioning 24pt/8pt inset - HUDBannerCoordinator (@MainActor): priority queue with pre-emption (lower priority int = higher precedence), dismissed-this-launch dedup by id, 300ms gap to next banner per UI-SPEC Surface 5 - BannerContent with four presets: keychain-empty (1) > input-monitoring-denied (2) > hotkey-bind-failed (3) > ollama-url-rejected (4); Sendable/Equatable/Identifiable - HUDBanner (SwiftUI): 4pt arc-reactor-glow leading stripe + title/body/action/dismiss-× layout on .hudWindow visual-effect background with separatorColor border - AppDelegate: EntitlementGateProbe protocol + InfoPlistEntitlementGateProbe impl + injectable onEntitlementFailure (@MainActor) defaulting to presentEntitlementFailureAlert + NSApp.terminate, skipped under XCTest host; bootstrap chain installs menu-bar + HUD panel + banner panel (Plan 04 prepends logging and appends config/keychain/hotkey) - 9 new tests in JarvisAppTests: 3 HUD panel (style flags, summon/dismiss, Escape), 4 banner coordinator (enqueue, priority pre-emption, dismiss-drain, dismissed-this-launch dedup), 2 AppDelegate (hard-block triggers / does-not-trigger with mocked probe) |