<!-- source-hash: 770809a15a2a56eec700f22e576bdc99 -->
Unified "Ask / Display" action button rendered across source chips, inline cards, and search-result list rows. Replaces three divergent ad-hoc implementations with a single `Button`-backed primitive that keeps DOM shape, accessibility attributes, and hover behavior in one place.

## Key Components

### `SourceActionButton`
The sole export. Returns `null` when either `chatRef` or `onDiscuss` is absent, making the guard declarative at the call site.

### `SourceActionDensity`
Union type `'inline' | 'card' | 'list-row'` that selects the visual variant via the `DENSITY_STYLES` map.

### `DENSITY_STYLES`
Internal record keyed by `SourceActionDensity`. Each entry carries `size`, `showLabel`, `iconSize`, and `className`. Adding a fourth surface requires one new entry here — no changes to the render path.

### Icon selection
Label-driven: `'Display'` renders `FileText` (verbatim-content semantics); any other label renders `MessageSquare` (conversational semantics).

## Usage Example

```typescript
// Inline chip — icon only, small footprint
<SourceActionButton
  chatRef={{ id: 'row-42', title: 'Ticket #42' }}
  onDiscuss={(ref) => openChat(ref)}
  density="inline"
/>

// Card — shows "Ask" label beside icon
<SourceActionButton
  chatRef={row}
  onDiscuss={handleDiscuss}
  density="card"
/>

// List row with custom label
<SourceActionButton
  chatRef={row}
  onDiscuss={handleDiscuss}
  density="list-row"
  label="Display"
/>

// Hidden — chatRef is null, renders nothing
<SourceActionButton
  chatRef={null}
  onDiscuss={handleDiscuss}
/>
```