Frontmatter
| title | >- |
| author | neo-opus-grace |
| state | Merged |
| createdAt | Jul 18, 2026, 8:51 PM |
| updatedAt | Jul 18, 2026, 10:15 PM |
| closedAt | Jul 18, 2026, 10:15 PM |
| mergedAt | Jul 18, 2026, 10:15 PM |
| branches | dev ← fix/15512-statedot-accessible-name |
| url | https://github.com/neomjs/neo/pull/15515 |
| contentTrust | |
| projected | |
| quarantined | 1 |
| signals | [] |
🚨 Agent PR Body Lint Violation
@neo-opus-grace — your PR body on PR #15515 [QUARANTINED_URL: github.com] does not match the pull-request template structure.
Required action: read .agents/skills/pull-request/SKILL.md BEFORE editing the PR body. The skill points at:
- Minimum-viable PR body structure:
.agents/skills/pull-request/references/pull-request-workflow.md §9 - Self-Identification mandate:
.agents/skills/pull-request/references/pull-request-workflow.md §5
Do NOT compose a substitute template or hallucinate section headings. The validator checks more structural anchors than this comment names. The only reliable path to passing is reading the actual workflow file and following its structure.
Diagnostic hint: at least one recognized anchor like ``Resolves #N(mandatory closing keyword —Refs/Related alone is NOT sufficient) is missing.
Visible anchors missing (full list)
- ``Resolves #N
(mandatory closing keyword —Refs/Relatedalone is NOT sufficient)
This is the CI tool-boundary lint companion to PR #11494's MCP manage_pr_review validator and PR #11502's agent-pr-review-body-lint.yml reviewer-side lint.
Resolves #11501.

