From 25aeb58ae88ce8a013394885863d84b5429e8780 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Tue, 26 May 2026 17:14:26 -0400 Subject: [PATCH] Freeze active time during review --- src/session.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/src/session.rs b/src/session.rs index 7550eda..b6d9b08 100644 --- a/src/session.rs +++ b/src/session.rs @@ -201,6 +201,13 @@ impl SessionController { EventType::RuntimeTransition => { let payload: RuntimeTransitionPayload = parse_payload(&record, "runtime transition")?; + if payload.action == "complete_for_review" { + if let Some(started_at) = active_started_at.take() { + active_elapsed_secs = active_elapsed_secs.saturating_add( + record.timestamp_unix_secs.saturating_sub(started_at), + ); + } + } if payload.action == "return_to_active" && open_transition_started_at.take().is_some() { @@ -3348,4 +3355,37 @@ mod tests { fs::remove_file(path).unwrap(); } + + #[test] + fn timing_summary_stops_active_elapsed_during_review() { + let path = temp_log_path("timing-review"); + let mut session = SessionController::new(&path).unwrap(); + session.enter_planning().unwrap(); + session + .start_manual_commitment( + "Implement session controller", + "session tests pass", + Duration::from_secs(1500), + ) + .unwrap(); + + std::thread::sleep(Duration::from_millis(1200)); + session.complete_for_review().unwrap(); + std::thread::sleep(Duration::from_millis(2200)); + + let summary = session.timing_summary().unwrap().unwrap(); + let created_at = session.active_commitment().unwrap().created_at_unix_secs; + let wall_elapsed = unix_secs_now().saturating_sub(created_at); + + assert_eq!(session.runtime_state(), RuntimeState::Review); + assert!(!summary.currently_in_transition); + assert!(wall_elapsed >= 3); + assert!( + summary.active_elapsed_secs <= 2, + "active elapsed should freeze at review start, got {}", + summary.active_elapsed_secs + ); + + fs::remove_file(path).unwrap(); + } }