Osy#the first language built for agents
Agents firstAgentic appsWorkflowsDurable Execution — built inSecurityTestingThe editorThe UI modelOne program

Reference / UI

commands — the verbs a control accepts

control X { commands { SelectAll; ExportCsv(string filename); } }

A `commands { }` block declares the verbs a control accepts — the mirror of its events. An event is the control telling the app something happened; a command is the app asking the control to do something. Declaring them is what lets the platform hold the shim to its own contract.

stable4 examples compiled by CIuicontrolsauthoring

Summary#

A control's contract has always run one way: props in, events out. An app can hand a control values and be told when something happened, but it has no way to name something the control DOES.

A commands { } block is the other direction. It declares the verbs the control publishes — SelectAll, ExportCsv(string filename) — as a typed, checked part of its contract rather than a method a caller has to know about from documentation.

A control declares its verbs, the generated typings require the shim to implement them, and the platform refuses a shim that does not. An app reaches them two ways: from content it writes inside one of the control's own chrome slots, and by handing a verb back as a value — an entry in a list the control renders. Both name a verb; neither hands the app a callable.

The block is optional, and a control that declares none is unaffected.

Signature#

control <Name> {
  commands {
    <CommandName>;
    <CommandName>(<Type> <param>, …);
  }
}

Description#

Declaring the verbs a control publishes#

control DataGrid {
  contractVersion "1.1"
  participation headless
  props { string title; }
  events { rowPicked(int index); }
  commands {
    /// Select every row.
    SelectAll;
    ExportCsv(string filename);
  }
}

Events and commands are the same idea pointing opposite ways, and the generated typings read that way — one typed emit overload per event on the host, one typed method per command on the handle:

export interface DataGridHost {
  emit(event: "rowPicked", index: number): void;       // OUT — the platform provides it
}

export interface DataGridHandle extends ControlHandle<DataGridProps> {
  commands: {                                          // IN — the shim implements it
    selectAll(): void;
    exportCsv(filename: string): void;
  };
}

