Mojo::DOM's return shape for ->at(selector)/->find(selector) results — enough of its
chainable node API (->text, ->attr(name), ->parent, ->find(selector)->each) to cover
every call site across the legacy plugin corpus (verified: every ->at(...)/->find(...)
selector string across ~/LANraragi/lib/LANraragi/Plugin/**\/*.pm is a single simple selector —
a tag name, .class, #id, or [attr=val]/[attr^=val], never a descendant combinator or
pseudo-class). Deliberately not a real DOM (no querySelector, no live tree, no Node
interface) and not backed by a third-party HTML-parsing library (dispatcher.ts's own docs:
no external module imports at all, so a plugin's --allow-net/--allow-read grant is never
implicitly widened by the dispatcher's own needs) — a small hand-rolled parser and selector
matcher, sized to exactly what this corpus exercises.
text: string
parent: LegacyDomNode | undefined
attr(name: string): string | undefined
at(selector: string): LegacyDomNode | undefined
find(selector: string):
children(selector?: string):
->children(selector?) (Mojo::DOM::children) — direct children only (optionally filtered
by selector), distinct from find, which also matches nested descendants.
toString(): string
->to_string — real Mojo::DOM re-serializes the (possibly-modified) tree back to markup;
this shim is read-only (no ->remove/mutation support), so the original source markup never
actually changes after parsing, and returning that original string verbatim is equivalent to
a real serialization for every real call site in the legacy plugin corpus (both existing uses
— EHentai.pm/Pixiv.pm — only ever call it on the root Mojo::DOM->new(...)/->dom
result to substring-search the whole page, never on a sub-node after ->at()/->find()).