Replace invoke with cek-call in reactive island primitives

All signal operations (computed, effect, batch, etc.) now dispatch
function calls through cek-call, which routes SX lambdas via cek-run
and native callables via apply. This replaces the invoke shim.

Key changes:
- cek.sx: add cek-call (defined before reactive-shift-deref), replace
  invoke in subscriber disposal and ReactiveResetFrame handler
- signals.sx: replace all 11 invoke calls with cek-call
- js.sx: fix octal escape in js-quote-string (char-from-code 0)
- platform_js.py: fix JS append to match Python (list concat semantics),
  add Continuation type guard in PLATFORM_CEK_JS, add scheduleIdle
  safety check, module ordering (cek before signals)
- platform_py.py: fix ident-char regex (remove [ ] from valid chars),
  module ordering (cek before signals)
- run_js_sx.py: emit PLATFORM_CEK_JS before transpiled spec files
- page-functions.sx: add cek and provide page functions for SX URLs

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 10:11:48 +00:00
parent 30d9d4aa4c
commit 455e48df07
20 changed files with 911 additions and 600 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-14T01:23:35Z";
var SX_VERSION = "2026-03-14T10:06:04Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -414,7 +414,7 @@
PRIMITIVES["rest"] = function(c) { if (c && typeof c.slice !== "function") { console.error("[sx-debug] rest called on non-sliceable:", typeof c, c, new Error().stack); return []; } return c ? c.slice(1) : []; };
PRIMITIVES["nth"] = function(c, n) { return c && n >= 0 && n < c.length ? c[n] : NIL; };
PRIMITIVES["cons"] = function(x, c) { return [x].concat(c || []); };
PRIMITIVES["append"] = function(c, x) { return (c || []).concat([x]); };
PRIMITIVES["append"] = function(c, x) { return (c || []).concat(Array.isArray(x) ? x : [x]); };
PRIMITIVES["append!"] = function(arr, x) { arr.push(x); return arr; };
PRIMITIVES["chunk-every"] = function(c, n) {
var r = []; for (var i = 0; i < c.length; i += n) r.push(c.slice(i, i + n)); return r;
@@ -744,10 +744,10 @@
// =========================================================================
// Character classification derived from the grammar:
// ident-start → [a-zA-Z_~*+\-><=/!?&]
// ident-char → ident-start + [0-9.:\/\[\]#,]
// ident-char → ident-start + [0-9.:\/\#,]
var _identStartRe = /[a-zA-Z_~*+\-><=/!?&]/;
var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/\[\]#,]/;
var _identCharRe = /[a-zA-Z0-9_~*+\-><=/!?.:&/#,]/;
function isIdentStart(ch) { return _identStartRe.test(ch); }
function isIdentChar(ch) { return _identCharRe.test(ch); }
@@ -759,6 +759,35 @@
var charFromCode = PRIMITIVES["char-from-code"];
// =========================================================================
// Platform: CEK module — explicit CEK machine
// =========================================================================
// Continuation type (needed by CEK even without the tree-walk shift/reset extension)
if (typeof Continuation === "undefined") {
function Continuation(fn) { this.fn = fn; }
Continuation.prototype._continuation = true;
Continuation.prototype.call = function(value) { return this.fn(value !== undefined ? value : NIL); };
PRIMITIVES["continuation?"] = function(x) { return x != null && x._continuation === true; };
}
// Standalone aliases for primitives used by cek.sx / frames.sx
var inc = PRIMITIVES["inc"];
var dec = PRIMITIVES["dec"];
var zip_pairs = PRIMITIVES["zip-pairs"];
var continuation_p = PRIMITIVES["continuation?"];
function makeCekContinuation(captured, restKont) {
var c = new Continuation(function(v) { return v !== undefined ? v : NIL; });
c._cek_data = {"captured": captured, "rest-kont": restKont};
return c;
}
function continuationData(c) {
return (c && c._cek_data) ? c._cek_data : {};
}
// === Transpiled from eval ===
// trampoline
@@ -1473,7 +1502,8 @@ if (isSxTruthy((pos >= lenSrc))) { return error("Unexpected end of input"); } el
if (isSxTruthy((ch == "("))) { pos = (pos + 1);
return readList(")"); } else if (isSxTruthy((ch == "["))) { pos = (pos + 1);
return readList("]"); } else if (isSxTruthy((ch == "{"))) { pos = (pos + 1);
return readMap(); } else if (isSxTruthy((ch == "\""))) { return readString(); } else if (isSxTruthy((ch == ":"))) { return readKeyword(); } else if (isSxTruthy((ch == "`"))) { pos = (pos + 1);
return readMap(); } else if (isSxTruthy((ch == "\""))) { return readString(); } else if (isSxTruthy((ch == ":"))) { return readKeyword(); } else if (isSxTruthy((ch == "'"))) { pos = (pos + 1);
return [makeSymbol("quote"), readExpr()]; } else if (isSxTruthy((ch == "`"))) { pos = (pos + 1);
return [makeSymbol("quasiquote"), readExpr()]; } else if (isSxTruthy((ch == ","))) { pos = (pos + 1);
if (isSxTruthy((isSxTruthy((pos < lenSrc)) && (nth(source, pos) == "@")))) { pos = (pos + 1);
return [makeSymbol("splice-unquote"), readExpr()]; } else { return [makeSymbol("unquote"), readExpr()]; } } else if (isSxTruthy((ch == "#"))) { pos = (pos + 1);
@@ -4385,224 +4415,6 @@ return scan(kont, []); };
})(); };
// === Transpiled from signals (reactive signal runtime) ===
// make-signal
var makeSignal = function(value) { return {["__signal"]: true, ["value"]: value, ["subscribers"]: [], ["deps"]: []}; };
// signal?
var isSignal = function(x) { return (isSxTruthy(isDict(x)) && dictHas(x, "__signal")); };
// signal-value
var signalValue = function(s) { return get(s, "value"); };
// signal-set-value!
var signalSetValue = function(s, v) { return dictSet(s, "value", v); };
// signal-subscribers
var signalSubscribers = function(s) { return get(s, "subscribers"); };
// signal-add-sub!
var signalAddSub = function(s, f) { return (isSxTruthy(!isSxTruthy(contains(get(s, "subscribers"), f))) ? append_b(get(s, "subscribers"), f) : NIL); };
// signal-remove-sub!
var signalRemoveSub = function(s, f) { return dictSet(s, "subscribers", filter(function(sub) { return !isSxTruthy(isIdentical(sub, f)); }, get(s, "subscribers"))); };
// signal-deps
var signalDeps = function(s) { return get(s, "deps"); };
// signal-set-deps!
var signalSetDeps = function(s, deps) { return dictSet(s, "deps", deps); };
// signal
var signal = function(initialValue) { return makeSignal(initialValue); };
// deref
var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() {
var ctx = sxContext("sx-reactive", NIL);
if (isSxTruthy(ctx)) {
(function() {
var depList = get(ctx, "deps");
var notifyFn = get(ctx, "notify");
return (isSxTruthy(!isSxTruthy(contains(depList, s))) ? (append_b(depList, s), signalAddSub(s, notifyFn)) : NIL);
})();
}
return signalValue(s);
})()); };
// reset!
var reset_b = function(s, value) { return (isSxTruthy(isSignal(s)) ? (function() {
var old = signalValue(s);
return (isSxTruthy(!isSxTruthy(isIdentical(old, value))) ? (signalSetValue(s, value), notifySubscribers(s)) : NIL);
})() : NIL); };
// swap!
var swap_b = function(s, f) { var args = Array.prototype.slice.call(arguments, 2); return (isSxTruthy(isSignal(s)) ? (function() {
var old = signalValue(s);
var newVal = apply(f, cons(old, args));
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? (signalSetValue(s, newVal), notifySubscribers(s)) : NIL);
})() : NIL); };
// computed
var computed = function(computeFn) { return (function() {
var s = makeSignal(NIL);
var deps = [];
var computeCtx = NIL;
return (function() {
var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } }
signalSetDeps(s, []);
return (function() {
var ctx = {["deps"]: [], ["notify"]: recompute};
scopePush("sx-reactive", ctx);
return (function() {
var newVal = invoke(computeFn);
scopePop("sx-reactive");
signalSetDeps(s, get(ctx, "deps"));
return (function() {
var old = signalValue(s);
signalSetValue(s, newVal);
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? notifySubscribers(s) : NIL);
})();
})();
})(); };
recompute();
registerInScope(function() { return disposeComputed(s); });
return s;
})();
})(); };
// effect
var effect = function(effectFn) { return (function() {
var deps = [];
var disposed = false;
var cleanupFn = NIL;
return (function() {
var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? invoke(cleanupFn) : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (function() {
var ctx = {["deps"]: [], ["notify"]: runEffect};
scopePush("sx-reactive", ctx);
return (function() {
var result = invoke(effectFn);
scopePop("sx-reactive");
deps = get(ctx, "deps");
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
})();
})()) : NIL); };
runEffect();
return (function() {
var disposeFn = function() { disposed = true;
if (isSxTruthy(cleanupFn)) {
invoke(cleanupFn);
}
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
return (deps = []); };
registerInScope(disposeFn);
return disposeFn;
})();
})();
})(); };
// *batch-depth*
var _batchDepth = 0;
// *batch-queue*
var _batchQueue = [];
// batch
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
invoke(thunk);
_batchDepth = (_batchDepth - 1);
return (isSxTruthy((_batchDepth == 0)) ? (function() {
var queue = _batchQueue;
_batchQueue = [];
return (function() {
var seen = [];
var pending = [];
{ var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) {
seen.push(sub);
pending.push(sub);
} } } } }
return forEach(function(sub) { return sub(); }, pending);
})();
})() : NIL); };
// notify-subscribers
var notifySubscribers = function(s) { return (isSxTruthy((_batchDepth > 0)) ? (isSxTruthy(!isSxTruthy(contains(_batchQueue, s))) ? append_b(_batchQueue, s) : NIL) : flushSubscribers(s)); };
// flush-subscribers
var flushSubscribers = function(s) { return forEach(function(sub) { return sub(); }, signalSubscribers(s)); };
// dispose-computed
var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); };
// with-island-scope
var withIslandScope = function(scopeFn, bodyFn) { scopePush("sx-island-scope", scopeFn);
return (function() {
var result = bodyFn();
scopePop("sx-island-scope");
return result;
})(); };
// register-in-scope
var registerInScope = function(disposable) { return (function() {
var collector = sxContext("sx-island-scope", NIL);
return (isSxTruthy(collector) ? invoke(collector, disposable) : NIL);
})(); };
// with-marsh-scope
var withMarshScope = function(marshEl, bodyFn) { return (function() {
var disposers = [];
withIslandScope(function(d) { return append_b(disposers, d); }, bodyFn);
return domSetData(marshEl, "sx-marsh-disposers", disposers);
})(); };
// dispose-marsh-scope
var disposeMarshScope = function(marshEl) { return (function() {
var disposers = domGetData(marshEl, "sx-marsh-disposers");
return (isSxTruthy(disposers) ? (forEach(function(d) { return invoke(d); }, disposers), domSetData(marshEl, "sx-marsh-disposers", NIL)) : NIL);
})(); };
// *store-registry*
var _storeRegistry = {};
// def-store
var defStore = function(name, initFn) { return (function() {
var registry = _storeRegistry;
if (isSxTruthy(!isSxTruthy(dictHas(registry, name)))) {
_storeRegistry = assoc(registry, name, invoke(initFn));
}
return get(_storeRegistry, name);
})(); };
// use-store
var useStore = function(name) { return (isSxTruthy(dictHas(_storeRegistry, name)) ? get(_storeRegistry, name) : error((String("Store not found: ") + String(name) + String(". Call (def-store ...) before (use-store ...).")))); };
// clear-stores
var clearStores = function() { return (_storeRegistry = {}); };
// emit-event
var emitEvent = function(el, eventName, detail) { return domDispatch(el, eventName, detail); };
// on-event
var onEvent = function(el, eventName, handler) { return domListen(el, eventName, handler); };
// bridge-event
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
var remove = domListen(el, eventName, function(e) { return (function() {
var detail = eventDetail(e);
var newVal = (isSxTruthy(transformFn) ? invoke(transformFn, detail) : detail);
return reset_b(targetSignal, newVal);
})(); });
return remove;
})(); }); };
// resource
var resource = function(fetchFn) { return (function() {
var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL});
promiseThen(invoke(fetchFn), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); });
return state;
})(); };
// === Transpiled from cek (explicit CEK machine evaluator) ===
// cek-run
@@ -4743,6 +4555,12 @@ return (function() {
// step-sf-deref
var stepSfDeref = function(args, env, kont) { return makeCekState(first(args), env, kontPush(makeDerefFrame(env), kont)); };
// cek-call
var cekCall = function(f, args) { return (function() {
var a = (isSxTruthy(isNil(args)) ? [] : args);
return (isSxTruthy(isNil(f)) ? NIL : (isSxTruthy(isLambda(f)) ? cekRun(continueWithCall(f, a, {}, a, [])) : (isSxTruthy(isCallable(f)) ? apply(f, a) : NIL)));
})(); };
// reactive-shift-deref
var reactiveShiftDeref = function(sig, env, kont) { return (function() {
var scanResult = kontCaptureToReactiveReset(kont);
@@ -4753,7 +4571,7 @@ return (function() {
return (function() {
var subDisposers = [];
return (function() {
var subscriber = function() { { var _c = subDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; invoke(d); } }
var subscriber = function() { { var _c = subDisposers; for (var _i = 0; _i < _c.length; _i++) { var d = _c[_i]; cekCall(d, NIL); } }
subDisposers = [];
return (function() {
var newReset = makeReactiveResetFrame(env, updateFn, false);
@@ -4762,7 +4580,7 @@ return (function() {
})(); };
signalAddSub(sig, subscriber);
registerInScope(function() { signalRemoveSub(sig, subscriber);
return forEach(function(d) { return invoke(d); }, subDisposers); });
return forEach(function(d) { return cekCall(d, NIL); }, subDisposers); });
return (function() {
var initialKont = concat(capturedFrames, [resetFrame], remainingKont);
return makeCekValue(signalValue(sig), env, initialKont);
@@ -4926,7 +4744,7 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
var updateFn = get(frame, "update-fn");
var first_p = get(frame, "first-render");
if (isSxTruthy((isSxTruthy(updateFn) && !isSxTruthy(first_p)))) {
invoke(updateFn, value);
cekCall(updateFn, [value]);
}
return makeCekValue(value, env, restK);
})() : (isSxTruthy((ft == "scope")) ? (function() {
@@ -4980,6 +4798,224 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
var trampolineCek = function(val) { return (isSxTruthy(isThunk(val)) ? evalExprCek(thunkExpr(val), thunkEnv(val)) : val); };
// === Transpiled from signals (reactive signal runtime) ===
// make-signal
var makeSignal = function(value) { return {["__signal"]: true, ["value"]: value, ["subscribers"]: [], ["deps"]: []}; };
// signal?
var isSignal = function(x) { return (isSxTruthy(isDict(x)) && dictHas(x, "__signal")); };
// signal-value
var signalValue = function(s) { return get(s, "value"); };
// signal-set-value!
var signalSetValue = function(s, v) { return dictSet(s, "value", v); };
// signal-subscribers
var signalSubscribers = function(s) { return get(s, "subscribers"); };
// signal-add-sub!
var signalAddSub = function(s, f) { return (isSxTruthy(!isSxTruthy(contains(get(s, "subscribers"), f))) ? append_b(get(s, "subscribers"), f) : NIL); };
// signal-remove-sub!
var signalRemoveSub = function(s, f) { return dictSet(s, "subscribers", filter(function(sub) { return !isSxTruthy(isIdentical(sub, f)); }, get(s, "subscribers"))); };
// signal-deps
var signalDeps = function(s) { return get(s, "deps"); };
// signal-set-deps!
var signalSetDeps = function(s, deps) { return dictSet(s, "deps", deps); };
// signal
var signal = function(initialValue) { return makeSignal(initialValue); };
// deref
var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() {
var ctx = sxContext("sx-reactive", NIL);
if (isSxTruthy(ctx)) {
(function() {
var depList = get(ctx, "deps");
var notifyFn = get(ctx, "notify");
return (isSxTruthy(!isSxTruthy(contains(depList, s))) ? (append_b(depList, s), signalAddSub(s, notifyFn)) : NIL);
})();
}
return signalValue(s);
})()); };
// reset!
var reset_b = function(s, value) { return (isSxTruthy(isSignal(s)) ? (function() {
var old = signalValue(s);
return (isSxTruthy(!isSxTruthy(isIdentical(old, value))) ? (signalSetValue(s, value), notifySubscribers(s)) : NIL);
})() : NIL); };
// swap!
var swap_b = function(s, f) { var args = Array.prototype.slice.call(arguments, 2); return (isSxTruthy(isSignal(s)) ? (function() {
var old = signalValue(s);
var newVal = apply(f, cons(old, args));
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? (signalSetValue(s, newVal), notifySubscribers(s)) : NIL);
})() : NIL); };
// computed
var computed = function(computeFn) { return (function() {
var s = makeSignal(NIL);
var deps = [];
var computeCtx = NIL;
return (function() {
var recompute = function() { { var _c = signalDeps(s); for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, recompute); } }
signalSetDeps(s, []);
return (function() {
var ctx = {["deps"]: [], ["notify"]: recompute};
scopePush("sx-reactive", ctx);
return (function() {
var newVal = cekCall(computeFn, NIL);
scopePop("sx-reactive");
signalSetDeps(s, get(ctx, "deps"));
return (function() {
var old = signalValue(s);
signalSetValue(s, newVal);
return (isSxTruthy(!isSxTruthy(isIdentical(old, newVal))) ? notifySubscribers(s) : NIL);
})();
})();
})(); };
recompute();
registerInScope(function() { return disposeComputed(s); });
return s;
})();
})(); };
// effect
var effect = function(effectFn) { return (function() {
var deps = [];
var disposed = false;
var cleanupFn = NIL;
return (function() {
var runEffect = function() { return (isSxTruthy(!isSxTruthy(disposed)) ? ((isSxTruthy(cleanupFn) ? cekCall(cleanupFn, NIL) : NIL), forEach(function(dep) { return signalRemoveSub(dep, runEffect); }, deps), (deps = []), (function() {
var ctx = {["deps"]: [], ["notify"]: runEffect};
scopePush("sx-reactive", ctx);
return (function() {
var result = cekCall(effectFn, NIL);
scopePop("sx-reactive");
deps = get(ctx, "deps");
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
})();
})()) : NIL); };
runEffect();
return (function() {
var disposeFn = function() { disposed = true;
if (isSxTruthy(cleanupFn)) {
cekCall(cleanupFn, NIL);
}
{ var _c = deps; for (var _i = 0; _i < _c.length; _i++) { var dep = _c[_i]; signalRemoveSub(dep, runEffect); } }
return (deps = []); };
registerInScope(disposeFn);
return disposeFn;
})();
})();
})(); };
// *batch-depth*
var _batchDepth = 0;
// *batch-queue*
var _batchQueue = [];
// batch
var batch = function(thunk) { _batchDepth = (_batchDepth + 1);
cekCall(thunk, NIL);
_batchDepth = (_batchDepth - 1);
return (isSxTruthy((_batchDepth == 0)) ? (function() {
var queue = _batchQueue;
_batchQueue = [];
return (function() {
var seen = [];
var pending = [];
{ var _c = queue; for (var _i = 0; _i < _c.length; _i++) { var s = _c[_i]; { var _c = signalSubscribers(s); for (var _i = 0; _i < _c.length; _i++) { var sub = _c[_i]; if (isSxTruthy(!isSxTruthy(contains(seen, sub)))) {
seen.push(sub);
pending.push(sub);
} } } } }
return forEach(function(sub) { return sub(); }, pending);
})();
})() : NIL); };
// notify-subscribers
var notifySubscribers = function(s) { return (isSxTruthy((_batchDepth > 0)) ? (isSxTruthy(!isSxTruthy(contains(_batchQueue, s))) ? append_b(_batchQueue, s) : NIL) : flushSubscribers(s)); };
// flush-subscribers
var flushSubscribers = function(s) { return forEach(function(sub) { return sub(); }, signalSubscribers(s)); };
// dispose-computed
var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); };
// with-island-scope
var withIslandScope = function(scopeFn, bodyFn) { scopePush("sx-island-scope", scopeFn);
return (function() {
var result = bodyFn();
scopePop("sx-island-scope");
return result;
})(); };
// register-in-scope
var registerInScope = function(disposable) { return (function() {
var collector = sxContext("sx-island-scope", NIL);
return (isSxTruthy(collector) ? cekCall(collector, [disposable]) : NIL);
})(); };
// with-marsh-scope
var withMarshScope = function(marshEl, bodyFn) { return (function() {
var disposers = [];
withIslandScope(function(d) { return append_b(disposers, d); }, bodyFn);
return domSetData(marshEl, "sx-marsh-disposers", disposers);
})(); };
// dispose-marsh-scope
var disposeMarshScope = function(marshEl) { return (function() {
var disposers = domGetData(marshEl, "sx-marsh-disposers");
return (isSxTruthy(disposers) ? (forEach(function(d) { return cekCall(d, NIL); }, disposers), domSetData(marshEl, "sx-marsh-disposers", NIL)) : NIL);
})(); };
// *store-registry*
var _storeRegistry = {};
// def-store
var defStore = function(name, initFn) { return (function() {
var registry = _storeRegistry;
if (isSxTruthy(!isSxTruthy(dictHas(registry, name)))) {
_storeRegistry = assoc(registry, name, cekCall(initFn, NIL));
}
return get(_storeRegistry, name);
})(); };
// use-store
var useStore = function(name) { return (isSxTruthy(dictHas(_storeRegistry, name)) ? get(_storeRegistry, name) : error((String("Store not found: ") + String(name) + String(". Call (def-store ...) before (use-store ...).")))); };
// clear-stores
var clearStores = function() { return (_storeRegistry = {}); };
// emit-event
var emitEvent = function(el, eventName, detail) { return domDispatch(el, eventName, detail); };
// on-event
var onEvent = function(el, eventName, handler) { return domListen(el, eventName, handler); };
// bridge-event
var bridgeEvent = function(el, eventName, targetSignal, transformFn) { return effect(function() { return (function() {
var remove = domListen(el, eventName, function(e) { return (function() {
var detail = eventDetail(e);
var newVal = (isSxTruthy(transformFn) ? cekCall(transformFn, [detail]) : detail);
return reset_b(targetSignal, newVal);
})(); });
return remove;
})(); }); };
// resource
var resource = function(fetchFn) { return (function() {
var state = signal({["loading"]: true, ["data"]: NIL, ["error"]: NIL});
promiseThen(cekCall(fetchFn, NIL), function(data) { return reset_b(state, {["loading"]: false, ["data"]: data, ["error"]: NIL}); }, function(err) { return reset_b(state, {["loading"]: false, ["data"]: NIL, ["error"]: err}); });
return state;
})(); };
// =========================================================================
// Platform interface — DOM adapter (browser-only)
// =========================================================================
@@ -5787,6 +5823,10 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
}
function scheduleIdle(fn) {
var cb = _wrapSxFn(fn);
if (typeof cb !== "function") {
console.error("[sx-ref] scheduleIdle: callback not callable, fn type:", typeof fn, "fn:", fn, "_lambda:", fn && fn._lambda);
return;
}
if (typeof requestIdleCallback !== "undefined") requestIdleCallback(cb);
else setTimeout(cb, 0);
}
@@ -5876,8 +5916,12 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
e.preventDefault();
// Re-read href from element at click time (not closed-over value)
var liveHref = el.getAttribute("href") || _href;
console.log("[sx-debug] bindBoostLink click:", liveHref, "el:", el.tagName, el.textContent.slice(0,30));
executeRequest(el, { method: "GET", url: liveHref }).then(function() {
console.log("[sx-debug] boost fetch OK, pushState:", liveHref);
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
}).catch(function(err) {
console.error("[sx-debug] boost fetch ERROR:", err);
});
});
}
@@ -5902,21 +5946,25 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
// Re-read href from element at click time (not closed-over value)
var liveHref = link.getAttribute("href") || _href;
var pathname = urlPathname(liveHref);
console.log("[sx-debug] bindClientRouteClick:", pathname, "el:", link.tagName, link.textContent.slice(0,30));
// Find target selector: sx-boost ancestor, explicit sx-target, or #main-panel
var boostEl = link.closest("[sx-boost]");
var targetSel = boostEl ? boostEl.getAttribute("sx-boost") : null;
if (!targetSel || targetSel === "true") {
targetSel = link.getAttribute("sx-target") || "#main-panel";
}
console.log("[sx-debug] targetSel:", targetSel, "trying client route...");
if (tryClientRoute(pathname, targetSel)) {
console.log("[sx-debug] client route SUCCESS, pushState:", liveHref);
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
if (typeof window !== "undefined") window.scrollTo(0, 0);
} else {
logInfo("sx:route server " + pathname);
console.log("[sx-debug] client route FAILED, server fetch:", liveHref);
executeRequest(link, { method: "GET", url: liveHref }).then(function() {
console.log("[sx-debug] server fetch OK, pushState:", liveHref);
try { history.pushState({ sxUrl: liveHref, scrollY: window.scrollY }, "", liveHref); } catch (err) {}
}).catch(function(err) {
logWarn("sx:route server fetch error: " + (err && err.message ? err.message : err));
console.error("[sx-debug] server fetch ERROR:", err);
});
}
});
@@ -6290,27 +6338,6 @@ return forEach(function(d) { return invoke(d); }, subDisposers); });
// =========================================================================
// Platform: CEK module — explicit CEK machine
// =========================================================================
// Standalone aliases for primitives used by cek.sx / frames.sx
var inc = PRIMITIVES["inc"];
var dec = PRIMITIVES["dec"];
var zip_pairs = PRIMITIVES["zip-pairs"];
var continuation_p = PRIMITIVES["continuation?"];
function makeCekContinuation(captured, restKont) {
var c = new Continuation(function(v) { return v !== undefined ? v : NIL; });
c._cek_data = {"captured": captured, "rest-kont": restKont};
return c;
}
function continuationData(c) {
return (c && c._cek_data) ? c._cek_data : {};
}
// =========================================================================
// Post-transpilation fixups
// =========================================================================