feat(tui): show minutes/hours in thinking timer (#3220)

What
  
- Show compact elapsed time in the TUI status indicator: Xs, MmSSs,
HhMMmSSs.
  - Add private helper fmt_elapsed_compact with a unit test.
  
  Why
  
- Seconds‑only becomes hard to read during longer runs; minutes/hours
improve clarity without extra noise.
  
  How
  
  - Implemented in codex-rs/tui/src/status_indicator_widget.rs only.
- The helper is used when rendering the existing “Working/Thinking”
timer.
- No changes to codex-common::elapsed::format_duration or other crates.
  
  Scope/Impact
  
  - TUI‑only; no public API changes; minimal risk.
  - Snapshot tests should remain unchanged (most show “0s”).
  
  Before/After
  
- Working (65s • Esc to interrupt) → Working (1m05s • Esc to interrupt)
  - Working (3723s • …) → Working (1h02m03s • …)
  
  Tests
  
  - Unit: fmt_elapsed_compact_formats_seconds_minutes_hours.
- Local checks: cargo fmt --all, cargo clippy -p codex-tui -- -D
warnings, cargo test -p codex-tui.
  
  Notes
  
- Open to adjusting the exact format or moving the helper if maintainers
prefer a shared location.

Signed-off-by: Enrique Moreno Tent <enriquemorenotent@gmail.com>
This commit is contained in:
Enrique Moreno Tent
2025-09-06 00:06:36 +02:00
committed by GitHub
parent 17a80d43c8
commit 6cfc012e9d

View File

@@ -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::<AppEvent>();