From 021eb877868b82d433849ec9ba0854b281fa39a3 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Wed, 6 May 2026 22:02:24 -0300 Subject: [PATCH 1/4] fix(tui): clear first inline viewport render --- codex-rs/tui/src/tui.rs | 66 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 64 insertions(+), 2 deletions(-) diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index 24f6e6f9461b..d1b51047c6df 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -3,6 +3,7 @@ use std::future::Future; use std::io::IsTerminal; use std::io::Result; use std::io::Stdout; +use std::io::Write; use std::io::stdin; use std::io::stdout; use std::panic; @@ -76,8 +77,15 @@ fn should_emit_notification(condition: NotificationCondition, terminal_focused: #[cfg(test)] mod tests { + use std::io::Write as _; + + use super::clear_for_viewport_change; use super::should_emit_notification; + use crate::custom_terminal::Terminal as CustomTerminal; + use crate::test_backend::VT100Backend; use codex_config::types::NotificationCondition; + use ratatui::layout::Position; + use ratatui::layout::Rect; #[test] fn unfocused_notification_condition_is_suppressed_when_focused() { @@ -102,6 +110,44 @@ mod tests { /*terminal_focused*/ false )); } + + #[test] + fn first_viewport_change_clears_from_new_viewport_when_old_viewport_is_empty() { + let width = 12; + let height = 4; + let backend = VT100Backend::new(width, height); + let mut terminal = CustomTerminal::with_options_and_cursor_position( + backend, + Position { + x: 0, + y: 1, + }, + ) + .expect("terminal"); + write!(terminal.backend_mut(), "shell line\r\nstale cells\r\nmore stale") + .expect("prefill terminal"); + + clear_for_viewport_change( + &mut terminal, + Rect::new(/*x*/ 0, /*y*/ 1, /*width*/ width, /*height*/ height - 1), + ) + .expect("clear transition"); + + let rows: Vec = terminal + .backend() + .vt100() + .screen() + .rows(/*start*/ 0, width) + .collect(); + assert!( + rows[0].contains("shell line"), + "expected content before the viewport to remain visible, rows: {rows:?}" + ); + assert!( + !rows.iter().skip(1).any(|row| row.contains("stale")), + "expected stale cells inside the new viewport to be cleared, rows: {rows:?}" + ); + } } pub fn set_modes() -> Result<()> { @@ -391,6 +437,21 @@ struct PendingHistoryLines { wrap_policy: HistoryLineWrapPolicy, } +fn clear_for_viewport_change( + terminal: &mut CustomTerminal, + new_area: Rect, +) -> Result<()> +where + B: Backend + Write, +{ + let clear_position = if terminal.viewport_area.is_empty() { + new_area.as_position() + } else { + terminal.viewport_area.as_position() + }; + terminal.clear_after_position(clear_position) +} + impl Tui { pub fn new(terminal: Terminal) -> Self { let (draw_tx, _) = broadcast::channel(1); @@ -642,8 +703,9 @@ impl Tui { area.y = size.height - area.height; } if area != terminal.viewport_area { - // TODO(nornagon): probably this could be collapsed with the clear + set_viewport_area above. - terminal.clear()?; + // On startup, the old viewport can still be empty. Clear from the + // new viewport top so stale shell cells do not show through spaces. + clear_for_viewport_change(terminal, area)?; terminal.set_viewport_area(area); } From 510c60f92c0f7e853265b028274045d2a9b949f7 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Wed, 6 May 2026 22:18:00 -0300 Subject: [PATCH 2/4] codex: fix CI failure on PR #21450 --- codex-rs/tui/src/tui.rs | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/codex-rs/tui/src/tui.rs b/codex-rs/tui/src/tui.rs index d1b51047c6df..06417e31ea00 100644 --- a/codex-rs/tui/src/tui.rs +++ b/codex-rs/tui/src/tui.rs @@ -116,20 +116,23 @@ mod tests { let width = 12; let height = 4; let backend = VT100Backend::new(width, height); - let mut terminal = CustomTerminal::with_options_and_cursor_position( - backend, - Position { - x: 0, - y: 1, - }, + let mut terminal = + CustomTerminal::with_options_and_cursor_position(backend, Position { x: 0, y: 1 }) + .expect("terminal"); + write!( + terminal.backend_mut(), + "shell line\r\nstale cells\r\nmore stale" ) - .expect("terminal"); - write!(terminal.backend_mut(), "shell line\r\nstale cells\r\nmore stale") - .expect("prefill terminal"); + .expect("prefill terminal"); clear_for_viewport_change( &mut terminal, - Rect::new(/*x*/ 0, /*y*/ 1, /*width*/ width, /*height*/ height - 1), + Rect::new( + /*x*/ 0, + /*y*/ 1, + /*width*/ width, + /*height*/ height - 1, + ), ) .expect("clear transition"); @@ -437,10 +440,7 @@ struct PendingHistoryLines { wrap_policy: HistoryLineWrapPolicy, } -fn clear_for_viewport_change( - terminal: &mut CustomTerminal, - new_area: Rect, -) -> Result<()> +fn clear_for_viewport_change(terminal: &mut CustomTerminal, new_area: Rect) -> Result<()> where B: Backend + Write, { From 554598aefe7db2569a4f1c940d261ab72ad5c09d Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Wed, 6 May 2026 22:28:04 -0300 Subject: [PATCH 3/4] fix(tui): restore add-to-history command --- codex-rs/tui/src/app/thread_routing.rs | 4 ++++ codex-rs/tui/src/app_command.rs | 7 +++++++ 2 files changed, 11 insertions(+) diff --git a/codex-rs/tui/src/app/thread_routing.rs b/codex-rs/tui/src/app/thread_routing.rs index 1a7986ad31de..b8d580a0b486 100644 --- a/codex-rs/tui/src/app/thread_routing.rs +++ b/codex-rs/tui/src/app/thread_routing.rs @@ -682,6 +682,10 @@ impl App { .await?; Ok(true) } + AppCommand::AddToHistory { text } => { + self.append_message_history_entry(thread_id, text.to_string()); + Ok(true) + } AppCommand::ReloadUserConfig => { app_server.reload_user_config().await?; self.refresh_in_memory_config_from_disk().await?; diff --git a/codex-rs/tui/src/app_command.rs b/codex-rs/tui/src/app_command.rs index 89fd2600f8ac..a38ffce08beb 100644 --- a/codex-rs/tui/src/app_command.rs +++ b/codex-rs/tui/src/app_command.rs @@ -92,6 +92,9 @@ pub(crate) enum AppCommand { cwds: Vec, force_reload: bool, }, + AddToHistory { + text: String, + }, Compact, SetThreadName { name: String, @@ -247,6 +250,10 @@ impl AppCommand { Self::ListSkills { cwds, force_reload } } + pub(crate) fn add_to_history(text: String) -> Self { + Self::AddToHistory { text } + } + pub(crate) fn compact() -> Self { Self::Compact } From 151abdee381a245e0810e4662876c39bc79714b6 Mon Sep 17 00:00:00 2001 From: Felipe Coury Date: Wed, 6 May 2026 22:39:26 -0300 Subject: [PATCH 4/4] fix(tui): remove unused add-to-history command --- codex-rs/tui/src/app/thread_routing.rs | 4 ---- codex-rs/tui/src/app_command.rs | 7 ------- 2 files changed, 11 deletions(-) diff --git a/codex-rs/tui/src/app/thread_routing.rs b/codex-rs/tui/src/app/thread_routing.rs index b8d580a0b486..1a7986ad31de 100644 --- a/codex-rs/tui/src/app/thread_routing.rs +++ b/codex-rs/tui/src/app/thread_routing.rs @@ -682,10 +682,6 @@ impl App { .await?; Ok(true) } - AppCommand::AddToHistory { text } => { - self.append_message_history_entry(thread_id, text.to_string()); - Ok(true) - } AppCommand::ReloadUserConfig => { app_server.reload_user_config().await?; self.refresh_in_memory_config_from_disk().await?; diff --git a/codex-rs/tui/src/app_command.rs b/codex-rs/tui/src/app_command.rs index a38ffce08beb..89fd2600f8ac 100644 --- a/codex-rs/tui/src/app_command.rs +++ b/codex-rs/tui/src/app_command.rs @@ -92,9 +92,6 @@ pub(crate) enum AppCommand { cwds: Vec, force_reload: bool, }, - AddToHistory { - text: String, - }, Compact, SetThreadName { name: String, @@ -250,10 +247,6 @@ impl AppCommand { Self::ListSkills { cwds, force_reload } } - pub(crate) fn add_to_history(text: String) -> Self { - Self::AddToHistory { text } - } - pub(crate) fn compact() -> Self { Self::Compact }