From 969ef0efa3532c72d76fff89e74ebc3d25c2abc5 Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Wed, 4 Mar 2026 08:39:49 -0500 Subject: [PATCH] Add keyboard navigation: e/c/slash shortcuts, j/k list nav, focus visibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - App.vue: e → edit on viewer pages; / → focus search (custom event); c → focus chat on home or navigate to /chat; update shortcuts panel with Lists + Chat sections - theme.css: add a:focus-visible to focus-ring rules (router-link cards now show visible keyboard focus outline) - SearchBar.vue: expose focus() via defineExpose for cross-component focus dispatch - NotesListView / TasksListView: j/k vim-style navigation with kb-active-item highlight; listen for shortcut:focus-search; cleanup on unmount - HomeView.vue: listen for shortcut:focus-chat, call chatInputRef.focus() Co-Authored-By: Claude Sonnet 4.6 --- frontend/src/App.vue | 43 ++++++++++++++++ frontend/src/assets/theme.css | 4 +- frontend/src/components/SearchBar.vue | 4 ++ frontend/src/views/HomeView.vue | 13 ++++- frontend/src/views/NotesListView.vue | 71 ++++++++++++++++++++++----- frontend/src/views/TasksListView.vue | 64 ++++++++++++++++++++---- 6 files changed, 174 insertions(+), 25 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b1660db..e041d5f 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -95,6 +95,24 @@ function onGlobalKeydown(e: KeyboardEvent) { case "t": router.push("/tasks/new"); break; + case "e": { + const name = router.currentRoute.value.name; + if (name === "note-view" || name === "task-view") { + router.push(router.currentRoute.value.path + "/edit"); + } + break; + } + case "/": + e.preventDefault(); + document.dispatchEvent(new CustomEvent("shortcut:focus-search")); + break; + case "c": + if (router.currentRoute.value.name === "home") { + document.dispatchEvent(new CustomEvent("shortcut:focus-chat")); + } else { + router.push("/chat"); + } + break; } } @@ -193,8 +211,33 @@ onUnmounted(() => { New task +
+
Lists
+
+ / + Focus search bar +
+
+ j + / + k + Move down / up +
+
+ Enter + Open selected item +
+
+ e + Edit current item (viewer) +
+
Chat
+
+ c + Focus chat (home) / go to chat +
Enter Send message diff --git a/frontend/src/assets/theme.css b/frontend/src/assets/theme.css index 2bbe523..712ae3a 100644 --- a/frontend/src/assets/theme.css +++ b/frontend/src/assets/theme.css @@ -106,9 +106,11 @@ body { input:focus-visible, textarea:focus-visible, select:focus-visible, -button:focus-visible { +button:focus-visible, +a:focus-visible { outline: none; box-shadow: var(--focus-ring); + border-radius: var(--radius-sm); } /* Responsive breakpoints: 480px (phone), 768px (tablet), 1024px (desktop) */ diff --git a/frontend/src/components/SearchBar.vue b/frontend/src/components/SearchBar.vue index 0e525e4..701954c 100644 --- a/frontend/src/components/SearchBar.vue +++ b/frontend/src/components/SearchBar.vue @@ -3,16 +3,20 @@ import { ref, watch } from "vue"; const emit = defineEmits<{ search: [query: string] }>(); const query = ref(""); +const inputRef = ref(null); let timeout: ReturnType; watch(query, (val) => { clearTimeout(timeout); timeout = setTimeout(() => emit("search", val), 300); }); + +defineExpose({ focus: () => inputRef.value?.focus() });