fix(a11y-android): carry the device's own node ref, don't infer it - #354
Merged
Conversation
The service reader assumed `AxNodeId(n)` equalled the companion's `ref` n, because both sides number the same nodes in the same pre-order. That holds only while the host keeps every node the device sent. The `Depth` and `Siblings` caps drop a subtree and carry on with later siblings, so past the first pruned subtree a host id is lower than the device's own ref, and an action dispatched by it reaches a different node and reports success. The companion has emitted `ref` on every node since its first release and resolves an action against its own freshly re-read, untruncated tree, so the ref namespace was never the host's kept set. Read it instead of inferring it: `json_to_node` records each node's ref in the pre-order `assign_ids` numbers by, and `RefTree` pairs the tree with that table so `invoke` and `set_value` dispatch by what the device called the node. A node with no `ref` fails the snapshot, like malformed bounds — dropping it would shift every later id. That removes the class rather than one path, so the guard refusing a native click on a `Depth`/`Siblings`-truncated tree goes; such a tree now actuates natively instead of falling back to a pointer click. Fixes #288 Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
xxx
marked this pull request as ready for review
August 6, 2026 03:57
xxx
added a commit
that referenced
this pull request
Aug 6, 2026
) The convention is that a changelog entry lands with its PR. Auditing all 101 commits since v1.1.0 for whether they touched CHANGELOG.md found five that changed what a user sees and carried no entry: - #354 a native Android action through the companion reached a different element than the one named, on any truncated tree, and reported success - #353 a failing device was classified by matching words in a message whose tail is the device's own output, so a wedged emulator read as "not ready" - #330 an element whose screen had been destroyed was reported as changed, advising a re-snapshot that cannot help - #310 a subtree dropped by a failed child read left the tree looking complete - #283 glass_clipboard_set on the iOS Simulator returned ok for a write that was still in flight All five are corrections to what glass reports rather than additions, so they join ### Fixed. No code changes. cargo test --workspace: 1938 passed, 0 failed. cargo fmt --check clean. Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Aug 6, 2026
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.
The on-device-service reader assumed
AxNodeId(n)equalled the companion'srefn, on the grounds that both sides number the same nodes in the same pre-order. That holds only while the host keeps every node the device sent. TheDepthandSiblingscaps drop a subtree and then continue with later siblings, so past the first pruned subtree a host id is lower than the device's own ref — an action dispatched by it reaches a different node and reports success.The companion has emitted
refon every node since its first release, and resolves an action against its own freshly re-read, untruncated tree. The ref namespace was therefore never the host's kept set.What changed
json_to_nodereads each node'srefand records it in the pre-orderassign_idsnumbers by;tree_from_jsonreturns aRefTreepairing the tree with that table.invokeandset_valuedispatch byRefTree::device_ref(..)— what the device calls the node — instead of the host id.set_valueaddresses the node its guard approved rather than the id the caller named.reffails the snapshot, like malformed bounds: dropping it would shift every later id, and inferring it is the bug this removes.Depth/Siblings-truncated tree is gone. Such a tree now actuates natively rather than falling back to a pointer click.The
uiautomatorreader carries no equivalent inference — it acts by tap coordinates and consults truncation only to explain an element missing from its own re-read tree.Verification
cargo test --workspace,cargo clippy --workspace --all-targets -- -D warningsandcargo fmt --checkare clean.Both action paths are covered end-to-end against the fake device, over a tree shaped
root > [Card > (c1, c2, c3), trailing]where the trailing node is deviceref5:depth: 1makes it host id 2 — a click by host id would actuatec1.siblings: 2makes it host id 4 — a write by host id would land inc3.Both assert the exact ref on the wire, and both were confirmed to fail against the id-dispatching code.
The Android device suite is schedule-only, so PR CI does not exercise this on a real emulator. Default limits (depth 30, siblings 4096) do not fire on a real app, so an on-device run would cover the untruncated path as a regression check rather than the fix itself.
Fixes #288
🤖 Generated with Claude Code