Verify
The script catches mechanical failures. The screenshot is where you catch design failures. Do both, every time.
Run it
node ~/.config/opencode/lib/design/verify.mjs "design/Foo.html" --full --out design/.verify/foo.png
Common variants:
| Case | Flags |
|---|---|
| Deck, one shot per slide | --each "section" --step --each-out design/.verify/deck |
| Document, print width | --width 900 --height 1200 --full |
| Poster or fixed canvas | --width 1080 --height 1350 --full |
| Narrow panel check | --width 900 |
| Slow animation to settle | --settle 2000 |
It serves the file on a throwaway localhost port, loads it headless, and prints JSON plus a summary on stderr. Exit code 1 means something is wrong.
Read the output
errors— console errors and uncaught exceptions. Usually a script referencing a missing element. Fix the cause, don't silence it.failedRequests— missing fonts, images, sibling files. A missing font silently falls back and the whole type decision is lost, so this one matters more than it looks.horizontalScroll— the page is wider than the viewport. On screen that's a scrollbar nobody wants; in print it means content is cut off the right edge. Find the element from theoverflowinglist.metrics.fontFamilies— what actually rendered. If the display face isn't there, the font didn't load and you're reviewing the fallback.images[].loaded: false— broken asset or a wrong relative path.metrics.nodeCount— a sanity check, not a target.
Check token discipline
Before judging the design, check that it's actually using the system. When tokens.css sits next to the artifact, the verifier does this for you under tokens: it warns when the page doesn't link the file, and lists every colour literal — hex, oklch(), rgb() and the like — in the page's own CSS and in its other local stylesheets, such as components.css.
Every hit is either a token that should exist or a value that bypassed the file. Structural CSS (a deck's scale transform, a poster's grid) legitimately contains px numbers; colour literals almost never do. Neutral viewer chrome, like a deck's letterbox or the desk around a document page, uses named colours such as black and white.
Then look at the screenshot
Open the file. Judge, in this order:
- Hierarchy — does the eye land on the headline first, the supporting detail second? Squint: if everything has equal weight, there is no hierarchy.
- Spacing — consistent rhythm, and enough of it. Uneven gaps between groups are the most common tell of an unconsidered layout.
- Alignment — anything that should share a left edge and doesn't.
- Clipping and overlap — text touching or crossing a boundary, an element escaping its container.
- Contrast — muted grey on light grey is the default failure. Check the actual values, don't trust the screenshot rendering.
- Content — no filler, no placeholder text left behind, no numbers you invented.
Fix and re-run
Fix from the error text directly; don't re-read the whole file to find the line. Re-run after each substantive change. Three rounds is the limit — after that, hand it over with an honest note about what's still wrong.
Export
When the artifact is going somewhere without the folder:
node ~/.config/opencode/lib/design/inline.mjs "design/Foo.html"
That writes Foo.standalone.html with the local stylesheets copied in. Run the verifier on the standalone copy as well — inlining is where relative paths and url() references break.
Never say a design is done because the script exited 0. The script doesn't know whether it looks good.