← Commit history

Update 5 file(s)

John Lauer ·0baacecba1 ·1mo ago ·parent 5acffa0
5 files changed +100−10
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.198\ No newline at end of file+0.9.199\ 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.198",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.198/adom-bridge-kicad-v0.9.198.zip",-  "sha256": "f29948616354a5a2caa53a9a7ad2e3df277e6cfea33ff2cd075137f08a39363d",-  "size": 468068,+  "version": "0.9.199",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.199/adom-bridge-kicad-v0.9.199.zip",+  "sha256": "a9af58611683d9f4ed4af9ff92cd851f26eeeae7914a23d9c15938585ff76f78",+  "size": 469492,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-20T01:05:00Z",+  "released_at": "2026-08-20T01: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.198",+  "version": "0.9.199",   "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/demo.py+26−3
@@ -604,8 +604,29 @@ def handle_demo(kicad_info: dict, args: dict) -> dict:             return {"success": False, "error": "no demo job has been started",                     "_hint": "Start one with kicad_demo {\"all\":true,\"background\":true}."}         out = {"success": True, **{k: snap.get(k) for k in-               ("active", "done", "step", "stepLabel", "beatIndex", "beatCount",+               ("done", "step", "stepLabel", "beatIndex", "beatCount",                 "percent", "elapsedSec", "estimatedSec", "error")}}+        # 0.9.199 (wiki issue #39, John): `active` used to be a BOOLEAN here and+        # an ARRAY in kicad_progress - the same key, two types, from one bridge.+        # A renderer written as `if (resp.active)` then treats a finished job as+        # running (a non-empty array and `true` are both truthy) and cannot tell+        # the two idle forms apart. So `active` is now the canonical ARRAY of+        # progress blocks everywhere, and the boolean moved to `running`.+        _running = bool(snap.get("active"))+        out["running"] = _running+        out["active"] = ([{+            "phase": "kicad.demo",+            "stepLabel": snap.get("stepLabel") or snap.get("step") or "running",+            "percent": snap.get("percent"),+            "elapsedSec": snap.get("elapsedSec"),+            "estimatedSec": snap.get("estimatedSec"),+            "etaSec": (None if snap.get("estimatedSec") is None or snap.get("elapsedSec") is None+                       else max(0.0, round(snap["estimatedSec"] - snap["elapsedSec"], 1))),+            "confidence": "typical",+            "startKind": "cold",+            "beatIndex": snap.get("beatIndex"),+            "beatCount": snap.get("beatCount"),+        }] if _running else [])         # Per-step evidence (#25): one completed-step screenshot rides the FIRST         # progress frame after that step lands — {step, title, image<=640px}.         with _JOB_LOCK:@@ -618,8 +639,10 @@ def handle_demo(kicad_info: dict, args: dict) -> dict:                             "PNG (render them all); speak result.narrate. Tear down with "                             "kicad_close {\"force\":true} when the user is done looking.")         else:-            out["_hint"] = ("Long-running job — render a real progress bar from percent/"-                            "elapsedSec/estimatedSec and show stepLabel. Poll every 2-3s. "+            out["_hint"] = ("Long-running job — render a real progress bar from active[0] "+                            "(percent/elapsedSec/estimatedSec/stepLabel). Poll every 2-3s. "+                            "`active` is an ARRAY (canonical contract); the boolean "+                            "still-running flag is `running`. "                             "stepShot (when present) is the completed step's own window as "                             "base64 PNG — pin it as that step's evidence.")         return out
server.py+67
@@ -109,6 +109,59 @@ BRIDGE_VERSION = _read_bridge_version() _DIALOG_BLOCK_AFTER_SEC = 3.0 _DIALOG_FIRST_SEEN: dict = {} +# stepShot cache: {phase: (stepLabel, {step,title,image})}. Keyed on the STEP+# LABEL so a shot is taken once per step, not once per poll - a 2.5s poll of a+# 20s step would otherwise capture eight identical images and push megabytes+# through the relay for one frame of information.+_STEPSHOT: dict = {}+_STEPSHOT_MAX_BYTES = 1_200_000   # keep a frame comfortably under the inline cap++# Which window IS this phase's surface? Without this a stepShot could pin the+# project manager as evidence for a footprint step - the exact mislabel that+# made evidence untrustworthy in 0.9.172.+_PHASE_SURFACE = {+    "show_symbol": "symbol editor",+    "show_library": "symbol editor",+    "show_footprint": "footprint editor",+    "show_3d_chip": "3d viewer",+    "show_3d_board": "3d viewer",+    "show_2d_board": "pcb editor",+    "show_schematic": "schematic editor",+}+++def _stepshot_for(phase: str, step_label: str):+    """A small base64 PNG of THIS phase's surface, at most one per step label."""+    cached = _STEPSHOT.get(phase)+    if cached and cached[0] == step_label:+        return cached[1]+    needle = _PHASE_SURFACE.get(phase)+    if not needle:+        return None+    try:+        import base64 as _b64+        from handlers.kicad_ui import _find_all_kicad_windows, _screenshot_hwnd+        win = None+        for w in _find_all_kicad_windows() or []:+            if needle in (w.get("title") or "").lower():+                win = w+                break+        if not win or not win.get("hwnd"):+            return None+        shot = _screenshot_hwnd(win["hwnd"], label=f"step-{phase}", max_dim=640)+        path = (shot or {}).get("fullPath") or (shot or {}).get("savedTo")+        if not path:+            return None+        raw = open(path, "rb").read()+        if len(raw) > _STEPSHOT_MAX_BYTES:+            return None+        out = {"step": step_label, "title": win.get("title"),+               "image": "data:image/png;base64," + _b64.b64encode(raw).decode()}+        _STEPSHOT[phase] = (step_label, out)+        return out+    except Exception:+        return None+ _PHASES = {     "launch": "cold_start", "show_symbol": "show_symbol",     "show_footprint": "show_footprint", "show_3d_chip": "show_3d_chip",@@ -597,6 +650,20 @@ def _handle_progress(kicad_info: dict, args: dict) -> dict:                     f["stepLabel"] = f"waiting for you: {blocker}"         except Exception:             pass++        # stepShot: the step's own evidence thumbnail, rendered verbatim by the+        # web surface. Skipped while blocked - the surface behind a modal is not+        # what the user needs to look at, the modal is.+        for f in frames:+            try:+                if f.get("blockedBy"):+                    continue+                ph = (f.get("phase") or "").split(".", 1)[-1]+                ss = _stepshot_for(ph, f.get("stepLabel") or "")+                if ss:+                    f["stepShot"] = ss+            except Exception:+                pass     return {         "success": True,         "busy": bool(frames),