feat(a11y): WalkA11y — every accessible element WITH its position - #177
Merged
Conversation
CollectA11y answers what a tree contains. Nothing answered where each thing is, and that is the other half every platform accessibility API asks for: a screen reader draws its focus ring, routes a touch and moves the pointer from a rectangle, so an element described without one can be read but never pointed at. WalkA11y descends the widget tree through the same childContainer convention CollectRuns uses, so a host no longer has to keep a flat list of everything it composed — which CollectA11y requires and a nested layout makes impractical. RolePresentation widgets are skipped exactly as CollectA11y skips them, but the walk still descends INTO them, because a box contributes nothing to announce while its children usually do. The node carries Bounds verbatim rather than accumulating ancestors' offsets. Bounds in this toolkit are already absolute: translateEvent converts a parent-local event to child-local with `ev.X + parentRect.X - childRect.X`, which only holds when both rectangles share the surface origin. Accumulating — the obvious reading of "placement within its parent surface" — would place every nested element at roughly twice its true distance from the origin, plausible-looking and wrong. A test locks that down rather than a comment alone. Nothing else is filtered. Whether an unnamed or zero-area element is worth publishing belongs to the platform bridge that knows what its own screen reader does with one. This is the primitive the NSAccessibility, AT-SPI and UI Automation bridges need to move into go-widgets/window, where they will make every go-widgets application accessible without the application doing anything. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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.
CollectA11yanswers what a tree contains. Nothing answered where eachthing is — and that is the other half every platform accessibility API asks for.
A screen reader draws its focus ring, routes a touch and moves the pointer from a
rectangle, so an element described without one can be read but never pointed at.
WalkA11ydescends the widget tree through the samechildContainerconventionCollectRunsalready uses, so a host no longer has to keep a flat list ofeverything it composed — which
CollectA11yrequires and a nested layout makesimpractical.
RolePresentationwidgets are skipped exactly asCollectA11yskips them, but the walk still descends into them: a box contributes nothing
to announce while its children usually do.
The part worth reviewing
The node carries
Bounds()verbatim rather than accumulating ancestors'offsets, because bounds in this toolkit are already absolute.
translateEventconverts a parent-local event to child-local with
ev.X + parentRect.X - childRect.X, which only holds when both rectangles sharethe surface origin.
Accumulating — the obvious reading of "placement within its parent surface" in
the
Widget.Boundsdoc — would place every nested element at roughly twice itstrue distance from the origin: plausible-looking, and pointing a screen reader at
the wrong part of the window.
TestWalkA11yBoundsAreNotAccumulatedlocks thatdown rather than leaving it to a comment.
Nothing else is filtered. Whether an unnamed or zero-area element is worth
publishing belongs to the platform bridge, which knows what its own screen reader
does with one.
Why now
This is the primitive the NSAccessibility, AT-SPI and UI Automation bridges need
in order to move into
go-widgets/window. They exist and work today — eachverified with its platform's own client — but they live in
go-news-reader/reader/internal/window, a duplicate of this org's windowinglibrary. Upstreamed onto
WalkA11y, they make every go-widgets applicationaccessible without the application doing anything, because
window.Runalreadyreceives the widget tree.
Verification
gofmt,go vet, full package tests green;WalkA11yat 100% statementcoverage and the whole-package 100% gate still passes.
🤖 Generated with Claude Code