← Commit history

v0.9.157: session sanitize - bridge projects are scrubbed from KiCad restore-on-launch (kicad.json system.open_projects) so a user launch never auto-opens OUR demo and hits the project lock warning; project paths recorded in the ownership ledger

John Lauer ·ffb3b6f0d1 ·1mo ago ·parent 1e76c9f
8 files changed +112−8
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.156+0.9.157
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.156",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.156/adom-bridge-kicad-v0.9.156.zip",-  "sha256": "504ef46dd51e4e7addcc45f4de7cb99a58bae307c827f94640db98d05ac9ed5a",-  "size": 437802,+  "version": "0.9.157",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.157/adom-bridge-kicad-v0.9.157.zip",+  "sha256": "4ad644794c75f42deb992d2e34bd938b27d6cf315c1a002130fe5c4155a13a6e",+  "size": 438915,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-17T21:17:20Z",+  "released_at": "2026-08-17T21:29:36Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
adom-bridge-kicad-v0.9.157.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.156",+  "version": "0.9.157",   "description": "Reverse bridge for KiCad \u2014 board/schematic introspection, lint via kicad-cli, plugin install, multi-instance probe, in-process DRC.",   "homepage": "https://wiki.adom.inc/adom/adom-bridge",   "author": "Adom Inc.",
handlers/demo.py+5
@@ -300,6 +300,11 @@ def _prepare(kicad_info: dict) -> dict:     (d / "adom-demo.kicad_pro").write_text('{"board":{},"meta":{"filename":"adom-demo.kicad_pro","version":1},"schematic":{},"sheets":[]}', encoding="utf-8")     out["projectDir"] = str(d)     out["project"] = str(d / "adom-demo.kicad_pro")+    try:+        from handlers.win_focus import register_owned_project+        register_owned_project(out["project"])+    except Exception:+        pass     out["schematic"] = str(d / "adom-demo.kicad_sch")     out["board"] = str(d / "adom-demo.kicad_pcb")     return out
handlers/make_part_project.py+6
@@ -209,6 +209,12 @@ def handle_make_part_project(kicad_info: dict, args: dict) -> dict:             p = out_dir / name             p.write_text(content, encoding="utf-8")             files[name.rsplit(".", 1)[-1]] = str(p)+            if name.endswith(".kicad_pro"):+                try:+                    from handlers.win_focus import register_owned_project+                    register_owned_project(str(p))+                except Exception:+                    pass         # stale single-instance locks from a previous open of the same project         for lck in out_dir.glob("*.lck"):             try:
handlers/win_focus.py+88−1
@@ -439,7 +439,8 @@ def _save_ledger() -> None:                     wins.append({"hwnd": int(h), "pid": _pid_of_hwnd(h)})             except Exception:                 pass-        _ledger_path().write_text(_json.dumps({"pids": sorted(live), "windows": wins}))+        _ledger_path().write_text(_json.dumps({"pids": sorted(live), "windows": wins,+                                               "projects": sorted(_owned_projects)}))     except Exception:         pass @@ -468,6 +469,11 @@ def load_owned_ledger() -> None:                 _owned_pids.add(int(p))         except Exception:             pass+    for p in data.get("projects", []):+        try:+            _owned_projects.add(str(p))+        except Exception:+            pass   def register_owned_pid(pid) -> None:@@ -549,6 +555,82 @@ def is_bridge_window(hwnd) -> bool:         return False  +_owned_projects: set = set()+_last_sanitize = 0.0+++def register_owned_project(path) -> None:+    """Project-creating verbs (make_part_project, demo prep) record their+    .kicad_pro here so session sanitizing knows exactly what is OURS."""+    try:+        if path:+            _owned_projects.add(str(path))+            _save_ledger()+    except Exception:+        pass+++def _any_kicad_process_running() -> bool:+    try:+        pm = _parent_pid_map()+        for pid in pm:+            if _pid_is_kicad(pid):+                return True+    except Exception:+        pass+    return False+++def _is_bridge_project_path(p: str) -> bool:+    q = (p or "").replace("/", "\\").lower()+    if any(q == str(o).replace("/", "\\").lower() for o in _owned_projects):+        return True+    # legacy pollution predating the project ledger: the bridge's own demo+    # naming (adom-* and in-<part>-demo under Documents)+    import re as _re+    return bool(_re.search(r"\\(adom-[a-z0-9-]+|in-[a-z0-9]+-demo)\\", q))+++def sanitize_kicad_session() -> int:+    """Remove BRIDGE projects from KiCad's restore-on-launch list (John,+    2026-08-17: every launch of HIS KiCad auto-opened OUR adom-demo and hit the+    project lock warning). kicad.json system.open_projects is what the PM+    restores at startup; KiCad rewrites it at exit, so edits only stick while+    NO KiCad runs — the sweep retries until that window opens. file_history is+    left alone (honest recents). Returns entries removed."""+    global _last_sanitize+    import time as _tm+    if not IS_WINDOWS:+        return 0+    _last_sanitize = _tm.monotonic()+    if _any_kicad_process_running():+        return 0+    removed = 0+    try:+        import glob as _glob+        import json as _json+        import os as _os+        appdata = _os.environ.get("APPDATA") or ""+        for cfg in _glob.glob(_os.path.join(appdata, "kicad", "*", "kicad.json")):+            try:+                data = _json.loads(open(cfg, encoding="utf-8").read())+                sysd = data.get("system") or {}+                op = sysd.get("open_projects") or []+                keep = [p for p in op if not _is_bridge_project_path(p)]+                if len(keep) != len(op):+                    sysd["open_projects"] = keep+                    data["system"] = sysd+                    open(cfg, "w", encoding="utf-8").write(_json.dumps(data, indent=2))+                    removed += len(op) - len(keep)+                    print(f"[session] scrubbed {len(op)-len(keep)} bridge project(s) "+                          f"from {cfg}", file=sys.stderr)+            except Exception:+                pass+    except Exception:+        pass+    return removed++ def heal_offscreen_windows() -> int:     """Boot repair (John, 2026-08-17: "when the user goes to open kicad normally     ... it starts way offscreen and kicad doesn't fix itself"): if a parked@@ -663,6 +745,11 @@ def _start_park_sweep() -> None:                         continue                     _t.sleep(0.1)                 else:+                    if _t.monotonic() - _last_sanitize > 60:+                        try:+                            sanitize_kicad_session()+                        except Exception:+                            pass                     _t.sleep(0.25)             except Exception:                 try:
server.py+6
@@ -1110,6 +1110,12 @@ def main():         atexit.register(win_focus.unpark_all)     except Exception:         pass+    # 0.9.157: scrub bridge projects from KiCad's restore-on-launch list (only+    # sticks while no KiCad runs; the sweep retries until that window opens).+    try:+        win_focus.sanitize_kicad_session()+    except Exception:+        pass     try:         from handlers.win_taskbar import badge_existing_on_boot         badge_existing_on_boot()