diff --git a/app/src/components/layout/sidebar.tsx b/app/src/components/layout/sidebar.tsx
index 77e92c28..15046e98 100644
--- a/app/src/components/layout/sidebar.tsx
+++ b/app/src/components/layout/sidebar.tsx
@@ -64,9 +64,7 @@ import {
import { Button } from "@/components/ui/button";
import {
useKeyboardShortcuts,
- NAV_SHORTCUTS,
- UI_SHORTCUTS,
- ACTION_SHORTCUTS,
+ useKeyboardShortcutsConfig,
KeyboardShortcut,
} from "@/hooks/use-keyboard-shortcuts";
import { getElectronAPI, Project, TrashedProject } from "@/lib/electron";
@@ -221,6 +219,9 @@ export function Sidebar() {
theme: globalTheme,
} = useAppStore();
+ // Get customizable keyboard shortcuts
+ const shortcuts = useKeyboardShortcutsConfig();
+
// State for project picker dropdown
const [isProjectPickerOpen, setIsProjectPickerOpen] = useState(false);
const [showTrashDialog, setShowTrashDialog] = useState(false);
@@ -496,13 +497,13 @@ export function Sidebar() {
id: "board",
label: "Kanban Board",
icon: LayoutGrid,
- shortcut: NAV_SHORTCUTS.board,
+ shortcut: shortcuts.board,
},
{
id: "agent",
label: "Agent Runner",
icon: Bot,
- shortcut: NAV_SHORTCUTS.agent,
+ shortcut: shortcuts.agent,
},
],
},
@@ -513,25 +514,25 @@ export function Sidebar() {
id: "spec",
label: "Spec Editor",
icon: FileText,
- shortcut: NAV_SHORTCUTS.spec,
+ shortcut: shortcuts.spec,
},
{
id: "context",
label: "Context",
icon: BookOpen,
- shortcut: NAV_SHORTCUTS.context,
+ shortcut: shortcuts.context,
},
{
id: "tools",
label: "Agent Tools",
icon: Wrench,
- shortcut: NAV_SHORTCUTS.tools,
+ shortcut: shortcuts.tools,
},
{
id: "profiles",
label: "AI Profiles",
icon: UserCircle,
- shortcut: NAV_SHORTCUTS.profiles,
+ shortcut: shortcuts.profiles,
},
],
},
@@ -573,26 +574,26 @@ export function Sidebar() {
// Build keyboard shortcuts for navigation
const navigationShortcuts: KeyboardShortcut[] = useMemo(() => {
- const shortcuts: KeyboardShortcut[] = [];
+ const shortcutsList: KeyboardShortcut[] = [];
// Sidebar toggle shortcut - always available
- shortcuts.push({
- key: UI_SHORTCUTS.toggleSidebar,
+ shortcutsList.push({
+ key: shortcuts.toggleSidebar,
action: () => toggleSidebar(),
description: "Toggle sidebar",
});
// Open project shortcut - opens the folder selection dialog directly
- shortcuts.push({
- key: ACTION_SHORTCUTS.openProject,
+ shortcutsList.push({
+ key: shortcuts.openProject,
action: () => handleOpenFolder(),
description: "Open folder selection dialog",
});
// Project picker shortcut - only when we have projects
if (projects.length > 0) {
- shortcuts.push({
- key: ACTION_SHORTCUTS.projectPicker,
+ shortcutsList.push({
+ key: shortcuts.projectPicker,
action: () => setIsProjectPickerOpen((prev) => !prev),
description: "Toggle project picker",
});
@@ -600,13 +601,13 @@ export function Sidebar() {
// Project cycling shortcuts - only when we have project history
if (projectHistory.length > 1) {
- shortcuts.push({
- key: ACTION_SHORTCUTS.cyclePrevProject,
+ shortcutsList.push({
+ key: shortcuts.cyclePrevProject,
action: () => cyclePrevProject(),
description: "Cycle to previous project (MRU)",
});
- shortcuts.push({
- key: ACTION_SHORTCUTS.cycleNextProject,
+ shortcutsList.push({
+ key: shortcuts.cycleNextProject,
action: () => cycleNextProject(),
description: "Cycle to next project (LRU)",
});
@@ -617,7 +618,7 @@ export function Sidebar() {
navSections.forEach((section) => {
section.items.forEach((item) => {
if (item.shortcut) {
- shortcuts.push({
+ shortcutsList.push({
key: item.shortcut,
action: () => setCurrentView(item.id as any),
description: `Navigate to ${item.label}`,
@@ -627,15 +628,16 @@ export function Sidebar() {
});
// Add settings shortcut
- shortcuts.push({
- key: NAV_SHORTCUTS.settings,
+ shortcutsList.push({
+ key: shortcuts.settings,
action: () => setCurrentView("settings"),
description: "Navigate to Settings",
});
}
- return shortcuts;
+ return shortcutsList;
}, [
+ shortcuts,
currentProject,
setCurrentView,
toggleSidebar,
@@ -644,6 +646,7 @@ export function Sidebar() {
projectHistory.length,
cyclePrevProject,
cycleNextProject,
+ navSections,
]);
// Register keyboard shortcuts
@@ -682,7 +685,7 @@ export function Sidebar() {
className="ml-1 px-1 py-0.5 bg-brand-500/10 border border-brand-500/30 rounded text-[10px] font-mono text-brand-400/70"
data-testid="sidebar-toggle-shortcut"
>
- {UI_SHORTCUTS.toggleSidebar}
+ {shortcuts.toggleSidebar}
@@ -735,12 +738,12 @@ export function Sidebar() {
)}
diff --git a/app/src/components/views/board-view.tsx b/app/src/components/views/board-view.tsx
index 84fcf2be..ab504986 100644
--- a/app/src/components/views/board-view.tsx
+++ b/app/src/components/views/board-view.tsx
@@ -86,7 +86,7 @@ import { Checkbox } from "@/components/ui/checkbox";
import { useAutoMode } from "@/hooks/use-auto-mode";
import {
useKeyboardShortcuts,
- ACTION_SHORTCUTS,
+ useKeyboardShortcutsConfig,
KeyboardShortcut,
} from "@/hooks/use-keyboard-shortcuts";
import { useWindowState } from "@/hooks/use-window-state";
@@ -189,6 +189,7 @@ export function BoardView() {
showProfilesOnly,
aiProfiles,
} = useAppStore();
+ const shortcuts = useKeyboardShortcutsConfig();
const [activeFeature, setActiveFeature] = useState(null);
const [editingFeature, setEditingFeature] = useState(null);
const [showAddDialog, setShowAddDialog] = useState(false);
@@ -292,14 +293,14 @@ export function BoardView() {
// Keyboard shortcuts for this view
const boardShortcuts: KeyboardShortcut[] = useMemo(() => {
- const shortcuts: KeyboardShortcut[] = [
+ const shortcutsList: KeyboardShortcut[] = [
{
- key: ACTION_SHORTCUTS.addFeature,
+ key: shortcuts.addFeature,
action: () => setShowAddDialog(true),
description: "Add new feature",
},
{
- key: ACTION_SHORTCUTS.startNext,
+ key: shortcuts.startNext,
action: () => startNextFeaturesRef.current(),
description: "Start next features from backlog",
},
@@ -309,7 +310,7 @@ export function BoardView() {
inProgressFeaturesForShortcuts.slice(0, 10).forEach((feature, index) => {
// Keys 1-9 for first 9 cards, 0 for 10th card
const key = index === 9 ? "0" : String(index + 1);
- shortcuts.push({
+ shortcutsList.push({
key,
action: () => {
setOutputFeature(feature);
@@ -319,8 +320,8 @@ export function BoardView() {
});
});
- return shortcuts;
- }, [inProgressFeaturesForShortcuts]);
+ return shortcutsList;
+ }, [inProgressFeaturesForShortcuts, shortcuts]);
useKeyboardShortcuts(boardShortcuts);
// Prevent hydration issues
@@ -1567,7 +1568,7 @@ export function BoardView() {
className="ml-3 px-2 py-0.5 text-[10px] font-mono rounded bg-primary-foreground/20 border border-primary-foreground/30 text-primary-foreground inline-flex items-center justify-center"
data-testid="shortcut-add-feature"
>
- {ACTION_SHORTCUTS.addFeature}
+ {shortcuts.addFeature}
@@ -1636,7 +1637,7 @@ export function BoardView() {
Start Next
- {ACTION_SHORTCUTS.startNext}
+ {shortcuts.startNext}
)}
diff --git a/app/src/hooks/use-keyboard-shortcuts.ts b/app/src/hooks/use-keyboard-shortcuts.ts
index 9622d10f..a9b901ff 100644
--- a/app/src/hooks/use-keyboard-shortcuts.ts
+++ b/app/src/hooks/use-keyboard-shortcuts.ts
@@ -1,6 +1,7 @@
"use client";
import { useEffect, useCallback } from "react";
+import { useAppStore } from "@/store/app-store";
export interface KeyboardShortcut {
key: string;
@@ -97,36 +98,10 @@ export function useKeyboardShortcuts(shortcuts: KeyboardShortcut[]) {
}
/**
- * Shortcut definitions for navigation
+ * Hook to get current keyboard shortcuts from store
+ * This replaces the static constants and allows customization
*/
-export const NAV_SHORTCUTS: Record = {
- board: "K", // K for Kanban
- agent: "A", // A for Agent
- spec: "D", // D for Document (Spec)
- context: "C", // C for Context
- tools: "T", // T for Tools
- settings: "S", // S for Settings
- profiles: "M", // M for Models/profiles
-};
-
-/**
- * Shortcut definitions for UI controls
- */
-export const UI_SHORTCUTS: Record = {
- toggleSidebar: "`", // Backtick to toggle sidebar
-};
-
-/**
- * Shortcut definitions for add buttons
- */
-export const ACTION_SHORTCUTS: Record = {
- addFeature: "N", // N for New feature
- addContextFile: "F", // F for File (add context file)
- startNext: "G", // G for Grab (start next features from backlog)
- newSession: "N", // N for New session (in agent view)
- openProject: "O", // O for Open project (navigate to welcome view)
- projectPicker: "P", // P for Project picker
- cyclePrevProject: "Q", // Q for previous project (cycle back through MRU)
- cycleNextProject: "E", // E for next project (cycle forward through MRU)
- addProfile: "N", // N for New profile (when in profiles view)
-};
+export function useKeyboardShortcutsConfig() {
+ const keyboardShortcuts = useAppStore((state) => state.keyboardShortcuts);
+ return keyboardShortcuts;
+}
diff --git a/app/src/store/app-store.ts b/app/src/store/app-store.ts
index 738de87f..77779ecb 100644
--- a/app/src/store/app-store.ts
+++ b/app/src/store/app-store.ts
@@ -37,6 +37,58 @@ export interface ApiKeys {
openai: string;
}
+// Keyboard Shortcuts
+export interface KeyboardShortcuts {
+ // Navigation shortcuts
+ board: string;
+ agent: string;
+ spec: string;
+ context: string;
+ tools: string;
+ settings: string;
+ profiles: string;
+
+ // UI shortcuts
+ toggleSidebar: string;
+
+ // Action shortcuts
+ addFeature: string;
+ addContextFile: string;
+ startNext: string;
+ newSession: string;
+ openProject: string;
+ projectPicker: string;
+ cyclePrevProject: string;
+ cycleNextProject: string;
+ addProfile: string;
+}
+
+// Default keyboard shortcuts
+export const DEFAULT_KEYBOARD_SHORTCUTS: KeyboardShortcuts = {
+ // Navigation
+ board: "K",
+ agent: "A",
+ spec: "D",
+ context: "C",
+ tools: "T",
+ settings: "S",
+ profiles: "M",
+
+ // UI
+ toggleSidebar: "`",
+
+ // Actions
+ addFeature: "N",
+ addContextFile: "F",
+ startNext: "G",
+ newSession: "N",
+ openProject: "O",
+ projectPicker: "P",
+ cyclePrevProject: "Q",
+ cycleNextProject: "E",
+ addProfile: "N",
+};
+
export interface ImageAttachment {
id: string;
data: string; // base64 encoded image data
@@ -203,6 +255,9 @@ export interface AppState {
// Profile Display Settings
showProfilesOnly: boolean; // When true, hide model tweaking options and show only profile selection
+ // Keyboard Shortcuts
+ keyboardShortcuts: KeyboardShortcuts; // User-defined keyboard shortcuts
+
// Project Analysis
projectAnalysis: ProjectAnalysis | null;
isAnalyzing: boolean;
@@ -303,6 +358,11 @@ export interface AppActions {
// Profile Display Settings actions
setShowProfilesOnly: (enabled: boolean) => void;
+ // Keyboard Shortcuts actions
+ setKeyboardShortcut: (key: keyof KeyboardShortcuts, value: string) => void;
+ setKeyboardShortcuts: (shortcuts: Partial) => void;
+ resetKeyboardShortcuts: () => void;
+
// AI Profile actions
addAIProfile: (profile: Omit) => void;
updateAIProfile: (id: string, updates: Partial) => void;
@@ -404,6 +464,7 @@ const initialState: AppState = {
defaultSkipTests: false, // Default to TDD mode (tests enabled)
useWorktrees: false, // Default to disabled (worktree feature is experimental)
showProfilesOnly: false, // Default to showing all options (not profiles only)
+ keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS, // Default keyboard shortcuts
aiProfiles: DEFAULT_AI_PROFILES,
projectAnalysis: null,
isAnalyzing: false,
@@ -907,6 +968,29 @@ export const useAppStore = create()(
// Profile Display Settings actions
setShowProfilesOnly: (enabled) => set({ showProfilesOnly: enabled }),
+ // Keyboard Shortcuts actions
+ setKeyboardShortcut: (key, value) => {
+ set({
+ keyboardShortcuts: {
+ ...get().keyboardShortcuts,
+ [key]: value,
+ },
+ });
+ },
+
+ setKeyboardShortcuts: (shortcuts) => {
+ set({
+ keyboardShortcuts: {
+ ...get().keyboardShortcuts,
+ ...shortcuts,
+ },
+ });
+ },
+
+ resetKeyboardShortcuts: () => {
+ set({ keyboardShortcuts: DEFAULT_KEYBOARD_SHORTCUTS });
+ },
+
// AI Profile actions
addAIProfile: (profile) => {
const id = `profile-${Date.now()}-${Math.random()
@@ -985,6 +1069,7 @@ export const useAppStore = create()(
defaultSkipTests: state.defaultSkipTests,
useWorktrees: state.useWorktrees,
showProfilesOnly: state.showProfilesOnly,
+ keyboardShortcuts: state.keyboardShortcuts,
aiProfiles: state.aiProfiles,
lastSelectedSessionByProject: state.lastSelectedSessionByProject,
}),