Collapse reactive islands into scopes: replace TrackingContext and *island-scope* with scope-push!/scope-pop!/context

Reactive tracking (deref/computed/effect dep discovery) and island lifecycle
now use the general scoped effects system instead of parallel infrastructure.
Two scope names: "sx-reactive" for tracking context, "sx-island-scope" for
island disposable collection. Eliminates ~98 net lines: _TrackingContext class,
7 tracking context platform functions (Python + JS), *island-scope* global,
and corresponding RENAME_MAP entries. All 20 signal tests pass (17 original +
3 new scope integration tests), plus CEK/continuation/type tests clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 23:09:09 +00:00
parent 1765216335
commit dcc73a68d5
11 changed files with 330 additions and 268 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-13T19:15:06Z";
var SX_VERSION = "2026-03-13T22:49:51Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -61,13 +61,6 @@
}
SxSignal.prototype._signal = true;
function TrackingCtx(notifyFn) {
this.notifyFn = notifyFn;
this.deps = [];
}
var _trackingContext = null;
function Macro(params, restParam, body, closure, name) {
this.params = params;
this.restParam = restParam;
@@ -243,12 +236,6 @@
function signalRemoveSub(s, fn) { var i = s.subscribers.indexOf(fn); if (i >= 0) s.subscribers.splice(i, 1); }
function signalDeps(s) { return s.deps.slice(); }
function signalSetDeps(s, deps) { s.deps = Array.isArray(deps) ? deps.slice() : []; }
function setTrackingContext(ctx) { _trackingContext = ctx; }
function getTrackingContext() { return _trackingContext || NIL; }
function makeTrackingContext(notifyFn) { return new TrackingCtx(notifyFn); }
function trackingContextDeps(ctx) { return ctx ? ctx.deps : []; }
function trackingContextAddDep(ctx, s) { if (ctx && ctx.deps.indexOf(s) < 0) ctx.deps.push(s); }
function trackingContextNotifyFn(ctx) { return ctx ? ctx.notifyFn : NIL; }
// invoke — call any callable (native fn or SX lambda) with args.
// Transpiled code emits direct calls f(args) which fail on SX lambdas
@@ -1208,14 +1195,7 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai
var before = trampoline(evalExpr(first(args), env));
var body = trampoline(evalExpr(nth(args, 1), env));
var after = trampoline(evalExpr(nth(args, 2), env));
callThunk(before, env);
pushWind(before, after);
return (function() {
var result = callThunk(body, env);
popWind();
callThunk(after, env);
return result;
})();
return dynamicWindCall(before, body, after, env);
})(); };
// sf-scope
@@ -1936,7 +1916,7 @@ return result; }, args);
// render-to-dom
var renderToDom = function(expr, env, ns) { setRenderActiveB(true);
return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(expr)), expr); if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(_islandScope) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); };
return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragment(); if (_m == "boolean") return createFragment(); if (_m == "raw-html") return domParseHtml(rawHtmlContent(expr)); if (_m == "string") return createTextNode(expr); if (_m == "number") return createTextNode((String(expr))); if (_m == "symbol") return renderToDom(trampoline(evalExpr(expr, env)), env, ns); if (_m == "keyword") return createTextNode(keywordName(expr)); if (_m == "dom-node") return expr; if (_m == "spread") return (sxEmit("element-attrs", spreadAttrs(expr)), expr); if (_m == "dict") return createFragment(); if (_m == "list") return (isSxTruthy(isEmpty(expr)) ? createFragment() : renderDomList(expr, env, ns)); return (isSxTruthy(isSignal(expr)) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? reactiveText(expr) : createTextNode((String(deref(expr))))) : createTextNode((String(expr)))); })(); };
// render-dom-list
var renderDomList = function(expr, env, ns) { return (function() {
@@ -1947,7 +1927,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy((name == "lake")) ? renderDomLake(args, env, ns) : (isSxTruthy((name == "marsh")) ? renderDomMarsh(args, env, ns) : (isSxTruthy(startsWith(name, "html:")) ? renderDomElement(slice(name, 5), args, env, ns) : (isSxTruthy(isRenderDomForm(name)) ? (isSxTruthy((isSxTruthy(contains(HTML_TAGS, name)) && sxOr((isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword")), ns))) ? renderDomElement(name, args, env, ns) : dispatchRenderForm(name, expr, env, ns)) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToDom(expandMacro(envGet(env, name), args, env), env, ns) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderDomIsland(envGet(env, name), args, env, ns) : (isSxTruthy(startsWith(name, "~")) ? (function() {
var comp = envGet(env, name);
return (isSxTruthy(isComponent(comp)) ? renderDomComponent(comp, args, env, ns) : renderDomUnknownComponent(name));
})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && _islandScope)) ? (function() {
})() : (isSxTruthy((isSxTruthy((indexOf_(name, "-") > 0)) && isSxTruthy((len(args) > 0)) && (typeOf(first(args)) == "keyword"))) ? renderDomElement(name, args, env, ns) : (isSxTruthy(ns) ? renderDomElement(name, args, env, ns) : (isSxTruthy((isSxTruthy((name == "deref")) && sxContext("sx-island-scope", NIL))) ? (function() {
var sigOrVal = trampoline(evalExpr(first(args), env));
return (isSxTruthy(isSignal(sigOrVal)) ? reactiveText(sigOrVal) : createTextNode((String(deref(sigOrVal)))));
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))))));
@@ -1983,14 +1963,14 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
})() : (isSxTruthy((attrName == "key")) ? (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return domSetAttr(el, "key", (String(attrVal)));
})() : (isSxTruthy(_islandScope) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() {
})() : (isSxTruthy(sxContext("sx-island-scope", NIL)) ? reactiveAttr(el, attrName, function() { return trampoline(evalExpr(attrExpr, env)); }) : (function() {
var attrVal = trampoline(evalExpr(attrExpr, env));
return (isSxTruthy(sxOr(isNil(attrVal), (attrVal == false))) ? NIL : (isSxTruthy(contains(BOOLEAN_ATTRS, attrName)) ? (isSxTruthy(attrVal) ? domSetAttr(el, attrName, "") : NIL) : (isSxTruthy((attrVal == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(attrVal))))));
})())))));
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? (function() {
var child = renderToDom(arg, env, newNs);
return (isSxTruthy((isSxTruthy(isSpread(child)) && _islandScope)) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? NIL : domAppend(el, child)));
return (isSxTruthy((isSxTruthy(isSpread(child)) && sxContext("sx-island-scope", NIL))) ? reactiveSpread(el, function() { return renderToDom(arg, env, newNs); }) : (isSxTruthy(isSpread(child)) ? NIL : domAppend(el, child)));
})() : NIL), assoc(state, "i", (get(state, "i") + 1)))));
})(); }, {["i"]: 0, ["skip"]: false}, args);
{ var _c = sxEmitted("element-attrs"); for (var _i = 0; _i < _c.length; _i++) { var spreadDict = _c[_i]; { var _c = keys(spreadDict); for (var _i = 0; _i < _c.length; _i++) { var key = _c[_i]; (function() {
@@ -2066,7 +2046,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); };
// dispatch-render-form
var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(_islandScope) ? (function() {
var dispatchRenderForm = function(name, expr, env, ns) { return (isSxTruthy((name == "if")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
var marker = createComment("r-if");
var currentNodes = [];
var initialResult = NIL;
@@ -2089,7 +2069,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
})() : (function() {
var condVal = trampoline(evalExpr(nth(expr, 1), env));
return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment()));
})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(_islandScope) ? (function() {
})()) : (isSxTruthy((name == "when")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
var marker = createComment("r-when");
var currentNodes = [];
var initialResult = NIL;
@@ -2116,7 +2096,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
var frag = createFragment();
{ var _c = range(2, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } }
return frag;
})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(_islandScope) ? (function() {
})())) : (isSxTruthy((name == "cond")) ? (isSxTruthy(sxContext("sx-island-scope", NIL)) ? (function() {
var marker = createComment("r-cond");
var currentNodes = [];
var initialResult = NIL;
@@ -2162,7 +2142,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
return frag;
})()) : (isSxTruthy(isDefinitionForm(name)) ? (trampoline(evalExpr(expr, env)), createFragment()) : (isSxTruthy((name == "map")) ? (function() {
var collExpr = nth(expr, 2);
return (isSxTruthy((isSxTruthy(_islandScope) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && isSxTruthy((typeOf(first(collExpr)) == "symbol")) && (symbolName(first(collExpr)) == "deref"))) ? (function() {
return (isSxTruthy((isSxTruthy(sxContext("sx-island-scope", NIL)) && isSxTruthy((typeOf(collExpr) == "list")) && isSxTruthy((len(collExpr) > 1)) && isSxTruthy((typeOf(first(collExpr)) == "symbol")) && (symbolName(first(collExpr)) == "deref"))) ? (function() {
var f = trampoline(evalExpr(nth(expr, 1), env));
var sig = trampoline(evalExpr(nth(collExpr, 1), env));
return (isSxTruthy(isSignal(sig)) ? reactiveList(f, sig, env, ns) : (function() {
@@ -2490,15 +2470,13 @@ return (isSxTruthy(testFn()) ? (function() {
domSetAttr(container, "data-sx-boundary", "true");
effect(function() { deref(retryVersion);
domSetProp(container, "innerHTML", "");
return (function() {
var savedScope = _islandScope;
_islandScope = NIL;
return tryCatch(function() { (function() {
scopePush("sx-island-scope", NIL);
return tryCatch(function() { (function() {
var frag = createFragment();
{ var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var child = _c[_i]; domAppend(frag, renderToDom(child, env, ns)); } }
return domAppend(container, frag);
})();
return (_islandScope = savedScope); }, function(err) { _islandScope = savedScope;
return scopePop("sx-island-scope"); }, function(err) { scopePop("sx-island-scope");
return (function() {
var fallbackFn = trampoline(evalExpr(fallbackExpr, env));
var retryFn = function() { return swap_b(retryVersion, function(n) { return (n + 1); }); };
@@ -2506,8 +2484,7 @@ return (function() {
var fallbackDom = (isSxTruthy(isLambda(fallbackFn)) ? renderLambdaDom(fallbackFn, [err, retryFn], env, ns) : renderToDom(apply(fallbackFn, [err, retryFn]), env, ns));
return domAppend(container, fallbackDom);
})();
})(); });
})(); });
})(); }); });
return container;
})(); };
@@ -4272,10 +4249,13 @@ callExpr.push(dictGet(kwargs, k)); } }
// deref
var deref = function(s) { return (isSxTruthy(!isSxTruthy(isSignal(s))) ? s : (function() {
var ctx = getTrackingContext();
var ctx = sxContext("sx-reactive", NIL);
if (isSxTruthy(ctx)) {
trackingContextAddDep(ctx, s);
signalAddSub(s, trackingContextNotifyFn(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);
})()); };
@@ -4302,21 +4282,18 @@ callExpr.push(dictGet(kwargs, k)); } }
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 = makeTrackingContext(recompute);
return (function() {
var prev = getTrackingContext();
setTrackingContext(ctx);
var ctx = {["deps"]: [], ["notify"]: recompute};
scopePush("sx-reactive", ctx);
return (function() {
var newVal = invoke(computeFn);
setTrackingContext(prev);
signalSetDeps(s, trackingContextDeps(ctx));
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); });
@@ -4331,17 +4308,14 @@ return (function() {
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 = makeTrackingContext(runEffect);
return (function() {
var prev = getTrackingContext();
setTrackingContext(ctx);
var ctx = {["deps"]: [], ["notify"]: runEffect};
scopePush("sx-reactive", ctx);
return (function() {
var result = invoke(effectFn);
setTrackingContext(prev);
deps = trackingContextDeps(ctx);
scopePop("sx-reactive");
deps = get(ctx, "deps");
return (isSxTruthy(isCallable(result)) ? (cleanupFn = result) : NIL);
})();
})();
})()) : NIL); };
runEffect();
return (function() {
@@ -4390,22 +4364,19 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() {
// dispose-computed
var disposeComputed = function(s) { return (isSxTruthy(isSignal(s)) ? (forEach(function(dep) { return signalRemoveSub(dep, NIL); }, signalDeps(s)), signalSetDeps(s, [])) : NIL); };
// *island-scope*
var _islandScope = NIL;
// with-island-scope
var withIslandScope = function(scopeFn, bodyFn) { return (function() {
var prev = _islandScope;
_islandScope = scopeFn;
return (function() {
var withIslandScope = function(scopeFn, bodyFn) { scopePush("sx-island-scope", scopeFn);
return (function() {
var result = bodyFn();
_islandScope = prev;
scopePop("sx-island-scope");
return result;
})();
})(); };
// register-in-scope
var registerInScope = function(disposable) { return (isSxTruthy(_islandScope) ? _islandScope(disposable) : NIL); };
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() {