Agent docs

0 · Overview and mental model

The UI decisions, patterns, and behaviors proven in Speedway, codified.

The doc set codifies the UI decisions, product patterns, intents, and behaviors proven in Speedway, the flagship app of the shared Versable design language. The ui-kit (packages/ui) is the vocabulary. Speedway's usage is the grammar. Both are captured here so any Versable app (walmart-mvp is the second consumer) can adopt the language without re-deriving it.

Audience: an agent (or person) orienting before any UI work in a Versable app.

Each doc has the same shape:

  • §A Principles and mental models: the heuristics, the "how to decide," the why.
  • §B Presets and how-to: the concrete patterns to reach for.
  • §C Use cases through the app: exhaustive, grouped by surface, with file:line evidence.

The doc map#

DocTraitLayer
01-foundations.mdcolor, type, spacing, radius, shadow, responsivenesselementary
02-buttons-and-actions.mdbutton variants, shades, icons, tooltips, wordingelementary
03-status-language.mdbadges, chips, banners, alerts, linkselementary
04-loading-and-states.mdskeletons, spinners, pending bars, empty/error stateselementary
05-cards-and-surfaces.mdcards, framed sections, stat tiles, spacingcomposite
06-tables.mdthe data-table model: selection, peek, expansion, tintscomposite
07-toolbars-and-filters.mdtoolbar presets, facets, filter chips, searchcomposite
08-navigation-and-shell.mdsidebar, topbar, breadcrumbs, page title, shellcomposition
09-page-composition.mdwidths, padding, section rhythm, page archetypescomposition
10-overlays.mdpeek panels, modals, dropdowns, toastscomposition
11-motion.mdanimations, transitions, entrance conventionscross-cutting
12-typography.mdthe ramp, roles, title sets, the ink ladder, mono, tracking, truncationcross-cutting

Start from what you were asked for, not from a trait#

The table above is organised by trait, which is right for keeping each rule in exactly one place, and wrong for the moment an agent is handed a request. If you were asked to build something, start here instead. Each recipe carries a bundle checklist: every row is a thing the request brings along whether or not anyone said it, with its default, the rule that governs it, and when you may deviate.

Read the recipe's bundle BEFORE the trait docs. The bundle is what makes completeness checkable rather than inferred, and incomplete assembly is the most corrected failure in both apps.

You were asked forRecipeTraits it pulls in
a list, table, queue, index, "show me all the X"app-patterns/10-recipe-browsable-list.md06 07 04 05 09 02
a record detail, preview, drill-in, "click a row and see it"app-patterns/11-recipe-record-detail.md10 06 04 03 02
a form, settings, or config surfaceapp-patterns/16-recipe-module-settings-form.md02 04 05 10
a dashboard or stat overviewnot yet written05 09 03 04
an import or upload flownot yet written02 04 10
an admin or diagnostic surfacenot yet written06 09

The unwritten rows are listed on purpose. An agent that finds no recipe for its request should know the gap is known rather than assume the request carries no bundle, and should fall back to the nearest written recipe plus the trait docs its row names.

The core mental models#

1. Three token layers. Only the middle one knows about themes. Layer 0 is the kit palette: full OKLCH ramps (50 to 950) per hue family, "one ramp, not anchors with filler" (packages/ui/theme/colors.css:14-21). Layer 1 is the kit semantic slots (primary, success, paper, surface, ink, muted, hairline, field dials, status soft/line pairs). These are defined once per theme, in versable-light and versable-dark. Layer 2 is an app's bespoke aliases (Speedway's app.css:11-48): values that point into the kit, with names kept so rules recolor in one place. The hard-won rule: app tokens must read the semantic layer, never the palette layer. Only the semantic layer flips per theme (app.css:50-59 documents the bug that taught this).

2. Identity is not status. Colors that say what a thing is (part-type, normalize, content, attribute, source, scrape, file, fitment identity tokens) are one vocabulary. Colors that say how it is doing (ok/warn/err/info/review) are another. "WHAT a thing is ... never how it's doing. A status colour can't say this" (colors.css:231-236). Never encode state in an identity color or vice versa.

3. The kit is generic. The app supplies meaning. Kit components take explicit props (active booleans, NavGroup[], slot nodes) rather than inferring from context. Speedway centralizes meaning in single-source maps. PAGE_META is the canonical example: the sidebar, page titles, and tooltips all read the same entry, so icon and label can never drift (app/lib/page-meta.ts:5-8).

4. Feedback is local and layered, never global dimming. A pending navigation shows a spinner on the clicked link (delayed 120ms) plus a topbar sweep bar (delayed 90ms). Content is never dimmed: "The sweep bar says 'working'; the route's skeleton says 'here is what is arriving'" (speedway app.css:1093-1102). This generalizes: loading feedback belongs to the thing that is loading.

5. Deliberate absence is a decision, not a gap. Speedway is full of dormant-not-deleted code with dated deprecation comments and revival instructions (workspace switcher, schemas nav, files pages). Owner rulings are recorded verbatim, in place, with dates. When these docs quote a ruling, the quote is binding. The pattern generalizes: record the ruling where the code is, and keep the alternative revivable.

6. Accessibility numbers are measured, not vibes. Contrast decisions cite measurements: solid warning moved to amber-600 under white ink because "white on amber-500 reads 2.05:1" and the owner wanted white like success (colors.css:168-174). Dark-mode muted text is a 65% mix because it is "the first step clearing 4.5:1 at 12-14px" (speedway app.css:67-69). Copy that discipline, not just the values.

