← Commit history

docs: kicad_demo README section + record the background-input rules (window-targeted not focus-targeted) and the never-test-on-John's-machine rule

John Lauer ·3d3e314060 ·3mo ago ·parent b236e40
2 files changed +83−14
README.md+42−14
@@ -4,20 +4,6 @@ A **reverse bridge** that lets Adom Desktop (AD) — and the AI driving it — c KiCad on Windows: open and drive every editor, install symbol/footprint libraries, place parts, run DRC/ERC, export manufacturing files, and screenshot any window back to the AI. -> ### Two artifacts on this page — which one do you want?->-> | | **Release ZIP** (`adom-bridge-kicad-v*.zip`) | **Package** (`adom-wiki pkg install adom/adom-desktop-kicad-bridge`) |-> |---|---|---|-> | What it is | The **bridge runtime** — the actual Python server that drives KiCad | **Skills + docs only**, for a container/agent |-> | Who loads it | **Adom Desktop** downloads it automatically via `adom-bridge-kicad-manifest.json` | You, into a container, to teach an agent the `kicad_*` verbs |-> | Contains | code, certs, UIA scripts, board template | `SKILL.md` files (no binaries, no images) |-> | You install it manually? | **No** — AD handles it | Yes |->-> **The package is not the bridge.** Installing the package does not give you a working-> bridge; AD loading the release zip does. Both are kept deliberately small — the zip is-> auto-bundled into AD, so it ships only what the runtime needs.-- KiCad is the user's **host app**. The bridge *detects* an existing KiCad and, if none is present, *installs* one for them — it never asks the user to download or click through anything by hand. @@ -38,6 +24,48 @@ KiCad is the user's **host app**. The bridge *detects* an existing KiCad and, if **▶ [Watch the demo](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)** (48s, 🔊 narrated) — the bridge driving real KiCad on a Windows VM: **select** a footprint and **zoom** the copper in the PCB editor, **rotate** the assembled board in 3D, then tour the schematic, symbol, and footprint editors. Genuine screen recording of the live windows (zoom / select / 3D rotation), narrated section by section — not animated stills.  +---++## The demo verb — `kicad_demo`++One verb that shows the whole bridge off, built for the AI that demos Adom Desktop+during Hydrogen Desktop's install. Six beats, in the order a hardware person thinks:++| Beat | Shows |+|---|---|+| 1 | the **schematic symbol** in the user's own Symbol Editor |+| 2 | the **footprint** for that same part |+| 3 | that part in **3D** |+| 4 | a **schematic** using it (U1 + two 10k resistors) |+| 5 | the **2D board layout** |+| 6 | the finished **board in 3D** |++```bash+adom-desktop kicad_demo '{}'                      # start: prepares, warms KiCad, opens beat 1+adom-desktop kicad_demo '{"step":"footprint"}'    # ...then follow data.nextCall each time+adom-desktop kicad_demo '{"all":true}'            # bulk (non-interactive; slower than one request budget)+adom-desktop kicad_close '{"force":true}'         # tear down+```++**It narrates itself.** Every beat returns `say` (a line to speak), `pointOut`+(what's actually on screen), `window` (match it in `kicad_screenshot_all`) and+`nextCall`. One beat per call is the default precisely so the AI can talk, screenshot,+then move on.++**It ships nothing.** The two demo parts, the schematic and the board are all+generated in code, and the 3D bodies come from KiCad's *own* bundled+`3dmodels/*.3dshapes` — so the runtime zip stays small and the 3D views are real.+Parts install into the user's real `Adom` library; the project lands in+`Documents/adom-kicad-demo`.++**No KiCad? It offers to install it.** `kicad_demo` returns an offer instead of an+error; `kicad_demo '{"installKiCad": true}'` silently installs the official build+(per-user, no UAC) and then runs the tour. Watching the AI install your EDA tool is+itself a good demo beat.++**It never takes your screen.** Every window opens in the background and all input is+window-targeted (UIA / `PostMessage`), so you can keep working while it runs.+ ---  ## How it fits together
skills/kicad-bridge-dev/SKILL.md+41
@@ -19,6 +19,47 @@ A reverse bridge AD spawns on the user's laptop (`spawn.kind: python`, `entrypoi 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. +## 🚫 NON-NEGOTIABLE: input is WINDOW-targeted, never FOCUS-targeted++Opening windows in the background (below) is only half of it. **How you send input+matters just as much**, because the global input APIs force you to foreground:++| API | Goes to | Verdict |+|---|---|---|+| `SendInput`, `keybd_event`, `mouse_event`, `SendKeys` | whatever owns FOCUS | ❌ forces `SetForegroundWindow`; a stray keystroke lands in the user's app |+| `SetCursorPos` | the user's PHYSICAL mouse | ❌ never — moving someone's pointer mid-work is the rudest thing we do |+| `PostMessage(hwnd, WM_KEYDOWN/WM_CHAR/WM_LBUTTONDOWN…)` | ONE window's queue | ✅ default — no focus, works while KiCad is buried |+| `SendMessage(hwnd, WM_SETTEXT, …)` | ONE control | ✅ set a text box without focus |+| UIA `ValuePattern.SetValue()` / `InvokePattern.Invoke()` | the a11y tree | ✅ set text / click controls with no focus |+| plugin `WM_COMMAND` (`bridge_client`) | KiCad's own command table | ✅ best for menu actions — deterministic AND background |++**What went wrong (2026-07-20, John):** `navigate_symbol.ps1` did an Alt-key+foreground trick → `SetForegroundWindow` → `SetCursorPos` + `mouse_event` →+`SendKeys` typing, just to put a name in the search box. He was working across+five projects and the bridge repeatedly seized his screen and typed over him:+*"when you take over my screen to type into the kicad window it's crazy rude!!!"*+It now uses UIA `ValuePattern.SetValue` (WM_SETTEXT fallback) + posted Enter.+`kicad_send_key`/`kicad_click` post to the target hwnd by default.++**Rules for any new verb:**+1. Reach for the plugin `WM_COMMAND` pathway first; then UIA; then `PostMessage`.+2. Never call `SetForegroundWindow`, `SetCursorPos`, `SendKeys` or `SendInput` on+   a default path. Gate them behind an explicit caller opt-in+   (`allowForeground` / `allowFocusSteal` / `allowCursorClick`), default OFF.+3. When something genuinely can't work in the background, **don't just do it** —+   return `errorCode: "foreground_required"` with a hint telling the AI to warn+   the user ("I need your screen for ~5 seconds") via `notify_user` and retry+   with the opt-in. `_foreground_warning()` in `handlers/kicad_ui.py` builds it.+4. Known limit: posted messages don't set global modifier state, so **chords+   (Ctrl+Shift+E) are unreliable via PostMessage** — use the plugin pathway.++## 🧪 And NEVER test on John's working machine++Test on a VM (ADOMGPU / a Hyper-V box), not `AdomLapper`. Even background windows+plus library installs on his primary box are disruptive, and a foregrounding bug+lands directly on top of whatever he's doing. If a VM isn't available, ASK before+driving his laptop.+ ## 🪟 NON-NEGOTIABLE: KiCad windows ALWAYS open in the BACKGROUND  **The bridge must NEVER steal the user's focus or interrupt their work.** A user may