Contract 2.0.0 · package sha256
a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e· generated fromconfigurator-runtime.d.ts(its header) byservices/onsite-scripter/scripts/build-contract-docs.mjs; edit the source, not this file.
Holostep configurator runtime: tenant contract 2.0.
Vendored into @holostep-configurators/runtime-client by runtime:types from an immutable
release manifest. Never edit by hand.
Read this first. You want to… → use … → not …
| 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: | 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 }, | 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 traps agents fell into, with only the declaration:
Pose read is not a PoseInput (it has both rotations): copy { position, quaternion, scale }
(a mirror reads its −1 on scale.x); leave scale out only when it holds a 0, which no write takes.
A partial write keeps the omitted fields as that read shows them, so a positive absolute scale
on a mirrored part names its rotation too.undo: 'rebase' erases every earlier undo step, the shopper's included. Label a sync instead.capture() draws committed state only: a pick the shopper just made may still be loading, and
it is not in the image unless you await session.idle() first. It rejects
HoloBusyError('capture-held') while a transaction holds the display: idle(), then again.timeoutMs
(10 s by default), one holding the display within 14 s of its hold, later while it still
loads. Show what you wrote.beforeCommit hook wrote outlive the hook: reset them when you unregister it.getSceneModel() is null until the first model is placed, and its type says so: guard it
(const m = session.getSceneModel(); if (!m) return;), never getSceneModel()!, which
compiles and then fails on a page whose model has not arrived yet.tx.setSceneModel(withNode(…))) is rolled back by a supersede: true sync, or the
store never receives its node (placement merges only nodes it has) and the next sync drops it.commit: 'app': an equal store write releases nothing. Call input.revert(part) when the
drop resolves to where the node already is.find(q).set({ material }) merges into the first: a field only the earlier look set
stays where the GLB's should show. Switch partial looks with apply({ rules }), or null the
old look's other fields in the same write (MaterialPatch).setTimeout or a debounce: await the write's Promise, the event that follows
its commit, or idle(), and abort an older write's signal for newest wins (Session.idle
lists what to wait on).They hold for every member; members repeat the ones that matter.
Deg), except the stored rotation
of a scene-model node (IModelNode.rotation), which is Euler XYZ in radians, the stored
format: rotation: { x: 90 } turns a part 90 rad, so convert with angle.degToRad (/math);
durations in milliseconds (names end in Ms); exposure in stops (Ev); screen points in CSS
px from the viewer element's top-left corner.rotationDeg is intrinsic X, then Y, then Z. The contract assumes
no product front: read it from describe(node).manifest.front.HexColor), or { linear: { r, g, b } } with
components 0..1.assetBaseUrl (a .holo's bare twin name beside the
.holo), and the viewer document's must be absolute; every other URL (collection items,
Collection.load, textures in rules, materials and apply, stage images and HDRs) resolves
as fetch() resolves it, against the page's document.baseURI. A scene-model URL without a
scheme or host resolves beneath assetBaseUrl, leading '/' dropped (SceneModelOptions).collection(),
materials.create, on, onFrame, beforeCommit, follow, input.drag,
labs.lightLinking) is immediate and returns a handle or a disposer, never a Report; it is
never queued, and a rollback empties its owner of staged content but never removes the handle,
which ends only through its disposer, its owner or its signal (spec section 6.5). An engine
query (Collection.load, labs.geometry.read, input.pick, input.raycast) is asynchronous
and resolves with data; Collection.load also adds its item, which through session commits
on its own once loaded. Enlisted means started through a tx (a write, Collection.load,
labs.geometry.read; input is on the session only), awaited or not: a rejection that work
never observed rolls the transaction back ('enlisted-failed', spec section 6.1, O6). A
configuration call (applyDocument) replaces a layer; it is not an operation and carries no
op or cause.durationMs or fadeMs, a camera move and a clip resolve
when they finish, and open-ended motion once it starts (spec section 6.3). No other write
waits for a frame.capture, export, activateAR, assembly.attach, the overlays, the drag
body, labs and the patch apply of undo and redo load their code after the first
complete frame. A call before then waits for it, a synchronous one taking effect at the first
frame after it loads; an AR tap is routed at once (spec section 5.11). A failed load fails the
call as an internal failure does (a write rolls back; another Promise rejects
HoloBusyError('code-unavailable')), and a later call retries it (spec section 6.3).
candidates() is start-up code: it never waits and never throws for code.setSceneModel, select, applyPatch,
attach, detach, undo, redo), apply, Collection.set, stage.set and transaction
form one first-in, first-out queue per session and commit in call order. Two pending replacing
writes to the same target coalesce only when they are adjacent in that queue (spec section
6.10, R4); a merging write or a dry run is never coalesced. Other writes (rules, camera, clips)
commit as soon as they are ready, and queue only behind an open transaction; two writes to
one rule both commit, in call order (spec section 6.1, O2b): the older one
shows until the newer commits. For newest wins, give each write its own AbortController and
abort the previous one when a new write starts (it rejects AbortError; catch it). A
transaction opens only after every write called before it, queued or not, has passed its
commit point.frame: a sync read is as of the last drawn frame, while a capture renders
committed state anew). Each read names its class. Intent is
not what is shown: for that, keep the latest 'scene-model:changed' payload.changed: false and no seq, revision, undo step or event.cause.transaction's Promise (all or nothing), idle() (everything loaded, eased and drawn) and
an onFrame hook (each frame). Never a timer (Session.idle).@stability stable or @stability labs. A field inherits
the tag of its interface unless it carries its own. A labs member at a stable path is also
tagged @experimental; an experimental method is optional here, and app code reaches it
through has(session, id). A labs member may change in a minor release after one minor of
'deprecated' problems; a stable member only grows.@since 2.0.0. A member inherits its declaration's version
unless it carries its own.collection(), materials.create, on, onFrame,
beforeCommit, follow, input.drag): session.collection('c', bad).set(…).catch(…) never
reaches its .catch, so wrap such a call in try. A query that matches nothing resolves with
matched: 0 and records 'target-unmatched', deferred while a scene write called before it
is pending (Report.matched, Report.pending). Asset failures in
declarative writes are reported in Report.problems, never thrown. instanceof against this
package's Holo*Error classes holds for errors the loaded runtime build throws, although the
two are different class objects: each class checks a brand (Symbol.for('holo.error') and
its name) that the runtime sets on every error it throws.session.document) holds page defaults: stage, camera,
limits and lens; "the document's" value means its merged value. The stored configurator
(IConfigurator; the runtime's copy is the scene model, getSceneModel()) holds nodes, their
poses and the selected materials; "the scene model's" means that.noUncheckedIndexedAccess (an indexed read may
be undefined: check it before use) and run on the fake (/testing). They assume
session: Session and signal: AbortSignal in scope and this package's exports imported;
the app functions they call are written as declare lines. The imports are left out for
brevity only: app code imports every name it uses, values (has, validate, the Holo*Error
classes, the pure helpers) from the package root and types from /contract:
import { has, HoloAssetError } from '@holostep-configurators/runtime-client'; and
import type { Session, CameraView } from '@holostep-configurators/runtime-client/contract';.
/math and /testing names come from those entry points. Besides the root's app-side boot
helpers (loadRuntimeAtFloor, the channel constants, bootFailureAdvice) and /server (spec
section 9.1), the package exports only what this file declares: 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.boot, RUNTIME_CONTRACT and RUNTIME_VERSION are not exported by the package root: they
are the runtime module's, which the root's loadRuntimeAtFloor(runtimeUrl, required)
resolves to once it has checked your floor (src/runtime/REQUIRED_CONTRACT.json). Write
const runtime = await loadRuntimeAtFloor(runtimeUrl, required), then runtime.boot(…).