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).
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.
- 5rounds
- 163gate runs, 6 failing
- 235screens in 63 shots, 93 kept
- 63looks recorded
- 21defects found by eye
- 8defects found by a gate
- 16gate false positives
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
| Defect | Found by | What | Fix | Checked by |
|---|---|---|---|---|
| D1 | eye L1 on S1 |
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 |
| D2 | eye L1 on S1 |
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 |
| D3 | eye L3 on S3 |
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 |
| D4 | eye L2 on S2 |
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 |
| D5 | eye L2 on S2 |
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 |
| D6 | eye L2 on S2 |
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 |
| D7 | eye L4 on S4 |
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 |
| D8 | eye L3 on S3 |
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 |
| D9 | eye L4 on S4 |
'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 |
| D10 | eye L4 on S4 |
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 |
| D11 | eye L6 on S6 |
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 |
| D12 | eye L6 on S6 |
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 |
| D13 | eye L6 on S6 |
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 |
| D14 | eye L7 on S7 |
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 |
| D15 | gate G15 |
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 |
| D16 | gate G15 |
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 |
| D17 | gate G19 |
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 |
| D18 | gate G3 |
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 |
| D19 | gate G27 |
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 |
| D20 | eye L12 on S12 |
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 |
| D21 | gate G35 |
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 |
| D22 | gate G32 |
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 |
| D23 | eye L20 on S20 |
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 |
| D24 | eye L32 on S32 |
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 |
| D25 | eye L33 on S33 |
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 |
| D26 | gate G114 |
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 |
| D27 | eye L46 on S46 |
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 |
| D28 | eye L47 on S47 |
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 |
| D29 | eye L56 on S56 |
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
buildervlmkit check integrity index.htmlverdict: 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
vlmkit check copy index.html --manifest copy.txtmissing 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.
vlmkit check design index.htmlverdict: 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..callout padding, .doc ul padding-leftcheck 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.
vlmkit check composition index.htmlverdict: 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.
vlmkit check color index.htmlverdict: 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)vlmkit check a11y contrast 'index.html?theme=light'output, 1 line(s), 0.9s
error: file not found: examples/sites/docs/index.html?theme=light
vlmkit check a11y contrast 'index.html?theme=dark'output, 1 line(s), 0.8s
error: file not found: examples/sites/docs/index.html?theme=dark
vlmkit check a11y focus index.htmlsteps 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=suspectvlmkit check a11y touch index.htmllevel 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
vlmkit 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
vlmkit 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
vlmkit scan scroll index.htmlcontainers 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.
vlmkit scan scroll index.html --viewport 375x740containers 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.
vlmkit scan handlers index.htmlregistrations 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.
vlmkit check interactions index.html --max-elements 80elements 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✓
.pager-linkPager links' accessible name runs the two spans together: 'NextMigrations' (G15 inventory, also the focus report). A screen reader reads one made-up word.
quarry.js copy fallbackEvery 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.
vlmkit check breakpoints index.html --sweepbreakpoints 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.
vlmkit check scroll index.htmlpageScrolled 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.
vlmkit check motion index.htmlactiveAnimations 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.
vlmkit verify flow index.html --flow flows/drawer.jsonverdict: 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 focusedflows/drawer.json step 5The 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.
vlmkit verify flow index.html --flow flows/filter.jsonverdict: 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 hiddenvlmkit verify flow index.html --flow flows/copy.jsonverdict: 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"vlmkit verify flow index.html --flow flows/theme.jsonverdict: 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=falsevlmkit verify flow migrations.html --flow flows/toc.jsonverdict: 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=1index.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.
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.
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.
index.html step 2, quarry plan outputPlan 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.
index.html · 768×1024 · full page, 5 screen(s)
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.
.sidebar @768-1199At tablet width the 240px sidebar breaks 'Rolling back a migration' onto two lines, the only wrapped nav item.
.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.
footer .footer-aboutFooter 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.
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.
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.
.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.
.steps .step @375On 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.
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.
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.
.doc :not(pre) > codeInline 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).
.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.
.tocContents column wraps long h3 entries leaving single words behind ('Replication lag keeps / growing', 'Permission denied on the / source').
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.
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.
cli.html · 1280×800 · full page, 8 screen(s)
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.
.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.
cli.html table.optionsOption 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.
#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.
cli.html · 375×812 · full page, 15 screen(s)
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.
cli.html Environment variables table @375At 375 the environment-variables table stays two columns: nowrap names take ~190px, descriptions get ~130px and break into three lines, inline codes tear ('--' / 'config').
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Rule .doc h2 + h3 { margin-top: 1.25rem } so a section heading sits closer to its first subsection than subsections sit to each other.
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.
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.
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.
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.
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).
A real space between the pager's direction span and title, so the link's name is 'Next Migrations' / 'Previous Getting started'.
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.
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.
Callout padding 0.875rem 1rem (14/16px, on the page's own steps) and list indent 1.5rem (24px).
R2 · fix pass
buildervlmkit check interactions index.html --max-elements 80elements 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✓
vlmkit check design index.htmlverdict: 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.vlmkit verify flow index.html --flow flows/drawer.jsonverdict: 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 hiddenvlmkit verify flow index.html --flow flows/copy.jsonverdict: 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: )flows/copy.jsonThe 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.
vlmkit check interactions index.html --max-elements 80elements 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✓
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.
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.
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.
vlmkit verify flow index.html --flow flows/copy.jsonverdict: 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"check interactions now names the pager link 'Next Migrations' (two words).
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.
check design: 'No design drift detected', the scale-outlier line (15px, 18px, 22px) is gone.
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.
index.html · 1280×800 · full page, 6 screen(s)
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.
index.html · 768×1024 · full page, 6 screen(s)
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).
index.html · 375×812 · full page, 11 screen(s)
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.
.doc p, .doc li, .callout pProse paragraphs end on single-word orphan lines at every width: 'nothing.', 'copied.', 'control.' (S12-6/7), 'option.' (S11-5), 'it.' and 'read.' (S10-3).
First body line is 71 characters at 1280 (S10-1), header filter still on the article's left rail.
Plan output columns align (S10-4).
Lede 4 lines on mobile (S12-1), 3 on tablet and desktop (S11-1, S10-1).
'Rolling back a migration' on one line at 768 (S11-1).
Overflowing blocks fade at the continuing edge at 768 (S11-3/S11-4) and 375 (S12-3/S12-4).
Balanced two-line tagline, mark on the first line, Last updated aligned (S11-5); same at 1280 (S10-6) and 375 (S12-11).
Step content full width on phones, disc beside the h3 only (S12-5..S12-8).
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Flags and multi-word codes stay whole on phones (S14, S15) and desktop (S13).
Troubleshooting h2 ~20px above its first h3 (S13).
Contents entries fit or split evenly (S13).
Command headings are plain monospace (S16).
plan and run tables share x=375/593/719 (S16).
Sidebar scrolled itself to show the marked Exit codes (S17).
Env table stacks into cards on phones (S18).
No single-word last lines at 375 (S19, S14).
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.
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).
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
buildervlmkit check integrity migrations.htmlverdict: 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
vlmkit check copy migrations.html --manifest copy.txtmissing 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.
vlmkit check design migrations.htmlverdict: 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.footer .footer-inner margin-topcheck 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.
vlmkit check composition migrations.htmlverdict: 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.
vlmkit check color migrations.htmlverdict: 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)vlmkit check integrity cli.htmlverdict: 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
cli.html table.stack thead @37512 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.
vlmkit check copy cli.html --manifest copy.txtmissing 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.
vlmkit check design cli.htmlverdict: 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.vlmkit check composition cli.htmlverdict: 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.
vlmkit check color cli.htmlverdict: 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)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.
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%)).
Footer top margin 2.5rem -> 2.25rem (36px), on the page's own step.
vlmkit check integrity cli.htmlverdict: 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
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.
vlmkit 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.migrations.html design run: no scale-outlier line any more (G32 had the 40px footer margin).
vlmkit 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
vlmkit 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
vlmkit check a11y touch migrations.htmllevel 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
vlmkit check a11y focus migrations.htmlsteps 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=suspectvlmkit 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
vlmkit 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
vlmkit check a11y touch cli.htmllevel 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
vlmkit check a11y focus cli.htmlsteps 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=suspectvlmkit scan scroll migrations.htmlcontainers 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.
vlmkit scan scroll migrations.html --viewport 375x740containers 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.
vlmkit scan handlers migrations.htmlregistrations 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.
vlmkit check interactions migrations.html --max-elements 80elements 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✓
vlmkit check breakpoints migrations.html --sweepbreakpoints 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.
vlmkit check scroll migrations.htmlpageScrolled 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.
vlmkit scan scroll cli.htmlcontainers 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.
vlmkit scan scroll cli.html --viewport 375x740containers 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.
vlmkit scan handlers cli.htmlregistrations 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.
vlmkit check interactions cli.html --max-elements 80elements 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✓
vlmkit check breakpoints cli.html --sweepbreakpoints 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.
vlmkit check scroll cli.htmlpageScrolled 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.
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.
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.
.search-input::-webkit-search-cancel-buttonThe 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
Clear button is a thin grey x matching the magnifier in light (S26) and dark (S27).
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.
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.
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.
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".
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.
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.
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.
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.
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.
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.
migrations.html .phases @768-~1000At 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.
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.
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.
cli.html table.options @768-~1000At 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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
2 x 2 phases at 768 (S37), 4 across at desktop (S36), stack on phones (S38).
Option tables are cards at 768 (S39).
vlmkit check breakpoints cli.html --sweepbreakpoints 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.
vlmkit check integrity migrations.htmlverdict: 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
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.
vlmkit verify flow index.html --flow flows/filter.jsonverdict: 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=pageskip 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.
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.
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.
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
buildervlmkit check integrity index.htmlverdict: 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
vlmkit check copy index.html --manifest copy.txtmissing 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.
vlmkit check composition index.htmlverdict: 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.
vlmkit check color index.htmlverdict: 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)vlmkit check design index.htmlverdict: 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.vlmkit check integrity migrations.htmlverdict: 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
vlmkit check copy migrations.html --manifest copy.txtmissing 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.
vlmkit check composition migrations.htmlverdict: 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.
vlmkit check color migrations.htmlverdict: 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)vlmkit 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.vlmkit check integrity cli.htmlverdict: 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
vlmkit check copy cli.html --manifest copy.txtmissing 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.
vlmkit check composition cli.htmlverdict: 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.
vlmkit check color cli.htmlverdict: 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)vlmkit check design cli.htmlverdict: 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.vlmkit 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
vlmkit 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
vlmkit check a11y focus index.htmlsteps 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=suspectvlmkit check a11y touch index.htmllevel 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
vlmkit 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
vlmkit 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
vlmkit check a11y focus migrations.htmlsteps 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=suspectvlmkit check a11y touch migrations.htmllevel 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
vlmkit 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
vlmkit 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
vlmkit check a11y focus cli.htmlsteps 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=suspectvlmkit check a11y touch cli.htmllevel 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
vlmkit scan scroll index.htmlcontainers 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.
vlmkit scan scroll index.html --viewport 375x740containers 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.
vlmkit scan handlers index.htmlregistrations 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.
vlmkit check interactions index.html --max-elements 80elements 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✓
vlmkit check breakpoints index.html --sweepbreakpoints 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.
vlmkit check scroll index.htmlpageScrolled 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.
vlmkit check motion index.htmlactiveAnimations 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.
vlmkit scan scroll migrations.htmlcontainers 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.
vlmkit scan scroll migrations.html --viewport 375x740containers 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.
vlmkit scan handlers migrations.htmlregistrations 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.
vlmkit check interactions migrations.html --max-elements 80elements 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✓
vlmkit check breakpoints migrations.html --sweepbreakpoints 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.
vlmkit check scroll migrations.htmlpageScrolled 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.
vlmkit check motion migrations.htmlactiveAnimations 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.
vlmkit scan scroll cli.htmlcontainers 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.
vlmkit scan scroll cli.html --viewport 375x740containers 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.
vlmkit scan handlers cli.htmlregistrations 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.
vlmkit check interactions cli.html --max-elements 80elements 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✓
vlmkit check breakpoints cli.html --sweepbreakpoints 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.
vlmkit check scroll cli.htmlpageScrolled 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.
vlmkit check motion cli.htmlactiveAnimations 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.
vlmkit verify flow index.html --flow flows/drawer.jsonverdict: 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 hiddenvlmkit verify flow cli.html --flow flows/drawer.jsonverdict: 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)flows/drawer.json step 2The 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.
vlmkit verify flow index.html --flow flows/filter.jsonverdict: 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=pagevlmkit verify flow index.html --flow flows/copy.jsonverdict: 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"vlmkit verify flow index.html --flow flows/theme.jsonverdict: 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=falsevlmkit verify flow migrations.html --flow flows/toc.jsonverdict: 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=1Edited 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.
vlmkit verify flow index.html --flow flows/drawer.jsonverdict: 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 hiddenvlmkit verify flow migrations.html --flow flows/drawer.jsonverdict: 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 hiddenvlmkit verify flow cli.html --flow flows/drawer.jsonverdict: 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 hiddenDrawer flow DONE 6/6 on cli.html (G121) as well as index (G119) and migrations (G120).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
.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.
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.
text-wrap: pretty extended from .doc p / li / lede to .doc td, so table cells and the stacked cards rebalance their last lines too.
vlmkit check integrity cli.htmlverdict: 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
vlmkit check copy cli.html --manifest copy.txtmissing 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.
vlmkit check design cli.htmlverdict: 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.vlmkit check composition cli.htmlverdict: 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.
vlmkit check color cli.htmlverdict: 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)vlmkit 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
vlmkit 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
vlmkit check a11y focus cli.htmlsteps 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=suspectvlmkit check a11y touch cli.htmllevel 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
vlmkit scan scroll cli.htmlcontainers 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.
vlmkit scan scroll cli.html --viewport 375x740containers 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.
vlmkit scan handlers cli.htmlregistrations 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.
vlmkit check interactions cli.html --max-elements 80elements 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✓
vlmkit check breakpoints cli.html --sweepbreakpoints 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.
vlmkit check scroll cli.htmlpageScrolled 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.
vlmkit check motion cli.htmlactiveAnimations 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.
vlmkit verify flow cli.html --flow flows/drawer.jsonverdict: 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 hiddenvlmkit check integrity index.htmlverdict: 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
vlmkit check integrity migrations.htmlverdict: 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
cli.html · 1280×800 · full page, 9 screen(s)
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.
cli.html · 375×812 · full page, 15 screen(s)
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.
Rebalanced shared grid; descriptions mostly two lines, defaults fit (S48).
No single-word endings in the stacked cards (S49).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
quarry.js scroll-spyJumping 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.
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.
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.
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.
vlmkit verify flow migrations.html --flow flows/toc.jsonverdict: 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=truestate: 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.
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.
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.
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).
Contents marks the jumped-to section on load with a hash (S58, S59); G141 checks the same after following contents links.
vlmkit check integrity index.htmlverdict: 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
vlmkit check integrity migrations.htmlverdict: 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
vlmkit check integrity cli.htmlverdict: 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
vlmkit check interactions index.html --max-elements 80elements 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✓
vlmkit check interactions migrations.html --max-elements 80elements 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✓
vlmkit check interactions cli.html --max-elements 80elements 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✓
vlmkit scan handlers index.htmlregistrations 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.
vlmkit scan handlers migrations.htmlregistrations 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.
vlmkit scan handlers cli.htmlregistrations 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.
vlmkit check scroll index.htmlpageScrolled 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.
vlmkit check scroll migrations.htmlpageScrolled 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.
vlmkit check scroll cli.htmlpageScrolled 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.
vlmkit verify flow index.html --flow flows/drawer.jsonverdict: 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 hiddenvlmkit verify flow migrations.html --flow flows/drawer.jsonverdict: 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 hiddenvlmkit verify flow cli.html --flow flows/drawer.jsonverdict: 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 hiddenvlmkit verify flow index.html --flow flows/filter.jsonverdict: 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=pagevlmkit verify flow index.html --flow flows/copy.jsonverdict: 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"vlmkit verify flow index.html --flow flows/theme.jsonverdict: 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=falseFinal 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.
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).
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).
Final migrations.html integrity: 0 fail, one clipped-content warn, the drawer filter's visually-hidden label at 375 (same as N2).
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).
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.
Final scan scroll migrations.html @1280: overflow 0px; the one warn is the header filter's visually-hidden label (see N3).
Final scan scroll migrations.html @375: overflow 0px; the one warn is the drawer filter's visually-hidden label (see N4).
Final scan scroll cli.html @1280: overflow 0px; the one warn is the header filter's visually-hidden label (see N3).
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).
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).
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.
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.
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.
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.
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.
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.
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
reviewervlmkit check integrity migrations.htmlverdict: 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
vlmkit check color index.htmlverdict: 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)vlmkit check composition cli.htmlverdict: 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.
vlmkit 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
Getting started, light
index.html · 1280×800 · full page, 6 screen(s)
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.
Migrations, dark, phone
migrations.html?theme=dark · 375×812 · full page, 12 screen(s)
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.
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.
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.
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.
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.
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.
Reviewer round: gates green on re-run; light desktop, dark phone and the drawer hold up. No defects raised.




























































































