Goldens are the frozen answers of an app: the readable text a pure unit produces for fixed inputs,
the output every installed version must reproduce, the pixels a Skia surface draws. They change only
on purpose, after a real look, in a commit that says why, and contract entries never change at all.
This skill writes them in the Jest golden project and its two checkers prove that the tree and the
git history keep that policy.
Rules that must hold
- A golden changes only by hand, one file at a time:
npx jest <file> --selectProjects golden -u, path first. Every script, hook and CI job runsjest --ci, which refuses to write a missing snapshot. No-u,--updateSnapshot,--ci=false,CI=falseor baseline--updateinpackage.json, git hooks (lefthook, husky, lint-staged), CI (GitHub Actions, EAS Workflows) or agent hooks. Why: an agent rubber-stamps whatever-uwrites, and after--selectProjectsa path is read as another project name, so every golden is rewritten (verified with--listTests). - Look before accepting. Read the
.snap.iosdiff line by line, open every new or changed PNG and its diff image, thenCI=1 npx jest --ci --selectProjects goldenpasses. Why: an unread golden freezes a bug for every later run. - Every commit that adds, changes or deletes a golden path carries
Gate-Change: <which goldens, why, what you looked at>(at least 10 characters). Golden paths:**/*.golden.test.ts.snap(.ios|.android),**/__image_snapshots__/**, the pixel matchertest/goldens/skia-golden.ts,.maestro/baselines/**, plusgoldenPathsincontracts/goldens.json. Why: onegit log --grep='^Gate-Change:'lists every gate change later. [contract]entries are append-only. Output that every installed version, every device or a file a user keeps must reproduce exactly (a backup or export format, a share code, a derived identifier such as a scheduled notification id, a date-keyed pick) is pinned in a describe titled[contract] <unit> goldens, registered incontracts/goldens.json, with at least three keys and, for date keys, a year-end/new-year pair. An existing entry is never edited, deleted or renamed, not even with a trailer; adding keys is fine. If the producing code must change, stop and ask: ship a versioned format next to the old one, or switch inputs from a future date. Why: users on the old and the new version would get different results from the same input; renaming a title deletes the entry (the key is the title).- Data goldens are readable text from pure code.
<unit>.golden.test.tsnext to the unit, one string per case (a header line with the inputs and headline numbers, then short lines or an ASCII grid), stored by jest-expo as<file>.golden.test.ts.snap.iosnext to the test; at most 120 lines per entry (aim for 60); no clock, machine locale or unseeded randomness; no rendering. Why: a golden is reviewed by reading its diff, and a machine-dependent golden differs between runs. - No component-tree snapshots and no inline snapshots, anywhere. A
.snapof a non-golden test is deleted and replaced by assertions on roles and text;toMatchInlineSnapshotis never used. Why: component trees churn on every style change and train-ureflexes;-uwould rewrite the test file itself, and Jest 29 with Prettier 3 fails the suite outright (verified). - Pixel goldens (apps that draw with Skia) render the production draw function offscreen in the
CanvasKit
goldenproject, undertest/goldens/, at three canvas sizes (narrow 320x400, phone 390x560, tablet 1024x700; a fixed-size drawing says// fixed-size: <why>, and a pipeline smoke test that draws a function defined in the test is exempt) and, when it animates, at moments 0, 0.5 and 1, with fonts loaded explicitly, through the one matchertest/goldens/skia-golden.ts(toMatchPixelGolden), never a per-call threshold.jest-image-snapshot6.5.2 and@types/jest-image-snapshot6.4.2 are pinned exactly. Why: goldens catch what assertions forget only when they draw what users see, at the sizes they see it. - Tolerances are gates: 0.1% of pixels for Skia goldens (the matcher), 0.2% for simulator and
emulator screenshots (e2e-maestro). Never raise one to hide a diff; a raise needs the reviewer's
agreement first, then the matcher and
contracts/goldens.jsonmaxPixelTolerancechange together in a commit withGate-Change: <name> <old> -> <new> approved by <reviewer>: <reason>. Why: a raised tolerance silently accepts every future regression below it. - Snapshots never outlive their tests and diffs never reach git. An obsolete entry is removed with
-uon that one file; an orphan.snapor baseline PNG is deleted with its test (baseline-orphan);.gitignorelistsreports/and**/__image_snapshots__/__diff_output__/;quality-gates.jsongatedPathslists the golden paths. Why: an obsolete entry makesjest --ciexit 1 even when every test passed (verified), and a golden path missing from the gate is changed without a trailer. - A failing golden is investigated, not re-accepted. Decide: a bug (fix the code, keep the golden), intended (update that one file, rules 1 to 3) or a contract entry (always a bug). A golden that differs between two runs of the same commit means the code reads time or randomness. Why: the generators, formatters and CanvasKit are deterministic, so noise is a bug.
Workflow
- Know the policy once per session: read references/golden-policy.md (the kinds of golden, where each lives, the only way to change one, contract entries, failure triage, iOS and Android notes, "When multi-package").
- Pick the kind. Readable output a reviewer checks: a data golden. Output installed versions
must reproduce: a
[contract]describe in a data golden. A Skia surface: a pixel golden. A whole screen on a device: a screenshot baseline (e2e-maestro). Seeded generator output: golden vectors (seeded-determinism-and-golden-vectors). Then read references/data-goldens.md or references/pixel-goldens.md. - A data golden: if
test/goldens/golden-text.tsis missing, copy it and its test fromtemplates/test/goldens/. Imitatetemplates/src/features/__FEATURE__/__FEATURE__-digest.golden.test.ts(the digest unit and its reviewed.snap.iosshow a complete, verified pair): adescribe<Unit>()function that returns one string, fixed inputs, titles that start with a third-person verb. - A contract: put its cases in
describe('[contract] <unit> goldens', ...), pin at least three keys (a year-end/new-year pair for date keys, and a leap day), and register it incontracts/goldens.json(the first time, copytemplates/contracts/goldens.jsonand its schema and replace the sample digest entry with yours; the checker fails a marked describe the register does not list, and an app with no contract needs no register). examples/backup-envelope.golden.test.ts shows a format contract. - A pixel golden: if
test/goldens/skia-golden.tsis missing, copy templates/test/goldens/skia-golden.ts and runnpm install --save-dev --save-exact jest-image-snapshot@6.5.2 @types/jest-image-snapshot@6.4.2(without the types,npm run typecheckfails with TS7016). The surface's golden test, draw code and layout come from skia-canvas-rendering's template. - Create the goldens deliberately:
npx jest <file> --selectProjects golden --listTests(it must list only that file), thennpx jest <file> --selectProjects golden -u. Read every new entry or open every new PNG with your file-reading tool, compare it with what users should get, thenCI=1 npx jest --ci --selectProjects golden. - Check the tree:
node ${CLAUDE_SKILL_DIR}/scripts/check-goldens.mjs .and fix everyFAILline (file, rule, fix); rerun untilRESULT: PASS. Rules and options: references/checkers.md. - Commit with the trailer (examples/gate-change-commit.txt):
write the message to a file, stage, run
node ${CLAUDE_SKILL_DIR}/scripts/check-golden-changes.mjs . --staged --message <file>, commit. At the end of the session run it with--range <base>..HEAD(base: the commit before the session's first); in CI,--range origin/main..HEAD. - When a golden fails later: follow "When a golden fails" in the policy reference. Bug: fix the code. Intended: step 6 for that one file, then step 8. A contract entry changed: revert, or stop and ask the reviewer. A wide re-acceptance (a Skia or font upgrade) follows run-goldens-rebaseline; after a Jest major (SDK 58 brings Jest 30) every file fails on its header: "Jest upgrades" in the policy.
Definition of done
- Every data golden has its
.snap.ios, every pixel golden its baselines, andCI=1 npx jest --ci --selectProjects goldenpasses with no obsolete snapshot. - Every new or changed entry was read and every new or changed PNG opened; the report names them and why they changed.
- Every contract is registered in
contracts/goldens.json, pins at least three keys (a year-boundary pair for date keys), and no existing[contract]entry changed. - No script, hook or CI job writes goldens; no tolerance went up without an approval trailer.
- Every golden change sits in a commit with a
Gate-Change:trailer, andnode ${CLAUDE_SKILL_DIR}/scripts/check-golden-changes.mjs . --range <base>..HEADprintsRESULT: PASS. -
node ${CLAUDE_SKILL_DIR}/scripts/check-goldens.mjs .printsRESULT: PASS(allowed while adopting:SKIP quality-gates.json [gated-path-missing] not yet created (quality-gates creates it),SKIP .gitignore [diff-output-ignored] not yet created (typescript-and-lint-rules creates it))
Anti-patterns
npx jest -uto make a red suite green. It rewrites every golden that differs, bugs included. Read the failure, fix the code, and update only the one file you meant to change.npx jest --selectProjects golden <file> -u. The path becomes a second project name and every golden is rewritten. Put the path first and preview with--listTests.- Editing a
.snap.iosby hand. The next-uor a renamed test undoes it, and it hides what the code really produces. Change the code, then regenerate that file. - Re-accepting a
[contract]entry "because the format improved". Installed versions and files users kept still expect the old output. Version the format or ask the reviewer. - Renaming a contract describe "for consistency". The snapshot key is the title, so the history check sees the old entry deleted. Keep the title; add a comment instead.
- A 400-line JSON dump as a golden. Nobody reviews it, so any change gets accepted. Describe the output as a header line and the fields that matter.
expect(screen.toJSON()).toMatchSnapshot()on a component. It churns on every style tweak. Assert roles and text (unit-and-component-tests); screens are covered by screenshots.new Date()ortoLocaleDateString()in a golden. It passes today on this machine only. Pass a date key or an instant literal and an explicit locale.failureThreshold: 0.01in a test "because CanvasKit is noisy". CanvasKit renders are byte-identical run to run; noise means the draw path reads time or randomness.- A pixel golden at one size or without the 0 and 1 moments. Clipping at narrow sizes and wrong start or end states are the most common canvas bugs.
Files in this skill
| File | What it is | Read/run when |
|---|---|---|
| references/golden-policy.md | The kinds of golden and where they live, the only way to change one (verified Jest facts), gated paths and the trailer, contract entries, failure triage, obsolete and orphan snapshots, Jest upgrades (the snapshot header), iOS and Android, When multi-package | Workflow step 1, and whenever a golden fails |
| references/data-goldens.md | What deserves a data golden, readable text, the template pair, the snapshot file, reading a diff, writing and evolving contracts, formats worth pinning, the games appendix | Workflow steps 3 and 4 |
| references/pixel-goldens.md | The golden project and matcher, sizes and moments, fonts, accepting baselines, reading a failure, Skia upgrades, screenshot baselines on iOS and Android, headless previews | Workflow step 5 |
| references/checkers.md | Both checkers: rules, options, rn-skills.config.json keys, the contract file, SKIP lines, the log capture, CI use, troubleshooting | Workflow steps 7 and 8 |
templates/test/goldens/golden-text.ts |
headerLine, goldenText, renderGrid: readable golden text |
Workflow step 3, when missing |
templates/test/goldens/golden-text.test.ts |
Its unit test | Copied with it |
| templates/test/goldens/skia-golden.ts | toMatchPixelGolden: 0.1% of pixels, diffs to reports/visual/diff/ (canonical, synced; this skill owns it) |
Workflow step 5, when missing |
templates/src/features/__FEATURE__/__FEATURE__-digest.ts |
A pure Tasks unit to pin: the daily digest plan and its notification id | Workflow step 3, as the shape of a pinned unit |
templates/src/features/__FEATURE__/__FEATURE__-digest.golden.test.ts |
The data golden pattern: a describe function, fixed inputs, a [contract] describe |
Workflow steps 3 and 4 |
templates/src/features/__FEATURE__/__FEATURE__-digest.golden.test.ts.snap.ios |
Its reviewed snapshot (what readable entries look like) | Workflow step 3 |
templates/contracts/goldens.json |
The contract register both checkers read; its digest entry is a sample to replace (canonical, synced; this skill owns it) | Workflow step 4 |
templates/contracts/goldens.schema.json |
Its JSON schema (keys and meaning; canonical, synced) | Workflow step 4 |
| examples/backup-envelope.golden.test.ts | A format contract: backup format 1 pinned for boundary inputs | Workflow step 4 |
| examples/backup-envelope.golden.test.ts.snap.ios | Its snapshot | Workflow step 4 |
| examples/gate-change-commit.txt | Commit messages with the Gate-Change trailer (created, re-accepted, contract key added, tolerance approved) | Workflow step 8 |
scripts/check-goldens.mjs |
Checks the tree: snapshots present, readable and owned, no component or inline snapshots, no ambient input or UI in goldens, contracts, pixel matcher, sizes, baselines, no automated -u, jest --ci everywhere, gated paths, ignored diffs |
Workflow step 7 and the definition of done |
scripts/check-golden-changes.mjs |
Checks commits or the staged change: Gate-Change trailers, [contract] entries never edited or deleted, tolerances never raised without approval |
Workflow step 8 and the definition of done |
scripts/lib/golden-config.mjs |
The checkers' shared reader of paths, rn-skills.config.json keys and contracts/goldens.json |
When a checker changes |
scripts/selftest.mjs |
Proves both checkers pass the templates and catch every planted bug | After changing a checker or a template |
scripts/check-lib.mjs |
Shared script library (synced; do not edit here) | Never by hand |
assets/shared.json |
Declares the synced files (check-lib, the pixel matcher, the register and its schema) | When adding a shared file |
assets/template-placeholders.json |
The placeholder values the template verification and the self-test use | When adding a placeholder |
tests/bases/app/ |
The minimal app the fixtures lay the templates on | When the fixture base changes |
tests/fixtures/ |
Overlay suites: good/, one bad-* per rule, pass-* and error-* cases; real git log captures for the history checker |
When adding a checker rule |
Related skills
unit-and-component-tests- the Jest projects (the golden project's config), component tests and theircomponent-snapshotrule.run-goldens-rebaseline- the manual ritual for re-accepting many goldens at once.skia-canvas-rendering- the surface draw code, its golden test template and draw-call label tests.seeded-determinism-and-golden-vectors- seeded generators and their oracle-filled vectors.e2e-maestro- simulator and emulator screenshot baselines (same policy, 0.2% tolerance).git-commits-and-reporting- the commit format, the Gate-Change trailer and the change report.quality-gates-quality-gates.jsongated paths and the commit-msg hook that asks for the trailer.data-export-and-restore- backup and export formats that contract goldens pin.level-generation-and-solvers- level and daily-level goldens in games.