

## Overview [#overview]

The prompt input system consists of:

* **`ChatFooter`** — Positioned footer with gradient overlay and `PromptInput`
* **File picker** — Paperclip button + inline `@file` mentions
* **Instructions picker** — Dialog + inline `/instruction` mentions
* **Streaming cancel** — Stop button replaces send when streaming

All variant footers (`Chat.Floating.Footer`, `Chat.Sidebar.Footer`, `Chat.Fullscreen.Footer`) delegate to `ChatFooter` internally.

## ChatFooter [#chatfooter]

```tsx
<Chat.Floating.Footer
  placeholder="Ask a question..."
  files={{ available: availableFiles }}
  instructions={{ available: availableInstructions }}
>
  <Chat.ScrollToBottom />
</Chat.Floating.Footer>
```

### Props [#props]

| Prop           | Type                | Description                                      |
| -------------- | ------------------- | ------------------------------------------------ |
| `placeholder`  | `string`            | Prompt placeholder text                          |
| `files`        | `FileConfig`        | File picker configuration                        |
| `instructions` | `InstructionConfig` | Instructions picker configuration                |
| `children`     | `ReactNode`         | Additional content (e.g. `ScrollToBottomButton`) |
| `className`    | `string`            | Additional CSS classes                           |

The footer reads `state.isStreaming` and `actions.send` from `ChatProvider` automatically — no need to pass them.

## File Picker [#file-picker]

The `files` prop configures both the paperclip attachment button and inline `@file` mentions.

### `FileConfig` [#fileconfig]

```ts
type FileConfig = {
  available: AttachableFile[];
  showPicker?: boolean;
  value?: AttachableFile[];
  onChange?: (files: AttachableFile[]) => void;
};
```

| Field        | Type               | Default                | Description                          |
| ------------ | ------------------ | ---------------------- | ------------------------------------ |
| `available`  | `AttachableFile[]` | —                      | Files available for selection        |
| `showPicker` | `boolean`          | `available.length > 0` | Show the paperclip attachment button |
| `value`      | `AttachableFile[]` | —                      | Controlled selection state           |
| `onChange`   | `(files) => void`  | —                      | Controlled selection handler         |

### `AttachableFile` [#attachablefile]

```ts
type AttachableFile = {
  id: string;
  name: string;
  size?: number;
  type?: string;
  matters?: { id: string; value: string }[];
};
```

| Field     | Type                              | Description                                                       |
| --------- | --------------------------------- | ----------------------------------------------------------------- |
| `id`      | `string`                          | Unique file identifier                                            |
| `name`    | `string`                          | Display name                                                      |
| `size`    | `number`                          | File size in bytes                                                |
| `type`    | `string`                          | MIME type (e.g. `"application/pdf"`)                              |
| `matters` | `{ id: string; value: string }[]` | Matters the file belongs to (enables matter filter in the picker) |

### Matter Filtering [#matter-filtering]

When files include `matters`, the attachment picker automatically shows a filter dropdown:

```tsx
const files: AttachableFile[] = [
  { id: "1", name: "Contract.pdf", matters: [{ id: "m1", value: "Smith v. Anderson" }] },
  { id: "2", name: "Invoice.pdf", matters: [{ id: "m2", value: "Tech Corp Acquisition" }] },
  { id: "3", name: "Notes.docx" }, // no matters — visible when no filter active
];
```

The picker derives the unique matter list from all files. When no matters exist on any file, the filter button is hidden.

### Uncontrolled (default) [#uncontrolled-default]

Just pass `available` — the component manages selection internally:

```tsx
<Chat.Sidebar.Footer
  files={{ available: filesFromHost }}
/>
```

### Controlled [#controlled]

Pass `value` + `onChange` when you need to manage selection externally:

```tsx
const [selected, setSelected] = useState<AttachableFile[]>([]);

<Chat.Sidebar.Footer
  files={{
    available: filesFromHost,
    value: selected,
    onChange: setSelected,
  }}
/>
```

## Instructions Picker [#instructions-picker]

The `instructions` prop configures the instructions dialog and inline `/instruction` mentions.

### `InstructionConfig` [#instructionconfig]

```ts
type InstructionConfig = {
  available: AttachableInstruction[];
  showPicker?: boolean;
  value?: AttachableInstruction[];
  onChange?: (instructions: AttachableInstruction[]) => void;
  onConfigureClick?: () => void;
};
```

| Field              | Type                      | Default                | Description                                            |
| ------------------ | ------------------------- | ---------------------- | ------------------------------------------------------ |
| `available`        | `AttachableInstruction[]` | —                      | Available instructions                                 |
| `showPicker`       | `boolean`                 | `available.length > 0` | Show instructions in the picker dialog                 |
| `value`            | `AttachableInstruction[]` | —                      | Controlled selection state                             |
| `onChange`         | `(instructions) => void`  | —                      | Controlled selection handler                           |
| `onConfigureClick` | `() => void`              | —                      | "Configure" button handler (e.g. navigate to settings) |

### `AttachableInstruction` [#attachableinstruction]

```ts
type AttachableInstruction = {
  id: string;
  name: string;
  description: string;
  content: string;
};
```

### Usage [#usage]

```tsx
<Chat.Fullscreen.Footer
  instructions={{
    available: instructionsFromHost,
    showPicker: true,
    onConfigureClick: () => router.push("/settings/instructions"),
  }}
/>
```

Users can:

* Select instructions via the picker dialog (switch to Instructions via the title dropdown)
* Type `/` in the prompt to trigger inline instruction mentions
* View selected instructions as chips above the prompt input

## Streaming Control [#streaming-control]

The prompt input automatically shows a stop button when `isStreaming` is true (read from `ChatProvider`). The stop button replaces the send arrow with a square stop icon.

```
isStreaming = false → [Send arrow button]
isStreaming = true  → [Stop square button] → calls actions.cancelStreaming()
```

The stop button:

* Appears when any message has `status: "running"` (set `isStreaming` on the variant root)
* Stays enabled even when the input is empty
* Returns to the send button when streaming completes

## Output: MessagePayload [#output-messagepayload]

When the user submits, the footer emits a `MessagePayload` via `actions.send()`:

```ts
type MessagePayload = {
  content_text: string;     // Plain text with instruction content expanded
  content_html: string;     // HTML with attachment chips
  submitted_at: number;     // Timestamp
  attachments: Attachment[];           // All attachments (files, containers, persons)
  attachments_by_type: AttachmentsByType; // IDs grouped by type
};
```

### Attachment Sources [#attachment-sources]

Each attachment has a `sources` array indicating how it was added:

| Source                   | Meaning                                       | In `content_text`?           |
| ------------------------ | --------------------------------------------- | ---------------------------- |
| `["inline"]`             | Mentioned via `@file` in the editor           | Yes, as `§{file_id}§` marker |
| `["external"]`           | Attached via paperclip button                 | No                           |
| `["inline", "external"]` | Both mentioned inline and attached via button | Yes                          |

**Instructions** are NOT attachments — their content is injected directly into `content_text`.

For the full `MessagePayload` shape and attachment type definitions, see [Data Contracts](./data-contracts).
