Skip to content
Back to skills

Visual Regression

ASecurity

Work with the Ladle visual-regression check — read a failing `visual` CI run, decide whether a pixel diff is a regression or an intended redesign, and adopt CI-produced baselines into the PR. Use when the `visual` workflow fails, when a PR needs updated component snapshots, when adding or changing a `*.stories.tsx` file, or when someone asks to update/refresh/accept UI snapshots.

  • 21 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added September 19, 2026
ai-agentsgobashdockergit

Security analysis

A100/100

Scanned September 19, 2026

npx -y skills add ahilles107/gravity --skill visual-regression --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Visual Regression?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Visual Regression
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/ahilles107-visual-regression/badge)](https://www.skillsdirectory.com/skills/ahilles107-visual-regression)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: visual-regression
description: Work with the Ladle visual-regression check — read a failing `visual` CI run, decide whether a pixel diff is a regression or an intended redesign, and adopt CI-produced baselines into the PR. Use when the `visual` workflow fails, when a PR needs updated component snapshots, when adding or changing a `*.stories.tsx` file, or when someone asks to update/refresh/accept UI snapshots.
---

# Visual regression for Ladle stories

The `visual` workflow screenshots every Ladle story and compares it byte for
byte against a committed baseline in
`apps/desktop/tests/visual/__screenshots__/`. One PNG per story id.

## The rule that governs everything here

**Adopt baselines from the pinned CI environment.** They are only reproducible inside the
pinned Playwright container that CI uses (`mcr.microsoft.com/playwright` — the
tag lives in `scripts/vr-ci.sh`). A macOS or bare-Linux checkout renders
different glyphs and antialiasing, so a locally produced PNG is guaranteed to
fail on the next push while looking correct in the diff.

`playwright.config.ts` enforces this: `--update-snapshots` throws unless
`CI=true`, and `updateSnapshots: "none"` makes a missing baseline a hard
failure rather than a silent write. Do not work around either guard.

## Adopting new baselines

Before adopting, verify the repository, branch and exact run commit. Inspect
`git status --short -- apps/desktop/tests/visual/__screenshots__` for existing
edits and untracked files. Do not overwrite them. The acceptance script
replaces the entire directory: identify that target, preserve local work, and
obtain approval for replacement if it is not already authorized.

When `visual` fails, the job regenerates the complete baseline set inside the
container and uploads it as the `visual-snapshots` artifact. Adopt it with:

```bash
scripts/vr-accept.sh            # newest `visual` run for the current branch
scripts/vr-accept.sh 1234567890 # or a specific run id
```

It downloads the artifact, replaces `__screenshots__` wholesale (so baselines
for deleted stories disappear too), and stages the result. Then commit:

```
test(desktop): update story baselines for <what changed>
```

The script warns when the run's `headSha` differs from local `HEAD`. If it
does, push first and re-run — otherwise you commit baselines describing a
different tree.

## Deciding first, adopting second

`vr-accept.sh` is mechanical; the judgement is yours. Before running it, look
at what actually changed:

```bash
VR_REPORT_DIR=$(mktemp -d "${TMPDIR:-/tmp}/gravity-visual.XXXXXX")
gh run download <run-id> --name visual-report --dir "$VR_REPORT_DIR"
```

The Playwright HTML report has expected/actual/diff for every failure. Read
the diffs and classify:

- **Intended** — the PR changes that component, spacing, or token. Adopt.
- **Unintended blast radius** — a shared token or a rule in `src/styles/`
  moved something the PR never meant to touch. Fix the CSS, don't adopt.
- **Non-determinism** — the same story flips between runs. Adopting hides it.
  Find the source (a live `Date`, an unpinned locale, a loading font, an
  animation) and make the story deterministic instead.

Say which category each diff falls into when you report back. "Snapshots
updated" without that judgement is not a review.

## Adding a story

New stories have no baseline, so the first CI run after adding one fails with
`snapshot doesn't exist`. That is the expected bootstrap: push, let it fail,
run `scripts/vr-accept.sh`, commit the new PNGs.

## Determinism requirements for stories

The suite pins `timezoneId: "UTC"`, `locale: "en-US"`, a 1024x720 viewport and
`deviceScaleFactor: 1`. Stories must supply the rest:

- Use the fixed fixtures in `src/test/fixtures.ts`. Their timestamps are frozen
  at 2024-05-01, which keeps `fmtTimestamp`/`fmtShortTime` off their
  `isToday()` branch. A story that builds a date from `new Date()` will pass
  once and fail forever after.
- No randomness, no network, no `setTimeout`-driven reveal.
- Keep the rendered component inside the viewport; content below the fold is
  not captured.

## Running the comparison yourself

You can run the real check locally if Docker is available — this uses the same
container as CI, so it is safe:

```bash
scripts/vr-ci.sh
```

What you must not do is `pnpm vr:update` on your machine. `pnpm vr` alone
(outside the container) will also report false diffs; it exists for the
container to call.

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.