← Commit history

Update 4 file(s)

John Lauer ·a09c392b30 ·1mo ago ·parent 928a37f
4 files changed +94−12
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.216\ No newline at end of file+0.9.217\ 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.216",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.216/adom-bridge-kicad-v0.9.216.zip",-  "sha256": "dbb8dd91e509f0f7c5f80fa24ec1190dc8bc7331777f64aad304f27ed560273e",-  "size": 483198,+  "version": "0.9.217",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.217/adom-bridge-kicad-v0.9.217.zip",+  "sha256": "3430f247cb8244f9cd8897bc993b07c78225ee3511766a360e910d94f72be932",+  "size": 484313,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-20T18:20:00Z",+  "released_at": "2026-08-20T18:45:00Z",   "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.216",+  "version": "0.9.217",   "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/show.py+87−5
@@ -77,7 +77,10 @@ def handle_show_symbol(kicad_info: dict, args: dict) -> dict:         a.get("libraryName") or a.get("library") or "")     if already:         _ev = _capture_evidence(already, "symbol-editor-reused")+        _sh = _build_shot(already, "symbol-editor-reused",+                          a.get("captureMaxWidth", _SHOT_DEFAULT_MAX)) if a.get("capture") else None         return {"success": True, "surface": "symbol", "reused": True,+                **({"shot": _sh} if _sh else {}),                 "window": already.get("title"),                 **({"evidence": _ev} if _ev else {}),                 "output": f"Symbol Editor is already showing {already.get('title')} - "@@ -87,7 +90,7 @@ def handle_show_symbol(kicad_info: dict, args: dict) -> dict:                          "opens, so reuse is the difference between about a second and "                          "about a minute (wiki issue #35)."}     r = handle_open_symbol_editor(kicad_info, a)-    r = _verify_window(r, _find_symbol_editor, "Symbol Editor")+    r = _verify_window(r, _find_symbol_editor, "Symbol Editor", args=a)     r.setdefault("surface", "symbol")     return r @@ -202,6 +205,81 @@ def _capture_evidence(win, label: str) -> dict | None:         return None  ++_SHOT_DEFAULT_MAX = 640+_SHOT_MIN_MAX, _SHOT_MAX_MAX = 120, 1600+++def _build_shot(win, label: str, max_width: int) -> dict | None:+    """The ab gallery-item `shot` contract (wiki #45, settled in adom-bridge#91).++    Why this exists next to `evidence` rather than inside it: the wiki Quick+    Actions caller is a BROWSER. It has no container for ab to auto-pull+    `fullPath` into, and the /command response carries no image bytes, so a page+    cannot render the thumbnail beside "opened in KiCad". Colby's split is the+    right one and is kept here deliberately:++        evidence -> render PROOF   (canvasRendered, hwnd, window)+        shot     -> the FRAME      (bytes a browser can paint)++    Opt-in via capture:true, so no existing caller pays for bytes it cannot use.+    """+    try:+        import base64+        if isinstance(win, dict):+            hwnd, title = win.get("hwnd"), win.get("title")+        else:+            hwnd, title = win, _window_title(win)+        if not hwnd:+            return None+        try:+            mw = int(max_width)+        except Exception:+            mw = _SHOT_DEFAULT_MAX+        mw = max(_SHOT_MIN_MAX, min(_SHOT_MAX_MAX, mw))++        from handlers.kicad_ui import _screenshot_hwnd+        shot = _screenshot_hwnd(hwnd, label=f"shot-{label}", max_dim=mw)+        path = (shot or {}).get("fullPath") or (shot or {}).get("savedTo")+        if not path:+            return None+        raw = open(path, "rb").read()+        b64 = base64.b64encode(raw).decode()+        # The MIME belongs to whoever encodes the bytes. Colby drops+        # thumbnailDataUrl straight into <img src> and does NOT rebuild it from+        # `image`, so a mislabelled prefix is our bug, not his. Derive it from+        # what we actually wrote rather than assuming PNG.+        ext = str(path).lower().rsplit(".", 1)[-1]+        mime = {"png": "image/png", "bmp": "image/bmp", "webp": "image/webp"}.get(ext, "image/png")+        return {+            "title": title,+            "image": b64,+            "thumbnailDataUrl": f"data:{mime};base64,{b64}",+            "maxWidth": mw,+            "path": str(path),+            "_hint": ("thumbnailDataUrl drops straight into an <img src>. `image` is the same "+                      "bytes raw for container callers, and `path` is the on-box file ab "+                      "auto-pulls. Request it with capture:true; captureMaxWidth defaults to "+                      f"{_SHOT_DEFAULT_MAX} and clamps to {_SHOT_MIN_MAX}-{_SHOT_MAX_MAX}."),+        }+    except Exception:+        return None+++def _attach_shot(result: dict, win, label: str, args: dict) -> dict:+    """Attach `shot` when the caller asked for it. Never replaces `evidence`."""+    try:+        if not isinstance(result, dict) or not (args or {}).get("capture"):+            return result+        sh = _build_shot(win, label, (args or {}).get("captureMaxWidth", _SHOT_DEFAULT_MAX))+        if sh:+            result["shot"] = sh+        else:+            result["shotUnavailable"] = "capture was requested but the frame could not be grabbed"+    except Exception:+        pass+    return result+ def _find_symbol_editor():     return _find_by_title("symbol editor") @@ -210,7 +288,8 @@ def _find_3d_viewer():     return _find_by_title("3d viewer")  -def _verify_window(result: dict, finder, label: str, waits: int = 12) -> dict:+def _verify_window(result: dict, finder, label: str, waits: int = 12,+                   args: dict = None) -> dict:     """A show_* verb has ONE job: put THAT surface on screen. If its window is     not there, the verb FAILS - even when every internal step reported success. @@ -231,7 +310,7 @@ def _verify_window(result: dict, finder, label: str, waits: int = 12) -> dict:                 result["evidence"] = ev             else:                 result["evidenceUnavailable"] = "window verified but the capture failed"-            return result+            return _attach_shot(result, win, label, args or {})         time.sleep(0.5)     return {**result, "success": False,             "error": f"{label} reported success but its window never appeared",@@ -318,7 +397,10 @@ def handle_show_footprint(kicad_info: dict, args: dict) -> dict:                                       a2.get("library") or a2.get("libraryName") or "")     if already_fp:         _ev = _capture_evidence(already_fp, "footprint-editor-reused")+        _sh = _build_shot(already_fp, "footprint-editor-reused",+                          a2.get("captureMaxWidth", _SHOT_DEFAULT_MAX)) if a2.get("capture") else None         return {"success": True, "surface": "footprint", "reused": True,+                **({"shot": _sh} if _sh else {}),                 "window": already_fp.get("title"),                 **({"evidence": _ev} if _ev else {}),                 "output": f"Footprint Editor is already showing {already_fp.get('title')} - "@@ -327,7 +409,7 @@ def handle_show_footprint(kicad_info: dict, args: dict) -> dict:                          "(wiki issue #35)."}     ensure_plugin_ready(kicad_info)     r = handle_open_footprint_editor(kicad_info, a2)-    r = _verify_window(r, _find_footprint_editor, "Footprint Editor")+    r = _verify_window(r, _find_footprint_editor, "Footprint Editor", args=a2)     r.setdefault("surface", "footprint")     return r @@ -347,7 +429,7 @@ def handle_show_3d_chip(kicad_info: dict, args: dict) -> dict:                 break             time.sleep(0.5)     r = handle_open_3d_viewer(kicad_info, {**a, "editor": "fp"})-    r = _verify_window(r, _find_3d_viewer, "3D Viewer")+    r = _verify_window(r, _find_3d_viewer, "3D Viewer", args=a)     r.setdefault("surface", "3d_chip")      # Wiki issue #42 item 4 (John): "the 3D Viewer window is titled just