← Commit history
BRIDGE_VERSION+1−1
@@ -1 +1 @@-1.9.284\ No newline at end of file+1.9.285\ No newline at end of file
addin/AdomBridge/AdomBridge.manifest+1−1
@@ -6,7 +6,7 @@     "description": {         "": "Adom Desktop Conduit bridge add-in. Exposes a localhost HTTP API for external automation of Fusion 360."     },-    "version": "1.0.11",+    "version": "1.0.12",     "runOnStartup": true,     "supportedOS": "windows" }\ No newline at end of file
addin/AdomBridge/commands/activity_palette.py+30−4
@@ -32,6 +32,19 @@ PALETTE_ID = "AdomActivityPalette" ADOM_LOGO_SVG = '<svg id="Layer_1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1072.9 236"><defs><style>.cls-1{fill:#00b6af;stroke-width:0px;}</style></defs><path class="cls-1" d="M212.94,23.05c-25.84-25.84-62.87-30.69-82.73-10.84-8.06,8.06-12.04,18.94-12.21,30.76h0s0,.02,0,.02c0,.04,0,.08,0,.12v73.29c0,.89.72,1.61,1.61,1.61h72.5l-.02-.02c12.17.03,23.42-3.94,31.69-12.2,19.85-19.85,15-56.89-10.84-82.73Z"/><path class="cls-1" d="M23.05,212.95c25.84,25.84,62.87,30.69,82.73,10.84,8.06-8.06,12.04-18.94,12.21-30.76h0s0-.02,0-.02c0-.04,0-.08,0-.12v-73.29c0-.89-.72-1.61-1.61-1.61H43.89l.02.02c-12.17-.03-23.42,3.94-31.69,12.2-19.85,19.85-15,56.89,10.84,82.73Z"/><path class="cls-1" d="M1065.5,25.22h-58.49c-2.98,0-5.68,1.87-6.73,4.65l-44.4,117.54c-.06.17-.31.17-.37,0l-45.44-117.59c-1.07-2.75-3.76-4.6-6.72-4.6h-58.79c-4.09,0-7.4,3.31-7.4,7.4v35.06l-.03-.12c-1.24,4.99-5.89,6-5.89,6,0,0-4.51,1.02-7.61-2.53v.03c-17.82-29.81-50.31-48.93-89.04-48.93-41.5,0-75.86,21.85-92.76,55.23v-.07c-2.86,5.19-8.74,5.13-8.74,5.13,0,0-5.63-.01-8.41-4.61-16.49-32.68-50.56-52.59-94.79-52.59h-73.46c-4.09,0-7.4,3.31-7.4,7.4v121.27s0,.03,0,.04c-.95,5.15-4.75,5.94-4.75,5.94,0,0-3.88.85-6.77-3.32l.05.23-46.05-126.83c-1.03-2.84-3.75-4.74-6.77-4.74h-57.14c-3.01,0-5.73,1.9-6.76,4.73l-68.67,188.38c-1.05,2.88-.18,6.21,2.4,8.19,1.31,1.01,2.98,1.47,4.64,1.47h34.96s6.43,0,6.43,0c2.5,0,4.83-1.39,5.9-3.65,8.49-17.86,26.5-29.64,48.37-29.64s39.71,11.73,48.19,29.53c1.1,2.32,3.49,3.76,6.06,3.76h120.78c31.22,0,57.85-9.95,77.02-28.77,7.38-7.25,13.43-15.72,18.05-25.1v.09c2.74-5.19,8.37-5.13,8.37-5.13,0,0,5.43,0,8.08,4.64,4.88,9.88,11.26,18.78,18.91,26.42,18.6,18.6,44.61,29.78,74.26,29.78,38.74,0,71.23-19.18,89.05-49.07l-.02.07c3.1-3.55,7.61-2.53,7.61-2.53,0,0,4.65,1,5.89,6l.03-.12v36.32c0,4.09,3.31,7.4,7.4,7.4h45.79c4.09,0,7.4-3.31,7.4-7.4v-86.26c0-.23.32-.28.39-.06l30.37,88.86c1,2.91,3.74,4.87,6.81,4.87h39.69c3.08,0,5.82-1.96,6.81-4.87l30.1-88.02c.07-.21.39-.16.39.06v85.43c0,4.09,3.31,7.4,7.4,7.4h45.79c4.09,0,7.4-3.31,7.4-7.4V32.62c0-4.09-3.31-7.4-7.4-7.4ZM372.31,147.57c-.15.21-.5.57-1.1.57h-30.33c-.61,0-.95-.35-1.1-.56-.15-.21-.38-.65-.19-1.23l15.06-45.79c.27-.81.92-.94,1.29-.94h0c.37,0,1.02.12,1.29.93l15.26,45.8c.19.58-.04,1.01-.19,1.23ZM525.72,173.84h-14.52c-.86,0-1.56-.7-1.56-1.56v-92.16c0-.86.7-1.56,1.56-1.56h14.52c30.81,0,48.48,17.47,48.48,47.92s-17.67,47.36-48.48,47.36ZM734.6,173.26c-26.51,0-42.33-17.71-42.34-47.36,0-14.59,3.88-26.68,11.21-34.98,7.33-8.29,18.09-12.67,31.13-12.67,12.11,0,22.28,3.84,29.58,11.13.5.5.98,1.01,1.45,1.54,7.4,8.33,11.31,20.42,11.31,34.97,0,29.21-16.22,47.36-42.34,47.36Z"/></svg>' _palette = None _last_reload_ts = 0.0+_user_closed = [False]   # the user clicked the palette's X: stay hidden until the next run+_handlers = []           # keep event handler references alive (Fusion drops unreferenced ones)+++class _PaletteClosedHandler(adsk.core.UserInterfaceGeneralEventHandler):+    """The user closed the palette with its X (John, 2026-08-30: "it has no close button so i+    can't get rid of it when its in the way"). Remember that so the run's periodic updates do+    not pop it straight back; the next run (action 'show') brings it back."""+    def notify(self, args):+        try:+            _user_closed[0] = True+        except Exception:+            pass   def _html(title, detail, percent, eta_sec, tone):@@ -163,12 +176,15 @@ def handle_activity_banner(app: adsk.core.Application, args: dict) -> dict:     try:         ui = app.userInterface         if action == "hide":+            _user_closed[0] = False             if _palette:                 try:                     _palette.isVisible = False                 except Exception:                     pass             return {"success": True, "visible": False}+        if action == "show":+            _user_closed[0] = False   # a new run starts: the palette is wanted again          payload = _payload(args.get("title") or "Working", args.get("detail") or "",                            args.get("percent"), args.get("etaSec"), args.get("tone") or "working")@@ -187,10 +203,16 @@ def handle_activity_banner(app: adsk.core.Application, args: dict) -> dict:             _palette = ui.palettes.add(                 PALETTE_ID, "Adom", path,                 True,    # isVisible-                False,   # showCloseButton - the CHAIN hides it; a user closing it mid-run-                         # would just lose the one warning meant for them+                True,    # showCloseButton - the user must be able to get it out of the way+                         # (John, 2026-08-30); a close sticks until the next run's 'show'                 True,    # isResizable                 360, 190)+            try:+                _h = _PaletteClosedHandler()+                _palette.closed.add(_h)+                _handlers.append(_h)+            except Exception:+                pass             # Dock AFTER add, and verified on hardware: add() then dockingState=Right             # reads back as 4 (docked right). Setting it any earlier is the pArea error.             try:@@ -198,7 +220,11 @@ def handle_activity_banner(app: adsk.core.Application, args: dict) -> dict:             except Exception:                 pass         else:-            if not _palette.isVisible:+            if _user_closed[0]:+                # The user closed it during this run: honour that, keep the content current+                # (so reopening shows the truth) but never pop it back in their way.+                pass+            elif not _palette.isVisible:                 _palette.isVisible = True         # DATA, NOT A RELOAD: the page applies the payload in place. Right after creation         # the page may not have run its script yet; retry briefly, and only if the message@@ -236,7 +262,7 @@ def handle_activity_banner(app: adsk.core.Application, args: dict) -> dict:         q = ui.palettes.itemById(PALETTE_ID)         return {"success": q is not None, "visible": bool(q.isVisible) if q else False,                 "dockingState": int(q.dockingState) if q else None,-                "delivered": delivered, "created": created,+                "delivered": delivered, "created": created, "userClosed": _user_closed[0],                 "deliveryError": (last_err or None)}     except Exception as e:         # Advisory only: a banner that cannot be drawn must never fail the import.
adom-bridge-fusion-manifest.json+4−4
@@ -1,10 +1,10 @@ {   "manifest_version": 1,   "name": "fusion360",-  "version": "1.9.284",-  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.284/adom-bridge-fusion-v1.9.284.zip",-  "sha256": "beaceda7864e3628e419faf44649fc315d9900c7d9e0560ca6b6cee5df9d9286",-  "size": 577266,+  "version": "1.9.285",+  "url": "https://wiki.adom.inc/download/adom/fusion-bridge/1.9.285/adom-bridge-fusion-v1.9.285.zip",+  "sha256": "fe650871145ec0b659ffa813b1c9076aab7352dfb7f63014c93148c95f5d3b76",+  "size": 577894,   "verbPrefixes": [     "fusion_"   ],
bridge.json+1−1
@@ -2,7 +2,7 @@   "manifest_version": 1,   "name": "fusion360",   "displayName": "Autodesk Fusion 360",-  "version": "1.9.284",+  "version": "1.9.285",   "description": "Drive Autodesk Fusion 360 from the cloud: launch Fusion, electronics board layout, design rules, exports (STEP/IGES/STL/3MF/USDZ/OBJ/DXF/DWG/Gerbers/BOM/CPL), fast APS server-indexed cloud search plus browse/recent/file-info/versions, cloud file download/upload and folder creation, and in-app parametric modeling (fusion_run_modeling_script). Never-charge: APS calls are capped to the free tier.",   "homepage": "https://wiki.adom.inc/adom/fusion-bridge",   "author": "Adom Inc.",
dashboard/Cargo.lock+1−1
@@ -4,7 +4,7 @@ version = 4  [[package]] name = "fusion-dashboard"-version = "1.9.284"+version = "1.9.285" dependencies = [  "serde_json", ]
dashboard/Cargo.toml+1−1
@@ -1,6 +1,6 @@ [package] name = "fusion-dashboard"-version = "1.9.284"+version = "1.9.285" edition = "2021"  [dependencies]
install.sh+1−1
@@ -21,7 +21,7 @@ echo "Installed fusion-bridge skills: $(ls "$PKG_DIR/skills" 2>/dev/null | tr '\ # binary (the three-artifact pattern: bridge zip -> ab, THIS pkg -> container skills + # this download, page media -> wiki). Per the wiki's PKG_VS_RELEASE policy, binaries are # platform-tagged RELEASE assets, never tarball content - the installer fetches them.-DASH_VERSION="1.9.284"+DASH_VERSION="1.9.285" DASH_URL="https://wiki.adom.inc/download/adom/fusion-bridge/${DASH_VERSION}/fusion-dashboard" DEST="" if [ -w /usr/local/bin ]; then DEST=/usr/local/bin
package.json+1−1
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.284",+  "version": "1.9.285",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {
page.json+11−8
@@ -1,6 +1,6 @@ {   "slug": "fusion-bridge",-  "version": "1.9.284",+  "version": "1.9.285",   "type": "app",   "description": "This package installs the bridge's SKILLS into your container so your AI knows how to drive it; Adom Bridge loads the bridge runtime itself from the release zip.",   "dependencies": {@@ -11,6 +11,10 @@     "uninstall": "./uninstall.sh"   },   "tags": [+    "ab",+    "bridge",+    "fusion",+    "desktop-app",     "fusion360",     "autodesk",     "cad",@@ -75,7 +79,10 @@     "build a basic parts library",     "add a 3d model to a component",     "make a fusion 360 library with 3d",-    "set up the fusion bridge"+    "set up the fusion bridge",+    "fusion dashboard",+    "fusion status page",+    "open the fusion launch page"   ],   "discovery_pitch": "Use the Adom Bridge Fusion 360 bridge when the user wants to DRIVE Autodesk Fusion 360 from the cloud: open/search their team's Fusion cloud designs, lay out a PCB or do electronics, export STEP/IGES/STL/Gerbers/BOM, or build a component library with real rendering 3D. It runs Fusion on the user's own machine via Adom Bridge. NOT for web/browser/login/form-filling (that's the native-browser extension) and NOT for KiCad (that's the kicad bridge).",   "brief": "Drive Autodesk Fusion 360 from the cloud via Adom Bridge: component libraries, IPC package generation, board layout, exports (STEP/Gerbers/BOM/CPL), fast APS cloud search, and parametric modeling.",@@ -104,9 +111,5 @@       "label": "Part volume",       "prompt": "How much material does each printed part in this assembly use?"     }-  ],-  "author": {-    "name": "John Lauer",-    "email": "[email protected]"-  }-}\ No newline at end of file+  ]+}
server.py+7−1
@@ -4822,7 +4822,10 @@ def _banner(action, title="", detail="", percent=None, eta_sec=None, tone="worki         _BANNER_STATE["active"] = False      def _send():-        for _to in (8, 20, 30):+        # A terminal hide must land even if the add-in is busy saving/reopening the library+        # for a while: keep trying for a few minutes (a palette left behind after a run is+        # what John saw, 2026-08-30).+        for _to in ((8, 20, 30, 30, 30, 30, 30, 30) if action == "hide" else (8, 20, 30)):             try:                 r = _proxy_to_addin("activity_banner",                                     {"action": action, "title": title, "detail": detail,@@ -12902,6 +12905,9 @@ def _handle_create_library(fusion_info: dict, args: dict) -> dict:         # ceiling) a non-pumping main thread is WORK, not a hang (#49 mid-mint kills).         _LONG_OP["name"] = "create_library:" + run_id         _banner_watchdog_start(run_id)   # keep the in-Fusion palette present for the whole run+        # 'show' (not 'update') at the start of a run: it re-opens a palette the user closed+        # with its X during an earlier run; every later update respects a close.+        _banner("show", title="%s: starting" % _lib_label(lbr_p), detail="checking the bundle and Fusion")         _LONG_OP["since"] = __import__("time").time()         _docs_before = _open_doc_names()   # chain hygiene: everything opened after this is ours         # OWNERSHIP (John, 2026-08-30): every document this chain opens is the BRIDGE's, so a