# zidane-ui/chat/rich-composer

Import: `import { … } from 'zidane-ui/chat/rich-composer'`. Source: [ui/src/chat/rich-composer/index.ts](https://github.com/Tahul/zidane/blob/main/ui/src/chat/rich-composer/index.ts).

## Components

### `PlusButton`

Component.

The + button: attach files.

```ts
function PlusButton(props: PlusButtonProps): Element
```

**Parameters**

- `props` (`PlusButtonProps`): The click handler.

**Returns** `Element`

**Props**

- `onClick` (`() => void`): Opens the file picker.

Source: [ui/src/chat/rich-composer/composer-controls.tsx:29](https://github.com/Tahul/zidane/blob/main/ui/src/chat/rich-composer/composer-controls.tsx#L29)

### `RichComposer`

Component.

`zidane-ui/chat/rich-composer`: the desktop app's Tiptap composer (mentions, commands, ghost text, shared drafts, prompt history). Its own entry, so an app that does not render it never loads it.

```ts
function RichComposer(props: RichComposerProps): Element
```

**Parameters**

- `props` (`RichComposerProps`): What the composer edits and sends, and its slots.

**Returns** `Element`

**Props**

- `activeSessionId?` (`string | null`): The session the composer writes to (`null`: a new chat).
- `attachmentLabel?` (`AttachmentLabelFn`): The icon and name an attachment tile shows.
- `attachmentsEnabled?` (`boolean`): Pasted and dropped files become attachments (off: images and long pastes stay inline, drops are ignored). Default true.
- `badges?` (`ReactNode`): The badge strip (`ConversationBadges`, the kit's `ComposerBadges`): the
  band at the top of the tray ring, above the card, the git bar's mirror.
  Built by `ConversationComposer`, so a keystroke here never re-renders it.
- `className?` (`string`): Classes merged on the root.
- `commandArgs?` (`ComposerCommandArgs`): The commands' fixed argument values (the command badge, the `/` popover's arguments).
- `disabled` (`boolean`): No folder picked: read-only, with its own placeholder.
- `fileRoot?` (`string`): Where `@` file mentions list from, when it isn't `folderPath`: a sandbox
  session's virtual root (`SidebarSession.workspace`).
- `folderPath` (`string | null`): The folder the session runs in (`''`: none; `null`: none picked yet).
- `footer?` (`ReactNode`): Rendered in the tray under the card (the desktop app's git bar).
- `hostId?` (`string | null`): The server the session (or the draft) runs on: `/` commands, their
  arguments and the ghost text come from it; this window's extension
  popups don't apply there. Absent = This computer.
- `inlineAutocomplete?` (`boolean`): Inline ghost-text autocomplete (`ComposerHost.autocomplete`). Default false.
- `leadingActions?` (`ReactNode`): Rendered next to the `+` attachment button — typically the inline
  AgentPicker. Kept as a slot so the Chatbox doesn't need to know
  which app-level controls the user wants inline.
- `messageDelivery?` (`MessageDelivery`): What Enter does with a prompt while `running`: `steer` it into the run or `queue` it for when the run ends.
  Alt+Enter does the other. Pass the user's "Messages during a run" setting. Default `queue`.
- `onSearchNavigate?` (`(direction: -1 | 1) => void`): Hops to the next (`1`) or previous (`-1`) match.
- `onSearchQueryChange?` (`(query: string | null) => void`): The search query as typed (`null`: search mode exited).
- `onSend` (`(text: string, attachments: ComposerAttachment[], skills: string[], opts?: { delivery?: MessageDelivery; keepDraft?: boolean }) => void | Promise<void>`): Sends a prompt. `opts.delivery` is set while `running`. Rejecting puts the prompt back in an empty composer.
- `onStop?` (`() => void`): Stops the run.
- `promptSuggestions?` (`boolean`): The next-prompt suggestion as a ghost while the composer is empty (`ComposerHost.suggestions`). Default false.
- `remoteSearchApply?` (`{ active: boolean; query: string; seq: number } | null`): Shared-search application (daemon mode) — Center threads the active
  session's `search:changed` signals here so the composer's `?` mode
  mirrors a search driven from another window. `seq` forces the apply
  effect to re-fire on every signal (values alone can repeat).
- `running?` (`boolean`): When true, the send button is replaced by a stop button that calls `onStop`.
- `scopeKey?` (`string | null`): The composer's scope on the composer bus (`composer-bus.ts`): pushes aimed at it land here.
- `searchOverlayOpen?` (`boolean`): True while the ⌘F overlay is open. The overlay and the composer
  mode drive the same document-wide highlight registry, so opening
  the overlay force-exits the composer's search mode.
- `searchStatus?` (`{ activeIndex: number; capped: boolean; count: number; hasNeedle: boolean } | null`): The search engine's counter while in search mode: matches, whether capped, the active one, whether a query is typed.
- `siteIcons?` (`ChatSiteIcons`): Site icons for link badges. Absent: links show the globe.
- `style?` (`CSSProperties`): Styles on the root.
- `textPreview?` (`ComponentType<TextPreviewSlotProps>`): What a text attachment opens as in the lightbox. Default: the kit's `TextPreview`.
- `trailingActions?` (`ReactNode`): Rendered on the right side of the controls row, immediately before
  the Send / Stop button — the per-session context gauge + cost pill
  cluster lives here. Slot keeps Chatbox agnostic of the actual
  pills (App.tsx owns the state, Center.tsx threads the node in).
  Children are responsible for their own collapse behavior; the
  Chatbox surface declares a CSS container so container queries on
  descendants can react to the prompt's inline-size.

Source: [ui/src/chat/rich-composer/RichComposer.tsx:209](https://github.com/Tahul/zidane/blob/main/ui/src/chat/rich-composer/RichComposer.tsx#L209)

### `SendStopButton`

Component.

Unified Send / Stop control — one button element that never unmounts, so
the idle→streaming→hover-stop states crossfade in place (no layout jump;
constant 32×32 footprint). Three visual states, all CSS-driven via the
`zg-sendstop` classes in rich-composer.css:

- idle (`data-state="send"`) — a lit accent sphere (the daemon orb's
  drawing) + send glyph; asleep while the prompt is empty.
- streaming (`data-state="run"`) — the same lit sphere, its glyph turned
  into a thin spinner ring: "the agent is working", not an alarm.
- streaming + hover/focus-visible — the stop affordance surfaces: the
  sphere eases to the danger colour and a square stop glyph fades in.

Clicking while streaming always stops (the hover state is a reveal, not
a gate — touch/keyboard users get the same action without hover).

```ts
function SendStopButton(props: SendStopButtonProps): Element
```

**Parameters**

- `props` (`SendStopButtonProps`): The run and draft state, and the handlers.

**Returns** `Element`

**Props**

- `canSend` (`boolean`): The draft can be sent.
- `onSend` (`() => void`): Sends the draft.
- `onStop?` (`() => void`): Stops the run.
- `running` (`boolean`): A run streams: the button stops it.

Source: [ui/src/chat/rich-composer/composer-controls.tsx:75](https://github.com/Tahul/zidane/blob/main/ui/src/chat/rich-composer/composer-controls.tsx#L75)

## Interfaces

### `PlusButtonProps`

Interface.

Props for [`PlusButton`](https://zidane.si/docs/reference/ui-chat-rich-composer.md#PlusButton).

```ts
interface PlusButtonProps
```

**Properties**

- `onClick` (`() => void`): Opens the file picker.

Source: [ui/src/chat/rich-composer/composer-controls.tsx:19](https://github.com/Tahul/zidane/blob/main/ui/src/chat/rich-composer/composer-controls.tsx#L19)

### `RichComposerProps`

Interface.

Props for [`RichComposer`](https://zidane.si/docs/reference/ui-chat-rich-composer.md#RichComposer).

```ts
interface RichComposerProps
```

**Properties**

- `activeSessionId?` (`string | null`): The session the composer writes to (`null`: a new chat).
- `attachmentLabel?` (`AttachmentLabelFn`): The icon and name an attachment tile shows.
- `attachmentsEnabled?` (`boolean`): Pasted and dropped files become attachments (off: images and long pastes stay inline, drops are ignored). Default true.
- `badges?` (`ReactNode`): The badge strip (`ConversationBadges`, the kit's `ComposerBadges`): the
  band at the top of the tray ring, above the card, the git bar's mirror.
  Built by `ConversationComposer`, so a keystroke here never re-renders it.
- `className?` (`string`): Classes merged on the root.
- `commandArgs?` (`ComposerCommandArgs`): The commands' fixed argument values (the command badge, the `/` popover's arguments).
- `disabled` (`boolean`): No folder picked: read-only, with its own placeholder.
- `fileRoot?` (`string`): Where `@` file mentions list from, when it isn't `folderPath`: a sandbox
  session's virtual root (`SidebarSession.workspace`).
- `folderPath` (`string | null`): The folder the session runs in (`''`: none; `null`: none picked yet).
- `footer?` (`ReactNode`): Rendered in the tray under the card (the desktop app's git bar).
- `hostId?` (`string | null`): The server the session (or the draft) runs on: `/` commands, their
  arguments and the ghost text come from it; this window's extension
  popups don't apply there. Absent = This computer.
- `inlineAutocomplete?` (`boolean`): Inline ghost-text autocomplete (`ComposerHost.autocomplete`). Default false.
- `leadingActions?` (`ReactNode`): Rendered next to the `+` attachment button — typically the inline
  AgentPicker. Kept as a slot so the Chatbox doesn't need to know
  which app-level controls the user wants inline.
- `messageDelivery?` (`MessageDelivery`): What Enter does with a prompt while `running`: `steer` it into the run or `queue` it for when the run ends.
  Alt+Enter does the other. Pass the user's "Messages during a run" setting. Default `queue`.
- `onSearchNavigate?` (`(direction: -1 | 1) => void`): Hops to the next (`1`) or previous (`-1`) match.
- `onSearchQueryChange?` (`(query: string | null) => void`): The search query as typed (`null`: search mode exited).
- `onSend` (`(text: string, attachments: ComposerAttachment[], skills: string[], opts?: { delivery?: MessageDelivery; keepDraft?: boolean }) => void | Promise<void>`): Sends a prompt. `opts.delivery` is set while `running`. Rejecting puts the prompt back in an empty composer.
- `onStop?` (`() => void`): Stops the run.
- `promptSuggestions?` (`boolean`): The next-prompt suggestion as a ghost while the composer is empty (`ComposerHost.suggestions`). Default false.
- `remoteSearchApply?` (`{ active: boolean; query: string; seq: number } | null`): Shared-search application (daemon mode) — Center threads the active
  session's `search:changed` signals here so the composer's `?` mode
  mirrors a search driven from another window. `seq` forces the apply
  effect to re-fire on every signal (values alone can repeat).
- `running?` (`boolean`): When true, the send button is replaced by a stop button that calls `onStop`.
- `scopeKey?` (`string | null`): The composer's scope on the composer bus (`composer-bus.ts`): pushes aimed at it land here.
- `searchOverlayOpen?` (`boolean`): True while the ⌘F overlay is open. The overlay and the composer
  mode drive the same document-wide highlight registry, so opening
  the overlay force-exits the composer's search mode.
- `searchStatus?` (`{ activeIndex: number; capped: boolean; count: number; hasNeedle: boolean } | null`): The search engine's counter while in search mode: matches, whether capped, the active one, whether a query is typed.
- `siteIcons?` (`ChatSiteIcons`): Site icons for link badges. Absent: links show the globe.
- `style?` (`CSSProperties`): Styles on the root.
- `textPreview?` (`ComponentType<TextPreviewSlotProps>`): What a text attachment opens as in the lightbox. Default: the kit's `TextPreview`.
- `trailingActions?` (`ReactNode`): Rendered on the right side of the controls row, immediately before
  the Send / Stop button — the per-session context gauge + cost pill
  cluster lives here. Slot keeps Chatbox agnostic of the actual
  pills (App.tsx owns the state, Center.tsx threads the node in).
  Children are responsible for their own collapse behavior; the
  Chatbox surface declares a CSS container so container queries on
  descendants can react to the prompt's inline-size.

Source: [ui/src/chat/rich-composer/RichComposer.tsx:75](https://github.com/Tahul/zidane/blob/main/ui/src/chat/rich-composer/RichComposer.tsx#L75)

### `SendStopButtonProps`

Interface.

Props for [`SendStopButton`](https://zidane.si/docs/reference/ui-chat-rich-composer.md#SendStopButton).

```ts
interface SendStopButtonProps
```

**Properties**

- `canSend` (`boolean`): The draft can be sent.
- `onSend` (`() => void`): Sends the draft.
- `onStop?` (`() => void`): Stops the run.
- `running` (`boolean`): A run streams: the button stops it.

Source: [ui/src/chat/rich-composer/composer-controls.tsx:46](https://github.com/Tahul/zidane/blob/main/ui/src/chat/rich-composer/composer-controls.tsx#L46)
