← Commit history

v0.9.114: sentinel exemption for sanctioned foregrounds (bring_to_user / announce_foreground set expect_foreground so the sentinel never fights the bridge's own user-requested fg paths)

John Lauer ·e230a5ac82 ·2mo ago ·parent 4cafdf6
5 files changed +23−8
BRIDGE_VERSION+1−1
@@ -1 +1 @@-0.9.113+0.9.114
adom-bridge-kicad-manifest.json+5−5
@@ -1,15 +1,15 @@ {   "manifest_version": 1,   "name": "kicad",-  "version": "0.9.113",-  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.113/adom-bridge-kicad-v0.9.113.zip",-  "sha256": "4afd53258aa6a175bd6b2a585473bb88c7e4ca111e1708a376aca3127d6af45e",-  "size": 408755,+  "version": "0.9.114",+  "url": "https://wiki.adom.inc/download/adom/kicad-bridge/0.9.114/adom-bridge-kicad-v0.9.114.zip",+  "sha256": "4aafd591a26ff321f3ba3242a03d7a0db35664971552b96104db0e8899a297a0",+  "size": 408981,   "verbPrefixes": [     "kicad_"   ],   "healthEndpoint": "/status",-  "released_at": "2026-08-16T13:20:09Z",+  "released_at": "2026-08-16T13:25:01Z",   "hero": "https://wiki.adom.inc/blob/app/kicad-bridge/kicad-hero.png",   "languages": [     "Python",
adom-bridge-kicad-v0.9.114.zipadded
⋯ 1 unchanged line ⋯
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "kicad",   "displayName": "KiCad EDA",-  "version": "0.9.113",+  "version": "0.9.114",   "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/win_focus.py+16−1
@@ -298,6 +298,16 @@ def _seconds_since_user_input() -> float: _USER_INTENT_WINDOW_S = 1.2 _sentinel_started = False _focus_events = None  # collections.deque, lazy+_expected_fg_until = 0.0  # sanctioned bridge-initiated foreground window+++def expect_foreground(seconds: float = 20.0) -> None:+    """The bridge is ABOUT to foreground a KiCad window legitimately (user asked:+    foreground:true / allowForeground / bring_to_user — always announced first).+    The sentinel must not fight it: exempt bounces for `seconds`."""+    global _expected_fg_until+    import time as _t+    _expected_fg_until = max(_expected_fg_until, _t.monotonic() + seconds)   def get_focus_events() -> list:@@ -337,7 +347,10 @@ def start_focus_sentinel() -> None:                 if fg != last_fg:                     if fg and is_kicad_window(fg):                         ago = _seconds_since_user_input()-                        if ago <= _USER_INTENT_WINDOW_S:+                        if _t.monotonic() < _expected_fg_until:+                            blessed.add(fg)+                            _log("sanctioned-foreground (allowed)", fg, ago)+                        elif ago <= _USER_INTENT_WINDOW_S:                             blessed.add(fg)                             _log("user-foregrounded (left alone)", fg, ago)                         elif _guard_count > 0:@@ -442,6 +455,7 @@ def bring_to_user(hwnd) -> bool:     if not IS_WINDOWS or not hwnd:         return False     try:+        expect_foreground(30.0)      # tell the sentinel this foreground is sanctioned         allow_activation(hwnd)       # a suppressed window can't be activated         _user32.ShowWindow(hwnd, 9)  # SW_RESTORE (in case minimized)         return _set_foreground_robust(hwnd)@@ -456,6 +470,7 @@ def announce_foreground(action: str, seconds: int = 5) -> None:     the calling AI thread via the forwarded caller headers — so the user sees WHY     their screen is about to be taken, BEFORE it happens. Fire-and-forget in a     daemon thread: the toast failing must never fail (or delay) the verb."""+    expect_foreground(max(20.0, float(seconds) + 15.0))  # sentinel: sanctioned fg incoming     def _fire():         try:             from caller_identity import ad_callback