Fix reactive islands client-side navigation and hydration
Three bugs prevented islands from working during SX wire navigation: 1. components_for_request() only bundled Component and Macro defs, not Island defs — client never received defisland definitions during navigation (components_for_page for initial HTML shell was correct). 2. hydrate-island used morph-children which can't transfer addEventListener event handlers from freshly rendered DOM to existing nodes. Changed to clear+append so reactive DOM with live signal subscriptions is inserted directly. 3. asyncRenderToDom (client-side async page eval) checked _component but not _island on ~-prefixed names — islands fell through to generic eval which failed. Now delegates to renderDomIsland. 4. setInterval_/setTimeout_ passed SX Lambda objects directly to native timers. JS coerced them to "[object Object]" and tried to eval as code, causing "missing ] after element list". Added _wrapSxFn to convert SX lambdas to JS functions before passing to timers. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -130,20 +130,191 @@
|
||||
(str "(" (join " " parts) ")"))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Form classification
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
(define SPECIAL_FORM_NAMES
|
||||
(list "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"))
|
||||
|
||||
(define HO_FORM_NAMES
|
||||
(list "map" "map-indexed" "filter" "reduce"
|
||||
"some" "every?" "for-each"))
|
||||
|
||||
(define special-form?
|
||||
(fn (name)
|
||||
(contains? SPECIAL_FORM_NAMES name)))
|
||||
|
||||
(define ho-form?
|
||||
(fn (name)
|
||||
(contains? HO_FORM_NAMES name)))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; aser-special — evaluate special/HO forms in aser mode
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; Control flow forms evaluate conditions normally but render branches
|
||||
;; through aser (serializing tags/components instead of rendering HTML).
|
||||
;; Definition forms evaluate for side effects and return nil.
|
||||
|
||||
(define aser-special
|
||||
(fn (name expr env)
|
||||
(let ((args (rest expr)))
|
||||
(cond
|
||||
;; if — evaluate condition, aser chosen branch
|
||||
(= name "if")
|
||||
(if (trampoline (eval-expr (first args) env))
|
||||
(aser (nth args 1) env)
|
||||
(if (> (len args) 2)
|
||||
(aser (nth args 2) env)
|
||||
nil))
|
||||
|
||||
;; when — evaluate condition, aser body if true
|
||||
(= name "when")
|
||||
(if (not (trampoline (eval-expr (first args) env)))
|
||||
nil
|
||||
(let ((result nil))
|
||||
(for-each (fn (body) (set! result (aser body env)))
|
||||
(rest args))
|
||||
result))
|
||||
|
||||
;; cond — evaluate conditions, aser matching branch
|
||||
(= name "cond")
|
||||
(let ((branch (eval-cond args env)))
|
||||
(if branch (aser branch env) nil))
|
||||
|
||||
;; case — evaluate match value, check each pair
|
||||
(= name "case")
|
||||
(let ((match-val (trampoline (eval-expr (first args) env)))
|
||||
(clauses (rest args)))
|
||||
(eval-case-aser match-val clauses env))
|
||||
|
||||
;; let / let*
|
||||
(or (= name "let") (= name "let*"))
|
||||
(let ((local (process-bindings (first args) env))
|
||||
(result nil))
|
||||
(for-each (fn (body) (set! result (aser body local)))
|
||||
(rest args))
|
||||
result)
|
||||
|
||||
;; begin / do
|
||||
(or (= name "begin") (= name "do"))
|
||||
(let ((result nil))
|
||||
(for-each (fn (body) (set! result (aser body env))) args)
|
||||
result)
|
||||
|
||||
;; and — short-circuit
|
||||
(= name "and")
|
||||
(let ((result true))
|
||||
(some (fn (arg)
|
||||
(set! result (trampoline (eval-expr arg env)))
|
||||
(not result))
|
||||
args)
|
||||
result)
|
||||
|
||||
;; or — short-circuit
|
||||
(= name "or")
|
||||
(let ((result false))
|
||||
(some (fn (arg)
|
||||
(set! result (trampoline (eval-expr arg env)))
|
||||
result)
|
||||
args)
|
||||
result)
|
||||
|
||||
;; map — evaluate function and collection, map through aser
|
||||
(= name "map")
|
||||
(let ((f (trampoline (eval-expr (first args) env)))
|
||||
(coll (trampoline (eval-expr (nth args 1) env))))
|
||||
(map (fn (item)
|
||||
(if (lambda? f)
|
||||
(let ((local (env-merge (lambda-closure f) env)))
|
||||
(env-set! local (first (lambda-params f)) item)
|
||||
(aser (lambda-body f) local))
|
||||
(invoke f item)))
|
||||
coll))
|
||||
|
||||
;; map-indexed
|
||||
(= name "map-indexed")
|
||||
(let ((f (trampoline (eval-expr (first args) env)))
|
||||
(coll (trampoline (eval-expr (nth args 1) env))))
|
||||
(map-indexed (fn (i item)
|
||||
(if (lambda? f)
|
||||
(let ((local (env-merge (lambda-closure f) env)))
|
||||
(env-set! local (first (lambda-params f)) i)
|
||||
(env-set! local (nth (lambda-params f) 1) item)
|
||||
(aser (lambda-body f) local))
|
||||
(invoke f i item)))
|
||||
coll))
|
||||
|
||||
;; for-each — evaluate for side effects, aser each body
|
||||
(= name "for-each")
|
||||
(let ((f (trampoline (eval-expr (first args) env)))
|
||||
(coll (trampoline (eval-expr (nth args 1) env)))
|
||||
(results (list)))
|
||||
(for-each (fn (item)
|
||||
(if (lambda? f)
|
||||
(let ((local (env-merge (lambda-closure f) env)))
|
||||
(env-set! local (first (lambda-params f)) item)
|
||||
(append! results (aser (lambda-body f) local)))
|
||||
(invoke f item)))
|
||||
coll)
|
||||
(if (empty? results) nil results))
|
||||
|
||||
;; defisland — evaluate AND serialize (client needs the definition)
|
||||
(= name "defisland")
|
||||
(do (trampoline (eval-expr expr env))
|
||||
(serialize expr))
|
||||
|
||||
;; Definition forms — evaluate for side effects
|
||||
(or (= name "define") (= name "defcomp") (= name "defmacro")
|
||||
(= name "defstyle") (= name "defhandler") (= name "defpage")
|
||||
(= name "defquery") (= name "defaction") (= name "defrelation"))
|
||||
(do (trampoline (eval-expr expr env)) nil)
|
||||
|
||||
;; Everything else — evaluate normally
|
||||
:else
|
||||
(trampoline (eval-expr expr env))))))
|
||||
|
||||
|
||||
;; Helper: case dispatch for aser mode
|
||||
(define eval-case-aser
|
||||
(fn (match-val clauses env)
|
||||
(if (< (len clauses) 2)
|
||||
nil
|
||||
(let ((test (first clauses))
|
||||
(body (nth clauses 1)))
|
||||
(if (or (and (= (type-of test) "keyword") (= (keyword-name test) "else"))
|
||||
(and (= (type-of test) "symbol")
|
||||
(or (= (symbol-name test) ":else")
|
||||
(= (symbol-name test) "else"))))
|
||||
(aser body env)
|
||||
(if (= match-val (trampoline (eval-expr test env)))
|
||||
(aser body env)
|
||||
(eval-case-aser match-val (slice clauses 2) env)))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Platform interface — SX wire adapter
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; Serialization:
|
||||
;; (serialize val) → SX source string representation of val
|
||||
;;
|
||||
;; Form classification:
|
||||
;; (special-form? name) → boolean
|
||||
;; (ho-form? name) → boolean
|
||||
;; (aser-special name expr env) → evaluate special/HO form through aser
|
||||
;;
|
||||
;; From eval.sx:
|
||||
;; eval-expr, trampoline, call-lambda, expand-macro
|
||||
;; env-has?, env-get, callable?, lambda?, component?, macro?
|
||||
;; primitive?, get-primitive, component-name
|
||||
;; env-has?, env-get, env-set!, env-merge, callable?, lambda?, component?,
|
||||
;; macro?, island?, primitive?, get-primitive, component-name
|
||||
;; lambda-closure, lambda-params, lambda-body
|
||||
;;
|
||||
;; From render.sx:
|
||||
;; HTML_TAGS, eval-cond, process-bindings
|
||||
;;
|
||||
;; From parser.sx:
|
||||
;; serialize (= sx-serialize)
|
||||
;;
|
||||
;; From signals.sx (optional):
|
||||
;; invoke
|
||||
;; --------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user