From 4f6acbf02dac15297db5e3da232d55679664b412 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Wed, 4 Nov 2026 18:46:01 +0000 Subject: [PATCH] Preserve transcript interaction when switching panes with arrow keys (#59472) ## Why Directional pane switching cleared the parent conversation's transcript selection or Find state, so returning to it interrupted the interaction. ## What changed Route left/right pane switches through the existing conversation toggle to park and restore each pane's transcript interaction. Explicitly focusing the main composer still clears transcript interaction. ## Testing Extend the side-conversation test to cover restoring selections, Find text and caret position, and composer input after scrolling. Add a snapshot verifying that Find remains editable after switching away and back. GitOrigin-RevId: 172f9be0df15517fbfe7f3773b63cbd1cef1cc77 --- codex-rs/tui/src/app/side_focus.rs | 10 +++-- codex-rs/tui/src/app/tests.rs | 37 +++++++++++++++++-- ...tests__direction_restores_parent_find.snap | 5 +++ 3 files changed, 46 insertions(+), 6 deletions(-) create mode 100644 codex-rs/tui/src/snapshots/codex_tui__app__tests__direction_restores_parent_find.snap diff --git a/codex-rs/tui/src/app/side_focus.rs b/codex-rs/tui/src/app/side_focus.rs index ffaeae3455..800e8ce8fd 100644 --- a/codex-rs/tui/src/app/side_focus.rs +++ b/codex-rs/tui/src/app/side_focus.rs @@ -40,12 +40,16 @@ impl App { && !on_child && (self.transcript_view.has_active_interaction() || self.backtrack.overlay_preview_active); - let result = if main || (left && on_child) { + let result = if main { self.focus_main_composer(tui, app_server).await - } else if (sidebar || right) + } else if (left && on_child) + || (right && !on_child && child.is_some_and(|id| self.side_threads[&id].pane_open)) + { + // Switching threads parks and restores each pane's transcript interaction. + self.toggle_side_conversation(tui, app_server).await + } else if sidebar && !on_child && let Some(child) = child - && (sidebar || self.side_threads[&child].pane_open) { let result = self.select_agent_thread(tui, app_server, child).await; if result.is_ok() && self.current_displayed_thread_id() == Some(child) { diff --git a/codex-rs/tui/src/app/tests.rs b/codex-rs/tui/src/app/tests.rs index e7c7b17878..64977b141e 100644 --- a/codex-rs/tui/src/app/tests.rs +++ b/codex-rs/tui/src/app/tests.rs @@ -3833,10 +3833,41 @@ async fn handle_start_side_seeds_navigation_before_thread_started() -> Result<() selected ); app.transcript_view.end_select_mode(); - // Ctrl-/ preserves interaction, so it exposes whether right actually cleaned up the parent. - keys!(toggle); + keys!(prefix, KeyCode::Left.into()); assert_eq!(app.current_displayed_thread_id(), Some(parent_thread_id)); - assert!(!app.transcript_view.has_active_interaction()); + assert!(app.transcript_view.is_select_mode()); + assert_eq!( + app.transcript_view.selected_text(&app.transcript_cells), + parent_text + ); + + keys!(prefix, main); + app.transcript_view.begin_search(); + assert!(app.transcript_view.paste_search(parent_row)); + keys!(KeyCode::Left.into()); + let find = app.transcript_view.search_footer(size.width).unwrap(); + keys!(prefix, KeyCode::Right.into()); + keys!(prefix, KeyCode::Left.into()); + assert_eq!(app.transcript_view.search_footer(size.width), Some(find)); + keys!(KeyCode::Char('x').into()); + let frame = side_frame(&mut app, &mut tui, size)?; + assert_app_snapshot!( + "direction_restores_parent_find", + frame.lines().find(|row| row.contains("Find:")).unwrap() + ); + + // Explicit composer focus ends Find. Scrolling alone keeps the composer active. + keys!(prefix, main); + read_side_history(&mut app, &mut tui, "parent")?; + keys!(prefix, KeyCode::Right.into()); + keys!(prefix, KeyCode::Left.into()); + assert!(side_frame(&mut app, &mut tui, size)?.contains(parent_row)); + // End flushes the composer's pending paste-burst character. + keys!(KeyCode::Char('!').into(), KeyCode::End.into()); + assert_eq!( + app.chat_widget.composer_text_with_pending(), + "parent draft-!" + ); keys!(toggle); read_side_history(&mut app, &mut tui, "child")?; diff --git a/codex-rs/tui/src/snapshots/codex_tui__app__tests__direction_restores_parent_find.snap b/codex-rs/tui/src/snapshots/codex_tui__app__tests__direction_restores_parent_find.snap new file mode 100644 index 0000000000..0bfa40e558 --- /dev/null +++ b/codex-rs/tui/src/snapshots/codex_tui__app__tests__direction_restores_parent_find.snap @@ -0,0 +1,5 @@ +--- +source: tui/src/app/tests.rs +expression: "frame.lines().find(|row| row.contains(\"Find:\")).unwrap()" +--- + Find: parent-2x4 │ GPT-6.1-Sol default