Phase 4 complete: client data cache + plan update

- Add page data cache in orchestration.sx (30s TTL, keyed by page-name+params)
- Cache hit path: sx:route client+cache (instant render, no fetch)
- Cache miss path: sx:route client+data (fetch, cache, render)
- Fix HTMX response dep computation to include :data pages
- Update isomorphic-sx-plan.md: Phases 1-4 marked done with details,
  reorder remaining phases (continuations→Phase 5, suspense→Phase 6,
  optimistic updates→Phase 7)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-07 00:06:22 +00:00
parent 732923a7ef
commit d3617ab7f3
4 changed files with 241 additions and 139 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-06T23:52:56Z";
var SX_VERSION = "2026-03-07T00:04:07Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -1930,6 +1930,31 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"
})()) : NIL); }, domQueryAll(container, "form"));
})(); };
// _page-data-cache
var _pageDataCache = {};
// _page-data-cache-ttl
var _pageDataCacheTtl = 30000;
// page-data-cache-key
var pageDataCacheKey = function(pageName, params) { return (function() {
var base = pageName;
return (isSxTruthy(sxOr(isNil(params), isEmpty(keys(params)))) ? base : (function() {
var parts = [];
{ var _c = keys(params); for (var _i = 0; _i < _c.length; _i++) { var k = _c[_i]; parts.push((String(k) + String("=") + String(get(params, k)))); } }
return (String(base) + String(":") + String(join("&", parts)));
})());
})(); };
// page-data-cache-get
var pageDataCacheGet = function(cacheKey) { return (function() {
var entry = get(_pageDataCache, cacheKey);
return (isSxTruthy(isNil(entry)) ? NIL : (isSxTruthy(((nowMs() - get(entry, "ts")) > _pageDataCacheTtl)) ? (dictSet(_pageDataCache, cacheKey, NIL), NIL) : get(entry, "data")));
})(); };
// page-data-cache-set
var pageDataCacheSet = function(cacheKey, data) { return dictSet(_pageDataCache, cacheKey, {"data": data, "ts": nowMs()}); };
// swap-rendered-content
var swapRenderedContent = function(target, rendered, pathname) { return (domSetTextContent(target, ""), domAppend(target, rendered), hoistHeadElementsFull(target), processElements(target), sxHydrateElements(target), domDispatch(target, "sx:clientRoute", {["pathname"]: pathname}), logInfo((String("sx:route client ") + String(pathname)))); };
@@ -1946,11 +1971,20 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"
var pageName = get(match, "name");
return (isSxTruthy(sxOr(isNil(contentSrc), isEmpty(contentSrc))) ? (logWarn((String("sx:route no content for ") + String(pathname))), false) : (function() {
var target = resolveRouteTarget(targetSel);
return (isSxTruthy(isNil(target)) ? (logWarn((String("sx:route target not found: ") + String(targetSel))), false) : (isSxTruthy(get(match, "has-data")) ? (logInfo((String("sx:route client+data ") + String(pathname))), resolvePageData(pageName, params, function(data) { return (function() {
return (isSxTruthy(isNil(target)) ? (logWarn((String("sx:route target not found: ") + String(targetSel))), false) : (isSxTruthy(get(match, "has-data")) ? (function() {
var cacheKey = pageDataCacheKey(pageName, params);
var cached = pageDataCacheGet(cacheKey);
return (isSxTruthy(cached) ? (function() {
var env = merge(closure, params, cached);
var rendered = tryEvalContent(contentSrc, env);
return (isSxTruthy(isNil(rendered)) ? (logWarn((String("sx:route cached eval failed for ") + String(pathname))), false) : (logInfo((String("sx:route client+cache ") + String(pathname))), swapRenderedContent(target, rendered, pathname), true));
})() : (logInfo((String("sx:route client+data ") + String(pathname))), resolvePageData(pageName, params, function(data) { pageDataCacheSet(cacheKey, data);
return (function() {
var env = merge(closure, params, data);
var rendered = tryEvalContent(contentSrc, env);
return (isSxTruthy(isNil(rendered)) ? logWarn((String("sx:route data eval failed for ") + String(pathname))) : swapRenderedContent(target, rendered, pathname));
})(); }), true) : (function() {
})(); }), true));
})() : (function() {
var env = merge(closure, params);
var rendered = tryEvalContent(contentSrc, env);
return (isSxTruthy(isNil(rendered)) ? (logInfo((String("sx:route server (eval failed) ") + String(pathname))), false) : (swapRenderedContent(target, rendered, pathname), true));