@octanejs/mcp-server 0.2.3 → 0.2.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -5,9 +5,10 @@ MCP server for agents working with [Octane](https://github.com/octanejs/octane).
5
5
  It serves two audiences:
6
6
 
7
7
  - **Octane users** (any project): skills and tools for bridging React packages
8
- to Octane, migrating React components to `.tsrx`, understanding Octane's
9
- intentional divergences from React, and setting up SSR. These work anywhere;
10
- the skills ship inside this package.
8
+ to Octane, engineering production-grade applications and libraries, migrating
9
+ React components to `.tsrx`, understanding Octane's intentional divergences
10
+ from React, and setting up SSR. These work anywhere; the skills ship inside
11
+ this package.
11
12
  - **Octane maintainers** (the octane monorepo): repo triage, validation
12
13
  planning, benchmark and React-test-port automation. These tools register
13
14
  only when the server detects an octane monorepo checkout at its root.
@@ -56,20 +57,50 @@ maintainer tools):
56
57
 
57
58
  ## Tools (always available)
58
59
 
60
+ The server initialization instructions direct coding agents to call
61
+ `octane_engineering_plan` and load `build-octane-software` before creating or
62
+ materially changing Octane code. This makes the correctness, performance
63
+ evidence, adversarial self-review, and handoff requirements available even when
64
+ the host does not automatically discover skills.
65
+
66
+ ### `octane_engineering_plan`
67
+
68
+ Returns structured engineering gates for application, library, or
69
+ framework-core work. Framework-core plans always require hot-path analysis,
70
+ comparable baseline/candidate performance evidence, the maintainer core and
71
+ performance skills, a second review of the final diff, and explicit residual
72
+ risk reporting. In repo mode, the response also includes validation commands for
73
+ the supplied paths. A framework-core request outside repo mode returns a blocking
74
+ condition directing the client to configure `OCTANE_REPO_ROOT`, because the
75
+ required maintainer skills and repository validation are otherwise unavailable.
76
+
77
+ ```json
78
+ {
79
+ "scope": "framework-core",
80
+ "changeKind": "performance",
81
+ "paths": ["packages/octane/src/runtime.ts"]
82
+ }
83
+ ```
84
+
59
85
  ### `octane_bridge_react_package`
60
86
 
61
87
  Scans a React package (by name from `node_modules`, or any source directory by
62
88
  path) for React API usage and returns an Octane compatibility report: which
63
- APIs map one-to-one, which need rewrites (`forwardRef`, `useDebugValue`,
64
- `lazy`, class components, synthetic `onChange`), whether a framework-agnostic
65
- core can be reused verbatim, whether an official `@octanejs/*` binding already
66
- exists, an overall verdict (`bridgeable`, `bridgeable-with-rewrites`,
67
- `needs-rework`), and a step-by-step plan.
89
+ APIs map one-to-one, which need rewrites (`forwardRef`, class components,
90
+ synthetic `onChange`, `react-dom/server` imports), whether a
91
+ framework-agnostic core can be reused verbatim, whether an official
92
+ `@octanejs/*` binding already exists, an overall verdict (`bridgeable`,
93
+ `bridgeable-with-rewrites`, `needs-rework`), and a step-by-step plan.
68
94
 
69
95
  ```json
70
96
  { "package": "jotai", "projectRoot": "/path/to/my-app" }
71
97
  ```
72
98
 
99
+ The event scan is host-aware: it recommends `onInput` only for direct standard
100
+ text-host wiring that appears to mean “every edit.” It leaves component callbacks,
101
+ selects, checkboxes/radios, dynamic input types, and explicitly intentional native
102
+ text commits alone.
103
+
73
104
  ### `octane_bindings`
74
105
 
75
106
  Returns the map of React packages with maintained `@octanejs/*` ports. The map
@@ -82,6 +113,8 @@ without registering its React-package mapping fails CI.
82
113
  Returns a skill by name. Bundled skills (shipped with this package):
83
114
 
84
115
  - `bridge-react-package` — the full workflow for porting a React library.
116
+ - `build-octane-software` — production engineering, performance, validation,
117
+ and adversarial self-review gates for Octane code.
85
118
  - `migrate-react-component` — React JSX to `.tsrx` conversion reference.
