Re-acceptance after the version seam (#512 §7) from the full Validate dispatch on 605991ef (run 34366858855): seven frames carried the version text — three version-mismatch banners, three PDF footers, the support preview — and now read `0.0.0-golden`; six of them were within threshold and still showed stale betas (beta.4, beta.8). The other 162 frames are kept as reviewed; 128 environment witnesses matched byte for byte. Sub-threshold drift in nine unrelated frames (tray, resize handles, compass, junction) is not accepted. Issue: #512 User-Visible: no Release: v1.74.0-beta.1 Baseline-Reviewed: https://github.com/Matysh/houseplan-card/actions/runs/34366858855
HP-QA-01 golden images
This layer catches visual regressions that DOM smokes cannot: wall seams and end caps, thick opening tunnels, Glow/sun clipping, hover contours, editor chrome, the open contextual tray at wide/medium/narrow widths in English and Russian (selection, tool options, group and palette), long dialog titles/footers, mobile clipping, themes and zoom/remount. The desktop and mobile device-dialog scenarios use a real light and make the complete source-role, Glow colour, brightness and radius controls visible; capturing only the top of that section fails the scenario before comparison. The Glow matrix also keeps one deliberately opaque custom-fill scene with a single source and two doorways: it makes hard spill wedges and fully unlit radial spokes visible instead of hiding them under a translucent room fill.
Safety contract
- A build fingerprint embedded by Rollup must match
src/, Rollup/TypeScript configuration and locked package inputs; stale committed demo bundles fail before the first screenshot. - Chromium, viewport, locale, timezone, colour profile, font rendering, animations and caret are controlled by the runner.
capturewrites only to ignoredartifacts/golden/; it never changes a baseline and never claims a missing baseline passed. Any scenario runtime error makes capture fail, including the initial no-baseline CI run.verifyrequires every image plus a matching matrix manifest and fails on missing/different/error scenarios, browser mismatch or a baseline whose hash no longer matches the reviewed manifest.acceptrequires--reviewed, a complete candidate report and current source fingerprint. It validates the whole set before copying anything and is the only command allowed to update baselines.scripts/golden-accept.mjswrapsacceptand additionally requires the reviewer to declare intent, with two separate flags because they assert two different things.--expect-change=<id,id>means "I know why this existing baseline moved";--expect-new=<id,id>means "I have looked at this new frame". Anything that differs, or arrives without a baseline, and is not named refuses the whole acceptance before a single file is copied; naming a scenario under the wrong flag refuses it too. Only the named scenarios are written: everything else keeps its reviewed bytes and its manifest hash, becausepassedmeans "within threshold", not "byte-identical", and copying every candidate let sub-threshold drift ratchet the baselines to the newest environment unseen (#351). The first flag is what makes a local capture admissible (see below) and blocks the one-command "accept everything so CI turns green"; the second stops an empty or clipped frame from becoming the contract unseen (#350).
Displayed version (#512)
Frames never show the real CARD_VERSION. The harness sets the test seam
window.__HP_VERSION_OVERRIDE__ = '0.0.0-golden' before any card is created and
feeds the same constant wherever it plays the backend (integration_version,
support facts), so the about dialog, the version-recovery banner and the support
and backup previews print 0.0.0-golden in every baseline. A beta bump therefore
changes no golden frame; the version-mismatch scenarios keep their own
0.0.0-golden-backend and still exercise the frontend≠backend relation. The
product never sets the seam (src/card-version.ts).
Workflow
Build and copy the exact current source first:
npm run build
npm run bundle:sync
npm run golden:capture
Review artifacts/golden/actual/ and, when existing references are present,
artifacts/golden/diff/. If every image is intentional:
node scripts/golden-accept.mjs --reviewed --expect-change=wall-junctions-plan-t-dark
npm run golden:verify
Never accept images merely to make CI green. A matrix/framing change increments
GOLDEN_MATRIX_VERSION; a normal rendering fix does not. The first canonical
Linux baseline was reviewed and accepted during the v1.60.3-beta.1 gate.
Capturing candidates without a second CI round trip (#334)
Accepting from the golden-images CI artifact still works and is still the
safest route: unpack it and pass --from=.... It costs two full CI runs per
visual fix, though — one to produce the artifact and one to verify the accepted
baseline — and at matrix version 48 that toll is paid often.
A local capture is admissible instead, because admissibility is now proved rather than assumed. Desktop font rasterisation can differ from the runner, but it cannot differ quietly: it would move every text-bearing scenario, not only the ones under edit. So the rule is simply that the capture must reproduce every accepted baseline the reviewer did not intend to change:
node scripts/golden-container.mjs # capture in the pinned image
node scripts/golden-accept.mjs --reviewed --expect-change=<the scenarios you changed>
If the environment is not pixel-equivalent, unrelated scenarios come out
different, the wrapper names them and refuses. A wrong container tag or a
mismatched font set therefore cannot corrupt baselines — it can only fail.
scripts/golden-container.mjs derives the image tag from the playwright
version locked in package-lock.json, so the container Chromium equals the
runner's; --image= overrides it when a distro-specific tag is needed
(...:v1.62.0-jammy). The host node_modules is shadowed by an anonymous
volume: the repository copy may be built for Windows, and npm ci inside the
container would otherwise replace it with Linux binaries. The run does write
dist/ and the three bundle copies, exactly as a local npm run bundle:sync
would.
Docker is not a requirement of the rule, only a convenience: any Linux
environment that satisfies the parity condition qualifies, WSL included. The
chromium string recorded in the manifest keeps the browser build itself
pinned, and golden:verify rejects a manifest captured by a different build.
scripts/golden-accept.mjs deliberately wraps demo/golden/accept.mjs instead
of replacing its checks: every .mjs under demo/golden belongs to
sourceFingerprint, so editing the acceptance tool itself would declare the
committed bundle, the documentation screenshot manifest and the baseline
manifest stale — the very double round trip this change removes. Narrowing that
corpus is worthwhile but separate: scripts/source-fingerprint.mjs is itself a
build input, so any change to it forces one bundle rebuild.
Scenarios may also declare a semantic pixel region (for example, a receiving
room that must contain warm light). golden:capture and golden:verify reject
the capture before baseline comparison when that visual precondition is empty;
a reviewed but meaningless PNG therefore cannot become the contract.