Show action required in terminal title (#18372)

Implements #18162

This updates the TUI terminal title to show an explicit action-required
state when Codex is blocked on user approval or input. The terminal
title now uses the activity title item to cover both active work and
blocked-on-user states, while still accepting the legacy spinner config
value.

Changes
- Rename the terminal title item from `spinner` to `activity` while
preserving legacy config compatibility
- Show `[ ! ] Action Required `while approval or input overlays are
active, with a blinking `[ . ]` alternate state
- Suppress the normal working spinner while Codex is blocked on user
action
- Add targeted coverage for action-required title behavior and legacy
title-item parsing

Testing
- Trigger an approval or input modal and confirm the tab title
alternates between `[ ! ] Action Required` and `[ . ] Action Required`
- Disable the activity title item and confirm the action-required title
does not appear
- Resolve the prompt and confirm the title returns to the normal
spinning/idel state


https://github.com/user-attachments/assets/e9ecc530-a6be-4fd7-b9a6-d550a790eb2c
This commit is contained in:
canvrno-oai authored and GitHub committed 2026-04-27 15:27:11 -07:00
1 parent e903d000b0
commit e64c765673
20 files changed
+450 -68

No files matched your search

+15 -7
View File
@@ -1035,6 +1035,8 @@ pub(crate) struct ChatWidget {
terminal_title_invalid_items_warned: Arc<AtomicBool>,
// Last terminal title emitted, to avoid writing duplicate OSC updates.
pub(crate) last_terminal_title: Option<String>,
// Last visible "action required" state observed by the terminal-title renderer.
last_terminal_title_requires_action: bool,
// Original terminal-title config captured when the setup UI opens.
//
// The outer `Option` tracks whether a setup session is active (`Some`)
@@ -2110,13 +2112,13 @@ impl ChatWidget {
.iter()
.any(|item| item == "run-state" || item == "status")
});
let title_uses_spinner = self
.config
.tui_terminal_title
.as_ref()
.is_none_or(|items| items.iter().any(|item| item == "spinner"));
let title_uses_activity = self.config.tui_terminal_title.as_ref().is_none_or(|items| {
items
.iter()
.any(|item| item == "activity" || item == "spinner")
});
if title_uses_status
|| (title_uses_spinner
|| (title_uses_activity
&& self.terminal_title_status_kind == TerminalTitleStatusKind::Undoing)
{
self.refresh_status_surfaces();
@@ -4917,7 +4919,12 @@ impl ChatWidget {
self.schedule_hook_timer_if_needed();
self.bottom_pane.pre_draw_tick();
self.refresh_goal_status_indicator_for_time_tick();
if self.should_animate_terminal_title_spinner() {
if self.terminal_title_shows_action_required() != self.last_terminal_title_requires_action {
self.refresh_terminal_title();
}
if self.should_animate_terminal_title_spinner()
|| self.should_animate_terminal_title_action_required()
{
self.refresh_terminal_title();
}
}
@@ -5627,6 +5634,7 @@ impl ChatWidget {
status_line_invalid_items_warned,
terminal_title_invalid_items_warned,
last_terminal_title: None,
last_terminal_title_requires_action: false,
terminal_title_setup_original_items: None,
terminal_title_animation_origin: Instant::now(),
status_line_project_root_name_cache: None,