UI Builder

The builder keeps the design document independent of rendered Svelte components. A BuilderSpec is plain serialisable data; a block registry maps each node type to metadata and an implementation; a reactive store owns mutation history and messages; and a recursive renderer turns the node forest into the preview.

The relay protocol remains version 1 with the same ten public members. Its only relay-visible changes in this refinement are additive block metadata and the larger 55-entry catalog; all 22 original block keys remain registered.

Source boundaries

BoundaryRepository surfaceContract
Data modelsrc/lib/builder/spec.tsversioned node tree, chat and activity state, serialisable block metadata, and mutation vocabulary
Internal block contractsrc/lib/builder/block-def.tsBlockProps plus internal BlockDef, including optional createDefaults() factories that never cross the relay boundary
Prop normalisationsrc/lib/builder/props.tssingle prop-normalisation boundary for preview and export, including the one shared unsafe-URL policy used by LinkButton, LinkPreview, and Navigation Menu
Date/time valuessrc/lib/builder/date-values.tsISO-only persisted date/time conversion boundary between BuilderSpec strings and @internationalized/date values
Contributed definitionssrc/lib/builder/blocks/defs/*.tsgrouped disclosure, overlay, menu, form, date, and utility definitions composed by the registry
Block registrysrc/lib/builder/blocks.ts55 addable block types = 46 showcase mappings + 9 builder-only definitions; composition validation, fresh defaults, registry ordering, and detached relay metadata
Statesrc/lib/builder/store.svelte.tsapply, select, undo, redo, persistence, messages, activity, and export
Renderersrc/lib/builder/Render.svelterecursive node rendering, editable mouse/keyboard selection, and unknown-type fallback
Field editorsrc/lib/builder-ui/FieldEditor.svelteall eleven field kinds, with ISO temporal values, plain-object record arrays, and filtered node references
Agent drawersrc/lib/builder-ui/ChatPanel.sveltealways-mounted Conversation and Activity panels, draft/send behavior, status display, and pinned-scroll-aware logs
Layers drawersrc/lib/builder-ui/LayersPanel.sveltespec-derived flattened tree, local disclosures, selection, and sibling reordering through existing operations
Relaysrc/lib/builder/relay.tsunchanged version 1 window.bitsBuilder surface, cloned state boundaries, and lifecycle markers
Export coordinatorsrc/lib/builder/export.tspreview-aligned Svelte projection, legacy dispatch, and grouped emitter dispatch
Source emitterssrc/lib/builder/emit/context.ts and src/lib/builder/emit/*.tsSourceContext spec/node lookup, Bits UI/date/icon import sets, top-level declarations, collision-free identifier allocation, and per-group source emitters
Persistencesrc/routes/api/builder/session/+server.tsvalidated GET and bounded POST of { spec, messages }
Workbenchsrc/routes/builder and src/routes/relaycanvas-first editor surface and relay-oriented workbench

The model contract is detailed in Spec model. The concrete block shape is in Block registry. Browser-agent integration is in Relay protocol.

Canvas-first IDE

/builder uses canvas-first adaptive drawers. A 3rem activity rail switches one left drawer between Agent and Layers; the flexible canvas remains the only growing middle region; and the right drawer contains the complete Inspector plus the editable JSON contract. A full-width, two-row bottom dock spans beneath the workbench and carries all 55 add controls: 46 showcase mappings in showcase order, then nine builder-only definitions.

Both drawers start closed. Their single-mounted DOM nodes collapse through size or visibility classes plus inert, rather than component teardown. ChatPanel and LayersPanel also remain mounted while the active left view changes, and the Agent Conversation and Activity panels remain mounted behind their tab state. Chat drafts, tab and scroll positions, the JSON draft, and layer disclosures therefore survive drawer and view switches.

Drawer visibility, the active left view, and layer disclosures are ephemeral UI state. They never enter localStorage, BuilderSpec, undo history, or relay state.

Editable JSON contract

The right drawer’s JSON tab edits the complete current specification. Apply parses the draft, then submits the existing set-spec operation through BuilderStore.apply; a successful replacement therefore uses the normal validation path, clears selection, enters undo history, schedules persistence, and records observable activity.

A parse or schema failure retains the draft and leaves the specification, history, and selection unchanged. Once edited, the draft survives tab and drawer switches. If the canvas changes after editing begins, the tab reports that the draft is stale and that Apply will replace the current specification; Reset discards it and loads current state.

Layers contract

LayersPanel.svelte derives a flattened presentation from builder.spec.root on every update; it does not cache a second document tree. Each row carries its node, parent id, pre-removal sibling index and count, and depth. Row activation submits the existing select operation.

Sibling reordering submits the existing move operation. For a row at index i, Move up sends i - 1; Move down sends i + 2 because the store computes the destination against the pre-removal sibling list. Disclosure state remains component-local and resets on specification replacement.

State flow

  1. Scalar, structured, temporal, editable-JSON, Layers, or relay controls submit one or more BuilderOp values.
  2. BuilderStore.apply validates and applies the batch, returning { ok, errors }.
  3. Valid operations apply in order. An error does not roll back earlier mutations in the batch. If at least one operation changes the spec, one pre-batch spec/selection snapshot enters undo history; a selection-only batch does not.
  4. New nodes receive deep-cloned defaults from createBlockProps(), including materialised createDefaults() values.
  5. Render.svelte recursively resolves each type through the block registry.
  6. The UI and window.bitsBuilder share the same store rather than mutate parallel documents.
  7. save() and load() exchange the spec and messages through the builder session endpoint.
  8. exportCode() creates a SourceContext, dispatches legacy and per-group emitters, collects Bits UI, date, and icon imports plus top-level declarations, allocates collision-free identifiers, and renders the finished Svelte source.

Trust boundary

The relay is an in-browser integration point for an OMP agent operating through the browser relay. The application dependency set contains no server-side LLM SDK, and the relay contract does not accept or store an LLM provider key. This is a transport choice, not a claim that arbitrary browser scripts are trusted: an agent must still inspect applyOps failures before reporting success.

Verification state

  • Current source gate: npm run check completed with 0 svelte-check errors and 0 warnings.
  • Dated relay, persistence, validation, history, canvas, export, responsive, and deployment receipts are recorded in private page (sign-in required).

loca.zone · bits.loca.zone · Svelte · Bits UI

3 items under this folder.