Context
Successor to #18564, which #18565 resolved and which must stay closed — a ticket resolved by a merged PR is a record, and reopening it makes the Resolves linkage ambiguous. #18564's fix was correct and incomplete: it removed the collision at library load and left it at first render.
@neo-opus-grace found the remainder on merged dev and I reproduced it independently before accepting.
route: learn/tutorials/DockLayoutsFirstLayout at dev@87e3786673
containers: 2 | svg: 0 | define.amd: true | livePreviews: 2
[error] Can only have one anonymous define call per script file
at …/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs:6:203The Problem
mermaid.esm.min.mjs is a 30 KB entry that lazily imports from dist/chunks/mermaid.esm.min/ — 206 chunks. The entry carries no define, which is why #18565's switch made loadFiles() and initialize() succeed. Ten chunks still do, so the anonymous define lands in Monaco's loader at the moment a diagram first renders.
| measure |
value |
.mjs files under mermaid/dist |
274 |
files containing typeof define |
10 |
define( call sites |
26 |
of those, guarded by define.amd |
22 |
⚠️ The .amd guard does not protect us, and that is the counter-intuitive part. Monaco's define has .amd, so typeof define=="function"&&define.amd takes the AMD branch exactly like the unguarded wrappers do. All 26 sites are hazards under Monaco. Two of them are vendored fastdom, not mermaid's own code, and check only typeof define == "function".
Why #18565's arm went green. LearnMermaidRender.spec.mjs drives guides/uibuildingblocks/DockLayouts, which has zero live-preview fences. @neo-opus-grace's diagnosis is the general form and it corrects that arm's AC-2 directly:
The precondition asserts the loader is present. The defect needs the loader to be busy.
define.amd === true on both routes, because the portal installs the loader app-wide — so the precondition passes everywhere and separates nothing. Monaco only has an anonymous define in flight where live previews instantiate editors.
| route |
mermaid fences |
live-preview fences |
define.amd |
with SVG |
guides/uibuildingblocks/DockLayouts |
2 |
0 |
true |
4/4 ✅ |
tutorials/DockLayoutsFirstLayout |
1 |
3 |
true |
0/2 ❌ |
Same session, same tab, failing route re-tested immediately after the passing one — so not a poisoned loader, not a cache, not the diagram type.
The Architectural Reality
Two approaches are already falsified, before anyone spends a cycle on them:
- Hiding
window.define around the render. Chunks load asynchronously and, on exactly the routes that break, Monaco is concurrently loading modules. Hiding define trades a mermaid failure for a Monaco one.
- Rewriting the identifier (
define → an undefined name). It breaks the typeof define && define.amd form: typeof X yields the truthy string "undefined", and the right-hand X.amd then throws a ReferenceError. A rewrite that neutralises one guard form detonates another.
What works — verified end to end, not reasoned. A module-scope shadow prepended to each affected chunk:
const define={amd:!1};
| guard form present in dist |
with the shadow |
typeof define=="function" |
"object"=="function" → false |
typeof define && define.amd |
"object" && false → false |
(typeof define)[0]=="f" |
"o"=="f" → false |
Every form takes the non-AMD branch and none throws. No affected file declares define already (grep -c 'const define\|let define\|var define\|function define' → 0), and an ES module's top-level binding is file-scoped, so the shadow cannot leak.
Proof, patching one chunk locally on the failing route and reverting byte-identical afterwards:
|
before |
after |
| containers |
2 |
2 |
| with SVG |
0 |
2 |
| console error |
anonymous-define |
none |
The Fix
A build step that copies node_modules/mermaid/dist/ into dist/mermaid/ and prepends the shadow to the affected files in transit, with main.addon.Mermaid#mermaidPath pointed at the built artifact.
It also closes a second, latent defect recorded on #18564's Out of Scope: dist/ contains no mermaid and no build script copies one, so today's node_modules path is unreachable in a real deployment. One artifact answers both.
Not the build-highlightjs shape. I proposed that precedent before reading it: buildScripts/build/highlightJs.mjs clones the upstream repository and builds from source. For mermaid's 206-chunk esbuild output that is disproportionate — copy-and-transform is the smaller correct thing, and it keeps the pinned npm version as the single source of truth.
Acceptance Criteria
Out of Scope
- #18548's author-runnable verification path — the documented render-check with a deliberate-syntax-error control, plus the
guide-authoring §3 corrections that span neo-agent-skills. This unblocks it further; it does not claim it.
- The portal instantiating guide content twice. Measured in passing on both routes — 2 containers for 1 fence, identical
textContent length. Pre-existing, unrelated, and it is why container counts read double.
- Patching
node_modules at install time (postinstall, patch-package). It would fix the dev path and leave dist/ unreachable, i.e. solve the smaller half of the problem and hide the larger one.
Avoided Traps
- Sampling instead of polling. The first measurement after the patch read
svg: 1 of 2 and I nearly filed a partial fix. Mermaid renders per-container asynchronously; the settled state is 2/2. This bit me twice today on the same surface.
- Asserting the container. #18548 names it: the container is emitted from the fence alone and is present throughout the outage.
- Trusting a
.amd guard. 22 of 26 sites have one and every one of them still fires under Monaco.
Decision Record impact
none. A build artifact and a path change; no runtime contract moves.
Related
- #18564 / #18565 — the entry-level half, resolved and staying closed
- #18548 — the verification-path ticket this unblocks further
- #18355 /
ffd860b664 — the Monaco lifecycle change that made the collision reachable
Handoff Retrieval Hints
query_raw_memories: "mermaid lazy chunk UMD define shadow monaco busy live preview"
- Reproduce:
#/learn/tutorials/DockLayoutsFirstLayout on a fresh origin, then poll document.querySelectorAll('.neo-mermaid svg').length
- Anchors:
node_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs (the fastdom wrappers), src/main/addon/Mermaid.mjs#mermaidPath, buildScripts/build/highlightJs.mjs (the precedent NOT to copy)
Origin Session ID: 0dd7d644-f279-4b53-a515-522346f5f28f
Context
Successor to #18564, which #18565 resolved and which must stay closed — a ticket resolved by a merged PR is a record, and reopening it makes the
Resolveslinkage ambiguous. #18564's fix was correct and incomplete: it removed the collision at library load and left it at first render.@neo-opus-grace found the remainder on merged
devand I reproduced it independently before accepting.route: learn/tutorials/DockLayoutsFirstLayout at dev@87e3786673 containers: 2 | svg: 0 | define.amd: true | livePreviews: 2 [error] Can only have one anonymous define call per script file at …/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs:6:203The Problem
mermaid.esm.min.mjsis a 30 KB entry that lazily imports fromdist/chunks/mermaid.esm.min/— 206 chunks. The entry carries nodefine, which is why #18565's switch madeloadFiles()andinitialize()succeed. Ten chunks still do, so the anonymousdefinelands in Monaco's loader at the moment a diagram first renders..mjsfiles undermermaid/disttypeof definedefine(call sitesdefine.amd⚠️ The
.amdguard does not protect us, and that is the counter-intuitive part. Monaco'sdefinehas.amd, sotypeof define=="function"&&define.amdtakes the AMD branch exactly like the unguarded wrappers do. All 26 sites are hazards under Monaco. Two of them are vendoredfastdom, not mermaid's own code, and check onlytypeof define == "function".Why #18565's arm went green.
LearnMermaidRender.spec.mjsdrivesguides/uibuildingblocks/DockLayouts, which has zero live-preview fences. @neo-opus-grace's diagnosis is the general form and it corrects that arm's AC-2 directly:define.amd === trueon both routes, because the portal installs the loader app-wide — so the precondition passes everywhere and separates nothing. Monaco only has an anonymousdefinein flight where live previews instantiate editors.define.amdguides/uibuildingblocks/DockLayoutstruetutorials/DockLayoutsFirstLayouttrueSame session, same tab, failing route re-tested immediately after the passing one — so not a poisoned loader, not a cache, not the diagram type.
The Architectural Reality
Two approaches are already falsified, before anyone spends a cycle on them:
window.definearound the render. Chunks load asynchronously and, on exactly the routes that break, Monaco is concurrently loading modules. Hidingdefinetrades a mermaid failure for a Monaco one.define→ an undefined name). It breaks thetypeof define && define.amdform:typeof Xyields the truthy string"undefined", and the right-handX.amdthen throws aReferenceError. A rewrite that neutralises one guard form detonates another.What works — verified end to end, not reasoned. A module-scope shadow prepended to each affected chunk:
const define={amd:!1};typeof define=="function""object"=="function"→ falsetypeof define && define.amd"object" && false→ false(typeof define)[0]=="f""o"=="f"→ falseEvery form takes the non-AMD branch and none throws. No affected file declares
definealready (grep -c 'const define\|let define\|var define\|function define'→ 0), and an ES module's top-level binding is file-scoped, so the shadow cannot leak.Proof, patching one chunk locally on the failing route and reverting byte-identical afterwards:
The Fix
A build step that copies
node_modules/mermaid/dist/intodist/mermaid/and prepends the shadow to the affected files in transit, withmain.addon.Mermaid#mermaidPathpointed at the built artifact.It also closes a second, latent defect recorded on #18564's Out of Scope:
dist/contains no mermaid and no build script copies one, so today'snode_modulespath is unreachable in a real deployment. One artifact answers both.Not the
build-highlightjsshape. I proposed that precedent before reading it:buildScripts/build/highlightJs.mjsclones the upstream repository and builds from source. For mermaid's 206-chunk esbuild output that is disproportionate — copy-and-transform is the smaller correct thing, and it keeps the pinned npm version as the single source of truth.Acceptance Criteria
guides/…/DockLayoutsalready passes today and re-proving it is not evidence.define.amd === trueis true on the passing and failing routes alike and separates nothing. The precondition must assert the condition the defect needs — an instantiated live preview / editor — or the arm repeats #18565's mistake with a new coordinate.dev@87e3786673the tutorial route is red and the guide route green, so a correct arm distinguishes them before any fix lands. Both directions on the same arm.chunk-DZP67EKU.mjssufficed for this route;ganttDiagram-*,architectureDiagram-*,cose-bilkent-*and the non-minmermaid.esm/twins all carry wrappers, and other diagram types load them. The transform is driven by a scan, never a filename list.define, the build refuses rather than emitting a silently broken artifact.dist/reachability. A diagram renders in a built environment wherenode_modulesis not part of the deployed tree.Out of Scope
guide-authoring§3 corrections that spanneo-agent-skills. This unblocks it further; it does not claim it.textContentlength. Pre-existing, unrelated, and it is why container counts read double.node_modulesat install time (postinstall,patch-package). It would fix the dev path and leavedist/unreachable, i.e. solve the smaller half of the problem and hide the larger one.Avoided Traps
svg: 1of 2 and I nearly filed a partial fix. Mermaid renders per-container asynchronously; the settled state is 2/2. This bit me twice today on the same surface..amdguard. 22 of 26 sites have one and every one of them still fires under Monaco.Decision Record impact
none. A build artifact and a path change; no runtime contract moves.Related
ffd860b664— the Monaco lifecycle change that made the collision reachableHandoff Retrieval Hints
query_raw_memories: "mermaid lazy chunk UMD define shadow monaco busy live preview"#/learn/tutorials/DockLayoutsFirstLayouton a fresh origin, then polldocument.querySelectorAll('.neo-mermaid svg').lengthnode_modules/mermaid/dist/chunks/mermaid.esm.min/chunk-DZP67EKU.mjs(the fastdom wrappers),src/main/addon/Mermaid.mjs#mermaidPath,buildScripts/build/highlightJs.mjs(the precedent NOT to copy)Origin Session ID: 0dd7d644-f279-4b53-a515-522346f5f28f