From e7ee16c6cf30118b766fe0fbaf65aea3f7cfe1eb Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Sun, 16 Aug 2026 12:54:08 -0400 Subject: [PATCH] frontend: dialogs keep focus, and a skip link past the chrome (task 1999) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BaseModal declared role="dialog" aria-modal="true" and then enforced none of it. Focus never moved into the panel, so Escape — handled ON the panel — did nothing at all in LabelsModal, the integration prompt and the shortcuts modal. Only the command palette escaped correctly, and only because it happens to focus its own input. Tab walked straight out of the dialog into the page that aria-modal had just told assistive tech was inert, and closing dropped focus to so the next Tab restarted from the top of the document. All three are one contract, so it lives in BaseModal rather than in each of the four callers: focus in on open, Tab trapped, focus restored to the opener. The panel takes tabindex="-1" so it can hold focus itself when it wraps nothing focusable. CommandPalette's input focus still wins, because a child's mounted hook runs before its parent's. The skip link is the other half. The header and sidebar are a dozen-odd tab stops that repeat on every navigation, and a keyboard user walked all of them again to reach their notes.
takes tabindex="-1" as well, because several browsers scroll to a bare anchor without moving focus to it — which would have made the link look like it worked while leaving the next Tab back at the top. The rest of the audit came back clean: no click handlers on non-focusable elements, and all 30 focus:outline-none uses already pair with a focus-visible ring. M3.5's keyboard pass held up; the gaps were in focus management, not styling. Co-Authored-By: Claude Opus 5 (1M context) --- frontend/src/components/AppShell.vue | 14 ++++- frontend/src/components/BaseModal.vue | 84 +++++++++++++++++++++++++-- 2 files changed, 92 insertions(+), 6 deletions(-) diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/AppShell.vue index 3a304af..c8541ed 100644 --- a/frontend/src/components/AppShell.vue +++ b/frontend/src/components/AppShell.vue @@ -210,6 +210,15 @@ async function signOut() {