@octanejs/three 0.1.0 → 0.1.3

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
@@ -4,7 +4,7 @@ An experimental React Three Fiber 9-compatible web renderer for Octane. Octane
4
4
  keeps ownership of component execution, hooks, context, Suspense, refs, and
5
5
  effects; this package supplies the Three-specific host layer.
6
6
 
7
- Milestones 0, 2, 3, 4, 5, and 6 are implemented on top of Octane's renderer SDK
7
+ Milestones 0–10 are implemented across this package and Octane's renderer SDK
8
8
  foundation. The current technical preview includes:
9
9
 
10
10
  - the serializable compiler preset, renderer entry point, renderer-local Three
@@ -30,12 +30,28 @@ foundation. The current technical preview includes:
30
30
  client-side Three-to-DOM pending/error projection;
31
31
  - same-renderer `createPortal` placement into borrowed `Object3D` targets,
32
32
  R3F-shaped state/event enclaves, nested context retention, one shared frame
33
- loop, physical Three event bubbling, and root-scoped target teardown; and
33
+ loop, physical Three event bubbling, and root-scoped target teardown;
34
+ - client-only Canvas SSR that streams and hydrates the existing DOM shell and
35
+ native canvas fallback without executing Three scene setup, constructors, or
36
+ loaders on the server;
37
+ - direct `HTMLCanvasElement` and `OffscreenCanvas` roots, public Octane
38
+ `act`/`flushSync` scheduling, callback-aware `unmountComponentAtNode`, and
39
+ demand-loop invalidation after WebGL context restoration;
40
+ - controlled WebXR session-loop handoff and teardown, plus compatible HMR that
41
+ retains live objects and incompatible `args` edits that reconstruct without
42
+ stale refs, handlers, or resources;
43
+ - the low-level `DOMRegion` Three-to-DOM boundary with an explicit target and
44
+ deterministic DOM ownership; and
34
45
  - public behavior, prepared-driver, and same-source compiled scene evidence
35
- against R3F 9.6.1 with the exact Three r172 oracle.
36
-
37
- Full Canvas SSR/hydration adoption, XR, OffscreenCanvas lifecycle, and live HMR
38
- behavior follow in later milestones.
46
+ against R3F 9.6.1 with the exact Three r172 oracle;
47
+ - a real asynchronous `MessageChannel` renderer proof with structured-cloned
48
+ batches and values, root-scoped resource/portal handles, listener IDs,
49
+ acknowledgement-gated refs/layout, rejection/fault semantics, teardown, and
50
+ native-event delivery; and
51
+ - a checked public export/subpath type matrix, Three r156/current compatibility
52
+ lanes, a packed external consumer, real WebGL creation-failure and context-
53
+ recovery coverage, and semantic-checksummed renderer and shipped-size
54
+ benchmarks.
39
55
 
40
56
  Three deliberate correctness fixes differ from R3F 9.6.1:
41
57
 
