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() });
-import { ref, computed, onMounted, nextTick } from "vue";
+import { ref, computed, onMounted, onUnmounted, nextTick } from "vue";
import { apiGet } from "@/api/client";
import type { Note, NoteListResponse } from "@/types/note";
import type { Task, TaskListResponse } from "@/types/task";
@@ -206,6 +206,17 @@ function onStatusToggle(id: number, status: TaskStatus) {
// ─── Chat widget ──────────────────────────────────────────────────────────────
const chatInputRef = ref<{ focus: () => void } | null>(null);
+
+function onFocusChatShortcut() {
+ chatInputRef.value?.focus();
+}
+
+onMounted(() => {
+ document.addEventListener("shortcut:focus-chat", onFocusChatShortcut);
+});
+onUnmounted(() => {
+ document.removeEventListener("shortcut:focus-chat", onFocusChatShortcut);
+});
const chatStore = useChatStore();
chatStore.fetchStatus().then(() => {
diff --git a/frontend/src/views/NotesListView.vue b/frontend/src/views/NotesListView.vue
index 6179e29..4330b40 100644
--- a/frontend/src/views/NotesListView.vue
+++ b/frontend/src/views/NotesListView.vue
@@ -1,5 +1,5 @@