Builder Block Registry
A builder node serialises a string type, never a Svelte component. The registry resolves that key to agent-readable metadata and the component used by the recursive renderer.
Source metadata contract
The metadata types below are copied verbatim from src/lib/builder/spec.ts:
/** One editable column inside a `records` field. */
export type BlockRecordColumn = {
key: string;
label: string;
kind: 'text' | 'textarea' | 'boolean' | 'select' | 'items';
/** for kind === 'select' */
options?: string[];
};
/** Serialisable block metadata exposed to agents via `window.bitsBuilder.blocks()`. */
export type BlockField = {
key: string;
label: string;
kind:
| 'text'
| 'textarea'
| 'number'
| 'boolean'
| 'select'
| 'items'
| 'date'
| 'time'
| 'datetime-local'
| 'records'
| 'node-ref';
/** for kind === 'select' */
options?: string[];
min?: number;
max?: number;
step?: number;
/** inline editor help rendered under the control */
description?: string;
/** for kind === 'records' */
columns?: BlockRecordColumn[];
/** for kind === 'node-ref': block types this reference may point at */
accepts?: string[];
};
export type BlockMeta = {
type: string;
label: string;
category: string;
container: boolean;
/** bits-ui namespace this block is built from, when applicable */
bits?: string;
/** showcase registry slug this block mirrors, when it mirrors one */
showcaseSlug?: string;
description: string;
defaults: Record<string, unknown>;
fields: BlockField[];
};Rendered definition and creation boundaries
BlockDef now lives in src/lib/builder/block-def.ts. Its current contract is:
export type BlockProps = { node: BuilderNode; editable?: boolean; children?: Snippet };
/**
* Internal registry entry. `component` and `createDefaults` are stripped before the
* definition is exposed through `blockMetas()` / `window.bitsBuilder.blocks()`.
*/
export type BlockDef = BlockMeta & {
component: Component<BlockProps>;
/**
* Optional factory for values that cannot be a frozen literal (today-relative
* dates). Its keys are merged over `defaults` when a node is created.
*/
createDefaults?: () => Record<string, unknown>;
};src/lib/builder/blocks.ts exposes three boundaries:
blocks[type]is application-only. It givesRender.sveltethe component and complete internal definition.createBlockProps(type)mergesdefaultswith a freshcreateDefaults()result, then deep-clones the merged object. New nodes therefore receive independent props, including today-relative date values materialised when the node is added.blockMetas()returns detached relay metadata for the palette andwindow.bitsBuilder.blocks()without exposing application-only registry objects.
BlockMeta.type is the BuilderNode.type lookup key. container controls whether a node may carry children; fields describe every inspector control; optional bits and showcaseSlug identify the corresponding Bits UI namespace and showcase entry.
Composition, order and boot invariants
Definitions come from the 22-entry legacyBlocks object in src/lib/builder/blocks.ts plus six modules:
src/lib/builder/blocks/defs/disclosure.tssrc/lib/builder/blocks/defs/overlays.tssrc/lib/builder/blocks/defs/menus.tssrc/lib/builder/blocks/defs/forms.tssrc/lib/builder/blocks/defs/dates.tssrc/lib/builder/blocks/defs/utilities.ts
Composition fixes the exposed order:
- the 46 showcase-mapped definitions in
src/lib/showcase/registry.tsorder - the nine definitions without
showcaseSlug
Module initialisation enforces six registry and inspector invariants:
- A duplicate type key across contributions throws with both owning module names.
- Every
showcaseSlugmust exist in the showcase registry and may be claimed by only one block definition. - Every showcase registry slug must have exactly one block definition.
- Every definition must have unique
fields[].keyvalues. Object.keys(defaults)must equal thefields[].keyset: no missing default and no default without an inspector field.createDefaults()may return only keys declared byfields.
The validator also requires each registry key to equal its definition’s type; final map construction rejects any remaining duplicate. These checks run during module initialisation and throw at boot. That makes “all configuration is in the inspector” executable rather than documentary.
Detached relay metadata
blockMetas() removes component and createDefaults, overlays the fresh createDefaults() values onto defaults, then applies structuredClone to the complete metadata object. A caller can mutate a nested record default, a columns array, or a nested column options array without reaching registry or inspector metadata.
Field behavior and storage
src/lib/builder-ui/FieldEditor.svelte renders all eleven kinds:
| Kind | Persisted storage |
|---|---|
text | string |
textarea | multiline string |
number | finite number; min, max, and step configure the native editor |
boolean | boolean |
select | option string selected from options |
items | normalized newline-delimited string; lines are trimmed and empty lines removed |
date | YYYY-MM-DD string or '' |
time | HH:mm string or '' |
datetime-local | YYYY-MM-DDTHH:mm string or '' |
records | ordered array of plain objects described by columns; text-like columns store strings and boolean columns store booleans |
node-ref | node id string or ''; available targets are constrained to block types in accepts |
ISO strings are the only persisted date/time representation. src/lib/builder/date-values.ts is the single conversion boundary between those strings and @internationalized/date values; malformed author input falls back rather than throwing during SSR. description renders help below a control. src/lib/builder/props.ts is the shared framework-free normalization boundary used by preview renderers and generated source.
These metadata shapes do not permit arbitrary runtime values. BuilderStore.apply remains the mutation boundary and returns validation errors to UI and relay callers.
Concrete enumeration — source-derived
The current source-derived count is 55 addable block types = 46 showcase mappings + 9 builder-only definitions. It remains pending the dated runtime receipt recorded in private page (sign-in required).
All 22 original type keys remain registered, so persisted version-1 documents remain valid. divider maps to showcase slug separator and tooltip-button maps to tooltip; these are metadata mappings, not renames.
blocks.ts is the authority for concrete definitions. This page deliberately does not hand-enumerate their names. Agents read the source-derived catalog from window.bitsBuilder.blocks() after relay installation, preventing a second list from drifting when blocks or fields change.
Historical note: the production relay returned 22 entries on 2026-08-30. That dated observation remains true for the deployment verified that day; it is not the current registry count.
loca.zone · bits.loca.zone · Svelte · Bits UI