skip to the page

@jam-overture/loom/sdk

Defining and registering primitives of your own, and the catalogue a model is shown.

Everything below is exported from that import. The names, the signatures and the sentences are read from the package itself rather than written here, so this page says what the copy of Loom in your node_modules says — and it changes in the same pull request the code does.

68 exports, in 13 modules. Generated from ./dist/sdk/index.d.ts, which is the declaration file this package publishes for @jam-overture/loom/sdk.

Install this first. @jam-overture/loom/sdk loads it the moment the import runs. Without it, the import itself fails — before any of your own code has run.

  • react^19.0.0optional peer dependency
pnpm add react

No import here has everything behind it. @jam-overture/loom/sdk publishes 68 of the 1,298 names this package publishes. The other 1,230 are behind one of the 16 other imports, and 15 of those 16 publish nothing this one does. The imports do not nest: a name that is not on this page is not a name that does not exist, and the search at the top of the page says which import it comes from.

Some of these names are published elsewhere too: 8 by @jam-overture/loom — the same declarations reached through two doors, so either import gives you the same thing.

The catalogue a model is shown

catalogue

What a deployment can build with, as data.

CataloguedProptype

type CataloguedProp = {
    readonly name: string;
    readonly required: boolean;
};

CataloguedPrimitivetype

type CataloguedPrimitive = {
    readonly type: PrimitiveType;
    /** One line, written by the primitive's author, about what it is for. */
    readonly description: string;
    /**
     * Declared props, name-sorted. `undefined` when the declared schema is not an
     * object schema and its keys therefore cannot be enumerated — unknown, which
     * is not the same claim as "none".
     */
    readonly props: readonly CataloguedProp[] | undefined;
    readonly slots: readonly SlotName[];
    /**
     * The binding names this primitive reads an answer under, in declaration
     * order. `undefined` when its author has not said — which, like `props`, is
     * not the same claim as "none" and is not rounded to one.
     *
     * An entry is a name, or the prop that gives one and the name it falls back
     * to. The second form is carried out rather than flattened to its
     * default, because the two say different things to a model: one name it must
     * write, against one name it may replace by setting a prop.
     *
     * It is the one field here that is about what the primitive *asks the
     * deployment for* rather than about what a tree may write on it, and it is
     * here because the name in a binding belongs to the primitive. A model is
     * told which sources exist by the data catalogue; without this it was never
     * told which names those answers may be filed under, so the only name it
     * could write was one it had seen on the page already.
     */
    readonly reads: readonly BindingDeclaration<BindingName>[] | undefined;
};

PrimitiveCataloguetype

type PrimitiveCatalogue = readonly CataloguedPrimitive[];

Role

role

What part a primitive plays, said by the author who knows and asked by every consumer that needs a semantic fact about a component rather than its name.

PrimitiveRoletype

The vocabulary, and it has one member on purpose.

type PrimitiveRole = "heading";

PRIMITIVE_ROLESvalue

Every role, so a host can render the vocabulary rather than keep its own copy of it — the same reason every other closed set in this package is exported.

const PRIMITIVE_ROLES: readonly PrimitiveRole[]

describePrimitiveRolefunction

One line per role, for a host putting the vocabulary in front of a person.

const describePrimitiveRole: (role: PrimitiveRole) => string

isPrimitiveRolefunction

Whether a string a host wrote is a role the runtime knows.

const isPrimitiveRole: (value: string) => value is PrimitiveRole

Audit

sdk/audit

The registration-time check, as a function a host calls rather than a side effect of building a registry.

PrimitiveAudittype

type PrimitiveAudit = {
    readonly type: PrimitiveType;
    readonly verdict: ConformanceVerdict;
    readonly placement: PlacementVerdict;
    readonly submission: SubmissionVerdict;
    /** What the author declared, beside what the probe saw. */
    readonly declaresSubmits: boolean;
    /** The props this primitive declared it frames. Empty for almost all. */
    readonly framesProps: readonly string[];
};

UnplacedSlotstype

A primitive that declared a region and then did not render it.

type UnplacedSlots = {
    readonly type: PrimitiveType;
    readonly slots: readonly string[];
};

UnplacedBehaviourstype

A primitive that took a control from the runtime and then did not place it.

type UnplacedBehaviours = {
    readonly type: PrimitiveType;
    readonly behaviours: readonly BehaviourName[];
};

ThrowingConfigurationstype

A primitive that threw under some configuration its own schema accepts.

type ThrowingConfigurations = {
    readonly type: PrimitiveType;
    readonly failures: readonly ProbeFailure[];
    /**
     * Whether *nothing* the probe tried came back — the line between a fault the
     * audit is certain of and one it is only reporting.
     *
     * `false`: some configurations rendered and this one threw, so the component
     * is callable and a value its own schema accepts crashes it. Certain.
     *
     * `true`: every configuration threw, which is what a broken component and a
     * hook-using one both look like from outside a renderer — and a hook-using
     * component is a legitimate primitive. A host with none of those
     * asserts the whole list empty; one that ships them asserts the `false` half
     * and reads the rest.
     */
    readonly everyConfiguration: boolean;
};

