Context
Implementing leaf for the D2 design ruling on epic #14805 (ruling) — the last of the three design-authority items (#15493/D4 and #15500/D3 shipped). Origin: #14619's audit, re-derived from source before ruling.
The Problem
Session state reaches the operator by hue alone, which WCAG 2.1 1.4.1 (Use of Color, Level A) forbids as a sole visual carrier:
StateDot.mjs renders no text, title, or aria-label — it only swaps a state class.
StateDot.scss gives all seven states identical geometry (8px circle); only --fm-dot differs.
AgentCard.applyRecord sets only the dot (getReference('state-dot').set({live, state})) — no state text anywhere in the card or its SCSS.
And the pulse cannot carry the signal: TOKENS.md:41-43 is correct that the reduced-motion property holds because the pulse conveys nothing unique. Re-loading signal there would trade a 1.4.1 failure for a reduced-motion failure.
The Architectural Reality
The missing channel is already specified and simply was never built — CARD-CONTRACT.md:13:
State line (mock: "▲ WORKING") | lifecycle.state (+ confidence) | mono, state-colored; renders the DTO's literal state vocabulary
And the vocabulary resolver already exists: stateLabel(state), exported from HealthSwatch.mjs alongside the stateClass that StateDot already imports. HealthSwatch proves the pattern in-tree (same class + visible text); HealthBar inherits it by composition and is already compliant. The card is the surface that skipped it.
So this is a conformance gap against a contract we already wrote, not a new design.
The Fix
- Render the contracted State line on
AgentCard, adjacent to the dot, consuming stateLabel(state) — never a second vocabulary.
- Give
StateDot an accessible name from the same stateLabel. This is 4.1.2 / 1.1.1 — a separate criterion. It must not be treated as satisfying 1.4.1: an aria-label does nothing for a sighted operator who cannot separate hues. Both are required.
- Do not add shape/glyph-per-state as the primary channel (ruled out: seven distinguishable glyphs in an 8px dot is a legibility problem dressed as an accessibility fix, and it forks the vocabulary). Shape may later reinforce text.
Acceptance Criteria
Out of Scope
- The pulse / reduced-motion treatment — deliberately untouched per the ruling.
- D3 / D4 (shipped) · #15489 (Accounts block) · #14618 (visual-regression harness, the mechanical guard this composes with).
Related
#14805 (epic) · #14619 (the audit that recorded the 1.4.1 failure) · #14578 (TOKENS.md) · CARD-CONTRACT.md (#14605) · #15487 (the re-baseline that reopened the design lane).
Live latest-open sweep at 2026-07-18T18:45Z plus a targeted StateDot/1.4.1 search: no equivalent leaf exists.
Authored by Grace (Claude Opus 4.8, Claude Code).
Context
Implementing leaf for the D2 design ruling on epic #14805 (ruling) — the last of the three design-authority items (#15493/D4 and #15500/D3 shipped). Origin:
#14619's audit, re-derived from source before ruling.The Problem
Session state reaches the operator by hue alone, which WCAG 2.1 1.4.1 (Use of Color, Level A) forbids as a sole visual carrier:
StateDot.mjsrenders no text,title, oraria-label— it only swaps a state class.StateDot.scssgives all seven states identical geometry (8px circle); only--fm-dotdiffers.AgentCard.applyRecordsets only the dot (getReference('state-dot').set({live, state})) — no state text anywhere in the card or its SCSS.And the pulse cannot carry the signal:
TOKENS.md:41-43is correct that the reduced-motion property holds because the pulse conveys nothing unique. Re-loading signal there would trade a 1.4.1 failure for a reduced-motion failure.The Architectural Reality
The missing channel is already specified and simply was never built —
CARD-CONTRACT.md:13:And the vocabulary resolver already exists:
stateLabel(state), exported fromHealthSwatch.mjsalongside thestateClassthatStateDotalready imports.HealthSwatchproves the pattern in-tree (same class + visible text);HealthBarinherits it by composition and is already compliant. The card is the surface that skipped it.So this is a conformance gap against a contract we already wrote, not a new design.
The Fix
AgentCard, adjacent to the dot, consumingstateLabel(state)— never a second vocabulary.StateDotan accessible name from the samestateLabel. This is 4.1.2 / 1.1.1 — a separate criterion. It must not be treated as satisfying 1.4.1: anaria-labeldoes nothing for a sighted operator who cannot separate hues. Both are required.Acceptance Criteria
AgentCardrenders the DTO's literal state vocabulary as text beside the dot; state is legible with colour removed (greyscale check).stateLabel(state)— no second state→text mapping introduced.StateDotexposes an accessible name from the same resolver.AgentDetail's dot (:192) covered by the same reasoning or explicitly scoped out with rationale.CARD-CONTRACT.md's conformance checklist reflects the now-implemented State line.Out of Scope
Related
#14805 (epic) · #14619 (the audit that recorded the 1.4.1 failure) · #14578 (TOKENS.md) ·
CARD-CONTRACT.md(#14605) · #15487 (the re-baseline that reopened the design lane).Live latest-open sweep at 2026-07-18T18:45Z plus a targeted StateDot/1.4.1 search: no equivalent leaf exists.
Authored by Grace (Claude Opus 4.8, Claude Code).