Record & replay
A recording is not a video and not a log of what a test did. It is the whole event stream, appended raw, plus periodic graph keyframes — so replaying it rebuilds the identical graph through the identical reducer, and every consumer surface works on it unchanged.
What a recording is
A .sqrec file is an append-only container holding the raw protocol frames a session produced, interleaved with periodic graph keyframes so a scrubber can seek without folding from the beginning. It is a black-box recording: everything the app emitted, not a curated subset chosen at record time. That is the premise — you cannot know at record time which event will turn out to matter.
Replay feeds those frames into a normal client. Not a special replay-mode client — the client, with the same fold path a live session uses. Which is why the inspector, the analytics screen and the performance center all work on a recording without knowing they are looking at one.
Inside the container
- Preamble
- A
SQRECmagic number and a format version. Truncated tails — a crash mid-append — are tolerated on read, and unknown record types are skipped, so a file written by a newer build is diagnosable rather than a parse error. - Header
- Session identity, the wall-clock anchor, the protocol version, and the surface that armed it —
cli,desktopormcp. Provenance is first-class here rather than buried in a metadata bag, because three front doors share one directory. - Frames
- The raw protocol frames, verbatim. No re-encode, so there is no second schema to drift from the first one. Each carries its offset on the session-monotonic event clock, never on wall-clock — which is what makes two replays of the same file identical.
- Keyframes
- A faithful snapshot of the consumer graph plus the index of the last frame folded into it, cut every five seconds and at stop. A seek restores the nearest one and folds forward from exactly the next frame, which is why scrubbing a long session is instant.
Recording
squiggle record # until Ctrl-Csquiggle record checkout --duration 60squiggle record -o ./repro.sqrecThe desktop’s ○ Record button in Simulator Studio arms the same recorder on the live session, and squiggle_record_start does it from an agent. All three write to .squiggle/recordings/ and stamp the producing surface into the file header, so a shared directory stays honest about who recorded what.
Recording is relay-first, like everything else — it coexists with an open Studio rather than fighting it for the port. When idle, the tap that watches for frames is a single no-op branch, so an armed recorder costs nothing until it is recording.
Replaying
squiggle replay # list available recordingssquiggle replay checkout # by namesquiggle replay ./repro.sqrec --speed 2squiggle replay checkout --assert flows/checkout.yamlDefault speed is max — there is no device to wait for, so the only reason to pace a replay is that a human is watching it.
Replay Studio
The desktop app opens the same file on a timeline: scrub (keyframe-restore then fold forward), play/pause, ×1/×2/×4/max, navigation markers, and a renders-per-second chart aligned under the track — all driving the reused inspector graph canvas rather than a second viewer written for recordings.
Its playhead is two-way scrub-synced with the Performance Center’s unified timeline: drag one and the other follows, so a frame drop in a chart and the interaction that caused it are one gesture apart.
Asserting against a recording
--assert re-runs a flow’s assertions against the recorded timeline. Action steps are skipped, because a recording is read-only. This is a genuinely different kind of check from a live run:
Two evaluation windows
- Positive assertions are evaluated over the whole timeline
route,visible,exists,storeandquerypass if they held at any point, and the report names the first event-time where they did. So a multi-screen journey validates even though no single frame satisfies every assertion at once.- Negative and window assertions stay end-state
notVisible,rerendersanddroppedFramesare evaluated at the end, because “never happened” and “happened fewer than N times” are not point-in-time questions.
${VAR} resolves from the flow’s env:, overridable with --env. The process exits non-zero on a failed assertion, so a recording can be a CI gate that needs no simulator.
Diffing two of them
squiggle diff a b answers “what changed between these two builds” with something other than a screenshot, and it needs no app running:
squiggle diff before.sqrec after.sqrecsquiggle diff before.sqrec after.sqrec --json | jq '.renders'It compares renders, routes, network calls, spans, actions and store mutations. Because both sides fold through the same reducer, a difference in the output is a difference in what the app did — not a difference in how two runs were observed.
What replay connects to
This is the part that is easy to miss: a recording is not a feature on its own, it is the substrate the rest of the product reuses.
- Authoring
squiggle flow --from-recordingderives a draft flow from a journey you drove by hand — including a comment listing the taps nothing durable identified.- Behavioural diff
squiggle diff a bcompares two recordings — renders, routes, network, spans, actions, mutations — with no app running. This is how “what changed between these two builds” gets an answer that is not a screenshot.- Agent gating
- Before an agent actuates anything, its program is checked against the newest relevant recording: do these assertions hold in territory we have already seen? A program aiming at a screen that does not exist is stopped at that tier, with zero live actions. If no recording covers the territory, the gate degrades to static-only and says so — it never silently passes.
- Inferred contracts
- What held across every green recording of a screen becomes a contract nobody wrote and nobody maintains. A violation is reported as
contract_driftnext to the goal, with its confidence (“held in 3 of 3” reads differently from “40 of 40”), and never as a hard failure — these are inferred, so they can be wrong. - Re-projection
squiggle reprojectfolds recordings back into the perf store and checks the store agrees with the canonical log. The projection is derived, so it can be rebuilt — and a mismatch is a bug worth catching.- Deterministic sessions
squiggle_deterministic_sessionpins the conditions that make two replays comparable, and named fixtures restore app-data preconditions, so a comparison is not confounded by the app starting from somewhere else.
Where they live
Recordings land in .squiggle/recordings/ (gitignored — they are machine-local evidence, not source), and each finished file is indexed into perf.db’s recordings table so the desktop, the CLI and an agent can list them without walking the directory.
Related: Performance reads the same store, and Agents & MCP explains the gate pyramid recordings feed.