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.
This commit is contained in:
canvrno-oai
2026-04-10 13:24:07 -07:00
committed by GitHub
Unverified
parent 5779be314a
commit aac1e74cd5
5 changed files with 83 additions and 1 deletions
@@ -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::<AppEvent>();
+1
View File
@@ -2151,6 +2151,7 @@ impl ChatWidget {
}
self.thread_name = event.thread_name;
self.refresh_terminal_title();
self.refresh_status_surfaces();
self.request_redraw();
}
}
@@ -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 "
@@ -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())
}),
}
}
@@ -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;