// Islands infrastructure. // // An "island" is a custom element that attaches behavior to server-rendered // markup. It never re-renders the server's HTML (no hydration). A key payoff // over the old `querySelectorAll` + `DOMContentLoaded` + `htmx:afterSwap` // re-init dance: the browser upgrades a custom element automatically whenever // it's connected to the document, INCLUDING content HTMX swaps in, so islands // need no manual re-initialization after a swap. // // Pure, testable logic goes in a sibling `*.logic.ts` (no DOM imports) so // `node --test` can exercise it without a DOM; the element file wires that // logic over the light-DOM children. /** Base class: runs `init()` exactly once, the first time the element is * connected (a move/reconnect won't re-run it). */ export abstract class MnwElement extends HTMLElement { #initialized = false; connectedCallback(): void { if (this.#initialized) return; this.#initialized = true; this.init(); // `data-island` means this element's behaviour ran. Nothing styles it; it // exists to be asserted on. // // Set AFTER init() on purpose. If init throws, the attribute is absent and // the element is visibly un-enhanced to a checker, which is the state that // used to be undetectable. // // 2026-08-14 is why this is here. A stale module in the bundle's import // graph made `core/index.js` fail to link, so no island on the site // registered at all — and the page looked fine, because every island in // this codebase enhances server-rendered markup that stands on its own // without it. Progressive enhancement means a dead bundle and a healthy one // differ only in behaviour, and behaviour is the thing nothing was // checking. `scripts/page-smoke.mjs` checks it now, and this is what it // reads. this.setAttribute('data-island', ''); } /** Wire behavior over the already-rendered light-DOM children. */ protected abstract init(): void; } /** Define a custom element once (idempotent, safe if the module is imported * from more than one entry). */ export function define(name: string, ctor: CustomElementConstructor): void { if (!customElements.get(name)) customElements.define(name, ctor); }