@@ -66,13 +82,15 @@ export default defineConfig({
66
82
  ```
67
83
 
68
84
  The same serializable `threeRenderers` value can be supplied to the Rsbuild and
69
- low-level Rspack integrations. Vite and Rsbuild additionally own application
70
- SSR and hydration; the Rspack plugin owns compilation and HMR only.
85
+ low-level Rspack integrations. Vite and Rsbuild own the production application
86
+ SSR/hydration lifecycle; the Rspack plugin owns the equivalent client/server
87
+ graph split, compilation, and HMR transforms rather than an application server.
71
88
 
72
89
  The preset selects `@octanejs/three/renderer`, keeps Three scene modules
73
90
  client-only on the server, ignores authored text inside scenes, exposes a
74
91
  renderer-local intrinsic catalogue without merging Three tags into DOM JSX,
75
- and declares `Canvas.children` as the DOM-to-Three renderer boundary.
92
+ and declares both `Canvas.children` as the DOM-to-Three renderer boundary and
93
+ `DOMRegion.children` as the explicit Three-to-DOM boundary.
76
94
 
77
95
  ## Canvas and scene modules
78
96
 
@@ -106,9 +124,9 @@ export function Scene() @{
106
124
  }
107
125
  ```
108
126
 
109
- The low-level API follows Octane's component-plus-props root convention. Both
110
- synchronous and asynchronous renderer factories settle before the component
111
- can execute:
127
+ The low-level API follows Octane's component-plus-props root convention and
128
+ accepts either an `HTMLCanvasElement` or an `OffscreenCanvas`. Both synchronous
129
+ and asynchronous renderer factories settle before the component can execute:
112
130
 
113
131
  ```ts
114
132
  import { createRoot } from '@octanejs/three';
@@ -119,6 +137,13 @@ root.render(Scene, { color: 'hotpink' });
119
137
  root.store.getState().advance(1 / 60);
120
138
  ```
121
139
 
140
+ Direct-root scheduling uses Octane's public `act` and `flushSync` semantics.
141
+ Call `root.unmount()` directly, or use
142
+ `unmountComponentAtNode(canvas, optionalCallback)` to remove the root registered
143
+ for either canvas kind. The optional callback runs synchronously only after
144
+ teardown completes successfully. Teardown disconnects events and XR, clears the
145
+ animation loop, releases scene resources, and disposes the renderer.
146
+
122
147
  Tests can inject the WebGL-free deterministic harness from
123
148
  `@octanejs/three/testing`; it drives the same root, host commits, hooks, and
124
149
  public `advance()` loop as an application. Its awaitable `fireEvent()` helper
@@ -136,6 +161,50 @@ that call its own lexical hook slot; keep that compatibility form unconditional
136
161
  and in stable order. Prefer `useStore(selector, equality?)` or
137
162
  `useThree(selector, equality?)` when using Octane's conditional-hook semantics.
138
163
 
164
+ ## Root lifecycle, XR, and HMR
165
+
166
+ When the configured renderer exposes Three's XR event surface, the root listens
167
+ for `sessionstart` and `sessionend`. A presenting session uses
168
+ `renderer.xr.setAnimationLoop`; `frameloop="never"` remains manual, and ending
169
+ the session invalidates the configured non-XR loop. Unmount removes both
170
+ listeners, clears the XR callback, and makes any retained callback inert.
171
+
172
+ WebGL context loss is prevented while the root is live. Context restoration
173
+ invalidates the root so an `always` or `demand` root renders again; teardown
174
+ removes both context listeners before forcing renderer context loss.
175
+
176
+ Universal HMR preserves component state and Three object identity for compatible
177
+ edits. A constructor `args` change reconstructs the affected object, detaches
178
+ and reattaches stable refs, retires old handlers, and disposes the old owned
179
+ resource once. Vite and the Rspack/Rsbuild path emit the same universal HMR
180
+ wrapper behavior.
181
+
182
+ ## DOM regions
183
+
184
+ `DOMRegion` is a low-level reverse-renderer boundary for mounting ordinary
185
+ Octane DOM content from a Three scene into an explicit DOM target:
186
+
187
+ ```tsx
188
+ // Scene.three.tsrx
189
+ import { DOMRegion } from '@octanejs/three';
190
+
191
+ export function Scene(props) @{
192
+ <DOMRegion target={props.overlayTarget}>
193
+ <button onClick={props.onClick}>Inspect object</button>
194
+ </DOMRegion>
195
+ }
196
+ ```
197
+
198
+ Each region owns one child container and one DOM root. Updating or moving its
199
+ target preserves that container, DOM state, and node identity; deleting the
200
+ region removes its owned DOM deterministically. The target may be an
201
+ `HTMLElement` or an object ref whose `current` value is an `HTMLElement` or
202
+ `null`.
203
+
204
+ `DOMRegion` is not Drei `Html` and is not the WebXR DOM Overlay API. It provides
205
+ no positioning, occlusion, transforms, styling, or layout contract. Those
206
+ policies belong to future higher-level packages.
207
+
139
208
  ## Portals
140
209
 
141
210
  `createPortal` keeps its children in the authored Octane owner/context tree but
@@ -201,15 +270,30 @@ useLoader.clear(GLTFLoader, '/model.glb');
201
270
  the resolved asset. Declarative Three resources remain owned by their mounted
202
271
  host tree, while objects passed through `primitive` remain caller-owned. A
203
272
  root-level Three suspension or render error is projected through `Canvas` to
204
- the nearest client DOM `@pending` or `@catch` arm. Streaming server fallback
205
- and Canvas hydration remain Milestone 7 work.
273
+ the nearest client DOM `@pending` or `@catch` arm. On the server, `Canvas`
274
+ streams its DOM shell and native `<canvas>` fallback without evaluating the
275
+ client-only scene. Hydration adopts that shell before measurement creates one
276
+ fresh Three root on the client.
206
277
 
207
278
  ## Compatibility target
208
279
 
209
280
  The compatibility baseline is `@react-three/fiber@9.6.1` at commit
210
281
  `2a528745e9aa7c9e6cca41e404b59d45cf0d0cc7`, with `three@0.172.0` as the exact
211
- behavioral oracle. React Native/Expo, R3F 10's WebGPU/TSL APIs, and Drei are
212
- separate follow-on efforts.
282
+ behavioral and differential oracle. The published peer range is
283
+ `three >=0.156.0`, guarded by minimum-r156 and current-release CI lanes; the
284
+ r172 lane remains immutable so a moving current dependency cannot change the
285
+ parity oracle. TypeScript consumers must install `@types/three` from the same
286
+ Three release line explicitly; its patch revision may differ from the runtime.
287
+ It is an optional peer so a package manager cannot silently auto-install current
288
+ declarations beside an older supported runtime:
289
+
290
+ ```bash
291
+ pnpm add three@0.156.0
292
+ pnpm add -D @types/three@0.156.0
293
+ ```
294
+
295
+ React Native/Expo, R3F 10's WebGPU/TSL APIs, and Drei are separate follow-on
296
+ efforts.
213
297
 
214
298
  See [`docs/three-port-plan.md`](../../docs/three-port-plan.md) for the delivery
215
299
  phases and [`UPSTREAM.md`](./UPSTREAM.md) for source and license provenance.
package/UPSTREAM.md CHANGED
@@ -8,9 +8,15 @@ This port targets the immutable React Three Fiber release `v9.6.1`:
8
8
  - exact Three behavior and type oracle: `three@0.172.0` and
9
9
  `@types/three@0.172.0`.
10
10
 
11
- The upstream package accepts `three >=0.156`, but this port initially supports
12
- only the pinned r172 lane. The broader peer range is gated on a real
13
- minimum-version validation lane.
11
+ The port advertises the upstream `three >=0.156` range. Its immutable
12
+ differential oracle stays on r172, while separate CI jobs typecheck and run the
13
+ Octane-owned compatibility suite against the minimum r156 pair and the current
14
+ registry release. This prevents either a moving latest dependency or the pinned
15
+ oracle from standing in for the other compatibility claim. `@types/three` is an
16
+ optional peer that TypeScript consumers install from the same Three release line;
17
+ its patch revision may differ from the runtime. Making it optional prevents
18
+ package-manager peer auto-install from silently pairing current declarations with
19
+ an older supported runtime.
14
20
 
15
21
  ## Source boundary
16
22
 
@@ -51,6 +57,49 @@ events bubble through physical Three ancestry. Octane's universal portal range
51
57
  retains logical context, error, effect, and scheduling ownership without
52
58
  embedding React Reconciler or representing the target as a fake host child.
53
59
 
60
+ Milestone 7 follows the pinned web `Canvas` contract: the server emits the
61
+ ordinary DOM wrapper, canvas, and native fallback while omitting Three children.
62
+ Hydration adopts those DOM nodes, then positive client measurement creates one
63
+ fresh Three root. Scene setup, constructors, and loaders remain outside the
64
+ server execution path; client Three suspension and errors project through the
65
+ owning DOM boundary, and deleting the Canvas tears down the nested root.
66
+
67
+ Milestone 8 follows the pinned direct-root, HMR, and XR lifecycle contracts while
68
+ keeping scheduling under Octane. Direct roots accept `HTMLCanvasElement` and
69
+ `OffscreenCanvas`, expose Octane's `act` and `flushSync`, and support
70
+ callback-aware `unmountComponentAtNode`. Octane completes that teardown and
71
+ callback synchronously instead of retaining R3F's 500-millisecond delay.
72
+ Compatible universal HMR edits retain Three objects; constructor `args` edits
73
+ reconstruct them with ref, handler, and owned-resource cleanup. A controlled WebXR session hands rendering to
74
+ `setAnimationLoop`, respects manual `frameloop="never"`, restores the configured
75
+ loop on session end, and disconnects without leaving a live callback. WebGL
76
+ context restoration invalidates the live root before teardown removes its
77
+ listeners.
78
+
79
+ Milestone 9 validates the renderer vocabulary independently of an in-process
80
+ Three host. A real asynchronous `MessageChannel` carries structured-cloned host
81
+ batches to a separate object driver and returns versioned acknowledgement,
82
+ completion, rejection, fault, and event messages. Logical topology, lifecycle,
83
+ refs, layout effects, and teardown publish only after acknowledgement; pre-ack
84
+ rejection remains retryable, while post-ack faults retain the accepted commit.
85
+ The existing portal proof separately verifies that only a root-scoped portal
86
+ handle, never the raw target, enters a cloned batch.
87
+
88
+ Milestone 10 hardens the release claim with an existence-checked 90-export/
89
+ 157-test crosswalk, public export and package-subpath type matrices, r156 and
90
+ current Three compatibility lanes, and an external packed consumer that builds
91
+ and executes the published package without React. Chromium coverage exercises
92
+ real WebGL construction failure and `WEBGL_lose_context` loss/restoration.
93
+ Renderer lifecycle/event benchmarks and minimal/full-catalogue shipped-size
94
+ benchmarks compare Octane Three, R3F 9.6.1, and direct Three behind semantic
95
+ checksums and committed ratio guards.
96
+
97
+ `DOMRegion` is an Octane-specific proof of the already-compiled Three-to-DOM
98
+ renderer boundary, so it is not part of the pinned R3F public-export inventory.
99
+ It mounts one DOM root under an explicit target and preserves that root while
100
+ the target moves. It is not Drei `Html`, is not the WebXR DOM Overlay API, and
101
+ defines no positioning, occlusion, styling, or layout behavior.
102
+
54
103
  ## License provenance
55
104
 
56
105
  React Three Fiber is MIT-licensed, Copyright 2019–2025 Poimandres. Adapted
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@octanejs/three",
3
- "version": "0.1.0",
3
+ "version": "0.1.3",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
+ "sideEffects": false,
6
7
  "engines": {
7
8
  "node": ">=22"
8
9
  },
@@ -45,18 +46,24 @@
45
46
  "./intrinsics/jsx-runtime": "./src/intrinsics.ts"
46
47
  },
47
48
  "dependencies": {
48
- "@types/three": "0.172.0",
49
49
  "zustand": "^5.0.0"
50
50
  },
51
51
  "peerDependencies": {
52
- "three": ">=0.172.0 <0.173.0",
53
- "octane": "0.1.7"
52
+ "@types/three": ">=0.156.0",
53
+ "three": ">=0.156.0",
54
+ "octane": "0.1.10"
55
+ },
56
+ "peerDependenciesMeta": {
57
+ "@types/three": {
58
+ "optional": true
59
+ }
54
60
  },
55
61
  "devDependencies": {
56
62
  "@react-three/fiber": "9.6.1",
57
63
  "@rsbuild/core": "^2.1.5",
58
64
  "@rspack/core": "^2.1.3",
59
65
  "@tsrx/react": "^0.2.37",
66
+ "@types/three": "0.172.0",
60
67
  "esbuild": "^0.28.1",
61
68
  "playwright": "^1.61.0",
62
69
  "react": "^19.2.0",
@@ -65,13 +72,14 @@
65
72
  "typescript": "^5.9.3",
66
73
  "vite": "^8.0.16",
67
74
  "vitest": "^4.1.9",
68
- "@octanejs/rsbuild-plugin": "0.1.2",
69
- "@octanejs/rspack-plugin": "0.1.2",
70
- "octane": "0.1.7",
71
- "@octanejs/vite-plugin": "0.1.7"
75
+ "@octanejs/rsbuild-plugin": "0.1.5",
76
+ "@octanejs/rspack-plugin": "0.1.5",
77
+ "@octanejs/vite-plugin": "0.1.10",
78
+ "octane": "0.1.10"
72
79
  },
73
80
  "scripts": {
74
81
  "test": "pnpm upstream:check && vitest run --root ../.. --project three",
82
+ "test:compat": "pnpm upstream:check && vitest run --root ../.. --project three --exclude 'packages/three/tests/**/*differential.test.ts' --exclude 'packages/three/tests/browser/**/*.test.ts'",
75
83
  "upstream:check": "node scripts/check-upstream-crosswalk.mjs"
76
84
  }
77
85
  }
