Deferred from #12 (flagged in QA).
SidebarLayout's drawer collapse mode hand-rolls its own off-canvas overlay (transform + scrim, in SidebarLayout.tsx): it has Escape and inert when closed, but no focus trap and no focus-into on open. The Drawer component now contains focus itself — a hand-rolled Tab trap it grew once the sandbox-safe rebuild (#39) replaced native <dialog>, which had given focus containment for free. So the two off-canvas overlays have diverged on accessibility, and the SidebarLayout one is the less-accessible path.
Goal: extract the Drawer's overlay behavior (focus-into on open + Tab containment + scroll-lock + Escape) into a shared primitive, and have SidebarLayout's drawer mode reuse it — one accessible overlay, no drift. (The remedy is extraction of the now hand-rolled trap, not "reuse the <dialog>-backed Drawer" — <dialog> is unavailable in the app iframe sandbox.)
Not dangerous today (the two don't share code that could silently diverge), but the no-focus-trap path shouldn't linger.
Deferred from #12 (flagged in QA).
SidebarLayout'sdrawercollapse mode hand-rolls its own off-canvas overlay (transform + scrim, inSidebarLayout.tsx): it has Escape andinertwhen closed, but no focus trap and no focus-into on open. TheDrawercomponent now contains focus itself — a hand-rolled Tab trap it grew once the sandbox-safe rebuild (#39) replaced native<dialog>, which had given focus containment for free. So the two off-canvas overlays have diverged on accessibility, and the SidebarLayout one is the less-accessible path.Goal: extract the Drawer's overlay behavior (focus-into on open + Tab containment + scroll-lock + Escape) into a shared primitive, and have
SidebarLayout's drawer mode reuse it — one accessible overlay, no drift. (The remedy is extraction of the now hand-rolled trap, not "reuse the<dialog>-backed Drawer" —<dialog>is unavailable in the app iframe sandbox.)Not dangerous today (the two don't share code that could silently diverge), but the no-focus-trap path shouldn't linger.