RegistryAudittype

type RegistryAudit = {
    readonly audits: readonly PrimitiveAudit[];
    /** Registered, renders, invisible to the portal. */
    readonly notDecorated: readonly PrimitiveType[];
    /** The probe could not answer — neither a pass nor a failure. */
    readonly notProbeable: readonly PrimitiveType[];
    /**
     * Declared a slot and dropped it. Unlike `notDecorated` this loses content
     * rather than a handle, so a host with no portal at all still wants it empty.
     */
    readonly unplacedSlots: readonly UnplacedSlots[];
    /**
     * Declared a behaviour and dropped its control. Like `unplacedSlots` this is
     * a promise the registration made and the component did not keep — and unlike
     * a slot, nothing else on the page hints that something is missing, because
     * the content a behaviour acts on renders perfectly without it.
     */
    readonly unplacedBehaviours: readonly UnplacedBehaviours[];
    /**
     * Renders no children — a leaf. Not a fault: `loom.stat` holds its value and
     * label as props and has nowhere to put a text node. It is here because it is
     * the one fact the renderer cannot derive, and a portal that offers "insert
     * into this node" needs it to avoid offering a place nothing will appear.
     */
    readonly leaves: readonly PrimitiveType[];
    /**
     * Threw on props built from its own schema. A fault whoever registered it
     * wants to know about — a tree the validator accepts can take the page down —
     * and never a reason to distrust the rest of this audit, which is answered by
     * the configurations that did render.
     *
     * Every primitive that threw is here, including one that threw under *all* of
     * them. That case used to reach only `notProbeable`, beside the class and
     * hook-using components that are legitimate primitives, so the most
     * extreme instance of the fault this list exists for sat in the one list a
     * host cannot assert empty. `everyConfiguration` marks it rather than hiding
     * it.
     */
    readonly throwsOnDeclaredProps: readonly ThrowingConfigurations[];
    /**
     * The registered primitives that post, as the probe observed them.
     *
     * This is the list a deployment holds its endpoint registry against: if
     * anything here is registered, `renderRequest` wants `endpoints`, and a
     * deployment that ships one without the other ships forms that render
     * disabled. Derived rather than declared, so it is the truth about the
     * components rather than the sum of their authors' intentions.
     */
    readonly submits: readonly PrimitiveType[];
    /**
     * Places an address and never declared it posts. Not a broken page — the
     * form works — but the declaration is what a deployment reads to know the
     * seam is load-bearing here, so an undeclared submitter is a form whose need
     * for an endpoint registry is invisible until someone fills it in.
     */
    readonly undeclaredSubmitters: readonly PrimitiveType[];
    /**
     * Declared it posts and placed no address under any configuration probed.
     * This is the failure the submission seam exists to prevent, caught one layer
     * earlier than it would otherwise be: a submit control that goes nowhere
     * renders, looks finished, and reports nothing until a visitor uses it.
     */
    readonly unwiredSubmitters: readonly PrimitiveType[];
    /**
     * The registered primitives that put a prop in a frame.
     *
     * This is the list a deployment holds its framable-origin registry against,
     * exactly as `submits` is held against its endpoint registry: if anything
     * here is registered, `renderRequest` wants `origins`, and a deployment that
     * ships one without the other ships embeds that render a refusal.
     *
     * Declared rather than probed, which is the one place this audit takes an
     * author's word for something it could in principle check. It could not
     * check this one usefully: the seam already refuses to register a `frames`
     * naming a prop the schema does not declare, and the failure left over — a
     * primitive that puts a URL in an `iframe` and never said so — is invisible
     * to a probe, because an `iframe` a primitive built out of a prop it did not
     * declare looks exactly like one it did. What would catch that is a lint over
     * the markup, not a call of the component. Named here rather than left as a
     * gap somebody discovers.
     */
    readonly frames: readonly PrimitiveType[];
};

RegistryAuditOptionstype

What the audit is told beyond the registry itself.

type RegistryAuditOptions = {
    /**
     * The answer states each type is probed in, beside the ones its own schema
     * closes over.
     *
     * A `Map` rather than an object, because the key is a primitive type and a
     * caller building one from `registry.primitives` has the branded strings
     * already — and because a lookup on an object literal is a lookup on
     * `Object.prototype` for any name that happens to be on it.
     */
    readonly answers?: ReadonlyMap<PrimitiveType, readonly ProbeAnswers[]>;
};

auditRegistryfunction

Shown in use on Scaffolding a project — The third file is the point

