Commit Graph

207 Commits

Author SHA1 Message Date
28273eb740 Fix component-source SSR override, add SX island tests
component-source from data/helpers.sx was overriding the native
OCaml version. The SX version calls env-get with wrong arity (1 arg
vs required 2), producing empty source. Re-bind the native version
in SSR overrides after file loading.

Note: source code still not visible because highlight function
returns empty — separate issue in the aser rendering pipeline.

Also adds:
- spec/tests/test-reactive-islands.sx — 22 SX-native tests for all
  14 reactive island demos (render + signal logic + DOM)
- tests/node/run-sx-tests.js — Node runner for SX test files
- tests/node/test-reactive-islands.js — 39 Node/happy-dom tests

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:44:11 +00:00
9742d0236e Convert WASM browser tests to SX deftest forms
Tests moved from inline JS assertions to web/tests/test-wasm-browser.sx
using the standard deftest/defsuite/assert-equal framework. The JS driver
(test_wasm_native.js) now just boots the kernel, loads modules, and runs
the SX test file.

15/15 source, 15/15 bytecode.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:37:12 +00:00
6550e9b2e4 Fix build-all.sh: deploy platform JS to shared/static/wasm/
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>
2026-03-31 17:05:31 +00:00
4eeb7777aa Fix platform JS: load .sxbc text format, not .sxbc.json
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>
2026-03-31 17:02:55 +00:00
200e5d5e47 Add .sxbc MIME type to OCaml HTTP server static file handler
Stops 404s for bytecoded module files — the server now serves .sxbc
files with the correct content type.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 17:00:44 +00:00
a7efcaf679 Fix hydration: effect was a no-op primitive, bytecode compiler emitted CALL_PRIM
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>
2026-03-31 16:56:31 +00:00
4cb4551753 Add build-all.sh, bytecode test mode — 5 failures with SX_TEST_BYTECODE=1
The hydration bug root cause: .sxbc bytecoded modules break effect
closures. Source-loaded modules work (15/15), bytecoded fail (10/15).

Run: SX_TEST_BYTECODE=1 node hosts/ocaml/browser/test_wasm_native.js

Failing: scoped static class, signal attr initial, reactive-spread,
CSSX in island scope, reactive attr update.

Also adds build-all.sh (full WASM pipeline) and sx_build target="wasm"
MCP tool integration.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 16:26:31 +00:00
42aa6b1e67 Fix test accessor, add CSSX hydration test — 14/14 pass
The reactive-spread test used host-get "className" which doesn't exist
on Node.js DOM stubs. Changed to dom-get-attr "class". Also added
render-to-dom CSSX-in-island-scope test verifying the full hydration
pattern produces correct class attributes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:35:03 +00:00
8fd01c2ab0 Add failing test: reactive-spread from module-loaded define
reactive-spread (module-loaded via K.load) doesn't apply CSSX classes
to DOM elements. Returns null instead of "sx-text-center". This is the
root cause of hydration stripping all styling — every rendering function
is define-bound from module load.

12 pass, 1 fail — the reactive-spread test is the blocker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 15:32:59 +00:00
521782d579 Fix WASM reactive signals: unify context/scope, fix flush-subscribers
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>
2026-03-31 15:12:25 +00:00
e2b29fb9f3 Fix effect SSR: rebind after file load instead of guarding spec
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>
2026-03-31 10:55:04 +00:00
128dbe1b25 Live demo islands with highlighted source: SSR-safe effect, native component-source
- 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>
2026-03-31 10:45:22 +00:00
b423ebcea9 Add resource SSR primitive, restore live demos in Examples section
- sx_primitives.ml: register resource as SSR stub returning signal
  {loading: true} — server renders loading state, client hydrates fetch
