Repository navigation
Below lg, notices queue: the newest on screen, "+N" opens the rest - #986
Conversation
A11Y-001, the compact queue approved on #778. Three notices stacked over a phone's page covered its own controls: at 360 px all of "Add First Contact", centre included, and in the contact dialog Friend, Other and Phone. Below lg (max-width: 1023px) the provider now shows one card, the newest not yet dismissed, and a "+N" button above it ("2 more notices", aria-expanded) that opens every card. From lg nothing changes. - Clocks: only the card on screen counts down; a queued notice has no clock and gets its whole window when it shows; hover or focus on the stack holds it, and an opened list holds every notice until pointer and focus have both left it. Escape closes the list and returns focus to "+N". - One element per notice: a queued notice keeps its live region and its actions in the accessibility tree, visually hidden (sr-only) and out of the tab order. It is announced once, when pushed, and not again when it comes on screen; a screen reader can still reach a queued Undo. - Crossing lg either way keeps every notice and doubles none. Measured (Chromium, local production build): collapsed at 360x780 the queue leaves "Add First Contact" clear (0x0 overlap) and covers the language bar as one short notice does; at 390x844 it covers nothing. With the contact dialog open, the "+2" above the card takes Phone's centre at 360 (Alternate phone at 390, as one notice did). The stack's old rows are kept as the "+2 opened" rows: what that choice costs. toast.more plural in the seven locales; the four fixture specs that mount the toast provide the media-query hook and usePlural. 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. |
|
TOAST-QUEUE-FOCUS-BOUNDARY at04dbd258: bounded source/test revision before accessibility acceptance.
|
|
Current-head CI blocker at04dbd258: CI38024675477 E2E job114132866087 failed at smoke step20; Core/DB/Mobile passed. Cause is not established from step metadata, so do not equate it with the earlier local geometry failure without matching evidence. Add this exact-run diagnosis to the existing toast revision: identify the failing assertion/artifact, reproduce with an isolated fixture, and repair without removing acceptance assertions or weakening the new focus/dialog cases6093866729. Preserve failed/skipped phases and exact commit/run evidence; no production action or merge requested. |
Review of 04dbd25 on #986, and its CI blocker. CI run 38024675477 (job 114132866087): two weekly-meal-planner cases failed their page-error check with "usePlural is not a function". With realToast that fixture mounts the real toast, and its locale-provider mock had no usePlural, which the "+N" label now calls; the module-graph test that caught four other fixtures does not cover it. Reproduced alone on 04dbd25, fixed by the same one-line mock the others use: 68/68. - A notice whose control holds focus stays on screen even when it is not the newest: a notice arriving, or the window crossing below lg, no longer leaves focus on a visually hidden, untabbable button. "+N" counts what is hidden. - The Escape that closes the "+N" list stops there (stopPropagation and stopImmediatePropagation), so the contact dialog's document listener underneath does not also close; with the list closed Escape passes. - Below lg, and with the list open, the stack stays held while either the pointer or focus is in it. From lg, either leaving resumes, as before. - "+N" is a measured surface (data-toast-more): corner, dialog, under-notice and controls-under reports count it beside the visible cards, and no longer count queued cards. Re-measured: at 390 the collapsed dialog row also covers Phone (centre clear); with "+2" opened "+N" crosses the top 21px of Add First Contact (centre clear), and the replaced-placement control gains it (BEFORE_OPENED). The 360 rows hold. - DIALOG_OPEN_EXPANDED is exercised: dialog open, "+2" opened, at 360 and 390. Escape is tested with the dialog still open. - The queue's E2E cases run with service workers blocked, as the corner cases do: once, public/sw.js let a refused contact write through. - Announcement wording: one live element per notice from its push; what a screen reader announces is stated as unverified. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Re 6093866729 and 6094147200, both addressed in 7fba590. CI blocker at 04dbd25: run 38024675477, job 114132866087Failing assertion. From the job log, not the step metadata: 2 failed, 8 skipped, 2,562 passed. Both failures are in
Each fails its Cause. With Reproduced in isolation. I ran that spec alone on 04dbd25's toast with the old mock: both tests fail with the same error. Then I added The four review points
Announcement claims, kept unverified. The PR body and the tests now say "one live element per notice, from its push" and stop short of "announced once". What a screen reader announces from that markup, and whether it can act on a queued Undo, is not verified. Validation (Chromium only, local production build)
#985's and #984's tests are untouched by this branch. No merge, deploy or production changes. Generated by Claude Code |
|
At7fba590/ACK6095308706, the prior arrival/resize focus, handled Escape, mobile mixed-hold and actual-dialog/+N geometry cases are source-corrected. One bounded keyboard-removal case remains: dismiss() removes a focused Undo/Dismiss without reconciling paused/focusIn/focusedId. If removal emits no blur, the activeElement check inside leave() is never reached, so later notices can remain paused indefinitely with the pointer outside. Add a real-DOM focused Dismiss/Undo removal→focus recovery→queue drain regression, plus resizing away a focused +N control. Reconcile the hold on removal while preserving hover/focus protections. This is source-derived, not an executed reproduction. Keep screen-reader announcement/actionability claims unverified; update remaining 'announced once' wording and stale unit-test count rather than treating element counts as announcement evidence. Preserve existing acceptance assertions and exact-head CI separation. |
… the hold lets go Review of 7fba590 on #986. A focused Dismiss or Undo takes its notice with it, and a focused "+N" goes with the mode; neither fires a blur, so the stack kept believing it held focus and its clocks never resumed. Reproduced in Chromium on 7fba590: after a focused "+N" was resized away, or a focused Undo removed its notice, no notice left on its own (8 s). Now, when a control holding focus is removed (and only then), focus goes on to the notice that took the removed one's place, else the newest on screen; with none left, back to where it came from, and the hold is released (an open list closes; clocks resume unless the pointer is on the stack). Recovery is set off only by such a removal: an ordinary focus move, whose blur React handles while focus is briefly on the page, is left alone (the first version mistook one for a loss and pulled focus back to the newest notice). E2E, keyboard only: a focused Dismiss, three times, hands focus to each next notice and finally back to its origin, and a new notice then leaves on its own; a focused "+N" removed by crossing lg hands focus to the newest notice and, focus tabbed out, all three leave; a focused Undo with nothing left on screen hands focus back and leaves nothing held. 9/9 with --repeat-each=3; the spec and the toast fixtures 240/242 (the 360 long row, local; a fixture-account setup error, passing on re-run). Remaining "announced once" wording is reworded: the markup is the intent, and no screen reader verified it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
|
Re 6095395366, addressed in b6c551f. Executed reproduction, on 7fba590 in Chromium. A removed control fires no blur. After a focused Fix
Real-DOM regressions (
Results
Wording. The remaining "announced once" comment in Generated by Claude Code |
This implements the mobile compact notification queue (A11Y-001) from plan 5972218163, approved on #778 in 5972876311. It is a separate draft from
main36a516d. Nothing is merged or deployed, and there are no production, security or config changes.The defect
Below
lg, every notice showed at once. At 360px, three notices stacked over the page covered the page's own controls:The change
Below
lg((max-width: 1023px)):+Nbutton above it (accessible name "2 more notices",aria-expanded) opens every card.From
lg: nothing changes.+Ntakes focus; Enter opens the list; each Dismiss/Undo takes focus in turn. Escape closes the list and returns focus to+N.lglg), so focus is never left on a hidden control.+Ncounts what is hidden.+Nis removed, focus goes to the notice that took its place (else the newest on screen). With none left, it goes back to where it came from and the hold is released. A removed control fires no blur, so before this the stack stayed held (reproduced on 7fba590).+Nlist consumes the Escape, so a dialog underneath stays open. With the list closed, Escape passes.lg, or list open)lg, either leaving resumes, as before this PR.One deviation from the approved plan: how notices are announced
The plan used two hidden live regions written on push, with the visible card dropping its role.
What I built instead (a proposal): each notice stays one live element from the moment it is pushed. A queued notice is visually hidden (
sr-only,data-queued) and removed from the tab order (tabIndex=-1), but not from the accessibility tree. Coming on screen later only changes its class.Why: the E2E run of the plan's version found every message in the accessibility tree twice: once as the hidden copy, once as the card.
Not verified: what a screen reader announces from this markup, and whether it can act on a queued Undo. Element and accessibility-tree counts do not establish either, so neither is claimed. This is a material choice and yours to accept; if you prefer the plan's two hidden live regions, I can switch back.
Files
components/ui/toast.tsxLIFETIME,ToastApi, tones, roles, haptics and desktop placement are unchanged.lib/i18n/messages/*.json(7)toast.more.one/toast.more.other.tests/toast-mobile-queue.test.ts+N, Escape, crossinglg, focus kept on screen, one live element per notice. These are element and accessibility-tree checks, not announcement evidence.tests/a-toast-you-can-still-reach-waits.test.tsuseEffect. Assertions unchanged.tests/e2e/form-error-semantics.spec.ts+2opened" rows.tests/e2e/{bill-schedule-repair,messaging-preserve-access,quick-capture-task,recurring-bill-payment}.spec.tstests/medications-fixture-module-graph.test.tsrequires them to loadlib/hooks/use-media-query.tsand mockusePlural. One line each, nothing else.tests/e2e/weekly-meal-planner.spec.tsrealToastfixture's locale-provider mock gainsusePlural, one line. Its absence was CI run 38024675477's failure at 04dbd25.Measured (Chromium, local production build, local Supabase)
Every pointer-intercepting surface is measured: the visible cards and
+N(data-toast-more). Queued cards take no pointer and are not counted.+N)+2above the card)With
+2opened (CORNER_EXPANDED,UNDER_NOTICE_EXPANDED,DIALOG_OPEN_EXPANDED, now each exercised, the dialog rows with the dialog open), the cards are where the old stack was and+2sits above them:+2(42×32) crosses the top 21px of Add First Contact; its centre stays clear.Opening the list is the reader's choice, and those rows are what it costs. The replaced-placement control at 390 with
+2opened also gains Add First Contact. That is recorded asBEFORE_OPENED, because #778'sBEFORErows counted cards only.One width differs locally. The "whole button" check compares the overlap with the button's own box rather than a fixed number: locally the button measures 181px wide, against 184px on #778.
Validation
form-error-semantics.spec.ts, both halves (7fba590)main(36a516d) built and run here (see #985). A single notice renders exactly as before in this branch. The new queue, focus and dialog cases also passed 60/60 with--repeat-each=3.weekly-meal-plannertoast.tsx.+Nresized away; a focused Undo. On 7fba590 the+Nand Undo cases leave the stack held. All pass here: 9/9 with--repeat-each=3.node-version-is-pinnedand twostream-cancellation-runtimetests.tsc --noEmitform-error-semanticsedit), #984 (8ec73c4), #981 and #982 headsAll of the above is Chromium coverage only. Safari and Firefox were not run.
Open choice
The
+Nbutton's visible text is+2; its accessible name is "2 more notices", treating "+" as a symbol (WCAG 2.5.3). If you want the name to contain "+2" verbatim, it is a one-key change.🤖 Generated with Claude Code
https://claude.ai/code/session_01TRY21ZKsFrfB3qtoP972A4
Generated by Claude Code