# Fix: Extract message content from v3 bubbleId entries in headers-only mode

## Context
New Cursor sessions use "headers-only" storage mode where `composerData.conversation[]` is empty. The actual message content lives in separate `bubbleId:<composerId>:<bubbleId>` entries with a `text` field (confirmed on real data). The transform already loads these entries and reads `tokenCount` and `createdAt` from them, but ignores `text` — producing messages with `content=None`.

## Changes

### 1. `qc_trace/schemas/cursor/transform_vscdb.py` — `_transform_headers_only()`

After looking up `bubble_entry` (line 297-299), extract content:

```python
content = bubble_entry.get("text") or None if bubble_entry else None
```

Pass `content=content` to all three `NormalizedMessage()` constructors (user at line 322, assistant at line 334, capability at line 309).

For assistant bubbles, also extract thinking blocks:

```python
thinking = None
if bubble_entry and bubble_type == 2:
    blocks = bubble_entry.get("allThinkingBlocks") or []
    if blocks:
        thinking = "\n".join(b.get("thinking", "") for b in blocks if b.get("thinking"))
    thinking = thinking or None
```

### 2. `tests/test_cursor_vscdb_transform.py`

Update the existing headers-only test fixture bubble entries to include `"text": "some message"` fields, and assert that `content` is populated on the resulting `NormalizedMessage` objects.

## Verify
1. `pytest tests/test_cursor_vscdb_transform.py -v`
2. `pytest tests/ -q` (full suite)
3. Release, wait for daemon update, check prod for "waldb" message
