Merge branch 'worktree-iso-phase-4' into macros
This commit is contained in:
@@ -14,7 +14,7 @@
|
||||
// =========================================================================
|
||||
|
||||
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
|
||||
var SX_VERSION = "2026-03-07T02:32:34Z";
|
||||
var SX_VERSION = "2026-03-07T09:03:03Z";
|
||||
|
||||
function isNil(x) { return x === NIL || x === null || x === undefined; }
|
||||
function isSxTruthy(x) { return x !== false && !isNil(x); }
|
||||
@@ -2031,7 +2031,11 @@ return domAppendToHead(link); }, domQueryAll(container, "link[rel=\"stylesheet\"
|
||||
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(!isSxTruthy(depsSatisfied_p(match))) ? (logInfo((String("sx:route deps miss for ") + String(pageName))), false) : (function() {
|
||||
var hasIo = get(match, "has-io");
|
||||
var ioDeps = get(match, "io-deps");
|
||||
var hasIo = (isSxTruthy(ioDeps) && !isSxTruthy(isEmpty(ioDeps)));
|
||||
if (isSxTruthy(hasIo)) {
|
||||
registerIoDeps(ioDeps);
|
||||
}
|
||||
return (isSxTruthy(get(match, "has-data")) ? (function() {
|
||||
var cacheKey = pageDataCacheKey(pageName, params);
|
||||
var cached = pageDataCacheGet(cacheKey);
|
||||
@@ -2476,7 +2480,7 @@ callExpr.push(dictGet(kwargs, k)); } }
|
||||
})(); };
|
||||
|
||||
// boot-init
|
||||
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), initStyleDict(), initIoPrimitives(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); };
|
||||
var bootInit = function() { return (logInfo((String("sx-browser ") + String(SX_VERSION))), initCssTracking(), initStyleDict(), processPageScripts(), processSxScripts(NIL), sxHydrateElements(NIL), processElements(NIL)); };
|
||||
|
||||
|
||||
// === Transpiled from router (client-side route matching) ===
|
||||
@@ -4359,16 +4363,20 @@ callExpr.push(dictGet(kwargs, k)); } }
|
||||
});
|
||||
}
|
||||
|
||||
// Register default proxied IO primitives
|
||||
function initIoPrimitives() {
|
||||
var defaults = [
|
||||
"highlight", "current-user", "request-arg", "request-path",
|
||||
"app-url", "asset-url", "config"
|
||||
];
|
||||
for (var i = 0; i < defaults.length; i++) {
|
||||
registerProxiedIo(defaults[i]);
|
||||
// Register IO deps as proxied primitives (idempotent, called per-page)
|
||||
function registerIoDeps(names) {
|
||||
if (!names || !names.length) return;
|
||||
var registered = 0;
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
if (!IO_PRIMITIVES[name]) {
|
||||
registerProxiedIo(name);
|
||||
registered++;
|
||||
}
|
||||
}
|
||||
if (registered > 0) {
|
||||
logInfo("sx:io registered " + registered + " proxied primitives: " + names.join(", "));
|
||||
}
|
||||
logInfo("sx:io registered " + defaults.length + " proxied primitives");
|
||||
}
|
||||
|
||||
|
||||
@@ -4445,6 +4453,7 @@ callExpr.push(dictGet(kwargs, k)); } }
|
||||
matchRoute: matchRoute,
|
||||
findMatchingRoute: findMatchingRoute,
|
||||
registerIo: typeof registerIoPrimitive === "function" ? registerIoPrimitive : null,
|
||||
registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null,
|
||||
asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null,
|
||||
asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null,
|
||||
_version: "ref-2.0 (boot+cssx+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)"
|
||||
|
||||
@@ -692,14 +692,17 @@ def _build_pages_sx(service: str) -> str:
|
||||
deps = components_needed(content_src, _COMPONENT_ENV)
|
||||
deps_sx = "(" + " ".join(_sx_literal(d) for d in sorted(deps)) + ")"
|
||||
|
||||
# Check if any dep component has IO refs (needs async rendering)
|
||||
# Collect IO primitive names referenced by dep components
|
||||
from .types import Component as _Comp
|
||||
has_io = "false"
|
||||
io_deps: set[str] = set()
|
||||
for dep_name in deps:
|
||||
comp = _COMPONENT_ENV.get(dep_name)
|
||||
if isinstance(comp, _Comp) and comp.io_refs:
|
||||
has_io = "true"
|
||||
break
|
||||
io_deps.update(comp.io_refs)
|
||||
io_deps_sx = (
|
||||
"(" + " ".join(_sx_literal(n) for n in sorted(io_deps)) + ")"
|
||||
if io_deps else "()"
|
||||
)
|
||||
|
||||
# Build closure as SX dict
|
||||
closure_parts: list[str] = []
|
||||
@@ -713,7 +716,7 @@ def _build_pages_sx(service: str) -> str:
|
||||
+ " :path " + _sx_literal(page_def.path)
|
||||
+ " :auth " + _sx_literal(auth)
|
||||
+ " :has-data " + has_data
|
||||
+ " :has-io " + has_io
|
||||
+ " :io-deps " + io_deps_sx
|
||||
+ " :content " + _sx_literal(content_src)
|
||||
+ " :deps " + deps_sx
|
||||
+ " :closure " + closure_sx + "}"
|
||||
|
||||
@@ -555,11 +555,13 @@ def mount_io_endpoint(app: Any, service_name: str) -> None:
|
||||
from .jinja_bridge import _get_request_context
|
||||
from .parser import serialize
|
||||
|
||||
# Allowlist of IO primitives + page helpers the client may call
|
||||
_ALLOWED_IO = {
|
||||
"highlight", "current-user", "request-arg", "request-path",
|
||||
"htmx-request?", "app-url", "asset-url", "config",
|
||||
}
|
||||
# Build allowlist from all component IO refs across this service
|
||||
from .jinja_bridge import _COMPONENT_ENV
|
||||
from .types import Component as _Comp
|
||||
_ALLOWED_IO: set[str] = set()
|
||||
for _val in _COMPONENT_ENV.values():
|
||||
if isinstance(_val, _Comp) and _val.io_refs:
|
||||
_ALLOWED_IO.update(_val.io_refs)
|
||||
|
||||
async def io_proxy(name: str) -> Any:
|
||||
if name not in _ALLOWED_IO:
|
||||
@@ -607,4 +609,4 @@ def mount_io_endpoint(app: Any, service_name: str) -> None:
|
||||
view_func=io_proxy,
|
||||
methods=["GET", "POST"],
|
||||
)
|
||||
logger.info("Mounted IO proxy endpoint for %s at /sx/io/<name>", service_name)
|
||||
logger.info("Mounted IO proxy for %s: %s", service_name, sorted(_ALLOWED_IO))
|
||||
|
||||
@@ -344,7 +344,6 @@
|
||||
(log-info (str "sx-browser " SX_VERSION))
|
||||
(init-css-tracking)
|
||||
(init-style-dict)
|
||||
(init-io-primitives)
|
||||
(process-page-scripts)
|
||||
(process-sx-scripts nil)
|
||||
(sx-hydrate-elements nil)
|
||||
|
||||
@@ -385,6 +385,7 @@ class JSEmitter:
|
||||
"try-client-route": "tryClientRoute",
|
||||
"try-eval-content": "tryEvalContent",
|
||||
"try-async-eval-content": "tryAsyncEvalContent",
|
||||
"register-io-deps": "registerIoDeps",
|
||||
"url-pathname": "urlPathname",
|
||||
"bind-inline-handler": "bindInlineHandler",
|
||||
"bind-preload": "bindPreload",
|
||||
@@ -477,7 +478,6 @@ class JSEmitter:
|
||||
"process-sx-scripts": "processSxScripts",
|
||||
"process-component-script": "processComponentScript",
|
||||
"init-style-dict": "initStyleDict",
|
||||
"init-io-primitives": "initIoPrimitives",
|
||||
"SX_VERSION": "SX_VERSION",
|
||||
"boot-init": "bootInit",
|
||||
"resolve-mount-target": "resolveMountTarget",
|
||||
@@ -1733,16 +1733,20 @@ ASYNC_IO_JS = '''
|
||||
});
|
||||
}
|
||||
|
||||
// Register default proxied IO primitives
|
||||
function initIoPrimitives() {
|
||||
var defaults = [
|
||||
"highlight", "current-user", "request-arg", "request-path",
|
||||
"app-url", "asset-url", "config"
|
||||
];
|
||||
for (var i = 0; i < defaults.length; i++) {
|
||||
registerProxiedIo(defaults[i]);
|
||||
// Register IO deps as proxied primitives (idempotent, called per-page)
|
||||
function registerIoDeps(names) {
|
||||
if (!names || !names.length) return;
|
||||
var registered = 0;
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
if (!IO_PRIMITIVES[name]) {
|
||||
registerProxiedIo(name);
|
||||
registered++;
|
||||
}
|
||||
}
|
||||
if (registered > 0) {
|
||||
logInfo("sx:io registered " + registered + " proxied primitives: " + names.join(", "));
|
||||
}
|
||||
logInfo("sx:io registered " + defaults.length + " proxied primitives");
|
||||
}
|
||||
'''
|
||||
|
||||
@@ -3993,6 +3997,7 @@ def public_api_js(has_html, has_sx, has_dom, has_engine, has_orch, has_cssx, has
|
||||
|
||||
if has_dom:
|
||||
api_lines.append(' registerIo: typeof registerIoPrimitive === "function" ? registerIoPrimitive : null,')
|
||||
api_lines.append(' registerIoDeps: typeof registerIoDeps === "function" ? registerIoDeps : null,')
|
||||
api_lines.append(' asyncRender: typeof asyncSxRenderWithEnv === "function" ? asyncSxRenderWithEnv : null,')
|
||||
api_lines.append(' asyncRenderToDom: typeof asyncRenderToDom === "function" ? asyncRenderToDom : null,')
|
||||
api_lines.append(f' _version: "{version}"')
|
||||
|
||||
@@ -648,7 +648,10 @@
|
||||
(do (log-warn (str "sx:route target not found: " target-sel)) false)
|
||||
(if (not (deps-satisfied? match))
|
||||
(do (log-info (str "sx:route deps miss for " page-name)) false)
|
||||
(let ((has-io (get match "has-io")))
|
||||
(let ((io-deps (get match "io-deps"))
|
||||
(has-io (and io-deps (not (empty? io-deps)))))
|
||||
;; Ensure IO deps are registered as proxied primitives
|
||||
(when has-io (register-io-deps io-deps))
|
||||
(if (get match "has-data")
|
||||
;; Data page: check cache, else resolve asynchronously
|
||||
(let ((cache-key (page-data-cache-key page-name params))
|
||||
@@ -1024,6 +1027,8 @@
|
||||
;; (try-eval-content source env) → DOM node or nil (catches eval errors)
|
||||
;; (try-async-eval-content source env callback) → void; async render,
|
||||
;; calls (callback rendered-or-nil). Used for pages with IO deps.
|
||||
;; (register-io-deps names) → void; ensure each IO name is registered
|
||||
;; as a proxied IO primitive on the client. Idempotent.
|
||||
;; (url-pathname href) → extract pathname from URL string
|
||||
;; (resolve-page-data name params cb) → void; resolves data for a named page.
|
||||
;; Platform decides transport (HTTP, cache, IPC, etc). Calls (cb data-dict)
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
(div :class "rounded-lg border border-stone-200 bg-white p-5 space-y-3"
|
||||
(h3 :class "text-sm font-medium text-stone-500 uppercase tracking-wide" "SX async rendering spec")
|
||||
(~doc-code :code
|
||||
(highlight ";; try-client-route dispatches on has-io flag\n(if has-io\n ;; Async render: IO primitives proxied via /sx/io/<name>\n (do\n (try-async-eval-content content-src env\n (fn (rendered)\n (when rendered\n (swap-rendered-content target rendered pathname))))\n true)\n ;; Sync render: pure components, no IO\n (let ((rendered (try-eval-content content-src env)))\n (swap-rendered-content target rendered pathname)))" "lisp"))))
|
||||
(highlight ";; try-client-route reads io-deps from page registry\n(let ((io-deps (get match \"io-deps\"))\n (has-io (and io-deps (not (empty? io-deps)))))\n ;; Register IO deps as proxied primitives on demand\n (when has-io (register-io-deps io-deps))\n (if has-io\n ;; Async render: IO primitives proxied via /sx/io/<name>\n (do\n (try-async-eval-content content-src env\n (fn (rendered)\n (when rendered\n (swap-rendered-content target rendered pathname))))\n true)\n ;; Sync render: pure components, no IO\n (let ((rendered (try-eval-content content-src env)))\n (swap-rendered-content target rendered pathname))))" "lisp"))))
|
||||
|
||||
;; Architecture explanation
|
||||
(div :class "rounded-lg border border-blue-200 bg-blue-50 p-5 space-y-3"
|
||||
@@ -49,8 +49,8 @@
|
||||
(ol :class "list-decimal list-inside text-blue-800 space-y-2 text-sm"
|
||||
(li "Server renders the page — " (code "highlight") " runs Python directly")
|
||||
(li "Client receives component definitions including " (code "~async-io-demo-content"))
|
||||
(li "On client navigation, " (code "has-io") " flag routes to async renderer")
|
||||
(li "Async renderer encounters " (code "(highlight ...)") " — checks " (code "IO_PRIMITIVES"))
|
||||
(li "On client navigation, " (code "io-deps") " list routes to async renderer")
|
||||
(li (code "register-io-deps") " ensures each IO name is proxied via " (code "registerProxiedIo"))
|
||||
(li "Proxied call: " (code "fetch(\"/sx/io/highlight?_arg0=...&_arg1=lisp\")"))
|
||||
(li "Server runs highlight, returns SX source (colored span elements)")
|
||||
(li "Client parses SX → AST, async renderer recursively renders to DOM")))
|
||||
|
||||
Reference in New Issue
Block a user