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 gives Render.svelte the component and complete internal definition.
  • createBlockProps(type) merges defaults with a fresh createDefaults() 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 and window.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.ts
  • src/lib/builder/blocks/defs/overlays.ts
  • src/lib/builder/blocks/defs/menus.ts
  • src/lib/builder/blocks/defs/forms.ts
  • src/lib/builder/blocks/defs/dates.ts
  • src/lib/builder/blocks/defs/utilities.ts

Composition fixes the exposed order:

  1. the 46 showcase-mapped definitions in src/lib/showcase/registry.ts order
  2. the nine definitions without showcaseSlug

Module initialisation enforces six registry and inspector invariants:

  1. A duplicate type key across contributions throws with both owning module names.
  2. Every showcaseSlug must exist in the showcase registry and may be claimed by only one block definition.
  3. Every showcase registry slug must have exactly one block definition.
  4. Every definition must have unique fields[].key values.
  5. Object.keys(defaults) must equal the fields[].key set: no missing default and no default without an inspector field.
  6. createDefaults() may return only keys declared by fields.

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:

KindPersisted storage
textstring
textareamultiline string
numberfinite number; min, max, and step configure the native editor
booleanboolean
selectoption string selected from options
itemsnormalized newline-delimited string; lines are trimmed and empty lines removed
dateYYYY-MM-DD string or ''
timeHH:mm string or ''
datetime-localYYYY-MM-DDTHH:mm string or ''
recordsordered array of plain objects described by columns; text-like columns store strings and boolean columns store booleans
node-refnode 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