86
119
  - `react-divergences` — Octane's intentional differences from React.
87
120
  - `setup-ssr` — server rendering and hydration setup.
@@ -101,11 +134,14 @@ invariants, and validation commands.
101
134
  ### `octane_triage_paths`
102
135
 
103
136
  Classifies repository-relative paths by Octane area (compiler, core runtime,
104
- SSR, ecosystem binding, mcp-server, benchmark, docs, RuleSync source).
137
+ SSR, ecosystem binding, vite-plugin, deploy adapter, evals, website,
138
+ mcp-server, benchmark, docs, RuleSync source).
105
139
 
106
140
  ### `octane_validate_plan`
107
141
 
108
- Recommends validation commands for changed paths and task kind.
142
+ Recommends validation commands for changed paths and task kind. Core task plans
143
+ include the quick benchmark ratio gate in addition to core tests, typechecking,
144
+ and repository-wide formatting.
109
145
 
110
146
  ### `octane_scaffold_react_port`
111
147
 
@@ -114,8 +150,13 @@ optionally writes the generated Vitest skeleton to an output file.
114
150
 
115
151
  ### `octane_benchmark`
116
152
 
117
- Runs a known benchmark workspace (`news`, `js-framework`, `recursive-context`,
118
- `signal-favoring`, `dbmon`) or all benchmarks.
153
+ Runs benchmark suites through the unified runner (`node benchmarks/bench.mjs`):
154
+ one manifest suite by name (`js-framework`, `todomvc`, `weather-app`,
155
+ `weather-app-lighthouse`, `chat-stream`, `dbmon`, `news`, `react-hosted-islands`,
156
+ `ssr-throughput`, `streaming-ssr`,
157
+ `codegen-size`, `bundle-size`, `three-renderer`, `three-bundle-size`, …) or every
158
+ suite with `all`; `quick` selects the reduced-iteration smoke pass. The suite
159
+ list mirrors the runner manifest and `node benchmarks/bench.mjs --list`.
119
160
 
120
161
  ### `octane_issue_context`
121
162
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octanejs/mcp-server",
3
- "version": "0.2.3",
3
+ "version": "0.2.6",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=22"
@@ -28,7 +28,8 @@
28
28
  "octane-mcp-server": "src/index.js"
29
29
  },
30
30
  "exports": {
31
- ".": "./src/index.js"
31
+ ".": "./src/index.js",
32
+ "./bridge": "./src/bridge.js"
32
33
  },
