fix(widgets): four accessibility defects an audit measured - #101
Merged
Conversation
Contrast: --selfstore-ok sat at 3.30:1 on white and --selfstore-warn at 3.19:1, where AA asks 4.5:1 for text. They are used as color on sev-ok, sev-warn and warn-note, so the status sentence itself failed. One step darker each, 5.02:1, same perceived hue. A test computes the ratio now. Focus: the gate declared role=dialog and aria-modal=true, which tells assistive tech it is modal and does nothing to Tab. A keyboard user left the blocking screen for the application behind it, still covered by the gate they were no longer in. Focus landing outside comes back, and closing returns it where it was. A guard rather than a focusable cycle, because the gate holds a child with its own shadow root. Announcements: no widget had a live region. selfstore-status is now a polite one across its three variants, and every error note is role=alert. Motion: the spinner respects prefers-reduced-motion. Found by the tests, and worth naming: pulling focus back fires another focusin, so the guard needed a re-entrancy flag or it recursed until the stack gave out. GLYPH also left the render function for module scope - it is the channel that carries severity when the two colors, darkened to the same threshold, share a luminance.
|
Merged
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.



An optimization audit on weight, green IT and accessibility. Weight and energy
came back clean; accessibility did not. Four defects, each measured, each
inherited by every app that uses the widgets.
1. The default severity colors were unreadable, as text
--selfstore-ok#16a34a--selfstore-warn#d97706Not decoration:
sev-ok,sev-warnandwarn-noteset them ascolor, sothe status sentence itself failed the 4.5:1 text threshold. Both move one step
darker (
#15803d,#b45309), landing at 5.02:1, without changing the coloranyone thinks they see.
A consequence worth stating: chosen for the same ratio, they now share a
luminance, so on a greyscale screen they are the same grey. That is acceptable
only because severity also carries a glyph, and a test asserts the glyph so
removing it fails here rather than in someone's hands.
2. The blocking gate did not hold the keyboard
role="dialog"andaria-modal="true"tell assistive tech the screen ismodal. Neither touches the Tab key. A keyboard user tabbed out of the first-run
gate into the application behind it, still covered by the screen they were no
longer in, driving something they could not see.
Focus that lands outside is pulled back, and closing the gate returns focus
where it was. A guard rather than a cycle through focusables: the gate holds a
<selfstore-connect>with its own shadow root, and walking focusables acrossshadow boundaries is the kind of code that quietly stops matching the DOM.
3. Nothing was ever announced
Not one
aria-livein any widget. A screen reader user was never told a savestarted, that access expired, or that a destination needs reconnecting - from
the element whose entire job is to say the situation changed.
<selfstore-status>is a polite live region across its three variants, set onthe host so an app can still override it. Every error note carries
role="alert".4. The spinner ignored
prefers-reduced-motionThe one animation in the library, an endless rotation, during exactly the wait
nobody can leave.
What the audit cleared
Worth recording so it is not re-audited: the core entry is 17.4 kB brotli,
and both heavy dependencies are already lazy - hash-wasm (61 kB) and fflate
(10.8 kB) load only when a key is derived or an archive is read. The 5-minute
sync interval is guarded by tab visibility,
syncIfStalethrottles at 20 s anddoes a cheap
stat()before downloading, and everything tears down. There isnothing to reclaim there.
One weight finding remains and is not in this PR: the simple store statically
imports Drive, WebDAV and S3, so an app using only
fileships 14 kB it neverruns. Same shape as the widget barrel fixed in #100.
Verification
Gate green, exit 0. 766 tests over 64 files, 8 new.
Two things the tests caught, both real: pulling focus back fires another
focusin, so the guard recursed until the stack gave out until a re-entrancyflag was added - it failed in this suite before it could fail in a browser. And
asserting "the listener is gone" had to be done on the listener, because a
detached host stays
document.activeElementin this DOM and the observableoutcome could not tell the two states apart.
Coverage moved less than a rounding step, so the ratchet stays.