How this multi-step form / checkout was judged, in the order it happened: every gate run with its whole output, every screenshot that was looked at with what was seen in it, and where each defect came from. Each finished round keeps the pictures of one full-page walk per width; the other shots keep what was seen in them. Open the site.
The brief the builder was given
# Brief: multi-step checkout — "Pantry Club" meal-kit subscription
- **Directory**: `examples/sites/checkout/`
- **Pattern**: multi-step form / checkout
- **Language**: English
## What it is
The sign-up and checkout flow of **Pantry Club**, a fictional weekly meal-kit subscription. One page,
`index.html`, walking through four steps. A notice says plainly that it is a demo and takes no
payment. Nothing is sent anywhere.
## Layout and behaviour
- **Steps**: 1 Plan · 2 Delivery · 3 Payment · 4 Review, shown as an ordered list with the current
step marked (`aria-current="step"`). Continue and Back buttons; moving to a step puts focus on its
heading.
- **1 Plan**: three plans as radio cards (2 people · 3 meals a week, $59.94; 4 people · 3 meals a
week, $107.88; 2 people · 5 meals a week, $94.90), dietary preferences as checkboxes (Vegetarian,
No pork, Dairy-free, Nut-free) and a delivery-day select.
- **2 Delivery**: full name, email, phone (optional), address line 1, address line 2 (optional),
city, state (select), ZIP code, and delivery instructions (optional, with a 200-character
counter). Use the right `autocomplete` values.
- **3 Payment**: card number (grouped in fours as you type), expiry MM/YY, CVC and name on card; a
"Billing address is the same as delivery" checkbox, checked by default, whose unchecking reveals
the billing fields; a promo code field — `WELCOME10` takes 10% off, anything else shows an error.
- **4 Review**: every choice summarised with an Edit link back to its step, a terms checkbox, and
"Place order", which replaces the form with a confirmation: order number PC-20417 and the first
delivery date.
- **Validation**: Continue on an invalid step shows an error summary at the top of the step with
links to each field, marks each field (`aria-invalid`, message tied with `aria-describedby`, an
icon and text — not colour alone) and moves focus to the summary. After the first attempt, fields
re-validate as they are left.
- **Order summary**: a sidebar that stays in view on desktop and is a collapsible "Show order
summary" panel on phones; it follows the plan, the promo code, free shipping, tax at 8% and the
total.
- Also write a `flow.json` for `vlmkit verify flow` that completes the whole checkout.
## Visual direction
Trustworthy and friendly: white and warm greys, one forest-green accent, large tap targets, text
fields whose boundaries are clearly visible, strong focus states, and errors that are impossible
to miss without being alarming.
## Required copy
```
Pantry Club
Choose your plan
Delivery details
Payment
Review your order
Order summary
Continue
Place order
This is a demo. No payment is taken.
Thanks — your first box is on its way.
```
## States to show (final round)
Each of the four steps on desktop; step 2 with validation errors; the promo code accepted and
rejected; the billing fields revealed; the confirmation; step 3 on a phone with the order summary
expanded.
## Gates this brief leans on
`check color` (text-field boundaries, WCAG 1.4.11), `check a11y focus` (focus moves between steps and
to errors), `check a11y touch`, `check interactions` (radio cards, checkboxes, reveal, errors),
`verify flow` (the whole checkout), `check layout` or `check scroll` (the sticky summary).
index.html · 1280×800 · full page, 2 screen(s)


