You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(a11y): name role=toolbar regions, hide linemetricsdiv from AT (#7255) (#7777)
* fix(a11y): name role=toolbar regions, hide linemetricsdiv from AT (#7255)
Two regressions called out in the 2026-05-16 follow-up on #7255, after the
firefox accessibility inspector flagged them:
(1) The "Ether X" announcement between the editor and chat button was the
outer ace iframe (titled "Ether") plus a single 'x' text leaf the renderer
appends to outerdocbody for line-height measurement (linemetricsdiv in
ace.ts). Add aria-hidden=true on creation so AT skips the measurement node
entirely. Same approach we used for sidediv in PR #7758.
(2) The two formatting/actions <ul role="toolbar"> regions and the
history-mode role=toolbar div had no accessible name. Lighthouse + the
firefox a11y panel both flagged this. Putting data-l10n-id directly on
the <ul> would either destroy its <li> children (textContent branch) or
not populate aria-label (the html10n auto-aria-label code path skips
non-form-control elements), and a hidden <span> child inside the <ul>
would be invalid HTML. Solution: three visually-hidden <span> labels
sitting just before #editbar, each carrying data-l10n-id for translation,
referenced from the toolbars via aria-labelledby. Apply the same treatment
to .show-more-icon-btn, whose aria-label was previously hardcoded English
(no data-l10n-id, so untranslated).
Adds Playwright assertions for linemetricsdiv aria-hidden and the resolved
accessible-name text of each toolbar. Updates the existing show-more test
to expect aria-labelledby (it previously asserted hardcoded English
aria-label).
Refs #7255
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(a11y): reuse translated history-controls label key (Qodo PR review)
Qodo flagged: adding `aria-labelledby="editbar-history-label"` on
#history-controls overrode the `aria-label` that pad_mode.ts sets from
`pad.historyMode.controlsLabel`. That key is already translated in
multiple locales (en/de/nl/...); the new `pad.editor.toolbar.history`
key was English-only, so non-English users would regress from a
localized history-toolbar name to the English fallback.
Point the hidden label span at the existing translated key instead of
minting a new one, drop the new key from en.json, and update the
Playwright expectation to match the translated string ("Pad history
controls"). The aria-label that pad_mode.ts still writes to
#history-controls is now redundant (aria-labelledby wins) but harmless,
and leaving it preserves the runtime relocalization path.
Refs #7777
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(a11y): mark toolbar li/a wrappers presentational (Lighthouse, #7255)
Lighthouse's axe-core `listitem` rule fires on every toolbar button
because role="toolbar" on the <ul> overrides its implicit role="list",
leaving the <li> children "orphaned" by axe's heuristic. Murphy's
2026-05-16 follow-up on #7255 attached the Chrome DevTools Lighthouse
panel screenshot of this exact failure.
Marking the <li>+<a> wrappers role="presentation" tells axe-core they
are layout scaffolding for the toolbar role, while the inner <button>
keeps its semantics for AT. Same treatment for SelectButton's <li>
wrapper. The Separator's <li> also gets aria-hidden=true so AT does
not announce an empty list item between toolbar buttons.
CSS and JS selectors that still target `.toolbar ul li` continue to
work — role="presentation" only affects the accessibility tree, not
the DOM tree. No visual or behavioral change for sighted users.
Adds a Playwright spec that walks every rendered toolbar <li>/<a>
and asserts role="presentation" so future toolbar.ts tweaks can't
silently re-introduce the Lighthouse failure.
Refs #7255
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(a11y): label #online_count for AT (#7255 - "number next to the user icon")
Murphy's 2026-05-16 follow-up cut off mid-bullet ("It's not clear what the
number next to the …"). Best guess: the user-count badge in the showusers
toolbar button. Currently it exposes a bare digit to AT — "5" with no
context — because the visible badge text is also the entire accessible
content.
Append a localized aria-label generated from a new pad.userlist.onlineCount
key (plural macro for one / other) whenever the count updates, so AT
announces "5 connected users" instead of the bare digit. Add role=status
and aria-live=polite so the count change is announced inline without
forcing the user to refocus the button.
Visible badge digit unchanged. html10n.get is null-safe (falls back to
an English template so AT never gets back "undefined" before the locale
bundle has loaded).
Adds a Playwright spec verifying role/aria-live and that the aria-label
contains "connected user".
Refs #7255
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(a11y): tighten #online_count + plugin-emitted toolbar <li>s (#7255)
Two small follow-ups on top of the toolbar/online-count work:
(1) #online_count had no accessible label on solo-author pads.
updateNumberOfOnlineUsers — which writes the localized aria-label —
only fires on userJoin/userLeave/status change, never on initial
single-author load. Call it at the end of init() so the badge ships
with its label on first paint. Also bind html10n's 'localized' event
so non-English users get the translated label after the locale bundle
arrives (matches the keyboard-hint / history-toolbar pattern).
Harden the Playwright spec to use polling toHaveAttribute instead of
one-shot getAttribute.
(2) Sweep role="presentation" onto plugin-emitted toolbar <li>s in
pad_editbar.ts init(). Core's toolbar.ts emits its <li>s with the role
already, but plugins (ep_headings2, ep_align, ep_font_*, ep_print, ...)
ship their own editbarButtons.ejs templates that emit <li> directly,
so Lighthouse's listitem rule kept firing on the "with plugins" test
runs. Runtime sweep covers anything in the editbar at init time, no
plugin coordination needed.
Refs #7255
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
<buttontype="button" class="show-more-icon-btn" aria-label="Show more toolbar buttons" aria-expanded="false"></button><!-- use on small screen to display hidden toolbar buttons -->
163
+
<buttontype="button" class="show-more-icon-btn"
164
+
aria-labelledby="editbar-showmore-label"
165
+
aria-expanded="false"></button><!-- use on small screen to display hidden toolbar buttons -->
0 commit comments