<!-- Generated by scripts/ax-docs.mts from the JSDoc in registry/ouro/ax/. Do not edit; run `pnpm run docs`. -->

# ax-component

A component's agent side: what an agent may read from it and which of its actions an agent may take, each bound to the CLI command that does the same (its twin). `defineComponentAx` declares it once beside the component; the app binds the twins with `bindAx`, because a component does not know the app's command names; the entries `bindAx` returns go into the X-ray's inventory under `ui`, and the X-ray checks every twin from outside. Declare it beside the component whose actions it names, such as `CallControls`, rather than the generic `Toolbar` that holds it: a container exports one only when it has agent reads or actions of its own. A component imports `defineComponentAx` from the kit's `ouro/ax/component` under the registry alias, which the shadcn CLI rewrites to the app's lib alias (`@/lib/ouro/ax/component`), and its item brings `ax-component` with it. The path is not spelled out here, since an installed file may not name the registry.

```sh
npx shadcn@latest add ourostack/ouro-ui/ax-component#v1.3.10
```

It installs `lib/ouro/ax/component.ts`, with `ax-effects`. Its tests are `ax-component-tests`. Every item runs on Node 22.18 or later as it is copied: the files import each other as `./<file>.ts`. The [Ouro AX guide](ax.md) shows how they fit together.

## Exports

### `AX_ACTION_ATTRIBUTE`

The attribute a component renders on the control for an action it declares, holding the action's id.

```ts
const AX_ACTION_ATTRIBUTE: "data-ax-action"
```

### `AxAction`

An action: either `agent: true` or `personOnly: "<why>"`, exactly one; a missing or doubled choice is a type error.

```ts
type AxAction = AxAgentAction | AxPersonOnlyAction;
```

### `AxAgentAction`

An action an agent may take; the app binds its CLI twin.

```ts
interface AxAgentAction extends AxActionBase {
  agent: true;
  personOnly?: never;
  scope?: AxScope;
}
```

### `AxBindings`

The app's commands for a component's twins, by read and action id, written as an agent types them, with a reference as a placeholder (`"call mute <meeting>"`). `refuseAs` names, for every person-only action, the CLI words an agent might try, which refuse (`"call unmute"`).

```ts
interface AxBindings {
  reads?: Readonly<Record<string, ReadTwin>>;
  actions?: Readonly<Record<string, string>>;
  refuseAs?: Readonly<Record<string, string>>;
}
```

### `AxPersonOnlyAction`

An action only the person takes, with the reason in one line, said to you, the agent ("You can't turn on the person's camera; only they can."): `bindAx` refuses a twin for it and asks for the CLI words that refuse.

```ts
interface AxPersonOnlyAction extends AxActionBase {
  personOnly: string;
  agent?: never;
  scope?: never;
}
```

### `AxRead`

Something an agent may read from the component, with the fields its twin's `--json` carries.

```ts
interface AxRead {
  id: string;
  summary?: string;
  fields: readonly string[];
  consent: ReadConsent;
  push?: "filtered";
}
```

### `AxScope`

Whose item an agent action may act on: `self`, only the calling agent's own, such as its own row in AgentPresence. An action without a scope acts on any item.

```ts
type AxScope = "self";
```

### `bindAx`

Binds a component's twins to the app's commands and returns its entries for the X-ray inventory (`ui`). It throws when a read or an agent action has no twin, a read with `push: "filtered"` has no stream, a person-only action is given a twin or has no `refuseAs` words, `refuseAs` names an action that is not person-only, a `scope: "self"` action's twin does not say who is calling (`--agent <me>`), or a binding names nothing the component declares. A scoped action's entry carries its scope.

```ts
function bindAx(ax: ComponentAx, bindings: AxBindings): UiEntry[]
```

Example:

```ts
const ui = [
  ...bindAx(mediaTileAx, { reads: { participant: "call participants <meeting>" }, actions: { pin: "call pin <participant>" } }),
  ...bindAx(callControlsAx, {
    actions: { mute: "call mute <meeting>", leave: "call leave <meeting>", "raise-hand": "call raise-hand <meeting>", "lower-hand": "call lower-hand <meeting>" },
    refuseAs: { unmute: "call unmute", camera: "call camera", share: "call share", captions: "call captions on" },
  }),
];
writeFileSync("inventory.json", JSON.stringify({ cli: commandNames, ui }));
```

### `ComponentAx`

```ts
interface ComponentAx {
  readonly component: string;
  readonly reads: readonly AxRead[];
  readonly actions: readonly AxAction[];
}
```

### `defineComponentAx`

Declares a component's agent side and checks it: unique read and action ids, a field list on each read, a known consent and effect, each action either `agent: true` or `personOnly` with a one-line reason, never both or neither, a reserved id (`isReservedPersonOnly`) never `agent: true`, and a scope only `"self"`, only on an agent action. A problem throws one line with its fix.

```ts
function defineComponentAx(def: { component: string; reads: readonly AxRead[]; actions: readonly AxAction[]; }): ComponentAx
```

Example:

