Skip to content

moooon

Motir

Vibe your whole project. Bring an idea — Motir's three AI layers plan it, track it, and ship it, end to end. You're looking at Motir, built in Motir.

  • Vibe Project
  • Open Source
  • AI Agent
  • AI Loop
1
requests
0
upvotes
145
planned
1,361
shipped

Motir · Work items

MOTIR-2493Done

/backlog rows are role="row" inside a role="list" — two CRITICAL axe violations on every populated backlog, and neither role can do its job

Surfaced by MOTIR-2482 — the first sweep ever to load /backlog with axe, and the first to load it POPULATED. Filed rather than absorbed, per that card's own acceptance criterion: "any defect it surfaces that is NOT a --el-text-* contrast failure logged as its own bug rather than absorbed here."

Repo: motir-core. One PR.

The measurement

tests/e2e/shell-a11y-wide.spec.ts sweeps /backlog against a project with four ranked rows. axe (WCAG 2.1 A+AA, zero rule exclusions) reports two CRITICAL violations, both on every row:

  • aria-required-children — on the scroll viewport and on each backlog-row-*
  • aria-required-parent — on each backlog-row-*

The cause — two roles that cannot compose

app/(authed)/backlog/_components/BacklogList.tsx:311 renders the scroll viewport as role="list" with an aria-label. app/(authed)/backlog/_components/BacklogRow.tsx:65 defaults rowRole = 'row', applied at line 116.

A list requires listitem children; a row requires a rowgroup / grid / table / treegrid parent. Neither holds, so BOTH fail and a screen reader gets neither a list it can count nor a grid it can navigate — the rows are announced with no container semantics at all.

The row component already KNOWS about the other arm: rowRole?: 'row' | 'listitem', and ProposedSprintPanel.tsx:254 passes rowRole="listitem" — the sprint-plan panel composes it correctly while the backlog itself does not.

The decision this card owes

Which semantic is right is a real choice, not a typo, and it should be made once and stated:

  • list + listitem — matches ProposedSprintPanel, matches the drag-to-rank interaction (an ordered list of items), and is the smaller diff (default rowRole at the BacklogList call site).
  • grid/treegrid + rowgroup + row + gridcell — matches the /items list view, which already ships a real treegrid with aria-level / aria-expanded (shell-a11y-detail.spec.ts sweeps it), and would let the backlog's inline-edit cells be reachable by the grid keyboard model.

Read the backlog design asset under design/backlog/ before choosing; the row IS the drag handle (design panel 3), which the chosen model must not break.

Acceptance criteria

  • /backlog reports ZERO aria-required-children and ZERO aria-required-parent violations under the same WCAG_TAGS and zero rule exclusions the other sweeps use, on a POPULATED backlog.
  • The chosen semantic is applied to the container AND its rows together — the container's role, the rows' role, and any intermediate wrapper agree, verified by the sweep rather than by reading.
  • The rank drag-and-drop still commits (the existing backlog dnd E2E stays green) — the row is the drag handle, and the role change must not alter the dnd-kit attribute spread order that BacklogRow.tsx:110 comments on.
  • ProposedSprintPanel's rowRole="listitem" override is reconciled with the choice: if the backlog becomes a list, the override is redundant and goes; if it becomes a grid, the panel keeps its own list semantics deliberately and the prop's doc comment says so.
  • The two disableRules entries this defect forced into tests/e2e/shell-a11y-wide.spec.ts's /backlog sweep — each commented with this card's key — are REMOVED in the same PR, so the route returns to a zero-exclusion sweep.

Context refs

  • app/(authed)/backlog/_components/BacklogList.tsx — the role="list" viewport (line ~311).
  • app/(authed)/backlog/_components/BacklogRow.tsxrowRole (lines 65 / 92 / 116) and the dnd-kit spread-order comment (line ~110).
  • app/(authed)/backlog/_components/ProposedSprintPanel.tsx — the rowRole="listitem" call site.
  • tests/e2e/shell-a11y-wide.spec.ts — the sweep that measured it, and the carve-out to remove.
  • app/(authed)/items/_components/ — the shipped treegrid, if the grid arm is chosen.