Adds gatewayWebSocketBackendHeaders so proxied upgrades carry application
identity while Bun keeps ownership of WebSocket framing.
Pre-existing working-tree change, committed as-is rather than authored
here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Component discovery is not utility-source discovery: scanning all
built-in and plugin component directories made Tailwind/Iconify generate
rules for components the app never renders. Packages that need scanning
opt in through styles.source.
Pre-existing working-tree change, committed as-is rather than authored
here.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Raises the typescript devDependency across the workspace, bumps package
versions, re-adds ignoreDeprecations, and repoints the @wrnexus registry.
These were pre-existing working-tree changes, committed as-is rather than
authored here. The .npmrc change redirects @wrnexus publishes from
registry.npmjs.org to registry.workroot.in — confirm that is intended
before publishing from this branch.
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>
Three bugs found by driving the dev server rather than reading code:
1. An island used inside a .wrn component still emitted a component mount
— only the page and nested-page render paths were covered.
2. Editing an island .tsx never rebuilt in dev. The bundle cache was keyed
on source path alone, and page modules are cached after the first
request so no compile runs to notice the change. The cache key now
includes mtime, and the file watcher rebuilds islands whose .tsx
changed.
3. A .wrn cache hit skipped island building entirely, so after a restart
with a warm cache no island bundle was ever produced. Island inputs are
now persisted beside the other artifacts and rebuilt on a cache hit.
The islands manifest is deliberately excluded from the artifact
completeness check: only the async compile path writes it, so requiring it
made the sync path miss the cache on every call.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The island pieces existed but nothing connected .wrn compilation to island
emission. Now:
- codegen emits a data-wrn-island placeholder for component tags bound to
.tsx imports, keeping .wrn components on the normal mount path
- the dev pipeline and static build resolve island imports, thread the
names into codegen, and build the bundles
- collectScripts adds /__wrnexus/islands.js only when island markup is
present, so island-free pages still ship nothing
- island routes classify as static-interactive via hasIslands
Three bugs found by driving a real page in the browser:
1. The mount runtime was never built anywhere, so the bootstrap 404'd and
no island mounted.
2. Building the runtime separately from the islands gave each its own copy
of React: "Cannot read properties of null (reading 'useState')". The
runtime is now an entrypoint of the same build so React stays in one
shared chunk. The existing single-React test only compared bundles
within one build and could not see across build boundaries.
3. Island props arrived as attribute strings, so start={3} was "3" and
incrementing produced "31" then "311". Props now follow JSX semantics:
{…} parses as JSON, quoted values stay strings, and a runtime
expression is a WRN-ISLAND-PROPS build error rather than a silent
wrong value.
island-codegen.ts no longer imports @wrnexus/core. Compiler modules are
bundled into the Node-only VS Code extension, which contains no other
packages, so a runtime import of core broke the editor compiler; the two
helpers are implemented locally and the core dependency is dropped again.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two guards protect the core promise: a route with no islands emits no
assets at all, and a page with several islands keeps React in one shared
chunk.
buildIslands now writes a generated entry per island instead of passing
component sources directly. Two islands sharing a source deduped to a
single entrypoint, and output order is not guaranteed to match input
order, so island names could bind to the wrong bundle.
Island modules are excluded from the editor compiler bundle: it globs
packages/compiler/src, and island-bundle.ts calls Bun.build while
island-codegen.ts imports @wrnexus/core — neither belongs in a Node-only
VS Code artifact.
Integration assertions share one build. bun test interferes with
Bun.build's module reads after several build calls in one process, while
the same calls succeed repeatedly outside the runner; production is
unaffected.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Disposes and re-creates island roots after a source change. Island state
resets by design; Fast Refresh needs a Babel/SWC transform plus a
runtime and is out of scope for v1.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A route mounting an island ships JavaScript, so reporting it as zero-JS
static would make the framework's performance accounting wrong.
Adds a separate needsIslandRuntime flag rather than reusing
needsClientRuntime: an island needs the island runtime, not WRNexus's
reactive runtime, and conflating them would ship the wrong bundle.
analyzeRuntimeRequirements takes island presence as an optional second
argument, so existing callers are unaffected.
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>
splitting:true keeps React in one shared chunk so a page with several
islands does not ship react-dom repeatedly.
Island .tsx is compiled against React's JSX runtime via a Bun onLoad
plugin. The repo's root tsconfig sets jsxImportSource to @wrnexus/core,
so islands would otherwise compile to the HTML-string renderer and never
mount. A @jsxImportSource pragma only affects the file carrying it, so
injecting one into the generated entry is not enough — the injection has
to happen per source file. App-authored islands stay plain .tsx.
The JSX test asserts built output rather than generated entry text,
because the entry-text assertion passed while the mechanism did not work.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mounts markers with client:only/load/visible/idle, and disposes roots on
route change so React roots, detached DOM, and store subscriptions do
not leak across client-side navigation.
Bundle load failures and malformed props JSON degrade to a warning and
leave the server markup intact rather than taking down the page.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A crashed island renders its error in dev and nothing in prod, leaving
the surrounding server-rendered page intact.
Island .tsx sources carry an explicit @jsxImportSource react pragma: the
repo's root tsconfig points jsxImportSource at @wrnexus/core, so without
it island JSX compiles to WRNexus's string renderer instead of React
elements.
Tests render on the client via createRoot rather than a server renderer,
because React error boundaries do not engage during SSR — and islands
are client-only regardless.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
.wrn keeps resolution priority so existing components are unaffected
when a .tsx file shares their name.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Emits the data-wrn-island placeholder, parses client:* strategies, and
rejects non-serializable props at compile time via WRN-ISLAND-PROPS so
the serialization boundary fails where it is cheapest to fix.
Island names become URL path segments when the browser fetches the
island bundle, so they are validated with core's existing
isSafeIslandName rather than relying on escaping alone. This adds
@wrnexus/core to the compiler's dependencies; core has no dependencies
of its own, so no cycle is introduced.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Resolves WRNexus stores from inside islands, reading through the cached
snapshot so React sees a stable reference. Unknown store names throw
with the list of registered stores rather than failing opaquely.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
useSyncExternalStore requires getSnapshot to return an identical
reference when unchanged; @wrnexus/store's readonlySnapshot returns a
fresh clone per call. The cache lives here rather than in the store
package so existing consumers are untouched.
createSelectorCache takes an optional equality function: the Object.is
default can never stabilize a selector that allocates, which is the
usual source of infinite re-renders.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
check-component-imports scans sources for <Component> tags and requires each to
be imported. The explanatory comment added with the prose-scoping fix wrote
<Blockquote> literally, so the checker demanded an import for a component the
file never renders. Naming it without angle brackets keeps the explanation and
clears the gate.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
hasExecutableSql returns as soon as it meets a quote character, so the quote
variable was assigned and never read: the `if (quote)` branch could never run.
eslint reported it as a useless assignment and the error blocks the release
gate on main. Removing the variable and the unreachable branch keeps behaviour
identical, since encountering a quote already means the SQL is executable.
Also drops an eslint-disable directive in csr's output error reporter that
suppressed nothing.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pagination reported the requested page only through its change output, so on a
server-rendered page the controls did nothing: the parent had to own client
state to react, and the emitted page never became a URL. An optional
hrefTemplate now renders the steps and page numbers as anchors, which work
before hydration and without JavaScript and give each page a crawlable URL.
Buttons remain the default for client-owned lists. End steps are clamped and
marked disabled rather than linking past the first or last page.
Output handler errors are no longer swallowed. Nothing awaits invokeOutput, so
a handler that threw became an unhandled rejection that never reached the
console and presented as a control that silently does nothing. Handler errors
are now reported as WRN-DEV-OUTPUT-HANDLER-ERROR.
Regenerates the component reference and the UI visual contract.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Typography styled every descendant element, including those inside nested UI
components. A <Blockquote variant="bordered"> placed inside <Typography> drew
two left borders: its own, plus the one these prose rules apply to any
<blockquote>. The same leak applied to headings, links, lists and code.
Prose rules now exclude the subtree of any nested `.wrn-component`, so they
style raw markup only. The exclusion sits inside :where() so specificity stays
at zero and applications can still override these rules with a plain class.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>