Commands
Every command the model runs, with its payload and result types and how to run it from code or as JSON. Generated from the registry.
The model runs 23 commands. Each one takes a JSON payload, checked against its JSON Schema, and returns a CommandResult: { ok: true, value }, or { ok: false, error }. Run one with model.run(name, payload) (typed by your registry), or give it as untrusted JSON to model.dispatch({ command, payload }), which validates it first. model.commands() returns this same list with the schemas, ready to become tool definitions for an assistant.
Every command can return invalid_payload (the payload does not match its schema, with the path of each problem), vetoed (a middleware refused it), queued (a middleware ran it while another command was running) and middleware_error; each command lists the ones its own rules add: not_found (a node it names is missing) and refused (a rule of the layout refuses the change). See the command bus for the results, the middleware and the events.
The payload types below use T, your registry (see typed data): a tab's data is checked against its component.
Tabs
tab.add
Add a new tab. component names what the tab shows and data holds its state (validated when the app registered a data schema). to is a tabset, a row, a border or a layout id; location center adds it to that tabset or border at index (-1 appends), an edge of a tabset splits it, an edge of a root row docks the tab to that side of the layout.
Payload (TabAddPayload<T>)
| field | type | required | description |
|---|---|---|---|
id | string | no | the new tab's id (generated when omitted) |
component | string | yes | what the tab shows (a key of the app's registry) |
data | JSON | no | the app's data (any JSON value) |
pinned | boolean | no | a pinned tab sits at the start of its strip, cannot close and cannot leave its tabset |
enableClose | boolean | no | whether the tab can be closed |
enableDrag | boolean | no | whether the tab can be dragged |
enablePopout | boolean | no | whether the tab can be popped out into a window |
minWidth | number | no | the smallest width, in px |
minHeight | number | no | the smallest height, in px |
maxWidth | number | no | the largest width, in px |
maxHeight | number | no | the largest height, in px |
borderWidth | number | no | its panel's width in a left or right border, in px |
borderHeight | number | no | its panel's height in a top or bottom border, in px |
to | string | yes | a tabset, a row, a border, or a layout id (its root row) |
location | "center" | "top" | "bottom" | "left" | "right" | no | center (default) goes into the target; an edge of a tabset splits it; an edge of a row docks beside its children |
index | integer | no | for a center drop: the insertion index; -1 appends |
select | boolean | no | whether the tab is selected in its new place |
Result ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Errors: not_found, refused, besides those every command can return.
model.run("tab.add", {
component: "editor",
data: {
name: "a.ts",
path: "/a.ts"
},
to: "tabset-1"
});{ "command": "tab.add", "payload": {"component":"editor","data":{"name":"a.ts","path":"/a.ts"},"to":"tabset-1"} }tab.select
Select a tab, making it visible. In a tabset the tabset also becomes the active one; in a border the border's panel opens. Selecting the selected tab changes nothing.
Payload ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Result ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Errors: not_found, besides those every command can return.
model.run("tab.select", {
tab: "tab-1"
});{ "command": "tab.select", "payload": {"tab":"tab-1"} }tab.close
Close a tab and remove it from the layout. Refused for a pinned tab or one whose enableClose is false.
Payload ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Result ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Errors: not_found, refused, besides those every command can return.
model.run("tab.close", {
tab: "tab-1"
});{ "command": "tab.close", "payload": {"tab":"tab-1"} }tab.move
Move a tab to another place: into a tabset or border at an index (location center), beside a tabset (an edge location splits it), or to an edge of a layout (to a root row or a layout id, with an edge location). Refused where the tab or the target does not allow it.
Payload (TabMovePayload)
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
to | string | yes | a tabset, a row, a border, or a layout id (its root row) |
location | "center" | "top" | "bottom" | "left" | "right" | no | center (default) goes into the target; an edge of a tabset splits it; an edge of a row docks beside its children |
index | integer | no | for a center drop: the insertion index; -1 appends |
select | boolean | no | whether the tab is selected in its new place |
Result ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Errors: not_found, refused, besides those every command can return.
model.run("tab.move", {
tab: "tab-1",
to: "tabset-2",
location: "right"
});{ "command": "tab.move", "payload": {"tab":"tab-1","to":"tabset-2","location":"right"} }tab.update
Replace a tab's data (and optionally switch its component). data is the whole new value, not a patch; it is validated when the app registered a schema for the component.
Payload (TabUpdatePayload<T>)
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
component | string | yes | the tab's component (its current one to keep it) |
data | JSON | no | the app's data (any JSON value) |
Result ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Errors: not_found, besides those every command can return.
model.run("tab.update", {
tab: "tab-1",
component: "editor",
data: {
name: "b.ts",
path: "/b.ts"
}
});{ "command": "tab.update", "payload": {"tab":"tab-1","component":"editor","data":{"name":"b.ts","path":"/b.ts"}} }tab.pin
Pin (value true) or unpin a tab of a tabset. Pinned tabs sit at the start of the strip, cannot be closed and cannot be dragged out of their tabset.
Payload ({ tab: string; value: boolean })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
value | boolean | yes | true pins, false unpins |
Result ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Errors: not_found, refused, besides those every command can return.
model.run("tab.pin", {
tab: "tab-1",
value: true
});{ "command": "tab.pin", "payload": {"tab":"tab-1","value":true} }tab.popout
Open a tab in a new browser window (a window layout). rect is the window's screen rect; a default is used without one. Refused when the tab does not allow popouts, is pinned or is already in a window.
Payload ({ tab: string; rect?: Rect })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
rect | object | no | the window's screen rect; without one, a 600x400 window offset 50px per open window (engine.popout passes the tab's place on screen) |
Result ({ window: string })
| field | type | required | description |
|---|---|---|---|
window | string | yes | the new window's id |
Errors: not_found, refused, besides those every command can return.
model.run("tab.popout", {
tab: "tab-1"
});{ "command": "tab.popout", "payload": {"tab":"tab-1"} }tab.configure
Change a tab's behaviour flags and size limits. A null value removes the tab's own value so the layout default applies.
Payload (TabConfigurePayload)
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
enableClose | boolean | null | no | whether the tab can be closed (null removes it: the layout default applies) |
enableDrag | boolean | null | no | whether the tab can be dragged (null removes it: the layout default applies) |
enablePopout | boolean | null | no | whether the tab can be popped out into a window (null removes it: the layout default applies) |
minWidth | number | null | no | the smallest width, in px (null removes it: the layout default applies) |
minHeight | number | null | no | the smallest height, in px (null removes it: the layout default applies) |
maxWidth | number | null | no | the largest width, in px (null removes it: the layout default applies) |
maxHeight | number | null | no | the largest height, in px (null removes it: the layout default applies) |
borderWidth | number | null | no | its panel's width in a left or right border, in px (null removes it: the layout default applies) |
borderHeight | number | null | no | its panel's height in a top or bottom border, in px (null removes it: the layout default applies) |
Result ({ tab: string })
| field | type | required | description |
|---|---|---|---|
tab | string | yes | the tab's id |
Errors: not_found, besides those every command can return.
model.run("tab.configure", {
tab: "tab-1",
enableClose: false
});{ "command": "tab.configure", "payload": {"tab":"tab-1","enableClose":false} }Tabsets
tabset.activate
Make a tabset the active one of its layout.
Payload ({ tabset: string })
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
Result ({ tabset: string })
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
Errors: not_found, besides those every command can return.
model.run("tabset.activate", {
tabset: "tabset-1"
});{ "command": "tabset.activate", "payload": {"tabset":"tabset-1"} }tabset.maximize
Maximize a tabset so it fills its layout (value true), or restore it (value false). Maximizing also makes it active. Refused when the tabset does not allow it or is the only tabset of its layout.
Payload ({ tabset: string; value: boolean })
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
value | boolean | yes | true maximizes, false restores |
Result ({ tabset: string })
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
Errors: not_found, refused, besides those every command can return.
model.run("tabset.maximize", {
tabset: "tabset-1",
value: true
});{ "command": "tabset.maximize", "payload": {"tabset":"tabset-1","value":true} }tabset.close
Close a tabset: its closable tabs close, and the tabset is removed once empty. Refused when the tabset's enableClose is false.
Payload ({ tabset: string })
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
Result ({ closed: string[] })
| field | type | required | description |
|---|---|---|---|
closed | string[] | yes | the ids of the tabs that closed |
Errors: not_found, refused, besides those every command can return.
model.run("tabset.close", {
tabset: "tabset-1"
});{ "command": "tabset.close", "payload": {"tabset":"tabset-1"} }tabset.move
Move a whole tabset: merge its tabs into another tabset (location center), place it beside a tabset (an edge), or dock it to an edge of a layout.
Payload (TabsetMovePayload)
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
to | string | yes | a tabset, a row, or a layout id (its root row) |
location | "center" | "top" | "bottom" | "left" | "right" | no | center (default) merges its tabs into the target; an edge of a tabset places it beside; an edge of a row docks it there |
index | integer | no | for a merge: where its tabs go; -1 appends |
Result ({ tabset: string })
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
Errors: not_found, refused, besides those every command can return.
model.run("tabset.move", {
tabset: "tabset-1",
to: "main",
location: "bottom"
});{ "command": "tabset.move", "payload": {"tabset":"tabset-1","to":"main","location":"bottom"} }tabset.popout
Open a whole tabset in a new browser window. Refused when any of its tabs does not allow popouts, when it is empty, or when it is already in a window.
Payload ({ tabset: string; rect?: Rect })
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
rect | object | no | the window's screen rect; without one, a 600x400 window offset 50px per open window (engine.popout passes the tabset's place on screen) |
Result ({ window: string })
| field | type | required | description |
|---|---|---|---|
window | string | yes | the new window's id |
Errors: not_found, refused, besides those every command can return.
model.run("tabset.popout", {
tabset: "tabset-1"
});{ "command": "tabset.popout", "payload": {"tabset":"tabset-1"} }tabset.configure
Change a tabset's behaviour flags, size limits or data. A null value removes the tabset's own value so the layout default applies (data: null removes the data).
Payload (TabsetConfigurePayload<T>)
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
enableDrop | boolean | null | no | whether tabs can be dropped into it (null removes it: the layout default applies) |
enableDrag | boolean | null | no | whether the whole tabset can be dragged (null removes it: the layout default applies) |
enableDivide | boolean | null | no | whether a drop on one of its edges can split it (null removes it: the layout default applies) |
enableMaximize | boolean | null | no | whether it can be maximized (null removes it: the layout default applies) |
enableClose | boolean | null | no | whether it can be closed (null removes it: the layout default applies) |
deleteWhenEmpty | boolean | null | no | whether it is removed when its last tab leaves (null removes it: the layout default applies) |
autoSelectTab | boolean | null | no | whether a tab added to it is selected (null removes it: the layout default applies) |
minWidth | number | null | no | the smallest width, in px (null removes it: the layout default applies) |
minHeight | number | null | no | the smallest height, in px (null removes it: the layout default applies) |
maxWidth | number | null | no | the largest width, in px (null removes it: the layout default applies) |
maxHeight | number | null | no | the largest height, in px (null removes it: the layout default applies) |
data | JSON | no | the app's data (any JSON value) |
Result ({ tabset: string })
| field | type | required | description |
|---|---|---|---|
tabset | string | yes | the tabset's id |
Errors: not_found, besides those every command can return.
model.run("tabset.configure", {
tabset: "tabset-1",
enableMaximize: false
});{ "command": "tabset.configure", "payload": {"tabset":"tabset-1","enableMaximize":false} }Rows
row.resize
Set the relative weights of a row's children, one positive number per child in order (the splitters issue this while dragged).
It may run as a step of a continuous gesture: model.run(…, { transient: true }).
Payload ({ row: string; weights: number[] })
| field | type | required | description |
|---|---|---|---|
row | string | yes | the row's id |
weights | number[] | yes | one positive weight per child, in order |
Result ({ row: string })
| field | type | required | description |
|---|---|---|---|
row | string | yes | the row's id |
Errors: not_found, besides those every command can return.
model.run("row.resize", {
row: "row-1",
weights: [
30,
70
]
});{ "command": "row.resize", "payload": {"row":"row-1","weights":[30,70]} }row.configure
Set (or, with null, remove) a row's data.
Payload (RowConfigurePayload<T>)
| field | type | required | description |
|---|---|---|---|
row | string | yes | the row's id |
data | JSON | no | the app's data (any JSON value) |
Result ({ row: string })
| field | type | required | description |
|---|---|---|---|
row | string | yes | the row's id |
Errors: not_found, besides those every command can return.
model.run("row.configure", {
row: "row-1",
data: {
name: "Editors"
}
});{ "command": "row.configure", "payload": {"row":"row-1","data":{"name":"Editors"}} }Borders
border.resize
Set the size in px of a border's panel (of its selected tab when that tab has its own border size). The size is clamped to the border's min and max.
It may run as a step of a continuous gesture: model.run(…, { transient: true }).
Payload ({ border: string; size: number })
| field | type | required | description |
|---|---|---|---|
border | string | yes | the border's id (border_<location> unless the layout names it) |
size | number | yes | the panel's new size, in px (kept within its limits) |
Result ({ border: string; size: number })
| field | type | required | description |
|---|---|---|---|
border | string | yes | the border's id |
size | number | yes | the size it got, in px |
Errors: not_found, besides those every command can return.
model.run("border.resize", {
border: "border_left",
size: 240
});{ "command": "border.resize", "payload": {"border":"border_left","size":240} }border.configure
Open or close a border's panel (open), switch it between docked and overlay (mode), show or hide it, or change its sizes, flags or data. Opening selects its first tab when none is selected. A null value removes the border's own value so the layout default applies.
Payload (BorderConfigurePayload<T>)
| field | type | required | description |
|---|---|---|---|
border | string | yes | the border's id (border_<location> unless the layout names it) |
open | boolean | no | open (true) or close (false) the border's panel |
mode | "docked" | "overlay" | null | no | docked (beside the layout) or overlay (over it) (null removes it: the layout default applies) |
show | boolean | null | no | false hides the border entirely (null removes it: the layout default applies) |
autoHide | boolean | null | no | hide the strip while the border has no tabs (a drag near its edge reveals it) (null removes it: the layout default applies) |
enableDrop | boolean | null | no | whether tabs can be dropped into it (null removes it: the layout default applies) |
autoSelectTabWhenOpen | boolean | null | no | whether a tab added while its panel is open is selected (null removes it: the layout default applies) |
autoSelectTabWhenClosed | boolean | null | no | whether a tab added while its panel is closed is selected (which opens it) (null removes it: the layout default applies) |
size | number | null | no | its panel's size, in px (null removes it: the layout default applies) |
minSize | number | null | no | its panel's smallest size, in px (null removes it: the layout default applies) |
maxSize | number | null | no | its panel's largest size, in px (null removes it: the layout default applies) |
data | JSON | no | the app's data (any JSON value) |
Result ({ border: string })
| field | type | required | description |
|---|---|---|---|
border | string | yes | the border's id |
Errors: not_found, refused, besides those every command can return.
model.run("border.configure", {
border: "border_left",
open: true
});{ "command": "border.configure", "payload": {"border":"border_left","open":true} }Windows
window.close
Close a popout window layout: its tabs move back into the main layout's active tabset (its first tabset when none is active), and the window closes.
Payload ({ window: string })
| field | type | required | description |
|---|---|---|---|
window | string | yes | the window layout's id |
Result ({ tabs: string[] })
| field | type | required | description |
|---|---|---|---|
tabs | string[] | yes | the tabs moved back into the main layout |
Errors: not_found, besides those every command can return.
model.run("window.close", {
window: "window-1"
});{ "command": "window.close", "payload": {"window":"window-1"} }window.configure
Record a popout window's screen rect (the engine does this when the window moves or resizes, so a saved layout reopens it in place).
It may run as a step of a continuous gesture: model.run(…, { transient: true }).
Payload ({ window: string; rect: Rect })
| field | type | required | description |
|---|---|---|---|
window | string | yes | the window layout's id |
rect | object | yes | the window's screen rect |
Result ({ window: string })
| field | type | required | description |
|---|---|---|---|
window | string | yes | the window's id |
Errors: not_found, besides those every command can return.
model.run("window.configure", {
window: "window-1",
rect: {
x: 100,
y: 80,
width: 800,
height: 600
}
});{ "command": "window.configure", "payload": {"window":"window-1","rect":{"x":100,"y":80,"width":800,"height":600}} }The layout
layout.configure
Change the layout defaults: the default behaviour of tabs, tabsets and borders, and layout settings (root orientation, edge docking). Fields are merged; a null value removes one.
Payload ({ defaults: LayoutDefaultsPatch })
| field | type | required | description |
|---|---|---|---|
defaults | object | yes | the defaults to change, by kind (tab, tabset, border, layout) |
Result (Record<never, never>)
No fields.
Errors: only those every command can return.
model.run("layout.configure", {
defaults: {
tab: {
enableClose: false
}
}
});{ "command": "layout.configure", "payload": {"defaults":{"tab":{"enableClose":false}}} }layout.load
Replace the whole layout with a JSON v1 document (a saved layout, an undo step, a remote sync). Nodes keep their identity by id, so tabs whose ids survive keep their mounted content.
Payload ({ layout: LayoutJson<T> })
| field | type | required | description |
|---|---|---|---|
layout | layout | yes | a layout document (JSON v1) |
Result ({ added: string[]; removed: string[] })
| field | type | required | description |
|---|---|---|---|
added | string[] | yes | ids only in the new layout |
removed | string[] | yes | ids only in the old layout |
Errors: only those every command can return.
model.run("layout.load", {
layout: {
version: 1,
root: {
type: "row",
children: [
{
type: "tabset",
children: [
{
component: "editor",
data: {
name: "a.ts"
}
}
]
}
]
}
}
});{ "command": "layout.load", "payload": {"layout":{"version":1,"root":{"type":"row","children":[{"type":"tabset","children":[{"component":"editor","data":{"name":"a.ts"}}]}]}}} }Batches
batch
Run several commands in order as one atomic step: if any fails, none applies. Emits one change event. Nested batches are flattened.
It may run as a step of a continuous gesture: model.run(…, { transient: true }).
Payload ({ commands: BatchEntry<T>[] })
| field | type | required | description |
|---|---|---|---|
commands | object[] | yes | the commands to run, in order |
Result ({ results: unknown[] })
| field | type | required | description |
|---|---|---|---|
results | JSON[] | yes | each command's value, in order |
Errors: those of the commands it runs (the first failure, at its path in commands); nothing applies unless all succeed.
model.run("batch", {
commands: [
{
command: "tab.close",
payload: {
tab: "tab-1"
}
},
{
command: "tab.close",
payload: {
tab: "tab-2"
}
}
]
});{ "command": "batch", "payload": {"commands":[{"command":"tab.close","payload":{"tab":"tab-1"}},{"command":"tab.close","payload":{"tab":"tab-2"}}]} }The command bus
How the model applies commands. Run them typed or as untrusted JSON, dry-run them, wrap them in middleware, batch them and listen to every commit.
Layout JSON
The JSON v1 layout document, field by field, with the layout defaults, the state it loads into and how a document is validated before it loads.