diff --git a/codex-rs/tui/src/snapshots/codex_tui__status_indicator_widget__summary_shimmer__tests__short_and_long_labels_sweep_smoothly_in_both_themes.snap b/codex-rs/tui/src/snapshots/codex_tui__status_indicator_widget__summary_shimmer__tests__short_and_long_labels_sweep_smoothly_in_both_themes.snap index 482ec825dc..2e4636199a 100644 --- a/codex-rs/tui/src/snapshots/codex_tui__status_indicator_widget__summary_shimmer__tests__short_and_long_labels_sweep_smoothly_in_both_themes.snap +++ b/codex-rs/tui/src/snapshots/codex_tui__status_indicator_widget__summary_shimmer__tests__short_and_long_labels_sweep_smoothly_in_both_themes.snap @@ -1,35 +1,40 @@ --- source: tui/src/summary_shimmer_tests.rs -assertion_line: 27 expression: "frames.join(\"\\n\")" --- dark Working 0ms: [128, 128, 128, 128, 128, 128, 128] -dark Working 500ms: [238, 198, 144, 128, 128, 128, 128] -dark Working 1000ms: [128, 156, 212, 240, 212, 156, 128] -dark Working 1500ms: [128, 128, 128, 128, 144, 198, 238] -dark Working 2000ms: [128, 128, 128, 128, 128, 128, 128] +dark Working 850ms: [238, 198, 144, 128, 128, 128, 128] +dark Working 1100ms: [128, 156, 212, 240, 212, 156, 128] +dark Working 1350ms: [128, 128, 128, 128, 144, 198, 238] +dark Working 3600ms: [128, 128, 128, 128, 128, 128, 128] +dark Working 5100ms: [128, 156, 212, 240, 212, 156, 128] dark Preparing bootstrap diagnostics 0ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] -dark Preparing bootstrap diagnostics 500ms: [128, 128, 128, 132, 175, 226, 237, 198, 145, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] -dark Preparing bootstrap diagnostics 1000ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 159, 213, 240, 213, 159, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] -dark Preparing bootstrap diagnostics 1500ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 145, 198, 237, 226, 175, 132, 128, 128, 128] -dark Preparing bootstrap diagnostics 2000ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] +dark Preparing bootstrap diagnostics 850ms: [128, 128, 128, 132, 175, 226, 237, 198, 145, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] +dark Preparing bootstrap diagnostics 1100ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 159, 213, 240, 213, 159, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] +dark Preparing bootstrap diagnostics 1350ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 145, 198, 237, 226, 175, 132, 128, 128, 128] +dark Preparing bootstrap diagnostics 3600ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] +dark Preparing bootstrap diagnostics 5100ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 159, 213, 240, 213, 159, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] dark 界é界 0ms: [128, 128, 128] -dark 界é界 500ms: [198, 129, 128] -dark 界é界 1000ms: [184, 240, 184] -dark 界é界 1500ms: [128, 129, 198] -dark 界é界 2000ms: [128, 128, 128] +dark 界é界 850ms: [198, 129, 128] +dark 界é界 1100ms: [184, 240, 184] +dark 界é界 1350ms: [128, 129, 198] +dark 界é界 3600ms: [128, 128, 128] +dark 界é界 5100ms: [184, 240, 184] light Working 0ms: [128, 128, 128, 128, 128, 128, 128] -light Working 500ms: [17, 57, 111, 128, 128, 128, 128] -light Working 1000ms: [128, 100, 44, 16, 44, 100, 128] -light Working 1500ms: [128, 128, 128, 128, 111, 57, 17] -light Working 2000ms: [128, 128, 128, 128, 128, 128, 128] +light Working 850ms: [17, 57, 111, 128, 128, 128, 128] +light Working 1100ms: [128, 100, 44, 16, 44, 100, 128] +light Working 1350ms: [128, 128, 128, 128, 111, 57, 17] +light Working 3600ms: [128, 128, 128, 128, 128, 128, 128] +light Working 5100ms: [128, 100, 44, 16, 44, 100, 128] light Preparing bootstrap diagnostics 0ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] -light Preparing bootstrap diagnostics 500ms: [128, 128, 128, 123, 80, 29, 18, 57, 110, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] -light Preparing bootstrap diagnostics 1000ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 127, 96, 42, 16, 42, 96, 127, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] -light Preparing bootstrap diagnostics 1500ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 110, 57, 18, 29, 80, 123, 128, 128, 128] -light Preparing bootstrap diagnostics 2000ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] +light Preparing bootstrap diagnostics 850ms: [128, 128, 128, 123, 80, 29, 18, 57, 110, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] +light Preparing bootstrap diagnostics 1100ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 127, 96, 42, 16, 42, 96, 127, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] +light Preparing bootstrap diagnostics 1350ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 110, 57, 18, 29, 80, 123, 128, 128, 128] +light Preparing bootstrap diagnostics 3600ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] +light Preparing bootstrap diagnostics 5100ms: [128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 127, 96, 42, 16, 42, 96, 127, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128] light 界é界 0ms: [128, 128, 128] -light 界é界 500ms: [57, 126, 128] -light 界é界 1000ms: [72, 16, 72] -light 界é界 1500ms: [128, 126, 57] -light 界é界 2000ms: [128, 128, 128] +light 界é界 850ms: [57, 126, 128] +light 界é界 1100ms: [72, 16, 72] +light 界é界 1350ms: [128, 126, 57] +light 界é界 3600ms: [128, 128, 128] +light 界é界 5100ms: [72, 16, 72] diff --git a/codex-rs/tui/src/summary_shimmer.rs b/codex-rs/tui/src/summary_shimmer.rs index 16fab1f580..2bae071405 100644 --- a/codex-rs/tui/src/summary_shimmer.rs +++ b/codex-rs/tui/src/summary_shimmer.rs @@ -1,8 +1,9 @@ -//! Smooth, whole-grapheme status shimmer with a two-second sweep. +//! Smooth, whole-grapheme status shimmer with the desktop's cadenced sweep. //! //! Only brightness changes: the moving band uses the terminal foreground, while //! the remaining text blends halfway into the background. The wave spans at //! least six terminal columns so short labels do not flash one letter at a time. +//! New headers wait 600 ms, then sweep for one second every four seconds. //! Unknown palettes use static dim text instead of a stepped animation. use std::time::Duration; @@ -20,6 +21,11 @@ use crate::terminal_palette::default_fg; use crate::terminal_palette::effective_stdout_color_level; use crate::terminal_palette::rgb_color; +// Match the desktop app's thinking and reasoning header timings. +const START_DELAY: Duration = Duration::from_millis(/*millis*/ 600); +const SWEEP_SECONDS: f64 = 1.0; +const INTERVAL_SECONDS: f64 = 4.0; + pub(crate) fn summary_shimmer( text: &str, elapsed: Duration, @@ -35,7 +41,9 @@ pub(crate) fn summary_shimmer( }; let width = text.width() as f64; let half_width = (width * 0.1).max(/*other*/ 3.0); - let position = (elapsed.as_secs_f64() % 2.0) / 2.0 * (width + 2.0 * half_width) - half_width; + let sweep = + (elapsed.saturating_sub(START_DELAY).as_secs_f64() % INTERVAL_SECONDS).min(SWEEP_SECONDS); + let position = sweep / SWEEP_SECONDS * (width + 2.0 * half_width) - half_width; let mut column = 0.0; text.graphemes(/*is_extended*/ true) .map(|grapheme| { diff --git a/codex-rs/tui/src/summary_shimmer_tests.rs b/codex-rs/tui/src/summary_shimmer_tests.rs index a14cd8cc38..04fd9ade20 100644 --- a/codex-rs/tui/src/summary_shimmer_tests.rs +++ b/codex-rs/tui/src/summary_shimmer_tests.rs @@ -25,7 +25,8 @@ fn short_and_long_labels_sweep_smoothly_in_both_themes() { ] { with_test_default_colors(colors, || { for text in ["Working", "Preparing bootstrap diagnostics", "界e\u{301}界"] { - for ms in [0, 500, 1000, 1500, 2000] { + // Initial delay, the first sweep, the gap, and the next sweep. + for ms in [0, 850, 1100, 1350, 3600, 5100] { let spans = summary_shimmer(text, Duration::from_millis(ms), MotionMode::Animated); let levels = spans @@ -52,7 +53,8 @@ fn working_has_overlapping_highlights_without_frame_to_frame_flashes() { }, || { let mut previous = Vec::new(); - for ms in (0..=2000).step_by(/*step*/ 16) { + // Sample two sweeps and the intervening gap at the status row's 32 ms cadence. + for ms in (0..=5600).step_by(/*step*/ 32) { let spans = summary_shimmer("Working", Duration::from_millis(ms), MotionMode::Animated); let brightness = spans @@ -66,13 +68,15 @@ fn working_has_overlapping_highlights_without_frame_to_frame_flashes() { assert!(brightness.iter().filter(|value| **value > 160).count() >= 2); } for (current, previous) in brightness.iter().zip(&previous) { - assert!(u8::abs_diff(*current, *previous) <= 7); + // At this speed the cosine band's maximum change over 32 ms is + // 112 * sin(PI * (13 * 0.032) / 6), rounded up. + assert!(u8::abs_diff(*current, *previous) <= 25); } previous = brightness; } let midpoint = summary_shimmer( "Working", - Duration::from_secs(/*secs*/ 1), + Duration::from_millis(/*millis*/ 1100), MotionMode::Animated, ); let expected = "Working"