Skip to content

fix(a11y): mark two non-interactive wrappers as presentation and retighten the lint ceiling - #7768

Merged
dwu96 merged 1 commit into
mainfrom
fix/a11y-noninteractive-elements
Sep 2, 2026
Merged

fix(a11y): mark two non-interactive wrappers as presentation and retighten the lint ceiling#7768
dwu96 merged 1 commit into
mainfrom
fix/a11y-noninteractive-elements

Conversation

@pepmach

@pepmach pepmach commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Why no screenshot: the diff adds two role="presentation" attributes and changes one CI number — the accessibility tree loses nothing and no pixel moves, so a before/after pair would be two identical images.

What

Site Change
design-critique/Composer.tsx drop surface role="presentation" — the drag handlers are a pointer-only shortcut; the keyboard path is the existing role="button" drop tile
design-critique/FindingRow.tsx row wrapper role="presentation" — hover styling only; activation already lives on the <Clickable> child
.github/workflows/ci.yml --max-warnings 604599

Both wrappers carried pointer handlers while their activation lived on a child control, so jsx-a11y read them as non-native interactive elements. presentation states what they are, and since a div's implicit role is generic, it removes nothing from the accessibility tree.

Why the ceiling moves

The step's own comment requires it: "the ceiling must EQUAL the measured count, not sit above it: slack is silent admission, and a warning that lands inside it never surfaces again."

