From ecbdd12ba929685386042795a0d20ea00ddbbec0 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Tue, 21 Jul 2026 00:03:15 -0400 Subject: [PATCH] =?UTF-8?q?m4:=20mobile=20nav=20=E2=80=94=20hamburger=20+?= =?UTF-8?q?=20slide-in=20drawer?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Below sm the sidebar had no replacement (nav was inaccessible on phones). The sidebar aside is now a responsive drawer: fixed + off-canvas with a hamburger toggle and backdrop on mobile, static in-flow on sm+ (same markup, no duplication). Closes on nav-click, backdrop, Esc, or route change. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm --- frontend/src/components/AppShell.vue | 39 ++++++++++++++++++++++++++-- 1 file changed, 37 insertions(+), 2 deletions(-) diff --git a/frontend/src/components/AppShell.vue b/frontend/src/components/AppShell.vue index b58d307..7986021 100644 --- a/frontend/src/components/AppShell.vue +++ b/frontend/src/components/AppShell.vue @@ -21,6 +21,7 @@ const ui = useUiStore(); const managing = ref(false); const showShortcuts = ref(false); const paletteOpen = ref(false); +const drawer = ref(false); const searchInput = ref(null); const searchText = ref(typeof route.query.q === "string" ? route.query.q : ""); let searchTimer: ReturnType | undefined; @@ -79,6 +80,10 @@ function onKeydown(e: KeyboardEvent) { showShortcuts.value = false; return; } + if (e.key === "Escape" && drawer.value) { + drawer.value = false; + return; + } if (e.metaKey || e.ctrlKey || e.altKey) return; if (gPending) { gPending = false; @@ -148,6 +153,7 @@ watch( () => route.name, (name) => { if (name !== "search") searchText.value = ""; + drawer.value = false; }, ); @@ -163,6 +169,26 @@ async function signOut() { class="sticky top-0 z-20 border-b border-neutral-200 bg-neutral-50/90 backdrop-blur dark:border-neutral-800 dark:bg-neutral-950/90" >
+
TS @@ -203,8 +229,17 @@ async function signOut() {
-