From f3f35526a8056e28a99ef57136ef548301eee9d2 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Fri, 6 Feb 2026 23:41:08 -0800 Subject: [PATCH] Show left/right arrows to navigate in tui request_user_input (#10921) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Screenshot 2026-02-06 at 10 25 13 AM Screenshot 2026-02-06 at 10 26 37 AM "left/right to navigate questions" in request_user_input footer --- .../src/bottom_pane/request_user_input/mod.rs | 115 +++++++++++++++++- ...tests__request_user_input_footer_wrap.snap | 3 +- ...quest_user_input_multi_question_first.snap | 3 +- ...equest_user_input_multi_question_last.snap | 3 +- 4 files changed, 117 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs index 954277244..a00aedb0f 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs +++ b/codex-rs/tui/src/bottom_pane/request_user_input/mod.rs @@ -443,10 +443,10 @@ impl RequestUserInputOverlay { }; tips.push(enter_tip); if question_count > 1 { - if is_last_question { - tips.push(FooterTip::new("ctrl + n first question")); - } else { - tips.push(FooterTip::new("ctrl + n next question")); + if self.has_options() && !self.focus_is_notes() { + tips.push(FooterTip::new("←/→ to navigate questions")); + } else if !self.has_options() { + tips.push(FooterTip::new("ctrl + p / ctrl + n change question")); } } if !(self.has_options() && notes_visible) { @@ -1042,6 +1042,14 @@ impl BottomPaneView for RequestUserInputOverlay { self.move_question(false); return; } + KeyEvent { + code: KeyCode::Left, + modifiers: KeyModifiers::NONE, + .. + } if self.has_options() && matches!(self.focus, Focus::Options) => { + self.move_question(false); + return; + } KeyEvent { code: KeyCode::Char('l'), modifiers: KeyModifiers::NONE, @@ -1050,6 +1058,14 @@ impl BottomPaneView for RequestUserInputOverlay { self.move_question(true); return; } + KeyEvent { + code: KeyCode::Right, + modifiers: KeyModifiers::NONE, + .. + } if self.has_options() && matches!(self.focus, Focus::Options) => { + self.move_question(true); + return; + } _ => {} } @@ -1643,6 +1659,97 @@ mod tests { assert_eq!(overlay.current_index(), 0); } + #[test] + fn left_right_move_between_questions_in_options() { + let (tx, _rx) = test_sender(); + let mut overlay = RequestUserInputOverlay::new( + request_event( + "turn-1", + vec![ + question_with_options("q1", "Pick one"), + question_with_options("q2", "Pick two"), + ], + ), + tx, + true, + false, + false, + ); + + assert_eq!(overlay.current_index(), 0); + overlay.handle_key_event(KeyEvent::from(KeyCode::Right)); + assert_eq!(overlay.current_index(), 1); + overlay.handle_key_event(KeyEvent::from(KeyCode::Left)); + assert_eq!(overlay.current_index(), 0); + } + + #[test] + fn options_notes_focus_hides_question_navigation_tip() { + let (tx, _rx) = test_sender(); + let mut overlay = RequestUserInputOverlay::new( + request_event( + "turn-1", + vec![ + question_with_options("q1", "Pick one"), + question_with_options("q2", "Pick two"), + ], + ), + tx, + true, + false, + false, + ); + let tips = overlay.footer_tips(); + let tip_texts = tips.iter().map(|tip| tip.text.as_str()).collect::>(); + assert_eq!( + tip_texts, + vec![ + "tab to add notes", + "enter to submit answer", + "←/→ to navigate questions", + "esc to interrupt", + ] + ); + + overlay.handle_key_event(KeyEvent::from(KeyCode::Tab)); + let tips = overlay.footer_tips(); + let tip_texts = tips.iter().map(|tip| tip.text.as_str()).collect::>(); + assert_eq!( + tip_texts, + vec!["tab or esc to clear notes", "enter to submit answer",] + ); + } + + #[test] + fn freeform_shows_ctrl_p_and_ctrl_n_question_navigation_tip() { + let (tx, _rx) = test_sender(); + let mut overlay = RequestUserInputOverlay::new( + request_event( + "turn-1", + vec![ + question_with_options("q1", "Area"), + question_without_options("q2", "Goal"), + ], + ), + tx, + true, + false, + false, + ); + overlay.move_question(true); + + let tips = overlay.footer_tips(); + let tip_texts = tips.iter().map(|tip| tip.text.as_str()).collect::>(); + assert_eq!( + tip_texts, + vec![ + "enter to submit all", + "ctrl + p / ctrl + n change question", + "esc to interrupt", + ] + ); + } + #[test] fn tab_opens_notes_when_option_selected() { let (tx, _rx) = test_sender(); diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_footer_wrap.snap b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_footer_wrap.snap index 3fd719464..872bfe1d0 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_footer_wrap.snap +++ b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_footer_wrap.snap @@ -1,5 +1,6 @@ --- source: tui/src/bottom_pane/request_user_input/mod.rs +assertion_line: 2600 expression: "render_snapshot(&overlay, area)" --- @@ -11,4 +12,4 @@ expression: "render_snapshot(&overlay, area)" 3. Option 3 Third choice. tab to add notes | enter to submit answer - ctrl + n next question | esc to interrupt + ←/→ to navigate questions | esc to interrupt diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_first.snap b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_first.snap index 536e34dbb..bb1c2a726 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_first.snap +++ b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_first.snap @@ -1,5 +1,6 @@ --- source: tui/src/bottom_pane/request_user_input/mod.rs +assertion_line: 2744 expression: "render_snapshot(&overlay, area)" --- @@ -10,4 +11,4 @@ expression: "render_snapshot(&overlay, area)" 2. Option 2 Second choice. 3. Option 3 Third choice. - tab to add notes | enter to submit answer | ctrl + n next question | esc to interrupt + tab to add notes | enter to submit answer | ←/→ to navigate questions | esc to interrupt diff --git a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_last.snap b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_last.snap index 95507c335..dbe06d404 100644 --- a/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_last.snap +++ b/codex-rs/tui/src/bottom_pane/request_user_input/snapshots/codex_tui__bottom_pane__request_user_input__tests__request_user_input_multi_question_last.snap @@ -1,5 +1,6 @@ --- source: tui/src/bottom_pane/request_user_input/mod.rs +assertion_line: 2770 expression: "render_snapshot(&overlay, area)" --- @@ -12,4 +13,4 @@ expression: "render_snapshot(&overlay, area)" - enter to submit all | ctrl + n first question | esc to interrupt + enter to submit all | ctrl + p / ctrl + n change question | esc to interrupt