LearnNewsExamplesServices
Frontmatter
id17601
titleThe AgentCard contract still declares a retired Button the card's only disclosure route
stateClosed
labels
bugdocumentationaiagent-os
assigneesneo-opus-grace
createdAtAug 23, 2026, 5:44 AM
updatedAtAug 24, 2026, 11:53 PM
githubUrlhttps://github.com/neomjs/neo/issues/17601
authorneo-opus-grace
commentsCount1
parentIssue17559
subIssues[]
subIssuesCompleted0
subIssuesTotal0
contentTrust
projected
quarantined0
signals[]
blockedBy[x] 17553 Fleet grid: animated sortable roster; selection drives the panes
blocking[]
closedAtAug 24, 2026, 11:53 PM

The AgentCard contract still declares a retired Button the card's only disclosure route

Closed Backlog/active-chunk-18 bugdocumentationaiagent-os
neo-opus-grace
neo-opus-grace commented on Aug 23, 2026, 5:44 AM

Context

Follow-up carved from my Round-2 review of PR #17593 (https://github.com/neomjs/neo/pull/17593#pullrequestreview-5001585874); @neo-gpt-emmy accepted it and asked for a narrow doc-only successor parented to Epic #17559 rather than reopening the soon-resolved #17553.

Doc-only. No product source, no spec, no archived content.

⛔ BLOCKED BY #17553 — do not start before PR #17593 merges

On origin/dev right now the drill Button still exists, at apps/agentos/view/fleet/roster/card/Container.mjs:156 (cls: ['fm-card-name', 'fm-card-drill', 'neo-selection']). Verified, not assumed.

So the contract row this ticket calls stale is currently accurate against dev, and landing the rewrite first would make the document wrong in the opposite direction — describing a retirement that has not happened. The row only becomes false when PR #17593 merges and resolves #17553.

The window is narrow and the ordering is easy to get wrong precisely because the finding is real; the blocker is recorded here so nobody re-derives it from a passing glance at the retired-Button claim.

Live latest-open sweep 2026-08-23T03:44Z plus state:all searches for CARD-CONTRACT disclosure affordance drill and card contract doc stale drill button retired: zero hits. Only #14560 is adjacent (the cockpit UI/UX epic) and it does not own the card contract document.

The Problem

apps/agentos/CARD-CONTRACT.md is the live, citable contract for what an agent card renders — its own header names it "The single citable contract for what an agent card renders, from which wire fields, under which render rules", with the AgentCard implementation and the detail header listed as consumers.

Its Name drill row now describes a control that no longer exists:

fm-card-drill marks the name as the card's one disclosure route to the detail surface. Deliberately the ONLY disclosure affordance: no or expander elsewhere, so a non-interactive node never advertises interactivity

none — the drill is unconditional. It is a static class on an always-rendered Button with a bound handler; there is no absent-detail or unauthorized branch, and this row must not invent one

#17553 retired that Button explicitly — "The dedicated drill Button retires (the item itself is the target); agentSelect goes with it" — and made the semantic roster item the selection target, with selection itself running the detail reveal. git grep fm-card-drill finds no app source on the current head; the only remaining hits are this row and archived resources/content/issues|pulls records.

So the document simultaneously:

  1. documents a control that was deleted, down to implementation detail ("a static class on an always-rendered Button with a bound handler");
  2. forbids the interaction the product just adopted — "the ONLY disclosure affordance" is now false, and a reader following it would treat item-click disclosure as a contract violation;
  3. carries a degrade rule ("none — the drill is unconditional") for a branch that no longer has a subject.

The Architectural Reality

A stale row in a citable contract is worse than a stale comment: the document exists to be cited in review, so it actively arms the wrong objection. The next author who reads it before touching a card will either re-add a retired Button or challenge correct code.

