diff --git a/codex-rs/config/src/tui_keymap.rs b/codex-rs/config/src/tui_keymap.rs index 2953f6e068..a2232c2fe8 100644 --- a/codex-rs/config/src/tui_keymap.rs +++ b/codex-rs/config/src/tui_keymap.rs @@ -122,6 +122,8 @@ pub struct TuiGlobalKeymap { pub struct TuiChatKeymap { /// Interrupt the active turn. pub interrupt_turn: Option, + /// Flush all queued follow-up messages into the active turn as one steer. + pub flush_queued_messages: Option, /// Decrease the active reasoning effort. pub decrease_reasoning_effort: Option, /// Increase the active reasoning effort. diff --git a/codex-rs/core/config.schema.json b/codex-rs/core/config.schema.json index cf442ca622..91e71e3753 100644 --- a/codex-rs/core/config.schema.json +++ b/codex-rs/core/config.schema.json @@ -3114,6 +3114,7 @@ "chat": { "decrease_reasoning_effort": null, "edit_queued_message": null, + "flush_queued_messages": null, "increase_reasoning_effort": null, "interrupt_turn": null }, @@ -3446,6 +3447,14 @@ ], "description": "Edit the most recently queued message." }, + "flush_queued_messages": { + "anyOf": [ + { + "$ref": "#/definitions/KeybindingsSpec" + } + ], + "description": "Flush all queued follow-up messages into the active turn as one steer." + }, "increase_reasoning_effort": { "anyOf": [ { @@ -3780,6 +3789,7 @@ "default": { "decrease_reasoning_effort": null, "edit_queued_message": null, + "flush_queued_messages": null, "increase_reasoning_effort": null, "interrupt_turn": null } @@ -5764,4 +5774,4 @@ }, "title": "ConfigToml", "type": "object" -} \ No newline at end of file +} diff --git a/codex-rs/tui/src/chatwidget/input_flow.rs b/codex-rs/tui/src/chatwidget/input_flow.rs index 579d97fbbb..a54b023bb0 100644 --- a/codex-rs/tui/src/chatwidget/input_flow.rs +++ b/codex-rs/tui/src/chatwidget/input_flow.rs @@ -98,6 +98,47 @@ impl ChatWidget { } } + /// Merge every queued follow-up visible at this keypress into one active-turn steer. + /// + /// Rejected steers retain dequeue priority, followed by locally queued messages in FIFO + /// order. Taking all four deques together preserves their message/history alignment while + /// leaving already-submitted pending steers and the live composer draft untouched. + pub(super) fn flush_queued_messages(&mut self) { + let rejected_messages = std::mem::take(&mut self.input_queue.rejected_steers_queue); + let mut rejected_history_records = + std::mem::take(&mut self.input_queue.rejected_steer_history_records); + rejected_history_records.resize( + rejected_messages.len(), + UserMessageHistoryRecord::UserMessageText, + ); + + let queued_messages = std::mem::take(&mut self.input_queue.queued_user_messages); + let mut queued_history_records = + std::mem::take(&mut self.input_queue.queued_user_message_history_records); + queued_history_records.resize( + queued_messages.len(), + UserMessageHistoryRecord::UserMessageText, + ); + + let mut messages = rejected_messages + .into_iter() + .zip(rejected_history_records) + .collect::>(); + messages.extend( + queued_messages + .into_iter() + .zip(queued_history_records) + .map(|(message, history_record)| (message.into_user_message(), history_record)), + ); + let (message, history_record) = merge_user_messages_with_history_record(messages); + self.resubmit_queued_user_message_with_history_record( + message, + history_record, + ShellEscapePolicy::Disallow, + ); + self.refresh_pending_input_preview(); + } + /// If idle and there are queued inputs, submit exactly one to start the next turn. pub(crate) fn maybe_send_next_queued_input(&mut self) -> bool { if self.input_queue.suppress_queue_autosend { diff --git a/codex-rs/tui/src/chatwidget/interaction.rs b/codex-rs/tui/src/chatwidget/interaction.rs index 094baa0b02..299c7085ac 100644 --- a/codex-rs/tui/src/chatwidget/interaction.rs +++ b/codex-rs/tui/src/chatwidget/interaction.rs @@ -124,6 +124,22 @@ impl ChatWidget { return; } + if self.turn_lifecycle.agent_turn_running + && self + .chat_keymap + .flush_queued_messages + .is_pressed(key_event) + && self.bottom_pane.no_modal_or_popup_active() + { + if key_event.kind == KeyEventKind::Repeat { + return; + } + if key_event.kind == KeyEventKind::Press && self.has_queued_follow_up_messages() { + self.flush_queued_messages(); + return; + } + } + if self.chat_keymap.interrupt_turn.is_pressed(key_event) && !self.input_queue.pending_steers.is_empty() && self.bottom_pane.is_task_running() diff --git a/codex-rs/tui/src/chatwidget/tests/review_mode.rs b/codex-rs/tui/src/chatwidget/tests/review_mode.rs index c5873a50fd..12d2c869fe 100644 --- a/codex-rs/tui/src/chatwidget/tests/review_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/review_mode.rs @@ -721,6 +721,159 @@ async fn steer_enter_during_final_stream_preserves_follow_up_prompts_in_order() assert!(lines_to_single_string(&second_insert[0]).contains("second follow-up")); } +#[tokio::test] +async fn shift_enter_flushes_queued_messages_as_one_steer_during_final_stream() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.thread_id = Some(ThreadId::new()); + chat.on_task_started(); + chat.on_agent_message_delta("Final answer line\n".to_string()); + + chat.bottom_pane + .set_composer_text("first queued".to_string(), Vec::new(), Vec::new()); + chat.handle_key_event(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)); + chat.bottom_pane + .set_composer_text("second queued".to_string(), Vec::new(), Vec::new()); + chat.handle_key_event(KeyEvent::new(KeyCode::Tab, KeyModifiers::NONE)); + chat.bottom_pane + .set_composer_text("still editing".to_string(), Vec::new(), Vec::new()); + + chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)); + + let expected_text = "first queued\nsecond queued"; + match next_submit_op(&mut op_rx) { + Op::UserTurn { items, .. } => assert_eq!( + items, + vec![UserInput::Text { + text: expected_text.to_string(), + text_elements: Vec::new(), + }] + ), + other => panic!("expected one merged queued-message steer, got {other:?}"), + } + assert_no_submit_op(&mut op_rx); + assert!(chat.input_queue.rejected_steers_queue.is_empty()); + assert!(chat.input_queue.queued_user_messages.is_empty()); + assert_eq!(chat.input_queue.pending_steers.len(), 1); + assert_eq!( + chat.input_queue + .pending_steers + .front() + .unwrap() + .user_message + .text, + expected_text + ); + assert_eq!(chat.bottom_pane.composer_text(), "still editing"); + assert!(drain_insert_history(&mut rx).is_empty()); +} + +#[tokio::test] +async fn shift_enter_flushes_rejected_before_queued_and_preserves_existing_pending_steers() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.thread_id = Some(ThreadId::new()); + chat.on_task_started(); + chat.input_queue.pending_steers.push_back(PendingSteer { + user_message: UserMessage::from("already pending"), + history_record: UserMessageHistoryRecord::UserMessageText, + compare_key: PendingSteerCompareKey { + message: "already pending".to_string(), + image_count: 0, + }, + }); + chat.input_queue + .rejected_steers_queue + .push_back(UserMessage::from("rejected first")); + chat.input_queue + .queued_user_messages + .push_back(UserMessage::from("queued second").into()); + + chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)); + + match next_submit_op(&mut op_rx) { + Op::UserTurn { items, .. } => assert_eq!( + items, + vec![UserInput::Text { + text: "rejected first\nqueued second".to_string(), + text_elements: Vec::new(), + }] + ), + other => panic!("expected one merged queued-message steer, got {other:?}"), + } + assert_no_submit_op(&mut op_rx); + assert!(chat.input_queue.rejected_steers_queue.is_empty()); + assert!( + chat.input_queue + .rejected_steer_history_records + .is_empty() + ); + assert!(chat.input_queue.queued_user_messages.is_empty()); + assert!( + chat.input_queue + .queued_user_message_history_records + .is_empty() + ); + assert_eq!( + chat.input_queue + .pending_steers + .iter() + .map(|pending| pending.user_message.text.as_str()) + .collect::>(), + vec!["already pending", "rejected first\nqueued second"] + ); +} + +#[tokio::test] +async fn shift_enter_repeat_and_release_do_not_flush_queued_messages() { + let (mut chat, _rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; + chat.thread_id = Some(ThreadId::new()); + chat.on_task_started(); + chat.input_queue + .queued_user_messages + .push_back(UserMessage::from("queued").into()); + chat.bottom_pane + .set_composer_text("still editing".to_string(), Vec::new(), Vec::new()); + + for kind in [KeyEventKind::Repeat, KeyEventKind::Release] { + chat.handle_key_event(KeyEvent::new_with_kind( + KeyCode::Enter, + KeyModifiers::SHIFT, + kind, + )); + } + + assert_no_submit_op(&mut op_rx); + assert_eq!(chat.input_queue.queued_user_messages.len(), 1); + assert!(chat.input_queue.pending_steers.is_empty()); + assert_eq!(chat.bottom_pane.composer_text(), "still editing"); +} + +#[tokio::test] +async fn shift_enter_without_queued_messages_still_inserts_newline() { + let (mut active_chat, _active_rx, mut active_op_rx) = + make_chatwidget_manual(/*model_override*/ None).await; + active_chat.thread_id = Some(ThreadId::new()); + active_chat.on_task_started(); + active_chat + .bottom_pane + .set_composer_text("active".to_string(), Vec::new(), Vec::new()); + + active_chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)); + + assert_no_submit_op(&mut active_op_rx); + assert_eq!(active_chat.bottom_pane.composer_text(), "active\n"); + + let (mut idle_chat, _idle_rx, mut idle_op_rx) = + make_chatwidget_manual(/*model_override*/ None).await; + idle_chat + .bottom_pane + .set_composer_text("idle".to_string(), Vec::new(), Vec::new()); + + idle_chat.handle_key_event(KeyEvent::new(KeyCode::Enter, KeyModifiers::SHIFT)); + + assert_no_submit_op(&mut idle_op_rx); + assert_eq!(idle_chat.bottom_pane.composer_text(), "idle\n"); +} + #[tokio::test] async fn manual_interrupt_restores_pending_steers_to_composer() { let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(/*model_override*/ None).await; diff --git a/codex-rs/tui/src/keymap.rs b/codex-rs/tui/src/keymap.rs index efd1be3adb..cdb1f52f54 100644 --- a/codex-rs/tui/src/keymap.rs +++ b/codex-rs/tui/src/keymap.rs @@ -84,6 +84,8 @@ pub(crate) struct AppKeymap { pub(crate) struct ChatKeymap { /// Interrupt the active turn. pub(crate) interrupt_turn: Vec, + /// Flush all queued follow-up messages into the active turn as one steer. + pub(crate) flush_queued_messages: Vec, /// Decrease the active reasoning effort. pub(crate) decrease_reasoning_effort: Vec, /// Increase the active reasoning effort. @@ -437,6 +439,11 @@ impl RuntimeKeymap { &defaults.chat.interrupt_turn, "tui.keymap.chat.interrupt_turn", )?, + flush_queued_messages: resolve_bindings( + keymap.chat.flush_queued_messages.as_ref(), + &defaults.chat.flush_queued_messages, + "tui.keymap.chat.flush_queued_messages", + )?, decrease_reasoning_effort: resolve_bindings( keymap.chat.decrease_reasoning_effort.as_ref(), &defaults.chat.decrease_reasoning_effort, @@ -931,6 +938,7 @@ impl RuntimeKeymap { }, chat: ChatKeymap { interrupt_turn: default_bindings![plain(KeyCode::Esc)], + flush_queued_messages: default_bindings![shift(KeyCode::Enter)], decrease_reasoning_effort: default_bindings![ alt(KeyCode::Char(',')), shift(KeyCode::Down) @@ -1189,6 +1197,10 @@ impl RuntimeKeymap { ("toggle_raw_output", self.app.toggle_raw_output.as_slice()), ("cycle_permissions", self.app.cycle_permissions.as_slice()), ("chat.interrupt_turn", self.chat.interrupt_turn.as_slice()), + ( + "chat.flush_queued_messages", + self.chat.flush_queued_messages.as_slice(), + ), ( "chat.decrease_reasoning_effort", self.chat.decrease_reasoning_effort.as_slice(), @@ -1233,6 +1245,10 @@ impl RuntimeKeymap { ("toggle_raw_output", self.app.toggle_raw_output.as_slice()), ("cycle_permissions", self.app.cycle_permissions.as_slice()), ("chat.interrupt_turn", self.chat.interrupt_turn.as_slice()), + ( + "chat.flush_queued_messages", + self.chat.flush_queued_messages.as_slice(), + ), ( "chat.decrease_reasoning_effort", self.chat.decrease_reasoning_effort.as_slice(), @@ -1344,6 +1360,10 @@ impl RuntimeKeymap { ("copy", self.app.copy.as_slice()), ("clear_terminal", self.app.clear_terminal.as_slice()), ("chat.interrupt_turn", self.chat.interrupt_turn.as_slice()), + ( + "chat.flush_queued_messages", + self.chat.flush_queued_messages.as_slice(), + ), ( "chat.decrease_reasoning_effort", self.chat.decrease_reasoning_effort.as_slice(), @@ -1411,11 +1431,18 @@ impl RuntimeKeymap { ("editor.kill_line_end", self.editor.kill_line_end.as_slice()), ("editor.yank", self.editor.yank.as_slice()), ], - [( - "composer.submit", - "editor.insert_newline", - key_hint::plain(KeyCode::Enter), - )], + [ + ( + "composer.submit", + "editor.insert_newline", + key_hint::plain(KeyCode::Enter), + ), + ( + "chat.flush_queued_messages", + "editor.insert_newline", + key_hint::shift(KeyCode::Enter), + ), + ], )?; validate_unique( @@ -2109,6 +2136,16 @@ mod tests { assert!(err.contains("editor.insert_newline")); } + #[test] + fn rejects_non_default_flush_queued_messages_editor_overlap() { + let mut keymap = TuiKeymap::default(); + keymap.chat.flush_queued_messages = Some(one("alt-enter")); + + let err = RuntimeKeymap::from_config(&keymap).expect_err("expected shadowing conflict"); + assert!(err.contains("chat.flush_queued_messages")); + assert!(err.contains("editor.insert_newline")); + } + #[test] fn rejects_shadowing_editor_binding_from_outer_main_handler() { let mut keymap = TuiKeymap::default(); @@ -2885,6 +2922,23 @@ mod tests { ); } + #[test] + fn default_flush_queued_messages_uses_allowed_shift_enter_overlap() { + let runtime = + RuntimeKeymap::from_config(&TuiKeymap::default()).expect("default keymap should parse"); + + assert_eq!( + runtime.chat.flush_queued_messages, + vec![key_hint::shift(KeyCode::Enter)] + ); + assert!( + runtime + .editor + .insert_newline + .contains(&key_hint::shift(KeyCode::Enter)) + ); + } + #[test] fn default_editor_delete_forward_word_includes_alt_d() { let runtime = RuntimeKeymap::defaults(); diff --git a/codex-rs/tui/src/keymap_setup.rs b/codex-rs/tui/src/keymap_setup.rs index 08e982f1b3..1e029d0b52 100644 --- a/codex-rs/tui/src/keymap_setup.rs +++ b/codex-rs/tui/src/keymap_setup.rs @@ -1015,6 +1015,7 @@ mod tests { vec![ "Composer.submit", "Chat.interrupt_turn", + "Chat.flush_queued_messages", "Editor.insert_newline", "Composer.queue", "Global.cycle_permissions", diff --git a/codex-rs/tui/src/keymap_setup/actions.rs b/codex-rs/tui/src/keymap_setup/actions.rs index 703e1f40c7..4887394bf6 100644 --- a/codex-rs/tui/src/keymap_setup/actions.rs +++ b/codex-rs/tui/src/keymap_setup/actions.rs @@ -95,6 +95,7 @@ pub(super) const KEYMAP_ACTIONS: &[KeymapActionDescriptor] = &[ action("global", "Global", "toggle_raw_output", "Toggle raw scrollback mode."), action("global", "Global", "cycle_permissions", "Cycle to the next permissions preset."), action("chat", "Chat", "interrupt_turn", "Interrupt the active turn."), + action("chat", "Chat", "flush_queued_messages", "Flush queued follow-ups into the active turn as one steer."), action("chat", "Chat", "decrease_reasoning_effort", "Decrease reasoning effort."), action("chat", "Chat", "increase_reasoning_effort", "Increase reasoning effort."), action("chat", "Chat", "edit_queued_message", "Edit the most recently queued message."), @@ -239,6 +240,7 @@ pub(super) fn binding_slot<'a>( ("global", "toggle_raw_output") => Some(&mut keymap.global.toggle_raw_output), ("global", "cycle_permissions") => Some(&mut keymap.global.cycle_permissions), ("chat", "interrupt_turn") => Some(&mut keymap.chat.interrupt_turn), + ("chat", "flush_queued_messages") => Some(&mut keymap.chat.flush_queued_messages), ("chat", "decrease_reasoning_effort") => Some(&mut keymap.chat.decrease_reasoning_effort), ("chat", "increase_reasoning_effort") => Some(&mut keymap.chat.increase_reasoning_effort), ("chat", "edit_queued_message") => Some(&mut keymap.chat.edit_queued_message), @@ -365,6 +367,7 @@ pub(super) fn bindings_for_action<'a>( ("global", "toggle_raw_output") => Some(runtime_keymap.app.toggle_raw_output.as_slice()), ("global", "cycle_permissions") => Some(runtime_keymap.app.cycle_permissions.as_slice()), ("chat", "interrupt_turn") => Some(runtime_keymap.chat.interrupt_turn.as_slice()), + ("chat", "flush_queued_messages") => Some(runtime_keymap.chat.flush_queued_messages.as_slice()), ("chat", "decrease_reasoning_effort") => Some(runtime_keymap.chat.decrease_reasoning_effort.as_slice()), ("chat", "increase_reasoning_effort") => Some(runtime_keymap.chat.increase_reasoning_effort.as_slice()), ("chat", "edit_queued_message") => Some(runtime_keymap.chat.edit_queued_message.as_slice()), diff --git a/codex-rs/tui/src/keymap_setup/picker.rs b/codex-rs/tui/src/keymap_setup/picker.rs index 1b28aabcaa..a917ccc664 100644 --- a/codex-rs/tui/src/keymap_setup/picker.rs +++ b/codex-rs/tui/src/keymap_setup/picker.rs @@ -61,6 +61,7 @@ struct KeymapContextTab { const KEYMAP_COMMON_ACTIONS: &[(&str, &str)] = &[ ("composer", "submit"), ("chat", "interrupt_turn"), + ("chat", "flush_queued_messages"), ("editor", "insert_newline"), ("composer", "queue"), ("global", "toggle_fast_mode"), diff --git a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_all_tab_search.snap b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_all_tab_search.snap index f778419bd3..272ba2438f 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_all_tab_search.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_all_tab_search.snap @@ -10,7 +10,7 @@ Toggle Vim Mode | unbound | Global toggle_vim_mode Toggle Vim Mode Turn Vim comp Toggle Raw Output | alt-r | Global toggle_raw_output Toggle Raw Output Toggle raw scrollback mode. alt-r Default Cycle Permissions | shift-tab | Global cycle_permissions Cycle Permissions Cycle to the next permissions preset. shift-tab Default Interrupt Turn | esc | Chat interrupt_turn Interrupt Turn Interrupt the active turn. esc Default +Flush Queued Messages | shift-enter | Chat flush_queued_messages Flush Queued Messages Flush queued follow-ups into the active turn as one steer. shift-enter Default Decrease Reasoning Effort | alt-,, shift-down | Chat decrease_reasoning_effort Decrease Reasoning Effort Decrease reasoning effort. alt-,, shift-down Default Increase Reasoning Effort | alt-., shift-up | Chat increase_reasoning_effort Increase Reasoning Effort Increase reasoning effort. alt-., shift-up Default Edit Queued Message | alt-up, shift-left | Chat edit_queued_message Edit Queued Message Edit the most recently queued message. alt-up, shift-left Default -Submit | enter | Composer submit Submit Submit the current composer draft. enter Default diff --git a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_custom.snap b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_custom.snap index 2fcf05a296..2276f85373 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_custom.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_custom.snap @@ -5,7 +5,7 @@ expression: "render_picker(params, 120)" Keymap All configurable shortcuts. - 109 actions, 1 customized, 2 unbound. + 110 actions, 1 customized, 2 unbound. [All] Common Customized (1) Unbound (2) App Composer Editor Vim Navigation Approval Debug diff --git a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_fast_mode_enabled.snap b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_fast_mode_enabled.snap index f60d917496..c9963b82d9 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_fast_mode_enabled.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_fast_mode_enabled.snap @@ -5,7 +5,7 @@ expression: "render_picker(params, 120)" Keymap All configurable shortcuts. - 110 actions, 0 customized, 3 unbound. + 111 actions, 0 customized, 3 unbound. [All] Common Customized (0) Unbound (3) App Composer Editor Vim Navigation Approval Debug diff --git a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_first_actions.snap b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_first_actions.snap index 6b4f0889ef..9f71a40139 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_first_actions.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_first_actions.snap @@ -2,11 +2,11 @@ source: tui/src/keymap_setup.rs expression: snapshot --- -tab: All (109 selectable) -tab: Common (21 selectable) +tab: All (110 selectable) +tab: Common (22 selectable) tab: Customized (0) (0 selectable) tab: Unbound (2) (2 selectable) -tab: App (11 selectable) +tab: App (12 selectable) tab: Composer (5 selectable) tab: Editor (17 selectable) tab: Vim (48 selectable) @@ -21,7 +21,7 @@ Toggle Vim Mode | unbound | Global toggle_vim_mode Toggle Vim Mode Turn Vim comp Toggle Raw Output | alt-r | Global toggle_raw_output Toggle Raw Output Toggle raw scrollback mode. alt-r Default Cycle Permissions | shift-tab | Global cycle_permissions Cycle Permissions Cycle to the next permissions preset. shift-tab Default Interrupt Turn | esc | Chat interrupt_turn Interrupt Turn Interrupt the active turn. esc Default +Flush Queued Messages | shift-enter | Chat flush_queued_messages Flush Queued Messages Flush queued follow-ups into the active turn as one steer. shift-enter Default Decrease Reasoning Effort | alt-,, shift-down | Chat decrease_reasoning_effort Decrease Reasoning Effort Decrease reasoning effort. alt-,, shift-down Default Increase Reasoning Effort | alt-., shift-up | Chat increase_reasoning_effort Increase Reasoning Effort Increase reasoning effort. alt-., shift-up Default Edit Queued Message | alt-up, shift-left | Chat edit_queued_message Edit Queued Message Edit the most recently queued message. alt-up, shift-left Default -Submit | enter | Composer submit Submit Submit the current composer draft. enter Default diff --git a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_narrow.snap b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_narrow.snap index cbaf5f080f..ba175b97d6 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_narrow.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_narrow.snap @@ -5,7 +5,7 @@ expression: "render_picker(params, 78)" Keymap All configurable shortcuts. - 109 actions, 0 customized, 2 unbound. + 110 actions, 0 customized, 2 unbound. [All] Common Customized (0) Unbound (2) App Composer Editor Vim Navigation Approval Debug diff --git a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_wide.snap b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_wide.snap index ab67c48b73..fc6f656026 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_wide.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__keymap_setup__tests__keymap_picker_wide.snap @@ -5,7 +5,7 @@ expression: "render_picker(params, 120)" Keymap All configurable shortcuts. - 109 actions, 0 customized, 2 unbound. + 110 actions, 0 customized, 2 unbound. [All] Common Customized (0) Unbound (2) App Composer Editor Vim Navigation Approval Debug