Session.idle ​Contract 2.0.0 · package sha256
a857eca95b35921109faf997094bb4192f12527d9d0fcd4be7e97f06425bad6e· generated fromconfigurator-runtime.d.tsbyservices/onsite-scripter/scripts/build-contract-docs.mjs; edit the source, not this file.
Method of Session · inherited by testing.FakeSession · stable · since 2.0.0 · called as session.idle
idle(options?: SignalOption): Promise<void>;
Resolves when nothing is open, queued, loading or animating, and the latest commit is shown:
the frame that draws it has been drawn, so info().scene, the canvas or a screenshot read
right after it shows that state. Open-ended motion (autoRotate, a clip played with
loop: true, a continuous hook) doesn't count, nor does refinement that keeps improving a
still frame afterwards (the soft ground shadow settling, a sharper still at rest). The only
call that waits for the screen. Where that frame cannot come it ends without that frame: on a
hidden page (a background tab), and while el is out of the viewport (the browser reports it,
or no animation frame comes for about 2 s: a cross-origin iframe scrolled away draws none).
The canvas and info().scene may then hold an older frame: for an image, capture()'s
default source renders committed state off-display there too. Rejects AbortError when
signal aborts.
On the fake (/testing) time moves only on tick, so a bare await fake.idle() never
settles: start it, then pump fake.tick(16) with an await between ticks until it settles,
with a bound (a few hundred ticks; one is not always enough), as createFakeSession shows.
What to wait on. Never a timer: a setTimeout or a debounce guesses, and a slow phone or a
large GLB outlasts the guess. Each of these settles exactly when its write has landed:
durationMs tween resolves when it finishes ('superseded': a newer write won);signal when
a new one starts, and await the newest; never debounce to let the runtime catch up;transaction Promise;cause.origin); 'camera:rest' when the camera has settled;idle();onFrame hook, never setInterval.
An open transaction counts as open: idle() waits for it to settle, so an app whose own
transaction's work waits on that app (a dialog, a click) finishes or aborts that
transaction before it awaits idle(), or the transaction rolls back with reason 'timeout'
after its timeoutMs without progress and its staged change is lost.
Errors: throws HoloScopeError inside work or beforeCommit.
Not for: waiting for one write, which is that write's own Promise.| Option | Type | Default | What it does |
|---|---|---|---|
signal |
AbortSignal |
undefined | Cancels this call; the member's JSDoc says what an abort does. |
signal) ​This call is a wait (SignalOption.signal, the kinds of call).
A wait (
idle,capture): aborting rejects AbortError and nothing rolls back.
Imports: values (has, validate, the Holo*Error classes, the pure helpers) from @holostep-configurators/runtime-client, types from @holostep-configurators/runtime-client/contract, /math and /testing names from those entries; boot from the runtime module the root's loadRuntimeAtFloor resolves to. A type your page defines is your app's: declare it there, never import it from the package.
Compiled under the contract's strict settings (examples/declaration/05-Session.idle.ts):
import type { DeepReadonly, SceneModel, Session } from '@holostep-configurators/runtime-client/contract';
declare const session: Session;
declare const signal: AbortSignal;
// What to wait on: the runtime says when a write has landed. No setTimeout, no debounce.
declare function showPrice(model: DeepReadonly<SceneModel>): void;
declare function setSaveEnabled(on: boolean): void;
// any commit, the shopper's, an undo's or yours, once it is shown: its event
session.on('scene-model:changed', ({ model }) => showPrice(model), { signal });
let latest: AbortController | null = null;
export async function pickFinish(material: string): Promise<void> {
latest?.abort(); // newest wins, with no debounce
const mine = (latest = new AbortController());
setSaveEnabled(false);
try {
// one write: its own Promise, at its commit point, its loads included
const r = await session.select('finish', material, {
actor: 'shopper', // a shopper's pick
origin: 'swatch',
signal: mine.signal,
});
// 'refused' or 'rolled-back': the finish didn't change, and Save comes back below
if (r.outcome === 'committed') await session.idle({ signal: mine.signal }); // drawn
} catch (error) {
// a newer pick aborted this one: still queued, it never lands; committed, it stops here
if ((error as Error).name !== 'AbortError') throw error;
} finally {
if (latest === mine) setSaveEnabled(true); // the newest pick settled, any outcome
}
}
| Rule | Result |
|---|---|
| 1. Neutral name | pass: idle names no product |
| 2. A precedent | pass: model-viewer (ar, camera-orbit, auto-rotate) (row G37); Unity Material.Lerp (row K17); WebGL KHR_parallel_shader_compile (row K35); Babylon.js Scene.whenReadyAsync (row K35) |
| 3. Two apps, or labs | pass: 28 apps (ledger: app-04c2774145, app-0c773a4ee3, app-37356bf09f, app-378d190f1b, app-4762caacdc, app-4f16d2f6da, app-4fd23bc869, app-510d750508 and 20 more; fleet cross-check: app-bcb7ef21da, app-e61a0b42ad, app-f3598fad77, app-fb16dd1985, app-fd09090420, configurator-template) |
| 4. The cheaper fix first | decided per need in Appendix B: rows G37, K17, K35 at tier stable (a member only where no rule, data, asset or app code serves the need) |