Skip to content

Fix dictation "Listening…" placeholder and shimmer interim text - #326211

Merged
Megan Rogge (meganrogge) merged 8 commits into
mainfrom
fix-dictation-listening-placeholder
Jul 16, 2026
Merged

Fix dictation "Listening…" placeholder and shimmer interim text#326211
Megan Rogge (meganrogge) merged 8 commits into
mainfrom
fix-dictation-listening-placeholder

Conversation

@meganrogge

@meganrogge Megan Rogge (meganrogge) commented Jul 16, 2026

Copy link
Copy Markdown
Collaborator
Screen.Recording.2026-07-16.at.3.49.39.PM.mov

Summary

Fixes two bugs with the built-in chat voice dictation placeholder and adds a shimmer animation to not-yet-finalized (streaming) dictation text.

Placeholder fixes

  • Overlap: The dictation "Listening…" placeholder (set via EditorOption.placeholder) overlapped with the chat input's own placeholder (rendered via editor decorations). The decoration placeholder is now suppressed while EditorOption.placeholder is set, so only one shows at a time.
  • Premature display: The placeholder appeared as soon as recording started, even while the on-device model was still downloading/loading. It now only shows once connected (recording and not preparing the model).

Shimmer for interim text

  • As interim (non-final) transcript text streams in, it now renders with a shimmer animation, matching the existing chat-thinking-shimmer pattern.
  • When the transcript finalizes, the shimmer clears and the solid, finalized text replaces it.
  • Added a beginFinalize() lock at the start of stopDictation plus a _finalized guard so a trailing interim event can't re-apply the shimmer after finalization (fixes the "final text never replaces the shimmer" race).
  • Added [chat-stt-dictation] trace logging across the transcript/state lifecycle to aid diagnosis.

Changes

  • dictationSession.ts: placeholder gating, shimmer decoration, beginFinalize()/_finalized guard, trace logging.
  • chatInputEditorContrib.ts: suppress decoration placeholder when EditorOption.placeholder is active.
  • speechToText/media/dictationSession.css: new file with shimmer keyframes/class.
  • chatSpeechToTextActions.ts / newChatInput.ts: thread ILogService into startDictation.

Co-authored-by: Copilot 223556219+Copilot@users.noreply.github.com

Megan Rogge (meganrogge) and others added 4 commits July 16, 2026 15:11
The chat input renders its placeholder as an editor decoration while dictation
sets the editor's placeholder option (rendered by PlaceholderTextContribution),
so both showed at once and overlapped. Suppress the decoration placeholder
while the editor's placeholder option is set so only one renders.

Also only show "Listening…" once the session is connected — the Recording
state with the on-device model finished preparing — instead of while the model
is still downloading/loading and transcription cannot happen yet.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
While dictation is streaming interim (not yet finalized) transcripts, decorate
the inserted text with a shimmer animation so the user can tell which portion is
still being transcribed. The shimmer is cleared once the final transcript is
applied (or on cancel / editor disposal / a service-side error), leaving solid
text.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The transcription service can fire a trailing interim transcript as it shuts
down, after stopAndTranscribe resolves and the final (solid) text has been
applied. That late interim update overwrote the final text and re-applied the
shimmer, so the finalized text never appeared. Ignore interim updates once a
final update has been applied.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Clear the shimmer and lock out interim updates the moment the user stops
talking (beginFinalize), before the async final transcription resolves, so a
trailing interim transcript can neither re-apply the shimmer nor overwrite the
final text. Also clear the shimmer when there is no final transcript to apply.

Thread ILogService through startDictation and add trace logging around the
transcript/state events and the stop lifecycle to diagnose why finalized text
was not replacing the shimmer.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves the chat speech-to-text (voice dictation) UX by preventing placeholder overlap, delaying the “Listening…” placeholder until the dictation session is actually ready, and visually distinguishing interim (streaming) transcript text via a shimmer decoration.

Changes:

  • Suppress the chat input’s decoration-based placeholder while the editor’s placeholder option is active (e.g. dictation “Listening…”), avoiding double placeholders.
  • Add interim-transcript shimmering via inline editor decorations, plus a finalize lock to avoid late interim updates overriding final text.
  • Thread ILogService into dictation startup and add trace logging across the dictation lifecycle.
