Root cause: default step-idx was 9, but the expression has 16 steps. At step 9, only "the joy" + empty emerald span renders. Changed default to 16 so all four words display after hydration. Reverted mutable-list changes — (list) already creates ListRef in the OCaml kernel, so append! works correctly with plain (list). Added spec/tests/test-stepper.sx (7 tests) proving the split-tag + steps-to-preview pipeline works correctly at each step boundary. Updated Playwright stepper.spec.js with four tests: - no raw SX visible after hydration - default view shows all four words - all spans inside h1 - stepping forward renders styled text Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
140 lines
4.7 KiB
Plaintext
140 lines
4.7 KiB
Plaintext
(define
|
|
is-html-tag?
|
|
(fn
|
|
(name)
|
|
(contains?
|
|
(list "div" "span" "h1" "h2" "h3" "p" "a" "button" "section" "nav")
|
|
name)))
|
|
|
|
(define
|
|
split-tag
|
|
(fn
|
|
(expr result)
|
|
(cond
|
|
(not (list? expr))
|
|
(append! result {:expr expr :type "leaf"})
|
|
(empty? expr)
|
|
nil
|
|
(not (= (type-of (first expr)) "symbol"))
|
|
(append! result {:expr expr :type "leaf"})
|
|
(is-html-tag? (symbol-name (first expr)))
|
|
(let
|
|
((ctag (symbol-name (first expr)))
|
|
(cargs (rest expr))
|
|
(cch (list))
|
|
(cat (list))
|
|
(spreads (list))
|
|
(ckw false))
|
|
(for-each
|
|
(fn
|
|
(a)
|
|
(cond
|
|
(= (type-of a) "keyword")
|
|
(do (set! ckw true) (append! cat a))
|
|
ckw
|
|
(do (set! ckw false) (append! cat a))
|
|
(and
|
|
(list? a)
|
|
(not (empty? a))
|
|
(= (type-of (first a)) "symbol")
|
|
(starts-with? (symbol-name (first a)) "~"))
|
|
(do (set! ckw false) (append! spreads a))
|
|
:else (do (set! ckw false) (append! cch a))))
|
|
cargs)
|
|
(append! result {:spreads spreads :tag ctag :type "open" :attrs cat})
|
|
(for-each (fn (c) (split-tag c result)) cch)
|
|
(append! result {:tag ctag :type "close"}))
|
|
:else (append! result {:expr expr :type "expr"}))))
|
|
|
|
(define
|
|
steps-to-preview
|
|
(fn
|
|
(all-steps target)
|
|
(if
|
|
(or (empty? all-steps) (<= target 0))
|
|
nil
|
|
(let
|
|
((pos (dict "i" 0)) (max-i (min target (len all-steps))))
|
|
(letrec
|
|
((bc-loop (fn (children) (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")) (inner (bc-loop (list)))) (append! children (concat (list (make-symbol tag)) inner))) (bc-loop children)) (= 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")) (bc-loop children)) :else (do (dict-set! pos "i" (+ (get pos "i") 1)) (bc-loop children))))))))
|
|
(let
|
|
((root (bc-loop (list))))
|
|
(cond
|
|
(= (len root) 1)
|
|
(first root)
|
|
(empty? root)
|
|
nil
|
|
:else (concat (list (make-symbol "<>")) root))))))))
|
|
|
|
(define
|
|
test-src
|
|
(quote (div (h1 (span "the ") (span "joy ") (span "of ") (span "sx")))))
|
|
|
|
(defsuite
|
|
"stepper-split-tag"
|
|
(deftest
|
|
"produces 16 steps for nested 4-span expression"
|
|
(let
|
|
((result (list)))
|
|
(split-tag test-src result)
|
|
(assert-equal 16 (len result))))
|
|
(deftest
|
|
"step sequence is open/open/.../close/close"
|
|
(let
|
|
((result (list)))
|
|
(split-tag test-src result)
|
|
(assert-equal "open" (get (first result) "type"))
|
|
(assert-equal "div" (get (first result) "tag"))
|
|
(assert-equal "leaf" (get (nth result 3) "type"))
|
|
(assert-equal "the " (get (nth result 3) "expr"))
|
|
(assert-equal "close" (get (last result) "type"))
|
|
(assert-equal "div" (get (last result) "tag"))))
|
|
(deftest
|
|
"children are correctly nested"
|
|
(let
|
|
((result (list)))
|
|
(split-tag test-src result)
|
|
(assert-equal "span" (get (nth result 2) "tag"))
|
|
(assert-equal "the " (get (nth result 3) "expr"))
|
|
(assert-equal "span" (get (nth result 4) "tag"))
|
|
(assert-equal "span" (get (nth result 8) "tag"))
|
|
(assert-equal "of " (get (nth result 9) "expr"))
|
|
(assert-equal "span" (get (nth result 10) "tag")))))
|
|
|
|
(defsuite
|
|
"stepper-preview"
|
|
(deftest
|
|
"full preview at step 16 equals source"
|
|
(let
|
|
((result (list)))
|
|
(split-tag test-src result)
|
|
(let
|
|
((expr (steps-to-preview result 16)))
|
|
(assert-equal test-src expr))))
|
|
(deftest
|
|
"step 10 includes of"
|
|
(let
|
|
((result (list)))
|
|
(split-tag test-src result)
|
|
(let
|
|
((expr (steps-to-preview result 10)))
|
|
(assert-true (string-contains? (str expr) "of ")))))
|
|
(deftest
|
|
"step 9 does NOT include of (leaf not yet processed)"
|
|
(let
|
|
((result (list)))
|
|
(split-tag test-src result)
|
|
(let
|
|
((expr (steps-to-preview result 9)))
|
|
(assert-false (string-contains? (str expr) "of ")))))
|
|
(deftest
|
|
"step 8 shows the and joy only"
|
|
(let
|
|
((result (list)))
|
|
(split-tag test-src result)
|
|
(let
|
|
((expr (steps-to-preview result 8)))
|
|
(assert-true (string-contains? (str expr) "the "))
|
|
(assert-true (string-contains? (str expr) "joy "))
|
|
(assert-false (string-contains? (str expr) "of "))))))
|