/** * embedScript — load another bundle's script and wait for its element to register. * * ★ WHAT AN APP SHELL DOES. A capability-5 plugin nests other plugins, so it needs three things: * work out the tree root it was served from, load a bundle's `.js` exactly once, and wait for the * custom element to actually exist before mounting it. Both shells in this repo — the Research * Gallery (`doxa-maps-spa`) and the Plugin Studio (`plugin-studio`) — had their own copy of all * three, identical in code (DEDUP-AUDIT.md, 2026-09-28: 33 lines, the only cross-plugin cluster * that is identical, closes over nothing, and spans one owner's plugins). * * Moved verbatim; the comments below are the originals, because each records a measurement. * * ★ "EXACTLY ONCE" WAS ONLY EVER TRUE INSIDE ONE BUNDLE, AND THAT IS THE BUG THIS FILE NOW FIXES. * An earlier version of this header claimed that sharing `_loaded` here meant "two shells on one * page ... now the second one reuses the first's promise". That was wrong, and the reason is the * packaging, not the code: every plugin ships as a self-contained IIFE with `inlineDynamicImports`, * so THIS MODULE IS COMPILED SEPARATELY INTO EACH BUNDLE and a module-scoped `const _loaded = {}` * is one cache PER BUNDLE, not per page. MEASURED on origin/main's shipped tree 2026-09-28: * 11 built bundles each carry their own copy. * * What that cost, and why it was invisible: every entry file guards its registration with * `if (!customElements.get(tag)) customElements.define(tag, …)`, so the second load did NOT throw. * It fetched from the HTTP cache (so the network panel looked innocent), then EXECUTED THE WHOLE * BUNDLE a second time — a second Vue, a second vue-i18n, a second pinia, ~375 KiB gzip of work for * doxa-research-map — and only at the very end declined to define the tag it had just built * everything for. Nothing warned. The page simply ran twice as much script as it needed. * Worse than the waste: if the two URLs are different VERSIONS of the same plugin, whichever * executed first owns the tag and the other is silently discarded. * * So the cache now lives on the PAGE (`globalThis`), which is the only scope that matches the * claim. See `registry()` for why the key is versioned. */ /** * The page-wide registry. Bundles cannot share a module, but they all share one global object. * * ★ THE KEY CARRIES A VERSION ON PURPOSE. A page can mix bundles built weeks apart, so an old * bundle and a new one must never read each other's registry through a shape either of them does * not expect. A changed shape therefore gets a NEW key, and the two generations fall back to a * cache each — today's behaviour, which merely wastes work — instead of one of them misreading the * other's and skipping a load that never happened. Separate is recoverable; corrupt is not. * * `globalThis` rather than `window` so this file is loadable under node, where the gates run it. */ const REGISTRY_KEY = '__dxMapsScriptRegistry1' function registry() { const g = typeof globalThis !== 'undefined' ? globalThis : self return g[REGISTRY_KEY] || (g[REGISTRY_KEY] = { loaded: Object.create(null), owners: Object.create(null) }) } /** * Resolve to the absolute URL the browser will actually request. * * ★ THIS IS WHAT MAKES TWO SHELLS AGREE. They do not pass the same string: StackLayout anchors a * sibling bundle to the tree root first (resolveBundleSrc), VerticalTabsLayout passes the row's src * untouched, and a host page may configure an absolute CDN URL. `/dx-maps/x/x.js` and * `https://host/dx-maps/x/x.js` are one file and must be one cache entry, so the KEY is the * resolved href — not the string the caller happened to hold. */ function absoluteHref(url) { try { if (typeof document !== 'undefined' && document.baseURI) return new URL(url, document.baseURI).href if (typeof location !== 'undefined' && location.href) return new URL(url, location.href).href } catch (_) { /* a non-URL string (a test stub, a bare name): key it as given */ } return url } /** * A