Skip to content

feat/load your own recording - #14

Merged
ribdsp merged 4 commits into
mainfrom
feat/load-your-own-recording
Aug 30, 2026
Merged

ribdsp merged 4 commits into
mainfrom
feat/load-your-own-recording

Conversation

@ribdsp

@ribdsp ribdsp commented Aug 29, 2026

Copy link
Copy Markdown
Owner
  • feat: read a recording from a file the person chose
  • feat: offer the file loader from the picker and the empty stage
  • docs: say how to load a file, and how to record one worth loading

ribdsp added 3 commits August 30, 2026 06:02
Loading a recording that is not one of the three bundled samples currently
means copying JSON into traces/public/recordings/, adding a line to the
static manifest, and redeploying. There is no ingestion path at all — no
file input, no drop target, no query parameter.

The three lines that put a recording into the store already exist and are
already shared by both callers, so this adds a second source for them
rather than a second way to load:

  useFileLoader — sibling of useSampleLoader, same three fields. A size
  guard before file.text() so a mis-dropped multi-gigabyte file is refused
  in a sentence instead of freezing the tab; JSON.parse's own SyntaxError
  passed through unwrapped, because it names the byte offset it gave up at
  and "invalid JSON" does not; loadingName always cleared in a finally.

  deriveRecordingName — pure, tested, and separate because both halves of
  its result travel: label into the activity feed, id into a localStorage
  key and into recordingId in a tool response. A file name is the one
  string here supplied verbatim by a person and re-validated by nothing
  downstream, so it is reduced to a closed character set once.

  SampleLoadError becomes RecordingLoadError and gains source. Both
  components ended a failure with "Samples live in
  traces/public/recordings/" — the right next step for a missing sample,
  and advice that sends someone to look in this repo for their own file.
  A failure has to be able to say which of the two it was.

Also installs a window-level dragover/drop preventDefault. Without it,
dropping a JSON file anywhere on the window navigates the tab to
file:///…, discarding every marker and hypothesis on the page. That guard
is not part of the drop feature and stays whether or not the affordance
does.

No confirmation dialog before replacing an open recording: the store's
loadRecording resets to initialState by design, loading a sample already
does exactly this with no prompt, and prompting on one path of two would
teach that the two differ.
Two entry points onto `useFileLoader`, plus the drop hazard that exists
whether or not a drop target does.

The picker grows a fourth listbox row. It has to live inside the `<ul>`:
the panel's key handler closes on Tab, so a control after the list would
be unreachable by keyboard. That also forced one source of index truth —
`activeIndex`, `aria-activedescendant`, the scroll effect and the End
bound each indexed `SAMPLE_RECORDINGS` separately, and index 3 was
`undefined`, which would have pointed `aria-activedescendant` at no
element. They now derive from one `ROWS` array.

The empty stage grows a matching row and is itself the drop target, since
someone arriving with a file they just recorded has no reason to guess
the header holds a menu.

Dropping a file on the window navigates the tab to it by default,
discarding every marker and hypothesis in the session. `useFileLoader`
cancels that at the window level, so a missed drop does nothing.

Each component tracks which of the two loaders it last used. Without it a
failed file left its alert on screen next to a sample that had since
loaded fine, because each hook only clears its own error. The remedial
hint branches on the same fact: pointing someone at this repository to
find their own file is the wrong next step.
"Drop the downloaded JSON into Traces" described nothing that existed.
It now names where the control is.

Adds what `npm i rrweb` plus `record()` leaves out, verified against
bugbait/src/lib/record.ts rather than assumed: the checkout interval, the
fetch patch, the console-event normalisation, the user-agent stamp, and
`maskAllInputs`, which is not a preference — without it the recording
holds the password and the card number in plain text.

Also the gap: a fetch patch sees fetch only, so XMLHttpRequest traffic is
invisible to read_network and shows as an empty timeline rather than an
error. Every fixture here uses fetch, so none of them expose it.

Cost is quoted from the three sample files rather than estimated, and the
sandbox claim about replaying someone else's recording is the one already
recorded in replay-engine.ts from a spike.
@vercel

vercel Bot commented Aug 29, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
traces Ready Ready Preview Aug 30, 2026 11:36am

…yer chrome

Two changes arrived together in one working tree. They are kept in one commit
because page.tsx carries both, and splitting them by hand would leave an
intermediate commit that does not build.

error-toast.tsx is new: reportError/reportSuccess plus one <ErrorToasts /> mount,
so a recording that fails to load says so where the eye already is rather than
only inside the panel that asked for it. The sample loader, the file loader and
the replay stage all route through it.

The rest is chrome. Activity rows are tinted by author and carry an icon chip, so
who did what survives video compression; their timestamps now tick in live
seconds, because a counter that jumps to "1m ago" and then sits still reads as
frozen while an agent is working. The scrubber track goes from 4px to 6px and the
handle from 11px to 13px, centred on the track rather than aligned to its top, so
it can be aimed with a mouse and seen at 1080p. The empty state names ChatGPT and
Chrome beside their icons.

tsc clean, 303 tests green across 25 files.
@ribdsp
ribdsp merged commit d780578 into main Aug 30, 2026
4 of 5 checks passed
@ribdsp
ribdsp deleted the feat/load-your-own-recording branch August 30, 2026 11:38

This branch was successfully deployed

1 active deployment
Preview — ad4aa346 Deployed Aug 30, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant