← Commit history

0.9.315: Tour remote: prev/next/pause cut the beat in flight at the next gesture; back goes to the beat before the one on screen; presses and acks recorded

John Lauer ·80a3105cb7 ·1mo ago ·parent ff5bf44
3 files changed +35−13
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.314\ No newline at end of file+0.9.315\ No newline at end of file
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.314",+  "version": "0.9.315",   "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+33−11
@@ -819,6 +819,8 @@ def _verify_beat(hwnd: int | None, expected_title: str, tries: int = 2) -> dict:         prev = _digest(); v["stable"] = False; v["settleMs"] = 0         t0 = _t.monotonic()         while _t.monotonic() - t0 < 8.0:+            if _pending_interrupt():+                v["interrupted"] = True; break             _t.sleep(0.8); cur = _digest()             if prev and cur and prev == cur:                 v["stable"] = True; break@@ -1238,9 +1240,12 @@ def on_panel_event(ev: dict) -> dict:         _PANEL_SEEN.add(str(at))     b = str(ev.get("button") or "").lower()     _ACK = {"mute": "Remote: muted", "unmute": "Remote: narration back on",-            "pause": "Remote: pausing after this beat", "play": "Remote: playing",-            "next": "Remote: next, after this beat", "back": "Remote: back, after this beat",+            "pause": "Remote: pausing", "play": "Remote: playing",+            "next": "Remote: next", "back": "Remote: back",             "stop": "Remote: stopping the tour"}+    with _JOB_LOCK:+        _JOB.setdefault("presses", []).append({"button": b, "at": at, "via": "callback",+                                               "beat": _JOB.get("beatIndex"), "step": _JOB.get("step")})     if b in _ACK:         _caption_ack(_ACK[b])     if b in ("mute", "unmute"):@@ -1252,7 +1257,7 @@ def on_panel_event(ev: dict) -> dict:     cmd = mapping.get(b)     if not cmd:         return {"ignored": b}-    if cmd in ("pause", "exit"):+    if cmd in ("pause", "exit", "next", "prev"):         _stop_narration()           # the user's hand: silence within the beat     if cmd == "exit":         # John, 2026-09-03: on the panel's X "exit immediately, remove the@@ -1412,13 +1417,27 @@ def _caption_ack(text: str) -> None:     indicating you got my signal')."""     try:         from handlers import ad_client-        ad_client.call("desktop_caption", {"id": "kicad-demo-ack", "text": text, "duration": 3500,-                                           "size": "medium", "position": "top",-                                           "reason": "Acknowledge a press on the KiCad tour remote the instant it arrives"}, timeout=6)+        r = ad_client.call("desktop_caption", {"id": "kicad-demo-ack", "text": text, "duration": 3500,+                                               "size": "medium", "position": "top",+                                               "reason": "Acknowledge a press on the KiCad tour remote the instant it arrives"}, timeout=6) or {}+        with _JOB_LOCK:+            _JOB.setdefault("captions", []).append({"text": text[:60], "ack": True,+                                                    "ok": bool(r.get("success")) and not r.get("error"),+                                                    "error": (r.get("error") or None)})     except Exception:  # pylint: disable=broad-except         pass  +def _pending_interrupt() -> bool:+    """A transport press that must cut the beat in flight (John, 2026-09-04:+    "i would expect your prev/next to be instant, not wait for next beat").+    Motion polls this between gestures; the narration wait polls it too."""+    if _EXIT["now"]:+        return True+    with _CTRL_LOCK:+        return _CTRL["cmd"] in ("prev", "next", "pause", "exit")++ def _adopt_tour_identity() -> None:     """Re-apply the identity of the request that STARTED the tour on this thread. @@ -1513,7 +1532,7 @@ def _run_demo_background(kicad_info: dict) -> None:             cmd = _drain_ctrl()             if cmd == "pause":                 _stop_narration()-                _panel_show(i + 1, "paused")+                _panel_show(max(1, i), "paused")      # the beat on screen, not the next                 _caption("KiCad tour paused" + ("" if _PANEL["ok"] else                          " - use the toast buttons or the dashboard to continue"),                          duration_ms=300000)@@ -1531,9 +1550,12 @@ def _run_demo_background(kicad_info: dict) -> None:                 exited = True                 break             if cmd == "prev":-                i = max(0, i - 1)-                if results:-                    results.pop()+                # i already points past the beat the user was watching (it+                # advanced at the bottom of the loop), so "back" is two steps:+                # measured on John's laptop 2026-09-04, i - 1 replayed the same+                # footprint beat twice.+                i = max(0, i - 2)+                del results[i:]             # ("next" simply proceeds: we are already between beats, so moving             # on IS skipping the wait; a next during a beat applies here.)             s_ = STEPS[i]@@ -1591,7 +1613,7 @@ def _run_demo_background(kicad_info: dict) -> None:                     try:                         from handlers import demo_motion                         kind = "3d" if s_ in ("footprint3d", "board3d") else "2d"-                        r["motion"] = demo_motion.choreograph(target, kind, abort=lambda: _EXIT["now"])+                        r["motion"] = demo_motion.choreograph(target, kind, abort=_pending_interrupt)                     except Exception as e:  # pylint: disable=broad-except                         r["motion"] = {"error": f"{type(e).__name__}: {e}"}             if _EXIT["now"]: