Two faults found by driving the island demo in a real browser. Both were
silent: the markup, every asset, and all 48 island tests were correct
either way.
An island renders nothing until it mounts, so its placeholder is
zero-height, and IntersectionObserver does not treat a zero-area target
consistently -- client:visible islands mounted on one load and not the
next. Visibility for those is now decided from the element's own rect,
driven by scroll and resize; a placeholder with real size still uses the
observer. The strategy had no test at all, which is why this shipped.
After an island source edit the browser kept running the old code. The
rebuild worked and the file was refetched, but the loader imports a URL
that does not change, and the browser caches modules by URL. Remounts now
carry a generation the dev loader folds into the request.
Verified in the browser: mounts with start={3} as a number, clicks reach
React (3 -> 5), and an edit to Counter.tsx now shows the new text and
stays interactive.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The HMR client script was dead in the browser. HMR_CLIENT_JS is a
TypeScript template literal, so the regex [ \t\r\n] inside it was expanded
into real control characters, producing a regex literal containing a raw
newline — a syntax error that took the whole script down with "Invalid
regular expression: missing /". It now uses \s, and a test asserts the
emitted client parses and holds no control characters inside regex
literals; that test fails if the bug is reintroduced.
HMR also corrupted CSP nonces. A document's nonce is fixed at load, but
morph copied attributes from freshly fetched HTML, overwriting the live
nonce with one the browser will not honour. syncAttrs now leaves nonce
alone, and nodes moved across are re-stamped with the live nonce.
Islands vanished on every HMR update: morph puts the server placeholder
back over the mounted island. The island runtime now remounts on
wrnexus:hmr-updated. Remounting swaps the container for a bare clone —
re-rendering the existing root is a no-op once HMR has wiped the DOM
externally, and unmounting throws asynchronously because the nodes React
wants to remove are already gone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Adds /__wrnexus/islands.js (the bootstrap) and the /__wrnexus/island/
prefix (mount runtime, island bundles, shared chunks) to all three
serving paths.
Dev reuses the browserArtifactPaths registry pattern from pipeline.ts.
Prod mirrors the clientModulesDir handler, including its filename
allowlist, so island names cannot escape the output directory.
The bootstrap is inert without a data-wrn-island marker, so island-free
pages still download nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>