doxa-maps/library/layouts
Doxa Maps 813a52ebbe One page loaded the same bundle twice and nothing said so
Every plugin ships as a self-contained IIFE, so library/utils/embedScript.js is
compiled separately into each one: eleven bundles on main each carry their own
copy. Its cache was a module-scoped const, which made "load exactly once" true
per BUNDLE rather than per page — the opposite of what the file's own header
claimed. Two shells nesting the same child (the Research Gallery and the Plugin
Studio both mounting doxa-research-map) each injected it.

Nothing failed, which is why it survived. The second request came from the HTTP
cache, so no failed request and no extra network row; the bundle then executed in
full a second time — a second Vue, a second vue-i18n, a second pinia, ~375 KiB
gzip of work — and its guarded customElements.define quietly declined at the very
end. Where the two URLs are different versions of one plugin, whichever executed
first owns the tag and the other is discarded in silence.

The cache now lives on globalThis, the only scope that matches the claim, under a
versioned key so a page mixing old and new bundles falls back to a cache each
rather than misreading a shape it does not expect. Entries are keyed by the
resolved href, because the callers do not pass the same string for one file.
A <script> already in the document is adopted, which is what covers bundles built
before this and host pages that write their own tag; that resolves immediately,
and it is safe only because every caller here follows with a wait on the element
itself — the note in the file says so, since a new caller must keep that pairing.

claimTag is a diagnostic, not a guard: by the time a second bundle arrives the
define is long since won, so the thing that was missing was anyone saying so.

The check imports the module twice under different URLs — two module instances
sharing one globalThis, the same shape as two IIFEs on one page. It asserts the
second caller receives the FIRST one's promise, not merely that one script tag
exists: mutation-testing showed the tag count alone is satisfied by the adoption
path, so it would have passed with the original bug restored.
2026-09-29 00:06:51 +00:00
..
app-wrapper-material Decouple the library's prose: no host-platform names in the QA widget, StackLayout or script notes 2026-09-26 17:28:45 +00:00
pane-wrapper Brand-neutral shared VerticalTabsLayout with lazy nested-element hosting; fix cap-5 prayer tab leaf path; neutralize library identifiers; add Forgejo CI build-gate workflow 2026-09-19 17:24:30 +00:00
README.md Move internal files out of the repo; fix five dead links; a fuller delegation diagram 2026-09-25 05:14:10 +00:00
StackLayout.vue One page loaded the same bundle twice and nothing said so 2026-09-29 00:06:51 +00:00
VerticalTabsLayout.vue One page loaded the same bundle twice and nothing said so 2026-09-29 00:06:51 +00:00

layouts/ — app wrappers

Each folder here is an app wrapper: a host shell that composes doxa-maps plugin-designs (the independently-built custom elements) into one application.

  layouts/<wrapper>/          ← the HOST page (not a bundle)
  ┌──────────┬──────────────────────────────┐
  │ sidebar  │  mounts an app-profile here    │
  │ (nav.js) │  ┌──────────────┐              │
  │  ├ item  │  │ <doxa-map>    │  ← a         │
  │  ├ section│ │  (a bundle)   │    micro-    │
  │  └ folder │ └──────────────┘    frontend  │
  └──────────┴──────────────────────────────┘
  • app-profile = a self-contained custom element (the micro-frontend). app wrapper = the host that mounts them and provides nav/chrome. A wrapper is a host page, not a bundle-in-a-bundle.
  • Wrappers today: app-wrapper-material/, pane-wrapper/.
  • Icons are inline SVG only (no CDN) — an F-Droid/offline hard requirement.

Sidebar contract, target filtering, sidebar modes, staging: ../documentation/architecture/app-wrappers.md.