Add flush-cssx-to-dom: client-side CSSX rule injection

Islands render independently on the client, so ~cssx/tw calls
collect!("cssx", rule) but no ~cssx/flush runs. Add flush-cssx-to-dom
in boot.sx that injects collected rules into a persistent <style>
element in <head>.

Called at all lifecycle points: boot-init, sx-mount, resolve-suspense,
post-swap (navigation morph), and swap-rendered-content (client routes).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 04:09:23 +00:00
parent f52b9e880b
commit 1d1e7f30bb
3 changed files with 54 additions and 5 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-13T03:40:30Z";
var SX_VERSION = "2026-03-13T04:08:59Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -2947,6 +2947,7 @@ return postSwap(target); });
sxProcessScripts(root);
sxHydrate(root);
sxHydrateIslands(root);
flushCssxToDom();
return processElements(root); };
// process-settle-hooks
@@ -3181,7 +3182,7 @@ return logWarn((String("sx:offline sync failed ") + String(get(entry, "action"))
})(); };
// swap-rendered-content
var swapRenderedContent = function(target, rendered, pathname) { return (disposeIslandsIn(target), domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); };
var swapRenderedContent = function(target, rendered, pathname) { return (disposeIslandsIn(target), domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), flushCssxToDom(), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); };
// resolve-route-target
var resolveRouteTarget = function(targetSel) { return (isSxTruthy((isSxTruthy(targetSel) && !isSxTruthy((targetSel == "true")))) ? domQuery(targetSel) : NIL); };
@@ -3400,7 +3401,8 @@ return processEmitElements(root); };
hoistHeadElementsFull(el);
processElements(el);
sxHydrateElements(el);
return sxHydrateIslands(el);
sxHydrateIslands(el);
return flushCssxToDom();
})() : NIL);
})(); };
@@ -3416,6 +3418,7 @@ return (function() {
processElements(el);
sxHydrateElements(el);
sxHydrateIslands(el);
flushCssxToDom();
return domDispatch(el, "sx:resolved", {"id": id});
})() : logWarn((String("resolveSuspense: no element for id=") + String(id))));
})(); };
@@ -3561,8 +3564,23 @@ callExpr.push(dictGet(kwargs, k)); } }
})() : NIL);
})() : NIL); };
// flush-cssx-to-dom
var flushCssxToDom = function() { return (function() {
var rules = sxCollected("cssx");
return (isSxTruthy(!isSxTruthy(isEmpty(rules))) ? ((function() {
var style = sxOr(domQuery("#sx-cssx-live"), (function() {
var s = domCreateElement("style", NIL);
domSetAttr(s, "id", "sx-cssx-live");
domSetAttr(s, "data-cssx", "");
domAppendToHead(s);
return s;
})());
return domSetProp(style, "textContent", (String(sxOr(domGetProp(style, "textContent"), "")) + String(join("", rules))));
})(), sxClearCollected("cssx")) : NIL);
})(); };
// boot-init
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), processElements(NIL)); };
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), sxHydrateIslands(NIL), flushCssxToDom(), processElements(NIL)); };
// === Transpiled from deps (component dependency analysis) ===