loupfeedSession replay

The minute before it went wrong.

loupfeed keeps a rolling buffer of the recent past in memory and throws it away continuously. Only when someone reports a problem — or something crashes — is that window frozen and uploaded. Sessions of people who never had a problem are never stored at all.

Masked by default · replaysSampleRate to sample · retention is an S3 lifecycle rule, not app code.

The capture model

Always recording. Almost never storing.

Events stream into a fixed-size ring in memory. The oldest frames fall off the back continuously, and nothing leaves the device on its own. That is the privacy win and the cost win at the same time — passive replay tools store everyone's session and hope you find the one that matters.

oldest frames discardednow
  • replayBufferSeconds bounds the window — 30s by default
  • A hard event and byte cap bounds memory on heavy pages
  • replaysSampleRate decides whether a given report carries a replay at all
  • Uploads are gzipped and chunked, and happen in the background after submit
// the buffer is running the moment you init import { replay } from '@loupfeed/core'; replay.isRecording() // true replay.snapshot() // freeze the window — called for you // by captureFeedback and on a crash // consent-gated jurisdictions: armed, but retaining nothing init({ dsn, replay: { requireConsent: true } }); replay.grantConsent();
Masking

Everything is hidden until you say otherwise.

The default posture is the opposite of most replay tools: mask everything, then widen deliberately. You reveal the product title you actually want to read, instead of hunting for the fields you forgot to hide.

maskAllText — every text node
maskAllInputs — every input value
Passwords — never overridable
recordCanvas — can leak rendered PII
network — bodies can carry tokens
console — opt in per level
// widen deliberately, element by element init({ dsn, replay: { unmask: ['.product-title', '.order-total'], block: ['[data-private]'], redactPatterns: [/\b[\w.]+@[\w.]+\b/g], maskInputOptions: { password: true, email: true }, }, });

Images and video marked data-private, and anything tagged data-fb-block, are recorded as empty placeholder boxes — the layout survives, the content does not. beforeSend is still the final scrub on the event envelope itself.

Mobile

Two recorders, because phones are not browsers.

rrweb is a DOM recorder and cannot run on React Native. Rather than pretend otherwise, loupfeed ships two mobile engines and lets you pick the trade-off — real pixels, or zero native dependencies.

Screenshot replay

Samples real screenshots at roughly 3 fps over a rolling ~12 second window, so the reviewer sees icons, images, backgrounds and actual input contents — video-like playback.

  • Recommended when you want to see what the user saw
  • Needs the react-native-view-shot native module
  • Not available in Expo Go — requires a native rebuild

Wireframe replay

Pure-JS snapshots of the view hierarchy — boxes plus optionally-masked text. Structure only, no pixels, and private by construction.

  • No native dependency at all — runs in Expo Go
  • Text masked by default; configureWireframeRecorder to reveal
  • Light enough to leave running continuously
// pick your recorder — nothing records until you do import { init, setReplayRecorder } from '@loupfeed/react-native'; import { screenshotRecorder } from '@loupfeed/react-native/screenshot'; init({ dsn, replaysSampleRate: 1.0 }); setReplayRecorder(screenshotRecorder);
With your own AI

A replay your agent can actually read.

Handing a coding agent a video file is useless. The get_replay MCP tool distills a recording into an action timeline — page text, moves, clicks, inputs, scrolls — so the agent reasons over what the user did instead of over pixels it cannot see. For mobile wireframes it returns per-frame summaries, and svg_frame renders any single frame as SVG.

get_replayaction timelinesvg_frameraw blob
# the same 58 seconds, as an agent sees them get_replay evt_8f21 0:41 scroll → “Order summary” in view 0:52 click → [data-fb=fb0a1b2c3d4e] ×3 0:52 network → (none) 0:58 end → no navigation, no error # → the button never fired a request. Look at its onClick.
Retention

The blob and the record have different lifetimes.

Replay blobs expire

A configurable TTL, enforced as an object-storage lifecycle rule rather than by application code that could forget.

Events are kept

The feedback or crash record is small and outlives its replay, so your history stays intact after the video is gone.

Storage is yours

S3 in your own account, a volume on your own box, or nothing but SQLite on a laptop while you develop.

The rest of the platform

Replay is one of four.

Turn it on before the privacy review, not after.

Masked by default means there is nothing to negotiate. Run it on your own infrastructure and the recordings never leave your account.