Level 2-3: lake morphing — server content flows through reactive islands
Lake tag (lake :id "name" children...) creates server-morphable slots within islands. During morph, the engine enters hydrated islands and updates data-sx-lake elements by ID while preserving surrounding reactive DOM (signals, effects, event listeners). Specced in .sx, bootstrapped to JS and Python: - adapter-dom.sx: render-dom-lake, reactive-attr marks data-sx-reactive-attrs - adapter-html.sx: render-html-lake SSR output - adapter-sx.sx: lake serialized in wire format - engine.sx: morph-island-children (lake-by-ID matching), sync-attrs skips reactive attributes - ~sx-header uses lakes for logo and copyright - Hegelian essay updated with lake code example Also includes: lambda nil-padding for missing args, page env ordering fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-10T14:05:08Z";
|
||||
var SX_VERSION = "2026-03-10T14:25:47Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -1245,10 +1245,10 @@ continue; } else { return NIL; } } };
|
||||
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? join("", map(function(x) { return renderValueToHtml(x, env); }, expr)) : (function() {
|
||||
var name = symbolName(head);
|
||||
var args = rest(expr);
|
||||
return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
|
||||
return (isSxTruthy((name == "<>")) ? join("", map(function(x) { return renderToHtml(x, env); }, args)) : (isSxTruthy((name == "raw!")) ? join("", map(function(x) { return (String(trampoline(evalExpr(x, env)))); }, args)) : (isSxTruthy((name == "lake")) ? renderHtmlLake(args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? renderHtmlElement(name, args, env) : (isSxTruthy((isSxTruthy(startsWith(name, "~")) && isSxTruthy(envHas(env, name)) && isIsland(envGet(env, name)))) ? renderHtmlIsland(envGet(env, name), args, env) : (isSxTruthy(startsWith(name, "~")) ? (function() {
|
||||
var val = envGet(env, name);
|
||||
return (isSxTruthy(isComponent(val)) ? renderHtmlComponent(val, args, env) : (isSxTruthy(isMacro(val)) ? renderToHtml(expandMacro(val, args, env), env) : error((String("Unknown component: ") + String(name)))));
|
||||
})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))));
|
||||
})() : (isSxTruthy(isRenderHtmlForm(name)) ? dispatchHtmlForm(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? renderToHtml(expandMacro(envGet(env, name), args, env), env) : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))));
|
||||
})());
|
||||
})()); };
|
||||
|
||||
@@ -1314,6 +1314,23 @@ continue; } else { return NIL; } } };
|
||||
return (String("<") + String(tag) + String(renderAttrs(attrs)) + String((isSxTruthy(isVoid) ? " />" : (String(">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("</") + String(tag) + String(">")))));
|
||||
})(); };
|
||||
|
||||
// render-html-lake
|
||||
var renderHtmlLake = function(args, env) { return (function() {
|
||||
var lakeId = NIL;
|
||||
var lakeTag = "div";
|
||||
var children = [];
|
||||
reduce(function(state, arg) { return (function() {
|
||||
var skip = get(state, "skip");
|
||||
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
||||
var kname = keywordName(arg);
|
||||
var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
||||
(isSxTruthy((kname == "id")) ? (lakeId = kval) : (isSxTruthy((kname == "tag")) ? (lakeTag = kval) : NIL));
|
||||
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
||||
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
|
||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||
return (String("<") + String(lakeTag) + String(" data-sx-lake=\"") + String(escapeAttr(sxOr(lakeId, ""))) + String("\">") + String(join("", map(function(c) { return renderToHtml(c, env); }, children))) + String("</") + String(lakeTag) + String(">"));
|
||||
})(); };
|
||||
|
||||
// render-html-island
|
||||
var renderHtmlIsland = function(island, args, env) { return (function() {
|
||||
var kwargs = {};
|
||||
@@ -1365,11 +1382,11 @@ continue; } else { return NIL; } } };
|
||||
var args = rest(expr);
|
||||
return (isSxTruthy(!isSxTruthy((typeOf(head) == "symbol"))) ? map(function(x) { return aser(x, env); }, expr) : (function() {
|
||||
var name = symbolName(head);
|
||||
return (isSxTruthy((name == "<>")) ? aserFragment(args, env) : (isSxTruthy(startsWith(name, "~")) ? aserCall(name, args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? aserCall(name, args, env) : (isSxTruthy(sxOr(isSpecialForm(name), isHoForm(name))) ? aserSpecial(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? aser(expandMacro(envGet(env, name), args, env), env) : (function() {
|
||||
return (isSxTruthy((name == "<>")) ? aserFragment(args, env) : (isSxTruthy(startsWith(name, "~")) ? aserCall(name, args, env) : (isSxTruthy((name == "lake")) ? aserCall(name, args, env) : (isSxTruthy(contains(HTML_TAGS, name)) ? aserCall(name, args, env) : (isSxTruthy(sxOr(isSpecialForm(name), isHoForm(name))) ? aserSpecial(name, expr, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? aser(expandMacro(envGet(env, name), args, env), env) : (function() {
|
||||
var f = trampoline(evalExpr(head, env));
|
||||
var evaledArgs = map(function(a) { return trampoline(evalExpr(a, env)); }, args);
|
||||
return (isSxTruthy((isSxTruthy(isCallable(f)) && isSxTruthy(!isSxTruthy(isLambda(f))) && isSxTruthy(!isSxTruthy(isComponent(f))) && !isSxTruthy(isIsland(f)))) ? apply(f, evaledArgs) : (isSxTruthy(isLambda(f)) ? trampoline(callLambda(f, evaledArgs, env)) : (isSxTruthy(isComponent(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : (isSxTruthy(isIsland(f)) ? aserCall((String("~") + String(componentName(f))), args, env) : error((String("Not callable: ") + String(inspect(f))))))));
|
||||
})())))));
|
||||
})()))))));
|
||||
})());
|
||||
})(); };
|
||||
|
||||
@@ -1502,13 +1519,13 @@ return result; }, args);
|
||||
return (isSxTruthy((typeOf(head) == "symbol")) ? (function() {
|
||||
var name = symbolName(head);
|
||||
var args = rest(expr);
|
||||
return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(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() {
|
||||
return (isSxTruthy((name == "raw!")) ? renderDomRaw(args, env) : (isSxTruthy((name == "<>")) ? renderDomFragment(args, env, ns) : (isSxTruthy((name == "lake")) ? renderDomLake(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() {
|
||||
var sigOrVal = trampoline(evalExpr(first(args), env));
|
||||
return (isSxTruthy(isSignal(sigOrVal)) ? reactiveText(sigOrVal) : createTextNode((String(deref(sigOrVal)))));
|
||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))));
|
||||
})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))));
|
||||
})() : (isSxTruthy(sxOr(isLambda(head), (typeOf(head) == "list"))) ? renderToDom(trampoline(evalExpr(expr, env)), env, ns) : (function() {
|
||||
var frag = createFragment();
|
||||
{ var _c = expr; for (var _i = 0; _i < _c.length; _i++) { var x = _c[_i]; domAppend(frag, renderToDom(x, env, ns)); } }
|
||||
@@ -1772,6 +1789,28 @@ return result; }, args);
|
||||
})();
|
||||
})();
|
||||
})();
|
||||
})(); };
|
||||
|
||||
// render-dom-lake
|
||||
var renderDomLake = function(args, env, ns) { return (function() {
|
||||
var lakeId = NIL;
|
||||
var lakeTag = "div";
|
||||
var children = [];
|
||||
reduce(function(state, arg) { return (function() {
|
||||
var skip = get(state, "skip");
|
||||
return (isSxTruthy(skip) ? assoc(state, "skip", false, "i", (get(state, "i") + 1)) : (isSxTruthy((isSxTruthy((typeOf(arg) == "keyword")) && ((get(state, "i") + 1) < len(args)))) ? (function() {
|
||||
var kname = keywordName(arg);
|
||||
var kval = trampoline(evalExpr(nth(args, (get(state, "i") + 1)), env));
|
||||
(isSxTruthy((kname == "id")) ? (lakeId = kval) : (isSxTruthy((kname == "tag")) ? (lakeTag = kval) : NIL));
|
||||
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
|
||||
})() : (append_b(children, arg), assoc(state, "i", (get(state, "i") + 1)))));
|
||||
})(); }, {["i"]: 0, ["skip"]: false}, args);
|
||||
return (function() {
|
||||
var el = domCreateElement(lakeTag, NIL);
|
||||
domSetAttr(el, "data-sx-lake", sxOr(lakeId, ""));
|
||||
{ var _c = children; for (var _i = 0; _i < _c.length; _i++) { var c = _c[_i]; domAppend(el, renderToDom(c, env, ns)); } }
|
||||
return el;
|
||||
})();
|
||||
})(); };
|
||||
|
||||
// reactive-text
|
||||
@@ -1782,7 +1821,12 @@ return result; }, args);
|
||||
})(); };
|
||||
|
||||
// reactive-attr
|
||||
var reactiveAttr = function(el, attrName, computeFn) { return effect(function() { return (function() {
|
||||
var reactiveAttr = function(el, attrName, computeFn) { (function() {
|
||||
var existing = sxOr(domGetAttr(el, "data-sx-reactive-attrs"), "");
|
||||
var updated = (isSxTruthy(isEmpty(existing)) ? attrName : (String(existing) + String(",") + String(attrName)));
|
||||
return domSetAttr(el, "data-sx-reactive-attrs", updated);
|
||||
})();
|
||||
return effect(function() { return (function() {
|
||||
var val = computeFn();
|
||||
return (isSxTruthy(sxOr(isNil(val), (val == false))) ? domRemoveAttr(el, attrName) : (isSxTruthy((val == true)) ? domSetAttr(el, attrName, "") : domSetAttr(el, attrName, (String(val)))));
|
||||
})(); }); };
|
||||
@@ -2053,15 +2097,22 @@ return (function() {
|
||||
})(); };
|
||||
|
||||
// morph-node
|
||||
var morphNode = function(oldNode, newNode) { return (isSxTruthy(sxOr(domHasAttr(oldNode, "sx-preserve"), domHasAttr(oldNode, "sx-ignore"))) ? NIL : (isSxTruthy((isSxTruthy(domHasAttr(oldNode, "data-sx-island")) && isSxTruthy(isProcessed(oldNode, "island-hydrated")) && isSxTruthy(domHasAttr(newNode, "data-sx-island")) && (domGetAttr(oldNode, "data-sx-island") == domGetAttr(newNode, "data-sx-island")))) ? NIL : (isSxTruthy(sxOr(!isSxTruthy((domNodeType(oldNode) == domNodeType(newNode))), !isSxTruthy((domNodeName(oldNode) == domNodeName(newNode))))) ? domReplaceChild(domParent(oldNode), domClone(newNode), oldNode) : (isSxTruthy(sxOr((domNodeType(oldNode) == 3), (domNodeType(oldNode) == 8))) ? (isSxTruthy(!isSxTruthy((domTextContent(oldNode) == domTextContent(newNode)))) ? domSetTextContent(oldNode, domTextContent(newNode)) : NIL) : (isSxTruthy((domNodeType(oldNode) == 1)) ? (syncAttrs(oldNode, newNode), (isSxTruthy(!isSxTruthy((isSxTruthy(domIsActiveElement(oldNode)) && domIsInputElement(oldNode)))) ? morphChildren(oldNode, newNode) : NIL)) : NIL))))); };
|
||||
var morphNode = function(oldNode, newNode) { return (isSxTruthy(sxOr(domHasAttr(oldNode, "sx-preserve"), domHasAttr(oldNode, "sx-ignore"))) ? NIL : (isSxTruthy((isSxTruthy(domHasAttr(oldNode, "data-sx-island")) && isSxTruthy(isProcessed(oldNode, "island-hydrated")) && isSxTruthy(domHasAttr(newNode, "data-sx-island")) && (domGetAttr(oldNode, "data-sx-island") == domGetAttr(newNode, "data-sx-island")))) ? morphIslandChildren(oldNode, newNode) : (isSxTruthy(sxOr(!isSxTruthy((domNodeType(oldNode) == domNodeType(newNode))), !isSxTruthy((domNodeName(oldNode) == domNodeName(newNode))))) ? domReplaceChild(domParent(oldNode), domClone(newNode), oldNode) : (isSxTruthy(sxOr((domNodeType(oldNode) == 3), (domNodeType(oldNode) == 8))) ? (isSxTruthy(!isSxTruthy((domTextContent(oldNode) == domTextContent(newNode)))) ? domSetTextContent(oldNode, domTextContent(newNode)) : NIL) : (isSxTruthy((domNodeType(oldNode) == 1)) ? (syncAttrs(oldNode, newNode), (isSxTruthy(!isSxTruthy((isSxTruthy(domIsActiveElement(oldNode)) && domIsInputElement(oldNode)))) ? morphChildren(oldNode, newNode) : NIL)) : NIL))))); };
|
||||
|
||||
// sync-attrs
|
||||
var syncAttrs = function(oldEl, newEl) { { var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() {
|
||||
var syncAttrs = function(oldEl, newEl) { return (function() {
|
||||
var raStr = sxOr(domGetAttr(oldEl, "data-sx-reactive-attrs"), "");
|
||||
var reactiveAttrs = (isSxTruthy(isEmpty(raStr)) ? [] : split(raStr, ","));
|
||||
{ var _c = domAttrList(newEl); for (var _i = 0; _i < _c.length; _i++) { var attr = _c[_i]; (function() {
|
||||
var name = first(attr);
|
||||
var val = nth(attr, 1);
|
||||
return (isSxTruthy(!isSxTruthy((domGetAttr(oldEl, name) == val))) ? domSetAttr(oldEl, name, val) : NIL);
|
||||
return (isSxTruthy((isSxTruthy(!isSxTruthy((domGetAttr(oldEl, name) == val))) && !isSxTruthy(contains(reactiveAttrs, name)))) ? domSetAttr(oldEl, name, val) : NIL);
|
||||
})(); } }
|
||||
return forEach(function(attr) { return (isSxTruthy(!isSxTruthy(domHasAttr(newEl, first(attr)))) ? domRemoveAttr(oldEl, first(attr)) : NIL); }, domAttrList(oldEl)); };
|
||||
return forEach(function(attr) { return (function() {
|
||||
var aname = first(attr);
|
||||
return (isSxTruthy((isSxTruthy(!isSxTruthy(domHasAttr(newEl, aname))) && isSxTruthy(!isSxTruthy(contains(reactiveAttrs, aname))) && !isSxTruthy((aname == "data-sx-reactive-attrs")))) ? domRemoveAttr(oldEl, aname) : NIL);
|
||||
})(); }, domAttrList(oldEl));
|
||||
})(); };
|
||||
|
||||
// morph-children
|
||||
var morphChildren = function(oldParent, newParent) { return (function() {
|
||||
@@ -2084,6 +2135,26 @@ return forEach(function(attr) { return (isSxTruthy(!isSxTruthy(domHasAttr(newEl,
|
||||
var leftover = nth(oldKids, i);
|
||||
return (isSxTruthy((isSxTruthy(domIsChildOf(leftover, oldParent)) && isSxTruthy(!isSxTruthy(domHasAttr(leftover, "sx-preserve"))) && !isSxTruthy(domHasAttr(leftover, "sx-ignore")))) ? domRemoveChild(oldParent, leftover) : NIL);
|
||||
})() : NIL); }, range(oi, len(oldKids)));
|
||||
})(); };
|
||||
|
||||
// morph-island-children
|
||||
var morphIslandChildren = function(oldIsland, newIsland) { return (function() {
|
||||
var oldLakes = domQueryAll(oldIsland, "[data-sx-lake]");
|
||||
var newLakes = domQueryAll(newIsland, "[data-sx-lake]");
|
||||
return (function() {
|
||||
var newLakeMap = {};
|
||||
{ var _c = newLakes; for (var _i = 0; _i < _c.length; _i++) { var lake = _c[_i]; (function() {
|
||||
var id = domGetAttr(lake, "data-sx-lake");
|
||||
return (isSxTruthy(id) ? dictSet(newLakeMap, id, lake) : NIL);
|
||||
})(); } }
|
||||
return forEach(function(oldLake) { return (function() {
|
||||
var id = domGetAttr(oldLake, "data-sx-lake");
|
||||
return (function() {
|
||||
var newLake = dictGet(newLakeMap, id);
|
||||
return (isSxTruthy(newLake) ? (syncAttrs(oldLake, newLake), morphChildren(oldLake, newLake)) : NIL);
|
||||
})();
|
||||
})(); }, oldLakes);
|
||||
})();
|
||||
})(); };
|
||||
|
||||
// swap-dom-nodes
|
||||
|
||||
Reference in New Issue
Block a user