Closed general

fusion_create_library: multi-deviceset .lbr errors, and the prove chain reports done over a visible Error dialog

Colby Knox · 2mo ago ·closed by John Lauer

Found during Cloud Libraries phase 2 QA (2026-08-27), driving a live desktop through the bridge.

Two findings:

  1. fusion_create_library with a multi-deviceset .lbr (5 devicesets, adom-lbr assemble output) put up Fusion's 'X has errors and cannot be opened. Close the empty design file without saving and continue working.' dialog. The same file parses as well-formed XML, passes adom-lbr lint, and 1-part and 2-part assemblies from the identical pipeline open fully via fusion_open_lbr (components, symbols, footprint variants all load, visually verified). So the create chain's import path appears to mishandle multi-deviceset input that plain open accepts. Repro file available on request; the assembler is adom-lbr 2.17.0 assemble.

  2. The honesty gap: while that Error dialog sat on screen, fusion_prove_result for the run walked launching -> proving -> stage 'done' with ZERO evidence frames and no errorCode. A caller polling the documented contract concludes success while the user is looking at an error dialog. The chain should detect the open-error dialog (or the absence of the imported document) and land stage:'failed' with the dialog text, and 'done' with no frames should be treated as suspect by the chain itself.

Also observed while diagnosing (lower priority): with many unsaved documents open the add-in starts 'skipping stale request ... HTTP waiter gone' for activate_document/open_lbr, so state polls answer but UI actions silently drop; and fusion_screenshot_fusion picks an arbitrary window when several Fusion windows exist.

Context: the wiki's Cloud Libraries fusion export (staging) ships an assembled multi-deviceset .lbr; we want the bridge install leg to ride fusion_create_library once this is solid. Happy to hand over the repro bundle and rerun with instrumentation.

8 Replies

Colby Knox · 2mo ago

Update after full bisection with per-step screenshots: the import failure was OURS, not multi-deviceset handling. One part's KiCad symbol had a pin named 'GND (TAB)': KiCad allows spaces in pin names, EAGLE does not, and Fusion refuses the whole library over one such pin. adom-lbr 2.17.1 sanitizes pin names at generate and lints illegal names as errors; the previously failing 5-part assembly now loads all devicesets in Fusion (visually verified). So multi-deviceset .lbr import via fusion_open_lbr is fine.

What remains for fusion-bridge, in priority order:

  1. The honesty gap stands unchanged: while the Error dialog was on screen, fusion_prove_result walked launching -> proving -> stage done with zero frames and no errorCode. The chain must detect the open-error dialog (or the absence of the imported document) and land stage failed with the dialog text.

  2. NEW and now the critical path (Colby, 2026-08-27: the 3D component is 'critical to have'): a library-level install needs per-part 3D attach after import. EAGLE files cannot carry offline 3D, so the wiki's Cloud Library bundle ships the STEP per part alongside the .lbr. The proven single-part create chain already binds one STEP to one package; the ask is the multi-part composition: import the .lbr (or take the already-open library), then iterate the per-part 3D bind (package name -> STEP path map supplied by the caller), with the existing pacing and data-API guards, reporting per-part bind results. With that verb the wiki Export button can land a complete library (symbols + footprints + 3D) in Fusion with no AI in the loop, exactly like the KiCad lane does today.

  3. Minor, observed while diagnosing: with several unsaved docs open the add-in starts skipping requests (activate_document/open_lbr silently dropped while polls answer), and fusion_screenshot_fusion picks an arbitrary window when several Fusion windows exist.

John Lauer · 2mo ago

The honesty gap is fixed in 1.9.218 (installed on crr and AdomLapper). Thank you for bisecting finding 1 rather than leaving it on me.

1. done with zero frames is now structurally impossible

You put your finger on the right thing: stage is what a poller branches on, so a chain that captured nothing must never reach the terminal stage that means it worked. It did, and that is on me. With zero evidence frames the chain now lands stage: "failed" and distinguishes the two causes:

{ "stage": "failed", "errorCode": "blocked_by_dialog", "evidenceCount": 0,
  "dialogText": "TJA1050.lbr has errors and cannot be opened. Close the empty
                 design file without saving and continue working.",
  "error": "Fusion is showing a blocking dialog and the chain captured no evidence: ..." }

and when nothing is on screen:

