From aac1e74cd5a9301009a44a0a536d0d5d15f13cd8 Mon Sep 17 00:00:00 2001 From: canvrno-oai Date: Fri, 10 Apr 2026 13:24:07 -0700 Subject: [PATCH] Add thread title to configurable TUI status line (#17187) - Add thread-title as an optional TUI status line item, omitted unless the user has set a custom name (`ChatWidget.thread_name`). - Refresh the status line when threads are renamded - Add snapshot coverage for renamed-thread footer behavior. --- .../tui/src/bottom_pane/status_line_setup.rs | 34 +++++++++++++++++- codex-rs/tui/src/chatwidget.rs | 1 + ...t__tests__renamed_thread_footer_title.snap | 9 +++++ .../tui/src/chatwidget/status_surfaces.rs | 4 +++ .../src/chatwidget/tests/status_and_layout.rs | 36 +++++++++++++++++++ 5 files changed, 83 insertions(+), 1 deletion(-) create mode 100644 codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__renamed_thread_footer_title.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 cdda2d7ca..ed4f76c1d 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() { + 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 74b203289..67bf8331d 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 000000000..e024f534a --- /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/status_surfaces.rs b/codex-rs/tui/src/chatwidget/status_surfaces.rs index bd58fd4e8..078d31617 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 50de7064c..20c0427f8 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,42 @@ 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 status_line_model_with_reasoning_fast_footer_snapshot() { use ratatui::Terminal;