Windows support — recording pipeline (capture + processing) [main / umbrella PR] - #43
Windows support — recording pipeline (capture + processing) [main / umbrella PR]#43shaik-zeeshan wants to merge 133 commits into
Conversation
- Gate macOS-only capture and privacy paths behind `target_os = "macos"` - Add `tauri-plugin-os` and update settings copy for Windows/Linux - Replace the sidecar prep shell script with a portable Bun/Node script
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 30954bd0d3
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| #[cfg(target_os = "macos")] | ||
| use super::output::{ | ||
| append_committed_segment_output_files, cleanup_unusable_segment_artifacts, | ||
| finalize_capture_outputs, |
There was a problem hiding this comment.
Gate the lifecycle activity dependency on macOS
On non-macOS builds this module is still compiled, but native_capture::activity is only declared behind #[cfg(target_os = "macos")] in native_capture.rs. The unconditional use super::activity::current_activity_snapshot at the top of this file will therefore fail on Windows/Linux before these newly gated imports help, so the cross-platform build still cannot compile. Please gate that import/usage as well, or provide a non-mac stub/gate the module.
Useful? React with 👍 / 👎.
- Document Windows 11 capture floor and runtime gating - Capture MVP scope, limitations, and settings copy for Windows
- Gate macOS-only tests and runtime paths for Windows CI - Introduce a cross-platform screen capture session trait - Update SUPPORTS.md and type-check workflow for Windows coverage
- Build the real `mnema-cli` sidecar in Windows CI - Use `libsqlite3-sys` vendored OpenSSL for SQLCipher on Windows - Update Windows storage-access research to match the new setup
- Add a Windows Graphics Capture/MF capture path behind platform support checks - Factor frame pacing and segment timestamp helpers into shared code - Update Windows support docs and segment output extensions
- Extend the last frame to wall-clock elapsed time before rotate/stop/finalize - Build staging textures from the source frame size so odd capture heights copy correctly
- Enable Windows-only segment rotation and lifecycle plumbing - Hold the last frame until segment close so durations clamp correctly - Add lookahead timing helpers and tests for frame scheduling
- Add Windows Capture Index key store via Credential Manager - Split OCR dependencies by target platform - Update platform support tracking for Windows
- Delete local SQLite, WAL, SHM, and index artifacts - Stop tracking transient `.mnema` runtime files
On Windows, WebviewWindowBuilder::build() deadlocks the WebView2 controller initialization when called from a synchronous command or event-loop callback: the controller is created via a callback that needs the main event loop to keep pumping, but the synchronous caller is blocking that loop. The native window frame appeared while its webview never initialized, leaving a blank white surface until something forced a reload. Startup windows were unaffected because they build in setup(), before the loop starts blocking. Build runtime windows on a separate thread so the main loop stays free to drive WebView2 creation. macOS/Linux keep building inline (macOS must run the Cocoa corner-radius tweak on the main thread). open_startup_window stays inline (pre-loop), and complete_onboarding closes onboarding only after main is built so the app never drops to zero windows. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- Ignore the local `.mnema` directory in `apps/desktop/src-tauri` - Keep generated Tauri artifacts excluded from version control
- Wire Windows capture to emit low-cadence JPEG frame artifacts - Flush frame artifacts on segment rotation and capture stop - Mark Windows frame export support in SUPPORTS.md
|
Issue #47 implementation note for this PR. Latest commit on this branch contains the Windows frame export work:
Summary:
Manual Win11 smoke:
Verification also passed: |
- Add native capture support flags for non-original resolution on Windows - Scale WGC output and update settings copy/support docs for Windows - Expand Windows smoke test coverage for resolution and bitrate handling
- Recreate the frame pool in session when resolution, DPI, or display mode changes - Surface Windows capture stop events as failed runtime sessions - Update Windows support docs and status tracking
- Hide microphone, system-audio, privacy-filter, and inactivity controls on Windows since the runtime MVP does not capture them - Add a CPU-cost hint on the screen frame-rate control noting that software H.264 makes 30 fps expensive on Windows - Reword Capture and Privacy sidebar descriptions to match what actually renders on Windows Refs #50.
Verify the two capabilities Windows audio capture depends on, against the vendored wasapi-0.23.0 crate source: - Capture-clock timestamps: YES. BufferInfo.timestamp is the QPC position (100 ns) from IAudioCaptureClient::GetBuffer, anchoring Audio Segment timing to the same QPC clock WGC screen frames use. - Peak-since-last-poll: no dedicated meter API (no IAudioMeterInformation wrapper), but satisfied by computing peak over the full drained PCM, matching the existing macOS Audio Activity Sample path. No escalation needed; wasapi 0.23 stays viable for slice 1a. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Verify the two capabilities Windows audio capture depends on, against the vendored wasapi-0.23.0 crate source: - Capture-clock timestamps: YES. BufferInfo.timestamp is the QPC position (100 ns) from IAudioCaptureClient::GetBuffer, anchoring Audio Segment timing to the same QPC clock WGC screen frames use. - Peak-since-last-poll: no dedicated meter API (no IAudioMeterInformation wrapper), but satisfied by computing peak over the full drained PCM, matching the existing macOS Audio Activity Sample path. No escalation needed; wasapi 0.23 stays viable for slice 1a.
Drives the real WASAPI default-endpoint capture + Media Foundation AAC/M4A sink end-to-end: records, rotates a segment, stops, and validates each output through the production MF Source Reader positive-duration probe. Windows-gated; no-op elsewhere. Verified on-device: two rotated mic_session-prefixed .m4a segments, openable (MF MF_PD_DURATION > 0) and cross-checked with ffprobe (aac / 48kHz / stereo / ~3s duration). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
📝 Commit-message correction: commit For the record, on this branch:
Leaving history as-is to avoid a force-push on the shared PR branch; this note is the canonical mapping. |
…ge + plan Decisions from the grill session, probe-validated on-device against Helium (Chromium, ships as chrome.exe) and Zen (Gecko): - ADR 0044: UI Automation is the third Browser URL Strategy - Windows-only, permission-free, engine-dialected (Chromium: Document ValuePattern; Gecko: focused-element climb). No-guess invariant carried cross-platform (no IsOffscreen scan, no address-bar reads); brand-less exe-stem -> engine allowlist as a parallel type; bounded-cost contract (IUIAutomation2 timeouts + wall-clock attempt budget + cold-poll for dormant Chromium); live reads with no BrowserUrlProbeCache; zero new UI. - CONTEXT.md: Browser URL Strategy now names three mechanisms; new relationships for the cross-platform no-guess invariant, stem->engine recognition, bounded-cost reader shape, and the no-permission-UX stance. - PLAN.md: implementation plan (5 slices: stem resolver, UIA reader, snapshot wiring, --windows-browser-url-smoke, docs/verification). Discharges the browser-URL deferral in ADR 0043. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds UI Automation as the third Browser URL Strategy on Windows — engine-dialected, permission-free, zero new UI — discharging the ADR 0043 browser-URL deferral. - capture-metadata: BrowserEngine enum + brand-less known_browser_engine_for_exe_stem allowlist (Chromium/Gecko families; Helium-as-chrome.exe is one Chromium hit). - native_capture_browser_url_uia.rs: bounded-cost UIA reader (Chromium ElementFromHandle->Document ValuePattern; Gecko GetFocusedElement->outermost Document climb), cold-poll for dormant Chromium, MTA COM per reading thread, no-guess invariant; adds the full `windows` 0.61 crate dep. - native_capture_metadata.rs: Windows collector threads hwnd/pid, gates via pure windows_browser_url_probe_engine, sanitizes via sanitize_url, live off-lock read. - --windows-browser-url-smoke [--exe <stem>] on-device smoke flag. - SUPPORTS.md + on-device smoke runbook updated. cargo check -p mnema green; capture-metadata (25) + gating unit tests (4) green. On-device smoke run remains operator-deferred. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01BZEoPCwtXexuuUothEkFfd
…ntics Both "Define ... semantics" checklist items are now decided/documented: - Screen: no per-app permission prompt/pane exists for unpackaged Win32 desktop apps -- WGC captures with no consent UI (yellow border is the OS affordance). Only deny-by-exception gates exist (Win11 "Screenshots and screen recording" privacy page / graphicsCaptureProgrammatic consent store incl. NonPackaged, and the LetAppsAccessGraphicsCaptureProgrammatic GPO/MDM policy), surfacing as a capture-start failure handled like the microphone treatment. - System audio: Windows has no permission category for WASAPI render-endpoint loopback at all (no prompt, no privacy page, no deep-link target); support = the non-prompting default-render probe, residual denials get the microphone treatment. - Also documents *why* live app privacy exclusion cannot exist on Windows (ADR 0025): WGC/DXGI deliver the composed monitor image with no capturer-side content filter; WDA_EXCLUDEFROMCAPTURE is own-windows self-exclusion only. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Qga8XEwQWBJcZYT8jP9TiR
…data Windows permissions/privacy/metadata (v1): design ADRs, metadata implementation, browser-URL design + plan
Design the Windows Broker Authorization Channel as a named pipe port of
the macOS Unix-socket channel (ADR 0014): same newline-framed JSON
protocol and one connection handler generic over AsyncRead+AsyncWrite,
only endpoint setup + accept loop cfg-forked. Endpoint
\.\pipe\{identifier}-{sid}-cli-access with a protected user-SID-only
SDDL DACL; ActiveRequestGuard stays in the handler for busy parity.
Also flips the low-disk Windows checklist/summary rows to [x]
(commit d490fea, ADR 0041) and records the decided-but-pending broker
+ crates/cli design in the Storage-access checklist.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017wmfH3R7vmpkRFAkCe1egi
…named pipe) Vertical tracer-bullet slices from ADR 0045: shared generic handler, pure name/SID helpers, Windows named-pipe server, CLI client transport + mnema:// launch fix, docs finalization. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017wmfH3R7vmpkRFAkCe1egi
Port the macOS/Linux Unix-socket CLI-access broker to Windows as a named
pipe, the last unbuilt Windows subsystem. The newline-framed JSON protocol
is unchanged; only endpoint setup and the accept loop are cfg-forked, with
the connection handler made generic over AsyncRead + AsyncWrite and shared.
Desktop server (broker_authorization_channel.rs):
- #[cfg(windows)] start() resolves the pipe name
\.\pipe\{identifier}-{sid}-cli-access (SID from the process token, in the
name so it keys ownership on a multi-session box) and builds a protected
per-user-SID DACL via SDDL D:P(A;;GA;;;{sid}) once, leaked for process
lifetime and reused per instance.
- create-first-instance -> connect().await -> create-next-instance ->
spawn(handle_connection) loop, with reject_remote_clients + explicit
first_pipe_instance; ActiveRequestGuard stays in the handler so a second
client gets the fast `busy` response (macOS parity).
- Onboarding gate switched to the ungated windows::is_onboarding_complete.
CLI client (crates/cli/src/main.rs):
- #[cfg(windows)] send_authorization_request opens via ClientOptions with a
pure, unit-tested classify_pipe_open_error mapping (FILE_NOT_FOUND ->
app_unavailable drives launch+retry; PIPE_BUSY -> bounded retry) and the
byte-for-byte framed write/read/decision path.
- App-launch fixed from the bare `cmd /C start "" mnema` to the registered
`mnema://access/request` scheme.
- Minimal windows-sys dep for the token -> SID call.
Also wire the mnema-cli sidecar as a Windows externalBin, add the required
windows-sys Security/Authorization features to both crates, and flip the
SUPPORTS.md rows (:49 broker, :203/:205 CLI path) to [x].
cargo check + cargo test green on Windows (22 CLI, 17 broker). End-to-end
handshake + search verified on-device against the running app: request ->
Allow -> grant persisted -> authorized search returns real screenText /
audioTranscript results; last-day vs all-retained scope enforcement
confirmed both ways.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014tyJQYLj81gYhSd7XkcqWC
- Updated SUPPORTS.md (status table line 49, research notes line 203, implementation map line 301) to document 2026-07-03 on-device HITL verification - Verified flow: app publishes pipe, `mnema access request` → Allow → approved → grant recorded in `broker-grants.json` → post-grant data commands succeed - Clarified that data commands gate on TTY via `can_prompt_for_authorization()`, making `access request` the non-TTY test driver - Noted deny/cancel/timeout and concurrent-`busy` branches remain operator-deferred
Address code-review findings on the ADR 0045 named-pipe port: - Accept loop: hand the connected client to handle_connection before creating the next instance, and recreate failed instances with a capped exponential backoff instead of returning — a transient create/connect failure no longer drops a live client, permanently kills the channel, or hot-spins on a dead instance. - CLI cold start: replace the single instant post-launch retry with a bounded ~15s poll so `mnema` commands connect once the launched app binds the pipe (cmd /C start returns immediately, unlike macOS open). - Fix UB: read TOKEN_USER out of the 1-byte-aligned Vec<u8> with read_unaligned rather than forming a misaligned reference (both the desktop and CLI SID helpers); add the missing null-pointer guard to the CLI copy. - Drop the dead 2s timeout wrapping the synchronous ClientOptions::open. - Cap the CLI authorization-response read at 64 KiB (both transports) so a hostile pipe server cannot exhaust CLI memory. - Treat an empty MNEMA_CLI_ACCESS_PIPE_NAME override as unset on both sides; add it to turbo.json passThroughEnv. - Reuse the already-resolved SID in start() instead of a second token lookup; gate the ungated Windows-only CLI helpers with cfg(any(test, windows)); drop the unused tokio UnixStream import and the orphaned current_onboarding_state_for_app; gate the duplex framing test for non-unix/non-windows targets. - Docs: update SUPPORTS.md CLI-sidecar row for the Windows externalBin bundling and expand the Broker row's operator-deferred branch list. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D26X5WExsFFkakXxY9o7Zi
…-channel feat(windows): Broker Authorization Channel via named pipe (ADR 0045)
…ths (#77) Fixes the 25 Windows-only test failures (13 app-infra, 12 mnema) and the production bugs they exposed: - app-infra workspace DB-prefix matching used a literal '/' terminator while Windows rows store '\': hidden-segment-workspace classification saw zero references (premature-reclaim hazard for workspaces still referenced by incomplete batches / nonterminal OCR), frame-equivalence dedupe never fired, and frame-preview related-frame lookups returned empty. All prefixes now use std::path::MAIN_SEPARATOR (no-op on macOS). - SegmentPlanner embedded the literal YYYY/MM/DD date prefix into paths, so planner-derived strings (persisted rows, active-workspace set) carried mixed separators and never string-matched read_dir-scanned paths; date_dir now joins the prefix component-wise. - The desktop test seam around the video-preview extractor had two competing guards (assert-based in app_infra.rs, serializing in frame_preview.rs); concurrent installs collided, poisoned the seam mutex, and a panic-in-Drop double panic aborted the whole test process (0xc0000409) in ~40% of parallel runs. One shared serializing poison-tolerant guard now lives at frame_preview.rs module level, installed outside async blocks (the serialization MutexGuard is !Send). - MicrophoneVadRuntime::default() eagerly loads Silero -> ort; under the Windows app's ort/load-dynamic an unset ORT_DYLIB_PATH fell through to LoadLibrary(onnxruntime.dll) from the system search path, deadlocking the loader on hosts with a Windows ML copy (the real cause of the headless CI windows_system_suspend hang, previously misdiagnosed as a WGC/WASAPI restart). capture-vad gains a dynamic-ort feature (enabled only from the Windows desktop target): Silero now fails fast to the non-ORT fallback when ORT_DYLIB_PATH is unset. - Test fixtures build platform-correct paths (component-wise joins, tempdir anchors, screen_segment_extension()), settings tests derive OCR expectations through the production normalization, and the transcription-unavailable notification test tracks the Transcription settings tab. cargo test on Windows: app-infra 528/0, mnema --lib 772/0 (6/6 parallel repeats, with and without ORT_DYLIB_PATH), capture-runtime 21/0, capture-vad 21/0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D26X5WExsFFkakXxY9o7Zi
) The name-filtered desktop-crate test step existed only because of the pre-existing Unix-path failures and the headless windows_system_suspend hang; both are fixed (see previous commit). The step now runs the complete mnema --lib and app-infra suites, re-including windows_system_suspend. No ORT_DYLIB_PATH is staged: with it unset the Silero adapter fails fast to its non-ORT fallback, and no test runs real ORT inference. The step is bounded at 30 minutes so any future headless hang fails visibly and fast. SUPPORTS.md: flip the Windows CI row to done — the workspace check covers the desktop crate (workspace member), and bun run check stays macOS-only since svelte-check output is platform-independent. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D26X5WExsFFkakXxY9o7Zi
Audited every Windows checklist item against the actual code on this branch. 48/56 checkboxes were accurate; 8 lagged behind code that had already landed (all under-claims, never over-claims). Flip them and fix one doc-text error: - Active app/window metadata, Exclude Current App: implemented (ADR 0043 metadata path + SetWinEventHook listener; tray item cfg-gated to macOS in status_bar.rs) -> [x] - App candidate discovery + icons: icon materialization done, discovery N/A by ADR 0025 -> [~] - Capture output format: decided + built (.mp4/.m4a, extension-agnostic SegmentPlanner) -> [x] - Audit cfg stubs: Windows builds/tests clean in windows-check CI, only dead-string cleanup remains -> [~] - System-audio capture, audio/video writers umbrella: fully wired (WASAPI loopback; Media Foundation stack per ADR 0024) -> [x] - Verify Open Captured URL: code present + platform-agnostic, only on-device confirmation outstanding -> [~] - Fix L130 wording: tauri.windows.conf.json keeps the CLI sidecar externalBin (does not drop it), matching the CLI-sidecar row - Bump Last reviewed to 2026-07-04 The three verify-* items (deep links, tray, global shortcuts) stay [ ]: their code is implemented but they are genuine on-device tasks. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GhLt1pWM5VyKNGMb4WAzpW
…y on macOS" capture stubs The Windows checklist was corrected on 2026-07-04, but the summary table (rows 15-51) still lagged the landed code. Reconcile the Windows column against verified source: - Browser URL metadata [ ]->[x]: UIA strategy shipped (ADR 0044, native_capture_browser_url_uia.rs); table still said "deferred/unsupported". - Audio decode to mono PCM [~]->[x]: the media-decode seam is wired into all Windows consumers (Whisper, Parakeet, speaker-analysis, system-audio), not "only system-audio ... so far". - Inactivity detection [~]->[x]: GetLastInputInfo-backed per-family pause/resume is implemented (native_capture_system_idle.rs). - Ask AI / User Context: keep [~] (cloud keys genuinely still macOS-only) but disambiguate the AI provider key store from the Capture Index key store, so the "see that row" pointer no longer misimplies Windows cloud keys work. Also tighten the residual dead capture stubs in crates/capture-screen/src/lib.rs: the non-macOS error paths that route through Windows adapters at runtime were gated cfg(not(macos)) and reported "supported only on macOS". Narrow the five Windows-dead stubs (start_capture_session, pause/resume_system_audio_writer, pause_screen_outputs_for_inactivity, resume_screen_outputs) to Linux-only and correct all eight messages to "not supported on this platform". Every caller of these free functions is macOS-gated, so the Windows build is unaffected (cargo check -p capture-screen green). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WvmQhiAPWJQvFSYCiWrXkF
Brings in 270 commits from main: Deepgram cloud transcription (ADR 0047/0048), MCP tool connectors incl. OAuth (ADR 0051), fractional frame rates (default 0.5 fps), the macOS capture-suspension model for unexpected stream stops, Lucide icons via unplugin-icons, the onboarding permissions factory, and the release.mnema.day updater feed. Conflict resolutions of note: - audio-transcription manifest: kept the platform-gated `models` vec (Windows hides Apple providers + full-precision Parakeet) and appended main's two Deepgram descriptors. - desktop_processing_registry: takes main's `deepgram_auth_status` param AND returns this branch's `(registry, gpu_state)` tuple; both managed states are installed. - lifecycle.rs: adopted main's `suspend_screen_after_unexpected_stop` path in tick_inactivity; kept both cfg-disjoint test modules (windows transient-liveness + macos suspension). - Icon.svelte deleted (main); the gpuAcceleration glyph maps to lucide/cpu in section-icons.ts; IconName now comes from groups.ts. - Onboarding: ported the Windows `permissionPermitsCapture` count into main's new onboarding-permissions.svelte.ts; main's new stepper/jump-to-blocker code uses the platform-filtered feature list. - updater endpoint: main's release.mnema.day + this branch's Windows passive installMode. Semantic (compile-level) merge fixes: - Ported fractional f64 frame rates through the Windows capture stack: WGC frame cap (frame_schedule.rs), MF sink-writer frame-rate ratio (round(rate*1000)/1000), default-bitrate math, win_smoke example, and the desktop segment-start path. 0.5 fps now works on Windows instead of truncating to 0. - Windows metadata refresh publishes through main's snapshot-history ring (publish_snapshot) instead of the removed latest_snapshot field. - capture_retention test uses main's CaptureDb::single constructor. - Gated 8 of main's new MCP tests to macOS: they dial real `sh -c` fixtures and/or hit the platform keychain, which is macOS-only (SUPPORTS.md marks MCP "Windows unaddressed"; the MNEMA_MCP_SERVER_SECRET_DIR fallback is cfg(test)-internal to app-infra and compiled out for these tests). Verified on Windows: cargo check --workspace --all-targets clean; mnema --lib 905/905, app-infra 633/633, capture crates green; svelte-check 0 errors; bun test 743/744 (the one failure spawns node, which this box lacks — passes in CI). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NyVrNDANGmVgbR1WTyEHHY
…d spawn via the parent PATH The bogus-PATH test asserts Unix executable resolution (the child's PATH env decides the spawn). On Windows, CreateProcess resolves `node` through the parent's search path regardless of the child PATH override, so the CI runner's installed Node is still found and the assertion fails (the one remaining windows-check failure on the merged tree, 904/905). The login-shell PATH mechanism these tests guard is Windows-unaddressed per SUPPORTS.md, matching the sibling MCP test gates from the merge. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NyVrNDANGmVgbR1WTyEHHY
…der and MCP secret stores
Add a shared #[cfg(windows)] backend (windows_credential_store.rs):
CredReadW/CredWriteW/CredDeleteW with an RAII CredFree guard, the
capture-index conventions ({service}:{account} target, app-id user
name, CRED_TYPE_GENERIC, CRED_PERSIST_LOCAL_MACHINE), ERROR_NOT_FOUND
mapped to absent/no-op, Zeroizing around plaintext on write, and the
2560-byte CRED_MAX_CREDENTIAL_BLOB_SIZE cap surfaced as a named
pre-write error.
ai_provider_key_store and mcp_server_secret_store consume it through
their existing platform free-function seams; their unsupported stubs
narrow to not(any(macos, windows)). This unblocks Ask AI / User
Context cloud keys, the Deepgram key, and bearer/OAuth MCP connector
secrets on Windows.
cargo test -p app-infra: 636 passed, 0 failed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018s32P9jwDFquLkFpoud9Wu
…dows via Job Objects Wrap the spawned connector child in process-wrap JobObject + KillOnDrop under #[cfg(windows)], the sibling of the Unix ProcessGroup::leader() wrap. KillOnDrop is required, not optional: process-wrap only sets JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE when that wrap is present, and that limit is the backstop that kills the tree when rmcp's spawned kill task never gets to run. Adds a Windows grandchild-kill test mirroring the Unix one (launcher powershell spawns a hidden sleeper, writes its PID, test drops the transport and asserts the grandchild dies). Verified by negative control: with the wrap disabled the test fails with a surviving grandchild. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018s32P9jwDFquLkFpoud9Wu
…ed on-device Add an #[ignore]d, env-gated measurement test that drives the production SemanticSearchEmbedder on the CPU backend over a mixed document/query workload and reports wall time, throughput, per-text latency, CPU% (GetProcessTimes / getrusage) and RSS (K32GetProcessMemoryInfo). Target-gated dev-deps only. Run on Windows 11 x86_64 against the real on-device nomic-embed-text-v1.5 model (release build): 5.33 texts/s batched (~7.6k chars/s) at ~7.2 cores, 0.76 s model load, 542 MiB working set after load (1052 MiB transient peak), ~24 ms per query embed. This discharges the ADR 0037 gate for claiming Windows Semantic Search support; the static vec0 link is already exercised on Windows by the app-infra suite. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018s32P9jwDFquLkFpoud9Wu
…adapters exist capture-screen: reword the frame-index rebuild-from-video error to platform-neutral (the path is genuinely unported off macOS and is reachable via the offline repair bin), and tighten the dead update_active_privacy_filter stub out of the Windows build (not(any(macos, windows))), matching its sibling stubs. Frontend: the video-fallback preview message in +page.svelte now matches the backend "macOS and Windows" reality, and twelve settings strings saying secrets live in the "macOS keychain" now say "system keychain" - on Windows they live in Credential Manager. cargo check -p capture-screen and svelte-check (1011 files) clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018s32P9jwDFquLkFpoud9Wu
…nd (secret-gated) Replace the post-build no-op stub with a real seam that fixes its own documented ordering flaw: bundle.windows.signCommand invokes scripts/windows-sign.ps1 per produced binary DURING bundling, so Authenticode lands before tauri emits the minisign -setup.exe.sig and the updater signature covers the signed bytes (verified against the tauri-bundler source: NSIS signs in nsis/mod.rs before the updater step runs). The script no-ops with a notice when AZURE_SIGNING_ENABLED is not "true" (unsigned preview posture unchanged), and when enabled requires all six AZURE_* values, signs via the TrustedSigning PowerShell module (Azure Artifact Signing) with an RFC 3161 timestamp, verifies the result, and fails hard on any error - never silently unsigned when signing was requested. release.yml passes the Azure secrets into the build step and replaces the old stub with a secret-gated Get-AuthenticodeSignature assertion; docs/release-process.md describes the seam, the six secrets, and the provisioning path (Azure Artifact Signing; individual accounts are US/Canada-only). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018s32P9jwDFquLkFpoud9Wu
… verification, signing seam Summary rows: Semantic Search Windows [~]->[x] with the on-device candle-CPU measurement numbers (ADR 0037 gate discharged); Ask AI / User Context / MCP OAuth rows updated for the Windows AI-provider and MCP-secret Credential Manager backends (Linux is now the only key-store-blocked platform, and the OAuth row documents the 2560-byte Credential Manager blob cap); MCP login-shell PATH marked [x] (deliberately N/A on Windows - GUI processes inherit the full user PATH) and node detection [~] (works via inherited PATH, on-device exercise owed); release row describes the signCommand seam. Checklist: cfg-audit and remove-only-macOS-errors items closed out; a new item records the shared windows_credential_store backend; the Ask AI verification item now points at the provider key store that unblocks it; the signing item describes the seam and the remaining Azure Artifact Signing provisioning gate. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018s32P9jwDFquLkFpoud9Wu
…d-final-gaps # Conflicts: # SUPPORTS.md # apps/desktop/src-tauri/Cargo.toml
The macOS blur-purge exists because WKWebView retains one decoded IOSurface per asset URL with no working-set ceiling and macOS swaps rather than purges under pressure. WebView2/Chromium has no such pathology: its decoded-image cache (cc/tiles) is a fixed working-set budget with LRU eviction, and Blink's MemoryCache is pruned by a MemoryPressureListener. There is also no WebView2 API that drops the in-RAM decoded-image cache (ClearBrowsingDataAsync clears disk cache only; SetMemoryUsageTargetLevel is a coarse best-effort process hint). So the shared handle_window_event blur seam already calls purge_webview_memory_cache_on_blur, which no-ops off macOS, and that is correct. Flip the Windows cell to [x] with the deliberately-N/A note, following the login-shell-PATH row precedent. No code change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mEwzvhwvq63d46EqR2wqh
The snapshot-interval capture-rate control lowered the screenFrameRate floor to 1/60 fps (one snapshot per minute). The macOS ScreenCaptureKit backend was updated so long intervals stay exact; the Windows Media Foundation backend had the same latent imprecision. Actual Windows frame pacing is already exact — `frame_cap_min_interval_ticks` is pure f64 division (1/60 fps -> 600_000_000 ticks = 60s). But the `MF_MT_FRAME_RATE` metadata ratio encoded the rate on the numerator side (`(rate * 1000).round() / 1000`), which truncated 1/60 fps to 17/1000 (0.017 fps, ~2% off) instead of the exact 1000/60000. Move the scale to the denominator (ms-per-frame) side so the full 1/60-10 fps ladder lands exactly, mirroring the macOS CMTime value-side fix. Add regression tests: frame_cap_min_interval_ticks(1/60) == 600M ticks, and frame_rate_ratio exactness across the ladder (1/60, 0.5, 1, 10). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011mEwzvhwvq63d46EqR2wqh
…al-gaps Windows: key-store ports, MCP Job Object teardown, semantic-search verification, signing seam
The first Quick Recall summon called WebviewWindowBuilder::build() synchronously inside toggle_quick_recall_window, which is reached from summon_quick_recall_window_command (a sync command) and handle_global_shortcut (an event-loop callback) — both contexts the open_new_app_window comment documents as deadlocking WebView2 controller creation on Windows. The deadlock wedged the main event loop and cascaded into a botched graceful exit, leaving a zombie process holding the single-instance lock (windows won't open, no page shows data). Mirror open_new_app_window: on Windows build + summon on a spawned thread so the main loop stays free to drive WebView2 creation. Other platforms keep building inline (macOS must run its Cocoa panel config on the calling main thread). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBK1EGsD6WVN2JAFvomEwu
macOS uses the native overlay title bar (traffic lights drawn over the webview), but Windows has no overlay equivalent, so the main window was showing native decorations *plus* the custom `.titlebar` header (double chrome) with no window controls in the custom bar. Make an `overlay_title_bar` window frameless (`decorations: false`) on Windows/Linux while keeping native decorations + overlay on macOS. The frontend now draws the whole title bar, including its own minimize / maximize-restore / close caption controls (new WindowsCaptionControls component), and drops the macOS traffic-light left inset on Windows so the controls hug the top-right corner. - windows.rs: `decorations = config.decorations && !overlay_title_bar` on non-macOS; drop the now-stale dead_code allow on the field. - WindowsCaptionControls.svelte: min/max/close via getCurrentWindow(), maximized state tracked via isMaximized() + onResized. - +layout.svelte: render caption controls on Windows; `.titlebar--windows` trims the left inset and hugs the right edge. - capabilities: allow window minimize/maximize/unmaximize/toggle-maximize/ is-maximized/close. Only the Main window sets `overlay_title_bar`, so it is the only window whose behavior changes. svelte-check + `cargo check -p mnema` green. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBK1EGsD6WVN2JAFvomEwu
The JPEG snapshot export (the frames shown in the timeline/Recall) ignored the configured screenFrameRate on Windows and fired every second regardless of the capture-rate setting. screenFrameRate gated only the H.264 encode via should_drop_frame; the snapshot export decision used the bare 1s DEFAULT_SCREEN_FRAME_EXPORT_INTERVAL floor. WGC delivers change-driven frames up to the refresh rate, so a snapshot landed every second whenever the screen changed. macOS is unaffected because ScreenCaptureKit is told to deliver samples only at screenFrameRate, giving it a max(1/rate, 1s) snapshot cadence; Windows had no equivalent, so it diverged even at the 0.5fps default (macOS every 2s, Windows every 1s). Add effective_frame_export_interval(floor, rate) = floor.max(1/rate) and thread the frame rate into windows_frame_export_runtime so the Windows export interval matches the macOS max(capture_interval, 1s floor) semantics. Set once at construction and preserved across segment rotation. The internal screen- activity sampler stays at 1s (change-detection, not user-facing snapshots). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PBK1EGsD6WVN2JAFvomEwu
Windows support — main / umbrella PR
This is the main Windows-support PR. It carries the complete Windows recording pipeline (tracking issue #85): runtime capture (screen + microphone + system audio) plus the media-processing seams that consume the captured artifacts — transcription, speaker analysis, speech-activity gating, exact previews, and scrub previews — at parity with macOS, minus the deliberately deferred items at the end.
Status — not merging yet: This PR stays open and is not being merged into
mainright now. It is the long-lived integration home for Windows support, which is being assembled feature by feature; the merge intomainhappens only once all the features are in place. The per-feature status index below records what is implemented (with its commit) and what is still open — it grows as more Windows features land on this branch.Status index
✅ Implemented, verified, and closed
Each item below was verified against the branch code — and, where practical, by running the crate's tests on Windows — and its tracking issue has been closed. The on-device / HITL verification box on each lives in the consolidated operator smoke pass (#84).
de880a1f34a4a5.mp4as a first-class visible-segment container (.movsweep)e455905media-decodeseam crate (MF Source Reader), via system-audio speech activity8f6a49df67b3faonnxruntime.dllpackaging5e0695c97b8745a2292edc13c095🔓 Open / deferred (tracked separately)
.m4ashorter than wall-clock) are implemented and unit-tested; the physical operator pass is deferred.Foundation (landed earlier on this branch, issues already closed)
What's delivered (detail)
Foundation: platform gating, packaging, screen capture MVP (#44–#50)
@tauri-apps/plugin-os), cross-platformbun/Node sidecar prep replacing the Bash script, macOS-specific capture/privacy/UI paths gated so shared desktop code compiles on all platforms, and a Windows CI compile job.ScreenCaptureSessiontrait seam; Windows primary-monitor capture to.mp4via WGC + Media Foundation H.264 (Windows runtime capture: record primary monitor to a single .mp4 #45), live ~60 s segment rotation with the WGC frame-lookahead fix (Windows runtime capture: live 60s segment rotation (gapless) #46), ~1 fps JPEG frame export throughScreenFrameArtifact(Windows runtime capture: 1 fps JPEG frame export + ScreenFrameArtifact #47).resolve_stream_resolutionmath;MF_MT_AVG_BITRATEfromcompute_effective_screen_bitrate_bps.RecreateonContentSizechange, stop errors flip shared liveness, lifecycle marks sessionsFailedonGraphicsCaptureItem.Closed..mnemastate dir ignored.Audio capture (#51–#58)
.m4asegments through the audio capture seam (Windows microphone capture to openable .m4a segments (audio capture seam) #52).ms-settingsdeep link (Windows microphone permission UX (graceful denial + ms-settings deep link) #54); VAD PCM feed + peak-since-last-poll activity samples (Windows microphone VAD PCM feed + peak-since-last-poll activity samples #55).system_audio_requires_screencapability (Windows system-audio loopback capture as an independent source #56, Decouple system audio from screen via system_audio_requires_screen capability (ADR 0022) #57 — ADR 0022), following default render-endpoint changes (Windows system-audio follows default render-endpoint changes #58).Inactivity & transient liveness (#59–#64 — ADR 0023)
GetLastInputInfoidle tracking + screen/audio activity snapshots (Windows inactivity inputs: GetLastInputInfo idle + screen/audio activity snapshot #59); per-family inactivity pause/resume with a shared start-segment primitive (Windows per-family inactivity pause/resume + shared start-segment primitive #60); fixedresume_user_captureand revealed the pause/resume control (Fix Windows resume_user_capture and reveal the pause/resume control #61).Media processing (#73–#83 — ADRs 0024/0025)
.m4anever contains the idle tail (Windows audio writer tail hold-back: committed Audio Segments never contain the inactivity tail #74)..mp4as a first-class visible-segment container —.mov-assumption sweep (Make .mp4 a first-class visible-segment container on Windows (.mov-assumption sweep) #75).media-decodeseam crate (Media Foundation Source Reader), proven through system-audio speech activity (media-decode seam crate: MF audio decode wired through system-audio speech activity #76); per ADR 0024, MF is the only Windows media backend.onnxruntime.dllpackaging (Parakeet on Windows: int8-only offering + onnxruntime.dll packaging #80), MF video frame extraction for exact previews + finalized-video validation (MF video frame extraction: exact frame preview + finalized-video validation on Windows #81), Sherpa speaker analysis via a decode + subprocess helper (Sherpa speaker analysis on Windows (decode + subprocess helper) #82), scrub preview generation for screen segments (Scrub Preview Generation works for Windows screen segments #83).Smoke harness & docs (#84, #85)
docs/windows/on-device-capture-smoke-runbook.md). The HITL pass itself (session-lock, system-suspend, inactivity) stays open as On-device Windows capture smoke pass: session-lock, system-suspend, inactivity #84.SUPPORTS.md/docs/windows/supports.mdupdates, Windows research docs, packaged-app Sherpa/ONNX DLL bundling, and a follow-up fix for system-audio permission handling + H.264 video processing.Decisions (ADRs — do not re-litigate)
Explicitly out of scope / deferred
Testing
cargo fmt --check,cargo check/cargo testforcapture-screen,media-decode,audio-transcription(local-whisper/parakeet-onnx),speaker-analysis(sherpa-onnx), and the desktop Tauri crate;bun run check;bun scripts/prepare-mnema-cli-sidecar.mjs debug.