vlmkit demo · judgment log

Quarry documentation

How this documentation site 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: documentation site — "Quarry" CLI docs

- **Directory**: `examples/sites/docs/`
- **Pattern**: documentation site
- **Language**: English

## What it is

The documentation for **Quarry**, a fictional command-line tool that copies data between databases
while both stay online. Three real pages sharing one stylesheet and one script:

1. `index.html` — **Getting started**: what Quarry is, install, first migration in four steps.
2. `migrations.html` — **Migrations**: how a migration runs (plan → copy → catch-up → cut-over),
   dry runs, rolling back, a short troubleshooting section.
3. `cli.html` — **CLI reference**: every command and its options, in tables.

## Layout and behaviour

- **Header**: the Quarry wordmark, a version badge `v2.4`, a search field that filters the sidebar
  as you type (client-side, over page and section titles), and a theme toggle (light / dark).
- **Sidebar**: the three pages grouped under "Guides" and "Reference", with each page's sections
  listed under it; the current page and section are marked (`aria-current`).
- **Article**: an `h1`, `h2` / `h3` sections, prose with inline code and links, code blocks with a
  **Copy** button (it copies the block and says "Copied" in a live region), callouts (Note,
  Warning), and on `cli.html` option tables. Prev / next page links at the bottom, then a footer
  with "Last updated 12 September 2026".
- **"On this page"**: a right-hand table of contents on wide screens that highlights the section in
  view as you scroll.
- **Widths**: three columns (sidebar · article · contents) from 1200px; two (no contents column)
  from 768px; below 768px one column, with the sidebar in a drawer opened by a "Menu" button.
  The drawer takes focus when it opens, closes on Escape and returns focus to the button.
- **Theme**: follows `prefers-color-scheme`, the toggle overrides it and remembers the choice in
  `localStorage`, and `?theme=light` / `?theme=dark` forces one (for gates and screenshots).
- Links in prose must be distinguishable from the text around them by more than colour.

## Visual direction

Crisp, quiet and technical — legibility first. Neutral greys, one accent colour, a comfortable
measure (about 70 characters), a monospace for code, and code blocks that read well in both themes.

## Required copy

```
Quarry moves data between databases while both stay online.
Getting started
Migrations
CLI reference
Install Quarry
Your first migration
How a migration runs
Dry runs
Rolling back a migration
Troubleshooting
On this page
Last updated 12 September 2026
A dry run changes nothing.
```

## States to show (final round)

Desktop in both themes; mobile with the drawer open; the sidebar filtered by a search term; a
"Copied" confirmation on a code block; the contents column highlighting a section other than the
first; `cli.html`'s tables at mobile width.

## Gates this brief leans on

`check composition` (three pages of heading levels), `check color` (links in prose),
`check a11y contrast` in both themes, `check interactions` (drawer, toggle, copy, search),
`check breakpoints --sweep`, `check scroll` / `scan scroll` (sticky sidebar and contents).

Defects

DefectFound byWhatFixChecked by
D1eye L1 on S1

article.doc (--measure)

Measure is ~84 characters per line (S1-1 first body line), the brief asks for about 70; 42rem at 16px is too wide in DejaVu and wider still in SF/Segoe.

Measure token 42rem -> 36rem (576px, ~72 characters of DejaVu at 16px); the header filter and the footer use the same variable, so they stay on the article's rails. Contents column widened 15rem -> 16rem out of the freed width.

L10
D2eye L1 on S1

index.html step 2, quarry plan output

Plan output columns SIZE and STRATEGY are one space apart ('41.2 GB parallel, 8 ranges'), so each row reads as one phrase and the table in the terminal output does not scan.

The plan output is generated from padded columns: TABLE padEnd(13), ROWS padStart(10), SIZE padStart(7), three spaces before STRATEGY, so numbers right-align under their headers.

L10
D3eye L3 on S3

.lede (index.html, migrations.html)

The lede is a wall: 6 lines at 17px on mobile (S3-1, S5-1), 5 on tablet, 4 on desktop, before the first heading.

Ledes cut to two sentences: index 'Quarry moves data between databases while both stay online. This guide installs the CLI and runs a first migration, start to finish.', migrations and cli ledes similarly halved.

L12
D4eye L2 on S2

.sidebar @768-1199

At tablet width the 240px sidebar breaks 'Rolling back a migration' onto two lines, the only wrapped nav item.

Tablet sidebar 15rem -> 16rem with 1.25rem/0.75rem padding and a 0.5rem right pad on section links, which leaves ~193px for 'Rolling back a migration' (~185px); nav links also get text-wrap: balance as a guard.

L11
D5eye L2 on S2

.code-block pre (tablet and mobile)

Code blocks that overflow are cut hard at the right border ('| sh', '...internal:54', 'nothing will be writte') with no cue that they scroll; scrollbars are hidden in headless and on phones.

quarry.js marks a code block that is wider than its box with data-scroll=left/right, and CSS fades that edge with a mask-image gradient, so a cut line reads as continuing. Such a block also becomes a focusable role=group named '<label> (scrolls sideways)' for keyboards. The install URL was shortened to https://quarry.example/install.sh so that line fits the tablet column outright.

L11
D6eye L2 on S2

footer .footer-about

Footer tagline wraps leaving 'online.' alone on a second line at 768, and the cube mark is centred between the two lines instead of sitting on the first.

Footer is a deliberate two-line column; the tagline has text-wrap: balance and the mark aligns to its first line (align-items: flex-start, 0.2rem top offset); 'Last updated' is indented 1.5rem to start under the tagline's text.

L11
D7eye L4 on S4

.doc :not(pre) > code

Inline code breaks inside a flag: '--' ends a line and 'batch-size' / 'keep-target' / 'apply-workers' / 'verbose' start the next (S4-2, S4-4, S4-5, S5-3, S5-10, S7-8); multi-word codes also split into two pills ('quarry' / 'init' in S3-5).

Inline code is white-space: nowrap, so flags and short commands never break inside themselves. Option names in tables are wrapped per word in span.nw so they may break only between the flag and its placeholder.

L15
D8eye L3 on S3

.steps .step @375

On mobile the step content keeps its 40px indent, leaving paragraphs and code ~295px wide, so code is cut after ~35 columns and pills wrap more.

Below 768px a step's disc sits beside its h3 only (h3 padding-left 2.5rem), step content runs the full column width, and the connecting hairline is dropped.

L12
D9eye L4 on S4

.doc h2 + h3 (migrations Troubleshooting)

'Troubleshooting' h2 sits ~45px above its first h3, the same distance as between two h3 subsections, so the h2 floats instead of heading its group.

Rule .doc h2 + h3 { margin-top: 1.25rem } so a section heading sits closer to its first subsection than subsections sit to each other.

L13
D10eye L4 on S4

.toc

Contents column wraps long h3 entries leaving single words behind ('Replication lag keeps / growing', 'Permission denied on the / source').

Contents column 16rem, h3 indent 1.625rem -> 1.5rem, and text-wrap: balance on contents links, so a wrapped entry splits into two even lines instead of leaving one word.

L13
D11eye L6 on S6

.doc h2 code (cli.html command headings)

Every command heading on cli.html renders inside the grey inline-code pill at 24px: a heavy box that no other heading has. The h2 code reset loses to the later .doc :not(pre) > code rule.

The heading reset is now '.doc h2 > code, .doc h3 > code' placed after the inline-code rule (same specificity, later wins): command headings are plain bold monospace in the heading colour.

L16
D12eye L6 on S6

cli.html table.options

Option tables do not share column positions: Default starts at x~443 to ~547 depending on the table (auto layout), so the reference's grid jumps sideways between commands.

Option tables carry a colgroup with shared targets (38% / 22% / 40%) under auto layout, so every command's table has the same columns wherever the content fits; option names break only between flag and placeholder.

L16
D13eye L6 on S6

#sidebar (scroll-spy)

When the section in view is far down the sidebar (cli.html 'Exit codes'), its teal mark sits under the bottom edge of the sidebar's own scroll view, half cut off; the sidebar does not follow the scroll-spy.

