Skip to content

feat: read a table, outline or grid as columns and rows with each row's element ID - #86

Merged
iamnbutler merged 1 commit into
mainfrom
feat/table-read
Oct 17, 2026
Merged

iamnbutler merged 1 commit into
mainfrom
feat/table-read

Conversation

@iamnbutler

Copy link
Copy Markdown
Contributor

desktop_read with table: true and a scope reads a table instead of text. It covers awkward item 5 in #75.

What it returns

  • Which table. The scope is a table, outline or grid (AXTable, AXOutline, AXGrid; web tables and ARIA grids are AXTable), or an area that holds one, such as a scroll area or web area. The first table in it is read, and tables_in_scope counts the others.

  • columns. The header titles, taken from the first of these that exists:

    • the AXColumn titles;
    • the header sort buttons a scroll view draws just above the table (Finder's list view);
    • a web table's header row, when every cell's role description says header.

    Otherwise it is null.

  • rows. Each row has:

    • cells: each cell's Accessibility text, in column order;
    • element: the row's ID in the scope snapshot, for desktop_click;
    • selected;
    • for outline rows, depth (0 at the top level) and expanded.
  • Counts. row_count and row_offset, plus truncated/truncation (row_limit, column_limit, cell_text_limit, text_limit, field_text_limit, output_limit, and the traversal's own limits).

  • Bounds. max_rows defaults to 100, at most 500, and row_offset pages through. Cells are cut to 256 characters, rows to 64 cells, and a page to 96 KB of cell text. Native fields are cut to 512 characters and the native reply to 1.5 MB.

How

  • New native table read. It reads Accessibility only (inspectAccessibilityTree) with desktop_read's wide budget, so there is no capture and no new snapshot. It finds the scope element again exactly as scoped read and inspect do, and returns the subtree as flat elements.
  • Row IDs. Each element is mapped to its ID in the scope snapshot when that snapshot stored the same element unchanged, matched by role, depth, bounds, label, title and identifier. When a key occurs more than once, the elements are matched in order, and only when both traversals hold the same number of them. Otherwise the ID is null. The note says to inspect again with scope set to the table, since a whole-window inspection stores only 200 elements.
  • Table assembly. src/table.ts builds the table from the flat list, which keeps it testable on fixtures.
  • New patch peekaboo-row-disclosure.patch, last in the stack. Outline rows are flat siblings in AX, so the observation worker now adds disclosureLevel and disclosing to AXRow elements. It makes one extra read each, only for rows. No Bridge op is added. A worker test is included.

Checks

  • npm run check passes. npm test passes 33, including test/table.test.mjs with fixtures for:

    • a Finder list view: header buttons in the scroll area, outline depth, expansion, selection;
    • a Numbers table: no header titles, an empty cell;
    • a web table in a scoped web area: header row, a row without an ID.

    The tests also cover paging and every limit.

  • The Peekaboo DetachedAXObservationWorkerTests suite passes (33 tests, including the new disclosure test).

  • Native and the signed dev app build. Smoke test against the dev app: a bogus scope snapshot is refused by the host, and a table read without a scope is refused.

  • Live checks are pending (screen locked). While the session is locked, AX returns no elements at all, so the fixtures model the standard AppKit and WebKit shapes; I couldn't survey them. The live checks in Run the live checks deferred while the screen was locked #42 will confirm or correct three assumptions:

    • Finder's header buttons sit just above the outline;
    • Numbers exposes cells as AXCell values;
    • Safari/Chrome header cells carry a "header" role description.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant