# React and web apps (https://docs.vitrinka.ai/qa/recorder/web)



## Install [#install]

```bash
bun add @vitrinka/web rrweb
```

`react`, `react-dom` and `rrweb` are peer dependencies.

## Mount it [#mount]

Wrap your app once and drop the pill where it should float. Nothing is captured until a tester starts a session from the pill.

```tsx
import { VitrinkaRecorderRoot, VitrinkaRecorderPill } from '@vitrinka/web/recorder';

<VitrinkaRecorderRoot url={process.env.NEXT_PUBLIC_VITRINKA_URL}>
  {children}
  <VitrinkaRecorderPill />
</VitrinkaRecorderRoot>
```

The recorder mounts only when `url` is set. Leave it unset in production and the root renders its children and nothing else.

## Link the device [#link]

Testers never type a token. **Link recorder** shows a short code, an **Open vitrinka** button and — on a desktop — a QR code:

1. On the same device, tap **Open vitrinka**; on a desktop, scan the QR with a phone that is signed in to vitrinka.
2. Approve the code — pick the workspace when you belong to several.
3. The recorder receives a token that can only create and append to sessions in that workspace. It cannot read boards, tasks or other sessions, expires 30 days after its last use, and is listed and revocable under **Settings → tokens**.

The same page is reachable from the user menu as **Link a device** for typing a code by hand.

A linked recorder records only on a page whose host matches one of the workspace's project domain rules — the same rule that picks the session's project. A rule whose environment is production — `production`, `prod`, `prd` or `live`, any case — refuses it unless that rule opts in: switch on **in-app recorder** beside the domain under **Settings → Projects → Routing**, or set `"inAppRecorder": true` on the rule (the settings `PUT` replaces the whole `domains` list, so send every rule back). The browser extension and native apps are not affected, and a recorder key keeps its own origin list.

A refused page gets a 403 that names the rule to change; linking again does not help. Only a 401 — the token was revoked, expired, or belongs to another workspace — asks the tester to link again.

## Move it out of the way [#move]

The recorder stays small: a 28px dot until someone records, then a dot and a timer that unfold into the controls when you hover, focus or tap them, and fold away again when you leave. Something wrong, such as being offline, always shows a line by itself.

Drag it to any corner or to the top or bottom centre — a quick flick lands where it is thrown. Push it past the left or right edge and it tucks into a thin tab; tap the tab to bring it back. Arrow keys on the focused recorder and **Move to** in its menu do the same without dragging. It remembers the spot on each site. On a phone, the note and link sheets open from the bottom of the screen, above the keyboard.

## Unattended builds [#key]

CI and end-to-end suites cannot approve a code. An admin mints a **recorder key** under **Settings → tokens** — project-pinned and limited to the exact origins you list (`*` is refused), which double as the CORS allow-list — and the build sets it as `NEXT_PUBLIC_VITRINKA_KEY`.

```js
// next.config.js
const { withVitrinkaRecorder } = require('@vitrinka/web/next');
module.exports = withVitrinkaRecorder(nextConfig);
```

`withVitrinkaRecorder` refuses a production build that carries a recorder key unless `VITRINKA_RECORDER_LANE` names an allowed lane (`preview` or `development`), so a key never ships to users.

## What is captured [#captured]

* **Replay** — an rrweb DOM stream with inputs masked; the replay is the session's keyframe, no screenshots are taken. When the session ends, vitrinka replays the stream in its capture sidecar and renders a still at every click, navigation, note and annotation, so the session board looks like an extension session's. Images and fonts the page loaded from its own server are not fetched for the stills, so they show blank.
* **Clicks** — the element's short selector, its rectangle and a redacted text snippet.
* **Navigation** — every route change.
* **Console** — errors and unhandled rejections.
* **Network** — `fetch` and `XMLHttpRequest`, with headers, URLs and bodies passed through the same redaction rules as the Expo recorder; the recorder's own uploads are skipped.
* **Notes and annotations** — what the tester types and marks, on an element or a dragged region.

The full contract is `PROTOCOL.md` in the vitrinka-kit repository.

<Callout type="warn">
  Source-available under the Elastic License 2.0: read, use, modify and redistribute it — you may not offer it to third parties as a managed service.
</Callout>
