From f1d029cf75e04246c4b30bf93e47d036ba394fb5 Mon Sep 17 00:00:00 2001 From: Eric Traut Date: Mon, 1 Jun 2026 09:30:20 -0700 Subject: [PATCH] Add reasoning-only status surface item (#25504) Closes #24886. ## Why Users can configure the TUI status line and terminal title with `model-with-reasoning`, but issue #24886 asks for a compact reasoning-only item. That lets a setup show just `default`, `low`, `medium`, `high`, or `xhigh` without repeating the model name. ## What changed - Added a `reasoning` item for `/statusline` and `/title` setup flows. - Rendered the item from the effective reasoning effort, including collaboration-mode overrides. - Registered `reasoning` with `codex doctor` so Codex-generated terminal-title config is not reported as invalid. - Updated TUI setup snapshots so the picker previews include the new item. --- codex-rs/cli/src/doctor/title.rs | 1 + ..._snapshot_uses_runtime_preview_values.snap | 2 +- .../tui/src/bottom_pane/status_line_setup.rs | 14 +++++++++ .../tui/src/bottom_pane/status_line_style.rs | 4 ++- .../src/bottom_pane/status_surface_preview.rs | 3 ++ codex-rs/tui/src/bottom_pane/title_setup.rs | 15 +++++++++ ...tatus_line_setup_popup_hardcoded_only.snap | 2 +- ...ts__status_line_setup_popup_live_only.snap | 2 +- ..._tests__status_line_setup_popup_mixed.snap | 2 +- ...__status_line_setup_popup_rate_limits.snap | 2 +- .../tui/src/chatwidget/status_surfaces.rs | 12 ++++++- .../src/chatwidget/tests/status_and_layout.rs | 31 +++++++++++++++++++ 12 files changed, 83 insertions(+), 7 deletions(-) diff --git a/codex-rs/cli/src/doctor/title.rs b/codex-rs/cli/src/doctor/title.rs index 0f9c803ddf..09dc80c0a2 100644 --- a/codex-rs/cli/src/doctor/title.rs +++ b/codex-rs/cli/src/doctor/title.rs @@ -143,6 +143,7 @@ fn terminal_title_item_id(item: &str) -> Option<&'static str> { "fast-mode" => Some("fast-mode"), "model" | "model-name" => Some("model"), "model-with-reasoning" => Some("model-with-reasoning"), + "reasoning" => Some("reasoning"), "task-progress" => Some("task-progress"), _ => None, } diff --git a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__status_line_setup__tests__setup_view_snapshot_uses_runtime_preview_values.snap b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__status_line_setup__tests__setup_view_snapshot_uses_runtime_preview_values.snap index 50eb41ed4a..132ce3dd23 100644 --- a/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__status_line_setup__tests__setup_view_snapshot_uses_runtime_preview_values.snap +++ b/codex-rs/tui/src/bottom_pane/snapshots/codex_tui__bottom_pane__status_line_setup__tests__setup_view_snapshot_uses_runtime_preview_values.snap @@ -14,8 +14,8 @@ expression: "render_lines(&view, 72)" [x] current-dir Current working directory [x] git-branch Current Git branch (omitted when unavaila… [ ] model-with-reasoning Current model name with reasoning level + [ ] reasoning Current reasoning level [ ] project-name Project name (omitted when unavailable) - [ ] pull-request-number Open pull request number for the current … gpt-5-codex · ~/codex-rs · jif/statusline-preview Press space to toggle; ←/→ to move; enter to confirm and close; esc to 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 59b83e224d..4dd62a7d20 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_setup.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_setup.rs @@ -60,6 +60,9 @@ pub(crate) enum StatusLineItem { /// Model name with reasoning level suffix. ModelWithReasoning, + /// Current reasoning level. + Reasoning, + /// Current working directory path. CurrentDir, @@ -144,6 +147,7 @@ impl StatusLineItem { match self { StatusLineItem::ModelName => "Current model name", StatusLineItem::ModelWithReasoning => "Current model name with reasoning level", + StatusLineItem::Reasoning => "Current reasoning level", StatusLineItem::CurrentDir => "Current working directory", StatusLineItem::ProjectRoot => "Project name (omitted when unavailable)", StatusLineItem::GitBranch => "Current Git branch (omitted when unavailable)", @@ -191,6 +195,7 @@ impl StatusLineItem { match self { StatusLineItem::ModelName => StatusSurfacePreviewItem::Model, StatusLineItem::ModelWithReasoning => StatusSurfacePreviewItem::ModelWithReasoning, + StatusLineItem::Reasoning => StatusSurfacePreviewItem::Reasoning, StatusLineItem::CurrentDir => StatusSurfacePreviewItem::CurrentDir, StatusLineItem::ProjectRoot => StatusSurfacePreviewItem::ProjectRoot, StatusLineItem::GitBranch => StatusSurfacePreviewItem::GitBranch, @@ -450,6 +455,15 @@ mod tests { ); } + #[test] + fn reasoning_is_selectable_id() { + assert_eq!(StatusLineItem::Reasoning.to_string(), "reasoning"); + assert_eq!( + "reasoning".parse::(), + Ok(StatusLineItem::Reasoning) + ); + } + #[test] fn run_state_is_canonical_and_accepts_status_legacy_id() { assert_eq!(StatusLineItem::Status.to_string(), "run-state"); diff --git a/codex-rs/tui/src/bottom_pane/status_line_style.rs b/codex-rs/tui/src/bottom_pane/status_line_style.rs index ffb2a289e9..170c4641d2 100644 --- a/codex-rs/tui/src/bottom_pane/status_line_style.rs +++ b/codex-rs/tui/src/bottom_pane/status_line_style.rs @@ -30,7 +30,9 @@ enum StatusLineAccent { impl StatusLineAccent { fn for_item(item: StatusLineItem) -> Self { match item { - StatusLineItem::ModelName | StatusLineItem::ModelWithReasoning => Self::Model, + StatusLineItem::ModelName + | StatusLineItem::ModelWithReasoning + | StatusLineItem::Reasoning => Self::Model, StatusLineItem::CurrentDir | StatusLineItem::ProjectRoot => Self::Path, StatusLineItem::GitBranch | StatusLineItem::PullRequestNumber diff --git a/codex-rs/tui/src/bottom_pane/status_surface_preview.rs b/codex-rs/tui/src/bottom_pane/status_surface_preview.rs index 71353c298a..bd0a94a4d4 100644 --- a/codex-rs/tui/src/bottom_pane/status_surface_preview.rs +++ b/codex-rs/tui/src/bottom_pane/status_surface_preview.rs @@ -32,6 +32,7 @@ pub(crate) enum StatusSurfacePreviewItem { RawOutput, Model, ModelWithReasoning, + Reasoning, TaskProgress, } @@ -63,6 +64,7 @@ impl StatusSurfacePreviewItem { StatusSurfacePreviewItem::RawOutput => "raw output", StatusSurfacePreviewItem::Model => "gpt-5.2-codex", StatusSurfacePreviewItem::ModelWithReasoning => "gpt-5.2-codex medium", + StatusSurfacePreviewItem::Reasoning => "medium", StatusSurfacePreviewItem::TaskProgress => "Tasks 0/0", } } @@ -94,6 +96,7 @@ impl StatusSurfacePreviewItem { Self::RawOutput, Self::Model, Self::ModelWithReasoning, + Self::Reasoning, Self::TaskProgress, ] .into_iter() diff --git a/codex-rs/tui/src/bottom_pane/title_setup.rs b/codex-rs/tui/src/bottom_pane/title_setup.rs index b9ab39054e..1a3993c1cd 100644 --- a/codex-rs/tui/src/bottom_pane/title_setup.rs +++ b/codex-rs/tui/src/bottom_pane/title_setup.rs @@ -82,6 +82,8 @@ pub(crate) enum TerminalTitleItem { Model, /// Current model name with reasoning level. ModelWithReasoning, + /// Current reasoning level. + Reasoning, /// Latest checklist task progress from `update_plan` (if available). TaskProgress, } @@ -122,6 +124,7 @@ impl TerminalTitleItem { TerminalTitleItem::FastMode => "Whether Fast mode is currently active", TerminalTitleItem::Model => "Current model name", TerminalTitleItem::ModelWithReasoning => "Current model name with reasoning level", + TerminalTitleItem::Reasoning => "Current reasoning level", TerminalTitleItem::TaskProgress => { "Latest task progress from update_plan (omitted until available)" } @@ -153,6 +156,7 @@ impl TerminalTitleItem { TerminalTitleItem::ModelWithReasoning => { Some(StatusSurfacePreviewItem::ModelWithReasoning) } + TerminalTitleItem::Reasoning => Some(StatusSurfacePreviewItem::Reasoning), TerminalTitleItem::TaskProgress => Some(StatusSurfacePreviewItem::TaskProgress), } } @@ -516,6 +520,15 @@ mod tests { ); } + #[test] + fn reasoning_is_selectable_id() { + assert_eq!(TerminalTitleItem::Reasoning.to_string(), "reasoning"); + assert_eq!( + "reasoning".parse::(), + Ok(TerminalTitleItem::Reasoning) + ); + } + #[test] fn parse_terminal_title_items_accepts_kebab_case_variants() { let items = parse_terminal_title_items( @@ -530,6 +543,7 @@ mod tests { "project-name", "model", "model-with-reasoning", + "reasoning", "weekly-limit", "codex-version", "used-tokens", @@ -553,6 +567,7 @@ mod tests { TerminalTitleItem::Project, TerminalTitleItem::Model, TerminalTitleItem::ModelWithReasoning, + TerminalTitleItem::Reasoning, TerminalTitleItem::WeeklyLimit, TerminalTitleItem::CodexVersion, TerminalTitleItem::UsedTokens, diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap index 10c1132c3f..2a91d27c1b 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_hardcoded_only.snap @@ -14,7 +14,7 @@ expression: status_line_popup_snapshot(&mut chat) [x] thread-title Current thread title, or thread identifier when unnamed [ ] model Current model name [ ] model-with-reasoning Current model name with reasoning level - [ ] current-dir Current working directory + [ ] reasoning Current reasoning level my-project · feat/awesome-feature · thread title Press space to toggle; ←/→ to move; enter to confirm and close; esc to close diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap index 3106adea2a..709f7734df 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_live_only.snap @@ -14,7 +14,7 @@ expression: status_line_popup_snapshot(&mut chat) [x] thread-title Current thread title, or thread identifier when unnamed [ ] model Current model name [ ] model-with-reasoning Current model name with reasoning level - [ ] current-dir Current working directory + [ ] reasoning Current reasoning level preview-live-root · feature/live-preview-branch · Live preview thread Press space to toggle; ←/→ to move; enter to confirm and close; esc to close diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_mixed.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_mixed.snap index 1cb2dcb5af..b0e29ea479 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_mixed.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_mixed.snap @@ -14,7 +14,7 @@ expression: status_line_popup_snapshot(&mut chat) [x] thread-title Current thread title, or thread identifier when unnamed [ ] model Current model name [ ] model-with-reasoning Current model name with reasoning level - [ ] current-dir Current working directory + [ ] reasoning Current reasoning level my-project · feature/mixed-preview · Mixed preview thread Press space to toggle; ←/→ to move; enter to confirm and close; esc to close diff --git a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_rate_limits.snap b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_rate_limits.snap index 781bc269bf..7dca3c19d5 100644 --- a/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_rate_limits.snap +++ b/codex-rs/tui/src/chatwidget/snapshots/codex_tui__chatwidget__tests__status_line_setup_popup_rate_limits.snap @@ -13,8 +13,8 @@ expression: status_line_popup_snapshot(&mut chat) [x] weekly-limit Remaining usage on the weekly usage limit (omitted when unavailable) [ ] model Current model name [ ] model-with-reasoning Current model name with reasoning level + [ ] reasoning Current reasoning level [ ] current-dir Current working directory - [ ] project-name Project name (omitted when unavailable) monthly 65% left · weekly 50% left Press space to toggle; ←/→ to move; enter to confirm and close; esc to close diff --git a/codex-rs/tui/src/chatwidget/status_surfaces.rs b/codex-rs/tui/src/chatwidget/status_surfaces.rs index 6a3bbbc07b..d8facdb0cf 100644 --- a/codex-rs/tui/src/chatwidget/status_surfaces.rs +++ b/codex-rs/tui/src/chatwidget/status_surfaces.rs @@ -562,6 +562,7 @@ impl ChatWidget { match item { StatusLineItem::ModelName => Some(self.model_display_name().to_string()), StatusLineItem::ModelWithReasoning => Some(self.model_with_reasoning_display_name()), + StatusLineItem::Reasoning => Some(self.reasoning_display_name().to_string()), StatusLineItem::CurrentDir => { Some(format_directory_display( self.status_line_cwd(), @@ -694,6 +695,7 @@ impl ChatWidget { StatusSurfacePreviewItem::RawOutput => StatusLineItem::RawOutput, StatusSurfacePreviewItem::Model => StatusLineItem::ModelName, StatusSurfacePreviewItem::ModelWithReasoning => StatusLineItem::ModelWithReasoning, + StatusSurfacePreviewItem::Reasoning => StatusLineItem::Reasoning, }; self.status_line_value_for_item(status_line_item) } @@ -759,12 +761,20 @@ impl ChatWidget { self.model_with_reasoning_display_name(), /*max_chars*/ 32, )), + TerminalTitleItem::Reasoning => Some(Self::truncate_terminal_title_part( + self.reasoning_display_name().to_string(), + /*max_chars*/ 32, + )), TerminalTitleItem::TaskProgress => self.terminal_title_task_progress(), } } + fn reasoning_display_name(&self) -> &'static str { + Self::status_line_reasoning_effort_label(self.effective_reasoning_effort()) + } + fn model_with_reasoning_display_name(&self) -> String { - let label = Self::status_line_reasoning_effort_label(self.effective_reasoning_effort()); + let label = self.reasoning_display_name(); let service_tier_label = self .current_service_tier() .and_then(|service_tier| { 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 c52c7b2dd9..4b13e53801 100644 --- a/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs +++ b/codex-rs/tui/src/chatwidget/tests/status_and_layout.rs @@ -2208,6 +2208,37 @@ async fn terminal_title_model_updates_on_model_change_without_manual_refresh() { assert_eq!(chat.last_terminal_title, Some("gpt-5.3-codex".to_string())); } +#[tokio::test] +async fn status_line_and_terminal_title_reasoning_render_only_effort() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.4")).await; + chat.config.tui_status_line = Some(vec!["reasoning".to_string()]); + chat.config.tui_terminal_title = Some(vec!["reasoning".to_string()]); + chat.set_reasoning_effort(Some(ReasoningEffortConfig::XHigh)); + chat.set_service_tier(Some(ServiceTier::Fast.request_value().to_string())); + + chat.refresh_status_line(); + chat.refresh_terminal_title(); + + assert_eq!(status_line_text(&chat), Some("xhigh".to_string())); + assert_eq!(chat.last_terminal_title, Some("xhigh".to_string())); +} + +#[tokio::test] +async fn status_line_reasoning_updates_on_mode_switch_without_manual_refresh() { + let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await; + chat.set_feature_enabled(Feature::CollaborationModes, /*enabled*/ true); + chat.config.tui_status_line = Some(vec!["reasoning".to_string()]); + chat.set_reasoning_effort(Some(ReasoningEffortConfig::High)); + + assert_eq!(status_line_text(&chat), Some("high".to_string())); + + let plan_mask = collaboration_modes::plan_mask(chat.model_catalog.as_ref()) + .expect("expected plan collaboration mode"); + chat.set_collaboration_mask(plan_mask); + + assert_eq!(status_line_text(&chat), Some("medium".to_string())); +} + #[tokio::test] async fn status_line_model_with_reasoning_updates_on_mode_switch_without_manual_refresh() { let (mut chat, _rx, _op_rx) = make_chatwidget_manual(Some("gpt-5.3-codex")).await;