<!-- source-hash: f3fb53b66abf55ba1b2ac14522e64cbb -->
Provides two composable React primitives for the chat compose-box attachment UX: a `+` add-button rendered inline in the bottom controls row, and a horizontal chip strip rendered above the textarea showing staged file previews with upload progress.

## Key Components

### Exports

| Export | Type | Description |
|--------|------|-------------|
| `ChatAttachmentAddButton` | Component | Borderless `+` icon button for triggering file picker; renders an invisible 28×28 spacer when `attachmentsEnabled` is false to prevent layout shift |
| `ChatAttachmentChipStrip` | Component | Horizontal strip of file chips above the textarea; returns `null` when no attachments are staged |
| `StagedAttachment` | Interface | Shape consumed by the chip strip, mirrors the hub's `useChatAttachments` output |
| `CHAT_ATTACHMENT_MIME_TYPES` | Constant | Allowed MIME types (JPEG, PNG, WebP, GIF, MP4, WebM, QuickTime) — excludes `image/svg+xml` for XSS safety |
| `CHAT_ATTACHMENT_CONCURRENT_UPLOADS_PER_USER` | Constant | Client-side cap of 5 concurrent staged files |

### Internal Helpers

| Name | Purpose |
|------|---------|
| `AttachmentChip` | Single chip: inline blob thumbnail (images), progress bar overlay, filename, status text, × remove button |
| `useObjectUrl` | Hook managing `URL.createObjectURL` lifecycle — revokes on unmount and file change |
| `formatFileSize` | Human-readable byte formatter (B / KB / MB / GB) |
| `extLabel` | Extracts up to 3-char file extension for non-image chip placeholders |

## Usage Example

```typescript
import {
  ChatAttachmentAddButton,
  ChatAttachmentChipStrip,
  StagedAttachment,
} from './chat-attachment-bar'

function Composebox() {
  const { attachments, addFiles, removeFile } = useChatAttachments()
  const { attachmentsEnabled } = useChatIdentity()

  return (
    <div className="flex flex-col">
      {/* Chip strip floats above textarea — hidden when empty */}
      <ChatAttachmentChipStrip
        attachments={attachments}
        onRemove={removeFile}
        disabled={isStreaming}
      />

      <textarea placeholder="Message…" />

      {/* Bottom controls row */}
      <div className="flex items-center gap-2">
        <ChatAttachmentAddButton
          attachmentsEnabled={attachmentsEnabled}
          attachmentsCount={attachments.length}
          onAddFiles={addFiles}
          disabled={isStreaming}
        />
        <ModelDisplay />
      </div>
    </div>
  )
}
```

> **Layout stability note:** `ChatAttachmentAddButton` renders an invisible `<span>` placeholder before `attachmentsEnabled` resolves, preventing the bottom-controls row from shifting when chat identity loads asynchronously (~100–500 ms).