Verify Like a User
You are not done when the code compiles. You are done when the page, as a person meets
it, does what they asked — and you can show the measurement that says so.
This skill is the discipline that turned a week of "looks fine in the diff" into
shipped UI: ground truth first, measure not eyeball, break it on purpose, fix the cause
not the symptom, re-measure, report honestly. It pairs with ui-ux (which
audits and polishes) — this one is about the loop that makes any UI change true.
Supporting files — read the one the step names:
- laws.md — the UX laws, each with the concrete rule it produced
- patterns.md — failure catalogue: symptom → cause → fix (search it first)
- measure.md — DOM measurement recipes and browser-automation gotchas
Output: {SKILL_OUTPUT_DIR}/verify-like-a-user/ — see ../OUTPUT.md
Step 0 — Mode
| Mode |
When |
Ends with |
| fix-a-screenshot |
user sends a screenshot and says what is wrong |
a measured before/after on that exact element |
| verify-a-change |
you just changed UI |
measurements at four widths + the click-through path |
| break-it |
"test like a user", "break the product" |
a ranked defect list with proof, then fixes |
| overhaul |
"this section is bad, redo it" |
audit → structure decision → rebuild → re-verify every page |
If the user's screenshot and your reading of the code disagree, the screenshot is
right. Find why the code you read is not what rendered (patterns.md §
"The class is in the source but not on the page").
Step 1 — Ground truth before pixels
- Read the project's design rules first:
DESIGN.md, CLAUDE.md, the tokens file,
globals.css. Note what a page title is (size/weight/face), what a section header
is, which radius tier means what, which tokens are serif.
- Find the page in the product that already does this right (the real settings page,
the real list page). Copy its classes, not its idea.
- Read laws.md. Decide, before touching anything, which structure the
screen needs: tabs vs rail, popup vs page, one column vs grid, one button that
flips vs two.
Do not start from what the previous version did. Start from what the rules say and
what the user said — in that order when they conflict, and say so.
Step 2 — Measure, don't eyeball
A screenshot tells you something is wrong. A measurement tells you what. Use
measure.md — every check below is one eval.
Minimum for any change to a screen:
- Widths: 1440, 1000, 640, 400. Primary action reachable at each (in the viewport,
or the container scrolls to it).
- Rects: the two things that must not overlap, do not. The thing that must align
with the box above it, does (same left/right edge to the pixel).
- Computed styles: the font-size and family that rendered on the element you
care about, not the class you wrote. Border width and style. Border radius on a tab.
- Overflow: which ancestor scrolls, and whether the content is taller than it.
scrollHeight > clientHeight on the wrong element is the dead end.
- Text:
scrollWidth > clientWidth on any title = clipping. Line-clamp actually
clamping (2 lines, not 4).
- State:
aria-selected, aria-pressed, aria-invalid on what you think is active.
Take the screenshot after the measurement, to see what the numbers mean.
Step 3 — Break it on purpose
Your own happy path proves nothing. Do these every time, and log each as pass or fail
with the number that proves it:
- The click-through path, not the direct URL. Navigate from where a person would
(the list, the sidebar, the card). Direct load and client navigation render
differently; redirects, layouts and loading states differ between them.
- Reload and Back mid-flow. Is typed work lost silently? Is there a
beforeunload guard where there should be?
- Short window. 1000×560. Is the primary button below a fold nothing scrolls to?
- Empty and full. Zero items, one item, forty items. Does an empty shelf say
"you have everything" or lie with "no matches"?
- Invalid input. Clear a required field, paste 10,000 characters, four emoji
(JavaScript counts UTF-16 units, not graphemes). Does the error name the field?
- Rapid double-click on any button that creates something. Count what got created.
- Mid-animation frames. Trigger a close or a transition and screenshot at ~80ms.
Seams, flashes and stray hairlines live there.
- Stale state. Change data another way (a script, another tab) and reload. Caches
with hour lifetimes, and code that assumes a row still exists, fail here.
- A second machine's view. Different host (
localhost vs 127.0.0.1), fresh
session, no cookies. The dev server treats them differently.
Rank what you find by what a person loses: dead ends and lost work first, lies
second ("Added" on something you cannot act on), looks last.
Step 4 — Fix the cause
Before writing a fix, look the symptom up in patterns.md. Most of these
have been paid for already:
- a rendered size that ignores the class → the class merger dropped it
- a border cut off at one edge → it was a shadow outside the box, clipped by a scroller
- content clipped equally top and bottom, nothing scrolls →
flex-1 centring, not
min-h-full
- three columns on one screen, two on a bigger one → viewport breakpoint measured the
window, not the pane; use container queries
- a redirect that renders an empty page and never moves the URL → do it in the router
config, not inside a nested segment
- a value imported from a
"use client" module is not an array → it is a client
reference; move the constant or make the consumer a client component
Fix the cause. If you also have to work around a symptom, say which is which in the
commit.
Then:
- Route every affordance for the same thing through one component (one back link,
one section header, one service mark). Eleven call sites edited by hand is eleven
chances to drift.
- One piece of state per control. A header that reads a prop snapshot and a footer
that reads live state will disagree the moment something changes.
- Never gate the input on decoration. The greeting can stream; the box appears on a
timer computed from the text, not on an event that may never fire.
Step 5 — Re-measure, then look once
Run the exact evals from Step 2 again. Numbers first, then one screenshot per
changed screen. If a screenshot contradicts a number, the screenshot wins — find out
why before moving on.
Do not call a flow done from one width, one path, or one screenshot. Two of the three
worst defects this skill was written from were only visible at 577px tall and only on
the click-through path.
Step 6 — Report honestly
Lead with what was driven (in a browser, against real data) versus what was only
compiled. Give the measurement, not the adjective:
Continue's right edge sits on the box's right edge (1027 = 1027). Meet your agent is
at 532 in a 577 viewport. Strip is the same DOM node across navigation; the brand
loading screen never appeared.
State what you could not reproduce and what you did not test. A user who is told "I
couldn't reproduce this at 1440 or 400 — send me your window size" can help you; one
told "fixed" cannot.
Write the record to {output_dir}/verify-YYYY-MM-DD-[slug].md in break-it and overhaul
modes: defects ranked, each with what you did / expected / got / proof, then the fixes
and their re-measurements.
Hard rules
- A screenshot from the user outranks your reading of the source.
- No "done" without a number from the rendered page.
- Four widths, the click-through path, a reload, and one mid-animation frame — every time.
- Search patterns.md before diagnosing from scratch; add to it when you find a new one.
- One component per recurring affordance; one state per control.
- Never commit a test file you did not run after editing it.
1---2name: verify-like-a-user3description: Build and check UI the way a person meets it — measure the rendered page instead of trusting the source, break it on purpose at several widths and along the paths people actually take, fix the root cause, and re-measure before calling it done. Carries a catalogue of UI failures that survive code review (clipped borders, dropped size classes, dead ends on short screens, stale caches, redirects that never move the URL) with symptom → cause → fix. Triggers on "verify this UI", "does this look right", "check it at other widths", "it looks bad, fix it", "break the product", "make these pages consistent", "why is this clipped / overflowing / huge / not clickable", "audit every page in this section", and any screenshot sent with "fix this".4---56# Verify Like a User78You are not done when the code compiles. You are done when the page, as a person meets9it, does what they asked — and you can show the measurement that says so.1011This skill is the discipline that turned a week of "looks fine in the diff" into12shipped UI: ground truth first, measure not eyeball, break it on purpose, fix the cause13not the symptom, re-measure, report honestly. It pairs with [`ui-ux`](../ui-ux/) (which14audits and polishes) — this one is about the loop that makes any UI change true.1516**Supporting files — read the one the step names:**17- [laws.md](laws.md) — the UX laws, each with the concrete rule it produced18- [patterns.md](patterns.md) — failure catalogue: symptom → cause → fix (search it first)19- [measure.md](measure.md) — DOM measurement recipes and browser-automation gotchas2021Output: `{SKILL_OUTPUT_DIR}/verify-like-a-user/` — see [../OUTPUT.md](../OUTPUT.md)2223---2425## Step 0 — Mode2627| Mode | When | Ends with |28|------|------|-----------|29| **fix-a-screenshot** | user sends a screenshot and says what is wrong | a measured before/after on that exact element |30| **verify-a-change** | you just changed UI | measurements at four widths + the click-through path |31| **break-it** | "test like a user", "break the product" | a ranked defect list with proof, then fixes |32| **overhaul** | "this section is bad, redo it" | audit → structure decision → rebuild → re-verify every page |3334If the user's screenshot and your reading of the code disagree, **the screenshot is35right**. Find why the code you read is not what rendered ([patterns.md](patterns.md) §36"The class is in the source but not on the page").3738---3940## Step 1 — Ground truth before pixels41421. Read the project's design rules first: `DESIGN.md`, `CLAUDE.md`, the tokens file,43 `globals.css`. Note what a page title *is* (size/weight/face), what a section header44 is, which radius tier means what, which tokens are serif.452. Find the page in the product that already does this right (the real settings page,46 the real list page). Copy its classes, not its idea.473. Read [laws.md](laws.md). Decide, before touching anything, which structure the48 screen needs: tabs vs rail, popup vs page, one column vs grid, one button that49 flips vs two.5051Do not start from what the previous version did. Start from what the rules say and52what the user said — in that order when they conflict, and say so.5354---5556## Step 2 — Measure, don't eyeball5758A screenshot tells you something is wrong. A measurement tells you what. Use59[measure.md](measure.md) — every check below is one `eval`.6061Minimum for any change to a screen:6263- **Widths:** 1440, 1000, 640, 400. Primary action reachable at each (in the viewport,64 or the container scrolls to it).65- **Rects:** the two things that must not overlap, do not. The thing that must align66 with the box above it, does (same left/right edge to the pixel).67- **Computed styles:** the font-size and family that *rendered* on the element you68 care about, not the class you wrote. Border width and style. Border radius on a tab.69- **Overflow:** which ancestor scrolls, and whether the content is taller than it.70 `scrollHeight > clientHeight` on the wrong element is the dead end.71- **Text:** `scrollWidth > clientWidth` on any title = clipping. Line-clamp actually72 clamping (2 lines, not 4).73- **State:** `aria-selected`, `aria-pressed`, `aria-invalid` on what you think is active.7475Take the screenshot *after* the measurement, to see what the numbers mean.7677---7879## Step 3 — Break it on purpose8081Your own happy path proves nothing. Do these every time, and log each as pass or fail82with the number that proves it:83841. **The click-through path, not the direct URL.** Navigate from where a person would85 (the list, the sidebar, the card). Direct load and client navigation render86 differently; redirects, layouts and loading states differ between them.872. **Reload and Back mid-flow.** Is typed work lost silently? Is there a88 `beforeunload` guard where there should be?893. **Short window.** 1000×560. Is the primary button below a fold nothing scrolls to?904. **Empty and full.** Zero items, one item, forty items. Does an empty shelf say91 "you have everything" or lie with "no matches"?925. **Invalid input.** Clear a required field, paste 10,000 characters, four emoji93 (JavaScript counts UTF-16 units, not graphemes). Does the error name the field?946. **Rapid double-click** on any button that creates something. Count what got created.957. **Mid-animation frames.** Trigger a close or a transition and screenshot at ~80ms.96 Seams, flashes and stray hairlines live there.978. **Stale state.** Change data another way (a script, another tab) and reload. Caches98 with hour lifetimes, and code that assumes a row still exists, fail here.999. **A second machine's view.** Different host (`localhost` vs `127.0.0.1`), fresh100 session, no cookies. The dev server treats them differently.101102Rank what you find by what a person loses: dead ends and lost work first, lies103second ("Added" on something you cannot act on), looks last.104105---106107## Step 4 — Fix the cause108109Before writing a fix, look the symptom up in [patterns.md](patterns.md). Most of these110have been paid for already:111112- a rendered size that ignores the class → the class merger dropped it113- a border cut off at one edge → it was a shadow outside the box, clipped by a scroller114- content clipped equally top and bottom, nothing scrolls → `flex-1` centring, not115 `min-h-full`116- three columns on one screen, two on a bigger one → viewport breakpoint measured the117 window, not the pane; use container queries118- a redirect that renders an empty page and never moves the URL → do it in the router119 config, not inside a nested segment120- a value imported from a `"use client"` module is not an array → it is a client121 reference; move the constant or make the consumer a client component122123Fix the cause. If you also have to work around a symptom, say which is which in the124commit.125126Then:127- Route every affordance for the same thing through **one component** (one back link,128 one section header, one service mark). Eleven call sites edited by hand is eleven129 chances to drift.130- **One piece of state per control.** A header that reads a prop snapshot and a footer131 that reads live state will disagree the moment something changes.132- Never gate the input on decoration. The greeting can stream; the box appears on a133 timer computed from the text, not on an event that may never fire.134135---136137## Step 5 — Re-measure, then look once138139Run the exact `eval`s from Step 2 again. Numbers first, then one screenshot per140changed screen. If a screenshot contradicts a number, the screenshot wins — find out141why before moving on.142143Do not call a flow done from one width, one path, or one screenshot. Two of the three144worst defects this skill was written from were only visible at 577px tall and only on145the click-through path.146147---148149## Step 6 — Report honestly150151Lead with what was **driven** (in a browser, against real data) versus what was only152**compiled**. Give the measurement, not the adjective:153154> Continue's right edge sits on the box's right edge (1027 = 1027). Meet your agent is155> at 532 in a 577 viewport. Strip is the same DOM node across navigation; the brand156> loading screen never appeared.157158State what you could not reproduce and what you did not test. A user who is told "I159couldn't reproduce this at 1440 or 400 — send me your window size" can help you; one160told "fixed" cannot.161162Write the record to `{output_dir}/verify-YYYY-MM-DD-[slug].md` in break-it and overhaul163modes: defects ranked, each with what you did / expected / got / proof, then the fixes164and their re-measurements.165166---167168## Hard rules1691701. A screenshot from the user outranks your reading of the source.1712. No "done" without a number from the rendered page.1723. Four widths, the click-through path, a reload, and one mid-animation frame — every time.1734. Search [patterns.md](patterns.md) before diagnosing from scratch; add to it when you find a new one.1745. One component per recurring affordance; one state per control.1756. Never commit a test file you did not run after editing it.