Every part of a Mini App, in tables. For what these things are, read Mini Apps; for how to put them together, read Building Mini Apps.

All of it is defined in packages/app-runtime, shared by the browser, the nodetool app debug harness, and the test suites — so all three behave the same.

Widgets

A widget is one thing on the app screen. Every widget can be wired to a value (binding), shown only under a condition (visibleWhen), greyed out under a condition (disabledWhen), and reformatted before display (format), on top of its own settings.

Widgets that show something

Widget Shows
Heading Fixed text at H1–H3.
Text Fixed or formatted text.
Markdown Rendered Markdown. The right choice for streamed prose.
Image An image. Fit contain or cover, fixed height, placeholder.
Audio An audio file, with a player.
Video A video, with a player, max height, placeholder.
Sketch A sketch document, layers composited. Max height, optional canvas size.
Timeline A timeline sequence, with its tracks and clips. Max height, optional metadata.
JSON Structured data, formatted.
Table A list, as rows. Max height, placeholder.
Output A value whose type varies; picks a display based on what arrives.
Progress How far along the run is.

Sketch and Timeline take a document reference — {type: "sketch", id} or {type: "timeline", id} — which is what the nodes that produce them emit. They also accept the document inline, so a node that returns the payload rather than a saved id renders too. Binding one to Image or Video instead shows nothing: a reference is not a media URL. On mobile these summarize the document (canvas size and layer count; duration, tracks, and clips) rather than drawing it.

Widgets the user changes

“Commits” means the control reports a final value when you let go or click away. That’s what makes on-release pacing possible — see Triggers and pacing.

Widget Takes Commits
Workflow Input Whatever the bound Input node declares, rendered to match. no
Text Input Text. Single-line or multiline. yes
Number Input A number. Min, max, step. yes
Slider A number. Min, max, step. yes
Switch On or off. no
Select One option from a fixed list. no
Image Input An image. no
Audio Input An audio file. no
Video Input A video. no
Document Input A document. no
Color Input A color. no
Resource Picker Picks which document a resource points at. no
Resource Gallery The same, from a grid of tiles. Tile size. no
Storyboard Scenes Edits the bound storyboard directly. Fires no event. no

Chat and AI

Widget Does
Chat Thread Shows a conversation. binding is the conversation (an app variable, or an output that emits messages), streamBinding the reply arriving from the current run. Max height, placeholder.
Chat Composer Writes the next message and runs the operation. binding is the workflow input it sends to, historyBinding the conversation variable it appends to. Sends the message text, a message object, or the whole conversation. Optional image attachments.
Model Select Picks a model and writes its reference. Kind: language, image, video, speech, transcription, or embedding.

A conversation is a list of {role, content} objects. Anything else a binding holds — a streamed string, a media ref, a list of results — reads as one assistant turn, so a thread bound straight to an LLM output still renders.

The thread does the bookkeeping a second turn needs: when a run settles, it folds the streamed reply into the conversation variable. An output slot is cleared at the start of every run, so a reply left there alone would disappear the moment the user sends again. This only happens when binding is a variable and streamBinding is an output.

Buttons and layout

Widget Does
Button Runs its click action. Style contained/outlined/text, color primary/secondary/warning.
Panel A titled box holding other widgets.
Columns Two side-by-side slots, left and right.
Divider A horizontal line.

Bindings

A binding is the string that says what a widget is wired to. You usually pick these from a menu rather than typing them. They point at node ids, so renaming a node in the graph editor never breaks an app.

Binding Points at
op:<opId>/in:<nodeId> An input of one of the app’s workflows.
op:<opId>/out:<nodeId> An output of one of the app’s workflows.
op:<opId>/prop:<nodeId>#<prop> A setting on a node, driven by a widget.
op:<opId>/exec#<field> Run status: running, progress, error, activity.
var:<variableId> A value the app remembers.
view:<componentId>#<prop> State belonging to one widget. Never saved.
node:<nodeId>#<prop> Old form of a node setting, resolved against the default operation.
<name> Old form: a bare node name, looked up in the live graph.

