android: what a screen reader says — names, roles, and the sheets out of the tree when they are down
Every control on the phone has a name now, in the app's own words, and the desktop's word where it already has one; the nav is a tab set whose badge count is part of the tab's name — "You, 3 transfers" rather than a stray 3 read out before it. The two sheets are dialogs with somewhere sensible for the focus to land, and the half sheet's grab handle is finally a real Close — the first dismissal a screen reader could reach, because the scrim never was one. A sheet that is down is inert rather than sitting off-screen in the accessibility tree with the hidden mini bar and the nav under it, and one live region is mounted for the life of the app so that an undo offer is actually announced; switches say on or off, chips say pressed, and the waveform is a slider where it can seek. Nothing visible changed. The walk counts the controls with no name, with a junk name, in a hidden layer, or with unsaid state — all four held at zero on ten screens, and the nameless count on a room and on both prompts besides, which is where the six fields named here actually live.
This commit is contained in:
1 parent
9df162360e
commit
4fc6e9c630
22 files changed
+355
-83
No files matched your search
+68
@@ -516,6 +516,74 @@ with a `ClassNotFoundException`. `src-tauri/android/build.sh` exports it;
|
||||
after adding a plugin, touch `src-tauri/build.rs` too, so the app's build
|
||||
script re-runs and rewrites the wiring.
|
||||
|
||||
## What a screen reader says
|
||||
|
||||
Every control on the phone is named by what it does, in the app's own
|
||||
words, and where the desktop already has a word for something it is the
|
||||
word used here too — Seek, View album artwork, Close artwork viewer. An
|
||||
icon-only control carries an `aria-label`; one with visible text is named
|
||||
by the text it shows.
|
||||
|
||||
The nav is a tab set: `role="tablist"` on the bar, `role="tab"` and
|
||||
`aria-selected` on the five destinations, and the count badge round
|
||||
seventeen added is part of the tab's own name — "You, 3 transfers",
|
||||
"Community, 2 unread" — rather than a digit read out before the word it
|
||||
belongs to. The badge itself is `aria-hidden`, because the number is in
|
||||
the name now.
|
||||
|
||||
The two sheets are dialogs: `role="dialog" aria-modal="true"`, the player
|
||||
sheet named for what is playing, the half sheet reading its own first
|
||||
title back out of the DOM so that twenty-seven kinds of sheet need no list
|
||||
of names to keep in step. Opening one puts the focus somewhere sensible —
|
||||
the player sheet on its Close, the half sheet on its grab handle, which is
|
||||
a real button now ("Close this sheet") and the first dismissal a screen
|
||||
reader could ever reach; the scrim behind it never was one.
|
||||
|
||||
A sheet that is down is `inert` and `aria-hidden`. Neither sheet is
|
||||
unmounted — they are translated off the bottom of the screen, and the half
|
||||
sheet deliberately keeps its last body rendered through the slide down —
|
||||
so before this the closed sheets' rows, the hidden mini bar and the nav
|
||||
under an open sheet were all still in the accessibility tree, and TalkBack
|
||||
could swipe into every one of them.
|
||||
|
||||
There is one live region, `.sr-live`, mounted for the life of the app and
|
||||
clipped to a single pixel, because a region that appears with its words
|
||||
already inside it is announced reliably by nobody; an announcement here is
|
||||
a change of text in a node that was always there. That is what carries an
|
||||
error and a notice, so an Undo offer is actually read out — and the Undo
|
||||
button itself stays reachable, only the sentence beside it is hidden from
|
||||
the tree so it is not said twice.
|
||||
|
||||
Artwork that opens the viewer is a control, not decoration. Artwork that
|
||||
is decoration carries `alt=""` and every icon is `aria-hidden`, so the
|
||||
tree never reads a filename out. The waveform is `role="slider"` with a
|
||||
value and arrow keys where it can seek, and `aria-hidden` on the mini bar
|
||||
where it is only a picture of the position. Switches say on or off, choice
|
||||
chips say pressed, and the two long descriptions and the gap line say
|
||||
expanded.
|
||||
|
||||
The check, so this does not quietly rot: the walk's probe sweeps every
|
||||
interactive element in the live DOM and counts four things — the ones with
|
||||
no accessible name (`a11yAnon`, with `a11yWho` naming the first three
|
||||
offenders so one run tells you what to add), the ones whose name is junk
|
||||
(`a11yBad`: bare digits, a filename, a lone glyph, an essay), the ones
|
||||
sitting in a layer that is off-screen but still in the tree (`a11yGhost`)
|
||||
and the ones that look chosen without saying so (`a11yState`). All four
|
||||
are asserted at zero on Home, the library, a record, a catalog record, the
|
||||
artist page, You, the taste page, a half sheet, the player sheet and the
|
||||
viewer — and the anonymous count on a room and on both shapes of prompt
|
||||
besides, because a room's message box and the prompt's own field are where
|
||||
the fields this round finally named actually live: assert it only where
|
||||
they are not and the round guards everything except its own work.
|
||||
|
||||
One known exception, left alone on purpose: the per-row ⋯ is a
|
||||
`role="button"` inside the row's own `<button>`, and the mini bar's three
|
||||
transport buttons sit inside its `role="button"` the same way. That is
|
||||
invalid ARIA, and both ends are named so nothing is anonymous; unpicking
|
||||
it means splitting `.trk` (one flex row with a whole-row `:active`) and
|
||||
rewriting the walk's `.trk .r` selectors, for no gain a screen reader
|
||||
would notice.
|
||||
|
||||
## Performance
|
||||
|
||||
`tests/mobile/perf.mjs` logs the frames the WebView produces through
|
||||
|
||||
Reference in new issue
Block a user