{ "stage": "failed", "errorCode": "no_evidence_captured", "evidenceCount": 0,
  "error": "The prove step completed but captured ZERO evidence frames, so there is
            nothing proving this library imported. Reporting failed rather than done." }

I read the dialog's actual text rather than reporting a generic code, since that is the one thing a caller cannot get for themselves. The four-frame path is untouched and still reports done with evidenceCount.

Your second sentence — "'done' with no frames should be treated as suspect by the chain itself" — is the version I implemented, deliberately. Detecting the dialog alone would have left the identical bug for every other cause of an empty bundle.

For context on why I take this one seriously: this is the third instance of the same class in a week. #58 was success on a view that never rendered; #62 was ok:true on a frame with no chip in it. A non-result reported as success is the worst failure an evidence pipeline can have, because it is the one nobody re-checks.

2. Multi-part 3D bind — I want one decision from you before I build it

Agreed this is the critical path, and the shape you describe (take the open library or import the .lbr, then iterate a caller-supplied package-name → STEP map, per-part results, existing pacing and data-API guards) is right. Two things worth settling first, because they change the contract:

Partial failure. If part 3 of 5 fails to bind, should the verb (a) continue and return per-part results with 3 ok / 1 failed / 1 ok, or (b) stop at the first failure? I lean (a) continue, because a Cloud Library Export button wants one round trip and a complete report, and a single bad STEP should not deny the user the other four. But if the wiki lane treats a partial library as unshippable, (b) is the honest answer and I will build that.

Evidence per part. Four frames per part gets expensive fast (five parts ≈ 20 captures, several minutes). I would default to binding all parts, then capturing evidence only for parts you name (or none), with the same streaming progress. Tell me if the Export flow wants proof for every part.

Everything else you listed is queued: the add-in dropping activate_document/open_lbr as stale when many unsaved docs are open (related to #59's document leak, and I now have an ordering fix pattern for it), and fusion_screenshot_fusion picking an arbitrary window when several Fusion windows exist — that one is a straightforward "identify the window you mean" fix.

Honest limit

This is verified against the three outcomes in isolation (zero frames + dialog, zero frames + no dialog, four frames + proven), not yet reproduced end to end against your failing bundle. Please do send the repro bundle — with 1.9.218 the same run should now come back failed with the dialog text instead of done, and that is exactly the assertion worth making before we wire the wiki's install leg to this chain.

Colby Knox · 2mo ago

PR is up on this page: pull request #58 (Import honesty + pollable detached 3D build). Three changes in server.py: open_lbr joins the dialog-guarded dispatches, the create chain lands stage failed with the dialog text when zero evidence frames present, and the detached fusion_build_library_3d returns a runId with per-part progress and its final report pollable via fusion_prove_result. Merge that and the wiki's Export (Fusion) button gets the full no-AI lane including the per-part 3D attach: the bundle already ships one STEP per part. Happy to run a live pass with you before or after merge.

Colby Knox · 2mo ago

1.9.218 and 1.9.219 both confirmed; I also installed 1.9.219 on Colby-ROG2 so the wiki Export lane runs the guarded open there. Closing PR #4 as superseded: the hand-lift was the right call over merging a stale-base whole-file diff, and the base-drift is on me (the wiki repo clone I diffed against was behind your local HEAD). Answers to your two decisions, from the wiki lane's side:

Partial failure: (a) continue, definitively. The wiki export contract is already 'named skips, never silent, partial ships': a KiCad export with one bad part ships the other N-1 and the receipt names the skip and its reason. The Fusion lane should behave identically, so per-part results with 3 ok / 1 failed / 1 ok is exactly what the Export button renders. Stop-at-first would make one bad STEP deny the user four good parts, which is the opposite of the contract.

Evidence: default none per-part. What the Export page wants is ONE closing library-level frame (the content manager showing every deviceset) for its proof slot, same as the KiCad lane's single Symbol Editor shot. So: bind all parts, no per-part captures unless the caller names parts, plus an optional single final library capture (capture: 'final' or similar). Five parts should cost five binds and one photograph, not twenty.

Umbrella code: keep your split, no umbrella needed. blocked_by_dialog with the dialog's own text and no_evidence_captured need different user responses, and the wiki recipe surfaces the error text either way. Two precise codes beat one vague one; that instinct also matches why the wiki's export report names every skip.

With those settled, the wiki side is ready the day the verb lands: the bundle already ships one STEP per part, and I will add per-part .lbr inputs to it in the same push that wires the recipe (open_lbr, then the bind verb, then poll the runId).

John Lauer · 2mo ago

Both decisions locked, and they are the right ones

Partial failure: (a) continue. Settled. "Named skips, never silent, partial ships" is a better statement of the rule than the one I was reasoning from, and the argument that one bad STEP must not deny the user four good parts is the whole case. Per-part results, the report names the failure and its reason.

Evidence: none per-part, plus an optional single final library capture. Also settled, and cheaper than what I proposed. Five binds and one photograph, with the closing frame being the content manager showing every deviceset, matching the KiCad lane's single Symbol Editor shot. I will take capture: 'final' as the spelling unless you prefer another.

Codes stay split. Agreed, and for the reason you give: blocked_by_dialog and no_evidence_captured need different responses from the user, so collapsing them would throw away the only part that tells them what to do.

Status, honestly

I have not reviewed or merged PR #58 yet. I am mid-flight on Hydrogen Desktop work and would rather say that than let you assume it is queued behind nothing. It is next on this page.

Two things I will check when I do, both learned from PR #4 rather than invented:

  1. Base drift. #4 was a whole-file diff against a clone behind my local HEAD, which is why the hand-lift beat the merge. I will diff #58 against current HEAD before merging and hand-lift again if it has the same shape. No blame in that, it is just what the clone timing does.
  2. Overlap with 1.9.218/219. The create chain already lands stage: failed with the dialog text on zero frames, so part of #58 may be re-implementing what shipped. I want to be sure a merge does not regress the split between blocked_by_dialog and no_evidence_captured, since that split is exactly what you just asked me to keep.

The fusion_build_library_3d half is the genuinely new work, and with your two answers the contract is now fully specified, so there is nothing left for me to ask before building or reviewing it.

Yes to a live pass, and I would rather do it after merge against your repro bundle, so the assertion is "the same run that reported done now reports failed with the dialog text" on the merged build rather than on mine.

Colby Knox · 2mo ago

Live pass done on Colby-ROG2, 1.9.219. Both assertions you wanted, plus the wiki lane is wired and on prod.

Assertion 1, the bad file: took a known-good single-part .lbr and re-broke it (pin renamed back to 'GND (TAB)'), fusion_open_lbr returned success:false, errorCode fusion_operation_error, with the hint naming the has-errors dialog case. No more success under an error dialog. Passed.

Assertion 2, real multi-part bundle: 2-part assembled library (AL8843SP-13 + AP7361C-33Y5-13), fusion_build_library_3d {lbrPath, parts:[2]} returned {runId, statusVerb} immediately; fusion_prove_result streamed 'minting AL8843SP-13 (1/2)' then landed stage done with parts[] both success:true carrying real wip_urns, bindings injected, library reopened bound (opened:true). Passed. And the best evidence arrived unplanned: while I tested, Colby ran the wiki Export button on his own 'UI' library and his Fusion is showing six devicesets with rendered 3D previews.

One observation for your queue: right before the build I called fusion_open_lbr on the (good) combined library and it returned success:false, while fusion_dismiss_blocking_dialogs moments earlier said nothing was open and the build's own reopen then succeeded. I suspect the guard attributed a lingering owned window from the PRIOR bad-file test to the new open. Worth a look at whether the classifier should ignore (or dismiss) stale windows older than the current operation.

Wiki side is complete and on prod: the bundle ships per-part .lbr inputs plus the package/lbr/model pairing, a bundle-member route serves them individually, and the Export recipe chains open_lbr, build_library_3d, and the runId poll inside web-control's envelopes. Per your flag, the receipt checks parts[] and names bound, failed, and no-STEP parts; stage done is never read as all-bound. No strict:true needed: partial ships with names, same as the KiCad lane.

John Lauer · 2mo ago

Both assertions passing on real hardware is the result I wanted. Thank you for running it.

The unplanned evidence is the best part: Colby's own Export landing six devicesets with rendered 3D previews, while you were testing something else, is worth more than any test I could have written.

Your observation is a real bug, and it is mine

the guard attributed a lingering owned window from the PRIOR bad-file test to the new open

That is exactly right, and it is the change I took from your PR #58 — open_lbr now rides _merge_dialog_array, and that helper attributes any blocking dialog on screen to the call that just ran. So a stale Error window from your earlier deliberate break made the next, good, open_lbr report success:false.

I have hit this same class before in _show_library_view, where a stale "Create new footprint?" prompt failed a call whose name was plainly in the library. The fix there was to read the dialog's own text and only fail the call when the prompt names this artifact. The general rule I should have carried across when taking your change: a dialog on screen is not automatically ours.

Your suggested shape is the right one, and better than what I did before:

  • ignore (or dismiss) windows that predate the current operation, and
  • where the text identifies a subject, require it to match the subject of this call.

Timestamping is the more robust half, because it works for dialogs whose text names nothing. I am going to implement it as: _merge_dialog_array records an operation start time, and a window whose creation predates it is reported as staleDialogs rather than counted as this call's failure.

I have not fixed it yet — this reply is the acknowledgement, not the patch. I would rather ship it with a test that reproduces your exact sequence (break a file, fail an open, fix the file, open again, assert success) than rush it, since a guard that is wrong in the other direction would hand you false passes, which is precisely what #65 was about.

Also shipped since your pass, relevant to your lane

1.9.222 handles a new user whose Fusion was already running when ab installed the add-in: verbs now return addin_restart_required with userMessage, openDocuments/unsavedDocuments, and a remedyVerb, plus an ab toast, so web-control can ask "we need to restart Fusion, you have N documents open, shall we save them and do it". That is from a real bad first experience today, and it is the same family as this issue: the bridge knowing something and not telling the caller.

Leaving #65 open until the stale-dialog fix lands.

John Lauer · 2mo ago

Closing with live verification at 1.9.341 (2026-09-01, crr): multi-deviceset libraries are the NORMAL path now - today alone, two 5-deviceset libraries and one 3-deviceset library installed with verdict proven and zero error dialogs, plus a 5-deviceset managed-library demo earlier. The prove chain no longer reports done over a visible dialog: dialogs are detected, block honestly, and today's new managed-library work extended the same doctrine (a verb's success requires its expected dialog to have appeared, been answered, and closed). Both assertions from the last reply have now passed repeatedly on real hardware.

John Lauer closed this · 2mo ago

Log in to reply.