# Holostep configurator runtime: contract 2.0 > The engine-neutral API a Holostep configurator page uses through `@holostep-configurators/runtime-client`: boot a session, change the scene model, restyle parts, move the camera, drag, assemble, capture and export. The package wins over any note. Contract 2.0.0 · package sha256 `a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e` · generated by `services/onsite-scripter/scripts/build-contract-docs.mjs`; do not edit. "Spec section N" and the short ids in parentheses (O6, R4, ID8, K47, …) in these pages and in the runtime's messages name the design record a rule comes from. The record is not shipped, and the package is complete without it: read them as provenance, never as something to look up. ## One obvious way You want to… → use … → not … (the declaration header's table): | You want to… | Use | Not | | -------------------------------------------------------- | ------------------------------------------------ | ------------------------------------------------------------ | | change a configured material (priced, saved) | select(group, material) | a rule with { materialNodeId }: look only, never priced | | change a variant, an option's visibility or a position | setSceneModel, or applyPatch with 'replace' ops | find().set({ visible }) or { pose }: a rule, never saved | | change a connection | assembly.attach / assembly.detach | moving nodes by pose | | address a placed model as a whole (pose, hide, follow) | node(nodeId) | find({ node }): that matches every part of the model | | restyle authored parts | find(q).set(…), reset with null | writing the opposite value; writing it every frame | | build the whole look from app state | one apply(spec); an omitted key stays unchanged | re-applying rules after every sync | | keep standing rules (hidden planes, ghosts) | find(q).set(…): its block comes after apply()'s | in apply({ rules }): the next apply({ rules }) replaces them | | show no map on a slot | maps: { slot: 'none' } | null: it removes your value, and the GLB's map shows again | | preview a flat colour over a textured finish | { baseColor, maps: { baseColor: 'none' } } | baseColor alone: it tints the map (dark wood goes black) | | switch a whole look, keeping no field of the old one | a Material from materials.create(spec), fadeMs | a MaterialSpec rule write: however complete, it merges | | switch between partial looks over the GLB's material | apply({ rules }): its block is replaced whole | a second find().set: fields only the old look set stay | | let the newest write to a rule win | an AbortController per write, abort the previous | call order alone: an older write still shows first | | retile a texture whose image select() may change | a texture without url: { scale }, { sizeM } | repeating the url: it pins the image | | generate geometry | collection(name).set(spec) | load() | | set the page's lighting, background or exposure | the page document's stage; a layer: stage.set | stage.set(s) with no { owner }: it rejects | | show a GLB you have, with no saved design | setSceneModel({ root: [{ …, enabled: true }] }) | a ModelNode without enabled: true: it is never drawn | | know a GLB's bounds before placing it | collection(name).load(url, { key }) | mixing load() keys with set() items in one collection | | make several changes all or nothing, across awaits | transaction(tx => …), writing through tx only | session or its handles inside work | | make one undo step over time (a drag, an assistant turn) | history.group(), with expectGroup on each write | a transaction: it holds the display | | undo or redo the last step (a toolbar button) | session.undo() / session.redo() | session.history.undo(): History only reads and groups | | sync a store into the runtime | setSceneModel with origin, supersede and a label | undo: 'rebase': it erases every earlier step | | start history clean after a later load (a saved design) | undo: 'rebase' on it; the first model needs none | undo: 'rebase' on a sync that runs after shopper edits | | show a value you just wrote | the value you wrote, corrected from the Report | reading it back while it is queued (it can take seconds) | | read the scene model, null before the first one | const m = getSceneModel(); if (!m) … | getSceneModel()!: it hides a page with no model yet | | capture what the shopper just configured | await idle(); capture(); on HoloBusyError, again | capture() alone: a pick still loading isn't in it | | move something or the camera every frame | onFrame(f => f.pose(…) / f.camera(…)) | part.set or camera.set in a hook | | compute layout inside every commit | beforeCommit, through has(); reset its rules | on('committed'): that shows one frame stale | | use an experimental member or option value | has(session, id); an option value: supports(id) | calling the optional member unguarded | | check what a frame costs (triangles, draw calls) | if supports('info.scene'): idle(); info().scene | timing frames in onFrame: that measures the device | ## The ten likeliest mistakes 1. **Importing a type your page defines from the package, or leaving out the imports.** Instead: values from the package root, types from `/contract`; declare your own types in your app. The declaration: "a type your page defines (a store value, an option of your own UI) is your app's even when a runtime call takes it, so declare it in your app and never import it from the package." See [read this first](read-this-first.md#conventions). 2. **Relying on call order for the newest of two writes to one rule.** Instead: an `AbortController` per write; abort the previous one and catch its `AbortError`. The declaration: "For newest wins, give each write its own AbortController and abort the previous one when a new write starts (it rejects AbortError; catch it)". See [`PartActions.set`](api/PartActions.set.md). 3. **Reading a write back while it is queued behind a transaction.** Instead: show the value you wrote and correct it from the Report. The declaration: "Behind an open transaction it settles only when that transaction closes (a stalled one at its `timeoutMs` (10 s by default), one holding the display within 14 s of its hold, later while it still loads): show the value you wrote." See [`Writer.select`](api/Writer.select.md). 4. **Writing through `session` or one of its handles inside a transaction.** Instead: write through the `tx` that `work` receives, and through its handles only. The declaration: "Only writes through `tx` and handles obtained from it belong to it". See [`Session.transaction`](api/Session.transaction.md). 5. **Changing a priced, saved choice with a rule.** Instead: `select(group, material)` for a material; `setSceneModel` or `applyPatch` for a variant. The declaration: "Not for: a priced, saved choice, which is `select(group, material)`." See [`PartState.material`](api/PartState.material.md). 6. **Passing `null` to hide a map.** Instead: `maps: { slot: 'none' }`; `null` only removes your own value. The declaration: "A map slot: a texture; 'none' (no map, whatever is underneath); or null (remove your value and show what is underneath)." See [`TextureSlot`](api/TextureSlot.md). 7. **Writing a `Pose` read straight back as a `PoseInput`.** Instead: copy `{ position, quaternion, scale }`; leave `scale` out only when it holds a 0. The declaration: "Not a `PoseInput`: to write one back, copy `{ position, quaternion, scale }`". See [`Pose`](api/Pose.md). 8. **Passing `undo: 'rebase'` to keep a sync out of history.** Instead: label the sync; a write equal to the current state commits nothing. The declaration: "'rebase' (a reserved label) records no step and rebases history to the result: every earlier step, a shopper's included, can no longer be undone". See [`CommitOptions.undo`](api/CommitOptions.undo.md). 9. **Switching between partial looks with a second `find(q).set({ material })`.** Instead: `apply({ rules })`, whose block is replaced whole, or null the old look's fields. The declaration: "use `apply({ rules })`, whose block is replaced whole, or null that field in the same write". See [`PartActions.set`](api/PartActions.set.md). 10. **Laying out from `on('committed')`, which shows one frame stale.** Instead: `beforeCommit`, reached through `has()`, keeping the `on` branch for a runtime without it. The declaration: "joins each commit, so it never shows one frame stale". See [`Session.beforeCommit`](api/Session.beforeCommit.md). ## Imports - Values from the root: `import { has, HoloAssetError } from '@holostep-configurators/runtime-client';` - `boot`, `RUNTIME_CONTRACT` and `RUNTIME_VERSION` from the runtime module the root's loader resolves to: `const runtime = await loadRuntimeAtFloor(runtimeUrl, required);`, then `runtime.boot(…)` (never `import { boot }`). - Types from `/contract`: `import type { Session, CameraView } from '@holostep-configurators/runtime-client/contract';` - `/math` and `/testing` names from those entries: `import { mat4 } from '@holostep-configurators/runtime-client/math';`, `import { createFakeSession } from '@holostep-configurators/runtime-client/testing';` - A type your page defines is your app's: declare it there, never import it from the package. ## Boot a page The package root's app-side helpers, which `@holostep-configurators/runtime-client` defines beside this declaration (no page of their own under `api/`). Signatures as the package types them: ```ts // The runtime module at a channel URL, once its RUNTIME_CONTRACT meets your floor (the same major, at // least `apiVersion`, a document range that holds `documentVersion`); rejects before anything boots. function loadRuntimeAtFloor(url: string, required: RequiredContract, importer?: (url: string) => Promise): Promise; // Your app's src/runtime/REQUIRED_CONTRACT.json: a 2.0 page requires // { apiVersion: '2.0.0', documentSchema: 'holo.viewer-document', documentVersion: 2 }. interface RequiredContract { apiVersion: string; documentSchema: string; documentVersion: number } // The channels: v1 stable; v2 (the v2 canary for listed accounts, v2 stable for the rest, null // until 2.0.0 is promoted); the v2 canary for every account, what a 2.0 page uses before that. // One module per page: the page's first boot picks the channel for every session on it. const V1_CHANNEL_URL: string, V2_CHANNEL_URL: string, V2_CANARY_CHANNEL_URL: string; // What to tell the shopper when boot or the module's import failed; sync, pure, never throws. function bootFailureAdvice(error: unknown, env?: BootEnv): 'update-device' | 'update-browser' | 'enable-3d' | 'retry'; // The browser facts it reads (env defaults to browserBootEnv()). function browserBootEnv(): BootEnv; // { userAgent: string; maxTouchPoints: number; hasWebGl2: boolean } // A page document imported as JSON: schema 'holo.viewer-document', version 2, JSON-safe; throws TypeError. function assertPageDocument(raw: unknown): ViewerDocumentJson; // Freezes a value and everything it holds, and returns it. function deepFreeze(value: T): T; // A stored configurator's id: 24 hex characters. const OBJECT_ID: RegExp; ``` The boot every page writes: one `try` around the import and the boot, and the advice for the shopper on failure (`HoloBootError.reason` first; an import failure keeps the browser check). One session per product on screen (`BootOptions.el`): each session holds a WebGL context, and browsers keep about 16 live per page, losing the oldest past that (a boot whose context is lost before its first frame rejects `HoloBootError` 'webgl-context'). A collection grid shows one session and swaps its model, or boots a card's session on hover and destroys it after. Give `el` a CSS size: under 64 CSS px on a side or under `display: none` it records 'host-unsized'. Log the session's Problems from boot on, one line each: `info().problems` once `boot` resolves, then every `'problem'` event (`console.warn(JSON.stringify(problem))`, or your monitoring). The runtime writes no console line of its own in production, so this log is the only trace of a failed asset, a lost context or a refused write that the page's owner and a support engineer can read. Keep that log in production. ```ts import { assertPageDocument, bootFailureAdvice, cameraViewFromStored, deepFreeze, loadRuntimeAtFloor, V2_CANARY_CHANNEL_URL } from '@holostep-configurators/runtime-client'; import type { SceneModel, Session } from '@holostep-configurators/runtime-client/contract'; declare const rawPageDocument: unknown; // your page document, imported as JSON declare function showNotice(advice: 'update-device' | 'update-browser' | 'enable-3d' | 'retry'): void; const pageDocument = deepFreeze(assertPageDocument(rawPageDocument)); // `scene` is your server's read of the stored configurator, started beside the boot. export async function start( el: HTMLElement, accountId: number, scene: Promise, ): Promise { try { const runtime = await loadRuntimeAtFloor(V2_CANARY_CHANNEL_URL, { apiVersion: '2.0.0', documentSchema: 'holo.viewer-document', documentVersion: 2, }); const session = await runtime.boot({ el, accountId, contract: '2.0', document: pageDocument }); // One line per Problem, production included: the runtime logs none of its own there. for (const problem of session.info().problems) console.warn(JSON.stringify(problem)); session.on('problem', ({ problem }) => console.warn(JSON.stringify(problem))); const model = await scene; if (!model) showNotice('retry'); // your server could not read it else { await session.setSceneModel(model); // the session's first model // The start view: no scene write moves the camera, so set it after the first model. await (model.defaultView ? session.camera.set(cameraViewFromStored(model.defaultView), { durationMs: 0 }) : session.camera.fit(undefined, { durationMs: 0 })); } return session; } catch (error) { showNotice(bootFailureAdvice(error)); return null; } } ``` ### Boot without a bundler A page with no build step (a Shopify theme section, a CMS block, a static HTML page) can't install the package. It imports the runtime module straight from its channel URL, the value of `V2_CANARY_CHANNEL_URL` (`V2_CHANNEL_URL` once 2.0.0 is promoted). That module exports `boot`, `RUNTIME_CONTRACT`, `RUNTIME_VERSION` and the `Holo*Error` classes. The page writes the root helpers it needs inline: `meetsFloor` is `loadRuntimeAtFloor`'s check, `advice` is `bootFailureAdvice`'s table, and `startView` is `cameraViewFromStored` with its camera write: no scene write moves the camera, so after the first model the page sets its start view, the stored `defaultView`, else a fit, or a product much smaller or larger than a metre starts as a speck or cropped. The root's other values (`has`, `validate`, `checkAsset`) are for build-time code and tests. The scene model comes from your own server, as on any page (next section). ```html
``` What to tell the shopper, by what failed (`advice` above): | What failed | `reason` | Advice | | --- | --- | --- | | `boot` rejected HoloBootError `'no-webgl2'` | `'no-webgl2'` | 'update-device' on an iPhone, iPad or iPod, else 'update-browser' | | `boot` rejected HoloBootError `'webgl-context'` | `'webgl-context'` | 'enable-3d' | | `boot` rejected HoloBootError `'disconnected'`, `'other'` or a newer value | `'disconnected'`, `'other'` | 'retry' | | the import rejected (offline, a blocked script, a browser that can't parse the module) | none: no HoloBootError | 'update-device' or 'update-browser' without WebGL 2, else 'retry' | | the module is below your floor (`meetsFloor` false) | none | 'retry' | `HoloBootError` is told by its `name`, since a page with no package has no class to test `instanceof` against before the import resolves. An invalid option rejects HoloInputError and an aborted boot AbortError, as on any page. ### Your server's read of the stored configurator `boot` reads no scene model: your own server reads the stored configurator with your API key and your page passes it to its first `setSceneModel` (the `boot` example). The route is `GET https://onsite-backend.holostep.io/api/v1/configurator/load/` with the header `Authorization: Bearer `, a key of your account with the `configurator:read` permission. The answer's `configuration` is the stored configurator (`IConfigurator`, which `setSceneModel` takes as its `SceneModel`). Read it on your server only, never from the page: the key must not reach the browser, and a request without it answers 400 'Authentication required'. An invalid or expired key answers 401, a key without `configurator:read` 403, and an id of another account or an inactive configurator 404. ```ts import type { IConfigurator } from '@holostep-configurators/runtime-client/contract'; declare const HOLOSTEP_API_KEY: string; // from your server's secret store // On your server (a Node route, an edge function, a Shopify app proxy). export async function loadStoredConfigurator(configuratorId: string): Promise { const response = await fetch( `https://onsite-backend.holostep.io/api/v1/configurator/load/${encodeURIComponent(configuratorId)}`, { headers: { authorization: `Bearer ${HOLOSTEP_API_KEY}` }, signal: AbortSignal.timeout(10_000), }, ); if (!response.ok) throw new Error(`configurator ${configuratorId}: HTTP ${response.status}`); const body = (await response.json()) as { configuration?: IConfigurator }; if (!body.configuration) throw new Error(`configurator ${configuratorId}: no configuration`); return body.configuration; // cache it: the page asks for it on every view } ``` ### Preload the model Give the page one `` per model URL it boots with: the model then downloads beside the runtime module instead of after your first `setSceneModel`. Your server already holds the scene model (previous section), so it writes the links into the page's ``: ```html ``` - Measured: the first product frame shows up to about 1.5 s sooner on Fast 4G and 3-4 s sooner on Slow 4G for large models. `boot` itself resolves 0.2-2 s later, since the preload competes with the runtime's own chunks: time the product frame, not `boot`. - `crossorigin` is required. The runtime fetches models in CORS mode; a preload without it matches in no engine, so the model is requested twice (Chrome, Safari and Firefox). - The URL is the one the runtime fetches, byte for byte: a model node's `optGlbUrl` when it has one, else its `glbUrl` (a `.holo` too), absolute (a relative one resolves beneath `assetBaseUrl`). Preload only what the first frame shows: a preload the page never uses costs the shopper's bandwidth and a console warning. On slow links Safari can warn that the preload was not used within a few seconds even when the runtime used it: check that the Network panel shows one model request. ## Read next - [Read this first](read-this-first.md): the table, the traps and the conventions (units, kinds of call, order, read classes, errors). - [The full declaration](llms-full.txt): this file, then `configurator-runtime.d.ts`, `math.d.ts` and `testing.d.ts` byte for byte. - [Migrating from 1.x](MIGRATION.md): every 1.x member, pattern and document key with its 2.0 recipe. - [Examples by area](examples/README.md): spec section 5's compiled examples. - [Check it headless](check-headless.md): confirm in a headless browser that the product is on screen, lit and framed before hand-off (the fake draws no pixels). - [Accessibility](accessibility.md): view controls for keyboard and switch users, status messages, the view's name. ## Call paths Each call you write from `session` and its page; a write's page is on the interface that declares it (`Writer` for the session's writes, which a `tx` shares). Every member's path is in `index.json`'s `callPaths`. - `session.activateAR` → [Session.activateAR](api/Session.activateAR.md) - `session.animations.list` → [Animations.list](api/Animations.list.md) - `session.animations.pause` → [Animations.pause](api/Animations.pause.md) - `session.animations.play` → [Animations.play](api/Animations.play.md) - `session.animations.reset` → [Animations.reset](api/Animations.reset.md) - `session.animations.stop` → [Animations.stop](api/Animations.stop.md) - `session.apply` → [Writer.apply](api/Writer.apply.md) - `session.applyDocument` → [Session.applyDocument](api/Session.applyDocument.md) - `session.applyPatch` → [Writer.applyPatch](api/Writer.applyPatch.md) - `session.assembly.attach` → [Assembly.attach](api/Assembly.attach.md) - `session.assembly.candidates` → [Assembly.candidates](api/Assembly.candidates.md) - `session.assembly.connections` → [Assembly.connections](api/Assembly.connections.md) - `session.assembly.connectors` → [Assembly.connectors](api/Assembly.connectors.md) - `session.assembly.detach` → [Assembly.detach](api/Assembly.detach.md) - `session.beforeCommit` → [Session.beforeCommit](api/Session.beforeCommit.md) - `session.camera.autoRotate` → [Camera.autoRotate](api/Camera.autoRotate.md) - `session.camera.fit` → [Camera.fit](api/Camera.fit.md) - `session.camera.lens` → [Camera.lens](api/Camera.lens.md) - `session.camera.limits` → [Camera.limits](api/Camera.limits.md) - `session.camera.mode` → [Camera.mode](api/Camera.mode.md) - `session.camera.project` → [Camera.project](api/Camera.project.md) - `session.camera.ray` → [Camera.ray](api/Camera.ray.md) - `session.camera.set` → [Camera.set](api/Camera.set.md) - `session.camera.views.go` → [Camera.views.go](api/Camera.views.go.md) - `session.canTryOn` → [Session.canTryOn](api/Session.canTryOn.md) - `session.capture` → [Session.capture](api/Session.capture.md) - `session.collection` → [Writer.collection](api/Writer.collection.md) - `session.collection(name, options).dispose` → [Collection.dispose](api/Collection.dispose.md) - `session.collection(name, options).load` → [Collection.load](api/Collection.load.md) - `session.collection(name, options).part` → [Collection.part](api/Collection.part.md) - `session.collection(name, options).part(key).bounds` → [PartActions.bounds](api/PartActions.bounds.md) - `session.collection(name, options).part(key).find` → [PartActions.find](api/PartActions.find.md) - `session.collection(name, options).part(key).follow` → [PartActions.follow](api/PartActions.follow.md) - `session.collection(name, options).part(key).set` → [PartActions.set](api/PartActions.set.md) - `session.collection(name, options).set` → [Collection.set](api/Collection.set.md) - `session.describe` → [Session.describe](api/Session.describe.md) - `session.destroy` → [Session.destroy](api/Session.destroy.md) - `session.endAR` → [Session.endAR](api/Session.endAR.md) - `session.export` → [Session.export](api/Session.export.md) - `session.find` → [Writer.find](api/Writer.find.md) - `session.find(query).bounds` → [PartActions.bounds](api/PartActions.bounds.md) - `session.find(query).find` → [PartActions.find](api/PartActions.find.md) - `session.find(query).first` → [Parts.first](api/Parts.first.md) - `session.find(query).first().bounds` → [PartActions.bounds](api/PartActions.bounds.md) - `session.find(query).first().find` → [PartActions.find](api/PartActions.find.md) - `session.find(query).first().follow` → [PartActions.follow](api/PartActions.follow.md) - `session.find(query).first().set` → [PartActions.set](api/PartActions.set.md) - `session.find(query).follow` → [PartActions.follow](api/PartActions.follow.md) - `session.find(query).list` → [Parts.list](api/Parts.list.md) - `session.find(query).list().bounds` → [PartActions.bounds](api/PartActions.bounds.md) - `session.find(query).list().find` → [PartActions.find](api/PartActions.find.md) - `session.find(query).list().follow` → [PartActions.follow](api/PartActions.follow.md) - `session.find(query).list().set` → [PartActions.set](api/PartActions.set.md) - `session.find(query).set` → [PartActions.set](api/PartActions.set.md) - `session.getSceneModel` → [Writer.getSceneModel](api/Writer.getSceneModel.md) - `session.history.group` → [History.group](api/History.group.md) - `session.history.group(options).commit` → [HistoryGroup.commit](api/HistoryGroup.commit.md) - `session.idle` → [Session.idle](api/Session.idle.md) - `session.info` → [Session.info](api/Session.info.md) - `session.input.drag` → [Input.drag](api/Input.drag.md) - `session.input.pick` → [Input.pick](api/Input.pick.md) - `session.input.raycast` → [Input.raycast](api/Input.raycast.md) - `session.input.revert` → [Input.revert](api/Input.revert.md) - `session.input.startDrag` → [Input.startDrag](api/Input.startDrag.md) - `session.labs.decal` → [Labs.decal](api/Labs.decal.md) - `session.labs.fire` → [Labs.fire](api/Labs.fire.md) - `session.labs.geometry.read` → [Labs.geometry.read](api/Labs.geometry.read.md) - `session.labs.geometry.write` → [Labs.geometry.write](api/Labs.geometry.write.md) - `session.labs.lightLinking` → [Labs.lightLinking](api/Labs.lightLinking.md) - `session.labs.mirror` → [Labs.mirror](api/Labs.mirror.md) - `session.labs.prefetch` → [Labs.prefetch](api/Labs.prefetch.md) - `session.labs.shading.graph` → [Labs.shading.graph](api/Labs.shading.graph.md) - `session.labs.shading.graph(uniforms, build, options).dispose` → [Shading.dispose](api/Shading.dispose.md) - `session.labs.shading.graph(uniforms, build, options).set` → [Shading.set](api/Shading.set.md) - `session.labs.skin` → [Labs.skin](api/Labs.skin.md) - `session.materials.create` → [Materials.create](api/Materials.create.md) - `session.materials.create(spec, options).dispose` → [Material.dispose](api/Material.dispose.md) - `session.materials.get` → [Materials.get](api/Materials.get.md) - `session.node` → [Writer.node](api/Writer.node.md) - `session.node(nodeId).bounds` → [PartActions.bounds](api/PartActions.bounds.md) - `session.node(nodeId).find` → [PartActions.find](api/PartActions.find.md) - `session.node(nodeId).find(query).bounds` → [PartActions.bounds](api/PartActions.bounds.md) - `session.node(nodeId).find(query).find` → [PartActions.find](api/PartActions.find.md) - `session.node(nodeId).find(query).first` → [Parts.first](api/Parts.first.md) - `session.node(nodeId).find(query).follow` → [PartActions.follow](api/PartActions.follow.md) - `session.node(nodeId).find(query).list` → [Parts.list](api/Parts.list.md) - `session.node(nodeId).find(query).set` → [PartActions.set](api/PartActions.set.md) - `session.node(nodeId).follow` → [PartActions.follow](api/PartActions.follow.md) - `session.node(nodeId).set` → [PartActions.set](api/PartActions.set.md) - `session.on` → [Session.on](api/Session.on.md) - `session.on('anchors:moved')` → [Events.anchors:moved](api/Events.anchors_moved.md) - `session.on('animation:finished')` → [Events.animation:finished](api/Events.animation_finished.md) - `session.on('animation:started')` → [Events.animation:started](api/Events.animation_started.md) - `session.on('animation:stopped')` → [Events.animation:stopped](api/Events.animation_stopped.md) - `session.on('ar:modes')` → [Events.ar:modes](api/Events.ar_modes.md) - `session.on('ar:status')` → [Events.ar:status](api/Events.ar_status.md) - `session.on('assembly:changed')` → [Events.assembly:changed](api/Events.assembly_changed.md) - `session.on('camera:changed')` → [Events.camera:changed](api/Events.camera_changed.md) - `session.on('camera:control-end')` → [Events.camera:control-end](api/Events.camera_control-end.md) - `session.on('camera:control-start')` → [Events.camera:control-start](api/Events.camera_control-start.md) - `session.on('camera:rest')` → [Events.camera:rest](api/Events.camera_rest.md) - `session.on('click')` → [Events.click](api/Events.click.md) - `session.on('committed')` → [Events.committed](api/Events.committed.md) - `session.on('document:applied')` → [Events.document:applied](api/Events.document_applied.md) - `session.on('drag:end')` → [Events.drag:end](api/Events.drag_end.md) - `session.on('drag:move')` → [Events.drag:move](api/Events.drag_move.md) - `session.on('drag:start')` → [Events.drag:start](api/Events.drag_start.md) - `session.on('history:changed')` → [Events.history:changed](api/Events.history_changed.md) - `session.on('hotspot:click')` → [Events.hotspot:click](api/Events.hotspot_click.md) - `session.on('hotspot:hover')` → [Events.hotspot:hover](api/Events.hotspot_hover.md) - `session.on('hover')` → [Events.hover](api/Events.hover.md) - `session.on('material:selected')` → [Events.material:selected](api/Events.material_selected.md) - `session.on('problem')` → [Events.problem](api/Events.problem.md) - `session.on('resize')` → [Events.resize](api/Events.resize.md) - `session.on('scene-model:changed')` → [Events.scene-model:changed](api/Events.scene-model_changed.md) - `session.on('xr:cta')` → [Events.xr:cta](api/Events.xr_cta.md) - `session.on('xr:end')` → [Events.xr:end](api/Events.xr_end.md) - `session.on('xr:start')` → [Events.xr:start](api/Events.xr_start.md) - `session.onFrame` → [Session.onFrame](api/Session.onFrame.md) - `session.overlays.anchors.clear` → [Overlays.anchors.clear](api/Overlays.anchors.clear.md) - `session.overlays.anchors.set` → [Overlays.anchors.set](api/Overlays.anchors.set.md) - `session.overlays.dimensions` → [Overlays.dimensions](api/Overlays.dimensions.md) - `session.overlays.hotspots.clear` → [Overlays.hotspots.clear](api/Overlays.hotspots.clear.md) - `session.overlays.hotspots.set` → [Overlays.hotspots.set](api/Overlays.hotspots.set.md) - `session.permits` → [Session.permits](api/Session.permits.md) - `session.redo` → [Session.redo](api/Session.redo.md) - `session.select` → [Writer.select](api/Writer.select.md) - `session.setSceneModel` → [Writer.setSceneModel](api/Writer.setSceneModel.md) - `session.stage.get` → [Stage.get](api/Stage.get.md) - `session.stage.set` → [Stage.set](api/Stage.set.md) - `session.state` → [Writer.state](api/Writer.state.md) - `session.supports` → [Session.supports](api/Session.supports.md) - `session.transaction` → [Session.transaction](api/Session.transaction.md) - `session.tryOn` → [Session.tryOn](api/Session.tryOn.md) - `session.undo` → [Session.undo](api/Session.undo.md) ## API pages ### 1. Basics - [Point2](api/Point2.md): A 2D point or size. - [Point3](api/Point3.md): A 3D point, vector or size. - [ReadonlyPoint3](api/ReadonlyPoint3.md): An out-only position, never accepted as input: `Readonly`, as `Pose` reads `Readonly`. - [Quat](api/Quat.md): Unit quaternion. - [ScreenPoint](api/ScreenPoint.md): A `Point2` in CSS px from the viewer element's top-left corner; y grows downwards. - [ProjectedPoint](api/ProjectedPoint.md): A projected point. - [Bounds](api/Bounds.md): Axis-aligned world bounds, metres. - [Ray](api/Ray.md): A ray in world space. - [Insets](api/Insets.md): CSS px. - [HexColor](api/HexColor.md): '#rgb' or '#rrggbb', sRGB. - [ColorValue](api/ColorValue.md): A HexColor, or linear Rec.709 components 0..1. - [JsonPrimitive](api/JsonPrimitive.md): A JSON leaf value. - [JsonValue](api/JsonValue.md): Any JSON value: what `state()`, the commit feed, `meta` and checkpoints hold. - [Easing](api/Easing.md): The time curve of a tween (pose, camera): the CSS Easing Functions Level 1 curves. - [DeepReadonly](api/DeepReadonly.md): A value you may read and must not change: every property and array is readonly, at every depth. - [PoseInput](api/PoseInput.md): A pose to write, with at most one rotation: `rotationDeg` or `quaternion`, never both (a type error; an untyped call with both rejects HoloInputError… - [Pose](api/Pose.md): A pose read. ### 2. Operations, reports, causes, problems - [Actor](api/Actor.md): Who made a write. - [OpId](api/OpId.md): Session-unique, increasing, assigned at the call. - [Outcome](api/Outcome.md): Where an operation ended. - [Reason](api/Reason.md): Why an operation did not commit. - [ReportFields](api/ReportFields.md): The fields every Report has. - [Report](api/Report.md): The one result of every write, discriminated by `outcome`. - [TransactionReport](api/TransactionReport.md): A transaction's report. - [Cause](api/Cause.md): What caused a commit. - [ProblemCode](api/ProblemCode.md): Open set: handle codes you don't know. - [Problem](api/Problem.md): Data, never an Error: safe to store, send and render. ### 3. Errors - [InputErrorCode](api/InputErrorCode.md): What a HoloInputError reports. - [HoloInputError](api/HoloInputError.md): Invalid input to a call; nothing ran. - [HoloScopeError](api/HoloScopeError.md): A write through the wrong writer. - [HoloTransactionClosedError](api/HoloTransactionClosedError.md): A write through a `tx` (or a handle obtained from it) after that transaction closed. - [HoloAssetError](api/HoloAssetError.md): An imperative load failed. - [HoloBusyError](api/HoloBusyError.md): The runtime cannot serve the call now. - [HoloBootError](api/HoloBootError.md): `boot` could not start the renderer. ### 4. Boot - [boot](api/boot.md): Starts a session in `el`. - [BootOptions](api/BootOptions.md): What `boot` takes. - [RuntimeContract](api/RuntimeContract.md): The compatibility facts of this runtime build. - [RUNTIME_CONTRACT](api/RUNTIME_CONTRACT.md): This runtime's contract, also exported by the bootstrap so an app can check it before `boot`. - [RUNTIME_VERSION](api/RUNTIME_VERSION.md): The runtime build's version, e.g. '2.0.3'. ### 5. Writers, transactions, session - [CommitOptions](api/CommitOptions.md): The options of a commit. - [CheckpointOption](api/CheckpointOption.md): Page state stored atomically with a recorded commit. - [NoCommitOptions](api/NoCommitOptions.md): What a write through a `tx` accepts in place of the commit options: none of them. - [RecordedOptions](api/RecordedOptions.md): Recorded writes through `session` also take `checkpoint`. - [CameraCommitOptions](api/CameraCommitOptions.md): The commit options a camera write takes: `origin`, `actor` and `expectRevision`. - [SignalOption](api/SignalOption.md): The cancel option every write takes, through `session` and through a `tx`; registrations and waits take it too. - [WriteOptions](api/WriteOptions.md): The options of a plain write through `session`. - [TransactionOptions](api/TransactionOptions.md): The options of `session.transaction`: the commit options, a checkpoint, a signal and two time limits. - [SessionState](api/SessionState.md): Recorded state plus the look. - [JsonPatchOp](api/JsonPatchOp.md): RFC 6902 over `SessionState`. - [SceneModelOptions](api/SceneModelOptions.md): The member options of `setSceneModel`. - [Writer](api/Writer.md): The write surface `Session` and `Tx` share. - [Tx](api/Tx.md): A transaction's scoped writer. - [BeforeCommitContext](api/BeforeCommitContext.md): What a `beforeCommit` function receives. - [HistoryGroupOptions](api/HistoryGroupOptions.md): What `history.group` takes. - [HistoryGroup](api/HistoryGroup.md): The handle `history.group()` returns. - [History](api/History.md): Undo state and grouping. - [FrameOptions](api/FrameOptions.md): What `onFrame` takes. - [NotAsync](api/NotAsync.md): A callback's return type with Promises excluded, so an `async` callback fails to compile. - [Session](api/Session.md): A configurator session. ### 6. Queries, rules and parts - [Role](api/Role.md): product: the configured thing (fit, AR, export, dimensions, picking). - [Query](api/Query.md): All fields are ANDed; an array of queries matches any of them. - [PartState](api/PartState.md): What a write through `find()` (a rule) may change. - [PartWriteOptions](api/PartWriteOptions.md): The member options of a rule write (`set` on `Parts` or a `Part`). - [FollowOptions](api/FollowOptions.md): What `follow` takes. - [PartActions](api/PartActions.md): The actions that exist on one part and on a set of parts. - [Parts](api/Parts.md): A saved search. - [Part](api/Part.md): One part of a loaded model or a collection. - [Placement](api/Placement.md): Where and how an imperatively created part sits. - [Hit](api/Hit.md): A hit, on the session's input (so its part takes commit options). ### 7. Collections and shapes - [CollectionOptions](api/CollectionOptions.md): What `collection(name, options)` takes. - [Collection](api/Collection.md): Content you create. - [CollectionReport](api/CollectionReport.md): `updated`: keys of items changed in place. - [CollectionSpec](api/CollectionSpec.md): JSON form of a collection's content. - [ItemBase](api/ItemBase.md): The fields every collection item has. - [ItemSpec](api/ItemSpec.md): One collection item: `ItemBase` plus one kind. - [Outline2D](api/Outline2D.md): Outline segments in 2D. - [ShapeRef](api/ShapeRef.md): How reads return a `mesh` shape: JSON, as an `ImageRef` is for an image, so `state()`, the feed's `patch` and `inverse`, undo steps and event payloads carry… - [Shape](api/Shape.md): Built shapes. - [LinesSpec](api/LinesSpec.md): A lines item: a polyline in item-local metres. - [LabelSpec](api/LabelSpec.md): A label drawn in the scene (text or image on a quad facing the camera). - [LightSpec](api/LightSpec.md): Light intensity: KHR_lights_punctual units ×1, relative and not photometric (a sun of 3 lights white to about 1). - [ShadowSpec](api/ShadowSpec.md): The runtime owns the shadow map and its filter per quality tier (`info().renderer.tier`): high 2048 px, medium 1024 px, low 1024 px with a tighter filter… ### 8. Materials and textures - [MaterialInput](api/MaterialInput.md): A MaterialSpec, however complete, merges field by field (RFC 7396) over what the part wears (authored maps such as baked AO stay). - [MapSlot](api/MapSlot.md): The map slots of a material (glTF and KHR names). - [TextureSlot](api/TextureSlot.md): A map slot: a texture; 'none' (no map, whatever is underneath); or null (remove your value and show what is underneath). - [Wrap](api/Wrap.md): How a texture repeats outside 0..1. - [TextureSampling](api/TextureSampling.md): How a texture is filtered. - [TextureInput](api/TextureInput.md): A URL, or a texture with placement. - [ImageRef](api/ImageRef.md): How reads return a texture you wrote as an `image`: JSON, so `state()`, the feed, `applyPatch` and `sceneHash` stay JSON. - [TextureSpec](api/TextureSpec.md): At most one of `url` and `image`. - [MaterialSpec](api/MaterialSpec.md): glTF 2.0 and KHR names. - [MaterialPatch](api/MaterialPatch.md): A MaterialSpec as a JSON Merge Patch (RFC 7396): what a rule's `material` takes. - [Material](api/Material.md): A material parts wear: one `Part.material` reads, or one `materials.create` makes. - [Materials](api/Materials.md): Create reusable materials and read stored ones. ### 9. Stage - [BackgroundSpec](api/BackgroundSpec.md): What is drawn behind the scene, in the colour pipeline. - [StageSettings](api/StageSettings.md): Colour pipeline: shading is linear Rec.709 in a frame of at least half-float. - [StageWriteOptions](api/StageWriteOptions.md): The member options of `stage.set`. - [Stage](api/Stage.md): The stage: layered settings over the document's. - [RuleSpec](api/RuleSpec.md): One persistent rule in JSON form: what `find(query).set(set)` creates. - [SceneSpec](api/SceneSpec.md): The look as one JSON document: what `apply()` writes (`state().look` reads it back as a `LookState`). - [LookState](api/LookState.md): The look as `state().look` reads it: a read, not `apply()`'s input, so every key is present and none means "unchanged". ### 10. Camera - [CameraView](api/CameraView.md): Orbit view. - [LookAtView](api/LookAtView.md): A view by eye point and target. - [CameraState](api/CameraState.md): Observed: as of the last drawn frame (this frame's inside `onFrame({ phase: 'late' })`). - [CameraLimits](api/CameraLimits.md): A JSON Merge Patch (RFC 7396) over your app limits, which lie over the document's. - [CameraLens](api/CameraLens.md): The camera's lens. - [CameraMoveOptions](api/CameraMoveOptions.md): The member options of a camera move. - [Camera](api/Camera.md): Camera writes are look-only and never undo steps; of the commit options they take only `origin`, `actor` and `expectRevision` (`CameraCommitOptions`). ### 11. Input and drag - [PickOptions](api/PickOptions.md): What `input.pick` takes. - [RaycastOptions](api/RaycastOptions.md): What `input.raycast` takes. - [DragMove](api/DragMove.md): One frame of a drag. - [DragOptions](api/DragOptions.md): What `input.drag` takes. - [Input](api/Input.md): Picking, raycasts and drags on the canvas. ### 12. Assembly - [ConnectorRef](api/ConnectorRef.md): One connector of one placed node. - [Connection](api/Connection.md): Stored top-level in `IConfigurator.connections`. - [Connector](api/Connector.md): Authored facts plus derived world positions (computed on read). - [AttachOptions](api/AttachOptions.md): The member options of `assembly.attach`. - [AttachReport](api/AttachReport.md): `attached: false` comes with `outcome: 'refused'` and a `reason` of 'no-candidate', 'occupied', 'rejected' or 'not-loaded': the first that applies, in the… - [Assembly](api/Assembly.md): Connectors and connections between placed models (spec section 6.12). ### 13. Overlays - [HotspotBadge](api/HotspotBadge.md): A badge the runtime draws as a `