diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index 65203b51..eb654815 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -264,7 +264,6 @@ impl ChatComposer { } /// Get the current composer text. - #[cfg(test)] pub(crate) fn current_text(&self) -> String { self.textarea.text().to_string() } diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index fc9f06c3..bd26581b 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -262,7 +262,6 @@ impl BottomPane { } /// Get the current composer text (for tests and programmatic checks). - #[cfg(test)] pub(crate) fn composer_text(&self) -> String { self.composer.current_text() } diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index ec749f4b..26d813a8 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -427,12 +427,20 @@ impl ChatWidget { // If any messages were queued during the task, restore them into the composer. if !self.queued_user_messages.is_empty() { - let combined = self + let queued_text = self .queued_user_messages .iter() .map(|m| m.text.clone()) .collect::>() .join("\n"); + let existing_text = self.bottom_pane.composer_text(); + let combined = if existing_text.is_empty() { + queued_text + } else if queued_text.is_empty() { + existing_text + } else { + format!("{queued_text}\n{existing_text}") + }; self.bottom_pane.set_composer_text(combined); // Clear the queue and update the status indicator list. self.queued_user_messages.clear(); diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 7805e07e..18bc515b 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1299,6 +1299,40 @@ fn interrupt_restores_queued_messages_into_composer() { let _ = drain_insert_history(&mut rx); } +#[test] +fn interrupt_prepends_queued_messages_before_existing_composer_text() { + let (mut chat, mut rx, mut op_rx) = make_chatwidget_manual(); + + chat.bottom_pane.set_task_running(true); + chat.bottom_pane + .set_composer_text("current draft".to_string()); + + chat.queued_user_messages + .push_back(UserMessage::from("first queued".to_string())); + chat.queued_user_messages + .push_back(UserMessage::from("second queued".to_string())); + chat.refresh_queued_user_messages(); + + chat.handle_codex_event(Event { + id: "turn-1".into(), + msg: EventMsg::TurnAborted(codex_core::protocol::TurnAbortedEvent { + reason: TurnAbortReason::Interrupted, + }), + }); + + assert_eq!( + chat.bottom_pane.composer_text(), + "first queued\nsecond queued\ncurrent draft" + ); + assert!(chat.queued_user_messages.is_empty()); + assert!( + op_rx.try_recv().is_err(), + "unexpected outbound op after interrupt" + ); + + let _ = drain_insert_history(&mut rx); +} + // Snapshot test: ChatWidget at very small heights (idle) // Ensures overall layout behaves when terminal height is extremely constrained. #[test]