Show a summary per file
File Description
src/vs/workbench/contrib/chat/browser/widget/input/editor/chatInputEditorContrib.ts Skips decoration placeholder when EditorOption.placeholder is set, avoiding placeholder overlap.
src/vs/workbench/contrib/chat/browser/speechToText/media/dictationSession.css Adds shimmer animation styling for interim dictation text.
src/vs/workbench/contrib/chat/browser/speechToText/dictationSession.ts Implements placeholder gating, interim shimmer decorations, finalize guard, and trace logging.
src/vs/workbench/contrib/chat/browser/actions/chatSpeechToTextActions.ts Passes ILogService into startDictation.
src/vs/sessions/contrib/chat/browser/newChatInput.ts Passes ILogService into startDictation from the Sessions UI.

Review details

  • Files reviewed: 5/5 changed files
  • Comments generated: 2
  • Review effort level: Low

Megan Rogge (meganrogge) and others added 4 commits July 16, 2026 15:48
Interim (not-yet-finalized) dictation text now renders in the input's
placeholder color instead of a single shimmer across the whole transcript:

- The settled leading portion — the part that has not changed since the
  previous interim transcript, trimmed back to a word boundary — is shown
  statically in the placeholder color, so words stop shimmering once the
  recognizer stops revising them.
- Only the still-processing trailing portion shimmers.
- If the recognizer revises an earlier word, the common prefix shrinks and that
  word re-enters the shimmering region.

Finalizing clears both decorations, leaving solid text.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…word

Two dictation finalization fixes:

- Park the caret at the start of the dictated region (a blinking cursor at the
  beginning) while transcription is in progress, instead of chasing the
  growing/revised interim text. The caret moves to the end only once the final
  transcript is applied.
- Append a short trailing silence to the audio before the final transcription
  pass. Whisper frequently drops the last word when the recording ends abruptly
  right after it (no trailing silence to mark the utterance end); the pad gives
  the model the context it needs to emit the final word.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Passing the desired caret as executeEdits' endCursorState (instead of a
setPosition() call afterward) guarantees the caret stays parked at the start of
the dictated region during interim updates. The prior approach let the editor
first place the caret at the end of the applied edit, causing it to visibly
chase the streaming text before being corrected.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The settle boundary always trimmed the common prefix back to the previous word
boundary, which left the final word shimmering forever once interim updates
stabilized (the recognizer keeps re-emitting the same text, so the tail never
"changed" but never settled either). Now the word-boundary trim is only applied
when the tail actually diverges from the previous interim; when the common
prefix already covers the whole current transcript, everything settles.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Screenshot Changes

Base: 44908a46 Current: d297b48b

Added (92)

editor/multiDiffEditor/MultiDiffEditorDocumentSwapBefore/Dark

current

editor/multiDiffEditor/MultiDiffEditorDocumentSwapBefore/Light

current

editor/multiDiffEditor/MultiDiffEditorDocumentSwapAfter/Dark

current

editor/multiDiffEditor/MultiDiffEditorDocumentSwapAfter/Light

current

editor/suggestWidget/MethodCompletions/Dark

current

editor/suggestWidget/MethodCompletions/Light

current

editor/suggestWidget/MethodCompletionsWithDetails/Dark

current

editor/suggestWidget/MethodCompletionsWithDetails/Light

current

editor/suggestWidget/MixedKinds/Dark

current

editor/suggestWidget/MixedKinds/Light

current

peekReference/PeekReferences/Dark

current

peekReference/PeekReferences/Light

current

imageCarousel/imageCarousel/SingleSection/Dark

current

imageCarousel/imageCarousel/SingleSection/Light

current

imageCarousel/imageCarousel/SingleSectionMiddleImage/Dark

current

imageCarousel/imageCarousel/SingleSectionMiddleImage/Light

current

imageCarousel/imageCarousel/MultipleSections/Dark

current

imageCarousel/imageCarousel/MultipleSections/Light

current

imageCarousel/imageCarousel/SingleImage/Dark

current

imageCarousel/imageCarousel/SingleImage/Light

current

onboarding/spotlightOverlay/SpotlightOverlay/Dark

current

onboarding/spotlightOverlay/SpotlightOverlay/Light

current

agentSessionsViewer/CompletedRead/Dark

current

agentSessionsViewer/CompletedRead/Light

current

agentSessionsViewer/CompletedUnread/Dark

current

agentSessionsViewer/CompletedUnread/Light

current

agentSessionsViewer/InProgress/Dark

current

agentSessionsViewer/InProgress/Light

current

agentSessionsViewer/NeedsInput/Dark

current

