Reactive code view stepper for home page
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 25s

- 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) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 21:58:42 +00:00
parent 169097097c
commit 7cc1bffc23

View File

@@ -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)