33
34
  "dependencies": {
34
35
  "@modelcontextprotocol/sdk": "^1.21.0",
@@ -12,20 +12,36 @@ of bridging by hand:
12
12
  | `zustand` | `@octanejs/zustand` |
13
13
  | `jotai` | `@octanejs/jotai` |
14
14
  | `@apollo/client` | `@octanejs/apollo-client` |
15
+ | `@tanstack/ai-react` | `@octanejs/tanstack-ai` |
16
+ | `@tanstack/react-form` | `@octanejs/tanstack-form` |
15
17
  | `@tanstack/react-query` | `@octanejs/tanstack-query` |
16
18
  | `@tanstack/react-router` | `@octanejs/tanstack-router` |
19
+ | `@tanstack/react-store` | `@octanejs/tanstack-store` |
17
20
  | `@tanstack/react-table` | `@octanejs/tanstack-table` |
18
21
  | `@tanstack/react-virtual` | `@octanejs/tanstack-virtual` |
19
22
  | `framer-motion` / `motion` | `@octanejs/motion` |
20
23
  | `@stylexjs/stylex` | `@octanejs/stylex` |
24
+ | `styled-components` | `@octanejs/styled-components` |
21
25
  | `react-router` / `react-router-dom` | `@octanejs/remix-router` |
22
26
  | `@lexical/react` | `@octanejs/lexical` |
23
27
  | `lucide-react` | `@octanejs/lucide` |
24
28
  | `@floating-ui/react` | `@octanejs/floating-ui` |
25
29
  | `radix-ui` | `@octanejs/radix` |
26
30
  | `react-i18next` | `@octanejs/i18next` |
27
-
28
- For anything else, run the `octane_bridge_react_package` tool to get a scan of the
31
+ | `react-hook-form` | `@octanejs/hook-form` |
32
+ | `@base-ui-components/react` | `@octanejs/base-ui` |
33
+ | `@dnd-kit/react` | `@octanejs/dnd-kit` |
34
+ | `sonner` | `@octanejs/sonner` |
35
+ | `recharts` | `@octanejs/recharts` |
36
+ | `@react-three/fiber` | `@octanejs/three` |
37
+ | `@visx/*` | `@octanejs/visx` |
38
+ | `react-redux` | `@octanejs/redux` |
39
+ | `@reduxjs/toolkit` | `@octanejs/redux-toolkit` |
40
+ | `@testing-library/react` | `@octanejs/testing-library` |
41
+ | `@mdx-js/react` | `@octanejs/mdx` |
42
+
43
+ The `octane_bindings` tool returns the same map machine-readably. For anything
44
+ else, run the `octane_bridge_react_package` tool to get a scan of the
29
45
  package's React API usage and a tailored plan, then follow the workflow below.
30
46
 
31
47
  ## Mental model
@@ -60,23 +76,25 @@ So a bridge never means "run the React package unchanged". It means:
60
76
  `useCallback`, `useRef`, `useContext`, `useId`, `useImperativeHandle`,
61
77
  `useSyncExternalStore` (full React 19 shape, including `getServerSnapshot`),
62
78
  `useTransition`, `useDeferredValue`, `useActionState`, `useOptimistic`,
63
- `useEffectEvent`, `use`, `startTransition`, `memo`, `createContext`,
64
- `Suspense`, `createPortal`, `flushSync`, `createRoot`, `hydrateRoot`.
65
- Everything imports from `octane` (no separate `react-dom`).
79
+ `useEffectEvent`, `useDebugValue` (accepted no-op), `use`, `startTransition`,
80
+ `memo`, `lazy` (also accepts a bare component from the loader),
81
+ `createContext`, `Suspense`, `createPortal`, `flushSync`, `createRoot`,
82
+ `hydrateRoot`. Everything imports from `octane` (no separate `react-dom`);
83
+ server rendering imports from `octane/server`, including the streaming
84
+ `renderToPipeableStream`/`renderToReadableStream`.
66
85
 
67
86
  3. **Handle the gaps:**
68
87
  - `forwardRef`: does not exist. Accept `ref` as a normal prop (React 19
69
88
  style) and drop the wrapper.
70
- - `useDebugValue`: shim as a no-op.
71
- - `lazy`: use dynamic `import()` plus `use()` inside a `Suspense` boundary.
72
89
  - Class components: rewrite as function components. Error boundary classes
73
90
  become `<ErrorBoundary>` or the `@try { } @catch (e) { }` directive.
74
- - Synthetic `onChange` on text inputs: use native `onInput`. Octane events
75
- are native and delegated.
76
- - Controlled inputs: Octane inputs are uncontrolled and native; `value` and
77
- `checked` are plain attributes. Port controlled-input logic to
78
- read-from-DOM plus explicit writes, or keep state in the store and write
79
- the attribute on change.
91
+ - Synthetic `onChange` on standard text hosts: use native `onInput` when the
92
+ callback means every edit. Octane events are native and delegated.
93
+ Controlled `value`/`checked` follow React's semantics (the prop drives the
94
+ DOM property and reasserts on commits), so per-edit controlled-input logic
95
+ ports unchanged apart from the event name. Do not blanket-rewrite public
96
+ callbacks, selects, or checkbox/radio handlers. A deliberate uncontrolled
97
+ text commit may keep `onChange` with `suppressNativeChangeWarning`.
80
98
  - StrictMode double-invoke: does not exist; delete test expectations that
81
99
  count double renders.
82
100
 
@@ -118,7 +136,7 @@ So a bridge never means "run the React package unchanged". It means:
118
136
 
119
137
  - `bridgeable`: only same-name hooks used; a mechanical rename of imports to
120
138
  `octane` plus a `.tsrx` re-author of components is enough.