- reactive-islands/index.sx: Examples section with all 15 demo islands
  rendered inline alongside highlighted source code

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 10:28:26 +00:00
d26029fee2 Fix call-lambda signal mutation, rebuild browser JS, reformat reactive-islands
- 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>
2026-03-31 10:05:02 +00:00
465ce1abcb Page helpers in pure SX: 3 OCaml primitives + SX data + SX helpers
Add pretty-print, read-file, env-list-typed primitives to OCaml kernel.
Convert Python reference data (attrs, headers, events, primitives) to SX
data files. Implement page helpers (component-source, handler-source,
read-spec-file, reference-data, etc.) as pure SX functions.

The helper dispatcher in HTTP mode looks up named functions in the env
and calls them directly, replacing the Python IO bridge path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 07:42:59 +00:00
9ce8659f74 Fix signal-add-sub! losing subscribers after remove, fix build pipeline
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>
2026-03-31 07:36:36 +00:00
d81a518732 Fix JIT compiler, CSSX browser support, double-fetch, SPA layout
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>
2026-03-30 20:48:43 +00:00
5b55b75a9a AJAX on main thread, fix double-push in click delegation
- 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>
2026-03-30 18:06:22 +00:00
80931e4972 Fix JIT closure isolation, SX wire format, server diagnostics
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>
2026-03-30 17:28:47 +00:00
408eca1cb0 Set arity in native compiler and bytecode modules
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>
2026-03-30 14:31:55 +00:00
b274e428eb WASM kernel fixes: parse, env sync, iterative CEK, click delegation
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>
2026-03-30 13:33:13 +00:00
03c2115f0d Fix WASM kernel deploy: 3.6MB js_of_ocaml → 68KB wasm_of_ocaml
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>
2026-03-30 13:10:13 +00:00
fffb5ab0b5 Revert iterative cek_run, restore working WASM kernel
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>
2026-03-30 11:10:51 +00:00
951b3a6586 Native bytecode compilation in MCP: 108s → 1.9s (57x faster)
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>
2026-03-30 10:45:38 +00:00
a24efc1a00 Bootstrap SX bytecode compiler to native OCaml
Transpile lib/compiler.sx → hosts/ocaml/lib/sx_compiler.ml (42 functions).
The bytecode compiler now runs as native OCaml instead of interpreted SX,
eliminating the 24s JIT warm-up for compiler functions.

