Fix stepper client-side [object Object] flash and missing CSSX styles
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 5m54s

Three issues in the stepper island's client-side rendering:

1. do-step used eval-expr with empty env for ~cssx/tw spreads — component
   not found, result leaked as [object Object]. Fixed: call ~cssx/tw
   directly (in scope from island env) with trampoline.

2. steps-to-preview excluded spreads — SSR preview had no styling.
   Fixed: include spreads in the tree so both SSR and client render
   with CSSX classes.

3. build-children used named let (let loop ...) which produces
   unresolved Thunks in render mode due to the named-let compiler
   desugaring interacting with the render/eval boundary. Fixed:
   rewrote as plain recursive function bc-loop avoiding named let.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 00:11:06 +00:00
parent f3f70cc00b
commit 7d7de86034
6 changed files with 322 additions and 53 deletions

View File

@@ -103,39 +103,38 @@
(let ((pos (dict "i" 0))
(max-i (min target (len all-steps))))
(letrec
((build-children (fn ()
(let ((children (list)))
(let loop ()
(if (>= (get pos "i") max-i)
children
(let ((step (nth all-steps (get pos "i")))
(stype (get step "type")))
(cond
(= stype "open")
(do
(dict-set! pos "i" (+ (get pos "i") 1))
(let ((tag (get step "tag"))
(attrs (or (get step "attrs") (list)))
(spreads (or (get step "spreads") (list)))
(inner (build-children)))
(append! children
(concat (list (make-symbol tag)) spreads attrs inner)))
(loop))
(= stype "close")
(do (dict-set! pos "i" (+ (get pos "i") 1))
children)
(= stype "leaf")
(do (dict-set! pos "i" (+ (get pos "i") 1))
(append! children (get step "expr"))
(loop))
(= stype "expr")
(do (dict-set! pos "i" (+ (get pos "i") 1))
(append! children (get step "expr"))
(loop))
:else
(do (dict-set! pos "i" (+ (get pos "i") 1))
(loop))))))))))
(let ((root (build-children)))
((bc-loop (fn (children)
(if (>= (get pos "i") max-i)
children
(let ((step (nth all-steps (get pos "i")))
(stype (get step "type")))
(cond
(= stype "open")
(do
(dict-set! pos "i" (+ (get pos "i") 1))
(let ((tag (get step "tag"))
(attrs (or (get step "attrs") (list)))
(spreads (or (get step "spreads") (list)))
(inner (bc-loop (list))))
(append! children
(concat (list (make-symbol tag)) spreads attrs inner)))
(bc-loop children))
(= stype "close")
(do (dict-set! pos "i" (+ (get pos "i") 1))
children)
(= stype "leaf")
(do (dict-set! pos "i" (+ (get pos "i") 1))
(append! children (get step "expr"))
(bc-loop children))
(= stype "expr")
(do (dict-set! pos "i" (+ (get pos "i") 1))
(append! children (get step "expr"))
(bc-loop children))
:else
(do (dict-set! pos "i" (+ (get pos "i") 1))
(bc-loop children))))))))
(let ((root (bc-loop (list))))
(cond
(= (len root) 1) (first root)
(empty? root) nil
@@ -194,16 +193,21 @@
(when (< i (len attrs))
(dom-set-attr el (keyword-name (nth attrs i)) (nth attrs (+ i 1)))
(loop (+ i 2))))
;; Evaluate spreads via ~cssx/tw (in scope from island env)
(for-each (fn (sp)
(let ((result (eval-expr sp (make-env))))
(when (and result (spread? result))
(let ((sattrs (spread-attrs result)))
(for-each (fn (k)
(if (= k "class")
(dom-set-attr el "class"
(str (or (dom-get-attr el "class") "") " " (get sattrs k)))
(dom-set-attr el k (get sattrs k))))
(keys sattrs))))))
(when (and (list? sp) (>= (len sp) 3)
(= (type-of (nth sp 1)) "keyword")
(= (keyword-name (nth sp 1)) "tokens")
(string? (nth sp 2)))
(let ((result (trampoline (~cssx/tw :tokens (nth sp 2)))))
(when (spread? result)
(let ((sattrs (spread-attrs result)))
(for-each (fn (k)
(if (= k "class")
(dom-set-attr el "class"
(str (or (dom-get-attr el "class") "") " " (get sattrs k)))
(dom-set-attr el k (get sattrs k))))
(keys sattrs)))))))
spreads)
(when parent (dom-append parent el))
(push-stack el))
@@ -214,9 +218,8 @@
(let ((val (get step "expr")))
(dom-append parent (create-text-node (if (string? val) val (str val))))))
(= step-type "expr")
(let ((rendered (render-to-dom (get step "expr") (make-env) nil)))
(when (and parent rendered)
(dom-append parent rendered)))))
;; Component expressions handled by lake's reactive render
nil))
(swap! step-idx inc)
(update-code-highlight)
(set-cookie "sx-home-stepper" (freeze-to-sx "home-stepper")))))
@@ -255,10 +258,11 @@
(build-code-dom)
(let ((preview (get-preview)))
(when preview (dom-set-prop preview "innerHTML" "")))
(let ((target (deref step-idx)))
(reset! step-idx 0)
(set-stack (list (get-preview)))
(for-each (fn (_) (do-step)) (slice (deref steps) 0 target)))
(batch (fn ()
(let ((target (deref step-idx)))
(reset! step-idx 0)
(set-stack (list (get-preview)))
(for-each (fn (_) (do-step)) (slice (deref steps) 0 target)))))
(update-code-highlight)
(run-post-render-hooks)))))))
(div :class "space-y-4"