'add .foo to my children' compiles to (dom-add-class (host-get me 'children') 'foo') where
children is a list. Fanned out via for-each inside dom-add-class/dom-remove-class rather
than calling .classList.add on the list itself. Net: add 10→13.
- tokenizer: add 'giving' as keyword so parse-take-cmd can detect it.
- parser.sx parse-take-cmd: loop over 'with <class>' / 'giving <class>' /
'from <sel>' / 'for <tgt>' clauses in any order for both the class and
attribute cases. Emits uniform (take! kind name from-sel for-tgt
attr-val with-val) 7-slot AST.
- compiler emit-take: pass with-cls for the class case through to runtime.
- runtime hs-take!: with a class 'with' replacement, toggle both classes
across scope + target. For attribute take, always strip the attr from
the scope 'others' (setting to with-val if given, otherwise removing).
- generator pw-body: translate evaluate(() => document.querySelector(s).
click()) and .dispatchEvent(new Event('name', …)) into dom-dispatch ops
so bubbling-click assertions in 'parent takes…' tests work.
- generator toHaveClass: strip JS regex word-boundaries (\\b) from the
expected class name.
- shared/static/wasm/sx/dom.sx: dom-child-list / dom-child-nodes mirror
the dom-query-all SX-list passthrough — childNodes arrives pre-SXified.
Net: take 6→15 (100%), remove 16→17, fetch 11→15.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- 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>
Two bugs fixed:
1. Links: bytecode compiler doesn't handle &rest params — treats them as
positional, so (first rest) gets a raw string instead of a list.
Replaced &rest with explicit optional params in all bytecode-compiled
web SX files (dom-query, dom-add-listener, browser-push-state, etc.).
The VM already pads missing args with Nil.
2. Reactive counter: signal-remove-sub! used (filter ...) which returns
immutable List, but signal-add-sub! uses (append!) which only mutates
ListRef. Subscribers silently vanished after first effect re-run.
Fixed by adding remove! primitive that mutates ListRef in-place.
Also:
- Added evalVM API to WASM kernel (compile + run through bytecode VM)
- Added scope tracing (scope-push!/pop!/peek/context instrumentation)
- Added Playwright reactive mode for debugging island signal/DOM state
- Replaced cek-call with direct calls in core-signals.sx effect/computed
- Recompiled all 23 bytecode modules
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The sx-get links were doing full page refreshes because click handlers
never attached. Root causes: VM frame management bug, missing primitives,
CEK/VM type dispatch mismatch, and silent error swallowing.
Fixes:
- VM frame exhaustion: frames <- [] now properly pops to rest_frames
- length primitive: add alias for len in OCaml primitives
- call_sx_fn: use sx_call directly instead of eval_expr (CEK checks
for type "lambda" but VmClosure reports "function")
- Boot error surfacing: Sx.init() now has try/catch + failure summary
- Callback error surfacing: catch-all handler for non-Eval_error exceptions
- Silent JIT failures: log before CEK fallback instead of swallowing
- vm→env sync: loadModule now calls sync_vm_to_env()
- sx_build_bytecode MCP tool added for bytecode compilation
Tests: 50 new tests across test-vm.sx and test-vm-primitives.sx covering
nested VM calls, frame integrity, CEK bridge, primitive availability,
cross-module symbol resolution, and callback dispatch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- ocaml_sync.py: escape newlines in eval/load_source to prevent
protocol desync (bridge crashed on any multi-line SX)
- Stepper: do-back uses rebuild-preview (O(1) render) instead of
replaying all steps. Hydration effect same. Cookie save on button
click only.
- dom.sx: remove duplicate dom-listen (was shadowing the one at
line 351 that adapter-dom.sx's dom-on wraps)
- orchestration.sx: fix bind-sse-swap close paren count
- safe_eq: Dict equality via __host_handle for DOM node identity
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
adapter-dom.sx defines dom-on as a wrapper around dom-listen (adds
post-render hooks). But dom-listen was never defined — my earlier
dom-on in dom.sx was overwritten by the adapter's version. Rename
to dom-listen so the adapter's dom-on can call it.
This fixes click handlers not firing on island buttons (stepper,
stopwatch, counter, etc.).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>