Streaming UI Script language.
A compact, line-oriented language designed for LLMs. Each statement is a single assignment of an identifier to an expression. The parser is forgiving — invalid lines are dropped and valid lines render immediately.
Statements
| Kind | Syntax | Example |
|---|---|---|
| Component assignment | name = Component(args...) | card = Card([header]) |
| State variable | $name = defaultValue | $days = "7" |
| Query / Mutation | name = Query(...) | Mutation(...) | data = Query("get_users", {}, {rows: []}) |
Expressions
| Type | Syntax | Example |
|---|---|---|
| String (single-line) | "text" or 'text' | "Hello" |
| String (multi-line) | `text` (backticks) | `line 1 — great for Script(...) bodies |
| Number | 123, -5, 12.5 | 42 |
| Boolean | true / false | true |
| Null | null | |
| Array | [a, b, c] | ["Mon","Tue"] |
| Object | {key: value, ...} | {limit: 10} |
| Reference | identifier | card |
| State reference | $name | $days |
| Member access | obj.field | data.rows.title (array pluck) |
| Component call | Component(args) | Header("Hi") |
| Builtin call | @Builtin(args) | @Filter(rows, "name", "contains", $q) |
| Ternary | cond ? a : b | $open ? form : null |
| Binary | a + b, a == b, a && b | "" + $days + " days" |
| Unary | !a, -a | !$open |
Comments
The parser strips three comment styles silently. They never appear in the rendered output and are safe to mix with real statements:
| Style | Syntax | Notes |
|---|---|---|
| Line | // rest of line | Skipped to end of line. Canonical form — used by editor comment-toggle shortcuts. |
| Line (alt) | # rest of line | Shell / Python-style alternative. Identical semantics to //; convenient for marking sections in chat-style examples. |
| Block | /* ... */ | Spans multiple lines. Useful for temporarily disabling a chunk of script. |
Comments inside string literals ("...", '...',
and multi-line `...`) are part of the string and are not
stripped. LLM-generated responses should still leave comments out to
save tokens — identifiers double as documentation.
Reactive state
Variables prefixed with $ are reactive. Declare them with a
literal default. When a state value changes, every expression that read it
re-evaluates and the affected components re-render.
$days = "7"
$showEdit = false
filter = Select("days", [SelectItem("7","7d"), SelectItem("30","30d")], null, null, $days)
data = Query("usage", {days: $days}, {rows: []})
Passing a $variable as the value argument of an Input, Select,
or Checkbox enables two-way binding: user input updates the variable,
which in turn re-runs anything that depends on it.
Built-in functions
| Group | Functions |
|---|---|
| Aggregation | @Count, @Sum, @Avg, @Min, @Max, @First, @Last |
| Numeric | @Round(n, decimals?), @Abs, @Floor, @Ceil |
| Filter / sort | @Filter(arr, "field", "op", value), @Sort(arr, "field", "asc"|"desc") |
| Array growth | @Push(arr, value) (returns new array with value appended), @Concat(a, b) |
| Iteration | @Each(arr, "varName", template) — varName is local to template |
| Array shortcuts | $rows.length, $rows.first, $rows.last, $rows.field (pluck) |
| Action steps | @Run(ref), @Set($var, value), @Reset($a, $b), @ToAssistant("msg"), @OpenUrl("url"), @Navigate("/path") |
Queries and mutations
Query(toolName, args, defaults, refreshSeconds?) reads data
and runs immediately. The defaults argument is rendered while
the response is in flight, and the optional refreshSeconds
sets up auto-polling. Mutation(toolName, args) only runs when
a button or action invokes @Run(ref).
$title = ""
create = Mutation("create_ticket", {title: $title})
list = Query("list_tickets", {}, {rows: []})
btn = Button("Create", Action([@Run(create), @Run(list), @Reset($title)]))
Hoisting and streaming
Forward references are allowed. The renderer puts skeletons in for missing
identifiers, then swaps them in as the LLM streams more lines. Always lay
out the structure first (root = Stack([a, b, c])) and define
the children below — the user sees the shell instantly.
JavaScript interactions
Two additional surfaces are always available:
Script("id", "body", deps?) for effect-style behaviour,
and @Js("code") as an action step. Both share a
ctx bridge that exposes reactive state, registered
tools, DOM refs, and lifecycle hooks. They are part of the default
("full") system prompt; use getSystemPrompt({ mode: "chat" })
when you want a compact, chat-focused prompt without these surfaces.
See the dedicated
JavaScript interactions
page for the full API.
Routing
Four routing primitives ship with every renderer:
Routes(items, default?),
Route(path, content), NavLink(label, to, …),
and the @Navigate("/path") action step. The current path
is exposed reactively as $route; matched URL parameters
land in the params loop variable inside each Route's
content. The default ("full") prompt documents the routing surface;
switch to getSystemPrompt({ mode: "chat" }) for the
compact chat-focused prompt that omits it. See the dedicated
routing reference (and
live demo) for the full surface.
Error handling
The parser collects errors per line and continues past invalid input. The
renderer logs unknown components and shows an inline placeholder. The
element fires an error event with the parse errors so you
can surface them in your dev tools.