M1.5 First Capture Journey — paused for user verdict - #31
Conversation
…layback Sol-implemented per .context/plans/m15-core-services-spec.md: CaptureEngine (AVAudioRecorder AAC, metering, pause/resume accounting), SecureSessionStore (CryptoKit AES-GCM combined boxes, Keychain key provider seam, atomic staged saves that preserve recovery audio on failure), PlaybackEngine (AVAudioPlayer(data:), never touches disk). 12 unit tests, TDD red/green evidence under .loop/verification/m1.5/first-capture/core/. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Opus-implemented per .context/plans/m15-ui-spec.md and the ADR-008 UX contract: NavigationSplitView shell (Today/Moments/Projects/Actions/Agent Runs/Search), complete 10-state capture machine with permission gate and secure-save recovery, MenuBarExtra, floating NSPanel HUD, Command-K palette, full keyboard map incl. Option-Space local monitor, runtime health status (informational, env-var socket probe), honest explanatory destinations, Reduce Motion/VoiceOver/grayscale-safe states. All data real from SecureSessionStore; no mocks or dead controls. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
All 10 first-capture scenarios pass on the packaged .app; scenario log records findings F1-F9. package-app.sh now writes NSMicrophoneUsageDescription via plutil because PlistBuddy's -c parser cannot carry an apostrophe and its failure did not trip set -e. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Single-flight start/save with recovery-sheet reopen; owned plaintext temp-file reconciliation at launch/termination with PID+start-identity ownership; ordered non-optimistic title/note/delete pipeline with failure reconciliation; non-destructive export via staged replace; corrupt-session-tolerant list() with per-entry diagnostics; playback timer teardown; surfaced pause/resume errors; stale staging-dir cleanup at store init. Red/green evidence in worker report. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… copy - F7: ⌘K palette lists matching captures inline (cap 5, overflow row), row 0 is the true Enter target, arrow-key wrap, Escape closes - F8: retry-save routing survives sheet dismissal transition (recoveringFromFailure sequencing) so Try Again lands on saved detail - Save-failure copy now discloses the private plaintext temp file honestly instead of promising no unencrypted disk write - Delete confirmations name the capture in Moments and detail views - VoiceOver labels on menu bar popover controls and idle toolbar button - Quieter toolbar New Capture (bordered), tabular digits in moment rows - ADR-009: recorded M1.5 review-finding deferrals Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
- Honest copy in the three remaining spots that promised at-rest encryption without disclosing the private in-recording temp file: permission explainer, Today capture card, mic usage description - Hold isSaving through the 400ms retry-routing window so a capture started mid-recovery cannot be navigated away (new minor #15) - ADR-009 addendum: record termination-preserves-pending-plaintext and Today-only mutation-error surfacing as deliberate checkpoint tradeoffs picked up by M2 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sol's confirmation pass proved the ADR-009 addendum wrong: quitting with an unresolved save failure left the only copy of the user's audio as a process-owned temp file, and next-launch reconciliation deleted it as a dead-owner orphan. - Save failure now renames the pending temp with a held- prefix (CaptureEngine.markRecordingHeld); reconciliation always preserves held- files. Retry/export/discard follow the renamed URL through pendingCapture. - New test proves a held file survives reconciliation after the owning process dies; ownership test extended with a held dead-owner file. - ADR-009 addendum rewritten to describe implemented behavior, including the remaining crash-window limitation left for M2's manifest. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Traceability: 27 M1.5 rows dispositioned (4 implemented, 23 in_progress for partially-covered requirements) with tested-commit, packaged-app, Computer Use, and review evidence; ledger validator passes (287 rows). Roadmap, progress, and state.json record the ADR-008 pause with user_verdict: pending on PR #31. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Traceability: 27 M1.5 rows dispositioned (4 implemented, 23 in_progress for partially-covered requirements) with tested-commit, packaged-app, Computer Use, and review evidence; ledger validator passes (287 rows). Roadmap, progress, and state.json record the ADR-008 pause with user_verdict: pending on PR #31. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Closeout addendum: Sol's confirmation pass on the final data-loss fix ( 🤖 Generated with Claude Code |
Live delta retest found typing "untitled" in the palette or search returned nothing while five "Untitled capture" rows were visible: both predicates matched the raw stored title, which is empty for untitled captures. Match SessionDisplay.title so what the user sees is what search finds. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The explicit .id(index) on each row replaced the ForEach element identity, so LazyVStack kept ghost rows on screen when typing reshaped the list: with query "untitled" the palette rendered four stale command rows while the real item array was all sessions, making the highlighted row diverge from the true Enter target. Scroll-on-selection now targets the element id instead. Found by live Computer Use retest: pressing Enter with "Start capture" highlighted opened a session detail. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Delta retest update (machine unlocked, live Computer Use, user present): two real palette bugs found and fixed on the spot.
Gates on the fixed tree: Still open: the post-fix visual re-probe of the palette, the retry→saved-detail routing re-run, and delete-dialog/AX spot checks were interrupted — the user began actively using the machine mid-probe, and injecting further keystrokes risked hitting their foreground apps. These will be covered by an idle-machine re-run or the user's hands-on pass. |
…test evidence The Today recents, Moments, and Search row buttons exposed only combined metadata as AXValue with no AXDescription, because the inner MomentRow's accessibility label does not survive SwiftUI's Button flattening. Put .accessibilityLabel(SessionDisplay.title(...)) on the Button itself, matching the MenuBarViews pattern; confirmed via the raw AX API that every row now reports its display title as AXDescription. Also lands the resumed Computer Use delta-retest evidence: post-fix palette render and Enter-target alignment, delete-dialog naming, and the full retry-save journey (failure sheet, held- protection file, Try Again to saved detail) all pass on the packaged bundle. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Delta retest complete — all probes pass on the packaged bundle (b27a676). Resumed the interrupted Computer Use probes after the machine was free:
Gates at b27a676: full PR remains paused at |
b27a676 to
ec4be21
Compare
M1.5 — First Capture Journey
user_verdict: pending — per ADR-008 this PR pauses for Krish's hands-on pass before merge. Testing setup: double-click
Test Current Build.command; guide in.context/TESTING.md.What this slice ships
The first real visible product: capture → encrypted local store → playback/search.
Capture Delegate.appviascripts/package-app.sh.Review trail (ADR-008 gate)
.loop/verification/m1.5/first-capture/ux/(screenshots, AX dumps,scenario-log.md).4f47b78(core lifecycle/single-flight/mutation ordering),1fea360(F7 inline palette results, F8 retry routing, F5 AX labels, honest copy, delete dialogs),2016873(isSaving holds through retry routing; "encrypted when saved" copy in explainer/Today/usage description),c82ed0d(data-loss blocker: save-failure audio renamedheld-, always preserved by launch reconciliation).Known limitations (ADR-009, deliberate checkpoint tradeoffs)
AES-GCM AAD binding + versioned on-disk format (M2); per-root store locking (M2); Developer ID signing/notarization (blocked on user identity — ad-hoc signing re-prompts TCC each rebuild); universal binary; testable AppModel target + XCUITest (M2);
/tmpsocket path convention (F1); rebindable capture shortcut vs Raycast ⌥Space (F6, M3). Addendum: quit with an unresolved save failure now preserves the audio as aheld-file, but there is no in-app recovery UI until M2's pending-capture manifest, and a crash while recording or mid-save still loses the temp. Store mutation errors surface on Today only.Verification
swift build,swift format lint --strict, 19 core tests green (includes new held-recording preservation tests)../scripts/verify-m0.shexit 0 — run from a short-path worktree (/tmp/m15-verify) because the repo's long path exceeds macOS's ~104-byte Unix-socket limit (SUN_LEN) for the runtime handshake test; same HEAD, full parity.🤖 Generated with Claude Code
Greptile Summary
The PR introduces the first complete native capture journey, including microphone recording, encrypted local persistence, playback, search, and recovery-oriented UI.
Confidence Score: 5/5
The PR appears safe to merge after the required user-verdict gate, with the remaining durability and distribution limitations explicitly acknowledged for later milestones.
The reviewed capture, persistence, playback, lifecycle, and packaging paths did not reveal an unacknowledged concrete failure that satisfies the publication criteria.
Important Files Changed
Sequence Diagram
sequenceDiagram actor User participant UI as SwiftUI App participant Model as AppModel participant Capture as CaptureEngine participant Store as SecureSessionStore participant Keychain as Keychain participant Playback as PlaybackEngine User->>UI: Start capture UI->>Model: requestStartCapture() Model->>Capture: Request permission and start Capture-->>UI: Recording state and level updates User->>UI: Pause / resume / stop UI->>Model: stopAndSave() Model->>Capture: stop() Capture-->>Model: Temporary audio URL Model->>Store: save(session, audio URL) Store->>Keychain: Load or create encryption key Store->>Store: Encrypt metadata and audio Store-->>Model: Saved session Model-->>UI: Open session detail User->>UI: Play capture UI->>Store: loadAudioData(session) Store->>Keychain: Load encryption key Store-->>Playback: Decrypted audio data Playback-->>User: Audio playbackReviews (1): Last reviewed commit: "Give session-row buttons an accessible t..." | Re-trigger Greptile