← Commit history

1.0.31: NATIVE (Rust) build. 1.0.31: the foreground toast is debounced (foregroundToastMinutes setting, default 10, 0 = off); the on-screen caption on the driven box is unchanged.

John Lauer ·818429918a ·1mo ago ·parent 46293d1
4 files changed +34−8
rust/Cargo.lock+3−3
@@ -244,7 +244,7 @@ dependencies = [  [[package]] name = "kicad-bridge"-version = "1.0.30"+version = "1.0.31" dependencies = [  "kicad-core",  "kicad-platform",@@ -255,7 +255,7 @@ dependencies = [  [[package]] name = "kicad-core"-version = "1.0.30"+version = "1.0.31" dependencies = [  "kicad-ipc-rs",  "kicad-platform",@@ -281,7 +281,7 @@ dependencies = [  [[package]] name = "kicad-platform"-version = "1.0.30"+version = "1.0.31" dependencies = [  "serde",  "serde_json",
rust/Cargo.toml+1−1
@@ -3,7 +3,7 @@ resolver = "2" members = ["crates/*"]  [workspace.package]-version = "1.0.30"+version = "1.0.31" edition = "2021" license = "MIT" publish = false
rust/crates/kicad-bridge/src/verbs_libraries.rs+1−1
@@ -62,7 +62,7 @@ pub static VERBS: &[Verb] = &[         mechanism: Mechanism::File, risk: "config", timeout_sec: 10,         input: "{\"overlayBadges\"?: bool, \"activityIndicator\"?: \"progress|flash|both|none\", \"restoreDefaults\"?: bool}",         example: "kicad_set_settings {\"overlayBadges\": false}",-        hint: "kicad_set_settings {\"overlayBadges\": false}.",+        hint: "kicad_set_settings {\"overlayBadges\": false}. foregroundToastMinutes (default 10) is how often a foreground the calling AI asked for may announce itself with a toast, which ab mirrors to the user's other boxes; 0 turns the toast off, the 3 s on-screen caption on the driven box stays.",         related: &["kicad_get_settings"],         pitfalls: &["live re-badging of open KiCad windows arrives with the window verbs (phase 3); the setting persists now"],     },
rust/crates/kicad-bridge/src/verbs_windows.rs+29−3
@@ -804,8 +804,14 @@ fn bring_to_user(hwnd: u64, reason: &str) -> bool {     if reason.trim().is_empty() {         return false;     }-    let msg = format!("KiCad bridge needs your screen for ~3s: {reason}. Background methods were exhausted; this was explicitly allowed by the calling AI (foreground:true).");-    let _ = ab::notify_user("Taking the foreground briefly", &msg, "warning");+    // The toast is debounced (settings.foregroundToastMinutes, 0 = never): ab mirrors it to+    // the user's other boxes, and one per verb from a busy thread was worse than the+    // foreground itself. The caption is local to the driven box and always shown.+    let toast = foreground_toast_due();+    if toast {+        let msg = format!("KiCad bridge needs your screen for ~3s: {reason}. Background methods were exhausted; this was explicitly allowed by the calling AI (foreground:true).");+        let _ = ab::notify_user("Taking the foreground briefly", &msg, "warning");+    }     let _ = ab::desktop_caption("kicad-foreground", reason, 3000, "Announce the foreground the calling AI asked for before the KiCad window moves");     sleep_ms(1200); // let the caption render BEFORE the window moves     native().etiquette_sanction(hwnd, 20.0); // the loop must not bounce a foreground the AI asked for@@ -814,10 +820,30 @@ fn bring_to_user(hwnd: u64, reason: &str) -> bool {         Err(e) if is_not_impl(&e) => ab::desktop_bring_to_front(hwnd, reason).is_ok(),         Err(_) => false,     };-    record_focus_event(json!({"event": "user-foregrounded", "hwnd": hwnd, "reason": reason, "ok": ok, "at": kicad_common::now_utc_iso()}));+    record_focus_event(json!({"event": "user-foregrounded", "hwnd": hwnd, "reason": reason, "ok": ok, "toast": toast, "at": kicad_common::now_utc_iso()}));     ok } +static LAST_FOREGROUND_TOAST: Mutex<Option<Instant>> = Mutex::new(None);++/// One foreground toast per `foregroundToastMinutes`; 0 means never. Records the time+/// when it says yes.+fn foreground_toast_due() -> bool {+    let minutes = kicad_core::settings::get_settings().get("foregroundToastMinutes").and_then(Value::as_u64).unwrap_or(10);+    if minutes == 0 {+        return false;+    }+    let mut last = match LAST_FOREGROUND_TOAST.lock() {+        Ok(g) => g,+        Err(p) => p.into_inner(),+    };+    let due = last.map(|t| t.elapsed() >= Duration::from_secs(minutes * 60)).unwrap_or(true);+    if due {+        *last = Some(Instant::now());+    }+    due+}+ // ── Dialogs (close_windows.py) ────────────────────────────────────────────────  #[derive(Clone, Debug, Default)]