Root cause: process-elements during WASM boot-init marks elements as
processed but process-one silently fails (effect functions don't execute
in WASM boot context). Deferred process-elements then skips them.
Fixes:
- boot-init: defer process-elements via set-timeout 0
- hydrate-island: defer process-elements via set-timeout 0
- process-elements: move mark-processed! after process-one so failed
boot-context calls don't poison the flag
- observe-intersection: native JS platform function (K.registerNative)
to avoid bytecode callback issue with IntersectionObserver
- Remove SX observe-intersection from boot-helpers.sx (was overriding
the working native version)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Browser kernel:
- Add `parse` native fn (matches server: unwrap single, list for multiple)
- Restore env==global_env guard on _env_bind_hook (let bindings must not
leak to _vm_globals — caused JIT CSSX "Not callable: nil" errors)
- Add _env_bind_hook call in env_set_id so set! mutations sync to VM globals
- Fire _vm_global_set_hook from OP_DEFINE so VM defines sync back to CEK env
CEK evaluator:
- Replace recursive cek_run with iterative while loop using sx_truthy
(previous attempt used strict Bool true matching, broke in wasm_of_ocaml)
- Remove dead cek_run_iterative function
Web modules:
- Remove find-matching-route and parse-route-pattern stubs from
boot-helpers.sx that shadowed real implementations from router.sx
- Sync boot-helpers.sx to dist/static dirs for bytecode compilation
Platform (sx-platform.js):
- Set data-sx-ready attribute after boot completes (was only in boot-init
which sx-platform.js doesn't call — it steps through boot manually)
- Add document-level click delegation for a[sx-get] links as workaround
for bytecoded bind-event not attaching per-element listeners (VM closure
issue under investigation — bind-event runs but dom-add-listener calls
don't result in addEventListener)
Tests:
- New test_kernel.js: 24 tests covering env sync, parse, route matching,
host FFI/preventDefault, deep recursion
- New navigation test: "sx-get link fetches SX not HTML and preserves layout"
(currently catches layout breakage after SPA swap — known issue)
Known remaining issues:
- JIT CSSX failures: closure-captured variables resolve to nil in VM bytecode
- SPA content swap via execute-request breaks page layout
- Bytecoded bind-event doesn't attach per-element addEventListener (root
cause unknown — when listen-target guard appears to block despite element
being valid)
Co-Authored-By: Claude Opus 4.6 (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>
Navigation pipeline now works end-to-end:
- outerHTML swap uses dom-replace-child instead of morph-node (morph has
a CEK continuation issue with nested for-each that needs separate fix)
- swap-dom-nodes returns the new element for outerHTML so post-swap
hydrates the correct (new) DOM, not the detached old element
- sx-render uses marker mode: islands rendered as empty span[data-sx-island]
markers, hydrated by post-swap. Prevents duplicate content from island
body expansion + SX response nav rows.
- dispose-island (singular) called on old island before morph, not just
dispose-islands-in (which only disposes sub-islands)
OCaml runtime:
- safe_eq: Dict equality checks __host_handle for DOM node identity
(js_to_value creates new Dict wrappers per call, breaking physical ==)
- contains?: same host handle check
- to_string: trampoline thunks (fixes <thunk> display)
- as_number: trampoline thunks (fixes arithmetic on leaked thunks)
DOM platform:
- dom-remove, dom-attr-list (name/value pairs), dom-child-list (SX list),
dom-is-active-element?, dom-is-input-element?, dom-is-child-of?, dom-on
All 5 reactive-nav Playwright tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>