Core SX has zero IO — platforms extend __io-registry via (defio name
:category :data/:code/:effect ...). The server web platform declares 44
operations in web/io.sx. batchable_helpers now derived from registry
(:batchable true) instead of hardcoded list. Startup validation warns if
bound IO ops lack registry entries. Browser gets empty registry, ready
for step 5 (IO suspension).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The text/sx AJAX response path (handle-sx-response) never called
hoist-head-elements, so <style> elements stayed in #sx-content instead
of moving to <head>. Additionally, CSS rules collected during client-side
island hydration were never flushed to the DOM.
- Add hoist-head-elements call to handle-sx-response (matching
handle-html-response which already had it)
- Add flush-collected-styles helper that drains collected CSS rules
into a <style data-sx-css> element in <head>
- Call flush after island hydration in post-swap, boot-init, and
run-post-render-hooks to catch reactive re-renders
- Unify on data-sx-css attribute (existing convention) in ~tw/flush
and shell template, removing the ad-hoc data-cssx attribute
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Reverted render-dom-lake SSR reuse — it broke OOB swaps (claimed
old lake elements during morph, stale content in copyright). The
framework's morphing handles lake updates correctly already.
Stepper: lake passes nil on client (prevents raw SX flash), effect
always calls rebuild-preview (no initial-render flag needed). Server
renders the expression for SSR; client rebuilds via render-to-dom
after boot when ~tw is available.
Removed initial-render dict flag — unnecessary complexity.
Copyright route not updating is a pre-existing issue: render-dom-island
renders the header island inline during OOB content rendering (sets
island-hydrated mark), but the copyright lake content doesn't reflect
the new path. Separate investigation needed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The change to re-render children when non-empty caused the stepper's
lake to show raw SX again (the ~tw expressions in steps-to-preview
were rendered as text). Reverted to the simpler approach: reuse the
existing SSR element and preserve its content entirely.
The copyright route not updating during SPA nav is a pre-existing
issue (path is an island parameter, not a reactive signal).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
After reusing an SSR lake element, set data-sx-lake-claimed attribute.
Subsequent dom-query uses :not([data-sx-lake-claimed]) to skip already-
reused elements, preventing SPA navigation from picking up stale lakes
from previous pages.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The text/sx AJAX response path (handle-sx-response) never called
hoist-head-elements, so <style> elements stayed in #sx-content instead
of moving to <head>. Additionally, CSS rules collected during client-side
island hydration were never flushed to the DOM.
- Add hoist-head-elements call to handle-sx-response (matching
handle-html-response which already had it)
- Add flush-collected-styles helper that drains collected CSS rules
into a <style data-sx-css> element in <head>
- Call flush after island hydration in post-swap, boot-init, and
run-post-render-hooks to catch reactive re-renders
- Unify on data-sx-css attribute (existing convention) in ~tw/flush
and shell template, removing the ad-hoc data-cssx attribute
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
boot-init now sets data-sx-ready on <html> and dispatches an sx:ready
CustomEvent after all islands are hydrated. Playwright tests use this
instead of networkidle + hard-coded sleeps (50+ seconds eliminated).
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>
Fixed three fundamental issues:
1. cek-try arg passing: handler was called with raw string instead of
(List [String msg]), causing "lambda expects 1 args, got N" errors
2. Silent island hydration failures: hydrate-island now wraps body
render in cek-try, displaying red error box with stack trace instead
of empty div. No more silent failures.
3. swap! thunk leak: apply result wasn't trampolined, storing thunks
as signal values instead of evaluated results
Also fixed: assert= uses = instead of equal? for value comparison,
assert-signal-value uses deref instead of signal-value, HTML entity
decoding in script tag test source via host-call replaceAll.
Temperature converter demo page now shows live test results:
✓ initial celsius is 20
✓ computed fahrenheit = celsius * 1.8 + 32
✓ +5 increments celsius
✓ fahrenheit updates on celsius change
✓ multiple clicks accumulate
1116/1116 OCaml tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The click buffer (capture + stopPropagation + replay) caused more
harm than good: synchronous XHR blocks the main thread during kernel
load, so there's no window where clicks can be captured. The buffer
was eating clicks after hydration due to property name mismatches.
Replace with pure CSS: buttons/links/[role=button] inside islands
get cursor:pointer from SSR. No JS needed, works immediately.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Buttons clicked before hydration get a subtle pulse animation
(sx-pending class) showing the click was captured. The animation
is removed when the click is replayed after hydration, or cleared
on boot completion as a fallback.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>