# commands — the verbs a control accepts

> 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.

<!-- id: ui-control-commands · area: ui · stability: stable · html: https://osysharp.com/reference/ui/control-commands/ -->

## Summary        {#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](#invoking), and by [handing a verb back as a value](#invoking-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      {#signature}
```osy syntax
control <Name> {
  commands {
    <CommandName>;
    <CommandName>(<Type> <param>, …);
  }
}
```

## Description    {#description}

### Declaring the verbs a control publishes   {#declaring}

```osy title="a grid that publishes two verbs" test app=ui-control-commands
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**:

```ts
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    {#implementing}

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

```ts syntax
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    {#what-to-publish}

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    {#invoking}

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:

```osy title="the app writes the find bar; the editor keeps the finding" test app=ui-control-commands-findbar
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    {#invoking-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:

```osy title="an editor whose block menu takes the app's own entries" test app=ui-control-commands-menu
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:

| Written | Runs |
|---|---|
| `Run = Archive` | an `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.DeleteBlock` | a 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    {#invoking-elsewhere}

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       {#examples}

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

```osy title="state as props, actions as commands" test app=ui-control-commands-player
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       {#see-also}
- [control — foreign UI controls (charts, grids, maps)](https://osysharp.com/reference/ui/controls/) — the `control` block these are declared in
- [styles — a control's own look knobs](https://osysharp.com/reference/ui/control-styles/) — the other opt-in rung: a control's own look knobs
- [probe — what a control says about itself](https://osysharp.com/reference/ui/control-probe/) — the other opt-in rung a shim implements on its handle: what the control says about itself
- [Slot (child content)](https://osysharp.com/reference/ui/slots/) — slots in general, including a component's own
