This repository has been archived on 2026-10-04. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
hope-voice-ios/docs/ENGINEERING_LEARNINGS.md
T

7.1 KiB

Engineering learnings

Updated 2026-09-19 from repository source, recorded test evidence, and prior debugging. This is guidance, not a claim that hardware or server checks were rerun today. Read PROJECT_STATUS.md for the current product scope.

Authorized accounts and transport — 2026-10-01

  • The latest direct request makes Hermes server-managed; do not restore model selectors or set_model from old handoffs. Server account auth is distinct from the previously canceled ChatGPT subscription/device login.
  • Scope credentials to canonical HTTPS origin, and transcripts to origin + immutable user id. Never import the global legacy transcript into an authenticated account.
  • Disable bootstrap immediately on logout, invalidate/cancel pending authentication, then disconnect before clearing account UI. New account identity resets drafts and task stores; reject stale room transcription/control callbacks during teardown.
  • Check generation and cancellation immediately after token fetch and before room.connect. Serialize normal mic actions with interruption actions and recheck Type mode after awaits; pending Voice requests cannot enable Type-mode capture.
  • Private IP does not justify plaintext signaling. Use same-origin URLComponents WSS (IPv6 safe). Exact HTTP certificate pinning does not establish the SDK's separate WSS trust. Installed trusted CA and real hardware qualification may be required; never introduce trust-all or plaintext fallback to bypass it.
  • A transient /me outage is not revocation: retain its Keychain bearer but show no unverified account UI. Only 401/403 clears saved authentication. Authentication URLSession is ephemeral with cookies/cache/credential persistence disabled and redirects rejected to protect password/bearer origin confinement.
  • Never log data-channel payloads or bootstrap response bodies. Keep release SDK logging at error. Offline auth fixtures use memory tokens, isolated preferences and deterministic no-network bootstrap errors.

Preserve behavior across UI changes

  • Observe nested observable objects directly where their data is rendered. Watching only the parent session does not guarantee transcript/state/task updates redraw.
  • Keep one conversation session across tabs. Server replacement must remain latest-wins and reject late callbacks from a torn-down session.
  • Keep Settings presentation outside the replaced session's identity. Otherwise saving a changed server can destroy the navigation stack and its success feedback.
  • Treat normalization as a programmatic edit: trimming a saved URL must not clear its own confirmation. Clear confirmation on the next genuine user edit instead.
  • A restored transcript is reading history, not a request to jump to the bottom. User scrolling, text selection, and VoiceOver row focus suspend auto-follow; explicit Latest restores it. Test the reading position, not just row existence.
  • Gate Send on transport readiness, reconnect state and pending sends. Retain an unsent draft on failure. Type mode must not enable microphone capture.
  • A ready room can still have a microphone failure. Show the actual error and its recovery action without disabling the usable text path.

These are regression invariants of the current Nearlight implementation, not a reason to restore the removed music UI or the canceled ChatGPT migration.

Match evidence to the claim

  • A token/TLS check is not an agent-join test; Docker health is not a Qwen-response test; app installation is not proof of microphone capture or audible output.
  • Offline UI fixtures must use isolated preferences and deterministic local failure endpoints. Never reset production preferences to make a UI test pass.
  • Keep live tests opt-in. Record which model/server was used, what was actually asserted, skips, result bundles, and any cleanup of test-created state.
  • Test repeated lifecycle sequences, not just one successful launch. Repeat runs exposed an intermittent playback timeout that a single green run missed.
  • After an interrupted tool call, inspect the existing process/result before restarting. A lost tool handle does not imply the underlying test stopped.
  • When UI snapshots expose no targets, cross-check screenshots and XCTest results; this occurred despite a rendered app and was not sufficient evidence of a crash.
  • Keep failed-run evidence. A later pass does not explain the earlier failure. Source review and automated large-text checks also do not replace manual VoiceOver or real-device speech/interruption testing.

Device connection and signing

Diagnose the layers separately: USB detection → pairing/trust → developer/signing readiness → install/launch → live voice behavior. An unlocked phone may still be unpaired. Once USB detects it, investigate pairing rather than repeatedly blaming the cable. Query fresh device details; do not reuse an old availability report.

The phone was eventually paired and the final Nearlight build installed/launched on September 12. That supersedes the earlier Finder/pairing blocker. Recheck its current availability and provisioning before the next run; the recorded personal provisioning was short-lived. Do not reset privacy settings or erase a device as a routine diagnostic step.

Project generation and assets

project.yml is the source for Voice.xcodeproj; the scheme is Voice_iOS, bundle ID dev.shane.voice. Adding an icon file alone is insufficient: the asset catalog must also be included in the resource build phase. The September 12 follow-up commits added the Lightwell icon and regenerated the project to include it. Regenerate deliberately and review generated diffs alongside source changes.

NAS deployment and historical audio findings

  • Inspect actual containers, images, mounts and health before recreating anything. A user concern about deletion is a reason to check, not proof of deletion.
  • Preserve the persistent certificate. A container restart need not rotate it; update the app pin only after verifying an actual certificate change.
  • Stage clean deployment source separately from a dirty remote checkout. Retain library/configuration mounts and a recoverable previous container.
  • Record the deployed image and source patch. Cross-repository changes must be published separately, with any push-triggered deployment reviewed first.
  • Do not print environment files, API keys, or full credential-bearing configuration.

Historical only—the embedded player no longer exists in this app:

  • WebM/Opus library downloads needed cached AAC for the iOS playback path tested. Preserve originals and verify byte ranges/cache behavior separately from conversion.
  • AVFoundation's resource-loader content type required a UTI, not an HTTP MIME type.
  • Buffering an entire 6 MB response before returning audio reproduced an AVPlayer timeout. Bounded 256 KB range delivery passed three subsequent live repetitions. These findings do not establish support for every radio format or indefinite stream.

See DEPLOYMENT.md and VERIFICATION.md for recorded deployment and test history. Do not reintroduce the client player to preserve history.