A command is declared PascalCase (a name written in Osy#) and implemented camelCase (a JavaScript method). Both sides derive that mapping from the declared name, so they cannot drift.

A command and an event cannot share a name: one name would mean both "tell the app this happened" and "ask the control to do this".

The shim must implement them#

Commands land on what mount RETURNS, not on the host, because they are things the control provides:

export function mount(el, props, host) {
  return {
    update(next) { … },
    destroy() { … },
    commands: {
      selectAll() { … },
      exportCsv(filename) { … },
    },
  };
}

The platform checks them when it takes the handle — at the mount that produced it, exactly as it checks update and destroy. A verb a control publishes and never writes fails there, loudly, naming the missing method. The alternative is that it fails the first time anyone asks for it, which is a user, in a browser, a long way from the code at fault.

Publishing a verb is a decision#

A command is not a way for an app to reach into a control. It is a named, typed verb the control chose to make part of its contract, so what an app can ask for is exactly what the control decided to publish — and it can be versioned like anything else in the block.

Prefer a prop for state (readOnly, density) and a command for an action that has no resting value. "Selected" is a state; "select everything, now" is not.

Invoking a command — a chrome slot#

A control declares a slots { } block naming regions of its own interface that an app fills. The content of such a slot is handed the control's commands, so the app writes the chrome and the control keeps the behaviour behind it:

control MarkdownEditor {
  contractVersion "1.1"
  participation headless
  props { string title; }
  commands { FindNext; FindPrev; ReplaceAll(string text); }
  slots { FindBar; }
}

[Page("/doc")] [AllowAnonymous]
component DocPage() {
  action Close() { }
  render {
    MarkdownEditor(title: "Doc") {
      slot FindBar { c =>
        Row {
          Button("Previous", onPress: c.FindPrev);
          Button("Next", onPress: c.FindNext);
          Button("Replace all", onPress: () => c.ReplaceAll("draft"));
          Button("Done", onPress: Close);
        }
      }
    }
  }
}

Values ride as props, verbs as commands. A search box has a resting value, so it is state the app owns and hands in (findQuery:); "replace every match, now" has no resting value, so it is a command. That split is what lets a whole find bar — boxes, count, buttons, and whether it is open at all — be the app's, while the matching and the document edits stay the control's. The count comes back as an ordinary event.

c is the control's commands. Naming them is the point: the app's own actions stay reachable in the same content (onClick: Close), and which of the two a name means is never in doubt. Had the commands simply been in scope unqualified, one could shadow a page action — and the failure would be a button that works and does the wrong thing.

c.FindNxt is a compile error listing the commands the control does declare. A command with parameters cannot be named bare — there would be nothing to supply them — so it is CALLED instead, in the form every event handler already uses: onClick: () => c.ReplaceAll("draft"), as above. The arguments are evaluated where they are written, when the content is built.

Where it renders is the control's decision. The shim asks for the slot and positions it; an app cannot put content somewhere the control did not offer, and a shim that ignores a slot simply shows nothing.

What an app never gets is the callable itself. It names a verb; the platform is what turns that name into a call on the control. So a slot's content can reach exactly the set of verbs the control published, and nothing else.

Invoking a command — a verb as a VALUE#

A chrome slot covers app content that CALLS the control. The other direction is a verb the app hands back — an entry in a list the control renders itself. That is what a menu wants: the control owns the menu's look and its keyboard model, and the app supplies what should be in it.

A control declares its item shape as an ordinary class, with an Action-typed member for the verb:

class MenuEntry {
  public string Label;
  public Action Run;
}

control MarkdownEditor {
  contractVersion "1.1"
  participation headless
  props {
    string title;
    MenuEntry[] extraItems = [];
  }
  commands {
    DeleteBlock;
    TurnIntoHeading(int level);
  }
}

[Page("/doc")] [AllowAnonymous]
component DocPage() {
  action Archive() { }
  render {
    MarkdownEditor(title: "Doc", extraItems: [
      new MenuEntry { Label = "Archive", Run = Archive },
      new MenuEntry { Label = "Delete block", Run = MarkdownEditor.DeleteBlock },
      new MenuEntry { Label = "Make it a heading", Run = () => MarkdownEditor.TurnIntoHeading(2) }
    ]);
  }
}

One member takes either kind of verb, and that is the point. A real menu is a mix: some entries run the app's own code, some run the control's. Split into separate props and every app has to reassemble one ordered list from two — and the order between them is lost.

Action is the type, the same one a component parameter uses for a callback (component X(Action onPress)). It is always nullary; arguments are bound at the call site.

The four spellings, and nothing else:

WrittenRuns
Run = Archivean action/method/callback parameter of this component, taking no arguments
Run = () => Archive(doc.Id)the same, with arguments — evaluated where they are written
Run = MarkdownEditor.DeleteBlocka command of the control being called
Run = () => MarkdownEditor.TurnIntoHeading(2)the same, with arguments

The qualifier must be the control being called: naming another control's verb is a compile error, because this entry is handed to this control and nothing else can run it. A verb that does not exist, a parameterised one named bare, the wrong number of arguments, and a plain value where a verb belongs are each refused with the working spelling named.

An Action member is only settable in a control's props at a call site. It holds a verb, not a value: there is no storage for it, no wire form to the server, and away from a call site nothing that could run it. For the same reason it is legal only on a class — a persisted entity refuses it.

The shim receives a plain function. Its generated typings say Run: () => void, and that is the whole contract: it cannot tell an app action from one of its own commands, which is exactly why one member takes both.

Reaching a control from ANYWHERE else#

Not yet possible — both forms above put the app's name for a verb inside the control's own call. An app action calling a mounted instance from somewhere else entirely is a separate design (it needs instance naming and a "not mounted yet" answer), and it is deliberately unbuilt until something real cannot be expressed without it. The shim-side contract does not change if it lands.

What NOT to do meanwhile is fake it with a prop that means "go" — an incrementing counter the control watches for changes. It reads as state, it is not, and it breaks the moment two callers want the verb in one render.

Examples#

A media player, where the split between props and commands is the clearest:

control Player {
  contractVersion "1.1"
  participation opaque
  props {
    string src;
    bool loop = false;
  }
  events { ended(bool natural); }
  commands {
    Play;
    Pause;
    SeekTo(int seconds);
  }
}

loop is a resting value the app owns, so it is a prop. Play has no resting value — asking twice means asking twice — so it is a command.

See also#

Related

control — foreign UI controls (charts, grids, maps)

A `control` block declares the contract of a foreign UI widget — a chart, a data grid, a map — that a small JavaScript…

styles — a control's own look knobs

A `styles { }` block declares the look values a control owns — its paddings, widths, shadows — as named knobs an app…

probe — what a control says about itself

A `probe { }` block declares the facts a control publishes about its OWN internal state, so an app's tests can ask for…