Specs
A spec is the JSON object that describes your entire UI tree. It tells @threadplane/render what to render, how to wire state, and when to show or hide an element.
#Spec Format
Every spec has three top-level properties:
| Property | Type | Description |
|---|---|---|
root | string | The key in elements that is the entry point for rendering |
elements | Record<string, UIElement> | A flat map of all element definitions, keyed by unique identifiers |
state | object | (Optional) Initial state used to create an internal state store when no external store is provided |
Elements are stored in a flat map rather than a nested tree. Parent-child relationships are expressed through the children property, which contains keys pointing to other elements in the map. This keeps lookups fast and makes specs easy to generate from server-side tools.
#UIElement Properties
Each element in the elements map is a UIElement object:
| Property | Type | Description |
|---|---|---|
type | string | The name used to look up the component in the registry |
props | Record<string, unknown> | Input values for the component. Can be static or dynamic expressions |
children | string[] | Keys of child elements in the elements map |
visible | unknown | Visibility condition -- evaluated by @json-render/core |
repeat | { statePath: string } | Repeat configuration for rendering lists |
on | Record<string, EventBinding> | Event handler bindings |
#Prop Expressions
Props can be static values or dynamic expressions resolved by @json-render/core:
#$state -- Read from State
Reads a value from the state store at the given JSON Pointer path:
#$bindState -- Two-Way Binding
Like $state, but also populates the bindings input so the component can write back to the store:
#$item -- Repeat Item Value
Inside a repeat loop, $item resolves to the current array item. Pass an empty string to get the whole item, or a path to access a nested property:
#$index -- Repeat Index
Inside a repeat loop, $index resolves to the current zero-based iteration index:
#$computed -- Computed Function
Calls a registered computed function with the given arguments:
The $computed value names a function you register in a functions map. Each function is a ComputedFunction from @json-render/core -- (args: Record<string, unknown>) => unknown. The args object is resolved first (so { $state: '/name' } becomes the current value at /name), then passed to your function:
Wire the map through the [functions] input on <render-spec>:
Or register it globally via provideRender() so every <render-spec> resolves it:
With either wiring, the $computed expression above resolves label to the uppercased value of /name. The input takes priority over the provideRender() config when both are present.
#Children
The children property is an array of element keys that reference other entries in the elements map:
The rendered ContainerComponent receives childKeys: ['heading', 'body'] and the full spec, enabling it to recursively render its children using RenderElementComponent.
#Deeply Nested Trees
Because children reference keys in the same flat map, you can build arbitrarily deep trees:
#Conditional Rendering
The visible property controls whether an element is rendered. When the condition evaluates to a falsy value, the element and all its children are excluded from the DOM.
#Static Visibility
#State-Driven Visibility
When /showMessage is true in the state store, the element renders. When it is false, it is removed.
#Default Visibility
When visible is omitted or undefined, the element is visible by default.
#Repeat Loops
The repeat property renders an element once for each item in a state array:
For each item in the array at /todos, the library:
- Creates a
RepeatScopewith theitem,index, andbasePath(e.g.,/todos/0) - Provides the scope via a child
Injectorusing theREPEAT_SCOPEtoken - Resolves props using the repeat scope context --
$itemand$indexexpressions are evaluated per-iteration - Renders the component with the resolved inputs
When an element has repeat, visibility evaluation is handled differently -- all items are rendered. To conditionally render individual repeat items, use conditional logic within the rendered component itself.
#Complete Example
Let's put it together. Here's a spec that combines children, state expressions, conditional rendering, and repeat loops:
#Next Steps
How components receive props and render children
Event handler bindings and action dispatch
Managing reactive state with signalStateStore()
Full API reference for the entry-point component
How an agent streams a spec that chat renders inline