Fix post-dismissal prompt handoff
This commit is contained in:
+106
-3
@@ -133,6 +133,7 @@ struct App {
|
|||||||
session_ratings_index: usize,
|
session_ratings_index: usize,
|
||||||
session_results: Vec<SessionResult>,
|
session_results: Vec<SessionResult>,
|
||||||
acknowledged_unknown_window: Option<WindowIdentity>,
|
acknowledged_unknown_window: Option<WindowIdentity>,
|
||||||
|
acknowledged_unknown_window_handoff_pending: bool,
|
||||||
last_tick_50ms: Instant,
|
last_tick_50ms: Instant,
|
||||||
last_tick_1s: Instant,
|
last_tick_1s: Instant,
|
||||||
}
|
}
|
||||||
@@ -223,6 +224,7 @@ impl App {
|
|||||||
session_ratings_index: 0,
|
session_ratings_index: 0,
|
||||||
session_results: Vec::new(),
|
session_results: Vec::new(),
|
||||||
acknowledged_unknown_window: None,
|
acknowledged_unknown_window: None,
|
||||||
|
acknowledged_unknown_window_handoff_pending: false,
|
||||||
last_tick_50ms: now,
|
last_tick_50ms: now,
|
||||||
last_tick_1s: now,
|
last_tick_1s: now,
|
||||||
})
|
})
|
||||||
@@ -460,9 +462,16 @@ impl App {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn tick_active_session(&mut self) -> Result<()> {
|
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();
|
let elapsed = self.session_start.elapsed();
|
||||||
self.session_remaining = self.user_duration.saturating_sub(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
|
let should_prompt = self.state == State::InProgress
|
||||||
&& self.session_controller.runtime_state() == RuntimeState::Active
|
&& self.session_controller.runtime_state() == RuntimeState::Active
|
||||||
@@ -495,6 +504,7 @@ impl App {
|
|||||||
|
|
||||||
fn acknowledge_unknown_window(&mut self, window_snapshot: &window::WindowSnapshot) {
|
fn acknowledge_unknown_window(&mut self, window_snapshot: &window::WindowSnapshot) {
|
||||||
self.acknowledged_unknown_window = Some(WindowIdentity::from(window_snapshot));
|
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 {
|
fn is_unknown_window_acknowledged(&mut self, window_snapshot: &window::WindowSnapshot) -> bool {
|
||||||
@@ -503,7 +513,14 @@ impl App {
|
|||||||
return true;
|
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 = None;
|
||||||
|
self.acknowledged_unknown_window_handoff_pending = false;
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,6 +645,13 @@ fn should_prompt_for_unknown_window(
|
|||||||
!(title_matches || class_matches)
|
!(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 {
|
fn usable_title_evidence(window_snapshot: &window::WindowSnapshot) -> bool {
|
||||||
let title = window_snapshot.title.trim();
|
let title = window_snapshot.title.trim();
|
||||||
!title.is_empty()
|
!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 {
|
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();
|
app.evidence_health = window_snapshot.health.clone();
|
||||||
let window_title = if app.state == State::Paused {
|
let window_title = if app.state == State::Paused {
|
||||||
constants::PAUSED.to_string()
|
constants::PAUSED.to_string()
|
||||||
@@ -1424,7 +1454,8 @@ mod tests {
|
|||||||
|
|
||||||
assert_eq!(app.state, State::InProgress);
|
assert_eq!(app.state, State::InProgress);
|
||||||
assert!(app.is_unknown_window_acknowledged(&prompted_unknown));
|
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 records = EventLog::open(&path).unwrap().records().unwrap();
|
||||||
let violation = records
|
let violation = records
|
||||||
@@ -1451,6 +1482,78 @@ mod tests {
|
|||||||
let _ = fs::remove_file(path);
|
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]
|
#[test]
|
||||||
fn rating_completion_returns_controller_to_planning_for_next_session() {
|
fn rating_completion_returns_controller_to_planning_for_next_session() {
|
||||||
let path = unique_event_log_path("rating-completion");
|
let path = unique_event_log_path("rating-completion");
|
||||||
|
|||||||
Reference in New Issue
Block a user