Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
6.2 KiB
Host blog → SPA via the SX-htmx engine (WASM OCaml kernel)
Turn the blog (lib/host/blog.sx) into a single-page app using the in-repo SX
hypermedia engine (web/engine.sx — "our htmx"): boot the WASM OCaml kernel
(the same evaluator the server runs) in the browser, and sx-boost every
link/form into a fragment swap into #content — no full reloads, history kept,
graceful degradation to plain server-rendered pages with no JS.
Status
DONE — server side (verified, all green):
lib/host/static.sx—GET /static/**serves files undershared/staticvia thefile-readprimitive (content-type by extension, path-traversal guarded, 404 on missing). Mounted in serve.sh + the route list. Tested: kernel JS 200 + correct ctype + exact bytes;.wasmbinary-exact withapplication/wasm; traversal/missing → 404.lib/host/blog.sxhost/blog--pageis now the SPA shell: full page = WASM boot scripts (/static/wasm/sx_browser.bc.wasm.js+sx-platform.js) + asx-boost="#content"wrapper div +#content. On theSX-Request: trueheader (a boosted nav) it returns ONLY the inner content (fragment) so the engine swaps it into#content. All 13 page handlers threadreq. Tested: full page carries scripts+boost+#content;SX-Requestreturns the bare fragment.docker-compose.dev-sx-host.ymlmounts./shared/staticso the live container can serve the kernel.lib/host/playwright/spa-check.spec.js+run-spa-check.sh— browser check (boot, boost, fragment swap, back button).
DONE — client side, partial:
- The WASM kernel BOOTS in a headless browser:
globalThis.SxKernelis an object,<html data-sx-ready="true">is set, the web-stack modules load. - Fixed: this worktree's
shared/static/wasm/sx_browser.bc.wasm.assets/was missing 5 of 11.wasmunits (sx-,unix-,re-,start-,dune__exe__Sx_browser-); copied the complete set from the main worktree.
BLOCKER — boost does not activate (boosted links: 0 / N):
- The bundled
.sxbcbytecode throwsVM: unknown opcode 0against this worktree'ssx_browser.bc.wasm.jskernel, so sx-platform.js falls back to.sxsource for every web-stack module. Source fallback works for all modules EXCEPTboot.sx, which then fails withExpected list, got string— so the boot sequence that wiresprocess-elements → process-boosteddoesn't complete and no link gets_sxBoundboost. - Root cause: the
.sxbcinshared/static/wasm/sx/are out of sync with the WASM kernel (sx.rose-ash.com avoids this because its Docker image ships a consistent bundle and it navigates via client-router page-routes, not boost).
UPDATE 2026-06-29 — kernel BOOT crash fixed (crypto WASM-safe)
The boot crash was NOT the build pipeline — it was the kernel's crypto stack
assuming 63-bit native int. On the web targets (js_of_ocaml 32-bit, wasm_of_ocaml
31-bit) sha2/cbor/cid/ed25519 truncated, and ed25519 precomputes sqrtm1 +
base_point AT MODULE INIT via a base-2^26 bignum whose 52-bit products overflow
→ Char.chr(-4) crash on load. Fixed in fce9e0c6 (sx_sha2 Int32 rounds +
Int64 length, sx_cbor Int64 width-select, sx_cid bounded base32, sx_ed25519 Int64
bignum mul/div_small). Verified: NIST/CID vectors match native↔js↔wasm; native
conformance 271/271; the freshly-built browser kernel now BOOTS (SxKernel
live, data-sx-ready=true, crypto-sha256 correct on js + wasm).
REMAINING for boost (separate layer — web-stack loading, NOT crypto):
.sxbcstill failVM: unknown opcode 0against the SX-levelvm.sxinterpreter, even freshly recompiled — a bytecode-format mismatch (likely the sx-vm-extensions merge changed sx_vm.ml's opcodes but the servedvm.sxwasn't regenerated). So the web stack falls back to.sxsource.boot.sxSOURCE then failsExpected list, got string(fails on the old main-worktree kernel too), soprocess-boostednever runs → boost 0/N. Next: either regeneratevm.sxto match the compiler so.sxbcload, or fix theboot.sxsource-eval failure so source fallback completes.
Rebuild attempt (2026-06-28) — FAILED, reverted (superseded by the fix above)
Tried it: dune build browser/sx_browser.bc.wasm.js succeeded (with many
integer-overflow warnings — "generated code might be incorrect"), and
node hosts/ocaml/browser/compile-modules.js shared/static/wasm recompiled all
35 .sxbc cleanly. But the freshly-built kernel crashes on init in the
browser: Fatal error: exception Invalid_argument("Char.chr") — so SxKernel
never initialises (worse than before). The integer-overflow truncation during
wasm codegen is the likely culprit (a SHA/char constant). Reverted
shared/static/wasm/ to the main-worktree bundle (which boots cleanly —
verified SxKernel + data-sx-ready). So a naive in-worktree rebuild is NOT the
fix; the wasm build itself needs investigating (wasm_of_ocaml version? the merged
sx-vm-extensions/resolver changes interacting with codegen?).
Next step — rebuild a consistent WASM bundle
scripts/sx-build-all.sh does: build the browser wasm target → sync web .sx
into hosts/ocaml/browser/dist/sx/ → node hosts/ocaml/browser/compile-modules.js
(recompiles .sxbc via the native sx_server binary) → copy into
shared/static/wasm/. The browser wasm target is NOT built in this worktree
(hosts/ocaml/_build/default/browser/ is empty), so this needs the
wasm_of_ocaml toolchain set up first. Once the .sxbc match the kernel, the
bytecode path loads (no source fallback), boot.sx runs, and process-boosted
binds the links — then the SPA Playwright check should pass.
Alternatively: build the browser kernel in the main worktree (which has the
pipeline) and copy a consistent sx_browser.bc.wasm.js + assets + .sxbc set
into this worktree's shared/static/wasm/.
Deploy note
The live container is NOT redeployed with the SPA shell yet — it keeps running the
pre-SPA blog.sx in memory (the native host doesn't hot-reload). Don't recreate
the container until the bundle is consistent and the SPA Playwright check is green,
to avoid shipping a kernel that boots but doesn't boost. (Even if it is recreated,
pages degrade gracefully: links still do normal full-page nav.)