name: kicad-bridge-dev description: DEVELOPER skill for building/debugging the Adom Desktop KiCad bridge. NOT shipped to users (source-only). Read before editing bridge code — architecture, the 4 control surfaces, how to add a verb, and every fresh-machine gotcha this bridge has hit. Trigger words — kicad bridge dev, edit kicad bridge, add kicad verb, kicad bridge architecture, debug kicad bridge, kicad control surfaces, kicad fresh machine.

kicad-bridge-dev

Developer notes for the adom-desktop-kicad-bridge. Source-only (belongs in the repo, never shipped to a user install). Read adom-desktop-bridge-sdk first for the AD-core-vs-author boundary.

What it is

A reverse bridge AD spawns on the user's laptop (spawn.kind: python, entrypoint: server.py, port: 0). It adds the kicad_* verb namespace. AD provisions Python (Runtime contract v1.9.63+) and passes ADOM_BIND_HOST — bind that, never 0.0.0.0.

The 4 control surfaces (how a verb reaches KiCad)

  1. kicad-cli (subprocesskicad-cli.exe) — headless: pcb drc, sch erc, pcb/sch export (gerber/pdf/svg/step/bom), pcb/sch upgrade. No GUI. Handlers: run_drc, run_erc, lint_*, export.py, format_upgrade, bom.py, netlist.py.
  2. KiCad IPC API (kipy) — KiCad 9+. Board/schematic introspection + automation (e.g. place_footprint confirms via kipy get_footprints()). Expands with KiCad 11.
  3. Embedded-Python reverse bridge (plugin_payload/adom_bridge.py) — usercustomize.py auto-injects at KiCad's USER_SITE → runs an HTTP server INSIDE each KiCad process, writes a %TEMP% discovery file; container-side handlers/bridge_client.py POSTs JSON-RPC to /rpc, dispatched onto KiCad's wx UI thread via wx.CallAfter. Methods: ping, list_frames, get_menu_ids, wm_command. Verbs: bridge_status, bridge_call.
  4. Win32 / UIA / SendKeys (handlers/kicad_ui.py, ctypes/user32) — the last resort: WM_COMMAND (open editors), SendInput/keybd_event (alt+3, ctrl+s), PrintWindow+EnumWindows (screenshots), GetWindowRect. For opening the 3D viewer, screenshots, clicks, keystrokes, window management.

Adding a verb (3 places, keep in sync)

  1. Write handle_<verb> in a handler; return a dict with success + a rich _hint (mandatory — the AI reads verb OUTPUT, not this skill).
  2. Register in server.py COMMAND_HANDLERS.
  3. Add kicad_<verb> to bridge.json verbs[] AND an entry (summary/hint/related/pitfalls) to _VERB_CATALOG in server.py (the kicad_describe catalog). A test checks these three stay aligned.

Dispatcher special cases (dispatch_command): readiness, describe, check_for_updates return BEFORE the plugin auto-install + the KiCad-not-installed pre-check (they must work with KiCad absent). upgrade also bypasses that pre-check (it INSTALLS KiCad — do not gate the installer behind "KiCad not installed") and refreshes the detection cache on success.

Fresh-machine gotchas (all hard-won, all fixed — don't regress them)

  • Install scope is mandatory. KiCad 10's NsisMultiUser installer: bare /S = rc=666660 (invalid params). Pass /allusers /S (elevated) or /currentuser /S (per-user, %LOCALAPPDATA%\Programs\KiCad, no UAC). See handlers/upgrade.py.
  • TLS on the portable Python. AD's provisioned Python has no CA bundle → CERTIFICATE_VERIFY_FAILED. We bundle certs/cacert.pem (certifi) and load it in _ssl_context(). Any new https fetch must use that context.
  • Truncated downloads read as EOF and pass MZ/size checks, then NSIS fails at install. upgrade.py HEADs Content-Length and refuses/deletes a size-mismatched file.
  • Config doesn't exist pre-first-launch. %APPDATA%/kicad/<ver>/ (lib tables) is created on KiCad's first GUI run. kicad_detect.ensure_win_user_config() bootstraps it (seeds tables from the install template) so install_library/_footprint work on a never-launched KiCad.
  • Detection cache is built once at startup. After kicad_upgrade installs KiCad, dispatch_command re-runs detect_all_kicad_versions() so subsequent verbs see it without a bridge restart. Detect paths include %LocalAppData%\Programs\KiCad (per-user installs).
  • Blocking dialogs stall everything. handlers/close_windows.py scans EVERY window owned by a running KiCad process (by PID — _win_scan_kicad_dialogs, reliable; title/owner matching is NOT), auto-expires benign ones (OpenGL notice), screenshots them, and returns hints. window_info self-heals by default; kicad_dismiss_dialogs {all|forceSoftwareCanvas}.
  • GPU-less hosts. No OpenGL → the notice pops and pcbnew/3D may not render. On expiring that notice we persist graphics.canvas_type=2 (Cairo) to kicad_common.json. NOTE: a truly minimal VM (Hyper-V, no GPU) may still not render the heavy editors even in Cairo — that's the hardware. Validate GUI/screenshot work on a real-GPU machine.

Diagnostics you can call

  • kicad_upgrade {"diagnoseOnly":true} — token elevation type + UAC EnableLUA + cached-installer/truncation facts (no install).
  • kicad_dismiss_dialogs {"debug":true} — raw process-based dialog scan (PIDs + every KiCad window + class + isDialog).

Testing on a fresh AD / VM

refresh_bridges {name:kicad} pulls the new cache; the RUNNING instance keeps its old code until it dies — bridge_kill {name:kicad} forces a fresh spawn that loads the new version and re-detects. The first verb after a kill returns a bridge_starting envelope — retry. Confirm the running version via bridge_log_read ("starting v0.9.xx").