A bare name is looked up according to how the widget uses it: a widget the user types into looks for an Input node, a widget that displays looks for an Output node and then a variable, and a condition or format token tries outputs, then inputs, then variables. A name that matches nothing is an error, not a silent no-op.

Actions

A widget event runs one action. Actions are settings the runtime carries out — there’s nowhere to write code.

Action Settings Effect
run operationId Runs that workflow, following its concurrency rule.
cancel operationId, optional invocationId Cancels that operation’s runs, or one specific run.
setVariable variableId, and a value or the widget’s own value Writes a variable.
toggleVariable variableId Flips an on/off variable.
resourceCommand resourceBindingId, command read, create, update, delete, or upload on a resource.
openResource resourceBindingId Opens that document in its editor.

The visual editor offers Run workflow, Cancel run, Set variable, and Toggle variable. The resource actions are set through the agent or by editing the document.

Triggers and pacing

Events fire on click (buttons) or change (everything the user edits). A change event also has a pace, which decides how often it fires while the user is still editing:

Pace Fires
live On every change.
release Once, when the control settles — the slider is let go, the field loses focus. Only offered on controls that commit.
debounce Once, after a short pause in editing.

Conditions

visibleWhen and disabledWhen each hold a binding, an operator, and a value to compare against. A condition whose binding points at nothing is treated as no condition, so broken wiring never silently hides a widget.

Operator Editor label True when
notEmpty is not empty The value is set, non-empty, and not false.
empty is empty The value is unset, empty, or false.
eq equals Equal, after converting your value to the same type.
neq does not equal Not equal.
gt / gte is greater than / is at least Numbers. A non-number never satisfies these.
lt / lte is less than / is at most Numbers.
contains contains Text contains the substring, or a list contains the item.

You always type the comparison value as text, and it’s converted to match what it’s compared against — so count gt "3" compares numbers and dark eq "true" compares on/off.

Format templates

format replaces {binding} tokens with the value, optionally passed through one filter: {op:main/out:n1|truncate:80}. An unknown filter or a binding that points at nothing renders as nothing.

Filter Argument Result
number digits The value as a number, optionally to N decimal places.
date short Local date and time, or just the date with short.
upper UPPERCASE.
lower lowercase.
join separator A list joined into text; defaults to ", ".
truncate length Cut to N characters, with an ellipsis.

Document schema

The rest of this page is the saved shape of an app — useful when editing a document by hand or reading one the agent wrote.

An app is stored as one ApplicationDocument. Version 3 is current; version 1 and 2 documents are upgraded on load, gaining one implicit main operation bound to the workflow they came from.

interface ApplicationDocument {
  schemaVersion: number;      // 3
  ui: PuckData;               // the layout: { root, content, zones }
  operations: OperationBinding[];
  resources: ResourceBinding[];
  variables: VariableDeclaration[];
  theme?: { id: string };
}

Operations

One operation is one workflow the app can run, plus its wiring.

interface OperationBinding {
  id: string;
  name: string;
  workflowId: string;
  workflowVersion?: number;   // fixed in a published app, latest in a draft
  inputs: Record<string, InputMapping>;    // keyed by node id
  outputs: Record<string, OutputMapping>;  // keyed by node id
  policy: "parallel" | "replace" | "queue";
  timeoutMs?: number;
}
Input mapping Where the value comes from
{ from: "widget" } The widget wired to it. The default when nothing else is set.
{ from: "variable", variableId } A value the app remembers.
{ from: "constant", value } A fixed value.
{ from: "resource", resourceBindingId } Whichever document that resource currently points at.
Output mapping Where the value goes
{ to: "display" } The slot display widgets read.
{ to: "variable", variableId } A variable, and the display slot.
policy If you start a run while one is already going
parallel Start anyway.
replace Cancel the running one, then start. The default.
queue Wait for it to finish, then start.