121
- - `bridgeable-with-rewrites`: needs the `forwardRef` / `useDebugValue` / `lazy` /
122
- event rewrites above, but no architectural blockers.
123
- - `needs-rework`: class components, `renderToPipeableStream`, `findDOMNode`, or
124
- React internals. Bridge the core, redesign the binding.
139
+ - `bridgeable-with-rewrites`: needs the `forwardRef` / event / `react-dom/server`
140
+ import rewrites above, but no architectural blockers.
141
+ - `needs-rework`: class components, `findDOMNode`, or React internals. Bridge
142
+ the core, redesign the binding.
@@ -0,0 +1,69 @@
1
+ # Skill: Build production-grade Octane software
2
+
3
+ Use this before creating or materially changing an Octane application, component,
4
+ library binding, or framework integration. It is the quality baseline for work
5
+ performed with Octane's MCP tools; task-specific skills add syntax and migration
6
+ details but do not replace these gates.
7
+
8
+ ## Start with the contract
9
+
10
+ 1. State the user-visible behavior, inputs, failure states, accessibility needs,
11
+ and server/hydration expectations before choosing components or hooks.
12
+ 2. Inspect the current project conventions, package versions, compiler/Vite
13
+ configuration, existing tests, and official `@octanejs/*` bindings. Do not
14
+ invent an API that current sources do not expose.
15
+ 3. Identify the important user journey and its likely performance budget: initial
16
+ JavaScript, render/hydration work, interaction latency, network/data work, and
17
+ memory lifetime.
18
+
19
+ ## Design for Octane's performance model
20
+
21
+ - Keep stable work outside reactive updates. Avoid state that can be derived,
22
+ effects that only mirror state, and subscriptions broader than the component
23
+ needs.
24
+ - Use keyed `@for` blocks for dynamic collections with stable domain keys. Keep
25
+ item components focused so an item update does not rebuild unrelated work.
26
+ - Prefer the platform and Octane's compiled directives over runtime abstraction
27
+ layers. Do not import React runtime packages or ship React-created JSX trees.
28
+ - Keep expensive parsing, sorting, formatting, and object construction off common
29
+ render paths; memoize only when the avoided work and invalidation rules justify
30
+ the retained state.
31
+ - Use native event semantics. `onInput` is the per-edit event for text controls;
32
+ do not add synthetic `onChange` compatibility or event wrapper allocation.
33
+ - For SSR, avoid client/server data divergence and duplicate fetches. Exercise
34
+ hydration with production-compiled output and preserve abort/error behavior.
35
+ - Treat bundle size and dependency cost as performance. Check for an official
36
+ binding before adding a compatibility layer or a second framework runtime.
37
+
38
+ ## Validate behavior and performance
39
+
40
+ - Test realistic public behavior: rendered output, native events, focus,
41
+ accessibility state, errors, loading, cleanup, and hydration when used.
42
+ - Include empty, loading, error, repeated-interaction, and large-data cases that
43
+ can expose stale state, duplicate work, or unbounded retention.
44
+ - Use a production build for final validation. Measure the important user journey
45
+ before and after performance-sensitive changes under comparable conditions.
46
+ - Do not claim that code is faster because it is shorter, uses memoization, or
47
+ causes fewer apparent renders. Report measured results and the command or
48
+ procedure that produced them; call inconclusive measurements inconclusive.
49
+
50
+ ## Adversarial self-review
51
+
52
+ Before handoff, reread the complete diff and try to reject it:
53
+
54
+ 1. Can a simpler design preserve the contract with less state, indirection, or
55
+ retained data?
56
+ 2. What happens for empty, large, rapid, nested, failing, aborted, and unmounted
57
+ cases?
58
+ 3. Are effects cleaned up, async results made stale safely, and subscriptions
59
+ scoped to their consumers?
60
+ 4. Does the same code work in development, production, SSR, and hydration modes
61
+ that the project supports?
62
+ 5. Did an optimization move work to startup, the server, garbage collection, or
63
+ another component rather than remove it?
64
+ 6. Are accessibility, security, diagnostics, and maintainability at least as
65
+ strong as before?
66
+
67
+ Fix findings, rerun the relevant checks, and review the final diff again. Report
68
+ validation, measured performance evidence, improvements made during self-review,
69
+ and any residual risk. Never hide an unverified path behind “all tests pass.”
@@ -42,11 +42,11 @@ locals, early returns) stays above it.
42
42
  | `<Suspense fallback={...}>` | `<Suspense>` or `@try { } @pending { }` |
