You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
As a keyboard-only user of a list that loads in batches, I want focus to stay on the "Load more" control while its batch loads so that I can load several batches in a row without re-tabbing from the top of the page each time.
Parent Epic: none (standalone; follow-up to #2060) Priority: Should Have
Problem
InfiniteScrollFooter sets disabled={status === 'loading'} on the Load more / Retry button (client/src/components/InfiniteScrollFooter/InfiniteScrollFooter.tsx). It is the same DOM node across the idle / loading / error states, so when a keyboard user activates it, the button they are standing on becomes disabled mid-interaction. A disabled element is removed from the tab order and loses focus, so the browser drops focus to <body>.
Consequence for the diary page (#2060's AC16 path): press Enter on "Load more", the batch loads correctly, but focus is gone. To load the next batch the user must Tab all the way back through the page — including every newly appended entry card. AC16's letter is met (one activation loads one batch) which is why #2060 was accepted, but repeated keyboard-only paging is materially worse than pointer paging.
The same defect ships to every future consumer of the shared component, not just the diary page.
Why this was deferred rather than fixed in PR #2063
It is not new in round 2 — disabled={status === 'loading'} was present at round 1 and I did not flag it, so it is not a regression introduced by the round-2 fixes.
The remedy is a design-system decision, not a mechanical edit: aria-disabled="true" + a no-op click guard keeps the node focusable but changes the semantics of every disabled button pattern in the app. wiki/Style-Guide.md is owned by ux-designer, who should rule on whether this becomes the house pattern for in-place async buttons or stays local to this component.
Acceptance Criteria
1. Given the Load more control has keyboard focus, When I activate it and the batch request is in flight, Then the control still has focus (document.activeElement) and focus has not moved to <body> or any other element.
3. Given the batch request is in flight, When a screen reader reads the control, Then it is exposed as unavailable/busy (e.g. aria-disabled="true") rather than silently ignoring activation.
4. Given the batch resolves and more entries remain, When the control returns to its idle state, Then it is still focused and a visible focus indicator is rendered in both light and dark mode.
5. Given the batch resolves and the end of the list is reached, When the control unmounts and is replaced by the end-of-list row, Then focus is moved somewhere deliberate and announced — not silently dropped to <body>.
6. Given ux-designer has ruled on the pattern, When the fix lands, Then wiki/Style-Guide.md records whether aria-disabled + click-guard is the house pattern for in-place async buttons or is scoped to InfiniteScrollFooter.
7. Given the E2E suite, When it runs, Then a test asserts criterion 1 and criterion 4 by keyboard alone (the existing keyboard test in e2e/tests/diary/diary-list.spec.ts asserts the batch loads but stops short of re-asserting focus after the load).
Notes
Applies to the shared component, so the fix is inherited by any future consumer — the diary page is only the first.
As a keyboard-only user of a list that loads in batches, I want focus to stay on the "Load more" control while its batch loads so that I can load several batches in a row without re-tabbing from the top of the page each time.
Parent Epic: none (standalone; follow-up to #2060)
Priority: Should Have
Problem
InfiniteScrollFootersetsdisabled={status === 'loading'}on the Load more / Retry button (client/src/components/InfiniteScrollFooter/InfiniteScrollFooter.tsx). It is the same DOM node across theidle/loading/errorstates, so when a keyboard user activates it, the button they are standing on becomesdisabledmid-interaction. A disabled element is removed from the tab order and loses focus, so the browser drops focus to<body>.Consequence for the diary page (#2060's AC16 path): press Enter on "Load more", the batch loads correctly, but focus is gone. To load the next batch the user must Tab all the way back through the page — including every newly appended entry card. AC16's letter is met (one activation loads one batch) which is why #2060 was accepted, but repeated keyboard-only paging is materially worse than pointer paging.
The same defect ships to every future consumer of the shared component, not just the diary page.
Why this was deferred rather than fixed in PR #2063
disabled={status === 'loading'}was present at round 1 and I did not flag it, so it is not a regression introduced by the round-2 fixes.aria-disabled="true"+ a no-op click guard keeps the node focusable but changes the semantics of every disabled button pattern in the app.wiki/Style-Guide.mdis owned byux-designer, who should rule on whether this becomes the house pattern for in-place async buttons or stays local to this component.Acceptance Criteria
document.activeElement) and focus has not moved to<body>or any other element.disabledattribute).aria-disabled="true") rather than silently ignoring activation.<body>.ux-designerhas ruled on the pattern, When the fix lands, Thenwiki/Style-Guide.mdrecords whetheraria-disabled+ click-guard is the house pattern for in-place async buttons or is scoped toInfiniteScrollFooter.e2e/tests/diary/diary-list.spec.tsasserts the batch loads but stops short of re-asserting focus after the load).Notes