// The swap-time script activator — hard-navigation parity for morphed regions. // // A fragment arrives as HTML and is parsed via `innerHTML`; per the HTML spec, // scripts created by the fragment parser never execute. So after a soft // navigation (or a live-update / dev-HMR re-render) the new page's per-region // scripts — tab wiring, copy buttons, diagram renderers — would silently be // dead. This pair restores the hard-load contract: after a swap, every // executable script in the swapped region runs exactly once. // // It is a PAIR by design — both appliers call both halves: // neutralizeScripts(next) BEFORE morph: stamp every executable script in the // parsed fragment with a pending type, so nothing // can run during the morph itself. // executeScripts(target) AFTER morph: rebuild each pending script as a // fresh element (the only spec-sanctioned way to // make one runnable — the Turbo/htmx approach), // restoring its original type. // // Why neutralize at all, when the spec already keeps fragment scripts inert? // Because that inertness rides the "already started" flag through importNode / // cloneNode — a subtle journey not every DOM implementation reproduces // (happy-dom executes imported scripts, for one), and one the morph layer // shouldn't have to trust. Stamping a non-executable type makes the invariant // explicit: a fragment script CANNOT run before activation, anywhere, and the // activation count is exactly the fragment's executable-script count. // // The pending stamp also solves morph's node-keeping subtlety for free: an // unchanged script element survives the swap already-run, but the DOM it bound // listeners to was just replaced. Attribute sync marks the survivor pending, so // it re-runs like everything else. // // THE REGION-SCRIPT CONTRACT — repeat-safe in the SAME realm, which is // STRICTER than surviving a reload: a reload gets a fresh JavaScript realm and // a fresh DOM, activation gets neither. Re-runs share the realm's global // lexical environment (a bare top-level `const`/`let` throws "already // declared" on the second activation — wrap the script in an IIFE, or use // `var`/function declarations), and morph PRESERVES unchanged elements (a // direct addEventListener on a preserved node accumulates — delegate, guard, // or bind only to elements the fragment actually replaced). // // Never activated (but still neutralized, so they can't run mid-swap either): // - Island interiors — React-owned DOM the swap layer must not run scripts // in; islands re-run their own effects via re-hydration. Their scripts are // stamped like the rest (a FRESH island's subtree is imported and connected // by the morph — in a drift DOM that's an execution vector) and restored in // place right after the morph, before re-hydration sees the subtree. // - `data-june-once` scripts — full-page-load only (analytics bootstrapping // and the like): restored in place without a rebuild, so a swap never runs // them. // Not touched at all: // - Non-executable types (JSON-LD and other data blocks) — nothing to run. // // Known limits: // - A `