Fix hydration: effect was a no-op primitive, bytecode compiler emitted CALL_PRIM
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>
This commit is contained in:
@@ -234,36 +234,37 @@
|
||||
}
|
||||
|
||||
/**
|
||||
* Try loading a pre-compiled bytecode module.
|
||||
* Tries .sxbc.json first, then .sxbc (SX s-expression format).
|
||||
* Try loading a pre-compiled .sxbc.json bytecode module.
|
||||
* Returns true on success, null on failure (caller falls back to .sx source).
|
||||
*/
|
||||
function loadBytecodeFile(path) {
|
||||
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');
|
||||
var sxbcUrl = _baseUrl + sxbcPath + _sxbcCacheBust;
|
||||
var bcPath = path.replace(/\.sx$/, '.sxbc.json');
|
||||
var url = _baseUrl + bcPath + _sxbcCacheBust;
|
||||
try {
|
||||
var xhr2 = new XMLHttpRequest();
|
||||
xhr2.open("GET", sxbcUrl, false);
|
||||
xhr2.send();
|
||||
if (xhr2.status === 200) {
|
||||
// Store text in global, parse via SX to avoid JS string escaping
|
||||
window.__sxbcText = xhr2.responseText;
|
||||
var result2 = K.eval('(load-sxbc (first (parse (host-global "__sxbcText"))))');
|
||||
delete window.__sxbcText;
|
||||
if (typeof result2 !== 'string' || result2.indexOf('Error') !== 0) {
|
||||
console.log("[sx-platform] ok " + path + " (bytecode-sx)");
|
||||
return true;
|
||||
}
|
||||
console.warn("[sx-platform] bytecode-sx FAIL " + path + ":", result2);
|
||||
}
|
||||
} catch(e) { delete window.__sxbcText; /* fall through to source */ }
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open("GET", url, false);
|
||||
xhr.send();
|
||||
if (xhr.status !== 200) return null;
|
||||
|
||||
return null;
|
||||
var json = JSON.parse(xhr.responseText);
|
||||
if (!json.module || json.magic !== 'SXBC') return null;
|
||||
|
||||
var module = {
|
||||
_type: 'dict',
|
||||
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.warn("[sx-platform] bytecode FAIL " + path + ":", result);
|
||||
return null;
|
||||
}
|
||||
console.log("[sx-platform] ok " + path + " (bytecode)");
|
||||
return true;
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -320,8 +321,6 @@
|
||||
"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",
|
||||
@@ -336,17 +335,15 @@
|
||||
];
|
||||
|
||||
var loaded = 0, bcCount = 0, srcCount = 0;
|
||||
var inBatch = false;
|
||||
if (K.beginModuleLoad) K.beginModuleLoad();
|
||||
for (var i = 0; i < files.length; i++) {
|
||||
if (!inBatch && K.beginModuleLoad) { K.beginModuleLoad(); inBatch = true; }
|
||||
var r = loadBytecodeFile(files[i]);
|
||||
if (r) { bcCount++; continue; }
|
||||
// Bytecode not available — end batch, load source
|
||||
if (inBatch && K.endModuleLoad) { K.endModuleLoad(); inBatch = false; }
|
||||
// Bytecode not available — load source inside the batch (don't break it)
|
||||
r = loadSxFile(files[i]);
|
||||
if (typeof r === "number") { loaded += r; srcCount++; }
|
||||
}
|
||||
if (inBatch && K.endModuleLoad) K.endModuleLoad();
|
||||
if (K.endModuleLoad) K.endModuleLoad();
|
||||
console.log("[sx-platform] Loaded " + files.length + " files (" + bcCount + " bytecode, " + srcCount + " source, " + loaded + " exprs)");
|
||||
return loaded;
|
||||
}
|
||||
@@ -404,58 +401,6 @@
|
||||
"hydrated:", !!islands[j]._sxBoundislandhydrated || !!islands[j]["_sxBound" + "island-hydrated"],
|
||||
"children:", islands[j].children.length);
|
||||
}
|
||||
// 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;
|
||||
fetch(url)
|
||||
.then(function(r) { return r.text(); })
|
||||
.then(function(html) {
|
||||
if (!html) return;
|
||||
var parser = new DOMParser();
|
||||
var doc = parser.parseFromString(html, "text/html");
|
||||
var srcPanel = doc.querySelector("#main-panel");
|
||||
var srcNav = doc.querySelector("#sx-nav");
|
||||
if (srcPanel) {
|
||||
target.outerHTML = srcPanel.outerHTML;
|
||||
}
|
||||
var navTarget = document.querySelector("#sx-nav");
|
||||
if (srcNav && navTarget) {
|
||||
navTarget.outerHTML = srcNav.outerHTML;
|
||||
}
|
||||
})
|
||||
.catch(function(e) { console.warn("[sx] popstate fetch error:", e); });
|
||||
});
|
||||
// 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");
|
||||
// Don't push URL here — execute-request's handle-history does it.
|
||||
// Double-push causes popstate handler to clobber the SX swap.
|
||||
// Store the element reference for SX to pick up
|
||||
window.__sxClickEl = el;
|
||||
try {
|
||||
K.eval('(execute-request (host-global "__sxClickEl") nil nil)');
|
||||
} catch(ex) {
|
||||
console.warn("[sx] click delegation error:", ex);
|
||||
location.href = el.href;
|
||||
}
|
||||
delete window.__sxClickEl;
|
||||
});
|
||||
|
||||
// Signal boot complete
|
||||
document.documentElement.setAttribute("data-sx-ready", "true");
|
||||
document.dispatchEvent(new CustomEvent("sx:boot-done"));
|
||||
console.log("[sx] boot done");
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user