mirror of
https://github.com/openai/codex.git
synced 2026-04-24 14:45:27 +00:00
docs(keymap): establish long-term keymap documentation
Publish keymap system documentation first so the implementation stack can be reviewed against explicit behavior and invariants. This commit adds the keymap system guide, action matrix, default keymap template, and config/example documentation updates, plus a rollout plan used to stage the additive refactor and validation work.
This commit is contained in:
@@ -6,6 +6,77 @@ For advanced configuration instructions, see [this documentation](https://develo
|
||||
|
||||
For a full configuration reference, see [this documentation](https://developers.openai.com/codex/config-reference).
|
||||
|
||||
## TUI keymap
|
||||
|
||||
The TUI supports rebinding shortcuts via `[tui.keymap]` in `~/.codex/config.toml`.
|
||||
|
||||
Use this complete, commented defaults template.
|
||||
Keymap template: https://github.com/openai/codex/blob/main/docs/default-keymap.toml
|
||||
For implementation details, safety contracts, and testing notes, see `docs/tui-keymap.md`.
|
||||
|
||||
### Precedence
|
||||
|
||||
Precedence is applied in this order (highest first):
|
||||
|
||||
1. Context-specific binding (`[tui.keymap.<context>]`)
|
||||
2. Global binding (`[tui.keymap.global]`) for chat/composer fallback actions
|
||||
3. Built-in preset defaults (`preset`)
|
||||
|
||||
### Presets
|
||||
|
||||
- `latest`: moving alias for the newest preset; today `latest -> v1`
|
||||
- `v1`: frozen legacy/current defaults
|
||||
|
||||
When defaults change in the future, a new version (for example `v2`) is added and
|
||||
`latest` may move to it. Pin to `v1` if you want stable historical behavior.
|
||||
|
||||
### Supported actions
|
||||
|
||||
- `global`: `open_transcript`, `open_external_editor`, `edit_previous_message`,
|
||||
`confirm_edit_previous_message`, `submit`, `queue`, `toggle_shortcuts`
|
||||
- `chat`: `edit_previous_message`, `confirm_edit_previous_message`
|
||||
- `composer`: `submit`, `queue`, `toggle_shortcuts`
|
||||
- `editor`: `insert_newline`, `move_left`, `move_right`, `move_up`, `move_down`,
|
||||
`move_word_left`, `move_word_right`, `move_line_start`, `move_line_end`,
|
||||
`delete_backward`, `delete_forward`, `delete_backward_word`, `delete_forward_word`,
|
||||
`kill_line_start`, `kill_line_end`, `yank`
|
||||
- `pager`: `scroll_up`, `scroll_down`, `page_up`, `page_down`, `half_page_up`,
|
||||
`half_page_down`, `jump_top`, `jump_bottom`, `close`, `close_transcript`,
|
||||
`edit_previous_message`, `edit_next_message`, `confirm_edit_message`
|
||||
- `list`: `move_up`, `move_down`, `accept`, `cancel`
|
||||
- `approval`: `open_fullscreen`, `approve`, `approve_for_session`,
|
||||
`approve_for_prefix`, `decline`, `cancel`
|
||||
- `onboarding`: `move_up`, `move_down`, `select_first`, `select_second`,
|
||||
`select_third`, `confirm`, `cancel`, `quit`, `toggle_animation`
|
||||
|
||||
For long-term behavior and evolution guidance, see `docs/tui-keymap.md`.
|
||||
For a quick action inventory, see `docs/keymap-action-matrix.md`.
|
||||
On onboarding API-key entry, printable `quit` bindings are treated as text input
|
||||
once the field contains text; use control/alt chords for always-available quit
|
||||
shortcuts.
|
||||
|
||||
### Key format
|
||||
|
||||
Use lowercase key identifiers with `-` separators, for example:
|
||||
|
||||
- `ctrl-a`
|
||||
- `shift-enter`
|
||||
- `alt-page-down`
|
||||
- `?`
|
||||
|
||||
Actions accept a single key or multiple keys:
|
||||
|
||||
- `submit = "enter"`
|
||||
- `submit = ["enter", "ctrl-j"]`
|
||||
- `submit = []` (explicitly unbind)
|
||||
|
||||
Some defaults intentionally include multiple variants for one logical shortcut
|
||||
because terminal modifier reporting can differ by platform/emulator. For
|
||||
example, `?` may arrive as plain `?` or `shift-?`, and control chords may
|
||||
arrive with or without `SHIFT`.
|
||||
|
||||
Aliases like `escape`, `pageup`, and `pgdn` are normalized.
|
||||
|
||||
## Connecting to MCP servers
|
||||
|
||||
Codex can connect to MCP servers configured in `~/.codex/config.toml`. See the configuration reference for the latest MCP server options:
|
||||
|
||||
116
docs/default-keymap.toml
Normal file
116
docs/default-keymap.toml
Normal file
@@ -0,0 +1,116 @@
|
||||
# Codex TUI keymap template (v1 defaults)
|
||||
#
|
||||
# Copy the sections you need into ~/.codex/config.toml.
|
||||
#
|
||||
# Canonical source (Codex repo):
|
||||
# https://github.com/openai/codex/blob/main/docs/default-keymap.toml
|
||||
# Implementation reference: docs/tui-keymap.md
|
||||
#
|
||||
# Value format:
|
||||
# - action = "ctrl-a" # one key
|
||||
# - action = ["ctrl-a", "alt-a"] # multiple keys for one action
|
||||
# - action = [] # explicit unbind for that action
|
||||
#
|
||||
# Precedence (highest first):
|
||||
# 1. [tui.keymap.<context>] override
|
||||
# 2. [tui.keymap.global] fallback (only for chat/composer actions)
|
||||
# 3. preset defaults
|
||||
|
||||
[tui.keymap]
|
||||
# Preset alias for built-in defaults.
|
||||
# "latest" currently points to "v1".
|
||||
preset = "latest"
|
||||
|
||||
[tui.keymap.global]
|
||||
# Open transcript overlay.
|
||||
open_transcript = "ctrl-t"
|
||||
# Open external editor for current draft.
|
||||
open_external_editor = "ctrl-g"
|
||||
# Begin/advance "edit previous message" when composer is empty.
|
||||
edit_previous_message = "esc"
|
||||
# Confirm selected message in edit-previous flow.
|
||||
confirm_edit_previous_message = "enter"
|
||||
# Submit current draft.
|
||||
submit = "enter"
|
||||
# Queue current draft while a task is running.
|
||||
queue = "tab"
|
||||
# Toggle composer shortcut overlay.
|
||||
# Include both forms because some terminals report `?` with SHIFT and others don't.
|
||||
toggle_shortcuts = ["?", "shift-?"]
|
||||
|
||||
[tui.keymap.chat]
|
||||
# Overrides [tui.keymap.global] for chat-only behavior.
|
||||
edit_previous_message = "esc"
|
||||
confirm_edit_previous_message = "enter"
|
||||
|
||||
[tui.keymap.composer]
|
||||
# Overrides [tui.keymap.global] for composer behavior.
|
||||
submit = "enter"
|
||||
queue = "tab"
|
||||
toggle_shortcuts = ["?", "shift-?"]
|
||||
|
||||
[tui.keymap.editor]
|
||||
# Text input editor bindings.
|
||||
insert_newline = ["ctrl-j", "ctrl-m", "enter", "shift-enter"]
|
||||
move_left = ["left", "ctrl-b"]
|
||||
move_right = ["right", "ctrl-f"]
|
||||
move_up = ["up", "ctrl-p"]
|
||||
move_down = ["down", "ctrl-n"]
|
||||
move_word_left = ["alt-b", "alt-left", "ctrl-left"]
|
||||
move_word_right = ["alt-f", "alt-right", "ctrl-right"]
|
||||
move_line_start = ["home", "ctrl-a"]
|
||||
move_line_end = ["end", "ctrl-e"]
|
||||
delete_backward = ["backspace", "ctrl-h"]
|
||||
delete_forward = ["delete", "ctrl-d"]
|
||||
delete_backward_word = ["alt-backspace", "ctrl-w", "ctrl-alt-h"]
|
||||
delete_forward_word = ["alt-delete"]
|
||||
kill_line_start = ["ctrl-u"]
|
||||
kill_line_end = ["ctrl-k"]
|
||||
yank = ["ctrl-y"]
|
||||
|
||||
[tui.keymap.pager]
|
||||
# Transcript/static overlay navigation.
|
||||
scroll_up = ["up", "k"]
|
||||
scroll_down = ["down", "j"]
|
||||
page_up = ["page-up", "shift-space", "ctrl-b"]
|
||||
page_down = ["page-down", "space", "ctrl-f"]
|
||||
half_page_up = ["ctrl-u"]
|
||||
half_page_down = ["ctrl-d"]
|
||||
jump_top = ["home"]
|
||||
jump_bottom = ["end"]
|
||||
close = ["q", "ctrl-c"]
|
||||
close_transcript = ["ctrl-t"]
|
||||
# Backtrack controls inside transcript overlay.
|
||||
edit_previous_message = ["esc", "left"]
|
||||
edit_next_message = ["right"]
|
||||
confirm_edit_message = ["enter"]
|
||||
|
||||
[tui.keymap.list]
|
||||
# Generic list/picker navigation.
|
||||
move_up = ["up", "ctrl-p", "k"]
|
||||
move_down = ["down", "ctrl-n", "j"]
|
||||
accept = ["enter"]
|
||||
cancel = ["esc"]
|
||||
|
||||
[tui.keymap.approval]
|
||||
# Approval modal actions.
|
||||
# Include ctrl+shift fallback for terminals that preserve SHIFT on Ctrl letter chords.
|
||||
open_fullscreen = ["ctrl-a", "ctrl-shift-a"]
|
||||
approve = ["y"]
|
||||
approve_for_session = ["a"]
|
||||
approve_for_prefix = ["p"]
|
||||
decline = ["esc", "n"]
|
||||
cancel = ["c"]
|
||||
|
||||
[tui.keymap.onboarding]
|
||||
# Onboarding/auth/trust screens.
|
||||
move_up = ["up", "k"]
|
||||
move_down = ["down", "j"]
|
||||
select_first = ["1", "y"]
|
||||
select_second = ["2", "n"]
|
||||
select_third = ["3"]
|
||||
confirm = ["enter"]
|
||||
cancel = ["esc"]
|
||||
quit = ["q", "ctrl-c", "ctrl-d"]
|
||||
# Include ctrl+shift fallback for terminals that preserve SHIFT on Ctrl punctuation chords.
|
||||
toggle_animation = ["ctrl-.", "ctrl-shift-."]
|
||||
@@ -1,3 +1,42 @@
|
||||
# Sample configuration
|
||||
|
||||
For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).
|
||||
For a full sample configuration, see [this documentation](https://developers.openai.com/codex/config-sample).
|
||||
|
||||
## Keymap snippet
|
||||
|
||||
```toml
|
||||
[tui.keymap]
|
||||
preset = "latest" # currently points to v1 defaults
|
||||
|
||||
[tui.keymap.global]
|
||||
open_transcript = "ctrl-t"
|
||||
open_external_editor = "ctrl-g"
|
||||
|
||||
[tui.keymap.chat]
|
||||
edit_previous_message = "esc"
|
||||
confirm_edit_previous_message = "enter"
|
||||
|
||||
[tui.keymap.composer]
|
||||
submit = "enter"
|
||||
queue = "tab"
|
||||
toggle_shortcuts = ["?", "shift-?"]
|
||||
|
||||
[tui.keymap.pager]
|
||||
close = ["q", "ctrl-c"]
|
||||
close_transcript = ["ctrl-t"]
|
||||
|
||||
[tui.keymap.list]
|
||||
accept = "enter"
|
||||
cancel = "esc"
|
||||
|
||||
[tui.keymap.approval]
|
||||
approve = "y"
|
||||
decline = ["esc", "n"]
|
||||
|
||||
[tui.keymap.onboarding]
|
||||
quit = ["q", "ctrl-c", "ctrl-d"]
|
||||
```
|
||||
|
||||
For a complete, commented template:
|
||||
Keymap template: https://github.com/openai/codex/blob/main/docs/default-keymap.toml
|
||||
For precedence, safety invariants, and testing notes, see `docs/tui-keymap.md`.
|
||||
|
||||
101
docs/keymap-action-matrix.md
Normal file
101
docs/keymap-action-matrix.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# TUI Keymap Action Matrix
|
||||
|
||||
This file defines the supported keymap actions and their default `v1` bindings.
|
||||
For runtime behavior, safety invariants, and testing guidance, see
|
||||
`docs/tui-keymap.md`.
|
||||
|
||||
## Preset behavior
|
||||
|
||||
- `latest` is an alias to the newest shipped preset.
|
||||
- Today, `latest -> v1`.
|
||||
- To keep stable behavior over time, pin `preset = "v1"`.
|
||||
|
||||
## Precedence
|
||||
|
||||
1. `tui.keymap.<context>.<action>`
|
||||
2. `tui.keymap.global.<action>` (chat/composer fallback actions only)
|
||||
3. Preset default (`v1` today)
|
||||
|
||||
## Default `v1` Compatibility Notes
|
||||
|
||||
- Some actions intentionally ship with multiple bindings for the same logical
|
||||
shortcut because terminals differ in modifier reporting.
|
||||
- Today this includes:
|
||||
- `composer.toggle_shortcuts`: `?` and `shift-?`
|
||||
- `approval.open_fullscreen`: `ctrl-a` and `ctrl-shift-a`
|
||||
- `onboarding.toggle_animation`: `ctrl-.` and `ctrl-shift-.`
|
||||
- Keep these paired defaults unless/until key-event normalization is made
|
||||
platform-consistent at a lower layer.
|
||||
|
||||
## Action Definitions
|
||||
|
||||
### `global`
|
||||
|
||||
- `open_transcript`: open transcript overlay
|
||||
- `open_external_editor`: open external editor for current draft
|
||||
- `edit_previous_message`: begin/advance edit-previous flow when composer is empty
|
||||
- `confirm_edit_previous_message`: confirm selected previous message for editing
|
||||
- `submit`: submit current draft
|
||||
- `queue`: queue current draft while a task is running
|
||||
- `toggle_shortcuts`: toggle composer shortcut overlay
|
||||
|
||||
### `chat`
|
||||
|
||||
- `edit_previous_message`: chat override for edit-previous flow
|
||||
- `confirm_edit_previous_message`: chat override for edit confirmation
|
||||
|
||||
### `composer`
|
||||
|
||||
- `submit`: composer override for submit
|
||||
- `queue`: composer override for queue
|
||||
- `toggle_shortcuts`: composer override for shortcut overlay toggle
|
||||
|
||||
### `editor`
|
||||
|
||||
- `insert_newline`: insert newline in text editor
|
||||
- `move_left` / `move_right` / `move_up` / `move_down`: cursor movement
|
||||
- `move_word_left` / `move_word_right`: word movement
|
||||
- `move_line_start` / `move_line_end`: line boundary movement
|
||||
- `delete_backward` / `delete_forward`: single-char deletion
|
||||
- `delete_backward_word` / `delete_forward_word`: word deletion
|
||||
- `kill_line_start` / `kill_line_end`: kill to line boundary
|
||||
- `yank`: paste kill-buffer contents
|
||||
|
||||
### `pager`
|
||||
|
||||
- `scroll_up` / `scroll_down`: row scroll
|
||||
- `page_up` / `page_down`: page scroll
|
||||
- `half_page_up` / `half_page_down`: half-page scroll
|
||||
- `jump_top` / `jump_bottom`: jump to top/bottom
|
||||
- `close`: close pager overlay
|
||||
- `close_transcript`: close transcript via transcript toggle binding
|
||||
- `edit_previous_message` / `edit_next_message`: backtrack navigation in transcript
|
||||
- `confirm_edit_message`: confirm selected backtrack message
|
||||
|
||||
### `list`
|
||||
|
||||
- `move_up` / `move_down`: list navigation
|
||||
- `accept`: select current item
|
||||
- `cancel`: close list view
|
||||
|
||||
### `approval`
|
||||
|
||||
- `open_fullscreen`: open full-screen approval details
|
||||
- `approve`: approve primary request
|
||||
- `approve_for_session`: approve-for-session option
|
||||
- `approve_for_prefix`: approve-for-prefix option
|
||||
- `decline`: decline request
|
||||
- `cancel`: cancel elicitation request
|
||||
- MCP elicitation safety rule: `Esc` is always treated as `cancel` (never
|
||||
`decline`) so dismissal cannot accidentally continue execution.
|
||||
|
||||
### `onboarding`
|
||||
|
||||
- `move_up` / `move_down`: onboarding list navigation
|
||||
- `select_first` / `select_second` / `select_third`: numeric selection shortcuts
|
||||
- `confirm`: confirm highlighted onboarding selection
|
||||
- `cancel`: cancel current onboarding sub-flow
|
||||
- `quit`: quit onboarding flow
|
||||
- `toggle_animation`: switch welcome animation variant
|
||||
- API-key entry guard: printable `quit` bindings are treated as text input once
|
||||
the API-key field has text; control/alt quit chords are not suppressed.
|
||||
69
docs/keymap-rollout-plan.md
Normal file
69
docs/keymap-rollout-plan.md
Normal file
@@ -0,0 +1,69 @@
|
||||
# TUI Keymap Additive Rollout Plan
|
||||
|
||||
This plan restructures the existing keymap implementation into a reviewable
|
||||
additive stack while preserving the final behavior from revision `0f03e76c`.
|
||||
|
||||
## Goals
|
||||
|
||||
- Keep the final behavior effectively identical to `0f03e76c`.
|
||||
- Reorder history to show intent and risk reduction clearly.
|
||||
- Make each change compile, format cleanly, and pass scoped tests.
|
||||
- Establish high-confidence behavior characterization before binding rewrites.
|
||||
|
||||
## Commit Sequence
|
||||
|
||||
1. **Docs and specification foundation**
|
||||
- Add/reshape long-term docs that explain how the keymap system works.
|
||||
- Include:
|
||||
- conceptual model
|
||||
- action matrix
|
||||
- default keymap template guidance with explicit URL
|
||||
- testing strategy notes
|
||||
- No functional code change.
|
||||
|
||||
2. **Additive config primitives (not wired)**
|
||||
- Introduce keymap config types and schema support in `core`.
|
||||
- Keep runtime behavior unchanged by not consuming these config values yet.
|
||||
- Ensure parsing/serialization paths are complete and documented.
|
||||
|
||||
3. **Behavior characterization tests (pre-rewrite)**
|
||||
- Add tests that lock down existing event behavior before switching bindings.
|
||||
- Cover key event matching and context-sensitive behaviors that must stay stable.
|
||||
- Use these tests as the safety net for subsequent rewiring.
|
||||
- Run `cargo llvm-cov` and target full branch coverage for keybinding logic.
|
||||
|
||||
4. **Binding replacement using characterized behavior**
|
||||
- Introduce keymap-driven binding resolution and wire call sites.
|
||||
- Replace legacy binding checks while preserving characterized behavior.
|
||||
- Keep docs in sync with any semantics that became explicit.
|
||||
|
||||
5. **UX polish and affordances**
|
||||
- Apply help/footer/tooltip/key-hint refinements once behavior is stable.
|
||||
- Keep affordance text concise and formatted for readability.
|
||||
- Update snapshots as needed.
|
||||
|
||||
## Validation Gates Per Commit
|
||||
|
||||
For each commit in the sequence:
|
||||
|
||||
1. Run formatting (`just fmt` in `codex-rs`).
|
||||
2. Run scoped tests for affected crates (minimum `cargo test -p codex-tui` for TUI
|
||||
changes and relevant `core` tests when `core` changes).
|
||||
3. Ensure branch compiles cleanly (`cargo test` path used as compile gate).
|
||||
4. For characterization and binding commits, run coverage and track branch
|
||||
coverage for keybinding logic (`cargo llvm-cov`).
|
||||
|
||||
## JJ Workflow
|
||||
|
||||
- Build stack from `trunk()` with explicit ordered changes.
|
||||
- Use `jj new -A` / `jj new -B` to place changes relative to target revisions.
|
||||
- Rebase existing implementation changes into the new stack as needed.
|
||||
- Resolve conflicts immediately and keep each change coherent.
|
||||
- Use clear `jj describe` messages with title + explanatory body.
|
||||
|
||||
## Acceptance Criteria
|
||||
|
||||
- Final tip behavior matches `0f03e76c` in practice.
|
||||
- Stack reads top-to-bottom as: docs -> config -> characterization -> rewiring ->
|
||||
UX polish.
|
||||
- Each commit is independently reviewable with passing checks.
|
||||
277
docs/tui-keymap.md
Normal file
277
docs/tui-keymap.md
Normal file
@@ -0,0 +1,277 @@
|
||||
# TUI Keymap Implementation Reference
|
||||
|
||||
This document is the long-term implementation reference for Codex TUI keybindings.
|
||||
It describes how keymap configuration is resolved at runtime, which safety
|
||||
contracts are intentionally strict, and how to test behavior end to end.
|
||||
|
||||
## Scope and boundaries
|
||||
|
||||
This keymap system is action-based and context-aware. It supports user rebinding
|
||||
for the TUI without requiring source code edits.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
1. Resolve config values into runtime key bindings.
|
||||
2. Apply deterministic precedence.
|
||||
3. Reject ambiguous bindings in dispatch scopes where collisions are unsafe.
|
||||
4. Preserve explicit safety semantics for approval elicitation and onboarding.
|
||||
|
||||
Non-responsibilities:
|
||||
|
||||
1. It does not choose which screen should handle an event.
|
||||
2. It does not persist config.
|
||||
3. It does not guarantee terminal modifier reporting consistency; defaults may
|
||||
include compatibility variants for that reason.
|
||||
|
||||
## Source-of-truth map
|
||||
|
||||
- Runtime resolution: `codex-rs/tui/src/keymap.rs`
|
||||
- Onboarding flow-level routing and quit guard:
|
||||
`codex-rs/tui/src/onboarding/onboarding_screen.rs`
|
||||
- Approval/MCP elicitation option semantics:
|
||||
`codex-rs/tui/src/bottom_pane/approval_overlay.rs`
|
||||
- Generic list popup navigation semantics:
|
||||
`codex-rs/tui/src/bottom_pane/list_selection_view.rs`
|
||||
- User-facing default template:
|
||||
`https://github.com/openai/codex/blob/main/docs/default-keymap.toml`
|
||||
- User-facing config overview: `docs/config.md`
|
||||
|
||||
## Config contract
|
||||
|
||||
`[tui.keymap]` is action-to-binding mapping by context.
|
||||
|
||||
```toml
|
||||
[tui.keymap]
|
||||
preset = "latest"
|
||||
|
||||
[tui.keymap.global]
|
||||
submit = "enter"
|
||||
|
||||
[tui.keymap.composer]
|
||||
submit = ["enter", "ctrl-j"]
|
||||
```
|
||||
|
||||
Rules:
|
||||
|
||||
1. Mapping direction is `action -> key_or_keys`.
|
||||
2. Values support:
|
||||
1. `action = "key-spec"`
|
||||
2. `action = ["key-spec-1", "key-spec-2"]`
|
||||
3. `action = []` for explicit unbind.
|
||||
3. Unknown contexts, actions, or key identifiers fail validation.
|
||||
4. Aliases are normalized (for example `escape -> esc`, `pgdn -> page-down`).
|
||||
5. Key identifiers are lowercase and use `-` separators.
|
||||
|
||||
## Contexts and actions
|
||||
|
||||
Supported contexts:
|
||||
|
||||
1. `global`
|
||||
2. `chat`
|
||||
3. `composer`
|
||||
4. `editor`
|
||||
5. `pager`
|
||||
6. `list`
|
||||
7. `approval`
|
||||
8. `onboarding`
|
||||
|
||||
Action inventory by context is documented in `docs/config.md` and the template
|
||||
at `https://github.com/openai/codex/blob/main/docs/default-keymap.toml`.
|
||||
|
||||
## Presets and compatibility policy
|
||||
|
||||
Preset semantics:
|
||||
|
||||
1. `latest` is an alias to the newest shipped preset.
|
||||
2. `v1` is the current frozen baseline.
|
||||
3. Today, `latest -> v1`.
|
||||
|
||||
User guidance:
|
||||
|
||||
1. Pin `preset = "v1"` for stable behavior over time.
|
||||
2. Use `preset = "latest"` to adopt new defaults when `latest` moves.
|
||||
|
||||
Developer policy:
|
||||
|
||||
1. Do not mutate old preset defaults after release.
|
||||
2. Add a new version (for example `v2`) for behavior changes.
|
||||
3. Update docs and migration notes whenever `latest` changes.
|
||||
|
||||
Compatibility detail:
|
||||
|
||||
Some actions intentionally ship with multiple default bindings because terminals
|
||||
can report modifier combinations differently. Examples include `?` vs `shift-?`
|
||||
and certain `ctrl` chords with optional `shift`.
|
||||
|
||||
## Resolution and precedence
|
||||
|
||||
Resolution order (highest first):
|
||||
|
||||
1. Context binding (`tui.keymap.<context>.<action>`)
|
||||
2. Global fallback (`tui.keymap.global.<action>`) for chat/composer fallback
|
||||
actions only
|
||||
3. Preset default binding
|
||||
|
||||
If no binding matches, normal unhandled-key fallback behavior applies.
|
||||
|
||||
## Conflict validation model
|
||||
|
||||
Validation is dispatch-order aware, not globally uniform.
|
||||
|
||||
Current conflict passes in `RuntimeKeymap::validate_conflicts` enforce:
|
||||
|
||||
1. App-level uniqueness for app actions and app-level chat controls.
|
||||
2. App/composer shadowing prevention, because app handlers execute before
|
||||
forwarding to composer handlers.
|
||||
3. Composer-local uniqueness for submit/queue/shortcut-toggle.
|
||||
4. Context-local uniqueness in editor, pager, list, approval, and onboarding.
|
||||
|
||||
Intentionally allowed:
|
||||
|
||||
1. Same key across different contexts that are not co-evaluated in a way that
|
||||
can cause unsafe shadowing.
|
||||
2. Shared defaults where runtime context gating keeps semantics unambiguous.
|
||||
|
||||
## Safety invariants
|
||||
|
||||
### MCP elicitation cancel semantics
|
||||
|
||||
For MCP elicitation prompts, `Esc` is always treated as `cancel`.
|
||||
|
||||
Implementation contract:
|
||||
|
||||
1. `Esc` is always included in cancel shortcuts.
|
||||
2. User-defined `approval.cancel` shortcuts are merged into cancel.
|
||||
3. Any overlap is removed from `approval.decline` in elicitation mode.
|
||||
|
||||
Rationale: dismissal must remain a safe abort path and never silently map to
|
||||
"continue without requested info".
|
||||
|
||||
### Onboarding API-key text-entry guard
|
||||
|
||||
During API-key entry, printable `onboarding.quit` bindings are suppressed only
|
||||
when the API-key field already has text.
|
||||
|
||||
Implementation contract:
|
||||
|
||||
1. Guard applies only when API-key entry mode is active.
|
||||
2. Guard applies only to printable char keys without control/alt modifiers.
|
||||
3. Guard applies only when input is non-empty.
|
||||
4. Control/alt quit chords are never suppressed by this guard.
|
||||
|
||||
Rationale: keep text-entry safe once typing has begun while preserving an
|
||||
intentional printable-quit path on empty input.
|
||||
|
||||
## Dispatch model and handler boundaries
|
||||
|
||||
High-level behavior:
|
||||
|
||||
1. App-level event handling runs before some lower-level handlers.
|
||||
2. Composer behavior depends on both app routing and composer-local checks.
|
||||
3. Onboarding screen routing applies flow-level rules before delegating to step
|
||||
widgets.
|
||||
4. Approval overlay and list selection use context-specific bindings resolved by
|
||||
`RuntimeKeymap`.
|
||||
|
||||
When changing dispatch order, re-evaluate conflict validation scopes in
|
||||
`keymap.rs` and associated tests.
|
||||
|
||||
## Diagnostics contract
|
||||
|
||||
Validation errors should be actionable and include:
|
||||
|
||||
1. Problem summary.
|
||||
2. Exact config path.
|
||||
3. Why the value is invalid or ambiguous.
|
||||
4. Concrete remediation step.
|
||||
|
||||
Categories currently covered:
|
||||
|
||||
1. Invalid key specification.
|
||||
2. Unknown action/context mapping.
|
||||
3. Same-scope ambiguity.
|
||||
4. Shadowing collisions in dispatch-coupled scopes.
|
||||
|
||||
## Debug path
|
||||
|
||||
When keybindings do not behave as expected, trace in this order:
|
||||
|
||||
1. Verify config normalization and schema validation in
|
||||
`codex-rs/core/src/config/tui_keymap.rs`.
|
||||
2. Verify resolved runtime bindings and conflict checks in
|
||||
`codex-rs/tui/src/keymap.rs` (`from_config`, `validate_conflicts`).
|
||||
3. Verify handler-level dispatch order in:
|
||||
1. `codex-rs/tui/src/app.rs` for app/chat/composer routing.
|
||||
2. `codex-rs/tui/src/pager_overlay.rs` for pager controls.
|
||||
3. `codex-rs/tui/src/bottom_pane/approval_overlay.rs` for approval safety
|
||||
behavior.
|
||||
4. `codex-rs/tui/src/onboarding/onboarding_screen.rs` for onboarding quit
|
||||
guard behavior.
|
||||
4. Reproduce with explicit bindings in `~/.codex/config.toml` and compare
|
||||
against:
|
||||
1. `docs/default-keymap.toml`
|
||||
2. `docs/keymap-action-matrix.md`
|
||||
|
||||
## Testing notes
|
||||
|
||||
### Commands
|
||||
|
||||
Run from `codex-rs/`:
|
||||
|
||||
1. `just fmt`
|
||||
2. `cargo test -p codex-tui --lib`
|
||||
3. Optional full crate run (includes integration tests):
|
||||
`cargo test -p codex-tui`
|
||||
4. Optional focused runs while iterating:
|
||||
`cargo test -p codex-tui --lib keymap::tests`
|
||||
|
||||
If `cargo test -p codex-tui` fails because the `codex` binary cannot be found
|
||||
in local `target/`, run `--lib` for keymap behavior checks and then validate the
|
||||
integration target in an environment where workspace binaries are available.
|
||||
|
||||
For intentional UI/text output changes in `codex-tui`:
|
||||
|
||||
1. `cargo insta pending-snapshots -p codex-tui`
|
||||
2. `cargo insta show -p codex-tui <path/to/snapshot.snap.new>`
|
||||
3. `cargo insta accept -p codex-tui` only when the full snapshot set is
|
||||
expected.
|
||||
|
||||
### Behavior coverage checklist
|
||||
|
||||
Use this checklist before landing keymap behavior changes:
|
||||
|
||||
1. Precedence: context override beats global fallback and preset defaults.
|
||||
2. Unbind behavior: `action = []` actually removes the binding.
|
||||
3. Conflict rejection:
|
||||
1. Same-context duplicates fail.
|
||||
2. App/composer shadowing fails for submit, queue, and toggle-shortcuts.
|
||||
4. Approval safety:
|
||||
1. `Esc` resolves elicitation to cancel.
|
||||
2. Decline shortcuts never contain cancel overlaps in elicitation mode.
|
||||
5. Onboarding safety:
|
||||
1. Printable quit key is suppressed when API-key input is active and
|
||||
non-empty.
|
||||
2. Printable quit key is not suppressed when input is empty.
|
||||
3. Control/alt quit chords are not suppressed.
|
||||
6. Footer/help hints continue to reflect effective primary bindings.
|
||||
7. `https://github.com/openai/codex/blob/main/docs/default-keymap.toml`,
|
||||
`docs/config.md`, and `docs/example-config.md` stay aligned with runtime
|
||||
action names and defaults.
|
||||
|
||||
### Manual sanity checks
|
||||
|
||||
1. Start onboarding and enter API-key mode.
|
||||
2. Bind `onboarding.quit` to a printable key.
|
||||
3. Verify that key quits when input is empty, then types once text exists.
|
||||
4. Verify `ctrl-c` or another control quit chord still exits.
|
||||
5. Trigger an MCP elicitation request and verify `Esc` cancels, not declines.
|
||||
|
||||
## Documentation maintenance
|
||||
|
||||
When adding/changing keymap API surface:
|
||||
|
||||
1. Update runtime definitions and defaults in `codex-rs/tui/src/keymap.rs`.
|
||||
2. Update `docs/default-keymap.toml`.
|
||||
3. Update `docs/config.md` and `docs/example-config.md` snippets.
|
||||
4. Update this file with behavioral or safety contract changes.
|
||||
5. Add/update regression tests in `codex-rs/tui`.
|
||||
Reference in New Issue
Block a user