xray.

In CI

Sweep a whole page, compare against a baseline, and fail on new findings.

The overlay answers "what is wrong with this element". xray record answers "what is wrong with this app, and is it getting worse" — which is the question a build can act on.

xray record http://localhost:5173 --baseline xray.baseline.json

Exit 0 when nothing new appeared, 1 when it did, 2 when the sweep could not run at all.

Create the baseline with --update-baseline, commit it, and every run afterwards compares against it:

New (1):
  + LOCKED      src/Card.tsx:36
      padding: 8px — 8px is --salt-spacing-100 at medium only.

Failing: 1 new finding. Fix it, or re-record with --update-baseline.

Playwright is optional

The analysis needs a resolved cascade, so it needs a real browser — but only this command does, so Playwright is an optional peer dependency rather than a dependency.

pnpm add -D playwright && pnpm exec playwright install chromium

Point it at a dev server running the plugin and it reuses the client already on the page; point it at a preview build or a static page and it injects the same bundle itself, so both paths report identically.

A baseline survives editing the files it describes

Findings are compared by file, property, kind, token and value — deliberately not by line number.

A baseline keyed on lines goes stale the moment someone adds an import, and a diff that reports every finding in a reformatted file as both removed and added is a diff nobody reads. Line numbers still travel in the report, as the place to look rather than as part of a finding's name.

Flags

--baseline <file>Compare against a recorded baseline and fail on new findings
--update-baselineWrite the baseline from this run and exit successfully
--out <file>Write the recording as JSON
--fail-on <severity>high, medium or low. Default high
--per-source <n>Elements to analyse per source location. Default 3
--max-elements <n>Cap on elements swept. Default 2000
--wait <selector>Wait for this selector before sweeping
--timeout <ms>How long to wait for the page. Default 30000
--fullPrint the whole report as well as the diff

From the console

The same sweep is available without the CLI:

__xray.record(); // aggregated by source location
__xray.report(); // the same thing as text

Elements are sampled per source location — the fiftieth row of a list is the same JSX line as the first — and token resolution is cached per theme context, which is what keeps a full sweep in the tens of milliseconds.

Element counts are a sample

Because reporting is per source location, a value hardcoded in a shared component appears once against the component rather than once per usage. That is usually what you want, but it means a finding's element count is a sample, not a census.

On this page