@displayxr/inline3d 1.29.0 → 1.30.0

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/CHANGELOG.md CHANGED
@@ -5,6 +5,130 @@ entry points (`.`, `./three`) are frozen for 1.x, while the **scene subpaths** (
5
5
  `./splat`, `./model`) are a preview tier whose options may change in any release. Entries below say
6
6
  which tier they touch, because that is what tells you whether an upgrade can move your pixels.
7
7
 
8
+ ## 1.30.0 — 2026-10-05
9
+
10
+ Touches the **preview tier** only (`./call`, `./call/full`, the new `./camera` and `./splat/playcanvas`). RFC 0003
11
+ **phase C2**: the `./call` public surface is trimmed to what a page needs (one warning release
12
+ for the old names), options are regrouped, theming gets a real API, and the stereo camera
13
+ becomes its own subpath. The core entry points are untouched.
14
+
15
+ ### `./splat/playcanvas` — new preview subpath: splats without Spark/three on the page
16
+
17
+ - **`addSplat` from `@displayxr/inline3d/splat/playcanvas`** is `./splat`'s `addSplat` with the
18
+ engine fixed to `'playcanvas'`: same options, same synchronous handle. `./splat` imports `three`
19
+ and `@sparkjsdev/spark` statically (Spark is its default engine and its Spark path is
20
+ synchronous), so a page passing `engine: 'playcanvas'` there still downloaded both, measured at
21
+ ~1.7 MB gzipped on displayxr.org. This entry imports neither. `playcanvas` is still loaded on
22
+ demand, at the first `addSplat`. Passing any other `engine` throws at the call.
23
+ - Internal: the deferred PlayCanvas handle moved from `inline3d-splat.js` to
24
+ `inline3d-splat-deferred.js`, shared by both entries. `./splat` behaves exactly as before.
25
+ - `test/splat-pc-entry.test.mjs` walks the entry's static import graph and fails on any bare
26
+ package import, so the property can't silently regress.
27
+
28
+ ### `./camera` — new preview subpath (RFC 0003 §4)
29
+
30
+ - **`openCamera(opts)`** → a `StereoCamera`: device selection (`prefer: 'auto' | 'stereo' | 'mono'
31
+ | deviceId | MediaStream`, the `displayxrStereo` hint, the > 2.5:1 pair heuristic, the
32
+ busy-device skip), `calibration` / `rectify` (moved here from `./call`), `format` / `stereo`
33
+ (`{ rectified, baselineMm, horizontalFovDeg }`) / `stream` / `video`, `on('ended')` (revoked,
34
+ unplugged, taken — never fired by `close()`), `close()`. Rejects only when no camera opens, with
35
+ `code` `'camera-busy'` | `'permission-denied'` (new: a refused prompt / policy / insecure
36
+ context) | `'no-camera'`.
37
+ - **`addCameraView(wall, canvas, cam, { mirror, autoConverge, depth, aspect })`** — the self view:
38
+ woven on a 3D wall, flat elsewhere, mirrored **correctly** (each half mirrored AND the halves
39
+ swapped — pinned by a synthetic-pair test: a subject in front stays in front; the naive per-half
40
+ mirror is proven pseudoscopic). `autoConverge` runs the call's disparity tracker on the face.
41
+ - **`cam.capturePhoto({ type, quality, name })`** → `{ blob, width, height, layout, convergencePx,
42
+ stereo, suggestedName: '<base>_2x1.jpg', xmp }`: the RAW pair, with the stereo record written
43
+ INTO the JPEG as XMP (`dxr:` namespace — layout, columns×rows, convergence, baseline, FOV,
44
+ rectified, eye size, software). **`cam.record({ mimeType, mono, audio })`** → `stop()` →
45
+ `{ blob, mono?, suggestedName: '<base>_2x1.webm', monoSuggestedName?, convergencePx, tagged }`,
46
+ the record as `DXR_*` Matroska tags; `mono: true` adds a left-eye copy (Decision 13). Readers:
47
+ `readJpegStereoMeta`, `readWebmStereoMeta`, `readJpegXmp`, `parseStereoXmp`. Decision 9: the
48
+ metadata lives in the file, no sidecar. `suggestedName`, `isStereoCamera`, `CAMERA_SDK`.
49
+ - The call is built on it: `CallOptions.camera` accepts a `StereoCamera` (left open when the
50
+ call ends), `camera: 'auto'` opens one through `openCamera()`, and the call's self view IS an
51
+ `addCameraView` — one implementation of capture, mirroring and convergence. A camera that ends
52
+ under a call (`ended`) drops it to audio-only with "Retry camera".
53
+ - Also in the CDN bundle `dist/call.js` (~153 KB min now, was ~124). `camera.d.ts`,
54
+ `docs/camera.md`, `samples/camera/`.
55
+
56
+ ### `./call` — the public surface (RFC 0003 §2)
57
+
58
+ **Renamed / regrouped options** (the 1.29 spelling still works for this release with one
59
+ `console.warn` per key; removed in 1.31):
60
+
61
+ | 1.29 | 1.30 |
62
+ |---|---|
63
+ | `accent` | `theme.accent` |
64
+ | `tileAspect` | `theme.tileAspect` |
65
+ | `inviteBase`, `updateUrl` | `invite: { base, updateUrl }` |
66
+ | `browserUrl` | `landing: { browserUrl, allow2D }` |
67
+ | `maxLifted` | `liftOptions.max` |
68
+ | `calibration`, `rectify` | `openCamera({ calibration, rectify })` of `./camera`, passed as `camera` |
69
+ | `log` | `debug: true` + `diagnostics()` |
70
+ | `recoverSession`, `wallOptions` | removed — a call always recovers the document's session (`sharedInline3D()`) |
71
+ | `scrollIntoView` | removed — always on with `ui: true` |
72
+
73
+ **New options:** `theme` (`accent`, `ink`, `shell`, `danger`, `radius`, `font`, `tileAspect`,
74
+ `strings`) → CSS custom properties `--dxr-accent/-ink/-shell/-danger/-radius/-font` on the host
75
+ and the string table; `ui: 'tiles'` (badges + plates only); `layout: 'none'` (tiles carry
76
+ `data-dxr-peer`, `call.tile(id)` returns them, the grid box steps aside); `landing.allow2D`.
77
+ `<dxr-call>` attributes are unchanged (`accent`, `tile-aspect`, `invite-base`, `browser-url` map
78
+ to the new groups) plus `ui="tiles"`.
79
+
80
+ **Handle renames:** `cameraOff(on)` → `setCameraOff(on)` with **`cameraOff` now the boolean
81
+ getter**; `format` → `localFormat`; `join()` resolves `void`; **new** `tile(peerId)`,
82
+ `diagnostics()` (explicitly unstable: the wall, the transport, per-peer `hello` / `route` /
83
+ `convergencePx` / `autoConverge` / `lift` / `quality`, the lift pool). **Removed from the public
84
+ handle** (kept as non-enumerable getters that warn once, gone in 1.31): `wall`, `format`,
85
+ `sendHint`, `_debug`. `CallPeer` is trimmed to `id`, `format`, `display` (`'3D' | '2D→3D' |
86
+ '2D'`), `state`, `muted`, `cameraOff`, `speaking`; the rest is in `diagnostics()`.
87
+ `handle.state` no longer lists `'idle'`.
88
+
89
+ **Events:** `format` → **`display`** `{ id, display }` (the old event still fires for a listener
90
+ that subscribes to it, with a warning; gone in 1.31); `session` is internal (subscribing warns);
91
+ `left` carries `reason`. New types `CallErrorCode` (adds `'permission-denied'`), `CallWarningCode`,
92
+ `CallDisplay`, `CallTheme`, `CallStrings`, `LiftFunction`.
93
+
94
+ **Removed from the `./call` entry — 38 internal helpers**, each kept for this release as a
95
+ `@deprecated` wrapper that `console.warn`s once on first use (a primitive constant cannot warn)
96
+ and removed in 1.31; all stay importable by file path from `js/call/*.js` / `js/camera/*.js`:
97
+ `normalizeCallOptions`, `newRoomId`, `isValidRoomId`, `buildInviteLink`, `normalizeHello`,
98
+ `makeHello`, `routeFor`, `badgeFor`, `createLiveGate`, `convergenceShiftPx`, `lowPass`,
99
+ `clampShift`, `eyeCropRect`, `mirrorSwapOps`, `mirrorSwapPixels`, `maxBitrateKbps`,
100
+ `measureFocusDisparity`, `preferVideoCodecs`, `sortCodecCapabilities`, `VIDEO_CODEC_ORDER`,
101
+ `MeshTransport`, `clampMaxPeers`, `qrEncode`, `roomKey`, `SIGNAL_PROTOCOL`, `WIRE_VERSION`,
102
+ `CALL_SDK`, `PLATE_TEXT` (→ `theme.strings`), `CALL_ACCENTS` (→ `theme.accent` takes the names),
103
+ `normalizeMono3D`, `resolveLift`, `createLiftPool`, `createFrameWatch`, `liftConvergenceFor`,
104
+ `liftPriorityFor`, `setLiftPriority`, `defaultLiftSpecifier`, `LIFT_PRIORITY`. Types gone with
105
+ them: `CallHello`, `CallCalibration`, `CallRoute`, `Mono3DReason`, `CallMono3DInfo`,
106
+ `CallLiftHandle`, `CallLiftFunction` (→ `LiftFunction`), `LiftStreamPriority`, `CallLiftPool`.
107
+ **Stays public:** `mountCall`, `addCall`, `dxrSignaling`, `DXR_SIGNAL_DEFAULT`,
108
+ `parseInviteLink` (Decision 7), `DxrCallElement`, `defineCallElement`, `attrsToOpts`,
109
+ `CALL_EVENT_PREFIX` — 9 value exports (from 47).
110
+
111
+ Internal moves (no API): `js/call/capture.js` → `js/camera/capture.js`, `js/call/disparity.js` →
112
+ `js/camera/disparity.js`, the SBS geometry and convergence math → `js/camera/geometry.js` /
113
+ `js/camera/converge.js`, option normalisation → `js/call/options.js`; the old paths re-export.
114
+
115
+ ### Gates (RFC 0003 §7, C2)
116
+
117
+ - **API snapshot:** `tools/api-surface.mjs` pins the public surface of `call.d.ts`,
118
+ `call-full.d.ts` and `camera.d.ts` in `test/api-snapshot/*.api.txt`; `npm test` (and CI) fail
119
+ when the surface changes without `node tools/api-surface.mjs --update` in the same change. The
120
+ deprecated set is part of the record, so 1.31's removal is a visible diff.
121
+ - **Snippets:** `tools/check-snippets.mjs` compiles every fenced `js`/`ts`/`jsx` block of
122
+ `docs/call.md` + `docs/camera.md` and every call/camera sample against the public `.d.ts` only
123
+ (and fails on a deprecated import); run by `npm test` where `typescript` is installed and by
124
+ CI's typecheck job.
125
+ - **Tests:** camera unit tests (the mirror-and-swap proof, XMP/WebM round-trips, error codes,
126
+ view routes), the surface tests (deprecation wrappers, legacy options, handle renames, theme /
127
+ strings / parts / `ui: 'tiles'` / `layout: 'none'`), and a headless Chrome e2e
128
+ (`npm run test:e2e:camera`: the mirrored pair's pixels on a mock woven wall, a JPEG whose XMP
129
+ parses, a WebM whose tags parse). Panel runs still required: the mirrored self view on glass,
130
+ an SBS photo opened in `/player`.
131
+
8
132
  ## 1.29.0 — 2026-09-30
