Skip to main content

session-replay

Inspect, troubleshoot, and extend Analytics session replay recordings.

Informações da origem

Repositório
BuilderIO/agent-native
Última atividade na origem
2 de outubro de 2026 às 14:50
Idioma detectado do SKILL.md
inglês
Estrelas
7.065
Forks
640

Opções de instalação

Por padrão, está selecionado o prompt que primeiro revisa a origem. Você pode mudar para um comando direto ou baixar uma cópia local.

Revise os arquivos de origem

Leia o SKILL.md e os arquivos complementares exibidos pelo SkillsMP antes de decidir se vai instalar.

Exibindo SKILL.md

SKILL.md
Instruções da origem · Visualização somente leitura
name
session-replay
description
Inspect, troubleshoot, and extend Analytics session replay recordings.
scope
dev
# Session Replay Use this skill when working on `/sessions`, replay ingest, replay storage, or agent answers about browser recordings in the Analytics template. ## Source Of Truth - Replay ingest writes `session_recordings` and `session_replay_chunks`. - The UI and agent must use `list-session-recordings`, `get-session-replay-summary`, and `get-session-replay-events`. - `/sessions/:recordingId` is keyed by `session_recordings.id`, not `analytics_events.session_id`. - Do not add actions that synthesize "sessions" from `analytics_events`. Events can be linked beside a recording through `session_id`, but they are not playable replay rows by themselves. ## Storage And Access - Never expose object-storage URLs or raw `session_replay_chunks` rows to the browser or agent. - Playback bytes must go through scoped server helpers that check `session-recording` access before reading private blob refs. - SQL inline chunks are a local/dev fallback only; production should use private or encrypted blob storage. - A local Analytics app pointed at a production database must also use the key that encrypted those replay blobs. Set `ANALYTICS_SECRETS_ENCRYPTION_KEY` in an untracked local env file; do not replace the workspace-wide `BETTER_AUTH_SECRET` just to read production replay storage. - When sharing a replay with an external agent, use `create-session-replay-agent-link`. It mints a two-hour `agent_access` URL scoped to the recording, embeds a small SSR discovery payload on `/sessions/:recordingId`, and advertises `/api/session-replay/agent-context.json` plus bounded `/api/session-replay/agent-events.json` and `/api/session-replay/agent-diagnostics.json` reads. - Do not make session recordings public just so an agent can inspect them. Tokenized agent links are the intended handoff path. ## Console And Network Capture - While recording, the core client (`session-replay.ts` in `@agent-native/core`) patches console (`log`/`info`/`warn`/`error`/`debug`), window `error` / `unhandledrejection`, `fetch`, and XHR, and emits rrweb custom events tagged `agent-native.console` and `agent-native.network`. - Capture is on by default whenever session replay is enabled. Tune or disable it with the `console` / `network` options on the session replay config; each accepts a boolean or an options object (`{ maxEvents?: number }`); `network` also accepts `captureErrorBodies` (default true) and `maxErrorBodyLength` (default 2048) to control the bounded 5xx response-body snippet. - Privacy bounds: request bodies and headers are never captured. Response bodies are captured only as a bounded, redacted snippet for 5xx (server error) responses, capped at `maxErrorBodyLength` chars; non-5xx and network-failure (status 0) responses never carry a body. URLs are scrubbed, messages are truncated, and recorder self-traffic (the replay ingest and tracking endpoints) is excluded. - Per-session budgets: 1000 console events and 2000 network events, with a truncation notice event once a budget is hit. - On ingest, `deriveReplaySignals` computes the real `errorCount` from tagged console events plus the additive `networkErrorCount` column on `session_recordings`. Keep new columns additive. ## App Events In Sessions - `trackEvent` also emits an `agent-native.event` custom event holding only `{ name }` (120 chars max, 1000 per replay, counted across page reloads). Telemetry names such as `pageview`, `action.response`, and `session status` stay unmarked; lifecycle aliases don't get a second marker. Never add event properties to the payload. - The replay viewer shows these markers, plus failed `/_agent-native/actions/<name>` requests as "Action failed", only while the Sessions triage Lab is on. With the Lab off the viewer and the agent timeline keep their earlier shape. - `recordAnalyticsEvents` writes the per-session event index (`analytics_session_events`) and a per-tenant coverage start inside the transaction that stores the events, in every sink mode. After that transaction commits it writes the daily catalog (`analytics_event_catalog_daily`) and each event's latest sighting (`analytics_event_catalog_latest`) best-effort, together in one short transaction, because the catalog lists events from the latest table. A catalog failure only warns, and the catalog never decides a filter. Lists, did/didn't filters, and the catalog read only these tables, never BigQuery. The catalog keeps the 1,000 most recently seen events, sorted by volume, and sets `truncated` when it cut the list; its app flags still count every event in the range. - Unique indexes hold caller text raw, so it must stay short enough for an index entry: index rows use hashed ids, event names and apps are cut to 200 and 100 characters, and a session id over 256 characters skips the index. Bound any new caller value before it reaches a key. - Event filters exclude a session if any of its recordings started before the tenant's coverage start, because one analytics session can span tabs. Coverage starts only after a session write succeeds, and the reported start is the latest among the viewer's tenants. "Didn't" also needs at least one index row for the session and no gap marker: a failed index write rolls back to a savepoint and records the batch's sessions in `analytics_session_event_gaps` in the same transaction, so a later successful batch cannot make them look complete. If the marker cannot be written either, the batch fails and its events are not stored. Keep session index writes inside that transaction. Deploys ship code before the scheduled migration creates these tables, so until `analytics_session_event_coverage` exists ingest stores events unindexed and warns: with no coverage, no session can read as complete. Reads in that window report no coverage instead of failing: no event names, a null coverage start, an empty catalog, and no session matching an event filter. That is the only unmarked gap, and it holds only while the coverage table is the last index table a migration creates. The retention sweep removes a session's index rows together, once all of them are two days past replay retention, and its gap marker after that. The BigQuery-cutover purge leaves these tables alone. ## Agent Diagnostics Surface - `buildSessionReplayAgentContext` includes a `diagnostics` section: up to 50 console entries and 50 network entries, errors/failures first, with totals and truncated flags. Agent-context instructions steer agents to diagnostics as the primary debugging signal. - The agent timeline includes `console-error` / `network-error` markers; error markers are kept preferentially under the 200-marker cap. - `apis.diagnostics` advertises the fuller bounded list: `GET /api/session-replay/agent-diagnostics.json?id=<recordingId>&agent_access=<token>&kind=console|network|all&level=<level>&limit=<n>&offset=<n>&fromMs=<n>&toMs=<n>` (limit defaults to 200, max 500). It uses the same recording-scoped `agent_access` token as the other agent JSON APIs. - `offset` and `fromMs`/`toMs` (inclusive offsetMs window) enable full enumeration of a session's captured entries: page with `offset`, or window with `fromMs`/`toMs` around a timeline marker's `offsetMs`. Providing any of these switches ordering to strictly chronological (no errors-first reshuffle) so pages are stable and disjoint. `total`/`errorCount`/ `warnCount`/`failedCount` reflect the filtered (windowed/level/kind) population, not just the returned page, and each kind's response includes `hasMore` alongside `truncated` so an agent can tell whether more entries remain. Route validation rejects negative/non-numeric `offset`/`fromMs`/ `toMs` and `fromMs > toMs` with 400. ## Dev Tools Panel - The `/sessions/:recordingId` replay player has a Dev Tools toggle that opens a panel with Console and Network tabs: filter chips, search, an error-count badge, and playback-time highlighting. - Rows expand inline under the selected line (Chrome-style). Expanding a row does not seek; use Jump to to move the playhead. Extend this panel instead of adding a separate debugging surface. ## Playback Viewer - Wait for all replay chunks (`isComplete`) before constructing the rrweb `Replayer`. Progressive chunk publishes should only update the loading bar; rebuilding the player mid-load desyncs the scrubber and playhead. - Pass normal events to `Replayer` untouched. rrweb rebuilds them in a sandboxed iframe; pre-processing DOM, stylesheet, resource, or mutation payloads makes playback diverge from the captured page. In particular, never rewrite `href`, `src`, `_cssText`, CSS `url()`, or Meta URLs to `about:blank`; that exact remediation broke historical replay CSS in PR #2040. Handle request privacy at capture or the sandbox boundary instead of mutating stored rrweb events. Historical captures without inlined resources require live stylesheet/image/font requests for accurate rendering; the viewer accepts that fidelity tradeoff, uses rrweb's script-disabled sandbox plus `referrerpolicy="no-referrer"`, and must never add credentials or proxy those URLs through a privileged server. - Capture-time URL scrubbing must preserve load-bearing DOM resource attributes: `src`, `srcset`, `poster`, `data`, and `href` only on resource links such as stylesheets, preloads, and icons. Signed CDN query parameters are part of the resource identity; redacting them produces missing CSS, fonts, images, and oversized fallback icons. Keep scrubbing Meta/navigation URLs, anchor hrefs, and console/network diagnostics. Captured `_cssText` and CSS `@import`/`url()` values must remain byte-identical. - rrweb rebuilds into an `about:srcdoc` iframe, which inherits the Analytics document's CSP. Analytics currently sends no CSP header; if a future change adds restrictive `style-src`, `font-src`, or `img-src` directives, verify historical replays and resolve external imports/fonts at capture before blocking the recorded resource origins. Do not diagnose current font loss as CSP without checking the deployed response headers first. - Let rrweb own iframe sizing entirely via Meta / ViewportResize, and keep the outer wrapper on the exact same raw dimensions for fit-to-stage scaling. Player geometry and pointer coordinates are fully stock and untouched — do not add width/aspect-ratio "recovery" heuristics or pointer-coordinate projection. There is no such thing as a stored recording with corrupt viewport geometry: a census of all production recordings found zero stored widths >= 3,000px. The 2026-07 "ultra-wide replay" bugs (stages rendered 3,000–9,500px wide, frozen/teleporting cursors, giant icons) were caused entirely by demo mode's fetch interceptor: its number redactor faked any integer >= 1000 inside raw replay JSON at *view* time, corrupting Meta / ViewportResize widths, pointer x/y coordinates, and numeric values inside `_cssText` and SVG attributes before rrweb ever saw the payload (heights below 1000 stayed real, which is why the symptom looked like a viewport problem rather than a redaction bug — two different sessions that both stored a 1,152px width read back as the same 4,491px, a deterministic salted-hash fingerprint of the redactor, not two coincidentally identical malformed recordings). This is fixed in `packages/core/src/demo/fetch-interceptor.ts`: raw replay payload and manifest URLs are skipped from demo number redaction entirely, and must never be routed through it again. Do not reintroduce viewport clamping or pointer-coordinate projection in the player — they can now only corrupt genuine future recordings (for example, a real 3440x900 ultrawide browser window, or a short vertical window under 1,000px tall). - Keep rrweb's stock cursor stylesheet and its hotspot transform. During playback, hide the viewer's native pointer over Analytics' transparent click-to-pause overlay so it cannot masquerade as a frozen recorded cursor. - Keep rrweb's recorded focus handling enabled. Focus and focus-visible state affect menus, forms, and keyboard UX; disabling `triggerFocus` makes a valid snapshot diverge from the source page. - `insertStyleRules` may suppress known toast/snackbar containers only. Never hide generic framework primitives such as `[data-radix-popper-content-wrapper]`: Radix dropdowns, selects, tooltips, and other real recorded product UI all share that wrapper. - Keep the realistic-fidelity purity/pass-through tests in `SessionDetailPage.spec.ts` — raw event identity, raw viewport dimensions, and raw resize-state derivation (including the 3,189x885 tripwire against reintroducing a clamp) — as regression guards against reintroducing any viewport "recovery" or pointer-projection heuristic. Do not change their expectations merely to bless a new sanitizer or clamp; validate the affected replay in a browser first. An interim clamp for the exact 3,189x885 pair was also deleted once the view-time redaction root cause was proven; the earlier 3,000-3,999px band was rejected because it also catches real 3440px-wide displays. Neither the exact exception nor the band belongs in the player. - The event timeline soft-highlights the active marker, auto-scrolls it into view (pausing briefly after manual scroll), and supports search. It appears beside the player from ~880px content width upward. - Dev Tools height is capped so the replay stage never collapses into a ribbon on short viewports; the scrubber playhead stays visually distinct from red error marker dots. ## Debugging A User-Reported Bug 1. Search the reporting user's email on `/sessions` to find their recordings. 2. Open the relevant session at `/sessions/:recordingId` and click **Copy for agent** to mint the two-hour tokenized link. 3. Paste the link to an agent. The agent fetches `/api/session-replay/agent-context.json`, reads the `diagnostics` section and timeline markers first, then drills into `apis.diagnostics` (filtered by `kind`/`level`) and `apis.events` for the fuller bounded lists as needed. 4. For human verification, open the Dev Tools panel in the replay player and jump-to-seek from the failing console or network row. ## Capture Defaults - Replay is on by default for signed-in hosted users when `VITE_AGENT_NATIVE_ANALYTICS_PUBLIC_KEY` or `configureTracking({ key })` is present. The default sample rate is 100% of eligible sessions. - Replay remains off when no first-party analytics key is configured, and it is not auto-enabled on localhost/local dev. Consumers can still enable replay directly with `configureTracking({ key, endpoint, sessionReplay: { enabled: true } })`. - Apps can opt out with `configureTracking({ sessionReplay: false })`. - Agent-Native templates already call `configureTracking()` in their roots; hosted template deployments only need the normal Agent-Native Analytics Vite/Netlify env vars on the recorded site. - Inputs are masked by default. Page text is visible unless marked with `.an-mask` or `data-an-mask`. - Use `.an-block`, `.an-ignore`, `data-an-block`, or `data-an-ignore` for sensitive zones that should not be captured. - A definitive upload `409` abandons only the conflicted replay identity and immediately starts rrweb again under a fresh per-tab id, producing a new Meta + FullSnapshot for long-lived SPA tabs. Recovery is limited to one restart until an upload succeeds so a misconfigured endpoint cannot loop; Analytics tracks the content-free `session replay upload rejected` lifecycle event so conflicts and recovery success are measurable. - Do not label an old recording "corrupt" from pointer coordinates, unknown mutation node ids, or changing Meta geometry alone. Those shapes can be legitimate with scrolling, iframes/shadow DOM, navigation, and resize. A historical-artifact notice needs a durable capture/ingest marker or another low-false-positive invariant; do not guess from playback heuristics.
Ver no GitHub