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:
@@ -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))))))))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user