9
133
 
10
134
  Touches the **core** (additive: `sharedInline3D()`, `handle.rewoven()`) and the **preview tier**
package/README.md CHANGED
@@ -24,6 +24,8 @@ npm install @displayxr/inline3d
24
24
  import { createInline3D } from '@displayxr/inline3d';
25
25
  import { EyeCamera, EdgeFeather } from '@displayxr/inline3d/three'; // optional three.js glue
26
26
  import { addSplat } from '@displayxr/inline3d/splat'; // experimental: 3DGS in a tile
27
+ // …or '@displayxr/inline3d/splat/playcanvas': the same addSplat on PlayCanvas, without the
28
+ // three + Spark download (use it whenever you pass engine: 'playcanvas')
27
29
  import { addModel } from '@displayxr/inline3d/model'; // experimental: glTF/GLB in a tile
28
30
  // (Draco / meshopt / KTX2 too —
29
31
  // you serve the decoder files)
@@ -210,6 +212,8 @@ docs/
210
212
  the WebXR→inline-3d mapping table and the whole render loop
211
213
  woven-canvas-rules.md never show a raw side-by-side frame: the join, the rules,
212
214
  handle.firstWoven / rewoven(), reading the browser's `withheld` log line
215
+ camera.md The 3D camera primitive (/camera): openCamera, the mirrored self view,
216
+ capturePhoto / record with the stereo record inside the file.
213
217
  call.md 3D video calls: the <dxr-call> / mountCall() one-line embed, options,
214
218
  events, headless, theming, React, self-hosting, troubleshooting
215
219
  proposals/ browser-side asks the SDK is waiting on