Skip to content

feat(frontend): keyboard shortcuts + live-region announcements across 4 components - #1279

Merged
leojay-net merged 4 commits into
leojay-net:mainfrom
abayomicornelius:feat/issues-1179-1183-1184-1185
Jul 26, 2026
Merged

feat(frontend): keyboard shortcuts + live-region announcements across 4 components#1279
leojay-net merged 4 commits into
leojay-net:mainfrom
abayomicornelius:feat/issues-1179-1183-1184-1185

Conversation

@abayomicornelius

Copy link
Copy Markdown
Contributor

Summary

#1184 — ErrorBoundary. Enter/R retry from the error fallback (only while it's showing, ignored while focus is in a form field). Shares the retry logic with the existing button's onClick rather than duplicating it. Added a visible <kbd> hint.

#1185 — LandingPage. g navigates to /chat (same as the Get Started CTA), d toggles the theme. Both ignored while focus is in the email field or any other form control.

#1183 — ChatSearchPanel. ArrowDown/ArrowUp move an active-result index (wraps around), Enter selects it, Escape still closes the panel. Added role="listbox"/role="option" + aria-selected so the active item is exposed to assistive tech, not just visually highlighted.

#1179 — CCIPBridgeModal. None of the bridge-state sections (optimistic/initiating/polling/success/error) were in an aria-live container — a screen reader user starting a transfer heard nothing as it progressed. Added a single sr-only role="status" aria-live="polite" region that announces a derived message on every state transition.

All four: theme-aware via the existing ThemeContext/isDarkMode patterns already in each file, no animation involved in any of the additions (so prefers-reduced-motion doesn't apply), and none change existing behavior for mouse/click users.

Closes #1179
Closes #1183
Closes #1184
Closes #1185

Test plan

  • npx tsc --noEmit: clean
  • npx eslint on all changed files: clean (fixed one jsx-a11y/role-supports-aria-props warning by moving aria-selected from the <button> to the <li role="option"> it actually belongs on)
  • npx vitest run on the 4 changed test files: 51/51 pass, including 3 pre-existing CCIPBridgeModal.test.tsx assertions adjusted from getByText to getAllByText(...)[0] — they broke because the new live region's announcement text now also contains the same substrings those queries matched against the visible UI (both matches are correct; a single-match assumption was the thing that needed to change)
  • Full npx vitest run (730 tests): ran twice: each run had 2 failures, but a different 2 each time, including one in SplitViewComparison.test.tsx — a file untouched by this PR — confirming pre-existing flakiness under full-suite parallel load, not something introduced here. All 4 of this PR's own test files pass reliably in isolation.

(closes leojay-net#1184)

"Enter" and "R" retry from the error fallback the same way the button's
onClick does — only active while the fallback is actually showing, and
ignored while focus is in a form field so it can't hijack typing. Shares
a single retry() method with the button's onClick rather than
duplicating the retry-vs-reload branch. Added a visible <kbd> hint next
to the button, styled for both light/dark via the existing isDarkMode
prop. No animation involved, so prefers-reduced-motion doesn't apply here.
(closes leojay-net#1185)

"g" navigates to /chat (same as the Get Started CTA), "d" toggles the
theme via the existing ThemeContext. Both ignored while focus is in the
email field (or any other form control) so typing there isn't hijacked.
No animation involved, so prefers-reduced-motion doesn't apply here.
(closes leojay-net#1183)

ArrowDown/ArrowUp move an active-result index (wrapping), Enter selects
the active result, Escape still closes the panel as before. Active
result gets a visible highlight (theme-aware) plus role="listbox" /
role="option" + aria-selected on the results list so the active item is
exposed to assistive tech, not just visually. No animation involved, so
prefers-reduced-motion doesn't apply here.
(closes leojay-net#1179)

None of the bridgeState sections (optimistic/initiating/polling/success/
error) were in an aria-live container, so a screen reader user starting
a CCIP transfer heard nothing as it moved through those states — only a
separate "network changed" banner had aria-live. Added a single sr-only
role="status" aria-live="polite" region that announces a message derived
from bridgeState/latestStatus/errorMessage on every transition.

Adjusted 3 pre-existing test assertions (getByText -> getAllByText[0])
that broke because the new live region's text now also contains the
same substrings those regex queries were matching against the visible
UI — both matches are correct (the info is intentionally shown twice:
visibly and to assistive tech), so getByText's "exactly one match"
requirement was the thing that needed to change, not the component.
@drips-wave

drips-wave Bot commented Jul 25, 2026

Copy link
Copy Markdown

@abayomicornelius Great news! 🎉 Based on an automated assessment of this PR, the linked Wave issue(s) no longer count against your application limits.

You can now already apply to more issues while waiting for a review of this PR. Keep up the great work! 🚀

Learn more about application limits

@leojay-net
leojay-net merged commit 3864492 into leojay-net:main Jul 26, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants