From b7d95a8b4e16671aa5b68c6780ccff6456437aa4 Mon Sep 17 00:00:00 2001 From: giles Date: Sun, 1 Mar 2026 14:51:07 +0000 Subject: [PATCH] Fix sx.js component kwarg evaluation: distinguish expressions from data MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three issues with the eager kwarg evaluation in renderComponentDOM and renderStrComponent: 1. Data arrays (e.g. tags list of dicts) were being passed to sxEval which tried to call a dict as a function — causing blank pages. Fix: only evaluate arrays with a Symbol head (actual expressions); pass data arrays through as-is. 2. Expression arrays like (get t "src") inside map lambdas lost their scope when deferred — causing "get,t,src" URLs. Fix: eagerly evaluate these Symbol-headed expressions in the caller's env. 3. Bare symbol `t` used as boolean in editor.sx threw "Undefined symbol". Fix: use `true` literal instead. Co-Authored-By: Claude Opus 4.6 --- blog/sx/editor.sx | 2 +- shared/static/scripts/sx.js | 36 ++++++++++++++++++++++++++---------- 2 files changed, 27 insertions(+), 11 deletions(-) diff --git a/blog/sx/editor.sx b/blog/sx/editor.sx index 597c8ef..4a2cff4 100644 --- a/blog/sx/editor.sx +++ b/blog/sx/editor.sx @@ -38,7 +38,7 @@ (div :class "flex items-center gap-[16px] mt-[32px] pt-[16px] border-t border-stone-200" (select :name "status" :class "text-[14px] rounded-[4px] border border-stone-200 px-[8px] py-[6px] bg-white text-stone-600" - (option :value "draft" :selected t "Draft") + (option :value "draft" :selected true "Draft") (option :value "published" "Published")) (button :type "submit" :class "px-[20px] py-[6px] bg-stone-700 text-white text-[14px] rounded-[8px] hover:bg-stone-800 transition-colors cursor-pointer" create-label)))) diff --git a/shared/static/scripts/sx.js b/shared/static/scripts/sx.js index d517a25..ef55184 100644 --- a/shared/static/scripts/sx.js +++ b/shared/static/scripts/sx.js @@ -827,15 +827,23 @@ if (isKw(args[i]) && i + 1 < args.length) { // Evaluate kwarg values eagerly in the caller's env so expressions // like (get t "src") resolve while lambda params are still bound. - // Render-only forms (HTML tags, <>, ~comp) go through renderDOM instead. var v = args[i + 1]; if (typeof v === "string" || typeof v === "number" || - typeof v === "boolean" || isNil(v)) { + typeof v === "boolean" || isNil(v) || isKw(v)) { kwargs[args[i].name] = v; - } else if (_isRenderExpr(v)) { - kwargs[args[i].name] = renderDOM(v, env); - } else { + } else if (isSym(v)) { kwargs[args[i].name] = sxEval(v, env); + } else if (Array.isArray(v) && v.length && isSym(v[0])) { + // Expression with Symbol head — evaluate in caller's env. + // Render-only forms go through renderDOM; data exprs through sxEval. + if (_isRenderExpr(v)) { + kwargs[args[i].name] = renderDOM(v, env); + } else { + kwargs[args[i].name] = sxEval(v, env); + } + } else { + // Data arrays, dicts, etc — pass through as-is + kwargs[args[i].name] = v; } i += 2; } else { @@ -1115,15 +1123,23 @@ if (isKw(args[i]) && i + 1 < args.length) { // Evaluate kwarg values eagerly in the caller's env so expressions // like (get t "src") resolve while lambda params are still bound. - // Render-only forms (HTML tags, <>, ~comp) go through renderStr. var v = args[i + 1]; if (typeof v === "string" || typeof v === "number" || - typeof v === "boolean" || isNil(v)) { + typeof v === "boolean" || isNil(v) || isKw(v)) { kwargs[args[i].name] = v; - } else if (_isRenderExpr(v)) { - kwargs[args[i].name] = new RawHTML(renderStr(v, env)); - } else { + } else if (isSym(v)) { kwargs[args[i].name] = sxEval(v, env); + } else if (Array.isArray(v) && v.length && isSym(v[0])) { + // Expression with Symbol head — evaluate in caller's env. + // Render-only forms go through renderStr; data exprs through sxEval. + if (_isRenderExpr(v)) { + kwargs[args[i].name] = new RawHTML(renderStr(v, env)); + } else { + kwargs[args[i].name] = sxEval(v, env); + } + } else { + // Data arrays, dicts, etc — pass through as-is + kwargs[args[i].name] = v; } i += 2; } else { children.push(args[i]); i++; }