# Chat Streaming & Background Tasks - Status: IN PROGRESS

**Date:** 2026-03-14
**Status:** Partially working, root cause identified

## Summary

Chat streaming in the macOS app renders responses slowly and the background task handoff has several issues. Root cause identified via logging.

## Root Cause: `streamingMessageId` Race Condition

The `streamingMessageId` (which links incoming WebSocket deltas to the correct chat message) is being cleared between when the message is sent and when the first delta arrives.

### Evidence from logs:

```
[03:37:40] [Send] streamingMessageId SET to 12AE5143...
[03:37:40] [Send] sendToGatewayAPI returned
[03:37:45] [Chat] SKIPPED: streamId=false, msg=true, sess=true  ← streamId is nil!
```

The ID is set at 03:37:40 but by 03:37:45 (when the first delta arrives), it's `nil`.

### Likely cause:

The `@Observable` macro on `ConversationViewModel` may be interfering with the `streamingMessageId` property when `updateCurrentSession()` triggers SwiftUI re-evaluation. The property is marked `private var` but not `@ObservationIgnored`, so the observation system may be involved in clearing it.

### Fix needed:

Mark `streamingMessageId` as `@ObservationIgnored` to prevent the observation system from interfering:

```swift
@ObservationIgnored
private var streamingMessageId: UUID?
```

## Other Issues Found

### 1. Slow streaming (partially fixed)

- **Root cause:** Agent WebSocket events were using incremental `delta` field (3-5 chars per token) instead of cumulative `text` field.
- **Fix applied:** `handleAgentEvent` now prefers the `text` field when available.
- **Remaining:** The `streamingMessageId` race means deltas are skipped entirely, so streaming appears to not work at all.

### 2. Chat delta parsing (fixed)

- **Root cause:** `handleChatEvent` used wrong key path (`payload["delta"]["text"]` vs actual `payload["message"]["content"][0]["text"]`).
- **Fix applied:** Corrected the key path.

### 3. Background task handoff

- **Current behavior:** 30s timer fires, creates background task, but response still shows in main chat (when streaming works at all).
- **Fix applied:** Handoff is now silent (no "Continuing in background..." text), final response replaces the partial message.
- **Remaining:** Need to verify once streaming is fixed.

### 4. Auto-scroll on send

- **Not yet fixed:** Chat doesn't scroll down when you send a message.

### 5. Thinking indicator

- **Partially working:** Bouncing dots show for `.thinking` status but only on first message due to the `streamingMessageId` race.

## Files Modified

| File                          | Changes                                                                |
| ----------------------------- | ---------------------------------------------------------------------- |
| `WebSocketService.swift`      | Fixed chat delta parsing, agent event uses cumulative text             |
| `ConversationViewModel.swift` | Batched UI updates, handoff logic, thinking indicator support, logging |
| `ConversationView.swift`      | ThinkingDotsView, text input word wrap, PTT disabled                   |
| `MainWindow.swift`            | Task list panel open by default                                        |

## Next Steps

1. **Fix `streamingMessageId` race** - Add `@ObservationIgnored` annotation
2. **Fix auto-scroll** - Scroll to bottom on message send
3. **Verify streaming speed** - Once messages are properly linked
4. **Verify background handoff** - Once streaming works
5. **Remove debug logging** - Clean up VoiceLog calls from chat path
