-
Notifications
You must be signed in to change notification settings - Fork 0
Description
Summary
All keyboard shortcuts used throughout the GUI should be user-configurable via the Settings panel, with a "record" interaction — the user clicks a shortcut field then presses their desired key combination to assign it, similar to how IDEs and DAWs handle keybinding configuration.
This builds on #550 (which defines the default shortcuts) by adding a configuration and recording layer.
Proposed Design
Shortcut Settings Section
A new "Keyboard Shortcuts" section in the Settings workspace view, displaying a table of all registered actions and their current bindings:
Keyboard Shortcuts
──────────────────────────────────────────────
Action Shortcut [Reset]
──────────────────────────────────────────────
Mappings view ⌘1 [⊗]
Devices view ⌘2 [⊗]
Profiles view ⌘3 [⊗]
Settings view ⌘4 [⊗]
Plugins view ⌘5 [⊗]
Toggle Event Stream ⌘E [⊗]
Toggle Chat Panel ⌘J [⊗]
Developer Mode ⌘⇧D [⊗]
Mappings sub-view ⌘⇧M [⊗]
Config History ⌘⇧H [⊗]
Raw Config ⌘⇧R [⊗]
──────────────────────────────────────────────
[Reset All to Defaults]
Recording Interaction
- User clicks on a shortcut cell (or a "Record" button next to it)
- The cell enters recording mode — shows pulsing "Press keys..." placeholder
- User presses their desired key combination (e.g.,
Ctrl+Alt+M) - The combo is captured, validated, and displayed
- If the combo conflicts with another binding, show an inline warning: "Already assigned to: Toggle Chat Panel — Reassign?" with confirm/cancel
- Press
Escapeto cancel recording without changing the binding - The
[⊗]button clears the shortcut (disables that action's hotkey)
Recording Implementation
// keyboard-shortcuts.ts
interface ShortcutBinding {
action: string; // e.g., 'workspace.mappings'
label: string; // e.g., 'Mappings view'
default: string; // e.g., 'CmdOrCtrl+1'
current: string | null; // user override, null = use default
}
/**
* Capture a key combo from a keydown event.
* Returns a normalised string like 'CmdOrCtrl+Shift+D'.
*/
function captureCombo(e: KeyboardEvent): string | null {
// Ignore bare modifier presses (Shift, Ctrl, Alt, Meta alone)
if (['Shift', 'Control', 'Alt', 'Meta'].includes(e.key)) return null;
const parts: string[] = [];
if (e.metaKey || e.ctrlKey) parts.push('CmdOrCtrl');
if (e.altKey) parts.push('Alt');
if (e.shiftKey) parts.push('Shift');
parts.push(normalizeKey(e.key)); // e.g., 'D', '1', 'F5'
return parts.join('+');
}Persistence
Store in localStorage under key conductor:shortcuts as a JSON map of action → combo string. Consistent with other GUI preferences (#559). The shortcut store merges user overrides with defaults at startup.
// shortcut-store.js
import { writable } from 'svelte/store';
const DEFAULTS = {
'workspace.mappings': 'CmdOrCtrl+1',
'workspace.devices': 'CmdOrCtrl+2',
'workspace.profiles': 'CmdOrCtrl+3',
'workspace.settings': 'CmdOrCtrl+4',
'workspace.plugins': 'CmdOrCtrl+5',
'panel.events': 'CmdOrCtrl+E',
'panel.chat': 'CmdOrCtrl+J',
'developer.toggle': 'CmdOrCtrl+Shift+D',
'mappings.current': 'CmdOrCtrl+Shift+M',
'mappings.history': 'CmdOrCtrl+Shift+H',
'mappings.rawConfig': 'CmdOrCtrl+Shift+R',
};
// Load user overrides from localStorage, merge with defaults
function createShortcutStore() { ... }Conflict Detection
- Internal conflicts: Warn if two actions share the same combo. Allow reassignment with confirmation.
- Reserved combos: Block assignment of OS-level shortcuts (
Cmd+Q,Cmd+W,Cmd+C/V/X/Z/A) and Tauri defaults. Show "Reserved by system" message. - Context-aware: Shortcuts are suppressed when focus is in an input/textarea (typing takes priority, per GUI: Add keyboard shortcuts for workspace pane and view navigation #550).
Architecture
┌─────────────────────────────────────────┐
│ shortcut-store.js │
│ - defaults map │
│ - user overrides (localStorage) │
│ - merged active bindings │
│ - conflict detection │
└─────────┬───────────────────────────────┘
│
┌─────┴──────┐ ┌──────────────────┐
│ App.svelte │ │ SettingsPanel │
│ keydown │◄─reads───│ ShortcutEditor │
│ handler │ │ (record + table) │
└────────────┘ └──────────────────┘
The global keydown handler in App.svelte (from #550) reads from the shortcut store rather than hardcoding combos. The Settings panel writes to the store. Single source of truth.
Related Issues
- GUI: Add keyboard shortcuts for workspace pane and view navigation #550 — Defines the default shortcuts and the global keydown handler (implementation dependency)
- feat(gui): Developer menu with daemon/GUI output viewer #433 — Developer mode toggle shortcut (
Cmd+Shift+D) should be configurable here - Epic: Settings Persistence Architecture (ADR-017) #559 — State persistence boundaries (shortcuts = GUI preference → localStorage)
Priority
P4 — Enhancement on top of #550. Implement #550 first with hardcoded defaults, then layer configurability on top.
Acceptance Criteria
- Settings panel has a "Keyboard Shortcuts" section showing all registered actions and their bindings
- Clicking a shortcut cell enters recording mode — captures the next key combo pressed
-
Escapecancels recording without changing the binding - Conflict detection warns when a combo is already assigned to another action
- Reserved system shortcuts cannot be assigned (Cmd+Q, Cmd+C/V/X/Z, etc.)
-
[⊗]button clears individual shortcuts; "Reset All to Defaults" restores all - Custom bindings persist across app restarts (localStorage)
- Global keydown handler reads from the shortcut store (not hardcoded)
- Shortcuts suppressed when focus is in input/textarea elements
🤖 Generated with Claude Code