← Commit history
CLAUDE.md+10
@@ -16,6 +16,16 @@ all of this and filed its own bridge bugs as "adom-desktop" requests. Don't.) - So: a `fusion_*` verb behaves wrong, a hint is missing, APS misbehaves, the add-in is stale → it's   **your code**, fix it here and republish. It is NOT an adom-bridge request. +## ⛔ NEVER LET FUSION DRAW A DIALOG (John, 3 reports; read the skill)++A modal freezes the add-in's single main thread, so the bridge stops answering and every+later verb reads as a crash - and it dings the user's speaker. **Prevention, never cleanup:**+prove the command unnecessary offline (`_resolve_artifact_name` against the `.lbr`), or+pre-answer it (`electron_run {confirm: "yes"|"no"}` -> `SET CONFIRM`, so the dialog is never+drawn). A dismissal sweep is a BACKSTOP whose every firing is a defect report. If you are+writing a hint telling the caller to dismiss a dialog afterwards, you have the bug.+Full rules + the command table: `skills/fusion-no-modal-dialogs/SKILL.md`.+ ## THE AUTOMATION DOCTRINE (John, 2026-08-18, verbatim - this outranks tidy error messages)  > "your whole goal is to automate the life of the adom user and never slow them down or stop
addin/AdomBridge/commands/electronics.py+29
@@ -327,6 +327,9 @@ def handle_electron_run(app: adsk.core.Application, args: dict) -> dict:         args: Dict with:             - command: The EAGLE command to execute (required).               Examples: "EDIT resistor.sym", "USE mylib.lbr", "WINDOW FIT"+            - confirm: "yes" | "no" (optional). Pre-answers any confirmation the command+              would raise, via SET CONFIRM, so the modal is never drawn. "yes" = we mean to+              create; "no" = inspect only. EDIT with no `confirm` defaults to "no".     """     eagle_cmd = args.get("command", "")     if not eagle_cmd:@@ -394,6 +397,32 @@ def handle_electron_run(app: adsk.core.Application, args: dict) -> dict:             },         } +    # ⛔ DECLARE CONFIRM INTENT BEFORE ANY COMMAND THAT CAN PROMPT.+    # John, 2026-08-26 (3rd report of the same modal): "why do you still allow these dialogs+    # to pop up ... dialogs are dangerous cuz they're usually modals and kill programmatic+    # code from running ... and it dings the computer's speaker."+    # EAGLE/Electron has a deterministic answer for this and we were only using it for+    # EXPORT: SET CONFIRM pre-answers the confirmation so the modal is NEVER DRAWN. There is+    # no third state - a command that can prompt must say which answer it wants:+    #   confirm="yes" -> we INTEND to create (EDIT <new>.pac to author a footprint)+    #   confirm="no"  -> we are only INSPECTING; a prompt means a bad name, decline it+    # The caller declaring intent is what makes this deterministic instead of a race with a+    # dismissal sweep, which loses whenever Fusion is slow.+    _confirm = str(args.get("confirm") or "").strip().lower()+    if _confirm in ("yes", "no"):+        try:+            app.executeTextCommand("Electron.run SET CONFIRM %s" % _confirm.upper())+        except Exception:+            pass  # best effort; the server-side name pre-check is the primary guard+    elif cmd_verb == "EDIT":+        # An EDIT with no declared intent is the exact call that raised John's dialog. We+        # cannot know whether a create is wanted, so refuse to gamble the main thread: the+        # safe answer is NO (decline the create), which also leaves no stray artifact.+        try:+            app.executeTextCommand("Electron.run SET CONFIRM NO")+        except Exception:+            pass+     # Auto-suppress overwrite/confirmation dialogs for EXPORT commands.     # Without this, EAGLE shows a Qt "Overwrite?" dialog that blocks the     # add-in thread and is invisible to the CLI/AI — freezing everything.
addin/AdomBridge/commands/open_lbr.py+3
@@ -78,6 +78,9 @@ def handle_open_lbr(app: adsk.core.Application, args: dict) -> dict:          sym_ref = symbol_name if symbol_name.endswith(".sym") else f"{symbol_name}.sym"         try:+            # inspect-only navigation: pre-answer NO so a stale/renamed sym_ref can never+            # raise a blocking "Create new ...?" modal on the add-in main thread.+            app.executeTextCommand("Electron.run SET CONFIRM NO")             app.executeTextCommand(f"Electron.run EDIT {sym_ref}")             results.append(f"EDIT {sym_ref}: ok")         except Exception as e:
page.json+7−10
@@ -11,10 +11,6 @@     "uninstall": "./uninstall.sh"   },   "tags": [-    "ab",-    "bridge",-    "fusion",-    "desktop-app",     "fusion360",     "autodesk",     "cad",@@ -79,10 +75,7 @@     "build a basic parts library",     "add a 3d model to a component",     "make a fusion 360 library with 3d",-    "set up the fusion bridge",-    "fusion dashboard",-    "fusion status page",-    "open the fusion launch page"+    "set up the fusion bridge"   ],   "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.",@@ -111,5 +104,9 @@       "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
skills/fusion-no-modal-dialogs/SKILL.mdadded+150
@@ -0,0 +1,150 @@+---+name: fusion-no-modal-dialogs+description: >+  NEVER let Fusion raise a modal dialog from automation. A modal freezes the add-in's main+  thread (so every later verb reads as a crash), dings the user's speaker with an alert+  sound, and cannot be reliably cleaned up after the fact. Every command that CAN prompt+  must pre-answer it (SET CONFIRM) or be proven unnecessary offline first. Read before+  writing any code that issues EDIT, EXPORT, WRITE, ADD, SHOW, GRID, OPEN, or CLOSE against+  Fusion Electronics, and before "fixing" a dialog by dismissing it.+  Trigger words - create new footprint, create new symbol, dialog popped, modal, blocking+  dialog, dismiss dialog, SET CONFIRM, EDIT .pac, EDIT .sym, add-in not responding, Fusion+  hung, unsaved changes prompt, save changes dialog, alert sound, speaker ding.+discovery_triggers:+  - "create new footprint"+  - "create new symbol"+  - "fusion dialog"+  - "blocking modal"+  - "SET CONFIRM"+  - "add-in not responding"+---++# Never let Fusion draw a dialog++**The doctrine (John, verbatim, after the THIRD report of the same modal):**++> "why do you still allow these dialogs to pop up? i've asked you 100's of times to never+> allow that. you can pre-empt it by always deterministically closing out items from code+> and telling the api you call to 'not save changes' or you could literally save it+> somewhere programatically so 'there are no changes' and then you wouldn't get prompts like+> that. dialogs are dangerous cuz they're usually modals and kill programmatic code from+> running, especially async tasks you're calling into fusion. and it dings the computer's+> speaker with an error alert audio sound which is annoying to the user."++## Why a dialog is a SEVERITY-1 bug, not an inconvenience++1. **It freezes the add-in's main thread.** Fusion's API is single-threaded. A modal sits on+   that thread, so the add-in's HTTP server stops answering. Every subsequent verb times out+   and reads as "add-in not responding" or "Fusion crashed". One un-answered prompt looks+   exactly like a dead bridge, and the debugging goes somewhere else entirely.+2. **It kills in-flight async work.** Any chain already running (a library import, a matrix+   run, a capture sweep) stalls mid-way and leaves half-built state behind.+3. **It dings the user's speaker.** An alert sound at a human who did not touch anything, on+   a machine they are using for something else.+4. **Dismissal is a race you lose.** Sweeping the dialog after it appears means it was+   already drawn, already sounded, and already blocked the thread. When Fusion is slow the+   sweep arrives late and the modal just sits there.++## The rule++> **A command that can prompt must either be proven unnecessary offline, or carry its answer+> with it. Never issue one and plan to clean up.**++Three techniques, in priority order. Use the highest one that applies.++### 1. Prove it offline. Do not ask Fusion a question you can answer yourself.++The `.lbr` on disk lists every symbol, package, and deviceset. `EDIT <name>.pac` on a name+that is not in the library is what raises **"Create new footprint 'X'?"**. That is knowable+in milliseconds without Fusion.++```python+resolved, err = _resolve_artifact_name(name, "pac", lbr_path)+if err:+    return err          # EDIT is NEVER issued. No dialog is possible.+```++**The trap that made this fail for months:** the guard needs the `.lbr` path, and+`fusion_capture_library_views` never took one, so the single verb that EDITs the most names+was the one verb structurally unable to run the guard. It "handled" the prompt by raising it+and dismissing it. If a guard cannot reach the data it needs, **give it the data** (here:+`_remember_open_library()` persists the open `.lbr` so any later verb can pre-check). Do not+downgrade to sweeping.++### 2. Pre-answer it. Declare intent so the modal is never drawn.++EAGLE/Electron has a deterministic answer built in, and it is not a dismissal - the dialog+is never rendered at all:++```+SET CONFIRM YES    # we INTEND to create this artifact+SET CONFIRM NO     # inspect only; a prompt means a bad name, decline it+```++Every `electron_run` that can prompt takes `confirm: "yes" | "no"`. **`EDIT` with no declared+intent defaults to `"no"`**, because an undeclared EDIT is precisely the call that raised+John's dialog, and declining leaves no stray artifact.++This answers John's "why can't creating a footprint be triggered so the dialog doesn't show+up?" - it can: `confirm: "yes"` **is** the create, performed without a question.++### 3. Remove the reason to ask.++For save/close prompts, do not answer "don't save" - arrange for there to be nothing to+save. Save programmatically first, or close with the explicit no-save flag, so Fusion has no+question. See `_close_docs_opened_since` and `handle_close_all_documents({force, closeUntitled})`.++## Dismissal is a BACKSTOP, and it must shout++Keep a sweep as a last resort, but treat every firing as a **defect report**: it means a+prevention hole exists. Log it loudly and record it, so it cannot rot into the normal path.++```python+dismissed = _dismiss_dialogs_bg()+if dismissed:+    print("[Fusion Bridge] ⚠ PRE-CHECK HOLE: EDIT %s.%s still raised a dialog" % (name, ext))+    _bridge_action_ledger("dialog_precheck_hole", {...})+```++If you find yourself writing a hint that tells the CALLER to run+`fusion_dismiss_blocking_dialogs` afterwards, stop. You are telling a user to sweep up a+mess you chose to make.++## Never swallow a guard's failure++The first version of `_remember_open_library` ended in `except Exception: pass`. It raised+`NameError` on every call (this module imports `time` inside functions, not at module level),+so the guard was **silently disarmed while the code read as correct**. The offline test+caught it only because it asserted on the EDITs actually sent, not on the guard's presence.++- A prevention guard that fails quietly is worse than none: it removes the pressure to notice.+- `except Exception: pass` around a safety mechanism is a bug, always. Log it.+- **Test what reached Fusion, not what the code says.** Stub the add-in proxy, record every+  command, and assert the dangerous one is absent:++```python+bad = [e for e in edits if "TJA1042T.pac" in e]+assert not bad, "EDIT was issued -> dialog would pop"+```++## Commands that prompt (assume prompting unless proven otherwise)++| Command | Prompt | Prevention |+|---|---|---|+| `EDIT <new>.sym/.pac/.dev` | "Create new ...?" | Resolve name offline; else `confirm` |+| `EXPORT` onto an existing file | "Overwrite?" | Pre-delete the target, `SET CONFIRM YES` |+| `WRITE` | Save As | Never call from automation; pass an explicit path |+| `ADD` | component picker | Never from automation |+| `SHOW <name>` | interactive highlight | Avoid; use a capture instead |+| `GRID` (no params) | grid settings | Always pass params |+| closing a dirty document | "Save changes?" | Save first, or close with `force` |+| opening a malformed `.lbr` | "has errors and cannot be opened" | `_preflight_lbr` refuses it offline |++## The check before you ship any Fusion-touching code++1. List every command this path can send.+2. For each, ask: **can it prompt?** If unsure, assume yes.+3. For each that can: is it proven unnecessary offline, or does it carry `confirm`?+4. Is there a dismissal sweep doing load-bearing work? If yes, that is the bug.+5. Does any guard swallow its own failure? Make it shout.+6. Test by recording the commands that actually reach Fusion.