Files
rose-ash/sx/sx/page-helpers-demo.sx
giles b0920a1121 Rename all 1,169 components to path-based names with namespace support
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>
2026-03-12 22:00:12 +00:00

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))))