Stepper: isomorphic code highlighting + steps-to-preview (WIP)

Code view: SSR now uses same highlighting logic as client update-code-highlight
(bg-amber-100 for current step, font-bold for active, opacity-40 for future).

steps-to-preview: pure function that replays step machine as SX expression
tree — intended for isomorphic preview rendering. Currently working for
simple cases but needs fix for partial step counts (close-loop issue).

Close steps now carry open-attrs/open-spreads for steps-to-preview.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 03:37:27 +00:00
parent 55061d6451
commit e021184935

View File

@@ -1,3 +1,55 @@
;; steps-to-preview — pure function: replay step machine as SX expression tree.
;; Given a list of steps and a target index, build the SX that represents
;; the partial render at that step. Works on both server and client.
(define steps-to-preview
(fn (all-steps target)
(if (or (empty? all-steps) (<= target 0))
nil
;; Use mutable lists for the stacks so append!/init work correctly
(let ((stack (list (list)))
(tag-stack (list)))
;; Replay steps 0..target-1
(for-each (fn (step)
(let ((step-type (get step "type")))
(cond
(= step-type "open")
(do (append! stack (list))
(append! tag-stack (get step "tag")))
(= step-type "close")
(when (> (len stack) 1)
(let ((children (last stack))
(tag (last tag-stack))
(attrs (or (get step "open-attrs") (list)))
(spreads (or (get step "open-spreads") (list)))
(expr (concat (list (make-symbol tag)) attrs spreads children)))
;; Pop stack: remove last, append expr to new last
(set! stack (init stack))
(set! tag-stack (init tag-stack))
(append! (last stack) expr)))
(= step-type "leaf")
(when (not (empty? stack))
(append! (last stack) (get step "expr")))
(= step-type "expr")
(when (not (empty? stack))
(append! (last stack) (get step "expr"))))))
(slice all-steps 0 (min target (len all-steps))))
;; Close any unclosed elements
(let close-loop ()
(when (> (len stack) 1)
(let ((children (last stack))
(tag (last tag-stack))
(expr (concat (list (make-symbol tag)) children)))
(set! stack (init stack))
(set! tag-stack (init tag-stack))
(append! (last stack) expr)
(close-loop))))
;; Return root content
(let ((root (first stack)))
(cond
(= (len root) 1) (first root)
(empty? root) nil
:else (concat (list (make-symbol "<>")) root)))))))
(defisland ~home/stepper ()
(let ((source "(div (~cssx/tw :tokens \"text-center\")\n (h1 (~cssx/tw :tokens \"text-3xl font-bold mb-2\")\n (span (~cssx/tw :tokens \"text-rose-500\") \"the \")\n (span (~cssx/tw :tokens \"text-amber-500\") \"joy \")\n (span (~cssx/tw :tokens \"text-emerald-500\") \"of \")\n (span (~cssx/tw :tokens \"text-violet-600 text-4xl\") \"sx\")))")
(steps (signal (list)))
@@ -32,7 +84,7 @@
cargs)
(append! result {"type" "open" "tag" ctag "attrs" cat "spreads" spreads})
(for-each (fn (c) (split-tag c result)) cch)
(append! result {"type" "close" "tag" ctag}))
(append! result {"type" "close" "tag" ctag "open-attrs" cat "open-spreads" spreads}))
:else
(append! result {"type" "expr" "expr" expr}))))
(build-code-tokens (fn (expr tokens step-ref indent)
@@ -226,13 +278,15 @@
(map (fn (tok)
(let ((step (get tok "step"))
(cur (deref step-idx))
(active (<= step cur))
(is-spread (get tok "spread"))
(cls (str (get tok "cls")
(if (and active (>= step 0) (not (get tok "spread")))
" font-bold text-xs"
(if (and (not active) (>= step 0) (not (get tok "spread")))
" opacity-40"
"")))))
(cond
(= step -1) ""
(and (= step cur) is-spread) " opacity-60"
(= step cur) " bg-amber-100 rounded px-0.5 font-bold text-sm"
(and (< step cur) is-spread) " opacity-60"
(< step cur) " font-bold text-xs"
:else " opacity-40"))))
(span :class cls (get tok "text"))))
(deref code-tokens))))
;; Controls
@@ -251,12 +305,8 @@
"text-violet-600 hover:text-violet-800 hover:bg-violet-50"
"text-violet-300 cursor-not-allowed"))
"\u25b6"))
;; Live preview lake — SSR shows the final rendered result
;; Live preview — declarative: same SX rendered by server (HTML) and client (DOM).
;; steps-to-preview replays the stack machine as SX expressions.
(lake :id "home-preview"
(div (~cssx/tw :tokens "text-center")
(h1 (~cssx/tw :tokens "text-3xl font-bold mb-2")
(span (~cssx/tw :tokens "text-rose-500") "the ")
(span (~cssx/tw :tokens "text-amber-500") "joy ")
(span (~cssx/tw :tokens "text-emerald-500") "of ")
(span (~cssx/tw :tokens "text-violet-600 text-4xl") "sx")))))))))
(steps-to-preview (deref steps) (deref step-idx))))))))