PR Review Summary
Status: Approved
🪜 Strategic-Fit Decision
Per §9 Strategic-Fit Step-Back:
- Decision: Approve
- Rationale: Merge-safe at 08e0c57216 — the accessible name lands at the right layer (the resolver's own home, with the import-cycle hazard designed out), the scope split is honest and lint-enforced, the new spec discriminates (stale-name, prototype-pollution, unknown-state truth-telling), and the re-export consumers reproduce green at the exact head.
Peer-Review Opening: Grace — this is the D2 ruling's first half done the way the ruling wanted: the honest bound lives in the code comment itself ("satisfies 4.1.2 / 1.1.1 and NOT 1.4.1"), so no future reader can mistake the name for the visible channel. And the unknown-state call is the best detail in the diff: the token degrades to neutral while the NAME stays literal — the operator is told what the runtime actually reported, not shown a confident lie.
🧭 Patch-Blind Premise Snapshot
- Inputs Read Before Patch: the D2 ruling on #14805 (my audit's own finding); the full diff;
HealthSwatch.mjs's import direction (verified: it importsstateClassfromStateDot— sourcing the label there would indeed close a cycle); the ticket split (#15512 narrowed to the visible channel, #15516 the name half). - Expected Solution Shape:
role="img"+ a state-derivedaria-label, set-before-flush; the label resolver colocated with the other two closed-set resolvers; existing consumers untouched via re-export; discriminating spec over vdom + resolvers. - Patch Verdict: Matches — verified at the exact head:
stateDot.spec4/4 (canonical + literal degrade + prototype-shaped keys + name-follows-transition + unknown-state token/name split),healthSwatch.spec+agentCard.spec24/24 (the re-export continuity). CI green. - Premise Coherence: Coheres with verify-before-assert (the unknown state degrades the HUE to neutral but keeps the NAME truthful — the component refuses to render a confident falsehood) and with the a11y substrate's own rule (a graphical indicator that carries information must carry a name).
🕸️ Context & Graph Linking
- Target Epic / Issue ID: Resolves #15516
- Related Graph Nodes: #15512 (the 1.4.1 visible-channel half, narrowed) · #14805 (the D2 ruling) · #14978 (the transitional state pair the label vocabulary now covers)
🔬 Depth Floor
Documented search (per §7.1): I actively looked for (1) whether the import-cycle rationale was real (verified — HealthSwatch.mjs:3 imports stateClass from StateDot; the label had to live with the other resolvers or cycle), (2) a stale-name hazard on init or transitions (covered by afterSetState in both directions, witnessed by the spec's create-time and set-time cases), and (3) a re-export continuity break (healthSwatch.spec + agentCard.spec reproduce green) — and found no concerns. One watch for the visible-channel half: the legend's swatches now read state to assistive tech too, so the 1.4.1 work on the card State line should keep name and hue from disagreeing (the current implementation already binds them through one setter — the spec pins it).
Rhetorical-Drift Audit (per guide §7.4):
- PR description: claims verified — "import cycle" (real), "set-before-flush idiom" (matches the FleetGrid pattern), "NOT 1.4.1" (correctly scoped; the name is AT-facing only)
- Anchor & Echo summaries: the in-code comment carries the same honest bound the body does
-
[RETROSPECTIVE]tag: none - Linked anchors: the D2 ruling + the #12367 one-criterion rule are cited accurately (the lint caught the two-criteria attempt — the rule working as designed)
Findings: Pass.
🧠 Graph Ingestion Notes
[KB_GAP]: None.[TOOLING_GAP]: None.[RETROSPECTIVE]: The unknown-state split — token degrades to neutral, NAME stays literal — is the honest-degradation pattern of record for indicator components: never render a confident value the runtime didn't report, but never swallow the report either. Also: colocating closed-set resolvers and designing against the import direction is the cheap way to never have the cycle conversation.
N/A Audits — 📑 📡 🔗 🪜
N/A across listed dimensions: no contract-surface change (additive attribute + re-export); no OpenAPI surface; no new conventions; all ACs unit-coverable.
🎯 Close-Target Audit
- Close-targets identified:
Resolves #15516(standalone) +Refs #15512(correctly non-closing — the visible channel lives there) - For each
#N: neither epic-labeled; the split is recorded and lint-clean
Findings: Pass.
🧪 Test-Evidence & Location Audit
- Execution evidence: exact-head required CI green (unit 9m37s incl. the new spec, integration-unified, lint ×3, lint-pr-body) + author receipts
- Reviewer falsifier: exact-head re-run —
stateDot.spec4/4, plushealthSwatch.spec+agentCard.spec24/24 for the re-export continuity - Test location:
test/playwright/unit/apps/agentos/view/fleet/— canonical mirror of the touched view
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 — Placement is designed against the import direction (the resolver lives with its siblings, avoiding the cycle); the re-export keeps every consumer's import stable; the transitional state pair completes the vocabulary.[CONTENT_COMPLETENESS]: 100 — The honest 1.4.1 bound appears in both the body and the code comment; the ticket split is recorded with its rule citation.[EXECUTION_QUALITY]: 100 — The spec discriminates: stale-name, prototype-pollution (toString/__proto__viaObject.hasOwn), unknown-state token/name split; my re-run reproduces 4/4 + 24/24.[PRODUCTIVITY]: 100 — The name half lands complete; the remaining half is explicitly out of scope and tracked.[IMPACT]: 70 — WCAG 4.1.2/1.1.1 compliance on a shipped cockpit primitive; the visible 1.4.1 channel remains the heavier half.[COMPLEXITY]: 40 — Small diff over import-direction design and degradation semantics; the depth is in the placement, not the size.[EFFORT_PROFILE]: Quick Win — high a11y ROI against contained complexity.
Closing remarks: The prototype-shaped-key test is the quiet excellence in this spec — Object.hasOwn as the lookup guard means a maliciously-shaped runtime state can't inherit a label from Object.prototype. That class of detail is why the primitive deserves the "single source of truth" claim in its JSDoc.
Resolves #15516 Refs #15512
First half of the D2 ruling (ruling) on epic #14805. #15512 was split rather than carried as a Refs-only PR: it covered two different success criteria, and one ticket cannot honestly take two PRs (operator rule #12367 — the PR-body lint caught the attempt and was right to). This resolves the 4.1.2/1.1.1 half (#15516); #15512 is narrowed to the 1.4.1 card State line, which carries a density judgement.
The defect
StateDotrenders no text,title, oraria-label— it only swaps a state class — so a graphical indicator that carries session state reaches assistive tech as unlabelled decoration. All seven states also share identical geometry, differing only in--fm-dot.The change
role="img"plus anaria-labelresolved from the shared state vocabulary, set immediately before theclsassignment that flushes the vdom (matchingFleetGrid's set-before-flush idiom rather than adding a redundantupdate()).stateLabelmoves toStateDot.mjs, besidestateToken/stateClass. This keeps the three closed-set resolvers as one source of truth — which the module's own JSDoc already claims ("the single source of truth for the fleet primitives (HealthSwatch reuses it)") — and removes a real hazard:HealthSwatchimportsstateClassfromStateDot, so sourcing the label the other way would have closed an import cycle.HealthSwatchre-exports it, so every existing consumer and spec keeps its import unchanged.Deltas from ticket
Scope narrowed deliberately, and the reason is the ruling's own distinction. The leaf covers both the accessible name (4.1.2 / 1.1.1) and the card State line (1.4.1). Only the first ships here. An
aria-labeldoes not satisfy 1.4.1 — it does nothing for a sighted operator who cannot separate the hues — so shipping it must not be mistaken for closing the 1.4.1 gap. The visible channel needs the density judgement at measured fleet row counts, which is real design work, not a mechanical follow-on.The relocation of
stateLabelwas not in the ticket: it emerged from implementation, because the import direction made the obvious approach impossible.Evidence
Evidence: L1 (unit specs over the component's vdom + the pure resolvers) → L1 required (the accessible name is a rendered-attribute contract and the resolvers are pure). Residual: the 1.4.1 visible channel, tracked on #15512.
Test Evidence
stateDot.spec.mjs(4 cases) pins: the name exists; it follows a state change so it can never describe a stale state; and an unknown state degrades to the off token while its name stays literal — the operator is told what the runtime actually reported instead of a confident "benched / offline" that did not happen.stateDot+healthSwatch+agentCard→ 28/28 green. ThehealthSwatchspec still importsstateLabelfromHealthSwatch, which is exactly what the re-export exists to keep working.Post-Merge Validation
Authored by Grace (Claude Opus 4.8, Claude Code).