Builder Spec Model

The authoritative source is src/lib/builder/spec.ts. The following excerpt is copied verbatim from that file; do not edit this page to evolve the types before changing the source contract.

export const SPEC_VERSION = 1 as const;
 
export type NodeId = string;
 
/** A single node on the builder canvas. */
export type BuilderNode = {
	id: NodeId;
	/** key into the block registry (src/lib/builder/blocks.ts) */
	type: string;
	props: Record<string, unknown>;
	/** present only for container blocks */
	children?: BuilderNode[];
};
 
export type BuilderSpec = {
	version: typeof SPEC_VERSION;
	title: string;
	root: BuilderNode[];
};
 
/** Mutation vocabulary shared by the UI, the relay API and the persisted log. */
export type BuilderOp =
	| { op: 'set-spec'; spec: BuilderSpec }
	| { op: 'set-title'; title: string }
	| { op: 'add'; node: Omit<BuilderNode, 'id'> & { id?: NodeId }; parentId?: NodeId | null; index?: number }
	| { op: 'update'; id: NodeId; props: Record<string, unknown> }
	| { op: 'remove'; id: NodeId }
	| { op: 'move'; id: NodeId; parentId?: NodeId | null; index: number }
	| { op: 'select'; id: NodeId | null }
	| { op: 'clear' };

Model semantics

  • version is the format discriminator and currently has the literal value 1.
  • root is a forest, so a screen can contain several top-level nodes.
  • type is a lookup key, not a Svelte component reference. This keeps the document serialisable.
  • children is present only when the block is a container.
  • props is open at the transport level. The selected block’s metadata supplies the editable field contract.
  • set-spec and clear operate at document scale; the remaining mutation cases address title, nodes, placement, props, or selection.
  • add may omit the id so the store can allocate one, and may address either a container or the root through parentId.
  • move requires the destination index and optionally addresses a container.

The same source file defines chat and relay state:

export type ChatRole = 'user' | 'agent' | 'system';
 
export type ChatMessage = {
	id: string;
	role: ChatRole;
	text: string;
	/** epoch ms */
	at: number;
	/** ops the agent applied alongside this message, for the transcript */
	ops?: BuilderOp[];
};
 
/** Everything the relay API hands an agent in one read. */
export type BuilderState = {
	version: typeof SPEC_VERSION;
	spec: BuilderSpec;
	selectedId: NodeId | null;
	messages: ChatMessage[];
	/** user messages with no agent reply after them */
	pending: ChatMessage[];
};

BuilderState.pending is derived state for relay consumption; the persisted session contract is { spec, messages }, not the whole BuilderState object.


loca.zone · bits.loca.zone · Svelte