Skip to content

Protocol Overview

phroun edited this page Aug 22, 2026 · 3 revisions

A client builds and drives an interface by sending text. The display owns the trinkets; the client owns the description of them.

w=new window title="Search" width=320 height=120 main children={
	pn=new panel layout=vbox children={
		q=new textinput placeholder="Search"
		new button caption="Go" action=app.search
	}
}
sub w.pn.q submit

The verbs

Eight, and one statement with no verb at all.

new build an object
set change one that exists
destroy remove one
sub / unsub start and stop hearing about it — see Events
describe ask what this build's vocabulary is
template declare a reusable shape — see Templates and Aliases
alias declare a property-name macro — same page
key=path surface an object's id, changing nothing

Anything else is refused:

frobnicate x
  ->  unknown verb "frobnicate"

Statements

One statement per line. A statement is a verb, a target, and named properties:

new button caption="OK" default
set w title="Renamed"
destroy w

# begins a comment to end of line. Inside a children={…} block, statements are separated by newlines or ;not by spaces:

new listview children={new item caption="A"; new item caption="B"}

Two statements separated only by spaces parse as one, and fail oddly:

new listview children={new item caption="A"  new item caption="B"}
  ->  property "new" is not supported by this type

Correlation keys

key= in front of a new names the object for later. The reply carries the number the display assigned:

p=new panel layout=vbox children={ b=new button caption="OK" }
  ->  reply: p = 1

Only top-level keys come back in the reply. b above is nested, so it is not in the reply and cannot be used as a short name:

set b caption="Renamed"
  ->  set: unknown key path "b"

It is reachable by path, though:

p=new panel layout=vbox children={ b=new button caption="OK" }
set p.b caption="Renamed"

or promote it to a short name with a bare surfacing statement, which changes nothing and answers with the id:

bid=p.b
set bid caption="Now works"

That is the same mechanism TreeView uses to reach a nested item and MDIPane uses to get its window ids.

A path names every level

A path is not a search. Each step between the top-level key and the object must be a correlation key of its own:

w=new window title="S" main children={
	pn=new panel layout=vbox children={
		q=new textinput placeholder="Search"
	}
}
sub w.pn.q submit     # works

Skipping the middle does not:

sub w.q submit
  ->  sub: unknown key path "w.q"

So an unkeyed container makes everything below it unreachable. Write the same tree with a bare new panel and there is no path to q at all — neither w.q nor w.pn.q nor q — and the only way to address it later is to have surfaced it at build time.

Key the containers you might need to reach through. It costs a word.

Keys last for the connection

A key survives the request that made it. Build in one batch, drive in the next:

p=new panel layout=vbox children={ b=new button caption="OK" }
set p bg=blue
set p.b caption="Renamed"

Both work. This is what makes the two-batch patterns possible — build the shape, read the ids out of the reply, send the data against them.

destroy takes the key with it:

destroy p
set p bg=red
  ->  set: unknown key path "p"

Properties

name=value, applied in the order written. That order matters more than it looks: a property that refers to children has to come after them.

new listview selected=2 children={ … }   # selected lands on 0
new listview children={ … } selected=2   # selected lands on 2

Neither errors. ListView has the full account, and Properties and Values covers the value syntax — flags, strings, colors, units.

An unknown property is refused rather than ignored:

new button nosuchthing=1
  ->  property "nosuchthing" is not supported by this type

Children

children={…} nests. A trinket that does not take children says so, and one that takes only a particular kind says that:

new button children={ new label caption="x" }
  ->  this type does not accept children

new listview children={ new label caption="x" }
  ->  listview: children must be items, got *trinkets.Label

set … children={…} appends to what is already there rather than replacing it, which is how a list grows or an MDIPane spawns a window.

Introspection

describe streams this build's whole vocabulary — every type, its properties with kinds, defaults and descriptions, and its events with their fields — ahead of the reply. It takes no arguments:

describe
  ->  305 lines, beginning
      propcommon name="acc_name" kind="string" default="" doc="…" enum=""

describe button
  ->  describe: takes no arguments

A running host is the authority on its own vocabulary. The tables in this wiki are generated from exactly this, so where a page and your build disagree, believe the build. Introspection has the detail.

What comes back

A request answers with a reply: the ids of its top-level correlation keys, plus anything describe streamed. Errors are reported against the statement that caused them and stop the request — a script is not applied halfway and then abandoned silently.

Events are separate. They arrive whenever the user does something, not in answer to anything, and only for what you subscribed to — with command as the exception that always flows. See Events.

Naming rules

Anything you declare — a template, an alias — begins with a capital. Everything the toolkit defines — types, properties, events — is lowercase. So a name's case tells you who owns it, and the two can never collide.

template Danger=button fg=bright_red
alias Caption="caption"

Correlation keys are yours and are conventionally lowercase; they live in a separate namespace from both.

See also

Object Model — what the objects are and how they nest · Properties and Values — the value syntax · Events · Introspection · Templates and Aliases

Clone this wiki locally