Restore stashed WIP: live streaming plan, forms, CI pipeline, streaming demo

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 22:07:59 +00:00
parent df1aa4e1d1
commit 5a68046bd8
21 changed files with 1463 additions and 120 deletions

View File

@@ -361,6 +361,7 @@ class JSEmitter:
"fetch-request": "fetchRequest",
"fetch-location": "fetchLocation",
"fetch-and-restore": "fetchAndRestore",
"fetch-streaming": "fetchStreaming",
"fetch-preload": "fetchPreload",
"dom-query-by-id": "domQueryById",
"dom-matches?": "domMatches",
@@ -3123,6 +3124,134 @@ PLATFORM_ORCHESTRATION_JS = """
}).catch(function() { location.reload(); });
}
function fetchStreaming(target, url, headers) {
// Streaming fetch for multi-stream pages.
// First chunk = OOB SX swap (shell with skeletons).
// Subsequent chunks = __sxResolve script tags filling suspense slots.
var opts = { headers: headers };
try {
var h = new URL(url, location.href).hostname;
if (h !== location.hostname &&
(h.indexOf(".rose-ash.com") >= 0 || h.indexOf(".localhost") >= 0)) {
opts.credentials = "include";
}
} catch (e) {}
fetch(url, opts).then(function(resp) {
if (!resp.ok || !resp.body) {
// Fallback: non-streaming
return resp.text().then(function(text) {
text = stripComponentScripts(text);
text = extractResponseCss(text);
text = text.trim();
if (text.charAt(0) === "(") {
var dom = sxRender(text);
var container = document.createElement("div");
container.appendChild(dom);
processOobSwaps(container, function(t, oob, s) {
swapDomNodes(t, oob, s);
sxHydrate(t);
processElements(t);
});
var newMain = container.querySelector("#main-panel");
morphChildren(target, newMain || container);
postSwap(target);
}
});
}
var reader = resp.body.getReader();
var decoder = new TextDecoder();
var buffer = "";
var initialSwapDone = false;
// Regex to match __sxResolve script tags
var RESOLVE_START = "<script>window.__sxResolve&&window.__sxResolve(";
var RESOLVE_END = ")</script>";
function processResolveScripts() {
// Strip and load any extra component defs before resolve scripts
buffer = stripSxScripts(buffer);
var idx;
while ((idx = buffer.indexOf(RESOLVE_START)) >= 0) {
var endIdx = buffer.indexOf(RESOLVE_END, idx);
if (endIdx < 0) break; // incomplete, wait for more data
var argsStr = buffer.substring(idx + RESOLVE_START.length, endIdx);
buffer = buffer.substring(endIdx + RESOLVE_END.length);
// argsStr is: "stream-id","sx source"
var commaIdx = argsStr.indexOf(",");
if (commaIdx >= 0) {
try {
var id = JSON.parse(argsStr.substring(0, commaIdx));
var sx = JSON.parse(argsStr.substring(commaIdx + 1));
if (typeof Sx !== "undefined" && Sx.resolveSuspense) {
Sx.resolveSuspense(id, sx);
}
} catch (e) {
console.error("[sx-ref] resolve parse error:", e);
}
}
}
}
function pump() {
return reader.read().then(function(result) {
buffer += decoder.decode(result.value || new Uint8Array(), { stream: !result.done });
if (!initialSwapDone) {
// Look for the first resolve script — everything before it is OOB content
var scriptIdx = buffer.indexOf("<script>window.__sxResolve");
// If we found a script tag, or the stream is done, process OOB
var oobEnd = scriptIdx >= 0 ? scriptIdx : (result.done ? buffer.length : -1);
if (oobEnd >= 0) {
var oobContent = buffer.substring(0, oobEnd);
buffer = buffer.substring(oobEnd);
initialSwapDone = true;
// Process OOB SX content (same as fetchAndRestore)
oobContent = stripComponentScripts(oobContent);
// Also strip bare <script type="text/sx"> (extra defs from resolve chunks)
oobContent = stripSxScripts(oobContent);
oobContent = extractResponseCss(oobContent);
oobContent = oobContent.trim();
if (oobContent.charAt(0) === "(") {
try {
var dom = sxRender(oobContent);
var container = document.createElement("div");
container.appendChild(dom);
processOobSwaps(container, function(t, oob, s) {
swapDomNodes(t, oob, s);
sxHydrate(t);
processElements(t);
});
var newMain = container.querySelector("#main-panel");
morphChildren(target, newMain || container);
postSwap(target);
// Dispatch clientRoute so nav links update active state
domDispatch(target, "sx:clientRoute",
{ pathname: new URL(url, location.href).pathname });
} catch (err) {
console.error("[sx-ref] streaming OOB swap error:", err);
}
}
// Process any resolve scripts already in buffer
processResolveScripts();
}
} else {
// Process resolve scripts as they arrive
processResolveScripts();
}
if (!result.done) return pump();
});
}
return pump();
}).catch(function(err) {
console.error("[sx-ref] streaming fetch error:", err);
location.reload();
});
}
function fetchPreload(url, headers, cache) {
fetch(url, { headers: headers }).then(function(resp) {
if (!resp.ok) return;
@@ -3587,6 +3716,14 @@ PLATFORM_ORCHESTRATION_JS = """
function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; });
}
function stripSxScripts(text) {
// Strip <script type="text/sx">...</script> (without data-components).
// These contain extra component defs from streaming resolve chunks.
var SxObj = typeof Sx !== "undefined" ? Sx : null;
return text.replace(/<script[^>]*type="text\\/sx"[^>]*>([\\s\\S]*?)<\\/script>/gi,
function(_, defs) { if (SxObj && SxObj.loadComponents) SxObj.loadComponents(defs); return ""; });
}
function extractResponseCss(text) {
if (!_hasDom) return text;
var target = document.getElementById("sx-css");