10 embedded mods
What this module drills
Two ways to hang somebody else's panel inside your game, and the bill each
one sends. A custom element is a class the browser builds wherever its
dashed tag lands — <mod-panel> written from JSX is a mount, no import, no
shared build. A frame is a second document with a window of its own. Same
panel on screen; nothing underneath is the same.
What the fiction stands for
The host is React — the JSX in these koans is your own app, the one you ship. The mod is an app somebody else built in another framework: Angular, Vue and Svelte can each compile a component down to a custom element, and that element is the whole handoff. Your bundler never learns their framework exists; it sees a dashed tag.
The koans register the element by hand with customElements.define, because
the seam behaves identically whichever framework produced it — Angular's
createCustomElement emits the same kind of class. The frame half stands for
the other option: shipping that same app as a standalone page and pointing an
<iframe> at it.
Why it bites
The seams are quiet. A JSX prop lands as a property when the element
already has one by that name, and the object arrives whole; with no such
property it lands as an attribute, where setAttribute runs String()
over it and a loadout becomes the literal text [object Object]. Name a
callback prop onNotify and React keeps it: no property, no attribute, no
listener, nothing thrown.
Past the props, one line decides the rest. A custom element owns no
document, no window and no realm of its own — its backdrop is a host node,
its pushState moves the host's address bar, its <style> letter-spaces
the game's own pause button, and nothing it wired up comes undone until it
unwires it. A frame owns all three, which settles those and opens the
opposite set: teardown is free, and list instanceof Array is false for a
perfectly good array the mod just built.
Two findings never reach a test — open a real browser and look instead. A
position: fixed backdrop inside a frame dims the frame and nothing else,
because fixed resolves against the frame's viewport, not the page's. And a
frame keeps its own history, so the view the player is on has no link to
paste, and Back steps the frame instead of the page.
Order
Straight through. 01 and 02 are the handoff — a tag name, then which door a
prop comes in by. 03 is both ways back: the callback React hands over, and
the one it swallows. 04 and 05 are those two browser findings in the only
form a test can hold, which is why they are written the way they are — 04
proves by containment which document a backdrop landed in, 05 keeps every
navigation on the host's side. 06 and 07 are what the custom element
charges: a tree of its own for the stylesheet, a disconnectedCallback for
the rest. 08 is what the frame charges — a second realm, and a door guard
that stops asking whose Array stamped the list. 08 runs under Node, the
rest under pnpm koan.