dependabot/github_actions/github/codeql-action-4
28
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). |
||
|
|
bc74494ada |
fix(mcp): v0.1 CRIT cluster — turnID resolver wired + tool-result double-cap (#20, #21, #157)
#21 (CRIT): ToolResultPacker.pack(_:) is now a pass-through decoder; redundant inner 8 KB cap removed. MCP dispatcher's SanitizeForModel.prepareForBoundary(_, capBytes: 8192) is the sole cap site. Doc-comment rewritten honestly: replay row reflects boundary-prepared bytes (8 KB), not original helper output. Replay fidelity beyond 8 KB lifted to v0.5/M-3 once SanitizeForModel can move to a shared module without dep cycle. #20 (CRIT): ReplayingToolResultObserver.turnIDResolver lock-protected via OSAllocatedUnfairLock<ResolverBox> (same Swift-6-async-safe pattern as InProcessConfirmationCache). New attach(turnIDResolver:) method. AgentOrchestrator.currentTurnID() actor-isolated accessor. installAgent attaches the resolver immediately after orchestrator assignment, with defensive systemLogger.warning if observer type mismatches. Tests: ToolResultPackerTests TP2/TP5 updated to assert no [TRUNCATED] marker; OrchestratorSubmitTests OS6 reframed + new OS6b reads SQLite events table directly to assert 8 KB exact; MCPRuntimeWiringTests adds attach pre/post-attach + replay-log dual-write correlation. AgentCore 243 + Memory 98 + Replay 34 + MCP all green; all 18 boundary gates green. |
||
|
|
4b1dd6404b |
fix(voice): v0.1 CRIT cluster — wake / STT / TTS / boot probes (#28, #29, #30, #31, #32, #33, #34, #36, #158)
#28 (CRIT): setCancelInFlight calls stopFeed() (preserves stream); rebuild consumer re-arms DAG against new ring. Wake-word no longer dies on audio-graph rebuild. #29 (CRIT): sttFactory reads PerTurnSnapshot.stt.whisperKitFallback; get_self_state returns canonical snake_case backend constants. Factory signature: () -> any STTProvider → () async -> any STTProvider (existing test sites compile unchanged under Swift 6 implicit-async-closure rules). #30 (CRIT): TTSEngineActor.synthesize emits .ttsStopped after .finished on natural completion (two-emit-site model: barge-in + natural-completion). #31 (CRIT): VoiceTTSAdapter.tierResolver wired to read snap.tts.tier from configStore. #32 (HIGH): Rolling-window trim in OpenWakeWordSession.runORT — mel ≤ 84, embeddings ≤ 16, nextEmbStart slides on drop. #33 (HIGH): three new boot probes — WakeWordBootHealthProbe, STTBootHealthProbe, TTSBootHealthProbe. New WakeWordDAG.isFeedArmed + VoiceTTSAdapter.hasEngine accessors. #34 (HIGH): vad.reset() at top of startSTTSession clears Silero VAD LSTM hidden state + wasSpeech between sessions. #36 (HIGH): new Broadcaster.Priority.agentHud (protects stateChange/turnEnd/error); installAgent adds subscriber pumping AgentHudIntent into dormantAgentContinuation. Voice 90 + AgentCore 242 tests pass; all 18 boundary gates green. |
||
|
|
a6f91e6b73 |
fix(bus,hud): v0.1 S1 cluster — webview didFail + menu-bar icon transition (#40, #41, #151)
#40: BridgeNavigationDelegate gains onDidFail closure (covers didFailProvisionalNavigation + didFail). WebviewBridge.failHandshake(reason:) transitions to new terminal .loadFailed state, cancels pending 2s timeout, fires existing alertPresenter, idempotent. AppDelegate.handleWebviewLoadFailure dual-surfaces: failHandshake + critical non-dismissible banner (defense-in-depth across test seams). WebviewBootHealthProbe gets .loadFailed case. #41: HudStateCoordinator emit closure now ALSO calls menuBarController.transition(to:) in addition to bridge.send(.hudState(...)). Doc-comment correctly notes this is not a HUD-08 single-writer violation — transition writes currentState, not BusOutbound.hudState. scripts/check-single-writer-hudstate.sh still green. Tests: 4 new Handshake cases (load-failure-from-sentHello with alert-not-duplicated, from-idle, idempotency, post-armed no-op), BridgeNavigationFailureTests end-to-end + per-arm, MenuBarIconStateTransitionTests drives dormantAgentContinuation and asserts .thinking → .speaking → .idle. 62/62 Bus pass; all 18 boundary gates green. |
||
|
|
a9531e65e3 |
feat(round-4): menu-bar icon turns red when subsystems are degraded
Slice 4 of Round 4 — the visible polish. With Slices 1–3 the agent
preamble + non-dismissible banner cover the "agent must know +
user must see" loop, but a closed HUD with no banner visible can
still hide the warning. This slice paints the menu-bar icon red so
the warning is glanceable from anywhere on the screen.
Option A from the slice plan (single-writer-safe):
- `MenuBarIconController.applyHealth(_ health: OverallHealth)` paints
`button.contentTintColor = NSColor.systemRed` for `.loud` and
`.critical`, clears the tint for `.ok` and `.soft`. Template images
recolor automatically with `contentTintColor` so no second icon
asset is needed.
- `currentHealth: OverallHealth?` exposed read-only for tests.
- `AppDelegate.runBootHealth` calls `menuBarController?.applyHealth(
snapshot.overallHealth)` after computing the degradation summary,
so the icon updates on every probe sweep (cold boot + Status panel
re-probe).
Single-writer invariant for HudState (HUD-08) is intact:
`contentTintColor` lives on `NSStatusItemButton`, not in HudState.
`scripts/check-single-writer-hudstate.sh` is green.
1 new test in MenuBarIconControllerTests:
* test_applyHealthTintsRedForLoudAndCritical — exercises all four
OverallHealth cases and asserts contentTintColor toggles correctly.
Boundary gates 18/18 green; App target compiles cleanly.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
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>
|
||
|
|
f9cd25d66e |
chore(test-naming): converge on Mock/Fake/Stub convention; document in CLAUDE.md; rename worst-mismatch stragglers
Per audit-2026-05-04 P2-15: 26 Mock*, 21 Fake*, 12 Stub* across the test corpus, often used interchangeably for the same role. New CLAUDE.md section "Test naming conventions" (line 127) sets the rule: - Mock*: records calls AND returns scripted responses (assertion driver). - Stub*: returns canned data without recording (deterministic input). - Fake*: alternative implementation with realistic behavior (in-memory state-holder). Renamed 15 worst-mismatch stragglers in the older corpus (per audit constraint, did NOT touch types added in today's P1+P2 fix commits): Stub* → Mock* (records + scripts): - HybridSearchTests: StubEmbedder → MockEmbedder - HybridSearchTests: StubStore → MockStore - SessionHistoryTests: StubStore → MockStore - InProcessMemoryToolsTests: StubHybrid → MockHybrid - InProcessMemoryToolsTests: StubHistory → MockHistory - InProcessMemoryToolsTests: StubForget → MockForget Mock* → Stub* (canned data, no recording): - STTBackendSwitchTests: MockSpeechAnalyzerBridge → StubSpeechAnalyzerBridge - STTBackendSwitchTests: MockWhisperKitBridge → StubWhisperKitBridge - VoiceWiringTests: MockVADEngineForW4 → StubVADEngineForW4 - VoiceWiringTests: MockSTTProviderForW4 → StubSTTProviderForW4 - VoiceWiringTests: MockTTSForW4 → StubTTSForW4 - VoiceWiringTests: MockOrchestratorForW4 → StubOrchestratorForW4 - VoiceWiringTests: MockBannerForW4 → StubBannerForW4 - VoiceWiringTests: MockBusForW4 → StubBusForW4 Fake* → Mock* (records + scripts): - WebviewBridgeOutboundTests: FakeJSEvaluator → MockJSEvaluator - HudStateCoordinatorBusWiringTests: FakeJSEvaluator → MockJSEvaluator (the comment on the type already said "Records each call for assertion") Skipped per audit constraint: - StubCapture/StubSink (Vision FrameAttachConfirmSendWiringTests, P1+P2) - MockVADEngine + MockOrchestratorForController siblings (Voice VoiceControllerTests/VADGatedSessionTests, today's track-B churn) Tests pass: Voice 87+23, MCP 85, Memory 88, Bus 58, AgentCore 19+72, Vision 19, Shell 7+5. App target builds clean. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
81357ca900 |
test(voice p1-2): production chunkPump factory extracted + integration test against real broadcaster
Per audit 2026-05-04 T2: the production chunkPump closure at AppDelegate
.installVoice was tested only end-to-end via synthetic pumps — the real
subscribe + readMono16k loop had zero coverage. Same divergence pattern
that produced BLOCKER-INT-1 (NoopBusGateway).
Extract the closure into App/Voice/ProductionChunkPump.swift as
makeProductionChunkPump(graphOwner:); AppDelegate becomes a one-line
caller. New ProductionChunkPumpTests covers three integration paths:
- PCT1: pump finishes immediately when graphOwner is nil
- PCT2: pump receives buffers via the broadcaster fan-out (publishes
through a tap-block-capturing GraphBuilder against a real
AudioGraphOwner)
- PCT3: pump's subscription does not steal samples from a sibling
subscription (P1-1 / Track B-7 invariant regression guard)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
707c45b355 |
feat(track-d 2): register SearchMemoryTool + ForgetFactTool with MCP runtime
Closes the audit's "tools exist but unregistered" finding from
.planning/audit-2026-05-03/memory-audit.md §4-5: SearchMemoryTool +
ForgetFactTool source files have lived in `packages/MCP/Sources/MCP/
InProcess/` since Plan 07-03 with zero non-test instantiation. The agent
loop has had no way to call into memory.
Wiring:
- New `App/MCP/InProcessMemoryAdapters.swift` bridges MCP's tool
dispatch protocols to Memory's actors:
* `HybridSearchAdapter: HybridSearchDispatching` wraps
`Memory.HybridSearch` and translates `[FactRef]` → `[SearchMemoryHit]`
(so MCP doesn't need to import Memory).
* `ForgetFactStoreAdapter: ForgetFactDispatching` wraps
`MemoryStore.forgetFact`.
- `installMemory` builds an `InProcessToolRegistry` and registers tools
based on preconditions:
* `forget_fact` registers when `memoryStore != nil` (writes need DB)
* `search_memory` registers when `memorySearchAvailable && embedder
init succeeds` (reads need DB + vec0 + Ollama loopback)
- Embedder construction (`OllamaEmbeddingClient` loopback assertion)
failure is caught + logged; degrades to "writes ok, no search"
rather than failing the whole subsystem.
- New `inProcessToolRegistry: InProcessToolRegistry?` ivar, exposed
@testable.
Test seam: `AppDelegate.buildInProcessToolRegistry(forgetDispatcher:
searchDispatcher:)` is the pure registry-construction helper — takes
already-built dispatchers (or nil) and applies the gating rules. Tests
inject stub dispatchers to assert positive registration without needing
a real `MemoryStore` (which requires vec0.dylib, env-gated in CI).
Tests added (compile-time gates per
.planning/debug/xctest-launch-runningboard-error-5.md):
- `test_D2_noToolsRegisteredWhenStoreUnavailable` — production reality
on every cold launch (vec0 placeholder)
- `test_D2_bothToolsRegisterWhenBothDispatchersPresent` — store + search
available
- `test_D2_onlyForgetRegistersWhenSearchUnavailable` — store ok, vec
failed (the future state once custom libsqlite3 lands but vec0.dylib
is missing)
- `test_D2_emptyRegistryWhenBothDispatchersNil` — degenerate case
Out of scope: exposing the registered tools to the LLM via
`AgentOrchestrator.availableTools` (currently `[]` — separate audit gap,
affects helper tools too). The "tools exist but unregistered" gap is
closed; the "tools exposed to LLM" gap is independent.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
75a10be480 |
fix(track-d 1): installMemory no longer short-circuits on vec failure
Pre-fix: a single `return` after `MemoryStore(databaseURL:)` threw left `memoryExtractionOrchestrator` AND `memoryExtractionCoordinator` both nil for the lifetime of the process — so the .turnEnd subscriber was a no-op and nothing about memory extraction was observable in system log even though the LLM call to Ollama is independent of the SQLite store. Post-fix: store nil → log warning, set memorySearchAvailable=false, but extractor + orchestrator + coordinator all construct. The applyOp closure captures `[weak self]` and routes to a debug log when self.memoryStore is nil — drain loop survives, log only the turnId hash (T-06-05-03 / memory- content discipline: never log subject/object content). `memorySearchAvailable` is added as an ivar to track whether search is viable (today: store!=nil ⟺ vec_version() succeeded ⟺ search works; when custom libsqlite3 lands, the flag will split from store!=nil). `installMemory` lowered from `private` to default `internal` so the new JarvisAppTests/MemoryInstallWiringTests can drive it directly via `@testable import Jarvis`. Tests run as compile-time gates (xctest harness launch is broken on Xcode 26 + ad-hoc bundles per .planning/debug/xctest-launch-runningboard-error-5.md), but the structural assertions still catch regressions of the form "the cascade came back". Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
08125a46d8 |
chore(test): remove tautologies and rubber-stamps per tests-audit.md
Per `.planning/audit-2026-05-03/tests-audit.md` section 5, five
`XCTAssertTrue(true)` placeholder tests were identified across the tree
that are mathematically incapable of failing. This commit fixes or
removes each, conservatively. Files touched and disposition:
- App/Tests/AppTests/MCPRuntimeWiringTests.swift:260
fixed: replaced trailing `XCTAssertTrue(true)` rubber-stamp at end of
CR-02 channel-drain test with a load-bearing assertion that the drain
task completed via `finish()`, not cancellation. The implicit `await
drainTask.value` is the real gate; the new assertion ensures regressions
that cancel-instead-of-finish surface.
- App/Tests/AppTests/WebviewBundleLoadTests.swift:60
deleted: `test_assetsDirectoryGitignored_documentedInSmokeScript`. Body
was `XCTAssertTrue(true)` with comment "intentionally has no assertion
— it exists to document the split of responsibility". Documentation,
not a test. Replaced with a top-level comment block documenting the
same split (smoke-test-hud.sh is the runtime gate).
- packages/AgentCore/Tests/AgentCoreTests/ToolChoiceTests.swift:38
fixed: removed trailing `XCTAssertTrue(true) // Test compiles → contract
holds.` from `testLLMProviderStreamRequiresToolChoice`. The closure
body itself is the contract — if the test compiles, the contract holds.
Comment updated to make the compile-time semantics explicit.
- packages/Shell/Tests/ShellTests/InputMonitoringDenialTests.swift:36
deleted: `test_localMonitorOnlyFallback`. Body was `XCTAssertTrue(true)`
documenting that the real fallback test lives in HotkeyBindingTests.
Replaced with a top-level comment pointing readers there.
- packages/Vision/Tests/VisionTests/PackageBoundaryTests.swift:14
deleted: `testVisionModuleCompilesStandalone`. Body was
`XCTAssertTrue(true)` with comment "compile is the assertion." VISION-03
isolation is enforced by `scripts/check-vision-isolation.sh` and the
Package.swift dependency graph, both of which are real gates. Folded
the documentation into the `testPublicAPISurface` doc-comment so the
explanation isn't lost.
HudStateEnumTests rubber-stamps (audit items #4–6 in section 5) were
left as-is. The voiceOverLabel literals are user-facing accessibility
text that VoiceOver actually speaks; they are load-bearing in the sense
the audit calls out as worth preserving ("replace with behavioral
assertions if the literal under test is load-bearing"). A regression
that breaks an a11y label IS a real regression. Conservative read; not
worth the blast radius of guessing wrong.
Pre-existing baseline issues unrelated to this batch:
- `packages/Shell/Tests/ShellTests/InputMonitoringDenialTests.swift`
fails to COMPILE on a clean tree (MockProbe missing
`isListenEventAccessGranted()`). Verified via `git stash` that the
failure is independent of this commit. Not addressed here per the
scope rules — flagged for follow-up.
Net test count: -3 tests (3 deleted, 0 added — the
MCPRuntimeWiringTests assertion replaces the tautology in-place).
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
|
||
|
|
ad93dac665 |
fix(blocker-int-1): replace NoopBusGateway with real MCPBusGatewayAdapter
ConfirmingToolDispatcher emits tool-call events through `BusGateway`. The prior `NoopBusGateway` (AppDelegate.swift:503 + dead struct at :1894-1904) silently dropped every emission, so the HUD's chat-panel ToolCallCard never rendered a Running / Completed transition for any tool dispatch. This is the canonical "test mock and prod stub diverge in the same direction" pattern called out in `.planning/audit-2026-05-03/tests-audit.md` §6: SpyBus in MCPRuntimeWiringTests counted invocations; production shipped with empty bodies — both satisfied the protocol; only the latter was load-bearing in production. The replacement `MCPBusGatewayAdapter` (App/MCPBusGatewayAdapter.swift): - Conforms to `BusGateway`, forwards to `OutboundBatcher.flushAndSend` with `.toolCallStart` / `.toolCallEnd` envelopes. - `updateArgsPreview` re-emits `.toolCallStart` with the same id; the HUD's `upsertToolCall` (webview/packages/hud/src/bus/client.ts) patches the existing card by id, transitioning awaiting-approval → running with the post-approval sanitized args. - `String → UUID` mapping via SHA256-derived RFC 4122 v5-shape UUID, so `emitToolCallStart(id="toolu_01X")` and `emitToolCallEnd(id="toolu_01X")` produce IDENTICAL UUIDs for HUD reconciliation across start/update/end. The dispatcher protocol uses `String` (CR-03 in ConfirmingToolDispatcher.swift — Anthropic toolu_01ABCD… / Ollama free-form, never UUID); the bus protocol uses `UUID` (Phase 2 closed schema). Bridge is one-way; the audit-trail correlation by String id already lives at ReplayingToolResultObserver. - Late binding via `@Sendable () async -> OutboundBatcher?` resolver: the batcher is built in installAgent (step 13), AFTER mcpInstallTask captures the gateway in step 10. The closure resolves it at emit-time; if the batcher is nil (early window before installAgent finishes, or shutdown), emissions drop silently — the replay log path still records the call so audit isn't lost. Tests: App/Tests/AppTests/MCPBusGatewayAdapterTests.swift (+5): - stableUUID determinism (same input → same UUID) - stableUUID distinguishes different ids - emitToolCallStart reaches the sink with right name/argsPreview/id - emitToolCallEnd id matches the start id (reconciliation invariant) - updateArgsPreview re-emits .toolCallStart with same id, updated args - nil batcher drops events silently (no trap) The recording sink + OutboundBatcher path the test exercises is the same path production uses end-to-end through to WebviewBridge.send. The remaining seam (sink → JS-side store) is already covered by RealWKWebViewIntegrationTests + webview/hud streaming.test.tsx D5/D6/D7. App build clean. Bus tests 58/58 (was 57/58 before F-A2-01 fix). MCP tests 85/85 unchanged. All 5 boundary gates green. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
e9c0a34298 |
fix(track-b 4): wire AudioGraphOwner + LiveSpeechAnalyzerBridge SpeechAnalyzer API
Track B-4 of the 2026-05-03 voice audit fix plan (.planning/audit-2026-05-03/voice.md). Two production paths were dead: - installVoice constructed WakeWordDAG but never built the AudioGraphOwner that owns the AVAudioEngine + ring buffer it feeds from. Mic taps were installed inside AudioGraph.init, but no AudioGraph was ever created in production, so the wake-word detached feed Task spun on an empty ring for the entire process lifetime. - LiveSpeechAnalyzerBridge.feed(_:) was `_ = chunk` — a structural shell left over from Plan 06-03 that was meant to be wired in 06-05 but never was. Changes: * AudioGraphOwner: add `setCancelInFlight` / `setReleaseORTSessions` cross-actor setters so MainActor install code can install the cancel-in-flight closure under Swift 6 mode. * PCMBufferBuilder: new pure-AVFoundation translator extracted from the bridge (`AudioChunk.pcm16k` → `AVAudioPCMBuffer` + sample-rate convert via `AVAudioConverter`). Lives outside the macOS-26-gated bridge so unit tests run on every supported macOS. * LiveSpeechAnalyzerBridge: replace the no-op feed with real wiring — `start()` builds `SpeechTranscriber` + `SpeechAnalyzer`, queries `SpeechAnalyzer.bestAvailableAudioFormat`, opens the `AsyncStream<AnalyzerInput>` input pipe, and spawns a result-drain Task. `feed()` packs the chunk into a PCM buffer (with optional format convert) and yields `AnalyzerInput(buffer:)`. `finish()` finalises through end-of-input and awaits the drain. Eager partials stream in init removes the ordering race against the SpeechAnalyzerSTT feed/drain Task pair. * AppDelegate: add strong properties `audioGraphOwner`, `audioGraphDegradationTask`, `audioGraphRebuildTask`. installVoice now constructs AudioGraphOwner with degradation/rebuild stream continuations, spawns degradation + rebuild consumers BEFORE `open()` (so the retry-with-aec=false path's `.aecUnavailable` is observed), opens the graph fail-soft, hands the ring buffer to `WakeWordDAG.start(ring:)`, and sets `cancelInFlight` to cancel the DAG before teardown. Failure to open is non-fatal: voice degrades to dormant + banner, app keeps running. * VoiceWiringTests testW1: assert the three new strong properties exist and start nil. * PCMBufferBuilderTests (new, +7): frame count, payload fidelity, empty input, same-format passthrough, 16k→24k upsample ratio, empty-input convert. Verification: * swift test packages/Voice — 77 tests, +7 PCMBufferBuilderTests, 3 skipped, 1 pre-existing failure (TTSInterruptTests.testI3, the 10s timeout flake unrelated to this work; deterministic before and after). * `bash scripts/check-app-builds.sh` — App target Debug build clean. * Boundary gates: install-order, no-modal-presentation, no-null-voice-adapters, orchestrator-events-single-consumer, single-writer-hudstate — all PASS. What's still TODO (Track B-5): * End-to-end voice loop test: feed a known WAV at the audio graph, assert STT result lands on the orchestrator. The pipe is now wired but no integration test exercises it yet. * VAD-gated chunk feeding from VoiceController to STT — the bridge accepts chunks now but VoiceController.startSTTSession() has no producer feeding `sttChunkCont`. Tracked in audit PLAN 06-03 anti-pattern callout. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
75a4ed6991 |
feat(09-04): replace NullVoiceAdapters with real voice adapter triad + lock install order (D-09, D-10, BLOCKER-1, BLOCKER-2, WARNING-5)
- New App/Voice/{VoiceOrchestratorAdapter,VoiceTTSAdapter,VoiceBusEmitterAdapter,RejectReasonCopy}.swift
replaces NullOrchestratorAdapter / NullTTSAdapter / NullBusEmitterAdapter (D-09).
- VoiceOrchestratorAdapter forwards submit/cancelAndSubmit to AgentOrchestrator,
appends user-side text to TurnTranscriptStore (BLOCKER-1), enqueues a HUDBanner
on every SubmitOutcome.rejected case via shared RejectReasonCopy (D-10).
- VoiceTTSAdapter wraps an optional TTSEngineActor (engine construction lands
in a follow-on plan; nil-engine path no-ops gracefully).
- VoiceBusEmitterAdapter wraps an OutboundBatcher whose sink is the WebviewBridge
so audio-level RMS reaches the HUD's RingMesh pulse.
- AppDelegate.installVoice now uses the production adapters; install order
in applicationWillFinishLaunching reordered to vision -> agent -> voice
with voiceInstallTask awaiting agentInstallTask?.value (WARNING-5).
- New broadcaster .voice subscriber in installAgent: per-turn assistantText
accumulator + turnSourceWasVoice filter so text-originated turns NEVER drive
emitTurnEnded on the voice adapter (BLOCKER-2).
- SubmitOutcome.superseded extended with newTurnId so cancelAndSubmit callers
can append user text under the new turn id (BLOCKER-1 user-side closure on
the cancel path).
- VoiceSubscriberTextTurnFilterTests proves text turns never trigger
emitTurnEnded; VoiceOrchestratorAdapterTests covers RejectReasonCopy +
banner id mapping + VoiceBusEmitterAdapter forwarding.
- New scripts/check-install-order.sh + scripts/check-no-null-voice-adapters.sh
catch regressions structurally.
|
||
|
|
da42c079fd |
feat(06-05): AppDelegate production wiring + VoiceWiringTests (W1-W4)
- AppDelegate: import Voice, add voiceController/pushToTalk/muteWakeWord properties - AppDelegate: installVoice() async method wires VoiceController to dormantVoiceContinuation - AppDelegate: NullOrchestratorAdapter/NullTTSAdapter/NullBusEmitterAdapter (Phase 7 stubs) - AppDelegate: AppDelegateBannerAdapter bridges VoiceBannerInterface→HUDBannerCoordinator - MenuBarIconController: contextMenu is now public (for MuteWakeWord) - VoiceWiringTests: W1-W4 type-level wiring verification - NOTE: check-app-builds.sh fails on pnpm (pre-existing infra limit); Swift compiles clean |
||
|
|
d95c4421fc |
fix(05-review): CR-02 wire ME-04 closure observer→channel→drain→ReplayLog
The orch→replay BoundedAsyncChannel<ReplayEvent> previously had no producer (ReplayingToolResultObserver wrote directly to replayLog) and the drain Task discarded everything it received. The channel + drain were dead code; ME-04's contract was structurally unverifiable in production. Now: - ReplayingToolResultObserver holds a BoundedAsyncChannel<ReplayEnvelope> (TurnID + ReplayEvent envelope so the consumer knows which turn to key against) and sends both pre- and post-sanitize bytes through it. - AppDelegate opens a real ReplayLog at boot and the drain Task consumes envelopes off the channel and calls replayLog.record(...). - AppDelegate calls MCPRuntimeWiring.build(...) (previously referenced only in a doc comment) so the dispatcher chain is actually instantiated. A NoopBusGateway placeholder fills the bus seam until Phase 6/7 orchestrator wiring lands. - MCPRuntimeWiring.build's signature switches replayLog parameter to replayChannel — cleaner separation, since the channel is the load-bearing seam. Adds three CR-02 regression tests in MCPRuntimeWiringTests: observer→channel produces envelopes, observer→channel→drain→ReplayLog end-to-end, and .dropOldest under burst saturation. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
d07ac08f0e |
fix(05-review): CR-03+WR-04 String toolUseId and success-path toolCallEnd
CR-03: BusGateway's toolUseId parameter is now String (matching the bus schema's Phase 2 closed contract) instead of UUID. Previously the dispatcher synthesized `UUID(uuidString: toolUse.id) ?? UUID()`, which silently produced a fresh random UUID for every Anthropic 'toolu_…' or Ollama free-form id — breaking correlation across bus / replay / orchestrator. The original String id now flows verbatim from toolCallStart through argsPreviewUpdate to toolCallEnd. The broker's internal UUID is now a fresh-per-call value and does NOT replace the bus-facing id. WR-04: ConfirmingToolDispatcher now emits toolCallEnd(ok: true, …) on the success path so bus consumers (HUD ToolCallCard) transition Running → Completed. On dispatch error post-approval, toolCallEnd fires with ok: false carrying the error description before re-throwing. Adds three new tests (busToolUseId verbatim across emissions, non-UUID id flows through, success path emits 3 events). Updates SpyBus and SpyBus-equivalent in MCPRuntimeWiringTests to use String toolUseId. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
daae27e720 |
feat(05-05): App-level MCPRuntimeWiring + ME-04 channel + SEC-07 observer
Closes ME-04 (Plan 04-05 deferred state). AppDelegate.applicationWill FinishLaunching now instantiates BoundedAsyncChannel<ReplayEvent>( capacity: 2048, policy: .dropOldest) for the orch→replay seam — the production instance that Plan 04-05's ChannelTopologyTests CT2 only verified as a primitive. - App/MCP/MCPRuntimeWiring.swift: dependency-graph constructor for the full chain (MCPClient → ToolRegistry → MCPToolDispatcher → ConfirmingToolDispatcher) plus a compose() test seam. The ConfirmationPresenterHolder breaks the broker↔presenter cyclic init. - App/MCP/ReplayingToolResultObserver.swift: SEC-07 observer that records pre-/post-sanitize bytes via two ReplayEvent.toolResultFull rows (one canonical, one ':raw'-suffixed toolUseId). Uses an injected turnIDResolver closure since orchestrator wiring lands in a later plan. - AppDelegate stores the orchToReplayChannel + drain Task and tears them down on applicationWillTerminate. - App/Tests/AppTests/MCPRuntimeWiringTests.swift: 3 structural tests (compose-returns-ConfirmingToolDispatcher; channel-capacity-+-policy; MCPRuntime-struct-addressable). No xcodebuild test launch — known Xcode 26 RunningBoard blocker per .planning/debug/. Deviations: - Replay / AgentCore / MCP / Bus Package.swifts had implicit transitive swift-log deps that SPM tolerated but Xcode framework-linker rejects once the App pulls them in as separate frameworks. Added explicit swift-log deps to all four (Rule 3, blocking). - HudStateCoordinatorBusWiringTests qualified two HudState references to Jarvis.HudState — the new framework deps disturbed the App vs Bus symbol disambiguation (Rule 1, surgical). - The plan's plain ReplayEvent.toolResultFull(toolUseId:rawBytes: sanitizedBytes:) variant doesn't exist (Phase 4 schema closed); the observer writes two rows with a ':raw' toolUseId suffix instead. xcodebuild build (Debug) succeeds with the new MCP deps wired into the App target. Test build (build-for-testing) also succeeds; runtime test execution is the documented Xcode 26 RunningBoard blocker per 03-05-PLAN. |
||
|
|
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. |
||
|
|
ca17844883 |
feat(03-01): extend HudState enum 5→7 cases with voiceOverLabels
- Append .reconfiguring and .booting to App/Theme/HudState.swift; preserve Phase 1 declaration order + existing labels verbatim. - Add voiceOverLabel arms 'Jarvis, reconfiguring audio' / 'Jarvis, starting up' per RESEARCH §HudState Enum Wire Format. - MenuBarIconController: add forced exhaustive switch arm for the two new cases; both share .idle profile (no animation) with TODO for Phase 3/4 dedicated treatment per HUD-08 precedence. - HudStateEnumTests: 6 methods covering case count, non-empty + unique voiceOverLabels, spec-verbatim labels, exhaustive switch. |
||
|
|
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. |
||
|
|
b76ff75d81 |
fix(01-IN-07): replace tautology with real SEC-01 pasteboard assertion
Phase 1 REVIEW.md IN-07: `test_stateDumpDoesNotIncludeAPIKey` was an
`XCTAssertTrue(true)` tautology. It claimed to test SEC-01 /
T-04-02 but could not fail on a regression where `copyStateDump()`
accidentally started writing the raw API key to the pasteboard.
App/Tests/AppTests/AppDelegateWiringTests.swift
- Introduce `RecognizableKeychain` fake that returns a distinctive
sentinel plaintext ("sk-ant-test-do-not-leak").
- Clear NSPasteboard before the test so prior-test contamination
can't produce a false positive.
- Trigger `copyStateDump` via `perform(Selector("copyStateDump"))`.
- Assert pasteboard string:
* does NOT contain the recognizable plaintext
* does NOT contain any "sk-ant-" prefix
* DOES contain "\"apiKeyStored\"" (sanity — exercised the path)
Now this test literally fails if copyStateDump regresses.
|
||
|
|
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.
|
||
|
|
929e4cee6b |
fix(01-CR-02): shorten API key plaintext lifetime in wizard @State
Phase 1 REVIEW.md CR-02 (SEC-01): `WizardStageAPIKeyView` kept the raw
Anthropic API key in an `@State String` for the lifetime of the view —
outliving the Keychain write, retained by SwiftUI attribute-graph
diffing, and visible to any memory-dump forensic vector (crash report,
Xcode memory graph, `leaks(1)`).
App/Wizard/WizardStageAPIKeyView.swift:53-101
- `verify()` now captures the key into a single `capturedKey` local and
reassigns `apiKey = ""` immediately, so the @State slot drops its
strong reference before the async validate + keychain.set chain runs.
- Error branches selectively restore `apiKey = capturedKey` only where
the user plausibly wants to retry without retyping (unauthorized /
network / generic / keychain-error); `malformed` keeps apiKey empty.
- `.onDisappear { clearAPIKey() }` hook: zeros the @State on view
teardown (including back-navigation and success-path dismissal),
preventing the plaintext from persisting in the attribute graph after
dismiss.
- `clearAPIKey()` first overwrites with null bytes of the same length
(best-effort — Swift Strings are CoW/immutable so the backing page
can't be zeroed in place), then drops the @State to empty so ARC can
release the CoW storage.
App/Tests/AppTests/WizardStageAPIKeyViewTests.swift (new)
- Source-level grep gates for the three regression points: onDisappear
hook, clearAPIKey helper shape, and verify()'s capture-then-clear
pattern. SwiftUI @State lifecycle isn't observable from XCTest
without ViewInspector, so text-level gates are the practical
regression guard.
Jarvis.xcodeproj/project.pbxproj: regenerated via xcodegen to pick up
the new test file.
|
||
|
|
ebefb91043 |
feat(01-04): build first-launch wizard + full AppDelegate launch wiring
- AnthropicKeyValidator: local sk-ant- prefix check + URLSession-injectable GET to api.anthropic.com/v1/models; returns typed enum, never logs key - WizardState: @MainActor ObservableObject; D-09 stateless re-entry via refresh(); firstUnresolvedStage advances apiKey → tcc → hotkey → complete - WizardStageAPIKeyView: SecureField, validator + Keychain round-trip; key value lives only in Keychain (SEC-01) - WizardStageTCCView: Input Monitoring row actively probes; Mic/Camera/ Automation rows are explainer-only (D-08/SEC-04); no permission APIs called from the view - WizardStageHotkeyView: ShortcutRecorderView + CollisionDetector wiring; Skip leaves hotkey unbound (SHELL-02) - WizardView: three-stage root with header dots + Back footer - OnboardingWizardController: NSWindow hosting; first-launch .modalPanel + close-button disabled until API key stored (D-06) - AppDelegate: full 8-step launch chain — logging bootstrap (S-8) → entitlement gate (Plan 03) → config load (NSAlert on malformed, D-19/S-6) → menu bar + HUD + banner install → Keychain fetch (.keychainEmpty banner on itemNotFound) → Input Monitoring probe (.inputMonitoringDenied banner on denial, SHELL-06) → HotkeyBinder init → Wizard open on first launch. copyStateDump writes boolean-only presence; API key VALUE never clipboarded - JarvisApp: Settings scene stub; real NSPanels managed by AppDelegate - Tests: 11 new (WizardState 4, AnthropicKeyValidator 3, WizardTCC 1, AppDelegateWiring 4) + updated AppDelegateEntitlementTests for the new launch chain; all 25 JarvisAppTests pass - Re-inserted PBXCopyFilesBuildPhase for Contents/Helpers per Plan 01/03 precedent (xcodegen strips empty copyFiles) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> |
||
|
|
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) |
||
|
|
f8da4b7c39 |
feat(01-03): add MenuBarIconController + MenuBarContextMenu + JarvisAppTests target
- MenuBarIconController (@MainActor) owns NSStatusItem, loads Icon-MenuBar-Template asset with isTemplate=true, re-anchors layer.frame after anchorPoint change (Q7 gotcha #2), transitions across 5 HudStates via CAAnimationFactory with 150ms crossfade + rate-limited VoiceOver announcements (3s limiter per UI-SPEC Surface 3 line 421) - MenuBarContextMenu builds Setup… / Settings… (disabled) / Show Dev Overlay / Copy State Dump / Quit Jarvis (⌘Q), with a ClosureTarget trampoline for closure-based actions - Left-click vs right-click disambiguation via NSApp.currentEvent.type inspection (D-16 / UI-SPEC Surface 3 lines 406-409) - JarvisAppTests Xcode unit-test bundle target added via project.yml; scheme wires test action against JarvisAppTests - MenuBarIconControllerTests covers state transitions, no-op-on-same-state, context-menu items, and HudState voiceOverLabels (4 tests pass) - PBXCopyFilesBuildPhase for Contents/Helpers re-applied post-xcodegen (Plan 01 invariant — MCP-05 acceptance-criteria grep) |