const auditRegistry: (registry: PrimitiveRegistry, options?: RegistryAuditOptions) => RegistryAudit

decorationFromAuditfunction

The audit, as the predicate addressNode needs.

const decorationFromAudit: (audit: RegistryAudit) => DecorationLookup

describeRegistryAuditfunction

One line per primitive, for a CLI or a failing test's message.

const describeRegistryAudit: (audit: RegistryAudit) => string

Catalogue

sdk/catalogue

The one function that turns a registry into something that can leave the process.

catalogueOffunction

The registry, projected into the catalogue other components read.

Shown in use on Connecting a model — The slot, and what goes in it

const catalogueOf: (registry: PrimitiveRegistry) => PrimitiveCatalogue

Conformance

sdk/conformance

The conformance probe.

ProbeFailuretype

A configuration the schema accepts that the component threw on.

type ProbeFailure = {
    readonly props: JsonObject;
    readonly reason: string;
    /**
     * The binding names the node had answers under when it threw, sorted, and
     * absent when it had none.
     *
     * Without it two failures of `loom.feed` at its default props read as the
     * same line twice, and the one that matters — *it renders until you answer
     * it* — is the one a reader cannot pick out.
     */
    readonly answered?: readonly string[];
};

NotProbeableCausetype

Why a probe declined to answer.

type NotProbeableCause = "not-callable" | "threw";

NotProbeabletype

type NotProbeable = {
    readonly outcome: "not-probeable";
    readonly cause: NotProbeableCause;
    readonly reason: string;
    /** Every configuration that threw. Empty exactly when nothing was called. */
    readonly failures: readonly ProbeFailure[];
};

describeProbeFailuresfunction

Named rather than counted. Whoever reads this has to reproduce it, and {"type":"select"} is the whole reproduction.

const describeProbeFailures: (failures: readonly ProbeFailure[]) => string

ConformanceVerdicttype

type ConformanceVerdict = {
    readonly outcome: "decorates";
} | {
    readonly outcome: "not-decorated";
} | NotProbeable;

ProbeConfigurationtype

One call of the probe: the props the node carries, and the answers it had.

type ProbeConfiguration = {
    readonly props: JsonObject;
    /** `NO_DATA` means *this node asked nothing*, which is most of them. */
    readonly data: NodeData;
};

unaskedfunction

Props with nothing answered beside them, which is most configurations.

const unasked: (props: JsonObject) => ProbeConfiguration

ProbeAnswerstype

An answer state a caller wants a primitive probed in, beside the states its own schema closes over.

type ProbeAnswers = {
    /** Answers by binding name, as the render walk would hand them over. */
    readonly data: NodeData;
    /**
     * The props the node carries in this state. Absent means its defaults, which
     * is the configuration a binding name's default was chosen for.
     */
    readonly props?: JsonObject;
};

probeConfigurationsfunction

The prop configurations a primitive is probed under.

const probeConfigurations: (choices: readonly ClosedChoice[]) => readonly JsonObject[]

probeStatesfunction

The states a primitive is probed in: what its schema closes over, then what its registrant declared it can be answered with.

const probeStates: (configurations: readonly JsonObject[], answers?: readonly ProbeAnswers[]) => readonly ProbeConfiguration[]

probeEditableDecorationfunction

Decoration is a promise that holds however the primitive is configured, so one configuration that fails to decorate makes the answer not-decorated even if the rest pass. A configuration that throws answers nothing either way and is skipped; when none of them answer, every failure is carried on the verdict, so the reason the probe declined is a value rather than the prose of whichever configuration happened to be first.

const probeEditableDecoration: (primitive: LoomPrimitive, text?: PrimitiveText<string>, configurations?: readonly ProbeConfiguration[], declaredFrames?: readonly string[]) => ConformanceVerdict

PlacementVerdicttype

The second probe: does a primitive place what it was handed?

type PlacementVerdict = {
    readonly outcome: "probed";
    /** Declared slots that no probed configuration placed. */
    readonly unplacedSlots: readonly string[];
    /** Declared behaviours whose control no probed configuration placed. */
    readonly unplacedBehaviours: readonly BehaviourName[];
    /** Whether any probed configuration placed the children it was handed. */
    readonly rendersChildren: boolean;
    /**
     * The configurations that answered — one unasked `{}` alone when the
     * schema closes over nothing and the caller supplied no answers.
     */
    readonly probed: readonly ProbeConfiguration[];
    /**
     * Configurations built from the primitive's own schema that it threw on.
     * Never a reason to distrust the verdict — the configurations that
     * answered still answered — and always a fault: a component that throws
     * on a value its schema accepts is one a valid tree can crash a page with.
     */
    readonly threw: readonly ProbeFailure[];
} | NotProbeable;

probePlacementfunction

