The SDK keeps a rolling window of what the app rendered in memory. When a report is
filed that window is frozen and attached; the rest is discarded. Nothing is
uploaded unless someone files a report, and there is no screen-recording
permission prompt — this is not ReplayKit.
Turning it on
Server-gated — enable it for the app in the dashboard, and the SDK picks it up
from GET /api/config on the next start.
There is no client-side switch for replay on iOS — the server gate is the only control. If you need it off for a particular build, turn it off for that app in the dashboard.
Format
iOS replays are traceitx-vtree-v1 — a frame timeline, not video. A
viewport, a list of frames, and per-frame operations on a lightweight node tree
carrying role, frame, text, bg, alpha, cornerRadius and masked.
That keeps a replay in the tens of kilobytes rather than the megabytes video costs, and it means text stays text — searchable, and redactable before it leaves the device. It also means no video encoder runs, which is the difference between a background buffer you can leave on and one you cannot.
Images
Image bytes are optional and content-addressed, keyed by the first 16 hex
characters of their sha256. Because the key is a content hash, dedupe is
inherent — the same asset across many frames costs one entry. The server’s asset
mode (off, bundled, all) decides whether they travel at all; see
Server config.
An unresolved image renders as a placeholder box rather than failing the replay.
Privacy
Views passed to markSensitive(_:) carry masked: true and paint as a solid
block. The masking happens in the producer, before the blob is written — masked
content never exists in the buffer. See Sensitive content.
Cost
Frames are captured on the UI thread’s cadence and diffed, so a static screen
costs almost nothing. replayDurationSec bounds the window; samplingRate
decides what fraction of sessions allocate a buffer at all — at 0.1, nine in
ten sessions never allocate one.