Files
rose-ash/sx/sx/docs-content.sx
giles 31a6e708fc
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 12m0s
more plans
2026-03-09 18:07:23 +00:00

111 lines
8.8 KiB
Plaintext

;; Docs page content — fully self-contained, no Python intermediaries
(defcomp ~sx-home-content ()
(div :id "main-content" :class "max-w-3xl mx-auto px-4 py-6"
(highlight "(defcomp ~sx-header ()
(a :href \"/\"
:sx-get \"/\" :sx-target \"#main-panel\"
:sx-select \"#main-panel\"
:sx-swap \"outerHTML\" :sx-push-url \"true\"
:class \"block max-w-3xl mx-auto px-4 pt-8 pb-4 text-center no-underline\"
(span :class \"text-4xl font-bold font-mono text-violet-700 block mb-2\"
\"(<sx>)\")
(p :class \"text-lg text-stone-500 mb-1\"
\"Framework free reactive hypermedia\")
(p :class \"text-xs text-stone-400\"
\"© Giles Bradshaw 2026\")))" "lisp")))
(defcomp ~docs-introduction-content ()
(~doc-page :title "Introduction"
(~doc-section :title "What is sx?" :id "what"
(p :class "text-stone-600"
"sx is an s-expression language for building web UIs. It combines htmx's server-first hypermedia approach with React's component model. The server sends s-expression source code over the wire. The client parses, evaluates, and renders it to DOM.")
(p :class "text-stone-600"
"The same evaluator runs on both server (Python) and client (JavaScript). Components defined once render identically in both environments."))
(~doc-section :title "Design decisions" :id "design"
(p :class "text-stone-600"
"HTML elements are first-class: (div :class \"card\" (p \"hello\")) renders exactly what you'd expect. Components use defcomp with keyword parameters and optional children. The evaluator supports let bindings, conditionals, lambda, map/filter/reduce, and ~80 primitives.")
(p :class "text-stone-600"
"sx replaces the pattern of shipping a JS framework + build step + client-side router + state management library just to render some server data. For most applications, sx eliminates the need for JavaScript entirely — htmx attributes handle interactivity, hyperscript handles small behaviours, and the server handles everything else."))
(~doc-section :title "What sx is not" :id "not"
(ul :class "space-y-2 text-stone-600"
(li "Not a general-purpose programming language — it's a UI rendering language")
(li "Not a full Lisp — it has macros, TCO, and delimited continuations, but no full call/cc")
(li "Not production-hardened at scale — it runs one website")))))
(defcomp ~docs-getting-started-content ()
(~doc-page :title "Getting Started"
(~doc-section :title "Minimal example" :id "minimal"
(p :class "text-stone-600"
"An sx response is s-expression source code with content type text/sx:")
(~doc-code :code (highlight "(div :class \"p-4 bg-white rounded\"\n (h1 :class \"text-2xl font-bold\" \"Hello, world!\")\n (p \"This is rendered from an s-expression.\"))" "lisp"))
(p :class "text-stone-600"
"Add sx-get to any element to make it fetch and render sx:"))
(~doc-section :title "Hypermedia attributes" :id "attrs"
(p :class "text-stone-600"
"Like htmx, sx adds attributes to HTML elements to trigger HTTP requests:")
(~doc-code :code (highlight "(button\n :sx-get \"/api/data\"\n :sx-target \"#result\"\n :sx-swap \"innerHTML\"\n \"Load data\")" "lisp"))
(p :class "text-stone-600"
"sx-get, sx-post, sx-put, sx-delete, sx-patch — all work the same way. The response is parsed as sx and rendered into the target element."))))
(defcomp ~docs-components-content ()
(~doc-page :title "Components"
(~doc-section :title "defcomp" :id "defcomp"
(p :class "text-stone-600"
"Components are defined with defcomp. They take keyword parameters and optional children:")
(~doc-code :code (highlight "(defcomp ~card (&key title subtitle &rest children)\n (div :class \"border rounded p-4\"\n (h2 :class \"font-bold\" title)\n (when subtitle (p :class \"text-stone-500\" subtitle))\n (div :class \"mt-3\" children)))" "lisp"))
(p :class "text-stone-600"
"Use components with the ~ prefix:")
(~doc-code :code (highlight "(~card :title \"My Card\" :subtitle \"A description\"\n (p \"First child\")\n (p \"Second child\"))" "lisp")))
(~doc-section :title "Component caching" :id "caching"
(p :class "text-stone-600"
"Component definitions are sent in a <script type=\"text/sx\" data-components> block. The client caches them in localStorage keyed by a content hash. On subsequent page loads, the client sends an SX-Components header listing what it has. The server only sends definitions the client is missing.")
(p :class "text-stone-600"
"This means the first page load sends all component definitions (~5-15KB). Subsequent navigations send zero component bytes — just the page content."))
(~doc-section :title "Parameters" :id "params"
(p :class "text-stone-600"
"&key declares keyword parameters. &rest children captures remaining positional arguments. Missing parameters evaluate to nil. Components always receive all declared parameters — use (when param ...) or (if param ... ...) to handle optional values."))))
(defcomp ~docs-evaluator-content ()
(~doc-page :title "Evaluator"
(~doc-section :title "Special forms" :id "special"
(p :class "text-stone-600"
"Special forms have lazy evaluation — arguments are not evaluated before the form runs:")
(~doc-code :code (highlight ";; Conditionals\n(if condition then-expr else-expr)\n(when condition body...)\n(cond (test1 body1) (test2 body2) (else default))\n\n;; Bindings\n(let ((name value) (name2 value2)) body...)\n(define name value)\n\n;; Functions\n(lambda (x y) (+ x y))\n(fn (x) (* x x))\n\n;; Sequencing\n(do expr1 expr2 expr3)\n(begin expr1 expr2)\n\n;; Threading\n(-> value (fn1 arg) (fn2 arg))" "lisp")))
(~doc-section :title "Higher-order forms" :id "higher"
(p :class "text-stone-600"
"These operate on collections with function arguments:")
(~doc-code :code (highlight "(map (fn (x) (* x 2)) (list 1 2 3)) ;; => (2 4 6)\n(filter (fn (x) (> x 2)) (list 1 2 3 4 5)) ;; => (3 4 5)\n(reduce (fn (acc x) (+ acc x)) 0 (list 1 2 3)) ;; => 6\n(some (fn (x) (> x 3)) (list 1 2 3 4)) ;; => true\n(every? (fn (x) (> x 0)) (list 1 2 3)) ;; => true" "lisp")))))
(defcomp ~docs-primitives-content (&key prims)
(~doc-page :title "Primitives"
(~doc-section :title "Built-in functions" :id "builtins"
(p :class "text-stone-600"
"sx provides ~80 built-in pure functions. They work identically on server (Python) and client (JavaScript).")
(div :class "space-y-6" prims))))
(defcomp ~docs-special-forms-content (&key forms)
(~doc-page :title "Special Forms"
(~doc-section :title "Syntactic constructs" :id "special-forms"
(p :class "text-stone-600"
"Special forms are syntactic constructs whose arguments are NOT evaluated before dispatch. Each form has its own evaluation rules — unlike primitives, which receive pre-evaluated values. Together with primitives, special forms define the complete language surface.")
(p :class "text-stone-600"
"Forms marked with a tail position enable " (a :href "/essays/tco" :class "text-violet-600 hover:underline" "tail-call optimization") " — recursive calls in tail position use constant stack space.")
(div :class "space-y-10" forms))))
(defcomp ~docs-server-rendering-content ()
(~doc-page :title "Server Rendering"
(~doc-section :title "Python API" :id "python"
(p :class "text-stone-600"
"The server-side sx library provides several entry points for rendering:")
(~doc-code :code (highlight "from shared.sx.helpers import sx_page, sx_response, sx_call\nfrom shared.sx.parser import SxExpr\n\n# Build a component call from Python kwargs\nsx_call(\"card\", title=\"Hello\", subtitle=\"World\")\n\n# Return an sx wire-format response\nreturn sx_response(sx_call(\"card\", title=\"Hello\"))\n\n# Return a full HTML page shell with sx boot\nreturn sx_page(ctx, page_sx)" "python")))
(~doc-section :title "sx_call" :id "sx-call"
(p :class "text-stone-600"
"sx_call converts Python kwargs to an s-expression component call. Snake_case becomes kebab-case. SxExpr values are inlined without quoting. None becomes nil. Bools become true/false."))
(~doc-section :title "sx_response" :id "sx-response"
(p :class "text-stone-600"
"sx_response returns a Quart Response with content type text/sx. It prepends missing component definitions, scans for CSS classes, and sets SX-Css-Hash and SX-Css-Add headers."))
(~doc-section :title "sx_page" :id "sx-page"
(p :class "text-stone-600"
"sx_page returns a minimal HTML document that boots the page from sx source. The browser loads component definitions and page sx from inline <script> tags, then sx.js renders everything client-side. CSS rules are pre-scanned and injected."))))