Component names now reflect filesystem location using / as path separator and : as namespace separator for shared components: ~sx-header → ~layouts/header ~layout-app-body → ~shared:layout/app-body ~blog-admin-dashboard → ~admin/dashboard 209 files, 4,941 replacements across all services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
265 lines
13 KiB
Plaintext
265 lines
13 KiB
Plaintext
;; page-helpers-demo.sx — Demo: same SX spec functions on server and client
|
|
;;
|
|
;; Shows page-helpers.sx functions running on Python (server-side, via sx_ref.py)
|
|
;; and JavaScript (client-side, via sx-browser.js) with identical results.
|
|
;; Server renders with render-to-html. Client runs as a defisland — pure SX,
|
|
;; no JavaScript file. The button click triggers spec functions via signals.
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Shared card component — used by both server and client results
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~page-helpers-demo/demo-result-card (&key (title :as string) (ms :as number) (desc :as string) (theme :as string) &rest children)
|
|
(let ((border (if (= theme "blue") "border-blue-200 bg-blue-50/30" "border-stone-200"))
|
|
(title-c (if (= theme "blue") "text-blue-700" "text-stone-700"))
|
|
(badge-c (if (= theme "blue") "text-blue-400" "text-stone-400"))
|
|
(desc-c (if (= theme "blue") "text-blue-500" "text-stone-500"))
|
|
(body-c (if (= theme "blue") "text-blue-600" "text-stone-600")))
|
|
(div :class (str "rounded-lg border p-4 " border)
|
|
(h4 :class (str "font-semibold text-sm mb-1 " title-c)
|
|
title " "
|
|
(span :class (str "text-xs " badge-c) (str ms "ms")))
|
|
(p :class (str "text-xs mb-2 " desc-c) desc)
|
|
(div :class (str "text-xs space-y-0.5 " body-c)
|
|
children))))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Client-side island — runs spec functions in the browser on button click
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defisland ~page-helpers-demo/demo-client-runner (&key sf-source attr-detail req-attrs attr-keys)
|
|
(let ((results (signal nil))
|
|
(running (signal false))
|
|
(run-demo (fn (e)
|
|
(reset! running true)
|
|
(let* ((t0 (now-ms))
|
|
|
|
;; 1. categorize-special-forms
|
|
(t1 (now-ms))
|
|
(sf-exprs (sx-parse sf-source))
|
|
(sf-result (categorize-special-forms sf-exprs))
|
|
(sf-ms (- (now-ms) t1))
|
|
(sf-cats {})
|
|
(sf-total 0)
|
|
;; 2. build-reference-data
|
|
(t2 (now-ms))
|
|
(ref-result (build-reference-data "attributes"
|
|
{"req-attrs" req-attrs "beh-attrs" (list) "uniq-attrs" (list)}
|
|
attr-keys))
|
|
(ref-ms (- (now-ms) t2))
|
|
(ref-sample (slice (or (get ref-result "req-attrs") (list)) 0 3))
|
|
|
|
;; 3. build-attr-detail
|
|
(t3 (now-ms))
|
|
(attr-result (build-attr-detail "sx-get" attr-detail))
|
|
(attr-ms (- (now-ms) t3))
|
|
|
|
;; 4. build-component-source
|
|
(t4 (now-ms))
|
|
(comp-result (build-component-source
|
|
{"type" "component" "name" "~demo-card"
|
|
"params" (list "title" "subtitle")
|
|
"has-children" true
|
|
"body-sx" "(div :class \"card\"\n (h2 title)\n (when subtitle (p subtitle))\n children)"
|
|
"affinity" "auto"}))
|
|
(comp-ms (- (now-ms) t4))
|
|
|
|
;; 5. build-routing-analysis
|
|
(t5 (now-ms))
|
|
(routing-result (build-routing-analysis (list
|
|
{"name" "home" "path" "/" "has-data" false "content-src" "(~home-content)"}
|
|
{"name" "dashboard" "path" "/dash" "has-data" true "content-src" "(~dashboard)"}
|
|
{"name" "about" "path" "/about" "has-data" false "content-src" "(~about-content)"}
|
|
{"name" "settings" "path" "/settings" "has-data" true "content-src" "(~settings)"})))
|
|
(routing-ms (- (now-ms) t5))
|
|
|
|
(total-ms (- (now-ms) t0)))
|
|
|
|
;; Post-process sf-result: count forms per category
|
|
(for-each (fn (k)
|
|
(dict-set! sf-cats k (len (get sf-result k))))
|
|
(keys sf-result))
|
|
|
|
(reset! results
|
|
{"sf-cats" sf-cats
|
|
"sf-total" (reduce (fn (acc k) (+ acc (get sf-cats k))) 0 (keys sf-cats))
|
|
"sf-ms" sf-ms
|
|
"ref-sample" ref-sample "ref-ms" ref-ms
|
|
"attr-result" attr-result "attr-ms" attr-ms
|
|
"comp-result" comp-result "comp-ms" comp-ms
|
|
"routing-result" routing-result "routing-ms" routing-ms
|
|
"total-ms" total-ms})))))
|
|
|
|
(<>
|
|
(button
|
|
:class (if (deref running)
|
|
"px-4 py-2 rounded-md bg-blue-600 text-white font-medium text-sm cursor-default mb-4"
|
|
"px-4 py-2 rounded-md bg-violet-600 text-white font-medium text-sm hover:bg-violet-700 transition-colors mb-4")
|
|
:on-click run-demo
|
|
(if (deref running)
|
|
(str "Done (" (get (deref results) "total-ms") "ms total)")
|
|
"Run in Browser"))
|
|
|
|
(when (deref results)
|
|
(let ((r (deref results)))
|
|
(div :class "grid grid-cols-1 md:grid-cols-2 gap-4"
|
|
|
|
(~page-helpers-demo/demo-result-card
|
|
:title "categorize-special-forms"
|
|
:ms (get r "sf-ms") :theme "blue"
|
|
:desc "Parses special-forms.sx and classifies each form by category (control flow, binding, quoting, etc)."
|
|
(p :class "text-sm mb-1"
|
|
(str (get r "sf-total") " forms in "
|
|
(len (keys (get r "sf-cats"))) " categories"))
|
|
(map (fn (k)
|
|
(div (str k ": " (get (get r "sf-cats") k))))
|
|
(keys (get r "sf-cats"))))
|
|
|
|
(~page-helpers-demo/demo-result-card
|
|
:title "build-reference-data"
|
|
:ms (get r "ref-ms") :theme "blue"
|
|
:desc "Takes raw attribute tuples and generates reference table rows with documentation hrefs."
|
|
(p :class "text-sm mb-1"
|
|
(str (len (get r "ref-sample")) " attributes with detail page links"))
|
|
(map (fn (item)
|
|
(div (str (get item "name") " → "
|
|
(or (get item "href") "no detail page"))))
|
|
(get r "ref-sample")))
|
|
|
|
(~page-helpers-demo/demo-result-card
|
|
:title "build-attr-detail"
|
|
:ms (get r "attr-ms") :theme "blue"
|
|
:desc "Builds a detail page data structure for a single attribute (sx-get): title, wire ID, handler status."
|
|
(div (str "title: " (get (get r "attr-result") "attr-title")))
|
|
(div (str "wire-id: " (or (get (get r "attr-result") "attr-wire-id") "none")))
|
|
(div (str "has handler: " (if (get (get r "attr-result") "attr-handler") "yes" "no"))))
|
|
|
|
(~page-helpers-demo/demo-result-card
|
|
:title "build-component-source"
|
|
:ms (get r "comp-ms") :theme "blue"
|
|
:desc "Reconstructs a defcomp source definition from a component metadata dict (name, params, body)."
|
|
(pre :class "bg-blue-50 p-2 rounded overflow-x-auto"
|
|
(get r "comp-result")))
|
|
|
|
(div :class "rounded-lg border border-blue-200 bg-blue-50/30 p-4 md:col-span-2"
|
|
(h4 :class "font-semibold text-blue-700 text-sm mb-1"
|
|
"build-routing-analysis "
|
|
(span :class "text-xs text-blue-400" (str (get r "routing-ms") "ms")))
|
|
(p :class "text-xs text-blue-500 mb-2"
|
|
"Classifies pages as client-routable or server-only based on whether they have data dependencies.")
|
|
(div :class "text-xs text-blue-600"
|
|
(p :class "text-sm mb-1"
|
|
(str (get (get r "routing-result") "total-pages") " pages: "
|
|
(get (get r "routing-result") "client-count") " client-routable, "
|
|
(get (get r "routing-result") "server-count") " server-only"))
|
|
(div :class "space-y-0.5"
|
|
(map (fn (pg)
|
|
(div (str (get pg "name") " → " (get pg "mode")
|
|
(when (not (empty? (get pg "reason")))
|
|
(str " (" (get pg "reason") ")")))))
|
|
(get (get r "routing-result") "pages")))))))))))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Main page component — server-rendered content + client island
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~page-helpers-demo/content (&key
|
|
sf-categories sf-total sf-ms
|
|
ref-sample ref-ms
|
|
attr-result attr-ms
|
|
comp-source comp-ms
|
|
routing-result routing-ms
|
|
server-total-ms
|
|
sf-source
|
|
attr-detail req-attrs attr-keys)
|
|
|
|
(div :class "max-w-3xl mx-auto px-4"
|
|
(div :class "mb-8"
|
|
(h2 :class "text-2xl font-bold text-stone-800 mb-2" "Bootstrapped Page Helpers")
|
|
(p :class "text-stone-600 mb-4"
|
|
"These functions are defined once in "
|
|
(code :class "text-violet-700" "page-helpers.sx")
|
|
" and bootstrapped to both Python ("
|
|
(code :class "text-violet-700" "sx_ref.py")
|
|
") and JavaScript ("
|
|
(code :class "text-violet-700" "sx-browser.js")
|
|
"). The server ran them in Python during this page load. Click the button below to run the identical functions client-side in the browser — same spec, same inputs, same results."))
|
|
|
|
;; Server results
|
|
(div :class "mb-8"
|
|
(h3 :class "text-lg font-semibold text-stone-700 mb-3"
|
|
"Server Results "
|
|
(span :class "text-sm font-normal text-stone-500"
|
|
(str "(Python, " server-total-ms "ms total)")))
|
|
|
|
(div :class "grid grid-cols-1 md:grid-cols-2 gap-4"
|
|
|
|
(~page-helpers-demo/demo-result-card
|
|
:title "categorize-special-forms"
|
|
:ms sf-ms :theme "stone"
|
|
:desc "Parses special-forms.sx and classifies each form by category (control flow, binding, quoting, etc)."
|
|
(p :class "text-sm mb-1"
|
|
(str sf-total " forms in "
|
|
(len (keys sf-categories)) " categories"))
|
|
(map (fn (k)
|
|
(div (str k ": " (get sf-categories k))))
|
|
(keys sf-categories)))
|
|
|
|
(~page-helpers-demo/demo-result-card
|
|
:title "build-reference-data"
|
|
:ms ref-ms :theme "stone"
|
|
:desc "Takes raw attribute tuples and generates reference table rows with documentation hrefs."
|
|
(p :class "text-sm mb-1"
|
|
(str (len ref-sample) " attributes with detail page links"))
|
|
(map (fn (item)
|
|
(div (str (get item "name") " → "
|
|
(or (get item "href") "no detail page"))))
|
|
ref-sample))
|
|
|
|
(~page-helpers-demo/demo-result-card
|
|
:title "build-attr-detail"
|
|
:ms attr-ms :theme "stone"
|
|
:desc "Builds a detail page data structure for a single attribute (sx-get): title, wire ID, handler status."
|
|
(div (str "title: " (get attr-result "attr-title")))
|
|
(div (str "wire-id: " (or (get attr-result "attr-wire-id") "none")))
|
|
(div (str "has handler: " (if (get attr-result "attr-handler") "yes" "no"))))
|
|
|
|
(~page-helpers-demo/demo-result-card
|
|
:title "build-component-source"
|
|
:ms comp-ms :theme "stone"
|
|
:desc "Reconstructs a defcomp source definition from a component metadata dict (name, params, body)."
|
|
(pre :class "bg-stone-50 p-2 rounded overflow-x-auto"
|
|
comp-source))
|
|
|
|
(div :class "rounded-lg border border-stone-200 p-4 md:col-span-2"
|
|
(h4 :class "font-semibold text-stone-700 text-sm mb-1"
|
|
"build-routing-analysis "
|
|
(span :class "text-xs text-stone-400" (str routing-ms "ms")))
|
|
(p :class "text-xs text-stone-500 mb-2"
|
|
"Classifies pages as client-routable or server-only based on whether they have data dependencies.")
|
|
(div :class "text-xs text-stone-600"
|
|
(p :class "text-sm mb-1"
|
|
(str (get routing-result "total-pages") " pages: "
|
|
(get routing-result "client-count") " client-routable, "
|
|
(get routing-result "server-count") " server-only"))
|
|
(div :class "space-y-0.5"
|
|
(map (fn (pg)
|
|
(div (str (get pg "name") " → " (get pg "mode")
|
|
(when (not (empty? (get pg "reason")))
|
|
(str " (" (get pg "reason") ")")))))
|
|
(get routing-result "pages")))))))
|
|
|
|
;; Client execution area — pure SX island, no JavaScript file
|
|
(div :class "mb-8"
|
|
(h3 :class "text-lg font-semibold text-stone-700 mb-3"
|
|
"Client Results "
|
|
(span :class "text-sm font-normal text-stone-500" "(JavaScript, sx-browser.js)"))
|
|
|
|
(~page-helpers-demo/demo-client-runner
|
|
:sf-source sf-source
|
|
:attr-detail attr-detail
|
|
:req-attrs req-attrs
|
|
:attr-keys attr-keys))))
|