Stepper: steps-to-preview for isomorphic preview text (WIP)

steps-to-preview is a pure recursive descent function inside the island's
letrec that builds an SX expression tree from steps[0..target-1].
The preview lake uses it to show partial text (e.g. "the joy of " at step 9).

Still WIP: stepper island doesn't SSR because DOM-only code (effect,
dom-query, dom-create-element) runs in the island body and fails on server.
Need to guard client-only code so SSR can render the pure parts
(code view, counter, preview).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-24 04:24:12 +00:00
parent bf305deae1
commit 8ccf5f7c1e
2 changed files with 50 additions and 9 deletions

View File

@@ -14,7 +14,7 @@
// =========================================================================
var NIL = Object.freeze({ _nil: true, toString: function() { return "nil"; } });
var SX_VERSION = "2026-03-24T04:10:36Z";
var SX_VERSION = "2026-03-24T04:23:51Z";
function isNil(x) { return x === NIL || x === null || x === undefined; }
function isSxTruthy(x) { return x !== false && !isNil(x); }

View File

@@ -94,6 +94,52 @@
(when is-tag
(dict-set! step-ref "v" (+ (get step-ref "v") 1)))))
:else nil)))
(steps-to-preview (fn (all-steps target)
;; Recursive descent: build SX expression tree from steps[0..target-1].
;; "open" recurses for children; "close"/end-of-steps returns.
;; Unclosed elements close naturally when steps run out.
(if (or (empty? all-steps) (<= target 0))
nil
(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)))
(inner (build-children)))
;; Skip spreads (~cssx/tw) — just structure + text
(append! children
(concat (list (make-symbol tag)) 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)))
(cond
(= (len root) 1) (first root)
(empty? root) nil
:else (concat (list (make-symbol "<>")) root))))))))
(get-preview (fn () (dom-query "[data-sx-lake=\"home-preview\"]")))
(get-code-view (fn () (dom-query "[data-code-view]")))
(get-stack (fn () (deref dom-stack-sig)))
@@ -250,14 +296,9 @@
"text-violet-600 hover:text-violet-800 hover:bg-violet-50"
"text-violet-300 cursor-not-allowed"))
"\u25b6"))
;; Live preview lake — client builds incrementally via do-step effect.
;; SSR shows the full result; client effect replays 0→N for animation.
;; Live preview — shows partial result up to current step.
;; Same SX rendered by server (HTML) and client (DOM).
(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))))))))