← Commit history

0.9.253: install_library_bundle capture proof (background Symbol Editor shot, closed after; skipped when KiCad runs)

Colby Knox ·0e97f77ffc ·1mo ago ·parent 5639ef2
5 files changed +57−14
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.252\ No newline at end of file+0.9.253\ No newline at end of file
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.252",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.252/adom-bridge-kicad-v0.9.252.zip",-  "sha256": "599b975ada07114872d9c9138d59a14e5b9f4356eb98cb53982af1b8437749d5",-  "size": 452444,+  "version": "0.9.253",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.253/adom-bridge-kicad-v0.9.253.zip",+  "sha256": "38149498618f100760933e128d83265afe79c90359a817f6d36b82037f4f58c0",+  "size": 453215,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-26T21:13:35Z",+  "released_at": "2026-08-26T21:24:52Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.252",+  "version": "0.9.253",   "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/install_library_bundle.py+45−6
@@ -69,14 +69,43 @@ def _find_bundle_parts(root: Path):     return sym, pretty, (models if models.is_dir() else None)  -def _count_symbols(sym_path: Path) -> int:-    """Top-level symbols only (sub-units are named NAME_<n>_<n>)."""+def _top_symbol_names(sym_path: Path) -> list:+    """Top-level symbol names only (sub-units are named NAME_<n>_<n>)."""     try:         text = sym_path.read_text(encoding="utf-8", errors="replace")     except OSError:-        return 0+        return []     names = re.findall(r'\(symbol\s+"([^"]+)"', text)-    return sum(1 for n in names if not re.search(r"_\d+_\d+$", n))+    return [n for n in names if not re.search(r"_\d+_\d+$", n)]+++def _capture_proof(kicad_info: dict, args: dict, lib_name: str, first_symbol: str, result: dict):+    """Proof frame for the browser caller: open the Symbol Editor on the+    freshly installed library IN THE BACKGROUND (the show path's contract),+    grab the shot, then close the editor we opened. Best effort: any failure+    lands in proofSkipped, never in the install's success."""+    try:+        from handlers.show import handle_show_symbol+        show = handle_show_symbol(kicad_info, {+            "libraryName": lib_name,+            **({"symbolName": first_symbol} if first_symbol else {}),+            "capture": True,+            "captureMaxWidth": args.get("captureMaxWidth", 480),+        })+        if show.get("shot"):+            result["shot"] = show["shot"]+        else:+            result["proofSkipped"] = show.get("error") or show.get("shotUnavailable") or "no frame captured"+        # Close only an editor this call cold-opened; a reused window belongs+        # to whoever opened it.+        if not show.get("reused"):+            try:+                from handlers.close_windows import handle_close_symbol_editor+                handle_close_symbol_editor(kicad_info, {})+            except Exception:+                pass+    except Exception as e:+        result["proofSkipped"] = str(e)   def handle_install_library_bundle(kicad_info: dict, args: dict) -> dict:@@ -119,7 +148,8 @@ def handle_install_library_bundle(kicad_info: dict, args: dict) -> dict:             return {"success": False, "error": "No .kicad_sym found in the bundle",                     "_hint": "Expected an Adom wiki Cloud Library bundle: <Lib>.kicad_sym + <Lib>.pretty/ + 3dmodels/."} -        report = {"libraryName": lib_name, "symbols": _count_symbols(sym),+        top_names = _top_symbol_names(sym)+        report = {"libraryName": lib_name, "symbols": len(top_names),                   "footprints": 0, "modelsInstalled": 0, "modelRefsRewritten": 0}          sym_res = handle_install_library(kicad_info, {@@ -165,7 +195,7 @@ def handle_install_library_bundle(kicad_info: dict, args: dict) -> dict:         if kicad_running:             summary += ". KiCad is running and reads library tables at startup, so restart KiCad to see it." -        return {+        result = {             "success": True,             "output": summary,             "data": report,@@ -177,5 +207,14 @@ def handle_install_library_bundle(kicad_info: dict, args: dict) -> dict:                    "KiCad was not running; the library appears on next launch.")             ),         }+        # Proof frame (capture:true): only when KiCad was NOT already running.+        # A running instance predates the lib-table registration, so an editor+        # there cannot see the new library; the restart note is the message.+        if args.get("capture"):+            if kicad_running:+                result["proofSkipped"] = "kicad_running: the open KiCad predates the install; restart to see the library"+            else:+                _capture_proof(kicad_info, args, lib_name, top_names[0] if top_names else "", result)+        return result     finally:         shutil.rmtree(str(extract_dir), ignore_errors=True)
server.py+5−1
@@ -359,7 +359,7 @@ _VERB_CATALOG = {         "related": ["kicad_install_symbol", "kicad_install_footprint", "kicad_list_versions"],         "pitfalls": ["each KiCad version has its OWN lib tables — install under every version the user needs", "restart/reload KiCad to see a newly-added library", "model-ref rewriting touches only the INSTALLED copy, never the source, and only refs whose basename matches an installed model (stock ${KICAD*_3DMODEL_DIR} refs stay)"]},     "install_library_bundle": {"summary": "Install an Adom wiki Cloud Library bundle zip (symbols + footprints + 3D models) in one call.", "long": False,-        "hint": "Pass zipPath (staged on THIS machine via write_file / write_file_chunk) and optionally libraryName (defaults to the zip stem). Unpacks the bundle (<Lib>.kicad_sym + <Lib>.pretty/ + 3dmodels/), installs symbols and footprints, carries the 3D models with ref rewriting, and registers both lib tables. Returns {symbols, footprints, modelsInstalled, kicadRunning, restartNeeded}.",+        "hint": "Pass zipPath (staged on THIS machine via write_file / write_file_chunk) and optionally libraryName (defaults to the zip stem). Unpacks the bundle (<Lib>.kicad_sym + <Lib>.pretty/ + 3dmodels/), installs symbols and footprints, carries the 3D models with ref rewriting, and registers both lib tables. Returns {symbols, footprints, modelsInstalled, kicadRunning, restartNeeded}. capture:true additionally opens a BACKGROUND Symbol Editor on the new library, returns the `shot` frame (thumbnailDataUrl for browsers), and closes the editor it opened; skipped with proofSkipped when KiCad was already running (its tables predate the install).",         "related": ["kicad_install_library", "kicad_lint_library", "kicad_open_symbol_editor"],         "pitfalls": ["a RUNNING KiCad reads lib tables at startup only — when kicadRunning is true the user must restart KiCad themselves (never close it for them)", "zip entries with .. or absolute paths are refused"]},     "install_symbol": {"summary": "Install a .kicad_sym into the user's sym-lib-table.", "long": False,@@ -961,6 +961,9 @@ def dispatch_command(command: str, args: dict) -> dict:                       "open_footprint_editor", "open_3d_viewer",                       "place_footprint", "install_symbol", "install_footprint",                       "install_plugin", "upgrade",+                      # capture:true opens a background Symbol Editor for the proof+                      # shot, so the bundle install needs the full guard machinery.+                      "install_library_bundle",                       # demo warms eeschema+pcbnew and opens editors/3D — without                       # membership here the capture/restore machinery skips it and                       # every window it spawns lands ON TOP of the user (verified@@ -973,6 +976,7 @@ def dispatch_command(command: str, args: dict) -> dict:                       "show_project", "show_symbol", "show_footprint", "show_3d_chip", "show_schematic", "show_2d_board", "show_3d_board", "show_library"}     _SPAWNS_WINDOW_FOCUS = {"launch", "open_board", "open_schematic", "open_symbol_editor",                             "open_footprint_editor", "open_3d_viewer", "install_library",+                            "install_library_bundle",                             "place_footprint", "install_symbol", "install_footprint", "demo", "export_molecule",                             "show_project", "show_symbol", "show_footprint", "show_3d_chip", "show_schematic", "show_2d_board", "show_3d_board", "show_library"}