Archived
186 lines
24 KiB
Markdown
186 lines
24 KiB
Markdown
# 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).
|