package/src/config.ts CHANGED
@@ -35,6 +35,11 @@ export const threeRendererBoundaries = {
35
35
  prop: 'children',
36
36
  server: 'omit-child',
37
37
  },
38
+ DOMRegion: {
39
+ ownerRenderer: THREE_RENDERER_ID,
40
+ childRenderer: 'dom',
41
+ prop: 'children',
42
+ },
38
43
  },
39
44
  } as const;
40
45
 
@@ -41,6 +41,10 @@ type ArgsProp<T extends ConstructorRepresentation> =
41
41
  export type ThreeAttachFunction<T = unknown> = (parent: unknown, self: T) => void | (() => void);
42
42
 
43
43
  export type Attach<T = unknown> = string | ThreeAttachFunction<T>;
44
+ /** R3F-compatible name for a function attachment, adapted to Octane cleanup callbacks. */
45
+ export type AttachFnType<T = unknown> = ThreeAttachFunction<T>;
46
+ /** R3F-compatible name for the public attachment union. */
47
+ export type AttachType<T = unknown> = Attach<T>;
44
48
  export type ThreeKey = string | number | symbol | bigint;
45
49
 
46
50
  export type ThreeRef<T> =
@@ -100,15 +104,40 @@ export type MathType<T extends MathTypes> = T extends THREE.Color
100
104
  ? T | MutableOrReadonlyParameters<T['set']> | number
