diff --git a/ANDROID.md b/ANDROID.md index 61391ea..b397718 100644 --- a/ANDROID.md +++ b/ANDROID.md @@ -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 ` {/snippet} -
-
+