Files
rose-ash/sx/sx/routing-analyzer.sx
giles de80d921e9 Prefix all SX URLs with /sx/ for WhatsApp-safe sharing
All routes moved under /sx/ prefix:
- / redirects to /sx/
- /sx/ serves home page
- /sx/<path:expr> is the catch-all for SX expression URLs
- Bare /(...) and /~... redirect to /sx/(...) and /sx/~...
- All ~600 hrefs, sx-get attrs, defhandler paths, redirect
  targets, and blueprint routes updated across 44 files

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-12 19:07:09 +00:00

97 lines
6.0 KiB
Plaintext

;; Routing analyzer — live demonstration of client-side routing classification.
;; Shows which pages route client-side (pure, instant) vs server-side (IO/data).
;; @css bg-green-100 text-green-800 bg-violet-600 bg-stone-200 text-violet-600 text-stone-600 text-green-600 rounded-full h-2.5 grid-cols-2 bg-blue-100 text-blue-800 bg-amber-100 text-amber-800 grid-cols-4 marker:text-stone-400 bg-blue-50 bg-amber-50 text-blue-700 text-amber-700 border-blue-200 border-amber-200 bg-blue-500 bg-amber-500 grid-cols-3 border-green-200 bg-green-50 text-green-700
(defcomp ~routing-analyzer-content (&key pages total-pages client-count
server-count registry-sample)
(~doc-page :title "Routing Analyzer"
(p :class "text-stone-600 mb-6"
"Live classification of all " (strong (str total-pages)) " pages by routing mode. "
"Pages without " (code ":data") " dependencies are "
(span :class "text-green-700 font-medium" "client-routable")
" — after initial load they render instantly from the page registry without a server roundtrip. "
"Pages with data dependencies fall back to "
(span :class "text-amber-700 font-medium" "server fetch")
" transparently. Powered by "
(a :href "/sx/(language.(spec.router))" :class "text-violet-700 underline" "router.sx")
" route matching and "
(a :href "/sx/(language.(spec.deps))" :class "text-violet-700 underline" "deps.sx")
" IO detection.")
(div :class "mb-8 grid grid-cols-4 gap-4"
(~analyzer-stat :label "Total Pages" :value (str total-pages)
:cls "text-violet-600")
(~analyzer-stat :label "Client-Routable" :value (str client-count)
:cls "text-green-600")
(~analyzer-stat :label "Server-Only" :value (str server-count)
:cls "text-amber-600")
(~analyzer-stat :label "Client Ratio" :value (str (round (* (/ client-count total-pages) 100)) "%")
:cls "text-blue-600"))
;; Route classification bar
(div :class "mb-8"
(div :class "flex items-center gap-2 mb-2"
(span :class "text-sm font-medium text-stone-600" "Client")
(div :class "flex-1")
(span :class "text-sm font-medium text-stone-600" "Server"))
(div :class "w-full bg-amber-200 rounded-full h-4 overflow-hidden"
(div :class "bg-green-500 h-4 rounded-l-full transition-all"
:style (str "width: " (round (* (/ client-count total-pages) 100)) "%"))))
(~doc-section :title "Route Table" :id "routes"
(div :class "space-y-2"
(map (fn (page)
(~routing-row
:name (get page "name")
:path (get page "path")
:mode (get page "mode")
:has-data (get page "has-data")
:content-expr (get page "content-expr")
:reason (get page "reason")))
pages)))
(~doc-section :title "Page Registry Format" :id "registry"
(p :class "text-stone-600 mb-4"
"The server serializes page metadata as SX dict literals inside "
(code "<script type=\"text/sx-pages\">")
". The client's parser reads these at boot, building a route table with parsed URL patterns. "
"No JSON involved — the same SX parser handles everything.")
(when (not (empty? registry-sample))
(div :class "not-prose"
(pre :class "text-xs leading-relaxed whitespace-pre-wrap overflow-x-auto bg-stone-100 rounded border border-stone-200 p-4"
(code (highlight registry-sample "lisp"))))))
(~doc-section :title "How Client Routing Works" :id "how"
(ol :class "list-decimal pl-5 space-y-2 text-stone-700"
(li (strong "Boot: ") "boot.sx finds " (code "<script type=\"text/sx-pages\">") ", calls " (code "parse") " on the SX content, then " (code "parse-route-pattern") " on each page's path to build " (code "_page-routes") ".")
(li (strong "Click: ") "orchestration.sx intercepts boost link clicks via " (code "bind-client-route-link") ". Extracts the pathname from the href.")
(li (strong "Match: ") (code "find-matching-route") " from router.sx tests the pathname against all parsed patterns. Returns the first match with extracted URL params.")
(li (strong "Check: ") "If the matched page has " (code ":has-data true") ", skip to server fetch. Otherwise proceed to client eval.")
(li (strong "Eval: ") (code "try-eval-content") " merges the component env + URL params + closure, then parses and renders the content expression to DOM.")
(li (strong "Swap: ") "On success, the rendered DOM replaces " (code "#main-panel") " contents, " (code "pushState") " updates the URL, and the console logs " (code "sx:route client /path") ".")
(li (strong "Fallback: ") "If anything fails (no match, eval error, missing component), the click falls through to a standard server fetch. Console logs " (code "sx:route server /path") ". The user sees no difference.")))))
(defcomp ~routing-row (&key (name :as string) (path :as string) (mode :as string) (has-data :as boolean) (content-expr :as string?) (reason :as string?))
(div :class (str "rounded border p-3 flex items-center gap-3 "
(if (= mode "client")
"border-green-200 bg-green-50"
"border-amber-200 bg-amber-50"))
;; Mode badge
(span :class (str "inline-block px-2 py-0.5 rounded text-xs font-bold uppercase "
(if (= mode "client")
"bg-green-600 text-white"
"bg-amber-500 text-white"))
mode)
;; Page info
(div :class "flex-1 min-w-0"
(div :class "flex items-center gap-2"
(span :class "font-mono font-semibold text-stone-800 text-sm" name)
(span :class "text-stone-400 text-xs font-mono" path))
(when reason
(div :class "text-xs text-stone-500 mt-0.5" reason)))
;; Content expression
(when content-expr
(div :class "hidden md:block max-w-xs truncate"
(code :class "text-xs text-stone-500" content-expr)))))