diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index aaad1be0c2..17b0942d9d 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -18,7 +18,7 @@ use std::path::PathBuf; use crate::app_event::ConnectorsSnapshot; use crate::app_event_sender::AppEventSender; use crate::bottom_pane::pending_thread_approvals::PendingThreadApprovals; -use crate::bottom_pane::queued_user_messages::QueuedUserMessages; +use crate::bottom_pane::pending_input_preview::PendingInputPreview; use crate::bottom_pane::unified_exec_footer::UnifiedExecFooter; use crate::key_hint; use crate::key_hint::KeyBinding; @@ -94,8 +94,8 @@ pub(crate) use status_line_setup::StatusLineItem; pub(crate) use status_line_setup::StatusLineSetupView; mod paste_burst; mod pending_thread_approvals; +mod pending_input_preview; pub mod popup_consts; -mod queued_user_messages; mod scroll_state; mod selection_popup_common; mod textarea; @@ -172,8 +172,8 @@ pub(crate) struct BottomPane { /// When a status row exists, this summary is mirrored inline in that row; /// when no status row exists, it renders as its own footer row. unified_exec_footer: UnifiedExecFooter, - /// Queued user messages to show above the composer while a turn is running. - queued_user_messages: QueuedUserMessages, + /// Preview of pending nudges and queued drafts shown above the composer. + pending_input_preview: PendingInputPreview, /// Inactive threads with pending approval requests. pending_thread_approvals: PendingThreadApprovals, context_window_percent: Option, @@ -223,7 +223,7 @@ impl BottomPane { is_task_running: false, status: None, unified_exec_footer: UnifiedExecFooter::new(), - queued_user_messages: QueuedUserMessages::new(), + pending_input_preview: PendingInputPreview::new(), pending_thread_approvals: PendingThreadApprovals::new(), esc_backtrack_hint: false, animations_enabled, @@ -312,7 +312,7 @@ impl BottomPane { /// Update the key hint shown next to queued messages so it matches the /// binding that `ChatWidget` actually listens for. pub(crate) fn set_queued_message_edit_binding(&mut self, binding: KeyBinding) { - self.queued_user_messages.set_edit_binding(binding); + self.pending_input_preview.set_edit_binding(binding); self.request_redraw(); } @@ -769,14 +769,14 @@ impl BottomPane { true } - /// Update the queued messages preview shown above the composer. - pub(crate) fn set_queued_user_messages( + /// Update the pending-input preview shown above the composer. + pub(crate) fn set_pending_input_preview( &mut self, queued: Vec, pending_nudges: Vec, ) { - self.queued_user_messages.pending_nudges = pending_nudges; - self.queued_user_messages.messages = queued; + self.pending_input_preview.pending_nudges = pending_nudges; + self.pending_input_preview.messages = queued; self.request_redraw(); } @@ -1019,19 +1019,19 @@ impl BottomPane { flex.push(0, RenderableItem::Borrowed(&self.unified_exec_footer)); } let has_pending_thread_approvals = !self.pending_thread_approvals.is_empty(); - let has_queued_messages = !self.queued_user_messages.messages.is_empty() - || !self.queued_user_messages.pending_nudges.is_empty(); + let has_pending_input = !self.pending_input_preview.messages.is_empty() + || !self.pending_input_preview.pending_nudges.is_empty(); let has_status_or_footer = self.status.is_some() || !self.unified_exec_footer.is_empty(); - let has_inline_previews = has_pending_thread_approvals || has_queued_messages; + let has_inline_previews = has_pending_thread_approvals || has_pending_input; if has_inline_previews && has_status_or_footer { flex.push(0, RenderableItem::Owned("".into())); } flex.push(1, RenderableItem::Borrowed(&self.pending_thread_approvals)); - if has_pending_thread_approvals && has_queued_messages { + if has_pending_thread_approvals && has_pending_input { flex.push(0, RenderableItem::Owned("".into())); } - flex.push(1, RenderableItem::Borrowed(&self.queued_user_messages)); + flex.push(1, RenderableItem::Borrowed(&self.pending_input_preview)); if !has_inline_previews && has_status_or_footer { flex.push(0, RenderableItem::Owned("".into())); } @@ -1407,7 +1407,7 @@ mod tests { StatusDetailsCapitalization::CapitalizeFirst, STATUS_DETAILS_DEFAULT_MAX_LINES, ); - pane.set_queued_user_messages(vec!["Queued follow-up question".to_string()], Vec::new()); + pane.set_pending_input_preview(vec!["Queued follow-up question".to_string()], Vec::new()); let width = 48; let height = pane.desired_height(width); @@ -1434,7 +1434,7 @@ mod tests { }); pane.set_task_running(true); - pane.set_queued_user_messages(vec!["Queued follow-up question".to_string()], Vec::new()); + pane.set_pending_input_preview(vec!["Queued follow-up question".to_string()], Vec::new()); pane.hide_status_indicator(); let width = 48; @@ -1462,7 +1462,7 @@ mod tests { }); pane.set_task_running(true); - pane.set_queued_user_messages(vec!["Queued follow-up question".to_string()], Vec::new()); + pane.set_pending_input_preview(vec!["Queued follow-up question".to_string()], Vec::new()); let width = 48; let height = pane.desired_height(width); diff --git a/codex-rs/tui/src/bottom_pane/queued_user_messages.rs b/codex-rs/tui/src/bottom_pane/pending_input_preview.rs similarity index 93% rename from codex-rs/tui/src/bottom_pane/queued_user_messages.rs rename to codex-rs/tui/src/bottom_pane/pending_input_preview.rs index 450b27dbdb..5f1d76aaaa 100644 --- a/codex-rs/tui/src/bottom_pane/queued_user_messages.rs +++ b/codex-rs/tui/src/bottom_pane/pending_input_preview.rs @@ -17,7 +17,7 @@ use crate::wrapping::adaptive_wrap_lines; /// queued user messages to pop back into the composer. Because some terminals /// intercept certain modifier-key combinations, the displayed binding is /// configurable via [`set_edit_binding`](Self::set_edit_binding). -pub(crate) struct QueuedUserMessages { +pub(crate) struct PendingInputPreview { pub pending_nudges: Vec, pub messages: Vec, /// Key combination rendered in the hint line. Defaults to Alt+Up but may @@ -25,7 +25,7 @@ pub(crate) struct QueuedUserMessages { edit_binding: key_hint::KeyBinding, } -impl QueuedUserMessages { +impl PendingInputPreview { pub(crate) fn new() -> Self { Self { pending_nudges: Vec::new(), @@ -97,7 +97,7 @@ impl QueuedUserMessages { } } -impl Renderable for QueuedUserMessages { +impl Renderable for PendingInputPreview { fn render(&self, area: Rect, buf: &mut Buffer) { if area.is_empty() { return; @@ -119,20 +119,20 @@ mod tests { #[test] fn desired_height_empty() { - let queue = QueuedUserMessages::new(); + let queue = PendingInputPreview::new(); assert_eq!(queue.desired_height(40), 0); } #[test] fn desired_height_one_message() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue.messages.push("Hello, world!".to_string()); assert_eq!(queue.desired_height(40), 2); } #[test] fn render_one_message() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue.messages.push("Hello, world!".to_string()); let width = 40; let height = queue.desired_height(width); @@ -143,7 +143,7 @@ mod tests { #[test] fn render_two_messages() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue.messages.push("Hello, world!".to_string()); queue.messages.push("This is another message".to_string()); let width = 40; @@ -155,7 +155,7 @@ mod tests { #[test] fn render_more_than_three_messages() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue.messages.push("Hello, world!".to_string()); queue.messages.push("This is another message".to_string()); queue.messages.push("This is a third message".to_string()); @@ -169,7 +169,7 @@ mod tests { #[test] fn render_wrapped_message() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue .messages .push("This is a longer message that should be wrapped".to_string()); @@ -183,7 +183,7 @@ mod tests { #[test] fn render_many_line_message() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue .messages .push("This is\na message\nwith many\nlines".to_string()); @@ -196,7 +196,7 @@ mod tests { #[test] fn long_url_like_message_does_not_expand_into_wrapped_ellipsis_rows() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue.messages.push( "example.test/api/v1/projects/alpha-team/releases/2026-02-17/builds/1234567890/artifacts/reports/performance/summary/detail/session_id=abc123def456ghi789" .to_string(), @@ -228,7 +228,7 @@ mod tests { #[test] fn render_one_pending_nudge() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue.pending_nudges.push("Implement the plan.".to_string()); let width = 48; let height = queue.desired_height(width); @@ -239,7 +239,7 @@ mod tests { #[test] fn render_pending_nudges_above_queued_messages() { - let mut queue = QueuedUserMessages::new(); + let mut queue = PendingInputPreview::new(); queue.pending_nudges.push("Implement the plan.".to_string()); queue .pending_nudges diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__queued_user_messages__tests__render_one_pending_nudge.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__pending_input_preview__tests__render_one_pending_nudge.snap similarity index 100% rename from codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__queued_user_messages__tests__render_one_pending_nudge.snap rename to codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__pending_input_preview__tests__render_one_pending_nudge.snap diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__queued_user_messages__tests__render_pending_nudges_above_queued_messages.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__pending_input_preview__tests__render_pending_nudges_above_queued_messages.snap similarity index 100% rename from codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__queued_user_messages__tests__render_pending_nudges_above_queued_messages.snap rename to codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__pending_input_preview__tests__render_pending_nudges_above_queued_messages.snap diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 02575c5476..1d9db6f076 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -1901,7 +1901,7 @@ impl ChatWidget { self.maybe_send_pending_nudges(); } else if let Some(combined) = self.drain_queued_messages_for_restore() { self.restore_user_message_to_composer(combined); - self.refresh_queued_user_messages(); + self.refresh_pending_input_preview(); } self.request_redraw(); @@ -3435,7 +3435,7 @@ impl ChatWidget { { if let Some(user_message) = self.queued_user_messages.pop_back() { self.restore_user_message_to_composer(user_message); - self.refresh_queued_user_messages(); + self.refresh_pending_input_preview(); self.request_redraw(); } return; @@ -3479,7 +3479,7 @@ impl ChatWidget { && (self.is_plan_streaming_in_tui() || self.stream_controller.is_some()); if should_defer_as_pending_nudge { self.pending_nudges.push_back(user_message); - self.refresh_queued_user_messages(); + self.refresh_pending_input_preview(); } else if self.is_session_configured() { // Submitted is only emitted when steer is enabled. // Reset any reasoning header only when we are actually submitting a turn. @@ -4096,7 +4096,7 @@ impl ChatWidget { || self.is_review_mode { self.queued_user_messages.push_back(user_message); - self.refresh_queued_user_messages(); + self.refresh_pending_input_preview(); } else { self.submit_user_message(user_message); } @@ -4106,7 +4106,7 @@ impl ChatWidget { if !self.is_session_configured() { tracing::warn!("cannot submit user message before session is configured; queueing"); self.queued_user_messages.push_front(user_message); - self.refresh_queued_user_messages(); + self.refresh_pending_input_preview(); return; } @@ -4760,7 +4760,7 @@ impl ChatWidget { self.submit_user_message(user_message); } // Update the list to reflect the remaining queued messages (if any). - self.refresh_queued_user_messages(); + self.refresh_pending_input_preview(); } fn maybe_send_pending_nudges(&mut self) -> bool { @@ -4781,12 +4781,12 @@ impl ChatWidget { while let Some(pending_nudge) = self.pending_nudges.pop_front() { self.submit_user_message(pending_nudge); } - self.refresh_queued_user_messages(); + self.refresh_pending_input_preview(); true } - /// Rebuild and update the queued user messages from the current queue. - fn refresh_queued_user_messages(&mut self) { + /// Rebuild and update the bottom-pane pending-input preview. + fn refresh_pending_input_preview(&mut self) { let messages: Vec = self .queued_user_messages .iter() @@ -4798,7 +4798,7 @@ impl ChatWidget { .map(|nudge| nudge.text.clone()) .collect(); self.bottom_pane - .set_queued_user_messages(messages, pending_nudges); + .set_pending_input_preview(messages, pending_nudges); } pub(crate) fn set_pending_thread_approvals(&mut self, threads: Vec) { diff --git a/codex-rs/tui/src/chatwidget/tests.rs b/codex-rs/tui/src/chatwidget/tests.rs index 843190958d..5c02c23f4b 100644 --- a/codex-rs/tui/src/chatwidget/tests.rs +++ b/codex-rs/tui/src/chatwidget/tests.rs @@ -1169,7 +1169,7 @@ async fn interrupted_turn_restores_queued_messages_with_images_and_elements() { text_elements: second_elements, mention_bindings: Vec::new(), }); - chat.refresh_queued_user_messages(); + chat.refresh_pending_input_preview(); chat.bottom_pane .set_composer_text(existing_text, existing_elements, existing_images.clone()); @@ -1239,7 +1239,7 @@ async fn interrupted_turn_restore_keeps_active_mode_for_resubmission() { text_elements: Vec::new(), mention_bindings: Vec::new(), }); - chat.refresh_queued_user_messages(); + chat.refresh_pending_input_preview(); chat.handle_codex_event(Event { id: "interrupt".into(), @@ -3168,7 +3168,7 @@ async fn alt_up_edits_most_recent_queued_message() { .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.refresh_pending_input_preview(); // Press Alt+Up to edit the most recent (last) queued message. chat.handle_key_event(KeyEvent::new(KeyCode::Up, KeyModifiers::ALT)); @@ -3202,7 +3202,7 @@ async fn assert_shift_left_edits_most_recent_queued_message_for_terminal( .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.refresh_pending_input_preview(); // Press Shift+Left to edit the most recent (last) queued message. chat.handle_key_event(KeyEvent::new(KeyCode::Left, KeyModifiers::SHIFT)); @@ -7088,7 +7088,7 @@ async fn interrupt_restores_queued_messages_into_composer() { .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.refresh_pending_input_preview(); // Deliver a TurnAborted event with Interrupted reason (as if Esc was pressed). chat.handle_codex_event(Event { @@ -7128,7 +7128,7 @@ async fn interrupt_prepends_queued_messages_before_existing_composer_text() { .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.refresh_pending_input_preview(); chat.handle_codex_event(Event { id: "turn-1".into(),