Collapse reactive islands into scopes: replace TrackingContext and *island-scope* with scope-push!/scope-pop!/context

Reactive tracking (deref/computed/effect dep discovery) and island lifecycle
now use the general scoped effects system instead of parallel infrastructure.
Two scope names: "sx-reactive" for tracking context, "sx-island-scope" for
island disposable collection. Eliminates ~98 net lines: _TrackingContext class,
7 tracking context platform functions (Python + JS), *island-scope* global,
and corresponding RENAME_MAP entries. All 20 signal tests pass (17 original +
3 new scope integration tests), plus CEK/continuation/type tests clean.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 23:09:09 +00:00
parent 1765216335
commit dcc73a68d5
11 changed files with 330 additions and 268 deletions

View File

@@ -59,7 +59,7 @@
;; Signal → reactive text in island scope, deref outside
:else
(if (signal? expr)
(if *island-scope*
(if (context "sx-island-scope" nil)
(reactive-text expr)
(create-text-node (str (deref expr))))
(create-text-node (str expr))))))
@@ -143,7 +143,7 @@
(render-dom-element name args env ns)
;; deref in island scope → reactive text node
(and (= name "deref") *island-scope*)
(and (= name "deref") (context "sx-island-scope" nil))
(let ((sig-or-val (trampoline (eval-expr (first args) env))))
(if (signal? sig-or-val)
(reactive-text sig-or-val)
@@ -215,7 +215,7 @@
;; Inside island scope: reactive attribute binding.
;; The effect tracks signal deps automatically — if none
;; are deref'd, it fires once and never again (safe).
*island-scope*
(context "sx-island-scope" nil)
(reactive-attr el attr-name
(fn () (trampoline (eval-expr attr-expr env))))
;; Static attribute (outside islands)
@@ -237,7 +237,7 @@
(let ((child (render-to-dom arg env new-ns)))
(cond
;; Reactive spread: track signal deps, update attrs on change
(and (spread? child) *island-scope*)
(and (spread? child) (context "sx-island-scope" nil))
(reactive-spread el (fn () (render-to-dom arg env new-ns)))
;; Static spread: already emitted via provide, skip
(spread? child) nil
@@ -392,7 +392,7 @@
(cond
;; if — reactive inside islands (re-renders when signal deps change)
(= name "if")
(if *island-scope*
(if (context "sx-island-scope" nil)
(let ((marker (create-comment "r-if"))
(current-nodes (list))
(initial-result nil))
@@ -440,7 +440,7 @@
;; when — reactive inside islands
(= name "when")
(if *island-scope*
(if (context "sx-island-scope" nil)
(let ((marker (create-comment "r-when"))
(current-nodes (list))
(initial-result nil))
@@ -486,7 +486,7 @@
;; cond — reactive inside islands
(= name "cond")
(if *island-scope*
(if (context "sx-island-scope" nil)
(let ((marker (create-comment "r-cond"))
(current-nodes (list))
(initial-result nil))
@@ -563,7 +563,7 @@
;; map — reactive-list when mapping over a signal inside an island
(= name "map")
(let ((coll-expr (nth expr 2)))
(if (and *island-scope*
(if (and (context "sx-island-scope" nil)
(= (type-of coll-expr) "list")
(> (len coll-expr) 1)
(= (type-of (first coll-expr)) "symbol")
@@ -1168,7 +1168,7 @@
(dom-set-attr container "data-sx-boundary" "true")
;; The entire body is rendered inside ONE effect + try-catch.
;; Body renders WITHOUT *island-scope* so that if/when/cond use static
;; Body renders WITHOUT island scope so that if/when/cond use static
;; paths — their signal reads become direct deref calls tracked by THIS
;; effect. Errors from signal changes throw synchronously within try-catch.
;; The error boundary's own effect handles all reactivity for its subtree.
@@ -1179,31 +1179,30 @@
;; Clear container
(dom-set-prop container "innerHTML" "")
;; Save and clear island scope BEFORE try-catch so it can be
;; restored in both success and error paths.
(let ((saved-scope *island-scope*))
(set! *island-scope* nil)
(try-catch
(fn ()
;; Body renders statically — signal reads tracked by THIS effect,
;; throws propagate to our try-catch.
(let ((frag (create-fragment)))
(for-each
(fn (child)
(dom-append frag (render-to-dom child env ns)))
body-exprs)
(dom-append container frag))
(set! *island-scope* saved-scope))
(fn (err)
;; Restore scope first, then render fallback
(set! *island-scope* saved-scope)
;; Push nil island scope to suppress reactive rendering in body.
;; Pop in both success and error paths.
(scope-push! "sx-island-scope" nil)
(try-catch
(fn ()
;; Body renders statically — signal reads tracked by THIS effect,
;; throws propagate to our try-catch.
(let ((frag (create-fragment)))
(for-each
(fn (child)
(dom-append frag (render-to-dom child env ns)))
body-exprs)
(dom-append container frag))
(scope-pop! "sx-island-scope"))
(fn (err)
;; Pop scope first, then render fallback
(scope-pop! "sx-island-scope")
(let ((fallback-fn (trampoline (eval-expr fallback-expr env)))
(retry-fn (fn () (swap! retry-version (fn (n) (+ n 1))))))
(let ((fallback-dom
(if (lambda? fallback-fn)
(render-lambda-dom fallback-fn (list err retry-fn) env ns)
(render-to-dom (apply fallback-fn (list err retry-fn)) env ns))))
(dom-append container fallback-dom))))))))
(dom-append container fallback-dom)))))))
container)))