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>
This commit is contained in:
@@ -233,31 +233,9 @@
|
||||
* Returns true on success, null on failure (caller falls back to .sx source).
|
||||
*/
|
||||
function loadBytecodeFile(path) {
|
||||
// Try .sxbc.json (JSON dict format)
|
||||
var jsonPath = path.replace(/\.sx$/, '.sxbc.json');
|
||||
var jsonUrl = _baseUrl + jsonPath + _cacheBust;
|
||||
try {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", jsonUrl, false);
|
||||
xhr.send();
|
||||
if (xhr.status === 200) {
|
||||
var json = JSON.parse(xhr.responseText);
|
||||
if (json.module && json.magic === 'SXBC') {
|
||||
var module = {
|
||||
_type: 'dict',
|
||||
arity: json.module.arity || 0,
|
||||
bytecode: { _type: 'list', items: json.module.bytecode },
|
||||
constants: { _type: 'list', items: json.module.constants.map(deserializeConstant) },
|
||||
};
|
||||
var result = K.loadModule(module);
|
||||
if (typeof result !== 'string' || result.indexOf('Error') !== 0) {
|
||||
console.log("[sx-platform] ok " + path + " (bytecode-json)");
|
||||
return true;
|
||||
}
|
||||
console.warn("[sx-platform] bytecode-json FAIL " + path + ":", result);
|
||||
}
|
||||
}
|
||||
} catch(e) { /* fall through to .sxbc */ }
|
||||
console.log("[sx-platform] loadBytecodeFile:", path, "(sxbc-only, no json)");
|
||||
// .sxbc.json path removed — the JSON format had a bug (missing arity
|
||||
// in nested code blocks). Use .sxbc (SX text) format only.
|
||||
|
||||
// Try .sxbc (SX s-expression format, loaded via load-sxbc primitive)
|
||||
var sxbcPath = path.replace(/\.sx$/, '.sxbc');
|
||||
@@ -336,6 +314,8 @@
|
||||
"sx/adapter-html.sx",
|
||||
"sx/adapter-sx.sx",
|
||||
"sx/adapter-dom.sx",
|
||||
// Client libraries (CSSX etc. — needed by page components)
|
||||
"sx/cssx.sx",
|
||||
// Boot helpers (platform functions in pure SX)
|
||||
"sx/boot-helpers.sx",
|
||||
"sx/hypersx.sx",
|
||||
@@ -418,22 +398,18 @@
|
||||
"hydrated:", !!islands[j]._sxBoundislandhydrated || !!islands[j]["_sxBound" + "island-hydrated"],
|
||||
"children:", islands[j].children.length);
|
||||
}
|
||||
// Register popstate handler for back/forward navigation.
|
||||
// Fetch HTML (not SX) and extract #main-panel content.
|
||||
// Fallback popstate handler for back/forward navigation.
|
||||
// Only fires before SX engine boots — after boot, boot.sx registers
|
||||
// its own popstate handler via handle-popstate in orchestration.sx.
|
||||
window.addEventListener("popstate", function() {
|
||||
if (document.documentElement.hasAttribute("data-sx-ready")) return;
|
||||
var url = location.pathname + location.search;
|
||||
var target = document.querySelector("#main-panel");
|
||||
if (!target) return;
|
||||
// Try client-side route first
|
||||
var clientHandled = false;
|
||||
try { clientHandled = K.eval('(try-client-route "' + url.replace(/"/g, '\\"') + '" "#main-panel")'); } catch(e) {}
|
||||
if (clientHandled) return;
|
||||
// Server fetch — request full HTML (no SX-Request header)
|
||||
fetch(url)
|
||||
.then(function(r) { return r.text(); })
|
||||
.then(function(html) {
|
||||
if (!html) return;
|
||||
// Parse the full HTML and extract #main-panel
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(html, "text/html");
|
||||
var srcPanel = doc.querySelector("#main-panel");
|
||||
@@ -441,26 +417,20 @@
|
||||
if (srcPanel) {
|
||||
target.outerHTML = srcPanel.outerHTML;
|
||||
}
|
||||
// Also update nav if present
|
||||
var navTarget = document.querySelector("#sx-nav");
|
||||
if (srcNav && navTarget) {
|
||||
navTarget.outerHTML = srcNav.outerHTML;
|
||||
}
|
||||
// Re-hydrate
|
||||
var newTarget = document.querySelector("#main-panel");
|
||||
if (newTarget) {
|
||||
try { K.eval("(post-swap (dom-query \"#main-panel\"))"); } catch(e) {}
|
||||
try { K.eval("(sx-hydrate-islands (dom-query \"#main-panel\"))"); } catch(e) {}
|
||||
}
|
||||
})
|
||||
.catch(function(e) { console.warn("[sx] popstate fetch error:", e); });
|
||||
});
|
||||
// Event delegation for sx-get links — bytecoded bind-event doesn't
|
||||
// attach per-element listeners (VM closure issue), so catch clicks
|
||||
// at the document level and route through the SX engine.
|
||||
// Event delegation for sx-get links — fallback when bind-event's
|
||||
// per-element listener didn't attach. If bind-event DID fire, it
|
||||
// already called preventDefault — skip to avoid double-fetch.
|
||||
document.addEventListener("click", function(e) {
|
||||
var el = e.target.closest("a[sx-get]");
|
||||
if (!el) return;
|
||||
if (e.defaultPrevented) return;
|
||||
if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return;
|
||||
e.preventDefault();
|
||||
var url = el.getAttribute("href") || el.getAttribute("sx-get");
|
||||
1
shared/static/wasm/sx/cssx.sx
Symbolic link
1
shared/static/wasm/sx/cssx.sx
Symbolic link
@@ -0,0 +1 @@
|
||||
../../../../shared/sx/templates/cssx.sx
|
||||
3
shared/static/wasm/sx/cssx.sxbc
Normal file
3
shared/static/wasm/sx/cssx.sxbc
Normal file
File diff suppressed because one or more lines are too long
@@ -256,6 +256,25 @@
|
||||
"sx:afterSwap"
|
||||
(dict "target" target-el "swap" swap-style)))))))
|
||||
|
||||
(define
|
||||
flush-cssx!
|
||||
:effects (mutation io)
|
||||
(fn
|
||||
()
|
||||
(let
|
||||
((rules (collected "cssx")))
|
||||
(clear-collected! "cssx")
|
||||
(when
|
||||
(not (empty? rules))
|
||||
(let
|
||||
((style (dom-query "#sx-css")))
|
||||
(when
|
||||
style
|
||||
(dom-set-prop
|
||||
style
|
||||
"textContent"
|
||||
(str (dom-get-prop style "textContent") (join "" rules)))))))))
|
||||
|
||||
(define
|
||||
handle-sx-response
|
||||
:effects (mutation io)
|
||||
@@ -508,7 +527,8 @@
|
||||
(sx-hydrate root)
|
||||
(sx-hydrate-islands root)
|
||||
(run-post-render-hooks)
|
||||
(process-elements root)))
|
||||
(process-elements root)
|
||||
(flush-cssx!)))
|
||||
|
||||
(define
|
||||
process-settle-hooks
|
||||
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-15eb71d8.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-15eb71d8.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-5c519624.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-5c519624.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-eb076217.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-eb076217.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-f4a8777b.wasm
Normal file
BIN
shared/static/wasm/sx_browser.bc.wasm.assets/sx-f4a8777b.wasm
Normal file
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -1792,7 +1792,7 @@
|
||||
blake2_js_for_wasm_create: blake2_js_for_wasm_create};
|
||||
}
|
||||
(globalThis))
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-15eb71d8",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-0c758f5b",[2,3,5]],["std_exit-10fb8830",[2]],["start-80fdb768",0]],"generated":(b=>{var
|
||||
({"link":[["runtime-0db9b496",0],["prelude-d7e4b000",0],["stdlib-23ce0836",[]],["sx-eb076217",[2]],["jsoo_runtime-f96b44a8",[2]],["js_of_ocaml-651f6707",[2,4]],["dune__exe__Sx_browser-36a151d2",[2,3,5]],["std_exit-10fb8830",[2]],["start-80fdb768",0]],"generated":(b=>{var
|
||||
c=b,a=b?.module?.export||b;return{"env":{"caml_ba_kind_of_typed_array":()=>{throw new
|
||||
Error("caml_ba_kind_of_typed_array not implemented")},"caml_exn_with_js_backtrace":()=>{throw new
|
||||
Error("caml_exn_with_js_backtrace not implemented")},"caml_int64_create_lo_mi_hi":()=>{throw new
|
||||
|
||||
Reference in New Issue
Block a user