rpi_tui/countdown_timer.rs
1//! Deadline-based countdown timer.
2//!
3//! Adapted from `packages/coding-agent/src/modes/interactive/.../countdown-timer.ts`.
4//! The TS original spins a `setInterval`; the Rust port is **deadline-based**:
5//! it stores an [`Instant`] and the host re-renders on the existing render tick
6//! (the 120ms task that re-renders while a run is in flight already drives
7//! visible updates). The owner reads [`remaining_secs`] each render.
8//!
9//! `Instant::now()` is allowed here — the `Date.now()`/`Instant::now()` ban in
10//! this codebase applies only to Workflow scripts, not to normal Rust library
11//! code.
12
13use std::time::{Duration, Instant};
14
15/// A simple countdown bounded by an absolute deadline.
16///
17/// No thread, no timer: compute remaining time on demand.
18pub struct CountdownTimer {
19 deadline: Instant,
20}
21
22impl CountdownTimer {
23 /// Create a timer that expires after `duration`.
24 pub fn new(duration: Duration) -> Self {
25 Self {
26 deadline: Instant::now() + duration,
27 }
28 }
29
30 /// Seconds remaining until expiry (clamped at 0).
31 pub fn remaining_secs(&self) -> u32 {
32 let now = Instant::now();
33 if now >= self.deadline {
34 0
35 } else {
36 let remaining = self.deadline - now;
37 remaining
38 .as_secs()
39 .saturating_add(u64::from(remaining.subsec_nanos() > 0))
40 .min(u32::MAX as u64) as u32
41 }
42 }
43
44 /// Whether the deadline has passed.
45 pub fn expired(&self) -> bool {
46 Instant::now() >= self.deadline
47 }
48}
49
50#[cfg(test)]
51mod tests {
52 use super::*;
53
54 #[test]
55 fn test_new_timer_not_expired() {
56 let t = CountdownTimer::new(Duration::from_secs(10));
57 assert!(!t.expired());
58 assert!(t.remaining_secs() <= 10);
59 }
60
61 #[test]
62 fn test_expired_timer() {
63 let t = CountdownTimer::new(Duration::from_millis(0));
64 // Sleep is unnecessary for a zero-duration timer; deadline is now/now+0.
65 let r = t.remaining_secs();
66 // For a 0ms timer remaining is 0 (clamped) — but allow 1s of slack on
67 // slow CI by asserting <= 1.
68 assert!(r <= 1, "expected <= 1, got {r}");
69 }
70}