Repository navigation
A11Y-001: a row action revealed on hover also shows on keyboard focus and touch - #1005
Conversation
… and touch Row actions hidden until their row is hovered (delete, edit, pin, the "…" menu) were invisible to the people who cannot hover. In a seeded local household, Tab reached the grocery list's "Delete item" at opacity 0 (WCAG 2.4.7), and on a phone or tablet there is no hover, so it never appeared at all. tests/mobile-hover-reveal.test.ts guarded the touch half only where the classes sat side by side (`opacity-0 group-hover:opacity-100`). 26 controls had a class in between (`opacity-0 transition group-hover:opacity-100`) and slipped past it. Five wrappers already in the touch-safe form used `focus-visible`, which a <div> holding the buttons never matches. All 31 now use the form the codebase already uses elsewhere: opacity-100 sm:opacity-0 sm:group-hover:opacity-100 focus-visible:opacity-100 (a control) | focus-within:opacity-100 (a wrapper) coarse:opacity-100 Desktop keeps the clean hover reveal. The five decorative arrow icons inside visible links stay hover-only. Tests: - tests/a-control-revealed-on-hover-shows-on-focus-and-touch.test.ts reads every className (cn() arguments included) with the TypeScript parser, so class order no longer matters (31 problems on main, 0 here). - tests/e2e/a-control-revealed-on-hover-shows-on-focus.spec.ts rebuilds each of those elements on /login in the app's real stylesheet: hidden until hover at 1280 px, shown while it or its button has keyboard focus, shown on a coarse pointer and at 390 px (main: 31 hidden). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
The latest updates on your projects. Learn more about Vercel for GitHub. |
|
Bounded source/proof follow-up at9651fc3f5db764b02d857a43efcf867f6f69a290: Todos' action wrapper retains hidden ... sm:flex, so its edit/delete controls are still display:none below640px despite the new opacity utilities. The reconstructed E2E strips non-opacity classes and checks opacity only, allowing a390px pass while the actual controls remain absent. This is a pre-existing display restriction left unresolved, not a newly introduced permission change. Preserve the full responsive wrapper classes in a representative fixture or test the actual row, and assert rendered bounds/display plus keyboard/touch reachability at390px. Resolve the Todo display restriction within the existing lane or explicitly exclude it from the31-control touch-safe claim. Keep handlers, role/disabled guards, ARIA and menu behavior unchanged. The AST/CSS fixture remains useful utility coverage but is not a populated-route interaction sweep. Report exact commit/tests; no production accounts or data needed. |
Showing the row actions on touch puts WCAG 2.5.8 on them. axe at 390 px on a populated household found the notes grid card's pin and delete, and the grocery list's "Edit list", at 20 by 20 (p-1 around a 12px icon). p-1.5 makes them 24. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
…f checks it Review found the todos row's action wrapper still `hidden … sm:flex`: display:none below 640 px whatever its opacity, and nothing else in the row opens the editor or deletes. On a seeded local household at 390 px, "Edit task" and "Delete task" were not displayed in any of 48 rows. The wrapper is displayed at every width now (the opacity reveal stays), and its buttons are 26 px (p-1.5). The proof missed it because the e2e fixture kept only opacity classes. It now keeps every class but the animating ones, gives each row the relative, sized box a real row has, and reads three things per element: displayed, a real box, opaque. The unit rule also refuses a revealed element that is `hidden` below an sm: display. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Todos display restriction and the fixture's blind spot. Fixed at Confirmed, as you said. The todos row's action wrapper was The repair (
The proof, tightened:
Real rows (author-run, not a CI test). A seeded local household (the repo's seed scripts, local Supabase only, nothing remote), on the same builds:
Other checks on
The AST/CSS fixture is utility coverage, as you said. The populated-route evidence above was run by hand; it is not a sweep of every route. Generated by Claude Code |
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
…ith the populated-row repairs) No textual conflicts. shopping-module, notes-module and reminders-module carry both sides: #1005's phone/focus/touch reveal and 24px targets, and #1007's full-contrast checked row, unfaded note counts, named snooze summary and full-contrast AI tag. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KP9rt5rVQ9jDMpZp2xBy3K
A11Y-001, WCAG 2.4.7 (focus visible) and 2.5.8 (target size), plus the touch reach the existing M-005 guard was meant to protect. Found by sweeping a populated household: earlier signed-in sweeps used an empty one, so per-row controls never rendered.
The defect
Row actions hidden until their row is hovered (delete, edit, pin, the "…" member menu, add-container, and others) were invisible to anyone who cannot hover:
main, Tab reaches the grocery list's "Delete item" and the todos' "Edit task" at opacity 0.hidden … sm:flex, so "Edit task" and "Delete task" weredisplay:nonebelow 640 px, and nothing else in the row reaches them.tests/mobile-hover-reveal.test.tsguards the touch half, but only where the classes sit side by side (opacity-0 group-hover:opacity-100). 26 controls had a class in between (opacity-0 transition group-hover:opacity-100) and slipped past it.5 wrappers were already in the touch-safe form, but used
focus-visible:opacity-100. A<div>that holds the buttons never matches:focus-visibleitself.The fix
All 31 use the form the codebase already uses elsewhere (e.g.
shopping-module.tsx:251):Two follow-ups make what is now visible on a phone usable there:
8716ee2).7769ef3).The five decorative
ArrowRighticons inside links that are themselves visible stay hover-only. Handlers, disabled guards, ARIA labels and menus are unchanged.Tests
tests/a-control-revealed-on-hover-shows-on-focus-and-touch.test.ts(unit; the scan lives intests/helpers/hover-reveals.ts)className,cn(...)arguments included, is read with the TypeScript parser, so class order and spacing no longer matter.coarse:opacity-100, and not a bareopacity-0);focus-visiblefor a control,focus-withinfor a wrapper);hiddenbelow ansm:display.tests/e2e/a-control-revealed-on-hover-shows-on-focus.spec.ts(Chromium, no account) rebuilds every one of those elements on/login, which loads the app's real stylesheet. It keeps all of each element's classes except the animating ones, inside agroup relativerow with a real row's minimum height. For each element it reads three things: displayed, a non-zero box, and opaque.(pointer: coarse)asserted) and 390 px: each is shown without hover.maindisplay:none/dashboard/todos: "Edit task" and "Delete task" (author-run)The head merges current
main(83e2b01); the overlapping suites pass on the merge (34/34) and tsc is 0.Not run:
The fixture is utility coverage, and the populated rows above were checked by hand.
The other populated-household findings (dimmed done/inactive rows, the reminders' unnamed snooze
<summary>, the meals sidebar toggles and carousel) are in a separate PR.No migration, configuration or dependency change.
🤖 Generated with Claude Code
https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4