Context
@tobiu reported it with the diagnosis attached, which is why this is a leaf rather than an investigation:
loader.js:965 Uncaught Error: Can only have one anonymous define call per script file
at c.enqueueDefineAnonymousModule (loader.js:965:11)
at p (loader.js:1297:48)
at mermaid.min.js:456:3421
Mermaid.mjs:66 Uncaught (in promise) ReferenceError: mermaid is not defined
at Mermaid.loadFiles (Mermaid.mjs:66:9)loader.js is Monaco's AMD loader, and it is in the stack calling into mermaid's define. Split off #18548, which owns the broader "a guide's diagram can be render-verified before merge" path and whose ACs span this repo and neo-agent-skills. This is the proximate defect underneath it.
The Problem
main.addon.Mermaid loads node_modules/mermaid/dist/mermaid.min.js — the UMD bundle. UMD checks for define.amd before falling back to a global. main.addon.MonacoEditor:129 loads Monaco's loader.js, which publishes exactly that define.
On any page carrying both, mermaid's anonymous define lands in Monaco's loader, which refuses it (Can only have one anonymous define call per script file). The library then never attaches to window, and loadFiles throws mermaid is not defined on the next line. Every diagram on the page fails.
Measured on the running portal, dev @ bf4a73ce4b:
| probe |
result |
!!(window.define && window.define.amd) on the guide route |
true — the loader is present |
.neo-mermaid containers |
4 |
.neo-mermaid svg |
0 |
The portal is the surface where both libraries meet, which is why the symptom reads as portal-only.
Regressed by ffd860b664 (2026-09-05, "fix(monaco): restore editor initialization and lifecycle (#5822) (#18355)"), which restructured loadFiles and its loader install. The Mermaid addon has not changed since January — the collision arrived from the other side.
The Architectural Reality
src/main/addon/Mermaid.mjs — mermaidPath points at mermaid.min.js (UMD); loadFiles uses DomAccess.loadScript; loadFiles and render both reach a bare global mermaid.
src/main/addon/MonacoEditor.mjs:129 — await DomAccess.loadScript(path + '/loader.js').
node_modules/mermaid/dist/ ships mermaid.esm.min.mjs, and the package's own exports map resolves . to an ESM entry. The UMD bundle is the one build we should not be using.
src/util/HighlightJs.mjs:104 is the in-repo precedent for importing a library as a module: await import(/* webpackIgnore: true */ path).
The Fix
Import the ESM build instead of script-tagging the UMD one, and hold the library on the addon instead of on window — the same shape as util.HighlightJs#hljs. An ESM import has no define to find, so the conflict cannot occur regardless of load order.
One trap this has to get right, and it is not obvious. Neo.config.basePath is written for DomAccess.loadScript, whose <script src> resolves against the document. A dynamic import() resolves against the importing module's own URL, so a bare basePath from src/main/addon/ resolves to /src/node_modules/… and 404s. The path must be resolved against document.baseURI explicitly, which also makes it depth-independent. (HighlightJs avoids this only because src/util/ happens to sit at the depth its ../../ assumes — worth knowing before that file moves.)
Acceptance Criteria
Out of Scope
- #18548's verification path — a documented, author-runnable render check with a deliberate-syntax-error control, plus the
guide-authoring §3 corrections. Those ACs span neo-agent-skills; this ticket unblocks them by making the renderer work, and does not claim them.
- Monaco's AMD loader itself. It is global and will collide with the next UMD script someone loads. Removing that hazard means changing how Monaco is loaded, which is a far larger change with its own risk; recorded here as the residual it is.
- Mermaid's reachability in a
dist build. dist/ contains no mermaid and no build script copies it, so the node_modules path is a live question for a real deployment — separate from this dev-mode regression, and not made worse by it.
- The portal instantiating guide content twice. Measured in passing: 4 containers for 2 fences, in two identical pairs. Pre-existing, unrelated, noted so the next reader of that count is not surprised.
Avoided Traps
- Asserting the container exists. #18548 names this explicitly: the container proves the fence parsed, not that anything rendered.
- Concluding from a hidden pane. Mermaid renders on layout, so an unlaid-out browser pane produces zero SVGs for a perfectly good fence — the reason the measurement above records a visible, sized viewport.
- Keeping the UMD bundle and stashing
window.define around the load. It works, and it leaves the page with a global mermaid plus a load-order dependency that the next maintainer has to know about.
Decision Record impact
none.
Related
- #18548 — the verification-path ticket this splits from and unblocks
- #18355 /
ffd860b664 — the Monaco lifecycle change that introduced the collision
- #18354 — Monaco bundling, adjacent
Handoff Retrieval Hints
query_raw_memories: "mermaid UMD monaco AMD loader anonymous define portal diagram"
- Reproduce: portal route
#/learn/guides/uibuildingblocks/DockLayouts (slashes, not dots — a dotted route silently renders an empty pane), then evaluate document.querySelectorAll('.neo-mermaid svg').length
- Anchors:
src/main/addon/Mermaid.mjs, src/main/addon/MonacoEditor.mjs:129, src/util/HighlightJs.mjs:104
Origin Session ID: 0dd7d644-f279-4b53-a515-522346f5f28f
Context
@tobiu reported it with the diagnosis attached, which is why this is a leaf rather than an investigation:
loader.js:965 Uncaught Error: Can only have one anonymous define call per script file at c.enqueueDefineAnonymousModule (loader.js:965:11) at p (loader.js:1297:48) at mermaid.min.js:456:3421 Mermaid.mjs:66 Uncaught (in promise) ReferenceError: mermaid is not defined at Mermaid.loadFiles (Mermaid.mjs:66:9)loader.jsis Monaco's AMD loader, and it is in the stack calling into mermaid'sdefine. Split off #18548, which owns the broader "a guide's diagram can be render-verified before merge" path and whose ACs span this repo andneo-agent-skills. This is the proximate defect underneath it.The Problem
main.addon.Mermaidloadsnode_modules/mermaid/dist/mermaid.min.js— the UMD bundle. UMD checks fordefine.amdbefore falling back to a global.main.addon.MonacoEditor:129loads Monaco'sloader.js, which publishes exactly thatdefine.On any page carrying both, mermaid's anonymous
definelands in Monaco's loader, which refuses it (Can only have one anonymous define call per script file). The library then never attaches towindow, andloadFilesthrowsmermaid is not definedon the next line. Every diagram on the page fails.Measured on the running portal,
dev @ bf4a73ce4b:!!(window.define && window.define.amd)on the guide routetrue— the loader is present.neo-mermaidcontainers.neo-mermaid svgThe portal is the surface where both libraries meet, which is why the symptom reads as portal-only.
Regressed by
ffd860b664(2026-09-05, "fix(monaco): restore editor initialization and lifecycle (#5822) (#18355)"), which restructuredloadFilesand its loader install. The Mermaid addon has not changed since January — the collision arrived from the other side.The Architectural Reality
src/main/addon/Mermaid.mjs—mermaidPathpoints atmermaid.min.js(UMD);loadFilesusesDomAccess.loadScript;loadFilesandrenderboth reach a bare globalmermaid.src/main/addon/MonacoEditor.mjs:129—await DomAccess.loadScript(path + '/loader.js').node_modules/mermaid/dist/shipsmermaid.esm.min.mjs, and the package's ownexportsmap resolves.to an ESM entry. The UMD bundle is the one build we should not be using.src/util/HighlightJs.mjs:104is the in-repo precedent for importing a library as a module:await import(/* webpackIgnore: true */ path).The Fix
Import the ESM build instead of script-tagging the UMD one, and hold the library on the addon instead of on
window— the same shape asutil.HighlightJs#hljs. An ESM import has nodefineto find, so the conflict cannot occur regardless of load order.One trap this has to get right, and it is not obvious.
Neo.config.basePathis written forDomAccess.loadScript, whose<script src>resolves against the document. A dynamicimport()resolves against the importing module's own URL, so a barebasePathfromsrc/main/addon/resolves to/src/node_modules/…and 404s. The path must be resolved againstdocument.baseURIexplicitly, which also makes it depth-independent. (HighlightJsavoids this only becausesrc/util/happens to sit at the depth its../../assumes — worth knowing before that file moves.)Acceptance Criteria
--listwithNEO_AGENTOS_RUNTIME_ROOTunset), not merely against a local run — most of the e2e tier does not run there.mermaid. The library is held on the addon;git grepfinds no bare-global read left in the file. A UMD global is a name any other script on the page can collide with, which is the class of defect being removed.Out of Scope
guide-authoring§3 corrections. Those ACs spanneo-agent-skills; this ticket unblocks them by making the renderer work, and does not claim them.distbuild.dist/contains no mermaid and no build script copies it, so thenode_modulespath is a live question for a real deployment — separate from this dev-mode regression, and not made worse by it.Avoided Traps
window.definearound the load. It works, and it leaves the page with a globalmermaidplus a load-order dependency that the next maintainer has to know about.Decision Record impact
none.Related
ffd860b664— the Monaco lifecycle change that introduced the collisionHandoff Retrieval Hints
query_raw_memories: "mermaid UMD monaco AMD loader anonymous define portal diagram"#/learn/guides/uibuildingblocks/DockLayouts(slashes, not dots — a dotted route silently renders an empty pane), then evaluatedocument.querySelectorAll('.neo-mermaid svg').lengthsrc/main/addon/Mermaid.mjs,src/main/addon/MonacoEditor.mjs:129,src/util/HighlightJs.mjs:104Origin Session ID: 0dd7d644-f279-4b53-a515-522346f5f28f