design/backlog still annotates every row `role="row"` + `aria-selected` — the asset describes the composition MOTIR-2493 removed
Surfaced by MOTIR-2493 while fixing the shipped role composition — filed rather than absorbed, because a design/**-only diff takes the design/MOTIR-<id>-<slug> branch prefix (CI skips E2E + the Vercel preview) and cannot ride the same PR as MOTIR-2493's runtime-code diff. One PR, motir-core.
The measurement
MOTIR-2493 chose list + listitem for the backlog's ranked rows: BacklogRows' scroll viewport stays role="list", and BacklogRowBody now renders role="listitem" with aria-posinset/aria-setsize, and NO aria-selected (not an allowed attribute on listitem; the sibling checkbox's aria-checked carries selection).
The design asset still specifies the composition that was removed.
⚠️ AMENDED AT RUN TIME (2026-08-09, rung 2) — the measurement below undercounted the defect by two thirds. The card measured ONE file.
design/backlog/holds THREE mocks that all draw the same/backlogrows, and all three carried the stale annotation:
file <div role="row">elementsbacklog.mock.html21 (+1 prose mention = the 22 grep hits below) backlog-filter.mock.html9 backlog-scale.mock.html8 38 row elements, and not one of them had a legal parent:
grep -rn 'role="grid"\|role="rowgroup"\|role="gridcell"\|role="table"\|role="treegrid"' design/backlog/returns nothing. Underplan-rules.mdgate 8 (sweep-the-referrers) each straggler is "in this card's scope or a named sibling card — there is no third option"; all three aredesign/**on one branch prefix in one repo, so they are in scope here. Fixing onlybacklog.mock.htmlwould have returned 0 from AC 1's grep — reading as complete — while leaving 17 stale sites in the folder the design-reference rule sends the next agent to open first. See the planning bug filed against this miss.
design/backlog/backlog.mock.html— 22 grep hits of<div class="brow" role="row">, three (not four) of themrole="row" aria-selected="true"(the multi-select panel), plus the prose at line ~2045: "The row is arole="row"div with sibling controls".design/backlog/backlog-filter.mock.html— 9 sites across the closed-toolbar and active-filter panels (added at run time).design/backlog/backlog-scale.mock.html— 8 sites across the virtualized-window and lazy-load panels (added at run time).design/backlog/design-notes.md— line ~152 "Arole="row"div with sibling controls" (panel 2), and line ~475 "the issue rows stayrole="row"with sibling controls (the 4.2 contract)" (the backlog-filtering section's a11y list).
Why the asset is the thing that is wrong
The asset never specified a grid, a rowgroup, or gridcells — so its role="row" had no parent that could hold it, which is the defect MOTIR-2493 measured as two CRITICAL axe violations on every populated backlog. The half of the annotation that is load-bearing is the sibling-controls / no-nested-interactive contract (nested-interactive, finding #35), and that survives verbatim under listitem; only the role token is stale.
Acceptance criteria
grep -c 'role="row"' design/backlog/*.mock.htmlreturns 0 for every one of the three files (amended: was scoped tobacklog.mock.htmlalone), and every one of those rows readsrole="listitem"— the mock's rows sit inside the panel container that stands in for therole="list"viewport, so the mock's own markup composes the way the shipped page now does. Inbacklog-scale.mock.htmlthe role goes on the bounded.vpviewport, matching where the shipped role lives, with the inner flex container left role-less.- The three
aria-selected="true"rows in the multi-select panel express selection the way the app does — the checked checkbox + the selection tint — with noaria-selectedleft on alistitem. The.row-checkbuttons becomerole="checkbox" aria-checkedwith aSelect/Deselect <key>label, which is the accessible channel the shipped page uses oncearia-selectedis gone. - (added at run time) The two
DragOverlayclones inbacklog.mock.htmldo NOT becomelistitems — they are portalled outside the viewport, so per shipped they drop the role, their wrapper isaria-hidden, and nothing focusable is left inside it. design/backlog/design-notes.mdpanel 2 and the backlog-filtering a11y bullet both namerole="listitem"inside therole="list"viewport, KEEP the sibling-controls / no-nested-interactive contract as the point of the sentence, and statearia-posinset/aria-setsizeagainst the aggregate count (the virtualized list's set size, not the mounted window).- The notes record WHY the list arm was chosen over
grid— agridobliges the arrow-key cell keyboard model andgridcellper slot, which the backlog does not implement — so the next reader does not re-open a settled decision. AMENDED (rung 2): re-exported only where pixels actually change.design/backlog/backlog.pngis re-exported from the edited mock … even though a role attribute changes no pixels.backlog.pngIS re-exported — its legend prose changed.backlog-filter.png/backlog-scale.pngare NOT: rendering each mock before and after the edit produces byte-identical PNGs, which proves the attribute-only edits move no pixels — a stronger check than re-exporting. Re-exporting them anyway would have committed pure reflow noise, because this box's sans fallback differs from the original export box (the asset's-apple-system, BlinkMacSystemFont, 'Segoe UI', Robotostack exists on neither, and re-rendering the uneditedbacklog.mock.htmlhere gives 2480×9078 against the committed 2480×9326). Recorded so the next run does not read the identical PNGs as a skipped step.- The
role="row"annotations elsewhere in the file that describe a REAL grid are untouched — check before a blanket replace. (Verified: there are none — the folder contains no grid/rowgroup/gridcell/table/treegrid at all.) - The design-asset guard lane (
pnpm vitest run --config vitest.design.config.ts) is green — the one CI lane adesign/*branch does not skip.
Context refs
design/backlog/backlog.mock.html— the 22 grep hits + the prose at ~2045.design/backlog/backlog-filter.mock.html,design/backlog/backlog-scale.mock.html— the 17 sites the original measurement missed.design/backlog/design-notes.md— lines ~152 and ~475.app/(authed)/backlog/_components/BacklogRow.tsx— the shipped ARIA comment MOTIR-2493 added, which is the spec this asset must match. It says so itself: "the asset's ownrole="row"annotation is stale and is corrected by its own design card."app/(authed)/backlog/_components/BacklogList.tsx— therole="list"viewport and why the role stays on it.tests/e2e/shell-a11y-wide.spec.ts— the zero-exclusion/backlogsweep that holds the shipped side.