| 1 | # Keybindings |
| 2 | |
| 3 | This is the source-of-truth catalog of every keyboard shortcut the TUI recognizes. Bindings are grouped by **context** — the focus or modal state they fire in. A binding listed under "Composer" only takes effect when the composer is focused; one under "Transcript" only when the transcript has focus; and so on. |
| 4 | |
| 5 | Global key chords are not yet user-configurable — tracked for a future release (#436, #437). Hotbar slot actions are configurable with `[[hotbar]]` and `/hotbar`; the Hotbar activation chord remains `Alt-1` through `Alt-8`. |
| 6 | |
| 7 | ## Global (any context) |
| 8 | |
| 9 | | Chord | Action | |
| 10 | |----------------------|---------------------------------------------------------------| |
| 11 | | `F1` or `Ctrl-/` | Toggle the help overlay | |
| 12 | | `F2` | Toggle the typed Settings editor | |
| 13 | | `Ctrl-K` | Open the command palette (slash-command finder) | |
| 14 | | `Ctrl-C` | Cancel current turn / dismiss modal / arm-then-confirm quit | |
| 15 | | `Ctrl-B` | Move a supported foreground shell wait into `/jobs` so the turn can continue; use `/jobs` or `Bash` with `action: "wait"` to inspect it | |
| 16 | | `Ctrl-D` | Quit (only when the composer is empty) | |
| 17 | | `Tab` | When the composer is empty, cycle TUI mode: Plan → Act → Operate → Plan | |
| 18 | | `Shift+Tab` | Cycle permission posture: Ask → Auto-Review → Full Access. Live regardless of composer contents or whether a turn is running (suppressed only while a modal other than Config is open) | |
| 19 | | `Ctrl-T` | Cycle reasoning effort for the active provider. DeepSeek-style providers cycle off → high → max → off; OpenAI Codex cycles low → medium → high → xhigh → low. | |
| 20 | | `Ctrl-Shift-T` | Toggle live transcript overlay (sticky-tail auto-scroll) | |
| 21 | | `Ctrl-R` | Open the resume-session picker | |
| 22 | | `Ctrl-L` | Compact the conversation context (status line shows progress; no-op while a compaction is already running) | |
| 23 | | `Ctrl-O` | Open the reasoning detail for the selected or current turn, regardless of composer contents | |
| 24 | | `Ctrl-Alt-O` | Open the whole-turn Turn Inspector, regardless of composer contents | |
| 25 | | `Alt-V` / `Option-V` (macOS) | Open the details pager for the selected, visible, or most recent tool/sub-agent card; terminals that emit the legacy Option-V glyph are also handled | |
| 26 | | `Ctrl-Shift-E` / `Cmd-Shift-E` | Toggle the file-tree sidebar | |
| 27 | | `Alt-G` / `Alt-Shift-G` | Scroll transcript to top / bottom when the composer is empty | |
| 28 | | `Alt-1`-`Alt-8` | Dispatch Hotbar slots 1-8 when no modal or inline picker is open | |
| 29 | | `Alt-!` / `Alt-@` / `Alt-#` / `Alt-$` | Select the work-bar panel: Tasks / Agents / Context / Pinned | |
| 30 | | `Ctrl-Alt-0` | Toggle the work bar off / back to the top placement | |
| 31 | | `Alt-L` | Open the pager for the last message (composer empty) | |
| 32 | | `Alt-P` / `Alt-A` / `Alt-Y` | Jump to Plan / Act / Operate mode (alternative to `Tab` cycling) | |
| 33 | | `Ctrl-X` (Activity sidebar) | Cancel all running background shell jobs | |
| 34 | | `Esc` | Close topmost modal · cancel slash menu · dismiss toast | |
| 35 | |
| 36 | ## Composer |
| 37 | |
| 38 | Editing the message you're about to send. |
| 39 | |
| 40 | | Chord | Action | |
| 41 | |-----------------------------|---------------------------------------------------------| |
| 42 | | `Enter` | Send when idle; queue while busy; with an empty composer, send the next queued follow-up now | |
| 43 | | `Shift-Enter` / `Alt-Enter` / `Ctrl-J` | Insert a newline without sending (idle or busy) | |
| 44 | | `Ctrl-Enter` / `Cmd-Enter` | Steer the current turn; send normally when idle (when supported by the terminal) | |
| 45 | | `Ctrl-U` | Clear the whole draft (recoverable — see `Ctrl-Z`) | |
| 46 | | `Ctrl-Z` | Restore the cleared draft (only while the composer is empty) | |
| 47 | | `Ctrl-W` / `Ctrl-Backspace` / `Alt-Backspace` | Delete previous word | |
| 48 | | `Ctrl-A` / `Home` | Move to start of input / start of line (readline convention) | |
| 49 | | `Ctrl-E` / `End` | Move to end of input / end of line | |
| 50 | | `Ctrl-←` / `Alt-←` | Move backward one word | |
| 51 | | `Ctrl-→` / `Alt-→` | Move forward one word | |
| 52 | | `Shift-←` / `Shift-→` | Extend the selection one grapheme at a time | |
| 53 | | `Ctrl-Shift-←/→` / `Alt-Shift-←/→` | Extend the selection one word at a time | |
| 54 | | `Shift-Home` / `Shift-End` | Extend the selection to the start / end of the line | |
| 55 | | `Ctrl-Shift-Home` / `Ctrl-Shift-End` | Extend the selection to the start / end of the draft | |
| 56 | | `Ctrl-Shift-A` / `Cmd-A` | Select the whole draft (see note below) | |
| 57 | | Mouse drag | Select composer text; click moves the cursor | |
| 58 | | `Cmd-V` / `Ctrl-Shift-V` | Terminal-local paste (arrives as bracketed paste when supported) | |
| 59 | | `Ctrl-V` | Direct clipboard paste in a local or forwarded graphical session | |
| 60 | | `Ctrl-Y` | Yank (paste) from kill buffer | |
| 61 | | `↑` / `↓` | Cycle composer history (also selects popup/attachment items) | |
| 62 | | `Shift-↑` / `Shift-↓` | Browse conversation history | |
| 63 | | `Ctrl-P` / `Ctrl-N` | Navigate slash-command menu entries; `Ctrl-P` opens the file picker when the menu is empty | |
| 64 | | `Ctrl-G` / `Ctrl-S` | Stash current draft (`/stash pop` restores it); never sends or steers | |
| 65 | | `Alt-R` | Search prompt history (Alt-R to exit) | |
| 66 | | `Tab` | Slash-command / `@`-mention completion (popup-aware) | |
| 67 | | `Ctrl-Shift-O` / `F4` | Open the composer draft in `$VISUAL` / `$EDITOR`; F4 works when the terminal cannot distinguish Ctrl-Shift-O from Ctrl-O | |
| 68 | | `! command` | Run a shell command through normal approval, sandbox, and output surfaces | |
| 69 | |
| 70 | ### Selection semantics |
| 71 | |
| 72 | Typing, pasting, `Backspace`, or `Delete` with an active selection replaces or |
| 73 | removes the selected text, like any GUI editor. Plain movement keys (arrows, |
| 74 | `Home`/`End`, word motions) collapse the selection. When a selection covers the |
| 75 | whole draft, deleting or typing over it stashes the outgoing text the same way |
| 76 | `Ctrl-U` does, so `Ctrl-Z` (on an empty composer) or `Alt-R` draft recovery can |
| 77 | bring it back. |
| 78 | |
| 79 | Cursor movement and deletion are grapheme-aware: one `←`/`→` step or one |
| 80 | `Backspace` covers a full emoji ZWJ sequence, flag pair, or combining-mark |
| 81 | cluster — never half of one. CJK text moves and deletes per character as |
| 82 | expected. |
| 83 | |
| 84 | **Why select-all is not `Ctrl-A`:** the composer follows the readline |
| 85 | convention, where `Ctrl-A` jumps to the start of the input (paired with |
| 86 | `Ctrl-E`). Select-all is `Ctrl-Shift-A` on every platform (like |
| 87 | `Ctrl-Shift-O` / `Ctrl-Shift-E`, it needs a terminal with an enhanced keyboard |
| 88 | protocol). On macOS terminals that forward the Command key (kitty, WezTerm, |
| 89 | iTerm2 with Command remapping), native `Cmd-A` also selects all; `Cmd-Shift-A` |
| 90 | works everywhere on macOS because Cmd normalizes to Ctrl. |
| 91 | |
| 92 | ### Hotbar |
| 93 | |
| 94 | Hotbar trigger semantics are intentionally `Alt-1` through `Alt-8` only. On macOS keyboards this is the Option/Alt key plus the number row. Bare `1`-`8` is normal text input in the composer and remains owned by pickers, onboarding, approval prompts, and modal views. |
| 95 | |
| 96 | Function keys and `Cmd-1` through `Cmd-8` are not the primary Hotbar chords. Many terminals reserve those keys for tabs, windows, or OS shortcuts, and some never forward them to terminal apps. If a terminal is configured to send `Alt-1` for a custom shortcut, the Hotbar receives the same reliable chord. |
| 97 | |
| 98 | Since #3807 a missing `hotbar` key renders **no bar** — fresh configs show no Hotbar until you configure `[[hotbar]]` slots (an explicit `hotbar = []` also disables it). When configured, a bar looks like: |
| 99 | |
| 100 | | Slot | Chord | Default action | Label | |
| 101 | |------|---------|--------------------|-----------| |
| 102 | | 1 | `Alt-1` | `voice.toggle` | `voice` | |
| 103 | | 2 | `Alt-2` | `session.compact` | `compact` | |
| 104 | | 3 | `Alt-3` | `mode.plan` | `plan` | |
| 105 | | 4 | `Alt-4` | `mode.agent` | `agent` | |
| 106 | | 5 | `Alt-5` | `mode.operate` | `operate` | |
| 107 | | 6 | `Alt-6` | `palette.open` | `palette` | |
| 108 | | 7 | `Alt-7` | `sidebar.toggle` | `side` | |
| 109 | | 8 | `Alt-8` | `trust.toggle` | `trust` | |
| 110 | |
| 111 | | Focus state | Hotbar behavior | |
| 112 | |-------------|-----------------| |
| 113 | | Composer empty, text, or whitespace | `Alt-1`-`Alt-8` dispatches a configured slot | |
| 114 | | Sidebar focused, hidden, or auto | `Alt-1`-`Alt-8` still dispatches a configured slot | |
| 115 | | Slash menu or history search open | Blocked; the inline selector owns the key event | |
| 116 | | Command palette, help, approval, file picker, session picker, Fleet setup, or any modal stack | Blocked; the modal owns the key event | |
| 117 | | Onboarding | Blocked; onboarding owns numeric choices | |
| 118 | |
| 119 | ### `@` mentions |
| 120 | |
| 121 | Type `@<partial>` to open the file mention popup. `↑`/`↓` cycle the entries, `Tab` or `Enter` accepts. `Esc` hides the popup. As of v0.8.10 (#441), completions are re-ranked by mention frecency — files you mention often + recently float to the top. |
| 122 | |
| 123 | Two mentions resolve to curated git context instead of a path (v0.9.2, #4067): |
| 124 | |
| 125 | | Mention | Inlines | Byte budget | |
| 126 | |---------|---------|-------------| |
| 127 | | `@git` | `git status --short --branch` for the workspace | 8 KB | |
| 128 | | `@diff` | The working-tree diff, staged and unstaged (`git diff HEAD`) | 32 KB | |
| 129 | |
| 130 | Both appear in the completion popup alongside paths, and both show up in the context inspector with their resolved size and, when the diff exceeds its budget, the truncation marker. When git is missing, the workspace is not a repository, or there is nothing to show, the turn carries an explicit `<git-unavailable>` note rather than silently contributing nothing. A path that merely starts with the token (`@diff.txt`, `@git/config`) stays a file mention. |
| 131 | |
| 132 | ### `#` quick-add (memory) |
| 133 | |
| 134 | When `[memory] enabled = true`, typing `# foo` and pressing `Enter` appends `foo` as a timestamped bullet to your memory file *without* sending a turn. See `docs/MEMORY.md`. |
| 135 | |
| 136 | ## Transcript (when transcript has focus) |
| 137 | |
| 138 | | Chord | Action | |
| 139 | |----------------------|-----------------------------------------------------| |
| 140 | | `↑` / `↓` / `j` / `k`| Scroll one line (v0.8.13+: bare arrows also scroll when composer empty) | |
| 141 | | `Alt-↑` / `Alt-↓` | Scroll transcript (alternative) | |
| 142 | | `PgUp` / `PgDn` | Scroll one page | |
| 143 | | `Home` / `g` | Jump to top | |
| 144 | | `End` / `G` | Jump to bottom | |
| 145 | | `Ctrl-Home` / `Ctrl-End` | Jump to top / bottom (also works from the composer) | |
| 146 | | `Alt-[` / `Alt-]` | Jump between tool output blocks | |
| 147 | | `Esc Esc` | Backtrack to a previous user message (`←`/`→` steps, `Enter` rewinds) | |
| 148 | | `Esc` | Return focus to composer | |
| 149 | | Mouse drag | Select transcript text in Codewhale | |
| 150 | | `Ctrl-C` | Copy an active Codewhale selection | |
| 151 | | `Cmd-click` (macOS) / `Ctrl-click` (Linux/Windows) | Open an OSC 8 link in a supporting terminal (terminal-owned) | |
| 152 | |
| 153 | For terminal-native selection, hold `Shift` while dragging (terminal support |
| 154 | varies), then use the terminal's own copy command: usually `Cmd-C` on macOS or |
| 155 | `Ctrl-Shift-C` on Linux/Windows. Those commands are handled by the local |
| 156 | terminal and are intentionally separate from Codewhale's `Ctrl-C` selection |
| 157 | binding. Over SSH, Codewhale sends copy requests back through OSC 52, or via |
| 158 | tmux's `load-buffer -w` path when running inside tmux. |
| 159 | |
| 160 | ## Work bar (after `Alt-W` claims focus) |
| 161 | |
| 162 | | Chord | Action | |
| 163 | |----------------------|-----------------------------------------------------| |
| 164 | | `↑` / `↓` | Move selection | |
| 165 | | `Home` / `End` | Jump to the first / last row | |
| 166 | | `PageUp` / `PageDown`| Move selection a viewport at a time | |
| 167 | | `Enter` | Open the selected row's world (work inspector / agent details); on an already-open row, close it | |
| 168 | | `Esc` | Close the open detail, else return focus to the composer | |
| 169 | | any printable key | Return focus to the composer (typing always wins) | |
| 170 | |
| 171 | Mouse parity: clicking any work-bar row does what `Enter` does, in every |
| 172 | panel and placement. `Alt-!`/`Alt-@`/`Alt-#`/`Alt-$` switch panels. |
| 173 | |
| 174 | ## Slash-command palette (after `Ctrl-K` or typing `/`) |
| 175 | |
| 176 | | Chord | Action | |
| 177 | |--------------------------------|-----------------------------------------------------| |
| 178 | | `↑` / `↓` / `Ctrl+P` / `Ctrl+N`| Move selection | |
| 179 | | `Enter` / `Tab` | Run / complete the highlighted command | |
| 180 | | `Esc` | Dismiss palette | |
| 181 | |
| 182 | ## Session Picker (`Ctrl-R` or `/sessions`) |
| 183 | |
| 184 | | Chord | Action | |
| 185 | |----------------------|-----------------------------------------------------| |
| 186 | | `↑` / `↓` / `j` / `k`| Move selection in the session list | |
| 187 | | `1`-`9` | Open the visible session history at that list slot | |
| 188 | | `PgUp` / `PgDn` | Page the history pane | |
| 189 | | `Enter` | Resume the selected session | |
| 190 | | `/` | Search sessions | |
| 191 | | `s` | Cycle sort order | |
| 192 | | `a` | Toggle current-workspace scope vs all workspaces | |
| 193 | | `e` | Archive / restore the selected session | |
| 194 | | `x` | Show or hide archived sessions | |
| 195 | | `d` | Delete selected session after confirmation | |
| 196 | | `Esc` / `q` | Close the picker | |
| 197 | |
| 198 | Archive (`e`) is undestructive and needs no confirmation: the session stays on |
| 199 | disk and stays loadable, it just leaves the default list and stops being an |
| 200 | auto-resume candidate. Press `e` again to bring it back. Delete (`d`) is the |
| 201 | destructive one and keeps its confirmation. |
| 202 | |
| 203 | ## Approval modal (when a tool requests approval) |
| 204 | |
| 205 | | Chord | Action | |
| 206 | |----------------------|-----------------------------------------------------| |
| 207 | | `y` / `Y` | Approve once | |
| 208 | | `a` / `A` | Approve all (auto-approve subsequent calls) | |
| 209 | | `n` / `N` / `Esc` | Deny | |
| 210 | | `e` | Edit the approved input before running | |
| 211 | |
| 212 | ## Onboarding (first-run flow) |
| 213 | |
| 214 | | Chord | Action | |
| 215 | |----------------------|-----------------------------------------------------| |
| 216 | | `Enter` | Advance to next step (Welcome → Language → API/trust gates → setup checkpoint) | |
| 217 | | `Esc` | Step back one screen | |
| 218 | | `1`–`9` | Pick a language (Language step) | |
| 219 | | `0`–`9` | Pick a provider (Provider step; SGLang, vLLM, and Ollama are keyless by default) | |
| 220 | | `y` / `Y` | Trust the workspace (Trust step) | |
| 221 | | `n` / `N` | Skip the trust prompt | |
| 222 | |
| 223 | ## v0.8.29 audit notes |
| 224 | |
| 225 | - **`Shift+Enter` / `Alt+Enter` newlines now work in VSCode on Windows (#1359).** crossterm's `PushKeyboardEnhancementFlags` command unconditionally returns `Unsupported` on Windows (`is_ansi_code_supported() == false`), so the Kitty keyboard protocol escape was never written to the terminal. Without it, VSCode's xterm.js stays in legacy mode where `Shift+Enter` is indistinguishable from plain `Enter`, causing the composer to send the message instead of inserting a newline. The fix writes the push/pop escapes (`\x1b[>1u` / `\x1b[<1u`) directly on Windows, bypassing crossterm's capability gate. VSCode integrated terminal and Windows Terminal ≥1.17 both honour the Kitty keyboard protocol; terminals that do not understand the sequences silently discard them. |
| 226 | |
| 227 | ## v0.8.13 audit notes |
| 228 | |
| 229 | - **Ctrl-S is stash, not history search.** Fixed in this revision — `Alt-R` is history search. |
| 230 | - **Phantom `Alt+Up` removed.** The "Edit last queued message" binding was listed in README but never existed in the key dispatch code. |
| 231 | - **Bare Up/Down arrows scroll transcript when composer empty (v0.8.13).** Previously the `should_scroll_with_arrows` gate was hardcoded to false, meaning bare arrows always navigated composer history even when the composer was empty. Users in virtual terminals (Ghostty, Codex, Kitty-protocol) were especially affected because they couldn't use Cmd+Up / Alt+Up shortcuts. |
| 232 | - **Configurable keymap (#436) and `tui.toml` (#437) remain deferred.** The `TuiPrefs` struct and loader exist in `settings.rs` but are not wired at startup. The named-binding registry that would let `~/.codewhale/tui.toml` override individual entries is still pending. |
| 233 | - **No other broken bindings found.** Every other chord listed above resolves to a live handler in `crates/tui/src/tui/ui.rs` (key-event dispatch) or `crates/tui/src/tui/app.rs` (mode + state transitions). |
| 234 |