diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 4e33d60..5d45290 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -14,7 +14,7 @@ // ========================================================================= var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } }); - var SX_VERSION = "2026-03-13T02:18:19Z"; + var SX_VERSION = "2026-03-13T02:54:01Z"; function isNil(x) { return x === NIL || x === null || x === undefined; } function isSxTruthy(x) { return x !== false && !isNil(x); } @@ -87,6 +87,7 @@ SxSpread.prototype._spread = true; var _collectBuckets = {}; + var _provideStacks = {}; function isSym(x) { return x != null && x._sym === true; } function isKw(x) { return x != null && x._kw === true; } @@ -162,6 +163,35 @@ if (_collectBuckets[bucket]) _collectBuckets[bucket] = []; } + function providePush(name, value) { + if (!_provideStacks[name]) _provideStacks[name] = []; + _provideStacks[name].push({value: value !== undefined ? value : NIL, emitted: []}); + } + function providePop(name) { + if (_provideStacks[name] && _provideStacks[name].length) _provideStacks[name].pop(); + } + function sxContext(name) { + if (_provideStacks[name] && _provideStacks[name].length) { + return _provideStacks[name][_provideStacks[name].length - 1].value; + } + if (arguments.length > 1) return arguments[1]; + throw new Error("No provider for: " + name); + } + function sxEmit(name, value) { + if (_provideStacks[name] && _provideStacks[name].length) { + _provideStacks[name][_provideStacks[name].length - 1].emitted.push(value); + } else { + throw new Error("No provider for emit!: " + name); + } + return NIL; + } + function sxEmitted(name) { + if (_provideStacks[name] && _provideStacks[name].length) { + return _provideStacks[name][_provideStacks[name].length - 1].emitted.slice(); + } + return []; + } + function lambdaParams(f) { return f.params; } function lambdaBody(f) { return f.body; } function lambdaClosure(f) { return f.closure; } @@ -495,6 +525,12 @@ PRIMITIVES["collect!"] = sxCollect; PRIMITIVES["collected"] = sxCollected; PRIMITIVES["clear-collected!"] = sxClearCollected; + // provide/context/emit! — render-time dynamic scope + PRIMITIVES["provide-push!"] = providePush; + PRIMITIVES["provide-pop!"] = providePop; + PRIMITIVES["context"] = sxContext; + PRIMITIVES["emit!"] = sxEmit; + PRIMITIVES["emitted"] = sxEmitted; function isPrimitive(name) { return name in PRIMITIVES; } @@ -760,10 +796,10 @@ var args = rest(expr); return (isSxTruthy(!isSxTruthy(sxOr((typeOf(head) == "symbol"), (typeOf(head) == "lambda"), (typeOf(head) == "list")))) ? map(function(x) { return trampoline(evalExpr(x, env)); }, expr) : (isSxTruthy((typeOf(head) == "symbol")) ? (function() { var name = symbolName(head); - return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "deftype")) ? sfDeftype(args, env) : (isSxTruthy((name == "defeffect")) ? sfDefeffect(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { + return (isSxTruthy((name == "if")) ? sfIf(args, env) : (isSxTruthy((name == "when")) ? sfWhen(args, env) : (isSxTruthy((name == "cond")) ? sfCond(args, env) : (isSxTruthy((name == "case")) ? sfCase(args, env) : (isSxTruthy((name == "and")) ? sfAnd(args, env) : (isSxTruthy((name == "or")) ? sfOr(args, env) : (isSxTruthy((name == "let")) ? sfLet(args, env) : (isSxTruthy((name == "let*")) ? sfLet(args, env) : (isSxTruthy((name == "letrec")) ? sfLetrec(args, env) : (isSxTruthy((name == "lambda")) ? sfLambda(args, env) : (isSxTruthy((name == "fn")) ? sfLambda(args, env) : (isSxTruthy((name == "define")) ? sfDefine(args, env) : (isSxTruthy((name == "defcomp")) ? sfDefcomp(args, env) : (isSxTruthy((name == "defisland")) ? sfDefisland(args, env) : (isSxTruthy((name == "defmacro")) ? sfDefmacro(args, env) : (isSxTruthy((name == "defstyle")) ? sfDefstyle(args, env) : (isSxTruthy((name == "defhandler")) ? sfDefhandler(args, env) : (isSxTruthy((name == "defpage")) ? sfDefpage(args, env) : (isSxTruthy((name == "defquery")) ? sfDefquery(args, env) : (isSxTruthy((name == "defaction")) ? sfDefaction(args, env) : (isSxTruthy((name == "deftype")) ? sfDeftype(args, env) : (isSxTruthy((name == "defeffect")) ? sfDefeffect(args, env) : (isSxTruthy((name == "begin")) ? sfBegin(args, env) : (isSxTruthy((name == "do")) ? sfBegin(args, env) : (isSxTruthy((name == "quote")) ? sfQuote(args, env) : (isSxTruthy((name == "quasiquote")) ? sfQuasiquote(args, env) : (isSxTruthy((name == "->")) ? sfThreadFirst(args, env) : (isSxTruthy((name == "set!")) ? sfSetBang(args, env) : (isSxTruthy((name == "reset")) ? sfReset(args, env) : (isSxTruthy((name == "shift")) ? sfShift(args, env) : (isSxTruthy((name == "dynamic-wind")) ? sfDynamicWind(args, env) : (isSxTruthy((name == "provide")) ? sfProvide(args, env) : (isSxTruthy((name == "map")) ? hoMap(args, env) : (isSxTruthy((name == "map-indexed")) ? hoMapIndexed(args, env) : (isSxTruthy((name == "filter")) ? hoFilter(args, env) : (isSxTruthy((name == "reduce")) ? hoReduce(args, env) : (isSxTruthy((name == "some")) ? hoSome(args, env) : (isSxTruthy((name == "every?")) ? hoEvery(args, env) : (isSxTruthy((name == "for-each")) ? hoForEach(args, env) : (isSxTruthy((isSxTruthy(envHas(env, name)) && isMacro(envGet(env, name)))) ? (function() { var mac = envGet(env, name); return makeThunk(expandMacro(mac, args, env), env); -})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env))))))))))))))))))))))))))))))))))))))))); +})() : (isSxTruthy((isSxTruthy(renderActiveP()) && isRenderExpr(expr))) ? renderExpr(expr, env) : evalCall(head, args, env)))))))))))))))))))))))))))))))))))))))))); })() : evalCall(head, args, env))); })(); }; @@ -1170,6 +1206,18 @@ return append_b(inits, nth(binding, 1)); }, bindings) : reduce(function(acc, pai })(); })(); }; + // sf-provide + var sfProvide = function(args, env) { return (function() { + var name = trampoline(evalExpr(first(args), env)); + var val = trampoline(evalExpr(nth(args, 1), env)); + var bodyExprs = slice(args, 2); + var result = NIL; + providePush(name, val); + { var _c = bodyExprs; for (var _i = 0; _i < _c.length; _i++) { var e = _c[_i]; result = trampoline(evalExpr(e, env)); } } + providePop(name); + return result; +})(); }; + // expand-macro var expandMacro = function(mac, rawArgs, env) { return (function() { var local = envMerge(macroClosure(mac), env); @@ -1468,7 +1516,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m = var renderValueToHtml = function(val, env) { return (function() { var _m = typeOf(val); if (_m == "nil") return ""; if (_m == "string") return escapeHtml(val); if (_m == "number") return (String(val)); if (_m == "boolean") return (isSxTruthy(val) ? "true" : "false"); if (_m == "list") return renderListToHtml(val, env); if (_m == "raw-html") return rawHtmlContent(val); if (_m == "spread") return val; return escapeHtml((String(val))); })(); }; // RENDER_HTML_FORMS - var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each"]; + var RENDER_HTML_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "deftype", "defeffect", "map", "map-indexed", "filter", "for-each", "provide"]; // render-html-form? var isRenderHtmlForm = function(name) { return contains(RENDER_HTML_FORMS, name); }; @@ -1517,7 +1565,18 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return ""; if (_m = var f = trampoline(evalExpr(nth(expr, 1), env)); var coll = trampoline(evalExpr(nth(expr, 2), env)); return join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(item) { return (isSxTruthy(isLambda(f)) ? renderLambdaHtml(f, [item], env) : renderToHtml(apply(f, [item]), env)); }, coll))); -})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env)))))))))))); }; +})() : (isSxTruthy((name == "provide")) ? (function() { + var provName = trampoline(evalExpr(nth(expr, 1), env)); + var provVal = trampoline(evalExpr(nth(expr, 2), env)); + var bodyStart = 3; + var bodyCount = (len(expr) - 3); + providePush(provName, provVal); + return (function() { + var result = (isSxTruthy((bodyCount == 1)) ? renderToHtml(nth(expr, bodyStart), env) : join("", filter(function(r) { return !isSxTruthy(isSpread(r)); }, map(function(i) { return renderToHtml(nth(expr, i), env); }, range(bodyStart, (bodyStart + bodyCount)))))); + providePop(provName); + return result; +})(); +})() : renderValueToHtml(trampoline(evalExpr(expr, env)), env))))))))))))); }; // render-lambda-html var renderLambdaHtml = function(f, args, env) { return (function() { @@ -1722,7 +1781,7 @@ return (function() { var _m = typeOf(expr); if (_m == "number") return expr; if })(); }; // SPECIAL_FORM_NAMES - var SPECIAL_FORM_NAMES = ["if", "when", "cond", "case", "and", "or", "let", "let*", "lambda", "fn", "define", "defcomp", "defmacro", "defstyle", "defhandler", "defpage", "defquery", "defaction", "defrelation", "begin", "do", "quote", "quasiquote", "->", "set!", "letrec", "dynamic-wind", "defisland", "deftype", "defeffect"]; + var SPECIAL_FORM_NAMES = ["if", "when", "cond", "case", "and", "or", "let", "let*", "lambda", "fn", "define", "defcomp", "defmacro", "defstyle", "defhandler", "defpage", "defquery", "defaction", "defrelation", "begin", "do", "quote", "quasiquote", "->", "set!", "letrec", "dynamic-wind", "defisland", "deftype", "defeffect", "provide"]; // HO_FORM_NAMES var HO_FORM_NAMES = ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"]; @@ -1793,7 +1852,15 @@ return result; }, args); return append_b(results, aser(lambdaBody(f), local)); })() : invoke(f, item)); } } return (isSxTruthy(isEmpty(results)) ? NIL : results); -})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"), (name == "deftype"), (name == "defeffect"))) ? (trampoline(evalExpr(expr, env)), NIL) : trampoline(evalExpr(expr, env))))))))))))))); +})() : (isSxTruthy((name == "defisland")) ? (trampoline(evalExpr(expr, env)), serialize(expr)) : (isSxTruthy(sxOr((name == "define"), (name == "defcomp"), (name == "defmacro"), (name == "defstyle"), (name == "defhandler"), (name == "defpage"), (name == "defquery"), (name == "defaction"), (name == "defrelation"), (name == "deftype"), (name == "defeffect"))) ? (trampoline(evalExpr(expr, env)), NIL) : (isSxTruthy((name == "provide")) ? (function() { + var provName = trampoline(evalExpr(first(args), env)); + var provVal = trampoline(evalExpr(nth(args, 1), env)); + var result = NIL; + providePush(provName, provVal); + { var _c = slice(args, 2); for (var _i = 0; _i < _c.length; _i++) { var body = _c[_i]; result = aser(body, env); } } + providePop(provName); + return result; +})() : trampoline(evalExpr(expr, env)))))))))))))))); })(); }; // eval-case-aser @@ -1926,7 +1993,7 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme var renderDomUnknownComponent = function(name) { return error((String("Unknown component: ") + String(name))); }; // RENDER_DOM_FORMS - var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary"]; + var RENDER_DOM_FORMS = ["if", "when", "cond", "case", "let", "let*", "begin", "do", "define", "defcomp", "defisland", "defmacro", "defstyle", "defhandler", "map", "map-indexed", "filter", "for-each", "portal", "error-boundary", "provide"]; // render-dom-form? var isRenderDomForm = function(name) { return contains(RENDER_DOM_FORMS, name); }; @@ -2060,7 +2127,15 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme return domAppend(frag, val); })(); } } return frag; -})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns)))))))))))))); }; +})() : (isSxTruthy((name == "provide")) ? (function() { + var provName = trampoline(evalExpr(nth(expr, 1), env)); + var provVal = trampoline(evalExpr(nth(expr, 2), env)); + var frag = createFragment(); + providePush(provName, provVal); + { var _c = range(3, len(expr)); for (var _i = 0; _i < _c.length; _i++) { var i = _c[_i]; domAppend(frag, renderToDom(nth(expr, i), env, ns)); } } + providePop(provName); + return frag; +})() : renderToDom(trampoline(evalExpr(expr, env)), env, ns))))))))))))))); }; // render-lambda-dom var renderLambdaDom = function(f, args, env, ns) { return (function() { @@ -6453,6 +6528,11 @@ return (isSxTruthy((_batchDepth == 0)) ? (function() { collect: sxCollect, collected: sxCollected, clearCollected: sxClearCollected, + providePush: providePush, + providePop: providePop, + context: sxContext, + emit: sxEmit, + emitted: sxEmitted, _version: "ref-2.0 (boot+dom+engine+html+orchestration+parser+sx, bootstrap-compiled)" }; diff --git a/shared/sx/ref/adapter-async.sx b/shared/sx/ref/adapter-async.sx index 2e56652..5b47d82 100644 --- a/shared/sx/ref/adapter-async.sx +++ b/shared/sx/ref/adapter-async.sx @@ -341,7 +341,7 @@ (list "if" "when" "cond" "case" "let" "let*" "begin" "do" "define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler" "deftype" "defeffect" - "map" "map-indexed" "filter" "for-each")) + "map" "map-indexed" "filter" "for-each" "provide")) (define async-render-form? :effects [] (fn ((name :as string)) @@ -434,6 +434,20 @@ (filter (fn (r) (not (spread? r))) (async-map-fn-render f coll env ctx)))) + ;; provide — render-time dynamic scope + (= name "provide") + (let ((prov-name (async-eval (nth expr 1) env ctx)) + (prov-val (async-eval (nth expr 2) env ctx)) + (body-start 3) + (body-count (- (len expr) 3))) + (provide-push! prov-name prov-val) + (let ((result (if (= body-count 1) + (async-render (nth expr body-start) env ctx) + (let ((results (async-map-render (slice expr body-start) env ctx))) + (join "" (filter (fn (r) (not (spread? r))) results)))))) + (provide-pop! prov-name) + result)) + ;; Fallback :else (async-render (async-eval expr env ctx) env ctx)))) @@ -894,7 +908,7 @@ "define" "defcomp" "defmacro" "defstyle" "defhandler" "defpage" "defquery" "defaction" "begin" "do" "quote" "->" "set!" "defisland" - "deftype" "defeffect")) + "deftype" "defeffect" "provide")) (define ASYNC_ASER_HO_NAMES (list "map" "map-indexed" "filter" "for-each")) @@ -1032,6 +1046,17 @@ (= name "deftype") (= name "defeffect")) (do (async-eval expr env ctx) nil) + ;; provide — render-time dynamic scope + (= name "provide") + (let ((prov-name (async-eval (first args) env ctx)) + (prov-val (async-eval (nth args 1) env ctx)) + (result nil)) + (provide-push! prov-name prov-val) + (for-each (fn (body) (set! result (async-aser body env ctx))) + (slice args 2)) + (provide-pop! prov-name) + result) + ;; Fallback :else (async-eval expr env ctx))))) diff --git a/shared/sx/ref/adapter-dom.sx b/shared/sx/ref/adapter-dom.sx index 5ac9533..5c956a8 100644 --- a/shared/sx/ref/adapter-dom.sx +++ b/shared/sx/ref/adapter-dom.sx @@ -359,7 +359,7 @@ (list "if" "when" "cond" "case" "let" "let*" "begin" "do" "define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler" "map" "map-indexed" "filter" "for-each" "portal" - "error-boundary")) + "error-boundary" "provide")) (define render-dom-form? :effects [] (fn ((name :as string)) @@ -598,6 +598,19 @@ coll) frag) + ;; provide — render-time dynamic scope + (= name "provide") + (let ((prov-name (trampoline (eval-expr (nth expr 1) env))) + (prov-val (trampoline (eval-expr (nth expr 2) env))) + (frag (create-fragment))) + (provide-push! prov-name prov-val) + (for-each + (fn (i) + (dom-append frag (render-to-dom (nth expr i) env ns))) + (range 3 (len expr))) + (provide-pop! prov-name) + frag) + ;; Fallback :else (render-to-dom (trampoline (eval-expr expr env)) env ns)))) diff --git a/shared/sx/ref/adapter-html.sx b/shared/sx/ref/adapter-html.sx index 64de4cd..3ad5f9b 100644 --- a/shared/sx/ref/adapter-html.sx +++ b/shared/sx/ref/adapter-html.sx @@ -56,7 +56,7 @@ (list "if" "when" "cond" "case" "let" "let*" "begin" "do" "define" "defcomp" "defisland" "defmacro" "defstyle" "defhandler" "deftype" "defeffect" - "map" "map-indexed" "filter" "for-each")) + "map" "map-indexed" "filter" "for-each" "provide")) (define render-html-form? :effects [] (fn ((name :as string)) @@ -237,6 +237,21 @@ (render-to-html (apply f (list item)) env))) coll)))) + ;; provide — render-time dynamic scope + (= name "provide") + (let ((prov-name (trampoline (eval-expr (nth expr 1) env))) + (prov-val (trampoline (eval-expr (nth expr 2) env))) + (body-start 3) + (body-count (- (len expr) 3))) + (provide-push! prov-name prov-val) + (let ((result (if (= body-count 1) + (render-to-html (nth expr body-start) env) + (join "" (filter (fn (r) (not (spread? r))) + (map (fn (i) (render-to-html (nth expr i) env)) + (range body-start (+ body-start body-count)))))))) + (provide-pop! prov-name) + result)) + ;; Fallback :else (render-value-to-html (trampoline (eval-expr expr env)) env)))) diff --git a/shared/sx/ref/adapter-sx.sx b/shared/sx/ref/adapter-sx.sx index 677ed5a..fdc1190 100644 --- a/shared/sx/ref/adapter-sx.sx +++ b/shared/sx/ref/adapter-sx.sx @@ -174,7 +174,7 @@ "defhandler" "defpage" "defquery" "defaction" "defrelation" "begin" "do" "quote" "quasiquote" "->" "set!" "letrec" "dynamic-wind" "defisland" - "deftype" "defeffect")) + "deftype" "defeffect" "provide")) (define HO_FORM_NAMES (list "map" "map-indexed" "filter" "reduce" @@ -312,6 +312,17 @@ (= name "deftype") (= name "defeffect")) (do (trampoline (eval-expr expr env)) nil) + ;; provide — render-time dynamic scope + (= name "provide") + (let ((prov-name (trampoline (eval-expr (first args) env))) + (prov-val (trampoline (eval-expr (nth args 1) env))) + (result nil)) + (provide-push! prov-name prov-val) + (for-each (fn (body) (set! result (aser body env))) + (slice args 2)) + (provide-pop! prov-name) + result) + ;; Everything else — evaluate normally :else (trampoline (eval-expr expr env)))))) diff --git a/shared/sx/ref/bootstrap_py.py b/shared/sx/ref/bootstrap_py.py index afae46e..ea9f089 100644 --- a/shared/sx/ref/bootstrap_py.py +++ b/shared/sx/ref/bootstrap_py.py @@ -293,6 +293,11 @@ class PyEmitter: "collect!": "sx_collect", "collected": "sx_collected", "clear-collected!": "sx_clear_collected", + "provide-push!": "provide_push", + "provide-pop!": "provide_pop", + "context": "sx_context", + "emit!": "sx_emit", + "emitted": "sx_emitted", "is-raw-html?": "is_raw_html", "async-coroutine?": "is_async_coroutine", "async-await!": "async_await", diff --git a/shared/sx/ref/boundary.sx b/shared/sx/ref/boundary.sx index bc1d66d..49a7761 100644 --- a/shared/sx/ref/boundary.sx +++ b/shared/sx/ref/boundary.sx @@ -371,3 +371,51 @@ :effects [mutation] :doc "Clear a named render-time accumulator bucket. Used at flush points after emitting collected values (e.g. after writing a