diff --git a/src/App.tsx b/src/App.tsx
index 2e1397f..10b129e 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -5,6 +5,7 @@ import { BoardList } from "@/components/boards/BoardList";
import { BoardView } from "@/components/board/BoardView";
import { CommandPalette } from "@/components/command-palette/CommandPalette";
import { SettingsDialog } from "@/components/settings/SettingsDialog";
+import { useKeyboardShortcuts } from "@/hooks/useKeyboardShortcuts";
export default function App() {
const initialized = useAppStore((s) => s.initialized);
@@ -32,6 +33,8 @@ export default function App() {
setSettingsOpen(true);
}, []);
+ useKeyboardShortcuts();
+
if (!initialized) {
return (
diff --git a/src/hooks/useKeyboardShortcuts.ts b/src/hooks/useKeyboardShortcuts.ts
new file mode 100644
index 0000000..ea3722a
--- /dev/null
+++ b/src/hooks/useKeyboardShortcuts.ts
@@ -0,0 +1,57 @@
+import { useEffect } from "react";
+import { useBoardStore } from "@/stores/board-store";
+
+function isInputFocused(): boolean {
+ const el = document.activeElement;
+ if (!el) return false;
+ const tag = el.tagName.toLowerCase();
+ return (
+ tag === "input" ||
+ tag === "textarea" ||
+ (el as HTMLElement).isContentEditable
+ );
+}
+
+export function useKeyboardShortcuts(): void {
+ useEffect(() => {
+ function handleKeyDown(e: KeyboardEvent) {
+ const ctrl = e.ctrlKey || e.metaKey;
+
+ // Ctrl+K: open command palette (always works, even in inputs)
+ if (ctrl && e.key === "k") {
+ e.preventDefault();
+ document.dispatchEvent(new CustomEvent("open-command-palette"));
+ return;
+ }
+
+ // Skip remaining shortcuts when an input is focused
+ if (isInputFocused()) return;
+
+ // Ctrl+Shift+Z: redo
+ if (ctrl && e.shiftKey && e.key === "Z") {
+ e.preventDefault();
+ useBoardStore.temporal.getState().redo();
+ return;
+ }
+
+ // Ctrl+Z: undo
+ if (ctrl && e.key === "z") {
+ e.preventDefault();
+ useBoardStore.temporal.getState().undo();
+ return;
+ }
+
+ // Escape: close any open modal/dialog
+ // Note: Radix dialogs handle their own Escape, but this covers custom modals
+ if (e.key === "Escape") {
+ document.dispatchEvent(new CustomEvent("close-all-modals"));
+ return;
+ }
+ }
+
+ document.addEventListener("keydown", handleKeyDown);
+ return () => {
+ document.removeEventListener("keydown", handleKeyDown);
+ };
+ }, []);
+}