Model - Components
API reference for src/lanes/components/components, src/validate/components, src/schema/components on @huukhanhnguyen/model, reachable from ..
See the Guide for the ModelJSON / evaluate pipeline.
API reference
Signatures are generated from the live package .d.ts - not hand-written.
buildIsolatedParameterScope
function
Build an isolated-scope NodeCollection loaded with a parameters lane — a synthetic owner with NO .parent (see module doc: this is what stops the node-tree's upward owner walk immediately, so the scope can never see a host model's keys) — then apply per-call argsOverride to any parameter it names (a key not present keeps that parameter's own default expression). Shared by evaluateFlatComponent/evaluateComponent below and textureResolve.ts's textureParams (which stops here and reads the parameter values straight off the collection — no operations).
extraOwnerFields may include a definitions entry to OVERRIDE the fresh new Map() default — evaluateComponent uses this to share ONE DefinitionMap between a component's operations and opening folds (a nested block built by one is visible to the other's bake pass).
buildIsolatedParameterScope(parameters: (Operation | Container)[] | undefined, argsOverride: Record<string, string> | undefined, registry: Registry, extraOwnerFields?: Extra): { owner: IsolatedScopeOwner & Extra; collection: NodeCollection; }componentTableWarnings
function
Soft lint: Table.* in a component body/opening expression is isolation- law violation. A component receives table data via a PARAMETER; the host evaluates Table.get(...) at placeComponent args (outer scope).
componentTableWarnings(model: ModelJSON): RegistryError[]evaluateComponent
function
Evaluate one Component in its isolated, UNRESTRICTED (full 3D grammar) scope — lane "component" (see schema/model.ts's ModelLane/Component doc). argsOverride supplies this PLACEMENT's argument values (by parameter key); resolveComponent looks up another components[] entry by key (for placeComponent nesting, 3D mode — see resolvePlacedComponentBody on FlatLaneExtras); resolving guards against a reference cycle.
operations and opening evaluate as TWO SEPARATE folds (each starting from an empty stream) but share ONE DefinitionMap, so a nested block built by one is visible to the other's bake pass (isFlat's own bake, and — for a real placement — EntitiesNode's opening-subtract, which reads opening directly as already-resolved geometry).
evaluateComponent(component: Component, argsOverride: Record<string, string> | undefined, registry: Registry, resolveComponent: (key: string) => Component | undefined, resolving?: Set<string>, extra?: FlatLaneExtras): ResolvedComponentValueevaluateFlatComponent
function
Evaluate one ALWAYS-FLAT source (a sheet's ad hoc operations via evaluateFlatOperations below, or any flat-lane component content) in its isolated "flat" scope — lane "flat" (FLAT_LANE_METHODS-restricted, force-flattened to z=0 at every step). argsOverride supplies this call's argument values (by parameter key) — a key not present falls back to the parameter's own default expression. resolveComponent looks up a components[] entry by key (for placeComponent nesting, FLAT mode); resolving guards against a reference cycle (A places B places A).
evaluateFlatComponent(source: FlatComponentSource, argsOverride: Record<string, string> | undefined, registry: Registry, _resolveComponent: (key: string) => Component | undefined, resolving?: Set<string>, extra?: FlatLaneExtras): Entity[]evaluateFlatOperations
function
Evaluate a Sheet's own ad hoc operations (sheet-local flat content, authored directly in sheet-mm coordinates, so no placement transform is applied by the caller). Reuses evaluateFlatComponent's isolated scope + flat-lane machinery via a throwaway key.
parameters is the sheet's OWN signature (2026-07-26): the same Sheet.parameters lane resolveSheetParams reads paper size and scale from, wired here so a free parameter is READABLE by bare key from any ad hoc op's expression instead of being inert data nothing could reach. It is a true isolated signature — the host model's parameters stay invisible, so a sheet is a function of its own declarations exactly like a component is.
evaluateFlatOperations(operations: (Operation | Container)[], registry: Registry, resolveComponent: (key: string) => Component | undefined, extra?: FlatLaneExtras, parameters?: (Operation | Container)[]): Entity[]flatContractWarnings
function
flatContractWarnings(model: ModelJSON): RegistryError[]FlatLaneExtras
type
Extra scope hooks an isolated evaluation may need beyond resolveComponent (the by-key lookup passed as a plain function argument) — viewEntity's resolvePlacedView, tableEntity's resolvePlacedTable, imageEntity's imageUrlOf, Table.get()'s resolveTable, Material.get()'s resolveMaterial (settings-functions §B.2 — same wire as Table.get), and the TWO placeComponent hooks: resolvePlacedComponent (FLAT-mode — splices a gated, flattened result; wired for a truly-flat scope: sheets/rasterize) and resolvePlacedComponentBody (3D-mode — raw untransformed body+opening, no flatness gate; wired for the ROOT scope and a component's own "component"-lane scope). All Model-owned — absent when the caller has none (e.g. a standalone test), in which case each generator fails soft to empty/passthrough, and a bare Table.get(...) / Material.get(...) reference simply isn't defined (the same ReferenceError it always was before this hook existed).
type FlatLaneExtras
// = {
resolvePlacedView?: (key: string) => Entity[];
resolvePlacedTable?: (key: string) => Entity[];
/** 3D-mode placeComponent hook (2026-07-17) — raw (untransformed) body +
* opening volumes, NO flatness gate: valid to place a flat component in
* a 3D chain. Wired at the ROOT scope (Model's constructor) AND a
* component's own isolated "component"-lane scope (nested 3D
* placement) — never at a truly-flat scope, where placeComponent is a
* validate error outright (2026-07-26: paper places a VIEW) and the
* flat-mode resolver refuses (see EntitiesNode's dispatch,
* which checks `owner.lane === "flat"` OR the chain-level flat
* declaration). `flat` (the component's own `Component.flat`) rides
* along so a flat-declared CHAIN reaching this hook from a 3D lane can
* still splice the declared 2D representation
* (see EntitiesNode._resolvePlacedComponentFlat). */
resolvePlacedComponentBody?: (key: string, argsOverride?: Record<string, string>) => {
entities: Entity[];
opening: Entity[];
flat: boolean;
};
imageUrlOf?: (name: string) => string;
resolveTable?: (key: string) => unknown;
/** Material.get(name) — resolved materials[] record by name (fail-soft
* null). Wired like resolveTable so component/flat isolated scopes
* register a materials-lane dependency edge (settings-functions §B.2). */
resolveMaterial?: (name: string) => unknown;
/** `nesting`'s hook — a whole-scene-scan-capable flat-lane producer.
* Deliberately still NEVER wired onto the root scope, nor a component's
* own scope (a component's placed output can itself join the scene this
* would scan — same self-reference risk as at root) — UNLIKE
* resolvePlacedView/Table (see their own docs, and Model.ts's
* sceneFrame fold-depth guard), `nesting`'s whole-scene scan has no per-key
* identity to name a cycle by (it reads the WHOLE scene unconditionally,
* never one keyed entry), so a blanket lane-placement ban stays the
* simplest correct rule here — not yet worth the same detection
* machinery. Post VIEW-REFACTOR the `parts` keys name components[]
* entries (their view:"pattern" content is what gets nested — see
* Model._partPatternOf). Args already coerced by the calling
* EntitiesNode (`parts` array of {key,quantity}, `stock` object,
* `spacing`/`seed` numbers). */
resolvePlacedNesting?: (parts: {
key: string;
quantity: number;
}[], stock: {
width: number;
height: number;
}, spacing: number, seed: number, stockLength: number, kerfWidth: number) => Entity[];
/** Called once per step whose fold FAILED, after the lane has run.
* `EntitiesNode._stepFailed` is fail-soft by design — it records the
* error on the node, logs it, and passes the input through — so without
* this hook the only trace outside the node is a console line, and a
* caller (a sheet) cannot tell a placement that produced nothing from one
* that was never there. Reporting only; it must not change the fold. */
onStepError?: (failure: {
step: string;
method: string;
reason: string;
}) => void;
}openingCutWarnings
function
Soft lint for the opening law's GROUP BEHAVIOR (2026-07-26): the subtract does NOT reach inside groups, and that is deliberate — geometry ops pass groups through, and a void is a geometry op like any other. The failure mode is silent: the doorway simply is not there, and the author has no error to read.
So: a placeComponent whose component declares a non-empty opening, sitting in a scope where EVERYTHING is grouped, cuts nothing. Reported here rather than at eval time because it is not wrong — a scene may legitimately be all blocks — and because the JSON says enough to see it.
Position stopped mattering when the cut became a block BEHAVIOR run over the settled chain (behaviors.ts): a wall authored after the door is cut just the same, so only the group boundary is left to warn about.
Needs a registry to tell a producer from a transform; without one the lint is skipped rather than guessed.
openingCutWarnings(model: ModelJSON, registry?: Registry): RegistryError[]