

## Composition Model [#composition-model]

Every chat surface follows the same layered composition:

```
Chat.Floating / Chat.Sidebar / Chat.Fullscreen
  └── ChatProvider ({ state, actions, meta })
      └── PanelProvider + ArtifactPanelSync
          └── VariantLayoutProvider (variant-specific state)
              └── Shell
                  ├── Header (close, expand, history, new chat)
                  ├── Content / MessageList
                  │   ├── MessageEntry (role dispatch)
                  │   │   ├── MessageUser (bodyHtml + attachments)
                  │   │   ├── MessageAgent (ChunkList + actions)
                  │   │   └── MessageSystem (ChunkList)
                  │   └── ScrollAnchor
                  └── Footer (PromptInput + ScrollToBottom)
```

In fullscreen, the panel provider is `FullscreenLayoutProvider` which renders panels as resizable side panels. In floating and sidebar, `SheetPanelProvider` + `SheetPanelOutlet` render panels in a Sheet overlay.

All variants share the same `ChatProvider` context, message primitives, and chunk registry. The variant layer adds layout-specific behavior (floating animation, sidebar close, fullscreen panels).

## Shared Context: ChatProvider [#shared-context-chatprovider]

All variants wrap their children in a `ChatProvider` that exposes a `{ state, actions, meta }` context:

```ts
type ChatContextValue = {
  state: {
    conversationId: string | undefined;
    isStreaming: boolean;
  };
  actions: {
    send: (payload: MessagePayload, ctx?: OnSendCtx) => void | Promise<void>;
    cancelStreaming: () => void;
    newConversation: () => void;
  };
  meta: {
    registry: ChunkRegistry;
  };
};
```

Access it from any descendant via `useChatContext()`.

### ChatProvider Props [#chatprovider-props]

| Prop                | Type                     | Default | Description                                                |
| ------------------- | ------------------------ | ------- | ---------------------------------------------------------- |
| `children`          | `ReactNode`              | —       | Child components                                           |
| `conversationId`    | `string`                 | —       | Active conversation ID                                     |
| `onSend`            | `(payload, ctx) => void` | —       | Message submission handler                                 |
| `onNewConversation` | `() => void`             | —       | New conversation handler                                   |
| `isStreaming`       | `boolean`                | `false` | Whether an agent response is in progress                   |
| `onCancelStreaming` | `() => void`             | —       | Cancel streaming handler                                   |
| `registry`          | `ChunkRegistry`          | auto    | Custom chunk registry (defaults to all built-in renderers) |

### Default chunk registry [#default-chunk-registry]

When no `registry` prop is provided, `ChatProvider` creates one automatically and registers all built-in chunk renderers (`markdown`, `reasoning`, `task`, `table`, `card`, `alert`, `references`, `artifact`). Pass a custom registry to add your own renderers or filter built-ins — see the [Chunk Registry](./custom-chunks).

## Variant Layout Contexts [#variant-layout-contexts]

Each variant adds its own layout context on top of `ChatProvider`. These are independent — no variant knows about the others.

| Variant    | Hook                    | Key State                                                    |
| ---------- | ----------------------- | ------------------------------------------------------------ |
| Floating   | `useFloatingLayout()`   | `isCollapsed`, `isExpanded`, `showChrome`, `isPromptEngaged` |
| Sidebar    | `useSidebarLayout()`    | `isOpen` + `open()`, `close()`, `toggle()` actions           |
| Fullscreen | `useFullscreenLayout()` | `panels`, `activePanelIds`, `conversationNavOpen`            |

All layout contexts follow the same `{ state, actions, meta }` interface pattern. `meta.variant` identifies which variant is active (`"floating"`, `"sidebar"`, or `"fullscreen"`).

## Data Flow [#data-flow]

The library is data-layer agnostic. The data flow is:

```
Your data store (TanStack DB, Redux, SWR, etc.)
  ↓ ChatMessageView[] + ChunkRow[]
Chat components (render)
  ↓ MessagePayload (on submit)
Your transport layer (API calls, streaming)
  ↓ Write updates back to data store
```

Cubitt never reads from or writes to a data store directly. It takes normalized rows as props and emits structured payloads via callbacks. See [Data Contracts](./data-contracts) for the full type definitions.

## Compound Component Pattern [#compound-component-pattern]

The `Chat` namespace uses `Object.assign` to compose variants and shared primitives into a single export:

```tsx
import { Chat } from "@tilt-legal/cubitt-components/composites";

// Variants (each with sub-components)
<Chat.Floating>       // Chat.Floating.Shell, .Header, .Content, .Footer
<Chat.Sidebar>       // Chat.Sidebar.Shell, .Header, .Content, .Footer
<Chat.Fullscreen>    // Chat.Fullscreen.Shell, .MessageList, .Footer, .Nav

// Shared primitives
<Chat.MessageEntry>
<Chat.MessageList>
<Chat.MessageBubble>
<Chat.HtmlRenderer>
<Chat.ScrollAnchor>
<Chat.ScrollToBottom>
<Chat.ChunkList>
<Chat.PromptInput>

// Shared provider (for advanced usage)
<Chat.Provider>

// Hooks
Chat.useChatContext()
Chat.useChatSelection()
Chat.useCitationSync()

// Registry
Chat.createChunkRegistry()
Chat.registerChunk()
Chat.registerDefaultChunkRenderers()
Chat.useChunkRegistry()
```

All items are also available as named exports for tree-shaking:

```tsx
import {
  FloatingChat,
  MessageEntry,
  useChatContext,
} from "@tilt-legal/cubitt-components/composites";
```
