Files
rose-ash/web/page-helpers.sx
giles 9a64f13dc6 Fix stepper default: show full "the joy of sx" on load
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>
2026-04-02 13:55:40 +00:00

233 lines
11 KiB
Plaintext

(define special-form-category-map {:defmacro "Functions & Components" :for-each "Higher-Order Forms" :defpage "Domain Definitions" :let "Binding" :filter "Higher-Order Forms" :shift "Continuations" :and "Control Flow" :set! "Binding" :map-indexed "Higher-Order Forms" :dynamic-wind "Guards" :reduce "Higher-Order Forms" :cond "Control Flow" :defquery "Domain Definitions" :-> "Sequencing & Threading" :let* "Binding" :define "Binding" :reset "Continuations" :case "Control Flow" :do "Sequencing & Threading" :map "Higher-Order Forms" :some "Higher-Order Forms" :letrec "Binding" :if "Control Flow" :quote "Quoting" :every? "Higher-Order Forms" :defhandler "Domain Definitions" :fn "Functions & Components" :defstyle "Domain Definitions" :lambda "Functions & Components" :defaction "Domain Definitions" :or "Control Flow" :defcomp "Functions & Components" :quasiquote "Quoting" :when "Control Flow" :begin "Sequencing & Threading"})
(define
extract-define-kwargs
(fn
((expr :as list))
(let
((result {}) (items (slice expr 2)) (n (len items)))
(for-each
(fn
((idx :as number))
(when
(and (< (+ idx 1) n) (= (type-of (nth items idx)) "keyword"))
(let
((key (keyword-name (nth items idx)))
(val (nth items (+ idx 1))))
(dict-set!
result
key
(if
(= (type-of val) "list")
(str "(" (join " " (map serialize val)) ")")
(str val))))))
(range 0 n))
result)))
(define
categorize-special-forms
(fn
((parsed-exprs :as list))
(let
((categories {}))
(for-each
(fn
(expr)
(when
(and
(= (type-of expr) "list")
(>= (len expr) 2)
(= (type-of (first expr)) "symbol")
(= (symbol-name (first expr)) "define-special-form"))
(let
((name (nth expr 1))
(kwargs (extract-define-kwargs expr))
(category
(or
(get kwargs "category")
(get special-form-category-map name)
"Other")))
(when
(not (has-key? categories category))
(dict-set! categories category (list)))
(append! (get categories category) {:doc (or (get kwargs "doc") "") :example (or (get kwargs "example") "") :tail-position (or (get kwargs "tail-position") "") :syntax (or (get kwargs "syntax") "") :name name}))))
parsed-exprs)
categories)))
(define
build-ref-items-with-href
(fn
((items :as list)
(base-path :as string)
(detail-keys :as list)
(n-fields :as number))
(map
(fn
((item :as list))
(if
(= n-fields 3)
(let
((name (nth item 0))
(field2 (nth item 1))
(field3 (nth item 2)))
{:href (if (and field3 (some (fn ((k :as string)) (= k name)) detail-keys)) (str base-path name) nil) :exists field3 :desc field2 :name name})
(let ((name (nth item 0)) (desc (nth item 1))) {:href (if (some (fn ((k :as string)) (= k name)) detail-keys) (str base-path name) nil) :desc desc :name name})))
items)))
(define
build-reference-data
(fn
((slug :as string) (raw-data :as dict) (detail-keys :as list))
(case
slug
"attributes"
{:req-attrs (build-ref-items-with-href (get raw-data "req-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3) :beh-attrs (build-ref-items-with-href (get raw-data "beh-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3) :uniq-attrs (build-ref-items-with-href (get raw-data "uniq-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3)}
"headers"
{:req-headers (build-ref-items-with-href (get raw-data "req-headers") "/geography/hypermedia/reference/headers/" detail-keys 3) :resp-headers (build-ref-items-with-href (get raw-data "resp-headers") "/geography/hypermedia/reference/headers/" detail-keys 3)}
"events"
{:events-list (build-ref-items-with-href (get raw-data "events-list") "/geography/hypermedia/reference/events/" detail-keys 2)}
"js-api"
{:js-api-list (map (fn ((item :as list)) {:desc (nth item 1) :name (nth item 0)}) (get raw-data "js-api-list"))}
:else {:req-attrs (build-ref-items-with-href (get raw-data "req-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3) :beh-attrs (build-ref-items-with-href (get raw-data "beh-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3) :uniq-attrs (build-ref-items-with-href (get raw-data "uniq-attrs") "/geography/hypermedia/reference/attributes/" detail-keys 3)})))
(define
build-attr-detail
(fn ((slug :as string) detail) (if (nil? detail) {:attr-not-found true} {:attr-handler (get detail "handler") :attr-title slug :attr-example (get detail "example") :attr-not-found nil :attr-description (get detail "description") :attr-demo (get detail "demo") :attr-wire-id (if (has-key? detail "handler") (str "ref-wire-" (replace (replace slug ":" "-") "*" "star")) nil)})))
(define
build-header-detail
(fn ((slug :as string) detail) (if (nil? detail) {:header-not-found true} {:header-description (get detail "description") :header-demo (get detail "demo") :header-not-found nil :header-title slug :header-example (get detail "example") :header-direction (get detail "direction")})))
(define
build-event-detail
(fn ((slug :as string) detail) (if (nil? detail) {:event-not-found true} {:event-example (get detail "example") :event-demo (get detail "demo") :event-description (get detail "description") :event-not-found nil :event-title slug})))
(define
build-component-source
(fn
((comp-data :as dict))
(let
((comp-type (get comp-data "type"))
(name (get comp-data "name"))
(params (get comp-data "params"))
(has-children (get comp-data "has-children"))
(body-sx (get comp-data "body-sx"))
(affinity (get comp-data "affinity")))
(if
(= comp-type "not-found")
(str ";; component " name " not found")
(let
((param-strs (if (empty? params) (if has-children (list "&rest" "children") (list)) (if has-children (append (cons "&key" params) (list "&rest" "children")) (cons "&key" params))))
(params-sx (str "(" (join " " param-strs) ")"))
(form-name (if (= comp-type "island") "defisland" "defcomp"))
(affinity-str
(if
(and
(= comp-type "component")
(not (nil? affinity))
(not (= affinity "auto")))
(str " :affinity " affinity)
"")))
(str
"("
form-name
" "
name
" "
params-sx
affinity-str
"\n "
body-sx
")"))))))
(define
build-bundle-analysis
(fn
((pages-raw :as list)
(components-raw :as dict)
(total-components :as number)
(total-macros :as number)
(pure-count :as number)
(io-count :as number))
(let
((pages-data (list)))
(for-each
(fn
((page :as dict))
(let
((needed-names (get page "needed-names"))
(n (len needed-names))
(pct
(if
(> total-components 0)
(round (* (/ n total-components) 100))
0))
(savings (- 100 pct))
(pure-in-page 0)
(io-in-page 0)
(page-io-refs (list))
(comp-details (list)))
(for-each
(fn
((comp-name :as string))
(let
((info (get components-raw comp-name)))
(when
(not (nil? info))
(if
(get info "is-pure")
(set! pure-in-page (+ pure-in-page 1))
(do
(set! io-in-page (+ io-in-page 1))
(for-each
(fn
((ref :as string))
(when
(not
(some
(fn ((r :as string)) (= r ref))
page-io-refs))
(append! page-io-refs ref)))
(or (get info "io-refs") (list)))))
(append! comp-details {:io-refs (or (get info "io-refs") (list)) :render-target (get info "render-target") :deps (or (get info "deps") (list)) :source (get info "source") :name comp-name :is-pure (get info "is-pure") :affinity (get info "affinity")}))))
needed-names)
(append! pages-data {:pure-in-page pure-in-page :io-refs (len page-io-refs) :direct (get page "direct") :needed n :io-in-page io-in-page :components comp-details :savings savings :pct pct :path (get page "path") :name (get page "name")})))
pages-raw)
{:total-macros total-macros :pages pages-data :io-count io-count :pure-count pure-count :total-components total-components})))
(define
build-routing-analysis
(fn
((pages-raw :as list))
(let
((pages-data (list)) (client-count 0) (server-count 0))
(for-each
(fn
((page :as dict))
(let
((has-data (not (nil? (get page "data"))))
(content-src (or (get page "content-src") ""))
(mode nil)
(reason ""))
(cond
has-data
(do
(set! mode "server")
(set! reason "Has :data expression — needs server IO")
(set! server-count (+ server-count 1)))
(empty? content-src)
(do
(set! mode "server")
(set! reason "No content expression")
(set! server-count (+ server-count 1)))
:else (do
(set! mode "client")
(set! client-count (+ client-count 1))))
(append! pages-data {:reason reason :mode mode :content-expr (if (> (len content-src) 80) (str (slice content-src 0 80) "...") content-src) :has-data has-data :path (get page "path") :name (get page "name")})))
pages-raw)
{:pages pages-data :total-pages (+ client-count server-count) :server-count server-count :client-count client-count})))
(define
build-affinity-analysis
(fn ((demo-components :as list) (page-plans :as list)) {:components demo-components :page-plans page-plans}))