diff --git a/codex-rs/tui/src/status_indicator_widget.rs b/codex-rs/tui/src/status_indicator_widget.rs index 8f492c06..5b2fe720 100644 --- a/codex-rs/tui/src/status_indicator_widget.rs +++ b/codex-rs/tui/src/status_indicator_widget.rs @@ -31,6 +31,23 @@ pub(crate) struct StatusIndicatorWidget { frame_requester: FrameRequester, } +// Format elapsed seconds into a compact human-friendly form used by the status line. +// Examples: 0s, 59s, 1m00s, 59m59s, 1h00m00s, 2h03m09s +fn fmt_elapsed_compact(elapsed_secs: u64) -> String { + if elapsed_secs < 60 { + return format!("{elapsed_secs}s"); + } + if elapsed_secs < 3600 { + let minutes = elapsed_secs / 60; + let seconds = elapsed_secs % 60; + return format!("{minutes}m{seconds:02}s"); + } + let hours = elapsed_secs / 3600; + let minutes = (elapsed_secs % 3600) / 60; + let seconds = elapsed_secs % 60; + format!("{hours}h{minutes:02}m{seconds:02}s") +} + impl StatusIndicatorWidget { pub(crate) fn new(app_event_tx: AppEventSender, frame_requester: FrameRequester) -> Self { Self { @@ -136,13 +153,14 @@ impl WidgetRef for StatusIndicatorWidget { self.frame_requester .schedule_frame_in(Duration::from_millis(32)); let elapsed = self.elapsed_seconds(); + let pretty_elapsed = fmt_elapsed_compact(elapsed); // Plain rendering: no borders or padding so the live cell is visually indistinguishable from terminal scrollback. let mut spans = vec![" ".into()]; spans.extend(shimmer_spans(&self.header)); spans.extend(vec![ " ".into(), - format!("({elapsed}s • ").dim(), + format!("({pretty_elapsed} • ").dim(), "Esc".dim().bold(), " to interrupt)".dim(), ]); @@ -184,6 +202,22 @@ mod tests { use std::time::Instant; use tokio::sync::mpsc::unbounded_channel; + use pretty_assertions::assert_eq; + + #[test] + fn fmt_elapsed_compact_formats_seconds_minutes_hours() { + assert_eq!(fmt_elapsed_compact(0), "0s"); + assert_eq!(fmt_elapsed_compact(1), "1s"); + assert_eq!(fmt_elapsed_compact(59), "59s"); + assert_eq!(fmt_elapsed_compact(60), "1m00s"); + assert_eq!(fmt_elapsed_compact(61), "1m01s"); + assert_eq!(fmt_elapsed_compact(3 * 60 + 5), "3m05s"); + assert_eq!(fmt_elapsed_compact(59 * 60 + 59), "59m59s"); + assert_eq!(fmt_elapsed_compact(3600), "1h00m00s"); + assert_eq!(fmt_elapsed_compact(3600 + 60 + 1), "1h01m01s"); + assert_eq!(fmt_elapsed_compact(25 * 3600 + 2 * 60 + 3), "25h02m03s"); + } + #[test] fn renders_with_working_header() { let (tx_raw, _rx) = unbounded_channel::();