Compute a slot's value
Derive a slot's value from the property's facts with a function you write, instead of copying a fact or having AI write it.
Derive a slot’s value from the property’s facts with a function you write, rather than copying a single fact or handing the slot to AI.
Prerequisites
Section titled “Prerequisites”- A section to add the slot to — Install scaffolds one.
- Its
fixtures.tsbuilt from a scenario, so the facts the function reads resolve in preview — Pick a fixture scenario.
A slot’s value arrives one of three ways, and it takes exactly one of them. source: fact.<container>.<field> copies a known fact verbatim (bind a slot to a property
fact). A fill: on the slot hands it to the model
(have AI write a slot’s content). compute() is the third:
a function of yours over the same facts, run at derive time, deterministic and
never AI.
Add number and compute to the section’s existing kit import — the scaffolded
file takes only defineSchema, text and textBlock, so both are
missing until you do.
It goes where a fact binding would — under the same source:
// section.ts — a value derived from the facts, in `defineSchema`'s `slots`photo_count: number({ label: "Photos", source: compute((f) => f.photos.length),}),f is the derive-time facts view. It is the same vocabulary a binding names:
f.property.* carries every fact.property.* fact, f.unit and f.contact are
the primary unit and contact card themselves, and beside them sit the things no
slot can bind — the property’s photo, video, tour, floor-plan and document pools,
the notes distilled from those documents, its always-on narrative, and the
ambient year. Every fact the platform holds is reachable by construction, and
every leaf is typed, so a missing unit is a null you have to handle rather than
a crash in production. The fact
vocabulary lists every
entry and the shape it reads as.
The return type is checked against the slot it binds — a number() admits a
number or null, since a number slot’s blank is null. An async
callback fails to typecheck rather than silently running asynchronously at derive
time, and the function must stay pure for the same reason a renderer must: the
publisher and the editor have to produce the same output from the same facts. Reach
for the ambient year rather than the clock.
There is no second name to keep in sync and no fill decision to write. defineSchema
extracts the function keyed by the slot’s own name, so the slot key is the
handle, and a computed slot is source-bound exactly as a fact-bound one is — it takes a
source:, so giving it a fill: as well does not compile.
A computed slot is still an ordinary slot on the page, and still owes the editor a marker:
// Renderer.tsx — a computed slot renders like any other<Slot.Number slot={slot.photo_count} as="span" format={(n) => `${n} photos`} />format is a callback this markup owns — it shapes how the number reads at this one
call site, and it is the only formatting seam there is: nothing about presentation is
declared on the slot, and the kit ships no catalog of format names to reach for. It
runs only on a filled value, and it withholds the text leaf, so an edit to a formatted
slot round-trips through a re-render rather than typing live — see values and
emptiness.
Fixtures come free with it. A computed slot resolves from the scenario the same way
a fact-bound slot does, tagged computed where the provenance report prints it, so
there is no fixture value to author — and authoring one is a compile error, not a
value that quietly loses.
Complete diff
Section titled “Complete diff”Two additions — one in section.ts, one in Renderer.tsx — plus number and
compute on the section’s existing kit import. Nothing else in the section folder
moves: a computed slot needs no fill entry and no fixture value.
// section.ts — in `defineSchema`'s `slots`photo_count: number({ label: "Photos", source: compute((f) => f.photos.length),}),// Renderer.tsx — a computed slot renders like any other<Slot.Number slot={slot.photo_count} as="span" format={(n) => `${n} photos`} />Verify
Section titled “Verify”Verify with the author loop.
Rules that can fire
Section titled “Rules that can fire”no-nondeterminism— the function reads the clock or a random source.no-node-builtins-in-contract— the file imports a Node built-in to do the work; the derive-time bundle resolves no external specifier, so nothing there is reachable.missing-slot-marker— the new slot never reaches the rendered DOM with its marker.sidebar-order— the slot is declared in one position in the schema and rendered in another.
See also
Section titled “See also”- Computing a slot with
compute()— the full contract, including how the extracted function stays out of the emitted schema. - The fact vocabulary —
every entry
fcarries, the shape each reads as, and the members of every pool row. - Bind slot to fact — the direct binding, for when one fact is already the value.
- AI writes slot content — the third source, for when the value is prose nobody can derive.