Commit Graph

19 Commits

Author SHA1 Message Date
2f3e727a6f Transparent lazy module loading — code loads like data
When the VM or CEK hits an undefined symbol, it checks a symbol→library
index (built from manifest exports at boot), loads the library that
exports it, and returns the value. Execution continues as if the module
was always loaded. No import statements, no load-library! calls, no
Suspense boundaries — just call the function.

This is the same mechanism as IO suspension for data fetching. The
programmer doesn't distinguish between calling a local function and
calling one that needs its module fetched first. The runtime treats
code as just another resource.

Implementation:
- _symbol_resolve_hook in sx_types.ml — called by env_get_id (CEK path)
  and vm_global_get (VM path) when a symbol isn't found
- Symbol→library index built from manifest exports in sx-platform.js
- __resolve-symbol native calls __sxLoadLibrary, module loads, symbol
  appears in globals, execution resumes
- compile-modules.js extracts export lists into module-manifest.json
- Playground page demonstrates: (freeze-scope) triggers freeze.sxbc
  download transparently on first use

2650/2650 tests pass.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 22:23:45 +00:00
aee4770a6a Lazy module loading: compiler loads on demand, playground page
- load-library! native: islands can declare module dependencies at
  hydration time, triggering on-demand .sxbc loading
- JIT compiler lazy-load: compiler.sxbc loads via setTimeout after boot,
  eliminating "JIT: compiler not loaded" errors
- _import_hook on sx_types: infrastructure for hosts to resolve import
  suspensions inside eval_expr (server wiring deferred to Step 8)
- Playground page (/sx/(tools.(playground))): REPL island that lazy-loads
  the compiler module when navigated to — demonstrates the full
  lazy loading pipeline

Known remaining issues:
- SPA navigation broken for pages using let-match (orchestration.sx,
  router.sx) — bytecode compiler doesn't handle let-match special form
- Server-side "IO suspension in non-IO context" during http_load_files —
  needs cek_run import handling (deferred to Step 8)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 20:34:08 +00:00
2727577702 VM import suspension for browser lazy loading
Bytecode compiler now emits OP_PERFORM for (import ...) and compiles
(define-library ...) bodies. The VM stores the import request in
globals["__io_request"] and stops the run loop — no exceptions needed.
vm-execute-module returns a suspension dict, vm-resume-module continues.

Browser: sx_browser.ml detects suspension dicts from execute_module and
returns JS {suspended, op, request, resume} objects. The sx-platform.js
while loop handles cascading suspensions via handleImportSuspension.

13 modules load via .sxbc bytecode in 226ms (manifest-driven), both
islands hydrate, all handlers wired. 2650/2650 tests pass including
6 new vm-import-suspension tests.

Also: consolidated sx-platform-2.js → sx-platform.js, fixed
vm-execute-module missing code-from-value call, fixed bootstrap.py
protocol registry transpiler issues.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 17:11:12 +00:00
e46cdf3d4d Wire transpiled VM as active execute_module — 2644 tests pass
The transpiled VM (sx_vm_ref.ml, from lib/vm.sx) is now the ACTIVE
bytecode execution engine. sx_server.ml and sx_browser.ml call
Sx_vm_ref.execute_module instead of Sx_vm.execute_module.

Results:
- OCaml tests: 2644 passed, 0 failed
- WASM tests: 32 passed, 0 failed
- Browser: zero errors, zero warnings, islands hydrate
- Server: pages render, JIT compiles, all routes work

The VM logic now lives in ONE place: lib/vm.sx (SX).
OCaml gets it via transpilation (bootstrap_vm.py).
JS/browser gets it via bytecode compilation (compile-modules.js).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 13:34:11 +00:00
c578dedbcc Revert render-dom-lake child re-rendering — broke stepper preview
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>
2026-04-02 15:41:31 +00:00
d40a9c6796 sx-tools: WASM kernel updates, TW/CSSX rework, content refresh, new debugging tools
Build tooling: updated OCaml bootstrapper, compile-modules, bundle.sh, sx-build-all.
WASM browser: rebuilt sx_browser.bc.js/wasm, sx-platform-2.js, .sxbc bytecode files.
CSSX/Tailwind: reworked cssx.sx templates and tw-layout, added tw-type support.
Content: refreshed essays, plans, geography, reactive islands, docs, demos, handlers.
New tools: bisect_sxbc.sh, test-spa.js, render-trace.sx, morph playwright spec.
Tests: added test-match.sx, test-examples.sx, updated test-tw.sx and web tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:31:57 +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
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
9ac2e38c24 Store primitives + event-bridge + client? for isomorphic nav
- def-store/use-store/clear-stores: OCaml primitives with global
  mutable registry. Bypasses env scoping issues that prevented SX-level
  stores from persisting across bytecode module boundaries.

- client? primitive: _is_client ref (false on server, true in browser).
  Registered in primitives table for CALL_PRIM compatibility.

