Figure contract
Screenshots are the part of a manual that rots fastest. This contract exists so a stale figure is a build failure rather than something a reader discovers.
One id, one master
A figure's id is its filename stem, kebab-case and unique across the whole site:
static/img/figures/<module>/<area>/<nn>-<what-it-shows>.png
→ id: intelie-konnect/import-create/02-list-empty
Masters are 2880×1800 PNG (a 1440×900 viewport at 2×), committed to this repo and never served to a browser. Because they are byte-stable, a diff on a master means the product really changed.
The id is identical whether the shot came from the capture harness or from someone's keyboard, so a page cannot tell the difference.
Using one
<Figure
id="intelie-konnect/import-create/02-list-empty"
alt="The Import Connectors list with no connectors, showing the message No Connectors found."
caption="The empty list you start from."
priority
/>
alt is required and non-empty; the build throws otherwise. Write what a sighted reader
would see, in a sentence. caption says why the figure is there. Add priority to the
first figure on a page only, since it is usually the largest thing the reader waits for.
Never draw arrows, boxes or numbers onto a screenshot. Say in the step text what to look at; a marked-up image cannot be recaptured without redoing the markup by hand.
A figure is never shown wider than it was captured (a master's width ÷ 2, because masters are 2×), so a small crop stays sharp at its real size instead of being stretched to the column. Enlarge (and select-to-zoom) is offered only on figures wider than the 800px column; it opens the image in an in-page viewer that Esc closes.
What happens to it at build time
npm run images turns each master into AVIF and WebP at four widths, plus a wider variant
for click-to-zoom and a tiny blurred placeholder so nothing pops in white. The largest
variant a browser can use is 1600px, because the text column is 800px. Serving the master
would waste most of its bytes.
Derivatives are generated, hashed and gitignored. The repo holds masters only.
Missing figures are visible, then fatal
A figure with no master renders a dashed Screenshot pending capture box carrying its id,
so gaps are obvious in review instead of silent. STRICT_FIGURES=1 promotes that to a build
failure, which is how release builds are gated.
Prefer omitting a figure to referencing one you have not captured. An unwritten page with no figures is honest; a page full of pending boxes is not.
The release gate
Two things are fine while drafting and unacceptable in public: an uncaptured figure, and a
page still marked draft_stub: true. Both warn on an ordinary build and both fail under
npm run verify:release
which is npm run verify with STRICT_FIGURES=1 and STRICT_STUBS=1 set. Use it before
publishing; use plain verify while writing, so a half-finished section never blocks the
build you are iterating on.
Keeping them honest
| Command | What it does |
|---|---|
npm run figures:list | Scans the MDX for every referenced figure and writes the capture work list. |
npm run figures:check | Reports missing masters, orphaned files, dangling references and duplicate images. Always fatal. |
make figures m=<module> | Re-captures a module against the running local stack. |
make figures-bootstrap | Creates the dedicated capture workspace the harness signs in as. |
make figures-seed m=<module> | Fills that workspace with the sample data the module's screenshots show. |
The duplicate check matters more than it sounds: two figures with identical bytes almost always means a capture step silently did nothing.
Capture rules
Captures use a fixed 1440×900 viewport at 2×, the light theme, the en-US locale (so dates
appear exactly as the product formats them), UTC and reduced motion, and a spec masks anything
that changes on every run, such as timestamps and generated ids. Running a capture twice must
produce no file changes. If it does, something non-deterministic is leaking into the image,
and that is a harness bug rather than something to commit.
Captures run against a dedicated workspace, Northwind Trading, signed in as a fictional
user, so nothing personal or customer-related can reach this public site. make figures-seed m=<module> fills it with the sample data a module's screenshots need.
A step that says where to click gets a navigation figure: the screen the reader starts from, with the control the step names ringed in orange, the way a colleague would mark up a screenshot. They keep more of the screen than other figures (the top bar, the module sidebar), because the surrounding screen is what tells the reader where to look. A side panel is shot in a shorter window so its buttons sit under its fields rather than a screen's height below them.
A handful of figures cannot be automated: third-party sign-in screens and file pickers belong to other companies. Those are captured by hand into the same path and the same contract.
Next
- House style - voice, length and structure.
- Page template - where figures sit on a page.