Get the spoilers for every session replay.

Stop watching replays. Spoiler reads them for you and tells you what each user was trying to do, and where they got stuck.

Try it out

Your session replays already know how people use your product. Getting it out means hours of watching, or adding events everywhere. Spoiler turns every recording into tasks, steps and friction you can read, search and query.

How it works

In depth

From how your product works to how people use it.

  1. spoilervocabbuild

    Maps your product

    Point it at your source files and a model drafts a YAML feature map: your pages, controls and domain rules, each citing the line it came from. Review it once, reuse it for every session.

  2. spoilercompile

    Compiles the recording

    The rrweb recording becomes a trace: one line per action, matched to the feature map, with the server's answer and the timing. Plain code, no AI.

  3. spoileranalyze

    Writes the report

    A model groups the trace into tasks and friction. Code checks every claim against the trace and drops what it can't back.

1 · The feature map

What it learns from your code.

vocab build reads the files that define your routes, controls and rules, and a model drafts the map. Every entry cites its source line. What the sources leave open is listed as a gap, never guessed.

members.tsx
<invite.Form method="post" action="/api/invites">  <input name="email" type="email" data-testid="invite-email" placeholder="name@company.com" />  <button data-testid="send-invite">Send invite</button></invite.Form>

Control · vocabulary.yaml

features:  - id: members.invite.send    surface: workspace.members    name: "Send invite"    matchers: { testid: [send-invite] }    source: "members.tsx:11"
members.server.ts
export async function sendInvite(workspace: Workspace, email: string) {  const seatsHeld = await db.member.count({ where: { workspaceId: workspace.id } });  if (seatsHeld >= workspace.seats) {    return data({ error: `Couldn't send invite: all ${workspace.seats} seats are in use.` }, { status: 402 });  }

Rule · vocabulary.yaml

terms:  - term: seat    means: "Every member holds a seat until removed, deactivated members included. Invites fail with 402 while all seats are held."    ui_labels: ["Couldn't send invite: all 5 seats are in use."]    source: "members.server.ts:4"

Gap

Billing page: billing.tsx is not among the sources, so seat purchases are unnamed. What the sources leave open is listed, never guessed.

2 · The trace

Every action, as one line.

compile replays the recording against the map: what was clicked, what the server answered, what changed on screen, and friction like repeated errors. Plain code, so the same recording always gives the same trace.

The trace · 22 s recording

Time Action Result
1.0 se2 Typed "priya@example.com" into Invite email
3.0 se3 Clicked Send invite 402 “Couldn't send invite: all 5 seats are in use.”
6.5 se5 Clicked Send invite 402 Same message
10.6 se7 Clicked Deactivate member for Ben Ortiz 200 Ben Ortiz: Active → Deactivated
13.1 se8 Clicked Send invite 402 Same message
17.1 se10 Clicked Remove member for Ben Ortiz Asked “Remove Ben Ortiz?”
19.6 se11 Clicked Confirm remove 200 Ben Ortiz removed
22.1 se12 Clicked Send invite 201 “Invitation sent to priya@example.com”

3 · The report

What they tried, and where they got stuck.

analyze hands the trace and the map to a model. Code then checks the answer against the trace: every claim must cite the actions behind it, and whatever the trace can't back is dropped.

What Spoiler wrote

Task: Invite priya@example.com to the workspace.
Outcome: Finished with a workaround · 21.1 s active · 8 actions

Problem 1: Send invite failed three times with 402 and "Couldn't send invite: all 5 seats are in use." e3 e5 e8
Hypothesis: the message doesn't say that deactivated members still hold seats.

Problem 2: Deactivated Ben Ortiz, then retried Send invite, which failed the same way. e7 e8
Hypothesis: the admin expected deactivating a member to free a seat.

Dropped by the check

“Rage-clicked Send invite after the first refusal.” No cited action carries a rage flag.

Install

Let your coding agent set it up, or install it yourself.

Use curl to read https://spoiler.sh/agents.md, then follow it to set up Spoiler in this project.

Then set it up in your project, or try it on a sample recording with no account or API key.