- Event-bridge island: rewritten to use document-level addEventListener
  via effect + host-callback, fixing container-ref timing issue.

- Header island: uses def-store for idx/shade signals when client? is
  true, plain signals when false (SSR compatibility).

- web-signals.sx: SX store definitions removed, OCaml primitives replace.

Isomorphic nav still fixme — client? works from K.eval but the JIT
"Not callable: nil" bug prevents proper primitive resolution during
render-to-dom hydration. Needs JIT investigation.

100 passed, 1 skipped, 0 failed.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-28 11:54:29 +00:00
e0070041d6 Add .sxbc s-expression bytecode format
Bytecode modules are now serialized as s-expressions (.sxbc) in addition
to JSON (.sxbc.json). The .sxbc format is the canonical representation —
content-addressable, parseable by the SX parser, and suitable for CID
referencing. Annotation layers (source maps, variable names, tests, docs)
can reference the bytecode CID without polluting the bytecode itself.

Format: (sxbc version hash (code :arity N :bytecode (...) :constants (...)))

The browser loader tries .sxbc first (via load-sxbc kernel primitive),
falls back to .sxbc.json. Caddy needs .sxbc MIME type to serve the new
format (currently 404s, JSON fallback works).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 14:16:22 +00:00
c923a34fa8 Fix WASM browser click handlers: 8 bugs, 50 new VM tests
The sx-get links were doing full page refreshes because click handlers
never attached. Root causes: VM frame management bug, missing primitives,
CEK/VM type dispatch mismatch, and silent error swallowing.

Fixes:
- VM frame exhaustion: frames <- [] now properly pops to rest_frames
- length primitive: add alias for len in OCaml primitives
- call_sx_fn: use sx_call directly instead of eval_expr (CEK checks
  for type "lambda" but VmClosure reports "function")
- Boot error surfacing: Sx.init() now has try/catch + failure summary
- Callback error surfacing: catch-all handler for non-Eval_error exceptions
- Silent JIT failures: log before CEK fallback instead of swallowing
- vm→env sync: loadModule now calls sync_vm_to_env()
- sx_build_bytecode MCP tool added for bytecode compilation

Tests: 50 new tests across test-vm.sx and test-vm-primitives.sx covering
nested VM calls, frame integrity, CEK bridge, primitive availability,
cross-module symbol resolution, and callback dispatch.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-27 00:37:21 +00:00
00de248ee9 WIP: bytecode module pre-compilation and loading infrastructure
- compile-modules.js: Node.js build tool, all 23 .sx files compile to .sxbc.json
- api_load_module with shared globals (beginModuleLoad/endModuleLoad batch API)
- api_compile_module for runtime compilation
- sx-platform.js: bytecode-first loader with source fallback, JSON deserializer
- Deferred JIT enable (setTimeout after boot)

Known issues:
- WASM browser: loadModule loads but functions not accessible (env writeback
  issue with interned keys)
- WASM browser: compileModule fails ("Not callable: nil" — compile-module
  function from bytecode not working correctly in WASM context)
- Node.js js_of_ocaml: full roundtrip works (compile → load → call)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:54:55 +00:00
226c01bbf6 Browser JIT: compile SX lambdas to bytecode VM in WASM kernel
- Wire up jit_call_hook in sx_browser.ml (same pattern as server)
- Deferred JIT: _jit_enabled flag, enabled after boot-init completes
  (prevents "Undefined symbol" errors from compiling during .sx loading)
- enable-jit! native function called by sx-platform.js after boot
- sx-platform.js: async WASM kernel polling + JIT enable after init
- Error logging for JIT compile failures and runtime fallbacks

Performance: 858ms → 431ms (WASM CEK) → 101ms (WASM JIT)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 17:04:39 +00:00
10576f86d1 WASM browser build: interned env keys, async kernel boot, bundled .sx platform
- Symbol interning in sx_types.ml: env lookups use int keys (intern/unintern)
  to avoid repeated string hashing in scope chain walks
- sx-platform.js: poll for SxKernel availability (WASM init is async)
- shell.sx: load sx_browser.bc.wasm.js when SX_USE_WASM=1
- bundle.sh: fix .sx file paths (web-signals.sx rename)
- browser/dune: target byte+js+wasm modes
- Bundle 23 .sx platform files for browser (dom, signals, router, boot, etc.)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-26 16:37:42 +00:00
0c8c0b6426 Cache-bust .sx files, optimize stepper back/hydration
Platform:
- sx-platform.js: extract ?v= query from script tag URL, append to
  all .sx file XHR requests. Prevents stale cached .sx files.

Stepper performance:
- do-back: use rebuild-preview (pure SX→DOM render) instead of
  replaying every do-step from 0. O(1) instead of O(n).
- Hydration effect: same rebuild-preview instead of step replay.
- Cookie save moved from do-step to button on-click handlers only.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 14:54:43 +00:00