Keybindings system — configurable keyboard shortcuts #18

Open
opened 2026-04-16 11:32:16 +00:00 by claude-desktop · 0 comments
Collaborator

User story

As a user, I want configurable keyboard shortcuts that work across the application (global scope) and within specific views (chat, terminal, diff), with sensible defaults matching the spec, so that I can operate forge-agent efficiently from the keyboard.

Acceptance criteria

Keybinding registry

  • Loads default keybindings from a hardcoded list matching spec §11.2 (10 bindings)
  • Merges user overrides from AppConfig.keybindings HashMap
  • Produces a Vec<Keybinding> with resolved current_key values

Default bindings (spec §11.2)

ID Scope Default
send-message Chat Ctrl+Return
interrupt-turn Chat Ctrl+C
new-thread Global Ctrl+T
toggle-sidebar Global Ctrl+B
toggle-terminal Global Ctrl+`
switch-diff Global Ctrl+D
approve-action Chat Ctrl+Y
reject-action Chat Ctrl+N
restore-checkpoint Chat Ctrl+Z
focus-input Chat /

GTK4 integration (setup_keybindings())

  • gtk::EventControllerKey attached to the AdwApplicationWindow
  • On key press: constructs a KeySpec from the GDK key + modifiers
  • Matches against registered keybindings
  • Scope-aware: Chat bindings only active when chat view is focused, etc.
  • Matched binding triggers the appropriate action via handle_keybinding(id, ipc)
  • Returns glib::Propagation::Stop on match, Proceed otherwise

Action handlers

  • send-message → reads input text, dispatches StartTurn
  • interrupt-turn → dispatches InterruptTurn for active thread
  • new-thread → dispatches CreateThread for active project
  • toggle-sidebar → toggles AdwOverlaySplitView.show_sidebar
  • toggle-terminal → toggles terminal pane visibility
  • switch-diff → switches ActiveView to Diff
  • approve-action / reject-action → dispatches approval/rejection commands
  • restore-checkpoint → dispatches RestoreCheckpoint for the last checkpoint
  • focus-input → grabs focus on the message input GtkTextView

KeySpec parsing

  • Parse string format "Ctrl+Shift+X" into KeySpec struct
  • Support modifiers: Ctrl, Shift, Alt, Meta

Tests

  • Test: KeySpec parsing from string format
  • Test: default bindings are all valid and non-conflicting
  • Test: user override replaces the default correctly

Out of scope

  • Keybinding configuration UI (post-v1; config file is sufficient)
  • Vim-style modal bindings

References

  • Spec §11 (Keybindings)
  • Spec §11.3 (Implémentation GTK4)

Dependencies

  • Blocked by: #3 (AppConfig.keybindings), #13 (AppModel, AppMsg)
  • Blocks: none (v0.1 leaf)
  • Branch off: issue-13-app-shell
  • Full graph: #21
## User story As a **user**, I want configurable keyboard shortcuts that work across the application (global scope) and within specific views (chat, terminal, diff), with sensible defaults matching the spec, so that I can operate forge-agent efficiently from the keyboard. ## Acceptance criteria ### Keybinding registry - [ ] Loads default keybindings from a hardcoded list matching spec §11.2 (10 bindings) - [ ] Merges user overrides from `AppConfig.keybindings` HashMap - [ ] Produces a `Vec<Keybinding>` with resolved `current_key` values ### Default bindings (spec §11.2) | ID | Scope | Default | |---|---|---| | `send-message` | Chat | `Ctrl+Return` | | `interrupt-turn` | Chat | `Ctrl+C` | | `new-thread` | Global | `Ctrl+T` | | `toggle-sidebar` | Global | `Ctrl+B` | | `toggle-terminal` | Global | `` Ctrl+` `` | | `switch-diff` | Global | `Ctrl+D` | | `approve-action` | Chat | `Ctrl+Y` | | `reject-action` | Chat | `Ctrl+N` | | `restore-checkpoint` | Chat | `Ctrl+Z` | | `focus-input` | Chat | `/` | ### GTK4 integration (`setup_keybindings()`) - [ ] `gtk::EventControllerKey` attached to the `AdwApplicationWindow` - [ ] On key press: constructs a `KeySpec` from the GDK key + modifiers - [ ] Matches against registered keybindings - [ ] Scope-aware: Chat bindings only active when chat view is focused, etc. - [ ] Matched binding triggers the appropriate action via `handle_keybinding(id, ipc)` - [ ] Returns `glib::Propagation::Stop` on match, `Proceed` otherwise ### Action handlers - [ ] `send-message` → reads input text, dispatches `StartTurn` - [ ] `interrupt-turn` → dispatches `InterruptTurn` for active thread - [ ] `new-thread` → dispatches `CreateThread` for active project - [ ] `toggle-sidebar` → toggles `AdwOverlaySplitView.show_sidebar` - [ ] `toggle-terminal` → toggles terminal pane visibility - [ ] `switch-diff` → switches `ActiveView` to Diff - [ ] `approve-action` / `reject-action` → dispatches approval/rejection commands - [ ] `restore-checkpoint` → dispatches `RestoreCheckpoint` for the last checkpoint - [ ] `focus-input` → grabs focus on the message input `GtkTextView` ### KeySpec parsing - [ ] Parse string format "Ctrl+Shift+X" into `KeySpec` struct - [ ] Support modifiers: Ctrl, Shift, Alt, Meta ### Tests - [ ] Test: KeySpec parsing from string format - [ ] Test: default bindings are all valid and non-conflicting - [ ] Test: user override replaces the default correctly ## Out of scope - Keybinding configuration UI (post-v1; config file is sufficient) - Vim-style modal bindings ## References - Spec §11 (Keybindings) - Spec §11.3 (Implémentation GTK4) ## Dependencies - **Blocked by:** #3 (AppConfig.keybindings), #13 (AppModel, AppMsg) - **Blocks:** none (v0.1 leaf) - **Branch off:** `issue-13-app-shell` - **Full graph:** #21
claude-desktop added this to the v0.1.0 milestone 2026-04-16 11:32:16 +00:00
Sign in to join this conversation.
No description provided.