Skip to content

Windows UIA: Accessibility.zig contract declares elements absent from the live AccessibilityProvider.cpp tree #417

Description

@coneilen

Summary

The Windows shell has two independent descriptions of the UI Automation tree, and they have silently diverged:

  1. graphcode-windows/src/Accessibility.zig - defaultContract(), instantiated at runtime in App.init and wired to WM_GETOBJECT.
  2. graphcode-windows/src/AccessibilityProvider.cpp - the hand-rolled native fixed element table that the live UIA tree is actually traversed from.

Several ids declared in the Zig contract have no counterpart in the C++ table, so they cannot be found by a live UIA client no matter what the Zig-side unit tests assert.

Evidence

This was first discovered empirically during PR #408. A live-gate assertion for move-project-unavailable failed in CI (uia-live-gate.ps1:283, job 106845146915) with "project Move action was not exposed as a disabled UIA menu item" - because that id exists only in the Zig contract. #408 was fixed by replacing the assertion with a closed-set omission proof over the actions menu's real children, and that fix is on main.

However, the underlying divergence was never addressed, and more affected ids landed afterwards via PR #409:

Declared in Accessibility.zig but absent from AccessibilityProvider.cpp:

  • workspaces (Accessibility.zig:295)
  • workspace-new (:304)
  • workspace-rename (:311)
  • workspace-delete (:318)

Verified on main (4e88dfa): each of these appears only in Accessibility.zig and its own tests. The sole other match for workspace-new is workspace-new-tab-, an unrelated dynamic prefix emitted by AccessibilityProvider.cpp:812. No live-gate assertion references any of the four.

Why this went undetected

Accessibility.zig's tests assert against the same in-memory model that produced the elements, and by array index:

try std.testing.expectEqualStrings("workspaces", provider.elements.items[23].id);

This is self-referential - it can never detect that the live tree lacks the element. Index-based assertions have already caused one real bug in this file: an assertion on index 18 silently validated terminal-a instead of the intended move-project-unavailable at index 22, because both expose .text.

Impact

Not a functional regression - the workspace lifecycle feature works, and its non-UIA behavior is covered. The gap is that its accessibility exposure is unproven: assistive technology would not find these elements even though the contract claims them. This is a correctness/a11y gap, not a crash.

The UI Automation tree row in investigation/ui-parity-matrix.md is already Partial, so the ledger is not overclaiming at row level.

Confidence

High on the divergence itself (the move-project-unavailable CI failure is direct empirical proof of the mechanism, and the four ids are verifiably absent from the C++ table). I have not executed the live gate against these four specific ids, so the claim that they are unreachable live is inferred from the same mechanism rather than separately observed.

Suggested fix

  1. Add the missing elements to the AccessibilityProvider.cpp fixed table so the contract is honest, or remove them from defaultContract() if they are not meant to be live.
  2. Add a check that fails when an id in defaultContract() has no counterpart in the native table - the absence of this check is the root cause.
  3. Convert the index-based assertions in Accessibility.zig to assert on id, so element insertions cannot silently re-point an assertion at a neighbouring element.

Context

Found while closing out the Windows parity fleet (#408-#412, #414, #415, #416). Filed rather than fixed inline because it spans two workstreams' merged code and warrants its own review.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions