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>
Three fixes:
1. Framework: render-dom-lake preserves SSR elements during hydration.
When client-side render-to-dom encounters a lake with an existing
DOM element (from SSR), it reuses that element instead of creating
a new one. This prevents the SSR HTML from being replaced with
unresolvable raw SX expressions (~tw calls).
2. Stepper: skip rebuild-preview on initial hydration. Uses a non-
reactive dict flag (not a signal) to avoid triggering the effect
twice. On first run, just initializes the DOM stack from the
existing SSR content by computing open-element depth from step
types and walking lastElementChild.
3. Stepper: rebuild-preview computes correct DOM stack after re-render.
Same depth computation + DOM walk approach. This fixes the bug where
do-step after do-back would append elements to the wrong parent
(e.g. "sx" span outside h1).
Also: increased code view font-size from 0.5rem to 0.85rem.
Playwright tests:
- lake never shows raw SX during hydration (mutation observer)
- back 6 + forward 6 keeps all 4 spans inside h1
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>
WASM host-callbacks on document/body don't fire, and the CSS selector
[sx-on\:] doesn't work in the WASM query engine. Work around both
issues in sx-platform-2.js:
1. Keyboard shortcuts: global document keyup listener matches elements
by sx-trigger attribute containing key=='X', calls execute-request
via K.eval
2. sx-on: inline handlers: scan all elements for sx-on:* attributes,
bind JS Function handlers. Handle HTML attribute lowercasing
(afterSwap → afterswap) by listening for both camelCase and
lowercase event name forms
3. bind-event from: modifier resolves "body"/"document"/"window" to
direct references (dom-body, dom-document, dom-window)
4. Native SX key filter for [condition] triggers
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. parse-trigger-spec: strip [condition] from event names, store as
"filter" modifier
2. bind-event: native SX filter for key=='X' patterns (extracts key
char and checks event.key + not-input guard)
3. bind-event from: modifier: resolve "body"/"document"/"window" to
direct DOM references instead of dom-query
4. sx-platform-2.js: global keyboard dispatch — WASM host-callbacks
on document/body don't fire, so keyboard triggers with from:body
are handled from JS, calling execute-request via K.eval
5. bind-inline-handlers: map afterSwap/beforeRequest to sx: prefix,
eval JS bodies via Function constructor
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1. parse-trigger-spec: strip [condition] from event names, store as
"filter" modifier (e.g. keyup[key=='s'] → event="keyup", filter=...)
2. bind-event: evaluate filter conditions via JS Function constructor
when filter modifier is present
3. bind-inline-handlers: map afterSwap/beforeRequest etc. to sx:*
event names (matching what the engine dispatches)
4. bind-inline-handlers: detect JS syntax in body (contains ".") and
eval via Function instead of SX parse — enables this.reset() etc.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The poll trigger in bind-triggers called set-interval but discarded the
interval ID, so polls continued firing after the element was removed from
the DOM. Now the callback checks el.isConnected each tick and self-clears
when the element is gone (HTMX-style cleanup).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Tag names from dom-tag-name are lowercase (not uppercase) in the WASM
kernel — fix FORM/INPUT/SELECT/TEXTAREA comparisons in get-default-trigger.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
From other session: adapter-html/sx/dom fixes, orchestration
improvements, examples-content refactoring, SPA navigation test
updates, WASM copies synced.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
highlight.sx now returns a list of (span :class "..." "text") elements
instead of a string. The rendering pipeline handles the rest:
- Server render-to-html: produces <span class="...">text</span>
- Client render-to-dom: produces DOM span elements
- Aser: serializes spans as SX for client rendering
Key fixes:
- hl-span uses (make-keyword "class") not :class (keywords evaluate
to strings in list context)
- render-sx-tokens returns flat list of spans (no wrapper)
- hl-escape is identity (no escaping needed for tree values)
- highlight.sx added to browser bundle + platform loader
- ~docs/code renders src directly as child of pre
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Named let's sf-named-let used call-lambda which returns a thunk that
was never trampolined. The body executed in a disconnected env, so
set! couldn't reach outer let bindings. Fixed by using cek-call which
evaluates through the full CEK machine with proper env chain.
Also converted test-named-let-set.sx from assert= (uses broken = for
lists) to deftest/assert-equal (uses deep equal?).
JS standard: 1120/1120, JS full: 1600/1600. Zero failures.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
call_lambda returns a thunk (for TCO) but sf_named_let was passing
it directly to the CEK machine without trampolining. The loop body
never executed — set! mutations were lost and the loop returned
immediately.
One-line fix: wrap call_lambda result in trampoline.
All 87 Node tests now pass:
- test-named-let-set.js: 9/9 (was 3/9)
- test-highlight.js: 7/7 (was 1/7)
- test-smoke.js: 19/19
- test-reactive-islands.sx: 22/22
- test-reactive-islands.js: 39/39
Note: server-side source display still empty because the JIT
compiler handles named let differently (VM bytecode path, not
tree-walk). The JIT fix is separate.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
build-all.sh was missing the sx-platform.js → sx-platform-2.js copy,
so the served file was stale (still requesting .sxbc.json instead of
.sxbc).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The bytecode compiler generates .sxbc (SX text format), not .sxbc.json.
Updated loadBytecodeFile to fetch .sxbc and use load-sxbc for parsing.
Eliminates 404s for non-existent .sxbc.json files.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: sx_primitives.ml registered "effect" as a native no-op (for SSR).
The bytecode compiler's (primitive? "effect") returned true, so it emitted
OP_CALL_PRIM instead of OP_GLOBAL_GET + OP_CALL. The VM's CALL_PRIM handler
found the native Nil-returning stub and never called the real effect function
from core-signals.sx.
Fix: Remove effect and register-in-scope from the primitives table. The server
overrides them via env_bind in sx_server.ml (after compilation), which doesn't
affect primitive? checks.
Also: VM CALL_PRIM now falls back to cek_call for non-NativeFn values (safety
net for any other functions that get misclassified).
15/15 source mode, 15/15 bytecode mode.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three root causes for reactive attribute updates not propagating in WASM:
1. `context` CEK special form only searched kont provide frames, missing
`scope-push!` entries in the native scope_stacks hashtable. Unified by
adding scope_stacks fallback to step_sf_context.
2. `flush-subscribers` used bare `(sub)` call which failed to invoke
complex closures in for-each HO callbacks. Changed to `(cek-call sub nil)`.
3. Test eagerly evaluated `(deref s)` before render-to-dom saw it.
Fixed tests to use quoted expressions matching real browser boot.
WASM native: 10/10, WASM shell: 26/26.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The OCaml build produced new WASM assets after adding resource stub
and effect/register-in-scope SSR overrides to sx_primitives.ml and
sx_server.ml. Browser needs matching WASM to boot correctly.
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>
Registering map/for-each/reduce as PRIMITIVES caused the compiler to
emit CALL_PRIM for them everywhere. The OCaml VM's call-primitive
can't invoke VM closures, causing "Undefined symbol: resource" crashes.
Revert vm.sx to original CALL_PRIM handler. Remove map/for-each/reduce
from JS PRIMITIVES so compiler emits OP_CALL instead (handled by
vm-call which dispatches correctly).
3 JS VM tests remain failing (VM closure interop) but production is stable.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- sx_browser.ml: use cek_call instead of sx_call in call-lambda to
avoid eval_expr deep-copying Dict values (breaks signal mutation)
- sx-browser.js: rebuilt with latest transpiler changes
- reactive-islands/index.sx: pretty-printed (no semantic changes)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
dom-id returns "" for elements without an id attribute, and "" is
truthy in SX. This caused morph-children to build spurious entries
in old-by-id keyed by "", then match unrelated children via the
empty key — advancing oi past id-keyed children like #main-content
and skipping them in cleanup.
Three changes in morph-children (engine.sx):
- old-by-id reduce: normalize empty dom-id to nil so id-less
elements are excluded from the lookup dict
- match-id binding: normalize empty dom-id to nil so new children
without ids don't spuriously match old children
- Case 2 skip condition: use (not (empty? ...)) instead of bare
(dom-id old-child) to avoid treating "" as a real id
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
signal-add-sub! used (append! subscribers f) which returns a new list
for immutable List but discards the result — after signal-remove-sub!
replaces the subscribers list via dict-set!, re-adding subscribers
silently fails. Counter island only worked once (0→1 then stuck).
Fix: use (dict-set! s "subscribers" (append ...)) to explicitly update
the dict field, matching signal-remove-sub!'s pattern.
Build pipeline fixes:
- sx-build-all.sh now bundles spec→dist and recompiles .sxbc bytecode
- compile-modules.js syncs .sx source files alongside .sxbc to wasm/sx/
- Per-file cache busting: wasm, platform JS, and sxbc each get own hash
- bundle.sh adds cssx.sx to dist
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Three bugs in the DOM morph algorithm (web/engine.sx):
1. Empty-id keying: dom-id returns "" (not nil) for elements without an
id attribute, and "" is truthy in SX. Every id-less element was stored
under key "" in old-by-id, causing all new children to match the same
old element via the keyed branch — collapsing all children into one.
Fix: guard with (and id (not (empty? id))) in map building and matching.
2. Cleanup bug: the oi-cursor cleanup (range oi len) removed keyed elements
that were matched and moved from positions >= oi, and failed to remove
unmatched elements at positions < oi. Fix: track consumed indices in a
dict and remove all unconsumed elements regardless of position.
3. Island attr sync: morph-node delegated to morph-island-children without
first syncing the island element's own attributes (e.g. data-sx-state).
Fix: call sync-attrs before morph-island-children.
Also: pass explicit `true` to all dom-clone calls (deep clone parameter).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
JIT compiler:
- Fix jit_compile_lambda: resolve `compile` via symbol lookup in env
instead of embedding VmClosure in AST (CEK dispatches differently)
- Register eval-defcomp/eval-defisland/eval-defmacro runtime helpers
in browser kernel for bytecoded defcomp forms
- Disable broken .sxbc.json path (missing arity in nested code blocks),
use .sxbc text format only
- Mark JIT-failed closures as sentinel to stop retrying
CSSX in browser:
- Add cssx.sx symlink + cssx.sxbc to browser web stack
- Add flush-cssx! to orchestration.sx post-swap for SPA nav
- Add cssx.sx to compile-modules.js and mcp_tree.ml bytecode lists
SPA navigation:
- Fix double-fetch: check e.defaultPrevented in click delegation
(bind-event already handled the click)
- Fix layout destruction: change nav links from outerHTML to innerHTML
swap (outerHTML destroyed #main-panel when response lacked it)
- Guard JS popstate handler when SX engine is booted
- Rename sx-platform.js → sx-platform-2.js to bust immutable cache
Playwright tests:
- Add trackErrors() helper to all test specs
- Add SPA DOM comparison test (SPA nav vs fresh load)
- Add single-fetch + no-duplicate-elements test
- Improve MCP tool output: show failure details and error messages
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- AJAX requests (SX-Request: true) now render on main thread instead
of queueing behind slow full-page renders in worker pool
- Remove pushState from click handler — handle-history does it after
swap succeeds, preventing double-push that triggered popstate handler
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: _env_bind_hook mirrored ALL env_bind calls (including
lambda parameter bindings) to the shared VM globals table. Factory
functions like make-page-fn that return closures capturing different
values for the same param names (default-name, prefix, suffix) would
have the last call's values overwrite all previous closures' captured
state in globals. OP_GLOBAL_GET reads globals first, so all closures
returned the last factory call's values.
Fix: only sync root-env bindings (parent=None) to VM globals. Lambda
parameter bindings stay in their local env, found via vm_closure_env
fallback in OP_GLOBAL_GET.
Also in this commit:
- OP_CLOSURE propagates parent vm_closure_env to child closures
- Remove JIT globals injection (closure vars found via env chain)
- sx_server.ml: SX-Request header → returns text/sx (aser only)
- sx_server.ml: diagnostic endpoint GET /sx/_debug/{env,eval,route}
- sx_server.ml: page helper stubs for deep page rendering
- sx_server.ml: skip client-libs/ dir (browser-only definitions)
- adapter-html.sx: unknown components → HTML comment (not error)
- sx-platform.js: .sxbc fallback loader for bytecode modules
- Delete sx_http.ml (standalone HTTP server, unused)
- Delete stale .sxbc.json files (arity=0 bug, replaced by .sxbc)
- 7 new closure isolation tests in test-closure-isolation.sx
- mcp_tree.ml: emit arity + upvalue-count in .sxbc.json output
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sx_compiler.compile/compile_module now emit arity (local slot count) in
the bytecode dict. MCP sx_build_bytecode serializes arity into .sxbc.json.
sx-platform.js passes arity through to K.loadModule(). Without this, the
VM allocated only 16 local slots per module frame.
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>
The deployed sx_browser.bc.wasm.js was actually the js_of_ocaml output
(pure JS), not the wasm_of_ocaml loader. Nothing synced the correct
build output from _build/ to shared/static/wasm/.
- sx_build target=ocaml now auto-syncs WASM kernel + JS fallback + assets
- sx-build-all.sh syncs after dune build
- Correct 68KB WASM loader replaces 3.6MB JS imposter
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>
Iterative cek_run broke page-script parsing in browser. Reverted to
recursive — bytecode compilation overflow handled by native Sx_compiler.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace Node.js compile-modules.js with direct Sx_compiler.compile_module
calls in mcp_tree.ml. No subprocess, no JIT warm-up, no Node.js.
23 files compile in 1.9 seconds.
Also includes rebuilt WASM kernel (iterative cek_run) and all 23
bytecode modules recompiled with native compiler.
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>
- request-handler.sx: replace all dots (not just `.(`) and auto-quote
undefined symbols as strings so 3-level URLs like
/sx/(geography.(reactive.(examples.counter))) resolve correctly
- sx-platform.js: register popstate handler (was missing from manual
boot sequence) and fetch full HTML for back/forward navigation
- sx_ref.ml: add CEK step limit (10M steps) checked every 4096 steps
so runaway renders return 500 instead of blocking the worker forever
- Rename test-runner.sx → runner-placeholder.sx to avoid `test-` skip
- Playwright config: pin testDir, single worker, ignore worktrees
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>
WASM kernel (sx_browser.ml): remove jit_failed_sentinel marking on
JIT runtime errors. Functions stay JIT-compiled and retry on next call.
Errors are data-dependent, not JIT bugs.
signal-condition: rename in CEK dispatcher so reactive 'signal' function
works. The conditions system 'signal' special form is now 'signal-condition'.
adapter-html.sx: remove cek-try wrapping island render. Errors propagate.
Island SSR: 4/5 tests pass. Header renders perfectly. Stepper ~cssx/tw
not expanding because component calls in island body aren't evaluated
by render-to-html — they're serialized as text. Needs SX adapter fix.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: the new conditions system's 'signal' special form shadowed
the reactive 'signal' function. (signal 0) in island bodies raised
'Unhandled condition: 0' instead of creating a signal dict.
Fix: rename condition special form to 'signal-condition' in the CEK
dispatcher. The reactive 'signal' function now works normally.
adapter-html.sx: remove cek-try that swallowed island render errors.
Islands now render directly — errors propagate for debugging.
sx_render.ml: add sx_render_to_html that calls SX adapter via CEK.
Results: 4/5 island SSR tests pass:
- Header island: logo, tagline, styled elements ✓
- Navigation buttons ✓
- Geography content ✓
- Stepper: partially renders (code view OK, ~cssx/tw in heading)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
jit_compile_lambda now uses the live globals table directly instead
of Hashtbl.copy. Closure bindings that aren't already in globals are
injected into the live table. This ensures GLOBAL_GET always sees
the latest define values.
Previously: Hashtbl.copy created a stale snapshot. Functions defined
after the copy (like cssx-process-token from cssx.sx) resolved to nil
in JIT-compiled closures.
JIT error test now passes — 0 CSSX errors during navigation.
7/8 navigation tests pass. Remaining: back button content update.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>