It had drifted to 604 as a deliberate fleet unblock — 7362b6a2b (#7259) landed a third react-hooks/exhaustive-deps warning in ArtifactsPage.tsx while the ceiling said 603, so main measured one over its own ceiling and every open PR touching website/** went red on a warning its diff never wrote. That page measures zero warnings now, so the slack has no remaining justification. Removing two more warnings takes the tree to 599, which is what this sets.

Pattern harvest

Rule candidate: CI — make the eslint ratchet self-measuring instead of a hand-maintained literal. The defect class is "the ceiling and the tree disagree," and it fired twice in one day in opposite directions: #7696 raised 603 → 604 to unblock the fleet, and this PR lowers it to 599. Both were manual re-measurements of a number CI can compute. Two mechanisms, either sufficient: (a) have the Lint step measure the merge base itself and fail only on growth relative to it, which is what the step's comment already describes in prose — that removes the literal and with it the window where a merge into a moved main fails on a warning the diff never wrote; or (b) if the literal stays, add a check that fails when it does not equal the measured count, so slack cannot accumulate silently, which is the property the comment asks for and nothing currently enforces.

Not generalizable: the two role="presentation" fixes themselves. useSemanticElements is already an enforced lint rule, so this class is caught at authoring time; these two sites predate its coverage of their pattern rather than escaping it.

Deliberately not fixed here

The pin marker in DesignCritiquePage.tsx carries role={interactive ? 'button' : undefined} and tabIndex={interactive ? 0 : undefined}. jsx-a11y resolves only literal role values, so that pin is neither a button nor presentational to the linter, and a keyboard user gets a <span> with a click handler — a real defect. Fixing it means splitting into literal branches, which means hoisting the shared style object, which moves CSS box-shadow strings onto lines the diff-scoped i18n gate counts at zero tolerance. Two gates disagree on that line; resolving it belongs to that page (whose i18n debt is already tracked), not to a ceiling correction. Left as a warning, inside the new ceiling.

Tested

  • npx eslint src/ (cwd website, deps synced via npm ci against current main's lockfile): 601 → 599 warnings, 0 errors.
  • Ceiling verified exact: passes at --max-warnings 599, fails at 598.
  • Diff-scoped i18n gate with the PR's own base: 19 checks · PASS.
  • npx tsc -b: clean. DesignCritique* specs: 6 files, 107 tests, all pass.

Notes

PR #7569 is burning the same ceiling down repo-wide and touches these files too; it can drop the redundant hunks on its next rebase. Kept small so the ceiling correction can land on its own.

@pepmach
pepmach requested a review from a team September 2, 2026 00:16
@pepmach
pepmach requested a review from a team as a code owner September 2, 2026 00:16
@github-actions github-actions Bot added readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Sep 2, 2026
@pepmach
pepmach force-pushed the fix/a11y-noninteractive-elements branch from b61aff3 to 85a568e Compare September 2, 2026 01:46
@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Design Review (Fable 5) — ✅ PASS

Design-level review of dc25dbe64556da01ab49e3cd840dfb446372bdc3 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

Design-Verdict: PASS

Removes real silent-admission slack, wrappers keep child semantics intact, and the hand-maintained-literal root cause is correctly deferred to the named follow-up.

[DESIGN-REVIEWED] dc25dbe

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

GPT 5.6 Review — ✅ no blocking findings

GPT 5.6 completed its review of dc25dbe64556da01ab49e3cd840dfb446372bdc3 and found no blocking issues.

This comment is updated in place on each push.

Review details

No findings.
[GPT-REVIEWED] dc25dbe

False positive or not applicable? A repository writer can comment:
/ai-review override gpt dc25dbe64556da01ab49e3cd840dfb446372bdc3: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

UX Review (Fable 5) — ✅ PASS

UX-level review of dc25dbe64556da01ab49e3cd840dfb446372bdc3 — updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

This PR touches no rendered pixels, strings, or flows — it annotates two wrapper divs as role="presentation" (verified: keyboard activation genuinely lives on the role="button" drop tile in Composer and the <Clickable> in FindingRow, so nothing is removed from the accessibility tree or keyboard path) and retightens a CI lint ceiling. There is no user-experience surface to fault.

UX-Verdict: PASS

No user-visible change: both presentation wrappers keep their keyboard paths on the drop tile and <Clickable>, so AT and pointer behavior are unchanged.

[UX-REVIEWED] dc25dbe

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

Opus 4.8 Review — ✅ no blocking findings

Reviewed dc25dbe64556da01ab49e3cd840dfb446372bdc3 — this comment is updated in place on each push.

Review details

No findings.

[OPUS-REVIEWED] dc25dbe

Verdict parsed from the review's SHA-scoped output markers for commit dc25dbe64556da01ab49e3cd840dfb446372bdc3.

False positive or not applicable? A repository writer can comment:
/ai-review override fable dc25dbe64556da01ab49e3cd840dfb446372bdc3: <one-sentence reason>

@github-actions

github-actions Bot commented Sep 2, 2026

Copy link
Copy Markdown
Contributor

First Principles Review (Fable 5) — ✅ PASS

Premise-level review of dc25dbe64556da01ab49e3cd840dfb446372bdc3 — why this exists and whether the shipped surface is the smallest honest version. Updated in place on each push. A BLOCK verdict blocks PR readiness; PASS/CONCERNS are advisory.

The evidence checks out against the repo: both wrappers' activation genuinely lives elsewhere (the role="button" drop tile at Composer.tsx:75 and the <Clickable> at FindingRow.tsx:51), and the ceiling comment in ci.yml itself mandates ceiling == measured count. Final review:

First-Principles-Verdict: PASS

Two false-interactive wrappers get truthful roles and the ceiling returns to the count the CI comment itself mandates — every item is derived, nothing rides along.

What this change ships

Intent: stop two wrappers from lint-reading as unreachable controls and take back the slack the lint ceiling's own instruction forbids — a FIX.

  1. Composer drop surface marked layout-only; keyboard path stays on the role="button" tile (Composer.tsx:75) — justified
  2. FindingRow hover wrapper marked layout-only; activation stays on its <Clickable> child (FindingRow.tsx:51) — justified
  3. Lint warning ceiling 604 → 599 — justified: the step's own comment ("the ceiling must EQUAL the measured count") is the documented invariant, so the reset is derived, not discretionary
  4. Ceiling comment rewritten to state present behavior — intrinsic to item 3, per the no-narration comment rule

No new public surface, config key, or concept ships, so consumer counts don't arise. The two declared deferrals — the conditional-role pin in DesignCritiquePage.tsx (a real keyboard defect, blocked by a competing i18n gate) and the hand-maintained ceiling literal that has now been wrong twice — are both named in the description with their level and remainder stated, which is exactly what lens 6 asks of an out-of-scope cause; restating them would change nothing the author ships. The overlap with #7569 is declared and resolves by rebase, not by this PR growing.

[FIRST-PRINCIPLES-REVIEWED] dc25dbe

@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention and removed readiness: checking Automated validation is still running merge conflict Branch has merge conflicts with its base — author must resolve before merge labels Sep 2, 2026
@pepmach
pepmach force-pushed the fix/a11y-noninteractive-elements branch from 85a568e to 918eef3 Compare September 2, 2026 02:10
@pepmach pepmach changed the title fix(a11y): resolve four non-native interactive elements and retighten the lint ceiling fix(a11y): mark two non-interactive wrappers as presentation and retighten the lint ceiling Sep 2, 2026
@github-actions github-actions Bot added readiness: checking Automated validation is still running readiness: action required A blocking check or review needs attention and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
…ghten the lint ceiling

The drop surface in design-critique/Composer.tsx and the row wrapper in
FindingRow.tsx both carry pointer handlers while their activation lives on a
child control, so jsx-a11y read them as non-native interactive elements. Both
are now role="presentation", which states what they are and removes nothing
from the accessibility tree.

That takes the tree from 601 warnings to 599, so the eslint ceiling returns to
the count the tree actually measures. It had drifted to 604 as a fleet unblock
after a third exhaustive-deps warning landed in ArtifactsPage.tsx while the
ceiling said 603 — main measured one over its own ceiling and every PR touching
website/** went red on a warning its diff never wrote. That page measures zero
warnings now, so the slack has no remaining justification.

The pin marker in DesignCritiquePage.tsx is deliberately left alone. Its
role/tabIndex ternaries are invisible to jsx-a11y, which resolves only literal
role values, but splitting them into literal branches means hoisting a shared
style object — and that moves CSS box-shadow strings onto lines the diff-scoped
i18n gate counts at zero tolerance. Two gates disagree there; the fix belongs to
that page, not to a ceiling correction.
@pepmach
pepmach force-pushed the fix/a11y-noninteractive-elements branch from 918eef3 to dc25dbe Compare September 2, 2026 02:44
@github-actions github-actions Bot added readiness: checking Automated validation is still running and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
@github-actions github-actions Bot added readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running readiness: passed Eligible automated validation passed for the current revision and removed readiness: action required A blocking check or review needs attention readiness: checking Automated validation is still running labels Sep 2, 2026
@dwu96
dwu96 enabled auto-merge (squash) September 2, 2026 04:06

@dwu96 dwu96 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier 1 auto-approve: fix (3 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: fix with a clear root cause — two non-interactive wrapper divs in the design-critique app carried no role while their activation lives on inner controls, so jsx-a11y warned; adding role="presentation" removes both warnings and the eslint ceiling in ci.yml is retightened from 604 to the 599 the tree now measures.

@iamwhatever iamwhatever left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier 1 auto-approve: fix (3 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean, security checklist all-NO, AI reviewers green. Category: adds role="presentation" to two non-interactive design-critique wrappers whose activation already lives on child controls, and lowers the eslint --max-warnings ceiling 604 -> 599 to the count the tree now measures. Root cause is explicit: the wrappers carried an implicit generic role while jsx-a11y required the intent be stated; no behaviour or pixel change.

@dwu96
dwu96 merged commit ccc6a53 into main Sep 2, 2026
77 of 78 checks passed
@dwu96
dwu96 deleted the fix/a11y-noninteractive-elements branch September 2, 2026 04:07
@github-actions github-actions Bot removed the readiness: passed Eligible automated validation passed for the current revision label Sep 2, 2026

@chenmingwei23 chenmingwei23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tier 1 auto-approve: fix (3 files). Criteria: no conflict, no requested changes, security path denylist clean, design-doc gate clean, SAST annotations clean (Semgrep 0, CodeQL 0 alerts), security checklist all-NO, AI reviewers green. Category: fix -- two role=presentation a11y attributes on non-interactive wrappers plus eslint ceiling retightened 604 to 599, clear root cause, no behaviour change to controls.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants