Archived
9.3 KiB
9.3 KiB
Review Report
- Task: 20260831-105824-plan-code-ui-audit
- Agent: reviewer
- Model: openai-codex/gpt-5.6-sol
- Effort: high
- Reviewed: 2026-08-31T14:25:00Z
- Verdict: changes-requested
Findings
1. High — a newer microphone interruption can still finish with the microphone enabled
- Verified source facts:
.begancomputeswasActivebefore awaiting the prior operation (Sources/LiveKit/VoiceSession.swift:515-533). In the required begin A → end A/unmute in flight → begin B sequence, A has leftisMuted == trueuntil its SDK unmute completes, so begin B captureswasActive == false. B then waits for A's end task; A completes, enables the microphone, and setsisMuted = false(:580-585). B resumes but exits atguard wasActive(:531-535) without muting. - Impact: the retained task chain orders operations, but the final state can still be microphone-on during interruption B—the privacy failure iteration 07 was intended to remove.
- Correction: do not snapshot transient
isMutedbefore the predecessor completes. Track the user's intended pre-interruption mute state separately, or decide/force the newer begin's mute after its predecessor finishes. Add a deterministic microphone-operation seam for begin A → end A suspended in unmute → begin B, asserting the final hardware/UI state is muted.
2. High — server replacement coalescing stops after one pending replacement
- Verified source facts: the first replacement consumes one
pendingServerURLand launches a nested second task (Sources/Features/Conversation/ConversationView.swift:122-147). If URL C is accepted while that second replacement is awaitingcurrentSession.disconnect(), the top-level.onChangeseesisReplacingSession == trueand stores C at:92-96; however, the nested task only setsisReplacingSession = falseat:146and never checks pending again. - Impact: rapid A → B → C Applies can finish with a session and
activeServerURLon B while the persisted/visible accepted setting is C. No furtheronChangeoccurs, so conversation and media can target different servers until another distinct edit or relaunch. - Correction: use one retained replacement worker that loops until no pending latest URL remains. Each loop must capture/validate the URL, disconnect the exact current session, construct from that captured config, and atomically update session/active URL. Add an A → B → C coordinator test, including C arriving while B disconnects.
3. Medium — terminal failure cleanup leaves old session identity and error state visible into retry
- Verified source facts: explicit
disconnect()clearsagent,isMuted,micError,models, reconnect flags, and interruption ownership (Sources/LiveKit/VoiceSession.swift:464-486).teardownForFailure(message:)clears only wait/generation/interruption/state-model/wake-lock/media/room state (:243-265); it does not clearagent,isMuted,micError,models, orisReconnecting.ConversationViewdisplaysagentwithout requiring.ready(Sources/Features/Conversation/ConversationView.swift:173-177). - Impact: after a terminal disconnect, the failed and subsequent connecting/waiting UI can show the old agent and microphone error. A retry can also retain the previous server's model list until replacement events arrive, allowing stale session identity/configuration data to bleed into a fresh lifecycle.
- Correction: make failure teardown reset the same per-room/session fields as explicit disconnect before publishing
.failed, while preserving only deliberately persistent settings/history/tasks. Ignore control events outside active connecting/waiting/ready states. Add a small state-reset test seam if the Room cannot be faked.
Acceptance Criteria
Iteration 07 disposition
| Item | Result | Evidence / qualification |
|---|---|---|
| 1. Failure teardown blocks retry | Partial | .tearingDown correctly withholds .failed/Start until media and room cleanup finish, and connect() rejects that state. Per-session published fields are not fully reset (Finding 3). |
| 2. One retained mic operation chain | Changes required | Begin and end are retained and ordered, but begin snapshots transient mute state too early and can skip the final mute (Finding 1). |
| 3. Consume matched dedupe record | Implemented | Both suppress paths remove the record; P1/D1/P2/D2 and inverse tests pass with canonical and persistence counts of two. |
| 4. Coalesced captured-config replacement and failed Apply | Changes required | Captured configs, direct observation, and failed-state Apply are implemented; arbitrary latest-wins coalescing stops after the second replacement (Finding 2). |
| 5. Retained media interruption task/token | Implemented by inspection | A newer begin increments sequence, cancels prior work, and the resume path rechecks cancellation, sequence, active interruption, scope, and room immediately before play. Device qualification remains residual. |
| 6. Accurate suite evidence/title | Implemented | Current source/log breakdown is Transcript 24, MediaReconcile 7, ConversationState 5, MediaAPI 1, Pinned Loader 12; total 49. Unknown-total title is corrected. |
Iteration 06 finding disposition
| Prior finding | Iteration 07 result |
|---|---|
| Failure/retry overlap | Core retry race fixed with .tearingDown; state reset remains incomplete (Finding 3). |
| Mic begin/end ordering | Not fully fixed (Finding 1). |
| Repeated-pair transcript loss | Fixed and tested in both orders. |
| Server replacement identity | Two-value case fixed; three-or-more coalescing remains incorrect (Finding 2). |
| Old media resume during newer interruption | Fixed by cancellation/sequence/active-state checks; device-unverified. |
| Failed server lockout/direct observation | Fixed: failed Apply is allowed and Settings directly observes the session. |
| Suite count accuracy | Fixed for iteration 07/current aggregate evidence. |
The current code cannot be approved while Findings 1–3 remain. Real-device, current-library media-format, and separate Hope UI-redesign work are qualifications below, not code findings in this review.
Verification Evidence
- Read
iterations/07-request.md,iterations/07-implementation.md, prioriterations/06-review.md, aggregate implementation/manifest, and the exact current lifecycle, interruption, server replacement, transcript, resource-loader, media, and test sources. git status --short && git diff --check && git diff --stat→ 18 tracked product/config/doc files modified plus untrackedTests/and.artifacts/; diff check clean before reviewer artifact writes. Reviewer did not modify product code.- Loaded the repository XcodeBuildMCP skill.
session_show_defaults→/Users/shane/dev/voice-ios/Voice.xcodeproj, schemeVoice_iOS, iPhone 17 Pro simulatorAFDA137E-1657-428E-BF01-E5CD7989772C. - XcodeBuildMCP
test_sim→ 49 passed, 0 failed, 0 skipped in 9.3 s. Log:~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/test_sim_2026-08-31T14-20-50-489Z_pid94423_32855de5.log; result bundle:~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/result-bundles/test_sim_2026-08-31T14-20-50-489Z_pid94423_fd18ee90.xcresult. Log line 276 confirms 49 tests/5 suites and lines 198/258/270/271/275 confirm all suite executions. - XcodeBuildMCP
build_sim→ Build succeeded in 3.2 s. Log:~/Library/Developer/XcodeBuildMCP/workspaces/voice-ios-9eb036fdea81/logs/build_sim_2026-08-31T14-21-04-018Z_pid94423_8f26cb11.log. Only AppIntents metadata-skip noise appeared; no Swift compiler warning. rg -c '@Test\(' Tests/*.swift→ ConversationState 5, MediaAPI 1, MediaReconcile 7, PinnedMediaResourceLoader 12, TranscriptDedupe 24 (49 total).- Generated
Voice.xcodeproj/project.pbxprojcontains all five test files in the test Sources phase. - Findings 1–3 are deterministic source-order/state analyses. Runtime reproduction was not claimed because no fake microphone, Room, or server-replacement coordinator seams exist in the current test target.
Residual Risks
- Real device: microphone capture/permission, actual AVAudioSession mic and media interruption timing, remote TTS audibility, wake lock, side-button behavior, and supported-format playback remain unqualified.
- Server format: the current YouTube library is webm and cannot qualify AVPlayer playback; use an MP3/AAC endpoint on device. Resource-loader status/cancel/range behavior still lacks a true network integration test.
- Separate UI scope: Hope LED-wall redesign, complete accessibility/Dynamic Type/reduced-motion work, visualizer/barge-in refinement, and app icon remain intentionally separate and unimplemented.
- Plain LAN
ws://signaling and rotation of the self-signed pinned certificate remain documented deployment risks.
Required Actions
- Correct Findings 1–3 without broad UI redesign.
- Add pure/deterministic tests for the mic chain's transient-state case and an arbitrary latest-wins replacement loop (at least A→B→C); cover failure field reset where practical.
- Re-run all XcodeBuildMCP tests, separate simulator build, suite-count inspection, and
git diff --check, then request another independent review. - Keep device, server-format, and Hope UI work explicitly qualified rather than treating them as blockers to this code-correctness loop.