101
105
  : T | MutableOrReadonlyParameters<T['set']>;
102
106
 
103
- type MathProps<T> = {
107
+ export type MathProps<T> = {
104
108
  [K in keyof T as T[K] extends MathTypes ? K : never]: T[K] extends MathTypes
105
109
  ? MathType<T[K]>
106
110
  : never;
107
111
  };
108
112
 
113
+ export type Vector2 = MathType<THREE.Vector2>;
114
+ export type Vector3 = MathType<THREE.Vector3>;
115
+ export type Vector4 = MathType<THREE.Vector4>;
116
+ export type Color = MathType<THREE.Color>;
117
+ export type Layers = MathType<THREE.Layers>;
118
+ export type Quaternion = MathType<THREE.Quaternion>;
119
+ export type Euler = MathType<THREE.Euler>;
120
+ export type Matrix3 = MathType<THREE.Matrix3>;
121
+ export type Matrix4 = MathType<THREE.Matrix4>;
122
+
123
+ /**
124
+ * R3F-compatible authored-node props with Octane refs and renderable children.
125
+ * The historical name is retained for source compatibility without importing
126
+ * React or exposing React ownership semantics.
127
+ */
128
+ export interface ReactProps<T> {
129
+ children?: unknown;
130
+ ref?: ThreeRef<T>;
131
+ key?: ThreeKey;
132
+ }
133
+
134
+ export type ElementProps<T extends ConstructorRepresentation, P = InstanceType<T>> = Partial<
135
+ Overwrite<P, MathProps<P> & ReactProps<P> & EventProps<P>>
136
+ >;
137
+
109
138
  export type ThreeElement<T extends ConstructorRepresentation> = Mutable<
110
139
  Overwrite<
111
- Partial<Overwrite<Properties<InstanceType<T>>, MathProps<InstanceType<T>>>>,
140
+ ElementProps<T, Properties<InstanceType<T>>>,
112
141
  ArgsProp<T> & ThreeInstanceProps<InstanceType<T>> & EventProps<InstanceType<T>>
113
142
  >
114
143
  >;