Overview Benchmarks Blog Download
Writing  /  Engineering

Reading the modern web

Engineering 28 August 2026·5 min read

An agent that browses has to answer one question constantly: what does this page say? The obvious implementation is document.querySelector('main') and a walk of its children. That implementation is wrong on most of the web, and it is wrong in the worst possible way — it returns something, confidently.

The shape of the bug

querySelectorAll does not enter shadow roots. On any site built from web components — and that is YouTube, Reddit, Gmail, and a great deal of documentation — the interesting half of the page is invisible to it. It does not enter iframes either, so checkout forms and embedded editors are invisible too.

Helix's page toolkit knew this. Every walk in it is a composed walk: document, then each open shadow root, then each same-origin iframe, recursively. Elements, forms, tables, links — all composed.

Except the one that reads the page as prose. That one took node.childNodes and never crossed a shadow boundary in its life.

Measured on developer.mozilla.org — an ordinary documentation page, not an exotic one: a light-DOM walk reaches 14,472 characters. The composed walk returns 25,033. The missing 10,561 are every code example on the page, which MDN renders through an <mdn-code-example> component.

So asked to read the DOM documentation, the agent got the prose and none of the code, and reported it as the page. There was nothing in the reply to suggest anything was missing — which is why this kind of bug survives: it does not look like a bug, it looks like a short page.

The flattened tree has exactly three rules

Reading a page as it is rendered, rather than as it was authored, is a solved problem with three rules:

Getting the first two right together is what avoids the obvious trap: walking both the host's light children and its shadow root reads every slotted string twice. The test fixture now has prose behind a slot specifically to assert it appears exactly once.

A correction we owe the record

The first version of this investigation reported that 57,531 characters were hidden inside shadow roots. That number was wrong. It counted textContent, which on that page is dominated by the CSS inside <style> elements. The real figure — rendered text, measured before and after in the same session on the same document — is the 14,472 → 25,033 above.

We mention it because the correction came from the same discipline as the fix: measure it in a real browser, then measure it again when the result looks too good.

The other three

Reading was not the only thing wrong down there.

A page call that answered false was treated as a missing toolkit. The bridge evaluated (window.__helix && expr) and read a falsy result as proof the toolkit was absent — so it re-injected 63KB and ran the expression a second time. Several expressions legitimately answer false; the focus call inside type returns false whenever focus did not stick. So on exactly the stubborn pages where focusing was already failing, scrollIntoView, focus() and the selection reset all happened twice.

Root discovery was paid for once per query. Finding where the shadow roots are means walking querySelectorAll("*") over the whole composed tree, and that was being redone for every single selector — four times for one obstacle check. It is now computed once per synchronous call and dropped on the next microtask, which is what makes it safe: page JavaScript is single-threaded, so the DOM cannot change between two queries inside one function.

Nothing ever let go of the working tab. A run opened it, drove it, finished — and the tab stayed: pinned, with the debugger still attached, so Chrome kept showing "is debugging this browser" and the console and network buffers kept filling for a task that had ended hours ago. Closing at the end of a run would be wrong, because a follow-up lands seconds later and wants the same page and the same element refs. So the trigger is idleness — ten minutes, and never mid-call.

Helix is free, runs on your own Windows PC, and asks before it does anything it cannot undo.

Download Helix