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
page.json+5−3@@ -4,7 +4,7 @@ "slug": "kicad-bridge", "title": "KiCad - the KiCad Bridge", "brief": "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.",- "version": "1.0.111",+ "version": "1.0.114", "tags": [ "kicad", "pcb",@@ -53,7 +53,7 @@ "install_hint": "", "version_cmd": "" },- "readme": "# Adom Desktop \u2014 KiCad Bridge\n\nA **reverse bridge** that lets Adom Desktop (AD) \u2014 and the AI driving it \u2014 control the user's own\nKiCad on Windows: open and drive every editor, install symbol/footprint libraries, place parts, run\nDRC/ERC, export manufacturing files, and screenshot any window back to the AI.\n\nKiCad is the user's **host app**. The bridge *detects* an existing KiCad and, if none is present,\n*installs* one for them \u2014 it never asks the user to download or click through anything by hand.\n\n- **Page / docs:** https://wiki.adom.inc/adom/adom-desktop-kicad-bridge\n- **Bridge SDK:** https://wiki.adom.inc/adom/adom-desktop-bridges\n- **Verb namespace:** `kicad_*` \u00b7 **Status verb:** `kicad_bridge_status` \u00b7 **Health:** `GET /status`\n\n> Every screenshot in this README was captured on **ADOMBASELINE**, a stock Hyper-V VM with **no\n> GPU**, driven end-to-end through the bridge \u2014 install \u2192 libraries \u2192 editors \u2192 2D \u2192 3D. If it\n> renders there, it renders on a real laptop.\n\n---\n\n## Demo \u2014 narrated live editor tour (real screen recording)\n\n[](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)\n\n**\u25b6 [Watch the demo](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)** (48s, \ud83d\udd0a narrated) \u2014 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 \u2014 not animated stills.\n\n\n---\n\n## The demo verb \u2014 `kicad_demo`\n\nOne verb that shows the whole bridge off, built for the AI that demos Adom Desktop\nduring Hydrogen Desktop's install. Six beats, in the order a hardware person thinks:\n\n| Beat | Shows |\n|---|---|\n| 1 | the **schematic symbol** in the user's own Symbol Editor |\n| 2 | the **footprint** for that same part |\n| 3 | that part in **3D** |\n| 4 | a **schematic** using it (U1 + two 10k resistors) |\n| 5 | the **2D board layout** |\n| 6 | the finished **board in 3D** |\n\n```bash\nadom-desktop kicad_demo '{}' # start: prepares, warms KiCad, opens beat 1\nadom-desktop kicad_demo '{\"step\":\"footprint\"}' # ...then follow data.nextCall each time\nadom-desktop kicad_demo '{\"all\":true}' # bulk (non-interactive; slower than one request budget)\nadom-desktop kicad_close '{\"force\":true}' # tear down\n```\n\n**It narrates itself.** Every beat returns `say` (a line to speak), `pointOut`\n(what's actually on screen), `window` (match it in `kicad_screenshot_all`) and\n`nextCall`. One beat per call is the default precisely so the AI can talk, screenshot,\nthen move on.\n\n**It ships nothing.** The two demo parts, the schematic and the board are all\ngenerated in code, and the 3D bodies come from KiCad's *own* bundled\n`3dmodels/*.3dshapes` \u2014 so the runtime zip stays small and the 3D views are real.\nParts install into the user's real `Adom` library; the project lands in\n`Documents/adom-kicad-demo`.\n\n**No KiCad? It offers to install it.** `kicad_demo` returns an offer instead of an\nerror; `kicad_demo '{\"installKiCad\": true}'` silently installs the official build\n(per-user, no UAC) and then runs the tour. Watching the AI install your EDA tool is\nitself a good demo beat.\n\n**It never takes your screen.** Every window opens in the background and all input is\nwindow-targeted (UIA / `PostMessage`), so you can keep working while it runs.\n\n---\n\n## How it fits together\n\nThe bridge is a `spawn.kind: python` process AD launches on the user's machine (`entrypoint:\nserver.py`, `port: 0` \u2014 AD picks the port and passes `ADOM_BIND_HOST`). It speaks HTTP\n(`POST /command`, `GET /status`) and reaches KiCad through **four control surfaces**, picking the\nlightest one that can do the job.\n\n\n\n| # | Surface | Used for | Where |\n|---|---------|----------|-------|\n| 1 | **kicad-cli** (subprocess) | headless DRC/ERC, gerber/pdf/svg/step/bom export, format upgrade | `handlers/export.py`, `run_drc`, `run_erc`, `lint_*` |\n| 2 | **KiCad IPC (kipy)** | board/schematic introspection, confirmed footprint placement (KiCad 9+) | `handlers/place_footprint.py` |\n| 3 | **Embedded Python** | JSON-RPC *inside* each KiCad process, dispatched on its wx UI thread | `plugin_payload/adom_bridge.py`, `handlers/bridge_client.py` |\n| 4 | **Win32 / UIA / SendKeys** | open editors, screenshots, clicks/keys, dialog handling, window management | `handlers/kicad_ui.py`, `handlers/close_windows.py` |\n\n---\n\n## The window tour\n\nEverything below was opened and captured on the GPU-less VM. Sample data is the **RP2040 breakout**\ngenerated by `tour-pack-rp2040/` (an `AdomRP2040` symbol + `QFN-56_AdomRP2040` footprint + a board).\n\n### Schematic editor \u2014 `kicad_open_schematic`\n\nThe RP2040 breakout: the MCU (U1), USB-C, a 12 MHz crystal, decoupling, and mounting pins.\n\n\n\n### Symbol editor \u2014 `kicad_open_symbol_editor`\n\nOpens straight to a symbol (`kicad_install_symbol` puts it in a user library first).\n\n\n\n### Footprint editor \u2014 `kicad_open_footprint_editor`\n\n`{\"footprintName\":\"QFN-56_AdomRP2040\",\"library\":\"AdomRP2040\"}` loads the part directly \u2014 56 pins +\nthermal pad, 7\u00d77 mm, 0.4 mm pitch, courtyard and silkscreen.\n\n\n\n### PCB editor (2D) \u2014 `kicad_open_board`\n\nThe routed breakout \u2014 copper, silkscreen, the QFN-56 land pattern, mounting holes.\n\n\n\n### 3D viewer \u2014 `kicad_open_3d_viewer {\"editor\":\"pcb\"}`\n\nThe full board in 3D \u2014 board body, the USB-C connector's 3D model, the QFN chip body, SMD parts,\nplated through-holes. **Rendered on the CPU via the software-OpenGL fallback (below); reload 1.7 s.**\n\n\n\n---\n\n## Install & upgrade \u2014 zero manual steps\n\nThe bridge never tells the user to go download KiCad. `kicad_upgrade` fetches the official installer\nand runs it silently, picking the scope automatically:\n\n- **Elevated AD** \u2192 `/allusers /S` (system-wide, `%ProgramFiles%\\KiCad`).\n- **Non-elevated AD** \u2192 `/currentuser /S` (`%LocalAppData%\\Programs\\KiCad`, **no UAC prompt**).\n\n`kicad_readiness` reports whether KiCad is installed and ready without side effects; the AI routes on\nit before offering to install. Hard-won install details (all handled for you):\n\n- KiCad 10's NsisMultiUser installer **requires** a scope flag \u2014 bare `/S` errors `rc=666660`.\n- AD's portable Python has **no CA bundle**; the bridge ships `certs/cacert.pem` and uses it for TLS.\n- Downloads are size-checked against `Content-Length` (a truncated installer otherwise fails at NSIS).\n- `%APPDATA%/kicad/<ver>/` lib tables don't exist until first launch \u2014 the bridge **bootstraps** them\n so `install_library`/`install_footprint` work on a never-opened KiCad.\n- `kicad_upgrade {\"diagnoseOnly\":true}` reports token-elevation type + `EnableLUA` without installing.\n\n---\n\n## Dialogs & error handling \u2014 the bridge clears the pointless ones\n\nKiCad throws modal dialogs that stall automation. The bridge **scans every window owned by a running\nKiCad process** (by PID \u2014 reliable, unlike title matching), auto-expires the benign ones, screenshots\nwhat it dismissed, and returns a hint so the AI can decide what (if anything) to tell the user.\n\nAuto-expired benign dialogs include:\n\n- **\"Could not use OpenGL / falling back to software rendering\"** (GPU-less hosts).\n- **\"Welcome to KiCad \u2014 starting for the first time\"** first-run wizard (dismissing accepts defaults).\n- **\"This file was created by an older version of KiCad\"** conversion notice.\n\n\n\nVerbs:\n\n- `kicad_window_info` \u2014 lists windows and **self-heals** (auto-expires benign dialogs) by default.\n- `kicad_dismiss_dialogs {\"all\":true}` \u2014 clear everything blocking; `{\"screenshot\":true}` returns\n images of each; `{\"forceSoftwareCanvas\":true}` persists Cairo canvas; `{\"debug\":true}` dumps the\n raw PID-based scan.\n- `kicad_screenshot_all` \u2014 one call returns **every** open KiCad window, so the AI can spot an error\n dialog it didn't expect and read the message.\n\n---\n\n## Software-OpenGL fallback (last resort)\n\n`kicad_enable_software_opengl` deploys Mesa's `llvmpipe` (a CPU OpenGL rasterizer) into KiCad's `bin`\nso a box with **no usable GPU** \u2014 Hyper-V, RDP, headless CI \u2014 can still render the editors and the 3D\nviewer. This is how every 3D shot above exists.\n\n> \u26a0\ufe0f It renders on the CPU and is **slow**. It is a worst-case fallback only \u2014 a real GPU (or GPU-P /\n> DDA passthrough) is vastly better. The bridge keeps it so a GPU-less box isn't a dead end; it is\n> never suggested proactively.\n\n---\n\n## Verb reference\n\nAll 41 verbs (prefix `kicad_`). Call `kicad_describe` for the live catalog with per-verb hints,\nrelated verbs, and pitfalls.\n\n**Windows & UI** \u2014 `open_board`, `open_schematic`, `open_symbol_editor`, `open_footprint_editor`,\n`open_3d_viewer`, `open_editors`, `close_symbol_editor`, `close_footprint_editor`, `close_3d_viewer`,\n`close`, `window_info`, `dismiss_dialogs`, `screenshot_all`, `send_key`, `click`, `fix_keyboard`\n\n**Libraries & parts** \u2014 `install_library`, `install_symbol`, `install_footprint`, `install_plugin`,\n`place_footprint`, `adom_library_status`\n\n**Checks & export** \u2014 `run_drc`, `run_erc`, `lint_board`, `lint_schematic`, `lint_library`,\n`format_upgrade`, `export_gerber`, `export_pdf`, `export_svg`, `export_step`, `export_bom_csv`\n\n**Detect, install & meta** \u2014 `list_versions`, `readiness`, `describe`, `enable_software_opengl`,\n`check_for_updates`, `upgrade`, `bridge_status`, `bridge_call`\n\n---\n\n## Running / dependencies\n\nAD provisions everything; there is nothing to install by hand.\n\n- **Python** \u2265 3.11 (AD provisions it) \u2014 stdlib only, plus the bundled `certs/cacert.pem`.\n- **KiCad** \u2265 7.0 (host app; `kicad_upgrade` installs it if absent).\n- **OS:** Windows (macOS/Linux detection stubs exist; the GUI surfaces are Windows-first).\n\nThe bridge binds `ADOM_BIND_HOST` on an AD-assigned port \u2014 never `0.0.0.0`. Auto-updates via\n`updateManifestUrl` in `bridge.json`.\n\n---\n\n*Developer & publish notes live in `dev-skills/` and `publish-skills/` (source-only, never shipped to\na user install).*\n",+ "readme": "# Adom Desktop — KiCad Bridge\n\nA **reverse bridge** that lets Adom Desktop (AD) — and the AI driving it — control the user's own\nKiCad on Windows: open and drive every editor, install symbol/footprint libraries, place parts, run\nDRC/ERC, export manufacturing files, and screenshot any window back to the AI.\n\nKiCad is the user's **host app**. The bridge *detects* an existing KiCad and, if none is present,\n*installs* one for them — it never asks the user to download or click through anything by hand.\n\n- **Page / docs:** https://wiki.adom.inc/adom/adom-desktop-kicad-bridge\n- **Bridge SDK:** https://wiki.adom.inc/adom/adom-desktop-bridges\n- **Verb namespace:** `kicad_*` · **Status verb:** `kicad_bridge_status` · **Health:** `GET /status`\n\n> Every screenshot in this README was captured on **ADOMBASELINE**, a stock Hyper-V VM with **no\n> GPU**, driven end-to-end through the bridge — install → libraries → editors → 2D → 3D. If it\n> renders there, it renders on a real laptop.\n\n---\n\n## Demo — narrated live editor tour (real screen recording)\n\n[](https://wiki.adom.inc/blob/app/adom-desktop-kicad-bridge/kicad-bridge-demo.mp4)\n\n**▶ [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.\n\n\n---\n\n## The demo verb — `kicad_demo`\n\nOne verb that shows the whole bridge off, built for the AI that demos Adom Desktop\nduring Hydrogen Desktop's install. Six beats, in the order a hardware person thinks:\n\n| Beat | Shows |\n|---|---|\n| 1 | the **schematic symbol** in the user's own Symbol Editor |\n| 2 | the **footprint** for that same part |\n| 3 | that part in **3D** |\n| 4 | a **schematic** using it (U1 + two 10k resistors) |\n| 5 | the **2D board layout** |\n| 6 | the finished **board in 3D** |\n\n```bash\nadom-desktop kicad_demo '{}' # start: prepares, warms KiCad, opens beat 1\nadom-desktop kicad_demo '{\"step\":\"footprint\"}' # ...then follow data.nextCall each time\nadom-desktop kicad_demo '{\"all\":true}' # bulk (non-interactive; slower than one request budget)\nadom-desktop kicad_close '{\"force\":true}' # tear down\n```\n\n**It narrates itself.** Every beat returns `say` (a line to speak), `pointOut`\n(what's actually on screen), `window` (match it in `kicad_screenshot_all`) and\n`nextCall`. One beat per call is the default precisely so the AI can talk, screenshot,\nthen move on.\n\n**It ships nothing.** The two demo parts, the schematic and the board are all\ngenerated in code, and the 3D bodies come from KiCad's *own* bundled\n`3dmodels/*.3dshapes` — so the runtime zip stays small and the 3D views are real.\nParts install into the user's real `Adom` library; the project lands in\n`Documents/adom-kicad-demo`.\n\n**No KiCad? It offers to install it.** `kicad_demo` returns an offer instead of an\nerror; `kicad_demo '{\"installKiCad\": true}'` silently installs the official build\n(per-user, no UAC) and then runs the tour. Watching the AI install your EDA tool is\nitself a good demo beat.\n\n**It never takes your screen.** Every window opens in the background and all input is\nwindow-targeted (UIA / `PostMessage`), so you can keep working while it runs.\n\n---\n\n## How it fits together\n\nThe bridge is a `spawn.kind: python` process AD launches on the user's machine (`entrypoint:\nserver.py`, `port: 0` — AD picks the port and passes `ADOM_BIND_HOST`). It speaks HTTP\n(`POST /command`, `GET /status`) and reaches KiCad through **four control surfaces**, picking the\nlightest one that can do the job.\n\n\n\n| # | Surface | Used for | Where |\n|---|---------|----------|-------|\n| 1 | **kicad-cli** (subprocess) | headless DRC/ERC, gerber/pdf/svg/step/bom export, format upgrade | `handlers/export.py`, `run_drc`, `run_erc`, `lint_*` |\n| 2 | **KiCad IPC (kipy)** | board/schematic introspection, confirmed footprint placement (KiCad 9+) | `handlers/place_footprint.py` |\n| 3 | **Embedded Python** | JSON-RPC *inside* each KiCad process, dispatched on its wx UI thread | `plugin_payload/adom_bridge.py`, `handlers/bridge_client.py` |\n| 4 | **Win32 / UIA / SendKeys** | open editors, screenshots, clicks/keys, dialog handling, window management | `handlers/kicad_ui.py`, `handlers/close_windows.py` |\n\n---\n\n## The window tour\n\nEverything below was opened and captured on the GPU-less VM. Sample data is the **RP2040 breakout**\ngenerated by `tour-pack-rp2040/` (an `AdomRP2040` symbol + `QFN-56_AdomRP2040` footprint + a board).\n\n### Schematic editor — `kicad_open_schematic`\n\nThe RP2040 breakout: the MCU (U1), USB-C, a 12 MHz crystal, decoupling, and mounting pins.\n\n\n\n### Symbol editor — `kicad_open_symbol_editor`\n\nOpens straight to a symbol (`kicad_install_symbol` puts it in a user library first).\n\n\n\n### Footprint editor — `kicad_open_footprint_editor`\n\n`{\"footprintName\":\"QFN-56_AdomRP2040\",\"library\":\"AdomRP2040\"}` loads the part directly — 56 pins +\nthermal pad, 7×7 mm, 0.4 mm pitch, courtyard and silkscreen.\n\n\n\n### PCB editor (2D) — `kicad_open_board`\n\nThe routed breakout — copper, silkscreen, the QFN-56 land pattern, mounting holes.\n\n\n\n### 3D viewer — `kicad_open_3d_viewer {\"editor\":\"pcb\"}`\n\nThe full board in 3D — board body, the USB-C connector's 3D model, the QFN chip body, SMD parts,\nplated through-holes. **Rendered on the CPU via the software-OpenGL fallback (below); reload 1.7 s.**\n\n\n\n---\n\n## Install & upgrade — zero manual steps\n\nThe bridge never tells the user to go download KiCad. `kicad_upgrade` fetches the official installer\nand runs it silently, picking the scope automatically:\n\n- **Elevated AD** → `/allusers /S` (system-wide, `%ProgramFiles%\\KiCad`).\n- **Non-elevated AD** → `/currentuser /S` (`%LocalAppData%\\Programs\\KiCad`, **no UAC prompt**).\n\n`kicad_readiness` reports whether KiCad is installed and ready without side effects; the AI routes on\nit before offering to install. Hard-won install details (all handled for you):\n\n- KiCad 10's NsisMultiUser installer **requires** a scope flag — bare `/S` errors `rc=666660`.\n- AD's portable Python has **no CA bundle**; the bridge ships `certs/cacert.pem` and uses it for TLS.\n- Downloads are size-checked against `Content-Length` (a truncated installer otherwise fails at NSIS).\n- `%APPDATA%/kicad/<ver>/` lib tables don't exist until first launch — the bridge **bootstraps** them\n so `install_library`/`install_footprint` work on a never-opened KiCad.\n- `kicad_upgrade {\"diagnoseOnly\":true}` reports token-elevation type + `EnableLUA` without installing.\n\n---\n\n## Dialogs & error handling — the bridge clears the pointless ones\n\nKiCad throws modal dialogs that stall automation. The bridge **scans every window owned by a running\nKiCad process** (by PID — reliable, unlike title matching), auto-expires the benign ones, screenshots\nwhat it dismissed, and returns a hint so the AI can decide what (if anything) to tell the user.\n\nAuto-expired benign dialogs include:\n\n- **\"Could not use OpenGL / falling back to software rendering\"** (GPU-less hosts).\n- **\"Welcome to KiCad — starting for the first time\"** first-run wizard (dismissing accepts defaults).\n- **\"This file was created by an older version of KiCad\"** conversion notice.\n\n\n\nVerbs:\n\n- `kicad_window_info` — lists windows and **self-heals** (auto-expires benign dialogs) by default.\n- `kicad_dismiss_dialogs {\"all\":true}` — clear everything blocking; `{\"screenshot\":true}` returns\n images of each; `{\"forceSoftwareCanvas\":true}` persists Cairo canvas; `{\"debug\":true}` dumps the\n raw PID-based scan.\n- `kicad_screenshot_all` — one call returns **every** open KiCad window, so the AI can spot an error\n dialog it didn't expect and read the message.\n\n---\n\n## Software-OpenGL fallback (last resort)\n\n`kicad_enable_software_opengl` deploys Mesa's `llvmpipe` (a CPU OpenGL rasterizer) into KiCad's `bin`\nso a box with **no usable GPU** — Hyper-V, RDP, headless CI — can still render the editors and the 3D\nviewer. This is how every 3D shot above exists.\n\n> ⚠️ It renders on the CPU and is **slow**. It is a worst-case fallback only — a real GPU (or GPU-P /\n> DDA passthrough) is vastly better. The bridge keeps it so a GPU-less box isn't a dead end; it is\n> never suggested proactively.\n\n---\n\n## Verb reference\n\nAll 41 verbs (prefix `kicad_`). Call `kicad_describe` for the live catalog with per-verb hints,\nrelated verbs, and pitfalls.\n\n**Windows & UI** — `open_board`, `open_schematic`, `open_symbol_editor`, `open_footprint_editor`,\n`open_3d_viewer`, `open_editors`, `close_symbol_editor`, `close_footprint_editor`, `close_3d_viewer`,\n`close`, `window_info`, `dismiss_dialogs`, `screenshot_all`, `send_key`, `click`, `fix_keyboard`\n\n**Libraries & parts** — `install_library`, `install_symbol`, `install_footprint`, `install_plugin`,\n`place_footprint`, `adom_library_status`\n\n**Checks & export** — `run_drc`, `run_erc`, `lint_board`, `lint_schematic`, `lint_library`,\n`format_upgrade`, `export_gerber`, `export_pdf`, `export_svg`, `export_step`, `export_bom_csv`\n\n**Detect, install & meta** — `list_versions`, `readiness`, `describe`, `enable_software_opengl`,\n`check_for_updates`, `upgrade`, `bridge_status`, `bridge_call`\n\n---\n\n## Running / dependencies\n\nAD provisions everything; there is nothing to install by hand.\n\n- **Python** ≥ 3.11 (AD provisions it) — stdlib only, plus the bundled `certs/cacert.pem`.\n- **KiCad** ≥ 7.0 (host app; `kicad_upgrade` installs it if absent).\n- **OS:** Windows (macOS/Linux detection stubs exist; the GUI surfaces are Windows-first).\n\nThe bridge binds `ADOM_BIND_HOST` on an AD-assigned port — never `0.0.0.0`. Auto-updates via\n`updateManifestUrl` in `bridge.json`.\n\n---\n\n*Developer & publish notes live in `dev-skills/` and `publish-skills/` (source-only, never shipped to\na user install).*\n", "author": { "name": "John Lauer", "email": "[email protected]"@@ -90,6 +90,8 @@ "skills/kicad-tour/SKILL.md", "skills/kicad-tour/tour_runner.py", "skills/kicad-web-control/SKILL.md",+ "skills/kicad-uia/catalog.json",+ "skills/kicad-uia/SKILL.md", "skills/kicad-bridge-dev/SKILL.md", "skills/kicad-bridge-publish/SKILL.md", "skills/kicad-bridge-hero/SKILL.md",@@ -128,4 +130,4 @@ "authors": null, "author_handle": "john", "authors_json": null-}+}
skills/kicad-bridge-dev/SKILL.md+14@@ -950,3 +950,17 @@ and the Symbol Editor listed the Adom library EMPTY. Three test symbols had bare - **Report the end state, measured.** `_foreground.editorInFrontAtEnd` is read from AD's z-order at return time. The early one-look guard can only see the frame's creation; KiCad activates itself later during load, so never claim "background" from the early look alone. - **Never fight the user for the foreground.** One look within the guard window, one optional SetWindowPos, then hands off for the rest of the call and forever after. An earlier generation of guards left users unable to bring KiCad forward themselves; that is the failure to avoid. - Captions remain only for the opt-in `allowFocusSteal` path: one short line (under 60 characters), 3 seconds, naming the thread.++## Demo transport and captions (John, 2026-09-03)++- **ab 2.1.17 contract (DEMO_KIT.md in the ab repo):** the first `show` carries title, stops, reason, `muted` and `eventCallbackUrl` (loopback only, `http://127.0.0.1:<bridge port>/demo/event`); every press is POSTed there at once as `{id, button, at, state, stage, muted}` and `handlers/demo.on_panel_event` applies mute/unmute immediately, stops sound on pause/stop, and queues the transport for the beat boundary. Stage-advance shows send ONLY `{action, id, stage}`: resending state would un-pause or un-mute the user. The poll stays as the backstop and skips presses already seen by their `at` stamp. `state:"gone"` means re-show once.+- **The tour foregrounds each beat's window exactly once** (John, 2026-09-03: "you must foreground otherwise the user doesn't know what window you're demoing", but only once so they keep full control). No persistent focus guardian during the tour; `allow_activation` then one `_set_foreground_robust`, then hands off. Every other verb keeps the background contract.+- **The tour's remote is ab's `desktop_demo_panel`** (Adom Bridge 2.1.0+, adom/adom-bridge#116): show at stage 0 with the stop list and a reason, `poll` between beats (a beat cannot be interrupted mid-verb), map back/play/pause/next/stop onto prev/play/pause/next/exit, update `stage`/`state` on every beat, hide on done, exit and error. The toast-with-buttons transport is only the fallback when the verb answers "unknown verb".+- **One short caption per beat**, shown for about the beat's length, never a paragraph and never a minute. Each names the step ("KiCad tour 3/7: the 2D board"); the dashboard's step log carries the mechanism.++## The tour walks a real board (John, 2026-09-03: "a nice sexy cool board, not a lame board")++- `handlers/demo.py` fetches adom/esc-g431 (schematic + board + project) from the wiki files API at tour time, lifts the MCU symbol out of the schematic's embedded `lib_symbols` and the U5 footprint out of the board (placement, uuids, nets, pinfunctions stripped; KiCad's standard LQFP-48 body kept), installs both into the Adom library, and runs the beats in John's order: symbol, footprint, chip in 3D, schematic, board 2D, board 3D. Dead `C:/Users/...` model paths in the board are dropped so KiCad draws pads instead of complaining.+- Captions need `reason` (ab refuses one without it: that is why two whole tours ran caption-less) and use `size: medium`. One line per beat, medium size, about the beat's length.+- Bodies in 3D depend on KiCad's bundled 3D library being installed on the box (ConfRoomROG has none; AdomLapper does). Say so in the beat text instead of promising a body.+- Never narrate over a running tour from the hands-free player, and never open the dashboard in the cloud workspace: the user watches in ah's dock webview and clicks Play there.