43
43
  | Error boundary class | `<ErrorBoundary>` or `@try { } @catch (e) { }` |
44
44
  | `forwardRef((props, ref) => ...)` | plain function; `ref` arrives as a prop |
45
- | `<input onChange={...}>` | `<input onInput={...}>` (native event) |
46
- | controlled `value={state}` | uncontrolled; `value` is a plain attribute, read the DOM in handlers |
45
+ | text `<input onChange={...}>` meaning every edit | `<input onInput={...}>` (native event) |
46
+ | controlled `value={state}` | keep it React's controlled semantics apply; pair with `onInput` |
47
47
  | `className={clsx(...)}` | `class={[...]}` composes clsx-style natively |
48
- | `useDebugValue(x)` | delete it |
49
- | `React.lazy(() => import(...))` | dynamic `import()` + `use()` under Suspense |
48
+ | `useDebugValue(x)` | keep or delete present as an accepted no-op |
49
+ | `React.lazy(() => import(...))` | `lazy()` works as-is (and also accepts a bare component from the loader) |
50
50
  | `defaultProps` | parameter defaults / destructuring defaults |
51
51
 
52
52
  ## Text holes
@@ -64,8 +64,12 @@ treated as a renderable (component, element, coerced primitive).
64
64
  ## Hooks
65
65
 
66
66
  The hook API matches React, and there are no rules of hooks: a hook may sit