- bootstrap_compiler.py: transpiler script (like bootstrap.py for evaluator)
- sx_compiler.ml: 39KB native compiler (compile, compile-module, etc.)
- Bind compile/compile-module as native functions in setup_core_operations
- Add mutable_list to sx_runtime.ml (used by compiler pool)
- Add native parse function (wraps Sx_parser.parse_all)
- compile-match delegated via ref (uses letrec, transpiler can't handle)
- Compile all 23 bytecode modules successfully (was 0/23 due to WASM overflow)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 10:30:53 +00:00
1985c648eb Native bytecode compiler: 8x faster, compile-blob command
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>
2026-03-30 09:49:28 +00:00
7a4a6c8a85 Fix WASM stack overflow: make cek_run iterative
The CEK evaluator's cek_run was recursive (calls itself via cek_step).
Native OCaml handles deep recursion but wasm_of_ocaml compiles to JS
which has ~10K frame stack limit. Complex expressions (bytecode compiler)
exceeded this, causing "Maximum call stack size exceeded" in all WASM
bytecode compilation.

Replace recursive cek_run with iterative while loop — same semantics,
zero stack growth. Fixes sx_build_bytecode and browser-side evaluation.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 09:14:09 +00:00
671d19c978 SX request handler + AJAX nav + shared JIT globals + shell cleanup
- Remove prism.js, sweetalert2, body.js, sx-browser.js from shell —
  only WASM kernel (sx_browser.bc.wasm.js + sx-platform.js) loads
- Restore request-handler.sx integration: SX handles routing + AJAX
  detection, OCaml does aser → SSR → shell render pipeline
- AJAX fragment support: SX-Request header returns content fragment
  (~14KB) instead of full page (~858KB), cached with "ajax:" prefix
- Fix language/applications/etc page functions to return empty fragment
  instead of nil (was causing 404s)
- Shared JIT VM globals: env_bind hook mirrors ALL bindings to a single
  shared globals table — eliminates stale-snapshot class of JIT bugs
- Add native `parse` function for components that need SX parsing
- Clean up unused shell params (sx-js-hash, body-js-hash, head-scripts,
  body-scripts, use-wasm) from shell.sx, helpers.py, and server.ml

14/32 Playwright tests pass (navigation, SSR, isomorphic, geography).
Remaining failures are client-side (WASM bytecode 404s block hydration).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 03:03:57 +00:00
6845ced98f Enable pre-warm cache + faster Playwright config
Restore page pre-warming at HTTP server startup (was skipped) and
increase render workers from 2→4. Tighten Playwright timeouts and
run 3 workers in parallel for faster test runs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-30 00:00:46 +00:00
9af38a8fbe MCP: load signals + render pipeline, add evaluator stubs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 23:26:19 +00:00
501934f9c6 Skip pre-warm — start listening immediately, render on demand
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:49:08 +00:00
702074eaa9 Add _jit_warned check to prevent parse-loop infinite recompilation
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:38:54 +00:00
07f5d03ac1 Restore OCaml to 5c8b05a + Docker native server entrypoint
All OCaml files restored to the last known working state (5c8b05a).
All SX changes preserved and verified working with native server.
Docker compose updated to run sx_server.exe --http directly.

859KB homepage renders, 7/9 pages cached, ~30s startup (JIT fallback).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:35:40 +00:00
a38b5a9b44 Restore all OCaml + request-handler to working state (aa4c911)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 22:11:17 +00:00
3e1727004c Revert JIT VmClosure optimization — was producing wrong bytecode
The optimization to call the compiler through VM directly (instead
of CEK) when it's JIT-compiled was producing incorrect bytecode for
all subsequently compiled functions, causing "Expected number, got
symbol" errors across render-to-html, parse-loop, etc.

Revert to always using CEK for compilation. The compiler runs via
CEK which is slower but produces correct bytecode. JIT-compiled
USER functions still run at VM speed.

1166 passed, 0 failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:37:06 +00:00
85f72af74b Restore recursive cek_run, remove step counter/limit
The iterative cek_run with Atomic step counting and debug prints
added overhead and complexity. The debug print called value_to_str
twice per million steps which could be very slow on large expressions.

Restore the original recursive cek_run from before the iterative
conversion. Remove the step limit mechanism (was causing render
timeouts). The recursive version is simpler and proven.

1166 passed, 0 failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:29:36 +00:00
b4107fa52b Restore JIT closure injection — needed for top-level function JIT
Removing injection broke GLOBAL_GET for all JIT-compiled functions,
not just mutable closures. Top-level functions like render-to-html
need their referenced bindings in the VM globals table.

Restore the original injection (only injects values not already in
globals). Mutable closure vars (parser's pos etc.) still get stale
snapshots and fall back to CEK — that's the known limitation to fix
with cell-based boxing later.

1166 passed, 0 failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 21:19:35 +00:00
e41f918765 Fix JIT mutable closures: stop injecting stale snapshots into globals
Root cause: jit_compile_lambda copied closure variable VALUES into the
VM globals table. GLOBAL_GET found these stale snapshots instead of
falling through to vm_closure_env which has live bindings. When set!
mutated a variable (like parser's pos), the JIT code read the old
snapshot value.

Fix: don't inject closure bindings into globals at all. GLOBAL_GET
already has a fallback path that walks vm_closure_env — this sees
live env bindings that are updated by set!. No new opcodes needed.

This should fix JIT for parse-loop, skip-ws, read-expr and other
closure functions that use mutable variables.

1166 passed, 0 failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:57:41 +00:00
edf3354050 CEK loop: use local int ref instead of Atomic for step counter
Atomic.fetch_and_add on every CEK step added unnecessary overhead.
The step counter is per-invocation (not shared across threads), so
a plain int ref with incr is sufficient and faster.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:04:05 +00:00
1ad90ed23d Add CEK loop debug print every ~1M steps to diagnose hangs
Prints phase + control expression every 1,048,576 steps so infinite
loops become visible in logs. Only fires when step count is high
enough to indicate a real hang, not normal execution.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 20:00:15 +00:00
f978792e62 JIT: don't mutate l_compiled when skipping warned closures
Setting jit_failed_sentinel on new instances of warned function names
caused hanging — the mutation interfered with CEK execution. Now just
return None without touching l_compiled. The _jit_warned hash lookup
is a cheap check per call with no side effects.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:57:31 +00:00
62d8602de4 JIT: skip recompilation for known-failed closure names
New closure instances of functions like parse-loop get l_compiled=None
even though a previous instance already failed. Now check _jit_warned
by name and immediately sentinel-mark new instances of known failures.

The first failure is always logged. Subsequent instances are silently
sentinel-marked — no recompilation, no log spam, no suppressed errors.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:54:13 +00:00
7ea4c3a652 Remove JIT recompilation suppression — keep errors visible
Revert the _jit_warned check that silenced repeated JIT failures.
These errors indicate real JIT limitations (mutable closures) that
should remain visible until properly fixed. The sentinel on the
instance still prevents the same lambda from retrying.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:51:24 +00:00
4dde8ba684 Fix JIT infinite recompile loop for closure functions
When a JIT-compiled function failed on first call, the function name
was added to _jit_warned but this was never checked before recompiling.
Closures like parse-loop create new lambda instances on each call,
each with l_compiled=None, triggering fresh compilation + failure
in an infinite loop.

Fix: check _jit_warned before attempting compilation, and mark the
lambda with jit_failed_sentinel on first-call failure so the same
instance also stops retrying.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 19:37:12 +00:00
6134bd2ea5 Stepper persistence: use def-store instead of broken cookies
The home stepper's step-idx signal was not persisting across SX
navigation because set-cookie/freeze-to-sx wasn't working in the
WASM kernel. Replace with def-store which uses a global registry
that survives island re-hydration.

Also fix sx_http.exe build: add sx_http back to dune, inline scope
primitives (Sx_scope module was removed), add declarative form
stubs and render stubs, fix /sx/ home route mapping.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:27:08 +00:00
ef34122a25 Fix 30 test failures: OCaml renderer primitives + condition signal rename
OCaml HTML renderer (sx_render.ml) silently returned "" when env_get
failed for primitive function calls (str, +, len, etc.) inside HTML
elements. The Eval_error catch now falls through to eval_expr which
resolves primitives correctly. Fixes 21 rendering tests.

Rename condition system special form from "signal" to "signal-condition"
in evaluator.sx, matching the OCaml bootstrapped evaluator (sx_ref.ml).
This avoids clashing with the reactive signal function. Fixes 9
condition system tests.

1166 passed, 0 failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 18:08:47 +00:00
84a48f0de3 Fix navigation: deep URL routing, back button, render timeout
- 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>
2026-03-29 17:54:33 +00:00
20b3dfb8a0 Fix island state loss on SX navigation + cache busting
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>
2026-03-29 17:36:27 +00:00
8bba02fbc9 Use match for value dispatch in evaluator and compiler
Convert large cond chains doing string equality dispatch to use the
match special form: step-eval-list (42 arms), step-continue (31 arms),
compile-list (30 arms), ho-setup-dispatch (7 arms), value-matches-type?
(10 arms). Also fix test-canonical.sx to use defsuite/deftest format
and load canonical.sx in both test runners.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 07:53:16 +00:00
394c86b474 sx-http: SX request handler — move routing logic from OCaml to SX
New web/request-handler.sx: configurable SX function (sx-handle-request)
that receives path + headers + env and returns rendered HTML.
The handler decides full page vs AJAX fragment.

OCaml server: http_render_page now just calls the SX handler.
All routing, layout selection, AJAX detection moved to SX.
Header parsing added. is_sx_request removed from OCaml.

Configurable via SX_REQUEST_HANDLER env var (default: sx-handle-request).

WIP: handler has parse errors on some URL formats. Needs debugging.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 07:45:57 +00:00