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 stepper's split-tag and steps-to-preview used (list) with append!,
but in the WASM kernel (list) creates immutable List values — append!
returns a new list without mutating, so children accumulate nowhere.
Changed all accumulator initializations to (mutable-list):
- split-tag: cch, cat, spreads
- steps-to-preview: bc-loop inner children, initial call
- result and tokens lists in the parsing setup
Also includes WASM rebuild with append! primitive and &rest fixes.
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>
Revert (when (client?) ...) guard in signals.sx — it broke JS tests
since client? is false in Node.js too.
Instead, rebind effect and register-in-scope as no-ops in sx_server.ml
AFTER all .sx files load. The SX definition from signals.sx is replaced
only in the OCaml SSR context. JS tests and WASM browser keep the real
effect implementation.
Remove redundant browser primitive stubs from sx_primitives.ml — only
resource SSR stub needed (effect override moved to server setup).
JS tests: 1582/1585 (3 VM closure interop remain)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- signals.sx: guard effect body with (when (client?) ...) so effects
are no-op during SSR — only 2 stubs needed (effect, register-in-scope)
- sx_primitives.ml: add resource SSR stub (returns signal {loading: true}),
remove 27 unnecessary browser primitive stubs
- sx_server.ml: native component-source that looks up Component/Island
from env and pretty-prints the definition (replaces broken Python helper)
- reactive-islands/index.sx: Examples section with all 15 live demos
inline + highlighted source via component-source
- reactive-islands/demo.sx: replace 14 hardcoded highlight strings with
(component-source "~name") calls for always-current source
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>
Rewrite compile-modules.js to use the native OCaml sx_server binary
instead of the js_of_ocaml kernel in Node.js. Compiles 23 modules in
23s (was 3+ minutes). Uses batch epoch protocol with latin1 encoding
to preserve byte positions for multi-byte UTF-8 content.
- Add compile-blob server command: parse source natively, compile via
SX compile-module, return bytecode dict
- Fix orchestration.sxbc.json and boot.sxbc.json — never compiled
successfully with the old JS kernel, now work with native compiler
- Auto-copy compiled bytecode to shared/static/wasm/sx/ for serving
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Island markers rendered during SX navigation responses had no
data-sx-state attribute, so hydration found empty kwargs and path
was nil in the copyright display. Now adapter-dom.sx serializes
keyword args into data-sx-state on island markers, matching what
adapter-html.sx does for SSR.
Also fix post-swap to use parent element for outerHTML swaps in
SX responses (was using detached old target). Add SX source file
hashes to wasm_hash for proper browser cache busting — changing
any .sx file now busts the cache. Remove stale .sxbc bytecode
cache files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Bytecode modules are now serialized as s-expressions (.sxbc) in addition
to JSON (.sxbc.json). The .sxbc format is the canonical representation —
content-addressable, parseable by the SX parser, and suitable for CID
referencing. Annotation layers (source maps, variable names, tests, docs)
can reference the bytecode CID without polluting the bytecode itself.
Format: (sxbc version hash (code :arity N :bytecode (...) :constants (...)))
The browser loader tries .sxbc first (via load-sxbc kernel primitive),
falls back to .sxbc.json. Caddy needs .sxbc MIME type to serve the new
format (currently 404s, JSON fallback works).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>