From 52cce2c62877f67f510caefbc9a53d03cbfb7791 Mon Sep 17 00:00:00 2001 From: Ajay Ghanwat Date: Tue, 18 Aug 2026 19:16:45 +0530 Subject: [PATCH] style(docs): apply Prettier to the React islands spec and plan Formatting only; no content change. Co-Authored-By: Claude Opus 5 --- .../plans/2026-08-18-react-islands.md | 98 +++++++++++-------- .../specs/2026-08-18-react-islands-design.md | 36 +++---- 2 files changed, 76 insertions(+), 58 deletions(-) diff --git a/docs/superpowers/plans/2026-08-18-react-islands.md b/docs/superpowers/plans/2026-08-18-react-islands.md index e540bd8f..e7f67fd3 100644 --- a/docs/superpowers/plans/2026-08-18-react-islands.md +++ b/docs/superpowers/plans/2026-08-18-react-islands.md @@ -30,26 +30,26 @@ **New package `packages/react/`:** -| File | Responsibility | -|---|---| -| `package.json` | Package manifest; optional peer deps | -| `src/snapshot-cache.ts` | Referentially-stable snapshot + selector caching. No React import. | -| `src/store-bridge.ts` | `useWrnStore` hook over `useSyncExternalStore` | -| `src/error-boundary.tsx` | Per-island React error boundary | -| `src/island-runtime.ts` | Mount/unmount, strategies, root registry | -| `src/runtime-source.ts` | `getIslandRuntime()` returning browser JS (mirrors `@wrnexus/csr`) | -| `src/index.ts` | Public exports | +| File | Responsibility | +| ------------------------ | ------------------------------------------------------------------ | +| `package.json` | Package manifest; optional peer deps | +| `src/snapshot-cache.ts` | Referentially-stable snapshot + selector caching. No React import. | +| `src/store-bridge.ts` | `useWrnStore` hook over `useSyncExternalStore` | +| `src/error-boundary.tsx` | Per-island React error boundary | +| `src/island-runtime.ts` | Mount/unmount, strategies, root registry | +| `src/runtime-source.ts` | `getIslandRuntime()` returning browser JS (mirrors `@wrnexus/csr`) | +| `src/index.ts` | Public exports | **Modified:** -| File | Change | -|---|---| -| `packages/compiler/src/import-resolver.ts` | Resolve `.tsx`; tag `kind: "island"` | -| `packages/compiler/src/island-codegen.ts` *(new)* | Marker emission, props serialization, diagnostics | -| `packages/compiler/src/island-bundle.ts` *(new)* | Island entry generation + `Bun.build` with shared React chunk | -| `packages/dev-server/src/assets.ts` | Serve island routes in dev | -| `packages/dev-server/src/prod.ts` | Serve island routes in prod | -| `packages/cli/src/build.ts` | Emit island assets in static build | +| File | Change | +| ------------------------------------------------- | ------------------------------------------------------------- | +| `packages/compiler/src/import-resolver.ts` | Resolve `.tsx`; tag `kind: "island"` | +| `packages/compiler/src/island-codegen.ts` _(new)_ | Marker emission, props serialization, diagnostics | +| `packages/compiler/src/island-bundle.ts` _(new)_ | Island entry generation + `Bun.build` with shared React chunk | +| `packages/dev-server/src/assets.ts` | Serve island routes in dev | +| `packages/dev-server/src/prod.ts` | Serve island routes in prod | +| `packages/cli/src/build.ts` | Emit island assets in static build | --- @@ -58,11 +58,13 @@ The load-bearing piece. `readonlySnapshot` in `@wrnexus/store` returns a fresh `Object.freeze(clone(state))` on every call; `useSyncExternalStore` requires a stable reference or it throws and infinite-loops. This task builds the cache with **no React dependency**, so it is testable in isolation. **Files:** + - Create: `packages/react/package.json` - Create: `packages/react/src/snapshot-cache.ts` - Test: `packages/react/test/snapshot-cache.test.ts` **Interfaces:** + - Consumes: nothing. - Produces: - `createSnapshotCache(source: SnapshotSource): SnapshotCache` @@ -193,9 +195,7 @@ export interface SnapshotCache { * and spins if given a fresh object each call, which `@wrnexus/store`'s * `readonlySnapshot` does by design. */ -export function createSnapshotCache( - source: SnapshotSource, -): SnapshotCache { +export function createSnapshotCache(source: SnapshotSource): SnapshotCache { let cached: Readonly | undefined; let dirty = true; @@ -259,12 +259,14 @@ git commit -m "feat(react): add referentially-stable snapshot and selector cache ### Task 2: Store bridge hook **Files:** + - Create: `packages/react/src/store-bridge.ts` - Create: `packages/react/src/index.ts` - Modify: `package.json` (root) — add `react`, `react-dom` to devDependencies for tests - Test: `packages/react/test/store-bridge.test.ts` **Interfaces:** + - Consumes: `createSnapshotCache`, `createSelectorCache` from Task 1. - Produces: - `useWrnStore>(name: string, selector?: (state: Readonly) => R): R` @@ -436,10 +438,12 @@ git commit -m "feat(react): add useWrnStore bridge over useSyncExternalStore" ### Task 3: Island marker codegen **Files:** + - Create: `packages/compiler/src/island-codegen.ts` - Test: `packages/compiler/test/island-codegen.test.ts` **Interfaces:** + - Consumes: `escapeHtml` from `@wrnexus/core` (`packages/core/src/security.ts`). - Produces: - `type IslandStrategy = "only" | "load" | "visible" | "idle"` @@ -596,10 +600,12 @@ git commit -m "feat(compiler): add island marker codegen and props contract" ### Task 4: Resolve `.tsx` imports as islands **Files:** + - Modify: `packages/compiler/src/import-resolver.ts` - Test: `packages/compiler/test/island-resolution.test.ts` **Interfaces:** + - Consumes: nothing from prior tasks. - Produces: `ResolvedImport` gains an optional `kind?: "island"` field, set when the resolved path ends in `.tsx`. @@ -705,12 +711,12 @@ export interface ResolvedImport { Then change the success return inside `resolveWrnImport` from `return { declaration, resolved: realpathSync(found) };` to: ```ts - if (found) { - const resolved = realpathSync(found); - return resolved.endsWith(".tsx") - ? { declaration, resolved, kind: "island" } - : { declaration, resolved }; - } +if (found) { + const resolved = realpathSync(found); + return resolved.endsWith(".tsx") + ? { declaration, resolved, kind: "island" } + : { declaration, resolved }; +} ``` - [ ] **Step 5: Run test to verify it passes** @@ -735,10 +741,12 @@ git commit -m "feat(compiler): resolve .tsx imports and tag them as islands" ### Task 5: Island error boundary **Files:** + - Create: `packages/react/src/error-boundary.tsx` - Test: `packages/react/test/error-boundary.test.tsx` **Interfaces:** + - Consumes: nothing from prior tasks. - Produces: `IslandErrorBoundary` — a React component with props `{ name: string; development: boolean; children: ReactNode }`. @@ -857,11 +865,13 @@ git commit -m "feat(react): add per-island error boundary" ### Task 6: Island runtime — mount, strategies, unmount **Files:** + - Create: `packages/react/src/island-runtime.ts` - Modify: `packages/react/src/index.ts` - Test: `packages/react/test/island-runtime.test.ts` **Interfaces:** + - Consumes: `IslandErrorBoundary` (Task 5), `setStoreResolver` (Task 2). - Produces: - `mountIslands(root: ParentNode, options: MountOptions): Promise` @@ -1070,10 +1080,12 @@ git commit -m "feat(react): add island mount strategies and navigation-safe unmo ### Task 7: Island bundling with a shared React chunk **Files:** + - Create: `packages/compiler/src/island-bundle.ts` - Test: `packages/compiler/test/island-bundle.test.ts` **Interfaces:** + - Consumes: nothing from prior tasks. - Produces: - `generateIslandEntry(input: { name: string; sourcePath: string }): string` @@ -1226,6 +1238,7 @@ git commit -m "feat(compiler): bundle islands with a shared React chunk" ### Task 8: Serve island assets in dev, prod, and static build **Files:** + - Create: `packages/react/src/runtime-source.ts` - Modify: `packages/dev-server/src/assets.ts` - Modify: `packages/dev-server/src/prod.ts` @@ -1233,6 +1246,7 @@ git commit -m "feat(compiler): bundle islands with a shared React chunk" - Test: `packages/react/test/runtime-source.test.ts` **Interfaces:** + - Consumes: `mountIslands`, `unmountIslands` (Task 6). - Produces: `getIslandRuntime(development?: boolean): string` — browser JS served at `/__wrnexus/islands.js`, mirroring `getReactiveRuntime` in `@wrnexus/csr`. @@ -1323,15 +1337,15 @@ import { getIslandRuntime } from "@wrnexus/react/runtime"; Then inside `serve(pathname)`, next to the other `/__wrnexus/*.js` lines: ```ts - if (pathname === "/__wrnexus/islands.js") return jsResponse(getIslandRuntime(true)); +if (pathname === "/__wrnexus/islands.js") return jsResponse(getIslandRuntime(true)); ``` The bootstrap dynamically imports `/__wrnexus/island/runtime.js` (the bundled mount runtime) and `/__wrnexus/island/.js` (per-island bundles). Both live under the `/__wrnexus/island/` prefix, so add one prefix handler beside the existing `/__wrnexus/client/` handler at the top of `serve(pathname)`: ```ts - if (pathname.startsWith("/__wrnexus/island/")) { - return serveIslandArtifact(pathname) ?? new Response("Not Found", { status: 404 }); - } +if (pathname.startsWith("/__wrnexus/island/")) { + return serveIslandArtifact(pathname) ?? new Response("Not Found", { status: 404 }); +} ``` where `serveIslandArtifact` reads from the island build output directory produced by `buildIslands` (Task 7), mirroring how `serveWrnBrowserArtifact` serves `/__wrnexus/client/`. @@ -1341,8 +1355,8 @@ where `serveIslandArtifact` reads from the island build output directory produce In `packages/dev-server/src/prod.ts`, add the same import, then beside the existing `getReactiveRuntime()` route: ```ts - if (pathname === "/__wrnexus/islands.js") - return new Response(getIslandRuntime(), { headers: JS_HEADERS }); +if (pathname === "/__wrnexus/islands.js") + return new Response(getIslandRuntime(), { headers: JS_HEADERS }); ``` Add the same `/__wrnexus/island/` prefix handler here, serving the built island assets from the production output directory. @@ -1375,10 +1389,12 @@ git commit -m "feat(islands): serve the island runtime in dev, prod, and static A route containing an island is no longer zero-JS static — it is static-interactive. Without this, the framework's own performance reporting is wrong. **Files:** + - Modify: `packages/compiler/src/analysis.ts` - Test: `packages/compiler/test/island-classification.test.ts` **Interfaces:** + - Consumes: `ResolvedImport.kind` (Task 4). - Produces: `routeNeedsIslands(imports: ResolvedImport[]): boolean`, exported from `analysis.ts`. @@ -1457,10 +1473,12 @@ git commit -m "feat(compiler): classify island routes as static-interactive" Two tests protecting the project's core promise. These must fail loudly if a future change regresses them. **Files:** + - Create: `examples/basic-app/app/islands/Counter.tsx` - Test: `packages/compiler/test/island-integration.test.ts` **Interfaces:** + - Consumes: `buildIslands` (Task 7), `renderIslandMarker` (Task 3), `routeNeedsIslands` (Task 9). - Produces: nothing consumed downstream. @@ -1493,10 +1511,7 @@ import { join, resolve } from "node:path"; import { buildIslands } from "../src/island-bundle.ts"; import { routeNeedsIslands } from "../src/analysis.ts"; -const COUNTER = resolve( - import.meta.dir, - "../../../examples/basic-app/app/islands/Counter.tsx", -); +const COUNTER = resolve(import.meta.dir, "../../../examples/basic-app/app/islands/Counter.tsx"); test("a route with no islands ships zero framework JavaScript", async () => { const outDir = mkdtempSync(join(tmpdir(), "wrnexus-nojs-")); @@ -1522,8 +1537,8 @@ test("a page with multiple islands ships React exactly once", async () => { .filter((file) => file.endsWith(".js")) .map((file) => readFileSync(join(outDir, file), "utf8")); - const withReactInternals = bundles.filter((source) => - source.includes("react.development") || source.includes("REACT_ELEMENT_TYPE"), + const withReactInternals = bundles.filter( + (source) => source.includes("react.development") || source.includes("REACT_ELEMENT_TYPE"), ); expect(result.assets).toHaveLength(2); @@ -1564,12 +1579,14 @@ git commit -m "test(islands): guard zero-JS routes and single-React bundling" The spec's one author-facing rule — writes only from handlers or effects — is enforced in dev, not merely documented. React's own warning for this is too generic to diagnose quickly. **Files:** + - Create: `packages/react/src/render-phase.ts` - Modify: `packages/react/src/store-bridge.ts` (wrap actions in `useWrnActions`) - Modify: `packages/react/src/index.ts` - Test: `packages/react/test/render-phase.test.ts` **Interfaces:** + - Consumes: `useWrnActions` (Task 2). - Produces: - `beginRenderPhase(): void` @@ -1730,12 +1747,14 @@ git commit -m "feat(react): fail loudly on store writes during island render" On island source change, unmount the root and re-mount with the new bundle. Component state resets on edit; that is the accepted v1 trade-off, and the concrete trigger for reconsidering Fast Refresh later. **Files:** + - Modify: `packages/react/src/island-runtime.ts` - Modify: `packages/react/src/runtime-source.ts` - Modify: `packages/react/src/index.ts` - Test: `packages/react/test/island-hmr.test.ts` **Interfaces:** + - Consumes: `mountIslands`, `unmountIslands` (Task 6). - Produces: `remountIslands(root: ParentNode, options: MountOptions): Promise` @@ -1751,8 +1770,7 @@ import { islandRootCount, mountIslands, remountIslands } from "../src/island-run test("remount replaces island output without leaking roots", async () => { const window = new Window(); - window.document.body.innerHTML = - `
`; (globalThis as any).window = window; (globalThis as any).document = window.document; diff --git a/docs/superpowers/specs/2026-08-18-react-islands-design.md b/docs/superpowers/specs/2026-08-18-react-islands-design.md index 6d81a1f2..15435196 100644 --- a/docs/superpowers/specs/2026-08-18-react-islands-design.md +++ b/docs/superpowers/specs/2026-08-18-react-islands-design.md @@ -28,14 +28,14 @@ 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` | +| 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 @@ -100,7 +100,7 @@ separate island hydration channel is introduced. `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 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` @@ -182,15 +182,15 @@ 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 | Left to React. See "Dropped: the write-during-render guard" below. | -| Cleanup throws on unmount | Caught and logged; navigation must not break | +| 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 | Left to React. See "Dropped: the write-during-render guard" below. | +| 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.