Agent docs

StatusPill / StatusDot

The house vocabulary for how a record is doing, in two sizes.

The house vocabulary for how a record is doing, in two sizes. StatusPill is the full label for a row, job, or record's discrete state; StatusDot is the compact sibling for tight spaces.

The canon behind it. docs/design-language/03-status-language.md §A1 (03-status-language.md:13, three small-tag species, never interchangeable), §A4 (03-status-language.md:22, a pending mutation is not a status), §A5 (03-status-language.md:25, vocabulary is fixed, small, and shared), and §A8 (03-status-language.md:36, identity colors mark kind, not condition).

When to reach for it#

Reach for StatusPill/StatusDot for STATE: how a thing is doing right now (queued, running, done, failed, needs review). Both share StatusKind (ok | warn | err | info | review | neutral), drawn from the theme's status color pairs.

Do not reach for either to encode SELECTION or NAMING or IDENTITY; the canon calls these four things out as separate vocabularies that are never interchangeable (03-status-language.md §A1). Chip communicates selection (a filter, a removable tag). FieldChip communicates naming (a schema field or a source file). Identity color, a module's or a chain's own color, is informational and never means "healthy" or "unhealthy": Speedway's ModuleBadge and ChainBadge are separate app-local components (speedway/app/components/ModuleBadge.tsx), not built from StatusPill, precisely because painting a module's identity in status colors would make a reader ask what's wrong with it. A pending mutation is not a status either: "a pending cancel is NOT a badge state, the action button carries it as a loading label" (03-status-language.md §A4); see button.md for where that state actually lives.

StatusDot in this kit is the smaller, plainer sibling: kind plus an optional label and pulse, meant for ledgers, tab bars, and steppers. It has exactly one shipped call site in either app (walmart-mvp/frontend/src/pages/Home.tsx:363), without label or pulse. Do not confuse it with walmart-mvp/frontend/src/features/catalog/pipeline.tsx:169's own StatusDot, a same-named local component with a completely different signature (status/accented, not kind/label/pulse) used across Workspace.tsx and CatalogDetail.tsx for the catalog pipeline's five-stage tracker. The two are not interchangeable and importing the wrong one is a real risk: walmart imports the kit's version from @versable-git/ui in some files and its own from ./pipeline in others, by name alone.

Contract#

StatusPill:

  • kind: StatusKind (required). size: sm | md, default md. variant: soft | line | solid | soft-outline, default soft. Only soft and soft-outline have shipped usage in either app; line and solid are implemented (status-pill.tsx:17-55, solid inverts the soft/line pair rather than adding a third tone) but no shipped call site reaches for either.
  • dot renders a small leading dot in the kind's full-strength color; dotPulse pulses it, the "actively working" cue for an in-progress stage. Both have real shipped usage: dotPulse alone on a neutral/info pill for an ingesting or running job stage (speedway/app/components/JobsTable.tsx:150-168), and dot dotPulse={active} together on a stage-progress pill where active is computed from the job's status (walmart-mvp/frontend/src/pages/Jobs.tsx:118-131).
  • count renders a number after the label in tabular nums at reduced opacity. Shipped usage drives an interactive filter toggle, the pill's own count reflecting a live filtered length (walmart-mvp/frontend/src/pages/Jobs.tsx:842-848).
  • Icon and the rest of IconImportProps (iconSize, iconClassName) render a leading icon before the label. iconProps is part of the type (StatusPillProps extends IconImportProps) but status-pill.tsx never destructures or forwards it, so passing it today is a silent no-op; see icon.md.
  • skeleton renders a pill-shaped loading placeholder (a rounded bar sized to size) instead of content. No shipped usage found in either app; both apps skeleton the row or cell a status pill sits in rather than the pill alone.

CodePill, the pill for a module's own codes:

  • code: string (required) and codes: Record<code, { label, description?, kind? }> (required): the map a module's manifest declares for its error_types and review_reasons (gcp/contract/contracts/manifest.md rows 42 and 43: the label is the short human text a pill shows, the description the one-line tooltip, "so an app never invents strings for module codes"). The entry's label is the pill and its description the tooltip.
  • kind?: StatusKind: the tone for every code in the map that does not name its own (err for error types, review for review reasons are the usual calls); an entry's own kind wins; neutral when neither says.
  • A code the map does not declare renders as typed, in mono and the neutral tone, with a "Not a code this module declares" tooltip. A stale map shows rather than being papered over with an invented label. Every other StatusPill prop (size, variant, dot, Icon, count) passes through.

StatusDot:

  • kind: StatusKind (required). label renders text beside the dot; size: xs | sm, default sm. pulse draws an expanding-ring animation behind the dot for an in-progress stage.
  • No skeleton prop exists on StatusDot, unlike StatusPill.

