Ubiquitous language for the @stainless-code/layers domain. Keep terms stable across code, docs, and issues.
- Layer — one frame in a stack; a modal, dialog, drawer, popover, toast, etc. Owned by a
LayerStack; carries akey,payload, optionaldata(fromloadFn), aphase, and a caller-facingpromise. - Stack (
LayerStack) — ordered collection of active layers for one surface ("confirm","drawer","toast"). Identified byid; subscribed viasubscribe/getSnapshot(mountable layers) andgetQueuedSnapshot(serial-waiting layers). - Client (
LayerClient) — app-wide orchestrator owning named stacks;open()returns a typedPromise<Response>. #dispatch— internalLayerStackchoke point: label a snapshot-changing mutation, apply it,#flush. Not public; feedssubscribeNotify.- StackNotifyEvent — JSON-safe record of one observable stack transition (
action+active/queuedviews + optional projectedpayload). Emitted by core; Devtools bridges to TanStackEventClient. - subscribeNotify —
LayerClient:(listener) => unsubscribeforStackNotifyEvents. Distinct fromsubscribe/subscribeStacks(snapshot / stack-created). - seedNotify —
LayerClient: re-emit aregisterStackNotifyEventfor one stack, or all when omitted (Devtools attach / late subscribers). - Key — the logical identity of a layer (
find/upsert/gcTimeoperate onkeySignature(key)). Must be JSON-safe (string|boolean|null| finitenumber| plain objects/arrays of those); invalid segments throwLayerKeyError. Multiple live layers may share a key in aparallelstack. - Instance id (
LayerState.id) — the physical, unique id of oneLayerinstance (`${hashKey(key)}#n`); used for rendering keys and instance lookup/removal (getLayer,#remove). - Phase — resolution lifecycle axis:
pending→active→dismissed; orqueued(serial-waiting, not mounted); orerror. Does not includeexiting— animation is ontransition. Distinct fromactionStatusandtransition. - Transition — animation axis (
entering|settled|exiting), orthogonal tophase(likeactionStatus).dismissed + exiting= playing exit anim;dismissed + settled= cached/done. - Entering —
transition: "entering"while a layer mounts/opens; may coincide withphase: "pending"(loading) orphase: "active". - Settled —
transition: "settled"when enter/exit animation is done (or instant, delay0). - settle (
call.settle()) — resolve the current transition early:entering → settled(clears enter timer;phaseuntouched);exiting → remove(clears exit timer); no-op if alreadysettled. Completion is whichever of{ delay elapsed, call.settle() }fires first. - enteringDelay — ms the enter transition runs before settling;
0(default) = instant.call.settle()finishes early. - exitingDelay — ms the exit transition runs before removal;
0(default) = instant.call.settle()removes early. - Action status — independent axis (
idle|running) for in-layer mutations, separate fromphaseandtransition. - Call context (
LayerCallContext) — imperative handle handed to a layer component:end/dismiss(async — resolve the caller'sawait; returnPromise<boolean>),addBlocker,update(patch payload live),setRunning(drivesactionStatus),settle(drivestransition),ended,index,stackSize,root,stackId,layerId. Built bycreateCallContext. - open — push a layer onto a stack; returns
Promise<R>resolved bydismiss, or rejected byloadFn/ validation /cancelAll. The callerawaits the user's decision. - dismiss — async (
Promise<boolean>): consult blockers (unless{ force: true }), then resolve the layer's promise with a response, flipended, setphase: "dismissed"+transition: "exiting", then remove afterexitingDelayorcall.settle()(whichever first). Returntrueif dismissed,falseif vetoed. - cancelAll — force-clear a stack; reject every open/queued caller with
LayerCancelledError(skips blockers). Used for parent-dismiss child drain, group dispose, and host disconnect — distinct fromdismissAll(response)which completes withR. - cancelQueued — resolve and remove a serial
queuedlayer without mounting; skips blockers. Omit{ id }→ FIFO head for the key; pass{ id }→ exact queued instance. - blocker — consumer predicate gating user-intent dismissal;
true= allow, falsy = veto. Instance (call.addBlocker) or stack (stack.addBlocker) scope; multiple allowed; async-capable; throw/reject = veto (fail-closed). - addBlocker — register a blocker; returns a disposer.
call.addBlocker(fn)— instance scope (() => boolean | Promise<boolean>).stack.addBlocker(fn)— stack policy ((layer) => boolean | Promise<boolean>). - dismissing —
LayerState.dismissing:truewhile a user-intent dismiss is consulting blockers;falseon veto or removal. Use to disable close UI during async confirm. - dismiss mode —
DismissAllModeforstack.dismissAll:"skipBlocked"(default — close permitted, leave blocked open),"stopAtBlocked"(halt at first blocked),"force"(bypass blockers). Default per stack viaStackOptions.dismissAllMode. - upsert — reusing an active key updates its payload instead of stacking (singleton toasts/progress).
- scope — per-stack queueing on
StackOptions:scope: { strategy: "serial" | "parallel", onLoadError?: "block" | "advance" }(parallel/blockwhen omitted);serial= one occupying layer at a time,parallel= stack freely. SerialonLoadError:blockkeeps a failedloadFnasphase: "error"until dismiss;advanceremoves it and drains the queue. - gcTime — keep dismissed layers in a cache so re-opening the same key restores
datawithout re-runningloadFn. One slot per key (last-dismissed-wins); evicted aftergcTimewith explicit teardown. - loadFn — optional async load run on
open; cancelable viaAbortController;pending→activewithdataon success,erroron throw. - Observer / selector — adapters subscribe to
LayerStack.getSnapshot()and project via a selector (React/PreactuseSyncExternalStore, Solidfrom, Angularsignal, VueshallowRef, Lit reactive controllers, AlpineAlpine.reactive/ datastates, Svelte runes). - Options helper (
layerOptions) — identity function carrying<P, R, E, D>generics soLayerClient.openinfers the response type end-to-end. - Outlet — renders the active stack. React, Preact, and Solid ship
StackOutlet; Angular exposesrenderStack(vcr); Vue shipsStackOutlet; Lit ships<stack-outlet>; Alpine shipsx-layer-outlet+$layer; Svelte renders fromuseStack().current+callFor. See adapter ergonomics. - Stack handles (
useStackHandles) — headless{ states, getCall }for custom hosts (React/Preact/Solid/Angular/Vue/Lit; Alpine Rank-2 = subscribe + render yourself vialayerStack+callFor; Svelte'suseStack()already returns.current+callFor).StackOutletis the registered-component convenience built on it where shipped. - Layer group — a child stack owned by a parent layer and tied to its lifetime: created via
createLayerGroup/ adapteruseLayerGroup; when the parent dismisses, the group's stack iscancelAll'd (reason: "parentDismiss"). SameLayerClient, no second client. - Child stack — the stack a layer group opens onto; id derived from the parent's
stackId+layerIdviachildStackId(`${parentStackId}~${parentLayerId}~${name}`). - Validator (
validate) — a Standard Schema or sync(input) => outputfn that parses/validates a layer'spayloadatopen; the layer stores the parsed output. Failure rejectsopenwith aPayloadValidationError. - createLayer — core factory:
options+client→ headlessLayerHandle/ValidatedLayerHandle(layer ops +stack/client/options/currentescapes; no reactive field). - LayerHandle — return of plain
createLayer.open/upserttake payload only;dismisstakes{ id?, force? };update/cancelQueuedtake{ id? }(see cancelQueued). - ValidatedLayerHandle — when
options.validateis set:open/upsert= schema input;current/update= parsed output. Wired adapters also expose reactivestate/queued/topas output. - useLayer — adapter wired handle (
createLayer+ reactivestate/queued/top). Svelte:createLayer; Angular:injectLayer. - useLayerState / useLayerQueuedState / useQueuedStack — observe-only (mounted per-key / queued per-key / queued whole-stack); return
LayerState[](or framework wrappers). Renamed/added vs the old singularuseLayer(key, …). - current — live-checked bound instance on a handle (
Layer | null); correlation + explicit{ id }— not a hidden control default.