diff --git a/codex-rs/tui/src/bottom_pane/chat_composer.rs b/codex-rs/tui/src/bottom_pane/chat_composer.rs index ec1b00c938..ece1fc8ed2 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer.rs @@ -556,19 +556,6 @@ pub(crate) struct ComposerDraftSnapshot { const FOOTER_SPACING_HEIGHT: u16 = 0; -/// Builds the one-line nudge that replaces the ambient footer without adding layout height. -fn plan_mode_nudge_line() -> Line<'static> { - Line::from(vec![ - "Create a plan?".magenta(), - " ".into(), - key_hint::shift(KeyCode::Tab).into(), - " use Plan mode".into(), - " ".into(), - key_hint::plain(KeyCode::Esc).into(), - " dismiss".into(), - ]) -} - impl ChatComposer { fn slash_input(&self) -> SlashInput<'_> { SlashInput::new( @@ -639,7 +626,6 @@ impl ChatComposer { use_shift_enter_hint, mode: FooterMode::ComposerEmpty, hint_override: None, - plan_mode_nudge_visible: false, flash: None, context_window_percent: None, context_window_used_tokens: None, @@ -746,7 +732,6 @@ impl ChatComposer { self.effort_ignition = Some(EffortIgnition::new(tier, style)); self.effort_animation_style = Some(style); if self.footer.status_line_enabled - && !self.footer.plan_mode_nudge_visible && let Some(previous) = passive_footer_status_line(&self.footer_props()) { self.effort_status_line_transition = @@ -1440,23 +1425,6 @@ impl ChatComposer { self.footer.hint_override = items; } - /// Updates whether the Plan-mode nudge replaces the ambient footer row. - /// - /// Returns `true` only when the rendered footer can change so callers can avoid scheduling - /// redundant redraws while reevaluating nudge policy on routine composer updates. - pub(crate) fn set_plan_mode_nudge_visible(&mut self, visible: bool) -> bool { - if self.footer.plan_mode_nudge_visible == visible { - return false; - } - self.footer.plan_mode_nudge_visible = visible; - true - } - - #[cfg(test)] - pub(crate) fn plan_mode_nudge_visible(&self) -> bool { - self.footer.plan_mode_nudge_visible - } - pub(crate) fn set_remote_image_urls(&mut self, urls: Vec) { self.attachments .set_remote_image_urls(urls, &mut self.draft.textarea); @@ -4275,7 +4243,6 @@ impl ChatComposer { self.footer.quit_shortcut_expires_at = None; self.footer.mode = FooterMode::ComposerEmpty; self.footer.hint_override = Some(Vec::new()); - self.footer.plan_mode_nudge_visible = false; self.footer.flash = None; } @@ -4596,17 +4563,6 @@ impl ChatComposer { }; if let Some(line) = self.history_search_footer_line() { render_footer_line(hint_rect, buf, line); - } else if self.footer.plan_mode_nudge_visible { - let available_width = - hint_rect.width.saturating_sub(FOOTER_INDENT_COLS as u16) as usize; - render_footer_line( - hint_rect, - buf, - truncate_line_with_ellipsis_if_overflow( - plan_mode_nudge_line(), - available_width, - ), - ); } else { let available_width = hint_rect.width.saturating_sub(FOOTER_INDENT_COLS as u16) as usize; diff --git a/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs b/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs index fd63d2d000..09ceb5fc68 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer/footer_state.rs @@ -19,7 +19,6 @@ pub(super) struct FooterState { pub(super) use_shift_enter_hint: bool, pub(super) mode: FooterMode, pub(super) hint_override: Option>, - pub(super) plan_mode_nudge_visible: bool, pub(super) flash: Option, pub(super) context_window_percent: Option, pub(super) context_window_used_tokens: Option, diff --git a/codex-rs/tui/src/bottom_pane/chat_composer_effort_tests.rs b/codex-rs/tui/src/bottom_pane/chat_composer_effort_tests.rs index 437edf879f..aaae8c2d4f 100644 --- a/codex-rs/tui/src/bottom_pane/chat_composer_effort_tests.rs +++ b/codex-rs/tui/src/bottom_pane/chat_composer_effort_tests.rs @@ -59,25 +59,6 @@ fn effort_transition_does_not_queue_a_missing_outgoing_status_line() { assert!(composer.effort_status_line_transition.is_none()); } -#[test] -fn effort_transition_does_not_queue_while_plan_mode_nudge_is_visible() { - let (mut composer, _rx) = new_test_composer(); - composer.set_status_line_enabled(/*enabled*/ true); - composer.set_status_line(Some(Line::from("gpt-5.4 high · main"))); - composer.set_plan_mode_nudge_visible(/*visible*/ true); - composer.set_active_reasoning_effort( - Some(&ReasoningEffort::High), - /*animations_enabled*/ true, - ); - - assert!(composer.set_active_reasoning_effort( - Some(&ReasoningEffort::Ultra), - /*animations_enabled*/ true, - )); - assert!(composer.effort_ignition.is_some()); - assert!(composer.effort_status_line_transition.is_none()); -} - #[test] fn effort_composer_restored_baseline_and_reduced_motion_do_not_start() { let (mut composer, _rx) = new_test_composer(); diff --git a/codex-rs/tui/src/bottom_pane/mod.rs b/codex-rs/tui/src/bottom_pane/mod.rs index d2869815ac..a9a10b39c7 100644 --- a/codex-rs/tui/src/bottom_pane/mod.rs +++ b/codex-rs/tui/src/bottom_pane/mod.rs @@ -933,18 +933,6 @@ impl BottomPane { self.request_redraw(); } - /// Applies the externally decided Plan-mode nudge visibility to the footer presentation. - pub(crate) fn set_plan_mode_nudge_visible(&mut self, visible: bool) { - if self.composer.set_plan_mode_nudge_visible(visible) { - self.request_redraw(); - } - } - - #[cfg(test)] - pub(crate) fn plan_mode_nudge_visible(&self) -> bool { - self.composer.plan_mode_nudge_visible() - } - pub(crate) fn set_remote_image_urls(&mut self, urls: Vec) { self.composer.set_remote_image_urls(urls); self.request_redraw(); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 9dda0936c8..f31b9f9b29 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -667,11 +667,6 @@ pub(crate) struct ChatWidget { #[cfg(test)] pet_image_support_override: Option, thread_id: Option, - /// Nudge dismissals that should survive draft edits within the current thread scope. - /// - /// The nudge is only a discovery aid, so once a user dismisses it or enters Plan mode we keep it - /// hidden for that thread instead of resurfacing it on every matching draft. - dismissed_plan_mode_nudge_scopes: HashSet, thread_name: Option, thread_rename_block_message: Option, active_side_conversation: bool, @@ -835,31 +830,12 @@ enum SessionConfiguredDisplay { SideConversation, } -/// Scope used to keep Plan-mode nudge dismissal local to one conversation context. -#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)] -enum PlanModeNudgeScope { - /// Drafts entered before the server has assigned a thread id. - NewThread, - /// Drafts associated with one configured thread. - Thread(ThreadId), -} - #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub(crate) enum TurnAbortReason { Interrupted, BudgetLimited, } -/// Returns whether `text` contains the standalone word `plan`. -/// -/// This intentionally mirrors the App suggestion heuristic instead of trying to infer broader -/// planning intent from substrings such as `planning`. Slash and shell drafts still match here so -/// callers can keep lexical matching separate from presentation policy. -fn contains_plan_keyword(text: &str) -> bool { - text.split(|ch: char| !ch.is_alphanumeric() && ch != '_') - .any(|word| word.eq_ignore_ascii_case("plan")) -} - #[derive(Clone, Copy, Debug, Eq, PartialEq)] enum ThreadItemRenderSource { Live, @@ -1224,7 +1200,6 @@ impl ChatWidget { if let Some(pet) = self.ambient_pet.as_ref() { pet.schedule_next_frame(); } - self.refresh_plan_mode_nudge(); self.refresh_goal_status_indicator_for_time_tick(); if self.terminal_title_shows_action_required() != self.last_terminal_title_requires_action { self.refresh_terminal_title(); diff --git a/codex-rs/tui/src/chatwidget/constructor.rs b/codex-rs/tui/src/chatwidget/constructor.rs index 6cbac52b19..cb4f301b5d 100644 --- a/codex-rs/tui/src/chatwidget/constructor.rs +++ b/codex-rs/tui/src/chatwidget/constructor.rs @@ -198,7 +198,6 @@ impl ChatWidget { #[cfg(test)] pet_image_support_override: None, thread_id: None, - dismissed_plan_mode_nudge_scopes: HashSet::new(), thread_name: None, thread_rename_block_message: None, active_side_conversation: false, diff --git a/codex-rs/tui/src/chatwidget/input_flow.rs b/codex-rs/tui/src/chatwidget/input_flow.rs index 74ce8d2aec..5daab24c48 100644 --- a/codex-rs/tui/src/chatwidget/input_flow.rs +++ b/codex-rs/tui/src/chatwidget/input_flow.rs @@ -78,7 +78,6 @@ impl ChatWidget { if had_modal_or_popup && self.bottom_pane.no_modal_or_popup_active() { self.maybe_send_next_queued_input(); } - self.refresh_plan_mode_nudge(); } pub(super) fn defer_input_until_settings_applied(&mut self) { diff --git a/codex-rs/tui/src/chatwidget/interaction.rs b/codex-rs/tui/src/chatwidget/interaction.rs index 72680be29d..9206ccf693 100644 --- a/codex-rs/tui/src/chatwidget/interaction.rs +++ b/codex-rs/tui/src/chatwidget/interaction.rs @@ -153,14 +153,6 @@ impl ChatWidget { return; } - if matches!(key_event.code, KeyCode::Esc) - && key_event.kind == KeyEventKind::Press - && self.should_show_plan_mode_nudge() - { - self.dismiss_plan_mode_nudge(); - return; - } - if self.handle_plugins_popup_key_event(key_event) { return; } @@ -178,7 +170,6 @@ impl ChatWidget { self.add_error_message(PARENT_OWNED_INPUT_MESSAGE.to_string()); } else { self.cycle_collaboration_mode(); - self.refresh_plan_mode_nudge(); } } _ => { @@ -220,7 +211,6 @@ impl ChatWidget { pub(crate) fn apply_external_edit(&mut self, text: String) { self.bottom_pane.apply_external_edit(text); - self.refresh_plan_mode_nudge(); self.request_redraw(); } @@ -238,13 +228,11 @@ impl ChatWidget { pub(crate) fn show_selection_view(&mut self, params: SelectionViewParams) { self.bottom_pane.show_selection_view(params); - self.refresh_plan_mode_nudge(); self.request_redraw(); } pub(crate) fn show_bottom_pane_view(&mut self, view: Box) { self.bottom_pane.show_view(view); - self.refresh_plan_mode_nudge(); self.request_redraw(); } @@ -254,7 +242,6 @@ impl ChatWidget { view: Box, ) { self.bottom_pane.replace_view_if_present(view_id, view); - self.refresh_plan_mode_nudge(); } pub(crate) fn selected_index_for_present_view(&self, view_id: &'static str) -> Option { @@ -266,13 +253,8 @@ impl ChatWidget { view_id: &'static str, params: SelectionViewParams, ) -> bool { - let replaced = self - .bottom_pane - .replace_selection_view_if_present(view_id, params); - if replaced { - self.refresh_plan_mode_nudge(); - } - replaced + self.bottom_pane + .replace_selection_view_if_present(view_id, params) } pub(crate) fn no_modal_or_popup_active(&self) -> bool { @@ -465,13 +447,11 @@ impl ChatWidget { pub(crate) fn handle_paste(&mut self, text: String) { self.bottom_pane.handle_paste(text); - self.refresh_plan_mode_nudge(); } // Returns true if caller should skip rendering this frame (a future frame is scheduled). pub(crate) fn handle_paste_burst_tick(&mut self, frame_requester: FrameRequester) -> bool { if self.bottom_pane.flush_paste_burst_if_due() { - self.refresh_plan_mode_nudge(); // A paste just flushed; request an immediate redraw and skip this frame. self.request_redraw(); true diff --git a/codex-rs/tui/src/chatwidget/session_flow.rs b/codex-rs/tui/src/chatwidget/session_flow.rs index a3a7b95958..cbb91fef86 100644 --- a/codex-rs/tui/src/chatwidget/session_flow.rs +++ b/codex-rs/tui/src/chatwidget/session_flow.rs @@ -28,7 +28,6 @@ impl ChatWidget { self.review.recent_auto_review_denials = RecentAutoReviewDenials::default(); self.clear_thread_usage_state(); } - self.refresh_plan_mode_nudge(); self.turn_lifecycle.reset_thread(); self.clear_safety_buffering(); self.thread_name = session.thread_name.clone(); @@ -106,7 +105,6 @@ impl ChatWidget { mask.reasoning_effort = Some(session.reasoning_effort.clone()); } self.update_collaboration_mode_indicator(); - self.refresh_plan_mode_nudge(); } } let effort = self.effective_reasoning_effort(); diff --git a/codex-rs/tui/src/chatwidget/settings.rs b/codex-rs/tui/src/chatwidget/settings.rs index 8ef38ec0e0..9ab73e1145 100644 --- a/codex-rs/tui/src/chatwidget/settings.rs +++ b/codex-rs/tui/src/chatwidget/settings.rs @@ -384,48 +384,6 @@ impl ChatWidget { true } - /// Returns the dismissal scope that applies to the currently visible draft. - fn plan_mode_nudge_scope(&self) -> PlanModeNudgeScope { - self.thread_id - .map_or(PlanModeNudgeScope::NewThread, PlanModeNudgeScope::Thread) - } - - /// Returns whether the current draft should replace the normal footer with the Plan-mode nudge. - /// - /// `ChatWidget` owns this policy because it can combine lexical draft matching with mode - /// availability, interaction state, and thread-scoped dismissal. `ChatComposer` only renders - /// the resulting visibility bit. Keeping slash and shell drafts out here avoids advertising a - /// mode switch while the user is intentionally composing another local command. - pub(super) fn should_show_plan_mode_nudge(&self) -> bool { - let text = self.bottom_pane.composer_text(); - let trimmed = text.trim_start(); - self.collaboration_modes_enabled() - && collaboration_modes::plan_mask(self.model_catalog.as_ref()).is_some() - && self.active_mode_kind() != ModeKind::Plan - && self.bottom_pane.composer_input_enabled() - && !self.bottom_pane.is_task_running() - && self.bottom_pane.no_modal_or_popup_active() - && !trimmed.starts_with('/') - && !trimmed.starts_with('!') - && contains_plan_keyword(&text) - && !self - .dismissed_plan_mode_nudge_scopes - .contains(&self.plan_mode_nudge_scope()) - } - - /// Synchronizes the footer presentation with the current Plan-mode nudge policy. - pub(super) fn refresh_plan_mode_nudge(&mut self) { - self.bottom_pane - .set_plan_mode_nudge_visible(self.should_show_plan_mode_nudge()); - } - - /// Hides the nudge for the current thread scope until the user changes conversation context. - pub(super) fn dismiss_plan_mode_nudge(&mut self) { - self.dismissed_plan_mode_nudge_scopes - .insert(self.plan_mode_nudge_scope()); - self.refresh_plan_mode_nudge(); - } - pub(super) fn initial_collaboration_mask( _config: &Config, model_catalog: &ModelCatalog, @@ -580,7 +538,6 @@ impl ChatWidget { developer_instructions: Some(settings.developer_instructions), }); self.update_collaboration_mode_indicator(); - self.refresh_plan_mode_nudge(); self.refresh_model_dependent_surfaces(); } @@ -702,13 +659,8 @@ impl ChatWidget { { mask.reasoning_effort = Some(Some(effort)); } - if mask.mode == Some(ModeKind::Plan) { - self.dismissed_plan_mode_nudge_scopes - .insert(self.plan_mode_nudge_scope()); - } self.active_collaboration_mask = Some(mask); self.update_collaboration_mode_indicator(); - self.refresh_plan_mode_nudge(); self.refresh_model_dependent_surfaces(); let next_mode = self.active_mode_kind(); let next_model = self.current_model(); diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_mode_nudge.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_draft_footer.snap similarity index 66% rename from codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_mode_nudge.snap rename to codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_draft_footer.snap index d3df6e6261..e44d160442 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_mode_nudge.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_draft_footer.snap @@ -4,4 +4,4 @@ expression: "render_bottom_popup(&chat, 80)" --- › make a plan - Create a plan? shift + tab use Plan mode esc dismiss + gpt-5 default · /tmp/project diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_mode_nudge_narrow.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_draft_footer_narrow.snap similarity index 74% rename from codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_mode_nudge_narrow.snap rename to codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_draft_footer_narrow.snap index eca4819e3d..0c6f1ff3dd 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_mode_nudge_narrow.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__plan_draft_footer_narrow.snap @@ -4,4 +4,4 @@ expression: "render_bottom_popup(&chat, 36)" --- › make a plan - Create a plan? shift + tab use P… + gpt-5 default · /tmp/project diff --git a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs index 9babdd3dab..d17818fe09 100644 --- a/codex-rs/tui/src/chatwidget/tests/plan_mode.rs +++ b/codex-rs/tui/src/chatwidget/tests/plan_mode.rs @@ -1,12 +1,6 @@ use super::*; use pretty_assertions::assert_eq; -fn set_composer_text(chat: &mut ChatWidget, text: &str) { - chat.bottom_pane - .set_composer_text(text.to_string(), Vec::new(), Vec::new()); - chat.refresh_plan_mode_nudge(); -} - fn paste_hidden_plan_shell_payload(chat: &mut ChatWidget) -> String { let payload = format!("!echo {}", "x".repeat(1000)); chat.bottom_pane @@ -68,125 +62,32 @@ fn assert_literal_plan_prompt(chat: &ChatWidget, op: Result, p assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Plan); } -#[test] -fn plan_mode_nudge_matches_only_standalone_plain_text_keyword() { - assert!(contains_plan_keyword("plan")); - assert!(contains_plan_keyword("Make a Plan first.")); - assert!(!contains_plan_keyword("plane")); - assert!(!contains_plan_keyword("planning")); - assert!(contains_plan_keyword("/plan")); - assert!(contains_plan_keyword("!plan")); -} - #[tokio::test] -async fn plan_mode_nudge_shows_only_for_eligible_default_mode_drafts() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; - set_composer_text(&mut chat, "make a plan"); - chat.pre_draw_tick(); - assert!(chat.bottom_pane.plan_mode_nudge_visible()); - - set_composer_text(&mut chat, "/plan"); - chat.pre_draw_tick(); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); - - set_composer_text(&mut chat, "!plan"); - chat.pre_draw_tick(); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); - - set_composer_text(&mut chat, "make a plan"); - let plan_mask = collaboration_modes::plan_mask(chat.model_catalog.as_ref()) - .expect("expected plan collaboration mode"); - chat.set_collaboration_mask(plan_mask); - chat.pre_draw_tick(); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); -} - -#[tokio::test] -async fn plan_mode_nudge_hides_while_task_or_modal_is_active() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; - set_composer_text(&mut chat, "make a plan"); - chat.pre_draw_tick(); - assert!(chat.bottom_pane.plan_mode_nudge_visible()); - - chat.on_task_started(); - chat.pre_draw_tick(); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); - - chat.on_task_complete( - /*last_agent_message*/ None, /*duration_ms*/ None, /*from_replay*/ false, - ); - chat.show_selection_view(SelectionViewParams { - items: vec![SelectionItem { - name: "Keep planning".to_string(), - ..Default::default() - }], - ..Default::default() - }); - chat.pre_draw_tick(); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); -} - -#[tokio::test] -async fn plan_mode_nudge_dismissal_is_scoped_to_current_thread() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; - let first_thread = ThreadId::new(); - let second_thread = ThreadId::new(); - chat.thread_id = Some(first_thread); - set_composer_text(&mut chat, "make a plan"); - chat.pre_draw_tick(); - assert!(chat.bottom_pane.plan_mode_nudge_visible()); - - chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)); - chat.pre_draw_tick(); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); - - chat.thread_id = Some(second_thread); - chat.pre_draw_tick(); - assert!(chat.bottom_pane.plan_mode_nudge_visible()); - - chat.handle_key_event(KeyEvent::new(KeyCode::Esc, KeyModifiers::NONE)); - chat.pre_draw_tick(); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); - - chat.thread_id = Some(first_thread); - chat.pre_draw_tick(); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); -} - -#[tokio::test] -async fn plan_mode_nudge_shift_tab_uses_existing_mode_cycle_path() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; - set_composer_text(&mut chat, "make a plan"); - chat.pre_draw_tick(); - assert!(chat.bottom_pane.plan_mode_nudge_visible()); - - chat.handle_key_event(KeyEvent::from(KeyCode::BackTab)); - chat.pre_draw_tick(); - assert_eq!(chat.active_collaboration_mode_kind(), ModeKind::Plan); - assert!(!chat.bottom_pane.plan_mode_nudge_visible()); -} - -#[tokio::test] -async fn plan_mode_nudge_snapshot() { +async fn plan_draft_footer_snapshot() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; chat.set_token_info(Some(make_token_info( /*total_tokens*/ 50_000, /*context_window*/ 100_000, ))); - set_composer_text(&mut chat, "make a plan"); - chat.pre_draw_tick(); - - assert_chatwidget_snapshot!("plan_mode_nudge", render_bottom_popup(&chat, /*width*/ 80)); -} - -#[tokio::test] -async fn plan_mode_nudge_narrow_snapshot() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; - set_composer_text(&mut chat, "make a plan"); + chat.bottom_pane + .set_composer_text("make a plan".to_string(), Vec::new(), Vec::new()); chat.pre_draw_tick(); assert_chatwidget_snapshot!( - "plan_mode_nudge_narrow", - render_bottom_popup(&chat, /*width*/ 36) + "plan_draft_footer", + normalize_snapshot_paths(render_bottom_popup(&chat, /*width*/ 80)) + ); +} + +#[tokio::test] +async fn plan_draft_footer_narrow_snapshot() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5")).await; + chat.bottom_pane + .set_composer_text("make a plan".to_string(), Vec::new(), Vec::new()); + chat.pre_draw_tick(); + + assert_chatwidget_snapshot!( + "plan_draft_footer_narrow", + normalize_snapshot_paths(render_bottom_popup(&chat, /*width*/ 36)) ); } diff --git a/codex-rs/tui/src/chatwidget/turn_runtime.rs b/codex-rs/tui/src/chatwidget/turn_runtime.rs index 2864dc1dd5..305813a095 100644 --- a/codex-rs/tui/src/chatwidget/turn_runtime.rs +++ b/codex-rs/tui/src/chatwidget/turn_runtime.rs @@ -36,7 +36,6 @@ impl ChatWidget { || self.review.is_review_mode || self.mcp_startup_status.is_some(), ); - self.refresh_plan_mode_nudge(); self.refresh_status_surfaces(); }