The complete surface of the Mini App layer. For what these mean, see Mini Apps; for how to combine them, see Building Mini Apps.

Everything here is defined in packages/app-runtime, which the web runtime, the nodetool app debug harness, and the eval suites all share.

Widgets

Every widget accepts binding, visibleWhen, disabledWhen, and format on top of its own fields.

Display

Widget Shows
Heading Static text at H1–H3.
Text Static or formatted text.
Markdown Rendered Markdown. The right choice for streamed prose.
Image An image value. Fit contain or cover, fixed height, placeholder.
Audio An audio value with a player.
Video A video value with a player, max height, placeholder.
JSON A structured value, formatted.
Table An array value as rows. Max height, placeholder.
Output A value whose type varies; renders by shape.
Progress The bound operation’s progress.

Inputs

Widget Writes Commits
Workflow Input The bound Input node, rendered from its declared type. 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 A boolean. no
Select One of a fixed option list. no
Image Input An image. no
Audio Input An audio file. no
Video Input A video file. no
Document Input A document. no
Color Input A color. no
Resource Picker The selected resource of a resource binding. no
Resource Gallery The selected resource, from a grid. Tile size. no
Storyboard Scenes Edits the bound storyboard through the resource provider. Fires no event. no

“Commits” means the control reports a settled value on release or blur, which is what makes release pacing meaningful.

Actions and layout

Widget Does
Button Fires its click events. Variant contained/outlined/text, color primary/secondary/warning.
Panel A titled container holding other widgets.
Columns Two slots, left and right.
Divider A horizontal rule.

Binding grammar

A binding is one string. Bindings key on node IDs, so renaming a node in the graph editor never breaks an app.

Token Addresses
op:<opId>/in:<nodeId> An operation input.
op:<opId>/out:<nodeId> An operation output.
op:<opId>/prop:<nodeId>#<prop> A node property driven by a widget.
op:<opId>/exec#<field> Execution state: running, progress, error, activity.
var:<variableId> A declared app variable.
view:<componentId>#<prop> Widget-local state. Never persisted.
node:<nodeId>#<prop> Legacy node property, resolved against the default operation.
<name> Legacy bare name, resolved against the live graph.

A bare name resolves by how the widget uses it: a write widget looks for an Input node, a read widget 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 a validation error, not a silent no-op.

Actions

A widget event dispatches one action. Actions are data the runtime interprets.

Action Fields Effect
run operationId Runs the operation, subject to its policy.
cancel operationId, optional invocationId Cancels the operation’s live runs, or one specific run.
setVariable variableId, value or the firing widget’s value Writes a variable.
toggleVariable variableId Inverts a boolean variable.
resourceCommand resourceBindingId, command read, create, update, delete, or upload on a resource binding.
openResource resourceBindingId Opens the bound resource in its editor.

The visual editor exposes 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 (Button) or change (input widgets). A change event has a pace:

Pace Fires
live On every change.
release When the control commits — slider release, input blur. Only offered on committing controls.
debounce Trailing, after a quiet moment.

Conditions

visibleWhen and disabledWhen each hold a binding, an operator, and a literal. An unresolvable condition is treated as no condition, so a broken binding 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 coercing the literal to the value’s type.
neq does not equal Not equal.
gt / gte is greater than / is at least Numeric comparison. A non-numeric value never satisfies one.
lt / lte is less than / is at most Numeric comparison.
contains contains A string contains the substring, or an array contains the item.

Literals are stored as strings and coerced to the shape of the value they are compared against, so count gt "3" compares numbers and dark eq "true" compares booleans.

Format templates

format renders {binding} tokens in place of the raw value, with one optional filter: {op:main/out:n1|truncate:80}. An unknown filter or unresolvable binding renders as the empty string.

Filter Argument Result
number digits The value as a number, optionally fixed to N digits.
date short Locale date-time, or locale date with short.
upper Uppercased.
lower Lowercased.
join separator An array joined; defaults to ", ".
truncate length Cut to N characters with an ellipsis.

Document schema

workflow.app_doc holds an ApplicationDocument. Schema version 3 is current; version 1 and 2 documents are lifted to 3 on load, with one implicit main operation bound to the host workflow.

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

Operations

interface OperationBinding {
  id: string;
  name: string;
  workflowId: string;
  workflowVersion?: number;   // pinned in a release, floating 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 Value comes from
{ from: "widget" } The bound widget. The default when an input has no mapping.
{ from: "variable", variableId } An app variable.
{ from: "constant", value } A fixed value.
{ from: "resource", resourceBindingId } The resource currently selected for that binding.
Output mapping Value goes to
{ to: "display" } The output slot display widgets read.
{ to: "variable", variableId } An app variable, and the display slot.
Policy On a second run while one is live
parallel Start anyway.
replace Cancel the live runs, then start. The default.
queue Wait for them to settle, then start.

Variables

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

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

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

Instance state

Namespace Key Holds
inputs opId:nodeId, or opId:nodeId#prop { value, dirty, revision }. dirty is 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 Widget-local state.
invocations job id { id, operationId, status, progress, error, startedAt }.

Invocation status is pending, running, completed, failed, or cancelled. Streamed values append: strings concatenate, structured items collect into a list. A message whose job_id this instance did not start is dropped.

Agent tools

The builder agent edits the open document through these frontend tools:

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 <workflow_id>
npm run dev:nodetool -- app debug workflow.json --params '{"prompt":"hi"}'
npm run dev:nodetool -- app debug <id> --no-run    # static wiring check
npm run dev:nodetool -- app debug <id> --json      # full AppDebugReport

# Scripted interactions
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 declared operation, not only the first. Widgets are clicked by component id, unique type, or unique label. The bundle lands in nodetool-debug/app-<id>-<ts>/ with report.json, report.md, app.json, workflow.json, and one server/run-N.messages.jsonl per triggered run.

Not simulated: visibleWhen, disabledWhen, format, and from: "resource" inputs.