```ts
const mediaTileAx = defineComponentAx({
  component: "MediaTile",
  reads: [{ id: "participant", summary: "Who is in the tile, and their state.", fields: ["name", "muted", "camera", "speaking", "handRaised", "connection"], consent: "none" }],
  actions: [{ id: "pin", summary: "Keep this tile in the person's main view.", effect: "private", agent: true }],
});
const theirs = "You can't turn on the person's microphone, camera or screen; only they can.";
const callControlsAx = defineComponentAx({
  component: "CallControls",
  reads: [],
  actions: [
    { id: "mute", summary: "Mute the person's microphone.", effect: "others", agent: true },
    { id: "leave", summary: "Leave the call.", effect: "others", agent: true },
    { id: "raise-hand", summary: "Raise the person's hand.", effect: "others", agent: true },
    { id: "lower-hand", summary: "Lower the person's hand.", effect: "others", agent: true },
    { id: "unmute", summary: "Turn the person's microphone on.", effect: "others", personOnly: theirs },
    { id: "camera", summary: "Turn the person's camera on.", effect: "others", personOnly: theirs },
    { id: "share", summary: "Share the person's screen.", effect: "others", personOnly: theirs },
    { id: "captions", summary: "Turn captions on.", effect: "private", personOnly: "You can't turn the person's captions on or off; only they can." },
  ],
});
```

### `expectAxRendered`

Asserts that a rendered component links its declaration to its controls: each declared action (or each of `ids`, for a state that shows only some) is a control in `container` carrying `data-ax-action="<id>"`. It throws one line naming the actions with no such control.

```ts
function expectAxRendered(ax: ComponentAx, container: Rendered, options?: { ids?: readonly string[] | undefined; }): void
```

Example:

```ts
render(<CallControls />);
expectAxRendered(callControlsAx, document.body);
```

### `isReservedPersonOnly`

Whether an action id is reserved for the person, by meaning rather than spelling: `Unmute`, `mic-on`, `share-screen`, `startRecording` and `pick_up` are. So is a bare device or capture (`mic`, `camera`, `video`, `screen`, `transcript`) alone or turned on, started, resumed or enabled, and any toggle of one (`toggle-mic`, `mute-toggle`), since a toggle can turn it on. Admitting is the person's whoever it admits (`admit-all`, `let-in-guest`). A call verb (answer, accept, decline, reject, share, record) is the person's alone or beside a call word (`accept-call`, `record-meeting`), and join only beside one (`join-meeting`), so `answer-message`, `share-link`, `record-note` and `join-channel` stay an agent's. One-word compounds such as `sharescreen` and `turnon` count as their parts. An id that only reduces what the person exposes (with off, stop, end, disable or mute in it and no on, toggle, resume, start, enable or unmute, such as `mute`, `camera-off` or `stop-share`) is not reserved, but `mute-off` is, since it unmutes. Captions are not reserved: they are the person's own and private, and a component declares its captions action person-only itself. The declaration and the X-ray both ask it.

```ts
function isReservedPersonOnly(id: string): boolean
```

### `READ_CONSENTS`

A component's agent side: what an agent may read from it and which of its actions an agent may take, each bound to the CLI command that does the same (its twin). `defineComponentAx` declares it once beside the component; the app binds the twins with `bindAx`, because a component does not know the app's command names; the entries `bindAx` returns go into the X-ray's inventory under `ui`, and the X-ray checks every twin from outside. Declare it beside the component whose actions it names, such as `CallControls`, rather than the generic `Toolbar` that holds it: a container exports one only when it has agent reads or actions of its own. A component imports `defineComponentAx` from the kit's `ouro/ax/component` under the registry alias, which the shadcn CLI rewrites to the app's lib alias (`@/lib/ouro/ax/component`), and its item brings `ax-component` with it. The path is not spelled out here, since an installed file may not name the registry.

```ts
const READ_CONSENTS: readonly ["none", "listening"]
```

### `ReadConsent`

What a read needs before an agent gets its content: `none`, or `listening`, for what people say (captions, a transcript), which an agent reads only once the person has asked it to follow that meeting. Until then the twin answers `needs-human`, never the content.

```ts
type ReadConsent = (typeof READ_CONSENTS)[number];
```

### `ReadTwin`

A read's twin: the read command, or, for a read with `push: "filtered"`, the read, the untriggered stream (`push`, which must refuse) and the words trigger that makes it deliver (`trigger`, such as `--on mention`).

```ts
type ReadTwin = string | { read: string; push: string; trigger: string };
```

### `Rendered`

What `expectAxRendered` looks in: a rendered element or document.

```ts
interface Rendered {
  querySelector(selectors: string): unknown;
}
```

### `RESERVED_PERSON_ONLY`

The canonical ids of the actions that are the person's own, whatever the component: their microphone (`unmute`), camera (`camera`, `camera-on`) and screen (`share`), answering, joining or declining a call (`answer`, `decline`), who comes in from the lobby (`admit`), and what is recorded (`record`), each with the reason to declare it with. Aliases count too (`isReservedPersonOnly`): declaring one `agent: true` throws, and the X-ray fails a `ui` entry with one that is not person-only. The reverse stays an agent's, since an agent may always reduce what the person exposes: `mute`, `camera-off`, `stop-share`.

```ts
const RESERVED_PERSON_ONLY: Readonly<Record<string, string>>
```

### `UiEntry`

One line of the X-ray inventory's `ui` list: a bound read or action.

```ts
interface UiEntry {
  component: string;
  kind: "read" | "action";
  id: string;
  effect?: Effect;
  twin?: string;
  personOnly?: string;
  consent?: ReadConsent;
  fields?: string[];
  push?: string;
  trigger?: string;
  refuseAs?: string;
  scope?: AxScope;
}
```