Several operations may point at the same workflow with different wiring, and an app that runs three workflows declares three operations.

Variables

interface VariableDeclaration {
  id: string;
  name: string;
  type?: { type: string; optional?: boolean } | null;
  default?: unknown;
  scope: "instance" | "user";  // this open app, or saved per user
  persist: boolean;            // only user-scoped variables may be saved
}

The visual editor’s variable picker lists the Set Variable channels the graph publishes. Typed variables with a scope, default, and persistence are declared through the agent or by editing the document.

Resources

A resource is a handle to a real document the app may read or edit.

interface ResourceBinding {
  id: string;
  name: string;
  kind: "asset" | "timeline" | "storyboard" | "sketch";
  scope: { projectId?: string; fixedId?: string };
  operations: ("read" | "create" | "update" | "delete")[];
}

Bundles

A bundle is an app packaged for sharing: the app plus the full graph of every workflow it runs, in one JSON file. Export, import, and the shipped example apps all use it.

interface ApplicationBundle {
  schemaVersion: number;        // 1
  app: ApplicationDocument;     // operations reference workflows[].key
  workflows: { key: string; name: string; graph: Graph }[];
}

Inside a bundle, each operation’s workflowId holds a local nickname (key) instead of a real id. Importing creates the workflows and swaps the nicknames for the new ids — the same trick .nodetool workflow bundles use for their asset references. A bundle exported from a published app carries the locked-in graphs, so it reproduces exactly what that release ran.

What one open app holds

Group Keyed by Holds
inputs opId:nodeId, or opId:nodeId#prop { value, dirty, revision }. dirty turns true once a widget rather than a default wrote it.
outputs opId:nodeId { value, invocationId, status, revision }. Status is empty, pending, streaming, or done.
variables variable id The current value.
view componentId:prop State belonging to one widget.
invocations job id { id, operationId, status, progress, error, startedAt }.

A run’s status is pending, running, completed, failed, or cancelled. Streamed values accumulate: text is joined, structured items collect into a list. A message from a job this app didn’t start is thrown away.

Agent tools

The builder agent edits the open app through these tools. Every one takes an application_id — the app’s own id, listed in the ui_context block. A workflow id is never accepted; the workflows an app runs are named by target_workflow_id on its operations.

Area Tools
Layout ui_app_get_snapshot, ui_app_list_component_types, ui_app_add_component, ui_app_update_component, ui_app_remove_component, ui_app_select_component, ui_app_set_title
Operations ui_app_list_operations, ui_app_add_operation, ui_app_update_operation, ui_app_remove_operation
Variables ui_app_list_variables, ui_app_declare_variable, ui_app_update_variable, ui_app_remove_variable
Resources ui_app_list_resources, ui_app_add_resource, ui_app_remove_resource
Bindings ui_app_get_binding_targets

CLI

npm run dev:nodetool -- app debug <application_id>
npm run dev:nodetool -- app debug my-app.json --params '{"prompt":"hi"}'
npm run dev:nodetool -- app debug <id> --no-run    # check the wiring, don't run
npm run dev:nodetool -- app debug <id> --json      # full report

# Script the clicks and typing
npm run dev:nodetool -- app debug <id> --interact \
  '[{"set":{"key":"prompt","value":"hi"}},{"click":"Button-1"}]'
npm run dev:nodetool -- app debug <id> --interact \
  '[{"set":{"key":"tone","value":"terse","operationId":"draft"}},{"run":"draft"}]'

The harness runs every operation the app declares, not just the first. Widgets are clicked by component id, by type if only one exists, or by label if it’s unique. Results land in nodetool-debug/app-<id>-<ts>/ as report.json, report.md, app.json, workflow.json, and one server/run-N.messages.jsonl per run.

Not simulated: visibleWhen, disabledWhen, format, and inputs that come from a resource.