fix(announcer): swallow benign speech interruptions#36
Merged
Conversation
Speech synthesis "interrupted"/"canceled" errors fire on every normal announcement (each new announcement calls synth.cancel() to replace the previous one), but they were leaking to the console as "Error: Speech synthesis error: interrupted". speakSeries intended to swallow these, but gated the swallow branch on `e instanceof SpeechSynthesisErrorEvent` while speak() rejected a plain Error — so the check never matched, the error hit `throw e`, rejected the series promise, and surfaced as an unhandled rejection on the non-notification announce path. The same dead branch also defeated the network retry logic. - speak() now rejects an Error carrying the structured .error code. - Add a single handleSpeechError() classifier (replacing two copy-pasted catch blocks) that classifies by the .error code: interrupted/canceled stop retrying silently, network backs off and retries, anything else rethrows. - Drop the dependency on the SpeechSynthesisErrorEvent global, which isn't defined on every TV browser. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Error: Speech synthesis error: interruptedwas spamming the console during directional navigation. This makes the announcer correctly treat speechinterrupted/canceledas the benign, expected events they are.Why
The error originates in
speak()'sutterance.onerror(speech.ts).interrupted/canceledfire on every normal announcement, because each new announcement callssynth.cancel()to replace the in-flight one.speakSeriesalready intended to swallow these, but the swallow branch was gated one instanceof SpeechSynthesisErrorEvent— whilespeak()rejected a plainError. So:instanceofcheck was always false → the error hitelse { throw e },seriespromise,notificationannounce path (never.catched), surfaced as an unhandled rejection in the console.The same dead branch also defeated the
networkretry logic, andSpeechSynthesisErrorEventisn't defined as a global on every TV browser (aReferenceErrorwaiting to happen if the branch ever evaluated).Changes
speak()now rejects anErrorthat carries the structured.errorcode.handleSpeechError()classifier (replacing two copy-pasted catch blocks) that classifies by the.errorcode string:interrupted/canceled→ stop retrying silently (no longer surfaced),network→ back off and retry (this path was also dead before),SpeechSynthesisErrorEventglobal.Reviewer notes
synthesis-failed) on the non-notificationpath still has no.catchinannouncer.ts, so it would be an unhandled rejection. That's now rare and arguably should be visible — happy to add a.catchsafety net on the returnedseriesif preferred.Verification
npm run tsc— cleannpm run lint— 0 errors (speech.tsintroduces no new warnings)npm run test— 124/124 pass🤖 Generated with Claude Code