From 792a4f0e4b218ba7ba3cc288aa300f7588fe111f Mon Sep 17 00:00:00 2001 From: fabi Date: Tue, 30 Jun 2026 22:12:05 +0200 Subject: [PATCH] fix(frontend): robust Escape handling + deep-link back navigation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit focus-trap: focus the trapped container synchronously on mount instead of only after a deferred rAF. The keydown listener is node-scoped, so an Escape pressed before the rAF moved focus into the sheet landed on an element outside the node and was silently dropped — a real keyboard-a11y gap (open a sheet, immediately press Escape → nothing happened). The rAF still refines focus to the first control once laid out. recover: replace the `window.history.length > 1` back-chevron heuristic with SvelteKit's afterNavigate `from` signal. history.length is 2 on a fresh-tab deep link (about:blank + page), so history.back() landed on the blank entry. `from` is null only on a full-page load, so deep-linked users now correctly fall back to /join (or /feed when authed). Co-Authored-By: Claude Opus 4.8 --- frontend/src/lib/actions/focus-trap.ts | 9 +++++++-- frontend/src/routes/recover/+page.svelte | 13 +++++++++++-- 2 files changed, 18 insertions(+), 4 deletions(-) diff --git a/frontend/src/lib/actions/focus-trap.ts b/frontend/src/lib/actions/focus-trap.ts index c7d7f77..150f68e 100644 --- a/frontend/src/lib/actions/focus-trap.ts +++ b/frontend/src/lib/actions/focus-trap.ts @@ -64,11 +64,16 @@ export function focusTrap( node.addEventListener('keydown', onKeyDown); if (opts.autoFocus !== false) { - // Defer one frame so the element is fully laid out (sheets animate in). + // Focus the container synchronously on mount so the trap owns the keyboard + // immediately — otherwise an Escape pressed before the deferred focus below + // lands on an element *outside* the node, where this node-scoped listener + // never sees it (the keystroke is silently dropped). Then defer one frame to + // move focus onto the first control once the sheet has laid out / animated in. + if (!node.hasAttribute('tabindex')) node.setAttribute('tabindex', '-1'); + node.focus({ preventScroll: true }); requestAnimationFrame(() => { const list = focusables(node); const target = list[0] ?? node; - if (!node.hasAttribute('tabindex')) node.setAttribute('tabindex', '-1'); target.focus({ preventScroll: true }); }); } diff --git a/frontend/src/routes/recover/+page.svelte b/frontend/src/routes/recover/+page.svelte index 40de203..d2864ad 100644 --- a/frontend/src/routes/recover/+page.svelte +++ b/frontend/src/routes/recover/+page.svelte @@ -1,14 +1,23 @@