Repository navigation
fix(agent): a semantic tree that names tables, headers, regions and text - #56
Merged
Merged
Conversation
added 6 commits
September 23, 2026 11:04
The published 0.5.1 predates endpoint-libs 3.2 and still imports the framed_json that 3.2 removed, so it cannot compile against the endpoint-libs this tree now resolves. A caret alone kept selecting it, because 0.5.3 is not published yet. The path dependency keeps the version requirement beside it, so this goes back to a plain registry dep by deleting one path key once 0.5.3 is released. Resolved endpoint-libs moves 3.0.0 to 3.2.1; the requirement was already a caret and the lock was simply stale.
The runtime is becoming ours rather than a thin layer over Tauri, so it takes a name of its own instead of one that describes what it wraps. Docs call it IRB. Crate only. The repository, its GitHub URLs and the tauri dependencies keep their names, so this is a one-line change for a consumer: the package it asks for is izumo, and the module path is izumo rather than tauri_runtime_blitz.
The only tokio here this crate actually chooses. The rest of its tokio, the watch sender and the oneshot pair, are the types blitz-control-protocol names in its public API, so they cannot move until that crate does. An RwLock only ever written is what an async mutex is, and the bound that usually blocks the substitution, RwLock<T> being Sync only when T is, is satisfied trivially here because the guarded type is (). new is const, so it still serves as a static.
The watch sender and the oneshot pair were here only to feed blitz-control-protocol's public API, and that API no longer speaks either: the bridge returns an Arc<Once<DebugResponse>> and the event stream is an Arc<Latest<DebugEvent>>. Converting the call sites was the whole change, and with it the dependency is gone from the manifest. cargo tree -e normal -i tokio prints nothing for a headless build. What remains in a windowed one is tauri's own, which is not ours to remove without the fork. subscribe is gone with the watch: a Latest reader is a clone of the Arc, because the reading position lives in the reader rather than in a handle the slot hands out. The paint hook uses set_now, the synchronous setter, since a window event handler is not async. control_interface_is_absent_until_explicitly_enabled still fails, as it did before this change and on the commit before it. It starts a real control server and asserts on deep profiling; the failure predates this work.
The event loop answered a bridged request with `let _ = response.send(result)`. `Once::send` is an async fn, so that built a future and dropped it unpolled: the reply was never stored and the socket task waited until the client gave up. Every request that needs the window, `inspect` and `act` among them, hung for the client's whole timeout, while `initialize` (answered off the event loop) kept working, which is what made it read as a transport bug. `fill` is the synchronous half and is what a non-async caller should use. The test bridge made the same mistake and gets the same fix.
The dependency named a sibling checkout by path. CI checks out this repository alone, so the path does not exist there and the build cannot start, and publishing needs the version on crates.io regardless. ^0.5.3 resolves once ps-observability releases it, which is the order these land in.
pathscale
force-pushed
the
fix/semantic-tree-tables-and-text
branch
from
September 23, 2026 04:08
f028975 to
e57801f
Compare
added 2 commits
September 23, 2026 18:45
ps-anyrender, ps-blitz-dom, blitz-control-protocol and nagoya were required at a patch floor. No lockfile is tracked, so the newest release resolves anyway; the floor only had to be chased on every release.
The crate was renamed but every job still asked cargo for -p tauri-runtime-blitz, which no longer exists, so cargo fell back to treating the flags as applying to a package outside the workspace and refused.
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.
Twelve end-to-end QA suites ran against real sites and the semantic tree was the
biggest cap on what they could assert. Seven findings, all reproduced from one
document and each fixed with a test that fails without the fix.
The reproduction, which is the fixture the tests read:
Before, through
inspect_document:After:
What changed
A cell, a header and a row are named by their contents. The role list said
cellandrowwere deliberately absent because their content is a wholesubtree. That objection belongs to
generic: ARIA givescell,gridcell,columnheader,rowheaderandrownameFrom author and contents, and a row'sname being the run of its cells is what a screen reader announces on entering
the row. This is the finding that mattered most. Two site suites reported their
tables as absent from the accessibility tree; every cell was there and every one
of them was nameless, which from outside is the same thing.
A
<th>is acolumnheaderor arowheader, by itsscope. Both cellkinds mapped to
cell, so nothing distinguished a column's title from a valueunder it. blitz-dom's own accessibility tree already does this
(
packages/blitz-dom/src/accessibility.rs), including the fallback tocolumnheaderwhen noscopeis written; the two trees disagreed over the samedocument and now agree.
A named
<section>is aregion. HTML-AAM maps a section with anaccessible name to
regionand one without togeneric. Decided fromattributes, because the name is not computed at that point and computing it
there would walk a section's whole subtree once per element in the document.
This is the one place the two trees do not converge: blitz-dom reports
Role::Sectionfor every<section>, and the HTML-AAM rule is the one a QAsuite and a screen reader both act on.
A tooltip is named by what it says. Same class as the live-region finding:
the roles whose whole purpose is to say one thing were the roles reporting
nothing.
An option with no text falls back to
label, thenvalue.<option value="u64">is how a<datalist>is written. Empty contents no longer end thename search either: the contents arm returned
Some("")for any role on thenameFrom-contents list, which made every fallback below it unreachable.
A name is made of what is rendered. A responsive control writes a short
label and a long one and shows one of them:
sm:hiddenon the first andhidden sm:inlineon the second came back asBook Book a diagnostic, a nameno viewer sees at any width.
name_textnow skips an element child that is notrendered, which is
display: none,visibility: hidden, thehiddenattribute and
aria-hidden="true", the setnode_is_individually_visiblealready answers and the set accname states.
LabelIndexreads its labels thesame way, since a label becomes a name the moment it reaches a control.
The text a page shows is in the tree, as
role: "text"with the text as thename. This one was posed as a question rather than a defect, so the reasoning
for doing it: text that is not some element's accessible name could not be read
at all, so a
<pre>, a paragraph or a code block was invisible to every check,and a host could not tell "the page renders no prose" from "the tree does not
carry prose". A browser exposes those runs and so does blitz-dom, which gives
every text node
Role::TextRunwith its content, so this tree disagreed withboth. Text that already names a node is left out: it is in the tree as that
node's name, that node is the one a harness can act on, and reporting it twice
would make "the page says this once" false.
<style>and<script>content isexcluded. Only the agent snapshot; the diagnostics snapshot reports a layout and
style row per node and those rows describe element boxes.
Tests
Sixteen tests in
agent::semantic_tests, all reading throughinspect_documentrather than the naming helpers, because a name that is rightinside the crate and wrong at the socket is the defect they exist for. Every one
of them was run against the unfixed code first and fails there.
cargo test --all-features: 60 passed.cargo clippy --all-targets --all-features -- -D warningsand the headless--no-default-features --features agent-controlclippy are clean, as iscargo fmt --check.Version
0.3.8 is in this branch. A patch bump on purpose: the role and name changes
correct what a node reports rather than change the wire type, and the
textnode is additive, so consumers pinned at
^0.3pick these up without a repin.Known gap, not addressed here
aria-labelledbyis still not resolved into a name. A<section aria-labelledby="...">gets theregionrole from this change and will reportan empty name until that lands.
Two commits that are not one of the seven
perf(agent): exposing text made every text run ask each element above itwhether that element already carries those words, and the owned
semantic_roleallocated a
Stringper ancestor to compare against a fixed list. The role iseither a
&'static stror theroleattribute's own text, sosemantic_role_refhands both back borrowed.test(agent): a text run is a new id in a tree a harness drives by id, so asuite will press one by accident. It answers
notInteractable, and the testpins that down so the answer cannot become a panic in the control server.