7. Make the wrong state unrepresentable before documenting the ban. Where a misuse can be made impossible, the API change beats the Banned entry: PageSkeleton requires layout rather than defaulting to a guess about a page nobody looked at (packages/ui/src/page-skeleton/page-skeleton.types.ts:12), the split Button derives its caret's accessible name rather than asking every caller to remember one (packages/ui/src/button/button.tsx:264-265), and presets carry ruled combinations so a caller picks a name instead of reassembling the rule. A Banned entry is the fallback for what the API cannot reach, never the first tool.

Authority and provenance#

  • Kit source: packages/ui/src and packages/ui/theme in this repo (versable-builder). The owning statement of the kit version and every consumer pin is the first paragraph of the repo's CLAUDE.md; the numbers below are copied from it on 2026-10-24.
  • Consumer pins differ, and the difference decides whether a rule in these docs is reachable. The kit is at 0.5.0. Walmart pins ^0.2.2 in walmart-mvp/frontend/package.json and floats within 0.2.x, speedway pins an exact 0.2.2, and the forge console pins an exact 0.5.0. Under npm semver a caret on a 0.x version never crosses the minor, so walmart and speedway reach nothing added after 0.2.x until each takes a deliberate upgrade. Check any rule phrased "as of kit 0.N" against the consuming app's pin before you rely on it.
  • Usage evidence: Speedway at ~/Code/Versable/speedway, as of its 2026-08-06 customer launch.
  • Owner rulings quoted in these docs are verbatim from code comments. The dated ones (2026-07-07 palette round, 2026-07-17 identity placement, 2026-07-21 doc-29 workspace hiding, 2026-07-29 crumb and counting rules, 2026-08-03 nav and theme gating, 2026-08-05 card-follows-tile) carry the most weight.
  • Raw inventories behind these docs: docs/evidence/20260807-0230-speedway-design-language/ (five per-surface registers with file:line for every claim).

The eight laws#

The rules in the trait docs are instances. These eight are the classes behind them: each one was broken somewhere new on every screen swept in the 2026-09 and 2026-10 review rounds, because no single component owns it. Read them before any screen, and when a trait doc is silent, the law still binds.

LawWhat it saysTrait doc that owns itMeasured today by
Fit, then truncate, never clipContent gets its space first. If it must shrink, it truncates with the full value on hover. Nothing is hard-clipped while free space sits nearby.12 typography (truncation), 06 tablesclipped-with-free-space, page-scrolls-sideways
Show only what has something to sayA control or value with nothing to report hides or fades: a one-page pager, a "No data", a zero count, an inline clock.04 loading and statesnothing yet
One signal, said onceNo element repeats what another already says: two searches on one table, a dot beside an icon, a count stated twice.03 status language, 07 toolbarstwo-searches
Things beside a label share its lineIcons, counts and tips align to the text they sit next to.12 typographyicon-label-misaligned, sibling-scale-mismatch, toolbar-height-mismatch (height only)
Machine values stay exact, human text stays humanIds are never recased. Placeholders never end in an ellipsis. Empty values say N/A.12 typography (mono), 14 prop vocabularyplaceholder-ellipsis, empty-cell-without-na
Every composite declares its narrow behaviourHeaders, toolbars, tables, footers and the sidebar rail each say what happens at 390px.09 page compositionnarrow-overflows-viewport, narrow-row-wraps, page-scrolls-sideways
Every action answersA click shows pending, then a result or an error. The pointer appears only where a click does something.02 buttons and actionspointer-not-control, clickable-without-pointer
Both themes are first-classEvery colour pair holds contrast in light and dark.01 foundationstext-under-aa, run in both themes

A law with "nothing yet" is checked by eye and by the reading questions in packages/ui/docs/KIT-STANDARD.md. A component doc names the laws it enforces in its own "Laws it enforces" list, so a fix to a default can say which class it closes. The findings behind each law, with the owner's verbatim rulings, are in docs/plan/85-ui-sweep-pilot-findings.md; the laws were drafted on 2026-10-23 and the owner may reword one.

The ninth and tenth laws#

Two more were ruled after the eight above and bind in the same way. The ninth was ruled at sitting 1 on 2026-10-23 (D8, "yes, and file both themes"). The tenth, anatomy, was ruled on 2026-10-24 (E1). They carry the ids L9 and L10 in docs/app-patterns/18-defect-vocabulary.md, which continues plan 86's L1 to L8.

LawWhat it saysTrait doc that owns itMeasured today by
Controls are visible without hoverA control that matters is on screen at rest. Hover may add emphasis, never reveal the control. Touch, keyboard and agent users cannot find a hover-only control.02 buttons and actions, 06 tables (row actions)hover-only-control, hit-target-too-small (notes)
The same thing looks the sameOne kind of thing has one anatomy wherever it appears: card headers, form labels, surface edges, small-label species and state frames. A second look for the same thing is a defect.03 status language (species), 05 cards and surfaces (headers, edges), 04 loading and states (frames), 13 forms and fields (labels)nothing yet

The tenth law is the class behind a table not reaching the card edge, a card header unlike its siblings, a card nested in a card, a form with labels at two sizes, and a status pill that looks like an action. When a screen has two looks for one thing, pick the one the trait doc names and delete the other.

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