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

@@ -31,7 +31,7 @@
;; Parses the sx-trigger attribute value into a list of trigger descriptors.
;; Each descriptor is a dict with "event" and "modifiers" keys.
(define parse-time
(define parse-time :effects []
(fn ((s :as string))
;; Parse time string: "2s" → 2000, "500ms" → 500
;; Uses nested if (not cond) because cond misclassifies 2-element
@@ -42,7 +42,7 @@
(parse-int s 0))))))
(define parse-trigger-spec
(define parse-trigger-spec :effects []
(fn ((spec :as string))
;; Parse "click delay:500ms once,change" → list of trigger descriptors
(if (nil? spec)
@@ -80,7 +80,7 @@
raw-parts))))))
(define default-trigger
(define default-trigger :effects []
(fn ((tag-name :as string))
;; Default trigger for element type
(cond
@@ -98,7 +98,7 @@
;; Verb extraction
;; --------------------------------------------------------------------------
(define get-verb-info
(define get-verb-info :effects [io]
(fn (el)
;; Check element for sx-get, sx-post, etc. Returns (dict "method" "url") or nil.
(some
@@ -114,7 +114,7 @@
;; Request header building
;; --------------------------------------------------------------------------
(define build-request-headers
(define build-request-headers :effects [io]
(fn (el (loaded-components :as list) (css-hash :as string))
;; Build the SX request headers dict
(let ((headers (dict
@@ -150,7 +150,7 @@
;; Response header processing
;; --------------------------------------------------------------------------
(define process-response-headers
(define process-response-headers :effects []
(fn ((get-header :as lambda))
;; Extract all SX response header directives into a dict.
;; get-header is (fn (name) → string or nil).
@@ -174,7 +174,7 @@
;; Swap specification parsing
;; --------------------------------------------------------------------------
(define parse-swap-spec
(define parse-swap-spec :effects []
(fn ((raw-swap :as string) (global-transitions? :as boolean))
;; Parse "innerHTML transition:true" → dict with style + transition flag
(let ((parts (split (or raw-swap DEFAULT_SWAP) " "))
@@ -193,7 +193,7 @@
;; Retry logic
;; --------------------------------------------------------------------------
(define parse-retry-spec
(define parse-retry-spec :effects []
(fn ((retry-attr :as string))
;; Parse "exponential:1000:30000" → spec dict or nil
(if (nil? retry-attr)
@@ -205,7 +205,7 @@
"cap-ms" (parse-int (nth parts 2) 30000))))))
(define next-retry-ms
(define next-retry-ms :effects []
(fn ((current-ms :as number) (cap-ms :as number))
;; Exponential backoff: double current, cap at max
(min (* current-ms 2) cap-ms)))
@@ -215,7 +215,7 @@
;; Form parameter filtering
;; --------------------------------------------------------------------------
(define filter-params
(define filter-params :effects []
(fn ((params-spec :as string) (all-params :as list))
;; Filter form parameters by sx-params spec.
;; all-params is a list of (key value) pairs.
@@ -239,7 +239,7 @@
;; Target resolution
;; --------------------------------------------------------------------------
(define resolve-target
(define resolve-target :effects [io]
(fn (el)
;; Resolve the swap target for an element
(let ((sel (dom-get-attr el "sx-target")))
@@ -253,7 +253,7 @@
;; Optimistic updates
;; --------------------------------------------------------------------------
(define apply-optimistic
(define apply-optimistic :effects [mutation io]
(fn (el)
;; Apply optimistic update preview. Returns state for reverting, or nil.
(let ((directive (dom-get-attr el "sx-optimistic")))
@@ -278,7 +278,7 @@
state)))))
(define revert-optimistic
(define revert-optimistic :effects [mutation io]
(fn ((state :as dict))
;; Revert an optimistic update
(when state
@@ -299,7 +299,7 @@
;; Out-of-band swap identification
;; --------------------------------------------------------------------------
(define find-oob-swaps
(define find-oob-swaps :effects [mutation io]
(fn (container)
;; Find elements marked for out-of-band swapping.
;; Returns list of (dict "element" el "swap-type" type "target-id" id).
@@ -329,7 +329,7 @@
;; preserving event listeners, focus, scroll position, and form state
;; on keyed (id) elements.
(define morph-node
(define morph-node :effects [mutation io]
(fn (old-node new-node)
;; Morph old-node to match new-node, preserving listeners/state.
(cond
@@ -371,7 +371,7 @@
(morph-children old-node new-node))))))
(define sync-attrs
(define sync-attrs :effects [mutation io]
(fn (old-el new-el)
;; Sync attributes from new to old, but skip reactively managed attrs.
;; data-sx-reactive-attrs="style,class" means those attrs are owned by
@@ -398,7 +398,7 @@
(dom-attr-list old-el)))))
(define morph-children
(define morph-children :effects [mutation io]
(fn (old-parent new-parent)
;; Reconcile children of old-parent to match new-parent.
;; Keyed elements (with id) are matched and moved in-place.
@@ -472,7 +472,7 @@
;; - Lakes = server substance (content, morphed)
;; - The morph = Aufhebung (cancellation/preservation/elevation of both)
(define morph-island-children
(define morph-island-children :effects [mutation io]
(fn (old-island new-island)
;; Find all lake and marsh slots in both old and new islands
(let ((old-lakes (dom-query-all old-island "[data-sx-lake]"))
@@ -522,7 +522,7 @@
;; as SX and rendered in the island's signal context. If the marsh has a
;; :transform function, it reshapes the content before evaluation.
(define morph-marsh
(define morph-marsh :effects [mutation io]
(fn (old-marsh new-marsh island-el)
(let ((transform (dom-get-data old-marsh "sx-marsh-transform"))
(env (dom-get-data old-marsh "sx-marsh-env"))
@@ -555,7 +555,7 @@
;;
;; Values are JSON-parsed: "7" → 7, "\"hello\"" → "hello", "true" → true.
(define process-signal-updates
(define process-signal-updates :effects [mutation io]
(fn (root)
(let ((signal-els (dom-query-all root "[data-sx-signal]")))
(for-each
@@ -576,7 +576,7 @@
;; Swap dispatch
;; --------------------------------------------------------------------------
(define swap-dom-nodes
(define swap-dom-nodes :effects [mutation io]
(fn (target new-nodes (strategy :as string))
;; Execute a swap strategy on live DOM nodes.
;; new-nodes is typically a DocumentFragment or Element.
@@ -630,7 +630,7 @@
(morph-children target wrapper))))))
(define insert-remaining-siblings
(define insert-remaining-siblings :effects [mutation io]
(fn (parent ref-node sib)
;; Insert sibling chain after ref-node
(when sib
@@ -643,7 +643,7 @@
;; String-based swap (fallback for HTML responses)
;; --------------------------------------------------------------------------
(define swap-html-string
(define swap-html-string :effects [mutation io]
(fn (target (html :as string) (strategy :as string))
;; Execute a swap strategy using an HTML string (DOMParser pipeline).
(case strategy
@@ -674,7 +674,7 @@
;; History management
;; --------------------------------------------------------------------------
(define handle-history
(define handle-history :effects [io]
(fn (el (url :as string) (resp-headers :as dict))
;; Process history push/replace based on element attrs and response headers
(let ((push-url (dom-get-attr el "sx-push-url"))
@@ -700,7 +700,7 @@
(define PRELOAD_TTL 30000) ;; 30 seconds
(define preload-cache-get
(define preload-cache-get :effects [mutation]
(fn ((cache :as dict) (url :as string))
;; Get and consume a cached preload response.
;; Returns (dict "text" ... "content-type" ...) or nil.
@@ -712,7 +712,7 @@
(do (dict-delete! cache url) entry))))))
(define preload-cache-set
(define preload-cache-set :effects [mutation]
(fn ((cache :as dict) (url :as string) (text :as string) (content-type :as string))
;; Store a preloaded response
(dict-set! cache url
@@ -725,7 +725,7 @@
;; Maps trigger event names to binding strategies.
;; This is the logic; actual browser event binding is platform interface.
(define classify-trigger
(define classify-trigger :effects []
(fn ((trigger :as dict))
;; Classify a parsed trigger descriptor for binding.
;; Returns one of: "poll", "intersect", "load", "revealed", "event"
@@ -742,7 +742,7 @@
;; Boost logic
;; --------------------------------------------------------------------------
(define should-boost-link?
(define should-boost-link? :effects [io]
(fn (link)
;; Whether a link inside an sx-boost container should be boosted
(let ((href (dom-get-attr link "href")))
@@ -756,7 +756,7 @@
(not (dom-has-attr? link "sx-disable"))))))
(define should-boost-form?
(define should-boost-form? :effects [io]
(fn (form)
;; Whether a form inside an sx-boost container should be boosted
(and (not (dom-has-attr? form "sx-get"))
@@ -768,7 +768,7 @@
;; SSE event classification
;; --------------------------------------------------------------------------
(define parse-sse-swap
(define parse-sse-swap :effects [io]
(fn (el)
;; Parse sx-sse-swap attribute
;; Returns event name to listen for (default "message")