Fix island reactivity: trampoline callLambda result in dom-on handlers

dom-on wraps Lambda event handlers in JS functions that call callLambda.
callLambda returns a Thunk (TCO), but the wrapper never trampolined it,
so the handler body (swap!, set!, etc.) never executed. Buttons rendered
but clicks had no effect.

Fix: wrap callLambda result in trampoline() so thunks resolve and
side effects (signal mutations, DOM updates) execute.

Also use call-lambda instead of direct invocation for Lambda objects
(Lambda is a plain JS object, not callable as a function).

All 100 Playwright tests pass:
- 6 isomorphic SSR
- 5 reactive navigation (cross-demo)
- 61 geography page loads
- 7 handler response rendering
- 21 demo interaction + health checks

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 19:26:43 +00:00
parent 32df71abd4
commit 5b2ef0a2af
2 changed files with 35 additions and 11 deletions

View File

@@ -14,6 +14,24 @@
(define MATH_NS "http://www.w3.org/1998/Math/MathML")
;; --------------------------------------------------------------------------
;; dom-on — dom-listen with post-render hooks
;;
;; Wraps dom-listen so that run-post-render-hooks fires after every SX
;; event handler invocation. This is the SX-level hook integration;
;; the native dom-listen primitive is a clean addEventListener wrapper.
;; --------------------------------------------------------------------------
(define dom-on :effects [io]
(fn (el name handler)
(dom-listen el name
(if (lambda? handler)
(if (= 0 (len (lambda-params handler)))
(fn () (trampoline (call-lambda handler (list))) (run-post-render-hooks))
(fn (e) (trampoline (call-lambda handler (list e))) (run-post-render-hooks)))
handler))))
;; --------------------------------------------------------------------------
;; render-to-dom — main entry point
;; --------------------------------------------------------------------------
@@ -199,7 +217,7 @@
(starts-with? attr-name "on-")
(let ((attr-val (trampoline (eval-expr attr-expr env))))
(when (callable? attr-val)
(dom-listen el (slice attr-name 3) attr-val)))
(dom-on el (slice attr-name 3) attr-val)))
;; Two-way input binding: :bind signal
(= attr-name "bind")
(let ((attr-val (trampoline (eval-expr attr-expr env))))
@@ -1100,7 +1118,7 @@
(when (!= (dom-get-prop el "value") v)
(dom-set-prop el "value" v))))))
;; Element → signal (event listener)
(dom-listen el (if is-checkbox "change" "input")
(dom-on el (if is-checkbox "change" "input")
(fn (e)
(if is-checkbox
(reset! sig (dom-get-prop el "checked"))