Fixes WPT /dom/traversal/NodeIterator-removal.html (0/23 -> 23/23) and
/dom/nodes/moveBefore/moveBefore-nodeiterator.html: removing a node
that is an inclusive ancestor of a live NodeIterator's reference node
must move the reference per the DOM pre-removing steps. We never
adjusted iterators, so the reference kept pointing into the detached
subtree.
The frame keeps an intrusive list of live NodeIterators (mirroring
_live_ranges; iterators are slab-allocated for the frame lifetime, so
they are never unlinked) and Frame.removeNode runs the steps while the
tree is still intact:
- removing the root or an ancestor of the root leaves the iterator
untouched (matching browsers and the WPT model);
- with the pointer before the reference, the reference moves to the
first node following the removed subtree, if any;
- otherwise (or when there is no such node) it moves to the node
immediately preceding the removed node in tree order, clearing the
before-pointer in the fallthrough case.
Coverage:
- /dom/traversal/NodeIterator-removal.html 0/23 -> 23/23 (fully green)
- /dom/nodes/moveBefore/moveBefore-nodeiterator.html 0/1 -> 1/1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the 5 failing subtests of WPT
/dom/traversal/TreeWalker-acceptNode-filter.html (7/12 -> 12/12):
- A filter object was converted eagerly at createTreeWalker time, so an
object without a callable acceptNode threw "invalid argument" at
creation. Per WebIDL any object converts to the NodeFilter callback
interface; the TypeError belongs at invocation time.
- The acceptNode member was cached at conversion. Per "call a user
object's operation" it must be looked up with a fresh Get on every
traversal, rethrowing errors from a throwing getter.
- The callback was invoked with the default this; the spec requires the
filter object itself as the this value.
NodeFilter now stores the raw function or object (js.Object.Global) and
performs the per-invocation lookup, callability check and this-binding
in acceptNode.
Coverage: /dom/traversal/TreeWalker-acceptNode-filter.html 7/12 ->
12/12 (fully green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the last failing subtest of WPT /dom/traversal/TreeWalker.html
("Recursive filters need to throw"): per the DOM traversal "filter"
algorithm, a NodeFilter that re-enters the walker (calling parentNode()
etc. from inside the callback) must get an InvalidStateError.
DOMTreeWalker tracks an active flag around the filter invocation and
throws InvalidStateError when a traversal starts while it is set.
Coverage: /dom/traversal/TreeWalker.html 760/761 -> 761/761 (fully
green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes WPT /dom/traversal/TreeWalker-previousSiblingLastChildSkip.html
and 59 subtests of TreeWalker.html (701/761 -> 760/761):
TreeWalker.previousSibling()/nextSibling() only scanned the current
node's direct siblings, but the spec's "traverse siblings" algorithm
also:
- descends into a skipped (FILTER_SKIP) sibling's children - only a
rejected sibling excludes its whole subtree - so from B2 with B1
skipped, previousSibling() must return B1's last child;
- climbs to the parent when the siblings are exhausted and continues
from the parent's siblings, stopping at the root or at an accepted
parent.
Both directions now share the spec's traverseSiblings implementation.
Coverage:
- /dom/traversal/TreeWalker-previousSiblingLastChildSkip.html 0/1 -> 1/1
(fully green)
- /dom/traversal/TreeWalker.html 701/761 -> 760/761
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
std.json serializes []u8 that isn't valid UTF-8 as a JSON array of
numbers, so headers like "expires: mié, 15 jul 2026 ..." (Latin-1 0xE9)
appeared as byte arrays in Network.responseReceived. Stream such values
through the JSON writer with Latin-1 -> UTF-8 transcoding, matching
Chrome's behavior for DevTools.
The Accessibility domain handled getFullAXTree and queryAXTree but not
getPartialAXTree, so a client asking for the accessibility subtree rooted at
a specific node got UnknownMethod. Tools like Playwright/Puppeteer and
axe-core use getPartialAXTree for scoped a11y snapshots.
Add it by mirroring the existing methods: resolve the node via dom.getNode
(like queryAXTree) and emit its unfiltered AX subtree via bc.axnodeWriter
(like getFullAXTree). fetchRelatives is accepted for protocol compatibility;
subtree emission matches queryAXTree's scope. Adds tests for the missing-id
and unknown-id error paths.
- attachCompleter now takes the CompletionSource: Agent no longer
reaches through terminal.assist to configure what it just attached,
and attach/configure can't be sequenced wrong.
- history_paths flows through one channel: attach() loads the initial
history from the same State field modeCallback swaps from, instead of
setupRepl receiving a second copy that had to agree.
- all_names + closestCommand move to SlashCommand.zig, the command
registry - prompt_assist consumes it like everyone else and
Terminal's closestCommand re-export disappears.
- picker treats a tty that refuses raw mode as NotInteractive and
degrades to the line prompt instead of leaking termios errnos to
callers that can only blanket-catch.
- kitty keyboard flags become named ansi.zig constants shared by
Terminal.readLine and picker's RawTerminal.
- skipWhitespace was std.mem.indexOfNonePos; single-use style consts
inline into the style table; drop the dead CompletionSource default
and doc-comment splice in Spinner.
addPathCompletions and ghostPathFirstMatch were ~20-line twins (split
dir/base, openDir, iterate, prefix-match, '/' suffix for directories).
A fix landing in one copy — hidden-file filtering, the shared
symlink-to-directory suffix gap — would make Tab completion and the
ghost hint disagree. Both now consume one PathMatchIterator.
ic_style_def registration lived in setup while the Kind->style switch
lived 600 lines away in IcSink: adding a js_highlight.Kind
compile-errored the sink but silently skipped registration, rendering
the new kind unstyled with no test able to catch it (isocline is
live-only). One table now drives both; an unmapped or doubly-mapped
kind is a compile error.
Review pattern from #2943: prefer the local arena for scratch-only
allocations. The collected row list is only read before deleteRow's
removeChild call - the one point that can re-enter JS - so it does not
need to survive a nested callback.
Co-Authored-By: Karl Seguin <karlseguin@users.noreply.github.com>
Fixes the remaining failures of WPT /dom/ranges/Range-selectNode.html
(288/292) and improves several other /dom/ranges files: for
CharacterData, the DOM "node length" is the data length in UTF-16 code
units, but Node.getLength returned the UTF-8 byte length, so
selectNodeContents() on text containing non-ASCII set endOffset to the
byte count (25 instead of 17 in the test's fixture).
Node.getLength now defers to CData.getLength (already UTF-16). Since
Range/Selection offsets are therefore UTF-16 units, the Range routines
that slice the UTF-8 data by offset (insertNode's text split,
deleteContents, cloneContents, toString) convert them with
utf16OffsetToUtf8 (clamped to the end) instead of using them as byte
indices, which would cut surrogate pairs and multi-byte sequences
apart.
Coverage:
- /dom/ranges/Range-selectNode.html 288/292 -> 292/292 (fully green)
- /dom/ranges/Range-deleteContents.html 95/125 -> 105/125
- /dom/ranges/Range-extractContents.html 128/187 -> 141/187
- /dom/ranges/Range-cloneContents.html 168/187 -> 171/187
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes WPT /dom/nodes/Text-wholeText.html: wholeText must return the
concatenated data of the contiguous exclusive Text nodes (adjacent Text
siblings on both sides of the node) in tree order; we returned only the
node's own data.
The old own-data behavior is what every internal caller (semantic tree,
markdown dump, AX names, textarea default value, textContent) actually
wants, since they iterate the text nodes themselves; those now use the
new Text.ownData helper, and getWholeText implements the spec walk
(single-node case stays allocation-free).
Coverage: /dom/nodes/Text-wholeText.html 0/1 -> 1/1 (fully green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes 7 failing WPT /dom/nodes/getElementsByClassName-*.htm files:
- Class attribute matching (getElementsByClassName and .class
selectors) treated only the space character as a token separator, so
class="a\nb" or class="a\tb" never matched "a". Per the spec the
separators are ASCII whitespace (tab, LF, FF, CR, space);
classAttributeContains now accepts all of them at token boundaries
(deliberately not std.ascii.isWhitespace, which would also accept
vertical tab). Fixes getElementsByClassName-02/04/15/22/25.
- getElementsByClassName-20 exercised table.tBodies[0].rows[0].cells[0]
and -21 table.deleteRow(1); the table interfaces were bare stubs.
Added HTMLTableElement.tBodies (child tbody collection),
HTMLTableSectionElement.rows (child tr collection),
HTMLTableRowElement.cells (child td/th collection, a new `cells`
NodeLive mode since child_tag filters a single tag), and
HTMLTableElement.deleteRow with the spec's row ordering (thead rows,
then table/tbody rows in tree order, then tfoot rows), -1 meaning the
last row, and IndexSizeError for out-of-range indices.
Coverage: getElementsByClassName-02/04/15/20/21/22/25.htm each
0/1 -> 1/1 (fully green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Completion, ghost hints, and prompt highlighting (~640 lines) were
cohabiting with Terminal's readline lifecycle and output printing. They
now live in prompt_assist.zig, which owns the isocline callbacks, the
ps-* style palette, and the slash-command name table. The C callbacks
read a small prompt_assist.State (js_mode, completion_source,
history_paths) embedded in Terminal, replacing the three loose fields.
Pure code motion otherwise; the valueAt/renderSchemaHint tests move
with their code and stay in the suite via Terminal's test hook.
The numbered/interactive picker (ChoiceState, RawTerminal, render loop)
was ~215 self-contained lines inside Terminal.zig. It runs before - or
without - the isocline REPL (provider selection during setup), so
settings.zig no longer imports the isocline-configured Terminal at all.
Pure code motion; the ChoiceState tests move with it and stay in the
suite via Agent.zig's test hook.
Terminal.columns() had exactly one caller (Spinner's line renderer) and
Terminal itself never used it. Moving it breaks the Spinner<->Terminal
import cycle and stops Spinner type-checking against the isocline
@cImport for one ioctl.
- Add highlighting for JS functions, methods, types, and template
interpolations.
- Introduce teal color and adjust cyan to bright cyan.
- Import `ansi.zig` directly instead of through `Terminal`.
- Add PandaScript semantics note to the agent's system prompt.
- Refactor markdown table rendering and streaming.
Fixes WPT /dom/nodes/MutationObserver-inner-outer.html (0/3 -> 3/3):
setting innerHTML must queue a single "replace all" mutation record
(all removed children + all parsed children), and setting outerHTML a
single record replacing the element with the parsed nodes. We emitted
one record per removed child plus one per inserted child.
- Node.setHTML suppresses per-node records (removals via the new
notify_observers opt, insertions by making fragment parsing never
notify) and queues the combined record itself. Parsing still targets
the element directly so html5ever keeps the right fragment context
(e.g. raw-text content of <script>).
- Fragment parsing no longer notifies observers per inserted child:
every other fragment-parse target is a detached DocumentFragment
where notification is a no-op.
- Element.setOuterHTML moves the parsed nodes in with notification
suppressed and queues one record with removedNodes=[the element],
addedNodes=parsed children and the element's siblings.
- The unit test mutation_observer/childlist.html expected the old
6-record innerHTML behavior; updated to the spec's single record
(matches Chrome).
Coverage: /dom/nodes/MutationObserver-inner-outer.html 0/3 -> 3/3
(fully green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes WPT /dom/nodes/MutationObserver-textContent.html (1/4) and the
remaining failures of ParentNode-replaceChildren.html (25/29): the DOM
"replace all" algorithm (Element.textContent setter,
ParentNode.replaceChildren) must queue a single tree mutation record
with all removedNodes and addedNodes (and null previous/next sibling).
We emitted one record per removed child plus one per added child, so
observers saw 2+ records where the spec requires exactly one.
Frame.removeNode/appendNode gain a notify_observers opt (default true);
Node.replaceChildren suppresses the per-node records and queues the
combined record itself. Removing an added child from its previous
parent still notifies that parent's observers separately, as the spec
requires.
Coverage:
- /dom/nodes/MutationObserver-textContent.html 1/4 -> 4/4 (fully green)
- /dom/nodes/ParentNode-replaceChildren.html 25/29 -> 29/29 (fully green)
- /dom/nodes/MutationObserver-childList.html 29/38 -> 30/38
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes WPT /dom/nodes/MutationObserver-callback-arguments.html: per the
DOM spec ("invoke callback with a list of MutationRecord objects and mo,
and mo as callback this value"), the mutation callback's this value must
be the MutationObserver itself. We invoked it with the default receiver
(undefined -> globalThis), so `this === mo` failed.
deliverRecords now uses tryCallWithThis with the observer as receiver.
Coverage: /dom/nodes/MutationObserver-callback-arguments.html 0/1 -> 1/1
(fully green).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes all failing subtests of WPT /dom/nodes/Element-insertAdjacentElement.html
(1/6), Element-insertAdjacentText.html (5/6) and insert-adjacent.html (11/14):
- insertAdjacentElement/Text wrongly reused insertAdjacentHTML's rule of
throwing NoModificationAllowedError for a null or document parent. Per
DOM's insert-adjacent algorithm they instead return null (no-op) for a
null parent and otherwise rely on pre-insert validity, so inserting
before/after the document element reports HierarchyRequestError.
findAdjacentNodes takes the variant; insertAdjacentHTML keeps its
HTML-spec behavior.
- insertAdjacentElement now returns the inserted element (was void, so
scripts using the return value got undefined).
- The pre-insert validity checks did not implement the document-parent
rules: a document can't contain Text children and has at most one
element child. validateDocumentInsertion enforces both in
appendChild/insertBefore/replaceChild; replaceChild excludes the child
being replaced from the single-element rule, and replaceChildren
(which replaces every child) is intentionally unaffected.
Coverage:
- /dom/nodes/Element-insertAdjacentElement.html 1/6 -> 6/6 (fully green)
- /dom/nodes/Element-insertAdjacentText.html 5/6 -> 6/6 (fully green)
- /dom/nodes/insert-adjacent.html 11/14 -> 14/14 (fully green)
- /dom/nodes/Node-insertBefore.html 13/40 -> 16/40
- /dom/nodes/Node-replaceChild.html 14/29 -> 15/29
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the failing subtest of WPT /dom/nodes/Element-children.html
("HTMLCollection edge cases 1"): iterating an HTMLCollection with
for-in must yield only the supported indices; the supported names are
[LegacyUnenumerableNamedProperties] and must be skipped, while
Object.getOwnPropertyNames still returns indices + names.
v8 filters for-in through the named query interceptor, which
HTMLCollection did not register (only a descriptor callback), so every
name reported by the enumerator was treated as enumerable.
HTMLCollection now registers a named query reporting DontEnum for
supported names. The query deliberately does not report ReadOnly: v8
also consults it when a [[Set]] walks the prototype chain, and a
read-only property on the prototype would block the shadowing expando
that the spec's ignore-named-props rule requires
(HTMLCollection-as-prototype.html). Writability as observed through
getOwnPropertyDescriptor still comes from the descriptor callback, and
direct assignments to supported names still fail through the definer.
Coverage: /dom/nodes/Element-children.html 1/2 -> 2/2 (fully green);
/dom/collections stays fully green (53/53).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the last failing subtest of WPT
/dom/nodes/DOMImplementation-createHTMLDocument.html ("URL parsing"):
resolving `a.href = "http://example.org/?ä"` on an anchor inside a
createHTMLDocument() document returned "?%E4" instead of "?%C3%A4".
Two spec violations combined:
- Per DOM, documents synthesized by script (createHTMLDocument,
createDocument, new Document()) have the UTF-8 encoding. We had no
per-document encoding at all: document.characterSet always reflected
the frame's charset, so the new document inherited windows-1252 from
the test page (which uses that encoding deliberately to catch this).
Document gains a _charset override (same pattern as _content_type),
set to UTF-8 in DOMImplementation.createHTMLDocument/createDocument
and the Document constructor.
- Node.resolveURL encoded the query string with the frame's charset.
Per the URL/HTML specs the query percent-encoding uses the encoding
of the element's node document, so it now resolves the owning
document and uses its encoding, falling back to the frame's.
Coverage:
- /dom/nodes/DOMImplementation-createHTMLDocument.html 12/13 -> 13/13
(fully green)
- /dom/nodes/Document-constructor.html 3/5 -> 4/5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the remaining 30 failing subtests of WPT
/dom/nodes/Element-matches.html, in five clusters:
Matching (selector/List.zig):
- :link now matches a and area elements with an href attribute (in a
headless browser no link is ever visited, so :visited stays
unmatched and :link covers every hyperlink). :any-link gains the
missing area case.
- :empty now ignores comment and processing-instruction children, per
Selectors Level 3 only elements and non-empty text/cdata affect
emptiness (<p><!-- comment --></p> is :empty, <p> </p> is not).
- :lang() is implemented: the element's language is the nearest
ancestor-or-self lang attribute, falling back to the UA default (en)
for elements in a document and to no language at all in detached
subtrees, which is what the WPT expects for the detached/fragment
contexts. Matching is ASCII case-insensitive on the exact tag or a
`-` separated prefix (:lang(en) matches lang="en-AU").
Parsing (selector/Parser.zig):
- An empty selector-list segment ("div," or ",div") is now a parse
error instead of being silently skipped.
- Unexpected EOF closes open attribute brackets per CSS Syntax:
'#attr-value [align="center"' parses and matches.
- Attribute selectors accept a namespace component: [*|TiTlE] (any
namespace) and [|title] (no namespace). Attributes are stored by
qualified name and almost never namespaced, so both forms match by
name; [*|*=test] stays invalid.
Coverage:
- /dom/nodes/Element-matches.html 639/669 -> 669/669 (fully green)
- /dom/nodes/ParentNode-querySelectors-namespaces.html 0/1 -> 1/1
- /dom/nodes/moveBefore/moveBefore-lang.html 0/1 -> 1/1
- /dom/nodes/moveBefore/Node-moveBefore.html 31/32 -> 32/32
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>