const probePlacement: (primitive: LoomPrimitive, declaredSlots: readonly string[], text?: PrimitiveText<string>, configurations?: readonly ProbeConfiguration[], declaredBehaviours?: readonly BehaviourName[], declaredFrames?: readonly string[]) => PlacementVerdict

SubmissionVerdicttype

The third probe: does a primitive that posts put the address on the page?

type SubmissionVerdict = {
    readonly outcome: "places";
} | {
    readonly outcome: "not-placed";
} | NotProbeable;

probeSubmissionPlacementfunction

some rather than every, matching probePlacement. The question is whether this primitive posts at all, and a primitive that renders a form under one layout and a summary under another is answering honestly in both.

const probeSubmissionPlacement: (primitive: LoomPrimitive, text?: PrimitiveText<string>, configurations?: readonly ProbeConfiguration[], declaredFrames?: readonly string[]) => SubmissionVerdict

ColourPairingtype

The fourth probe: which colours does this primitive put on which grounds?

type ColourPairing = {
    readonly foreground: PaletteSlot;
    readonly background: PaletteSlot;
};

ColourVerdicttype

type ColourVerdict = {
    readonly outcome: "probed";
    /** Ink and ground both set by this primitive. */
    readonly painted: readonly ColourPairing[];
    /** Ink set here, ground left to whatever this is placed in. */
    readonly floating: readonly PaletteSlot[];
    /** Grounds this primitive puts its declared children and slots on. */
    readonly childGrounds: readonly PaletteSlot[];
} | NotProbeable;

probeColourPairingsfunction

The union across every configuration, rather than the intersection the decoration probe takes. Decoration is a promise that has to hold however the primitive is configured; a colour pairing is a fact about one configuration, and a loom.section that paints accent-subtle only under tone: "accent" renders that pairing on a real page whatever the other tones do.

const probeColourPairings: (primitive: LoomPrimitive, declaredSlots?: readonly string[], text?: PrimitiveText<string>, configurations?: readonly ProbeConfiguration[], declaredFrames?: readonly string[]) => ColourVerdict

Copy

sdk/copy

The words a node shows.

CopyDeclarationstype

A type's declared copy props, or undefined where it has not said.

type CopyDeclarations = {
    readonly copyFor: (type: PrimitiveType) => readonly string[] | undefined;
};

UnreadCopytype

A node whose words this reading could not see, and the props it could not classify.

type UnreadCopy = {
    readonly nodeId: NodeId;
    readonly type: PrimitiveType;
    /**
     * Its string-valued props, in the order the node carries them.
     *
     * String-valued, and that is a decision rather than an accident. Where
     * nothing has been declared, a value that is not a string is not a candidate
     * word at all: a `loom.divider` holding `weight: 2` would otherwise be
     * reported as a part whose words a reader might lose, and every layout
     * primitive in the library would join it. The opposite call is made on the
     * declared side, and a declaration is the whole of the difference.
     */
    readonly props: readonly string[];
};

UnspokenCopytype

A prop a type declared as copy, holding something this reading will not turn into a word.

type UnspokenCopy = {
    readonly nodeId: NodeId;
    readonly type: PrimitiveType;
    /** The declared copy props holding a non-string value, in declaration order. */
    readonly props: readonly string[];
};

NodeCopytype

type NodeCopy = {
    /**
     * In reading order: each element's own declared copy before its children's,
     * because a stat's label is above the things under it and a card's title is
     * above its body. Blank and whitespace-only values are left out — a caller
     * composing a sentence wants words, and an empty string is not one.
     */
    readonly words: readonly string[];
    /** What this reading could not see. Empty when every type under it declared. */
    readonly unread: readonly UnreadCopy[];
    /** What it was told to read and could not. Empty when every declared copy prop held a string. */
    readonly unspoken: readonly UnspokenCopy[];
};

copyInfunction

Reads a node and everything under it.

const copyIn: (node: LoomNode, declarations: CopyDeclarations) => NodeCopy

Definition

sdk/definition

The registration contract: what an author declares to make a component something a Loom tree may name.

PrimitiveDefinitiontype