Scroll-spy now keeps the marked link inside its column's own scroll view: if it is within 48px of the sidebar's (or contents') edge, that column's scrollTop is moved to centre it. Only the column scrolls, never the page.

L17
D14eye L7 on S7

cli.html Environment variables table @375

At 375 the environment-variables table stays two columns: nowrap names take ~190px, descriptions get ~130px and break into three lines, inline codes tear ('--' / 'config').

The environment-variables table joins the option tables as table.stack: below 768px each row becomes a card with the variable name on top and its description under it (ARIA table roles added so it stays a table for assistive tech).

L18
D15gate G15

.pager-link

Pager links' accessible name runs the two spans together: 'NextMigrations' (G15 inventory, also the focus report). A screen reader reads one made-up word.

A real space between the pager's direction span and title, so the link's name is 'Next Migrations' / 'Previous Getting started'.

G24
D16gate G15

quarry.js copy fallback

Every Copy button reports 'Enter: focus moves within' (G15): when the Clipboard API is refused, the fallback focuses a temporary textarea and then refocuses the button, so focus leaves the control on each copy.

The Clipboard API fallback no longer creates and focuses a textarea: it answers a copy event itself (execCommand('copy') with a one-shot copy listener that sets text/plain), so focus stays on the Copy button.

G29
D17gate G19

flows/drawer.json step 5

The drawer flow asserts '#sidebar-nav hidden' in the same step as Escape, while the 0.22s slide-out is still running (visibility flips at its end), so the flow fails on correct behaviour; the assertion belongs after the transition.

Edited flows/drawer.json: the Escape step asserts aria-expanded=false and focus on Menu immediately; a following wait 400ms step asserts the nav and scrim hidden, after the 0.22s slide-out.

G26
D18gate G3

.callout padding, .doc ul padding-left

check design scale-outlier: callout padding-left 18px and padding-bottom 15px sit off the page's 16/14px steps, list indent 22px next to 24px; snap them.

Callout padding 0.875rem 1rem (14/16px, on the page's own steps) and list indent 1.5rem (24px).

G25
D19gate G27

flows/copy.json

The copy flow asserts 'Copied' in the same step as the click; a click goes through the asynchronous Clipboard API, and the probe shows the label and live region change ~20ms later (at +0 still 'Copy', at +20 'Copied'), so the flow fails on correct behaviour.

Edited flows/copy.json: the click step now only asserts focus stays on the button; a wait 300ms step asserts 'Copied' on the button and in #announcer; a final wait 2600ms step checks the label reverts to Copy.

G29
D20eye L12 on S12

.doc p, .doc li, .callout p

Prose paragraphs end on single-word orphan lines at every width: 'nothing.', 'copied.', 'control.' (S12-6/7), 'option.' (S11-5), 'it.' and 'read.' (S10-3).

text-wrap: pretty on .doc paragraphs, list items and the lede, so the browser rebalances the last lines of a paragraph instead of leaving one word alone (Chromium 117+, ignored elsewhere).

L19
D21gate G35

cli.html table.stack thead @375

12 container-protrusion fails at 375: the stacked tables' header row is clipped to a 1px box, but the card rule '.doc table.stack tr' also makes that header tr a bordered grid card, so its th cells stick out 14-88px past their own painted row. Invisible, but the hidden header has broken geometry.

Card styling is scoped to 'table.stack tbody tr' (and 'tbody tr + tr'), so the clipped header row stays a plain table row; the thead gets the full visually-hidden recipe (1px, margin -1px, no padding or border, clip-path inset(50%)).

G40
D22gate G32

footer .footer-inner margin-top

check design scale-outlier on migrations.html: the footer's 40px top margin is the only 40px on a page whose steps are 36px; snap it.

Footer top margin 2.5rem -> 2.25rem (36px), on the page's own step.

G41
D23eye L20 on S20

.search-input::-webkit-search-cancel-button

The native search clear button is a bold navy x (S20, S21, S22): the only off-palette colour on the page, and heavier than the magnifier icon on the other side of the field.

The WebKit search cancel button is redrawn: appearance none, a 1rem mask of a 1.5px-stroke x (the magnifier's weight) painted in --text-3, darkening to --text on hover, so it follows both themes.

L26
D24eye L32 on S32

migrations.html .phases @768-~1000

At tablet widths the four phase cards stay in one row inside a ~432px article: ~93px cards, names broken at their hyphens ('Catch- / up', 'Cut- / over'), descriptions one or two words per line. No gate sees it.

The article is a size container ('.doc { container: doc / inline-size }'). Phase cards are 4 across when the article is at least 34rem wide, 2 x 2 below that (card 3, which starts the second row, drops its chevron), and a single column below 24rem. So 768px tablets get 2 x 2 and phones the vertical stack, whatever the viewport breakpoint.

L37
D25eye L33 on S33

cli.html table.options @768-~1000

At tablet widths the option tables squeeze their descriptions into ~160px (3-4 lines each, 'from / config' broken) while the phone card layout, which would fit, only starts below 768px.

The stacked-card rules for table.stack moved from the 767px media query into '@container doc (max-width: 34rem)', so option and environment tables become cards whenever the article is under 544px (tablets up to ~880px as well as phones), and stay tables where they have room.

L39
D26gate G114

flows/drawer.json step 2

The drawer flow only works on index.html: it expects focus on a[href='cli.html#exit-codes'], but on cli.html that last sidebar link is the same-page '#exit-codes', so the flow fails there ('got: a') although Shift+Tab did wrap to the last link.

Edited flows/drawer.json step 2: the target is '#sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a' (the last sidebar link on any page), so the same flow checks the trap on all three pages.

G121
D27eye L46 on S46

cli.html table.options col widths (desktop)

The shared option-table grid gives Default 22% for mostly 2-4 character values while Description gets ~200px, so descriptions run 3-4 lines (S46-5 --cutover <mode>, S46-6 --timeout).

Shared option-table targets 38/22/40 -> 34/20/46 (Option ~196px, Default ~115px, Description ~265px at the 576px measure); the one long default, ./quarry.toml, now reads quarry.toml (with 'relative to the current directory' in the description) so every default fits the narrower column and the grid stays shared.

L48
D28eye L47 on S47

.doc td (stacked cards on phones)

Table-cell descriptions end on single-word lines on phones ('from.', 'to.', 'up.', 'start.', 'value.', 'printed.'); the orphan fix (D20) covered p/li only.

text-wrap: pretty extended from .doc p / li / lede to .doc td, so table cells and the stacked cards rebalance their last lines too.

L49
D29eye L56 on S56

quarry.js scroll-spy

Jumping to a section (migrations.html#rolling-back-a-migration) lands its heading at the top, but the contents column marks the next heading, 'Before cut-over', because that h3 sits ~160px lower, still above the 30% reading line. The same would happen for 'Troubleshooting', whose first h3 is ~50px below it: the section you asked for is not the one marked.

Scroll-spy pins the heading the reader navigates to (contents or sidebar link, hashchange, or a #hash on load): while that heading sits between the header and the 30% reading line it stays current even if its first subsection crosses the line too; the pin is dropped once the heading leaves that zone after the jump has settled (1s), so ordinary scrolling behaves as before. flows/toc.json gained steps for #rolling-back-a-migration and #troubleshooting.

L58

Rounds

R1 · first draft

builder
N1decision

Gate set, fixed before the first run. Routed by skills/vlmkit/SKILL.md to markup-assist (page built from a brief, no reference design) plus dynamic-markup, because the brief specifies behaviour (drawer, scroll-spy, theme, three layouts). Run on all three pages unless noted: check integrity (1280/768/375 defect sweep); check copy --manifest copy.txt (all 13 required lines; the sidebar lists every page's sections and the footer carries the tagline, so the full manifest should pass on every page, not only index); check design; check composition (the brief leans on it: three pages of h1/h2/h3); check color (the brief requires prose links to differ by more than colour); check a11y contrast on ?theme=light AND ?theme=dark; check a11y focus; check a11y touch; scan scroll at 1280 and 375 (sticky sidebar and contents, code blocks and tables that must scroll or stack, no page overflow-x on phones); scan handlers (no pointer-only controls: the drawer's click-outside close is delegated to document on purpose); check interactions (theme toggle, Copy buttons, filter field); check breakpoints --sweep (768 and 1200 boundaries plus overflow between them); check scroll (sticky header, sidebar and contents must actually stick); check motion (the only motion, the drawer slide and smooth scroll, must sit behind prefers-reduced-motion). Plus verify flow with flows/*.json because check interactions has no viewport flag and so can never see the drawer, which only exists below 768px: drawer.json at 375 (focus moves in, Tab wraps, Escape closes and returns focus), filter.json, copy.json, theme.json, toc.json (the scroll-spy marks the section you jump to). Not run: check animation (nothing animates on load), stress i18n (English only, no translated copy).

G1exit 0vlmkit check integrity index.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.0s
vlmkit check integrity
source: index.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 5.1%, 100 text block(s)
  768x900: 8 component(s), ink 7.2%, 90 text block(s)
  375x700: 8 component(s), ink 12.4%, 67 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
G2exit 0vlmkit check copy index.html --manifest copy.txt

missing 0 · placeholders 0 · manifestLines 13 · statesExplored 1 · revealedOnly 0

output, 9 line(s), 1.4s
vlmkit check copy
source: index.html

status: ok
rendered text: 4869 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 13 line(s), missing 0

No copy issues detected.
G3exit 0vlmkit check design index.html

verdict: COHERENT (1 finding(s))

verdict coherent · drifting 0 · roles 7

output, 26 line(s), 1.7s
vlmkit check design
source: index.html

verdict: COHERENT (1 finding(s))
  roles judged: 3 of 7 seen, spacing values: 27
  coverage: 21 of 288 visible element(s) carried an inferable role
    no role: span x49, li x38, a x37, div x25, p x25, code x22, +15 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  button           9 inst    2 styles  reuse   4.5x  1 one-off  ok
  h2               4 inst    1 styles  reuse     4x  0 one-off  ok
  h3               4 inst    1 styles  reuse     4x  0 one-off  ok
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  note             1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: search (1), input:search (1), h1 (1), note (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: 15px (1x) next to 14px (32x) — main#content>article.doc>div.callout paddingBottom; 22px (2x) next to 24px (15x) — main#content>article.doc>ul paddingLeft; 18px (2x) next to 16px (75x) — main#content>article.doc>div.callout paddingLeft. Snap them to the established value or add them deliberately.
D18 defect .callout padding, .doc ul padding-left

check design scale-outlier: callout padding-left 18px and padding-bottom 15px sit off the page's 16/14px steps, list indent 22px next to 24px; snap them.

G4exit 0vlmkit check composition index.html

verdict: COMPOSED (0 finding(s))

verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0

output, 17 line(s), 1.6s
vlmkit check composition
source: index.html

verdict: COMPOSED (0 finding(s))
  measured: 8 label(s), 3 heading level(s), 79 wide block(s) on 8 left / 5 right rail(s) — from 247 visible box(es)
  1 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     36px / 700   main#content>article.doc>h1
  h2     24px / 700   h2#what-quarry-is
  h3     18px / 600   h3#step-describe
  body 13px, largest 36px (2.77x), heaviest +300 weight

Group gaps: 28px between heading-led groups vs 6px inside them (4.67x, 9 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.html

verdict: CONSISTENT (0 finding(s))

verdict consistent · base #ffffff · bodyInk #1b1f24 · linkInk #4b5563 · 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), 37 link(s) in a text flow — from 265 visible box(es)

Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
  surfaces  3 distinct
    #ffffff   87.5%     5 el  
    #f7f8fa   11.9%    11 el  div.header-inner>div.header-brand>span.version
    #f0f2f4    0.6%    14 el  article.doc>p>code
  ink       6 distinct
    #1b1f24   74.3%    70 el  div.header-brand>a.brand>span.brand-name
    #4b5563   19.1%    35 el  div.header-inner>div.header-brand>span.version
    #646c78    2.2%    24 el  div.header-search>div.search>kbd.search-kbd
    #0b5f58    1.8%     6 el  a.skip-link
    #0f766e    1.7%    11 el  main#content>article.doc>p.eyebrow
    …and 1 more
  marks     4 distinct
    #e3e6ea   91.5%   110 el  header.site-header
    #0f766e    4.7%     7 el  a.skip-link
    #858e99    2.6%     4 el  input#search-header
    #d0d5db    1.3%    12 el  div.header-inner>div.header-brand>span.version
  base #ffffff, body ink #1b1f24, link ink #4b5563 on 32 element(s)
G6exit 1vlmkit check a11y contrast 'index.html?theme=light'
output, 1 line(s), 0.9s
error: file not found: examples/sites/docs/index.html?theme=light
G7exit 1vlmkit check a11y contrast 'index.html?theme=dark'
output, 1 line(s), 0.8s
error: file not found: examples/sites/docs/index.html?theme=dark
G8exit 0vlmkit check a11y focus index.html

steps 47 · findings 7

output, 13 line(s), 1.5s
  vlmkit check a11y focus
  source: index.html
  captured 47 focus step(s)
  ! 7 finding(s)
    32 focusable element(s) are viewport-pinned (fixed / sticky); jumps into or out of them are not read as order defects
    [skip-row] Focus jumped down by 231px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.callout.callout-note>p>a`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 360px (from `main#content>article.doc>div.callout.callout-note>p>a` to `ol.steps>li.step>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 404px (from `ol.steps>li.step>div.code-block>div.code-head>button.copy-button` to `ol.steps>li.step>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 585px (from `ol.steps>li.step>div.code-block>div.code-head>button.copy-button` to `ol.steps>li.step>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 241px (from `article.doc>ol.steps>li.step>p>a` to `ol.steps>li.step>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    … 2 more (see the report, or --json for all)
  report: examples/sites/docs/test-results/a11y-focus-order/index-c90ccdb8/report.md
  exits 0 — 7 warn(s) did not fail this command. To gate on one: --rule skip-row=suspect
G9exit 0vlmkit check a11y touch index.html

level AA · inspected 47 · failures 0 · wcagExempt 5

output, 6 line(s), 1.4s
  vlmkit check a11y touch
  source: index.html  level: WCAG AA (24×24 min)
  inspected 47 interactive element(s)
  ✓ 0 undersized target(s)
  5 undersized target(s) excused by WCAG 2.5.8 itself (5 in a sentence)
  report: examples/sites/docs/test-results/a11y-touch/index-c90ccdb8/report.md
G10exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/index.html?theme=light'

inspected 169 · failures 0

output, 5 line(s), 1.6s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/index.html?theme=light
  inspected 169 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/index-02c22ca4/report.md
G11exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/index.html?theme=dark'

inspected 169 · failures 0

output, 5 line(s), 1.5s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/index.html?theme=dark
  inspected 169 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/index-9bdde255/report.md
G12exit 0vlmkit scan scroll index.html

containers 1 · overflowX 0 · issues 1

output, 26 line(s), 1.4s
vlmkit scan scroll
source: index.html (1280x720)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x4266 — horizontal overflow 0px, vertical scroll 3546px
scroll containers: 1 (dead scrollports 9, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(0,60) 272x660

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(2) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(1) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(2) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(3) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(4) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(4) > div:nth-of-type(2) > pre:nth-of-type(1) (overflow: auto auto)
  - nav.toc (overflow: auto auto)

Issues:
  ! clipped-content div.header-search > div:nth-of-type(1) > label:nth-of-type(1): div.header-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 1 expectedScrollports entry ready for a UI Contract.
G13exit 0vlmkit scan scroll index.html --viewport 375x740

containers 5 · overflowX 0 · issues 1

output, 26 line(s), 1.3s
vlmkit scan scroll
source: index.html (375x740)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 375x5464 — horizontal overflow 0px, vertical scroll 4724px
scroll containers: 5 (dead scrollports 5, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(-320,0) 320x740
  - article.doc > div:nth-of-type(2) > pre:nth-of-type(1): axis=x overflow=128px box=(21,1605) 333x52
  - article.doc > div:nth-of-type(3) > pre:nth-of-type(1): axis=x overflow=58px box=(21,1771) 333x74
  - ol.steps > li:nth-of-type(1) > div:nth-of-type(1) > pre:nth-of-type(1): axis=x overflow=192px box=(61,2588) 293x96
  - ol.steps > li:nth-of-type(2) > div:nth-of-type(1) > pre:nth-of-type(1): axis=x overflow=184px box=(61,3129) 293x251

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(3) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(4) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(4) > div:nth-of-type(2) > pre:nth-of-type(1) (overflow: auto auto)
  - nav.toc (overflow: auto auto)

Issues:
  ! clipped-content div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1): div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 5 expectedScrollports entries ready for a UI Contract.
G14exit 0vlmkit scan handlers index.html

registrations 26 · elements 14 · suspects 0

output, 25 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: 26 across 14 element(s) + globals, on a page presenting 47 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:scroll, window:resize, window:hashchange

Issues:
  warn [unprobed-handler-types] 3 handler type(s) registered and NONE exercised: click, input, keydown — 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.
G15exit 0vlmkit check interactions index.html --max-elements 80

elements 47 · suspects 0 · warns 0

output, 53 line(s), 10.1s
vlmkit check interactions
source: index.html

status: ok
interactive elements: 47

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Troubleshooting" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Dry runs" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "How a migration runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "CLI reference" focus✓
  - [link] "NextMigrations" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Describe the source and target" focus✓
  - [link] "Review the plan" focus✓
  - [link] "Rehearse with a dry run" focus✓
  - [link] "Run the migration and cut over" focus✓
  - [link] "Next steps" focus✓
  - [link] "Back to top" focus✓
D15 defect .pager-link

Pager links' accessible name runs the two spans together: 'NextMigrations' (G15 inventory, also the focus report). A screen reader reads one made-up word.

D16 defect quarry.js copy fallback

Every Copy button reports 'Enter: focus moves within' (G15): when the Clipboard API is refused, the fallback focuses a temporary textarea and then refocuses the button, so focus leaves the control on each copy.

G16exit 0vlmkit check breakpoints index.html --sweep

breakpoints 2 · issues 0 · sweepRanges 0

output, 11 line(s), 3.3s
vlmkit check breakpoints
source: index.html

status: ok
width sweep: 320-1300px step 25 (40 widths) — clean
breakpoints checked: 768, 1200px (each at B-1 / B / B+1)

  768px: clean (max-width: 768px)
  1200px: clean (max-width: 1200px)

All boundaries consistent.
G17exit 0vlmkit check scroll index.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.4s
vlmkit check scroll
source: index.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G18exit 0vlmkit check motion index.html

activeAnimations 0 · running 0 · issues 0

output, 12 line(s), 1.4s
vlmkit check motion
source: index.html

status: ok
samples: 0
active animations: 0
active transitions: 0
running animations: 0
paused animations: 0
reduced-motion rule: yes

No motion issues detected.
G19exit 1vlmkit verify flow index.html --flow flows/drawer.json

verdict: FAILED (4/5 steps)

done false · passed 4 · total 5

output, 21 line(s), 1.8s
vlmkit verify flow
source: index.html

verdict: FAILED (4/5 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar a[href='cli.html#exit-codes'] focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✗ step 5: Escape closes the drawer and returns focus to Menu
    ✓ .menu-button aria-expanded=false 
    ✗ #sidebar-nav hidden (got: visible)
    ✓ .menu-button focused
D17 defect flows/drawer.json step 5

The drawer flow asserts '#sidebar-nav hidden' in the same step as Escape, while the 0.22s slide-out is still running (visibility flips at its end), so the flow fails on correct behaviour; the assertion belongs after the transition.

G20exit 0vlmkit verify flow index.html --flow flows/filter.json

verdict: DONE (3/3 steps)

done true · passed 3 · total 3

output, 17 line(s), 1.7s
vlmkit verify flow
source: index.html

verdict: DONE (3/3 steps)

✓ step 1: Typing filters the sidebar to matching page and section titles
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=2 
    ✓ #sidebar-nav mark count=2 
    ✓ #sidebar-nav a[href='migrations.html#rolling-back-a-migration'] visible 
    ✓ #sidebar-nav a[href='cli.html#quarry-rollback'] visible 
    ✓ #sidebar-nav a[href='#install-quarry'] hidden 
✓ step 2: A term that matches nothing says so
    ✓ .nav-empty visible 
    ✓ .nav-empty text~"zebra" 
✓ step 3: Escape clears the filter and every section comes back
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=17 
    ✓ .nav-empty hidden
G21exit 0vlmkit verify flow index.html --flow flows/copy.json

verdict: DONE (1/1 steps)

done true · passed 1 · total 1

output, 8 line(s), 1.8s
vlmkit verify flow
source: index.html

verdict: DONE (1/1 steps)

✓ step 1: Copy says Copied on the button and in the live region
    ✓ #install-quarry ~ .code-block .copy-button text~"Copied" 
    ✓ #announcer text~"Copied"
G22exit 0vlmkit verify flow index.html --flow flows/theme.json

verdict: DONE (2/2 steps)

done true · passed 2 · total 2

output, 11 line(s), 1.7s
vlmkit verify flow
source: index.html

verdict: DONE (2/2 steps)

✓ step 1: The toggle switches to dark and reports it
    ✓ html data-theme=dark 
    ✓ .theme-toggle aria-pressed=true 
✓ step 2: And back to light
    ✓ html data-theme=light 
    ✓ .theme-toggle aria-pressed=false
G23exit 0vlmkit verify flow migrations.html --flow flows/toc.json

verdict: DONE (2/2 steps)

done true · passed 2 · total 2

output, 10 line(s), 2.9s
vlmkit verify flow
source: migrations.html

verdict: DONE (2/2 steps)

✓ step 1: Following a contents link marks that section in both columns
✓ step 2: after the smooth scroll settles
    ✓ .toc-list a[href='#dry-runs'] aria-current=true 
    ✓ .nav-sections a[href='#dry-runs'] aria-current=true 
    ✓ .toc-list a[aria-current] count=1
S1

index.html · 1280×800 · full page, 6 screen(s)

6 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L1 saw

Desktop 1280, index.html, 6 screens. S1-1: the three columns start on one top edge (sidebar "Guides", article eyebrow "Guides", "On this page" all at y~101). Header: cube mark + "Quarry" + "v2.4" pill over the sidebar; the filter field starts exactly on the article's left edge (x=320); moon toggle far right over the contents column. Sidebar: "Getting started" is a teal pill (current page) and "What Quarry is" carries the teal bar; Migrations and CLI reference bold, CLI sections in monospace; "Exit codes" is cut by the viewport bottom (the sidebar scrolls). Article: h1 36px, grey lede, h2s. PROBLEM: the measure is too long, the first body line "Quarry is a command-line tool for online database migrations. It copies every table" is ~84 characters against the brief's ~70. The lede runs 4 lines at 19px, heavy for an intro. Contents column: two h3 entries wrap in the 216px column ("Describe the source and / target", "Run the migration and cut / over"), acceptable. S1-2/S1-3: code blocks read well (hairline border, "Terminal" label + Copy button, grey $ prompt, bold command, teal flags, brown URLs); Note callout with teal rule and icon; the four steps have teal number discs joined by a hairline. Scroll-spy follows the screens: Install Quarry, Your first migration, Review the plan, Run the migration… are marked in both sidebar and contents in turn. S1-4: PROBLEM: in the plan output SIZE and STRATEGY are one space apart ("41.2 GB parallel, 8 ranges"), so each row reads as one phrase. S1-6: Next steps list with underlined teal links, one "Next > Migrations" card in the right half, footer rule, mark + tagline, and "Last updated 12 September 2026" wrapping under it only because the row is too narrow for both.

D1 defect article.doc (--measure)

Measure is ~84 characters per line (S1-1 first body line), the brief asks for about 70; 42rem at 16px is too wide in DejaVu and wider still in SF/Segoe.

D2 defect index.html step 2, quarry plan output

Plan output columns SIZE and STRATEGY are one space apart ('41.2 GB parallel, 8 ranges'), so each row reads as one phrase and the table in the terminal output does not scan.

S2

index.html · 768×1024 · full page, 5 screen(s)

S2 screen 1S2 screen 2S2 screen 3S2 screen 4S2 screen 5
L2 saw

Tablet 768, index.html, 5 screens. Two columns: 240px sidebar + article, no contents column, as the brief says; the filter field starts at x=280, the article's left edge, toggle at right. S2-1: PROBLEM: in the sidebar "Rolling back a migration" breaks onto two lines ("Rolling back a / migration"), the only wrapped item, and it breaks the list's rhythm. The lede runs 5 lines. S2-2: PROBLEM: the install-script block is cut hard at the right edge, "| sh" sliced by the border, with nothing saying the block scrolls (headless hides scrollbars, and phones show none either). Same cut in S2-3 ("...internal:54" in the init command, "parallel," in the plan table) and S2-4 ("1 h 52 m"). Otherwise the steps, the Note callout and the code chrome sit well, and the whole sidebar fits the 1024px height. S2-5: PROBLEM: the footer tagline wraps to two lines leaving "online." alone on the second, and the cube mark is centred between the two lines instead of sitting on the first. Next card right-aligned in its half.

D4 defect .sidebar @768-1199

At tablet width the 240px sidebar breaks 'Rolling back a migration' onto two lines, the only wrapped nav item.

D5 defect .code-block pre (tablet and mobile)

Code blocks that overflow are cut hard at the right border ('| sh', '...internal:54', 'nothing will be writte') with no cue that they scroll; scrollbars are hidden in headless and on phones.

D6 defect footer .footer-about

Footer tagline wraps leaving 'online.' alone on a second line at 768, and the cube mark is centred between the two lines instead of sitting on the first.

S3

index.html · 375×812 · full page, 11 screen(s)

11 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L3 saw

Mobile 375, index.html, 11 screens. S3-1: header row is Menu (icon + "Menu"), cube + "Quarry" + "v2.4", moon toggle at right; balanced, 20px gutters; no sidebar and no contents column, as the brief says. PROBLEM: the lede takes 6 lines at 17px, a wall of grey before the first heading. S3-3/S3-4: PROBLEM: the install blocks are cut at the right edge ("https://get.quarry.example/i", "2 September") with no cue that they scroll. S3-5 to S3-9: PROBLEM: inside the steps the content is indented 40px, so paragraphs and code get ~295px; inline code pills split across lines ("quarry" / "init" in S3-5, "quarry status" / "--watch" in S3-9), which reads as two separate codes; the init and plan blocks are cut after ~35 columns. Step discs and hairline are fine, the Note callout reads well, h3s wrap cleanly. S3-11: Next card full width, footer tagline wraps with the mark centred on both lines, Last updated below. No horizontal page overflow anywhere.

D3 defect .lede (index.html, migrations.html)

The lede is a wall: 6 lines at 17px on mobile (S3-1, S5-1), 5 on tablet, 4 on desktop, before the first heading.

D8 defect .steps .step @375

On mobile the step content keeps its 40px indent, leaving paragraphs and code ~295px wide, so code is cut after ~35 columns and pills wrap more.

S4

migrations.html · 1280×800 · full page, 6 screen(s)

6 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L4 saw

Desktop 1280, migrations.html, 6 screens. S4-1: sidebar now marks "Migrations" as the current page (teal pill) with "How a migration runs" barred; contents lists 4 h2s with their h3s indented. The four phase cards (mono teal numbers, bold names, grey one-liners, a chevron in each gap) read as a sequence at a glance, the strongest visual on the page. Contents column: PROBLEM: long h3 entries wrap and leave single words behind ("Replication lag keeps / growing", "Permission denied on the / source"). S4-2: PROBLEM: inline code breaks at the hyphen: "--" ends one line and "batch-size" starts the next, so a flag is torn in two; the same in S4-4 ("--" / "keep-target") and S4-5 ("--" / "apply-workers", "quarry status --" / "verbose"). Scroll-spy moves through Plan, Copy, Catch-up. S4-3: dry-run output with teal check marks and an amber "!" reads well; Note callout; "Dry runs" marked in both columns. S4-4: Ctrl + C keycaps, Warning callout in amber tint with a triangle icon, clearly different from the teal Note. S4-5: PROBLEM: "Troubleshooting" h2 sits ~45px above its first h3, the same gap as between two h3 subsections, so the h2 floats rather than heading its group. S4-6: Previous and Next cards side by side, equal width; footer as on index; contents marks the last entry at page bottom.

D7 defect .doc :not(pre) > code

Inline code breaks inside a flag: '--' ends a line and 'batch-size' / 'keep-target' / 'apply-workers' / 'verbose' start the next (S4-2, S4-4, S4-5, S5-3, S5-10, S7-8); multi-word codes also split into two pills ('quarry' / 'init' in S3-5).

D9 defect .doc h2 + h3 (migrations Troubleshooting)

'Troubleshooting' h2 sits ~45px above its first h3, the same distance as between two h3 subsections, so the h2 floats instead of heading its group.

D10 defect .toc

Contents column wraps long h3 entries leaving single words behind ('Replication lag keeps / growing', 'Permission denied on the / source').

S5

migrations.html · 375×812 · full page, 12 screen(s)

12 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L5 saw

Mobile 375, migrations.html, 12 screens. S5-1: lede 6 lines again. S5-1/S5-2: phases become a vertical stack of four cards with a small down chevron between them at the left, readable and not cramped. S5-3: PROBLEM: "(--" ends a line and "parallel" starts the next inside one inline-code pill; the same tearing in S5-10 ("--" / "apply-workers", "quarry status --" / "verbose") and S5-11 ("quarry run --" / "dry-run"). S5-5/S5-6: the dry-run output is cut at the right edge ("nothing will be writte", "all with p") with no sign it scrolls. Note and Warning callouts fit the width and read well. S5-9: the Troubleshooting h2 is as far from its first h3 as the h3s are from each other. S5-11/S5-12: Previous and Next cards stack full width (Previous text left, Next right), then the footer; no horizontal overflow.

S6

cli.html · 1280×800 · full page, 8 screen(s)

S6 screen 1S6 screen 2S6 screen 3S6 screen 4S6 screen 5S6 screen 6S6 screen 7S6 screen 8
L6 saw

Desktop 1280, cli.html, 8 screens. S6-1: "Reference" eyebrow, h1, a two-line lede with a quarry.toml pill, a "Usage" block, then Global options with a captioned table ("Options for every command"): grey header row, hairline rows, mono bold option names, defaults, sans descriptions; reads cleanly. Contents lists the commands in monospace. PROBLEM (S6-2 on): every command heading ("quarry init", "quarry plan"...) renders inside the grey inline-code pill at 24px, a heavy box on each h2 that no other heading has; I meant plain monospace. PROBLEM: the option tables do not share column positions: the Default column starts at x~489 for init, ~500 plan, ~510 run, ~443 status, ~547 cutover, ~481 rollback, ~516 verify, so scrolling the reference the grid jumps sideways table to table. S6-4: the run table's longer descriptions wrap to two lines, fine; mixed defaults ("off" in sans, "4"/"5000"/"manual" in mono) are literal vs descriptive, acceptable. S6-5: quarry status sample output reads well. S6-8: exit-codes table, one "Previous > Migrations" card on the left, footer. PROBLEM: the current section in the sidebar ("Exit codes", marked teal) sits under the bottom edge of the sidebar's viewport, half cut off, so the highlight is only visible if you scroll the sidebar yourself.

D11 defect .doc h2 code (cli.html command headings)

Every command heading on cli.html renders inside the grey inline-code pill at 24px: a heavy box that no other heading has. The h2 code reset loses to the later .doc :not(pre) > code rule.

D12 defect cli.html table.options

Option tables do not share column positions: Default starts at x~443 to ~547 depending on the table (auto layout), so the reference's grid jumps sideways between commands.

D13 defect #sidebar (scroll-spy)

When the section in view is far down the sidebar (cli.html 'Exit codes'), its teal mark sits under the bottom edge of the sidebar's own scroll view, half cut off; the sidebar does not follow the scroll-spy.

S7

cli.html · 375×812 · full page, 15 screen(s)

S7 screen 1S7 screen 2S7 screen 3S7 screen 4S7 screen 5S7 screen 6S7 screen 7S7 screen 8S7 screen 9S7 screen 10S7 screen 11S7 screen 12S7 screen 13S7 screen 14S7 screen 15
L7 saw

Mobile 375, cli.html, 15 screens. S7-1/S7-2: option tables turn into one card per option: mono option name left, "Default ./quarry.toml" small and right-aligned, description under it; easy to scan, no horizontal scrolling. --help/--version cards correctly show no Default. S7-3: PROBLEM: the Environment variables table stays a two-column table: the nowrap variable names take ~190px and "Used for" is squeezed into ~130px, so each description breaks into three lines ("Password for / the source / connection.") and the inline codes tear ("--" / "config", "--no-" / "color"). S7-4 to S7-13: command headings in grey pills (same as desktop), Usage blocks cut at the right edge ("--target <url>" sliced) with no scroll cue, "Required" set bold on the right of --source/--target cards; S7-8 "--" / "max-lag" torn inside a pill; S7-9 status output cut after "rows". S7-14/S7-15: the exit-codes table fits 375 well in two columns. Previous card and footer below. No page-level horizontal overflow.

D14 defect cli.html Environment variables table @375

At 375 the environment-variables table stays two columns: nowrap names take ~190px, descriptions get ~130px and break into three lines, inline codes tear ('--' / 'config').

S8

drawer open

index.html · 375×812 · click .menu-button · wait 400

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L8 saw

Mobile 375, index.html after clicking Menu. The drawer is open over the page at 320px: a sticky head with "Documentation" and a bordered X close button, then the filter field at full drawer width (only its placeholder "Filter pages and sections" and the magnifier show; the visually-hidden label is not visible, as intended), then Guides / Getting started (teal pill) / its sections with "What Quarry is" barred, Migrations, Reference, CLI reference with the commands in monospace running past the bottom (the drawer scrolls). A grey scrim dims the 55px strip of page on the right, including the header's moon toggle. Nothing overlaps or clips inside the drawer; the layout is the desktop sidebar re-used, which keeps the two consistent.

S9

dark via ?theme=dark

index.html?theme=dark · 1280×800

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L9 saw

Desktop 1280, index.html?theme=dark: the URL parameter forces dark as designed. Page #0f1216-ish near-black, text off-white, secondary text a cool grey, accent a lighter teal; the header toggle shows the sun icon (pressed state). Sidebar current page is a dark teal pill with light-teal text, section bar and contents highlight are teal; the h1, lede and body keep the same hierarchy as light; inline code pills are a slightly lighter slate with a hairline border and stay legible. Borders are subtle but visible (header rule, sidebar rule, search field outline). Same measure problem as light (first body line ~84 characters). No colour looks off-palette; nothing reads as washed out.

N2false-positiveabout G1

clipped-content on 'div.drawer-search > div > label' @375 is the visually-hidden <label> of the drawer's filter field (1px box, clip-path inset(50%), overflow hidden: the standard sr-only pattern). The same gate names that exact pattern and exempts it at 1280 and 768 ('visually-hidden (sr-only) pattern - 1px box, text for AT only'); at 375 the copy it looks at sits inside the closed, off-canvas drawer, which is presumably why the exemption missed. S8 (drawer open) shows the field with only its placeholder and no stray label text: nothing a sighted user should see is cut off. The label stays: it gives the field a programmatic name that does not disappear on typing, unlike the placeholder.

N3false-positiveabout G12

scan scroll @1280 reports clipped-content on the header filter's visually-hidden <label> (198px of text in a 1px sr-only box). check integrity measured the same element at the same width in G1 and exempted it as the sr-only pattern; the two gates disagree about one element. S1-1 shows the header field with its placeholder only, no clipped text.

N4false-positiveabout G13

scan scroll @375 reports clipped-content on the drawer filter's visually-hidden <label>: same sr-only pattern as G12/G1, inside the closed drawer. S8 shows the open drawer with no visible label text. Page overflow-x is 0 at 375, which is what this run was for.

N5false-positiveabout G8

All 7 skip-row warns are Tab jumping between Copy buttons and links across prose, and the gate asks to 'confirm no focusable element was unintentionally skipped'. Confirmed from its own sequence table (47 stops: skip link, brand, filter, toggle, 20 sidebar links, 10 Copy buttons and 6 prose links in document order, pager, contents) and from S1-2..S1-5: between each pair there are only paragraphs, a callout's text, inline <code> (not focusable) and code blocks whose only control is the Copy button that was visited. A documentation page with long prose between controls produces exactly this.

N6acceptedabout G14

unprobed-handler-types (click, input, keydown registered, none exercised) is the inventory gate saying it pressed nothing. They are exercised elsewhere: G15 check interactions pressed Enter on the theme toggle (pressed false -> true) and on every Copy button (announces); G20 fills the filter field and presses Escape in it; G19 drives the drawer's click and Tab/Shift+Tab/Escape keydown handlers at 375; G22 clicks the toggle twice.

N7toolabout G6

The protocol asks for check a11y contrast in every theme via the brief's URL parameter, but a relative page path with a query string is taken as a filename. Verbatim: 'error: file not found: examples/sites/docs/index.html?theme=light' (G6) and '...index.html?theme=dark' (G7). Worked around with an absolute file:// URL (G10, G11), which the gate loads with the query intact; judge.mjs shot accepts 'index.html?theme=dark' directly (S9), so the two tools disagree on the same argument.

N8tool

check a11y focus and check a11y contrast write their reports and screenshots into ./test-results/ under the current directory, and judge.mjs runs gates with the site directory as cwd, so every run drops a test-results/ tree inside the published site folder (examples/sites/docs/test-results/a11y-focus-order/..., .../a11y-contrast/...). I delete it before finishing; a site author who did not notice would publish it.

F1fix for D1

Measure token 42rem -> 36rem (576px, ~72 characters of DejaVu at 16px); the header filter and the footer use the same variable, so they stay on the article's rails. Contents column widened 15rem -> 16rem out of the freed width.

F2fix for D2

The plan output is generated from padded columns: TABLE padEnd(13), ROWS padStart(10), SIZE padStart(7), three spaces before STRATEGY, so numbers right-align under their headers.

F3fix for D3

Ledes cut to two sentences: index 'Quarry moves data between databases while both stay online. This guide installs the CLI and runs a first migration, start to finish.', migrations and cli ledes similarly halved.

F4fix for D4

Tablet sidebar 15rem -> 16rem with 1.25rem/0.75rem padding and a 0.5rem right pad on section links, which leaves ~193px for 'Rolling back a migration' (~185px); nav links also get text-wrap: balance as a guard.

F5fix for D5

quarry.js marks a code block that is wider than its box with data-scroll=left/right, and CSS fades that edge with a mask-image gradient, so a cut line reads as continuing. Such a block also becomes a focusable role=group named '<label> (scrolls sideways)' for keyboards. The install URL was shortened to https://quarry.example/install.sh so that line fits the tablet column outright.

F6fix for D6

Footer is a deliberate two-line column; the tagline has text-wrap: balance and the mark aligns to its first line (align-items: flex-start, 0.2rem top offset); 'Last updated' is indented 1.5rem to start under the tagline's text.

F7fix for D7

Inline code is white-space: nowrap, so flags and short commands never break inside themselves. Option names in tables are wrapped per word in span.nw so they may break only between the flag and its placeholder.

F8fix for D8

Below 768px a step's disc sits beside its h3 only (h3 padding-left 2.5rem), step content runs the full column width, and the connecting hairline is dropped.

F9fix for D9

Rule .doc h2 + h3 { margin-top: 1.25rem } so a section heading sits closer to its first subsection than subsections sit to each other.

F10fix for D10

Contents column 16rem, h3 indent 1.625rem -> 1.5rem, and text-wrap: balance on contents links, so a wrapped entry splits into two even lines instead of leaving one word.

F11fix for D11

The heading reset is now '.doc h2 > code, .doc h3 > code' placed after the inline-code rule (same specificity, later wins): command headings are plain bold monospace in the heading colour.

F12fix for D12

Option tables carry a colgroup with shared targets (38% / 22% / 40%) under auto layout, so every command's table has the same columns wherever the content fits; option names break only between flag and placeholder.

F13fix for D13

Scroll-spy now keeps the marked link inside its column's own scroll view: if it is within 48px of the sidebar's (or contents') edge, that column's scrollTop is moved to centre it. Only the column scrolls, never the page.

F14fix for D14

The environment-variables table joins the option tables as table.stack: below 768px each row becomes a card with the variable name on top and its description under it (ARIA table roles added so it stays a table for assistive tech).

F15fix for D15

A real space between the pager's direction span and title, so the link's name is 'Next Migrations' / 'Previous Getting started'.

F16fix for D16

The Clipboard API fallback no longer creates and focuses a textarea: it answers a copy event itself (execCommand('copy') with a one-shot copy listener that sets text/plain), so focus stays on the Copy button.

F17fix for D17

Edited flows/drawer.json: the Escape step asserts aria-expanded=false and focus on Menu immediately; a following wait 400ms step asserts the nav and scrim hidden, after the 0.22s slide-out.

F18fix for D18

Callout padding 0.875rem 1rem (14/16px, on the page's own steps) and list indent 1.5rem (24px).

R2 · fix pass

builder
G24exit 0vlmkit check interactions index.html --max-elements 80

elements 47 · suspects 0 · warns 0

output, 53 line(s), 10.1s
vlmkit check interactions
source: index.html

status: ok
interactive elements: 47

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Troubleshooting" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Dry runs" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "How a migration runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Next Migrations" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Describe the source and target" focus✓
  - [link] "Review the plan" focus✓
  - [link] "Rehearse with a dry run" focus✓
  - [link] "Run the migration and cut over" focus✓
  - [link] "Next steps" focus✓
  - [link] "Back to top" focus✓
G25exit 0vlmkit check design index.html

verdict: COHERENT (0 finding(s))

verdict coherent · drifting 0 · roles 7

output, 25 line(s), 1.6s
vlmkit check design
source: index.html

verdict: COHERENT (0 finding(s))
  roles judged: 3 of 7 seen, spacing values: 24
  coverage: 21 of 288 visible element(s) carried an inferable role
    no role: span x49, li x38, a x37, div x25, p x25, code x22, +15 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  button           9 inst    2 styles  reuse   4.5x  1 one-off  ok
  h2               4 inst    1 styles  reuse     4x  0 one-off  ok
  h3               4 inst    1 styles  reuse     4x  0 one-off  ok
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  note             1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: search (1), input:search (1), h1 (1), note (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.
G26exit 0vlmkit verify flow index.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.1s
vlmkit verify flow
source: index.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar a[href='cli.html#exit-codes'] focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
G27exit 1vlmkit verify flow index.html --flow flows/copy.json

verdict: FAILED (0/1 steps)

done false · passed 0 · total 1

output, 8 line(s), 1.6s
vlmkit verify flow
source: index.html

verdict: FAILED (0/1 steps)

✗ step 1: Copy says Copied on the button and in the live region
    ✗ #install-quarry ~ .code-block .copy-button text~"Copied" (got: Copy)
    ✗ #announcer text~"Copied" (got: )
D19 defect flows/copy.json

The copy flow asserts 'Copied' in the same step as the click; a click goes through the asynchronous Clipboard API, and the probe shows the label and live region change ~20ms later (at +0 still 'Copy', at +20 'Copied'), so the flow fails on correct behaviour.

G28exit 0vlmkit check interactions index.html --max-elements 80

elements 47 · suspects 0 · warns 0

output, 53 line(s), 10.0s
vlmkit check interactions
source: index.html

status: ok
interactive elements: 47

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Troubleshooting" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Dry runs" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "How a migration runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Next Migrations" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Describe the source and target" focus✓
  - [link] "Review the plan" focus✓
  - [link] "Rehearse with a dry run" focus✓
  - [link] "Run the migration and cut over" focus✓
  - [link] "Next steps" focus✓
  - [link] "Back to top" focus✓
N9supersededabout D16

Misdiagnosis, recorded as such. G24 (after the fix) and G28 (after also swapping the .is-copied class for a data-copied attribute) still print 'Enter: focus moves within' on every Copy button. A Playwright probe of the site from my scratchpad shows document.activeElement is the same Copy button before the key and at +0/20/100/400/1500ms after Enter (and after a click). In this headless Chromium navigator.clipboard.writeText succeeds, so the textarea fallback I blamed never ran. The line is how check interactions describes a focused button that renames itself: see the tool note. The fallback rewrite (answer a copy event, no temporary field, no focus change) stays because it is still the right fallback where writeText is refused, but it is not what G15 reported, and there is no focus defect to verify.

N10toolabout G28

check interactions reports 'Enter: focus moves within' for a button whose own text changes on activation, although focus does not move. Minimal case in my scratchpad (tooltest/rename.html, two buttons, run outside the site): the button whose click handler sets textContent 'Copy' -> 'Copied' printed '[button] "Copy" focus✓ | Enter: focus moves within'; the one that only writes a role=status region printed '[button] "Save" focus✓ | Enter: layout change | announces'. The dynamic-markup skill defines 'focus moves within' as arrow keys moving focus inside a roving composite, so on a plain button it reads as a focus bug. It sent me after a non-existent focus-stealing defect (D16). Presumably the probe keys the focused element by role + accessible name, and a renamed control looks like a different element.

F19fix for D19

Edited flows/copy.json: the click step now only asserts focus stays on the button; a wait 300ms step asserts 'Copied' on the button and in #announcer; a final wait 2600ms step checks the label reverts to Copy.

G29exit 0vlmkit verify flow index.html --flow flows/copy.json

verdict: DONE (3/3 steps)

done true · passed 3 · total 3

output, 12 line(s), 4.5s
vlmkit verify flow
source: index.html

verdict: DONE (3/3 steps)

✓ step 1: Click Copy on the Homebrew install block
    ✓ #install-quarry ~ .code-block .copy-button focused 
✓ step 2: once the asynchronous clipboard write resolves, the button and the live region say Copied
    ✓ #install-quarry ~ .code-block .copy-button text~"Copied" 
    ✓ #announcer text~"Copied" 
✓ step 3: and the button goes back to Copy after 2.5 s
    ✓ #install-quarry ~ .code-block .copy-button text~"Copy"
V1verified D15 by G24

check interactions now names the pager link 'Next Migrations' (two words).

V2verified D17 by G26

drawer flow DONE 6/6: focus into the dialog, Shift+Tab/Tab wrap, Escape returns focus to Menu at once, drawer and scrim hidden after the slide-out.

V3verified D18 by G25

check design: 'No design drift detected', the scale-outlier line (15px, 18px, 22px) is gone.

V4verified D19 by G29

copy flow DONE 3/3: Copied on the button and in the live region after the clipboard write, back to Copy after 2.5 s.

S10

index.html · 1280×800 · full page, 6 screen(s)

S10 screen 1S10 screen 2S10 screen 3S10 screen 4S10 screen 5S10 screen 6
L10 saw

Desktop 1280, index.html after the fix pass, 6 screens. S10-1: the article now starts at x=360 and the header filter starts at x=360 too, so they still share a rail; the first body line "Quarry is a command-line tool for online database migrations. It copies" is 71 characters (was ~84). Lede is 3 lines. The contents column is 16rem: "Describe the source and target" and "Run the migration and cut over" now fit on one line each; 88px of air between article and contents. S10-2: the install-script line fits its block whole. S10-4: the plan output scans as a table: ROWS right-aligned under their header, three spaces before STRATEGY ("41.2 GB parallel, 8 ranges"). S10-6: footer reads as two deliberate lines, mark + tagline, then "Last updated 12 September 2026" starting under the tagline's first letter. PROBLEM, new: short orphan last lines in prose: the Note ends on "it." alone (S10-3), step 1's paragraph ends on "read." alone (S10-3). Code blocks, steps, callout and scroll-spy otherwise unchanged and correct.

S11

index.html · 768×1024 · full page, 6 screen(s)

S11 screen 1S11 screen 2S11 screen 3S11 screen 4S11 screen 5S11 screen 6
L11 saw

Tablet 768, index.html after the fix pass, 6 screens. S11-1: the 16rem sidebar fits "Rolling back a migration" on one line; nothing in the nav wraps now. Lede 3 lines. S11-2: the curl line fits whole ("| sh" visible). S11-3/S11-4: blocks that are still wider than the column (init command, plan output) fade out over their last ~3rem ("...internal", "STRATEGY" / "parallel," dissolving), which reads as "continues to the right" rather than a hard cut. S11-5/S11-6: the footer tagline breaks into two balanced lines ("Quarry moves data between / databases while both stay online.") with the mark on the first line and Last updated aligned under the text. PROBLEM: orphans again: "The CLI reference lists every command and / option." (S11-5), "explains every line / of it." (S11-4).

S12

index.html · 375×812 · full page, 11 screen(s)

S12 screen 1S12 screen 2S12 screen 3S12 screen 4S12 screen 5S12 screen 6S12 screen 7S12 screen 8S12 screen 9S12 screen 10S12 screen 11
L12 saw

Mobile 375, index.html after the fix pass, 11 screens. S12-1: lede down to 4 lines (was 6). S12-3/S12-4: install blocks fade at the right edge ("https://quarry.example/inst...", "2 September...") instead of being sliced. S12-5 to S12-8: steps now put the teal disc beside the h3 only; paragraphs and code use the full 335px column, the connecting hairline is gone on phones; inline code pills stay whole ("quarry init", "quarry plan", "quarry status --watch" never split). The nowrap pill costs one short line in S12-9 ("In a second terminal," then the pill on the next line), an acceptable trade. PROBLEM: orphans at this width too: "control." (S12-6), "copied." (S12-7), "A dry run changes / nothing." (S12-7). S12-11: footer mark sits on the first tagline line, Last updated aligned under the text.

D20 defect .doc p, .doc li, .callout p

Prose paragraphs end on single-word orphan lines at every width: 'nothing.', 'copied.', 'control.' (S12-6/7), 'option.' (S11-5), 'it.' and 'read.' (S10-3).

V5verified D1 by L10

First body line is 71 characters at 1280 (S10-1), header filter still on the article's left rail.

V6verified D2 by L10

Plan output columns align (S10-4).

V7verified D3 by L12

Lede 4 lines on mobile (S12-1), 3 on tablet and desktop (S11-1, S10-1).

V8verified D4 by L11

'Rolling back a migration' on one line at 768 (S11-1).

V9verified D5 by L11

Overflowing blocks fade at the continuing edge at 768 (S11-3/S11-4) and 375 (S12-3/S12-4).

V10verified D6 by L11

Balanced two-line tagline, mark on the first line, Last updated aligned (S11-5); same at 1280 (S10-6) and 375 (S12-11).

V11verified D8 by L12

Step content full width on phones, disc beside the h3 only (S12-5..S12-8).

F20fix for D20

text-wrap: pretty on .doc paragraphs, list items and the lede, so the browser rebalances the last lines of a paragraph instead of leaving one word alone (Chromium 117+, ignored elsewhere).

S13

troubleshooting + contents

migrations.html · 1280×800 · scroll-to #troubleshooting · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L13 saw

Desktop 1280, migrations.html scrolled to Troubleshooting. The h2 now sits ~20px above its first h3 "The copy stalls on one table" and ~52px below the Warning callout, so it clearly heads its group. Contents column: "Replication lag keeps growing" fits on one line and "Permission denied / on the source" breaks into two even lines (no lone word). "--apply-workers" stays one pill. The Warning callout's command pill takes its own line after "cut over with", acceptable. Contents marks "After cut-over" while the Troubleshooting heading is still mid-screen, consistent with the 30% reading line.

S14

inline flags in Copy section

migrations.html · 375×812 · scroll-to #copy · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L14 saw

Mobile 375, migrations.html scrolled to the Copy phase. "(--parallel, 4 by default)" and "--batch-size" are whole pills; the opening parenthesis travels with its pill onto the next line. The Plan paragraph ends on "same ranges." rather than a single word. Phase card 4 (Cut-over) visible at top, h3 spacing even, no overflow.

S15

inline flags in troubleshooting

migrations.html · 375×812 · scroll-to #cutover-times-out · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L15 saw

Mobile 375, migrations.html scrolled to "Cut-over times out". "--apply-workers" and "quarry status --verbose" are each one intact pill (the latter takes its own line, leaving "the transaction with" short above it, an acceptable rag). Headings, spacing and the 20px gutter are consistent; nothing clipped.

S16

command heading + run table

cli.html · 1280×800 · scroll-to #quarry-run · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L16 saw

Desktop 1280, cli.html around quarry run. The command heading "quarry run" is now plain bold monospace in the heading colour, no grey pill. The plan and run tables share their grid: Option starts at x=375, Default at x=593, Description at x=719 in both, so the reference reads as one column system. The narrower Description column wraps more ("Split each large table into / this many ranges."), fine. Sidebar marks quarry plan, contents marks quarry plan.

S17

page bottom: sidebar follows Exit codes

cli.html · 1280×800 · scroll-to .site-footer · wait 400

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L17 saw

Desktop 1280, cli.html scrolled to the footer. The sidebar has scrolled itself so its last item, "Exit codes", is fully visible and carries the teal bar (the Guides label has moved up out of view instead); contents also marks Exit codes. Exit-codes table, Previous card and two-line footer below. Page did not jump: only the sidebar column moved.

S18

env table on a phone

cli.html · 375×812 · scroll-to #environment-variables · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L18 saw

Mobile 375, cli.html at Environment variables. The table is now a stack of cards: QUARRY_SOURCE_PASSWORD, QUARRY_TARGET_PASSWORD, QUARRY_CONFIG in bold mono on top, the description under each at full card width; "--config" stays one pill. The intro paragraph breaks into three shorter lines ("Passwords are read from / the environment and never / from quarry.toml."): with the nowrap pill, greedy wrapping would have left the pill alone on the last line, so this is text-wrap: pretty avoiding that, not a defect. Option cards above unchanged.

S19

orphans in step 3

index.html · 375×812 · scroll-to #step-dry-run · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L19 saw

Mobile 375, index.html at step 3. Paragraphs end on two or more words: "is copied.", "changes nothing."; the step disc sits beside "Rehearse with a dry run" and the code block runs full width. The plan output above fades at its right edge. No orphans in this screen.

V12verified D7 by L15

Flags and multi-word codes stay whole on phones (S14, S15) and desktop (S13).

V13verified D9 by L13

Troubleshooting h2 ~20px above its first h3 (S13).

V14verified D10 by L13

Contents entries fit or split evenly (S13).

V15verified D11 by L16

Command headings are plain monospace (S16).

V16verified D12 by L16

plan and run tables share x=375/593/719 (S16).

V17verified D13 by L17

Sidebar scrolled itself to show the marked Exit codes (S17).

V18verified D14 by L18

Env table stacks into cards on phones (S18).

V19verified D20 by L19

No single-word last lines at 375 (S19, S14).

V20verified D16 by G29

Outcome check only, see N9: step 1 of the copy flow asserts the Copy button is still focused after the click (and G28's probe of every Copy button is focus-clean in the sense that matters: document.activeElement never leaves it). In headless Chromium writeText succeeds, so the copy-event fallback is not exercised by any gate here; it is kept because it is the correct fallback, not because a gate proved it.

N11supersededabout G6

Same check re-run as G10 with an absolute file:// URL (index.html?theme=light): 169 elements, 0 failures. G6 failed only on the path argument (see N7).

N12supersededabout G7

Same check re-run as G11 with an absolute file:// URL (index.html?theme=dark): 169 elements, 0 failures. G7 failed only on the path argument (see N7).

R3 · all pages, dark theme and states

builder
G30exit 0vlmkit check integrity migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.2s
vlmkit check integrity
source: migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 7.5%, 104 text block(s)
  768x900: 8 component(s), ink 10.4%, 88 text block(s)
  375x700: 8 component(s), ink 16.2%, 65 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
G31exit 0vlmkit check copy migrations.html --manifest copy.txt

missing 0 · placeholders 0 · manifestLines 13 · statesExplored 1 · revealedOnly 0

output, 9 line(s), 1.4s
vlmkit check copy
source: migrations.html

status: ok
rendered text: 5510 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 13 line(s), missing 0

No copy issues detected.
G32exit 0vlmkit check design migrations.html

verdict: DRIFT (2 finding(s))

verdict drift · drifting 1 · roles 7

output, 30 line(s), 1.6s
vlmkit check design
source: migrations.html

verdict: DRIFT (2 finding(s))
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule component-drift=suspect
  roles judged: 3 of 7 seen, spacing values: 25
  coverage: 23 of 258 visible element(s) carried an inferable role
    no role: a x39, li x38, span x37, p x26, code x22, div x15, +16 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  h3              10 inst    1 styles  reuse    10x  0 one-off  ok
  button           4 inst    2 styles  reuse     2x  1 one-off  drift
  h2               4 inst    1 styles  reuse     4x  0 one-off  ok
  note             2 inst    2 styles  reuse     1x  2 one-off  not judged
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: note (2), search (1), input:search (1), h1 (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).

Findings
  ! [component-drift] button: 4 "button" elements render 2 distinct styles (used 3x, 1x; a system reuses each style 3x or more, and this role averages 2x). Dominant style, used 3x: padding 0/10/0/10, radius 6, 13px/500, border 1, bg rgba(0, 0, 0, 0). Deviating: div.header-inner>div.header-actions>button.icon-button (padding 0/0/0/0, radius 8, no painted text, border 1, bg rgb(255, 255, 255)) — differs in padding-right 10 → 0, padding-left 10 → 0, border-radius 6 → 8 and 1 more. This reports inconsistency, not which style is correct.

Informational (true, but does not carry the verdict)
  i [scale-outlier]: 1 spacing value(s) sit just off the page's own scale: 40px (1x) next to 36px (12x) — div.layout>footer.site-footer>div.footer-inner marginTop. Snap them to the established value or add them deliberately.
D22 defect footer .footer-inner margin-top

check design scale-outlier on migrations.html: the footer's 40px top margin is the only 40px on a page whose steps are 36px; snap it.

G33exit 0vlmkit check composition migrations.html

verdict: COMPOSED (0 finding(s))

verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0

output, 17 line(s), 1.7s
vlmkit check composition
source: migrations.html

verdict: COMPOSED (0 finding(s))
  measured: 14 label(s), 3 heading level(s), 59 wide block(s) on 5 left / 5 right rail(s) — from 228 visible box(es)
  1 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     36px / 700   main#content>article.doc>h1
  h2     24px / 700   h2#how-a-migration-runs
  h3     18px / 600   h3#plan
  body 14px, largest 36px (2.57x), heaviest +300 weight

Group gaps: 36px between heading-led groups vs 4px inside them (9x, 15 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.
G34exit 0vlmkit check color migrations.html

verdict: CONSISTENT (0 finding(s))

verdict consistent · base #ffffff · bodyInk #1b1f24 · linkInk #4b5563 · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0

output, 27 line(s), 1.6s
vlmkit check color
source: migrations.html

verdict: CONSISTENT (0 finding(s))
  measured: 1 control(s), 39 link(s) in a text flow — from 243 visible box(es)

Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
  surfaces  4 distinct
    #ffffff   90.4%     5 el  
    #f7f8fa    7.3%    12 el  div.header-inner>div.header-brand>span.version
    #fff7e0    1.5%     1 el  main#content>article.doc>div.callout
    #f0f2f4    0.9%    19 el  article.doc>p>code
  ink       5 distinct
    #1b1f24   75.3%    72 el  div.header-brand>a.brand>span.brand-name
    #4b5563   19.8%    39 el  div.header-inner>div.header-brand>span.version
    #0b5f58    2.1%     7 el  a.skip-link
    #646c78    2.1%    15 el  div.header-search>div.search>kbd.search-kbd
    #0f766e    0.7%     7 el  main#content>article.doc>p.eyebrow
  marks     6 distinct
    #e3e6ea   82.0%   125 el  header.site-header
    #0f766e    5.2%     7 el  a.skip-link
    #e2cfa6    5.2%     3 el  main#content>article.doc>div.callout
    #858e99    3.1%     4 el  input#search-header
    #d0d5db    2.6%    20 el  div.header-inner>div.header-brand>span.version
    …and 1 more
  base #ffffff, body ink #1b1f24, link ink #4b5563 on 33 element(s)
G35exit 1vlmkit check integrity cli.html

verdict: DEFECTS (12 fail, 10 warn, 33 exempted)

verdict defects · findings 22 · fails 12 · warns 10 · exempted 33 · viewports 3

output, 49 line(s), 6.0s
vlmkit check integrity
source: cli.html

verdict: DEFECTS (12 fail, 10 warn, 33 exempted)
  1280x800: 8 component(s), ink 3.9%, 247 text block(s)
  768x900: 8 component(s), ink 5.4%, 235 text block(s)
  375x700: 8 component(s), ink 9.6%, 210 text block(s)

Findings:
  x [container-protrusion] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) out of article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) @375: article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) sticks out 14px (horizontal) past its painted parent article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) — the child is wider/taller than the container allows; shrink it, let it wrap, or make the overflow an authored overlay (position + z-index).
  x [container-protrusion] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) out of article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) @375: article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) sticks out 80px (horizontal) past its painted parent article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) — the child is wider/taller than the container allows; shrink it, let it wrap, or make the overflow an authored overlay (position + z-index).
  x [container-protrusion] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) out of article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) @375: article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) sticks out 14px (horizontal) past its painted parent article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) — the child is wider/taller than the container allows; shrink it, let it wrap, or make the overflow an authored overlay (position + z-index).
  x [container-protrusion] (text) out of article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: Text inside article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) sticks out 35px past its painted box (overflow is visible) — a long word or fixed width; allow wrapping (overflow-wrap) or widen the box.
  x [container-protrusion] (text) out of article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) @375: Text inside article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) sticks out 70px past its painted box (overflow is visible) — a long word or fixed width; allow wrapping (overflow-wrap) or widen the box.
  x [container-protrusion] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) out of table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) @375: table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) sticks out 14px (horizontal) past its painted parent table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) — the child is wider/taller than the container allows; shrink it, let it wrap, or make the overflow an authored overlay (position + z-index).
  x [container-protrusion] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) out of table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) @375: table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) sticks out 88px (horizontal) past its painted parent table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) — the child is wider/taller than the container allows; shrink it, let it wrap, or make the overflow an authored overlay (position + z-index).
  x [container-protrusion] (text) out of table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: Text inside table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) sticks out 46px past its painted box (overflow is visible) — a long word or fixed width; allow wrapping (overflow-wrap) or widen the box.
  x [container-protrusion] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) out of article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) @375: article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) sticks out 14px (horizontal) past its painted parent article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) — the child is wider/taller than the container allows; shrink it, let it wrap, or make the overflow an authored overlay (position + z-index).
  x [container-protrusion] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) out of article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) @375: article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) sticks out 80px (horizontal) past its painted parent article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) — the child is wider/taller than the container allows; shrink it, let it wrap, or make the overflow an authored overlay (position + z-index).
  x [container-protrusion] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) out of article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) @375: article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) sticks out 14px (horizontal) past its painted parent article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) — the child is wider/taller than the container allows; shrink it, let it wrap, or make the overflow an authored overlay (position + z-index).
  x [container-protrusion] (text) out of article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: Text inside article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) sticks out 35px past its painted box (overflow is visible) — a long word or fixed width; allow wrapping (overflow-wrap) or widen the box.
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] table.plain.stack > thead:nth-of-type(1) @375: table.plain.stack > thead:nth-of-type(1) clips 116px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) clips 112px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) clips 112px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) clips 112px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) clips 112px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) clips 112px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) clips 112px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) clips 112px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) clips 112px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) @375: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) @375: clipped away by article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  … 18 more
D21 defect cli.html table.stack thead @375

12 container-protrusion fails at 375: the stacked tables' header row is clipped to a 1px box, but the card rule '.doc table.stack tr' also makes that header tr a bordered grid card, so its th cells stick out 14-88px past their own painted row. Invisible, but the hidden header has broken geometry.

G36exit 0vlmkit check copy cli.html --manifest copy.txt

missing 0 · placeholders 0 · manifestLines 13 · statesExplored 1 · revealedOnly 0

output, 9 line(s), 1.5s
vlmkit check copy
source: cli.html

status: ok
rendered text: 5220 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 13 line(s), missing 0

No copy issues detected.
G37exit 0vlmkit check design cli.html

verdict: COHERENT (0 finding(s))

verdict coherent · drifting 0 · roles 11

output, 28 line(s), 1.8s
vlmkit check design
source: cli.html

verdict: COHERENT (0 finding(s))
  roles judged: 7 of 11 seen, spacing values: 26
  coverage: 228 of 651 visible element(s) carried an inferable role
    no role: span x93, code x85, div x35, a x34, li x30, col x24, +22 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  cell           107 inst    2 styles  reuse  53.5x  0 one-off  ok
  row             46 inst    1 styles  reuse    46x  0 one-off  ok
  columnheader    26 inst    2 styles  reuse    13x  0 one-off  ok
  rowgroup        18 inst    1 styles  reuse    18x  0 one-off  ok
  button           9 inst    2 styles  reuse   4.5x  1 one-off  ok
  h2               9 inst    1 styles  reuse     9x  0 one-off  ok
  table            9 inst    1 styles  reuse     9x  0 one-off  ok
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: search (1), input:search (1), h1 (1), h3 (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.
G38exit 0vlmkit check composition cli.html

verdict: COMPOSED (0 finding(s))

verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0

output, 17 line(s), 1.7s
vlmkit check composition
source: cli.html

verdict: COMPOSED (0 finding(s))
  measured: 10 label(s), 3 heading level(s), 176 wide block(s) on 5 left / 4 right rail(s) — from 612 visible box(es)
  1 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     36px / 700   main#content>article.doc>h1
  h2     24px / 700   h2#global-options
  h3     18px / 600   h3#environment-variables
  body 14px, largest 36px (2.57x), heaviest +300 weight

Group gaps: 52px between heading-led groups vs 0px inside them (0x, 11 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.
G39exit 0vlmkit check color cli.html

verdict: CONSISTENT (0 finding(s))

verdict consistent · base #ffffff · bodyInk #1b1f24 · linkInk #4b5563 · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0

output, 23 line(s), 1.6s
vlmkit check color
source: cli.html

verdict: CONSISTENT (0 finding(s))
  measured: 1 control(s), 34 link(s) in a text flow — from 629 visible box(es)

Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
  surfaces  2 distinct
    #ffffff   92.1%     5 el  
    #f7f8fa    7.9%    38 el  div.header-inner>div.header-brand>span.version
  ink       5 distinct
    #1b1f24   60.3%   167 el  div.header-brand>a.brand>span.brand-name
    #4b5563   27.9%    95 el  div.header-inner>div.header-brand>span.version
    #646c78    9.5%    27 el  div.header-search>div.search>kbd.search-kbd
    #0b5f58    1.7%     5 el  a.skip-link
    #0f766e    0.6%     3 el  main#content>article.doc>p.eyebrow
  marks     4 distinct
    #e3e6ea   96.8%   278 el  header.site-header
    #0f766e    1.5%     6 el  a.skip-link
    #858e99    1.1%     4 el  input#search-header
    #d0d5db    0.6%    12 el  div.header-inner>div.header-brand>span.version
  base #ffffff, body ink #1b1f24, link ink #4b5563 on 34 element(s)
N13acceptedabout G32

component-drift on migrations.html: 4 buttons in 2 styles, 3 ghost Copy buttons (radius 6, transparent border, text) against the header theme toggle (36px square, radius 8, bordered, icon only). They are two components, not a drifted one: the toggle shares its style with the drawer's close button and the Menu button, which only exist below 768px, so at 1280 it looks like a lone variant. index.html (8 Copy buttons) and cli.html (8) average above 3x and pass. In the final round this run carries --allow for the toggle with that reason instead of a rule change.

F21fix for D21

Card styling is scoped to 'table.stack tbody tr' (and 'tbody tr + tr'), so the clipped header row stays a plain table row; the thead gets the full visually-hidden recipe (1px, margin -1px, no padding or border, clip-path inset(50%)).

F22fix for D22

Footer top margin 2.5rem -> 2.25rem (36px), on the page's own step.

G40exit 0vlmkit check integrity cli.html

verdict: NO DEFECTS, 10 WARN (0 fail, 10 warn, 30 exempted)

verdict clean · findings 10 · fails 0 · warns 10 · exempted 30 · viewports 3

output, 38 line(s), 5.9s
vlmkit check integrity
source: cli.html

verdict: NO DEFECTS, 10 WARN (0 fail, 10 warn, 30 exempted)
  exits 0 — 10 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 3.9%, 247 text block(s)
  768x900: 8 component(s), ink 5.4%, 235 text block(s)
  375x700: 8 component(s), ink 9.6%, 210 text block(s)

Findings:
  ! [clipped-content] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) @375: article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] table.plain.stack > thead:nth-of-type(1) @375: table.plain.stack > thead:nth-of-type(1) clips 177px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) @375: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @375: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(3) @375: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @375: clipped away by article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  … 15 more
V21verified D21 by G40

cli.html integrity: 0 fail (was 12 container-protrusion fails); only clipped-content warns on the visually-hidden header rows and the drawer label remain.

G41exit 0vlmkit check design migrations.html --allow 'div.header-inner>div.header-actions>button.icon-button;the theme toggle is its own icon-button component (square, bordered, no text) shared with the mobile Menu and drawer close buttons, not a variant of the ghost Copy button'

verdict: COHERENT (0 finding(s))

verdict coherent · drifting 0 · roles 7

output, 27 line(s), 1.7s
vlmkit check design
source: migrations.html

verdict: COHERENT (0 finding(s))
  roles judged: 3 of 7 seen, spacing values: 24
  coverage: 23 of 258 visible element(s) carried an inferable role
    no role: a x39, li x38, span x37, p x26, code x22, div x15, +16 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  h3              10 inst    1 styles  reuse    10x  0 one-off  ok
  button           3 inst    1 styles  reuse     3x  0 one-off  ok
  h2               4 inst    1 styles  reuse     4x  0 one-off  ok
  note             2 inst    2 styles  reuse     1x  2 one-off  not judged
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: note (2), search (1), input:search (1), h1 (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).

allowed: 1 button instance(s) declared deliberate and left out of the reuse figure

No design drift detected.
V22verified D22 by G41

migrations.html design run: no scale-outlier line any more (G32 had the 40px footer margin).

G42exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/migrations.html?theme=light'

inspected 168 · failures 0

output, 5 line(s), 1.7s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/migrations.html?theme=light
  inspected 168 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/migrations-f0596757/report.md
G43exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/migrations.html?theme=dark'

inspected 168 · failures 0

output, 5 line(s), 1.5s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/migrations.html?theme=dark
  inspected 168 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/migrations-e7fbdbb0/report.md
G44exit 0vlmkit check a11y touch migrations.html

level AA · inspected 44 · failures 0 · wcagExempt 0

output, 5 line(s), 1.5s
  vlmkit check a11y touch
  source: migrations.html  level: WCAG AA (24×24 min)
  inspected 44 interactive element(s)
  ✓ 0 undersized target(s)
  report: examples/sites/docs/test-results/a11y-touch/migrations-6dd1b9b0/report.md
G45exit 0vlmkit check a11y focus migrations.html

steps 44 · findings 3

output, 10 line(s), 1.7s
  vlmkit check a11y focus
  source: migrations.html
  captured 44 focus step(s)
  ! 3 finding(s)
    38 focusable element(s) are viewport-pinned (fixed / sticky); jumps into or out of them are not read as order defects
    [skip-row] Focus jumped down by 903px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 399px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 1067px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `div.layout>main#content>article.doc>nav.pager>a.pager-link.pager-prev`). Confirm no focusable element was unintentionally skipped.
  report: examples/sites/docs/test-results/a11y-focus-order/migrations-6dd1b9b0/report.md
  exits 0 — 3 warn(s) did not fail this command. To gate on one: --rule skip-row=suspect
G46exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/cli.html?theme=light'

inspected 314 · failures 0

output, 5 line(s), 1.6s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/cli.html?theme=light
  inspected 314 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/cli-6393d62b/report.md
G47exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/cli.html?theme=dark'

inspected 314 · failures 0

output, 5 line(s), 1.6s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/cli.html?theme=dark
  inspected 314 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/cli-594ae7e3/report.md
G48exit 0vlmkit check a11y touch cli.html

level AA · inspected 44 · failures 0 · wcagExempt 0

output, 5 line(s), 1.5s
  vlmkit check a11y touch
  source: cli.html  level: WCAG AA (24×24 min)
  inspected 44 interactive element(s)
  ✓ 0 undersized target(s)
  report: examples/sites/docs/test-results/a11y-touch/cli-0459c88f/report.md
G49exit 0vlmkit check a11y focus cli.html

steps 44 · findings 8

output, 13 line(s), 1.7s
  vlmkit check a11y focus
  source: cli.html
  captured 44 focus step(s)
  ! 8 finding(s)
    34 focusable element(s) are viewport-pinned (fixed / sticky); jumps into or out of them are not read as order defects
    [skip-row] Focus jumped down by 1193px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 613px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 549px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 893px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 545px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    … 3 more (see the report, or --json for all)
  report: examples/sites/docs/test-results/a11y-focus-order/cli-0459c88f/report.md
  exits 0 — 8 warn(s) did not fail this command. To gate on one: --rule skip-row=suspect
G50exit 0vlmkit scan scroll migrations.html

containers 1 · overflowX 0 · issues 1

output, 21 line(s), 1.4s
vlmkit scan scroll
source: migrations.html (1280x720)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x4248 — horizontal overflow 0px, vertical scroll 3528px
scroll containers: 1 (dead scrollports 4, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(0,60) 272x660

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1) (overflow: auto auto)
  - nav.toc (overflow: auto auto)

Issues:
  ! clipped-content div.header-search > div:nth-of-type(1) > label:nth-of-type(1): div.header-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 1 expectedScrollports entry ready for a UI Contract.
G51exit 0vlmkit scan scroll migrations.html --viewport 375x740

containers 2 · overflowX 0 · issues 1

output, 21 line(s), 1.4s
vlmkit scan scroll
source: migrations.html (375x740)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 375x5713 — horizontal overflow 0px, vertical scroll 4973px
scroll containers: 2 (dead scrollports 3, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(-320,0) 320x740
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1): axis=x overflow=136px box=(21,2389) 333x273

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(3) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1) (overflow: auto auto)
  - nav.toc (overflow: auto auto)

Issues:
  ! clipped-content div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1): div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 2 expectedScrollports entries ready for a UI Contract.
G52exit 0vlmkit scan handlers migrations.html

registrations 25 · elements 12 · suspects 0

output, 23 line(s), 1.6s
vlmkit scan handlers
source: migrations.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: 25 across 12 element(s) + globals, on a page presenting 44 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "$ quarry run --dry-run Dry run for shop ": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry rollback": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry rollback --yes": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:scroll, window:resize×2, window:hashchange

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G53exit 0vlmkit check interactions migrations.html --max-elements 80

elements 44 · suspects 0 · warns 0

output, 50 line(s), 5.5s
vlmkit check interactions
source: migrations.html

status: ok
interactive elements: 44

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Previous Getting started" focus✓
  - [link] "Next CLI reference" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Plan" focus✓
  - [link] "Copy" focus✓
  - [link] "Catch-up" focus✓
  - [link] "Cut-over" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Before cut-over" focus✓
  - [link] "After cut-over" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "The copy stalls on one table" focus✓
  - [link] "Replication lag keeps growing" focus✓
  - [link] "Cut-over times out" focus✓
  - [link] "Permission denied on the source" focus✓
  - [link] "Back to top" focus✓
G54exit 0vlmkit check breakpoints migrations.html --sweep

breakpoints 2 · issues 0 · sweepRanges 0

output, 11 line(s), 3.2s
vlmkit check breakpoints
source: migrations.html

status: ok
width sweep: 320-1300px step 25 (40 widths) — clean
breakpoints checked: 768, 1200px (each at B-1 / B / B+1)

  768px: clean (max-width: 768px)
  1200px: clean (max-width: 1200px)

All boundaries consistent.
G55exit 0vlmkit check scroll migrations.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.4s
vlmkit check scroll
source: migrations.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G56exit 0vlmkit scan scroll cli.html

containers 1 · overflowX 0 · issues 1

output, 27 line(s), 1.3s
vlmkit scan scroll
source: cli.html (1280x720)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x6446 — horizontal overflow 0px, vertical scroll 5726px
scroll containers: 1 (dead scrollports 19, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(0,60) 272x660

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(2) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) (overflow: auto auto)
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(5) (overflow: auto auto)
  - article.doc > div:nth-of-type(6) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(7) (overflow: auto auto)
  - article.doc > div:nth-of-type(8) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(9) (overflow: auto auto)
  - article.doc > div:nth-of-type(10) > pre:nth-of-type(1) (overflow: auto auto)

Issues:
  ! clipped-content div.header-search > div:nth-of-type(1) > label:nth-of-type(1): div.header-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 1 expectedScrollports entry ready for a UI Contract.
G57exit 0vlmkit scan scroll cli.html --viewport 375x740

containers 3 · overflowX 0 · issues 10

output, 38 line(s), 1.4s
vlmkit scan scroll
source: cli.html (375x740)

status: warn
  exits 0 — 10 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 375x7429 — horizontal overflow 0px, vertical scroll 6689px
scroll containers: 3 (dead scrollports 17, clipped 10)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(-320,0) 320x740
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1): axis=x overflow=97px box=(21,1813) 333x52
  - article.doc > div:nth-of-type(10) > pre:nth-of-type(1): axis=x overflow=66px box=(21,4251) 333x140

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(2) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) (overflow: auto auto)
  - article.doc > div:nth-of-type(5) (overflow: auto auto)
  - article.doc > div:nth-of-type(6) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(7) (overflow: auto auto)
  - article.doc > div:nth-of-type(8) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(9) (overflow: auto auto)
  - article.doc > div:nth-of-type(11) (overflow: auto auto)
  - article.doc > div:nth-of-type(12) > pre:nth-of-type(1) (overflow: auto auto)

Issues:
  ! clipped-content article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1): div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content table.plain.stack > thead:nth-of-type(1): table.plain.stack > thead:nth-of-type(1) clips 177px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 3 expectedScrollports entries ready for a UI Contract.
G58exit 0vlmkit scan handlers cli.html

registrations 35 · elements 22 · suspects 0

output, 33 line(s), 1.7s
vlmkit scan handlers
source: cli.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: 35 across 22 element(s) + globals, on a page presenting 44 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "quarry <command> [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry init --source <url> --target <url": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry plan [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry run [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry status shop · catch-up copied 7": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry cutover [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry rollback [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry verify [options]": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:scroll, window:resize×2, window:hashchange

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G59exit 0vlmkit check interactions cli.html --max-elements 80

elements 44 · suspects 0 · warns 0

output, 50 line(s), 10.2s
vlmkit check interactions
source: cli.html

status: ok
interactive elements: 44

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Previous Migrations" focus✓
  - [link] "Global options" focus✓
  - [link] "Environment variables" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [link] "Back to top" focus✓
G60exit 0vlmkit check breakpoints cli.html --sweep

breakpoints 2 · issues 0 · sweepRanges 0

output, 11 line(s), 3.4s
vlmkit check breakpoints
source: cli.html

status: ok
width sweep: 320-1300px step 25 (40 widths) — clean
breakpoints checked: 768, 1200px (each at B-1 / B / B+1)

  768px: clean (max-width: 768px)
  1200px: clean (max-width: 1200px)

All boundaries consistent.
G61exit 0vlmkit check scroll cli.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.4s
vlmkit check scroll
source: cli.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
S20

sidebar filtered: roll

index.html · 1280×800 · fill #search-header "roll" · wait 500

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L20 saw

Desktop 1280, index.html with "roll" typed in the header filter. The sidebar shows only what matches: Guides > Migrations > "Rolling back a migration" with "Roll" in a teal-tinted mark, Reference > CLI reference > "quarry rollback" with "roll" marked; Getting started and every other section are gone, page titles stay as context for their matching sections. The field has the teal focus ring and a teal border; the "/" hint is gone while it has text. PROBLEM: Chrome's native clear button draws a bold navy "x" at the right of the field, the only colour on the page that belongs to neither the greys nor the teal.

D23 defect .search-input::-webkit-search-cancel-button

The native search clear button is a bold navy x (S20, S21, S22): the only off-palette colour on the page, and heavier than the magnifier icon on the other side of the field.

S21

filter with no match

index.html · 1280×800 · fill #search-header "zebra" · wait 500

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L21 saw

Desktop 1280, index.html with "zebra" in the filter. The sidebar empties and says 'No pages or sections match "zebra".' in secondary grey, aligned with the page links' text edge, with curly quotes; group labels are hidden too, so nothing dangles. Same off-palette navy clear "x" in the field. Article and contents untouched.

S22

drawer open, filtered: dry

index.html · 375×812 · click .menu-button · wait 400 · fill #search-drawer "dry" · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L22 saw

Mobile 375, drawer opened with Menu, then "dry" typed in the drawer's own filter field. The drawer shows Guides > Migrations > "Dry runs" with "Dry" marked; nothing else. The field has the teal focus ring; the scrim dims the page on the right. The sidebar filter is the same component as on desktop, so behaviour matches. Same navy clear "x" as S20.

S23

Copied confirmation

index.html · 1280×800 · scroll-to #install-quarry · click #install-quarry ~ .code-block .copy-button · wait 150

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L23 saw

Desktop 1280, index.html after clicking Copy on the Homebrew block. That block's button now reads "Copied" with a check icon in teal (its border shows because the pointer is still over it); the next block's button still reads "Copy", so the confirmation is clearly tied to the block that was copied. Nothing else on the page moved.

S24

keyboard focus on the first sidebar link

index.html · 1280×800 · press Tab · press Tab · press Tab · 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.

L24 saw

Desktop 1280, index.html after five Tab presses (skip link, brand, filter, theme toggle, then the first sidebar link). "Getting started" carries a clear 2px teal focus ring offset from its teal pill, visible against both the white page and the tint. Keyboard focus is obvious at a glance.

S25

contents highlights Dry runs

migrations.html · 1280×800 · scroll-to #dry-runs · wait 500

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L25 saw

Desktop 1280, migrations.html scrolled so the "Dry runs" heading sits mid-screen. Contents marks "Cut-over": the heading the reader has passed above the 30% reading line, a section other than the first, and the sidebar marks its parent "How a migration runs". Dry runs itself becomes current once its heading passes that line, as G23 showed after following its contents link. Correct; for the final state shot I will land the heading at the top.

F23fix for D23

The WebKit search cancel button is redrawn: appearance none, a 1rem mask of a 1.5px-stroke x (the magnifier's weight) painted in --text-3, darkening to --text on hover, so it follows both themes.

S26

filter: migr, redrawn clear button

index.html · 1280×800 · fill #search-header "migr" · wait 400

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L26 saw

Desktop 1280, index.html, "migr" in the filter. The clear button is now a thin grey x drawn at the magnifier's stroke weight, part of the field rather than stuck on it. Sidebar: Getting started (current-page pill) keeps only "Your first migration"; the Migrations page title itself matches ("Migr" marked on the heading) and shows its two matching sections "How a migration runs" and "Rolling back a migration". Marks are legible, text colour unchanged under them.

S27

dark: filter cut

index.html?theme=dark · 1280×800 · fill #search-header "cut" · wait 400

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L27 saw

Desktop 1280, index.html?theme=dark with "cut" in the filter. Dark header, sun toggle. The clear x is a light grey stroke on the dark field and the focus ring is the light teal; the only match, Reference > CLI reference > "quarry cutover", has "cut" in a dark-teal mark that still reads. Body text, lede grey and contents highlight all hold their contrast in dark.

V23verified D23 by L26

Clear button is a thin grey x matching the magnifier in light (S26) and dark (S27).

S28

dark: dry-run output, Note

migrations.html?theme=dark · 1280×800 · scroll-to #dry-runs · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L28 saw

Desktop 1280, migrations.html?theme=dark around Dry runs. Code block on a slightly raised dark slate with a hairline border and header rule; the output's check marks are light teal, command bold off-white, flag teal, output text a softer grey; all legible without glare. Inline code pills are a lighter slate with a thin border. The "A dry run changes nothing." lead-in is bold off-white. Sidebar current page pill is a dark teal with light-teal text; contents marks Cut-over in teal. Hierarchy identical to light.

S29

dark: Warning callout

migrations.html?theme=dark · 1280×800 · scroll-to #after-cut-over · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L29 saw

Desktop 1280, migrations.html?theme=dark around Rolling back. The Warning callout becomes a deep amber-brown tint with an amber left rule and an amber "Warning" title and triangle: clearly a warning, not neon. Ctrl + C keycaps read as keys (lighter slate, heavier bottom border). Code blocks and the --keep-target pill hold. Contents marks "Before cut-over", sidebar marks "Rolling back a migration".

S30

dark: option tables

cli.html?theme=dark · 1280×800 · scroll-to #quarry-run · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L30 saw

Desktop 1280, cli.html?theme=dark around quarry plan/run. Tables keep their structure in dark: header row on the raised slate with grey labels, hairline row dividers, bold off-white option names, grey defaults, off-white descriptions; the shared column grid (Default at x=593) holds. Command heading "quarry run" plain bold mono. Usage block legible. Nothing low-contrast.

S31

dark: drawer

index.html?theme=dark · 375×812 · click .menu-button · wait 400

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L31 saw

Mobile 375, index.html?theme=dark with the drawer open. The drawer is the page's near-black with a hairline right edge and shadow; the scrim darkens the page behind it further, so the drawer still reads as the top layer. Head "Documentation" and bordered close button, the filter field with its grey outline, current page in a dark teal pill with light-teal text and its first section barred. The sun toggle behind the scrim is dimmed. The drawer and page are close in value: the separation leans on the scrim, but it holds.

S32

tablet: phase cards

migrations.html · 768×1024 · scroll-to .phases · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L32 saw

Tablet 768, migrations.html at the phase cards. PROBLEM: the four cards still sit in one row inside a 432px article, so each is ~93px wide: the names break at their hyphens ("Catch- / up", "Cut- / over") and the one-line descriptions stack one or two words per line ("Bulk- / copy / rows in / parallel / ranges"), five to six lines tall; the chevrons are squeezed into 20px gaps. It reads as broken, not designed. Everything else on the screen (lede, sidebar, prose) is fine at this width. No gate flagged it: nothing overflows, nothing is clipped.

D24 defect migrations.html .phases @768-~1000

At tablet widths the four phase cards stay in one row inside a ~432px article: ~93px cards, names broken at their hyphens ('Catch- / up', 'Cut- / over'), descriptions one or two words per line. No gate sees it.

S33

tablet: option tables

cli.html · 768×1024 · scroll-to #quarry-cutover · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L33 saw

Tablet 768, cli.html around quarry status / cutover. The option tables keep their three columns inside 432px: Option ~164px (option names break cleanly between flag and placeholder, "--timeout / <duration>"), Default ~95px ("from / config" breaks), Description ~160px, so every description runs 3-4 short lines ("How long to wait for / the lag to reach / zero before giving / up."). PROBLEM: the table is legible but cramped, and the card layout that phones get would read far better at this width.

D25 defect cli.html table.options @768-~1000

At tablet widths the option tables squeeze their descriptions into ~160px (3-4 lines each, 'from / config' broken) while the phone card layout, which would fit, only starts below 768px.

S34

wide screen 1600

index.html · 1600×900

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L34 saw

Wide 1600x900, index.html. The layout is capped at 1440px and centred: 80px margins, header brand/filter/toggle sit on the same rails as the sidebar, article and contents below them. The article stays 576px, centred in its column with ~168px of air on each side, so the page does not stretch into a wall of text; the contents column sits at the right edge of the cap. Balanced; nothing to fix.

S35

OS dark preference, no parameter

index.html · 1280×800 · dark

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L35 saw

Desktop 1280, index.html with the OS preference emulated as dark and no ?theme parameter. The page renders in the dark palette from prefers-color-scheme alone, and the toggle shows the sun with its pressed state, so the script reads the OS preference correctly when nothing is stored or forced. Same result as the forced ?theme=dark in S9.

F24fix for D24

The article is a size container ('.doc { container: doc / inline-size }'). Phase cards are 4 across when the article is at least 34rem wide, 2 x 2 below that (card 3, which starts the second row, drops its chevron), and a single column below 24rem. So 768px tablets get 2 x 2 and phones the vertical stack, whatever the viewport breakpoint.

F25fix for D25

The stacked-card rules for table.stack moved from the 767px media query into '@container doc (max-width: 34rem)', so option and environment tables become cards whenever the article is under 544px (tablets up to ~880px as well as phones), and stay tables where they have room.

S36

desktop: phase cards at the 36rem measure

migrations.html · 1280×800 · scroll-to .phases · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L36 saw

Desktop 1280, migrations.html at the phase cards, now inside the 576px measure. Four cards of ~129px in one row, equal heights, chevrons centred in the 20px gaps; names on one line each ("Catch-up", "Cut-over" whole). The descriptions run three to four short lines ("Compare / schemas / and size / every table"), taller than I would like, but even across the row and still reading as a four-step sequence at a glance. Acceptable at this width; the 2 x 2 is reserved for narrower articles.

S37

tablet: phase cards 2x2

migrations.html · 768×1024 · scroll-to .phases · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L37 saw

Tablet 768, migrations.html at the phase cards. Now a 2 x 2 grid in the 432px article: Plan > Copy on the first row, Catch-up > Cut-over on the second, chevrons only between the two cards of a row (card 3, which starts the second row, has none), numbers 1-4 carry the order across the break. Names whole, descriptions on two lines each. Reads as designed, where S32 read as broken.

S38

mobile: phase stack

migrations.html · 375×812 · scroll-to .phases · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L38 saw

Mobile 375, migrations.html at the phase cards. Single column of four cards with a small down chevron between each pair at the left, one-line descriptions ("Compare schemas and size every table"): same as before the container change, so the phone layout did not regress.

S39

tablet: option cards

cli.html · 768×1024 · scroll-to #quarry-cutover · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L39 saw

Tablet 768, cli.html around quarry status / cutover. Option tables are now cards at this width too: "--timeout <duration>" with "Default 30s" on the same line at the right, the description underneath at the full 400px, so each description takes one or two lines instead of four; "Default from config" no longer breaks. Terminal sample and usage block unchanged.

V24verified D24 by L37

2 x 2 phases at 768 (S37), 4 across at desktop (S36), stack on phones (S38).

V25verified D25 by L39

Option tables are cards at 768 (S39).

G62exit 0vlmkit check breakpoints cli.html --sweep

breakpoints 2 · issues 0 · sweepRanges 0

output, 11 line(s), 4.8s
vlmkit check breakpoints
source: cli.html

status: ok
width sweep: 320-1300px step 25 (40 widths) — clean
breakpoints checked: 768, 1200px (each at B-1 / B / B+1)

  768px: clean (max-width: 768px)
  1200px: clean (max-width: 1200px)

All boundaries consistent.
G63exit 0vlmkit check integrity migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 7.6s
vlmkit check integrity
source: migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 7.5%, 104 text block(s)
  768x900: 8 component(s), ink 10.3%, 88 text block(s)
  375x700: 8 component(s), ink 16.3%, 65 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
N14toolabout G62

check breakpoints discovers only media-query boundaries ('breakpoints checked: 768, 1200px'). The phase cards and option tables switch shape on a container query on the article (34rem, reached at a ~880px viewport, and 24rem), which is not in its list, so no B-1/B/B+1 comparison runs there; the width sweep (step 25) only looks for overflow. That is the same blind spot that let D24 (four ~93px phase cards at 768) and D25 through clean breakpoint runs: nothing overflowed, it was just cramped. Both were found by eye.

G64exit 0vlmkit verify flow index.html --flow flows/filter.json

verdict: DONE (7/7 steps)

done true · passed 7 · total 7

output, 25 line(s), 2.5s
vlmkit verify flow
source: index.html

verdict: DONE (7/7 steps)

✓ step 1: / from anywhere on the page jumps to the filter field
    ✓ #search-header focused 
✓ step 2: Typing filters the sidebar to matching page and section titles
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=2 
    ✓ #sidebar-nav mark count=2 
    ✓ #sidebar-nav a[href='migrations.html#rolling-back-a-migration'] visible 
    ✓ #sidebar-nav a[href='cli.html#quarry-rollback'] visible 
    ✓ #sidebar-nav a[href='#install-quarry'] hidden 
✓ step 3: A term that matches nothing says so
    ✓ .nav-empty visible 
    ✓ .nav-empty text~"zebra" 
✓ step 4: Escape clears the filter and every section comes back
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=17 
    ✓ .nav-empty hidden 
✓ step 5: Enter opens the first match: 'dry' then Enter lands on migrations.html
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=1 
✓ step 6: and the first match is followed
✓ step 7: after the navigation
    ✓ h1 text~"Migrations" 
    ✓ .nav-page-link[href='migrations.html'] aria-current=page
S40

skip link on first Tab

index.html · 1280×800 · 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.

L40 saw

Desktop 1280, index.html after one Tab. "Skip to content" appears at the top left over the brand, bold teal on white inside a teal border plus the 2px focus ring: impossible to miss, and it is the first stop. Everything else at rest. The border-plus-ring is a little heavy, but it only exists while focused, so I leave it.

S41

mobile: keyboard focus on a scrolling code block

index.html · 375×812 · scroll-to #step-plan · focus #step-plan ~ .code-block pre · wait 200

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L41 saw

Mobile 375, index.html at step 2 with keyboard focus on the plan output block (it scrolls sideways at this width, so quarry.js made it a focusable group). The teal focus ring is drawn just inside the block's border (outline-offset -2px), so it is not clipped by the rounded, overflow-hidden frame; the right-edge fade still shows the columns continue. Paragraph above with intact inline-code pills.

R4 · final check

builder
G65exit 0vlmkit check integrity index.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.1s
vlmkit check integrity
source: index.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 4.9%, 100 text block(s)
  768x900: 8 component(s), ink 7.0%, 90 text block(s)
  375x700: 8 component(s), ink 12.5%, 67 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
G66exit 0vlmkit check copy index.html --manifest copy.txt

missing 0 · placeholders 0 · manifestLines 13 · statesExplored 1 · revealedOnly 0

output, 9 line(s), 1.4s
vlmkit check copy
source: index.html

status: ok
rendered text: 4786 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 13 line(s), missing 0

No copy issues detected.
G67exit 0vlmkit check composition index.html

verdict: COMPOSED (0 finding(s))

verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0

output, 17 line(s), 1.6s
vlmkit check composition
source: index.html

verdict: COMPOSED (0 finding(s))
  measured: 8 label(s), 3 heading level(s), 80 wide block(s) on 8 left / 5 right rail(s) — from 247 visible box(es)
  1 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     36px / 700   main#content>article.doc>h1
  h2     24px / 700   h2#what-quarry-is
  h3     18px / 600   h3#step-describe
  body 13px, largest 36px (2.77x), heaviest +300 weight

Group gaps: 27.9px between heading-led groups vs 6px inside them (4.65x, 9 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.
G68exit 0vlmkit check color index.html

verdict: CONSISTENT (0 finding(s))

verdict consistent · base #ffffff · bodyInk #1b1f24 · linkInk #4b5563 · 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), 37 link(s) in a text flow — from 265 visible box(es)

Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
  surfaces  3 distinct
    #ffffff   89.0%     5 el  
    #f7f8fa   10.4%    11 el  div.header-inner>div.header-brand>span.version
    #f0f2f4    0.6%    14 el  article.doc>p>code
  ink       6 distinct
    #1b1f24   73.1%    70 el  div.header-brand>a.brand>span.brand-name
    #4b5563   18.5%    35 el  div.header-inner>div.header-brand>span.version
    #0f766e    3.2%    11 el  main#content>article.doc>p.eyebrow
    #646c78    2.4%    24 el  div.header-search>div.search>kbd.search-kbd
    #0b5f58    2.0%     6 el  a.skip-link
    …and 1 more
  marks     4 distinct
    #e3e6ea   90.4%   110 el  header.site-header
    #0f766e    5.4%     7 el  a.skip-link
    #858e99    2.9%     4 el  input#search-header
    #d0d5db    1.4%    12 el  div.header-inner>div.header-brand>span.version
  base #ffffff, body ink #1b1f24, link ink #4b5563 on 32 element(s)
G69exit 0vlmkit check design index.html

verdict: COHERENT (0 finding(s))

verdict coherent · drifting 0 · roles 7

output, 25 line(s), 1.6s
vlmkit check design
source: index.html

verdict: COHERENT (0 finding(s))
  roles judged: 3 of 7 seen, spacing values: 23
  coverage: 21 of 288 visible element(s) carried an inferable role
    no role: span x49, li x38, a x37, div x25, p x25, code x22, +15 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  button           9 inst    2 styles  reuse   4.5x  1 one-off  ok
  h2               4 inst    1 styles  reuse     4x  0 one-off  ok
  h3               4 inst    1 styles  reuse     4x  0 one-off  ok
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  note             1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: search (1), input:search (1), h1 (1), note (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.
G70exit 0vlmkit check integrity migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.3s
vlmkit check integrity
source: migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 7.5%, 104 text block(s)
  768x900: 8 component(s), ink 10.3%, 88 text block(s)
  375x700: 8 component(s), ink 16.3%, 65 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
G71exit 0vlmkit check copy migrations.html --manifest copy.txt

missing 0 · placeholders 0 · manifestLines 13 · statesExplored 1 · revealedOnly 0

output, 9 line(s), 1.4s
vlmkit check copy
source: migrations.html

status: ok
rendered text: 5510 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 13 line(s), missing 0

No copy issues detected.
G72exit 0vlmkit check composition migrations.html

verdict: COMPOSED (0 finding(s))

verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0

output, 17 line(s), 1.7s
vlmkit check composition
source: migrations.html

verdict: COMPOSED (0 finding(s))
  measured: 14 label(s), 3 heading level(s), 59 wide block(s) on 5 left / 5 right rail(s) — from 228 visible box(es)
  1 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     36px / 700   main#content>article.doc>h1
  h2     24px / 700   h2#how-a-migration-runs
  h3     18px / 600   h3#plan
  body 14px, largest 36px (2.57x), heaviest +300 weight

Group gaps: 36px between heading-led groups vs 4px inside them (9x, 15 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.
G73exit 0vlmkit check color migrations.html

verdict: CONSISTENT (0 finding(s))

verdict consistent · base #ffffff · bodyInk #1b1f24 · linkInk #4b5563 · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0

output, 27 line(s), 1.6s
vlmkit check color
source: migrations.html

verdict: CONSISTENT (0 finding(s))
  measured: 1 control(s), 39 link(s) in a text flow — from 243 visible box(es)

Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
  surfaces  4 distinct
    #ffffff   90.4%     5 el  
    #f7f8fa    7.3%    12 el  div.header-inner>div.header-brand>span.version
    #fff7e0    1.5%     1 el  main#content>article.doc>div.callout
    #f0f2f4    0.9%    19 el  article.doc>p>code
  ink       5 distinct
    #1b1f24   75.3%    72 el  div.header-brand>a.brand>span.brand-name
    #4b5563   19.8%    39 el  div.header-inner>div.header-brand>span.version
    #0b5f58    2.1%     7 el  a.skip-link
    #646c78    2.1%    15 el  div.header-search>div.search>kbd.search-kbd
    #0f766e    0.7%     7 el  main#content>article.doc>p.eyebrow
  marks     6 distinct
    #e3e6ea   82.0%   125 el  header.site-header
    #0f766e    5.2%     7 el  a.skip-link
    #e2cfa6    5.2%     3 el  main#content>article.doc>div.callout
    #858e99    3.1%     4 el  input#search-header
    #d0d5db    2.6%    20 el  div.header-inner>div.header-brand>span.version
    …and 1 more
  base #ffffff, body ink #1b1f24, link ink #4b5563 on 33 element(s)
G74exit 0vlmkit check design migrations.html --allow 'div.header-inner>div.header-actions>button.icon-button;the theme toggle is its own icon-button component (square, bordered, no text) shared with the mobile Menu and drawer close buttons, not a variant of the ghost Copy button'

verdict: COHERENT (0 finding(s))

verdict coherent · drifting 0 · roles 7

output, 27 line(s), 1.6s
vlmkit check design
source: migrations.html

verdict: COHERENT (0 finding(s))
  roles judged: 3 of 7 seen, spacing values: 24
  coverage: 23 of 258 visible element(s) carried an inferable role
    no role: a x39, li x38, span x37, p x26, code x22, div x15, +16 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  h3              10 inst    1 styles  reuse    10x  0 one-off  ok
  button           3 inst    1 styles  reuse     3x  0 one-off  ok
  h2               4 inst    1 styles  reuse     4x  0 one-off  ok
  note             2 inst    2 styles  reuse     1x  2 one-off  not judged
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: note (2), search (1), input:search (1), h1 (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).

allowed: 1 button instance(s) declared deliberate and left out of the reuse figure

No design drift detected.
G75exit 0vlmkit check integrity cli.html

verdict: NO DEFECTS, 10 WARN (0 fail, 10 warn, 55 exempted)

verdict clean · findings 10 · fails 0 · warns 10 · exempted 55 · viewports 3

output, 38 line(s), 5.8s
vlmkit check integrity
source: cli.html

verdict: NO DEFECTS, 10 WARN (0 fail, 10 warn, 55 exempted)
  exits 0 — 10 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 3.9%, 247 text block(s)
  768x900: 8 component(s), ink 5.7%, 233 text block(s)
  375x700: 8 component(s), ink 9.6%, 210 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] table.plain.stack > thead:nth-of-type(1) @768,375: table.plain.stack > thead:nth-of-type(1) clips 177px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  … 40 more
G76exit 0vlmkit check copy cli.html --manifest copy.txt

missing 0 · placeholders 0 · manifestLines 13 · statesExplored 1 · revealedOnly 0

output, 9 line(s), 1.4s
vlmkit check copy
source: cli.html

status: ok
rendered text: 5220 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 13 line(s), missing 0

No copy issues detected.
G77exit 0vlmkit check composition cli.html

verdict: COMPOSED (0 finding(s))

verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0

output, 17 line(s), 1.8s
vlmkit check composition
source: cli.html

verdict: COMPOSED (0 finding(s))
  measured: 10 label(s), 3 heading level(s), 176 wide block(s) on 5 left / 4 right rail(s) — from 612 visible box(es)
  1 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     36px / 700   main#content>article.doc>h1
  h2     24px / 700   h2#global-options
  h3     18px / 600   h3#environment-variables
  body 14px, largest 36px (2.57x), heaviest +300 weight

Group gaps: 52px between heading-led groups vs 0px inside them (0x, 11 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.
G78exit 0vlmkit check color cli.html

verdict: CONSISTENT (0 finding(s))

verdict consistent · base #ffffff · bodyInk #1b1f24 · linkInk #4b5563 · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0

output, 23 line(s), 1.6s
vlmkit check color
source: cli.html

verdict: CONSISTENT (0 finding(s))
  measured: 1 control(s), 34 link(s) in a text flow — from 629 visible box(es)

Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
  surfaces  2 distinct
    #ffffff   92.1%     5 el  
    #f7f8fa    7.9%    38 el  div.header-inner>div.header-brand>span.version
  ink       5 distinct
    #1b1f24   60.3%   167 el  div.header-brand>a.brand>span.brand-name
    #4b5563   27.9%    95 el  div.header-inner>div.header-brand>span.version
    #646c78    9.5%    27 el  div.header-search>div.search>kbd.search-kbd
    #0b5f58    1.7%     5 el  a.skip-link
    #0f766e    0.6%     3 el  main#content>article.doc>p.eyebrow
  marks     4 distinct
    #e3e6ea   96.8%   278 el  header.site-header
    #0f766e    1.5%     6 el  a.skip-link
    #858e99    1.1%     4 el  input#search-header
    #d0d5db    0.6%    12 el  div.header-inner>div.header-brand>span.version
  base #ffffff, body ink #1b1f24, link ink #4b5563 on 34 element(s)
G79exit 0vlmkit check design cli.html

verdict: COHERENT (0 finding(s))

verdict coherent · drifting 0 · roles 11

output, 28 line(s), 1.7s
vlmkit check design
source: cli.html

verdict: COHERENT (0 finding(s))
  roles judged: 7 of 11 seen, spacing values: 25
  coverage: 228 of 651 visible element(s) carried an inferable role
    no role: span x93, code x85, div x35, a x34, li x30, col x24, +22 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  cell           107 inst    2 styles  reuse  53.5x  0 one-off  ok
  row             46 inst    1 styles  reuse    46x  0 one-off  ok
  columnheader    26 inst    2 styles  reuse    13x  0 one-off  ok
  rowgroup        18 inst    1 styles  reuse    18x  0 one-off  ok
  button           9 inst    2 styles  reuse   4.5x  1 one-off  ok
  h2               9 inst    1 styles  reuse     9x  0 one-off  ok
  table            9 inst    1 styles  reuse     9x  0 one-off  ok
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: search (1), input:search (1), h1 (1), h3 (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.
G80exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/index.html?theme=light'

inspected 169 · failures 0

output, 5 line(s), 1.6s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/index.html?theme=light
  inspected 169 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/index-02c22ca4/report.md
G81exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/index.html?theme=dark'

inspected 169 · failures 0

output, 5 line(s), 1.7s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/index.html?theme=dark
  inspected 169 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/index-9bdde255/report.md
G82exit 0vlmkit check a11y focus index.html

steps 47 · findings 7

output, 13 line(s), 1.7s
  vlmkit check a11y focus
  source: index.html
  captured 47 focus step(s)
  ! 7 finding(s)
    32 focusable element(s) are viewport-pinned (fixed / sticky); jumps into or out of them are not read as order defects
    [skip-row] Focus jumped down by 231px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.callout.callout-note>p>a`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 411px (from `main#content>article.doc>div.callout.callout-note>p>a` to `ol.steps>li.step>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 404px (from `ol.steps>li.step>div.code-block>div.code-head>button.copy-button` to `ol.steps>li.step>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 585px (from `ol.steps>li.step>div.code-block>div.code-head>button.copy-button` to `ol.steps>li.step>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 241px (from `article.doc>ol.steps>li.step>p>a` to `ol.steps>li.step>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    … 2 more (see the report, or --json for all)
  report: examples/sites/docs/test-results/a11y-focus-order/index-c90ccdb8/report.md
  exits 0 — 7 warn(s) did not fail this command. To gate on one: --rule skip-row=suspect
G83exit 0vlmkit check a11y touch index.html

level AA · inspected 47 · failures 0 · wcagExempt 4

output, 6 line(s), 1.7s
  vlmkit check a11y touch
  source: index.html  level: WCAG AA (24×24 min)
  inspected 47 interactive element(s)
  ✓ 0 undersized target(s)
  4 undersized target(s) excused by WCAG 2.5.8 itself (4 in a sentence)
  report: examples/sites/docs/test-results/a11y-touch/index-c90ccdb8/report.md
G84exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/migrations.html?theme=light'

inspected 168 · failures 0

output, 5 line(s), 1.8s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/migrations.html?theme=light
  inspected 168 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/migrations-f0596757/report.md
G85exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/migrations.html?theme=dark'

inspected 168 · failures 0

output, 5 line(s), 1.8s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/migrations.html?theme=dark
  inspected 168 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/migrations-e7fbdbb0/report.md
G86exit 0vlmkit check a11y focus migrations.html

steps 44 · findings 3

output, 10 line(s), 1.6s
  vlmkit check a11y focus
  source: migrations.html
  captured 44 focus step(s)
  ! 3 finding(s)
    38 focusable element(s) are viewport-pinned (fixed / sticky); jumps into or out of them are not read as order defects
    [skip-row] Focus jumped down by 903px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 399px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 1067px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `div.layout>main#content>article.doc>nav.pager>a.pager-link.pager-prev`). Confirm no focusable element was unintentionally skipped.
  report: examples/sites/docs/test-results/a11y-focus-order/migrations-6dd1b9b0/report.md
  exits 0 — 3 warn(s) did not fail this command. To gate on one: --rule skip-row=suspect
G87exit 0vlmkit check a11y touch migrations.html

level AA · inspected 44 · failures 0 · wcagExempt 0

output, 5 line(s), 1.5s
  vlmkit check a11y touch
  source: migrations.html  level: WCAG AA (24×24 min)
  inspected 44 interactive element(s)
  ✓ 0 undersized target(s)
  report: examples/sites/docs/test-results/a11y-touch/migrations-6dd1b9b0/report.md
G88exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/cli.html?theme=light'

inspected 314 · failures 0

output, 5 line(s), 1.6s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/cli.html?theme=light
  inspected 314 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/cli-6393d62b/report.md
G89exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/cli.html?theme=dark'

inspected 314 · failures 0

output, 5 line(s), 1.6s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/cli.html?theme=dark
  inspected 314 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/cli-594ae7e3/report.md
G90exit 0vlmkit check a11y focus cli.html

steps 44 · findings 8

output, 13 line(s), 1.7s
  vlmkit check a11y focus
  source: cli.html
  captured 44 focus step(s)
  ! 8 finding(s)
    34 focusable element(s) are viewport-pinned (fixed / sticky); jumps into or out of them are not read as order defects
    [skip-row] Focus jumped down by 1193px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 613px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 549px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 893px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 545px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    … 3 more (see the report, or --json for all)
  report: examples/sites/docs/test-results/a11y-focus-order/cli-0459c88f/report.md
  exits 0 — 8 warn(s) did not fail this command. To gate on one: --rule skip-row=suspect
G91exit 0vlmkit check a11y touch cli.html

level AA · inspected 44 · failures 0 · wcagExempt 0

output, 5 line(s), 1.5s
  vlmkit check a11y touch
  source: cli.html  level: WCAG AA (24×24 min)
  inspected 44 interactive element(s)
  ✓ 0 undersized target(s)
  report: examples/sites/docs/test-results/a11y-touch/cli-0459c88f/report.md
G92exit 0vlmkit scan scroll index.html

containers 1 · overflowX 0 · issues 1

output, 26 line(s), 1.4s
vlmkit scan scroll
source: index.html (1280x720)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x4305 — horizontal overflow 0px, vertical scroll 3585px
scroll containers: 1 (dead scrollports 9, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(0,60) 272x660

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(2) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(1) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(2) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(3) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(4) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(4) > div:nth-of-type(2) > pre:nth-of-type(1) (overflow: auto auto)
  - nav.toc (overflow: auto auto)

Issues:
  ! clipped-content div.header-search > div:nth-of-type(1) > label:nth-of-type(1): div.header-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 1 expectedScrollports entry ready for a UI Contract.
G93exit 0vlmkit scan scroll index.html --viewport 375x740

containers 5 · overflowX 0 · issues 1

output, 26 line(s), 1.3s
vlmkit scan scroll
source: index.html (375x740)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 375x5264 — horizontal overflow 0px, vertical scroll 4524px
scroll containers: 5 (dead scrollports 5, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(-320,0) 320x740
  - article.doc > div:nth-of-type(2) > pre:nth-of-type(1): axis=x overflow=97px box=(21,1524) 333x52
  - article.doc > div:nth-of-type(3) > pre:nth-of-type(1): axis=x overflow=58px box=(21,1690) 333x74
  - ol.steps > li:nth-of-type(1) > div:nth-of-type(1) > pre:nth-of-type(1): axis=x overflow=152px box=(21,2480) 333x96
  - ol.steps > li:nth-of-type(2) > div:nth-of-type(1) > pre:nth-of-type(1): axis=x overflow=136px box=(21,2994) 333x251

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(3) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(4) > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - ol.steps > li:nth-of-type(4) > div:nth-of-type(2) > pre:nth-of-type(1) (overflow: auto auto)
  - nav.toc (overflow: auto auto)

Issues:
  ! clipped-content div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1): div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 5 expectedScrollports entries ready for a UI Contract.
G94exit 0vlmkit scan handlers index.html

registrations 35 · elements 22 · suspects 0

output, 33 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: 35 across 22 element(s) + globals, on a page presenting 47 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "$ brew install quarry-data/tap/quarry": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ curl -fsSL https://quarry.example/inst": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry --version quarry 2.4.1 (build 5": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry init \ --source postgres://quar": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry plan Plan for shop (PostgreSQL ": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry run --dry-run": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry run": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry cutover": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:scroll, window:resize×2, window:hashchange

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G95exit 0vlmkit check interactions index.html --max-elements 80

elements 47 · suspects 0 · warns 0

output, 53 line(s), 10.1s
vlmkit check interactions
source: index.html

status: ok
interactive elements: 47

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Troubleshooting" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Dry runs" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "How a migration runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Next Migrations" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Describe the source and target" focus✓
  - [link] "Review the plan" focus✓
  - [link] "Rehearse with a dry run" focus✓
  - [link] "Run the migration and cut over" focus✓
  - [link] "Next steps" focus✓
  - [link] "Back to top" focus✓
G96exit 0vlmkit check breakpoints index.html --sweep

breakpoints 2 · issues 0 · sweepRanges 0

output, 11 line(s), 3.1s
vlmkit check breakpoints
source: index.html

status: ok
width sweep: 320-1300px step 25 (40 widths) — clean
breakpoints checked: 768, 1200px (each at B-1 / B / B+1)

  768px: clean (max-width: 768px)
  1200px: clean (max-width: 1200px)

All boundaries consistent.
G97exit 0vlmkit check scroll index.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.5s
vlmkit check scroll
source: index.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G98exit 0vlmkit check motion index.html

activeAnimations 0 · running 0 · issues 0

output, 12 line(s), 1.3s
vlmkit check motion
source: index.html

status: ok
samples: 0
active animations: 0
active transitions: 0
running animations: 0
paused animations: 0
reduced-motion rule: yes

No motion issues detected.
G99exit 0vlmkit scan scroll migrations.html

containers 1 · overflowX 0 · issues 1

output, 21 line(s), 1.3s
vlmkit scan scroll
source: migrations.html (1280x720)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x4248 — horizontal overflow 0px, vertical scroll 3528px
scroll containers: 1 (dead scrollports 4, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(0,60) 272x660

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1) (overflow: auto auto)
  - nav.toc (overflow: auto auto)

Issues:
  ! clipped-content div.header-search > div:nth-of-type(1) > label:nth-of-type(1): div.header-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 1 expectedScrollports entry ready for a UI Contract.
G100exit 0vlmkit scan scroll migrations.html --viewport 375x740

containers 2 · overflowX 0 · issues 1

output, 21 line(s), 1.3s
vlmkit scan scroll
source: migrations.html (375x740)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 375x5713 — horizontal overflow 0px, vertical scroll 4973px
scroll containers: 2 (dead scrollports 3, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(-320,0) 320x740
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1): axis=x overflow=136px box=(21,2389) 333x273

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(3) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1) (overflow: auto auto)
  - nav.toc (overflow: auto auto)

Issues:
  ! clipped-content div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1): div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 2 expectedScrollports entries ready for a UI Contract.
G101exit 0vlmkit scan handlers migrations.html

registrations 25 · elements 12 · suspects 0

output, 23 line(s), 1.6s
vlmkit scan handlers
source: migrations.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: 25 across 12 element(s) + globals, on a page presenting 44 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "$ quarry run --dry-run Dry run for shop ": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry rollback": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry rollback --yes": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:scroll, window:resize×2, window:hashchange

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G102exit 0vlmkit check interactions migrations.html --max-elements 80

elements 44 · suspects 0 · warns 0

output, 50 line(s), 5.8s
vlmkit check interactions
source: migrations.html

status: ok
interactive elements: 44

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Previous Getting started" focus✓
  - [link] "Next CLI reference" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Plan" focus✓
  - [link] "Copy" focus✓
  - [link] "Catch-up" focus✓
  - [link] "Cut-over" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Before cut-over" focus✓
  - [link] "After cut-over" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "The copy stalls on one table" focus✓
  - [link] "Replication lag keeps growing" focus✓
  - [link] "Cut-over times out" focus✓
  - [link] "Permission denied on the source" focus✓
  - [link] "Back to top" focus✓
G103exit 0vlmkit check breakpoints migrations.html --sweep

breakpoints 2 · issues 0 · sweepRanges 0

output, 11 line(s), 3.6s
vlmkit check breakpoints
source: migrations.html

status: ok
width sweep: 320-1300px step 25 (40 widths) — clean
breakpoints checked: 768, 1200px (each at B-1 / B / B+1)

  768px: clean (max-width: 768px)
  1200px: clean (max-width: 1200px)

All boundaries consistent.
G104exit 0vlmkit check scroll migrations.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.9s
vlmkit check scroll
source: migrations.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G105exit 0vlmkit check motion migrations.html

activeAnimations 0 · running 0 · issues 0

output, 12 line(s), 1.9s
vlmkit check motion
source: migrations.html

status: ok
samples: 0
active animations: 0
active transitions: 0
running animations: 0
paused animations: 0
reduced-motion rule: yes

No motion issues detected.
G106exit 0vlmkit scan scroll cli.html

containers 1 · overflowX 0 · issues 1

output, 27 line(s), 1.7s
vlmkit scan scroll
source: cli.html (1280x720)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x6446 — horizontal overflow 0px, vertical scroll 5726px
scroll containers: 1 (dead scrollports 19, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(0,60) 272x660

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(2) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) (overflow: auto auto)
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(5) (overflow: auto auto)
  - article.doc > div:nth-of-type(6) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(7) (overflow: auto auto)
  - article.doc > div:nth-of-type(8) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(9) (overflow: auto auto)
  - article.doc > div:nth-of-type(10) > pre:nth-of-type(1) (overflow: auto auto)

Issues:
  ! clipped-content div.header-search > div:nth-of-type(1) > label:nth-of-type(1): div.header-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 1 expectedScrollports entry ready for a UI Contract.
G107exit 0vlmkit scan scroll cli.html --viewport 375x740

containers 3 · overflowX 0 · issues 10

output, 38 line(s), 1.4s
vlmkit scan scroll
source: cli.html (375x740)

status: warn
  exits 0 — 10 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 375x7429 — horizontal overflow 0px, vertical scroll 6689px
scroll containers: 3 (dead scrollports 17, clipped 10)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(-320,0) 320x740
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1): axis=x overflow=97px box=(21,1813) 333x52
  - article.doc > div:nth-of-type(10) > pre:nth-of-type(1): axis=x overflow=66px box=(21,4251) 333x140

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(2) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) (overflow: auto auto)
  - article.doc > div:nth-of-type(5) (overflow: auto auto)
  - article.doc > div:nth-of-type(6) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(7) (overflow: auto auto)
  - article.doc > div:nth-of-type(8) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(9) (overflow: auto auto)
  - article.doc > div:nth-of-type(11) (overflow: auto auto)
  - article.doc > div:nth-of-type(12) > pre:nth-of-type(1) (overflow: auto auto)

Issues:
  ! clipped-content article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1): div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content table.plain.stack > thead:nth-of-type(1): table.plain.stack > thead:nth-of-type(1) clips 177px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 3 expectedScrollports entries ready for a UI Contract.
G108exit 0vlmkit scan handlers cli.html

registrations 35 · elements 22 · suspects 0

output, 33 line(s), 1.8s
vlmkit scan handlers
source: cli.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: 35 across 22 element(s) + globals, on a page presenting 44 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "quarry <command> [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry init --source <url> --target <url": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry plan [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry run [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry status shop · catch-up copied 7": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry cutover [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry rollback [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry verify [options]": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:scroll, window:resize×2, window:hashchange

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G109exit 0vlmkit check interactions cli.html --max-elements 80

elements 44 · suspects 0 · warns 0

output, 50 line(s), 10.1s
vlmkit check interactions
source: cli.html

status: ok
interactive elements: 44

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Previous Migrations" focus✓
  - [link] "Global options" focus✓
  - [link] "Environment variables" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [link] "Back to top" focus✓
G110exit 0vlmkit check breakpoints cli.html --sweep

breakpoints 2 · issues 0 · sweepRanges 0

output, 11 line(s), 3.3s
vlmkit check breakpoints
source: cli.html

status: ok
width sweep: 320-1300px step 25 (40 widths) — clean
breakpoints checked: 768, 1200px (each at B-1 / B / B+1)

  768px: clean (max-width: 768px)
  1200px: clean (max-width: 1200px)

All boundaries consistent.
G111exit 0vlmkit check scroll cli.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.5s
vlmkit check scroll
source: cli.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G112exit 0vlmkit check motion cli.html

activeAnimations 0 · running 0 · issues 0

output, 12 line(s), 1.4s
vlmkit check motion
source: cli.html

status: ok
samples: 0
active animations: 0
active transitions: 0
running animations: 0
paused animations: 0
reduced-motion rule: yes

No motion issues detected.
G113exit 0vlmkit verify flow index.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.1s
vlmkit verify flow
source: index.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar a[href='cli.html#exit-codes'] focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
G114exit 1vlmkit verify flow cli.html --flow flows/drawer.json

verdict: FAILED (1/6 steps)

done false · passed 1 · total 6

output, 13 line(s), 1.6s
vlmkit verify flow
source: cli.html

verdict: FAILED (1/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✗ step 2: Shift+Tab from the first control wraps to the last link
    ✗ #sidebar a[href='cli.html#exit-codes'] focused (got: a)
D26 defect flows/drawer.json step 2

The drawer flow only works on index.html: it expects focus on a[href='cli.html#exit-codes'], but on cli.html that last sidebar link is the same-page '#exit-codes', so the flow fails there ('got: a') although Shift+Tab did wrap to the last link.

G115exit 0vlmkit verify flow index.html --flow flows/filter.json

verdict: DONE (7/7 steps)

done true · passed 7 · total 7

output, 25 line(s), 2.6s
vlmkit verify flow
source: index.html

verdict: DONE (7/7 steps)

✓ step 1: / from anywhere on the page jumps to the filter field
    ✓ #search-header focused 
✓ step 2: Typing filters the sidebar to matching page and section titles
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=2 
    ✓ #sidebar-nav mark count=2 
    ✓ #sidebar-nav a[href='migrations.html#rolling-back-a-migration'] visible 
    ✓ #sidebar-nav a[href='cli.html#quarry-rollback'] visible 
    ✓ #sidebar-nav a[href='#install-quarry'] hidden 
✓ step 3: A term that matches nothing says so
    ✓ .nav-empty visible 
    ✓ .nav-empty text~"zebra" 
✓ step 4: Escape clears the filter and every section comes back
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=17 
    ✓ .nav-empty hidden 
✓ step 5: Enter opens the first match: 'dry' then Enter lands on migrations.html
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=1 
✓ step 6: and the first match is followed
✓ step 7: after the navigation
    ✓ h1 text~"Migrations" 
    ✓ .nav-page-link[href='migrations.html'] aria-current=page
G116exit 0vlmkit verify flow index.html --flow flows/copy.json

verdict: DONE (3/3 steps)

done true · passed 3 · total 3

output, 12 line(s), 4.6s
vlmkit verify flow
source: index.html

verdict: DONE (3/3 steps)

✓ step 1: Click Copy on the Homebrew install block
    ✓ #install-quarry ~ .code-block .copy-button focused 
✓ step 2: once the asynchronous clipboard write resolves, the button and the live region say Copied
    ✓ #install-quarry ~ .code-block .copy-button text~"Copied" 
    ✓ #announcer text~"Copied" 
✓ step 3: and the button goes back to Copy after 2.5 s
    ✓ #install-quarry ~ .code-block .copy-button text~"Copy"
G117exit 0vlmkit verify flow index.html --flow flows/theme.json

verdict: DONE (2/2 steps)

done true · passed 2 · total 2

output, 11 line(s), 1.7s
vlmkit verify flow
source: index.html

verdict: DONE (2/2 steps)

✓ step 1: The toggle switches to dark and reports it
    ✓ html data-theme=dark 
    ✓ .theme-toggle aria-pressed=true 
✓ step 2: And back to light
    ✓ html data-theme=light 
    ✓ .theme-toggle aria-pressed=false
G118exit 0vlmkit verify flow migrations.html --flow flows/toc.json

verdict: DONE (2/2 steps)

done true · passed 2 · total 2

output, 10 line(s), 2.9s
vlmkit verify flow
source: migrations.html

verdict: DONE (2/2 steps)

✓ step 1: Following a contents link marks that section in both columns
✓ step 2: after the smooth scroll settles
    ✓ .toc-list a[href='#dry-runs'] aria-current=true 
    ✓ .nav-sections a[href='#dry-runs'] aria-current=true 
    ✓ .toc-list a[aria-current] count=1
F26fix for D26

Edited flows/drawer.json step 2: the target is '#sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a' (the last sidebar link on any page), so the same flow checks the trap on all three pages.

G119exit 0vlmkit verify flow index.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.2s
vlmkit verify flow
source: index.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
G120exit 0vlmkit verify flow migrations.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.1s
vlmkit verify flow
source: migrations.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
G121exit 0vlmkit verify flow cli.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.1s
vlmkit verify flow
source: cli.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
V26verified D26 by G121

Drawer flow DONE 6/6 on cli.html (G121) as well as index (G119) and migrations (G120).

S42

index.html · 1280×800 · full page, 6 screen(s)

6 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L42 saw

Final, desktop 1280, index.html, 6 screens. S42-1: header, sidebar, article and contents start on shared rails (filter and article at x=360; Guides / eyebrow / On this page on one top line); first body line ~71 characters; lede 3 lines; current page pill + barred section; contents entries all on one line. S42-2: three install blocks, the curl line whole, version output grey. S42-3: Note ends on two words ("without it."), step 1 disc and hairline, init command with teal flags and brown URLs. S42-4: plan output aligned in columns. S42-5: steps 3 and 4 with Copy buttons; the one blemish is the prose link "Dry runs" breaking across two lines ("Dry / runs"), which is ordinary line-breaking and I leave. S42-6: Next steps list with underlined links, Next > Migrations card, two-line footer. Scroll-spy marks the right section on every screen in both columns. Nothing clipped, crowded or off-palette.

S43

index.html · 375×812 · full page, 11 screen(s)

11 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L43 saw

Final, mobile 375, index.html, 11 screens. S43-1: Menu / cube Quarry v2.4 / toggle header, 4-line lede, 20px gutters. S43-3/S43-4: install blocks fade at their right edge where they continue; Note callout fits. S43-5 to S43-9: steps put the disc beside the (now evenly broken) h3, prose and code use the full width, inline-code pills never split; the plan output fades at the edge; "A dry run / changes nothing." ends on two words. S43-10/S43-11: Next steps, full-width Next card, footer with the mark on the tagline's first line and Last updated aligned under it. No horizontal overflow, no clipped text, header stays pinned.

S44

migrations.html · 1280×800 · full page, 6 screen(s)

6 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L44 saw

Final, desktop 1280, migrations.html, 6 screens. S44-1: phase cards four across inside the 576px measure, names whole, descriptions 2-4 short lines, chevrons centred; contents lists the page with "How a migration runs" marked. S44-2: Copy / Catch-up / Cut-over h3s with intact flag pills; contents follows ("Copy"). S44-3: dry-run output with teal checks and the amber "!", Note callout; contents and sidebar both on "Dry runs", a section other than the first. S44-4: Ctrl + C keycaps, rollback blocks. S44-5: amber Warning callout, Troubleshooting h2 tight on its first h3. S44-6: last troubleshooting entry, Previous/Next cards of equal width, two-line footer; contents marks the last entry at the bottom of the page. No regressions from the container-query change.

S45

migrations.html · 375×812 · full page, 12 screen(s)

12 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L45 saw

Final, mobile 375, migrations.html, 12 screens. S45-1/S45-2: lede 4 lines, phases as a vertical stack with down chevrons. S45-3/S45-4: flags stay whole ("(--parallel", "--batch-size"), the opening parenthesis travels with its pill. S45-5/S45-6: the dry-run output fades at the right edge; Note fits. S45-7 to S45-9: keycaps, rollback blocks, amber Warning with its command pill on its own line. S45-10 to S45-12: troubleshooting entries, "quarry status --verbose" and "quarry run --dry-run" whole (each costs a slightly short line before it, acceptable), stacked Previous/Next cards, footer. No horizontal overflow, nothing clipped.

S46

cli.html · 1280×800 · full page, 9 screen(s)

9 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L46 saw

Final, desktop 1280, cli.html, 9 screens. S46-1: Reference eyebrow, h1, two-line lede, Usage block, Global options table; contents with commands in mono. S46-2: Environment variables table; command heading "quarry init" in plain mono. S46-3 to S46-7: every option table shares one grid (Default at x=593); the sidebar scrolls itself to keep the current command visible (S46-7: quarry rollback). PROBLEM: the grid is lopsided: the Default column takes 22% of the table for values that are mostly two to four characters ("off", "4", "1s", "24h"), while Description is squeezed to ~200px, so descriptions run three or four lines ("--cutover <mode>": 4 lines; "--timeout": 3; "--resume": 3) and the table reads as tall and airy on the left, crowded on the right. S46-8/S46-9: exit codes, Previous card, footer; contents marks Exit codes at the bottom.

D27 defect cli.html table.options col widths (desktop)

The shared option-table grid gives Default 22% for mostly 2-4 character values while Description gets ~200px, so descriptions run 3-4 lines (S46-5 --cutover <mode>, S46-6 --timeout).

S47

cli.html · 375×812 · full page, 15 screen(s)

15 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L47 saw

Final, mobile 375, cli.html, 15 screens. Option and environment tables are cards (name + "Default ..." on one line, description below), exit codes a compact two-column table, usage blocks fade at the edge, command headings plain mono. PROBLEM: the card descriptions end on lone words, because text-wrap: pretty covers paragraphs but not table cells: "Connection string of the database to copy / from." and "... / to." (S47-4/S47-5), "catch- / up." (S47-7), "... may / start." (S47-8/S47-9), "... any / value." (S47-3), "... error is / printed." (S47-14). No overflow, nothing clipped.

D28 defect .doc td (stacked cards on phones)

Table-cell descriptions end on single-word lines on phones ('from.', 'to.', 'up.', 'start.', 'value.', 'printed.'); the orphan fix (D20) covered p/li only.

F27fix for D27

Shared option-table targets 38/22/40 -> 34/20/46 (Option ~196px, Default ~115px, Description ~265px at the 576px measure); the one long default, ./quarry.toml, now reads quarry.toml (with 'relative to the current directory' in the description) so every default fits the narrower column and the grid stays shared.

F28fix for D28

text-wrap: pretty extended from .doc p / li / lede to .doc td, so table cells and the stacked cards rebalance their last lines too.

G122exit 0vlmkit check integrity cli.html

verdict: NO DEFECTS, 10 WARN (0 fail, 10 warn, 55 exempted)

verdict clean · findings 10 · fails 0 · warns 10 · exempted 55 · viewports 3

output, 38 line(s), 5.9s
vlmkit check integrity
source: cli.html

verdict: NO DEFECTS, 10 WARN (0 fail, 10 warn, 55 exempted)
  exits 0 — 10 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 4.0%, 247 text block(s)
  768x900: 8 component(s), ink 5.7%, 233 text block(s)
  375x700: 8 component(s), ink 9.6%, 210 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] table.plain.stack > thead:nth-of-type(1) @768,375: table.plain.stack > thead:nth-of-type(1) clips 177px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  … 40 more
G123exit 0vlmkit check copy cli.html --manifest copy.txt

missing 0 · placeholders 0 · manifestLines 13 · statesExplored 1 · revealedOnly 0

output, 9 line(s), 1.4s
vlmkit check copy
source: cli.html

status: ok
rendered text: 5253 chars
disclosure states: 1 explored (details / tabs / aria-expanded)
manifest: 13 line(s), missing 0

No copy issues detected.
G124exit 0vlmkit check design cli.html

verdict: COHERENT (0 finding(s))

verdict coherent · drifting 0 · roles 11

output, 28 line(s), 1.6s
vlmkit check design
source: cli.html

verdict: COHERENT (0 finding(s))
  roles judged: 7 of 11 seen, spacing values: 25
  coverage: 228 of 651 visible element(s) carried an inferable role
    no role: span x93, code x85, div x35, a x34, li x30, col x24, +22 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.
  text-free: 1 (0 judged on box alone — font-size/weight is not observable without painted text)

Role reuse (instances / distinct styles; drift below 3x from 3 instances)
  cell           107 inst    2 styles  reuse  53.5x  0 one-off  ok
  row             46 inst    1 styles  reuse    46x  0 one-off  ok
  columnheader    26 inst    2 styles  reuse    13x  0 one-off  ok
  rowgroup        18 inst    1 styles  reuse    18x  0 one-off  ok
  button           9 inst    2 styles  reuse   4.5x  1 one-off  ok
  h2               9 inst    1 styles  reuse     9x  0 one-off  ok
  table            9 inst    1 styles  reuse     9x  0 one-off  ok
  search           1 inst    1 styles  reuse     1x  1 one-off  not judged
  input:search     1 inst    1 styles  reuse     1x  1 one-off  not judged
  h1               1 inst    1 styles  reuse     1x  1 one-off  not judged
  not judged: search (1), input:search (1), h1 (1), h3 (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.
G125exit 0vlmkit check composition cli.html

verdict: COMPOSED (0 finding(s))

verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0

output, 17 line(s), 1.7s
vlmkit check composition
source: cli.html

verdict: COMPOSED (0 finding(s))
  measured: 10 label(s), 3 heading level(s), 176 wide block(s) on 5 left / 4 right rail(s) — from 612 visible box(es)
  1 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     36px / 700   main#content>article.doc>h1
  h2     24px / 700   h2#global-options
  h3     18px / 600   h3#environment-variables
  body 14px, largest 36px (2.57x), heaviest +300 weight

Group gaps: 52px between heading-led groups vs 0px inside them (0x, 11 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.
G126exit 0vlmkit check color cli.html

verdict: CONSISTENT (0 finding(s))

verdict consistent · base #ffffff · bodyInk #1b1f24 · linkInk #4b5563 · invisibleControls 0 · colorOnlyLinks 0 · unreadableColors 0

output, 23 line(s), 1.6s
vlmkit check color
source: cli.html

verdict: CONSISTENT (0 finding(s))
  measured: 1 control(s), 34 link(s) in a text flow — from 629 visible box(es)

Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
  surfaces  2 distinct
    #ffffff   92.1%     5 el  
    #f7f8fa    7.9%    38 el  div.header-inner>div.header-brand>span.version
  ink       5 distinct
    #1b1f24   62.0%   167 el  div.header-brand>a.brand>span.brand-name
    #4b5563   26.4%    95 el  div.header-inner>div.header-brand>span.version
    #646c78    9.3%    27 el  div.header-search>div.search>kbd.search-kbd
    #0b5f58    1.7%     5 el  a.skip-link
    #0f766e    0.6%     3 el  main#content>article.doc>p.eyebrow
  marks     4 distinct
    #e3e6ea   96.8%   278 el  header.site-header
    #0f766e    1.5%     6 el  a.skip-link
    #858e99    1.1%     4 el  input#search-header
    #d0d5db    0.6%    12 el  div.header-inner>div.header-brand>span.version
  base #ffffff, body ink #1b1f24, link ink #4b5563 on 34 element(s)
G127exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/cli.html?theme=light'

inspected 314 · failures 0

output, 5 line(s), 1.6s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/cli.html?theme=light
  inspected 314 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/cli-6393d62b/report.md
G128exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/cli.html?theme=dark'

inspected 314 · failures 0

output, 5 line(s), 1.6s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/cli.html?theme=dark
  inspected 314 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/cli-594ae7e3/report.md
G129exit 0vlmkit check a11y focus cli.html

steps 44 · findings 8

output, 13 line(s), 1.5s
  vlmkit check a11y focus
  source: cli.html
  captured 44 focus step(s)
  ! 8 finding(s)
    34 focusable element(s) are viewport-pinned (fixed / sticky); jumps into or out of them are not read as order defects
    [skip-row] Focus jumped down by 1214px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 613px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 549px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 872px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    [skip-row] Focus jumped down by 545px (from `main#content>article.doc>div.code-block>div.code-head>button.copy-button` to `main#content>article.doc>div.code-block>div.code-head>button.copy-button`). Confirm no focusable element was unintentionally skipped.
    … 3 more (see the report, or --json for all)
  report: examples/sites/docs/test-results/a11y-focus-order/cli-0459c88f/report.md
  exits 0 — 8 warn(s) did not fail this command. To gate on one: --rule skip-row=suspect
G130exit 0vlmkit check a11y touch cli.html

level AA · inspected 44 · failures 0 · wcagExempt 0

output, 5 line(s), 1.4s
  vlmkit check a11y touch
  source: cli.html  level: WCAG AA (24×24 min)
  inspected 44 interactive element(s)
  ✓ 0 undersized target(s)
  report: examples/sites/docs/test-results/a11y-touch/cli-0459c88f/report.md
G131exit 0vlmkit scan scroll cli.html

containers 1 · overflowX 0 · issues 1

output, 27 line(s), 1.4s
vlmkit scan scroll
source: cli.html (1280x720)

status: warn
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 1280x6381 — horizontal overflow 0px, vertical scroll 5661px
scroll containers: 1 (dead scrollports 19, clipped 1)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(0,60) 272x660

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(2) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) (overflow: auto auto)
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(5) (overflow: auto auto)
  - article.doc > div:nth-of-type(6) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(7) (overflow: auto auto)
  - article.doc > div:nth-of-type(8) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(9) (overflow: auto auto)
  - article.doc > div:nth-of-type(10) > pre:nth-of-type(1) (overflow: auto auto)

Issues:
  ! clipped-content div.header-search > div:nth-of-type(1) > label:nth-of-type(1): div.header-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 1 expectedScrollports entry ready for a UI Contract.
G132exit 0vlmkit scan scroll cli.html --viewport 375x740

containers 3 · overflowX 0 · issues 10

output, 38 line(s), 1.3s
vlmkit scan scroll
source: cli.html (375x740)

status: warn
  exits 0 — 10 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
page: 375x7451 — horizontal overflow 0px, vertical scroll 6711px
scroll containers: 3 (dead scrollports 17, clipped 10)

Scroll containers:
  - #sidebar: axis=y overflow=141px box=(-320,0) 320x740
  - article.doc > div:nth-of-type(4) > pre:nth-of-type(1): axis=x overflow=97px box=(21,1834) 333x52
  - article.doc > div:nth-of-type(10) > pre:nth-of-type(1): axis=x overflow=66px box=(21,4273) 333x140

Dead scrollports (declared scrollable, content fits):
  - article.doc > div:nth-of-type(1) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(2) (overflow: auto auto)
  - article.doc > div:nth-of-type(3) (overflow: auto auto)
  - article.doc > div:nth-of-type(5) (overflow: auto auto)
  - article.doc > div:nth-of-type(6) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(7) (overflow: auto auto)
  - article.doc > div:nth-of-type(8) > pre:nth-of-type(1) (overflow: auto auto)
  - article.doc > div:nth-of-type(9) (overflow: auto auto)
  - article.doc > div:nth-of-type(11) (overflow: auto auto)
  - article.doc > div:nth-of-type(12) > pre:nth-of-type(1) (overflow: auto auto)

Issues:
  ! clipped-content article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1): article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1): div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! clipped-content table.plain.stack > thead:nth-of-type(1): table.plain.stack > thead:nth-of-type(1) clips 177px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Contract hint: --json emits 3 expectedScrollports entries ready for a UI Contract.
G133exit 0vlmkit scan handlers cli.html

registrations 35 · elements 22 · suspects 0

output, 33 line(s), 1.6s
vlmkit scan handlers
source: cli.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: 35 across 22 element(s) + globals, on a page presenting 44 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "quarry <command> [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry init --source <url> --target <url": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry plan [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry run [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry status shop · catch-up copied 7": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry cutover [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry rollback [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry verify [options]": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:scroll, window:resize×2, window:hashchange

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G134exit 0vlmkit check interactions cli.html --max-elements 80

elements 44 · suspects 0 · warns 0

output, 50 line(s), 10.0s
vlmkit check interactions
source: cli.html

status: ok
interactive elements: 44

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Previous Migrations" focus✓
  - [link] "Global options" focus✓
  - [link] "Environment variables" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [link] "Back to top" focus✓
G135exit 0vlmkit check breakpoints cli.html --sweep

breakpoints 2 · issues 0 · sweepRanges 0

output, 11 line(s), 3.4s
vlmkit check breakpoints
source: cli.html

status: ok
width sweep: 320-1300px step 25 (40 widths) — clean
breakpoints checked: 768, 1200px (each at B-1 / B / B+1)

  768px: clean (max-width: 768px)
  1200px: clean (max-width: 1200px)

All boundaries consistent.
G136exit 0vlmkit check scroll cli.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.4s
vlmkit check scroll
source: cli.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G137exit 0vlmkit check motion cli.html

activeAnimations 0 · running 0 · issues 0

output, 12 line(s), 1.3s
vlmkit check motion
source: cli.html

status: ok
samples: 0
active animations: 0
active transitions: 0
running animations: 0
paused animations: 0
reduced-motion rule: yes

No motion issues detected.
G138exit 0vlmkit verify flow cli.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.1s
vlmkit verify flow
source: cli.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
G139exit 0vlmkit check integrity index.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.1s
vlmkit check integrity
source: index.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 4.9%, 100 text block(s)
  768x900: 8 component(s), ink 7.0%, 90 text block(s)
  375x700: 8 component(s), ink 12.5%, 67 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
G140exit 0vlmkit check integrity migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.3s
vlmkit check integrity
source: migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 7.5%, 104 text block(s)
  768x900: 8 component(s), ink 10.3%, 88 text block(s)
  375x700: 8 component(s), ink 16.3%, 65 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
S48

cli.html · 1280×800 · full page, 9 screen(s)

S48 screen 1S48 screen 2S48 screen 3S48 screen 4S48 screen 5S48 screen 6S48 screen 7S48 screen 8S48 screen 9
L48 saw

Final after D27, desktop 1280, cli.html, 9 screens. Option tables now split 34/20/46: Option at x=375, Default at x=570, Description at x=685 in every table. The Default column still fits every value on one line ("from config", "all planned", mono "quarry.toml"), and descriptions have ~265px: --timeout, --resume, --hook and --verbose now take two lines instead of three. --cutover <mode> is still four because of its four inline pills, and --keep-reverse <duration> breaks between flag and placeholder as designed. S48-1: the global --config row reads "quarry.toml" / "Configuration file to read, relative to the current directory.". Environment variables and exit-code tables unchanged; sidebar follows the current command (S48-7 quarry rollback); footer and Previous card at the end. Page is 65px shorter.

S49

cli.html · 375×812 · full page, 15 screen(s)

S49 screen 1S49 screen 2S49 screen 3S49 screen 4S49 screen 5S49 screen 6S49 screen 7S49 screen 8S49 screen 9S49 screen 10S49 screen 11S49 screen 12S49 screen 13S49 screen 14S49 screen 15
L49 saw

Final after D28, mobile 375, cli.html, 15 screens. Card descriptions no longer end on one word: "Connection string of the database to / copy from.", "Same as --no-color when set to / any value.", "Workers that replay changes during / catch-up.", "Lag at which an automatic cut-over / may start.", "The command failed; the error / is printed.". Cards keep option + "Default ..." on the first line; usage blocks fade at the edge; exit codes two-column; Previous card and footer at the end. No overflow, nothing clipped.

V27verified D27 by L48

Rebalanced shared grid; descriptions mostly two lines, defaults fit (S48).

V28verified D28 by L49

No single-word endings in the stacked cards (S49).

S50

state: desktop, dark theme

index.html?theme=dark · 1280×800 · full page, 6 screen(s)

6 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L50 saw

State: desktop, dark theme (?theme=dark), index.html full page, 6 screens. Same composition as light: shared rails, 576px measure, three columns. Near-black page, off-white headings and body, cool-grey lede and secondary text, light teal for eyebrow, links (still underlined), current page pill, section bars and step discs; the toggle shows the sun. Code blocks sit on a raised slate with visible hairlines; flags teal, URLs a warm sand, output grey; the plan table aligned (S50-4). Note callout keeps its teal rule; footer and Next card legible (S50-6). Nothing glares, nothing sinks into the background.

S51

state: desktop dark, migrations top

migrations.html?theme=dark · 1280×800

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L51 saw

State: desktop dark, migrations.html top. Phase cards are raised slate tiles with light-teal numbers, off-white names and grey descriptions, chevrons visible between them; lede grey; "How a migration runs" marked in sidebar and contents in teal; current page "Migrations" in the dark-teal pill. Hierarchy and spacing identical to light.

S52

state: desktop dark, cli top

cli.html?theme=dark · 1280×800

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L52 saw

State: desktop dark, cli.html top. Reference eyebrow, h1, lede with the quarry.toml pill, Usage block, and the Global options table on the dark: raised header row, hairline rows, bold off-white option names, grey mono defaults, off-white descriptions; the rebalanced grid (Default at x=570) holds. Contents lists the commands in mono with Global options marked.

S53

state: mobile, drawer open

index.html · 375×812 · click .menu-button · wait 400

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L53 saw

State: mobile 375, drawer open (Menu clicked). The 320px drawer sits over the page with its own head ("Documentation" + bordered close X), the filter field, then the whole nav: Guides / Getting started (current, teal pill) with "What Quarry is" barred, Migrations, Reference / CLI reference with mono commands running past the fold (the drawer scrolls). The scrim dims the rest of the page and the header's toggle. Nothing clipped; tap targets are full-width rows.

S54

state: sidebar filtered by 'roll'

cli.html · 1280×800 · fill #search-header "roll" · wait 500

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L54 saw

State: desktop 1280, cli.html, sidebar filtered by "roll". Only two entries remain: Guides > Migrations > "Rolling back a migration" and Reference > CLI reference (current page, teal pill) > "quarry rollback", each with the match in a teal-tinted mark; every other page, section and the Getting started group are hidden. The field shows the teal focus ring and the redrawn grey clear x. Article and contents untouched.

S55

state: Copied on the dry-run block

migrations.html · 1280×800 · scroll-to #dry-runs · click #dry-runs ~ .code-block .copy-button · wait 150

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L55 saw

State: desktop 1280, migrations.html, "Copied" confirmation on the dry-run block. After the click that block's button reads "Copied" with a check icon in teal (bordered while hovered); the button changes in place, so the header row does not shift. The live region (not visible) received "Copied" as well (G116). Contents marks Cut-over, the section above the reading line.

S56

state: contents highlights Rolling back

migrations.html#rolling-back-a-migration · 1280×800 · wait 600

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L56 saw

State: desktop 1280, loaded as migrations.html#rolling-back-a-migration. The browser lands the heading "Rolling back a migration" just under the sticky header, as the scroll-padding intends, and the sidebar marks "Rolling back a migration". PROBLEM: the contents column marks "Before cut-over", the first h3 of that section, not the section the reader jumped to: that h3 sits ~160px lower and is still above the 30% reading line, so the spy picks it. The reader asked for one section and the contents column names another.

D29 defect quarry.js scroll-spy

Jumping to a section (migrations.html#rolling-back-a-migration) lands its heading at the top, but the contents column marks the next heading, 'Before cut-over', because that h3 sits ~160px lower, still above the 30% reading line. The same would happen for 'Troubleshooting', whose first h3 is ~50px below it: the section you asked for is not the one marked.

S57

state: cli tables at mobile width

cli.html · 375×812 · scroll-to #quarry-run · wait 300

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L57 saw

State: mobile 375, cli.html tables at mobile width (scrolled to quarry run). The plan and run option tables are cards: bold mono option name left, "Default from config / none / auto / off" right on the same line, description below at full card width; captions ("Options for quarry run") sit above each stack. Usage block, headings and gutters consistent; nothing overflows.

F29fix for D29

Scroll-spy pins the heading the reader navigates to (contents or sidebar link, hashchange, or a #hash on load): while that heading sits between the header and the 30% reading line it stays current even if its first subsection crosses the line too; the pin is dropped once the heading leaves that zone after the jump has settled (1s), so ordinary scrolling behaves as before. flows/toc.json gained steps for #rolling-back-a-migration and #troubleshooting.

G141exit 0vlmkit verify flow migrations.html --flow flows/toc.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 18 line(s), 5.4s
vlmkit verify flow
source: migrations.html

verdict: DONE (6/6 steps)

✓ step 1: Following a contents link marks that section in both columns
✓ step 2: after the smooth scroll settles
    ✓ .toc-list a[href='#dry-runs'] aria-current=true 
    ✓ .nav-sections a[href='#dry-runs'] aria-current=true 
    ✓ .toc-list a[aria-current] count=1 
✓ step 3: Jumping to a section whose first subsection is close below still marks that section
✓ step 4: after the scroll: Rolling back a migration, not Before cut-over
    ✓ .toc-list a[href='#rolling-back-a-migration'] aria-current=true 
    ✓ .toc-list a[aria-current] count=1 
✓ step 5: Troubleshooting, whose first h3 sits ~50px under it
✓ step 6: after the scroll: Troubleshooting is marked in both columns
    ✓ .toc-list a[href='#troubleshooting'] aria-current=true 
    ✓ .nav-sections a[href='#troubleshooting'] aria-current=true
S58

state: contents highlights the section jumped to

migrations.html#rolling-back-a-migration · 1280×800 · wait 600

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L58 saw

State after D29: desktop 1280, loaded as migrations.html#rolling-back-a-migration. The heading lands under the header and the contents column now marks "Rolling back a migration" itself (teal bar and text), with "Before cut-over" below it unmarked although that h3 is ~160px lower and inside the reading zone; the sidebar marks the same section. This is the "contents column highlighting a section other than the first" state, and it names the section the reader asked for.

S59

jump to Troubleshooting

migrations.html#troubleshooting · 1280×800 · wait 600

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L59 saw

Desktop 1280, loaded as migrations.html#troubleshooting, the hardest case: the first h3 "The copy stalls on one table" sits only ~50px under the h2. Contents and sidebar both mark "Troubleshooting". The four troubleshooting entries read with even spacing; the h2 is tight on its first h3 (D9).

V29verified D29 by L58

Contents marks the jumped-to section on load with a hash (S58, S59); G141 checks the same after following contents links.

G142exit 0vlmkit check integrity index.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.2s
vlmkit check integrity
source: index.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 4.9%, 100 text block(s)
  768x900: 8 component(s), ink 7.0%, 90 text block(s)
  375x700: 8 component(s), ink 12.5%, 67 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
G143exit 0vlmkit check integrity migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.2s
vlmkit check integrity
source: migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 7.5%, 104 text block(s)
  768x900: 8 component(s), ink 10.3%, 88 text block(s)
  375x700: 8 component(s), ink 16.3%, 65 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
G144exit 0vlmkit check integrity cli.html

verdict: NO DEFECTS, 10 WARN (0 fail, 10 warn, 55 exempted)

verdict clean · findings 10 · fails 0 · warns 10 · exempted 55 · viewports 3

output, 38 line(s), 6.1s
vlmkit check integrity
source: cli.html

verdict: NO DEFECTS, 10 WARN (0 fail, 10 warn, 55 exempted)
  exits 0 — 10 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 4.0%, 247 text block(s)
  768x900: 8 component(s), ink 5.7%, 233 text block(s)
  375x700: 8 component(s), ink 9.6%, 210 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: article.doc > div:nth-of-type(17) > table:nth-of-type(1) > thead:nth-of-type(1) clips 270px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.
  ! [clipped-content] table.plain.stack > thead:nth-of-type(1) @768,375: table.plain.stack > thead:nth-of-type(1) clips 177px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] table.plain.stack > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by table.plain.stack > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(5) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(7) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(9) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(11) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(13) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  - [text-collision] article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(2) @768: clipped away by article.doc > div:nth-of-type(15) > table:nth-of-type(1) > thead:nth-of-type(1) (overflow is not visible) — its box still overlaps text below the clip, but no glyph is painted there
  … 40 more
G145exit 0vlmkit check interactions index.html --max-elements 80

elements 47 · suspects 0 · warns 0

output, 53 line(s), 10.2s
vlmkit check interactions
source: index.html

status: ok
interactive elements: 47

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Troubleshooting" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Dry runs" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "How a migration runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Next Migrations" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Describe the source and target" focus✓
  - [link] "Review the plan" focus✓
  - [link] "Rehearse with a dry run" focus✓
  - [link] "Run the migration and cut over" focus✓
  - [link] "Next steps" focus✓
  - [link] "Back to top" focus✓
G146exit 0vlmkit check interactions migrations.html --max-elements 80

elements 44 · suspects 0 · warns 0

output, 50 line(s), 5.5s
vlmkit check interactions
source: migrations.html

status: ok
interactive elements: 44

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Previous Getting started" focus✓
  - [link] "Next CLI reference" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Plan" focus✓
  - [link] "Copy" focus✓
  - [link] "Catch-up" focus✓
  - [link] "Cut-over" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Before cut-over" focus✓
  - [link] "After cut-over" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "The copy stalls on one table" focus✓
  - [link] "Replication lag keeps growing" focus✓
  - [link] "Cut-over times out" focus✓
  - [link] "Permission denied on the source" focus✓
  - [link] "Back to top" focus✓
G147exit 0vlmkit check interactions cli.html --max-elements 80

elements 44 · suspects 0 · warns 0

output, 50 line(s), 10.1s
vlmkit check interactions
source: cli.html

status: ok
interactive elements: 44

  - [link] "Skip to content" focus✓
  - [link] "Quarry" focus✓
  - [searchbox] "Filter pages and sections" focus✓
  - [button] "Dark theme" focus✓ | Enter: pressed false -> true
  - [link] "Getting started" focus✓
  - [link] "What Quarry is" focus✓
  - [link] "Install Quarry" focus✓
  - [link] "Your first migration" focus✓
  - [link] "Next steps" focus✓
  - [link] "Migrations" focus✓
  - [link] "How a migration runs" focus✓
  - [link] "Dry runs" focus✓
  - [link] "Rolling back a migration" focus✓
  - [link] "Troubleshooting" focus✓
  - [link] "CLI reference" focus✓
  - [link] "Global options" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [button] "Copy" focus✓ | Enter: focus moves within | announces
  - [link] "Previous Migrations" focus✓
  - [link] "Global options" focus✓
  - [link] "Environment variables" focus✓
  - [link] "quarry init" focus✓
  - [link] "quarry plan" focus✓
  - [link] "quarry run" focus✓
  - [link] "quarry status" focus✓
  - [link] "quarry cutover" focus✓
  - [link] "quarry rollback" focus✓
  - [link] "quarry verify" focus✓
  - [link] "Exit codes" focus✓
  - [link] "Back to top" focus✓
G148exit 0vlmkit scan handlers index.html

registrations 35 · elements 22 · suspects 0

output, 33 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: 35 across 22 element(s) + globals, on a page presenting 47 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - li.step>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "$ brew install quarry-data/tap/quarry": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ curl -fsSL https://quarry.example/inst": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry --version quarry 2.4.1 (build 5": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry init \ --source postgres://quar": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry plan Plan for shop (PostgreSQL ": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry run --dry-run": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry run": scroll (no role)
  - ol.steps>li.step>div.code-block>pre "$ quarry cutover": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:hashchange, window:scroll, window:resize×2

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G149exit 0vlmkit scan handlers migrations.html

registrations 25 · elements 12 · suspects 0

output, 23 line(s), 1.6s
vlmkit scan handlers
source: migrations.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: 25 across 12 element(s) + globals, on a page presenting 44 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "$ quarry run --dry-run Dry run for shop ": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry rollback": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry rollback --yes": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:hashchange, window:scroll, window:resize×2

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G150exit 0vlmkit scan handlers cli.html

registrations 35 · elements 22 · suspects 0

output, 33 line(s), 1.7s
vlmkit scan handlers
source: cli.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: 35 across 22 element(s) + globals, on a page presenting 44 control(s)

  - header.site-header>div.header-inner>div.header-actions>button.icon-button "Dark theme": click
  - header.site-header>div.header-inner>div.header-brand>button.menu-button "Menu": click (no role)
  - div.layout>div#sidebar>div.drawer-head>button.icon-button "Close menu": click (no role)
  - div.layout>div#sidebar "Documentation Filter pages and sections ": keydown (delegation container)
  - div.header-inner>div.header-search>div.search>input#search-header "Filter pages and sections": input, keydown
  - div#sidebar>div.drawer-search>div.search>input#search-drawer "Filter pages and sections": input, keydown (no role)
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - article.doc>div.code-block>div.code-head>button.copy-button "Copy": click
  - main#content>article.doc>div.code-block>pre "quarry <command> [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry init --source <url> --target <url": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry plan [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry run [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "$ quarry status shop · catch-up copied 7": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry cutover [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry rollback [options]": scroll (no role)
  - main#content>article.doc>div.code-block>pre "quarry verify [options]": scroll (no role)
  - globals: document:DOMContentLoaded, document:keydown×2, document:click×2, window:hashchange, window:scroll, window:resize×2

Issues:
  warn [unprobed-handler-types] 4 handler type(s) registered and NONE exercised: click, input, keydown, scroll — 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.
G151exit 0vlmkit check scroll index.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.4s
vlmkit check scroll
source: index.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G152exit 0vlmkit check scroll migrations.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.4s
vlmkit check scroll
source: migrations.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G153exit 0vlmkit check scroll cli.html

pageScrolled 1080 · stickyFixed 3 · engagedSticky 3 · snaps 0 · issues 0

output, 14 line(s), 1.5s
vlmkit check scroll
source: cli.html

status: ok
page scrolled: 1080px
sticky/fixed elements: 3 (sticky engaged by the scroll: 3)
snap containers driven: 0

Sticky / fixed:
  - header.site-header: sticky top=0px viewport y 0 -> 0
  - #sidebar: sticky top=60px viewport y 60 -> 60
  - nav.toc: sticky top=60px viewport y 60 -> 60

No scroll-behavior issues detected.
G154exit 0vlmkit verify flow index.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.3s
vlmkit verify flow
source: index.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
G155exit 0vlmkit verify flow migrations.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.1s
vlmkit verify flow
source: migrations.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
G156exit 0vlmkit verify flow cli.html --flow flows/drawer.json

verdict: DONE (6/6 steps)

done true · passed 6 · total 6

output, 23 line(s), 2.1s
vlmkit verify flow
source: cli.html

verdict: DONE (6/6 steps)

✓ step 1: Menu opens the drawer as a modal dialog and moves focus into it
    ✓ .menu-button aria-expanded=true 
    ✓ #sidebar role=dialog 
    ✓ #sidebar aria-modal=true 
    ✓ #sidebar-nav visible 
    ✓ .drawer-close focused 
✓ step 2: Shift+Tab from the first control wraps to the last link
    ✓ #sidebar-nav .nav-group:last-of-type .nav-sections li:last-child a focused 
✓ step 3: Tab from the last link wraps back to the close button
    ✓ .drawer-close focused 
✓ step 4: Tab reaches the drawer's filter field
    ✓ #search-drawer focused 
✓ step 5: Escape closes the drawer and returns focus to Menu at once
    ✓ .menu-button aria-expanded=false 
    ✓ .menu-button focused 
✓ step 6: and once the 0.22s slide-out has finished, the drawer is out of the page
    ✓ #sidebar-nav hidden 
    ✓ .scrim hidden
G157exit 0vlmkit verify flow index.html --flow flows/filter.json

verdict: DONE (7/7 steps)

done true · passed 7 · total 7

output, 25 line(s), 2.6s
vlmkit verify flow
source: index.html

verdict: DONE (7/7 steps)

✓ step 1: / from anywhere on the page jumps to the filter field
    ✓ #search-header focused 
✓ step 2: Typing filters the sidebar to matching page and section titles
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=2 
    ✓ #sidebar-nav mark count=2 
    ✓ #sidebar-nav a[href='migrations.html#rolling-back-a-migration'] visible 
    ✓ #sidebar-nav a[href='cli.html#quarry-rollback'] visible 
    ✓ #sidebar-nav a[href='#install-quarry'] hidden 
✓ step 3: A term that matches nothing says so
    ✓ .nav-empty visible 
    ✓ .nav-empty text~"zebra" 
✓ step 4: Escape clears the filter and every section comes back
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=17 
    ✓ .nav-empty hidden 
✓ step 5: Enter opens the first match: 'dry' then Enter lands on migrations.html
    ✓ #sidebar-nav .nav-sections li:not([hidden]) count=1 
✓ step 6: and the first match is followed
✓ step 7: after the navigation
    ✓ h1 text~"Migrations" 
    ✓ .nav-page-link[href='migrations.html'] aria-current=page
G158exit 0vlmkit verify flow index.html --flow flows/copy.json

verdict: DONE (3/3 steps)

done true · passed 3 · total 3

output, 12 line(s), 4.6s
vlmkit verify flow
source: index.html

verdict: DONE (3/3 steps)

✓ step 1: Click Copy on the Homebrew install block
    ✓ #install-quarry ~ .code-block .copy-button focused 
✓ step 2: once the asynchronous clipboard write resolves, the button and the live region say Copied
    ✓ #install-quarry ~ .code-block .copy-button text~"Copied" 
    ✓ #announcer text~"Copied" 
✓ step 3: and the button goes back to Copy after 2.5 s
    ✓ #install-quarry ~ .code-block .copy-button text~"Copy"
G159exit 0vlmkit verify flow index.html --flow flows/theme.json

verdict: DONE (2/2 steps)

done true · passed 2 · total 2

output, 11 line(s), 1.7s
vlmkit verify flow
source: index.html

verdict: DONE (2/2 steps)

✓ step 1: The toggle switches to dark and reports it
    ✓ html data-theme=dark 
    ✓ .theme-toggle aria-pressed=true 
✓ step 2: And back to light
    ✓ html data-theme=light 
    ✓ .theme-toggle aria-pressed=false
N15false-positiveabout G144

Final cli.html integrity: 0 fail, 10 clipped-content warns, all visually-hidden content. Nine are the stacked tables' header rows, kept in the accessibility tree with the .visually-hidden recipe (1px, clip-path inset(50%)) while the rows show as cards below 34rem of article (so at 768 and 375). The tenth is the drawer filter's label (see N2). S49, S57 and S39 show the card layouts with no header text anywhere, and S53 the drawer with no label text. Removing the thead instead would cost screen-reader users the column headers, so the pattern stays.

N16toolabout G144

check integrity contradicts itself about one element in one run. It warns '[clipped-content] article.doc > div:nth-of-type(2) > table:nth-of-type(1) > thead:nth-of-type(1) @768,375: ... clips 270px of its content behind overflow: hidden - cut-off content unless the oversize is decorative.' It also lists, under exempted, '[text-collision] ... thead:nth-of-type(1) > tr:nth-of-type(1) > th:nth-of-type(1) @768: clipped away by ... thead:nth-of-type(1) (overflow is not visible) - its box still overlaps text below the clip, but no glyph is painted there' (14 such exemptions). So it knows nothing inside the thead is painted, yet still reports the thead as cut-off content. It also exempts the visually-hidden <label> as 'visually-hidden (sr-only) pattern' at 1280/768 but warns on the identical label at 375 inside the closed drawer, and scan scroll never exempts it at all (G92, G93).

N17false-positiveabout G142

Final index.html integrity: 0 fail, one clipped-content warn, the drawer filter's visually-hidden label at 375 (same as N2; S53 shows the open drawer without any label text).

N18false-positiveabout G143

Final migrations.html integrity: 0 fail, one clipped-content warn, the drawer filter's visually-hidden label at 375 (same as N2).

N19false-positiveabout G92

Final scan scroll index.html @1280: horizontal overflow 0px; the one warn is clipped-content on the header filter's visually-hidden label (see N3).

N20false-positiveabout G93

Final scan scroll index.html @375: horizontal overflow 0px; the one warn is the drawer filter's visually-hidden label (see N4). Code blocks that scroll sideways are listed as scroll containers, as intended.

N21false-positiveabout G99

Final scan scroll migrations.html @1280: overflow 0px; the one warn is the header filter's visually-hidden label (see N3).

N22false-positiveabout G100

Final scan scroll migrations.html @375: overflow 0px; the one warn is the drawer filter's visually-hidden label (see N4).

N23false-positiveabout G131

Final scan scroll cli.html @1280: overflow 0px; the one warn is the header filter's visually-hidden label (see N3).

N24false-positiveabout G132

Final scan scroll cli.html @375: overflow 0px; the 10 warns are the nine visually-hidden table header rows and the drawer label (see the G144 notes).

N25false-positiveabout G82

Final focus order, index.html: 47 stops in document order (skip link, brand, filter, toggle, sidebar, article, pager, contents); the 7 skip-row warns are Tab moving between Copy buttons and prose links across paragraphs and code with nothing focusable between them (same reading as N5, re-checked against the report's sequence table).

N26false-positiveabout G86

Final focus order, migrations.html: 3 skip-row warns, all jumps between Copy buttons (and the last Copy to the Previous card) across prose and callouts that hold no controls; no stop is out of order or skipped.

N27false-positiveabout G129

Final focus order, cli.html: 8 skip-row warns, jumps between the Usage blocks' Copy buttons across option tables. Tables are static reference content with no controls, so there is nothing to skip. The stop order follows the reading order.

N28acceptedabout G148

Final scan handlers index.html: the only warn is unprobed-handler-types (the inventory presses nothing). Every registered type is exercised elsewhere in this round: check interactions G145 (Enter on toggle and Copy), verify flow G154 (drawer click and Tab/Shift+Tab/Escape keydown at 375), G157 (filter input, '/', Escape, Enter), G158 (Copy click), G159 (toggle click). No pointer-only control on any page.

N29acceptedabout G149

Final scan handlers migrations.html: unprobed-handler-types only; the same handlers are driven by G146, G155 and the toc flow G141 (contents clicks, hashchange). The pre scroll listeners are the code-scroll fade, exercised by the S41/S43 shots.

N30acceptedabout G150

Final scan handlers cli.html: unprobed-handler-types only; driven by G147 (check interactions) and G156 (drawer flow on cli.html). No pointer-only control.

N31acceptedabout G74

Final check design migrations.html ran with --allow for the header theme toggle (reason in the command, see N13): with only three Copy buttons on this page the toggle reads as a one-off variant of 'button', although it is the icon-button component shared with the Menu and drawer close buttons. Result: 'allowed: 1 button instance(s) declared deliberate', no drift. index.html and cli.html pass without it.

done

Quarry docs: three static pages (Getting started, Migrations, CLI reference) sharing one stylesheet and one classic script. Header with wordmark, v2.4 badge, a sidebar filter (/ to focus, Escape clears, Enter opens the first match) and a theme toggle. Sticky sidebar with aria-current page and section, a scroll-spy 'On this page' column from 1200px, and a focus-trapping drawer below 768px. Code blocks have Copy with a live region and fade where they scroll; Note/Warning callouts; option tables that become cards when the article is narrow. Both themes follow the OS, are overridable and persisted, and can be forced with ?theme. Four rounds, 29 defects: 21 found by eye, 8 by gates (3 of those in my own verify-flow scripts, 1 a misdiagnosis recorded as superseded). The eye found everything about layout quality that no gate measured: an 84-character measure, cramped phase cards and tables at 768 (container queries), inline code torn at hyphens, orphans, an off-palette clear button, and the scroll-spy marking the wrong section after a jump. The gates found a pager name read as 'NextMigrations', 12 container-protrusion fails in hidden table headers, and off-scale spacing. Final gate set clean on all three pages: 0 contrast failures in either theme, no overflow at any width, every flow DONE. The remaining warns are visually-hidden labels and header rows (false positives, noted) and skip-row focus jumps across prose (noted).

R5 · independent review

reviewer
G160exit 0vlmkit check integrity migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)

verdict clean · findings 1 · fails 0 · warns 1 · exempted 2 · viewports 3

output, 15 line(s), 5.0s
vlmkit check integrity
source: migrations.html

verdict: NO DEFECTS, 1 WARN (0 fail, 1 warn, 2 exempted)
  exits 0 — 1 warn(s) did not fail this command. To gate on one: --rule clipped-content=suspect
  1280x800: 8 component(s), ink 7.5%, 104 text block(s)
  768x900: 8 component(s), ink 10.3%, 88 text block(s)
  375x700: 8 component(s), ink 16.3%, 65 text block(s)

Findings:
  ! [clipped-content] div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) @375: div.drawer-search > div:nth-of-type(1) > label:nth-of-type(1) clips 198px of its content behind overflow: hidden — cut-off content unless the oversize is decorative.

Exempted candidates (the tool's call — audit the rule, not the page):
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @1280: visually-hidden (sr-only) pattern — 1px box, text for AT only
  - [text-clipped] div.header-search > div:nth-of-type(1) > label:nth-of-type(1) @768: visually-hidden (sr-only) pattern — 1px box, text for AT only
G161exit 0vlmkit check color index.html

verdict: CONSISTENT (0 finding(s))

verdict consistent · base #ffffff · bodyInk #1b1f24 · linkInk #4b5563 · 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), 37 link(s) in a text flow — from 265 visible box(es)

Palette (by painted area; reported, never judged — see the rejected candidates in the module docs)
  surfaces  3 distinct
    #ffffff   89.0%     5 el  
    #f7f8fa   10.4%    11 el  div.header-inner>div.header-brand>span.version
    #f0f2f4    0.6%    14 el  article.doc>p>code
  ink       6 distinct
    #1b1f24   73.1%    70 el  div.header-brand>a.brand>span.brand-name
    #4b5563   18.5%    35 el  div.header-inner>div.header-brand>span.version
    #0f766e    3.2%    11 el  main#content>article.doc>p.eyebrow
    #646c78    2.4%    24 el  div.header-search>div.search>kbd.search-kbd
    #0b5f58    2.0%     6 el  a.skip-link
    …and 1 more
  marks     4 distinct
    #e3e6ea   90.4%   110 el  header.site-header
    #0f766e    5.4%     7 el  a.skip-link
    #858e99    2.9%     4 el  input#search-header
    #d0d5db    1.4%    12 el  div.header-inner>div.header-brand>span.version
  base #ffffff, body ink #1b1f24, link ink #4b5563 on 32 element(s)
G162exit 0vlmkit check composition cli.html

verdict: COMPOSED (0 finding(s))

verdict composed · proximity 0 · flatSteps 0 · railNearMisses 0

output, 17 line(s), 1.7s
vlmkit check composition
source: cli.html

verdict: COMPOSED (0 finding(s))
  measured: 10 label(s), 3 heading level(s), 176 wide block(s) on 5 left / 4 right rail(s) — from 612 visible box(es)
  1 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     36px / 700   main#content>article.doc>h1
  h2     24px / 700   h2#global-options
  h3     18px / 600   h3#environment-variables
  body 14px, largest 36px (2.57x), heaviest +300 weight

Group gaps: 52px between heading-led groups vs 0px inside them (0x, 11 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.
G163exit 0vlmkit check a11y contrast 'file://$PWD/examples/sites/docs/index.html?theme=dark'

inspected 169 · failures 0

output, 5 line(s), 1.5s
  vlmkit check a11y contrast
  html: file://examples/sites/docs/index.html?theme=dark
  inspected 169 text-bearing element(s)
  ✓ 0 contrast failure(s)
  report: examples/sites/docs/test-results/a11y-contrast/index-9bdde255/report.md
S60

Getting started, light

index.html · 1280×800 · full page, 6 screen(s)

S60 screen 1S60 screen 2S60 screen 3S60 screen 4S60 screen 5S60 screen 6
L60 saw

Desktop, Getting started, light (6 screens). Three columns under a sticky header (cube mark, Quarry, the v2.4 badge, a centred filter field with its "/" key hint, the theme button): a sidebar grouped Guides / Migrations / Reference with the current page on a tinted pill and the current section on a green rule; the article at a ~64-character measure; "On this page" on the right. The contents column follows the reading position screen by screen (What Quarry is → Install Quarry → Your first migration → Review the plan → Run the migration → Next steps). Code blocks carry a Terminal label and a Copy button, the Note callout has a green rule, the four steps hang from numbered circles on a thin rail, prose links are underlined. The sidebar is its own scroller, so "Exit codes" cut at the bottom edge is its scroll affordance, not a clip. Ends with a Next card and the last-updated line.

S61

Migrations, dark, phone

migrations.html?theme=dark · 375×812 · full page, 12 screen(s)

S61 screen 1S61 screen 2S61 screen 3S61 screen 4S61 screen 5S61 screen 6S61 screen 7S61 screen 8S61 screen 9S61 screen 10S61 screen 11S61 screen 12
L61 saw

Phone, Migrations, dark (12 screens — they overlap by ~325px because judge.mjs counted the closed drawer, pinned but translated off-screen, as a top inset; coverage is complete). A compact header with Menu, the wordmark and the theme button; the four phase cards stack with chevrons between them; section headings, inline code and underlined links stay legible on the dark ground; the Note (teal) and Warning (amber) callouts keep their colour coding without relying on it (icon and word). The dry-run block is wider than the screen and its lines stop at the right edge — screen 5 looked cut off at 1x, so I took S63 at 2x. Previous / Next cards and the footer close it.

S62

CLI reference, phone, the Menu drawer

cli.html · 375×812 · click [aria-controls]

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L62 saw

Phone, CLI reference, after Menu: the drawer covers ~85% of the screen with the page dimmed behind it, a "Documentation" title and a close button at the top, the filter field, then the full navigation with CLI reference as the current page and Global options as the current section. The theme button stays visible on the dimmed strip. Nothing overlaps; the list is readable at full size.

S63

the dry-run block at 2x, phone, dark

migrations.html?theme=dark · 375×812 · element #dry-runs ~ .code-block · @2x

1 screen(s), looked at when taken; not kept once the round was done — a round keeps one full-page walk per width.

L63 saw

The dry-run block at 2x, phone, dark: the right edge carries a fade — "writte", "with p", "needed", "(expe" dim into the block's background rather than stopping hard — so the block reads as scrollable, the affordance the builder describes. The prompt, command and flag colours, the green ticks and the amber "!" all hold their contrast.

N32decision

Independent review: integrity on migrations (its warn is the builder's noted sr-only false positive), color, composition on cli and dark-theme contrast re-run green; 20 fresh screens including a 2x close-up found nothing the builder's log missed. Separately, S61's overlap is a judge.mjs defect (an off-screen pinned drawer counted as an inset), to fix in the tool.

done

Reviewer round: gates green on re-run; light desktop, dark phone and the drawer hold up. No defects raised.