In a custom element, when this.innerHTML = '....' is called, we need to be
careful to prevent endless recursion. The html5ever callback used to determine
the context element should not invoke the custom-element constructor, else we'll
enter an endless loop.
This also fixes an ungating problem added with the new HttpClient when a
waitForImport can block forever.
Both issues were see on a WooCommerce site - though the HttpClient is only
due to an earlier HttpClient refactor.
- Links now emit OSC 8 hyperlinks (clickable where supported) with a dim
url fallback; a bare link (label == url) drops the redundant suffix.
- Blockquote lines (`> `) render with a dim bar.
- Horizontal rules (`---`/`***`/`___`) render as a dim line.
- Backslash escapes unescape only markdown-special chars, leaving e.g.
C:\\Users intact.
Style the assistant's answer in the interactive agent REPL with ANSI
escapes: headings, bullet/numbered lists, bold/italic/inline-code/strike,
and links. Rendering is gated to the REPL on a real terminal, so
`--task`/piped output stays verbatim for programmatic consumption.
Adds src/agent/md_term.zig (markdown -> ANSI renderer) and wires it into
Terminal.printAssistant.
When deciding what type of window to return, we compare the origin of the popup
to its opener. If they match, we return a full Window. For this to work properly
we need to store the origin as soon as the window is opened, and not wait until
the async response is received.
Also, expand CrossOriginWindow to expose more methods, most notably: close.
The /save script documentation lived as a hand-maintained string in
Agent.zig whose primitives table drifted whenever a tool changed. Move
it to src/script/skill.zig and render the reference (signatures, option
lists, enums, defaults, per-parameter descriptions) from Schema.all()
at first use, keeping the curated per-tool notes behind an exhaustive
switch on Tool so a new or renamed tool is a compile error until its
doc entry exists.
The rendered skill is shared by three consumers:
- the /save and revision system prompts (built lazily in Agent.zig)
- a new mcp://skill/pandascript resource
- `zig build skills`, which writes zig-out/skills/<name>/SKILL.md with
Claude Code frontmatter via a registry-based generator exe, so
future Lightpanda skills are one registry entry each
Schema.FieldEntry now retains per-parameter schema descriptions, which
previously existed only in the raw JSON.
Track active connections and shut down sockets during deinit to unblock
pending reads. Drain the job queue properly before stopping the worker.
Also fix `--http-port` references to `--port` in docs and errors.
Event dispatch using a try/catch (tryCallWithThis) so that the EventManager can
decide how to handle the error. Previously, using the `callWithThis` would
result in always logging the error. Now, the EventManager can skip logging
JsExceptions.
error.TryCatchRethrow is used as a control flow (sorry). It signals that an
error has already been throw in v8 and that the method should exit (ultimately,
it ends up being the return value passed to our v8 bridge, which knows to ignore
it). We shouldn't log this as a WARN, it's completely normal that it happens AND
it really contains no meaningful information as-is.
Add an explicit 'default' ResponseType, which mostly behaves like `text`, but
can be used do discriminate between an explicit text and the default value.
Also, on JSON parse error, return null, not an exception.
We had a basic DORect implementation (which probably covered more cases). This
commit extends the implementation to be more complete:
- Add DOMRectReadOnly (which DOMRect inherits from)
- Add StructuredClone support to DOMRect and DOMRectReadOnly
- Make DOMRect and DOMRectReadOnly usable from Worker
DOMRect can be on the hot path with IntersectionObserver, so we maintain a
value-based data object (DOMRect.Data) for internal use (things returned to js
always have to be heap allocated (and always were, we're just more explicit
about this part now)).
Pervious, extension was limited to HtmlElement. CustomElements can now extend
any html element interface.
The implementation isn't great. This is a special case, and rather than coming
up with some abstract way to define this in bridge, we make the Snapshot
aware of this specific case directly.
`lightpanda agent <script>` replays a saved script token-free (no LLM),
but running it under `agent` reads wrong. Add `run` as a dedicated,
script-only command: `lightpanda run <script>`.
`run` exposes only a required [SCRIPT] positional plus the common
options; its LLM-session flags (--task/--save/--list-models/REPL) are
absent, so they're rejected at parse time. `parseArgs` normalizes a
`run` invocation to an `.agent` command, reusing the existing token-free
replay path unchanged, so nothing downstream needs a `.run` case.
`agent <script>` keeps working. Telemetry records `run` as its own mode
code (R) via a new Config.command field, so its adoption is measurable
separately from agent script replay (AR).
`stream_active` relied on every runTools exit path balancing pause with
`endStreamedText`. If one is ever missed, a stale-true flag skips the
spinner pause on the next turn, interleaving frames with streamed text.
Reset it alongside `streamed_text` so the invariant is local.
Streaming defaulted on for every mode, so one-shot `--task` and script
runs interleaved intermediate assistant commentary into stdout, which
wrappers treat as the answer. Gate the stream hook on `Terminal.isRepl`
so non-interactive modes keep the buffered final answer.
Streaming wrote assistant deltas straight to stdout regardless of
`suppress_answer`, so `--save`/capture turns (which set it to hide the
answer) leaked model text past the `runTurn` guard. Pass a null stream
hook when the turn's answer is suppressed.
Introduces an HTTP transport option to serve multipleagents from a
single process. Each connection is routed to its own isolated
browsing session using the `Mcp-Session-Id` header.
Also adds new session management tools (`session_new`, `session_list`,
`session_close`) and refactors the MCP server to support multiple V8
isolates with parking.
Rather than having a generic SVG type (which gets us past parsing, but even the
slightest JS usage is likely to fail), this adds concrete types for a number of
known SVG types. A lot of these are empty (but even that's enough to pass
something like `instanceof SVGAElement`), but a handful of the more important
accessors and methods are implemented (e.g. I ran into a site that made
extensive use of SVGSVGElement.getElementById).
Currently, we use the calling frame when mutating an element's attributes (e.g.
add/delete). This commit changes it to the element's owning frame. This should
fix both some errors and potential uaf.
Fixes the remaining WPT /dom/collections failures around setting,
defining and describing HTMLCollection properties:
- HTMLCollection-own-props.html: assigning to a supported named property
or to any array index must fail (TypeError in strict mode), while
expandos for unsupported names must keep working.
- HTMLCollection-supported-property-indices.html: indexed property
descriptors must be {enumerable: true, configurable: true, writable:
false}, Object.defineProperty on any array index must throw, and
deleting a supported index must fail.
- HTMLCollection-supported-property-names.html: named property
descriptors must be {enumerable: false, configurable: true, writable:
false} ([LegacyUnenumerableNamedProperties]) and defineProperty on a
supported name must throw.
Bridge changes, mirroring the existing interceptor callbacks:
- bridge.Indexed gains a definer callback; bridge.NamedIndexed gains
definer and descriptor callbacks, exposed through new indexedFull and
namedIndexedFull builders so existing indexed/indexedReadWrite/
namedIndexed call sites are untouched. Definer and descriptor
handlers reuse the deleter (name -> bool) and getter (name -> value)
calling shapes. The stale void-returning definer/descriptor typedefs
in the v8 binding are bridged with @ptrCast; v8 actually reads the
returned u32 as v8::Intercepted.
- Caller's query helpers (shared queryReturn) now accept, besides the
existing bool shape, query callbacks returning the
v8.PropertyAttribute bits directly (e.g. ReadOnly), with
error.NotHandled falling through to the ordinary lookup.
HTMLCollection registers:
- an indexed setter and definer that always fail (no indexed setter in
Web IDL), and an indexed query reporting ReadOnly for supported
indices;
- a named definer that fails for supported names, and a named
*descriptor* (not query) reporting {writable: false, enumerable:
false, configurable: true}. Using the descriptor callback instead of
query is deliberate: the ordinary [[Set]] path does not consult it, so
assigning through a derived object (HTMLCollection-as-prototype.html)
still creates an expando on the receiver, as the spec's
ignore-named-props rule requires, while direct assignments fail via
the definer.
Coverage: /dom/collections is now fully green (53/53):
- HTMLCollection-own-props.html 4/8 -> 8/8
- HTMLCollection-supported-property-indices.html 4/7 -> 7/7
- HTMLCollection-supported-property-names.html 5/6 -> 6/6
- HTMLCollection-as-prototype.html stays 2/2
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the Object.getOwnPropertyNames related failures in WPT
/dom/collections:
- HTMLCollection-live-mutations.window.html: "ownPropertyNames" case
- HTMLCollection-supported-property-names.html: 3 getOwnPropertyNames cases
- namednodemap-supported-property-names.html: all 3 cases
- domstringmap-supported-property-names.html: all 5 cases
Object.getOwnPropertyNames returned no interceptor-backed properties
because these collections registered no enumerator callbacks.
Using the bridge's existing indexed and named enumerator support:
- HTMLCollection registers an indexed enumerator (the supported
indices) and a named enumerator implementing the spec's supported
property names: for each element in tree order, its non-empty id and,
for HTML elements, its non-empty name attribute, without duplicates.
- NamedNodeMap registers indexed and named enumerators (attribute
qualified names in order, via List.getNames).
- DOMStringMap registers a named enumerator (camel-cased data-*
attribute names). The previously unused kebabToCamel helper is fixed
to follow the spec conversion: only a '-' followed by an ASCII
lowercase letter is folded to an uppercase letter, a bare "data-"
attribute maps to the empty name, and a trailing '-' is preserved.
(As originally written, this commit also added named enumerator
interceptor support to the js bridge; main gained equivalent support
independently, so the rebase keeps main's bridge API.)
Coverage:
- /dom/collections/HTMLCollection-live-mutations.window.html 4/5 -> 5/5
- /dom/collections/HTMLCollection-supported-property-names.html 2/6 -> 5/6
- /dom/collections/namednodemap-supported-property-names.html 0/3 -> 3/3
- /dom/collections/domstringmap-supported-property-names.html 0/5 -> 5/5
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes 2 failing tests in WPT /dom/collections/HTMLCollection-delete.html
("Strict id" and "Strict name"): deleting a supported indexed or named
property of an HTMLCollection must fail (and throw a TypeError in strict
mode), since those properties are backed by the live collection.
HTMLCollection registered no deleter interceptors, so `delete c[0]` and
`delete c.name` fell through to the ordinary [[Delete]] which succeeded
on the (non-own) property.
HTMLCollection now registers an indexed and a named deleter (via the
bridge's indexedReadWrite/namedIndexed deleter callbacks) that return
false for supported properties - which v8 turns into a TypeError in
strict mode - and error.NotHandled for unsupported ones, leaving those
to the ordinary path.
(As originally written, this commit also added indexed setter/deleter
interceptor support to the js bridge; main gained equivalent support
independently, so the rebase keeps main's bridge API.)
Coverage: /dom/collections/HTMLCollection-delete.html 2/4 -> 4/4.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes the failing test in WPT /dom/abort/abort-signal-timeout.html:
"Signal returned by AbortSignal.timeout() is not aborted after frame
detach".
The scheduler task registered by AbortSignal.timeout() kept running
after the iframe that created the signal was removed from the document,
so the signal aborted and the abort event fired. Per the DOM spec, the
abort is queued as a global task on the signal's relevant global, and
such tasks must not run once the global's document stops being fully
active.
The timeout callback now walks the signal's frame and its ancestors and
skips the abort when a hosting iframe element is no longer connected.
Coverage: /dom/abort/abort-signal-timeout.html 0/1 -> 1/1.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes 1 failing test in WPT /dom/abort/event.any.html (and its .worker
variant): "AbortController abort(null) should set signal.reason".
abort() reason parameters were typed ?js.Value.Global, and the JS->Zig
conversion collapses an explicit JS null into a missing argument, so
controller.abort(null) fell back to the default "AbortError"
DOMException. Per the DOM spec, the abort reason defaults to a new
AbortError only when the reason is not given (undefined); an explicit
null must be stored as-is.
The parameters are now ?js.Value, which preserves the missing vs
explicit-null distinction, and the new AbortSignal.reasonFromJs helper
only falls back to the default for a missing or undefined reason.
Coverage: /dom/abort/event.any.worker.html 15/16 -> 16/16,
/dom/abort/event.any.html 15/16 -> 16/16.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fixes 3 failing tests in WPT /dom/abort/abort-signal-any.any.html (and
its .worker variant):
- "Dependent signals are aborted correctly for reentrant aborts": when a
source signal aborted a second source from within an abort listener,
the shared dependent signal dispatched its abort event twice. Per the
DOM spec, only dependent signals that are not already aborted are
appended to the list of signals to dispatch. Dependend.markAborted now
reports whether the dependent was newly marked, and abort() only
dispatches for those.
- "Dependent signals should use the same DOMException instance" (already
aborted source / source aborted later): Reason.dom held a DOMException
by value, so every .reason access wrapped a fresh JS object and the
identity check source.reason === dependent.reason failed. Reason.dom
is now a *DOMException allocated once at abort time; the JS bridge
identity-maps the pointer so the source and all dependents expose the
very same DOMException instance.
Coverage: /dom/abort/abort-signal-any.any.worker.html 11/14 -> 14/14,
/dom/abort/abort-signal-any.any.html 11/14 -> 14/14.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Instruct the /save synthesis model to annotate the generated script with
short `//` intent comments — one per logical block (navigate, extract,
fan out, aggregate, return) describing what the block accomplishes, so a
later reader (human or agent) grasps the script at a glance.
The save synthesis prompt previously ended with "no commentary", which
discouraged code comments; reword it to forbid only prose/markdown fences
outside the code while explicitly asking for the `//` intent comments. The
same prompt backs the MCP `save` tool, so both surfaces benefit. Also add a
matching best-practice bullet + example to the agent's script-writing skill.