Installation
Loom is two packages. @jam-overture/loom is the framework — the tree, the
change model and the Gate — with several entry points behind it.
@jam-overture/loom-primitives is the starter library: the
one hundred and six primitives every example on this site
is built from.
Install both, and a React renderer to mount what they produce.
pnpm add @jam-overture/loom @jam-overture/loom-primitives react react-dom
You can skip the second one. The framework has no opinion about which primitives exist — it is a vocabulary you register, and the starter library is one somebody already wrote. Every page here uses it because a book about a language needs words in it.
What you actually need
zod is the only hard dependency — every primitive declares its props as a Zod
schema, and validation happens at the seam where a tree becomes an element.
Everything else is a peer and optional, because the runtime is deliberately usable without any of it:
| Peer | You need it when |
|---|---|
react | you are rendering. The tree itself has no opinion about React. |
@jam-overture/loom-primitives | you want the starter library rather than a vocabulary of your own. |
drizzle-orm + postgres | you are persisting trees rather than holding them in memory. |
@anthropic-ai/sdk | you want a model to interpret a sentence into a proposal. |
A deployment with none of the three still builds trees, applies deltas and gates proposals. That is not an accident of packaging — the model is a seam, not the centre.
The entry points
| Import | What is behind it | For |
|---|---|---|
| @jam-overture/loom | The tree, the delta, the ids, the builders, the Gate and the pipeline. Start here. | any app |
| @jam-overture/loom/react | Rendering: a tree to React elements, theme mounting, addressing, render diagnostics. | any app |
| @jam-overture/loom-primitives | The starter library — the primitives every example on this site is built from. Its own package, installed beside the framework. | any app |
| @jam-overture/loom-primitives/compositions | The starter library's fifty-seven bands under their own names, for assembling a page without going through the catalogue. | any app |
| @jam-overture/loom/sdk | Defining and registering primitives of your own, and the catalogue a model is shown. | hosts |
| @jam-overture/loom/store | Persisting trees and revisions: the store contract, and an in-memory implementation. | hosts |
| @jam-overture/loom/postgres | The Postgres store. Same contract, a database behind it. | hosts |
| @jam-overture/loom/write | The write path: proposing, holding, confirming and applying a change. | hosts |
| @jam-overture/loom/signals | Reader signals — what a published page reports about how it is read, and the parser a receiver checks them with. | hosts |
| @jam-overture/loom/signals/broadcast | The reader-signal broadcaster alone, for a browser bundle — about 5 KB, with no schema library. | hosts |
| @jam-overture/loom/signals/postgres | The Postgres buffer and counters, for a deployment that keeps what its readers did. | hosts |
| @jam-overture/loom/telemetry | The journal — proposal, provenance, disposition and outcome, recorded as they happen. | hosts |
| @jam-overture/loom/telemetry/postgres | The Postgres journal, for a deployment that keeps its telemetry. | hosts |
| @jam-overture/loom/anthropic | The model seam: an interpreter that turns a sentence into a proposal. Optional. | hosts |
| @jam-overture/loom/cli | Scaffolding and inspection from a terminal. | tooling |
| @jam-overture/loom/testing | Fixtures and doubles: the sample trees, clocks and scripted interpreters Loom tests itself with. | tooling |
| @jam-overture/loom/testing/contracts | The suites that tell you whether your own store, hold store or journal keeps its promises. | tooling |
The split is the important part. @jam-overture/loom is the tree and the change
model and knows nothing about React, storage or any model provider. Each of the
others is one of those things, behind its own door, so that importing the core
never drags a database driver into your bundle.
The last row is the one that is not a door but a second package, and the
reason is the same split one step out: the framework does not depend on the
library, so the library does not ship inside it. It takes
@jam-overture/loom as a peer, which is a package's way of saying there must
be exactly one copy of this in your tree — two would be two registries that
reject each other's entries.
The table above is the map you need to write your first import. The API reference lists the same doors with the number of names behind each, and says the thing this table cannot: they do not nest, so none of them has the whole package behind it.
TypeScript
The package ships its own types and is written for strict mode. Two settings
are worth turning on if they are not already: exactOptionalPropertyTypes and
noUncheckedIndexedAccess. The runtime's own source uses both, and its types
distinguish "absent" from "present and undefined" in several places where the
difference matters.
Nothing throws across a seam. Functions that can fail return a Result:
import { createStarterPrimitiveRegistry } from "@jam-overture/loom-primitives"
import { describeRegistryError } from "@jam-overture/loom/sdk"
const built = createStarterPrimitiveRegistry()
if (!built.ok) {
throw new Error(`the registry was refused — ${describeRegistryError(built.error)}`)
}
const registry = built.value
That shape repeats everywhere: check ok, read value or error. Rendering is
the one place that never fails at all — it always returns an element, and says
what it could not honour in a list of diagnostics beside it.