Two thin clients — the native SwiftUI menu-bar app (wrec-app) and the wrec CLI — talk to a local Rust daemon over the same Unix-socket JSON protocol. The daemon owns all recording state and drives a native Swift capture engine that does the actual pixel work: ScreenCaptureKit hands frames straight to AVAssetWriter, which writes the hardware-encoded .mov. Rust never touches frames.

Process boundaries

  1. wrec-app starts its channel's daemon. A CLI first launches the matching app when needed, then sends the request over that channel's socket.
  2. daemon resolves the target and settings, owns the queue and job lifecycle, and persists events and metrics.
  3. capture-engine receives one resolved recording job and owns ScreenCaptureKit, AVAssetWriter, system audio, microphone input, pause, resume, and finalization.
  4. The .mov file is written by AVAssetWriter while status and metrics travel back through the daemon to either client. Clean stops finish at the exact last sample; ten-second fragments bound media loss when finalization cannot run.
Clients stay thin The app and CLI own user interaction only. They do not import backend, macOS recorder, or capture-engine code.
One protocol for every client Both clients use the same newline-delimited JSON protocol. Dev, Nightly, and Release have different sockets, and the handshake rejects cross-channel connections.
Daemon owns recording state The Rust daemon is the source of truth for target listing, permissions, one active job, queued jobs, pause/resume/stop, history, metrics, and persisted events.
Frames stay native The daemon launches a separate Swift capture-engine process for each active job. ScreenCaptureKit feeds AVAssetWriter directly; Rust never copies, transforms, or encodes video frames.
Stops preserve media Normal controls, macOS Stop Sharing, capture failures, and catchable termination signals converge on writer finalization. Ten-second movie fragments keep an abruptly interrupted file playable through its last committed fragment.
Separate packages share one runtime shape The app package carries wrec-app, daemon, and capture-engine. The standalone CLI package carries wrec, daemon, and capture-engine.
The app owns permission The app requests Screen Recording directly. The daemon stays in its launch chain, and the CLI launches the matching app when needed, so internal helpers do not become user-facing permission entries.
Agent rule Use the CLI with --json, trust daemon job state, refresh target ids per task, and follow error.next for recoverable failures.