feat: announce fenced-code copy result to screen readers#208
Merged
Conversation
The copy button conveyed success/failure only by rewriting its own aria-label, which is not announced on an unfocused element, so SR users got no copy confirmation. Add a visually-hidden aria-live region (sibling of the button, not nested — a role=button subtree is often exposed atomically) that announces Copied politely and Copy failed assertively, cleared after the feedback window so a repeat copy re-announces.
…ontracts Address 4 MEDIUM review findings on the fenced-code copy button's SR live region: - Rapid-repeat re-announce: clear the live region synchronously at click start so a repeated identical copy result is a fresh empty->text mutation the AT re-announces (the 1500ms revert stays as a fallback clear). Pin it with a re-announce test plus a stale-revert-does-not-blank variant. - announce() politeness-before-text ordering is now covered: spy the failure click path and assert aria-live is set to assertive BEFORE the text lands. - aria-atomic="true" is asserted through the whole announce -> revert cycle, not only at build time. - .quoll-copy-button-wrap gets an explicit position:static theme rule (with a test) so the wrapper can never silently become the absolute button's anchor.
Simplify pass: the sr-only live-region comment and the wrapper position:static comment were mis-placed (and the latter duplicated) above .quoll-copy-status; move each above the rule it documents. Comments only.
- Fix inaccurate 'LATER macrotask' comment on the click-start live-region clear: the clipboard promise settle is a microtask continuation, not a macrotask. Reworded to describe the async behaviour without naming a specific task category. - Add a test covering the compound outcome sequence (fail via mockRejectedValueOnce, then succeed via mockResolvedValueOnce on an immediate re-click before the revert): pins that the click-start clear resets aria-live from assertive back to polite and clears the text synchronously before the second settle lands.
The compound-sequence test checked textContent==='' at the click-start clear but not that aria-live was synchronously reset to polite, so a regression leaving the prior failure's assertive politeness in the success window would slip through (the later success settle re-sets polite before the final assertion). Add the synchronous aria-live==='polite' assertion the test's own comment already promised.
d69dd52 to
e550505
Compare
mtskf
added a commit
that referenced
this pull request
Jul 12, 2026
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.
Summary
Screen readers now announce the result of the fenced-code copy button. Previously the button conveyed success/failure only by rewriting its own
aria-label, and a label change on an unfocused element is not announced — so SR users who clicked (or pressed Enter on) the button got no copy confirmation. This was the only async-feedback surface in the webview without a live region.Changes
fenced-code-copy-button-widget.ts:toDOMnow returns a small wrapper hosting the button plus a visually-hiddenaria-livestatus node (a sibling, not a child — arole=buttonsubtree is often exposed atomically, so a nested live region may never be observed; mirrors the standalonerole="alert"banner pattern). The copy handler announces "Copied" (polite) / "Copy failed" (assertive) alongside the existing label swap, and clears the region after the feedback window so a repeat copy re-announces.theme.ts: adds the canonical sr-only clip rule for.quoll-copy-status(out of flow, so it never disturbs the collapsed open-fence row's zero height).toDOMtests updated to pull the button back out of the wrapper.Related
Test Plan
pnpm compilegreenpnpm test:unitgreen (full suite, 3332 tests)pnpm lintclean on touched files