diff --git a/docs/superpowers/specs/2026-08-18-react-islands-design.md b/docs/superpowers/specs/2026-08-18-react-islands-design.md
new file mode 100644
index 00000000..8d2777ea
--- /dev/null
+++ b/docs/superpowers/specs/2026-08-18-react-islands-design.md
@@ -0,0 +1,241 @@
+# React Islands for WRNexus — Design
+
+**Date:** 2026-08-18
+**Status:** Approved for implementation
+**Scope:** Add opt-in React islands to WRNexus without altering the existing SSR-first rendering model.
+
+## Goal
+
+Give WRNexus authors access to the npm React ecosystem — charts, editors, date pickers,
+drag-and-drop, maps — without rebuilding those components natively and without adopting React
+as the framework's rendering model.
+
+This is explicitly **not** a migration path toward React, and not a replacement for `.wrn`
+components. Islands are a consumption path for third-party components.
+
+### Non-goals
+
+- Server-rendering islands (deferred; see "Deferred to v2").
+- React Fast Refresh.
+- `bind:` syntax sugar for store binding.
+- Replacing the `.wrn` authoring format.
+
+## Guiding constraint
+
+WRNexus's differentiator is that non-interactive routes ship **zero** framework JavaScript.
+Every decision below is subordinate to preserving that. An app that uses no islands must be
+byte-for-byte unchanged, and a route with no islands must ship no React.
+
+## Decisions
+
+| Question | Decision |
+|---|---|
+| Purpose | npm ecosystem access |
+| Server rendering | Client-only by default; SSR opt-in deferred to v2 |
+| Authoring | `import Chart from "./Chart.tsx"` in `.wrn` frontmatter, used as `` |
+| Data flow | Two-way store access via `useSyncExternalStore` (read + write through actions) |
+| Bundling | Extend the existing Bun pipeline |
+| Packaging | New isolated package `@wrnexus/react` |
+
+## Architecture
+
+### Package boundary
+
+All React-specific code lives in a new package, `@wrnexus/react`. `react` and `react-dom` are
+declared as **optional peer dependencies**, so both the bundle cost and the dependency itself
+land only on apps that import a `.tsx` island.
+
+This boundary is deliberate: React concerns must never leak into `core`, `csr`, `store`, or
+`compiler` beyond the narrow, explicitly enumerated hooks below. If islands do not earn their
+keep, the feature is removed by deleting one package and reverting a small number of tagged
+integration points.
+
+### Island lifecycle
+
+The compiler emits a placeholder element carrying `data-wrn-island` (name, strategy, serialized
+props) alongside the existing `data-wrn-scope` marker.
+
+The island runtime is lazy-loaded using the same marker-presence pattern as
+`loadComponentControllers` in `packages/csr/src/index.ts` — fetched only if a `data-wrn-island`
+marker exists in the document. A page with no islands downloads nothing, including React.
+
+Mount strategies:
+
+- `client:only` (default) — mount via `createRoot` once the bundle arrives.
+- `client:load` — mount on document load.
+- `client:visible` — mount via `IntersectionObserver`.
+- `client:idle` — mount on `requestIdleCallback`.
+
+**Unmount is mandatory.** WRNexus has client-side navigation (`packages/csr/src/nav-runtime.ts`).
+Island roots are tracked per scope and explicitly `root.unmount()`-ed on route change. Omitting
+this leaks React roots, detached DOM, and store subscriptions on every navigation.
+
+### Asset serving
+
+Two additions following the existing `/__wrnexus/*` convention:
+
+- `/__wrnexus/islands.js` — the mount runtime.
+- `/__wrnexus/island/.js` — per-island bundles, content-hashed.
+
+Registered in the three existing locations:
+
+- `packages/dev-server/src/assets.ts` (dev)
+- `packages/dev-server/src/prod.ts` (prod)
+- `packages/cli/src/build.ts` (static build)
+
+## Store bridge
+
+### The hook
+
+`useWrnStore(name, selector?)`, built on `useSyncExternalStore`.
+
+`StoreInstanceCore` (`packages/store/src/types.ts`) already provides both halves of React's
+external-store contract — `subscribe(listener) => unsubscribe` and `snapshot()` — so the
+adapter is thin. Islands resolve the browser container via `browserStoreContainer()` from
+`packages/store/src/client.ts`, reading the store already hydrated from the server render. No
+separate island hydration channel is introduced.
+
+### Snapshot caching (load-bearing)
+
+`readonlySnapshot` in `packages/store/src/index.ts` returns `Object.freeze(clone(state))` — a
+**new reference on every call**. `useSyncExternalStore` requires `getSnapshot()` to return a
+referentially identical value when nothing has changed; otherwise React throws
+*"The result of getSnapshot should be cached to avoid an infinite loop"* and spins.
+
+**The cache lives in the `@wrnexus/react` adapter, not in `@wrnexus/store`.** The adapter holds
+one cached snapshot per store instance, returns the same reference until the store's `subscribe`
+callback fires, then recomputes.
+
+Rationale: changing `readonlySnapshot` would alter semantics for all existing consumers and the
+current test suite in order to serve one new caller. Keeping the cache in the adapter leaves the
+store package untouched and confines this feature's blast radius to the new package.
+
+### Selectors
+
+`snapshot()` returns whole state, so without a selector any mutation re-renders every island
+bound to that store. `useWrnStore("cart", s => s.itemCount)` caches the selected value and
+compares with `Object.is`, re-rendering only on actual change.
+
+### Writes
+
+Writes go through `instance.actions.*`, never direct state assignment. The `mutableState` Proxy
+would technically accept a raw write, but that bypasses action naming and the `StoreMutation`
+record that subscribers and devtools depend on.
+
+### The one author-facing rule
+
+Write → action → store notifies → snapshot changes → island re-renders. This terminates cleanly
+**provided writes never occur during render**. Writes belong in event handlers or effects.
+
+This rule is enforced in dev (see Error handling), not merely documented. It is also the reason
+this shape was chosen over generated `bind:` sugar: the cycle stays visible in the author's own
+code rather than being hidden in generated glue.
+
+## Compiler and bundler changes
+
+### Detection
+
+`candidates()` in `packages/compiler/src/import-resolver.ts` currently resolves `.wrn`, `.ts`,
+`.d.ts` and index variants. Add `.tsx` and `index.tsx`, and tag `ResolvedImport` with
+`kind: "island"` when the resolved path ends in `.tsx`.
+
+Because authoring uses an explicit frontmatter import, detection requires no heuristics and no
+configuration.
+
+### Server codegen
+
+Where a `.wrn` component import generates a server render call, an island import instead emits
+the placeholder marker with name, strategy, and props serialized as JSON through the existing
+`escapeHtml`. Since islands are client-only in v1, the server never imports React.
+
+### Props contract
+
+Island props must be JSON-serializable. Passing a function, symbol, or class instance is a
+compile-time diagnostic (`WRN-ISLAND-PROPS`) rather than a runtime failure. This makes the
+serialization boundary explicit at the point where it is cheapest to correct.
+
+### Bundling
+
+Each island gets a generated entry (component + mount runtime), bundled via `Bun.build`, with
+content-hashed output.
+
+**React must be emitted as a shared chunk.** Five islands on one page must not ship five copies
+of `react-dom`. This is a day-one splitting requirement, not a later optimization, because
+getting it wrong fails silently and multiplies bundle size.
+
+### Route classification
+
+The compiler already classifies routes (static, static-interactive, request SSR, and so on), and
+that classification determines whether a route ships JavaScript. A route containing an island is
+no longer zero-JS static — it is static-interactive. Islands must feed into that existing
+classifier so the framework's performance reporting stays accurate.
+
+### HMR
+
+On island source change: unmount the root and re-mount with the new bundle. Correct and simple;
+the cost is that component state resets on edit.
+
+React Fast Refresh requires a Babel/SWC transform plus a runtime and is out of scope. If authors
+report that state-preserving edits matter, that is the concrete evidence that would justify
+introducing Vite or esbuild for island bundling — and the bundler interface is the intended swap
+point.
+
+## Error handling
+
+| Condition | Behavior |
+|---|---|
+| `react`/`react-dom` not installed | Compiler diagnostic `WRN-ISLAND-REACT-MISSING`, naming the install command — not a raw module-resolution failure |
+| Island throws during render | Per-island error boundary. Dev: render error in place with component name and stack. Prod: log, render nothing, leave surrounding server HTML intact |
+| Island bundle fails to load | Placeholder remains, warning logged; page stays functional because everything else was server-rendered |
+| Non-serializable props | Compile-time `WRN-ISLAND-PROPS` |
+| Unknown store name | Dev: throw, listing available store names. Prod: warn, return undefined |
+| Action fired during render | Dev: throw with a targeted message pointing at the handler/effect rule (React's own warning is too generic to diagnose quickly) |
+| Cleanup throws on unmount | Caught and logged; navigation must not break |
+
+Islands failing **locally** is the most valuable property of this model: a crashed chart leaves
+the rest of the page working.
+
+## Testing
+
+### Compiler unit tests
+
+- `.tsx` resolution through `candidates()`
+- Marker emission with correct name, strategy, and props
+- JSON serialization and escaping of props
+- `WRN-ISLAND-PROPS` diagnostic for non-serializable props
+- `WRN-ISLAND-REACT-MISSING` diagnostic
+- Route reclassification from static to static-interactive when an island is present
+
+### Store bridge unit tests
+
+- **`getSnapshot()` returns a referentially identical value across repeated calls with no
+ mutation, and a new one after a mutation.** This single test stands between the
+ implementation and an infinite render loop.
+- Selector memoization and `Object.is` change detection
+- `subscribe`/`unsubscribe` symmetry
+
+### Island runtime tests (`happy-dom`, already a dev dependency)
+
+- Mount per strategy: `only`, `load`, `visible`, `idle`
+- Error boundary containment
+- **Unmount on navigation** — roots disposed, store subscriptions released; subscription counts
+ stay flat across repeated simulated navigations
+
+### Integration guards
+
+Both protect the core promise:
+
+1. A route with no islands ships **zero** framework JavaScript.
+2. A page with multiple islands ships React exactly **once**.
+
+All of the above run under the existing `bun test packages` and `test:examples`, so
+`check:production` covers islands from day one.
+
+## Deferred to v2
+
+- **SSR opt-in** — `renderToString` + `hydrateRoot` for libraries that support it. The marker and
+ bundling design already accommodate this; only the server codegen path and a hydration
+ strategy are missing.
+- **`bind:` sugar** — generated two-way binding in `.wrn` markup, layered over the v1 store
+ bridge as pure syntax. Add only if authors ask.
+- **React Fast Refresh** — see HMR above.