From e02118493515ad9c96a2624ec5a3d350b1a118e1 Mon Sep 17 00:00:00 2001 From: giles Date: Tue, 24 Mar 2026 03:37:27 +0000 Subject: [PATCH] Stepper: isomorphic code highlighting + steps-to-preview (WIP) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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) --- sx/sx/home-stepper.sx | 78 +++++++++++++++++++++++++++++++++++-------- 1 file changed, 64 insertions(+), 14 deletions(-) diff --git a/sx/sx/home-stepper.sx b/sx/sx/home-stepper.sx index 4c80cc6..9eb9884 100644 --- a/sx/sx/home-stepper.sx +++ b/sx/sx/home-stepper.sx @@ -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))))))))