Fix post-dismissal prompt handoff

This commit is contained in:
2026-05-26 10:19:27 -04:00
parent 3b6ba569ce
commit 41da2c11a2
+106 -3
View File
@@ -133,6 +133,7 @@ struct App {
session_ratings_index: usize,
session_results: Vec<SessionResult>,
acknowledged_unknown_window: Option<WindowIdentity>,
acknowledged_unknown_window_handoff_pending: bool,
last_tick_50ms: Instant,
last_tick_1s: Instant,
}
@@ -223,6 +224,7 @@ impl App {
session_ratings_index: 0,
session_results: Vec::new(),
acknowledged_unknown_window: None,
acknowledged_unknown_window_handoff_pending: false,
last_tick_50ms: now,
last_tick_1s: now,
})
@@ -460,9 +462,16 @@ impl App {
}
fn tick_active_session(&mut self) -> Result<()> {
self.tick_active_session_with_snapshot(window::get_snapshot())
}
fn tick_active_session_with_snapshot(
&mut self,
window_snapshot: window::WindowSnapshot,
) -> Result<()> {
let elapsed = self.session_start.elapsed();
self.session_remaining = self.user_duration.saturating_sub(elapsed);
let window_snapshot = update_session_stats(self);
let window_snapshot = update_session_stats_with_snapshot(self, window_snapshot);
let should_prompt = self.state == State::InProgress
&& self.session_controller.runtime_state() == RuntimeState::Active
@@ -495,6 +504,7 @@ impl App {
fn acknowledge_unknown_window(&mut self, window_snapshot: &window::WindowSnapshot) {
self.acknowledged_unknown_window = Some(WindowIdentity::from(window_snapshot));
self.acknowledged_unknown_window_handoff_pending = true;
}
fn is_unknown_window_acknowledged(&mut self, window_snapshot: &window::WindowSnapshot) -> bool {
@@ -503,7 +513,14 @@ impl App {
return true;
}
if self.acknowledged_unknown_window_handoff_pending
&& is_post_dismissal_app_foreground(window_snapshot)
{
return true;
}
self.acknowledged_unknown_window = None;
self.acknowledged_unknown_window_handoff_pending = false;
false
}
@@ -628,6 +645,13 @@ fn should_prompt_for_unknown_window(
!(title_matches || class_matches)
}
fn is_post_dismissal_app_foreground(window_snapshot: &window::WindowSnapshot) -> bool {
window_snapshot
.title
.to_lowercase()
.contains(&constants::APP_TITLE.to_lowercase())
}
fn usable_title_evidence(window_snapshot: &window::WindowSnapshot) -> bool {
let title = window_snapshot.title.trim();
!title.is_empty()
@@ -843,7 +867,13 @@ fn handle_key_press(app: &mut App, key_code: KeyCode) -> Result<()> {
}
fn update_session_stats(app: &mut App) -> window::WindowSnapshot {
let window_snapshot = window::get_snapshot();
update_session_stats_with_snapshot(app, window::get_snapshot())
}
fn update_session_stats_with_snapshot(
app: &mut App,
window_snapshot: window::WindowSnapshot,
) -> window::WindowSnapshot {
app.evidence_health = window_snapshot.health.clone();
let window_title = if app.state == State::Paused {
constants::PAUSED.to_string()
@@ -1424,7 +1454,8 @@ mod tests {
assert_eq!(app.state, State::InProgress);
assert!(app.is_unknown_window_acknowledged(&prompted_unknown));
assert!(!app.is_unknown_window_acknowledged(&dismissal_foreground));
assert!(app.is_unknown_window_acknowledged(&dismissal_foreground));
assert!(app.is_unknown_window_acknowledged(&prompted_unknown));
let records = EventLog::open(&path).unwrap().records().unwrap();
let violation = records
@@ -1451,6 +1482,78 @@ mod tests {
let _ = fs::remove_file(path);
}
#[test]
fn post_dismissal_app_foreground_does_not_prompt_or_clear_triggering_acknowledgement() {
let path = unique_event_log_path("violation-prompt-app-handoff");
let mut app = App::new_with_event_log_path(&path).unwrap();
let prompted_unknown = window::WindowSnapshot {
title: "Browser - unrelated".to_string(),
class: Some("firefox".to_string()),
health: antidrift::domain::EvidenceHealth::Available,
};
let dismissal_foreground = window::WindowSnapshot {
title: "AntiDrift".to_string(),
class: Some("terminal".to_string()),
health: antidrift::domain::EvidenceHealth::Available,
};
let different_unknown = window::WindowSnapshot {
title: "Chat - unrelated".to_string(),
class: Some("chat".to_string()),
health: antidrift::domain::EvidenceHealth::Available,
};
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();
let commitment_id = app
.session_controller
.active_commitment()
.unwrap()
.id
.clone();
let mut log = EventLog::open(&path).unwrap();
log.append(
EventType::PolicyApplied,
RuntimeState::Active,
Some(commitment_id.clone()),
serde_json::json!({
"schema_version": antidrift::domain::POLICY_SCHEMA_VERSION,
"policy_id": "policy-app-handoff-test",
"commitment_id": commitment_id,
"runtime_state": "active",
"enforcement_level": "warn",
"allowed_context": {
"window_classes": [],
"window_title_substrings": ["Editor"],
"domains": [],
"repos": [],
"commands": [],
},
}),
)
.unwrap();
let mut app = App::new_with_event_log_path(&path).unwrap();
app.enter_violation_prompt(&prompted_unknown);
app.violation_dismissal_reason = "Needed to inspect docs".to_string();
app.dismiss_violation_if_valid().unwrap();
app.tick_active_session_with_snapshot(dismissal_foreground)
.unwrap();
assert_eq!(app.state, State::InProgress);
assert!(app.is_unknown_window_acknowledged(&prompted_unknown));
app.tick_active_session_with_snapshot(different_unknown)
.unwrap();
assert_eq!(app.state, State::ViolationPrompt);
let _ = fs::remove_file(path);
}
#[test]
fn rating_completion_returns_controller_to_planning_for_next_session() {
let path = unique_event_log_path("rating-completion");