Agent docs

3 · Status language

Badges, chips, pills, dots, banners, alerts, and the vocabulary rules behind them.

Badges, chips, pills, dots, banners, alerts, and the vocabulary rules behind them. Kit sources: packages/ui/src/status, chip, field-chip, alert. Evidence registers under docs/evidence/20260807-0230-speedway-design-language/ in this repo.

Audience: an agent (or person) choosing badges, chips, pills, or banners.

§A Principles and mental models#

1. Three small-tag species, never interchangeable. StatusPill/StatusDot communicate STATE. Chip communicates SELECTION (always a real button, demoted to span-with-close when removable, for valid HTML). FieldChip communicates NAMING (a schema field or file). The file tone is warm amber because "a file is a thing you can open". Misusing one for another's job is a defect.

2. Six status kinds, two visual weights. StatusKind = ok | warn | err | info | review | neutral, drawn from theme soft/line contrast pairs. StatusDot for tight spaces (tab triggers, ledgers, steppers), with pulse as the "actively working" ring. StatusPill for standalone display: sizes sm/md, variants soft | line | solid | soft-outline. Solid inverts the soft/line pair rather than minting a third tone. Deliberately not daisyUI badge ("padding reads as bloated; a pill is meant to read quiet").

3. Status consolidates. It never dumps raw signals. The house pattern is a ranked collapse of many raw signals into ONE badge with a stated priority order: ModuleStatus ("review beats everything, then a run in flight, then output landed, then finished-empty"), StagePill (error → ingesting → running → setupHold → flagged → done → queued). setupHold is styled as a to-do (primary tint), "held jobs are a to-do, not a wait state."

4. A pending mutation is not a status. "A pending cancel is NOT a badge state, the action button carries it as a loading label" (JobStateBadge.tsx:36-39). Badges describe the record. Buttons describe the in-flight intent.

5. Vocabulary is fixed, small, and shared.

  • Review reasons: exactly three surfaced words, Missing / Invalid value / Conflict, one shared map across row chip, editor, and filter. Content items always surface as "Missing" whatever the internal reason (owner 2026-07-30, stated at every callsite). Severity is two-tier under the three labels: Missing = warn, Invalid/Conflict = err.
  • Decision history: Saved (ok), Edited (info), Dismissed (neutral), Reverted (warn), one map reused between table and history panel.
  • Third-party vocabularies are translated at the boundary into house kinds via one explicit switch (scrape-requests: delivered→ok, error→err, cancelled→neutral, everything in flight→warn).

6. Non-trivial status explains itself. Every rich badge carries a tip stating why ("A module left a value for a person to settle"), and count-bearing pills use tabular nums. A suggestion's presence is its own layered cue: a slow pulsing dot (2.8s, deliberately calmer than default) beside the reason pill, tooltipped with the suggested value.

7. Banners: tone separates error from note. The reason precedes what it explains. IngestOutcomeBanner paints rejected files error-tinted and no-data-rows files neutral, "the banner must not paint the two the same colour." Banners render ABOVE the tables they explain: "'Failed' and '0 parts' are both unreadable without it." Kit Alert (info/success/warning/error/neutral) is the inline callout. Toasts are for transient events. A successful action may still carry a non-blocking warning string.

8. Identity colors mark kind, not condition. ModuleBadge (quiet ring chip + identity dot) is purely informational. ChainBadge wears the same look but links, with stopPropagation inside clickable rows. Identity ink never signals health.

§B Presets#

Each row names the situation, the composition that reaches it, and the showcase example that renders it, so this table is the layer between a principle above and a contract doc below.

SituationPresetReaches it, and where it renders
Table status cellcol.status factory → StatusPill from a row mappercol.status(mapper) in the column list; the data-table gallery (apps/playground/src/app/components/data-table/gallery.tsx:174)
Stage/state pillsoft-outline + leading dot (+ dotPulse when active), roomier pxStatusPill with dot and dotPulse={active}; the status gallery's live pill row (apps/playground/src/app/components/status/gallery.tsx:47)
Tight statusStatusDot sm, pulse for in-flightStatusDot kind size="sm" pulse; the compact dot table (apps/playground/src/app/components/status/examples/status-dot.tsx:19)
Needs-review countamber bold count or warn pill; green 0 when cleara known-only count on a rail item, nothing until the number is real (apps/playground/src/app/patterns/sidebar-anatomy/example.tsx:31)
Error vs note bannerCard with error/25 + error/5 tint vs base-300 + base-200/40no showcase example yet
Suspended / hard statesAlert tone="error", customer copy plain and action-oriented; staff copy technicalAlert tone="error" with a plain string child; the alert gallery (apps/playground/src/app/components/alert/gallery.tsx:19)
One-time secret fallbackAlert warning + readonly Input with select-all-on-focusno showcase example yet
Member state beside a role controlStatusPill info sm for a pending invite; the row's own colour untouchedStatusPill kind="info" size="sm" next to the role Select (apps/playground/src/app/patterns/member-row-inline-role/example.tsx:103)
Event tone in a feedStatusDot per event on a connector line, grouped by dayone StatusDot per row, tone from the event kind (apps/playground/src/app/patterns/recent-activity-feed/example.tsx:68)

§C Use cases through Speedway#

  • JobStateBadge: queued/Running share warn kind, distinguished by icon + label only, with done=ok, cancelled=neutral, error=Failed/err.
  • Review reason chips + the has-suggestion pulse dot. Log's decision pills.
  • Content fields: unfilled renders "Dismissed" (user) or "Missing" (anything else), the review queue's word by ruling.
  • Nav badges: plain numeric spans. Amber only when attention is owed. No badge at all when the count is not loaded.
  • Auth: deliberately uninformative status ("Incorrect email or password", unconditional "If an account exists..." with constant-time verification), a security constraint on copy, not a tone choice.
  • Known deviations to keep bounded: loadsheet readiness uses raw daisyUI badges. Contentgen preview uses raw badge-warning/error cells.
@versable-git/ui · reference, canon, and method, read in place