Agent docs

0 · Overview and mental model

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

This 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 surfacenot yet written02 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), currently at 0.0.25.
  • Consumer pins differ, and the difference decides whether a rule in these docs is reachable. Walmart pins ^0.0.24 (walmart-mvp/frontend/package.json:21), speedway pins ^0.0.14 (speedway/package.json:36). Under npm semver a caret on a 0.0.x version is a patch pin, so ^0.0.14 resolves to exactly 0.0.14: everything added to the kit after that is unreachable in a clean speedway install. Speedway's dev tree hides this, because its node_modules/@versable-git/ui is a symlink to packages/ui and therefore runs the local 0.0.25. So dev behaviour and a clean install disagree there. Any rule in these docs phrased "as of kit 0.0.N" must be checked against the consuming app's pin before you rely on it, and that especially covers 02 §A9, whose claim that a kit Button is covered for accessibility depends on 0.0.24.
  • 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).
@versable-git/ui · reference, canon, and method, read in place