From a0d56541cf123f20f90cbf3ceb5c2f7506a0321e Mon Sep 17 00:00:00 2001 From: Jeremy Rose <172423086+nornagon-openai@users.noreply.github.com> Date: Tue, 7 Oct 2025 11:34:05 -0700 Subject: [PATCH] tui: breathing spinner on true-color terms (#4853) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit uses the same logic as shimmer_spans to render the `•` spinner. on terminals without true-color support, fall back to the existing `•/◦` blinking logic. https://github.com/user-attachments/assets/19db76f2-8fa2-440d-9fde-7bed67f4c4dc --- codex-rs/tui/src/exec_cell/render.rs | 15 +++++++++++---- codex-rs/tui/src/status_indicator_widget.rs | 9 +++------ 2 files changed, 14 insertions(+), 10 deletions(-) diff --git a/codex-rs/tui/src/exec_cell/render.rs b/codex-rs/tui/src/exec_cell/render.rs index 9c1231f0..51824ddc 100644 --- a/codex-rs/tui/src/exec_cell/render.rs +++ b/codex-rs/tui/src/exec_cell/render.rs @@ -8,6 +8,7 @@ use crate::history_cell::HistoryCell; use crate::render::highlight::highlight_bash_to_lines; use crate::render::line_utils::prefix_lines; use crate::render::line_utils::push_owned_lines; +use crate::shimmer::shimmer_spans; use crate::wrapping::RtOptions; use crate::wrapping::word_wrap_line; use codex_ansi_escape::ansi_escape_line; @@ -116,10 +117,16 @@ pub(crate) fn output_lines( } pub(crate) fn spinner(start_time: Option) -> Span<'static> { - let blink_on = start_time - .map(|st| ((st.elapsed().as_millis() / 600) % 2) == 0) - .unwrap_or(false); - if blink_on { "•".into() } else { "◦".dim() } + let elapsed = start_time.map(|st| st.elapsed()).unwrap_or_default(); + if supports_color::on_cached(supports_color::Stream::Stdout) + .map(|level| level.has_16m) + .unwrap_or(false) + { + shimmer_spans("•")[0].clone() + } else { + let blink_on = (elapsed.as_millis() / 600).is_multiple_of(2); + if blink_on { "•".into() } else { "◦".dim() } + } } impl HistoryCell for ExecCell { diff --git a/codex-rs/tui/src/status_indicator_widget.rs b/codex-rs/tui/src/status_indicator_widget.rs index e60204cd..ce4f6eab 100644 --- a/codex-rs/tui/src/status_indicator_widget.rs +++ b/codex-rs/tui/src/status_indicator_widget.rs @@ -15,6 +15,7 @@ use ratatui::widgets::WidgetRef; use crate::app_event::AppEvent; use crate::app_event_sender::AppEventSender; +use crate::exec_cell::spinner; use crate::key_hint; use crate::shimmer::shimmer_spans; use crate::tui::FrameRequester; @@ -163,15 +164,11 @@ impl WidgetRef for StatusIndicatorWidget { let now = Instant::now(); let elapsed_duration = self.elapsed_duration_at(now); let pretty_elapsed = fmt_elapsed_compact(elapsed_duration.as_secs()); - let blink_on = (elapsed_duration.as_millis() / 600).is_multiple_of(2); // Plain rendering: no borders or padding so the live cell is visually indistinguishable from terminal scrollback. let mut spans = Vec::with_capacity(5); - if blink_on { - spans.push("• ".into()); - } else { - spans.push("◦ ".dim()); - } + spans.push(spinner(Some(self.last_resume_at))); + spans.push(" ".into()); spans.extend(shimmer_spans(&self.header)); spans.extend(vec![ " ".into(),