Add :effects annotations to all spec files and update bootstrappers

Bootstrappers (bootstrap_py.py, js.sx) now skip :effects keyword in
define forms, enabling effect annotations throughout the spec without
changing generated output.

Annotated 180+ functions across 14 spec files:
- signals.sx: signal/deref [] pure, reset!/swap!/effect/batch [mutation]
- engine.sx: parse-* [] pure, morph-*/swap-* [mutation io]
- orchestration.sx: all [mutation io] (browser event binding)
- adapter-html.sx: render-* [render]
- adapter-dom.sx: render-* [render], reactive-* [render mutation]
- adapter-sx.sx: aser-* [render]
- adapter-async.sx: async-render-*/async-aser-* [render io]
- parser.sx: all [] pure
- render.sx: predicates [] pure, process-bindings [mutation]
- boot.sx: all [mutation io] (browser init)
- deps.sx: scan-*/transitive-* [] pure, compute-all-* [mutation]
- router.sx: all [] pure (URL matching)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 23:22:34 +00:00
parent 0f9b449315
commit 2f42e8826c
16 changed files with 274 additions and 259 deletions

View File

@@ -18,7 +18,7 @@
;; render-to-dom — main entry point
;; --------------------------------------------------------------------------
(define render-to-dom
(define render-to-dom :effects [render]
(fn (expr (env :as dict) (ns :as string))
(set-render-active! true)
(case (type-of expr)
@@ -66,7 +66,7 @@
;; render-dom-list — dispatch on list head
;; --------------------------------------------------------------------------
(define render-dom-list
(define render-dom-list :effects [render]
(fn (expr (env :as dict) (ns :as string))
(let ((head (first expr)))
(cond
@@ -165,7 +165,7 @@
;; render-dom-element — create a DOM element with attrs and children
;; --------------------------------------------------------------------------
(define render-dom-element
(define render-dom-element :effects [render]
(fn ((tag :as string) (args :as list) (env :as dict) (ns :as string))
;; Detect namespace from tag
(let ((new-ns (cond (= tag "svg") SVG_NS
@@ -236,7 +236,7 @@
;; render-dom-component — expand and render a component
;; --------------------------------------------------------------------------
(define render-dom-component
(define render-dom-component :effects [render]
(fn ((comp :as component) (args :as list) (env :as dict) (ns :as string))
;; Parse kwargs and children, bind into component env, render body.
(let ((kwargs (dict))
@@ -283,7 +283,7 @@
;; render-dom-fragment — render children into a DocumentFragment
;; --------------------------------------------------------------------------
(define render-dom-fragment
(define render-dom-fragment :effects [render]
(fn ((args :as list) (env :as dict) (ns :as string))
(let ((frag (create-fragment)))
(for-each
@@ -296,7 +296,7 @@
;; render-dom-raw — insert unescaped content
;; --------------------------------------------------------------------------
(define render-dom-raw
(define render-dom-raw :effects [render]
(fn ((args :as list) (env :as dict))
(let ((frag (create-fragment)))
(for-each
@@ -317,7 +317,7 @@
;; render-dom-unknown-component — visible warning element
;; --------------------------------------------------------------------------
(define render-dom-unknown-component
(define render-dom-unknown-component :effects [render]
(fn ((name :as string))
(error (str "Unknown component: " name))))
@@ -334,11 +334,11 @@
"map" "map-indexed" "filter" "for-each" "portal"
"error-boundary"))
(define render-dom-form?
(define render-dom-form? :effects []
(fn ((name :as string))
(contains? RENDER_DOM_FORMS name)))
(define dispatch-render-form
(define dispatch-render-form :effects [render]
(fn ((name :as string) expr (env :as dict) (ns :as string))
(cond
;; if — reactive inside islands (re-renders when signal deps change)
@@ -580,7 +580,7 @@
;; render-lambda-dom — render a lambda body in DOM context
;; --------------------------------------------------------------------------
(define render-lambda-dom
(define render-lambda-dom :effects [render]
(fn ((f :as lambda) (args :as list) (env :as dict) (ns :as string))
;; Bind lambda params and render body as DOM
(let ((local (env-merge (lambda-closure f) env)))
@@ -604,7 +604,7 @@
;; - Attribute bindings: (deref sig) in attr → reactive attribute
;; - Conditional fragments: (when (deref sig) ...) → reactive show/hide
(define render-dom-island
(define render-dom-island :effects [render mutation]
(fn ((island :as island) (args :as list) (env :as dict) (ns :as string))
;; Parse kwargs and children (same as component)
(let ((kwargs (dict))
@@ -678,7 +678,7 @@
;;
;; Supports :tag keyword to change wrapper element (default "div").
(define render-dom-lake
(define render-dom-lake :effects [render]
(fn ((args :as list) (env :as dict) (ns :as string))
(let ((lake-id nil)
(lake-tag "div")
@@ -722,7 +722,7 @@
;; Renders as <div data-sx-marsh="name">children</div>.
;; Stores the island env and transform on the element for morph retrieval.
(define render-dom-marsh
(define render-dom-marsh :effects [render]
(fn ((args :as list) (env :as dict) (ns :as string))
(let ((marsh-id nil)
(marsh-tag "div")
@@ -769,7 +769,7 @@
;; reactive-text — create a text node bound to a signal
;; Used when (deref sig) appears in a text position inside an island.
(define reactive-text
(define reactive-text :effects [render mutation]
(fn (sig)
(let ((node (create-text-node (str (deref sig)))))
(effect (fn ()
@@ -780,7 +780,7 @@
;; Used when an attribute value contains (deref sig) inside an island.
;; Marks the attribute name on the element via data-sx-reactive-attrs so
;; the morph algorithm knows not to overwrite it with server content.
(define reactive-attr
(define reactive-attr :effects [render mutation]
(fn (el (attr-name :as string) (compute-fn :as lambda))
;; Mark this attribute as reactively managed
(let ((existing (or (dom-get-attr el "data-sx-reactive-attrs") ""))
@@ -801,7 +801,7 @@
;; reactive-fragment — conditionally render a fragment based on a signal
;; Used for (when (deref sig) ...) or (if (deref sig) ...) inside an island.
(define reactive-fragment
(define reactive-fragment :effects [render mutation]
(fn ((test-fn :as lambda) (render-fn :as lambda) (env :as dict) (ns :as string))
(let ((marker (create-comment "island-fragment"))
(current-nodes (list)))
@@ -823,13 +823,13 @@
;; existing DOM nodes are reused across updates. Only additions, removals,
;; and reorderings touch the DOM. Without keys, falls back to clear+rerender.
(define render-list-item
(define render-list-item :effects [render]
(fn ((map-fn :as lambda) item (env :as dict) (ns :as string))
(if (lambda? map-fn)
(render-lambda-dom map-fn (list item) env ns)
(render-to-dom (apply map-fn (list item)) env ns))))
(define extract-key
(define extract-key :effects [render]
(fn (node (index :as number))
;; Extract key from rendered node: :key attr, data-key, or index fallback
(let ((k (dom-get-attr node "key")))
@@ -838,7 +838,7 @@
(let ((dk (dom-get-data node "key")))
(if dk (str dk) (str "__idx_" index)))))))
(define reactive-list
(define reactive-list :effects [render mutation]
(fn ((map-fn :as lambda) (items-sig :as signal) (env :as dict) (ns :as string))
(let ((container (create-fragment))
(marker (create-comment "island-list"))
@@ -924,7 +924,7 @@
;;
;; Handles: input[text/number/email/...], textarea, select, checkbox, radio
(define bind-input
(define bind-input :effects [render mutation]
(fn (el (sig :as signal))
(let ((input-type (lower (or (dom-get-attr el "type") "")))
(is-checkbox (or (= input-type "checkbox")
@@ -959,7 +959,7 @@
;; position. Registers a disposer to clean up portal content on island
;; teardown.
(define render-dom-portal
(define render-dom-portal :effects [render]
(fn ((args :as list) (env :as dict) (ns :as string))
(let ((selector (trampoline (eval-expr (first args) env)))
(target (or (dom-query selector)
@@ -999,7 +999,7 @@
;; (fn (err retry) ...)
;; Calling (retry) re-renders the body, replacing the fallback.
(define render-dom-error-boundary
(define render-dom-error-boundary :effects [render]
(fn ((args :as list) (env :as dict) (ns :as string))
(let ((fallback-expr (first args))
(body-exprs (rest args))