Skip to content

[bug] Spoken words vanish between end-of-turn and the reply — dictation should land in the thread as a pending message with a transcribing status #281

Description

@serge-ivo

The report

It is not capturing everything I'm saying. I want everything I say transcribed into the message as
I speak it, in the thread right away. Currently the transcribed text disappears after I submit and
reappears once the LLM has processed it. I want to see what I said (dictation), then the converted
transcription, and a status on the message in the thread saying it is being transcribed.

Where the text actually goes today

The live transcript lives in the composer, never in the thread, and it is destroyed at
end-of-turn:

  1. Speaking → streamed partials land in voice.interim, rendered in the textarea
    (InstanceDetail.tsx:786, value={voice.interim || input} — accent, italic, readOnly).
  2. End-of-turn → use-voice.ts:312 does setInterim("Transcribing…"). The words you just spoke
    are overwritten by that literal string. This is the disappearance in the report — it is not a
    race or a dropped update, it is an assignment.
  3. Final transcript returns → onSenddoSendonly now is a user message appended
    (InstanceDetail.tsx:405).

So there is a window — the whole upload-and-transcribe round trip — where the user's words exist
nowhere on screen. In hands-free that window is exactly when the user is not looking at the
composer and expects to see what was heard.

What is being asked for

Move the utterance into the thread, as a real message with a lifecycle, instead of a transient
composer value:

State Shows
dictating live partials, updating as spoken, in a pending user bubble
transcribing the same bubble, with a "transcribing" status on it
sent replaced in place by the final transcript
failed stays, with the failure and the audio retained so nothing is silently lost

doSend already appends optimistically, so the pattern exists — it just starts too late. The bubble
should be created when speech starts, not when transcription ends.

The capture-loss half, and why the fix above is also the diagnostic

Evidence from the Coder Lead thread (all voice turns, all with audioKey):

  • "Coder Lead" — a bare fragment, alone as a whole turn.
  • "What do they need to do for this model?" → the agent replied "I don't have context on what
    'this model' refers to."
    The referent is missing, consistent with words being cut.

Candidate causes, all plausible and not currently distinguishable from the outside:

  • the VAD ends the turn mid-sentence (silenceMs / sensitivity) and the tail is never recorded;
  • the final Whisper transcript replaces the streamed partials, so anything the partials caught
    but the final pass dropped is lost with no trace;
  • isTooShortToTranscribe discards a clip that did contain speech.

Right now a lost tail is invisible: the partials are gone (overwritten by "Transcribing…") and
only the final text is ever recorded. Keeping the dictation in the thread makes the loss
observable
— if the bubble showed "…verify the application is built on the platform for the MCP"
during dictation and the final message reads "…for this model", the discrepancy is on screen and
reportable instead of a vague feeling that words go missing.

Worth logging both strings (partial-at-end-of-turn vs final) when they diverge materially, at least
behind a debug flag, so the cause can be identified rather than guessed.

Notes

  • Voice transcription lost when agent message arrives mid-dictation #175 (turn lost when an agent message arrives mid-dictation) is fixed and recovers text into the
    composer via onRecoveredText. That is the same instinct as this ticket — do not throw away what
    the user said — applied one step earlier.
  • The audio is already persisted per turn (audioKey, replay on double-tap), so a failed or
    suspect transcription always has the original to fall back on.

Verification

  • Speaking in hands-free shows a pending user bubble in the thread that updates as words arrive.
  • The bubble is never blanked; it carries a transcribing status until the final text replaces it.
  • A failed transcription leaves the bubble with its partials and the recording, not an empty gap.
  • Where the final transcript materially differs from the last partial, the divergence is recorded.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions