Agent docs

Kit standard

The checks a consumer's screens are graded on, each runnable by the app's own agent with a pass or fail and no author in the loop.

A screen meets the kit standard when it passes every check below without anyone asking the kit's authors what they meant. That sentence is the whole contract: a check that needs a judgement call from this repo is not a check and does not appear here. The four things a reskin is graded on are the four the owner named together, because they are one judgement about the same screens: the reskin itself (is the kit used as the kit draws things), the categorical layout (is the page composed the way the canon composes pages), visual mess (do neighbours agree, does small text read, is the ramp kept), and the hierarchy of what information and controls are shown (does the prime slot hold the prime thing, is the action set ranked, do counts tell one story).

Two halves. The measured half is bin/kit-standard.mjs, which drives a live app in both themes at two widths (1280 and 390) and exits non-zero on a fix-severity finding. The reading half is a list of yes-or-no questions the grading agent answers per screen with a named piece of evidence, because a script cannot decide which page archetype a screen is or whether an element earns its place. Both halves cite the canon line they enforce and the kit source they hold to, so a finding can be argued with at the rule rather than at the verdict.

The standard extends two instruments that already exist rather than repeating them. canon-check reads a diff for the deviation classes nobody writes a note about (a raw control, a colour literal, a font loaded outside the slots, a shadowed kit export). The console's presentation-audit.mjs reads the rendered page for axes 14 to 35 of its presentation standard (content escaping a container, a machine token where a person's words belong, an inert control with no reason, a stuck skeleton). This bin reads the rendered page for the kit's own composition rules. A reskin runs all three; none of the three sees what another does.

Run it#

From a consumer on 0.3.0 or later, npx kit-standard (every tarball through 0.2.2 omitted bin/). From this repo, the path form:

node packages/ui/bin/kit-standard.mjs --self-test
node packages/ui/bin/kit-standard.mjs \
--base http://127.0.0.1:5109 \
--routes '/dashboard,/jobs,/jobs/*,/files,/files/*,/jobs/new,/admin,/team,/account,/admin/auth,/admin/auth/forge/local,/login' \
--theme-key forge-theme \
--env-file ../versable-forge-v6/.env.local --login-keys FORGE_LOGIN_USER,FORGE_LOGIN_PASSWORD \
--json round-1.json --baseline round-0.json

--widths defaults to 1280,390; 390 is the phone width the canon's narrow rule names, and it replaced 375 in 0.3.0, so a baseline written at 375 does not match: write a fresh one once. --routes-file <path> reads routes from a file, one per line, with blank lines and # comments skipped; the app templates keep theirs in kit.routes.txt.

