Merge branch 'worktree-api-urls' into macros

This commit is contained in:
2026-03-13 04:37:53 +00:00
7 changed files with 69 additions and 13 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-13T04:08:59Z";
var SX_VERSION = "2026-03-13T04:16:14Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }
@@ -1934,6 +1934,8 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
return assoc(state, "skip", true, "i", (get(state, "i") + 1));
})() : ((isSxTruthy(!isSxTruthy(contains(VOID_ELEMENTS, tag))) ? (function() {
var child = renderToDom(arg, env, newNs);
if (child && child._spread) console.log("[sx-debug] SPREAD detected in element child:", tag, child.attrs);
if (child && !child._spread && child.nodeType === 11 && arg && arg[0] && arg[0].name && arg[0].name.indexOf("cssx") >= 0) console.log("[sx-debug] ~cssx child NOT spread:", tag, "type:", typeof child, "nodeType:", child.nodeType, "childNodes:", child.childNodes ? child.childNodes.length : "N/A");
return (isSxTruthy(isSpread(child)) ? forEach(function(key) { return (function() {
var val = dictGet(spreadAttrs(child), key);
return (isSxTruthy((key == "class")) ? (function() {
@@ -1974,7 +1976,9 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
return envSet(local, "children", childFrag);
})();
}
return renderToDom(componentBody(comp), local, ns);
var _compResult = renderToDom(componentBody(comp), local, ns);
if (componentName(comp).indexOf("cssx") >= 0) console.log("[sx-debug] renderDomComponent", componentName(comp), "returned:", _compResult, "isSpread:", isSpread(_compResult), "type:", typeOf(_compResult), "_spread:", _compResult && _compResult._spread);
return _compResult;
})();
})(); };
@@ -2017,8 +2021,10 @@ return (function() { var _m = typeOf(expr); if (_m == "nil") return createFragme
var condVal = trampoline(evalExpr(nth(expr, 1), env));
return (isSxTruthy(condVal) ? renderToDom(nth(expr, 2), env, ns) : (isSxTruthy((len(expr) > 3)) ? renderToDom(nth(expr, 3), env, ns) : createFragment()));
})();
if (result && result._spread) console.log("[sx-debug] reactive-if result IS a spread:", result.attrs, "— will be LOST in fragment wrapping");
return (isSxTruthy(domParent(marker)) ? (forEach(function(n) { return domRemove(n); }, currentNodes), (currentNodes = (isSxTruthy(domIsFragment(result)) ? domChildNodes(result) : [result])), domInsertAfter(marker, result)) : (initialResult = result));
})(); });
if (initialResult && initialResult._spread) console.log("[sx-debug] reactive-if initialResult IS a spread — returning frag instead of spread!");
return (function() {
var frag = createFragment();
domAppend(frag, marker);
@@ -3601,7 +3607,7 @@ callExpr.push(dictGet(kwargs, k)); } }
// transitive-deps-walk
var transitiveDepsWalk = function(n, seen, env) { return (isSxTruthy(!isSxTruthy(contains(seen, n))) ? (append_b(seen, n), (function() {
var val = envGet(env, n);
return (isSxTruthy((typeOf(val) == "component")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(componentBody(val))) : (isSxTruthy((typeOf(val) == "macro")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(macroBody(val))) : NIL));
return (isSxTruthy(sxOr((typeOf(val) == "component"), (typeOf(val) == "island"))) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(componentBody(val))) : (isSxTruthy((typeOf(val) == "macro")) ? forEach(function(ref) { return transitiveDepsWalk(ref, seen, env); }, scanRefs(macroBody(val))) : NIL));
})()) : NIL); };
// transitive-deps
@@ -3615,7 +3621,7 @@ callExpr.push(dictGet(kwargs, k)); } }
// compute-all-deps
var computeAllDeps = function(env) { return forEach(function(name) { return (function() {
var val = envGet(env, name);
return (isSxTruthy((typeOf(val) == "component")) ? componentSetDeps(val, transitiveDeps(name, env)) : NIL);
return (isSxTruthy(sxOr((typeOf(val) == "component"), (typeOf(val) == "island"))) ? componentSetDeps(val, transitiveDeps(name, env)) : NIL);
})(); }, envComponents(env)); };
// scan-components-from-source

View File

