diff --git a/codex-rs/tui/src/bottom_pane/chat_composer_history.rs b/codex-rs/tui/src/bottom_pane/chat_composer_history.rs index 93accfcd1a..04b745d1ff 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer_history.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer_history.rs @@ -55,11 +55,18 @@ impl ChatComposerHistory { /// Record a message submitted by the user in the current session so it can /// be recalled later. pub fn record_local_submission(&mut self, text: &str) { - if !text.is_empty() { - self.local_history.push(text.to_string()); - self.history_cursor = None; - self.last_history_text = None; + if text.is_empty() { + return; + } + + // Avoid inserting a duplicate if identical to the previous entry. + if self.local_history.last().is_some_and(|prev| prev == text) { + return; } + + self.local_history.push(text.to_string()); + self.history_cursor = None; + self.last_history_text = None; } /// Should Up/Down key presses be interpreted as history navigation given @@ -187,6 +194,29 @@ mod tests { use codex_core::protocol::Op; use std::sync::mpsc::channel; + #[test] + fn duplicate_submissions_are_not_recorded() { + let mut history = ChatComposerHistory::new(); + + // Empty submissions are ignored. + history.record_local_submission(""); + assert_eq!(history.local_history.len(), 0); + + // First entry is recorded. + history.record_local_submission("hello"); + assert_eq!(history.local_history.len(), 1); + assert_eq!(history.local_history.last().unwrap(), "hello"); + + // Identical consecutive entry is skipped. + history.record_local_submission("hello"); + assert_eq!(history.local_history.len(), 1); + + // Different entry is recorded. + history.record_local_submission("world"); + assert_eq!(history.local_history.len(), 2); + assert_eq!(history.local_history.last().unwrap(), "world"); + } + #[test] fn navigation_with_async_fetch() { let (tx, rx) = channel::();