Repository navigation
fix(frontend): return focus after chip picks, flag skeletons busy, label filter group - #1370
Conversation
…g loading skeletons busy Low-severity accessibility items from the 2026-10-08 review: - UX-27: Escape in the RI Exchange column-filter popovers (Active RIs and Reshape) closed them and dropped focus to <body>. They now return focus to the column's filter button, as the History popover already does. - UX-31: with the chip-select search input showing, aria-activedescendant stayed on the unfocused trigger, so screen readers never announced the highlighted option; it now lives on whichever element holds focus (the search input, which also gets aria-controls). Picking an option from the search input hid the focused element and dropped focus to <body>; focus now returns to the chip. - UX-34: skeleton containers now carry aria-busy="true" while a skeleton shows. Success renders replace the children without calling back into lib/skeleton.ts, so a MutationObserver clears the flag once no placeholder is left; teardownSkeleton clears it directly. The ladder canvas tab stop was already removed by LeanerCloud#688. - UX-36: #topbar-filters gets role="group" so its aria-label is exposed. The small-target links reported on Admin General could not be reproduced on this base and are left out. Refs LeanerCloud#729 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Warning Review limit reached
This review includes 9 billable files and costs up to $2.25. Or wait 59 minutes for your next included review. View limit details
Comment |
Conflicts: - frontend/src/riexchange.ts: took main. Main (LeanerCloud#1315, Refs LeanerCloud#18) replaced the per-table Active RI and reshape popover code this branch edited with the shared lib/column-filter-popover.ts, whose Escape handler already calls preventDefault and closeColumnFilterPopover(true), returning focus to the column's filter button, while outside clicks leave focus alone. So this branch's UX-27 behavior survives through the shared lib with no extra change; the branch's restoreFocus edits to the removed functions are dropped. - frontend/src/__tests__/riexchange-active-ri-filters.test.ts: kept both. Main's re-anchor and reshape-commit tests, followed by this branch's three UX-27 tests (Escape returns focus for the Active RI and reshape popovers, outside click does not), which now exercise the shared lib. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Independent review (IAmJSD's agent, fresh context, given only the diff, the issue and the PR text): APPROVE Blocking:
|
|
Local verification at
|
|
Marked ready and merged by IAmJSD's agent, at IAmJSD's request (IAmJSD reviewed the before/after screenshots; cristim is traveling), after the independent review and local verification above, all at |
Conflicts: - frontend/src/lib/skeleton.ts: kept both accessibility layers. Main's aria-busy + MutationObserver (LeanerCloud#729/LeanerCloud#1370) and this branch's sr-only "Loading…" status and 8 s "Still loading" note both run from activateSkeleton; teardownSkeleton clears the slow timer and aria-busy. - frontend/src/__tests__/skeleton.test.ts: kept both suites (slow fallback and aria-busy); the aria-busy table also covers showSkeletonText. - frontend/src/riexchange.ts: kept main's titled "Exchange Automation" frame (LeanerCloud#190) with aria-busy and put this branch's showSkeletonText inside it, replacing the visible "Loading exchange automation settings..." line. Imports unioned (skeletonText no longer used). - frontend/src/__tests__/riexchange-automation-settings.test.ts: kept main's LeanerCloud#190 frame tests; folded this branch's assertions (skeleton, sr-only status, no loading paragraph) into the loading test. - frontend/src/apikeys.ts, frontend/src/settings.ts: import unions. - frontend/src/styles/responsive.css: kept this branch's skeleton-table display:table rule and main's scroll-affordance rules (LeanerCloud#185). Also updated tests-e2e/purchasing-sections.spec.ts (from main) to expect the skeleton inside the frame instead of the removed loading paragraph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Picking an option from the filter chip's search list dropped keyboard focus to
<body>. Screen readers were never told which option was highlighted in that search list, loading skeletons weren't marked busy, and the global filter bar's label was ignored because itsdivhad no role. This PR fixes those items from the 2026-10-08 accessibility review. The review's other focus item, Escape in the RI Exchange column-filter popovers (UX-27), is already fixed on main by the shared popover from #1315; this PR adds regression tests for it.Refs #729
Partial: the UX-36 "34 small inline links on Admin General" item could not be reproduced (Admin General has no
<a>elements) and is not addressed; it needs confirmation of which page the review meant.What changed
lib/column-filter-popover.ts(refactor(frontend): share one column-filter popover across Plans and RI #1315) already returns focus to the filter button on Escape and leaves it alone on outside click. This PR adds 3 integration tests for the Active RIs and Reshape popovers inriexchange-active-ri-filters.test.ts.frontend/src/lib/chip-select.ts:aria-activedescendantgoes on whichever element holds focus while the menu is open (the search input when shown, otherwise the trigger), and the search input getsaria-controlspointing at the listbox, which now has anid.selectByValuereturns focus to the trigger when focus was inside the widget.frontend/src/lib/skeleton.ts:activateSkeletonsetsaria-busy="true"on the container.teardownSkeletonclears it, and a per-containerMutationObserverclears it on the success path, where callers replace the children directly.frontend/src/index.html:role="group"on#topbar-filters.riexchange-active-ri-filters.test.ts,chip-select.test.ts,skeleton.test.ts,html.test.ts, a one-line update inmobile-nav.test.ts, and newtests-e2e/a11y-focus-return.spec.ts.Why this approach
role="combobox"would be a bigger refactor of a shared widget.aria-busy, the observer avoids editing every skeleton caller: about ten modules render success by replacing children and never call teardown, and a missed one would leave a region stuck as busy. The observer only exists while a skeleton is showing.Screenshots
Screenshots were taken before this branch was merged with main; before = main at a14dc8f. After is this branch. Both are headless Chromium at 1440x900 against the repo's mocked e2e API. Each shot carries a black caption showing
document.activeElementand the relevant ARIA state.<body>,aria-activedescendantsat on the unfocused chip, and#topbar-filtershas no role. After, focus returns to the chip,aria-activedescendantwas on the focused search input, and#topbar-filtersisrole="group""Global filters". The "Failed to load dashboard: HTTP 404" banner in both shots comes from the shared mock.aria-hiddenand#recommendations-listhas noaria-busy. After, it hasaria-busy="true"while the skeleton shows; clearing after render is covered by jest.Verification
Run on this branch's head
e7f304df:frontend/src/riexchange.tsandfrontend/src/__tests__/riexchange-active-ri-filters.test.tsresolved, see the merge commit.aria-activedescendant, Enter pick and mouse pick returning focus, short-list behavior unchanged;aria-busyset on show, kept after observers run, cleared by teardown and by a success render, re-armed on reload;#topbar-filtersrole="group".e2e-local.sh: 204 passed, including the newa11y-focus-return.spec.ts(3 tests: Escape returns focus in the Active RI popover; the Account chip search keepsaria-activedescendanton the input and returns focus to the chip after a pick; the filter bar is a labelled group).scripts/test-git-secrets-allowlist.sh: 137 pass.Notes for review
aria-busyhalf of UX-34, and therole="group"half of UX-36. UX-27 is done on main via refactor(frontend): share one column-filter popover across Plans and RI #1315 and only gets tests here.e5ac75d2, after the reviewed commitb50ca383) removedtabindex="0"from the canvas (frontend/src/ladder-timeline.ts:36).<a>on all four Admin sub-tabs, plus Home, Purchases and RI Exchange, found no link under 24px ona14dc8f2. Admin General has no<a>elements; the only sub-24px targets there are three native 13px provider checkboxes inside their text labels. The review's screenshot (usab-admin-general.png) is only in a local archive, so it is unclear whether the item still applies or which page it meant.aria-busyMutationObserveris a small runtime mechanism; explicit clears in each render path are the alternative.aria-activedescendantstays on the trigger as before.Overlaps
Textual conflict with the open branch for #186 (
lib/skeleton.tsand__tests__/skeleton.test.ts, checked withgit merge-tree). Open branches touching the same files on other lines: #730 and #215 (index.html,mobile-nav.test.ts); #190, #676, #727, #728, #739, #749, #751, #756 (index.html).Note for the UI owner
This changes UI code that @cristim owns. IAmJSD reviewed the before/after screenshots and asked for it to be merged; any design point you disagree with can be a follow-up.
🤖 Generated with Claude Code