The second command is the console's. --theme-key is the localStorage key the app's own boot script reads (forge-theme for the console; the bin also sets data-theme after every load, so the theme cannot silently stay light the way a wrong key makes it). --env-file and --login-keys name where the sign-in credential lives; the values are read from that file and never printed. Routes under --login-path (default /login) are audited signed out in a context of their own; everything else signs in first, and a run that cannot sign in refuses rather than auditing a redirect ten times and calling it clean. A record route is written as its list plus /* (/jobs/*): no record URL is knowable ahead of time, so the bin opens the list, follows its first record link (one level deeper, never /new), and audits that page. Findings keep the pattern as their route, so a baseline survives the record changing; the resolved URL rides in the row and the console line.

Exit 0 means no fix-severity finding, 1 means at least one, 2 means the run could not happen (no browser, no app, no credential). --json writes every finding with a stable identity (route, theme, width, check, element recipe) and --baseline diffs two runs by that identity: what appeared, what was fixed, what escalated from note to fix. A count cannot see a row that got worse; the diff can. A baseline row on a route, theme or width the current run did not visit is reported as not run, never as fixed; the console's first round-1 run used seven routes against a ten-route baseline and would otherwise have read three unvisited routes as cleared.

Run --self-test before doubting a finding. It plants one defect of every class on a blank page and asserts the check fires, then plants the same defect hidden and asserts it stays quiet; it also fails if any check in the table below has no fixture that makes it fire. A check nobody has watched fail is not a check.

What a pass is#

A screen passes the measured half when the bin reports zero fix findings for it in both themes at both widths. Notes are read, not blocked on.

A screen passes the reading half when every question below is answered "yes" with its evidence named, or "excused" with the kit defect (next section) that excuses it, or "deviates" with the one-line reason the surface's own doc records (canon 10-overlays.md §A1: deviation is legitimate and never free).

A reskin passes when every screen in the route list passes both halves. The route list is the app's; the console's is the twelve routes above, which are its presentation audit's own itinerary, the job and file record pages (discovered from their lists, quoted in the shell so * is not expanded), and the sign-in page.

The measured half: the checks#

Severity fix holds the exit; note is informational and stays in the report. Every check names the canon line it enforces (cite), the kit source it holds to (holds to), and the self-test fixture that proves it fires.

Reskin: the kit's controls used as the kit draws them#

checkseveritywhat it measuresciteholds to
kit-zero-toolbar-buttonfixan outline neutral button with no icon and a transparent background anywhere in the page body or in an open modal; the kit's zero reads as a placeholder. A coloured outline, a shaded one, an icon button and the pager's own page-number and Previous/Next controls passdocs/design-language/02-buttons-and-actions.md:47button.tsx (variant, shade)
shade-count-in-groupfix at three, note at twoshaded buttons (a quiet variant resting on a wash of its own colour, alpha between the /5 and /20 rungs) sharing one flex or grid row; a control group carries one shade, two with a stated reason, never threedocs/design-language/02-buttons-and-actions.md:49button.tsx (shade), internal/surface.ts (shadeOn)
uppercase-on-controlfixcomputed text-transform: uppercase on a button, a table header or a status pilldocs/design-language/12-typography.md:32kicker.tsx (uppercase lives here and nowhere else)
hand-typed-kickernotean uppercase run that is not kicker-shaped (11px tracked or 12px wide): the idiom re-typed by handdocs/design-language/12-typography.md:23kicker.tsx
kicker-carries-tonenotea kicker-shaped label in a saturated ink; a coloured label reads as a statusdocs/design-language/12-typography.md:35kicker.tsx (text-base-content/65, never a tone)
type-size-off-rampnoterendered text at a size outside 11, 12, 14, 16, 18, 20, 24, 30, 36px, aggregated per sizedocs/design-language/12-typography.md:14the ramp, 12-typography.md §A1
button-underlinefixa button underlined at restdocs/design-language/12-typography.md:46button.tsx (hover feedback is the shade wash)
native-selectnotea native <select> rendered, except the page-size control inside the kit Pagerdocs/design-language/13-forms-and-fields.md:24select.tsx (role=listbox); the exempt shape is pager.tsx:118
table-outside-cardnotea table with no card or modal box around itdocs/design-language/05-cards-and-surfaces.md:25card.tsx, table.tsx

Categorical layout#

checkseveritywhat it measuresciteholds to
page-scrolls-sidewaysfixthe document's scrollWidth exceeds its clientWidth; never overlap, never a page scrolldocs/design-language/06-tables.md:30table.tsx (a minWidth scrolls the table, not the page)
page-title-countfixzero or more than one rendered h1 in maindocs/design-language/12-typography.md:25page-title.tsx
title-after-contentnotea card or table renders before the page title, unless the card holds the title (the auth shell)docs/design-language/09-page-composition.md:26page-title.tsx
two-crumb-trailsfixtwo breadcrumb navs on one page; one location line per pagedocs/design-language/08-navigation-and-shell.md:15breadcrumbs.tsx (presets: trail, topbar crumb)
single-crumb-trailfixa trail in the body with one crumb; the topbar crumb preset (inside header or the top 64px) is one crumb by design and exemptdocs/design-language/08-navigation-and-shell.md:33breadcrumbs.tsx (hideSingle)
nested-cardfix inside an overlay or inside a card that has its own title, note inside an untitled carda card inside a modal box, dialog or aside; a card inside a carddocs/design-language/05-cards-and-surfaces.md:22card.tsx, modal.tsx
prose-past-measurenotea paragraph, list item or definition over 140 characters rendered wider than 76ch of its own fontdocs/design-language/09-page-composition.md:18the canon's 68ch measure, 09-page-composition.md §A2; no kit primitive

Visual mess#

checkseveritywhat it measuresciteholds to
focus-by-border-onlyfixeach control in main focused by script and read: a focus that changes only the control's border colour, or changes nothing, with no outline or shadow appearing on the control or the three frames above it; the ring is the only outline and a hue change is not a ringdocs/design-language/13-forms-and-fields.md:34theme/fields.css (.field-ring, the frame's :focus-within ring), select.tsx (the control carries it)
sibling-scale-mismatchfixtwo controls sharing a flex or grid row and overlapping vertically whose heights differ by more than 4px or type sizes by more than 1px; a control inside a kit Input's frame (the password eye) is part of that field, not a siblingdocs/app-patterns/10-recipe-browsable-list.md:172button.tsx, input.tsx (the size scales)
toolbar-height-mismatchfixa button, input, combobox trigger or applied badge sharing a flex row whose height differs from the row's modal height by more than 4px; an applied Chip (.badge) is exempt when the row centres its itemsdocs/design-language/07-toolbars-and-filters.md:16src/table/facet-bar.tsx
text-under-aafix for grey ink under 18px, note for a tone or larger textWCAG contrast of every rendered text run against its composited backdrop, in the theme being run; colours go through a canvas pixel because daisyUI 5 computes to oklch(). Grey ink under 4.5:1 is the ladder broken; a semantic tone on small text in the light theme is the open token call the canon recordsdocs/design-language/12-typography.md:38kicker.tsx (/65 is the small-text floor), theme/colors.css

A gallery that frames each example in a kit Card marks the frame kit-example-frame (the playground's ExampleCard does); nested-card and table-not-bled read that frame as page chrome, so an example that is itself a card or a table is graded on its own shape. A consumer screen never carries the class.

A region that shows a banned shape on purpose (a showcase demonstrating what not to do) opts out of named checks with data-kit-specimen="check-a check-b". The exemption covers only the checks listed and only what sits inside the element, so a specimen never hides anything it did not name. A consumer screen does not carry it. Kit-default defects that wait for a kit change go in a known list passed with --known <path> ([{check, el, route?, reason}]): a finding that matches an entry is printed and kept in --json with known set, and does not fail the run, so a gate stops new defects while the listed ones wait. An entry that no longer matches anything is reported as unused, which is the cue to delete it.

Every finding carries a law (L1 to L9, or none) and a kind (visual, behaviour, copy, a11y, perf, data, safety) from the defect vocabulary in docs/plan/87-defect-vocabulary.md, in the console line and in --json, and the run ends with a count by law and kind.

Both themes at both widths are not a check but the run itself: every route is visited four times, and a finding names which theme and width it was measured in. A dark-only sign-off cannot come out of this bin.

Hierarchy of information and controls#

checkseveritywhat it measuresciteholds to
count-disagrees-with-rowsfixthe kit's Showing a-b of n line beside a table whose rendered row count is not b-a+1 (or not zero when n is zero), skeletons excludeddocs/design-language/07-toolbars-and-filters.md:31data-table.tsx:182
empty-state-beside-rowsfixan empty-state sentence (No data, Nothing here, No results, No matches) inside a card that also renders rowsdocs/app-patterns/10-recipe-browsable-list.md:144list-state.tsx
id-column-firstnotea table whose first header reads id, uuid or key; the reader's anchor is the namedocs/design-language/06-tables.md:27column-factories.tsx (col.link, col.mono)
row-actions-unrankednotea table with rows carrying more than three actions in the open, per table; a FilterableCell, a copyCells button, a segmented control's own radio buttons, and anything invisible at rest are not actions a person seesdocs/design-language/02-buttons-and-actions.md:27row-menu.tsx
title-levelsnotemore than four distinct heading sizes among h1 to h4 and card titlesdocs/design-language/12-typography.md:25page-title.tsx, card.tsx, kicker.tsx

The laws: classes no single component owns#

These check the laws in docs/design-language/00-overview.md ("The eight laws"). A law check is calibrated, not decreed: a note becomes a fix after a run over real screens shows it is right more often than wrong. Fit and baselines start as notes for that reason.

checkseveritywhat it measuresciteholds to
two-searchesfixtwo search inputs (type=search, role=searchbox, or a placeholder or label starting with Search) inside one card, dialog or page; the signal is said oncedocs/design-language/00-overview.md:103data-table.tsx (Toolbar.Search)
placeholder-ellipsisfixan input or textarea whose placeholder ends in ... or the ellipsis character; a placeholder is a hint, not a prompt to continuedocs/design-language/00-overview.md:105input.tsx
empty-cell-without-nanotea table body cell with no text, icon or control (not a spanning empty state); a missing value reads N/Adocs/design-language/00-overview.md:105column-factories.tsx
narrow-overflows-viewportfixat 480px or narrower: an element whose right edge passes the viewport and sits in no scroller of its own; one finding per outermost offender, ten per pagedocs/design-language/00-overview.md:106app-shell, table.tsx
narrow-row-wrapsfixat 480px or narrower: a header or toolbar row whose children fall onto more than three linesdocs/design-language/00-overview.md:106page-title.tsx, facet-bar.tsx
pointer-not-controlfixa pointer cursor on an element that is not a link, button, label, summary, table row, tabindex element or an ARIA control, and has no such ancestor; the opposite of clickable-without-pointerdocs/design-language/00-overview.md:107button.tsx
clipped-with-free-spacenotetext in an overflow: hidden box that is cut without an ellipsis while its row has 24px or more free; the truncation half is truncate-without-tooltipdocs/design-language/00-overview.md:101src/truncated-text
icon-label-misalignednotethe vertical centre of an icon and of its label, inside one button, tab or link-button, more than 2px apartdocs/design-language/00-overview.md:104button.tsx
row-controls-misalignednotecontrols in one flex row (field frames, selects, buttons) whose centres sit more than 2px apart, compared only where their heights overlapdocs/ui-categorical/alignment.md, rule 3the app's row
quiet-primarynotea form's submit button with no fill and no borderdocs/design-language/02-buttons-and-actions.md:25the app's submit Button
hover-only-controlnotea button, link or menu item that rests at an effective opacity under 0.05 (its own times every ancestor's); a control must be visible without hover. This is the proposed ninth law, and its cite moves to its own row once the canon pen adds itdocs/design-language/00-overview.md:107row-menu.tsx, any opacity-0 group-hover reveal
hit-target-too-smallnotea button, tab, menu item, checkbox, radio, switch or link smaller than 24 by 24px (the WCAG 2.2 floor); a checkbox counts its label, and a link inside running text is exemptdocs/design-language/00-overview.md:107button.tsx, input.tsx

The kit's patterns, where a screen departs from a proven composite#

checkseveritywhat it measuresciteholds to
dropzone-in-bordered-cardfixa dashed dropzone edge inside a card that draws its own border; the compact pattern makes the dashed edge the carddocs/design-language/05-cards-and-surfaces.md:53dropzone.tsx, the compact-dropzone pattern
table-not-bledfixa table inside a card body inset more than 4px from the body's edge, a SchemaForm rows table included; rows meet the card edgesdocs/design-language/05-cards-and-surfaces.md:25table.tsx:45 (BLEED)
empty-table-no-statefixa table with a header and an empty body and no skeleton; a kit Table renders its empty state in one spanning cell, a bare empty body says nothingdocs/design-language/04-loading-and-states.md:21table.tsx (the empty slot), list-state.tsx
auth-card-countnotea signed-out route (login, signup, forgot, reset) with more or fewer than one carddocs/design-language/05-cards-and-surfaces.md:33card.tsx, the auth-card-shell pattern
auth-card-shapenotethat card not centred, wider than 480px, or missing an input or a submitdocs/design-language/05-cards-and-surfaces.md:33card.tsx, the auth-card-shell pattern
search-not-firstnotea search input inside a card with a control rendered to its left on the same row; the search slot leads the toolbardocs/design-language/07-toolbars-and-filters.md:18data-table.tsx (Toolbar.Search leads)
pager-needlessfixa kit pager whose Previous and Next are both disabled: one page, and the bar still renders; the pager hides when there is nothing to pagedocs/design-language/00-overview.md:102pager.tsx, data-table.tsx (the pager hides at one page)
tab-without-iconfixa tab strip (role=tablist) with a tab that has no icon; one finding per strip, naming the bare tabsdocs/design-language/00-overview.md:104tabs.tsx (item.icon)
status-with-copynotea status pill whose own word reappears in a sentence beside it (a pill reading Complete next to "This job is complete"); the pill is the whole statementdocs/design-language/00-overview.md:103status-pill.tsx
truncate-without-tooltipfixtext cut with an ellipsis that shows no tooltip on hover. The probe dispatches the pointer events a mouse would, waits a third of a second, and looks for a role=tooltip or a title attribute; it checks the first 15 truncated runs on a pagedocs/design-language/00-overview.md:101the kit truncation tooltips (status-pill.tsx, chip.tsx, table cells)
clickable-without-pointerfixan enabled button, link, tab, radio, switch, menu item or option whose computed cursor is not the pointer. A disabled or aria-disabled control, a link on the browser's own default and anything hidden pass; one finding per distinct element classdocs/design-language/00-overview.md:107theme/components.css (the base-layer pointer rule)

Copy: the words an app puts on screen#

The kit asks for short, plain words. These three checks are notes for now. They measure length and punctuation only. Whether a sentence is formal and plain is the reading half's call. A placeholder ending in an ellipsis is placeholder-ellipsis, above, and is a fix.

checkseveritywhat it measuresciteholds to
helper-text-longnotehelper text under a field over about 90 charactersthis sectioninput.tsx (bottom)
helper-text-semicolonnotehelper text with a semicolon in itthis sectioninput.tsx (bottom)
tab-label-longnotea tab label of more than three words; a bare count is not a wordthis sectiontabs.tsx (item label)

Kit defects the standard excuses#

A standard that requires a component with a known source defect fails on contact, so each defect below names the check or question it excuses and what a consumer may do until the kit fixes it. A consumer is not graded down for the workaround; it is graded down for a workaround with no row behind it. Rows are in the versable-builder store; the first three are gcp row #492, confirmed in the 0.2.2 source.

defectwhereexcusesuntil fixed, a consumer may
formatRelativeTime has no future branch, so any future time renders "just now". Fixed in 0.3.0 (test/kit-defects-492.test.tsx); the excuse holds for a pin below 0.3.0timestamp.tsxreading question 10 for any expiry, schedule or due columnpass absolute on that column, as the console's Invitations table does, until the pin moves
ConfirmModal hardcodes its title icon to Trash and only for preset="delete". Fixed in 0.3.0 with an Icon prop; the excuse holds for a pin below 0.3.0confirm-modal.tsxreading question 4's icon clause for a non-delete confirmship the confirm without a title icon; do not hand-roll a raw confirm, modal.md bans it
FacetBar treats the caller's first option as the all-sentinel, so a single facet shows an unapplied filter as applied. Fixed in 0.3.0 (allValue is required on a single facet); the excuse holds for a pin below 0.3.0facet-bar.tsxreading question 5 for a single-select facetlist an explicit { value: "all", label: "Any time" } first, as the console's History facet does
The FacetBar count line and the Select's "nothing to choose" row were text-base-content/50, under AA in the light theme, as were the SchemaForm review's "Not set" placeholders at /45. Fixed in 0.3.0, all at the /65 floor; the excuse holds for a pin below 0.3.0facet-bar.tsx, select.tsx, schema-form/review.tsx, schema-form/widgets.tsxtext-under-aa on those runsnothing; the finding is the kit's and does not count against the screen until the pin moves
Input renders its top caption outside the <label>, so the visible caption is not the control's accessible nameinput.tsxreading question 11's caption clausenothing; the kit's, tracked in canon 02-buttons-and-actions.md §A9
The compact-dropzone pattern's own example types text-[15px] for its headlinethe pattern page's sourcetype-size-off-ramp at 15px on a dropzone headlinecopy the pattern as shipped; the ramp question is the kit's to settle

When one of these is fixed, its row closes and the excuse leaves this table in the same commit; a consumer on a pin that predates the fix keeps the excuse until it upgrades.

The reading half: yes or no, per screen#

Each question is answered once per screen with the evidence line it names. "Yes" needs the evidence; "no" is a finding; "excused" names a row from the table above; "deviates" names the sentence in the surface's own doc that records why. There is no fifth answer. Screenshots are taken with the app's own tool at 1280 and 375 in both themes and the answer cites the file path; a screenshot read is described before the question is answered, because a prepared question turns looking into a slower DOM assertion.

Categorical layout

  1. Which archetype is this screen, and does the DOM agree? A reading page caps its width and scrolls the whole page; a work surface goes full width and its table's card owns the scroll. Evidence: the archetype named, and scrollHeight against clientHeight on main and on the table's card. A work surface whose main scrolls is a no. docs/design-language/09-page-composition.md:13.
  2. Is the composition order the canon's? Title, then stat tiles, then cards, sections and tables, with nothing the page drew itself as chrome (no page-owned crumb, sidebar or topbar). Evidence: the order of main's children by name. docs/design-language/09-page-composition.md:26, docs/design-language/08-navigation-and-shell.md:15.
  3. Does every instance of this page type render all its zones? A record page, a list page, an auth card: name the zones and confirm each instance lays them out even when a zone is empty. Evidence: the zone list and one screenshot per instance. The console's presentation standard, axis 22.

Hierarchy of information and controls

  1. Has the action set been identified and ranked? Every action on the surface listed as (icon, title, details, action, colour); two or three in the open as icon-only controls with a rich tooltip; the rest in one menu; anything destructive coloured. Evidence: the inventory, and the menu opened in a screenshot. docs/design-language/02-buttons-and-actions.md:27; the console's presentation standard, axis 26. The icon clause on a non-delete confirm is excused, see above.
  2. Does every visible element name the decision it changes? For each element, one clause. "None, but we know it" is a no, unless the surface is an admin inspect surface whose audience differs. Evidence: the list. This is the class the owner's rejected rebuild failed first (contract/v6/ui-regression-classes.md in the operations workspace, class A). A single-select facet showing an unapplied value as applied is excused, see above.
  3. Is width assigned from hierarchy, not from what the grid permits? The smallest table on the page does not sit in the widest slot; a card that gains nothing from room does not get it. Evidence: each card or table's width beside a one-line reason. The same doc, class B.
  4. Is the count line honest? "N of M" only while a filter is active, the plain count otherwise, and the count and the rows come from one source. Evidence: the line's text in the filtered and unfiltered states. docs/design-language/07-toolbars-and-filters.md:31.
  5. Does the identifying column hold the human identifier? The name cell is the anchor; a machine id sits at the bottom of the action menu, copyable. Evidence: the first column's header and one cell. The console's presentation standard, axis 27.

Visual mess

  1. Was every floating layer opened over content, in both themes? Each dropdown, popover and menu opened while it overlaps rows; its painted surface has an opaque background and a border or shadow, and elementFromPoint at its centre lands inside it. Evidence: the measured values per layer. docs/ui-categorical/patterns.md:15 in this repo, class 1.
  2. Is mono only for identifiers, and colour on text only for status or intent? Evidence: every mono run and every toned run listed with what it is. docs/design-language/12-typography.md:19, docs/design-language/12-typography.md:35. A future timestamp column is excused, see above.
  3. Is every interactive thing a real control with a name? No div with an onClick; every icon-only control has an accessible name; a caption is associated with its field. Evidence: the list of icon-only controls with their names. docs/design-language/02-buttons-and-actions.md:41. The Input caption clause is excused, see above.
  4. Do the loading, empty, filtered-empty and failed states each say what happened, without collapsing into one another? Evidence: one screenshot per state, induced. docs/design-language/04-loading-and-states.md:21, docs/app-patterns/10-recipe-browsable-list.md:144.

Reskin

  1. Does each screen shape follow its pattern? For every kit pattern the inventory names for this shape (the compact dropzone, the auth card shell, the bulk selection bar, the confirm-and-report action, the job title composite), the screen matches the pattern page or its doc records the deviation. Evidence: the pattern route and the screenshot side by side. docs/KIT-INVENTORY.md:118.
  2. Does every mutation settle into feedback owned by something that survives it? A toast or inline state on trigger, success and error, never a component that unmounts on success. Evidence: the handler's three paths, and the toast observed within five seconds. docs/design-language/02-buttons-and-actions.md:35.
  3. Does each table speak one selection language? A checkbox column and a click tint never disagree. Evidence: click a row body, read the tint, the checkbox and the bulk bar together. docs/ui-categorical/patterns.md:175, class 8.
  4. Was every behaviour of the old page written as a row before the reskin, and does each row carry a result? A rebuild that regenerates every decision is pinned to nothing. Evidence: the parity ledger's path and its empty cells, if any. This is the class that let a ruled plan pass its own checks while the owner rejected the page.

Grading rounds#

Two, and the limit is hard. A round is the console lane running the bin over its route list with --json, answering the sixteen questions per screen, and handing both files back with what moved since the last round and what proved it. This repo reads the handback and answers only two things: whether a finding it disputes is a defect in the instrument (in which case the check gets a fixture and the fix in the same commit, never a workaround), and whether a question was answered with its evidence. It does not re-grade the screens by eye; that is the verdict lane this seat was redefined out of.

If the second round fails, the third round is not a grade. It is this file rewritten more precisely at the points the two rounds show were open to reading, and the bin given fixtures for whatever the rounds proved it could not see. A standard that needs its author in the loop to apply is the dispatch tax the fleet charter names, and the remedy is writing, not grading.

A disputed finding is argued at the element recipe, not the verdict: the handback names the check, the el string from the JSON and why the kit shape it flagged is a kit shape. Five of the first run's eleven classes were the instrument being wrong about kit shapes (the topbar crumb, the password eye inside an Input, a FilterableCell counted as a row action, the base-content ink read as saturated, a separator glyph read as small text), and every one became a quiet fixture in --self-test. That is the loop: the instrument is fixed, never defended.

What the measured half cannot see#

A hover state, an opened overlay, a wrong sentence, a missing icon, which archetype a page should be, whether an element earns its place, what a colour means. A clean run is the floor. The reading half exists for exactly these, and the questions are binary so that the answer never needs this repo.

The self-test covers the checks, not the app. It proves each check fires on a planted defect and stays quiet on a hidden one; it does not prove the console has none. The round-0 baseline for the console, measured 2026-09-08 against the running dev console after calibration, is the first real reading: 35 fix findings, every one small grey text under AA in the light theme at the /45, /50, /55 and /60 rungs the ladder removed, and 72 notes across seven shapes. That file is the baseline the first grading round diffs against.

@versable-git/ui · reference, canon, and method, read in place