@@ -73,7 +73,7 @@
(append! seen n)
(let ((val (env-get env n)))
(cond
(= (type-of val) "component")
(or (= (type-of val) "component") (= (type-of val) "island"))
(for-each (fn ((ref :as string)) (transitive-deps-walk ref seen env))
(scan-refs (component-body val)))
(= (type-of val) "macro")
@@ -105,7 +105,7 @@
(for-each
(fn ((name :as string))
(let ((val (env-get env name)))
(when (= (type-of val) "component")
(when (or (= (type-of val) "component") (= (type-of val) "island"))
(component-set-deps! val (transitive-deps name env)))))
(env-components env))))

View File

@@ -2757,7 +2757,7 @@ def transitive_deps_walk(n, seen, env):
if sx_truthy((not sx_truthy(contains_p(seen, n)))):
seen.append(n)
val = env_get(env, n)
if sx_truthy((type_of(val) == 'component')):
if sx_truthy(((type_of(val) == 'component') if sx_truthy((type_of(val) == 'component')) else (type_of(val) == 'island'))):
for ref in scan_refs(component_body(val)):
transitive_deps_walk(ref, seen, env)
return NIL
@@ -2780,7 +2780,7 @@ def transitive_deps(name, env):
def compute_all_deps(env):
for name in env_components(env):
val = env_get(env, name)
if sx_truthy((type_of(val) == 'component')):
if sx_truthy(((type_of(val) == 'component') if sx_truthy((type_of(val) == 'component')) else (type_of(val) == 'island'))):
component_set_deps(val, transitive_deps(name, env))
return NIL

View File

@@ -36,6 +36,13 @@
(defcomp ~dep-island ()
(div "no deps"))
;; Islands with dependencies — defisland bodies must be scanned
(defisland ~dep-island-with-child ()
(div (~dep-leaf) "island content"))
(defisland ~dep-island-with-chain ()
(div (~dep-branch) "deep island"))
;; --------------------------------------------------------------------------
;; 1. scan-refs — finds component references in AST nodes
@@ -145,6 +152,15 @@
(deftest "accepts name without tilde"
(let ((deps (transitive-deps "dep-branch" (test-env))))
(assert-contains "~dep-leaf" deps)))
(deftest "island direct dep scanned"
(let ((deps (transitive-deps "~dep-island-with-child" (test-env))))
(assert-contains "~dep-leaf" deps)))
(deftest "island transitive deps scanned"
(let ((deps (transitive-deps "~dep-island-with-chain" (test-env))))
(assert-contains "~dep-branch" deps)
(assert-contains "~dep-leaf" deps))))
@@ -173,7 +189,13 @@
(deftest "handles multiple top-level components"
(let ((needed (components-needed "(div (~dep-leaf) (~dep-island))" (test-env))))
(assert-contains "~dep-leaf" needed)
(assert-contains "~dep-island" needed))))
(assert-contains "~dep-island" needed)))
(deftest "island deps included in page bundle"
(let ((needed (components-needed "(~dep-island-with-chain)" (test-env))))
(assert-contains "~dep-island-with-chain" needed)
(assert-contains "~dep-branch" needed)
(assert-contains "~dep-leaf" needed))))
;; --------------------------------------------------------------------------

View File

@@ -476,10 +476,12 @@
(classes (map (fn (r) (get r "cls")) valid))
(rules (map (fn (r) (get r "rule")) valid))
(_ (for-each (fn (rule) (collect! "cssx" rule)) rules)))
;; Return spread: injects class + data-tw onto parent element
(if (empty? classes)
nil
(make-spread {"class" (join " " classes)
;; Return spread: injects class + data-tw onto parent element.
;; The if is inside make-spread's arg so it goes through eval-expr
;; (not render-to-dom), avoiding reactive-if wrapping in islands.
(make-spread (if (empty? classes)
{}
{"class" (join " " classes)
"data-tw" (or tokens "")}))))

View File

@@ -113,6 +113,17 @@ class TestTransitiveDeps:
deps = transitive_deps("~card", env)
assert "~unknown" in deps
def test_island_deps_scanned(self):
"""Island bodies must be scanned for component dependencies."""
env = make_env(
'(defcomp ~leaf (&key) (span "leaf"))',
'(defcomp ~branch (&key) (div (~leaf)))',
'(defisland ~my-island () (div (~branch) "island"))',
)
deps = transitive_deps("~my-island", env)
assert "~branch" in deps
assert "~leaf" in deps
def test_without_tilde_prefix(self):
env = make_env(
'(defcomp ~card (&key) (div (~shared:misc/badge)))',

View File

@@ -705,6 +705,21 @@ class TestParityDeps:
ref_d = ref_env[key].deps
assert set(hw_d) == set(ref_d), f"Deps mismatch for {key}"
def test_transitive_deps_island(self):
"""Island bodies must be scanned for component deps."""
from shared.sx.deps import _transitive_deps_fallback
from shared.sx.ref.sx_ref import transitive_deps as ref_td
hw_env, ref_env = self._make_envs(
'(defcomp ~leaf (&key) (span "leaf"))',
'(defcomp ~branch (&key) (div (~leaf)))',
'(defisland ~my-island () (div (~branch) "island content"))',
)
hw_deps = _transitive_deps_fallback("~my-island", hw_env)
ref_deps = set(ref_td("~my-island", ref_env))
assert hw_deps == ref_deps
assert "~branch" in ref_deps
assert "~leaf" in ref_deps
def test_scan_components_from_sx(self):
from shared.sx.deps import _scan_components_from_sx_fallback
from shared.sx.ref.sx_ref import scan_components_from_source as ref_sc