diff --git a/shared/static/scripts/sx-browser.js b/shared/static/scripts/sx-browser.js index 92243d3..5928da0 100644 --- a/shared/static/scripts/sx-browser.js +++ b/shared/static/scripts/sx-browser.js @@ -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); } diff --git a/sx/sx/home-stepper.sx b/sx/sx/home-stepper.sx index 95ee512..4a6f1e2 100644 --- a/sx/sx/home-stepper.sx +++ b/sx/sx/home-stepper.sx @@ -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))))))))