Files
hope-voice-api/ios/.artifacts/20260831-105824-plan-code-ui-audit/implementation.md
T
2026-10-04 07:23:49 -04:00

186 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Implementation Report
- Task: 20260831-105824-plan-code-ui-audit
- Agent: coder
- Model: litellm-lan/qwen3.8-27b (actual fixed model for this session)
- Status: complete
## Summary
**Iteration 01:** All 10 confirmed defects from `iterations/01-request.md` are fixed with minimal, targeted changes across 7 source files. A new `VoiceTests` unit-test target was added to `project.yml`, the Xcode project regenerated, and all 7 new tests pass on the iOS simulator via XcodeBuildMCP with zero new compiler warnings.
**Iteration 02:** All 7 corrections from `iterations/02-request.md` are implemented: text-mode binding replaced with `.onChange`, transcript data-final dedupe replaces open partial in place, `appliedDesired` gate removed for late-audio reconciliation, deterministic media reconcile tests added, join-mode sync operations made independent, and `trackID` captured before async tasks in `stop()`. All 12 tests pass with zero compiler warnings.
**Iteration 03:** All 6 items from `iterations/03-request.md` are implemented: symmetric time-bounded transcript dedupe (5-second window), lifecycle-safe mic interruption ownership (`micMutedByInterruption` flag), media interruption pause/resume semantics (`isInterruptionActive` flag + server coherence), correct HTTP 206 total length (`parseTotalLength` with `Int64?`), construction-only stream URL (doc clarification), and stale comment updates. All 16 tests pass with zero compiler warnings.
**Iteration 04:** All 10 product corrections and 2 documentation corrections from `iterations/04-request.md` are implemented: centralized terminal failure cleanup (`teardownForFailure`), text-mode microphone invariant (API-level guards + UI disabled state), server config semantics (sanitize invalid values, validate before session replacement), visible history + truthful Clear, conversation-state inference alignment (both-silent → listening), stale-safe media publishing (generation capture), resource loader hardening (shared session, task retention, status validation), TLS pin scoping (pinned host only), model picker disconnected copy (`isReady` check), and documentation updates (PLAN.md, README.md, DEVICE_TESTING.md). All 16 tests pass with zero compiler warnings.
**Iteration 05:** All 8 corrections from `iterations/05-request.md` are implemented: project regeneration with all 5 test files present + stream URL/EMA test fixes, single canonical transcript collection with source-aware clock-testable dedupe, real server URL Apply semantics with local draft and legacy sanitization, intentional idempotent room teardown with `isTearingDown` flag, no-resume media interruption safety with separate `resumeSuppressed` flag, resource-loader lifecycle completion (ObjectIdentifier keying, main-queue serialization, correct 206/200 content-length, removeLoader on replacement), and serialized microphone interruption operations with sequence-based supersession. All 42 tests pass across 5 suites with zero compiler warnings.
**Iteration 06:** All 8 corrections from `iterations/06-request.md` are implemented: removed `clearAll()` from `startSession()` (history preserved on new session), consume-on-suppress cross-source dedupe (second same-source repeat accepted after opposite-source suppression), centralized `teardownForFailure(message:)` with `.failed` set before disconnect, truthful `.idle` publication only after complete teardown in `disconnect()`, server Apply disabled while session not idle + `isReplacingSession` guard, await-based mic interruption serialization (flag set after mute completes, `.ended` awaits mute task), true map serialization via `delegateQueue: .main` on pinned URLSession, and 5 new focused tests. All 47 tests pass across 5 suites with zero compiler warnings.
**Iteration 07:** All 6 corrections from `iterations/07-request.md` are implemented: `.tearingDown` state prevents retry during failure cleanup (`.failed` published only after complete teardown), single retained mic operation chain for both begin and end (every new op awaits predecessor, newer begin orders after in-flight old unmute), consume (remove) on cross-source suppress (P1,D1,P2,D2 and D1,P1,D2,P2 produce exactly two rows), server replacement coalescing with `pendingServerURL` + captured-config construction + `.failed` Apply permitted, retained media interruption task with sequence recheck before `player.play()`, and suite count/test title corrections. All 49 tests pass across 5 suites with zero compiler warnings.
**Iteration 08:** All 3 corrections from `iterations/08-request.md` are implemented: explicit `userWantsMicMuted` intent field separate from transient `isMuted` (interruption begin decision moved after predecessor await, using user intent not pre-await snapshot; begin A → end A/unmute → begin B now finishes muted), single retained worker loop for server replacement (every accepted Apply writes `pendingServerURL`, one Task drains arbitrary pending URLs until empty; covers A→B→C semantics where C arrives during B's disconnect), and terminal failure teardown clears all per-room/session ephemeral fields before publishing `.failed` (agent, isMuted, userWantsMicMuted, micError, models, selectedModel, isReconnecting, reconnectAttempted, micMutedByInterruption; inbound control events restricted to active lifecycle states only). All 59 tests pass across 6 suites with zero compiler warnings.
**Iteration 09:** The single correction from `iterations/09-request.md` is implemented: old-room `RoomDelegate` callbacks (participantDidConnect, didUpdateConnectionState) are gated on active lifecycle states and the `isTearingDown` flag via a pure `shouldAcceptRoomCallback(state:isTearingDown:)` predicate, preventing them from repopulating ephemeral fields during failure teardown. A shared `resetEphemeralFields()` helper is called both at the start of teardown/disconnect and immediately before publishing `.failed`/`.idle`, so any callback delivered during the `teardownRoom()` await is erased. Six new deterministic tests verify the gating predicate for all state/teardown combinations. All 65 tests pass across 6 suites with zero compiler warnings.
Per-defect detail, design decisions, verification evidence, and residual risks are in `iterations/01-implementation.md`, `iterations/02-implementation.md`, `iterations/03-implementation.md`, `iterations/04-implementation.md`, `iterations/05-implementation.md`, `iterations/06-implementation.md`, `iterations/07-implementation.md`, `iterations/08-implementation.md`, and `iterations/09-implementation.md`.
## Files Changed
**Iteration 01:**
- `Sources/Media/MediaAPI.swift` — no-response POSTs validate HTTP success without decoding a body (defect 1)
- `Sources/Media/MediaPlayerController.swift` — desired-state sync, rate-KVO reporting, polling restart after endConversation (defects 2–4)
- `Sources/LiveKit/VoiceSession.swift` — text-mode join sync, failed-state retry, deterministic model preference, applyTextMode (defects 5, 7, 8, 6-support)
- `Sources/App/SettingsStore.swift` — onTextModeChange callback (defect 6)
- `Sources/Features/Conversation/ConversationView.swift` — registers text-mode handler, routes toggle through session (defect 6)
- `Sources/LiveKit/TranscriptHandler.swift` — role/text-aware cross-source final dedupe (defect 9)
- `Sources/LiveKit/TranscriptHistory.swift` — identical-final dedupe in history (defect 9)
- `project.yml`, `Voice.xcodeproj/project.pbxproj` — VoiceTests target (defect 10)
- `Tests/MediaAPITests.swift`, `Tests/TranscriptDedupeTests.swift`, `Tests/MediaReconcileTests.swift` — new unit tests (defect 10)
**Iteration 02:**
- `Sources/App/SettingsStore.swift` — removed `onTextModeChange` callback; `textMode.didSet` only persists
- `Sources/Features/Conversation/ConversationView.swift` — replaced callback with `.onChange(of: settings.textMode)`; fixed `textModeBinding` to `$settings.textMode`
- `Sources/LiveKit/TranscriptHandler.swift` — data final replaces open partial in place; removed `closePartial`; cross-source dedupe for identical finals
- `Sources/Media/MediaPlayerController.swift` — removed `appliedDesired` gate; rewrote `applyDesiredState`; exposed `reconcile` as internal; added `trackID` param to `report`/`publishMediaState`; fixed `stop()` to capture trackID before clear
- `Sources/LiveKit/VoiceSession.swift` — independent best-effort `setVoice`/`setModel`/`setMode` in `syncSettingsOnJoin`
- `Tests/TranscriptDedupeTests.swift` — added regression test; updated differing-partial expectation; added no-partial test
- `Tests/MediaReconcileTests.swift` — rewrote with deterministic reconcile tests including late-audio path
**Iteration 03:**
- `Sources/LiveKit/TranscriptHandler.swift` — symmetric time-bounded dedupe with `recentFinals: [String: Date]` and 5-second window; dedupe check before upsert for finals
- `Sources/LiveKit/VoiceSession.swift` — `micMutedByInterruption` flag with lifecycle-safe reset on `.began`; resume only if interruption actually muted the mic
- `Sources/Media/MediaPlayerController.swift` — `isInterruptionActive` flag; pause on `.began`, honor `.shouldResume` on `.ended`, send `pause` control to server on no-resume; guard `applyDesiredState("playing")` with interruption check
- `Sources/Media/PinnedMediaResourceLoader.swift` — `parseTotalLength(_:) -> Int64?` for HTTP 206 Content-Range parsing; shared lazy `pinnedSession`; `activeTasks` dictionary for task retention/cancellation; status validation (reject non-200/206); `removeLoader(_:)` method
- `Sources/Media/MediaAPI.swift` — `streamURL(trackID:)` doc clarification (construction-only, no download)
- `Sources/Features/Media/MediaPanelView.swift` — `serverConfig` comment update
- `Tests/TranscriptDedupeTests.swift` — added symmetric dedupe tests (data→primary, primary→data), interleaved roles, clear/reset
- `Tests/PinnedMediaResourceLoaderTests.swift` (new) — 7 focused parser tests + stream URL construction
**Iteration 04:**
- `Sources/LiveKit/VoiceSession.swift` — `teardownForFailure()` centralized failure cleanup; called from final agent timeout and terminal disconnect; text-mode mic invariant guards in `setMuted(false)` and `enableMicrophone()`
- `Sources/Features/Conversation/ConversationView.swift` — `makeSession` sanitizes invalid URLs to documented default; `.onChange(of: settings.serverURL)` validates before replacing; mic toggle `.disabled(settings.textMode)`; Clear calls both `clear()` and `clearHistory()`
- `Sources/Features/Conversation/OrbView.swift` — `TranscriptListView` shows restored persisted finals + current live events; scroll to last visible row
- `Sources/LiveKit/ConversationState.swift` — both-silent returns to `.listening` (matching web reference); updated doc comment
- `Sources/Media/MediaPlayerController.swift` — `publishMediaState` captures room/generation/pollGen before spawning Task; validates all three inside Task
- `Sources/Networking/TLSPinning.swift` — pin only for `TLSPinning.host`; other HTTPS hosts use `.performDefaultHandling`
- `Sources/Features/Settings/SettingsView.swift` — model picker branches on `session.isReady` (not non-nil)
- `PLAN.md` — status section rewritten to classify verified/pending/blocked items
- `README.md` — added status blockquote clarifying pending device qualification and UI redesign
- `docs/DEVICE_TESTING.md` — clarified `isIdleTimerDisabled` behavior (prevents auto-lock, not side-button lock)
- `Tests/ConversationStateTests.swift` (new) — 5 state-transition tests covering remote speech ending → listening, user speaking → listening, agent priority, stop → idle, no stale thinking chip
**Iteration 08:**
- `Sources/LiveKit/VoiceSession.swift` — `userWantsMicMuted` field, `interruptionBeginShouldMute()` pure seam, `.began` decision after predecessor await, `teardownForFailure` clears all ephemeral fields, `disconnect()` clears `userWantsMicMuted` + `selectedModel`, `handleControlEvent` restricted to active states
- `Sources/Features/Conversation/ConversationView.swift` — single retained worker loop for server replacement (replaces finite nested task)
- `Tests/ConversationStateTests.swift` — 5 new interruption begin decision tests
- `Tests/ServerReplacementCoordinatorTests.swift` (new) — 5 pure coordinator tests for A→B→C drain semantics
**Iteration 09:**
- `Sources/LiveKit/VoiceSession.swift` — `resetEphemeralFields()` shared helper, `shouldAcceptRoomCallback(state:isTearingDown:)` pure predicate, `participantDidConnect` gated on active states + teardown flag, `didUpdateConnectionState` early return for `isTearingDown` + per-branch active-state guards, final reset before `.failed`/`.idle` publication
- `Tests/ConversationStateTests.swift` — 6 new callback gating predicate tests
**Iteration 05:**
- `Voice.xcodeproj/project.pbxproj` — regenerated via xcodegen; all 5 test files present
- `Sources/LiveKit/TranscriptHandler.swift` — single canonical `events` collection, source-aware dedupe with injectable clock, `clearAll()`, convenience init
- `Sources/LiveKit/TranscriptHistory.swift` — persistence-only store, removed adjacent-text dedupe, added `sync(finals:)`
- `Sources/LiveKit/VoiceSession.swift` — `isTearingDown` flag + `teardownRoom()`, `interruptionSequence` + `micMuteTask` for serialized mic interruption, cancel/invalidate in teardown paths
- `Sources/Features/Settings/SettingsView.swift` — local draft URL, Apply button, inline error, validate-on-apply only
- `Sources/App/SettingsStore.swift` — legacy invalid value sanitization at init, `defaultServerURL` constant
- `Sources/Features/Conversation/ConversationView.swift` — `activeServerURL` tracking, serialized session replacement, `clearAll()` for transcript
- `Sources/Features/Conversation/OrbView.swift` — `TranscriptListView` renders only canonical events
- `Sources/Media/MediaPlayerController.swift` — `resumeSuppressed` flag, `wasPlayingBeforeInterruption`, server-synced resume, suppression clear on confirmed paused, explicit user play clears suppression, `currentAsset` + `clearCurrentAsset()` for loader cleanup
- `Sources/Media/PinnedMediaResourceLoader.swift` — `ObjectIdentifier(loadingRequest)` keying, main-queue serialization, correct 206/200 content-length, `removeLoader` cancels in-flight tasks
- `Tests/PinnedMediaResourceLoaderTests.swift` — stream URL fix, 5 new lifecycle/rewrite tests (11 total)
- `Tests/ConversationStateTests.swift` — EMA test uses 5 zero ticks for deterministic threshold crossing
- `Tests/TranscriptDedupeTests.swift` — rewritten with 17 comprehensive tests using ClockBox
## Verification
**Iteration 01:**
- `/opt/homebrew/bin/xcodegen generate` → success; VoiceTests target present in pbxproj.
- XcodeBuildMCP `test_sim` (scheme Voice_iOS, iPhone 17 Pro simulator): **7 passed, 0 failed, 0 skipped** (⏱️ 11.8s).
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T11-42-09-094Z_pid79604_74bb46fe.log`
- Result bundle: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T11-42-09-094Z_pid79604_6f2671aa.xcresult`
- Warning scan of final log: no Swift compiler warnings (only pre-existing appintentsmetadataprocessor noise).
**Iteration 02:**
- XcodeBuildMCP `test_sim` (scheme Voice_iOS, iPhone 17 Pro simulator): **12 passed, 0 failed, 0 skipped** (⏱️ 10.8s). No compiler warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T12-00-11-168Z_pid79604_d7b032fc.log`
- Result bundle: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T12-00-11-169Z_pid79604_372f266d.xcresult`
- XcodeBuildMCP `build_sim`: **Build succeeded** (⏱️ 3.3s). No warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/build_sim_2026-08-31T12-00-24-796Z_pid79604_731d5dd5.log`
**Iteration 03:**
- XcodeBuildMCP `test_sim` (scheme Voice_iOS, iPhone 17 Pro simulator): **16 passed, 0 failed, 0 skipped** (⏱️ 10.5s). No compiler warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T12-48-00-045Z_pid94423_6c681d47.log`
- Result bundle: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T12-48-00-045Z_pid94423_a445b75e.xcresult`
- XcodeBuildMCP `build_sim`: **Build succeeded** (⏱️ 2.8s). No warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/build_sim_2026-08-31T12-48-13-189Z_pid94423_17c712db.log`
- `git diff --check`: no whitespace issues.
**Iteration 04:**
- Same test/build runs as iteration 03 (no additional code changes after the final test pass). **16 passed, 0 failed, 0 skipped**; build succeeded; no warnings.
**Iteration 05:**
- `/opt/homebrew/bin/xcodegen generate` → success; all 5 test files present in pbxproj.
- XcodeBuildMCP `test_sim` (scheme Voice_iOS, iPhone 17 Pro simulator): **42 passed, 0 failed, 0 skipped** (⏱️ 9.0s). No compiler warnings.
- Suites: ConversationStateTests (5), MediaAPITests (3), TranscriptDedupeTests (17), PinnedMediaResourceLoaderTests (11), MediaReconcileTests (6)
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T13-28-46-192Z_pid94423_55b32e1d.log`
- Result bundle: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T13-28-46-192Z_pid94423_d2ea27f4.xcresult`
- XcodeBuildMCP `build_sim`: **Build succeeded** (⏱️ 2.9s). No warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/build_sim_2026-08-31T13-29-09-951Z_pid94423_8d28d092.log`
- `git diff --check`: no whitespace issues.
**Iteration 06:**
- XcodeBuildMCP `test_sim` (scheme Voice_iOS, iPhone 17 Pro simulator): **47 passed, 0 failed, 0 skipped** (⏱️ 11.5s). No compiler warnings.
- Suites: TranscriptDedupeTests (22), MediaReconcileTests (6), ConversationStateTests (5), MediaAPITests (3), PinnedMediaResourceLoaderTests (11)
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T13-43-02-377Z_pid94423_3a678946.log`
- Result bundle: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T13-43-02-377Z_pid94423_ef6e8331.xcresult`
- Final line: `Test run with 47 tests in 5 suites passed after 0.219 seconds.`
- XcodeBuildMCP `build_sim`: **Build succeeded** (⏱️ 2.8s). No warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/build_sim_2026-08-31T13-43-16-941Z_pid94423_61640c54.log`
- `git diff --check`: no whitespace issues.
**Iteration 07:**
- XcodeBuildMCP `test_sim` (scheme Voice_iOS, iPhone 17 Pro simulator): **49 passed, 0 failed, 0 skipped** (⏱️ 10.9s). No compiler warnings.
- Suites: TranscriptDedupeTests (24), MediaReconcileTests (7), ConversationStateTests (5), MediaAPITests (1), PinnedMediaResourceLoaderTests (12)
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T14-12-21-664Z_pid94423_3cc88c6f.log`
- Result bundle: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T14-12-21-664Z_pid94423_d0e637f2.xcresult`
- XcodeBuildMCP `build_sim`: **Build succeeded** (⏱️ 4.7s). No warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/build_sim_2026-08-31T14-12-14-582Z_pid94423_62867a1e.log`
- `git diff --check`: no whitespace issues.
**Iteration 08:**
- XcodeBuildMCP `test_sim` (scheme Voice_iOS, iPhone 17 Pro simulator): **59 passed, 0 failed, 0 skipped** (⏱️ 9.6s). No compiler warnings.
- Suites: TranscriptDedupeTests (24), ConversationStateTests (10), MediaReconcileTests (7), ServerReplacementCoordinatorTests (5), PinnedMediaResourceLoaderTests (12), MediaAPITests (1)
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T21-42-23-821Z_pid94423_e6f0b81b.log`
- Result bundle: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T21-42-23-821Z_pid94423_9b7f8cf9.xcresult`
- XcodeBuildMCP `build_sim`: **Build succeeded** (⏱️ 2.9s). No warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/build_sim_2026-08-31T21-42-36-151Z_pid94423_26f18482.log`
- `git diff --check`: no whitespace issues.
**Iteration 09:**
- XcodeBuildMCP `test_sim` (scheme Voice_iOS, iPhone 17 Pro simulator): **65 passed, 0 failed, 0 skipped** (⏱️ 9.9s). No compiler warnings.
- Suites: TranscriptDedupeTests (24), ConversationStateTests (16), MediaReconcileTests (7), ServerReplacementCoordinatorTests (5), PinnedMediaResourceLoaderTests (12), MediaAPITests (1)
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T21-57-47-408Z_pid94423_4ab5a91f.log`
- Result bundle: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T21-57-47-409Z_pid94423_16ad0db3.xcresult`
- XcodeBuildMCP `build_sim`: **Build succeeded** (⏱️ 6.0s). No warnings.
- Log: `~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/build_sim_2026-08-31T21-57-39-068Z_pid94423_6f60e1bf.log`
- `git diff --check`: no whitespace issues.
## Risks And Limitations
- Simulator-only verification; real-device mic/interruption/playback behavior not claimed as qualified.
- Transcript dedupe replaces the open partial with the data final's text regardless of whether they match (data topic is authoritative). Near-duplicates across sources are not handled.
- xcodegen 2.45.4 requires the platform-suffixed dependency name (`Voice_iOS`) in the test target spec.
- The late-audio test verifies `player.currentItem != nil` after Phase 2 but does not assert `player.rate > 0` because AVPlayer's rate is asynchronous. A real-device or instrumented test would confirm actual playback starts.
- Iteration 03/04: terminal failure cleanup, text-mode invariant, server config sanitization, media-state publishing staleness, resource loader hardening, and TLS pin scoping are not covered by unit tests (require live LiveKit room / network / AVPlayer). Logic mirrors existing patterns; device qualification required.
- Dedupe window is fixed at 5 seconds; legitimate repeated utterances within that window will be suppressed (unlikely in practice).
## Follow-up For Reviewer
See `iterations/01-implementation.md` → "Follow-up For Reviewer" for iteration 01 review points. See `iterations/02-implementation.md` → "Follow-up For Reviewer" for iteration 02 review points (onChange propagation through NavigationStack, real-device late-audio path, rapid text-mode toggle during join). See `iterations/03-implementation.md` → "Follow-up For Reviewer" for iteration 03 review points (real LiveKit dedupe timing, interruption edge cases, media pause control timing, Content-Range parsing). See `iterations/04-implementation.md` → "Follow-up For Reviewer" for iteration 04 review points (terminal failure cleanup on device, text-mode invariant, server config sanitization, conversation state inference, media-state publishing staleness, resource loader hardening, TLS pin scoping, model picker copy). See `iterations/05-implementation.md` → "Follow-up For Reviewer" for iteration 05 review points (isTearingDown flag correctness, resumeSuppressed lifecycle, removeLoader cancel-all behavior, mic interruption sequence edge cases, serialized session replacement window). See `iterations/06-implementation.md` → "Follow-up For Reviewer" for iteration 06 review points (teardownForFailure message idempotency, consume-on-suppress alternating pattern, disconnect idle timing vs Apply guard, mic interruption await-based serialization, delegateQueue .main thread impact). See `iterations/07-implementation.md` → "Follow-up For Reviewer" for iteration 07 review points (.tearingDown state transition, mic chain ordering with in-flight unmute, dedupe consume remove semantics, server coalescing intermediate session, media interruption sequence recheck before play). See `iterations/08-implementation.md` → "Follow-up For Reviewer" for iteration 08 review points (userWantsMicMuted set/clear points, interruption begin decision after await, worker loop drain-until-empty, failure field reset completeness, control-event state restriction). See `iterations/09-implementation.md` → "Follow-up For Reviewer" for iteration 09 review points (callback gating predicate, final reset before .failed/.idle publication, no regression in previously accepted behaviors).