Normalize /statusline & /title items (#18886)

This change aligns the `/statusline` and `/title` UIs around the same
normalized item model so both surfaces use consistent ids, labels, and
preview semantics. It keeps the shared preview work from #18435 ,
tightens the remaining mismatches by standardizing item naming, expands
title/status item coverage where appropriate, and makes `/title` preview
use the same title-specific formatting path as the real rendered
terminal title.

- Normalizes persisted item ids and keeps legacy aliases for
compatibility
- Aligns `status-line` and `terminal-title` items with the shared
preview model
- Routes `terminal-title` preview through title-specific formatting and
truncation
- Updates the affected status/title setup snapshots

Added to `/statusline`:
- status
- task-progress
  
Normalized in `/statusline`:
- model-name -> model
- project-root -> project-name

Added to `/title`:
- current-dir
- context-remaining
- context-used
- five-hour-limit
- weekly-limit
- codex-version
- used-tokens
- total-input-tokens
- total-output-tokens
- session-id
- fast-mode
- model-with-reasoning

Normalized in `/title`:
- project -> project-name
- thread -> thread-title
- model-name -> model
This commit is contained in:
canvrno-oai authored and GitHub committed 2026-04-21 16:13:09 -07:00
1 parent ef00014a46
commit 2202675632
9 files changed
+570 -33

No files matched your search

@@ -13,7 +13,7 @@ expression: terminal_title_popup_snapshot(&mut chat)
[ ] app-name Codex app name
[ ] project Project name (falls back to current directory name)
[ ] spinner Animated task spinner (omitted while idle or when animations are off)
[ ] status Compact session status text (Ready, Working, Thinking)
[ ] run-state Compact session run-state text (Ready, Working, Thinking)
[ ] model Current model name
thread title | feat/awesome-feature | Tasks 0/0
@@ -13,7 +13,7 @@ expression: terminal_title_popup_snapshot(&mut chat)
[x] task-progress Latest task progress from update_plan (omitted until available)
[ ] app-name Codex app name
[ ] spinner Animated task spinner (omitted while idle or when animations are off)
[ ] status Compact session status text (Ready, Working, Thinking)
[ ] run-state Compact session run-state text (Ready, Working, Thinking)
[ ] model Current model name
preview-live-root | Live preview thread | feature/live-preview-branch | Tasks 2/5
@@ -12,7 +12,7 @@ expression: terminal_title_popup_snapshot(&mut chat)
[x] task-progress Latest task progress from update_plan (omitted until available)
[ ] app-name Codex app name
[ ] spinner Animated task spinner (omitted while idle or when animations are off)
[ ] status Compact session status text (Ready, Working, Thinking)
[ ] run-state Compact session run-state text (Ready, Working, Thinking)
[ ] git-branch Current Git branch (omitted when unavailable)
[ ] model Current model name
+53 -13
View File
@@ -7,7 +7,7 @@ use super::*;
/// Items shown in the terminal title when the user has not configured a
/// custom selection. Intentionally minimal: spinner + project name.
pub(super) const DEFAULT_TERMINAL_TITLE_ITEMS: [&str; 2] = ["spinner", "project"];
pub(super) const DEFAULT_TERMINAL_TITLE_ITEMS: [&str; 2] = ["spinner", "project-name"];
/// Braille-pattern dot-spinner frames for the terminal title animation.
pub(super) const TERMINAL_TITLE_SPINNER_FRAMES: [&str; 10] =
@@ -421,18 +421,7 @@ impl ChatWidget {
pub(super) fn status_line_value_for_item(&mut self, item: &StatusLineItem) -> Option<String> {
match item {
StatusLineItem::ModelName => Some(self.model_display_name().to_string()),
StatusLineItem::ModelWithReasoning => {
let label =
Self::status_line_reasoning_effort_label(self.effective_reasoning_effort());
let fast_label = if self
.should_show_fast_status(self.current_model(), self.config.service_tier)
{
" fast"
} else {
""
};
Some(format!("{} {label}{fast_label}", self.model_display_name()))
}
StatusLineItem::ModelWithReasoning => Some(self.model_with_reasoning_display_name()),
StatusLineItem::CurrentDir => {
Some(format_directory_display(
self.status_line_cwd(),
@@ -441,6 +430,7 @@ impl ChatWidget {
}
StatusLineItem::ProjectRoot => self.status_line_project_root_name(),
StatusLineItem::GitBranch => self.status_line_branch.clone(),
StatusLineItem::Status => Some(self.terminal_title_status_text()),
StatusLineItem::UsedTokens => {
let usage = self.status_line_total_usage();
let total = usage.tokens_in_context_window();
@@ -502,6 +492,7 @@ impl ChatWidget {
let trimmed = name.trim();
(!trimmed.is_empty()).then(|| trimmed.to_string())
}),
StatusLineItem::TaskProgress => self.terminal_title_task_progress(),
}
}
@@ -547,6 +538,10 @@ impl ChatWidget {
match item {
TerminalTitleItem::AppName => Some("codex".to_string()),
TerminalTitleItem::Project => self.terminal_title_project_name(),
TerminalTitleItem::CurrentDir => Some(Self::truncate_terminal_title_part(
format_directory_display(self.status_line_cwd(), /*max_width*/ None),
/*max_chars*/ 32,
)),
TerminalTitleItem::Spinner => self.terminal_title_spinner_text_at(now),
TerminalTitleItem::Status => Some(self.terminal_title_status_text()),
TerminalTitleItem::Thread => self.thread_name.as_ref().and_then(|name| {
@@ -563,14 +558,59 @@ impl ChatWidget {
TerminalTitleItem::GitBranch => self.status_line_branch.as_ref().map(|branch| {
Self::truncate_terminal_title_part(branch.clone(), /*max_chars*/ 32)
}),
TerminalTitleItem::ContextRemaining => self
.status_line_value_for_item(&StatusLineItem::ContextRemaining)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::ContextUsed => self
.status_line_value_for_item(&StatusLineItem::ContextUsed)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::FiveHourLimit => self
.status_line_value_for_item(&StatusLineItem::FiveHourLimit)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::WeeklyLimit => self
.status_line_value_for_item(&StatusLineItem::WeeklyLimit)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::CodexVersion => self
.status_line_value_for_item(&StatusLineItem::CodexVersion)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::UsedTokens => self
.status_line_value_for_item(&StatusLineItem::UsedTokens)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::TotalInputTokens => self
.status_line_value_for_item(&StatusLineItem::TotalInputTokens)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::TotalOutputTokens => self
.status_line_value_for_item(&StatusLineItem::TotalOutputTokens)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::SessionId => self
.status_line_value_for_item(&StatusLineItem::SessionId)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::FastMode => self
.status_line_value_for_item(&StatusLineItem::FastMode)
.map(|value| Self::truncate_terminal_title_part(value, /*max_chars*/ 32)),
TerminalTitleItem::Model => Some(Self::truncate_terminal_title_part(
self.model_display_name().to_string(),
/*max_chars*/ 32,
)),
TerminalTitleItem::ModelWithReasoning => Some(Self::truncate_terminal_title_part(
self.model_with_reasoning_display_name(),
/*max_chars*/ 32,
)),
TerminalTitleItem::TaskProgress => self.terminal_title_task_progress(),
}
}
fn model_with_reasoning_display_name(&self) -> String {
let label = Self::status_line_reasoning_effort_label(self.effective_reasoning_effort());
let fast_label =
if self.should_show_fast_status(self.current_model(), self.config.service_tier) {
" fast"
} else {
""
};
format!("{} {label}{fast_label}", self.model_display_name())
}
/// Computes the compact runtime status label used by the terminal title.
///
/// Startup takes precedence over normal task states, and idle state renders