← Commit history

1.0.50: 1.0.50: the background guard (backgroundGuard) is OFF by default. John, after watching it live: 'you just are too aggressive with it and kicad acts weird when you have it on'. The bridge now moves no KiCad window unless the user turns the guard on (dashboard: Keep KiCad in the background, or kicad_set_settings); its focus log still records what the guard would have done. Wiki #114 tracks the work before it may be on by default again. Also: settings saves now persist only what someone chose; before, every save wrote every default into the file, so a machine that had saved any setting would never see a changed default. A stored backgroundGuard true written that way is ignored; a deliberate one is kept. Includes 1.0.49: a failed footprint load no longer blames a stock library on being registered after launch.

John Lauer ·cad51acbb4 ·1mo ago ·parent 3a88842
2 files changed +69−9
rust/crates/kicad-core/src/settings.rs+68−8
@@ -27,8 +27,10 @@ pub fn defaults() -> Vec<(&'static str, Value)> {         ("foregroundToastMinutes", json!(10)),         // The A/B switch John asked for (2026-09-21): true = a KiCad window the bridge is opening is         // sent behind the user's window; false = the bridge never moves or pushes any KiCad window,-        // so whatever KiCad does with the foreground is what the user sees.-        ("backgroundGuard", json!(true)),+        // so whatever KiCad does with the foreground is what the user sees. DEFAULT OFF from 1.0.50+        // (John, 2026-09-21: "you just are too aggressive with it and kicad acts weird when you have+        // it on"); wiki #114 tracks the work before it may be on by default again.+        ("backgroundGuard", json!(false)),     ] } @@ -53,16 +55,29 @@ pub fn settings_path() -> PathBuf { /// Python `dict.update`). An unreadable or invalid file yields the defaults. pub fn get_settings() -> Map<String, Value> {     let mut out = defaults_map();-    if let Ok(text) = std::fs::read_to_string(settings_path()) {-        if let Ok(Value::Object(m)) = serde_json::from_str::<Value>(&text) {-            for (k, v) in m {-                out.insert(k, v);-            }+    let file = read_file_map();+    let chosen_guard = file.get(GUARD_CHOSEN).and_then(Value::as_bool).unwrap_or(false);+    for (k, v) in file {+        if k == GUARD_CHOSEN || (k == "backgroundGuard" && !chosen_guard) {+            continue;         }+        out.insert(k, v);     }     out } +/// Marker written beside `backgroundGuard` when someone sets it on purpose. Before 1.0.50 every+/// save wrote EVERY default into the file, so a stored `backgroundGuard:true` may be nobody's+/// choice; without this marker it is ignored and the (now off) default applies.+const GUARD_CHOSEN: &str = "backgroundGuardChosen";++fn read_file_map() -> Map<String, Value> {+    std::fs::read_to_string(settings_path()).ok()+        .and_then(|t| serde_json::from_str::<Value>(&t).ok())+        .and_then(|v| match v { Value::Object(m) => Some(m), _ => None })+        .unwrap_or_default()+}+ /// Type a raw value by its default's type. The first Python cut bool()'d everything, /// so `activityIndicator:"flash"` persisted as true; this is the fix carried over. pub fn coerce(key: &str, raw: &Value) -> Result<Value, String> {@@ -138,7 +153,27 @@ pub fn set_settings(args: &Value) -> Value {                       "output": "no settings changed",                       "_hint": format!("Settable keys: {}.", keys.join(", "))});     }-    let text = serde_json::to_string_pretty(&Value::Object(s.clone())).unwrap_or_default();+    // Persist only what someone CHOSE: the keys already in the file plus the ones set now.+    // Writing the whole map (as before 1.0.50) froze every default into the file, so changing a+    // default in a release reached nobody who had ever saved a setting.+    let mut file = read_file_map();+    if args.get("restoreDefaults").and_then(truthy).unwrap_or(false) {+        for (k, _) in defaults() {+            file.remove(k);+        }+        file.remove(GUARD_CHOSEN);+    }+    for (k, _) in defaults() {+        if args.get(k).is_some() {+            if let Some(v) = s.get(k) {+                file.insert(k.to_string(), v.clone());+            }+            if k == "backgroundGuard" {+                file.insert(GUARD_CHOSEN.to_string(), json!(true));+            }+        }+    }+    let text = serde_json::to_string_pretty(&Value::Object(file)).unwrap_or_default();     if let Err(e) = crate::libraries::write_atomic(&settings_path(), text.as_bytes()) {         return json!({"success": false, "error": format!("could not persist settings: {e}")});     }@@ -180,6 +215,31 @@ fn python_repr(m: &Map<String, Value>) -> String { mod tests {     use super::*; +    #[test]+    fn only_chosen_settings_are_saved_and_an_unchosen_guard_does_not_count() {+        let dir = std::env::temp_dir().join(format!("kb-settings-{}", std::process::id()));+        let _ = std::fs::remove_dir_all(&dir);+        std::fs::create_dir_all(dir.join(".adom")).unwrap();+        let old = std::env::var_os("USERPROFILE");+        std::env::set_var("USERPROFILE", &dir);+        // a file the old save-everything code wrote: guard true, nobody chose it+        std::fs::write(settings_path(), r#"{"overlayBadges":true,"activityIndicator":"progress","foregroundToastMinutes":10,"backgroundGuard":true}"#).unwrap();+        assert_eq!(get_settings().get("backgroundGuard"), Some(&json!(false)));+        // changing another setting does not freeze the guard into the file+        set_settings(&json!({"foregroundToastMinutes": 0}));+        let f: Value = serde_json::from_str(&std::fs::read_to_string(settings_path()).unwrap()).unwrap();+        assert_eq!(f["foregroundToastMinutes"], json!(0));+        assert!(f.get(GUARD_CHOSEN).is_none());+        // someone turns it on on purpose: it sticks+        set_settings(&json!({"backgroundGuard": true}));+        assert_eq!(get_settings().get("backgroundGuard"), Some(&json!(true)));+        // restore defaults: back to off, marker gone+        set_settings(&json!({"restoreDefaults": true}));+        assert_eq!(get_settings().get("backgroundGuard"), Some(&json!(false)));+        match old { Some(v) => std::env::set_var("USERPROFILE", v), None => std::env::remove_var("USERPROFILE") }+        let _ = std::fs::remove_dir_all(&dir);+    }+     #[test]     fn coerce_types_by_default() {         assert_eq!(coerce("overlayBadges", &json!("off")).unwrap(), json!(false));
rust/crates/kicad-platform/src/win/etiquette.rs+1−1
@@ -1180,7 +1180,7 @@ pub fn set_flash_suppressed(on: bool) { }  /// The user's A/B switch (setting `backgroundGuard`, the dashboard's "Keep KiCad in the background").-static GUARD_ENABLED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(true);+static GUARD_ENABLED: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false); // default off from 1.0.50 (wiki #114)  pub fn enabled() -> bool {     GUARD_ENABLED.load(std::sync::atomic::Ordering::Relaxed)