Now calls (highlight text) to produce syntax-colored SX output.
OCaml-side aser has full env so code/span tags resolve correctly.
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>
aser in SX doesn't have access to helper/now/state-get. eval-expr
evaluates the body in the current env, returns the AST. The OCaml
handler interception then calls aser server-side with the full env.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Handler closure env didn't have HTML tags (code, span, div etc.)
causing Undefined symbol errors. aser without explicit env uses
the global env which has all tags registered.
Also reverts oob-code highlight change (code tag not available in
aser context).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Was displaying plain text in (code text). Now calls (highlight text)
to produce colored SX output.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
highlight was double-wrapping in (code ...). Match component-source
pattern — return pretty-printed string, page handles display.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Keywords were printed as strings because list treated :path as kwargs.
Now uses make-keyword to build keyword nodes. Output goes through
highlight for syntax coloring.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Builds (defhandler name :path ... :method ... params body) from
the handler dict fields and pretty-prints it with syntax highlighting.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Both used (env-get name) with one arg which always returned nil.
Now use eval-expr + make-symbol with cek-try fallback, same pattern
as the handler dispatch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Handler bodies contain HTML elements (div, p, span) which need the
render pipeline. eval-expr treats them as function calls. aser
evaluates and serializes HTML to wire format.
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>
env-get requires (env-get env name) with two args but dispatch.sx
was calling it with one arg, always returning nil. Now uses
eval-expr + make-symbol to resolve the handler binding in the
current env, with cek-try fallback to nil.
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>
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>
- sx_load_check: validate all .sx files parse cleanly (108 files)
- sx_env: search defined symbols by pattern/type
- sx_handler_list: list registered defhandler forms
- sx_page_list: list page functions from page-functions.sx (41 pages)
- sx_request: HTTP request to running server, returns status + body
These tools help debug silent load failures, missing definitions,
and handler routing issues.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
api and call-handler now use (error ...) for not-found and method
mismatch, so error boundaries catch them properly instead of
rendering error messages as page content.
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>
Comprehensive interaction tests covering GET handlers (click-to-load,
tabs, polling, search, filter, keyboard, lazy-load), POST handlers
(form-submission, inline-validation, reset), mutation handlers
(delete-row, edit-row, inline-edit, bulk-update, put-patch), and
special handlers (dialogs, oob-swaps, animations, progress-bar, retry,
json-encoding, vals-and-headers).
Tests use trackErrors + waitForSxReady. Will pass once server is
restarted with request primitives and handler dispatch.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Request primitives (now, state-get/set!/clear!, request-form/arg,
into, request-headers-all, etc.) added to test runner environment.
17 new tests covering all primitives with round-trip, default value,
and type assertion checks.
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>
The OCaml parser doesn't handle \xe2\x80\x94 (em-dash). Replaced
with -- throughout.
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>
These were lost during an earlier sx_replace_node edit. Both use
case expressions to route slugs to the correct data tables.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Same pattern as tools — passthrough returning nil fell through to
parent geography page. Now returns empty string so layout renders
with correct nav context.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The tools page function returned nil when no child route matched,
causing a 404. Now returns empty string so the layout renders.
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>
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>
- Add missing ./sx/sxc:/app/sxc:ro volume mount to dev-sx compose —
container was using stale image copy of docs.sx where ~docs/code had
(&key code) instead of (&key src), so highlight output was silently
discarded
- Register HTML tags as special forms in WASM browser kernel so keyword
attrs are preserved during render-to-dom
- Add trace-boot, hydrate-debug, eval-at modes to sx-inspect.js for
debugging boot phases and island hydration
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>
New tests: scope/context, multi-signal, reactive DOM, VM HO forms.
Fix rest primitive to not error-log on nil input (defensive).
1 remaining pre-existing failure: named-let set! scoping in JS
bootstrapper — not related to WASM/hydration changes.
32/32 WASM, 1593/1594 JS full.
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>
Named let creates a loop continuation but set! inside the loop body
does not mutate bindings in the enclosing let scope. Affects both
the WASM kernel and native OCaml CEK evaluator.
6 failing Node tests cover:
- set! counter (simplest case)
- set! counter with named let params
- set! list accumulator via append
- append! + set! counter combo
- set! string concatenation
- nested named let set!
3 baselines pass: plain let set!, functional named let, plain append!
Also adds spec/tests/test-named-let-set.sx (7 assertions, first
fails and aborts — confirms bug exists in spec test suite too).
This is the root cause of empty source code blocks on all example
pages: tokenize-sx uses set! in named let → empty tokens →
highlight returns "(<> )" → empty <code> blocks.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The SX-level VM couldn't find for-each/map/etc. because they're CEK
special forms, not primitives in the JS host. Added vm-resolve-ho-form
which creates wrapper functions that dispatch callbacks through
vm-call-external (handling VmClosure callbacks correctly).
Also fixed vm-call dispatch order: Lambda/Component checked before
generic callable? to avoid calling SX Lambdas as JS functions.
JS full: 1585/1585 (was 1582/3 failed). All 3 pre-existing failures fixed.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Root cause: set! inside named let loop does not persist mutations
to the outer scope in the WASM kernel. tokenize-sx uses this pattern
(set! acc (append acc ...)) inside (let loop () ...) and gets empty
tokens. This makes highlight return "(<> )" for all inputs, causing
empty source code blocks on every reactive island example page.
6 failing tests document the bug at each level:
- set! in named let loop (root cause)
- tokenize-sx (empty tokens)
- highlight (empty output)
- component-source + highlight (end-to-end)
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>
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>
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>
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>
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>
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>