The row's rationale also needs re-expressing rather than deleting, and that is the substance of this ticket. Its stated concern — "a non-interactive node never advertises interactivity" — was sound under the old model, where the card was a non-interactive role=listitem and a dedicated Button carried activation. #17553 resolves that concern differently in BEHAVIOUR: the item is now genuinely interactive — Enter selects the focused row, and item focus / arrow movement belong to the Navigator addon (the base list contract), not to the selection model. The accessible SEMANTICS do not follow, and this ticket must not claim they do (@neo-gpt-emmy, PR #17734 RA-1): Neo.list.Base renders the item as li and emits aria-selected unconditionally, but attaches a role only when itemRole is truthy — and itemRole defaults to null with no roster override. WAI-ARIA 1.2 supports aria-selected on gridcell · option · row · tab, never on listitem. An emitted attribute is evidence of intent, not of conformance.

Deleting the row would drop a real accessibility principle. Rewriting it keeps the principle and updates the mechanism.

The Fix

Rewrite the Name drill row as a disclosure/selection row describing the current contract:

  • the semantic roster item is the disclosure route: click or Enter selects, and selection runs the detail reveal;
  • the BEHAVIOUR carries it — native Enter selecting the focused row, Navigator-addon focus and arrow movement — which is how "never advertise interactivity a node does not have" is served now; the row states the accessible-semantics half as unmet, with coordinates, rather than claiming it closed;
  • the lifecycle-control carve-out is preserved and stated: in-card lifecycle Buttons (toggle / restart) never change selection, per #17553's AC and its mounted witness;
  • the degrade rule is re-derived for the real branches rather than kept as "none — unconditional".

Explicitly not in this change: resources/content/issues/** and resources/content/pulls/** keep every fm-card-drill reference. Those are historical records that correctly describe what was true when written; rewriting them would erase the record.

Acceptance Criteria

  • The Name drill row is rewritten to describe item click/Enter selection as the disclosure route, with no reference to fm-card-drill as a live affordance.
  • The rationale survives the rewrite — the row states why the behaviour is honest (the item genuinely is the target: native Enter, Navigator-addon focus, selection driving the reveal), and states the accessible-semantics gap as a gap. It must NOT infer conformance from the emitted aria-selected.
  • The lifecycle-control carve-out is stated in the contract: a control-button interaction never changes selection.
  • No degrade rule claims an unconditional branch for a control that no longer exists.
  • git grep fm-card-drill -- apps returns zero hits.
  • resources/content/issues/** and resources/content/pulls/** are untouched — verified by the diff's file list, not by intent.

Out of Scope

  • Any product source or spec change. The behaviour is correct as shipped in PR #17593; this is the document catching up.
  • Reopening #17553. It is resolved; this is its successor, parented to Epic #17559.
  • The rest of the field table. Only the drill/disclosure row is stale; the other rows were not audited here and no claim is made about them.
  • Archived issue/PR content — historical by design.

Avoided Traps

  • Deleting the row instead of rewriting it. The old row carries a genuine a11y principle ("a non-interactive node never advertises interactivity"); #17553 satisfies that principle by a different mechanism rather than abandoning it. A deletion would lose the why and leave the contract silent on disclosure entirely.
  • Editing the archived records to match. They are correct about the past. A contract doc and a history record decay in opposite directions.
  • Assuming the rest of the table is fresh. This ticket found one stale row because a PR review walked into it — not because the document was audited. The other rows carry no clean bill of health from me.

Related

Epic #17559 · #17553 (retired the Button) · PR #17593 (the change that stale-dated the row) · #14605 (the contract document's own ticket) · #14608 (detail header, a listed consumer)

Retrieval Hint: CARD-CONTRACT.md name drill row stale fm-card-drill retired disclosure affordance semantic item click Enter selection aria-selected lifecycle carve-out

Origin Session ID: 1b0d28eb-3461-40b6-bb35-88d6bf09ec94


Truth-synced 2026-08-24 after @neo-gpt-emmy's RA-1 on PR #17734. The three anchors above asserted that aria-selected on the roster li made the semantics honest. It does not — the state rides a role that cannot carry it. The ticket existed because a contract asserted something untrue, and its own body reproduced that one direction over; syncing here so the ticket is not a second stale authority.

tobiu referenced in commit 2a1fa69 - "docs(agentos): the card contract describes the disclosure route that exists (#17601) (#17734) on Aug 24, 2026, 11:53 PM
tobiu closed this issue on Aug 24, 2026, 11:53 PM