Clicks on island elements before hydration completes are captured
and replayed after boot finishes:
- shell.sx: inline script (capture phase) buffers clicks on
[data-sx-island] elements that aren't hydrated yet into window._sxQ
- boot.sx: after hydration + process-elements, replays buffered clicks
by calling target.click() on elements still connected to the DOM
This makes SSR islands feel interactive immediately — the user can
click a button while the SX kernel is still loading/hydrating, and
the action fires as soon as the handler is wired up.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add wasm_hash (MD5 of sx_browser.bc.js) to shell template
- Script tags: /wasm/sx_browser.bc.js?v={hash}, /wasm/sx-platform.js?v={hash}
- Pass wasm_hash through helpers.py and ocaml_bridge.py
- Fix missing close paren in bind-sse-swap (broke SX parsing)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The DOM adapter treated all Dict values as empty (create-fragment).
But DOM nodes (text nodes, elements) from create-text-node/host-call
are wrapped as Dict { __host_handle: N } by js_of_ocaml. Now checks
for __host_handle and passes them through as DOM nodes.
Fixes stopwatch button text ("Start") and timer display ("0.0s")
which were missing because create-text-node results were discarded.
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>