Fix duplicate sx-cssx-live style tags
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 7m5s

Cache the style element reference in _cssx-style-el so flush-cssx-to-dom
never creates more than one. Previous code called dom-query on every
flush, which could miss the element during rapid successive calls,
creating duplicates.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-14 17:16:13 +00:00
parent 41f4772ba7
commit a4826a7a60
8 changed files with 562 additions and 116 deletions

View File

@@ -372,17 +372,20 @@
(dom-set-attr el (keyword-name (nth attrs i)) (nth attrs (+ i 1)))
(loop (+ i 2))))
;; Evaluate spread components (e.g. ~cssx/tw) and apply to element
(console-log " spreads:" (len spreads))
(for-each (fn (sp)
(let ((rendered (render-to-dom sp (make-env) nil)))
(when (and rendered (spread? rendered))
(let ((sattrs (spread-attrs rendered)))
(console-log " eval spread:" (sx-serialize sp))
(let ((result (eval-expr sp (make-env))))
(console-log " result type:" (type-of result) "spread?:" (spread? result))
(when (and result (spread? result))
(let ((sattrs (spread-attrs result)))
(console-log " attrs:" (keys sattrs))
(for-each (fn (k)
(if (= k "class")
(dom-set-attr el "class"
(str (or (dom-get-attr el "class") "") " " (get sattrs k)))
(dom-set-attr el k (get sattrs k))))
(keys sattrs))))
nil))
(keys sattrs))))))
spreads)
(when parent (dom-append parent el))
(set! dom-stack (append dom-stack (list el)))
@@ -409,13 +412,17 @@
(loop)))))
(do-back (fn ()
(when (and (deref parsed-ok) (> (deref step-idx) 0))
;; Reset and replay up to step-idx - 1
(let ((target (- (deref step-idx) 1)))
(do-parse)
(let loop ((n 0))
(when (< n target)
(let ((target (- (deref step-idx) 1))
(container (dom-query "#render-preview")))
;; Clear DOM and reset stack
(when container (dom-set-prop container "innerHTML" ""))
(set! dom-stack (list (dom-query "#render-preview")))
(reset! step-idx 0)
;; Replay to target
(let loop ()
(when (< (deref step-idx) target)
(do-step)
(loop (+ n 1)))))))))
(loop)))))))))
(div :class "space-y-4"
(div
(label :class "text-xs text-stone-400 block mb-1" "Component expression")
@@ -474,7 +481,7 @@
(deref steps)))))
(div :class "rounded border border-stone-200 p-3 min-h-24"
(div :class "text-xs text-stone-400 mb-2" "Live DOM")
(div :id "render-preview")))))))
(div :id "render-preview"))))))

View File

@@ -1,5 +1,5 @@
;; ---------------------------------------------------------------------------
;; SX app boot — styles and behaviors injected on page load
;; SX app boot — styles, behaviors, and post-render hooks
;;
;; Replaces inline_css and init_sx from Python app config.
;; Called as a data-init script on every page.
@@ -11,6 +11,25 @@
(collect! "cssx" "@keyframes sxJiggle{0%,100%{transform:translateX(0)}25%{transform:translateX(-.5px)}75%{transform:translateX(.5px)}}")
(collect! "cssx" "a.sx-request{animation:sxJiggle .3s ease-in-out infinite}")
;; CSSX flush hook — inject collected CSS rules into a <style> tag.
;; The spec calls (run-post-render-hooks) after hydration/swap/mount.
;; This is the application's CSS injection strategy.
(console-log "init-client: registering cssx flush hook, type:" (type-of (fn () nil)))
(register-post-render-hook
(fn ()
(console-log "cssx flush: running, rules:" (len (collected "cssx")))
(let ((rules (collected "cssx")))
(when (not (empty? rules))
(let ((style (or (dom-query "[data-cssx]")
(let ((s (dom-create-element "style" nil)))
(dom-set-attr s "data-cssx" "")
(dom-append-to-head s)
s))))
(dom-set-prop style "textContent"
(str (or (dom-get-prop style "textContent") "")
(join "" rules))))
(clear-collected! "cssx")))))
;; Nav link aria-selected update on client-side routing
(dom-listen (dom-body) "sx:clientRoute"
(fn (e)