67
- behind a condition, after an early return, or in a loop, because identity comes
68
- from the call site, not call order.
67
+ behind a condition or after an early return, because identity comes from the
68
+ call site, not call order. The one exception is a plain JS loop — a slot-keyed
69
+ hook there is a compile error (every iteration would share one call-site slot);
70
+ use the keyed `@for` directive or extract a child component. Dependency arrays
71
+ may be omitted: the compiler infers them from lexical captures (explicit arrays
72
+ keep React's exact behavior; `null` means every render).
69
73
 
70
74
  ```tsx
71
75
  export function Panel(props) @{
@@ -92,6 +96,20 @@ Events are native, delegated DOM events. `onClick`, `onInput`, `onSubmit`,
92
96
  `onKeyDown` behave exactly like the platform. `onChange` on a text input fires
93
97
  on commit (native change), not per keystroke.
94
98
 
99
+ `OCTANE_NATIVE_TEXT_ONCHANGE` reports likely React-style text-host wiring. Its
100
+ phase-preserving fixes are `onInput` and `onInputCapture`; it does not rewrite the
101
+ event. Keep component/library callbacks named `onChange`, and keep native change on
102
+ select and checkbox/radio hosts. If an uncontrolled text field intentionally saves
103
+ only on commit/blur, keep `onChange` and add the non-serialized host hint:
104
+
105
+ ```tsx
106
+ <input
107
+ defaultValue={draft}
108
+ onChange={(event) => save(event.currentTarget.value)}
109
+ suppressNativeChangeWarning
110
+ />
111
+ ```
112
+
95
113
  ## Refs
96
114
 
97
115
  React 19 style. `ref={cb}` with optional cleanup return, `ref={refObject}`, or
@@ -3,17 +3,50 @@
3
3
  Use this when behavior differs from React and you need to decide whether it is a
4
4
  bug or by design. Do not "fix" these toward React.
5
5
 
6
- ## No rules of hooks
6
+ ## No rules of hooks — except plain JS loops
7
7
 
8
8
  Hooks are tracked by compiler-assigned call-site slot, not call order. A hook may
9
- sit behind a condition, after an early return, or in a loop. Code that relies on
10
- hook-order errors firing does not apply.
9
+ sit behind a condition or after an early return; code that relies on hook-order
10
+ errors firing does not apply. The one restriction: a slot-keyed hook inside a
11
+ plain JS loop is a **compile error** (every iteration would share the one
12
+ call-site slot). Loop with the keyed `@for` template directive or extract a
13
+ child component instead. `use()` and `useContext` are exempt (call-order /
14
+ context-identity keyed, not slot-keyed).
11
15
 
12
- ## No controlled components, no synthetic onChange
16
+ ## Dependency arrays are compiler-inferred when omitted
13
17
 
14
- `value` and `checked` are plain attributes; inputs are uncontrolled and native.
15
- There is no per-keystroke synthetic `onChange`; use native `onInput`. React's
16
- controlled-input value-reassertion model does not exist and must not be added.
18
+ Omitting the array on `useEffect`, `useLayoutEffect`, `useInsertionEffect`,
19
+ `useMemo`, `useCallback`, or `useImperativeHandle` does not mean "every render"
20
+ the compiler derives dependencies from lexical captures, omitting stable hook
21
+ results (state setters/dispatchers, refs, state getters, `useEffectEvent`
22
+ results). Locally declared custom hooks in full-compiled `.tsrx`/`.tsx` modules
23
+ also qualify when they transparently forward a callback and final dependency
24
+ parameter to one of those hooks; plain `.ts`/`.js`, imported/method, or
25
+ transforming wrappers require an explicit list. Explicit arrays keep React's
26
+ exact behavior and are never rewritten; `null` explicitly means run or
27
+ recompute after every render.
28
+
29
+ ## State hooks expose a current-state getter
30
+
31
+ `useState` and `useReducer` have a stable third tuple member
32
+ (`[state, update, getState]`) that reads the latest scheduled hook-cell value.
33
+ Ordinary two-item destructures keep the allocation-free React shape.
34
+
35
+ ## Controlled inputs match React — on native events
36
+
37
+ Controlled `value`/`checked` follow React's semantics exactly: the prop drives
38
+ the DOM property and reasserts on every commit and after discrete events;
39
+ `defaultValue`/`defaultChecked` are the uncontrolled escape hatch. But there is
40
+ no synthetic event layer: `onInput` is the per-keystroke handler for text
41
+ controls, and native `change` fires on blur/commit. Do not add a synthetic
42
+ `onChange` normalization.
43
+
44
+ The compiler warning `OCTANE_NATIVE_TEXT_ONCHANGE` points out a statically known
45
+ text host with `onChange`/`onChangeCapture` but no usable input handler; development
46
+ runtime checking covers unresolved final props on uncontrolled text controls. This
47
+ is migration guidance only. Deliberate native text commit behavior uses the
48
+ non-serialized `suppressNativeChangeWarning` host hint. Selects, checkables, custom
49
+ elements, and component callbacks remain quiet.
17
50
 
18
51
  ## Native delegated events
19
52
 
@@ -21,6 +54,16 @@ controlled-input value-reassertion model does not exist and must not be added.
21
54
  synthetic layer. Timing, bubbling, and `event.target` semantics match the
22
55
  platform, not React's wrapper.
23
56
 
57
+ ## Parallel `use()` — no suspense waterfalls
58
+
59
+ The compiler unconditionally memoizes `use()`
60
+ argument creations per call site, starts provably-independent fetches together,
61
+ suspends once per stratum, and prefetches independent descendant fetch trees.
62
+ React runs the same code as a serial waterfall — do not "fix" fetch-start
63
+ timing, batch replay counts, or prefetch behavior toward React. True data
64
+ dependencies stay sequential; unwrap order, hydration-seed order, and rejection
65
+ routing match React.
66
+
24
67
  ## Keyed reconciler moves differ
25
68
 
26
69
  Reconciliation is LIS-based (minimal DOM moves), not React's `lastPlacedIndex`.
@@ -28,6 +71,20 @@ The final DOM and survivor node identity are guaranteed identical to React; the
28
71
  set of physically moved nodes is not. Tests asserting which nodes moved will
29
72
  diverge; tests asserting final order and identity will pass.
30
73
 
74
+ ## Synchronous first root mount and root API extensions
75
+
76
+ The first `root.render()` mounts synchronously, so render-then-unmount in one
77
+ outer batch can expose intermediate DOM that React's concurrent root elides.
78
+ `root.render(App, props)` is supported alongside `root.render(<App />)`. A root
79
+ whose managed DOM was externally removed unmounts safely instead of throwing
80
+ the browser's incidental `NotFoundError`.
81
+
82
+ ## `lazy()` accepts bare components
83
+
84
+ React's `{ default }` module shape works, and Octane additionally accepts a
85
+ component directly from the loader. Suspense and ViewTransition are ordinary
86
+ components, so wrapping them in `lazy()` is valid; nested lazy wrappers are not.
87
+
31
88
  ## class / className composes clsx-style
32
89
 
33
90
  Strings, numbers, arrays, objects, and nesting compose into a class string;
@@ -35,11 +92,10 @@ falsy parts drop out. React coerces an array to `"a,b"`; Octane yields `"a b"`.
35
92
 
36
93
  ## Not present at all
37
94
 
38
- - Class components.
95
+ - Class components (rewrite as function components).
39
96
  - Server Components / `'use client'` / `'use server'`.
40
97
  - StrictMode double-invoke (renders and effects run once).
41
98
  - `forwardRef` (refs are props, React 19 style).
42
- - `useDebugValue` (shim as no-op).
43
99
  - SuspenseList, Profiler, findDOMNode.
44
100
 
45
101
  ## Everything else matches
@@ -47,4 +103,4 @@ falsy parts drop out. React coerces an array to `"a,b"`; Octane yields `"a b"`.
47
103
  Observable hook, effect, Suspense, and transition semantics match React,
48
104
  including effect ordering (child-first on mount, parent-first cleanup on
49
105
  deletion), `Object.is` state bailouts, batching, and `useId` stability across
50
- server render and hydration.
106
+ server render and hydration. `useDebugValue` exists as an accepted no-op.
@@ -4,9 +4,11 @@ Use this when adding server-side rendering to an Octane app.
4
4
 
5
5
  ## The API
6
6
 
7
- The entry points mirror React: `octane/server` (`react-dom/server`) has
8
- `renderToString` (sync) and `renderToStaticMarkup`; `octane/static`
9
- (`react-dom/static`) has `prerender` (async, awaits Suspense data). All return
7
+ The entry points mirror React. `octane/server` (`react-dom/server`) has
8
+ `renderToString` (sync), `renderToStaticMarkup` (non-hydratable), and the two
9
+ streaming renderers `renderToPipeableStream` (Node streams) and
10
+ `renderToReadableStream` (web streams); `octane/static` (`react-dom/static`)
11
+ has `prerender` (async, awaits Suspense data). The buffered renderers return
10
12
  `{ html, css }`.
11
13
 
12
14
  ```ts
@@ -28,9 +30,22 @@ const { html, css } = await prerender(App, props, {
28
30
  leaves `@pending` fallbacks in place; use `prerender` to await the data.
29
31
  - Options are optional: `nonce` stamps CSP nonces on the emitted inline tags (all
30
32
  renderers); `signal` aborts a suspended render with the request and `timeoutMs`
31
- bounds how long a `use(thenable)` may take to settle (async `prerender`; global
33
+ bounds how long a `use(thenable)` may take to settle (async renders; global
32
34
  default via `setSsrSuspenseTimeout`); `onError` observes render errors.
33
35
 
36
+ ### Streaming
37
+
38
+ `renderToPipeableStream(App, props?, options?)` returns `{ pipe, abort }`; the
39
+ shell — the full page with `@pending` fallbacks for anything still suspended —
40
+ flushes immediately, then each Suspense boundary streams out of order as a
41
+ hidden segment plus an inline swap script when its data settles.
42
+ `renderToReadableStream` is the same engine over web streams: it resolves with
43
+ a `ReadableStream<Uint8Array>` once the shell is ready and rejects on a shell
44
+ error; consume the stream concurrently rather than awaiting its `allReady`
45
+ promise first. `StreamOptions` extends `RenderOptions` with `onShellReady()`,
46
+ `onShellError(err)`, and `onAllReady()`. `hydrateRoot` adopts streamed-in DOM
47
+ byte-for-byte, including per-boundary `use()` value or rejection seeds.
48
+
34
49
  On the client:
35
50
 
36
51
  ```ts
@@ -43,24 +58,35 @@ hydration-stable; the client adopts server DOM instead of rebuilding it.
43
58
 
44
59
  ## Two integration paths
45
60
 
46
- 1. **Vite plugin (dev SSR + routing)**: `@octanejs/vite-plugin` matches routes
47
- from `octane.config.ts`, renders pages into `index.html` at
48
- `<!--ssr-head-->` / `<!--ssr-body-->`, and wires hydration automatically.
49
- Production server output is not generated yet; for production SSR today use
50
- path 2.
61
+ 1. **Vite plugin (routing + dev and production SSR)**: `@octanejs/vite-plugin`
62
+ matches routes from `octane.config.ts`, streams pages with
63
+ `renderToReadableStream()` into `index.html` around `<!--ssr-head-->` /
64
+ `<!--ssr-body-->`, and wires hydration automatically. In production,
65
+ `vite build` emits hashed client assets in `dist/client` plus a
66
+ self-contained SSR server at `dist/server/entry.js` (exports
67
+ `handler`/`nodeHandler`; preview with `octane-preview`);
68
+ `server.render: 'buffered'` switches it to the await-everything `prerender`.
69
+ A deploy adapter prepares the output for a host: `adapter: vercel()` from
70
+ `@octanejs/adapter-vercel` emits Vercel's Build Output API, while
71
+ `adapter: cloudflare()` from `@octanejs/adapter-cloudflare` emits a module
72
+ Worker at `dist/server/worker.js` for Workers Static Assets. Cloudflare apps
73
+ keep a user-owned `wrangler.jsonc` pointing `main` at that Worker and
74
+ `assets.directory` at `dist/client`, with `nodejs_compat` enabled. Leave
75
+ `assets.not_found_handling` unset or `"none"` so navigation misses reach SSR.
51
76
  2. **Custom server**: write `entry-server.ts` exporting a function that calls
52
- `prerender()` (or `renderToString()`) and splices the result into your HTML template, and
53
- `entry-client.ts` calling `hydrateRoot`. Serialize app data (for example a
54
- dehydrated query-client cache) into your own inline JSON script and read it
55
- before hydrating.
77
+ `prerender()` (or `renderToString()`, or a streaming renderer) and splices
78
+ the result into your HTML template, and `entry-client.ts` calling
79
+ `hydrateRoot`. Serialize app data (for example a dehydrated query-client
80
+ cache) into your own inline JSON script and read it before hydrating.
56
81
 
57
82
  ## Data and Suspense on the server
58
83
 
59
84
  `use(promise)` suspends a pass; `prerender()` awaits it and re-renders, so
60
85
  `@try { } @pending { }` boundaries resolve to their success arm in the emitted
61
- HTML. Resolved values serialize into the seed script and hydration consumes
62
- them without re-fetching. For query-style data, prefetch into a cache before
63
- rendering and dehydrate it yourself.
86
+ HTML, while the streaming renderers flush the fallback in the shell and stream
87
+ the resolved boundary behind it. Resolved values serialize into the seed script
88
+ and hydration consumes them without re-fetching. For query-style data, prefetch
89
+ into a cache before rendering and dehydrate it yourself.
64
90
 
65
91
  ## Constraints to remember
66
92
 
@@ -68,6 +94,11 @@ rendering and dehydrate it yourself.
68
94
  `useSyncExternalStore` uses `getServerSnapshot`.
69
95
  - Server components must be compiled by the Octane compiler in server mode;
70
96
  you cannot feed client-compiled output to the renderers.
71
- - Output is buffered, not streamed: send it as one response.
97
+ - Hydration adopts the whole tree in one synchronous pass (no selective or
98
+ progressive hydration), and head elements hoisted from inside a streamed
99
+ Suspense boundary are re-created on hydration rather than shipped in the
100
+ stream.
72
101
  - Render errors reject the promise unless an `ErrorBoundary`/`@catch` inside
73
102
  the tree handles them; map rejections to HTTP status codes in your server.
103
+ With streaming, a recoverable error inside Suspense content keeps the emitted
104
+ fallback and marks only that boundary for client rendering.