<!-- source-hash: 3113ae48366d1440e4d993a3e0404da3 -->
Shared utility providing a single source of truth for formatting and parsing chat attachment markdown between the host-side bubble renderer and server-side LLM input cleaner, preventing drift between the two systems.

## Key Components

| Export | Type | Description |
|--------|------|-------------|
| `CHAT_ATTACHMENT_VIEW_URL_PREFIX` | `const` | Hub-default proxy URL prefix for attachment view routes |
| `CHAT_ATTACHMENT_VIEW_TOKEN_QUERY_PARAM` | `const` | Query param name (`t`) for the HMAC view token |
| `ANTHROPIC_SUPPORTED_IMAGE_MIME` | `const` | Image MIME types renderable as Anthropic image blocks |
| `ChatAttachment` | `interface` | Wire shape for a single chat attachment |
| `buildChatAttachmentViewUrl` | `function` | Constructs the signed proxy URL for an attachment |
| `escapeMarkdownInline` | `function` | Sanitizes filenames against markdown injection attacks |
| `formatChatAttachmentMarkdownForBubble` | `function` | Produces the `![]()` or `[Attached: ...]` markdown line |
| `CHAT_ATTACHMENT_MARKDOWN_PATTERN` | `RegExp` | Anchored regex matching both image and link attachment forms |
| `stripChatAttachmentMarkdown` | `function` | Strips attachment markdown from text and extracts storage paths |

## Usage Example

```typescript
import {
  CHAT_ATTACHMENT_VIEW_URL_PREFIX,
  formatChatAttachmentMarkdownForBubble,
  stripChatAttachmentMarkdown,
} from './chat-attachment-markdown'

// Host-side: format attachment for user bubble (pass runtime prefix)
const attachment: ChatAttachment = {
  storagePath: 'org-123/chat/screenshot.png',
  viewToken: 'hmac-signed-token',
  contentType: 'image/png',
  fileName: 'screenshot.png',
  size: 204800,
}

// Embedded app overrides prefix; hub uses the constant directly
const markdown = formatChatAttachmentMarkdownForBubble(
  attachment,
  'https://hub.example.com/api/storage/view/chat-attachments/',
)
// → "\n\n![screenshot.png](https://hub.example.com/api/storage/view/...?t=...)"

// Server-side: strip attachment lines before sending to LLM
const userMessage = `Please review this\n\n![screenshot.png](/api/storage/view/chat-attachments/org-123/chat/screenshot.png?t=abc)`
const { stripped, storagePaths } = stripChatAttachmentMarkdown(userMessage)
// stripped    → "Please review this"
// storagePaths → ["org-123/chat/screenshot.png"]
```

## Security Notes

- **Markdown injection**: `escapeMarkdownInline` escapes `[`, `]`, `(`, `)`, `\`, `<`, `>`, and `"` from filenames — preventing a crafted name like `screenshot](https://evil.com).png` from hijacking the image URL.
- **Anthropic image blocks**: raw Supabase signed URLs are used (not the proxy) because Anthropic's fetcher does not reliably follow `302` redirects.
- **View tokens**: HMAC-signed server-side; the client never generates them.