app
KiCad - the KiCad Bridge
Public Made by Adomby adom
Reference implementation of the KiCad bridge: multi-instance Python server, forward path via kicad-cli, reverse path via in-process plugin. Most complex of the three bundled bridges.
← Commit history
test stealth mode: keep KiCad behind the user's window during GUI phases (re-raise user's window, not minimize); documented in background+test dev skills
3 files changed
+89−2
skills/kicad-bridge-background/SKILL.md+38@@ -96,3 +96,41 @@ John mid-work (missing from the dispatcher focus set); 2026-08-14 the first full verb-suite run took over his foreground repeatedly while he typed in another AI thread — the old guard was too short for 3D-render self-raises and fought deliberate app switches.+++## Keeping KiCad in the background WHILE TESTING (John, 2026-08-14)++The runtime focus-guardian holds for a single verb's guard window (20-45s). A+TEST run fires dozens of GUI verbs back-to-back over many minutes, so there are+gaps between guarded verbs where a late async self-raise (3D render completion)+or a fresh window can surface on the user's screen — the guardian for the+PREVIOUS verb has already expired. The user felt this: KiCad windows popping up+mid-work during a suite run.++**The rule: the windows do NOT need to be minimized or moved — they just must+stay BEHIND the user's active window.** (John, verbatim: "it doesn't have to be+minimized, it just has to be in the background.")++**Mechanism the suite uses (`--no-stealth` to disable):**+- At the start of the GUI phase, capture the user's current top window+ (`desktop_list_windows` → the `z:0` entry, only if it is NOT a KiCad window).+- After EVERY GUI verb, `_keep_background()`: if a KiCad window is now on top,+ `desktop_raise_window` the user's window back over it. If the user has since+ switched to a different non-KiCad window, ADOPT that as the new one to protect+ — never fight the user's own window choices (same principle as the runtime+ guardian).+- This RAISES the user's own window (returning focus to them), it never+ minimizes/moves KiCad. Screenshots still work (the windows are just lower in+ z-order, fully rendered).++**Rejected alternatives and why:**+- *Minimize KiCad between verbs* — screenshots of a minimized window BLANK+ (PrintWindow returns a full-screen fallback rect), and it's more disruptive+ than the user asked for.+- *Park windows off-screen (x:-4000)* — screenshots DO work off-screen (verified),+ but it's more than the user wanted and complicates dependent verbs. Kept as a+ documented fallback if a future need is "never visible at all", not the default.++The passive `fg_assert` (fail the verb if KiCad is z:0) still runs as the+regression check; `_keep_background()` is the active remedy that keeps the+user's screen clean during the run.
skills/kicad-bridge-test/SKILL.md+6−2@@ -75,8 +75,12 @@ matrix. Any live verb that is neither tested nor on the explicit skip list - Uses `--ai-thread "kicad-bridge daily-test"` so the box's Activity Log attributes every action. -Prefer running while the box is idle anyway: GUI phases open ~6 KiCad windows-in the background, which is churn the user can feel in the taskbar.+GUI phases open ~6 KiCad windows. By default the suite runs in STEALTH: it+captures the user's top window at the start and re-raises it after every GUI+verb, so KiCad stays BEHIND the user's work the whole run (not minimized, not+moved — just never on top). Pass `--no-stealth` to disable. Details + the+rejected alternatives (minimize/off-screen) are in the kicad-bridge-background+skill. ## Daily automation (INSTALLED on gallia, 2026-08-14)
skills/kicad-bridge-test/run_verb_tests.py+45@@ -211,6 +211,8 @@ def main(): ap.add_argument("--only", default="", help="comma-separated verb subset") ap.add_argument("--skip-gui", action="store_true", help="phases 1-2 only") ap.add_argument("--full", action="store_true", help="run all six demo beats")+ ap.add_argument("--no-stealth", action="store_true",+ help="don't keep KiCad behind the user's window during GUI phases") ap.add_argument("--out", default="/tmp/kicad-verbtest") a = ap.parse_args() @@ -279,6 +281,48 @@ def main(): fg_violations = [] + # STEALTH (John, 2026-08-14: "keep the kicad windows in the background when+ # you test — it doesn't have to be minimized, just background"). Capture the+ # window the user has on top RIGHT NOW; after every GUI verb we re-raise it,+ # which pushes any KiCad window that surfaced back BEHIND the user's work.+ # Not minimized, not moved — just never on top of what the user is doing.+ def _current_top():+ r = ab(a.target, "desktop_list_windows", {}, timeout=30)+ try:+ inner = json.loads(r["output"]) if isinstance(r.get("output"), str) else r+ ws = (inner.get("data") or inner).get("windows") or []+ top = next((x for x in ws if x.get("z") == 0), None)+ return top or {}+ except Exception:+ return {}++ _kw = ("KiCad", "PCB Editor", "Schematic Editor", "Symbol Editor",+ "Footprint Editor", "3D Viewer")+ _user_win = {}+ if not a.skip_gui and not a.no_stealth:+ w = _current_top()+ # only adopt a NON-KiCad window as the one to protect+ if w and not any(k in (w.get("title") or "") for k in _kw):+ _user_win = w+ print(f"[stealth] keeping KiCad behind your window: {(_user_win.get('title') or '')[:50]}")++ def _keep_background():+ """Re-raise the user's window so KiCad sits behind it. If the user has+ since switched to another (non-KiCad) window, adopt THAT — never fight+ the user's own choices."""+ nonlocal _user_win+ if a.skip_gui or a.no_stealth:+ return+ top = _current_top()+ tt = top.get("title") or ""+ if top and not any(k in tt for k in _kw):+ _user_win = top # user moved on; protect their new window+ return # already on top — nothing to do+ # KiCad is on top: raise the user's window back over it+ hw = _user_win.get("hwnd")+ if hw:+ ab(a.target, "desktop_raise_window", {"hwnd": hw}, timeout=20)+ def fg_assert(verb): """After every GUI verb: the OS foreground window must NOT be KiCad. This is the enforcement of the ALWAYS-background contract — a verb that@@ -301,6 +345,7 @@ def main(): print(f" !! FOREGROUND STEAL after kicad_{verb}: {title[:60]}") except Exception: pass+ _keep_background() # push KiCad behind the user's window either way def run(verb, spec): if only and verb not in only: