From 786ae57bef4729130f4dc010b9e1f2eab948ffa9 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Mon, 6 Apr 2026 14:38:27 -0700 Subject: [PATCH 1/4] Settings to display thread title in statusline when user has renamed thread --- .../tui/src/bottom_pane/status_line_setup.rs | 34 ++++++- codex-rs/tui/src/chatwidget.rs | 1 + ...t__tests__renamed_thread_footer_title.snap | 9 ++ ...hread_footer_title_plan_mode_override.snap | 9 ++ .../tui/src/chatwidget/status_surfaces.rs | 4 + .../src/chatwidget/tests/status_and_layout.rs | 96 +++++++++++++++++++ 6 files changed, 152 insertions(+), 1 deletion(-) create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title.snap create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title_plan_mode_override.snap diff --git a/codex-rs/tui/src/bottom_pane/status_line_setup.rs b/codex-rs/tui/src/bottom_pane/status_line_setup.rs index cdda2d7ca4f1..053c25e53efb 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_setup.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_setup.rs @@ -14,7 +14,7 @@ //! - Git information (branch name) //! - Context usage (meter, window size) //! - Usage limits (5-hour, weekly) -//! - Session info (ID, tokens used) +//! - Session info (thread title, ID, tokens used) //! - Application version use ratatui::buffer::Buffer; @@ -98,6 +98,9 @@ pub(crate) enum StatusLineItem { /// Whether Fast mode is currently active. FastMode, + + /// Current thread title (if set by user). + ThreadTitle, } impl StatusLineItem { @@ -129,6 +132,7 @@ impl StatusLineItem { "Current session identifier (omitted until session starts)" } StatusLineItem::FastMode => "Whether Fast mode is currently active", + StatusLineItem::ThreadTitle => "Current thread title (omitted unless changed by user)", } } } @@ -149,6 +153,7 @@ const SELECTABLE_STATUS_LINE_ITEMS: &[StatusLineItem] = &[ StatusLineItem::TotalOutputTokens, StatusLineItem::SessionId, StatusLineItem::FastMode, + StatusLineItem::ThreadTitle, ]; /// Runtime values used to preview the current status-line selection. @@ -380,6 +385,33 @@ mod tests { ); } + #[test] + fn preview_includes_thread_title_in_left_sequence() { + let preview_data = StatusLinePreviewData::from_iter([ + (StatusLineItem::ModelName, "gpt-5".to_string()), + (StatusLineItem::ThreadTitle, "Roadmap cleanup".to_string()), + ]); + let items = vec![ + MultiSelectItem { + id: StatusLineItem::ModelName.to_string(), + name: String::new(), + description: None, + enabled: true, + }, + MultiSelectItem { + id: StatusLineItem::ThreadTitle.to_string(), + name: String::new(), + description: None, + enabled: true, + }, + ]; + + assert_eq!( + preview_data.line_for_items(&items), + Some(Line::from("gpt-5 · Roadmap cleanup")) + ); + } + #[test] fn setup_view_snapshot_uses_runtime_preview_values() { let (tx_raw, _rx) = unbounded_channel::(); diff --git a/codex-rs/tui/src/chatwidget.rs b/codex-rs/tui/src/chatwidget.rs index 74b203289239..67bf8331dd96 100644 --- a/codex-rs/tui/src/chatwidget.rs +++ b/codex-rs/tui/src/chatwidget.rs @@ -2151,6 +2151,7 @@ impl ChatWidget { } self.thread_name = event.thread_name; self.refresh_terminal_title(); + self.refresh_status_surfaces(); self.request_redraw(); } } diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title.snap new file mode 100644 index 000000000000..e024f534aee9 --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title.snap @@ -0,0 +1,9 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: terminal.backend() +--- +" " +" " +"› Ask Codex to do anything " +" " +" gpt-5.3-codex high · Roadmap cleanup " diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title_plan_mode_override.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title_plan_mode_override.snap new file mode 100644 index 000000000000..235b0521fb5c --- /dev/null +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title_plan_mode_override.snap @@ -0,0 +1,9 @@ +--- +source: tui/src/chatwidget/tests.rs +expression: terminal.backend() +--- +" " +" " +"› Ask Codex to do anything " +" " +" gpt-5.3-codex medium · Roadmap cleanup Plan mode (shift+tab to cycle) " diff --git a/codex-rs/tui/src/chatwidget/status_surfaces.rs b/codex-rs/tui/src/chatwidget/status_surfaces.rs index bd58fd4e8fc6..078d31617515 100644 --- a/codex-rs/tui/src/chatwidget/status_surfaces.rs +++ b/codex-rs/tui/src/chatwidget/status_surfaces.rs @@ -495,6 +495,10 @@ impl ChatWidget { "Fast off".to_string() }, ), + StatusLineItem::ThreadTitle => self.thread_name.as_ref().and_then(|name| { + let trimmed = name.trim(); + (!trimmed.is_empty()).then(|| trimmed.to_string()) + }), } } diff --git a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs index 50de7064cc5c..06c09353ef52 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -1198,6 +1198,102 @@ async fn status_line_model_with_reasoning_plan_mode_footer_snapshot() { ); } +#[tokio::test] +async fn renamed_thread_footer_title_snapshot() { + use ratatui::Terminal; + use ratatui::backend::TestBackend; + + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; + chat.show_welcome_banner = false; + chat.config.tui_status_line = Some(vec![ + "model-with-reasoning".to_string(), + "thread-title".to_string(), + ]); + chat.set_reasoning_effort(Some(ReasoningEffortConfig::High)); + chat.refresh_status_line(); + + let thread_id = ThreadId::new(); + chat.thread_id = Some(thread_id); + chat.handle_codex_event(Event { + id: "rename".to_string(), + msg: EventMsg::ThreadNameUpdated(codex_protocol::protocol::ThreadNameUpdatedEvent { + thread_id, + thread_name: Some("Roadmap cleanup".to_string()), + }), + }); + + let width = 80; + let height = chat.desired_height(width); + let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); + terminal + .draw(|f| chat.render(f.area(), f.buffer_mut())) + .expect("draw renamed-thread footer"); + assert_chatwidget_snapshot!( + "renamed_thread_footer_title", + normalized_backend_snapshot(terminal.backend()) + ); +} + +#[tokio::test] +async fn thread_title_status_item_stays_in_left_status_line() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; + chat.thread_name = Some("Roadmap cleanup".to_string()); + + let (left_line, right_line) = chat.status_line_lines_for_items(&[ + crate::bottom_pane::StatusLineItem::ModelName, + crate::bottom_pane::StatusLineItem::ThreadTitle, + ]); + + assert_eq!( + left_line, + Some(Line::from("gpt-5.3-codex · Roadmap cleanup")) + ); + assert_eq!(right_line, None); +} + +#[tokio::test] +async fn renamed_thread_footer_title_plan_mode_override_snapshot() { + use ratatui::Terminal; + use ratatui::backend::TestBackend; + + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; + chat.show_welcome_banner = false; + chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true); + chat.config.tui_status_line = Some(vec![ + "model-with-reasoning".to_string(), + "thread-title".to_string(), + ]); + chat.set_reasoning_effort(Some(ReasoningEffortConfig::High)); + chat.refresh_status_line(); + + let thread_id = ThreadId::new(); + chat.thread_id = Some(thread_id); + chat.handle_server_notification( + ServerNotification::ThreadNameUpdated( + codex_app_server_protocol::ThreadNameUpdatedNotification { + thread_id: thread_id.to_string(), + thread_name: Some("Roadmap cleanup".to_string()), + }, + ), + /*replay_kind*/ None, + ); + + let plan_mask = collaboration_modes::plan_mask(chat.model_catalog.as_ref()) + .expect("expected plan collaboration mode"); + chat.set_collaboration_mask(plan_mask); + + let width = 80; + let height = chat.desired_height(width); + let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); + terminal + .draw(|f| chat.render(f.area(), f.buffer_mut())) + .expect("draw renamed-thread plan-mode footer"); + assert_chatwidget_snapshot!( + "renamed_thread_footer_title_plan_mode_override", + normalized_backend_snapshot(terminal.backend()) + ); +} + #[tokio::test] async fn status_line_model_with_reasoning_fast_footer_snapshot() { use ratatui::Terminal; From 1712b6cebe3c8f2d591f8997a3f6afa303858f87 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Fri, 10 Apr 2026 12:00:30 -0700 Subject: [PATCH 2/4] Update stale test --- .../tui/src/chatwidget/tests/status_and_layout.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs index 06c09353ef52..e1288f4d59b0 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -1238,17 +1238,14 @@ async fn renamed_thread_footer_title_snapshot() { async fn thread_title_status_item_stays_in_left_status_line() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; chat.thread_name = Some("Roadmap cleanup".to_string()); + chat.config.tui_status_line = Some(vec!["model-name".to_string(), "thread-title".to_string()]); - let (left_line, right_line) = chat.status_line_lines_for_items(&[ - crate::bottom_pane::StatusLineItem::ModelName, - crate::bottom_pane::StatusLineItem::ThreadTitle, - ]); + chat.refresh_status_line(); assert_eq!( - left_line, - Some(Line::from("gpt-5.3-codex · Roadmap cleanup")) + status_line_text(&chat), + Some("gpt-5.3-codex · Roadmap cleanup".to_string()) ); - assert_eq!(right_line, None); } #[tokio::test] From 3ed0258f288fb9b630056316193ac167559ddc35 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Fri, 10 Apr 2026 12:02:29 -0700 Subject: [PATCH 3/4] Remmoved stale snap --- ...__renamed_thread_footer_title_plan_mode_override.snap | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title_plan_mode_override.snap diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title_plan_mode_override.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title_plan_mode_override.snap deleted file mode 100644 index 235b0521fb5c..000000000000 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title_plan_mode_override.snap +++ /dev/null @@ -1,9 +0,0 @@ ---- -source: tui/src/chatwidget/tests.rs -expression: terminal.backend() ---- -" " -" " -"› Ask Codex to do anything " -" " -" gpt-5.3-codex medium · Roadmap cleanup Plan mode (shift+tab to cycle) " From e9a65a2cb7e41e19bee92048b0c934ee3977bea7 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Fri, 10 Apr 2026 12:04:03 -0700 Subject: [PATCH 4/4] cleanup and rename --- .../tui/src/bottom_pane/status_line_setup.rs | 2 +- .../src/chatwidget/tests/status_and_layout.rs | 57 ------------------- 2 files changed, 1 insertion(+), 58 deletions(-) diff --git a/codex-rs/tui/src/bottom_pane/status_line_setup.rs b/codex-rs/tui/src/bottom_pane/status_line_setup.rs index 053c25e53efb..ed4f76c1d079 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_setup.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_setup.rs @@ -386,7 +386,7 @@ mod tests { } #[test] - fn preview_includes_thread_title_in_left_sequence() { + fn preview_includes_thread_title() { let preview_data = StatusLinePreviewData::from_iter([ (StatusLineItem::ModelName, "gpt-5".to_string()), (StatusLineItem::ThreadTitle, "Roadmap cleanup".to_string()), diff --git a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs index e1288f4d59b0..20c0427f8f0e 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -1234,63 +1234,6 @@ async fn renamed_thread_footer_title_snapshot() { ); } -#[tokio::test] -async fn thread_title_status_item_stays_in_left_status_line() { - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; - chat.thread_name = Some("Roadmap cleanup".to_string()); - chat.config.tui_status_line = Some(vec!["model-name".to_string(), "thread-title".to_string()]); - - chat.refresh_status_line(); - - assert_eq!( - status_line_text(&chat), - Some("gpt-5.3-codex · Roadmap cleanup".to_string()) - ); -} - -#[tokio::test] -async fn renamed_thread_footer_title_plan_mode_override_snapshot() { - use ratatui::Terminal; - use ratatui::backend::TestBackend; - - let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; - chat.show_welcome_banner = false; - chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true); - chat.config.tui_status_line = Some(vec![ - "model-with-reasoning".to_string(), - "thread-title".to_string(), - ]); - chat.set_reasoning_effort(Some(ReasoningEffortConfig::High)); - chat.refresh_status_line(); - - let thread_id = ThreadId::new(); - chat.thread_id = Some(thread_id); - chat.handle_server_notification( - ServerNotification::ThreadNameUpdated( - codex_app_server_protocol::ThreadNameUpdatedNotification { - thread_id: thread_id.to_string(), - thread_name: Some("Roadmap cleanup".to_string()), - }, - ), - /*replay_kind*/ None, - ); - - let plan_mask = collaboration_modes::plan_mask(chat.model_catalog.as_ref()) - .expect("expected plan collaboration mode"); - chat.set_collaboration_mask(plan_mask); - - let width = 80; - let height = chat.desired_height(width); - let mut terminal = Terminal::new(TestBackend::new(width, height)).expect("create terminal"); - terminal - .draw(|f| chat.render(f.area(), f.buffer_mut())) - .expect("draw renamed-thread plan-mode footer"); - assert_chatwidget_snapshot!( - "renamed_thread_footer_title_plan_mode_override", - normalized_backend_snapshot(terminal.backend()) - ); -} - #[tokio::test] async fn status_line_model_with_reasoning_fast_footer_snapshot() { use ratatui::Terminal;