;; 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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "bg-blue-50 p-2 rounded overflow-x-auto") (get r "comp-result"))) (div (~tw :tokens "rounded-lg border border-blue-200 bg-blue-50/30 p-4 md:col-span-2") (h4 (~tw :tokens "font-semibold text-blue-700 text-sm mb-1") "build-routing-analysis " (span (~tw :tokens "text-xs text-blue-400") (str (get r "routing-ms") "ms"))) (p (~tw :tokens "text-xs text-blue-500 mb-2") "Classifies pages as client-routable or server-only based on whether they have data dependencies.") (div (~tw :tokens "text-xs text-blue-600") (p (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "max-w-3xl mx-auto px-4") (div (~tw :tokens "mb-8") (h2 (~tw :tokens "text-2xl font-bold text-stone-800 mb-2") "Bootstrapped Page Helpers") (p (~tw :tokens "text-stone-600 mb-4") "These functions are defined once in " (code (~tw :tokens "text-violet-700") "page-helpers.sx") " and bootstrapped to both Python (" (code (~tw :tokens "text-violet-700") "sx_ref.py") ") and JavaScript (" (code (~tw :tokens "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 (~tw :tokens "mb-8") (h3 (~tw :tokens "text-lg font-semibold text-stone-700 mb-3") "Server Results " (span (~tw :tokens "text-sm font-normal text-stone-500") (str "(Python, " server-total-ms "ms total)"))) (div (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "bg-stone-50 p-2 rounded overflow-x-auto") comp-source)) (div (~tw :tokens "rounded-lg border border-stone-200 p-4 md:col-span-2") (h4 (~tw :tokens "font-semibold text-stone-700 text-sm mb-1") "build-routing-analysis " (span (~tw :tokens "text-xs text-stone-400") (str routing-ms "ms"))) (p (~tw :tokens "text-xs text-stone-500 mb-2") "Classifies pages as client-routable or server-only based on whether they have data dependencies.") (div (~tw :tokens "text-xs text-stone-600") (p (~tw :tokens "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 (~tw :tokens "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 (~tw :tokens "mb-8") (h3 (~tw :tokens "text-lg font-semibold text-stone-700 mb-3") "Client Results " (span (~tw :tokens "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))))