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>
Phase 1 Step 2 of architecture roadmap. The OCaml HTTP server is now
generic — all sx_docs-specific values (layout components, path prefix,
title, warmup paths, handler prefixes, CSS/JS, client libs) move into
sx/sx/app-config.sx as a __app-config dict. Server reads config at
startup with hardcoded defaults as fallback, so it works with no config,
partial config, or full config.
Removed: 9 demo data stubs, stepper cookie cache logic, page-functions.sx
directory heuristic. Added: 29-test server config test suite covering
standard, custom, no-config, and minimal-config scenarios.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Phase 1 Step 1 of the architecture roadmap. The old cssx.sx
(cssx-resolve, cssx-process-token, cssx-template, old tw function)
is superseded by the ~tw component system in tw.sx.
- Delete shared/sx/templates/cssx.sx
- Remove cssx.sx from all load lists (sx_server.ml, run_tests.ml,
mcp_tree.ml, compile-modules.js, bundle.sh, sx-build-all.sh)
- Replace (tw "tokens") inline style calls with (~tw :tokens "tokens")
in layouts.sx and not-found.sx
- Remove _css-hash / init-css-tracking / SX-Css header plumbing
(dead code — ~tw/flush + flush-collected-styles handle CSS now)
- Remove sx-css-classes param and meta tag from shell template
- Update stale data-cssx references to data-sx-css in tests
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The env_bind hook was copying SX-defined functions (e.g. has-key? from
stdlib.sx) into vm_globals, shadowing the native primitives seeded there.
CALL_PRIM then called the SX version which broke with wrong arg types.
Fix: env_bind hook skips names that are registered primitives. Native
implementations are authoritative for CALL_PRIM dispatch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move primitive seeding to end of make_server_env() so ho_via_cek
wrappers (map, filter, etc.) are already in vm_globals. The seeding
only adds primitives NOT already present, preserving wrappers.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Seed all primitives into vm_globals as NativeFn values at init.
CALL_PRIM now looks up vm.globals only (not the separate primitives
table). This means OP_DEFINE and registerNative naturally override
primitives — browser.sx's (define set-cookie ...) now takes effect.
The primitives Hashtbl remains for the compiler's primitive? predicate
but has no runtime dispatch role.
Tests: 2435 pass / 64 fail (pre-existing), vs 1718/771 baseline.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Parse Cookie header in OCaml HTTP server for get-cookie primitive
- Stepper saves step-idx to cookie via host-set! FFI on click
- Stepper restores from cookie: get-cookie on server, host-get FFI on client
- Cache key includes stepper cookie value to avoid stale SSR
- registerNative: also update Sx_primitives table for CALL_PRIM dispatch
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The slug extractor after (api. scanned for the first ) but for nested
URLs like (api.(delete.1)) it got "(delete.1" instead of "delete".
Now handles nested parens: extracts handler name and injects path
params into query string. Also strengthened the Playwright test to
accept confirm dialogs and assert strict row count decrease.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Skip the SX api function entirely. The OCaml handler interception
looks up handler:ex-{slug} in the env, extracts the body, and calls
aser directly with the full global env. This avoids the double-eval
problem where eval-expr evaluates HTML tags as function calls and
then tries to call the result.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
defhandler was only available via web-forms.sx which had load
dependencies that failed. Now registered as a native special form
in make_server_env, works in both coroutine and HTTP modes.
Key fix: custom special forms receive [List args; Env eval_env],
not flat args. The handler is now bound in the eval env, not the
make_server_env closure env.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The query string was stripped from path for routing but the debug
endpoint needs it to parse ?expr= and ?name= params.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Checks binary mtime every 10 requests. If the binary has been
rebuilt, closes the listen socket and exec's itself. No more
stale server after builds.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Paths containing (api.) are intercepted before page routing and
dispatched directly to the api function. The handler result is
rendered to SX wire format and returned without layout wrapping.
This fixes the issue where handler URLs went through page routing,
causing the handler result to be passed as a slug to the page
function, and the response to be wrapped in the full page layout.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
OCaml server:
- Accept POST/PUT/PATCH/DELETE for /sx/ paths (was GET-only)
- Parse request body, query string, set per-request context
- Add 16 request primitives: now, state-get/set!, request-form/arg,
request-json, request-header(s), request-method/body, into, etc.
- URL-encoded body parser for form submissions
Handler dispatch (sx/sx/handlers/dispatch.sx):
- `api` function routes URL paths like (api "click") to handler:ex-click
- `call-handler` checks HTTP method, binds params, evaluates body
- Handlers defined via defhandler in handlers/examples.sx now reachable
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
build-reference-data, build-attr-detail, etc. were undefined because
page-helpers.sx wasn't in the explicit core_files list.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Route errors and missing pages now show a styled error message inside
the normal layout (header, nav still work) instead of bare "nil" text
or a raw "Not Found" page. AJAX errors return renderable SX error
fragments instead of "nil" strings.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
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>
- 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>
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>
- 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>
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>
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>
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>
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>
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>
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>
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>
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>
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>
Server always returns full page. Client sx-select handles extraction.
No application logic in OCaml server.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AJAX fragment: extract #main-panel with matching close tag (depth
tracking) instead of taking everything to end of file. Prevents
shell closing tags from breaking the DOM swap.
Back button test: verifies content actually changes — checks for
"Geography" and "Rendering Pipeline" after going back, not just
that body has >100 chars. Tests forward nav content change too.
7/7 navigation tests pass including back button content verification.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Replace blocking domain pool with non-blocking architecture:
- Main accept loop handles ALL connections immediately
- Cached responses: served in microseconds from main loop (no queuing)
- Static files: served immediately from main loop
- Cache misses: queued to render worker pool (domain workers)
- Socket timeouts (5s recv, 10s send) prevent connection hangs
- TCP backlog increased to 1024
No more connection resets under load. 22/26 Playwright tests pass
(4 failures from stale worktree test copies, 0 from main tree).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
AJAX navigation: detect SX-Request/HX-Request headers and return just
the #main-panel fragment instead of the full page shell. Fixes layout
break where header and content appeared side-by-side after navigation.
New navigation test suite (tests/playwright/navigation.spec.js):
- layout stays vertical after clicking nav link
- content updates after navigation
- no raw SX component calls visible after navigation
- header island survives navigation
- full page width is used (no side-by-side split)
All 5 tests pass. 14 total Playwright tests pass.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Move client library sources (cssx.sx) before defcomp/defisland
definitions in component-defs so defines are evaluated first.
Identified root cause of CSSX "Not callable: nil" errors:
JIT compiler captures free variable values at compile time instead of
looking them up at runtime from vm_globals. When ~cssx/tw's JIT code
calls cssx-process-token, it uses the compile-time snapshot (nil)
instead of the runtime value (lambda). The function IS in global_env
(type-of returns "lambda") but the JIT bytecode doesn't see it.
Fix needed: JIT compiler should emit GLOBAL_GET instructions for free
variables that reference vm_globals at runtime, not capture at compile.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Switch SSR + shell from native Sx_render to SX adapter (render-to-html
from adapter-html.sx) via CEK evaluator. Handles reactive primitives
(signals, deref, computed) for island bodies. Native renderer as fallback.
Header island SSRs correctly: (<sx>), tagline, copyright, path.
Stepper renders body but ~cssx/tw shows as raw SX (client-affinity
component not expanded server-side, client not expanding either).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Island SSR reverted to placeholder approach (full SSR crashed on
reactive code). Placeholders include SX call expression for client
hydration. CSS hides placeholder text until WASM renders.
Remaining: need SX adapter (adapter-html.sx) for island SSR instead
of native Sx_render — the SX adapter handles signals/deref/computed
via the CEK machine. Native renderer doesn't support reactive primitives.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>