_blogs
// blogs / 20260808.md
// blogs / 20260808.md
Dev Log: August 08 Wrap-up
Overview
Had a bit of a "man vs. machine" battle today. I spent most of my time ironing out a frustrating UX bug in how our chat component handles voice input and manual typing at the same time. It wasn't a massive feature, but it's one of those small annoyances that can make a tool feel broken.
Taming the Speech-to-Text Logic
The issue was pretty annoying: if you were using the microphone to dictate a message but decided to manually type a correction midway through, the STT (Speech-to-Text) engine would often "overrule" you. It kept a stale version of the transcript in its internal buffer and would just overwrite your manual edits the moment the next bit of audio finished processing.
Basically, the mic was "resurrecting" misheard text that the user had already deleted. I had to figure out a way to detect when a user actually touched the keyboard so the system could rebase the transcript on the fly.
The fix involved keeping track of the last string the component itself pushed into the textbox. If the current text in the box doesn't match that "last emitted" value, it's a clear signal that the user changed something manually. At that point, I just reset the internal STT buffers to match the new text so the next recognition tick builds on top of the edit instead of wiping it out.
// A simplified look at how I'm tracking the manual overrides
private syncManualEdits(): void {
const currentInput = this.getCurrentInputValue().trim();
// If the box changed but NOT because of the STT engine...
if (currentInput !== this.lastValueSentToUI) {
// ...then the user must have typed something.
// Update our base buffer to match their manual changes.
this.sttBaseBuffer = currentInput;
this.sttCurrentResult = '';
}
}
It sounds simple on paper, but getting the timing right—especially when dealing with the native SpeechRecognition API and its asynchronous nature—is always a bit of a headache. I had to make sure this check happens right before we start a new recording chunk to ensure the state is fresh.
Wrapping Up
It feels much more fluid now. There’s nothing more jarring than fighting your own UI, so I'm glad this is sorted. I'll do some more edge-case testing tomorrow to make sure I didn't accidentally introduce any weirdness with the auto-restart logic for long dictations. Catch you tomorrow.