fix(voice): edits from the #705 review

- docs/api-reference.md and the voice-stream.ts overview now say a
  transcript frame is the current utterance so far and a final frame
  ends it, as the relay's own comment and the invariants doc do.
- VoiceInput.cleanup() hides the Send button before stop(true), so the
  button stays up for the words stop(true) salvaged and inserted.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer committed 2026-11-09 18:19:05 +01:00
1 parent 7f35f798ee
commit 0e13d9b12f
3 files changed
+9 -5

No files matched your search

+2 -1
View File
@@ -1842,7 +1842,8 @@ same speech-to-text service the CLI's own `/voice` mode uses. Gated on the synce
little-endian PCM, 16 kHz mono (<= 64 KB per frame), plus JSON control frames
`{"t":"finalize"}` (ask for the final transcript) and `{"t":"stop"}`. Server
sends `{"t":"ready"}`, `{"t":"transcript","text","final"}` (each frame is the
WHOLE running transcript, not a delta), `{"t":"error","message"}` and
current utterance so far, not a delta, and a `final: true` frame ends that
utterance), `{"t":"error","message"}` and
`{"t":"closed"}`. Close codes: `4003` disallowed Host/Origin, `4004`
unavailable (reason in the close reason), `4008` too many concurrent streams.
Streams are capped in count and length (`src/config/voice.ts`).
+3 -1
View File
@@ -1415,11 +1415,13 @@ const VoiceInput = {
/** Cleanup on SSE reconnect or page unload */
cleanup() {
// Hide the Send button first: stop(true) below may insert the words it
// salvaged and show the button again for them.
this._hideVoiceSendBtn();
if (this.isRecording) {
this.stop(true);
try { this.recognition?.abort(); } catch (_e) { /* already ended */ }
}
this._hideVoiceSendBtn();
DeepgramProvider._cleanup();
ClaudeVoiceProvider._cleanup();
this.recognition = null;
+4 -3
View File
@@ -13,9 +13,10 @@
* - `{"type":"KeepAlive"}` on open and every 8s, or upstream drops the socket
* between utterances.
* - Audio frames go up as raw binary.
* - Downstream, `TranscriptText`/`TranscriptInterim` carry the RUNNING transcript
* (each frame supersedes the previous one — they are not deltas to concatenate),
* and `TranscriptEndpoint` promotes the pending interim to final.
* - Downstream, `TranscriptText`/`TranscriptInterim` carry the current utterance
* so far (each frame supersedes the previous one; they are not deltas to
* concatenate), and `TranscriptEndpoint` promotes the pending interim to final,
* which ends that utterance.
* - `{"type":"CloseStream"}` finalizes; the endpoint frame that follows is the
* last transcript, so `finalize()` waits briefly for it rather than closing.
*