feat(a11y): focus return, typing announcement, shortcuts help (P3-4)

- Focus returns to the trigger when closing 4 genuine dialogs (room-topic
  viewer, reaction viewer, header topic, Search) — 20 inline popouts/menus
  correctly left as-is (returning focus to a hover target would be wrong).
- Typing indicator announced via a visually-hidden role="status" region;
  the visual text is aria-hidden to avoid double announcement.
- New keyboard-shortcuts help dialog (press ?, ignored while typing),
  mounted in ClientNonUIFeatures.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-02 11:45:22 -04:00
parent 4380041014
commit 21dda93d1b
9 changed files with 297 additions and 7 deletions
+11 -2
View File
@@ -42,6 +42,7 @@ import { toastQueueAtom } from '../../state/toast';
import { useReminders } from '../../hooks/useReminders';
import { useTauriUpdater } from '../../hooks/useTauriUpdater';
import { TauriDesktopFeatures } from '../../components/TauriDesktopFeatures';
import { KeyboardShortcutsDialog, useKeyboardShortcutsTrigger } from '../../features/shortcuts';
import { useRoomsListener } from '../../hooks/useRoomsListener';
import { threadNotificationsAtom } from '../../state/threadNotifications';
import { roomIdToActiveThreadIdAtomFamily } from '../../state/room/thread';
@@ -213,7 +214,7 @@ function InviteNotifications() {
]);
return (
<audio ref={audioRef} style={{ display: 'none' }}>
<audio ref={audioRef} style={{ display: 'none' }} aria-hidden="true">
<source src={soundSrc ?? InviteSound} type="audio/ogg" />
</audio>
);
@@ -496,7 +497,7 @@ function MessageNotifications() {
useRoomsListener(mx, ThreadEvent.NewReply, handleNewReply);
return (
<audio ref={audioRef} style={{ display: 'none' }}>
<audio ref={audioRef} style={{ display: 'none' }} aria-hidden="true">
<source src={soundSrc ?? NotificationSound} type="audio/ogg" />
</audio>
);
@@ -642,6 +643,13 @@ function LotusDenoiseFeature() {
return null;
}
// Registers the global `?` shortcut (ignored while typing) and renders the
// keyboard-shortcuts help dialog. Headless — the dialog self-gates on its atom.
function KeyboardShortcutsFeature() {
useKeyboardShortcutsTrigger();
return <KeyboardShortcutsDialog />;
}
export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
return (
<>
@@ -656,6 +664,7 @@ export function ClientNonUIFeatures({ children }: ClientNonUIFeaturesProps) {
<TauriDesktopFeatures />
<LotusDenoiseFeature />
<DeepLinkNavigator />
<KeyboardShortcutsFeature />
{children}
</>
);