Add custom keyboard shortcuts configuration#8
Add custom keyboard shortcuts configuration#8jasonkneen wants to merge 1 commit intotkattkat:mainfrom
Conversation
jasonkneen
commented
Dec 8, 2025
- Add configurable keyboard shortcuts for:
- Open spotlight (default: Cmd/Ctrl+Shift+C)
- New conversation (default: Cmd/Ctrl+N)
- Toggle sidebar (default: Cmd/Ctrl+B)
- Update settings UI to show all configurable shortcuts
- Register all shortcuts globally via Electron's globalShortcut
- Add IPC handlers for new-conversation and toggle-sidebar events
- Maintain backwards compatibility with existing spotlightKeybind setting
- Add configurable keyboard shortcuts for: - Open spotlight (default: Cmd/Ctrl+Shift+C) - New conversation (default: Cmd/Ctrl+N) - Toggle sidebar (default: Cmd/Ctrl+B) - Update settings UI to show all configurable shortcuts - Register all shortcuts globally via Electron's globalShortcut - Add IPC handlers for new-conversation and toggle-sidebar events - Maintain backwards compatibility with existing spotlightKeybind setting
There was a problem hiding this comment.
Pull request overview
This PR adds configurable keyboard shortcuts for three core application actions: opening spotlight search, starting a new conversation, and toggling the sidebar. It extends the existing settings system to manage these shortcuts through a unified interface while maintaining backwards compatibility with the legacy spotlightKeybind setting.
Key Changes:
- Added a
KeyboardShortcutsinterface to define the three configurable shortcuts with sensible defaults (Cmd/Ctrl+Shift+C for spotlight, Cmd/Ctrl+N for new conversation, Cmd/Ctrl+B for toggle sidebar) - Updated the settings UI to display all three keyboard shortcuts in a dedicated "Keyboard Shortcuts" section
- Implemented global shortcut registration for the new conversation and toggle sidebar actions via Electron's
globalShortcutAPI
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
static/settings.html |
Reorganized settings UI to create a dedicated "Keyboard Shortcuts" section with three configurable shortcuts, moving spotlight-specific settings to their own section |
src/types/index.ts |
Added KeyboardShortcuts interface and integrated it into the SettingsSchema |
src/renderer/settings.ts |
Refactored keybind handling to support multiple shortcuts, implemented migration logic for legacy settings, and added display updates for all three shortcuts |
src/renderer/main.ts |
Added IPC event listeners for new-conversation and toggle-sidebar shortcuts that trigger the appropriate UI actions |
src/preload.ts |
Exposed new IPC event handlers for the two new shortcuts and updated the saveSettings type signature to include keyboardShortcuts |
src/main.ts |
Refactored shortcut registration to handle all three shortcuts, added fallback to legacy spotlightKeybind, and implemented IPC event sending for new conversation and toggle sidebar actions |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| globalShortcut.register(DEFAULT_KEYBOARD_SHORTCUTS.spotlight, () => { | ||
| createSpotlightWindow(); | ||
| }); |
There was a problem hiding this comment.
If the default spotlight shortcut registration also fails (which could happen if another application is using it), the application will throw an unhandled error. Consider wrapping this fallback in a try-catch as well:
try {
globalShortcut.register(spotlightKey, () => {
createSpotlightWindow();
});
} catch (e) {
console.error('Failed to register spotlight keybind:', spotlightKey, e);
try {
globalShortcut.register(DEFAULT_KEYBOARD_SHORTCUTS.spotlight, () => {
createSpotlightWindow();
});
} catch (fallbackError) {
console.error('Failed to register default spotlight keybind:', fallbackError);
}
}| globalShortcut.register(DEFAULT_KEYBOARD_SHORTCUTS.spotlight, () => { | |
| createSpotlightWindow(); | |
| }); | |
| try { | |
| globalShortcut.register(DEFAULT_KEYBOARD_SHORTCUTS.spotlight, () => { | |
| createSpotlightWindow(); | |
| }); | |
| } catch (fallbackError) { | |
| console.error('Failed to register default spotlight keybind:', fallbackError); | |
| } |
| isRecordingKeybind = true; | ||
| // Set up keybind input handlers | ||
| function setupKeybindInput(inputEl: HTMLElement) { | ||
| const shortcutKey = inputEl.dataset.shortcut as keyof KeyboardShortcuts; |
There was a problem hiding this comment.
The dataset.shortcut value is cast to keyof KeyboardShortcuts without validation. If the HTML contains an invalid data-shortcut value, this could cause runtime errors when trying to access properties on the shortcuts object.
Consider adding validation:
const shortcutKey = inputEl.dataset.shortcut as keyof KeyboardShortcuts;
if (!shortcutKey || !(shortcutKey in DEFAULT_SHORTCUTS)) {
console.error('Invalid shortcut key:', shortcutKey);
return;
}| const shortcutKey = inputEl.dataset.shortcut as keyof KeyboardShortcuts; | |
| const shortcutKey = inputEl.dataset.shortcut as keyof KeyboardShortcuts; | |
| if (!shortcutKey || !(shortcutKey in DEFAULT_SHORTCUTS)) { | |
| console.error('Invalid shortcut key:', shortcutKey); | |
| return; | |
| } |
| if (!isRecordingKeybind) return; | ||
| if (!activeRecordingInput) return; | ||
|
|
||
| const shortcutKey = activeRecordingInput.dataset.shortcut as keyof KeyboardShortcuts; |
There was a problem hiding this comment.
The dataset.shortcut value is cast to keyof KeyboardShortcuts without validation. If the HTML contains an invalid data-shortcut value, this could result in undefined behavior when calling getShortcutValue(shortcutKey).
Consider adding validation similar to the suggestion for line 196, or refactor to share the validation logic.
| const claude = (window as any).claude; | ||
|
|
||
| interface KeyboardShortcuts { | ||
| spotlight: string; | ||
| newConversation: string; | ||
| toggleSidebar: string; | ||
| } | ||
|
|
There was a problem hiding this comment.
The KeyboardShortcuts interface is duplicated in this file (lines 5-9) and also exported from src/types/index.ts (lines 2-6). Consider importing the type from src/types/index.ts instead to maintain a single source of truth and avoid potential inconsistencies:
import type { KeyboardShortcuts, SettingsSchema } from '../types';Then remove the local interface definition.
| const claude = (window as any).claude; | |
| interface KeyboardShortcuts { | |
| spotlight: string; | |
| newConversation: string; | |
| toggleSidebar: string; | |
| } | |
| import type { KeyboardShortcuts } from '../types'; |
| interface Settings { | ||
| spotlightKeybind: string; | ||
| spotlightPersistHistory: boolean; | ||
| keyboardShortcuts: KeyboardShortcuts; | ||
| } |
There was a problem hiding this comment.
The Settings interface is defined here but it duplicates SettingsSchema from src/types/index.ts. Consider importing and reusing the exported SettingsSchema type to avoid duplication:
import type { KeyboardShortcuts, SettingsSchema } from '../types';Then use SettingsSchema directly instead of defining a local Settings interface.
| const DEFAULT_SHORTCUTS: KeyboardShortcuts = { | ||
| spotlight: 'CommandOrControl+Shift+C', | ||
| newConversation: 'CommandOrControl+N', | ||
| toggleSidebar: 'CommandOrControl+B', | ||
| }; |
There was a problem hiding this comment.
The default keyboard shortcuts are duplicated in two places: here in src/renderer/settings.ts (lines 18-22) and in src/main.ts (lines 13-17). If these values need to change, they would need to be updated in both places. Consider:
- Exporting
DEFAULT_KEYBOARD_SHORTCUTSfromsrc/types/index.tsor a shared constants file - Having the renderer fetch these defaults from the main process via IPC
This ensures a single source of truth for default values.
| const DEFAULT_SHORTCUTS: KeyboardShortcuts = { | |
| spotlight: 'CommandOrControl+Shift+C', | |
| newConversation: 'CommandOrControl+N', | |
| toggleSidebar: 'CommandOrControl+B', | |
| }; | |
| import { DEFAULT_SHORTCUTS } from '../types/index'; |
| saveSettings: (settings: { spotlightKeybind?: string; spotlightPersistHistory?: boolean; keyboardShortcuts?: { spotlight?: string; newConversation?: string; toggleSidebar?: string } }) => | ||
| ipcRenderer.invoke('save-settings', settings), |
There was a problem hiding this comment.
The inline type definition for keyboardShortcuts in the saveSettings parameter makes all nested properties optional, which differs from the KeyboardShortcuts interface in src/types/index.ts where all properties are required. This inconsistency could lead to confusion.
Consider using a proper Partial type or importing the actual type:
saveSettings: (settings: {
spotlightKeybind?: string;
spotlightPersistHistory?: boolean;
keyboardShortcuts?: Partial<KeyboardShortcuts>
}) => ipcRenderer.invoke('save-settings', settings),Or better yet, import and use Partial<SettingsSchema> to match what the main process expects.
| isRecordingKeybind = true; | ||
| // Set up keybind input handlers | ||
| function setupKeybindInput(inputEl: HTMLElement) { | ||
| const shortcutKey = inputEl.dataset.shortcut as keyof KeyboardShortcuts; |
There was a problem hiding this comment.
Unused variable shortcutKey.
| const shortcutKey = inputEl.dataset.shortcut as keyof KeyboardShortcuts; |
|
did this work for you? The sidebar shortcut hoes not seem to be working on my end after setting it to a key |