vision-verify — the visual gate
verify-build proves a change compiles and passes tests. It cannot prove the
page looks right — code review reasons about JSX/CSS, it never sees the result.
This skill closes that gap: render → screenshot → an independent visual grade
against a rubric. It is the visual complement to verify-build, not a
replacement — run both for UI-affecting work.
Proven 2026-07-05: caught a duplicate "Services" nav item on a live r1 site
that unit tests, tsc, next build, AND two code reviews all missed — then
confirmed the fix by re-screenshot. First bug on SF caught by seeing, not
reasoning about code.
When to use
- After ANY UI-affecting change, before "done" — especially landing/site render
(hero, nav, sections), the dashboard shell, or anything themed.
- To confirm a fix visually landed on a live/preview URL (the found → fix →
confirm loop).
Steps
Get a public URL to the page — a deployed /w/<slug>, the
<slug>.app.seldonframe.com subdomain, or a Vercel preview URL. It must be
publicly reachable (the renderer fetches it). If you just pushed a fix, first
confirm the deploy is live: curl -s https://app.seldonframe.com/api/version
→ the sha should be your commit (else wait for the deploy).
Render (from packages/crm), at desktop and — for anything responsive —
mobile:
node scripts/vision-shot.mjs <url> /tmp/vv-desktop.png 1280
node scripts/vision-shot.mjs <url> /tmp/vv-mobile.png 390
It cache-busts so a just-deployed change is captured fresh. Prints the saved
path. (Set MICROLINK_API_KEY for higher rate limits; the free endpoint works
without it, but can rate-limit on rapid retries — space them out.)
Grade — independent (maker ≠ checker), on haiku — PINNED. Dispatch the
vision-grader agent (Agent tool, subagent_type: "vision-grader" — its
definition in .claude/agents/vision-grader.md pins model: haiku; do NOT
pass a model override) — it Reads the PNG and grades it against the goal +
a rubric, returning { pass: boolean, gaps: string[] }. Read-a-PNG→verdict
is haiku work; a real session ran 4 graders on sonnet (~218k tokens — the
session's biggest line item) purely because the model was left to
dispatch-time habit. The pin lives in the agent definition so it can't
drift. (If haiku ever provably misses what sonnet catches — run 10
known-good + 10 known-bad screenshots — change the pin there, on evidence,
not per-dispatch.)
Do NOT let the code's author grade its own pixels — a fresh grader sees only
the artifact + the rubric, with no stake in the maker's reasoning. For a
quick self-check inline, Read the PNG yourself — but a real gate uses a
separate grader.
Prefer a before/after DIFF grade when a baseline exists: hand the grader
both screenshots and ask "what changed — is the change the requested one,
and did anything regress?" A visual diff is more sensitive to regressions
(the duplicate-nav class) and less subjective than an absolute grade.
Act. Pass → done. Gaps → feed each gap back to the maker → re-render →
re-grade until pass or a hard iteration cap (an objective stop, like /goal).
Rubric (adapt per surface)
Generic: renders with no broken images / empty sections; text is legible
(contrast); nothing overlaps or overflows the viewport (no horizontal scroll);
no duplicated nav/section elements; the specific change requested is visibly
present; nothing is truncated mid-word in a jarring way.
Per-surface hints:
- Hero: headline hierarchy clear; subhead + CTAs legible; any
lead-form-in-hero renders; a set background sits BEHIND text with a legibility
veil (never washing out the copy).
- Nav: exactly one of each item (the duplicate-"Services" class of bug);
wordmark + phone/CTA present.
- Pricing / tables: columns aligned; numbers tabular; no clipped cells.
- Dashboard shell: no sideways scroll; the summary reads before the detail.
Notes
- The Chrome-MCP
screenshot path is unreliable here (CDP clip.scale +
backgrounded-tab 0-viewport) — use vision-shot.mjs.
- Product version (next build): the same render+grade engine, server-side,
becomes SeldonChat's "confirm the edit looks right before I say done" and a
gate in the site-generation pipeline — the never-lies pillar made mechanical.
Spec:
docs/superpowers/specs/2026-07-05-vision-verify-spike.md.
1---2name: vision-verify3description: vision-verify — the visual gate4---56# vision-verify — the visual gate78`verify-build` proves a change *compiles and passes tests*. It cannot prove the9page *looks right* — code review reasons about JSX/CSS, it never sees the result.10This skill closes that gap: **render → screenshot → an independent visual grade11against a rubric.** It is the visual complement to `verify-build`, not a12replacement — run both for UI-affecting work.1314**Proven 2026-07-05:** caught a duplicate "Services" nav item on a live r1 site15that unit tests, tsc, `next build`, AND two code reviews all missed — then16confirmed the fix by re-screenshot. First bug on SF caught by *seeing*, not17reasoning about code.1819## When to use20- After ANY UI-affecting change, before "done" — especially landing/site render21 (hero, nav, sections), the dashboard shell, or anything themed.22- To confirm a fix visually landed on a live/preview URL (the found → fix →23 **confirm** loop).2425## Steps26271. **Get a public URL** to the page — a deployed `/w/<slug>`, the28 `<slug>.app.seldonframe.com` subdomain, or a Vercel preview URL. It must be29 publicly reachable (the renderer fetches it). If you just pushed a fix, first30 confirm the deploy is live: `curl -s https://app.seldonframe.com/api/version`31 → the `sha` should be your commit (else wait for the deploy).32332. **Render** (from `packages/crm`), at desktop and — for anything responsive —34 mobile:35 ```36 node scripts/vision-shot.mjs <url> /tmp/vv-desktop.png 128037 node scripts/vision-shot.mjs <url> /tmp/vv-mobile.png 39038 ```39 It cache-busts so a just-deployed change is captured fresh. Prints the saved40 path. (Set `MICROLINK_API_KEY` for higher rate limits; the free endpoint works41 without it, but can rate-limit on rapid retries — space them out.)42433. **Grade — independent (maker ≠ checker), on `haiku` — PINNED.** Dispatch the44 **`vision-grader` agent** (Agent tool, `subagent_type: "vision-grader"` — its45 definition in `.claude/agents/vision-grader.md` pins `model: haiku`; do NOT46 pass a model override) — it `Read`s the PNG and grades it against the goal +47 a rubric, returning `{ pass: boolean, gaps: string[] }`. Read-a-PNG→verdict48 is haiku work; a real session ran 4 graders on sonnet (~218k tokens — the49 session's biggest line item) purely because the model was left to50 dispatch-time habit. The pin lives in the agent definition so it can't51 drift. (If haiku ever provably misses what sonnet catches — run 1052 known-good + 10 known-bad screenshots — change the pin there, on evidence,53 not per-dispatch.)54 Do NOT let the code's author grade its own pixels — a fresh grader sees only55 the artifact + the rubric, with no stake in the maker's reasoning. For a56 quick self-check inline, `Read` the PNG yourself — but a real gate uses a57 separate grader.58 **Prefer a before/after DIFF grade when a baseline exists:** hand the grader59 both screenshots and ask "what changed — is the change the requested one,60 and did anything regress?" A visual diff is more sensitive to regressions61 (the duplicate-nav class) and less subjective than an absolute grade.62634. **Act.** Pass → done. Gaps → feed each gap back to the maker → re-render →64 re-grade until pass or a hard iteration cap (an objective stop, like `/goal`).6566## Rubric (adapt per surface)67Generic: renders with no broken images / empty sections; text is legible68(contrast); nothing overlaps or overflows the viewport (no horizontal scroll);69**no duplicated nav/section elements**; the specific change requested is visibly70present; nothing is truncated mid-word in a jarring way.7172Per-surface hints:73- **Hero:** headline hierarchy clear; subhead + CTAs legible; any74 lead-form-in-hero renders; a set background sits BEHIND text with a legibility75 veil (never washing out the copy).76- **Nav:** exactly one of each item (the duplicate-"Services" class of bug);77 wordmark + phone/CTA present.78- **Pricing / tables:** columns aligned; numbers tabular; no clipped cells.79- **Dashboard shell:** no sideways scroll; the summary reads before the detail.8081## Notes82- The Chrome-MCP `screenshot` path is unreliable here (CDP `clip.scale` +83 backgrounded-tab 0-viewport) — use `vision-shot.mjs`.84- **Product version (next build):** the same render+grade engine, server-side,85 becomes SeldonChat's "confirm the edit looks right before I say done" and a86 gate in the site-generation pipeline — the never-lies pillar made mechanical.87 Spec: `docs/superpowers/specs/2026-07-05-vision-verify-spike.md`.