From 3b6ba569ce4630e335943d1cc29c9c9dc29c5267 Mon Sep 17 00:00:00 2001 From: Felix Martin Date: Tue, 26 May 2026 10:11:49 -0400 Subject: [PATCH] Fix violation prompt window acknowledgement --- src/main.rs | 85 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 82 insertions(+), 3 deletions(-) diff --git a/src/main.rs b/src/main.rs index 41973c2..7630b7b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -120,6 +120,7 @@ struct App { transition_return_target: String, transition_duration_str: String, violation_message: String, + violation_window_snapshot: Option, violation_dismissal_reason: String, current_window_title: Rc, evidence_health: EvidenceHealth, @@ -209,6 +210,7 @@ impl App { transition_return_target: String::new(), transition_duration_str: "2".to_string(), violation_message: String::new(), + violation_window_snapshot: None, violation_dismissal_reason: String::new(), current_window_title: window_snapshot.title.into(), evidence_health: window_snapshot.health, @@ -314,6 +316,7 @@ impl App { fn enter_violation_prompt(&mut self, window_snapshot: &window::WindowSnapshot) { self.violation_message = format!("{}: {}", constants::VIOLATION_STATUS, window_snapshot.title); + self.violation_window_snapshot = Some(window_snapshot.clone()); self.violation_dismissal_reason.clear(); self.state = State::ViolationPrompt; } @@ -323,15 +326,28 @@ impl App { return Ok(()); } + let violation_window_snapshot = self.violation_window_snapshot.clone(); + let window_title = violation_window_snapshot + .as_ref() + .map(|snapshot| snapshot.title.clone()) + .unwrap_or_else(|| self.current_window_title.as_ref().clone()); + let evidence_health = violation_window_snapshot + .as_ref() + .map(|snapshot| snapshot.health.clone()) + .unwrap_or_else(|| self.evidence_health.clone()); + self.session_controller.record_violation( self.violation_dismissal_reason.clone(), serde_json::json!({ "message": self.violation_message, - "window_title": self.current_window_title.as_ref(), - "evidence_health": self.evidence_health, + "window_title": window_title, + "evidence_health": evidence_health, }), )?; - self.acknowledge_unknown_window(&window::get_snapshot()); + if let Some(window_snapshot) = violation_window_snapshot.as_ref() { + self.acknowledge_unknown_window(window_snapshot); + } + self.violation_window_snapshot = None; self.state = State::InProgress; Ok(()) } @@ -1372,6 +1388,69 @@ mod tests { let _ = fs::remove_file(path); } + #[test] + fn violation_dismissal_acknowledges_prompted_window_not_current_foreground() { + let path = unique_event_log_path("violation-prompt-original-ack"); + let mut app = App::new_with_event_log_path(&path).unwrap(); + let prompted_unknown = window::WindowSnapshot { + title: format!( + "Prompted Unknown Window {}", + SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_nanos() + ), + class: Some("prompted-unknown-class".to_string()), + health: antidrift::domain::EvidenceHealth::Available, + }; + let dismissal_foreground = window::WindowSnapshot { + title: "AntiDrift Terminal".to_string(), + class: Some("terminal".to_string()), + health: antidrift::domain::EvidenceHealth::Degraded( + "terminal foreground during dismissal".to_string(), + ), + }; + + app.user_intention = "write tests".to_string(); + app.user_success_condition = "tests pass".to_string(); + app.user_duration_str = "1".to_string(); + app.to_in_progress().unwrap(); + app.enter_violation_prompt(&prompted_unknown); + app.current_window_title = Rc::new(dismissal_foreground.title.clone()); + app.evidence_health = dismissal_foreground.health.clone(); + app.violation_dismissal_reason = "Needed to inspect docs".to_string(); + + app.dismiss_violation_if_valid().unwrap(); + + assert_eq!(app.state, State::InProgress); + assert!(app.is_unknown_window_acknowledged(&prompted_unknown)); + assert!(!app.is_unknown_window_acknowledged(&dismissal_foreground)); + + let records = EventLog::open(&path).unwrap().records().unwrap(); + let violation = records + .iter() + .find(|record| record.event_type == EventType::ViolationObserved) + .expect("dismissal should record a violation"); + assert_eq!( + violation.payload_json["evidence"]["window_title"], + prompted_unknown.title + ); + assert_eq!( + violation.payload_json["evidence"]["message"], + format!( + "{}: {}", + constants::VIOLATION_STATUS, + prompted_unknown.title + ) + ); + assert_eq!( + violation.payload_json["evidence"]["evidence_health"], + serde_json::json!("available") + ); + + let _ = fs::remove_file(path); + } + #[test] fn rating_completion_returns_controller_to_planning_for_next_session() { let path = unique_event_log_path("rating-completion");