Three fixes: 1. Framework: render-dom-lake preserves SSR elements during hydration. When client-side render-to-dom encounters a lake with an existing DOM element (from SSR), it reuses that element instead of creating a new one. This prevents the SSR HTML from being replaced with unresolvable raw SX expressions (~tw calls). 2. Stepper: skip rebuild-preview on initial hydration. Uses a non- reactive dict flag (not a signal) to avoid triggering the effect twice. On first run, just initializes the DOM stack from the existing SSR content by computing open-element depth from step types and walking lastElementChild. 3. Stepper: rebuild-preview computes correct DOM stack after re-render. Same depth computation + DOM walk approach. This fixes the bug where do-step after do-back would append elements to the wrong parent (e.g. "sx" span outside h1). Also: increased code view font-size from 0.5rem to 0.85rem. Playwright tests: - lake never shows raw SX during hydration (mutation observer) - back 6 + forward 6 keeps all 4 spans inside h1 Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
233 lines
11 KiB
Plaintext
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}))
|