<!-- source-hash: 1d656526a500eeade83769cf600951b1 -->
React context providing chat panel affordances to deeply nested descendants without prop drilling.

## Key Components

| Export | Type | Description |
|--------|------|-------------|
| `ChatPanelHandle` | Interface | Shape of the context value; exposes `closeChat()` |
| `ChatPanelContext` | `Context<ChatPanelHandle \| null>` | Raw context object; defaults to `null` when no provider is mounted |
| `useChatPanel` | Hook | Optional consumer; returns `null` safely outside a provider |

## Usage Example

**Provider** (in `EmbeddableChat` or equivalent shell):

```typescript
import { ChatPanelContext } from './chat-panel-context'

function EmbeddableChat() {
  const handleClose = () => { /* dismiss panel */ }

  return (
    <ChatPanelContext.Provider value={{ closeChat: handleClose }}>
      {/* card dispatcher and all descendants */}
    </ChatPanelContext.Provider>
  )
}
```

**Consumer** (e.g. `BlogCard`, `ProgramCard`, any inline card):

```typescript
import { useChatPanel } from './chat-panel-context'

function BlogCard({ href }: { href: string }) {
  const chatPanel = useChatPanel()

  const handleClick = (e: React.MouseEvent) => {
    const isSameTab = !e.metaKey && !e.ctrlKey
    if (isSameTab) {
      chatPanel?.closeChat() // null-safe — works outside provider too
    }
  }

  return <a href={href} onClick={handleClick}>Read more</a>
}
```

## Notes

- Returns `null` when consumed outside a provider (tests, standalone card renders) — always use optional chaining (`chatPanel?.closeChat()`).
- Intentionally minimal; add new affordances to `ChatPanelHandle` only when multiple card types need the same panel-level behavior.