fix(announcer): keep aria live-region label until next announcement (fixes #31)#33
Merged
Merged
Conversation
In aria mode, focusElementForAria() injected the label into the assertive live region, then a hard-coded 100ms setTimeout deleted the nodes and moved focus back to the canvas. On webOS/Samsung TV screen readers that teardown fired before the reader finished (often before it started) speaking, so nothing was announced. The cancel() path did the same immediate clear + focusCanvas(). Implements proposed fix #1 (replace-on-next-write) from issue #31: - Remove the 100ms teardown timer. The label is written and left in place; the region is cleared only when the next announcement replaces it (cleanAriaLabelParent moved before injecting new spans). - Skip the write when there are no phrases, so a canceled/empty series can't blank out the label a reader is still speaking. - cancel() (aria) no longer clears the region or moves focus; it drops partial phrases and properly stops the series (active=false, cancel nested) instead of returning early. - Remove the now-unused focusCanvas() helper; no focus is moved at all. Adds tests covering injection, persistence past the old 100ms window, replace-on-next-write, and cancel not wiping the region. Fixes #31 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.
Fixes #31.
Problem
In aria mode (
Announcer.aria = true),focusElementForAria()injected the focus label into the#aria-parentaria-live="assertive"region, then a hard-codedsetTimeout(…, 100)deleted those nodes and moved focus back to the canvas. On webOS/Samsung TV screen readers (Audio Guidance), that teardown fires before the reader has finished — often before it has started — so nothing is announced, even though the DOM mutates exactly as expected. Thecancel()path did the same immediate clear +focusCanvas().Fix — proposed option #1 (replace-on-next-write)
The live region now holds the current label until the next announcement replaces it, instead of being torn down on a timer:
focusElementForAria()— removed the 100ms teardown timer. The region is cleared (cleanAriaLabelParent()) immediately before the new spans are injected, so it always contains exactly the current label and the reader is never interrupted. Added an early return when there are no phrases to write, so a canceled/empty series can't blank out a label the reader is still speaking.cancel()(aria path) — no longer clears the DOM region or moves focus. It drops any partially-accumulated phrases and falls through to actually stop the series (active = false, cancel nested) instead of returning early.focusCanvas()helper — no focus is moved at all anymore, which also avoids focus changes interrupting the reader.Tests
Added
tests/announcer-aria.spec.tscovering:All 4 pass;
tsc, ESLint, and Prettier are clean. (The pre-existingflex.performance.spec.tstiming benchmark can flake under load and is unrelated to this change.)Reviewer note
This slightly changes
cancel()semantics in aria mode: the series now actually halts (active = false/ cancels nested) instead of returning early. That is more correct, but flagging in case anything relied on the old early-return behavior.🤖 Generated with Claude Code