type PrimitiveDefinition<TProps extends JsonObjectView = JsonObject, TText extends string = never, TBehaviour extends BehaviourName = never> = {
    readonly type: string;
    readonly description: string;
    readonly props: ZodType<TProps, ZodTypeDef, unknown>;
    readonly slots?: readonly string[];
    /**
     * Declared strings, by key. The keys are inferred, so the component's
     * `loom.text` is typed to exactly what was declared here — an author who
     * reads a key they did not declare finds out at the declaration, which is the
     * same bargain the props schema makes.
     */
    readonly text?: Readonly<Record<TText, string>>;
    /**
     * `"always"` for a primitive that is a target however it is configured, or
     * `{ whenProps }` naming the props that make it one — `href` on a card, which
     * is a plain surface without it. The named props must be props the schema
     * declares; the registry refuses a declaration that names one it does not, so
     * a renamed prop cannot leave this quietly pointing at nothing.
     */
    readonly interactive?: InteractiveWhen;
    /**
     * `true` for a primitive whose job includes sending what it collected. It
     * takes no conditional form, unlike `interactive`: a card is a target only
     * when the tree gives it an `href`, but a form is a form, and a primitive
     * that posts under one prop value and not another is two primitives.
     *
     * Nothing reads it at render time and nothing needs it to render — a
     * component is handed `loom.submit` whenever its node declared one, declared
     * or not. What it buys is that the audit can tell the two silent failures
     * apart: a primitive that says it posts and places no address, and one that
     * places an address without ever having said it posts.
     */
    readonly submits?: boolean;
    /**
     * The props whose values this primitive puts in an `iframe` — `["src"]` on an
     * embed. Empty for everything else, which is every other primitive there is.
     *
     * A list of prop names rather than a `true`, because the runtime has to know
     * *which* string to check and there is nothing it could infer that from: a
     * `src` reaching an `iframe` and a `src` reaching an `img` are the same JSON.
     * The named props must be props the schema declares; the registry refuses a
     * declaration that names one it does not, the same way it refuses an
     * `interactive` trigger that has been renamed out from under it — the drift
     * that actually happens is a prop renamed and a declaration left pointing at
     * nothing, which would silently stop checking the URL rather than fail.
     */
    readonly frames?: readonly string[];
    /**
     * The controls this primitive takes from the runtime's closed behaviour
     * vocabulary — `["copy"]` on a code panel. Optional, and empty for almost
     * everything: a Loom page is data, and a behaviour is the exception that has
     * to be asked for by name.
     *
     * Declaring one is a claim with two consequences the registry checks. The
     * strings the control needs become strings this primitive must declare, so it
     * has a name in every language the deployment serves; and a control is a
     * target the reader aims at, so this primitive must also declare itself
     * `interactive` or the Gate will let one sit inside an anchor.
     */
    readonly behaviours?: readonly TBehaviour[];
    /**
     * The prop each control takes its name from — `{ present: "label" }` on a
     * dialog whose trigger carries the page's own call to action.
     *
     * Optional, and absent for almost every primitive that takes a control at all.
     * A control's name is normally a string this primitive declares, which is what
     * makes *Copy* the same word on every code panel in a deployment and lets one
     * dictionary translate all of them. This is the exception for the control whose
     * words are **content**: a trigger that opens a dialog says *Watch the demo*,
     * the next one says *Book a call*, and neither is a fact about the primitive or
     * a thing a translator should hold.
     *
     * Three checks follow, and the first two are the registry's. The behaviour must
     * be one this primitive declared, and the prop must be one its schema declares
     * — the same drift `frames` and `interactive` are checked against, failing the
     * same silent way: a prop renamed and a declaration left pointing at nothing
     * would quietly go back to the declared string for ever. The third is not a
     * check but a property: the declared text is still required, so the control has
     * a name in every language before any node is written, and a node that fills
     * the prop with nothing gets it.
     */
    readonly names?: Readonly<Partial<Record<TBehaviour, string>>>;
    /**
     * What part this primitive plays — `"heading"` on anything a reader takes as
     * the title of what follows, whatever it is called. Optional, and absent on
     * almost everything: most primitives are an arrangement or a surface and play
     * no part a consumer asks about categorically.
     *
     * A role travels in the opposite direction from every other declaration here.
     * `interactive` and `frames` are read by the runtime and constrain what a tree
     * may do; a role is read by a *host* and constrains nothing. It is here rather
     * than in a host's own table for the same reason `description` is: the author
     * of the component is the one who knows, and a table maintained beside the
     * registry goes stale the day somebody registers a second heading.
     *
     * Typed rather than free — see `role.ts` for why the vocabulary is closed and
     * why it currently has one member. A host writing JavaScript can still hand
     * over a string the runtime does not know, so the registry refuses one rather
     * than letting a misspelling read as "declares no role".
     */
    readonly role?: P
…

Cut short here — the whole of it is longer than a page can usefully hold.

PrimitiveEntrytype

A definition with its prop type erased, which is what a heterogeneous registry can hold. definePrimitive is the only way to build one, so the erasure happens exactly once, in the one place that still has both the schema and the component type in hand and can prove they agree.

type PrimitiveEntry = {
    readonly type: string;
    readonly description: string;
    readonly slots: readonly string[];
    readonly component: LoomPrimitive;
    readonly declaredProps: readonly CataloguedProp[] | undefined;
    /**
     * The props whose accepted values can be listed, so the conformance probe can
     * ask the primitive what it does under each one rather than under whichever
     * shape it happens to take with no props at all. Empty for the
     * primitive whose rendering does not turn on a closed choice, which is most.
     */
    readonly choices: readonly ClosedChoice[];
    /** Declared strings, keys erased alongside the props type. Empty when none. */
    readonly text: PrimitiveText<string>;
    /** Absent for the ordinary primitive, which is not a target at all. */
    readonly interactive: InteractiveWhen | undefined;
    /** `false` for the ordinary primitive, which sends nothing anywhere. */
    readonly submits: boolean;
    /** Declared framable prop names, still raw: the registry is what checks them. */
    readonly frames: readonly string[];
    /** Declared behaviour names, still raw: the registry is what checks them. */
    readonly behaviours: readonly string[];
    /**
     * Which prop names which control, still raw: the registry is what checks that
     * both halves of each pair exist. Empty for the primitive that named none,
     * which is almost all of them.
     */
    readonly names: Readonly<Record<string, string>>;
    /**
     * The declared role, still raw: the registry is what checks it, for the same
     * reason it checks a behaviour name. `undefined` for the primitive that plays
     * no part a consumer asks about, which is most of them.
     */
    readonly role: string | undefined;
    /**
     * Declared copy prop names, still raw: the registry is what checks them.
     * `undefined` is carried through rather than defaulted to `[]`, because the
     * two mean different things here and all the way out to `copyIn`.
     */
    readonly copy: readonly string[] | undefined;
    /**
     * Declared binding names, still raw: the registry is what checks them.
     * `undefined` is carried through rather than defaulted to `[]`, because the
     * two mean different things here and all the way out to the render walk.
     */
    readonly reads: readonly BindingDeclaration[] | undefined;
    /**
     * The declared reading of this node's answers, or `undefined` where the author
     * has not said. Nothing to check at registration — there are no names in it
     * and no props it could name — so it is carried straight through.
     */
    readonly unshown: UnshownDeclaration | undefined;
    readonly validate: (props: JsonObject) => PropsVerdict;
};

definePrimitivefunction

Declares a primitive. The generic parameter is inferred from the schema, so the component is checked against what its own schema produces at the point of declaration rather than at the point of registration — an author who reads props.titel finds out here.

Shown in use on Quickstart — What just happened, Installation — The entry points, Primitives and the registry — Defining one, Where the content comes from — Your half: registering a source and What the Gate decides — The one list you should not write by hand

const definePrimitive: <TProps extends JsonObjectView, TText extends string = never, TBehaviour extends BehaviourName = never>(definition: PrimitiveDefinition<TProps, TText, TBehaviour>) => PrimitiveEntry

Interactivity

sdk/interactivity

How a library tells the gate which of its primitives a reader aims at.

interactiveTypesForfunction

A registry's interactivity declarations, in the shape a Gate policy takes.

Shown in use on What AI may change — The two lists you should not write by hand and What the Gate decides — The deployment decides what is consequential

const interactiveTypesFor: (registry: PrimitiveRegistry) => InteractiveTypes

Pairings

sdk/pairings

Every foreground-on-background a registry can put on a page, read off the components rather than listed by hand.

PairingBasistype

Whether one primitive set both ends of a pairing, or only the ink.

type PairingBasis = "painted" | "composed";

DerivedPairingtype

One foreground-on-background the library can put on a page, and what produces it.

type DerivedPairing = {
    readonly foreground: PaletteSlot;
    readonly background: PaletteSlot;
    readonly basis: PairingBasis;
    /** The primitives that produce it, so a failure names components rather than two slot ids. */
    readonly types: readonly PrimitiveType[];
};

ChildGroundtype

A ground a primitive puts its declared children and slots on.

type ChildGround = {
    readonly ground: PaletteSlot;
    readonly types: readonly PrimitiveType[];
};

UnprobedPropstype

The props a primitive declares that no probe configuration sets, and which therefore mark the edge of what this derivation can see.

type UnprobedProps = {
    readonly type: PrimitiveType;
    readonly props: readonly string[] | undefined;
};

RegistryPairingstype

What a registry's components paint, as the contrast bar needs to hear it.

type RegistryPairings = {
    readonly pairings: readonly DerivedPairing[];
    /** Inks a primitive sets without painting a ground of its own. */
    readonly floating: readonly PaletteSlot[];
    /** Every ground a primitive places children on, whether or not the ramp is held to it. */
    readonly childGrounds: readonly ChildGround[];
    /**
     * Grounds children land on that the text ramp is not held to. Not a failure:
     * `accent` is here because `loom.action` fills it and answers the ink itself.
     * It is reported because a *new* ground appearing here is the signal that
     * either a primitive is painting somewhere unconsidered or the declared list
     * has fallen behind the library — the same drift this module exists to catch,
     * one level up.
     */
    readonly groundsOutsideTheRamp: readonly ChildGround[];
    /** The probe could not answer — neither a pass nor a failure. */
    readonly notProbeable: readonly PrimitiveType[];
    /**
     * Where this derivation stops seeing, by primitive. Only primitives with at
     * least one such prop appear, so an empty list is a real claim: nothing in
     * this registry hides an element behind a prop the probe cannot set.
     *
     * Reported rather than fixed, because the alternative is a probe that invents
     * a price, a date and a URL — and a pairing derived from an invented value is
     * a fact about the invention. A list that says it is read off the components
     * should be able to say which part of them it read.
     */
    readonly unprobedProps: readonly UnprobedProps[];
};

registryPairingsfunction

Probes every primitive in a registry and assembles what they paint.

const registryPairings: (registry: PrimitiveRegistry, textGrounds: readonly PaletteSlot[]) => RegistryPairings

Registry

sdk/registry

The registry: the set of primitives a deployment can render, and the seam the renderer resolves against.

RegisteredPrimitivetype

type RegisteredPrimitive = {
    readonly type: PrimitiveType;
    readonly description: string;
    readonly slots: readonly SlotName[];
    readonly component: LoomPrimitive;
    readonly declaredProps: PrimitiveEntry["declaredProps"];
    /** The props the conformance probe can vary, from the declared schema. */
    readonly choices: PrimitiveEntry["choices"];
    /** Declared strings in the author's language, before any dictionary. */
    readonly text: PrimitiveText<string>;
    /** Whether it renders a target, and what makes it one. Absent for most. */
    readonly interactive: InteractiveWhen | undefined;
    /** Whether its author says it posts. `false` for most. */
    readonly submits: boolean;
    /** The props it puts in a frame. Empty for all but one primitive. */
    readonly frames: readonly string[];
    /** The controls it takes from the runtime's vocabulary. Empty for most. */
    readonly behaviours: readonly BehaviourName[];
    /**
     * The prop each of those controls is named by, where its author said so
     *. Empty for almost every primitive that takes a control at all: a
     * control's name is normally a string the primitive declared, and this is the
     * exception for the trigger whose words are the page's.
     */
    readonly names: ControlNameProps;
    /** What part it plays. `undefined` for most, which play none. */
    readonly role: PrimitiveRole | undefined;
    /**
     * The props a reader reads as words. `undefined` where the primitive
     * has not said, which is not the same answer as `[]` and is not rounded to
     * it.
     */
    readonly copy: readonly string[] | undefined;
    /**
     * The binding names it reads an answer under, each either a name or
     * the prop that gives one. `undefined` where the primitive has not
     * said, which is not the same answer as `[]` and is not rounded to it — the
     * walk reports under the first and stays quiet under the second.
     */
    readonly reads: readonly BindingDeclaration<BindingName>[] | undefined;
    /**
     * Its own reading of the answers it was given. `undefined` where the
     * author has not said, which here is the same answer as declaring one that
     * reports nothing — unlike `copy` and `reads`, because there is no emptiness
     * to distinguish: a reading is made per answer at render time, and a primitive
     * that has said nothing about its answers is reported on by nothing.
     */
    readonly unshown: UnshownDeclaration | undefined;
    readonly validate: (props: JsonObject) => PropsVerdict;
};

RegistryErrortype

type RegistryError = {
    readonly code: "invalid-primitive-type";
    readonly type: string;
} | {
    readonly code: "invalid-slot-name";
    readonly type: string;
    readonly slot: string;
} | {
    readonly code: "invalid-text-key";
    readonly type: string;
    readonly key: string;
} | {
    readonly code: "blank-text";
    readonly type: string;
    readonly key: string;
} | {
    readonly code: "undeclared-interactive-prop";
    readonly type: string;
    readonly prop: string;
} | {
    readonly code: "undeclared-frame-prop";
    readonly type: string;
    readonly prop: string;
} | {
    readonly code: "undeclared-copy-prop";
    readonly type: string;
    readonly prop: string;
} | {
    readonly code: "unknown-behaviour";
    readonly type: string;
    readonly behaviour: string;
} | {
    readonly code: "unnamed-behaviour";
    readonly type: string;
    readonly behaviour: string;
    readonly key: string;
} | {
    readonly code: "undeclared-interactive-behaviour";
    readonly type: string;
    readonly behaviour: string;
} | {
    readonly code: "unpaired-behaviour";
    readonly type: string;
    readonly behaviour: string;
    readonly requires: string;
} | {
    readonly code: "undeclared-named-behaviour";
    readonly type: string;
    readonly behaviour: string;
    readonly prop: string;
} | {
    readonly code: "undeclared-control-name-prop";
    readonly type: string;
    readonly behaviour: string;
    readonly prop: string;
} | {
    readonly code: "unknown-role";
    readonly type: string;
    readonly role: string;
} | {
    readonly code: "invalid-binding-name";
    readonly type: string;
    readonly name: string;
} | {
    readonly code: "undeclared-reads-prop";
    readonly type: string;
    readonly prop: string;
} | {
    readonly code: "duplicate-primitive-type";
    readonly type: string;
};

PrimitiveRegistrytype

type PrimitiveRegistry = PrimitiveResolver & PropsValidator & TextResolver & BehaviourResolver & FrameResolver & BindingReader & UnshownReader & CopyDeclarations & {
    /** In registration order, so a catalogue and an audit read predictably. */
    readonly primitives: readonly RegisteredPrimitive[];
    /**
     * The types that declared a given role, in registration order.
     *
     * Empty for a role nothing here declares, which is the honest answer and
     * not a failure: a deployment that registered no heading has no heading,
     * and a consumer deriving a page name from one should show what it shows
     * for a page that has none.
     *
     * Types rather than whole registrations, because every consumer this exists
     * for is matching nodes in a tree, and a node carries a type. A caller that
     * wants the registration has `primitives` and this is not in its way.
     */
    readonly typesWithRole: (role: PrimitiveRole) => readonly PrimitiveType[];
};

describeRegistryErrorfunction

Shown in use on Installation — TypeScript and Primitives and the registry — Registering

const describeRegistryError: (error: RegistryError) => string

createPrimitiveRegistryfunction

Builds a registry, or refuses. A duplicate type is refused rather than resolved last-wins: two registrations for one identifier means a tree's meaning depends on module evaluation order, which is not a thing anyone should have to debug from a rendered page.

Shown in use on Primitives and the registry — Registering and Starting from a band — Registering only what the bands need

const createPrimitiveRegistry: (entries: readonly PrimitiveEntry[]) => Result<PrimitiveRegistry, RegistryError>

Selection

sdk/selection

Taking part of a primitive library rather than all of it.

SelectionErrortype

Every name the library does not carry, not just the first.

type SelectionError = {
    readonly code: "unregistered-types";
    readonly types: readonly string[];
};

describeSelectionErrorfunction

const describeSelectionError: (error: SelectionError) => string

selectPrimitivesfunction

The entries a deployment chose, in the library's own order.

Shown in use on Primitives and the registry — Start from the starter library and Starting from a band — Registering only what the bands need

const selectPrimitives: (entries: readonly PrimitiveEntry[], types: readonly string[]) => Result<readonly PrimitiveEntry[], SelectionError>

Text

sdk/text

Translating the strings primitives own.

textMessageKeyfunction

A dictionary addresses one string as ${primitive type}.${key} — the same flat key a translation file uses, so a dictionary can be a JSON file a translator edits and a diff between two languages is readable.

const textMessageKey: (type: string, key: string) => string

textDictionarySchemaschema

const textDictionarySchema: z.ZodObject<…>

TextDictionarytype

type TextDictionary = z.infer<typeof textDictionarySchema>;

CataloguedTexttype

One declared string, as a translator needs to see it.

type CataloguedText = {
    readonly type: PrimitiveType;
    readonly key: string;
    /** What the primitive's author wrote, in their own language. */
    readonly source: string;
};

textCataloguefunction

Every string the registered library owns, in registration order and key-sorted within each primitive — the extraction a translation file is written from.

const textCatalogue: (registry: PrimitiveRegistry) => readonly CataloguedText[]

textResolverForfunction

A resolver over the registry's declarations with a dictionary laid on top.

const textResolverFor: (registry: PrimitiveRegistry, dictionary: TextDictionary) => TextResolver

TextCoveragetype

type TextCoverage = {
    readonly locale: string;
    /** How many declared strings this dictionary answers. */
    readonly translated: number;
    /** Declared strings it does not answer, which will render in the source language. */
    readonly untranslated: readonly CataloguedText[];
    /**
     * Dictionary keys naming no declared string — a primitive that was renamed or
     * removed, or a typo. Enumerated rather than counted because each one is a
     * line somebody has to delete, and because a whole primitive's worth of them
     * appearing at once is how a rename announces itself.
     */
    readonly unknown: readonly string[];
};

textCoveragefunction

What a dictionary covers, and where it has drifted from the library.

const textCoverage: (registry: PrimitiveRegistry, dictionary: TextDictionary) => TextCoverage

Vocabulary

sdk/vocabulary

How a library tells the Gate which primitives exist at all.

registeredTypesForfunction

Every type a registry can draw, in the shape a Gate policy takes.

Shown in use on What AI may change — The two lists you should not write by hand

const registeredTypesFor: (registry: PrimitiveRegistry) => readonly PrimitiveType[]

propsVocabularyForfunction

What a registry's primitives accept, in the shape the write path takes.

const propsVocabularyFor: (registry: PrimitiveRegistry) => PropsVocabulary