agentSessionsViewer/NeedsInput/Light

current

agentSessionsViewer/FailedWithDuration/Dark

current

agentSessionsViewer/FailedWithDuration/Light

current

agentSessionsViewer/FailedWithoutDuration/Dark

current

agentSessionsViewer/FailedWithoutDuration/Light

current

agentSessionsViewer/WithDiffChanges/Dark

current

agentSessionsViewer/WithDiffChanges/Light

current

agentSessionsViewer/WithFileChangesList/Dark

current

agentSessionsViewer/WithFileChangesList/Light

current

agentSessionsViewer/WithBadge/Dark

current

agentSessionsViewer/WithBadge/Light

current

agentSessionsViewer/WithMarkdownBadge/Dark

current

agentSessionsViewer/WithMarkdownBadge/Light

current

agentSessionsViewer/WithDescription/Dark

current

agentSessionsViewer/WithDescription/Light

current

agentSessionsViewer/WithMarkdownDescription/Dark

current

agentSessionsViewer/WithMarkdownDescription/Light

current

agentSessionsViewer/WithBadgeAndDiff/Dark

current

agentSessionsViewer/WithBadgeAndDiff/Light

current

agentSessionsViewer/Archived/Dark

current

agentSessionsViewer/Archived/Light

current

agentSessionsViewer/ArchivedUnread/Dark

current

agentSessionsViewer/ArchivedUnread/Light

current

agentSessionsViewer/CloudProvider/Dark

current

agentSessionsViewer/CloudProvider/Light

current

agentSessionsViewer/BackgroundProvider/Dark

current

agentSessionsViewer/BackgroundProvider/Light

current

agentSessionsViewer/ClaudeProvider/Dark

current

agentSessionsViewer/ClaudeProvider/Light

current

agentSessionsViewer/CloudProviderInProgress/Dark

current

agentSessionsViewer/CloudProviderInProgress/Light

current

agentSessionsViewer/InProgressWithDescription/Dark

current

agentSessionsViewer/InProgressWithDescription/Light

current

agentSessionsViewer/SectionToday/Dark

current

agentSessionsViewer/SectionToday/Light

current

agentSessionsViewer/SectionYesterday/Dark

current

agentSessionsViewer/SectionYesterday/Light

current

agentSessionsViewer/SectionLastWeek/Dark

current

agentSessionsViewer/SectionLastWeek/Light

current

agentSessionsViewer/SectionOlder/Dark

current

agentSessionsViewer/SectionOlder/Light

current

agentSessionsViewer/SectionArchived/Dark

current

agentSessionsViewer/SectionArchived/Light

current

agentSessionsViewer/SectionMore/Dark

current

agentSessionsViewer/SectionMore/Light

current

agentSessionsViewer/ApprovalRowJson/Dark

current

agentSessionsViewer/ApprovalRowJson/Light

current

agentSessionsViewer/ApprovalRowBash/Dark

current

agentSessionsViewer/ApprovalRowBash/Light

current

agentSessionsViewer/ApprovalRowPowerShell/Dark

current

agentSessionsViewer/ApprovalRowPowerShell/Light

current

agentSessionsViewer/ApprovalRowLongLabel/Dark

current

agentSessionsViewer/ApprovalRowLongLabel/Light

current

agentSessionsViewer/ApprovalRow1Line/Dark

current

agentSessionsViewer/ApprovalRow1Line/Light

current

agentSessionsViewer/ApprovalRow2Lines/Dark

current

agentSessionsViewer/ApprovalRow2Lines/Light

current

agentSessionsViewer/ApprovalRow3Lines/Dark

current

agentSessionsViewer/ApprovalRow3Lines/Light

current

agentSessionsViewer/ApprovalRow4Lines/Dark

current

agentSessionsViewer/ApprovalRow4Lines/Light

current

agentSessionsViewer/ApprovalRow3LongLines/Dark

current

agentSessionsViewer/ApprovalRow3LongLines/Light

current

@meganrogge
Megan Rogge (meganrogge) enabled auto-merge (squash) July 16, 2026 20:53
@meganrogge Megan Rogge (meganrogge) added this to the 1.130.0 milestone Jul 16, 2026
@meganrogge
Megan Rogge (meganrogge) merged commit a9d143d into main Jul 16, 2026
29 checks passed
@meganrogge
Megan Rogge (meganrogge) deleted the fix-dictation-listening-placeholder branch July 16, 2026 20:59
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants