Three parts: (a) parser `collect-transitions` recognises `style`
tokens (`*prop`) as a continuation, so
`transition *width from A to B *height from A to B` chains both
transitions instead of dropping the second. (b) Mock `El` class gets
`nextSibling`/`previousSibling` (plus `*ElementSibling` aliases) so
`transition *W of the next <span/>` can resolve the next-sibling
target via host-get. (c) Generator pattern for
`const X = await evaluate(() => { const el = document.querySelector(SEL);
el.dispatchEvent(new Event(NAME, ...)); return ... })`; optionally
prefixed by a destructuring assignment and allowing trailing
`expect(...).toBe(...)` junk because `_body_statements` only splits on
`;` at depth 0.
Remaining `can use initial to transition to original value` needs
`on click N` count-filtered events (same mock-sync block as cluster 13).
Suite hs-upstream-transition: 13/17 → 15/17. Smoke 0-195: 162/195
unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two fixes in `tests/hs-run-filtered.js`: (a) `mt` (matches-selector)
now splits comma-separated selector lists and matches if any clause
matches, so `qsa("#d1, #d2")` returns both elements. (b) `host-get` on
an `El` for `innerText` returns `textContent` (DOM-level alias) so
`when its innerText contains "foo"` predicates can see the mock's
stored text.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mock `El` now exposes `dataset` as a Proxy that syncs property
writes back to `attributes["data-*"]`, and `setAttribute("data-*", ...)`
populates the backing dataset with camelCase key. That way
`#target.dataset.val = "new"` updates the `data-val` attribute,
letting the swap command read/write the property correctly.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Five parts: (a) tests/hs-run-filtered.js `io-wait-event` mock now
registers a one-shot listener on the target element and resumes with
the event, instead of immediately resuming with nil. (b) Added
hs-wait-for-or runtime form carrying a timeout-ms; mock resumes
immediately when a timeout is present (0ms tests). (c) parser
parse-wait-cmd recognises `wait for EV(v1, v2)` destructure syntax,
emits :destructure list on wait-for AST. (d) compiler emit-wait-for
updated for :from/:or combos; a new `__bind-from-detail__` form
compiles to `(define v (host-get (host-get it "detail") v))`, and the
`do`-sequence handler preprocesses wait-for to splice these synthetic
bindings after the wait. (e) generator extracts `detail: ...` from
`CustomEvent` options so dispatched events carry their payload.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`some <html/>` compiles to (not (hs-falsy? (hs-query-first "html"))), which
called document.querySelector('html'). The mock's querySelector searched
only inside _body, so the html element wasn't found. Adjusted the mock to
return _html for the 'html' selector (and walk documentElement too).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Adds an `outerHTML` getter to the mock `El` class. Builds `<tag attrs>inner</tag>`
by merging `.id` / `.className` (set as direct properties via host-set!) with
the `.attributes` bag, and falling back to `innerText` / `textContent` when
there are no children or innerHTML.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Mock DOM:
- El now tracks defaultValue/defaultChecked/defaultSelected and a reset()
method that walks descendant form controls, restoring them.
- setAttribute(value|checked|selected) sets the matching default-* too, so
the initial HTML state can be restored later.
- parseHTMLFragments + _setInnerHTML capture a textarea's textContent as
its value AND defaultValue.
Generator (pw-body):
- add_action / add_assertion extract .first() / .last() / .nth(N) modifiers
into (nth (dom-query-all …) i) or a (let ((_all …)) (nth _all (- … 1)))
tail so multi-match helpers hit the right element.
Compiler:
- emit-reset! with a .<class>/.<sel> query target now compiles to hs-query-all
so 'reset .resettable' resets every matching control (not just the first).
Net: reset 1→8 (100%).
- generate-sx-tests.py: add_action/add_assertion accept .nth(N) in PW-body
tests so 'find(sel).nth(1).dispatchEvent(...)' lands as a dispatch on
the Nth matching element, and assertions target that same element.
- shared/static/wasm/sx/dom.sx: dom-query-all hands through an already-SX
list unchanged — the bridge often pre-converts NodeLists/arrays to SX
lists, so the host-get 'length' / host-call 'item' loop was returning
empty. Guards node-list=nil and non-list types too.
- tests/hs-run-filtered.js (mock DOM): fnd() understands
':nth-of-type(N)', ':first-of-type', ':last-of-type' by matching the
stripped base selector and returning the correct-indexed sibling.
Covers upstream tests that write 'find("div:nth-of-type(2)")' to
pick the HS-owning element.
- Runtime runtime.sx: hs-sorted-by, hs-fetch format normalizer (JSON/
Object/etc.), nil-safe hs-joined-by/hs-split-by, emit-fetch chain sets
the-result when wrapped in let((it …)).
Net: take 0→6, hide 11→12, show 15→16, fetch 11→15,
collectionExpressions 13→15 (remaining are a WASM JIT bug on
{…} literals inside arrays).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Runtime (lib/hyperscript/ + shared/static/wasm/sx/hs-*.sx):
- make: parser accepts `<tag.class#id/>` selectors and `from <expr>,…`; compiler
emits via scoped-set so `called <name>` persists; `called $X` lands on
window; runtime dispatches element vs host-new constructor by type.
- Values: `x as Values` walks form inputs/selects/textareas, producing
{name: value | [value,…]}; duplicates promote to array; multi-select and
checkbox/radio handled.
- toggle *display/*visibility/*opacity: paired with sensible inline defaults
in the mock DOM so toggle flips block/visible/1 ↔ none/hidden/0.
- add/remove/put at array: emit-set paths route list mutations back through
the scoped binding; add hs-put-at! / hs-splice-at! / hs-dict-without.
- remove OBJ.KEY / KEY of OBJ: rebuild dict via hs-dict-without and reassign,
since SX dicts are copy-on-read across the bridge.
- dom-set-data: use (host-new "Object") rather than (dict) so element-local
storage actually persists between reads.
- fetch: hs-fetch normalizes JSON/Object/Text/Response format aliases;
compiler sets `the-result` when wrapping a fetch in the `let ((it …))`
chain, and __get-cmd shares one evaluation via __hs-g.
Mock DOM (tests/hs-run-filtered.js):
- parseHTMLFragments accepts void elements (<input>, <br>, …);
- setAttribute tracks name/type/checked/selected/multiple;
- select.options populated on appendChild;
- insertAdjacentHTML parses fragments and inserts real El children into the
parent so HS-activated handlers attach.
Generator (tests/playwright/generate-sx-tests.py):
- process_hs_val strips `//` / `--` line comments before newline→then
collapse, and strips spurious `then` before else/end/catch/finally.
- parse_dev_body interleaves window-setup ops and DOM resets between
actions/assertions; pre-html setups still emit up front.
- generate_test_pw compiles any `<script type=text/hyperscript>` (flattened
across JS string-concat) under guard, exposing def blocks.
- Ordered ops for `run()`-style tests check window.obj.prop via new
_js_window_expr_to_sx; add DOM-constructing evaluate + _hyperscript
pattern for `as Values` tests (result.key[i].toBe(…)).
- js_val_to_sx handles backticks and escapes embedded quotes.
Net delta across suites:
- if 16→18, make 0→8, toggle 12→21, add 9→10, remove 11→16, put 29→31,
fetch 11→15, repeat 14→26, expressions/asExpression 20→25, set 27→28,
core/scoping 12→14, when 39→39 (no regression).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>