feat: hardening, error states, interruption handling, a11y (Phase 8)

This commit is contained in:
snowspeeder committed 2026-08-30 09:37:05 -04:00
1 parent 9f1f26ba54
commit 12ded179eb
6 files changed
+233 -38

No files matched your search

+32 -1
View File
@@ -27,7 +27,7 @@ Goal: full-parity native iOS app per UPDATE_PATH.md, verified in simulator via X
- [x] Phase 5: media player — commit 0af944e (webm playback blocked by iOS AVPlayer; see status below)
- [x] Phase 6: media search, choices, library panel (commit cbf1830)
- [x] Phase 7: background tasks panel (commit af296da)
- [ ] Phase 8: polish/hardening/parity pass
- [x] Phase 8: polish/hardening/parity pass (commit pending)
- [ ] Phase 9: docs & handoff
## Phase 2 status
@@ -151,6 +151,37 @@ Notes:
- Screenshot captured at /tmp/taskpanel.png (model can't view images; verified via UI snapshot text + logs instead).
- No server/agent changes needed — the task dispatch pipeline already worked; only the iOS client was missing.
## Phase 8 status (2026-08-30 ~13:40)
Done:
- **Stale-room guards**: `sessionGeneration` in VoiceSession, `pollGeneration` in MediaPlayerController. All async callbacks check generation before mutating state. RoomDelegate methods check `self.room === room`. Prevents post-disconnect mutations.
- **Audio interruption**: `handleAudioInterruption` in VoiceSession (mute mic on begin, resume on end if shouldResume). MediaPlayerController pauses/resumes AVPlayer on interruption.
- **Error states**: `isReconnecting` published property + reconnect banner in ConversationView. `stateError` published property + error banner in MediaPanelView.
- **Accessibility**: OrbView accessibilityLabel/Value (maps state to "Listening", "Thinking", etc.). ConversationView: Send, End, Tasks, Media, Settings labels. MediaPanelView: transport controls + refresh library labels.
- **Server URL change fix**: Changed `session` from `@ObservedObject` to `@State` in ConversationView so it can be reassigned inside async closures. Added `.onReceive(session.objectWillChange)` for observation. `.onChange(of: settings.serverURL)` disconnects old session and creates a new one when idle.
Verified in simulator:
- Build: zero errors, zero warnings.
- App launches cleanly.
- Server URL change (invalid → valid) triggers session recreation without crash.
- Connect/Disconnect/Reconnect matrix: Start → Ready → Hang up → Start again (3x). No crashes, state resets cleanly.
- Logs confirm no post-disconnect mutations (stale callbacks guarded).
Web parity checklist (vs /Users/shane/dev/hope-voice-api/web/app.js):
- [x] Token auth + LiveKit connect
- [x] Agent wait + agent_ready
- [x] Voice/model pickers + sync-on-join
- [x] Text mode toggle + barge-in hint
- [x] Transcript list + history
- [x] Media player (poll, reconcile, transport, seek)
- [x] Media search + choices + library
- [x] Background tasks panel
- [x] Stale-room guards (=== room checks)
- [x] Audio interruption handling
- [x] Error states (reconnect, mic error, media error)
- [x] Accessibility labels
- [ ] Web-only: drag-to-seek on waveform (iOS uses slider — acceptable)
- [ ] Web-only: keyboard shortcuts (N/A on iOS)
## Subagent notes
- Small context model: keep prompts self-contained, point at this file + UPDATE_PATH.md
- Watch for loops (repeated identical commands) — kill and redirect if seen