From 7cc1bffc23eb8f6bb6138e415d678da87b8092e6 Mon Sep 17 00:00:00 2001 From: giles Date: Sat, 14 Mar 2026 21:58:42 +0000 Subject: [PATCH] Reactive code view stepper for home page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Imperative code view with syntax colouring matching highlight.py - Token step indices aligned with split-tag (16 steps) - Component spreads (~cssx/tw) dimmed, not highlighted - Evaluated tokens bold+larger, current amber bg+largest, future faint - Lakes for DOM preview and code view (survive reactive re-renders) - dom-stack as signal (persists across re-renders) - schedule-idle for initial code DOM build + step replay - post-render hooks flush CSSX after each event handler - Self-registering spec defines (js-emit-define emits PRIMITIVES[]) - Generic render hooks replace flush-cssx-to-dom in spec - Fix nil→NIL in platform JS, fix append semantics Co-Authored-By: Claude Opus 4.6 (1M context) --- sx/sx/home-stepper.sx | 62 ++++++++++++++++++++++++++++++------------- 1 file changed, 44 insertions(+), 18 deletions(-) diff --git a/sx/sx/home-stepper.sx b/sx/sx/home-stepper.sx index 02b9487..f4da895 100644 --- a/sx/sx/home-stepper.sx +++ b/sx/sx/home-stepper.sx @@ -1,7 +1,7 @@ (defisland ~home/stepper () - (let ((source "(div (~cssx/tw :tokens \"p-6 rounded-lg border border-stone-200 bg-white 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\")))") + (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))) - (step-idx (signal 0)) + (step-idx (signal 9)) (dom-stack-sig (signal (list))) (code-tokens (signal (list))) (code-spans (list))) @@ -60,19 +60,35 @@ (is-comp (and (= (type-of head) "symbol") (starts-with? (symbol-name head) "~"))) (open-step (get step-ref "v"))) (append! tokens {"text" "(" "cls" "text-stone-400" "step" open-step}) - (when (or is-tag is-comp) - (dict-set! step-ref "v" (+ (get step-ref "v") 1))) (build-code-tokens head tokens step-ref indent) + (when is-tag + (dict-set! step-ref "v" (+ (get step-ref "v") 1))) (for-each (fn (a) (let ((is-child (and (list? a) (not (empty? a)) (= (type-of (first a)) "symbol") (or (is-html-tag? (symbol-name (first a))) - (starts-with? (symbol-name (first a)) "~"))))) - (if (and is-tag is-child) - (do (append! tokens {"text" (str "\n" (join "" (map (fn (_) " ") (range 0 (+ indent 1))))) "cls" "" "step" -1}) - (build-code-tokens a tokens step-ref (+ indent 1))) - (do (append! tokens {"text" " " "cls" "" "step" -1}) - (build-code-tokens a tokens step-ref indent))))) + (starts-with? (symbol-name (first a)) "~")))) + (is-spread (and (list? a) (not (empty? a)) + (= (type-of (first a)) "symbol") + (starts-with? (symbol-name (first a)) "~")))) + (if is-spread + ;; Component spread: save counter, process, restore + ;; All tokens inside share parent open-step + (let ((saved (get step-ref "v")) + (saved-tokens-len (len tokens))) + (append! tokens {"text" " " "cls" "" "step" -1}) + (build-code-tokens a tokens step-ref indent) + ;; Mark all tokens added during spread as spread tokens + (let mark-loop ((j saved-tokens-len)) + (when (< j (len tokens)) + (dict-set! (nth tokens j) "spread" true) + (mark-loop (+ j 1)))) + (dict-set! step-ref "v" saved)) + (if (and is-tag is-child) + (do (append! tokens {"text" (str "\n" (join "" (map (fn (_) " ") (range 0 (+ indent 1))))) "cls" "" "step" -1}) + (build-code-tokens a tokens step-ref (+ indent 1))) + (do (append! tokens {"text" " " "cls" "" "step" -1}) + (build-code-tokens a tokens step-ref indent)))))) (rest expr)) (append! tokens {"text" ")" "cls" "text-stone-400" "step" (if is-tag (get step-ref "v") open-step)}) (when is-tag @@ -97,7 +113,7 @@ (dom-set-attr sp "class" (get tok "cls")) (dom-set-prop sp "textContent" (get tok "text")) (dom-append code-el sp) - (append! code-spans (dict "el" sp "step" (get tok "step") "cls" (get tok "cls"))))) + (append! code-spans (dict "el" sp "step" (get tok "step") "cls" (get tok "cls") "spread" (get tok "spread"))))) (deref code-tokens))))))) (update-code-highlight (fn () (let ((cur (deref step-idx))) @@ -108,10 +124,13 @@ (when (not (= step-num -1)) (dom-set-attr el "class" (str base + (let ((is-spread (get s "spread"))) (cond - (= step-num cur) " bg-violet-100 rounded px-0.5 font-bold" - (< step-num cur) " font-bold text-lg" - :else " opacity-40")))))) + (and (= step-num cur) is-spread) " opacity-60" + (= step-num cur) " bg-amber-100 rounded px-0.5 font-bold text-sm" + (and (< step-num cur) is-spread) " opacity-60" + (< step-num cur) " font-bold text-xs" + :else " opacity-40"))))))) code-spans)))) (do-step (fn () (build-code-dom) @@ -179,13 +198,20 @@ ;; Defer code DOM build until lake exists (schedule-idle (fn () (build-code-dom) - (update-code-highlight)))))))) + ;; Replay to initial step-idx + (let ((target (deref step-idx))) + (reset! step-idx 0) + (set-stack (list (get-preview))) + (for-each (fn (_) (do-step)) (slice (deref steps) 0 target))) + (update-code-highlight) + (run-post-render-hooks)))))))) (div :class "space-y-4" ;; Code view lake — spans built imperatively, classes updated on step - (lake :id "code-view" :tag "pre" - :class "text-sm font-mono bg-stone-50 rounded p-4 overflow-x-auto leading-relaxed") + (div (~cssx/tw :tokens "font-mono bg-stone-50 rounded p-2 overflow-x-auto leading-relaxed whitespace-pre-wrap") + :style "font-size:0.5rem" + (lake :id "code-view")) ;; Controls - (div :class "flex items-center justify-center gap-3" + (div :class "flex items-center justify-center gap-2 md:gap-3" (button :on-click (fn (e) (do-back)) :class (str "px-2 py-1 rounded text-lg " (if (> (deref step-idx) 0)