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