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

115 lines
7.1 KiB
Markdown

# 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](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](DEPLOYMENT.md) and [VERIFICATION.md](VERIFICATION.md) for recorded
deployment and test history. Do not reintroduce the client player to preserve history.