Both pull from the same six-member StatusKind union so that pills, dots, and any future status-driven surface stay visually consistent rather than each inventing its own vocabulary (status.types.ts:5-9); neutral is the no-status baseline, not tied to a dedicated soft/line color pair the way the other five kinds are.

Sanctioned combinations#

CombinationProducesWhere usedWhy
variant="soft-outline" dot dotPulse={active} where active is derived from the record's own status fieldA stage-progress pill whose pulse state is a direct function of data, not a separate flag the caller trackswalmart-mvp/frontend/src/pages/Jobs.tsx:118-131, speedway/app/components/JobsTable.tsx:143-172Canon preset: "soft-outline + leading dot (+ dotPulse when active), roomier px" (03-status-language.md §B)
kind computed through a small lookup map keyed by the domain's own status enum, shared between the table cell and any detail view of the same recordOne status-to-kind mapping reused everywhere that record's status renders, instead of re-deriving it per call siteJobsTable.tsx:141-143 (comment: "Label text is shared with the job page... so a status never reads two ways"), walmart-mvp/frontend/src/pages/Jobs.tsx:344 (STAGE_LOG_LEVEL_KIND[l.level])Canon §A5: "vocabulary is fixed, small, and shared"
count bound to a live filtered array length, wrapped in a clickable element that toggles the filterA status pill doing double duty as a facet toggle with its own live countwalmart-mvp/frontend/src/pages/Jobs.tsx:836-849The pill already carries the kind and the label the filter represents; count keeps the affected row total visible without a second element
kind="neutral" for a benign non-color state (queued, cancelled) rather than reserving neutral for "no status"A visually quiet pill for a state that is real but not urgentJobsTable.tsx:208 (kind="neutral" for queued)neutral has no dedicated soft/line pair and borrows base chrome, which reads as calm rather than alarmed; matches canon's queued/cancelled treatment (03-status-language.md §C)

Banned combinations#

Do not use StatusPill/StatusDot for a pending mutation (a cancel in flight, a save in progress). The canon is explicit that this belongs on the acting button as a loading label, never on the record's status badge (03-status-language.md §A4); see button.md.

Do not use StatusPill where the canon calls for a Chip or FieldChip instead: selecting/filtering values, or naming a field or file. Reaching for StatusPill because it is the nearest small-tag component available produces a pill that visually claims to describe state when it is actually describing selection or naming.

Do not import walmart-mvp's local pipeline.tsx StatusDot expecting the kit's kind/label/pulse props, or vice versa. The two share a name and nothing else; check the import source (@versable-git/ui versus ./pipeline or @/features/catalog/pipeline) before reading or writing a call site that uses StatusDot.

Do not paint an identity concept (a module, a chain, a data source) in StatusKind colors. Identity color marks kind, not condition; the canon states ink for identity never signals health (03-status-language.md §A8). Reach for an app-local identity component instead, as Speedway's ModuleBadge/ChainBadge do.

Before you adopt this#

Five questions to answer before reaching for StatusPill/StatusDot.

  1. Does the shell, a parent layout, or a global provider already render this? Not applicable, StatusPill/StatusDot are inline content; no shell already renders them.
  2. Does this app already ship a local implementation of the same thing? Walmart's pipeline.tsx ships its own unrelated StatusDot; check the import source.
  3. Does this app's kit pin reach the version this component or prop landed in? Not applicable, no version-landed StatusPill/StatusDot prop is noted in this doc.
  4. Does the component derive its own accessible name and keyboard path, or must the call site supply them? Not applicable, these are non-interactive tags with no keyboard path to supply.
  5. Which canon §A rules bind this surface, and which does the composition break? §A4 keeps a pending mutation off the badge; §A8 keeps identity colour off condition.

Travels with#

RenderIcon, for StatusPill's leading Icon; see icon.md.

A per-domain kind lookup map (e.g. JOB_STATUS_KIND, STAGE_LOG_LEVEL_KIND, PART_STATUS_KIND), local to the app, translating that domain's own status enum into StatusKind. The canon frames third-party or domain vocabularies as translated at the boundary through one explicit switch (03-status-language.md §A5); grep for _KIND or _STATUS_KIND in a page before writing a new one.

Tooltip, wrapping a StatusPill used as an interactive filter toggle, to name what clicking it does (Jobs.tsx:836-841).

Snippet#

// walmart-mvp/frontend/src/pages/Jobs.tsx:118-131 (trimmed)
<StatusPill
kind={JOB_STATUS_KIND[r.status]}
size="sm"
variant="soft-outline"
dot
dotPulse={active}
className={onStageClick ? "transition-colors group-hover:border-current" : undefined}
>
{PIPELINE_STAGE_LABELS[i]} ({i + 1}/{PIPELINE_STAGE_LABELS.length})
</StatusPill>
@versable-git/ui · reference, canon, and method, read in place