L1 sawDesktop 1280, step 1 at load, two screens. Screen 1: sand demo bar across the top reads "(i) This is a demo. No payment is taken. Pantry Club is fictional…" in one line; white brand bar with the green bowl mark + "Pantry Club" left and a grey bold "Checkout" far right that looks like it could be a link but is plain text. Left column: 4-step tracker (1 green ring with pale halo "Plan" bold; 2-4 grey outlined circles, grey labels, grey connectors), then the white step card: "Choose your plan" h1, two-line intro, "Box size" legend, three plan rows. Selected row 1 has a green 2px border + mint tint + filled radio; prices right-aligned ($59.94 / a week). Row 2 is taller than rows 1 and 3 because the "Best value" pill sits on its own third line, so the three rows are 78/108/78px — uneven rhythm. Right column: sticky white "Order summary" card; the box illustration reads as a parcel; the item name breaks badly as "2 people · 3 / meals a week" and the meta as "6 servings · $9.99 per / serving" (orphan). Totals block is clear: Subtotal $59.94, Shipping Free (green), Tax (8%) $4.80, bold "Total today $64.74", renewal note. Screen 2 (scrolled 541): summary stays pinned at top 24px (sticky works); 2x2 checkbox tiles, "Delivery day" label + hint + 320px select "Select a day" with chevron; hairline, then "Continue" green button bottom right; footer line. Nothing clipped or overlapping.
D1 defect .summary-item__name / .summary-item__meta (desktop summary)The order-summary item breaks badly in the 360px card: the name wraps as '2 people · 3 / meals a week' (the number stranded from its noun) and the meta as '6 servings · $9.99 per / serving' (orphan), because the $59.94 price column squeezes the text to ~160px.
D2 defect .plan__badge (4-person card)The 'Best value' pill sits on its own line under the meta, so the 4-person card is 108px tall against 78px for the other two; the three plan rows lose their even rhythm and the pill reads as an afterthought rather than a label.
D5 defect .brand-bar__metaA grey bold 'Checkout' sits alone at the right of the brand bar, exactly where a Help or Sign-in link would be; it is plain text, so it looks clickable and does nothing, and it says nothing the page does not already say.
index.html · 375×812 · full page, 3 screen(s)
L2 sawMobile 375, step 1, three screens. Screen 1: the demo note wraps to 3 lines in the sand bar; brand bar with mark, "Pantry Club" and the same grey "Checkout" at right; full-bleed summary toggle row "bag icon Show order summary v $64.74" — reads well as a disclosure with the total visible. Tracker fits in one row (Plan bold/current, Delivery, Payment, Review). Step card with 16px gutters: "Choose your plan" big h1, 3-line intro, "Box size". Plan cards stack people / meals / servings / per-serving on the left with price right. Card 2 wraps its title as "3 meals a / week" because the $107.88 column squeezes it — an orphaned word, and it makes card 2 five lines tall with the "Best value" pill. Screen 2: third card, "Dietary preferences (optional)" + hint, four full-width checkbox tiles (52px, good tap size), "Delivery day" + 2-line hint + full-width select, full-width green "Continue". Screen 3: end of card and the footer sentence. No horizontal overflow visible; spacing between groups is consistent (~32px).
D3 defect .plan:nth-child(2) .plan__meals @375On a phone the 4-person card wraps its title as '3 meals a / week' — the wide $107.88 price column leaves ~135px for the text, stranding 'week' on a line of its own.
S3delivery day chosen by keyboard (ArrowDown x2)
index.html · 1280×800 · element .field--day · focus #day · press ArrowDown · press ArrowDown
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L3 sawClose-up of the Delivery day field after focus + ArrowDown twice: the closed select now reads "Tuesday" (keyboard changes the value without opening the list in this Chromium), and the change handler ran — a green line with a small truck icon appeared under it: "Your first box arrives Tuesday, October 13." with the date in bold. The focus ring is a 3px dark-green outline offset 2px from the grey field border, clearly visible against white. Label, hint and control stack with even ~6px gaps. This proves the flow can choose select options with press ArrowDown.
S4step 2: state chosen by typeahead 'Oregon'
index.html · 1280×800 · element #step-2 .fields · focus #day · press ArrowDown · click #step-1 button[type=submit] · focus #state · type Oregon
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L4 sawClose-up of step 2's field grid (desktop) after reaching it with a day chosen and typing "Oregon" into State: typeahead selected Oregon and the select shows the dark-green focus ring. Problem: the Email and Phone pair are misaligned — the Email hint "For your receipt and delivery updates." fits one line but the Phone hint "Only if there's a problem with a / delivery." wraps to two, so the Phone input sits ~20px lower than the Email input (tops ~153 vs ~173) and the pair no longer reads as one row. City / State / ZIP (no hints) line up correctly. Field borders are clearly visible grey on white; labels bold, "(optional)" in grey regular. The empty 3-row textarea and "200 characters left" counter sit under the ZIP row. The address grid otherwise has an even rhythm.
D4 defect #email / #phone row (step 2)Email and Phone sit side by side with hints of different length; the Phone hint wraps to two lines, so the Phone input is ~20px lower than the Email input and the pair no longer reads as one row.
G1exit 0vlmkit check integrity index.htmlverdict: CLEAN (0 fail, 0 warn, 45 exempted)
verdict clean · findings 0 · fails 0 · warns 0 · exempted 45 · viewports 3
output, 27 line(s), 4.3s
vlmkit check integrity
source: index.html
verdict: CLEAN (0 fail, 0 warn, 45 exempted)
1280x800: 8 component(s), ink 96.4%, 67 text block(s)
768x900: 8 component(s), ink 95.4%, 52 text block(s)
375x700: 8 component(s), ink 95.0%, 55 text block(s)
No integrity defects detected.
Exempted candidates (the tool's call — audit the rule, not the page):
- [text-collision] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] span.plan__badge > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
… 30 more
G2exit 1vlmkit check copy index.html --manifest copy.txtmissing 4 · placeholders 0 · manifestLines 10 · statesExplored 1 · revealedOnly 0
output, 13 line(s), 1.4s
vlmkit check copy
source: index.html
status: suspect
rendered text: 1160 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 10 line(s), missing 4
Issues:
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Delivery details" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Review your order" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Place order" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Thanks — your first box is on its way." (comparison is whitespace-normalized, case-sensitive).
G3exit 0vlmkit check design index.htmlverdict: COHERENT (1 finding(s))
verdict coherent · drifting 0 · roles 8
output, 26 line(s), 1.6s
vlmkit check design
source: index.html
verdict: COHERENT (1 finding(s))
roles judged: 1 of 8 seen, spacing values: 18
coverage: 12 of 152 visible element(s) carried an inferable role; 1 skipped as non-resting
no role: span x56, div x21, p x11, label x8, path x8, dd x6, +14 more tag(s)
A role comes from role="..." or from button/input/select/textarea/h1-h6. Layout elements
(div, span, p, a) have none, so a large skip count is normal — this gate judges components,
not every box. Add role="..." where an element IS a component to widen the coverage.
Role reuse (instances / distinct styles; drift below 3x from 3 instances)
input:checkbox 4 inst 1 styles reuse 4x 0 one-off ok
input:radio 2 inst 1 styles reuse 2x 0 one-off not judged
note 1 inst 1 styles reuse 1x 1 one-off not judged
h2 1 inst 1 styles reuse 1x 1 one-off not judged
list 1 inst 1 styles reuse 1x 1 one-off not judged
h1 1 inst 1 styles reuse 1x 1 one-off not judged
select 1 inst 1 styles reuse 1x 1 one-off not judged
button 1 inst 1 styles reuse 1x 1 one-off not judged
not judged: input:radio (2), note (1), h2 (1), list (1), h1 (1), select (1), button (1) — under --min-instances 3, so no finding can come from them.
To keep judging them: --min-instances 2 --min-reuse 2 (both — a 2-instance role cannot reach 3x however many instances the floor allows).
Informational (true, but does not carry the verdict)
i [scale-outlier]: 3 spacing value(s) sit just off the page's own scale: 11px (2x) next to 10px (10x) — select#day paddingTop; 20px (2x) next to 18px (8x) — main#main rowGap; 44px (1x) next to 40px (5x) — select#day paddingRight. Snap them to the established value or add them deliberately.D7 defect select padding, main#main row-gapcheck design flags three spacing values just off the page's own scale: 11px input padding next to the established 10px, the 20px main row gap next to 18px, and 44px select right padding next to 40px.
G4exit 0vlmkit check composition index.htmlverdict: COMPOSED (0 finding(s))
verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0
output, 16 line(s), 1.7s
vlmkit check composition
source: index.html
verdict: COMPOSED (0 finding(s))
measured: 0 label(s), 2 heading level(s), 31 wide block(s) on 4 left / 4 right rail(s) — from 137 visible box(es)
2 label(s) not judged: they open a box that paints its own group edge, so nothing above them could be mis-grouped with
Type hierarchy (largest instance of each declared level)
h1 30px / 700 h1#step-1-title
h2 18px / 700 h2#summary-title
body 16px, largest 30px (1.88x), heaviest +400 weight
Group gaps: 20px between heading-led groups vs 8px inside them (2.5x, 1 sample(s)). Context only — intact pages measure 0.86-3.00x, so this cannot carry a verdict.
Proximity, alignment and type hierarchy all read consistently.
G5exit 0vlmkit check color index.htmlverdict: CONSISTENT (0 finding(s))
verdict consistent · base #f6f3ee · bodyInk #1f1c19 · linkInk #000000 · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0
output, 25 line(s), 1.6s
vlmkit check color
source: index.html
verdict: CONSISTENT (0 finding(s))
measured: 1 control(s), 0 link(s) in a text flow — from 160 visible box(es)
Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
surfaces 4 distinct
#f6f3ee 58.9% 1 el
#ffffff 37.6% 20 el header.site-header>div.brand-bar
#f4ecdc 2.0% 2 el header.site-header>div.demo-notice
#edf6f0 1.6% 1 el div.plans>label.plan>input.plan__input
ink 4 distinct
#1f1c19 54.7% 42 el div.demo-notice>div.wrap>p
#6a635b 32.4% 26 el div.summary-item>div.summary-item__text>p.summary-item__meta
#57514a 9.2% 5 el div.brand-bar>div.wrap>p.brand-bar__meta
#ffffff 3.6% 3 el label.plan>span.plan__body>span.plan__badge
marks 5 distinct
#857d73 37.2% 64 el ol#steps>li.steps__item>span.steps__marker
#dcd5ca 35.4% 10 el header.site-header>div.brand-bar
#2d6a4f 14.3% 12 el ol#steps>li.steps__item>span.steps__marker
#e7e1d8 7.1% 4 el div#summary-panel>div.summary-item
#e6d9bf 6.0% 1 el header.site-header>div.demo-notice
base #f6f3ee, body ink #1f1c19, link ink #000000 on 7 element(s) G6exit 0vlmkit check a11y contrast index.htmlinspected 81 · failures 0
output, 5 line(s), 1.5s
vlmkit check a11y contrast
html: examples/sites/checkout/index.html
inspected 81 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-ca6a16e3/report.md
G7exit 1vlmkit check a11y focus index.htmlsteps 7 · findings 1
output, 6 line(s), 1.6s
vlmkit check a11y focus
source: index.html
captured 7 focus step(s)
✗ 1 finding(s)
[reverse] Focus moved up by 81px (from `form#step-1>fieldset.group>div.plans>label.plan>input.plan__input` at y=414 to `form#step-1>fieldset.group>div.choices>label.choice>input.choice__input` at y=334). Visual order is top-to-bottom; check `tabindex` or DOM order.
report: examples/sites/checkout/test-results/a11y-focus-order/index-ca6a16e3/report.md G8exit 0vlmkit check a11y touch index.htmllevel AA · inspected 9 · failures 0 · wcagExempt 0
output, 5 line(s), 1.5s
vlmkit check a11y touch
source: index.html level: WCAG AA (24×24 min)
inspected 9 interactive element(s)
✓ 0 undersized target(s)
report: examples/sites/checkout/test-results/a11y-touch/index-ca6a16e3/report.md
S5focus-order check: 2 Tabs at the gate's 1280x720 (radio -> Vegetarian)
index.html · 1280×720 · press Tab · press Tab
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L5 sawViewport shot at the focus gate's own 1280x720 after pressing Tab twice. Focus is on the "Vegetarian" tile: a 3px dark-green ring offset 3px around the tile, clearly visible. The browser has scrolled the page to centre it: the tile's top sits at viewport y≈334, and the three plan cards are ABOVE it (card 1 is scrolled almost off the top, only its bottom edge and "a week" visible; cards 2 and 3 at y≈38-238). So focus moved from the first plan radio (page y≈414) DOWN to the checkbox (page y≈801); the 334 in the gate report is a post-scroll viewport coordinate. Tab order matches reading order: plans, then the four diet tiles left-to-right, then the select, then Continue. The sticky summary stays pinned at top 24px while the column scrolls.
check a11y focus reports 'reverse: focus moved up by 81px' from the plan radio (y=414) to the Vegetarian checkbox (y=334). Both y values are VIEWPORT coordinates read after the browser scrolled the new focus into view: its own report lists Continue at y=639 although it sits at page y~1106 (S1-2), and S5 (Tab x2 at the gate's 1280x720) shows the focused Vegetarian tile at viewport y~334 BELOW the plan cards, which scrolled up past the top. In page coordinates focus moves down (414 -> ~801). The DOM order equals the visual order and no tabindex is used on the page.
check a11y focus compares focus positions in viewport coordinates without adding the scroll offset, so any Tab stop that makes the page scroll reads as moving backward. Verbatim: '[reverse] Focus moved up by 81px (from `form#step-1>fieldset.group>div.plans>label.plan>input.plan__input` at y=414 to `form#step-1>fieldset.group>div.choices>label.choice>input.choice__input` at y=334). Visual order is top-to-bottom; check `tabindex` or DOM order.' The report's own table shows it: stops 1-6 have y 334, 334, 396, 396, 534, 639 — all inside a 720px viewport although the page is 1341px tall. Any form longer than one screen will trip this.
G9exit 0vlmkit scan scroll index.htmlcontainers 0 · overflowX 0 · issues 13
output, 25 line(s), 1.4s
vlmkit scan scroll
source: index.html (1280x720)
status: warn
exits 0 — 13 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x1341 — horizontal overflow 0px, vertical scroll 621px
scroll containers: 0 (dead scrollports 1, clipped 13)
Dead scrollports (declared scrollable, content fits):
- #instructions (overflow: auto auto)
Issues:
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content span.plan__badge > span:nth-of-type(1): span.plan__badge > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
G10exit 0vlmkit scan handlers index.htmlregistrations 37 · elements 33 · suspects 0
output, 43 line(s), 1.7s
vlmkit scan handlers
source: index.html
status: 1 warn(s)
exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule unprobed-handler-types=suspect
registrations: 37 across 33 element(s), on a page presenting 9 control(s)
- main#main>div.flow>form#step-1 "Choose your plan Fresh ingredients and e": submit, focusout (delegation container)
- main#main>div.flow>form#step-2 "Delivery details Where should we bring y": submit, focusout (no role)
- main#main>div.flow>form#step-3 "Payment Use a test card: this demo never": submit, focusout (no role)
- main#main>div.flow>form#step-4 "Review your order Check the details, the": submit, focusout (no role)
- div.flow>form#step-2>div.actions>button.btn "Back": click (no role)
- div.flow>form#step-3>div.actions>button.btn "Back": click (no role)
- div.flow>form#step-4>div.actions>button.btn "Back": click (no role)
- div.review>section.review__block>div.review__head>a.review__edit "Edit plan": click (no role)
- div.review>section.review__block>div.review__head>a.review__edit "Edit delivery details": click (no role)
- div.review>section.review__block>div.review__head>a.review__edit "Edit payment": click (no role)
- main#main>div.flow>form#step-1>div#errors-1 "": click (no role)
- main#main>div.flow>form#step-2>div#errors-2 "": click (no role)
- main#main>div.flow>form#step-3>div#errors-3 "": click (no role)
- main#main>div.flow>form#step-4>div#errors-4 "": click (no role)
- fieldset.group>div.plans>label.plan>input.plan__input "2p3m": change
- fieldset.group>div.plans>label.plan>input.plan__input "4p3m": change
- fieldset.group>div.plans>label.plan>input.plan__input "2p5m": change
- fieldset.group>div.choices>label.choice>input.choice__input "Vegetarian": change
- fieldset.group>div.choices>label.choice>input.choice__input "No pork": change
- fieldset.group>div.choices>label.choice>input.choice__input "Dairy-free": change
- fieldset.group>div.choices>label.choice>input.choice__input "Nut-free": change
- form#step-1>div.field>div.select>select#day "Select a day Monday Tuesday Wednesday Th": change
- div.fields>div.field>div.select>select#state "Select AlabamaAlaskaArizonaArkansasCalif": change (no role)
- div#billing-fields>div.field>div.select>select#billing-state "Select AlabamaAlaskaArizonaArkansasCalif": change (no role)
- form#step-4>div.field>label.choice>input#terms "": change (no role)
- form#step-2>div.fields>div.field>textarea#instructions "": input (no role)
- fieldset.group>div.fields>div.field>input#card-number "": input (no role)
- fieldset.group>div.fields>div.field>input#expiry "": input (no role)
- fieldset.group>div.fields>div.field>input#cvc "": input (no role)
- form#step-3>fieldset.group>label.choice>input#billing-same "": change (no role)
- form#step-3>div.field>div.promo__row>button#promo-apply "Apply": click (no role)
- form#step-3>div.field>div.promo__row>input#promo "": keydown (no role)
- main#main>aside#summary>button#summary-toggle "Show order summary $64.74": click (no role)
Issues:
warn [unprobed-handler-types] 6 handler type(s) registered and NONE exercised: change, click, focusout, input, keydown, submit — this gate is an inventory and presses nothing. A clean result here says the wiring exists, not that it works. Run 'check interactions --handlers' to focus and activate the controls, add '--probe-drag' for the drag surfaces, or drive the rest with a 'verify flow' script.
G11exit 0vlmkit check interactions index.htmlelements 9 · suspects 0 · warns 0
output, 15 line(s), 10.0s
vlmkit check interactions
source: index.html
status: ok
interactive elements: 9
- [radio] "2p3m" focus✓ | ArrowDown: layout change
- [radio] "4p3m" unreachable | ArrowDown: layout change
- [radio] "2p5m" unreachable | ArrowDown: focus moves
- [checkbox] "Vegetarian" focus✓ | Space: layout change
- [checkbox] "No pork" focus✓ | Space: layout change
- [checkbox] "Dairy-free" focus✓ | Space: layout change
- [checkbox] "Nut-free" focus✓ | Space: layout change
- [combobox] "Select a day Monday Tuesday Wednesday Thursday Friday Saturday" focus✓ | ArrowDown: layout change | Esc closes
- [button] "Continue" focus✓ | Enter: focus moves within
G12exit 1vlmkit verify flow index.html --flow flow.jsonverdict: FAILED (16/34 steps)
done false · passed 16 · total 34
output, 66 line(s), 2.1s
vlmkit verify flow
source: index.html
verdict: FAILED (16/34 steps)
✓ step 1: Step 1: Continue with no delivery day shows the error summary and moves focus to it
✓ #errors-1 visible
✓ #errors-1 focused
✓ #errors-1 text~"Select a delivery day"
✓ #day aria-invalid=true
✓ #day aria-describedby=day-hint day-error
✓ #day-error visible
✓ step 2: Choosing a day re-validates the field and clears the summary
✓ #day-error count=0
✓ #errors-1 hidden
✓ #day-note text~"Monday, October 12"
✓ #summary-panel text~"Mon, Oct 12"
✓ step 3: The 4-person radio card updates the order summary
✓ #summary-panel text~"4 people · 3 meals a week"
✓ #summary-panel text~"$107.88"
✓ #summary-panel text~"$116.51"
✓ step 4: A dietary preference shows in the summary
✓ #summary-panel text~"Vegetarian"
✓ step 5: Continue moves to step 2, marks it current and focuses its heading
✓ #step-1 hidden
✓ #step-2 visible
✓ #step-2-title focused
✓ #step-2-title text~"Delivery details"
✓ #steps [data-step='2'] aria-current=step
✓ #steps [data-step='1'] text~"completed"
✓ step 6: Step 2: Continue with empty fields lists six errors and ties each message to its field
✓ #errors-2 focused
✓ #errors-2 li count=6
✓ #full-name aria-invalid=true
✓ #full-name aria-describedby=full-name-error
✓ #zip aria-invalid=true
✓ #phone[aria-invalid] count=0
✓ .field__error .icon count=6
✓ step 7: An error-summary link moves focus to its field
✓ #email focused
✓ step 8: Fill the name
✓ #full-name focused
✓ step 9: Leaving the name field re-validates it (error gone, summary down to five)
✓ #full-name[aria-invalid] count=0
✓ #full-name-error count=0
✓ #errors-2 li count=5
✓ step 10: Leaving a malformed email shows the format message
✓ #email-error text~"like name@example.com"
✓ step 11: Correct the email
✓ #email focused
✓ step 12: Address line 1
✓ #email-error count=0
✓ step 13: City
✓ #address1-error count=0
✓ step 14: State by typeahead
✓ #state-error count=0
✓ step 15: ZIP code
✓ #city-error count=0
✓ step 16: Delivery instructions count down from 200
✓ #instructions-count text~"179 characters left"
✓ #zip-error count=0
✗ step 17: Continue to step 3
✓ #step-3 visible
✓ #step-3-title focused
✓ #steps [data-step='3'] aria-current=step
✗ #billing-preview text~"1450 Alder Street, Portland, OR 97214" (got: )D6 defect #billing-preview (step 3)verify flow step 17 failed: the 'same as delivery' address preview under the billing checkbox is empty at step 3 (got: ''), so the customer cannot see which address they are agreeing to reuse. The one-line address is only recomputed on plan/day/diet changes, never when a step is entered.
G13exit 0vlmkit check scroll index.htmlpageScrolled 621 · stickyFixed 1 · engagedSticky 1 · snaps 0 · issues 0
output, 12 line(s), 1.4s
vlmkit check scroll
source: index.html
status: ok
page scrolled: 621px
sticky/fixed elements: 1 (sticky engaged by the scroll: 1)
snap containers driven: 0
Sticky / fixed:
- #summary: sticky top=24px viewport y 143 -> 24
No scroll-behavior issues detected.
G14exit 0vlmkit check breakpoints index.html --sweepbreakpoints 6 · issues 0 · sweepRanges 0
output, 15 line(s), 3.7s
vlmkit check breakpoints
source: index.html
status: ok
width sweep: 320-1280px step 25 (39 widths) — clean
breakpoints checked: 479, 480, 559, 560, 959, 960px (each at B-1 / B / B+1)
479px: clean (max-width: 479px)
480px: clean (min-width: 480px)
559px: clean (max-width: 559px)
560px: clean (min-width: 560px)
959px: clean (max-width: 959px)
960px: clean (min-width: 960px)
All boundaries consistent.
G15exit 0vlmkit check grounding index.htmltargets 3 · scale 0.5 · issues 0
output, 13 line(s), 1.3s
vlmkit check grounding
source: index.html (1280x720)
status: ok
frame: 640x360 screenshot px — medium, cap 640x480, scale 0.50 (every coordinate below is in these 640x360 pixels)
targets: 3 actionable in frame (6 disabled or out of the frame)
Action map (click point in screenshot px):
t1 radio "2p3m" @ (220,227) 287x40 div.plans > label:nth-of-type(1) > input:nth-of-type(1)
t2 radio "4p3m" @ (220,280) 287x54 div.plans > label:nth-of-type(2) > input:nth-of-type(1)
t3 radio "2p5m" @ (220,333) 287x40 div.plans > label:nth-of-type(3) > input:nth-of-type(1)
Every target in the frame is resolvable and routes its own clicks.
Gate set, run every round and in full in the final round: the protocol floor (check integrity; check copy --manifest copy.txt; check design; check composition; check color; check a11y contrast; check a11y focus; check a11y touch; scan scroll; scan handlers; check interactions) plus, for this brief: verify flow --flow flow.json (the whole checkout: validation, re-validation on leave, focus to summary and headings, promo rejected and accepted, billing reveal, Edit round-trip, confirmation; it also asserts the required copy that only exists in later states, which check copy cannot reach), check scroll (the brief's sticky desktop summary must actually stick), check breakpoints --sweep (layout changes at 480/560/960 and the summary becomes a disclosure below 960), check grounding (three Edit links on the review step). One theme only (the brief asks for white and warm greys, no dark mode), so contrast runs once per state. No check animation/motion: nothing animates except 150ms colour transitions, all cut to 0.01ms under prefers-reduced-motion.
The 4 missing lines (Delivery details, Review your order, Place order, Thanks — your first box is on its way.) are state-dependent by the brief's own design: step 2/3/4 headings, the step-4 button, and the confirmation that replaces the form. check copy renders the load state and sweeps only details/tabs/aria-expanded, so it cannot reach them. They are proven where they occur by verify flow text/visible assertions (step 5 asserts #step-2-title contains 'Delivery details'; later steps assert 'Review your order', 'Place order' and the confirmation title) — the flow will carry that evidence once D6 lets it past step 17.
All 13 clipped-content warnings name the 1px visually-hidden comma spans inside the plan cards (label.plan > span > span > span), the standard sr-only pattern that gives the radios their spoken pauses. check integrity's G1 exempts exactly these nodes as 'visually-hidden (sr-only) pattern — 1px box, text for AT only'. The one dead scrollport, #instructions, is a textarea whose default overflow:auto is correct: it scrolls once the text passes 3 rows (maxlength 200 keeps that small).
unprobed-handler-types is the inventory telling us it pressed nothing. No pointer-only control is reported. Every listed handler type (change, click, focusout, input, keydown, submit) is driven for real by verify flow (G12 and later), and check interactions probes the keyboard side.
check grounding lists the three plan radios as t1 radio "2p3m", t2 radio "4p3m", t3 radio "2p5m", i.e. their value attributes, under a column documented as the 'visible label'. check interactions (G11) does the same ([radio] "2p3m") and names the delivery-day select by its option text ('Select a day Monday Tuesday…') instead of its label. Chromium's own aria snapshot of the same page (Playwright locator.ariaSnapshot, a one-off diagnostic) gives radio "2 people , 3 meals a week , 6 servings , $9.99 per serving , $59.94 a week" and combobox "Delivery day". So the page is labelled correctly, and a reader of these two reports would think it is not.
Dropped the duplicate price from the summary item row (Subtotal carries it one line below), so the text column goes from ~160px to ~240px; the name and meta are built from no-wrap phrases ('2 people' · '3 meals a week', '6 servings' · '$9.99 each'), so any wrap can only fall at the middle dot.
The 'Best value' pill is now absolutely positioned to straddle the 4-person card's top border (top:-11px, aligned with the title text at 54px / 50px on phones); the plans list gap went 12 -> 18px to leave the pill room. It no longer adds a row, so all three cards share one height.
Below 480px the plan price drops from 20px to 18px, the card's column gap from 14 to 12px, and the panel padding from 20 to 16px, which returns ~20px to the title column so '3 meals a week' fits on one line at 375 and 360.
Email and Phone each take a full row (single-column is also the faster form layout); the phone input is capped at 20rem because a phone number is short. The CVC hint was shortened to '3 digits on the back' so the remaining Expiry/CVC pair stays single-line at the narrowest two-column width.
Removed the stray 'Checkout' label from the brand bar; the header is now the mark and wordmark only, which is the convention for a checkout.
goTo() now calls updateSummary() on every step change, so derived text (the one-line delivery address for the billing preview, the summary lines) is recomputed whenever a step is entered.
Snapped the three values to the page's scale: input padding 11 -> 10px (min-height still 48px), main row gap 20 -> 18px, select right padding 44 -> 40px.
R2 · fix pass + deep links
builderAdded ?step=2|3|4|done deep links (documented in README). Every gate renders only the load state, so in round 1 check color measured 1 control and never saw a text field, although the brief leans on it for text-field boundaries (WCAG 1.4.11); contrast, touch, focus and interactions were likewise blind to steps 2-4. The parameter opens a step with the earlier steps filled from one fixed sample customer (Jordan Rivera, Portland OR), so each gate can be pointed at each step. verify flow still starts at step 1 and fills everything itself.
S6step 1 after the fix pass
index.html · 1280×800 · full page, 2 screen(s)
L6 sawDesktop 1280, step 1 after the fix pass. Brand bar now holds only the green bowl mark and "Pantry Club" — the stray grey "Checkout" is gone and the bar reads as a clean checkout header. Summary card: the item shows the box illustration, "2 people · 3 meals a week" on ONE line and "6 servings · $9.99 each" on one line beneath, no stranded words; Subtotal $59.94 follows directly so no price is lost. The three plan rows are now the same height (~76px each, 18px apart); the "Best value" pill straddles the top border of the 4-person card, left-aligned with the title text at x≈207, and reads as a tab label with ~7px clear of card 1. Selected card 1 keeps its green border, mint fill and filled radio. Screen 2: diet tiles, delivery-day select and Continue unchanged; the summary is still pinned at top 24px while scrolled.
S7step 1 after the fix pass
index.html · 375×812 · full page, 3 screen(s)
L7 sawMobile 375, step 1 after the fix pass, three screens. Header has only the mark and wordmark. Plan cards: card 2 now reads "4 people" / "3 meals a week" / "12 servings" / "$8.99 per serving" with "$107.88 / a week" at the right — "3 meals a week" stays on one line, no orphan; all three cards are the same four-line height. The "Best value" pill sits on the top border of card 2, aligned with the text at x≈83, 8px clear of card 1's bottom edge. The card text column gained width from the 16px panel padding; the left edge of every card and field aligns on one rail at x=33. Screen 2-3: four full-width diet tiles, "Delivery day" with a 2-line hint and a full-width select, full-width green Continue, footer. No overflow, no clipping.
Summary name and meta each fit on one line; the wrap can now only fall at the middle dot.
Three plan rows at one height; the pill straddles card 2's top edge.
At 375 '3 meals a week' stays on one line in the 4-person card.
Brand bar shows only the mark and wordmark.
S8step 2 via ?step=2 (empty fields)
index.html?step=2 · 1280×800 · full page, 2 screen(s)
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L8 sawDesktop, step 2 opened with ?step=2 (step 1 prefilled: Tuesday). Tracker: step 1 is a solid green circle with a white check and a green connector to step 2, which has the ring + pale halo and a bold "Delivery" label; 3 and 4 grey. Summary shows "First delivery Tue, Oct 13". Card: "Delivery details" h1 + "Where should we bring your box?". Full name, then Email with its hint on its own full row, then Phone (optional) with its hint and a deliberately shorter 320px input — the misaligned Email/Phone pair from L4 is gone. Address line 1, Address line 2 (optional), then City / State (Select + chevron) / ZIP code on one aligned row, then Delivery instructions (optional) with hint, a 3-row textarea and "200 characters left". Actions: outlined "Back" left, green "Continue" right, above a hairline. Labels bold 16px, hints grey 14px, 6px label-to-control and ~22px between fields — an even rhythm. All field boundaries are clearly visible grey lines on white.
Email and Phone are on separate full rows; nothing left to misalign.
G16exit 1vlmkit verify flow index.html --flow flow.jsonverdict: FAILED (22/34 steps)
done false · passed 22 · total 34
output, 82 line(s), 2.3s
vlmkit verify flow
source: index.html
verdict: FAILED (22/34 steps)
✓ step 1: Step 1: Continue with no delivery day shows the error summary and moves focus to it
✓ #errors-1 visible
✓ #errors-1 focused
✓ #errors-1 text~"Select a delivery day"
✓ #day aria-invalid=true
✓ #day aria-describedby=day-hint day-error
✓ #day-error visible
✓ step 2: Choosing a day re-validates the field and clears the summary
✓ #day-error count=0
✓ #errors-1 hidden
✓ #day-note text~"Monday, October 12"
✓ #summary-panel text~"Mon, Oct 12"
✓ step 3: The 4-person radio card updates the order summary
✓ #summary-panel text~"4 people · 3 meals a week"
✓ #summary-panel text~"$107.88"
✓ #summary-panel text~"$116.51"
✓ step 4: A dietary preference shows in the summary
✓ #summary-panel text~"Vegetarian"
✓ step 5: Continue moves to step 2, marks it current and focuses its heading
✓ #step-1 hidden
✓ #step-2 visible
✓ #step-2-title focused
✓ #step-2-title text~"Delivery details"
✓ #steps [data-step='2'] aria-current=step
✓ #steps [data-step='1'] text~"completed"
✓ step 6: Step 2: Continue with empty fields lists six errors and ties each message to its field
✓ #errors-2 focused
✓ #errors-2 li count=6
✓ #full-name aria-invalid=true
✓ #full-name aria-describedby=full-name-error
✓ #zip aria-invalid=true
✓ #phone[aria-invalid] count=0
✓ .field__error .icon count=6
✓ step 7: An error-summary link moves focus to its field
✓ #email focused
✓ step 8: Fill the name
✓ #full-name focused
✓ step 9: Leaving the name field re-validates it (error gone, summary down to five)
✓ #full-name[aria-invalid] count=0
✓ #full-name-error count=0
✓ #errors-2 li count=5
✓ step 10: Leaving a malformed email shows the format message
✓ #email-error text~"like name@example.com"
✓ step 11: Correct the email
✓ #email focused
✓ step 12: Address line 1
✓ #email-error count=0
✓ step 13: City
✓ #address1-error count=0
✓ step 14: State by typeahead
✓ #state-error count=0
✓ step 15: ZIP code
✓ #city-error count=0
✓ step 16: Delivery instructions count down from 200
✓ #instructions-count text~"179 characters left"
✓ #zip-error count=0
✓ step 17: Continue to step 3
✓ #step-3 visible
✓ #step-3-title focused
✓ #steps [data-step='3'] aria-current=step
✓ #billing-preview text~"1450 Alder Street, Portland, OR 97214"
✓ step 18: Step 3: Continue with no card lists four errors
✓ #errors-3 focused
✓ #errors-3 li count=4
✓ #card-number aria-invalid=true
✓ step 19: Card number
✓ #card-number focused
✓ step 20: Expiry (the slash is inserted as you type)
✓ #card-number-error count=0
✓ step 21: Security code
✓ #expiry-error count=0
✓ step 22: Name on card
✓ #cvc-error count=0
✗ step 23: Unticking 'same as delivery' reveals the billing fields
✗ #billing-fields visible (got: hidden/absent)
✗ #billing-address1 visible (got: hidden/absent)
✓ #card-name-error count=0D8 defect blur re-validation vs. pointer clicks (step 3 billing tile; any control below a corrected field)verify flow step 23 failed: after the last card error was fixed, one click on the 'same as delivery' tile did nothing (#billing-fields still hidden). The click works on its own (S9), so the cause is the sequence: mousedown blurs #card-name, blur re-validation clears its error and hides the now-empty error summary at the top of the step, the whole form jumps up ~130px, and mouseup lands on a different element, so no click is dispatched. A real customer hits the same dead click on Continue or on any control below a field whose error appears or clears on blur.
S9step 3: click the billing tile (flow step 23 repro)
index.html?step=3 · 1280×800 · element #step-3 fieldset:nth-of-type(2) · click .choice:has(#billing-same)
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L9 sawClose-up of step 3's Billing address group (opened with ?step=3) after one click on the "Billing address is the same as delivery" tile: the box is now unticked (grey outline, white fill, no check) and the billing fields appeared directly below it — Billing address line 1, Billing address line 2 (optional), and a City / State (Select) / ZIP code row, all with clear grey borders and bold labels, the same field style as step 2. So the tile toggles on a click when nothing else on the page is changing; the failure in G16 step 23 is not the tile itself.
Flow step 17 now passes: #billing-preview contains '1450 Alder Street, Portland, OR 97214'.
Blur re-validation no longer runs while a mouse button is held: a capture-phase mousedown sets a flag, focusout queues the field name, and mouseup flushes the queue in a setTimeout(0), i.e. after the click has been dispatched. The error still appears or clears as the field is left, but the page moves only after the click, never between mousedown and mouseup. Tab-away keeps the immediate re-validation.
G17exit 1vlmkit verify flow index.html --flow flow.jsonverdict: FAILED (22/34 steps)
done false · passed 22 · total 34
output, 82 line(s), 2.3s
vlmkit verify flow
source: index.html
verdict: FAILED (22/34 steps)
✓ step 1: Step 1: Continue with no delivery day shows the error summary and moves focus to it
✓ #errors-1 visible
✓ #errors-1 focused
✓ #errors-1 text~"Select a delivery day"
✓ #day aria-invalid=true
✓ #day aria-describedby=day-hint day-error
✓ #day-error visible
✓ step 2: Choosing a day re-validates the field and clears the summary
✓ #day-error count=0
✓ #errors-1 hidden
✓ #day-note text~"Monday, October 12"
✓ #summary-panel text~"Mon, Oct 12"
✓ step 3: The 4-person radio card updates the order summary
✓ #summary-panel text~"4 people · 3 meals a week"
✓ #summary-panel text~"$107.88"
✓ #summary-panel text~"$116.51"
✓ step 4: A dietary preference shows in the summary
✓ #summary-panel text~"Vegetarian"
✓ step 5: Continue moves to step 2, marks it current and focuses its heading
✓ #step-1 hidden
✓ #step-2 visible
✓ #step-2-title focused
✓ #step-2-title text~"Delivery details"
✓ #steps [data-step='2'] aria-current=step
✓ #steps [data-step='1'] text~"completed"
✓ step 6: Step 2: Continue with empty fields lists six errors and ties each message to its field
✓ #errors-2 focused
✓ #errors-2 li count=6
✓ #full-name aria-invalid=true
✓ #full-name aria-describedby=full-name-error
✓ #zip aria-invalid=true
✓ #phone[aria-invalid] count=0
✓ .field__error .icon count=6
✓ step 7: An error-summary link moves focus to its field
✓ #email focused
✓ step 8: Fill the name
✓ #full-name focused
✓ step 9: Leaving the name field re-validates it (error gone, summary down to five)
✓ #full-name[aria-invalid] count=0
✓ #full-name-error count=0
✓ #errors-2 li count=5
✓ step 10: Leaving a malformed email shows the format message
✓ #email-error text~"like name@example.com"
✓ step 11: Correct the email
✓ #email focused
✓ step 12: Address line 1
✓ #email-error count=0
✓ step 13: City
✓ #address1-error count=0
✓ step 14: State by typeahead
✓ #state-error count=0
✓ step 15: ZIP code
✓ #city-error count=0
✓ step 16: Delivery instructions count down from 200
✓ #instructions-count text~"179 characters left"
✓ #zip-error count=0
✓ step 17: Continue to step 3
✓ #step-3 visible
✓ #step-3-title focused
✓ #steps [data-step='3'] aria-current=step
✓ #billing-preview text~"1450 Alder Street, Portland, OR 97214"
✓ step 18: Step 3: Continue with no card lists four errors
✓ #errors-3 focused
✓ #errors-3 li count=4
✓ #card-number aria-invalid=true
✓ step 19: Card number
✓ #card-number focused
✓ step 20: Expiry (the slash is inserted as you type)
✓ #card-number-error count=0
✓ step 21: Security code
✓ #expiry-error count=0
✓ step 22: Name on card
✓ #cvc-error count=0
✗ step 23: Unticking 'same as delivery' reveals the billing fields
✓ #billing-fields visible
✓ #billing-address1 visible
✗ #card-name-error count=0 (got: 1) G18exit 0vlmkit verify flow index.html --flow flow.jsonverdict: DONE (34/34 steps)
done true · passed 34 · total 34
output, 130 line(s), 2.6s
vlmkit verify flow
source: index.html
verdict: DONE (34/34 steps)
✓ step 1: Step 1: Continue with no delivery day shows the error summary and moves focus to it
✓ #errors-1 visible
✓ #errors-1 focused
✓ #errors-1 text~"Select a delivery day"
✓ #day aria-invalid=true
✓ #day aria-describedby=day-hint day-error
✓ #day-error visible
✓ step 2: Choosing a day re-validates the field and clears the summary
✓ #day-error count=0
✓ #errors-1 hidden
✓ #day-note text~"Monday, October 12"
✓ #summary-panel text~"Mon, Oct 12"
✓ step 3: The 4-person radio card updates the order summary
✓ #summary-panel text~"4 people · 3 meals a week"
✓ #summary-panel text~"$107.88"
✓ #summary-panel text~"$116.51"
✓ step 4: A dietary preference shows in the summary
✓ #summary-panel text~"Vegetarian"
✓ step 5: Continue moves to step 2, marks it current and focuses its heading
✓ #step-1 hidden
✓ #step-2 visible
✓ #step-2-title focused
✓ #step-2-title text~"Delivery details"
✓ #steps [data-step='2'] aria-current=step
✓ #steps [data-step='1'] text~"completed"
✓ step 6: Step 2: Continue with empty fields lists six errors and ties each message to its field
✓ #errors-2 focused
✓ #errors-2 li count=6
✓ #full-name aria-invalid=true
✓ #full-name aria-describedby=full-name-error
✓ #zip aria-invalid=true
✓ #phone[aria-invalid] count=0
✓ .field__error .icon count=6
✓ step 7: An error-summary link moves focus to its field
✓ #email focused
✓ step 8: Fill the name
✓ #full-name focused
✓ step 9: Leaving the name field re-validates it (error gone, summary down to five)
✓ #full-name[aria-invalid] count=0
✓ #full-name-error count=0
✓ #errors-2 li count=5
✓ step 10: Leaving a malformed email shows the format message
✓ #email-error text~"like name@example.com"
✓ step 11: Correct the email
✓ #email focused
✓ step 12: Address line 1
✓ #email-error count=0
✓ step 13: City
✓ #address1-error count=0
✓ step 14: State by typeahead
✓ #state-error count=0
✓ step 15: ZIP code
✓ #city-error count=0
✓ step 16: Delivery instructions count down from 200
✓ #instructions-count text~"179 characters left"
✓ #zip-error count=0
✓ step 17: Continue to step 3
✓ #step-3 visible
✓ #step-3-title focused
✓ #steps [data-step='3'] aria-current=step
✓ #billing-preview text~"1450 Alder Street, Portland, OR 97214"
✓ step 18: Step 3: Continue with no card lists four errors
✓ #errors-3 focused
✓ #errors-3 li count=4
✓ #card-number aria-invalid=true
✓ step 19: Card number
✓ #card-number focused
✓ step 20: Expiry (the slash is inserted as you type)
✓ #card-number-error count=0
✓ step 21: Security code
✓ #expiry-error count=0
✓ step 22: Name on card
✓ #cvc-error count=0
✓ step 23: Unticking 'same as delivery' reveals the billing fields
✓ #billing-fields visible
✓ #billing-address1 visible
✓ #card-name-error count=0
✓ step 24: Ticking it again hides them
✓ #billing-fields hidden
✓ step 25: Type an unknown promo code
✓ #promo focused
✓ step 26: An unknown promo code is rejected with a message
✓ #promo-message text~"isn’t a valid promo code"
✓ #promo aria-invalid=true
✓ #summary-promo hidden
✓ step 27: Type WELCOME10 in lower case
✓ #promo focused
✓ step 28: WELCOME10 takes 10% off and the summary follows
✓ #promo-message text~"WELCOME10 applied"
✓ #promo[aria-invalid] count=0
✓ #summary-promo visible
✓ #summary-promo text~"$10.79"
✓ #summary-panel text~"$104.86"
✓ #promo-apply text~"Remove"
✓ step 29: Continue to review
✓ #step-4 visible
✓ #step-4-title focused
✓ #step-4-title text~"Review your order"
✓ #step-4 text~"Card ending 4242, expires 12/28"
✓ #step-4 text~"1450 Alder Street"
✓ #step-4 text~"WELCOME10"
✓ #step-4 text~"Vegetarian"
✓ #step-4 button[type=submit] visible
✓ #step-4 button[type=submit] text~"Place order"
✓ step 30: Edit on the Delivery block goes back to step 2
✓ #step-2 visible
✓ #step-2-title focused
✓ step 31: …and Continue returns straight to review
✓ #step-4 visible
✓ #step-4-title focused
✓ step 32: Place order without agreeing to the terms is stopped
✓ #errors-4 focused
✓ #terms aria-invalid=true
✓ #confirmation hidden
✓ step 33: Agree to the terms
✓ #terms-error count=0
✓ step 34: Place order replaces the form with the confirmation
✓ #confirmation visible
✓ #step-4 hidden
✓ #steps hidden
✓ #confirmation-title focused
✓ #confirmation-title text~"Thanks — your first box is on its way."
✓ #confirmation text~"PC-20417"
✓ #confirmation text~"Monday, October 12, 2026"
✓ #summary-panel text~"$104.86"Refinement after G17: the queued re-validation is flushed synchronously in a capture-phase click listener (the click's target is already fixed, so the layout may move) instead of in a timer after mouseup; G17 showed the timer left a 1-task window where the error was still on screen after the click. mouseup keeps the timer as the fallback for a press that ends in no click.
G19exit 0vlmkit verify flow index.html --flow flow.jsonverdict: DONE (34/34 steps)
done true · passed 34 · total 34
output, 130 line(s), 2.8s
vlmkit verify flow
source: index.html
verdict: DONE (34/34 steps)
✓ step 1: Step 1: Continue with no delivery day shows the error summary and moves focus to it
✓ #errors-1 visible
✓ #errors-1 focused
✓ #errors-1 text~"Select a delivery day"
✓ #day aria-invalid=true
✓ #day aria-describedby=day-hint day-error
✓ #day-error visible
✓ step 2: Choosing a day re-validates the field and clears the summary
✓ #day-error count=0
✓ #errors-1 hidden
✓ #day-note text~"Monday, October 12"
✓ #summary-panel text~"Mon, Oct 12"
✓ step 3: The 4-person radio card updates the order summary
✓ #summary-panel text~"4 people · 3 meals a week"
✓ #summary-panel text~"$107.88"
✓ #summary-panel text~"$116.51"
✓ step 4: A dietary preference shows in the summary
✓ #summary-panel text~"Vegetarian"
✓ step 5: Continue moves to step 2, marks it current and focuses its heading
✓ #step-1 hidden
✓ #step-2 visible
✓ #step-2-title focused
✓ #step-2-title text~"Delivery details"
✓ #steps [data-step='2'] aria-current=step
✓ #steps [data-step='1'] text~"completed"
✓ step 6: Step 2: Continue with empty fields lists six errors and ties each message to its field
✓ #errors-2 focused
✓ #errors-2 li count=6
✓ #full-name aria-invalid=true
✓ #full-name aria-describedby=full-name-error
✓ #zip aria-invalid=true
✓ #phone[aria-invalid] count=0
✓ .field__error .icon count=6
✓ step 7: An error-summary link moves focus to its field
✓ #email focused
✓ step 8: Fill the name
✓ #full-name focused
✓ step 9: Leaving the name field re-validates it (error gone, summary down to five)
✓ #full-name[aria-invalid] count=0
✓ #full-name-error count=0
✓ #errors-2 li count=5
✓ step 10: Leaving a malformed email shows the format message
✓ #email-error text~"like name@example.com"
✓ step 11: Correct the email
✓ #email focused
✓ step 12: Address line 1
✓ #email-error count=0
✓ step 13: City
✓ #address1-error count=0
✓ step 14: State by typeahead
✓ #state-error count=0
✓ step 15: ZIP code
✓ #city-error count=0
✓ step 16: Delivery instructions count down from 200
✓ #instructions-count text~"179 characters left"
✓ #zip-error count=0
✓ step 17: Continue to step 3
✓ #step-3 visible
✓ #step-3-title focused
✓ #steps [data-step='3'] aria-current=step
✓ #billing-preview text~"1450 Alder Street, Portland, OR 97214"
✓ step 18: Step 3: Continue with no card lists four errors
✓ #errors-3 focused
✓ #errors-3 li count=4
✓ #card-number aria-invalid=true
✓ step 19: Card number
✓ #card-number focused
✓ step 20: Expiry (the slash is inserted as you type)
✓ #card-number-error count=0
✓ step 21: Security code
✓ #expiry-error count=0
✓ step 22: Name on card
✓ #cvc-error count=0
✓ step 23: Unticking 'same as delivery' reveals the billing fields
✓ #billing-fields visible
✓ #billing-address1 visible
✓ #card-name-error count=0
✓ step 24: Ticking it again hides them
✓ #billing-fields hidden
✓ step 25: Type an unknown promo code
✓ #promo focused
✓ step 26: An unknown promo code is rejected with a message
✓ #promo-message text~"isn’t a valid promo code"
✓ #promo aria-invalid=true
✓ #summary-promo hidden
✓ step 27: Type WELCOME10 in lower case
✓ #promo focused
✓ step 28: WELCOME10 takes 10% off and the summary follows
✓ #promo-message text~"WELCOME10 applied"
✓ #promo[aria-invalid] count=0
✓ #summary-promo visible
✓ #summary-promo text~"$10.79"
✓ #summary-panel text~"$104.86"
✓ #promo-apply text~"Remove"
✓ step 29: Continue to review
✓ #step-4 visible
✓ #step-4-title focused
✓ #step-4-title text~"Review your order"
✓ #step-4 text~"Card ending 4242, expires 12/28"
✓ #step-4 text~"1450 Alder Street"
✓ #step-4 text~"WELCOME10"
✓ #step-4 text~"Vegetarian"
✓ #step-4 button[type=submit] visible
✓ #step-4 button[type=submit] text~"Place order"
✓ step 30: Edit on the Delivery block goes back to step 2
✓ #step-2 visible
✓ #step-2-title focused
✓ step 31: …and Continue returns straight to review
✓ #step-4 visible
✓ #step-4-title focused
✓ step 32: Place order without agreeing to the terms is stopped
✓ #errors-4 focused
✓ #terms aria-invalid=true
✓ #confirmation hidden
✓ step 33: Agree to the terms
✓ #terms-error count=0
✓ step 34: Place order replaces the form with the confirmation
✓ #confirmation visible
✓ #step-4 hidden
✓ #steps hidden
✓ #confirmation-title focused
✓ #confirmation-title text~"Thanks — your first box is on its way."
✓ #confirmation text~"PC-20417"
✓ #confirmation text~"Monday, October 12, 2026"
✓ #summary-panel text~"$104.86"verify flow DONE 34/34: step 23 reveals #billing-fields on the first click and #card-name-error is already gone right after it; every later step (promo rejected/accepted, review, Edit round-trip, terms error, confirmation) passes.
G17 was the first D8 attempt (re-validation deferred to a timer after mouseup): the tile click landed, but the flow read #card-name-error before the timer ran. Replaced by the click-phase flush (F9); G18/G19 pass 34/34.
G20exit 0vlmkit check design index.htmlverdict: COHERENT (1 finding(s))
verdict coherent · drifting 0 · roles 8
output, 26 line(s), 1.6s
vlmkit check design
source: index.html
verdict: COHERENT (1 finding(s))
roles judged: 1 of 8 seen, spacing values: 16
coverage: 12 of 154 visible element(s) carried an inferable role; 1 skipped as non-resting
no role: span x60, div x21, p x9, label x8, path x8, dd x6, +14 more tag(s)
A role comes from role="..." or from button/input/select/textarea/h1-h6. Layout elements
(div, span, p, a) have none, so a large skip count is normal — this gate judges components,
not every box. Add role="..." where an element IS a component to widen the coverage.
Role reuse (instances / distinct styles; drift below 3x from 3 instances)
input:checkbox 4 inst 1 styles reuse 4x 0 one-off ok
input:radio 2 inst 1 styles reuse 2x 0 one-off not judged
note 1 inst 1 styles reuse 1x 1 one-off not judged
h2 1 inst 1 styles reuse 1x 1 one-off not judged
list 1 inst 1 styles reuse 1x 1 one-off not judged
h1 1 inst 1 styles reuse 1x 1 one-off not judged
select 1 inst 1 styles reuse 1x 1 one-off not judged
button 1 inst 1 styles reuse 1x 1 one-off not judged
not judged: input:radio (2), note (1), h2 (1), list (1), h1 (1), select (1), button (1) — under --min-instances 3, so no finding can come from them.
To keep judging them: --min-instances 2 --min-reuse 2 (both — a 2-instance role cannot reach 3x however many instances the floor allows).
Informational (true, but does not carry the verdict)
i [scale-outlier]: 1 spacing value(s) sit just off the page's own scale: 20px (1x) next to 18px (11x) — ol#steps marginBottom. Snap them to the established value or add them deliberately.D9 defect ol#steps margin-bottomcheck design now flags the step tracker's 20px bottom margin as the one value next to the page's established 18px (11 uses), left over after D7 moved the main gap to 18px.
G21exit 0vlmkit check integrity index.htmlverdict: CLEAN (0 fail, 0 warn, 45 exempted)
verdict clean · findings 0 · fails 0 · warns 0 · exempted 45 · viewports 3
output, 27 line(s), 4.1s
vlmkit check integrity
source: index.html
verdict: CLEAN (0 fail, 0 warn, 45 exempted)
1280x800: 8 component(s), ink 96.3%, 65 text block(s)
768x900: 8 component(s), ink 95.3%, 51 text block(s)
375x700: 8 component(s), ink 94.9%, 54 text block(s)
No integrity defects detected.
Exempted candidates (the tool's call — audit the rule, not the page):
- [text-collision] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] span.plan__badge > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
… 30 more
The three flagged values (11px padding, 20px main gap, 44px select padding) no longer appear.
Tracker bottom margin 20 -> 18px, onto the page's established step.
G22exit 1vlmkit check color 'index.html?step=2'output, 1 line(s), 0.7s
error: file not found: examples/sites/checkout/index.html?step=2
G23exit 1vlmkit check color 'file://$PWD/examples/sites/checkout/index.html?step=2'output, 1 line(s), 0.8s
error: file not found: examples/sites/checkout/file:examples/sites/checkout/index.html?step=2
Gates cannot open a local page in any state but its default: a query string on a path is read as part of the filename (G22: 'error: file not found: examples/sites/checkout/index.html?step=2'), and a file:// URL is joined onto the cwd as a relative path (G23: 'Error: file not found: examples/sites/checkout/file:examples/sites/checkout/index.html?step=2'; the same happens straight from the CLI with --no-ledger: 'file not found: /home/user/vlmkit/examples/sites/checkout/file:/home/user/vlmkit/examples/sites/checkout/index.html?step=2'). judge.mjs shot accepts 'index.html?step=2' fine, so only the gates are limited. For a multi-step form that leaves every gate except verify flow blind to steps 2-4.
Same run as G23's tool note: the query string made the path unresolvable; the per-step gate runs go through a local static server instead (see the next decision note).
Superseded by the http runs: the per-step gates now open http://127.0.0.1:48217/index.html?step=N.
Per-step gate runs use a plain static server bound to 127.0.0.1 (python3 -m http.server 48217 --bind 127.0.0.1 --directory examples/sites/checkout), started for these runs only, because the gates cannot take a query string on a file path (N10). The page itself still makes no network requests and works from file://; the server only lets a gate open index.html?step=2|3|4|done. index.html with no parameter keeps being measured straight from disk.
G24exit 0vlmkit check color 'http://127.0.0.1:48217/index.html?step=2'verdict: CONSISTENT (0 finding(s))
verdict consistent · base #f6f3ee · bodyInk #1f1c19 · linkInk #ffffff · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0
output, 26 line(s), 1.5s
vlmkit check color
source: http://127.0.0.1:48217/index.html?step=2
verdict: CONSISTENT (0 finding(s))
measured: 9 control(s), 0 link(s) in a text flow — from 118 visible box(es)
Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
surfaces 3 distinct
#f6f3ee 60.0% 1 el
#ffffff 38.2% 16 el header.site-header>div.brand-bar
#f4ecdc 1.8% 2 el header.site-header>div.demo-notice
ink 4 distinct
#1f1c19 58.1% 30 el div.demo-notice>div.wrap>p
#6a635b 34.0% 16 el div.summary-item>div.summary-item__text>p.summary-item__meta
#57514a 4.8% 3 el li.steps__item>span.steps__marker>span.steps__num
#ffffff 3.1% 1 el form#step-2>div.actions>button.btn
marks 5 distinct
#857d73 45.6% 48 el ol#steps>li.steps__item>span.steps__marker
#dcd5ca 38.2% 10 el header.site-header>div.brand-bar
#e7e1d8 7.4% 4 el div#summary-panel>div.summary-item
#e6d9bf 6.3% 1 el header.site-header>div.demo-notice
#2d6a4f 2.5% 8 el ol#steps>li.steps__item>span.steps__marker
base #f6f3ee, body ink #1f1c19, link ink #ffffff on 1 element(s)
http://127.0.0.1:48217/index.html?step=2 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=2 --out app.har, then --har app.har G25exit 0vlmkit check design index.htmlverdict: COHERENT (0 finding(s))
verdict coherent · drifting 0 · roles 8
output, 25 line(s), 1.6s
vlmkit check design
source: index.html
verdict: COHERENT (0 finding(s))
roles judged: 1 of 8 seen, spacing values: 15
coverage: 12 of 154 visible element(s) carried an inferable role; 1 skipped as non-resting
no role: span x60, div x21, p x9, label x8, path x8, dd x6, +14 more tag(s)
A role comes from role="..." or from button/input/select/textarea/h1-h6. Layout elements
(div, span, p, a) have none, so a large skip count is normal — this gate judges components,
not every box. Add role="..." where an element IS a component to widen the coverage.
Role reuse (instances / distinct styles; drift below 3x from 3 instances)
input:checkbox 4 inst 1 styles reuse 4x 0 one-off ok
input:radio 2 inst 1 styles reuse 2x 0 one-off not judged
note 1 inst 1 styles reuse 1x 1 one-off not judged
h2 1 inst 1 styles reuse 1x 1 one-off not judged
list 1 inst 1 styles reuse 1x 1 one-off not judged
h1 1 inst 1 styles reuse 1x 1 one-off not judged
select 1 inst 1 styles reuse 1x 1 one-off not judged
button 1 inst 1 styles reuse 1x 1 one-off not judged
not judged: input:radio (2), note (1), h2 (1), list (1), h1 (1), select (1), button (1) — under --min-instances 3, so no finding can come from them.
To keep judging them: --min-instances 2 --min-reuse 2 (both — a 2-instance role cannot reach 3x however many instances the floor allows).
No design drift detected.check design: COHERENT, 0 findings, no scale outliers left.
R3 · states: errors, promo, billing, review, confirmation, phone summary
builderS10step 2 with validation errors (Continue on empty fields)
index.html?step=2 · 1280×800 · full page, 3 screen(s) · click #step-2 button[type=submit]
3 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L10 sawDesktop, step 2 after Continue on empty fields (3 screens). The error summary sits directly under "Delivery details / Where should we bring your box?": a pale pink panel with a 5px red left bar, a red (!) icon and "6 fields need your attention" in dark red bold, then six underlined dark-red links — Enter your full name / email address / the first line of your address / city / Select your state / Enter your ZIP code. It has focus, shown by the same 3px dark-green ring used everywhere (offset 3px), so it is impossible to miss, yet the tint is soft rather than alarming. Each failing field has a 2px red border plus a bold red line with a (!) icon under it ("Enter your full name", "Enter your email address", …); Phone and Address line 2 (optional) stay grey and unmarked, so the page distinguishes required from optional. Screen 2: the City / State / ZIP row carries three errors; "Enter your / ZIP code" wraps to two lines in the 131px ZIP column, so that row's error line is ragged — legible and still under its field, acceptable. Screen 3: Back + Continue (hover-darkened after the click). The summary stays pinned at top 24px throughout.
S11step 2 with validation errors (Continue on empty fields)
index.html?step=2 · 375×812 · full page, 3 screen(s) · click #step-2 button[type=submit]
3 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L11 sawMobile 375, step 2 with the same six errors (3 screens). The summary box is focused (dark-green ring) directly under the heading; its title wraps as "6 fields need your / attention" and two links wrap ("Enter your email / address", "Enter the first line of / your address") — all still underlined and readable with a clear hanging indent. Fields stack full width with 2px red borders and icon + bold red messages beneath; City has its own row, State and ZIP share one (7/5 split) with "Select your state" on one line and "Enter your / ZIP code" on two. Phone keeps its grey unmarked border. Back and a wider Continue sit side by side at the bottom (both 52px tall). No text overlaps a border, nothing is clipped, and the page scrolls vertically only (2204px).
S12step 3: card filled, promo SAVE20 rejected
index.html?step=3 · 1280×800 · full page, 2 screen(s) · fill #card-number "4242424242424242" · fill #expiry "1228" · fill #cvc "123" · fill #card-name "Jordan Rivera" · fill #promo "SAVE20" · click #promo-apply
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L12 sawDesktop, step 3 opened with ?step=3, card fields filled and SAVE20 applied (2 screens). Tracker: Plan and Delivery done (green checks, green connectors), Payment current. "Payment" h1 with "Use a test card: this demo never charges or stores it." Card number typed as 16 bare digits now reads "4242 4242 4242 4242" (grouped in fours by the input handler); Expiry "12/28" got its slash; Expiry/CVC hints ("MM/YY", "3 digits on the back") are both one line so the pair aligns. The "Card details" legend sits only 12px above the "Card number" label — two bold lines close together, readable but tight. Screen 2: the checked "Billing address is the same as delivery" tile (green border, mint fill, white tick) with the delivery address previewed underneath in grey: "1450 Alder Street, Apt 3, Portland, OR 97214". Promo: red-bordered "SAVE20" input + outlined "Apply", and the error line (!) "“SAVE20” isn’t a valid promo code. Check the spelling and try" wraps, leaving "again." alone on a second line — an orphan in the one message the customer reads closely. Summary total unchanged at $64.74, no promo row, which is correct.
D10 defect #promo-message (rejected code)The rejected-code message wraps and strands 'again.' on a line of its own under the promo field; the sentence is longer than it needs to be for the width it has.
Shortened the message to '“SAVE20” isn’t a valid promo code. Check the spelling.' (~50 characters), which fits the field's width on one line at desktop.
S13promo rejected, shorter message
index.html?step=3 · 1280×800 · element #step-3 .promo · fill #promo "SAVE20" · click #promo-apply
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L13 sawClose-up of the promo field at desktop after typing SAVE20 and pressing Apply: red-bordered "SAVE20" input, outlined "Apply" button of the same 48px height, and the error on ONE line with its (!) icon: "“SAVE20” isn’t a valid promo code. Check the spelling." — no stranded word. The hint above still names WELCOME10, so the customer has a way forward. Colour is not the only cue: icon, words and the thicker red border all say "rejected".
S14promo WELCOME10 accepted (field + summary)
index.html?step=3 · 1280×800 · fill #promo "welcome10" · click #promo-apply · scroll-to #step-3 .promo
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L14 sawDesktop, step 3 after typing "welcome10" and pressing Apply, scrolled to the promo field. The code was normalised to "WELCOME10", the input turned read-only (warm off-white fill), the button now says "Remove", and a green line with a check-circle icon reads "WELCOME10 applied: 10% off your first box (−$5.99)." The sticky summary picked it up: a new row "Promo [WELCOME10 tag] −$5.99" in green between Subtotal $59.94 and Shipping Free, Tax (8%) dropped to $4.32, "Total today $58.27", and the renewal note still says "Then $64.74 a week" — the discount is honestly first-box only. Arithmetic checks: 59.94 − 5.99 = 53.95; 8% = 4.32; 53.95 + 4.32 = 58.27. Card fields are empty here (only the promo was driven), billing tile checked with the address preview.
S15promo WELCOME10 accepted (field + summary)
index.html?step=3 · 375×812 · fill #promo "welcome10" · click #promo-apply · scroll-to #step-3 .promo
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L15 sawMobile 375, the same promo-accepted state, one screen around the promo. Fields stack full width; the billing tile wraps its label to "Billing address is the same as / delivery" with the tick box vertically centred — fine. The address preview under it wraps as "1450 Alder Street, Apt 3, Portland, OR / 97214", leaving the ZIP code alone on the second line, split from its city and state. Promo row: "WELCOME10" read-only input and "Remove" button side by side at equal 48px height; the success line wraps into two clean lines "WELCOME10 applied: 10% off / your first box (−$5.99)." with a green check icon. No overflow.
D11 defect #billing-preview (and the confirmation's Delivering to)At 375 the one-line delivery address wraps as '…Portland, OR / 97214', stranding the ZIP code from its city and state; the address parts are joined as one string, so the browser breaks at the last space.
Rejected-code message is one line.
The address is rendered as no-wrap parts ('1450 Alder Street', 'Apt 3', 'Portland, OR 97214') joined by ', ', so it can only wrap between parts and the ZIP stays with its city and state; the billing preview and the confirmation's 'Delivering to' share the renderer.
S16mobile billing preview after D11
index.html?step=3 · 375×812 · element #step-3 fieldset:nth-of-type(2) · scroll-to #billing-preview
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L16 sawClose-up at 375 of step 3's Billing address group: the checked tile "Billing address is the same as / delivery" (green border, mint fill, white tick) and under it the preview now breaks between address parts — "1450 Alder Street, Apt 3," on the first line and "Portland, OR 97214" together on the second. The ZIP stays with its city and state; grey 14px text, left edge on the same rail as the tile.
The preview wraps between parts: 'Portland, OR 97214' stays together.
S17step 3: billing fields revealed
index.html?step=3 · 1280×800 · full page, 2 screen(s) · click .choice:has(#billing-same)
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L17 sawDesktop, step 3 after one click on the "same as delivery" tile (2 screens). Screen 1: tracker at step 3, Payment h1, Card details group, empty card fields. Screen 2: the tile is now unticked (white fill, grey border, empty box) and the billing fields appeared under it: Billing address line 1, Billing address line 2 (optional), and a City / State (Select) / ZIP code row — same field styling as step 2, aligned on the same rails. The reveal itself works. Problem: the hierarchy breaks once the fields are open. "Card details" and "Billing address" are 18px section headings, but "Promo code (optional)" is a 16px bold field label exactly like "City" or "ZIP code", and it follows the ZIP row with only ~40px of space, so the promo input reads as one more billing-address field rather than its own section. Back / Continue below, summary pinned at right with $64.74.
D12 defect #step-3 .promo labelWith the billing fields revealed, 'Promo code (optional)' is styled as an ordinary 16px field label and follows the billing ZIP row directly, so it reads as part of the billing address; the page's other sections ('Card details', 'Billing address') have 18px headings and this one does not.
The promo label now carries the section-heading style (18px/700, like the 'Card details' and 'Billing address' legends), so Promo code reads as the third section of the step rather than a billing field; '(optional)' stays grey regular as in 'Dietary preferences (optional)'.
S18billing revealed, promo as its own section
index.html?step=3 · 1280×800 · click .choice:has(#billing-same) · scroll-to #step-3 .promo
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L18 sawDesktop, step 3 with billing revealed, scrolled to the lower half. "Billing address" (18px heading, top edge) → unticked tile → Billing address line 1 / line 2 (optional) / City-State-ZIP, all 16px bold labels with 22px between fields. Then, ~40px below the ZIP row, "Promo code (optional)" now renders at the 18px section-heading size with "(optional)" in grey regular, followed by its hint, the input and "Apply". It reads as the third section of the step, a peer of Card details and Billing address, not as a sixth billing field. Hairline, Back / Continue; summary unchanged at $64.74.
Promo code heading now matches the section-heading size; the billing block ends at the ZIP row.
S19step 4 review (sample customer)
index.html?step=4 · 1280×800 · full page, 2 screen(s)
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L19 sawDesktop, step 4 opened with ?step=4 (sample customer), 2 screens. Tracker: Plan, Delivery, Payment done with checks and an all-green connector; Review current. "Review your order" + "Check the details, then place your order." Three warm off-white blocks with 18px headings and a right-aligned underlined green "Edit": Plan (Box "2 people · 3 meals a week, $59.94 a week", Delivery day "Tuesdays, first box Tuesday, October 13"), Delivery (Name, Contact, a three-line Address "1450 Alder Street / Apt 3 / Portland, OR 97214", Instructions) and Payment ("Card ending 4242, expires 12/28 / Jordan Rivera", Billing "Same as delivery address", Promo "None"). Grey term column, ink values, even 8px row gaps. Flaw: the 150px term column is too narrow for "Dietary preferences", which wraps to "Dietary / preferences" — the only two-line label, so the Plan block's rows go uneven. Screen 2: terms tile (unticked, grey border) "I agree to the subscription terms: my box renews every week at $64.74 until I skip or cancel." with the price bold; Back + green "Place order". Summary pinned with Total today $64.74.
D13 defect .review__row dt (desktop)The review's 150px term column wraps 'Dietary preferences' to two lines at desktop, the only wrapped term, so the Plan block's row rhythm breaks.
S20step 4 review (sample customer)
index.html?step=4 · 375×812 · full page, 3 screen(s)
3 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L20 sawMobile 375, step 4 review, 3 screens. Summary toggle bar on top, tracker with three green checks and Review current. The review blocks stack term above value (grey 16px term, ink value), so "Dietary preferences" fits on its line here; values wrap cleanly ("2 people · 3 meals a week, / $59.94 a week", "Tuesdays, first box Tuesday, / October 13"). Each block's "Edit" sits right of its heading with a 44px-tall hit area. The terms tile wraps to four lines with the checkbox aligned to the first. Back and a wider "Place order" side by side at the bottom, both 52px. Nothing clipped; nested padding (16px card + 20px block) leaves ~267px of text width, which is enough.
Term column 150 -> 168px and terms set at 15px (line-height 1.6, so each term still sits on its value's 24px line box); 'Dietary preferences' fits on one line and the longest value still has ~13px to spare.
S21review Plan block after D13
index.html?step=4 · 1280×800 · element #step-4 .review__block:first-child
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L21 sawClose-up of the review's Plan block at desktop after D13: three rows on one line each — "Box: 2 people · 3 meals a week, $59.94 a week", "Dietary preferences: No preferences", "Delivery day: Tuesdays, first box Tuesday, October 13". The grey 15px terms fit the 168px column and share a baseline with their 16px values; the longest value ends ~10px short of the block's padding. "Edit" stays right-aligned beside the "Plan" heading. Even row rhythm restored.
'Dietary preferences' is on one line; all three rows align.
S22step 4: Place order without the terms
index.html?step=4 · 1280×800 · click #step-4 button[type=submit]
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L22 sawDesktop viewport after pressing "Place order" on step 4 without ticking the terms. The page scrolled up to the error summary (its top 16px below the viewport edge, heading scrolled off above): pale pink panel, red left bar, dark-green focus ring, "(!) 1 field needs your attention" and one underlined link "Tick the box to agree to the subscription terms". Below it the three review blocks, unchanged (the Plan block now keeps "Dietary preferences" on one line). The terms tile itself is below the fold here, so its own error state is not visible in this frame; the summary link is the way back down to it.
S23confirmation after Place order
index.html?step=4 · 1280×800 · full page, 2 screen(s) · click .choice:has(#terms) · click #step-4 button[type=submit]
L23 sawDesktop confirmation after ticking the terms and pressing Place order (2 screens; the page is only 820px tall). The tracker is gone and the step card is replaced by a centred confirmation card: a 56px green check-circle, "Thanks — your first box is on its way." as a bold h1 on one line, the lead "Your weekly plan is set up. Here’s what we have on file.", then a bordered 480px facts table — Order number PC-20417, First delivery Tuesday, October 13, 2026, Plan 2 people · 3 meals a week, Delivering to "1450 Alder Street, Apt 3, / Portland, OR 97214" — grey terms, bold values, hairlines between rows. A small grey note: "A real order would send a receipt to jordan@example.com. This demo sent nothing and charged nothing." The summary stays at right (Total today $64.74), card tops aligned at y=143. Calm, clearly finished; nothing clipped.
S24confirmation after Place order
index.html?step=4 · 375×812 · full page, 2 screen(s) · click .choice:has(#terms) · click #step-4 button[type=submit]
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L24 sawMobile 375 confirmation, 2 screens — broken. The facts table keeps its desktop 140px term column, so values get ~110px: "Tuesday, / October 13, / 2026" and "2 people · 3 / meals a / week" wrap into three-line stacks. Worse, the address overflows: "1450 Alder Street," runs straight through the table's right border to the screen edge, and the last line "Portland, OR 9721…" is cut off at the viewport edge — the ZIP is not readable. That comes from D11's fix: each address part is white-space:nowrap, and a part wider than its column can only overflow. The heading wraps as "Thanks — your first / box is on its way." (fine, centred), the check icon and the note below are fine.
D14 defect .confirmation__facts @375 / .nw address partsOn a phone the confirmation's facts table keeps the 140px term column, leaving ~110px for values: dates and plan wrap into three-line stacks, and the no-wrap address parts introduced by F12 overflow the table and are clipped at the screen edge ('Portland, OR 9721…'). Regression from the D11 fix, which assumed every address part fits its container.
Two changes. (1) The keep-together phrases are now inline-blocks carrying their own trailing separator ('1450 Alder Street,' 'Apt 3,' 'Portland, OR 97214'; '2 people ·' '3 meals a week'), so a line still breaks only between phrases when they fit, but a phrase wider than its box wraps inside itself instead of overflowing. (2) Below 480px the confirmation's term/value rows stack like the review rows, giving the values the full ~275px.
S25mobile confirmation after D14
index.html?step=done · 375×812 · full page, 2 screen(s)
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L25 sawMobile 375 confirmation (?step=done) after D14, 2 screens. The facts table now stacks each term above its value: "Order number / PC-20417", "First delivery / Tuesday, October 13, 2026" on one line, "Plan / 2 people · 3 meals a week" on one line, "Delivering to / 1450 Alder Street, Apt 3, / Portland, OR 97214" — two clean lines, the ZIP with its city and state, everything well inside the table's rounded border with ~18px to spare. Hairlines between rows, grey terms, bold values. Heading, check icon and the "this demo sent nothing" note unchanged. No overflow, nothing clipped.
Values are full width on phones; the address wraps between parts and stays inside the table.
G26exit 0vlmkit check integrity 'http://127.0.0.1:48217/index.html?step=done'verdict: CLEAN (0 fail, 0 warn, 0 exempted)
verdict clean · findings 0 · fails 0 · warns 0 · exempted 0 · viewports 3
output, 10 line(s), 3.9s
vlmkit check integrity
source: http://127.0.0.1:48217/index.html?step=done
verdict: CLEAN (0 fail, 0 warn, 0 exempted)
1280x800: 8 component(s), ink 94.2%, 34 text block(s)
768x900: 8 component(s), ink 92.9%, 18 text block(s)
375x700: 8 component(s), ink 92.6%, 18 text block(s)
No integrity defects detected.
http://127.0.0.1:48217/index.html?step=done is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=done --out app.har, then --har app.har
Probe for the eye-vs-gate question on D14: a scratch copy of the site OUTSIDE the site dir with only the D14 fix reverted (address parts back to white-space:nowrap, the phone stacking rule removed), served on 127.0.0.1:48218. The next check integrity run is against that copy, not the site; it asks whether the gate would have caught the confirmation overflow had it been able to reach the state.
G27exit 1vlmkit check integrity 'http://127.0.0.1:48218/index.html?step=done'verdict: DEFECTS (1 fail, 0 warn, 0 exempted)
verdict defects · findings 1 · fails 1 · warns 0 · exempted 0 · viewports 3
output, 11 line(s), 4.0s
vlmkit check integrity
source: http://127.0.0.1:48218/index.html?step=done
verdict: DEFECTS (1 fail, 0 warn, 0 exempted)
1280x800: 8 component(s), ink 94.2%, 30 text block(s)
768x900: 8 component(s), ink 92.9%, 16 text block(s)
375x700: 8 component(s), ink 71.7%, 16 text block(s)
Findings:
x [page-overflow-x] @375: The page scrolls horizontally by 9px at 375px viewport width — sticking out: dl.confirmation__facts > div:nth-of-type(4) > dd:nth-of-type(1) > span:nth-of-type(3) (right edge 384px).
http://127.0.0.1:48218/index.html?step=done is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48218/index.html?step=done --out app.har, then --har app.har
Probe run against the reverted scratch copy (N14), not the site; the site itself is clean in the same state (G26). Answer to the question: check integrity WOULD have caught half of D14 — '[page-overflow-x] @375: The page scrolls horizontally by 9px … sticking out: dl.confirmation__facts > div:nth-of-type(4) > dd:nth-of-type(1) > span:nth-of-type(3) (right edge 384px)', i.e. the clipped 'Portland, OR 97214'. It did not report '1450 Alder Street,' crossing the table's own border (no container-protrusion row), nor the three-line value stacks from the 140px term column, which only the eye saw in S24. In round 3 it could not reach the state at all until the ?step=done deep link and the local server existed.
S26phone step 3 with the order summary expanded
index.html?step=3 · 375×812 · full page, 3 screen(s) · click #summary-toggle
L26 sawMobile 375, step 3 (?step=3) after tapping the summary bar, 3 screens. Screen 1: the bar now reads "Hide order summary" with the chevron flipped up, total $64.74 still at the right; the full-bleed panel opened directly below it on the warm off-white surface: "Order summary", the box illustration with "2 people · 3 meals a week / 6 servings · $9.99 each", Dietary preferences None, First delivery Tue, Oct 13, Subtotal $59.94, Shipping Free (green), Tax (8%) $4.80, a bold "Total today $64.74" and the renewal note. Its bottom edge meets the grey page cleanly, and the tracker (two checks, Payment current) and the Payment card follow. Screens 2-3: on the phone the card fields stack full width (Expiry and CVC each on their own row), then the checked billing tile with the two-line address preview, the 18px "Promo code (optional)" section with input + Apply, and Back / Continue. Everything within the 16px gutters.
S27terms tile error state
index.html?step=4 · 1280×800 · element #step-4 .field--terms · click #step-4 button[type=submit]
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L27 sawClose-up of step 4's terms tile after "Place order" was pressed unticked: the tile's edge turned 2px red with a pale pink fill and the tick box outlined red, and directly beneath it a bold red line with the (!) icon: "Tick the box to agree to the subscription terms". The agreement text (with the bold $64.74 renewal price) stays fully legible in ink on the tint. Icon, words and edge all carry the error, not colour alone.
S28keyboard: Tab then ArrowDown on the plan cards
index.html · 1280×800 · element #step-1 .plans · @2x · press Tab · press ArrowDown
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L28 saw2x close-up of the three plan cards after Tab (focus lands on the checked radio) and ArrowDown. Selection and focus moved together to the 4-person card, as native radios do: it now has the 2px green border, mint fill and filled radio dot, and a separate 3px dark-green focus ring 3px outside the card, so "selected" and "focused" read as two different signals. Card 1 went back to a grey border with an empty radio. The "Best value" pill sits on top of the ring's upper edge and stays fully legible (white on green). The ring's far left/right are cut only by this crop's edge. Price column right-aligned, tabular figures, even 18px gaps.
G28exit 0vlmkit check color 'http://127.0.0.1:48217/index.html?step=3'verdict: CONSISTENT (0 finding(s))
verdict consistent · base #f6f3ee · bodyInk #1f1c19 · linkInk #ffffff · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0
output, 27 line(s), 1.6s
vlmkit check color
source: http://127.0.0.1:48217/index.html?step=3
verdict: CONSISTENT (0 finding(s))
measured: 5 control(s), 0 link(s) in a text flow — from 116 visible box(es)
Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
surfaces 4 distinct
#f6f3ee 60.9% 1 el
#ffffff 35.9% 12 el header.site-header>div.brand-bar
#f4ecdc 2.1% 2 el header.site-header>div.demo-notice
#edf6f0 1.1% 1 el input#billing-same
ink 4 distinct
#1f1c19 60.9% 31 el div.demo-notice>div.wrap>p
#6a635b 30.8% 15 el div.summary-item__text>p.summary-item__meta>span.nw
#57514a 4.9% 2 el li.steps__item>span.steps__marker>span.steps__num
#ffffff 3.3% 1 el form#step-3>div.actions>button.btn
marks 5 distinct
#dcd5ca 38.9% 10 el header.site-header>div.brand-bar
#857d73 28.2% 32 el ol#steps>li.steps__item>span.steps__marker
#2d6a4f 18.2% 20 el ol#steps>li.steps__item>span.steps__marker
#e7e1d8 7.9% 4 el div#summary-panel>div.summary-item
#e6d9bf 6.7% 1 el header.site-header>div.demo-notice
base #f6f3ee, body ink #1f1c19, link ink #ffffff on 1 element(s)
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har G29exit 0vlmkit check a11y contrast 'http://127.0.0.1:48217/index.html?step=2'inspected 50 · failures 0
output, 6 line(s), 1.5s
vlmkit check a11y contrast
html: http://127.0.0.1:48217/index.html?step=2
inspected 50 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-0d8f1101/report.md
http://127.0.0.1:48217/index.html?step=2 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=2 --out app.har, then --har app.har
G30exit 0vlmkit check a11y contrast 'http://127.0.0.1:48217/index.html?step=3'inspected 51 · failures 0
output, 6 line(s), 1.4s
vlmkit check a11y contrast
html: http://127.0.0.1:48217/index.html?step=3
inspected 51 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-19231d1c/report.md
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har
G31exit 0vlmkit check a11y contrast 'http://127.0.0.1:48217/index.html?step=4'inspected 69 · failures 0
output, 6 line(s), 1.4s
vlmkit check a11y contrast
html: http://127.0.0.1:48217/index.html?step=4
inspected 69 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-3caffc24/report.md
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
G32exit 0vlmkit check a11y contrast 'http://127.0.0.1:48217/index.html?step=done'inspected 37 · failures 0
output, 6 line(s), 1.4s
vlmkit check a11y contrast
html: http://127.0.0.1:48217/index.html?step=done
inspected 37 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-628ce05f/report.md
http://127.0.0.1:48217/index.html?step=done is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=done --out app.har, then --har app.har
G33exit 0vlmkit check a11y touch 'http://127.0.0.1:48217/index.html?step=3'level AA · inspected 9 · failures 0 · wcagExempt 0
output, 6 line(s), 1.4s
vlmkit check a11y touch
source: http://127.0.0.1:48217/index.html?step=3 level: WCAG AA (24×24 min)
inspected 9 interactive element(s)
✓ 0 undersized target(s)
report: examples/sites/checkout/test-results/a11y-touch/index-19231d1c/report.md
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har
G34exit 0vlmkit check a11y touch 'http://127.0.0.1:48217/index.html?step=4'level AA · inspected 6 · failures 0 · wcagExempt 0
output, 6 line(s), 1.5s
vlmkit check a11y touch
source: http://127.0.0.1:48217/index.html?step=4 level: WCAG AA (24×24 min)
inspected 6 interactive element(s)
✓ 0 undersized target(s)
report: examples/sites/checkout/test-results/a11y-touch/index-3caffc24/report.md
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
G35exit 0vlmkit check interactions 'http://127.0.0.1:48217/index.html?step=3'elements 9 · suspects 0 · warns 0
output, 16 line(s), 5.4s
vlmkit check interactions
source: http://127.0.0.1:48217/index.html?step=3
status: ok
interactive elements: 9
- [textbox] "" focus✓
- [textbox] "" focus✓
- [textbox] "" focus✓
- [textbox] "" focus✓
- [checkbox] "" focus✓ | Space: layout change
- [textbox] "" focus✓
- [button] "Apply" focus✓ | Enter: layout change | announces
- [button] "Back" focus✓ | Enter: focus moves within
- [button] "Continue" focus✓ | Enter: focus moves within
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har
check interactions at step 3 lists four '[textbox] ""' rows, '[checkbox] ""' and a fifth '[textbox] ""', i.e. empty names, although every control has a <label>. Chromium's own aria snapshot of the same URL gives textbox "Card number", "Expiry date", "Security code (CVC)", "Name on card", checkbox "Billing address is the same as delivery" and textbox "Promo code (optional)". Same pattern as N7 (radios shown by value): the name column is not the accessible name, so the inventory cannot be matched against a brief's control list by name.
G36exit 0vlmkit check interactions 'http://127.0.0.1:48217/index.html?step=4'elements 6 · suspects 0 · warns 1
output, 17 line(s), 4.4s
vlmkit check interactions
source: http://127.0.0.1:48217/index.html?step=4
status: ok
exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule inert-control=suspect
interactive elements: 6
- [link] "Edit plan" focus✓
- [link] "Edit delivery details" focus✓
- [link] "Edit payment" focus✓
- [checkbox] "" focus✓ | Space: no response
- [button] "Back" focus✓ | Enter: focus moves within
- [button] "Place order" focus✓ | Enter: focus moves within
Issues:
warn [inert-control] checkbox "" (form#step-4>div.field>label.choice>input#terms) shows no observable response to Space — no ARIA change, no layout change. Dead control, or its response is outside this probe.
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
G37exit 0vlmkit check integrity 'http://127.0.0.1:48217/index.html?step=2'verdict: CLEAN (0 fail, 0 warn, 4 exempted)
verdict clean · findings 0 · fails 0 · warns 0 · exempted 4 · viewports 3
output, 16 line(s), 4.1s
vlmkit check integrity
source: http://127.0.0.1:48217/index.html?step=2
verdict: CLEAN (0 fail, 0 warn, 4 exempted)
1280x800: 8 component(s), ink 96.7%, 46 text block(s)
768x900: 8 component(s), ink 95.7%, 30 text block(s)
375x700: 8 component(s), ink 94.9%, 30 text block(s)
No integrity defects detected.
Exempted candidates (the tool's call — audit the rule, not the page):
- [text-clipped] li.steps__item.is-done > span:nth-of-type(2) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] li.steps__item.is-done > span:nth-of-type(2) > span:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-collision] li.steps__item.is-done > span:nth-of-type(2) > span:nth-of-type(1) @375: clipped away by li.steps__item.is-done > span:nth-of-type(2) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-clipped] li.steps__item.is-done > span:nth-of-type(2) > span:nth-of-type(1) @375: visually-hidden (sr-only) pattern — 1px box, text for AT only
http://127.0.0.1:48217/index.html?step=2 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=2 --out app.har, then --har app.har
G38exit 0vlmkit check integrity 'http://127.0.0.1:48217/index.html?step=3'verdict: CLEAN (0 fail, 0 warn, 8 exempted)
verdict clean · findings 0 · fails 0 · warns 0 · exempted 8 · viewports 3
output, 20 line(s), 4.2s
vlmkit check integrity
source: http://127.0.0.1:48217/index.html?step=3
verdict: CLEAN (0 fail, 0 warn, 8 exempted)
1280x800: 8 component(s), ink 96.3%, 49 text block(s)
768x900: 8 component(s), ink 95.2%, 33 text block(s)
375x700: 8 component(s), ink 94.6%, 33 text block(s)
No integrity defects detected.
Exempted candidates (the tool's call — audit the rule, not the page):
- [text-clipped] #steps > li:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] #steps > li:nth-of-type(2) > span:nth-of-type(2) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] #steps > li:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] #steps > li:nth-of-type(2) > span:nth-of-type(2) > span:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-collision] #steps > li:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(1) @375: clipped away by #steps > li:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] #steps > li:nth-of-type(2) > span:nth-of-type(2) > span:nth-of-type(1) @375: clipped away by #steps > li:nth-of-type(2) > span:nth-of-type(2) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-clipped] #steps > li:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(1) @375: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] #steps > li:nth-of-type(2) > span:nth-of-type(2) > span:nth-of-type(1) @375: visually-hidden (sr-only) pattern — 1px box, text for AT only
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har
G39exit 0vlmkit check integrity 'http://127.0.0.1:48217/index.html?step=4'verdict: CLEAN (0 fail, 0 warn, 30 exempted)
verdict clean · findings 0 · fails 0 · warns 0 · exempted 30 · viewports 3
output, 28 line(s), 4.4s
vlmkit check integrity
source: http://127.0.0.1:48217/index.html?step=4
verdict: CLEAN (0 fail, 0 warn, 30 exempted)
1280x800: 8 component(s), ink 96.6%, 63 text block(s)
768x900: 8 component(s), ink 95.6%, 47 text block(s)
375x700: 8 component(s), ink 95.3%, 47 text block(s)
No integrity defects detected.
Exempted candidates (the tool's call — audit the rule, not the page):
- [text-collision] div.review > section:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @1280: clipped away by div.review > section:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.review > section:nth-of-type(2) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @1280: clipped away by div.review > section:nth-of-type(2) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.review > section:nth-of-type(3) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @1280: clipped away by div.review > section:nth-of-type(3) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-clipped] #steps > li:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] #steps > li:nth-of-type(2) > span:nth-of-type(2) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] #steps > li:nth-of-type(3) > span:nth-of-type(2) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.review > section:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.review > section:nth-of-type(2) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.review > section:nth-of-type(3) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-collision] div.review > section:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @768: clipped away by div.review > section:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.review > section:nth-of-type(2) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @768: clipped away by div.review > section:nth-of-type(2) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.review > section:nth-of-type(3) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) @768: clipped away by div.review > section:nth-of-type(3) > div:nth-of-type(1) > a:nth-of-type(1) > span:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-clipped] #steps > li:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] #steps > li:nth-of-type(2) > span:nth-of-type(2) > span:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] #steps > li:nth-of-type(3) > span:nth-of-type(2) > span:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
… 15 more
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
S29terms checkbox after focus + Space (inert-control check)
index.html?step=4 · 1280×800 · element #step-4 .field--terms · focus #terms · press Space
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L29 sawClose-up of step 4's terms tile after focusing #terms and pressing Space once: the box is now solid green with a white tick, the tile's edge turned 2px green and its fill mint — the checked state, exactly as when it is clicked. (The focus ring sits 3px outside the tile, beyond this element crop.) So the control does respond to Space; the only thing that changes is the native checked state and its paint, with no layout shift, because nothing else on the page depends on the terms box until Place order is pressed.
N17false-positiveabout G36warn [inert-control] says the terms checkbox 'shows no observable response to Space — no ARIA change, no layout change'. S29 (focus #terms + Space) shows it ticked: green box with a white tick, 2px green edge, mint fill. It is a native <input type=checkbox>; its checked state flips, which is the ARIA transition (checked false -> true), and verify flow G19 steps 31-33 depend on exactly that state (error stays until it is ticked, then Place order succeeds). The step-1 diet boxes and the step-3 billing box only pass this probe because ticking them also moves layout (summary line, revealed fields).
check interactions' inert-control probe does not read the native checked property of an <input type=checkbox>: a checkbox whose only effect is its own checked state (a terms box) is reported 'no ARIA change, no layout change. Dead control, or its response is outside this probe.' Verbatim: 'warn [inert-control] checkbox "" (form#step-4>div.field>label.choice>input#terms) shows no observable response to Space — no ARIA change, no layout change.' The same run also shows the name as "" (see N16).
S30tablet: step 2 layout (summary as bordered disclosure)
index.html?step=2 · 768×1024 · full page, 2 screen(s)


L30 sawTablet 768x1024, step 2 via ?step=2, 2 screens. Single column: the summary is a bordered rounded bar "Show order summary v … $64.74" above the tracker and the step card; fields full width, Phone input 320px, City / State / ZIP on one row, Back / Continue at the bottom. Two faults. (1) Rails: the demo note, the logo and the footer text start at x=24, but the summary bar, tracker and card start at x=48 — the main column is capped at 720px and centred while the header and footer run the full width, so the page has two left edges 24px apart and the logo does not sit over the content. (2) The demo note wraps as "…nothing you type leaves this / page.", a one-word orphan on its second line in the very first thing on the page. Otherwise the card, hints and field rhythm match desktop.
D15 defect .wrap (header/notice/footer) vs main.checkout, 560-959pxAt tablet widths the header, demo note and footer start at x=24 while the content column (capped at 720px and centred) starts at x=48, so the logo and footer do not share a left edge with the summary bar, tracker and card.
D16 defect .demo-notice p @768At 768px the demo note wraps with 'page.' alone on its second line — an orphan in the first line of copy on the page.
Moved the 720px cap from main.checkout to every .wrap below 960px, so the demo note, header, main column and footer share one centred column and one left edge (x=48 at 768).
Running text (p, li, dd) now uses text-wrap: pretty, which avoids one-word last lines, and h1/h2 use text-wrap: balance; applies page-wide rather than rewording the one sentence.
S31tablet after D15/D16 (rails, notice wrap)
index.html?step=2 · 768×1024
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L31 sawTablet 768, step 2 after D15/D16 (first screen). One left rail now: the demo note's info icon, the Pantry Club mark, the summary bar, the tracker's column and the step card all start at x=48, and their right edges share x=720, so the header sits squarely over the content. The note wraps as "This is a demo. No payment is taken. Pantry Club is fictional, and nothing you type / leaves this page." — a balanced two-line block with no orphan. Tracker, card and fields are unchanged from S30.
Note, header, summary bar and card share the x=48 rail at 768.
The note's second line is 'leaves this page.', not a lone 'page.'.
G40exit 0vlmkit check integrity index.htmlverdict: CLEAN (0 fail, 0 warn, 45 exempted)
verdict clean · findings 0 · fails 0 · warns 0 · exempted 45 · viewports 3
output, 27 line(s), 4.3s
vlmkit check integrity
source: index.html
verdict: CLEAN (0 fail, 0 warn, 45 exempted)
1280x800: 8 component(s), ink 96.3%, 67 text block(s)
768x900: 8 component(s), ink 95.3%, 51 text block(s)
375x700: 8 component(s), ink 94.9%, 54 text block(s)
No integrity defects detected.
Exempted candidates (the tool's call — audit the rule, not the page):
- [text-collision] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-collision] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: clipped away by div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] span.plan__badge > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
- [text-clipped] div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
… 30 more
G41exit 1vlmkit check copy index.html --manifest copy.txtmissing 4 · placeholders 0 · manifestLines 10 · statesExplored 1 · revealedOnly 0
output, 13 line(s), 1.4s
vlmkit check copy
source: index.html
status: suspect
rendered text: 1137 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 10 line(s), missing 4
Issues:
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Delivery details" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Review your order" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Place order" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Thanks — your first box is on its way." (comparison is whitespace-normalized, case-sensitive).
G42exit 0vlmkit check design index.htmlverdict: COHERENT (0 finding(s))
verdict coherent · drifting 0 · roles 8
output, 25 line(s), 1.7s
vlmkit check design
source: index.html
verdict: COHERENT (0 finding(s))
roles judged: 1 of 8 seen, spacing values: 15
coverage: 12 of 154 visible element(s) carried an inferable role; 1 skipped as non-resting
no role: span x60, div x21, p x9, label x8, path x8, dd x6, +14 more tag(s)
A role comes from role="..." or from button/input/select/textarea/h1-h6. Layout elements
(div, span, p, a) have none, so a large skip count is normal — this gate judges components,
not every box. Add role="..." where an element IS a component to widen the coverage.
Role reuse (instances / distinct styles; drift below 3x from 3 instances)
input:checkbox 4 inst 1 styles reuse 4x 0 one-off ok
input:radio 2 inst 1 styles reuse 2x 0 one-off not judged
note 1 inst 1 styles reuse 1x 1 one-off not judged
h2 1 inst 1 styles reuse 1x 1 one-off not judged
list 1 inst 1 styles reuse 1x 1 one-off not judged
h1 1 inst 1 styles reuse 1x 1 one-off not judged
select 1 inst 1 styles reuse 1x 1 one-off not judged
button 1 inst 1 styles reuse 1x 1 one-off not judged
not judged: input:radio (2), note (1), h2 (1), list (1), h1 (1), select (1), button (1) — under --min-instances 3, so no finding can come from them.
To keep judging them: --min-instances 2 --min-reuse 2 (both — a 2-instance role cannot reach 3x however many instances the floor allows).
No design drift detected. G43exit 0vlmkit check composition index.htmlverdict: COMPOSED (0 finding(s))
verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0
output, 16 line(s), 1.7s
vlmkit check composition
source: index.html
verdict: COMPOSED (0 finding(s))
measured: 0 label(s), 2 heading level(s), 31 wide block(s) on 4 left / 4 right rail(s) — from 139 visible box(es)
2 label(s) not judged: they open a box that paints its own group edge, so nothing above them could be mis-grouped with
Type hierarchy (largest instance of each declared level)
h1 30px / 700 h1#step-1-title
h2 18px / 700 h2#summary-title
body 16px, largest 30px (1.88x), heaviest +400 weight
Group gaps: 18px between heading-led groups vs 8px inside them (2.25x, 1 sample(s)). Context only — intact pages measure 0.86-3.00x, so this cannot carry a verdict.
Proximity, alignment and type hierarchy all read consistently.
G44exit 1vlmkit check copy 'http://127.0.0.1:48217/index.html?step=2' --manifest copy.txtmissing 4 · placeholders 0 · manifestLines 10 · statesExplored 1 · revealedOnly 0
output, 14 line(s), 1.4s
vlmkit check copy
source: http://127.0.0.1:48217/index.html?step=2
status: suspect
rendered text: 1359 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 10 line(s), missing 4
Issues:
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Choose your plan" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Review your order" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Place order" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Thanks — your first box is on its way." (comparison is whitespace-normalized, case-sensitive).
http://127.0.0.1:48217/index.html?step=2 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=2 --out app.har, then --har app.har
G45exit 1vlmkit check copy 'http://127.0.0.1:48217/index.html?step=4' --manifest copy.txtmissing 4 · placeholders 0 · manifestLines 10 · statesExplored 1 · revealedOnly 0
output, 14 line(s), 1.4s
vlmkit check copy
source: http://127.0.0.1:48217/index.html?step=4
status: suspect
rendered text: 1155 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 10 line(s), missing 4
Issues:
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Choose your plan" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Delivery details" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Continue" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Thanks — your first box is on its way." (comparison is whitespace-normalized, case-sensitive).
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
G46exit 1vlmkit check copy 'http://127.0.0.1:48217/index.html?step=done' --manifest copy.txtmissing 6 · placeholders 0 · manifestLines 10 · statesExplored 1 · revealedOnly 0
output, 16 line(s), 1.4s
vlmkit check copy
source: http://127.0.0.1:48217/index.html?step=done
status: suspect
rendered text: 818 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 10 line(s), missing 6
Issues:
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Choose your plan" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Delivery details" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Payment" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Review your order" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Continue" (comparison is whitespace-normalized, case-sensitive).
x copy-missing: Manifest line not found in rendered text or any of 1 revealed disclosure state(s): "Place order" (comparison is whitespace-normalized, case-sensitive).
http://127.0.0.1:48217/index.html?step=done is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=done --out app.har, then --har app.har
Every required line is present verbatim and visibly, but no single state shows all ten, and check copy renders one state. Union of four check copy runs in this round: load G41 finds Pantry Club, Choose your plan, Payment (tracker), Order summary, Continue, This is a demo. No payment is taken.; ?step=2 G44 adds Delivery details; ?step=4 G45 adds Review your order and Place order; ?step=done G46 adds Thanks — your first box is on its way. That makes 10 of 10. The same lines are also asserted visible in their states by verify flow (steps 5, 29, 34). The 'missing' rows are the brief's own multi-step design: later headings, the step-4 button and the confirmation that replaces the form.
Per-state run for the copy union described on G41: this state shows its own lines. The ones reported missing belong to the other steps and are found by the other runs (G41/G44/G45/G46 together cover all 10 lines).
Per-state run for the copy union described on G41: this state shows its own lines. The ones reported missing belong to the other steps and are found by the other runs (G41/G44/G45/G46 together cover all 10 lines).
Per-state run for the copy union described on G41: this state shows its own lines. The ones reported missing belong to the other steps and are found by the other runs (G41/G44/G45/G46 together cover all 10 lines).
G47exit 0vlmkit check color index.htmlverdict: CONSISTENT (0 finding(s))
verdict consistent · base #f6f3ee · bodyInk #1f1c19 · linkInk #ffffff · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0
output, 25 line(s), 1.7s
vlmkit check color
source: index.html
verdict: CONSISTENT (0 finding(s))
measured: 1 control(s), 0 link(s) in a text flow — from 162 visible box(es)
Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
surfaces 4 distinct
#f6f3ee 59.4% 1 el
#ffffff 37.1% 20 el header.site-header>div.brand-bar
#f4ecdc 2.0% 2 el header.site-header>div.demo-notice
#edf6f0 1.6% 1 el div.plans>label.plan>input.plan__input
ink 4 distinct
#1f1c19 55.1% 42 el div.demo-notice>div.wrap>p
#6a635b 32.2% 27 el div.summary-item__text>p.summary-item__meta>span.nw
#57514a 9.0% 4 el li.steps__item>span.steps__marker>span.steps__num
#ffffff 3.7% 3 el label.plan>span.plan__body>span.plan__badge
marks 5 distinct
#857d73 37.2% 64 el ol#steps>li.steps__item>span.steps__marker
#dcd5ca 35.2% 10 el header.site-header>div.brand-bar
#2d6a4f 14.4% 12 el ol#steps>li.steps__item>span.steps__marker
#e7e1d8 7.1% 4 el div#summary-panel>div.summary-item
#e6d9bf 6.1% 1 el header.site-header>div.demo-notice
base #f6f3ee, body ink #1f1c19, link ink #ffffff on 1 element(s) G48exit 0vlmkit check color 'http://127.0.0.1:48217/index.html?step=2'verdict: CONSISTENT (0 finding(s))
verdict consistent · base #f6f3ee · bodyInk #1f1c19 · linkInk #ffffff · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0
output, 26 line(s), 1.7s
vlmkit check color
source: http://127.0.0.1:48217/index.html?step=2
verdict: CONSISTENT (0 finding(s))
measured: 9 control(s), 0 link(s) in a text flow — from 118 visible box(es)
Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
surfaces 3 distinct
#f6f3ee 60.0% 1 el
#ffffff 38.2% 16 el header.site-header>div.brand-bar
#f4ecdc 1.8% 2 el header.site-header>div.demo-notice
ink 4 distinct
#1f1c19 58.2% 29 el div.demo-notice>div.wrap>p
#6a635b 33.6% 15 el div.summary-item__text>p.summary-item__meta>span.nw
#57514a 4.9% 3 el li.steps__item>span.steps__marker>span.steps__num
#ffffff 3.2% 1 el form#step-2>div.actions>button.btn
marks 5 distinct
#857d73 45.6% 48 el ol#steps>li.steps__item>span.steps__marker
#dcd5ca 38.2% 10 el header.site-header>div.brand-bar
#e7e1d8 7.4% 4 el div#summary-panel>div.summary-item
#e6d9bf 6.3% 1 el header.site-header>div.demo-notice
#2d6a4f 2.5% 8 el ol#steps>li.steps__item>span.steps__marker
base #f6f3ee, body ink #1f1c19, link ink #ffffff on 1 element(s)
http://127.0.0.1:48217/index.html?step=2 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=2 --out app.har, then --har app.har G49exit 0vlmkit check color 'http://127.0.0.1:48217/index.html?step=3'verdict: CONSISTENT (0 finding(s))
verdict consistent · base #f6f3ee · bodyInk #1f1c19 · linkInk #ffffff · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0
output, 27 line(s), 1.7s
vlmkit check color
source: http://127.0.0.1:48217/index.html?step=3
verdict: CONSISTENT (0 finding(s))
measured: 5 control(s), 0 link(s) in a text flow — from 116 visible box(es)
Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
surfaces 4 distinct
#f6f3ee 60.9% 1 el
#ffffff 35.9% 12 el header.site-header>div.brand-bar
#f4ecdc 2.1% 2 el header.site-header>div.demo-notice
#edf6f0 1.1% 1 el input#billing-same
ink 4 distinct
#1f1c19 60.9% 31 el div.demo-notice>div.wrap>p
#6a635b 30.8% 15 el div.summary-item__text>p.summary-item__meta>span.nw
#57514a 4.9% 2 el li.steps__item>span.steps__marker>span.steps__num
#ffffff 3.3% 1 el form#step-3>div.actions>button.btn
marks 5 distinct
#dcd5ca 38.9% 10 el header.site-header>div.brand-bar
#857d73 28.2% 32 el ol#steps>li.steps__item>span.steps__marker
#2d6a4f 18.2% 20 el ol#steps>li.steps__item>span.steps__marker
#e7e1d8 7.9% 4 el div#summary-panel>div.summary-item
#e6d9bf 6.7% 1 el header.site-header>div.demo-notice
base #f6f3ee, body ink #1f1c19, link ink #ffffff on 1 element(s)
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har G50exit 0vlmkit check a11y contrast index.htmlinspected 81 · failures 0
output, 5 line(s), 1.8s
vlmkit check a11y contrast
html: examples/sites/checkout/index.html
inspected 81 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-ca6a16e3/report.md
G51exit 0vlmkit check a11y contrast 'http://127.0.0.1:48217/index.html?step=2'inspected 50 · failures 0
output, 6 line(s), 1.6s
vlmkit check a11y contrast
html: http://127.0.0.1:48217/index.html?step=2
inspected 50 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-0d8f1101/report.md
http://127.0.0.1:48217/index.html?step=2 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=2 --out app.har, then --har app.har
G52exit 0vlmkit check a11y contrast 'http://127.0.0.1:48217/index.html?step=3'inspected 51 · failures 0
output, 6 line(s), 1.7s
vlmkit check a11y contrast
html: http://127.0.0.1:48217/index.html?step=3
inspected 51 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-19231d1c/report.md
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har
G53exit 0vlmkit check a11y contrast 'http://127.0.0.1:48217/index.html?step=4'inspected 69 · failures 0
output, 6 line(s), 1.6s
vlmkit check a11y contrast
html: http://127.0.0.1:48217/index.html?step=4
inspected 69 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-3caffc24/report.md
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
G54exit 0vlmkit check a11y contrast 'http://127.0.0.1:48217/index.html?step=done'inspected 37 · failures 0
output, 6 line(s), 1.6s
vlmkit check a11y contrast
html: http://127.0.0.1:48217/index.html?step=done
inspected 37 text-bearing element(s)
✓ 0 contrast failure(s)
report: examples/sites/checkout/test-results/a11y-contrast/index-628ce05f/report.md
http://127.0.0.1:48217/index.html?step=done is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=done --out app.har, then --har app.har
G55exit 1vlmkit check a11y focus index.htmlsteps 7 · findings 1
output, 6 line(s), 1.6s
vlmkit check a11y focus
source: index.html
captured 7 focus step(s)
✗ 1 finding(s)
[reverse] Focus moved up by 78px (from `form#step-1>fieldset.group>div.plans>label.plan>input.plan__input` at y=412 to `form#step-1>fieldset.group>div.choices>label.choice>input.choice__input` at y=334). Visual order is top-to-bottom; check `tabindex` or DOM order.
report: examples/sites/checkout/test-results/a11y-focus-order/index-ca6a16e3/report.md G56exit 0vlmkit check a11y touch index.htmllevel AA · inspected 9 · failures 0 · wcagExempt 0
output, 5 line(s), 1.7s
vlmkit check a11y touch
source: index.html level: WCAG AA (24×24 min)
inspected 9 interactive element(s)
✓ 0 undersized target(s)
report: examples/sites/checkout/test-results/a11y-touch/index-ca6a16e3/report.md
G57exit 0vlmkit check a11y touch 'http://127.0.0.1:48217/index.html?step=3'level AA · inspected 9 · failures 0 · wcagExempt 0
output, 6 line(s), 1.5s
vlmkit check a11y touch
source: http://127.0.0.1:48217/index.html?step=3 level: WCAG AA (24×24 min)
inspected 9 interactive element(s)
✓ 0 undersized target(s)
report: examples/sites/checkout/test-results/a11y-touch/index-19231d1c/report.md
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har
G58exit 0vlmkit check a11y touch 'http://127.0.0.1:48217/index.html?step=4'level AA · inspected 6 · failures 0 · wcagExempt 0
output, 6 line(s), 1.5s
vlmkit check a11y touch
source: http://127.0.0.1:48217/index.html?step=4 level: WCAG AA (24×24 min)
inspected 6 interactive element(s)
✓ 0 undersized target(s)
report: examples/sites/checkout/test-results/a11y-touch/index-3caffc24/report.md
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
G59exit 0vlmkit scan scroll index.htmlcontainers 0 · overflowX 0 · issues 13
output, 25 line(s), 1.4s
vlmkit scan scroll
source: index.html (1280x720)
status: warn
exits 0 — 13 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x1316 — horizontal overflow 0px, vertical scroll 596px
scroll containers: 0 (dead scrollports 1, clipped 13)
Dead scrollports (declared scrollable, content fits):
- #instructions (overflow: auto auto)
Issues:
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content span.plan__badge > span:nth-of-type(1): span.plan__badge > span:nth-of-type(1) clips 17px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
G60exit 0vlmkit scan scroll index.html --viewport 375x812containers 0 · overflowX 0 · issues 13
output, 25 line(s), 1.4s
vlmkit scan scroll
source: index.html (375x812)
status: warn
exits 0 — 13 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 375x1731 — horizontal overflow 0px, vertical scroll 919px
scroll containers: 0 (dead scrollports 1, clipped 13)
Dead scrollports (declared scrollable, content fits):
- #instructions (overflow: auto auto)
Issues:
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(2) > span:nth-of-type(3) clips 21px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(1) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(3) > span:nth-of-type(3) clips 20px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(1) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(2) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1): div.plans > label:nth-of-type(3) > span:nth-of-type(1) > span:nth-of-type(4) > span:nth-of-type(1) clips 19px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
! clipped-content span.plan__badge > span:nth-of-type(1): span.plan__badge > span:nth-of-type(1) clips 17px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
G61exit 0vlmkit scan handlers index.htmlregistrations 41 · elements 33 · suspects 0
output, 44 line(s), 1.6s
vlmkit scan handlers
source: index.html
status: 1 warn(s)
exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule unprobed-handler-types=suspect
registrations: 41 across 33 element(s) + globals, on a page presenting 9 control(s)
- main#main>div.flow>form#step-1 "Choose your plan Fresh ingredients and e": submit, focusout (delegation container)
- main#main>div.flow>form#step-2 "Delivery details Where should we bring y": submit, focusout (no role)
- main#main>div.flow>form#step-3 "Payment Use a test card: this demo never": submit, focusout (no role)
- main#main>div.flow>form#step-4 "Review your order Check the details, the": submit, focusout (no role)
- div.flow>form#step-2>div.actions>button.btn "Back": click (no role)
- div.flow>form#step-3>div.actions>button.btn "Back": click (no role)
- div.flow>form#step-4>div.actions>button.btn "Back": click (no role)
- div.review>section.review__block>div.review__head>a.review__edit "Edit plan": click (no role)
- div.review>section.review__block>div.review__head>a.review__edit "Edit delivery details": click (no role)
- div.review>section.review__block>div.review__head>a.review__edit "Edit payment": click (no role)
- main#main>div.flow>form#step-1>div#errors-1 "": click (no role)
- main#main>div.flow>form#step-2>div#errors-2 "": click (no role)
- main#main>div.flow>form#step-3>div#errors-3 "": click (no role)
- main#main>div.flow>form#step-4>div#errors-4 "": click (no role)
- fieldset.group>div.plans>label.plan>input.plan__input "2p3m": change
- fieldset.group>div.plans>label.plan>input.plan__input "4p3m": change
- fieldset.group>div.plans>label.plan>input.plan__input "2p5m": change
- fieldset.group>div.choices>label.choice>input.choice__input "Vegetarian": change
- fieldset.group>div.choices>label.choice>input.choice__input "No pork": change
- fieldset.group>div.choices>label.choice>input.choice__input "Dairy-free": change
- fieldset.group>div.choices>label.choice>input.choice__input "Nut-free": change
- form#step-1>div.field>div.select>select#day "Select a day Monday Tuesday Wednesday Th": change
- div.fields>div.field>div.select>select#state "Select AlabamaAlaskaArizonaArkansasCalif": change (no role)
- div#billing-fields>div.field>div.select>select#billing-state "Select AlabamaAlaskaArizonaArkansasCalif": change (no role)
- form#step-4>div.field>label.choice>input#terms "": change (no role)
- form#step-2>div.fields>div.field>textarea#instructions "": input (no role)
- fieldset.group>div.fields>div.field>input#card-number "": input (no role)
- fieldset.group>div.fields>div.field>input#expiry "": input (no role)
- fieldset.group>div.fields>div.field>input#cvc "": input (no role)
- form#step-3>fieldset.group>label.choice>input#billing-same "": change (no role)
- form#step-3>div.field>div.promo__row>button#promo-apply "Apply": click (no role)
- form#step-3>div.field>div.promo__row>input#promo "": keydown (no role)
- main#main>aside#summary>button#summary-toggle "Show order summary $64.74": click (no role)
- globals: document:mousedown, document:click, document:mouseup, window:blur
Issues:
warn [unprobed-handler-types] 6 handler type(s) registered and NONE exercised: change, click, focusout, input, keydown, submit — this gate is an inventory and presses nothing. A clean result here says the wiring exists, not that it works. Run 'check interactions --handlers' to focus and activate the controls, add '--probe-drag' for the drag surfaces, or drive the rest with a 'verify flow' script.
G62exit 0vlmkit check interactions index.htmlelements 9 · suspects 0 · warns 0
output, 15 line(s), 10.1s
vlmkit check interactions
source: index.html
status: ok
interactive elements: 9
- [radio] "2p3m" focus✓ | ArrowDown: layout change
- [radio] "4p3m" unreachable | ArrowDown: layout change
- [radio] "2p5m" unreachable | ArrowDown: focus moves
- [checkbox] "Vegetarian" focus✓ | Space: layout change
- [checkbox] "No pork" focus✓ | Space: layout change
- [checkbox] "Dairy-free" focus✓ | Space: layout change
- [checkbox] "Nut-free" focus✓ | Space: layout change
- [combobox] "Select a day Monday Tuesday Wednesday Thursday Friday Saturday" focus✓ | ArrowDown: layout change | Esc closes
- [button] "Continue" focus✓ | Enter: focus moves within
G63exit 0vlmkit check interactions 'http://127.0.0.1:48217/index.html?step=3'elements 9 · suspects 0 · warns 0
output, 16 line(s), 5.3s
vlmkit check interactions
source: http://127.0.0.1:48217/index.html?step=3
status: ok
interactive elements: 9
- [textbox] "" focus✓
- [textbox] "" focus✓
- [textbox] "" focus✓
- [textbox] "" focus✓
- [checkbox] "" focus✓ | Space: layout change
- [textbox] "" focus✓
- [button] "Apply" focus✓ | Enter: layout change | announces
- [button] "Back" focus✓ | Enter: focus moves within
- [button] "Continue" focus✓ | Enter: focus moves within
http://127.0.0.1:48217/index.html?step=3 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=3 --out app.har, then --har app.har
G64exit 0vlmkit check interactions 'http://127.0.0.1:48217/index.html?step=4'elements 6 · suspects 0 · warns 1
output, 17 line(s), 4.5s
vlmkit check interactions
source: http://127.0.0.1:48217/index.html?step=4
status: ok
exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule inert-control=suspect
interactive elements: 6
- [link] "Edit plan" focus✓
- [link] "Edit delivery details" focus✓
- [link] "Edit payment" focus✓
- [checkbox] "" focus✓ | Space: no response
- [button] "Back" focus✓ | Enter: focus moves within
- [button] "Place order" focus✓ | Enter: focus moves within
Issues:
warn [inert-control] checkbox "" (form#step-4>div.field>label.choice>input#terms) shows no observable response to Space — no ARIA change, no layout change. Dead control, or its response is outside this probe.
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
G65exit 0vlmkit verify flow index.html --flow flow.jsonverdict: DONE (34/34 steps)
done true · passed 34 · total 34
output, 130 line(s), 2.8s
vlmkit verify flow
source: index.html
verdict: DONE (34/34 steps)
✓ step 1: Step 1: Continue with no delivery day shows the error summary and moves focus to it
✓ #errors-1 visible
✓ #errors-1 focused
✓ #errors-1 text~"Select a delivery day"
✓ #day aria-invalid=true
✓ #day aria-describedby=day-hint day-error
✓ #day-error visible
✓ step 2: Choosing a day re-validates the field and clears the summary
✓ #day-error count=0
✓ #errors-1 hidden
✓ #day-note text~"Monday, October 12"
✓ #summary-panel text~"Mon, Oct 12"
✓ step 3: The 4-person radio card updates the order summary
✓ #summary-panel text~"4 people · 3 meals a week"
✓ #summary-panel text~"$107.88"
✓ #summary-panel text~"$116.51"
✓ step 4: A dietary preference shows in the summary
✓ #summary-panel text~"Vegetarian"
✓ step 5: Continue moves to step 2, marks it current and focuses its heading
✓ #step-1 hidden
✓ #step-2 visible
✓ #step-2-title focused
✓ #step-2-title text~"Delivery details"
✓ #steps [data-step='2'] aria-current=step
✓ #steps [data-step='1'] text~"completed"
✓ step 6: Step 2: Continue with empty fields lists six errors and ties each message to its field
✓ #errors-2 focused
✓ #errors-2 li count=6
✓ #full-name aria-invalid=true
✓ #full-name aria-describedby=full-name-error
✓ #zip aria-invalid=true
✓ #phone[aria-invalid] count=0
✓ .field__error .icon count=6
✓ step 7: An error-summary link moves focus to its field
✓ #email focused
✓ step 8: Fill the name
✓ #full-name focused
✓ step 9: Leaving the name field re-validates it (error gone, summary down to five)
✓ #full-name[aria-invalid] count=0
✓ #full-name-error count=0
✓ #errors-2 li count=5
✓ step 10: Leaving a malformed email shows the format message
✓ #email-error text~"like name@example.com"
✓ step 11: Correct the email
✓ #email focused
✓ step 12: Address line 1
✓ #email-error count=0
✓ step 13: City
✓ #address1-error count=0
✓ step 14: State by typeahead
✓ #state-error count=0
✓ step 15: ZIP code
✓ #city-error count=0
✓ step 16: Delivery instructions count down from 200
✓ #instructions-count text~"179 characters left"
✓ #zip-error count=0
✓ step 17: Continue to step 3
✓ #step-3 visible
✓ #step-3-title focused
✓ #steps [data-step='3'] aria-current=step
✓ #billing-preview text~"1450 Alder Street, Portland, OR 97214"
✓ step 18: Step 3: Continue with no card lists four errors
✓ #errors-3 focused
✓ #errors-3 li count=4
✓ #card-number aria-invalid=true
✓ step 19: Card number
✓ #card-number focused
✓ step 20: Expiry (the slash is inserted as you type)
✓ #card-number-error count=0
✓ step 21: Security code
✓ #expiry-error count=0
✓ step 22: Name on card
✓ #cvc-error count=0
✓ step 23: Unticking 'same as delivery' reveals the billing fields
✓ #billing-fields visible
✓ #billing-address1 visible
✓ #card-name-error count=0
✓ step 24: Ticking it again hides them
✓ #billing-fields hidden
✓ step 25: Type an unknown promo code
✓ #promo focused
✓ step 26: An unknown promo code is rejected with a message
✓ #promo-message text~"isn’t a valid promo code"
✓ #promo aria-invalid=true
✓ #summary-promo hidden
✓ step 27: Type WELCOME10 in lower case
✓ #promo focused
✓ step 28: WELCOME10 takes 10% off and the summary follows
✓ #promo-message text~"WELCOME10 applied"
✓ #promo[aria-invalid] count=0
✓ #summary-promo visible
✓ #summary-promo text~"$10.79"
✓ #summary-panel text~"$104.86"
✓ #promo-apply text~"Remove"
✓ step 29: Continue to review
✓ #step-4 visible
✓ #step-4-title focused
✓ #step-4-title text~"Review your order"
✓ #step-4 text~"Card ending 4242, expires 12/28"
✓ #step-4 text~"1450 Alder Street"
✓ #step-4 text~"WELCOME10"
✓ #step-4 text~"Vegetarian"
✓ #step-4 button[type=submit] visible
✓ #step-4 button[type=submit] text~"Place order"
✓ step 30: Edit on the Delivery block goes back to step 2
✓ #step-2 visible
✓ #step-2-title focused
✓ step 31: …and Continue returns straight to review
✓ #step-4 visible
✓ #step-4-title focused
✓ step 32: Place order without agreeing to the terms is stopped
✓ #errors-4 focused
✓ #terms aria-invalid=true
✓ #confirmation hidden
✓ step 33: Agree to the terms
✓ #terms-error count=0
✓ step 34: Place order replaces the form with the confirmation
✓ #confirmation visible
✓ #step-4 hidden
✓ #steps hidden
✓ #confirmation-title focused
✓ #confirmation-title text~"Thanks — your first box is on its way."
✓ #confirmation text~"PC-20417"
✓ #confirmation text~"Monday, October 12, 2026"
✓ #summary-panel text~"$104.86" G66exit 0vlmkit check scroll index.htmlpageScrolled 596 · stickyFixed 1 · engagedSticky 1 · snaps 0 · issues 0
output, 12 line(s), 1.4s
vlmkit check scroll
source: index.html
status: ok
page scrolled: 596px
sticky/fixed elements: 1 (sticky engaged by the scroll: 1)
snap containers driven: 0
Sticky / fixed:
- #summary: sticky top=24px viewport y 143 -> 24
No scroll-behavior issues detected.
G67exit 0vlmkit check breakpoints index.html --sweepbreakpoints 6 · issues 0 · sweepRanges 0
output, 15 line(s), 3.7s
vlmkit check breakpoints
source: index.html
status: ok
width sweep: 320-1280px step 25 (39 widths) — clean
breakpoints checked: 479, 480, 559, 560, 959, 960px (each at B-1 / B / B+1)
479px: clean (max-width: 479px)
480px: clean (min-width: 480px)
559px: clean (max-width: 559px)
560px: clean (min-width: 560px)
959px: clean (max-width: 959px)
960px: clean (min-width: 960px)
All boundaries consistent.
G68exit 0vlmkit check grounding 'http://127.0.0.1:48217/index.html?step=4'targets 2 · scale 0.5 · issues 0
output, 13 line(s), 1.5s
vlmkit check grounding
source: http://127.0.0.1:48217/index.html?step=4 (1280x720)
status: ok
frame: 640x360 screenshot px — medium, cap 640x480, scale 0.50 (every coordinate below is in these 640x360 pixels)
targets: 2 actionable in frame (4 disabled or out of the frame)
Action map (click point in screenshot px):
t1 link "Edit plan" @ (344,197) 30x22 div.review > section:nth-of-type(1) > div:nth-of-type(1) > a:nth-of-type(1)
t2 link "Edit delivery details" @ (344,295) 30x22 div.review > section:nth-of-type(2) > div:nth-of-type(1) > a:nth-of-type(1)
Every target in the frame is resolvable and routes its own clicks.
http://127.0.0.1:48217/index.html?step=4 is live and not pinned — a re-run may measure different data. Pin it: vlmkit snapshot record-har http://127.0.0.1:48217/index.html?step=4 --out app.har, then --har app.har
index.html · 1280×800 · full page, 2 screen(s)
L32 sawFinal, desktop 1280, step 1 at load (2 screens). Sand demo note on one line; brand bar with mark + "Pantry Club" only. Tracker with step 1 current (green ring + halo, bold "Plan"). Step card: "Choose your plan", two-line intro, "Box size", three equal-height plan rows 18px apart — row 1 selected (green 2px border, mint fill, filled radio), the "Best value" pill straddling row 2's top edge aligned with its title; prices right-aligned in tabular figures. Screen 2: "Dietary preferences (optional)" + hint, 2x2 tiles, "Delivery day" + hint + 320px select, hairline, green "Continue" at right, footer. Sticky summary: box art, "2 people · 3 meals a week" / "6 servings · $9.99 each" on single lines, None / Pick a day, Subtotal $59.94, Shipping Free, Tax $4.80, Total today $64.74, renewal note; it stays pinned at top 24px on screen 2. Left rail x=153 inside the card, x=112 for the page; nothing overlaps or clips.
index.html · 375×812 · full page, 3 screen(s)
L33 sawFinal, mobile 375, step 1 (3 screens). Demo note in three lines, brand bar, full-bleed "Show order summary v $64.74" bar, one-row tracker. Plan cards stack people / meals / servings / per-serving with the price column right; the "Best value" pill sits on card 2's top edge; the three cards share one height and "3 meals a week" never wraps. Screen 2: the diet hint now breaks as "We’ll steer your weekly menu / around these." (text-wrap: pretty — no lone word), four full-width 52px tiles, Delivery day with a two-line hint and full-width select, full-width green Continue. Screen 3: card end and footer. 16px gutters, one rail at x=33 inside the card, no horizontal overflow.
S34final: step 2 on desktop
index.html?step=2 · 1280×800 · full page, 2 screen(s)
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L34 sawFinal, desktop, step 2 (?step=2), 2 screens. Tracker: Plan done (check, green connector), Delivery current. "Delivery details / Where should we bring your box?"; Full name, Email + hint, Phone (optional) + hint with the 320px input, Address line 1, Address line 2 (optional), City / State (Select) / ZIP on one aligned row, Delivery instructions (optional) + hint + textarea + "200 characters left". Back left, Continue right under a hairline. Every field boundary is a clear grey line on white; bold labels, grey hints, a steady 6px/22px rhythm. The summary (First delivery Tue, Oct 13) is pinned beside the form on both screens.
S35final: step 3 on desktop
index.html?step=3 · 1280×800 · full page, 2 screen(s)
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L35 sawFinal, desktop, step 3 (?step=3), 2 screens. Tracker with Plan and Delivery done, Payment current. "Payment" + "Use a test card: this demo never charges or stores it." Card details: Card number with the 4242 hint, Expiry (MM/YY) and Security code (CVC) side by side with one-line hints, Name on card. Billing address: the ticked "Billing address is the same as delivery" tile with "1450 Alder Street, Apt 3, Portland, OR 97214" previewed in grey. Promo code (optional) at the 18px section size with its WELCOME10 hint, a 320px input and "Apply". Back / Continue. Three sections of equal weight read clearly in order.
S36final: step 4 on desktop
index.html?step=4 · 1280×800 · full page, 2 screen(s)
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L36 sawFinal, desktop, step 4 (?step=4), 2 screens. Tracker all green up to Review (current). "Review your order" + "Check the details, then place your order." Plan block: Box / Dietary preferences / Delivery day each on one line in the 168px term column; Delivery block: Name, Contact, three-line Address, Instructions; Payment block: Card ending 4242, expires 12/28 with the cardholder, Billing "Same as delivery address", Promo "None". Each block has an underlined "Edit" at the right of its heading. The unticked terms tile with the bold $64.74 renewal price, then Back and "Place order". Summary pinned with Total today $64.74.
S37final: step 2 with validation errors
index.html?step=2 · 1280×800 · full page, 3 screen(s) · click #step-2 button[type=submit]
3 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L37 sawFinal, desktop, step 2 after Continue on empty fields (3 screens). Directly under the heading and intro: the focused error summary (pale pink, 5px red left bar, dark-green focus ring) with "(!) 6 fields need your attention" and six underlined links in field order. Each failing field has a 2px red border and a bold red (!) message under it; Phone and Address line 2 (optional) stay plain grey. In the City / State / ZIP row the ZIP message wraps to "Enter your / ZIP code" in its 131px column — ragged but legible and under its own field (accepted in L10). Back / Continue at the end; summary pinned throughout. The errors are unmistakable without being loud: a soft tint, one strong edge, icons and plain words.
S38final: promo code rejected
index.html?step=3 · 1280×800 · element #step-3 .promo · fill #promo "SAVE20" · click #promo-apply
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L38 sawFinal close-up of the promo field after SAVE20 + Apply: red-bordered input "SAVE20", the 48px "Apply" button beside it, and the error on one line with its (!) icon: "“SAVE20” isn’t a valid promo code. Check the spelling." The 18px "Promo code (optional)" section heading and the WELCOME10 hint sit above. No discount is applied (the summary row stays hidden — confirmed in G65 step 26).
S39final: promo code accepted (field + summary)
index.html?step=3 · 1280×800 · fill #promo "welcome10" · click #promo-apply · scroll-to #step-3 .promo
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L39 sawFinal, desktop, step 3 after typing "welcome10" + Apply, viewport scrolled to the promo. The code was upper-cased to "WELCOME10", the field went read-only (warm off-white fill), the button reads "Remove", and a green check line says "WELCOME10 applied: 10% off your first box (−$5.99)." The sticky summary shows the new "Promo [WELCOME10] −$5.99" row in green, Tax (8%) $4.32, "Total today $58.27", and still "Then $64.74 a week" for the renewal. Card fields above are empty (only the promo was driven), the billing tile ticked with the address preview.
S40final: billing fields revealed
index.html?step=3 · 1280×800 · element #step-3 fieldset:nth-of-type(2) · click .choice:has(#billing-same)
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L40 sawFinal close-up of step 3's Billing address group after one click on the tile: box unticked (grey outline, white), and the billing fields revealed beneath — Billing address line 1, Billing address line 2 (optional), City / State (Select) / ZIP code — with the same 1px grey boundaries, bold labels and rails as the delivery fields. The first click works (the D8 dead-click is gone; G65 step 23 confirms it right after a corrected field).
S41final: confirmation after Place order
index.html?step=4 · 1280×800 · full page, 2 screen(s) · click .choice:has(#terms) · click #step-4 button[type=submit]
2 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L41 sawFinal desktop confirmation after ticking the terms and pressing Place order (2 screens of an 820px page). The tracker and form are gone; a centred card shows the green check-circle, "Thanks — your first box is on its way." on one line, "Your weekly plan is set up. Here’s what we have on file.", and the facts table: Order number PC-20417, First delivery Tuesday, October 13, 2026, Plan 2 people · 3 meals a week, Delivering to "1450 Alder Street, Apt 3, / Portland, OR 97214". The grey note says the demo sent and charged nothing. Summary still at the right (Total today $64.74), card tops aligned at y=143. Finished and calm; nothing clipped.
S42final: phone step 3 with the order summary expanded
index.html?step=3 · 375×812 · full page, 3 screen(s) · click #summary-toggle
3 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L42 sawFinal, phone 375, step 3 with the order summary expanded (3 screens). The bar reads "Hide order summary ^ $64.74" and the panel below it shows the item (box art, "2 people · 3 meals a week", "6 servings · $9.99 each"), None / Tue, Oct 13, Subtotal $59.94, Shipping Free, Tax $4.80, bold "Total today $64.74" and the renewal note, on the warm off-white surface, full bleed. Then the tracker (two checks, Payment current) and the Payment card: stacked card fields, the ticked billing tile with "1450 Alder Street, Apt 3, / Portland, OR 97214", the 18px Promo code section with input + Apply, Back / Continue, footer. All within the 16px gutters.
S43final: focus-gate check, 2 Tabs at 1280x720
index.html · 1280×720 · press Tab · press Tab
1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.
L43 sawFinal repeat of the focus-gate check: 1280x720 viewport after two Tabs. Focus is on the "Vegetarian" tile (3px dark-green ring), which the browser scrolled to viewport y≈334; the plan cards sit ABOVE it (card 1 almost scrolled off the top, cards 2-3 at y≈66-237), the select and Continue below. Focus moved down the page from the plan radio to the first diet tile, in reading order — the "reverse" in G55 is the post-scroll viewport coordinate again.
N23false-positiveabout G55Same as G7 (N1/N2): '[reverse] Focus moved up by 78px (from … input.plan__input at y=412 to … input.choice__input at y=334)'. S43, taken this round at the gate's own 1280x720 after two Tabs, shows the focused Vegetarian tile at viewport y~334 BELOW the plan cards, which have scrolled up past the top; in page coordinates focus moves from ~412 down to ~801. DOM order equals visual order and the page sets no tabindex on any control.
N24false-positiveabout G59Same inventory as G9 (N5): the 13 clipped-content warnings are the 1px visually-hidden comma spans inside the plan cards (the sr-only pattern check integrity G40 exempts as 'visually-hidden (sr-only) pattern'), and the one dead scrollport is the 3-row delivery-instructions textarea, whose default overflow:auto is correct. No page-overflow-x at 1280.
N25false-positiveabout G60At 375x812 the inventory is identical to G59: the same 13 sr-only comma spans and the textarea's default overflow:auto; no page-overflow-x, no scroll container expected on this page.
unprobed-handler-types is the inventory reporting it pressed nothing; no pointer-only control. All six handler types it lists (change, click, focusout, input, keydown, submit) are driven for real by verify flow G65 (34/34) and the keyboard side by check interactions G62-G64.
N27false-positiveabout G64Same as G36 (N17/N18): inert-control on #terms, whose only response to Space is its native checked state and paint. S29 shows Space ticking it, and G65 steps 31-33 depend on that state (error until ticked, then Place order succeeds).
Pantry Club checkout, four rounds. One static page (index.html + styles.css + app.js) with the brief's four steps, radio-card plans, checkbox tiles, delivery-day and state selects, grouped card number, MM/YY expiry, billing reveal, WELCOME10 promo, review with Edit links, terms, and the PC-20417 confirmation; a sticky desktop summary that becomes a disclosure below 960px; an error summary that takes focus and links to fields, with aria-invalid, aria-describedby, an icon and a thicker edge on every failing field; blur re-validation after the first attempt. Final gate set, all green or noted: integrity, design, composition, color (15 text controls over steps 1-3), a11y contrast (5 states), touch (3 states), check scroll (sticky 143->24px), breakpoints --sweep (320-1280 clean), grounding and interactions (3 states), and verify flow DONE 34/34 through the whole checkout. check copy covers all 10 required lines across four states (G41/G44/G45/G46) because 4 of them exist only in later steps. 16 defects, all verified: 12 found by eye (card rhythm, awkward wraps and orphans, misaligned rows, a stray header label, promo hierarchy, a confirmation overflow on phones the D11 fix had caused, tablet rails), 4 by gates (verify flow found the empty billing preview and a real dead click caused by blur re-validation moving the layout between mousedown and mouseup; check design found off-scale spacing twice). Tool findings: the focus gate compares post-scroll viewport y (false 'reverse' on any long form); interactions/grounding print values or empty strings as names; inert-control ignores a checkbox's native checked state; gates cannot open a local page with a query string or a file:// URL, so per-step runs needed a 127.0.0.1 static server.