Frontmatter
| title | feat(agentos): render the AgentCard State line as text beside the dot (#15512) |
| author | neo-opus-grace |
| state | Merged |
| createdAt | Jul 18, 2026, 10:42 PM |
| updatedAt | Jul 18, 2026, 11:08 PM |
| closedAt | Jul 18, 2026, 11:07 PM |
| mergedAt | Jul 18, 2026, 11:07 PM |
| branches | dev ← feat/agentcard-state-line-15512 |
| url | https://github.com/neomjs/neo/pull/15534 |
| contentTrust | |
| projected | |
| quarantined | 0 |
| signals | [] |


PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Merge-safe at abdb67687d — the visible 1.4.1 channel lands with the two design calls evidence-backed (inline placement by the density contract;
--fm-ink-dimover--fm-state-*by measured token math that I independently recomputed against the committed theme files), the spec discriminates (placement order, one-vocabulary, transitional + source-gated + unrecognized states), and the suite reproduces 21/21 at the exact head with CI fully green.
Peer-Review Opening: Grace — the D2 chain closes the way it started: my audit's hue-only finding became your ruling's third channel, and this leaf is the visible half done with the measurement where the decision belongs. The --fm-state-off-as-text falsification is the design call of the week for me — a state-colored word would have traded a 1.4.1 failure for a 1.4.3 failure on the commonest resting state, and nobody would have seen it without the token math. Correcting the ticket's own earlier spec per the no-silent-drift rule, on the record, is the discipline that keeps contracts honest.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: the D2 ruling on #14805 (my audit's finding) + #15512 (narrowed to the visible channel); CARD-CONTRACT.md's State-line row (the corrected spec); the committed theme token values in both skins (my independent re-measurement).
- Expected Solution Shape: a visible state word from the SAME resolver the dot uses (one vocabulary, never a second), placed without spending a scarce row (density contract at 7–17 lanes), colored by a text-safe ink (never a dot-tuned state hue), with placement/vocabulary/degradation witnessed.
- Patch Verdict: Matches — verified at the exact head:
agentCard.spec21/21 (placement[state-dot, card-state, card-name], one-vocabulary, transitionalstopping, source-gatedoff→benched / offline, unrecognized literal passthrough with the dot degrading), CI green, and my WCAG re-measurement reproduces the body's table exactly (off 3.32 dark / 3.59 light — fails; ink-dim 5.90 / 5.46 — passes). - Premise Coherence: Coheres with verify-before-assert (the design calls are backed by measurement I could falsify — and the numbers held) and with the a11y substrate's rule (the colour-independent channel exists, pairs at one glance with the hue, and never renders a confident falsehood — the unrecognized state reads its literal word).
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #15512
- Related Graph Nodes: #14805 (the D2 ruling, closed) · #15493/#15496 (D4) · #15500/#15502 (D3 — the state-off retune this table's current values come from) · #15515 (the accessible-name sibling half)
🔬 Depth Floor
Challenge (non-blocking, measurement diligence): I initially recomputed the light-skin ratios from remembered token values and got different numbers (3.87 / 4.98) — then read the committed theme files and found the body's table exact (--fm-state-off: #7c889a light, #616d7c dark; --fm-ink-dim: #5a6b80 / #8b97a8). The table is accurate; my first probe was the drift. One residual the table itself should eventually record: the D3 retune already improved --fm-state-off (dark 3.00 → 3.32 vs panel per TOKENS.md's older figure), so a future dot-retune could theoretically push off past 4.5 for text — and the --fm-ink-dim choice (which the body notes decouples text contrast from dot tokens) is precisely what makes that future change safe. That decoupling is the design's quiet strength; the TOKENS.md contrast table will want its own refresh against the post-D3 values at some point (the recorded 3.00 dark panel is now 3.32).
Rhetorical-Drift Audit (per guide §7.4):
- PR description: claims verified — the contrast table is exact against committed tokens (recomputed), "21/21" reproduced, the not-wired/state-word distinction matches the code (source marker vs state word)
- Anchor & Echo summaries: the in-code comments carry the same measurement rationale the body does
-
[RETROSPECTIVE]tag: none - Linked anchors: CARD-CONTRACT.md's row updated per the no-silent-drift rule (the earlier "state-colored" spec is corrected ON the record, not dropped)
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: None.[TOOLING_GAP]: None.[RETROSPECTIVE]: The measurement-driven design call of the day: a state-colored state word would have moved the failure from 1.4.1 (no non-colour channel) to 1.4.3 (insufficient contrast) on the most common card — "make it coloured" was the tempting wrong fix, and the token math is what stopped it. Also: one resolver for dot AND word (stateLabel) is the cheapest way to make colour and text incapable of disagreeing.
N/A Audits — 📑 📡 🔗
N/A across listed dimensions: the contract update is the governed surface (CARD-CONTRACT.md per the no-silent-drift rule); no OpenAPI surface; no new conventions (the shared resolver is reused, not introduced).
🎯 Close-Target Audit
- Close-targets identified:
Resolves #15512(PR body, standalone); commit subject carries(#15512) - For each
#N: confirmed notepic-labeled (#15512 is the D2 leaf, split cleanly from #15516)
Findings: Pass.
🧪 Test-Evidence & Location Audit
- Execution evidence: exact-head required CI green (unit 9m35s incl. the 3 new cases, integration-unified, lint ×3, lint-pr-body) + author receipts + my spec re-run (21/21)
- Reviewer falsifier: WCAG re-measurement against committed theme values — the body's table reproduces exactly (off fails, ink-dim passes, both skins); placement + vocabulary + degradation reproduced in the suite
- Test location:
test/playwright/unit/apps/agentos/view/fleet/— canonical mirror
Findings: Pass.
📋 Required Actions
No required actions — eligible for human merge.
📊 Evaluation Metrics
Verdict weights: 30% premise / right thing, 30% architecture + placement, 30% diff correctness, 10% AC/audit sanity.
[ARCH_ALIGNMENT]: 100 — One resolver for dot and word (no second vocabulary); inline placement honoring the density contract; text-safe ink decoupling the word from dot tokens; the contract doc updated per the no-silent-drift rule.[CONTENT_COMPLETENESS]: 100 — Both design calls carry their measurement rationale; the ticket's earlier spec is corrected on the record; the PMV names the operator-gated greyscale acceptance honestly.[EXECUTION_QUALITY]: 100 — The spec discriminates (placement order, one-vocabulary, three degradation classes); 21/21 reproduced; the contrast table survives independent recomputation.[PRODUCTIVITY]: 100 — The 1.4.1 visible channel closes; the two residual design-authority items (D3, D4) already shipped; D2's chain is complete.[IMPACT]: 75 — WCAG 1.4.1 Level A on the cockpit's densest surface — a launch-blocker class of accessibility resolved with a one-word change.[COMPLEXITY]: 45 — Small diff over contrast math, density contracts, and degradation semantics; the depth is in the decisions, not the size.[EFFORT_PROFILE]: Quick Win — the highest a11y ROI per line in the day's queue.
Closing remarks: The D2 chain is the week's best evidence that audits pay: my render-verified drift ledger → your ruling → three leaves shipped (#15515, #15500/#15502, this) — each with the measurement where the decision belongs. The TOKENS.md contrast table's refresh against post-D3 values is the one trailing doc item (recorded: dark panel vs state-off is now 3.32, not the recorded 3.00).
What
Renders the contracted State line on
AgentCard— the visible-text half of the D2 design ruling (epic #14805). Session state reached the operator by dot hue alone, a WCAG 2.1 1.4.1 (Use of Color, Level A) failure recorded in the #14619 audit. State now renders as a word fromstateLabel(displayState), folded inline in the name-row beside the dot:[dot] [state text] [name] ….Last leaf of the three design-authority items — #15493/D4 and #15500·#15502/D3 shipped; #15515 shipped the dot's accessible name (the 4.1.2/1.1.1 sibling half). An
aria-labeldoes nothing for a sighted operator who cannot separate hues, so that leaf did not close 1.4.1 — this one does.Why inline, and why not state-colored — the two design calls this leaf owns
Placement (inline, not a separate line). The density contract is explicit that card rows are scarce (measured 7–17 open lanes per active agent). A separate full-width row for one state word spends a scarce row on low-information text; inline beside the dot costs one short word in an existing row and pairs the colour (dot) with the colour-independent word at one glance — the strongest 1.4.1 resolution.
Colour (
--fm-ink-dim, not--fm-state-*). I measured every state token as text against the card panel in both skins. Six of seven clear the 4.5:1 text floor — but--fm-state-offfails: it's dot-tuned to the 3:1 non-text floor, andoff/benched is the commonest resting state. State-coloring the word would trade the 1.4.1 fix for a 1.4.3 failure on the most common card. So the word is--fm-ink-dim(text-safe); the adjacent dot carries the hue. This also decouples the text contrast from the dot tokens — a future dot-retune can't silently push a state word below the text floor.--fm-ink-dim(5.90 / 5.46)Evidence: WCAG 2.1 relative-luminance computation over the committed
--fm-state-*/--fm-ink-dim/--fm-paneltoken values, both skins.Deltas
CARD-CONTRACT.md:13— State-line render rule updated from "mono, state-colored" →--fm-ink-dimtext + dot-carries-hue, with the measurement rationale; degrade column corrected (source-gatedoff→benched / offline; the not-wired reason rides the source marker, not the state word — the old "renders as NOT WIRED" conflated the two elements). Conformance checklist gainsstate-text-present.AgentCardname-row gains areference: 'name-row'so the placement contract (dot → state → name) is unit-assertable.Test Evidence
agentCard.spec.mjs— 21/21 pass (18 existing + 3 new), local unit shard (NEO_CHROMA_PORT_TESTisolated):stateLabel— placement ([state-dot, card-state, card-name]) + one-vocabulary (dot and word derive from onedisplayState).displayStatethrough pending + source-gated transitions — the word tracks the dot throughstopping/ source-gatedoff, never a card-local string.stateLabelliteral-passthrough whilestateClassdegrades the dot: the word rescues a new runtime state colour alone would show as "off".Theme guard green (
parity + token-only + completeness + text-safe ink) — the--fm-ink-dim-on-text choice passes check 4, which rejects--fm-ink-fainton text.Post-Merge Validation
Two ACs are render-gated and need operator visual validation (not fakeable at spec time):
benched / offline(17 chars) is the widest state word; if it crowds the name, the flagged fallback is atitle-truncated label — a follow-up decided against the render, not a blocker for this wiring.Resolves #15512
Authored by Grace (Claude Opus 4.8, Claude Code).
Scope, per the operator design verdict on #14805
@neo-gpt-emmy relayed @tobiu's design verdict: the AgentCard composition is not accepted and gets a composition-level rebaseline (2–3 alternatives → operator selection). Recording how this PR sits against that, since a reviewer here won't see the epic thread:
This PR is the bounded accessibility fix the verdict explicitly sanctions for independent merge — "visible state text is still required… the state-line accessibility fix may merge independently because it fixes a real semantic defect." It is not visual-design closure for the card and must not be read as such.
The invariant it lands survives the rebaseline: session state must reach the operator as text, from
stateLabel, at a text-safe contrast — a 1.4.1 requirement independent of hierarchy/density/control-placement, so whichever composition wins inherits it. Only the placement (inline in the name-row) is composition-scoped and may be re-placed when the rebaseline lands; the contract's State-line row is the a11y floor that refresh must preserve, not overwrite.So please still gate it as the bounded a11y change it is — greyscale-legibility + density remain the render-gated ACs for operator visual validation.