# Rich composer

> The desktop app's Tiptap composer as a React component, with completions, typed badges, ghost text, prompt modes, history, attachments and a draft shared across windows, over a composer host.

`zidane-ui/chat/rich-composer` holds the desktop app's composer: `RichComposer`. It completes `@` files, `/` commands and skills, `#` chats and extension triggers, shows links and commands as badges while you type, offers ghost text and the next prompt, has a shell mode (`!`) and a transcript search mode (`?`), recalls earlier prompts with ↑ / ↓, stages attachments, and shares its draft with every window on the same session. It reads a `ComposerHost`. Sending stays yours: `onSend` gets the text, the attachments and the skills it activates.

It is its own entry point, so an app that uses only [`Composer`](https://zidane.si/docs/ui-composer.md) never loads it. The previews below run on the fixture host. Type `/` or `@` to open a popover, or `git sta` to see ghost text.

## Mount it

Wrap the composer in a `ComposerHostProvider` and a `PromptHistoryProvider` (both from `zidane-ui/chat/composer`). Pass the session, the folder, and what sending does.

```tsx
import 'zidane-ui/styles.css'
import { ComposerHostProvider, PromptHistoryProvider } from 'zidane-ui/chat/composer'
import { RichComposer } from 'zidane-ui/chat/rich-composer'

export function Prompt({ host, sessionId, folder, send }) {
  return (
    <ComposerHostProvider host={host}>
      <PromptHistoryProvider>
        <RichComposer
          disabled={false}
          folderPath={folder}
          activeSessionId={sessionId}
          hostId={null}
          onSend={(text, attachments, skills) => send({ text, attachments, skills })}
          inlineAutocomplete
        />
      </PromptHistoryProvider>
    </ComposerHostProvider>
  )
}
```

`AppHostProvider` (`zidane-ui/app`) puts `host.composer` in a `ComposerHostProvider` for you. Reject from `onSend` to put the prompt back in an empty composer.

Slots and options:

- `leadingActions` and `trailingActions` sit beside the + button and before Send (the desktop app puts its pickers and status pills there). `badges` is the band above the card. `footer` sits in the tray under it.
- `attachmentsEnabled`, `inlineAutocomplete` and `promptSuggestions` turn the attachment folding, the ghost text and the next-prompt ghost on or off.
- `scopeKey` names the composer on the composer bus (`pushComposerPrompt`, `pushComposerAttachment`, `pushComposerMention` from `zidane-ui/chat/composer`), so a panel can push content into it.
- `commandArgs` shares the commands' argument values with the rest of your app, `siteIcons` draws favicons in link badges, and `attachmentLabel` and `textPreview` set how attachment tiles and text previews look.
- The search mode reports its query with `onSearchQueryChange` and takes the match counter back as `searchStatus`.

## Pick a host

| Host | From | Talks to |
| - | - | - |
| `createServerComposerHost({ client })` | `zidane-ui/adapters` | zidane-server. Pass the client of your chat host (`createServerHost(…).client`). |
| `createFixtureComposerHost(options)` | `zidane-ui/chat/testing` | Memory, for demos and tests. |
| Your own | `zidane-ui/chat/headless` | Anything that implements `ComposerHost`. |

zidane-server runs the extensions' completions (`completions.providers`, `completions.suggest`, `completions.submit`), so the server host offers the extension members. `@` lists a folder's files on the server (`files.list`, read again after a tool result), `#` its sessions (`sessions.listMeta`, read again on `sessions:changed`), and `completions.locationOf` is the folder's checkout and project there (`files.location`). It has no call to resolve ghost text for a new chat: the server host answers none before the chat has a session.

## What a host offers

Every member is optional. A member you leave out turns off what needs it.

| Member | What it does | Left out |
| - | - | - |
| `drafts` | The draft per session, its staged files, and the edits other windows make | Each window keeps its own draft |
| `completions.files` | The files `@` lists | `@` lists nothing |
| `completions.commands`, `commandArgs` | The `/` commands and their argument values | `/` lists skills only |
| `completions.chats` | The sessions `#` can reference | `#` lists nothing |
| `completions.watchSkills` | The enabled skills, as they change | `/` lists commands only |
| `completions.watchExtensionProviders`, `extensionItems`, `submit` | Extension completion triggers, their items, and their submit hooks | No extension popovers |
| `completions.locationOf` | A folder's checkout and project roots | `#` cannot scope to the project |
| `autocomplete` | Ghost text, and ghost text another window resolved | No ghost text |
| `suggestions` | The next prompt once a run ends, and when it goes stale | No next-prompt ghost |

The contract every implementation runs is `describeComposerHostContract` in `zidane-ui/chat/testing/contract`.

## Related

- [Composer](https://zidane.si/docs/ui-composer.md): the kit's plain composer, its pickers and its badge strip.
- [The desktop composer](https://zidane.si/docs/desktop-composer.md): what the composer does in Zidane Desktop.
