Move sx docs markup from Python to .sx files (Phase 2)
Migrate ~2,500 lines of SX markup from Python string concatenation in essays.py to proper .sx defcomp definitions: - docs-content.sx: 8 defcomps for docs pages (intro, getting-started, components, evaluator, primitives, css, server-rendering, home) - protocols.sx: 6 defcomps for protocol documentation pages - essays.sx: 9 essay defcomps (pure content, no params) - examples.sx: template defcomp receiving data values, calls highlight internally — Python passes raw code strings, never SX - reference.sx: 6 defcomps for data-driven reference pages essays.py reduced from 2,699 to 619 lines. Docs/protocol/essay functions become one-liners returning component names. Example functions use sx_call to pass data values to the template. Reference functions pass data-built component trees via SxExpr. renders.py: removed _code, _example_code, _placeholder, _clear_components_btn (now handled by .sx templates). helpers.py: removed inline hero code building, uses ~sx-home-content. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
115
sx/sx/docs-content.sx
Normal file
115
sx/sx/docs-content.sx
Normal file
@@ -0,0 +1,115 @@
|
||||
;; Docs page content — fully self-contained, no Python intermediaries
|
||||
|
||||
(defcomp ~sx-home-content ()
|
||||
(div :id "main-content"
|
||||
(~sx-hero (highlight "(div :class \"p-4 bg-white rounded shadow\"\n (h1 :class \"text-2xl font-bold\" \"Hello\")\n (button :sx-get \"/api/data\"\n :sx-target \"#result\"\n \"Load data\"))" "lisp"))
|
||||
(~sx-philosophy)
|
||||
(~sx-how-it-works)
|
||||
(~sx-credits)))
|
||||
|
||||
(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 and TCO, but no continuations or 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-css-content ()
|
||||
(~doc-page :title "On-Demand CSS"
|
||||
(~doc-section :title "How it works" :id "how"
|
||||
(p :class "text-stone-600"
|
||||
"sx scans every response for CSS class names used in :class attributes. It looks up only those classes in a pre-parsed Tailwind CSS registry and ships just the rules that are needed. No build step. No purging. No unused CSS.")
|
||||
(p :class "text-stone-600"
|
||||
"On the first page load, the full set of used classes is embedded in a <style> block. A hash of the class set is stored. On subsequent navigations, the client sends the hash in the SX-Css header. The server computes the diff and sends only new rules via SX-Css-Add and a <style data-sx-css> block."))
|
||||
(~doc-section :title "The protocol" :id "protocol"
|
||||
(~doc-code :code (highlight "# First page load:\nGET / HTTP/1.1\n\nHTTP/1.1 200 OK\nContent-Type: text/html\n# Full CSS in <style id=\"sx-css\"> + hash in <meta name=\"sx-css-classes\">\n\n# Subsequent navigation:\nGET /about HTTP/1.1\nSX-Css: a1b2c3d4\n\nHTTP/1.1 200 OK\nContent-Type: text/sx\nSX-Css-Hash: e5f6g7h8\nSX-Css-Add: bg-blue-500,text-white,rounded-lg\n# Only new rules in <style data-sx-css>" "bash")))
|
||||
(~doc-section :title "Advantages" :id "advantages"
|
||||
(ul :class "space-y-2 text-stone-600"
|
||||
(li "Zero build step — no Tailwind CLI, no PostCSS, no purging")
|
||||
(li "Exact CSS — never ships a rule that isn't used on the page")
|
||||
(li "Incremental — subsequent navigations only ship new rules")
|
||||
(li "Component-aware — pre-scans component definitions at registration time")))
|
||||
(~doc-section :title "Disadvantages" :id "disadvantages"
|
||||
(ul :class "space-y-2 text-stone-600"
|
||||
(li "Requires the full Tailwind CSS file loaded in memory at startup (~4MB parsed)")
|
||||
(li "Regex-based class scanning — can miss dynamically constructed class names")
|
||||
(li "No @apply support — classes must be used directly")
|
||||
(li "Tied to Tailwind's utility class naming conventions")))))
|
||||
|
||||
(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."))))
|
||||
28
sx/sx/essays.sx
Normal file
28
sx/sx/essays.sx
Normal file
File diff suppressed because one or more lines are too long
58
sx/sx/examples.sx
Normal file
58
sx/sx/examples.sx
Normal file
@@ -0,0 +1,58 @@
|
||||
;; Example page template and reference index
|
||||
;; Template receives data values (code strings, titles), calls highlight internally.
|
||||
|
||||
(defcomp ~example-page-content (&key title description demo-description demo
|
||||
sx-code sx-lang handler-code handler-lang
|
||||
comp-placeholder-id wire-placeholder-id wire-note
|
||||
comp-heading handler-heading)
|
||||
(~doc-page :title title
|
||||
(p :class "text-stone-600 mb-6" description)
|
||||
(~example-card :title "Demo" :description demo-description
|
||||
(~example-demo demo))
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mt-6" "S-expression")
|
||||
(~example-source :code (highlight sx-code (if sx-lang sx-lang "lisp")))
|
||||
(when comp-placeholder-id
|
||||
(<>
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mt-6"
|
||||
(if comp-heading comp-heading "Component"))
|
||||
(~doc-placeholder :id comp-placeholder-id)))
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mt-6"
|
||||
(if handler-heading handler-heading "Server handler"))
|
||||
(~example-source :code (highlight handler-code (if handler-lang handler-lang "python")))
|
||||
(div :class "flex items-center justify-between mt-6"
|
||||
(h3 :class "text-lg font-semibold text-stone-700" "Wire response")
|
||||
(~doc-clear-cache-btn))
|
||||
(when wire-note
|
||||
(p :class "text-stone-500 text-sm mb-2" wire-note))
|
||||
(when wire-placeholder-id
|
||||
(~doc-placeholder :id wire-placeholder-id))))
|
||||
|
||||
(defcomp ~reference-index-content ()
|
||||
(~doc-page :title "Reference"
|
||||
(p :class "text-stone-600 mb-6"
|
||||
"Complete reference for the sx client library.")
|
||||
(div :class "grid gap-4 sm:grid-cols-2"
|
||||
(a :href "/reference/attributes"
|
||||
:sx-get "/reference/attributes" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Attributes")
|
||||
(p :class "text-stone-600 text-sm" "All sx attributes — request verbs, behavior modifiers, and sx-unique features."))
|
||||
(a :href "/reference/headers"
|
||||
:sx-get "/reference/headers" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Headers")
|
||||
(p :class "text-stone-600 text-sm" "Custom HTTP headers used to coordinate between the sx client and server."))
|
||||
(a :href "/reference/events"
|
||||
:sx-get "/reference/events" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "Events")
|
||||
(p :class "text-stone-600 text-sm" "DOM events fired during the sx request lifecycle."))
|
||||
(a :href "/reference/js-api"
|
||||
:sx-get "/reference/js-api" :sx-target "#main-panel" :sx-select "#main-panel"
|
||||
:sx-swap "outerHTML" :sx-push-url "true"
|
||||
:class "block p-5 rounded-lg border border-stone-200 hover:border-violet-300 hover:shadow-sm transition-all no-underline"
|
||||
(h3 :class "text-lg font-semibold text-violet-700 mb-1" "JS API")
|
||||
(p :class "text-stone-600 text-sm" "JavaScript functions for parsing, evaluating, and rendering s-expressions.")))))
|
||||
98
sx/sx/protocols.sx
Normal file
98
sx/sx/protocols.sx
Normal file
@@ -0,0 +1,98 @@
|
||||
;; Protocol documentation pages — fully self-contained
|
||||
|
||||
(defcomp ~protocol-wire-format-content ()
|
||||
(~doc-page :title "Wire Format"
|
||||
(~doc-section :title "The text/sx content type" :id "content-type"
|
||||
(p :class "text-stone-600"
|
||||
"sx responses use content type text/sx. The body is s-expression source code. The client parses and evaluates it, then renders the result into the DOM.")
|
||||
(~doc-code :code (highlight "HTTP/1.1 200 OK\nContent-Type: text/sx\nSX-Css-Hash: a1b2c3d4\n\n(div :class \"p-4\"\n (~card :title \"Hello\"))" "bash")))
|
||||
(~doc-section :title "Request lifecycle" :id "lifecycle"
|
||||
(p :class "text-stone-600"
|
||||
"1. User interacts with an element that has sx-get/sx-post/etc.")
|
||||
(p :class "text-stone-600"
|
||||
"2. sx.js fires sx:beforeRequest, then sends the HTTP request with SX-Request: true header.")
|
||||
(p :class "text-stone-600"
|
||||
"3. Server builds s-expression tree, scans CSS classes, prepends missing component definitions.")
|
||||
(p :class "text-stone-600"
|
||||
"4. Client receives text/sx response, parses it, evaluates it, renders to DOM.")
|
||||
(p :class "text-stone-600"
|
||||
"5. sx.js fires sx:afterSwap and sx:afterSettle.")
|
||||
(p :class "text-stone-600"
|
||||
"6. Any sx-swap-oob elements are swapped into their targets elsewhere in the DOM."))
|
||||
(~doc-section :title "Component definitions" :id "components"
|
||||
(p :class "text-stone-600"
|
||||
"On full page loads, component definitions are in <script type=\"text/sx\" data-components>. On subsequent sx requests, missing definitions are prepended to the response body. The client caches definitions in localStorage keyed by a content hash."))))
|
||||
|
||||
(defcomp ~protocol-fragments-content ()
|
||||
(~doc-page :title "Cross-Service Fragments"
|
||||
(~doc-section :title "Fragment protocol" :id "protocol"
|
||||
(p :class "text-stone-600"
|
||||
"Rose Ash runs as independent microservices. Each service can expose HTML or sx fragments that other services compose into their pages. Fragment endpoints return text/sx or text/html.")
|
||||
(p :class "text-stone-600"
|
||||
"The frag resolver is an I/O primitive in the render tree:")
|
||||
(~doc-code :code (highlight "(frag \"blog\" \"link-card\" :slug \"hello\")" "lisp")))
|
||||
(~doc-section :title "SxExpr wrapping" :id "wrapping"
|
||||
(p :class "text-stone-600"
|
||||
"When a fragment returns text/sx, the response is wrapped in an SxExpr and embedded directly in the render tree. When it returns text/html, it's wrapped in a ~rich-text component that inserts the HTML via raw!. This allows transparent composition across service boundaries."))
|
||||
(~doc-section :title "fetch_fragments()" :id "fetch"
|
||||
(p :class "text-stone-600"
|
||||
"The Python helper fetch_fragments() fetches multiple fragments in parallel via asyncio.gather(). Fragments are cached in Redis with short TTLs. Each fragment request is HMAC-signed for authentication."))))
|
||||
|
||||
(defcomp ~protocol-resolver-io-content ()
|
||||
(~doc-page :title "Resolver I/O"
|
||||
(~doc-section :title "Async I/O primitives" :id "primitives"
|
||||
(p :class "text-stone-600"
|
||||
"The sx resolver identifies I/O nodes in the render tree, groups them, executes them in parallel via asyncio.gather(), and substitutes results back in.")
|
||||
(p :class "text-stone-600"
|
||||
"I/O primitives:")
|
||||
(ul :class "space-y-2 text-stone-600"
|
||||
(li (span :class "font-mono text-violet-700" "frag") " — fetch a cross-service fragment")
|
||||
(li (span :class "font-mono text-violet-700" "query") " — read data from another service")
|
||||
(li (span :class "font-mono text-violet-700" "action") " — execute a write on another service")
|
||||
(li (span :class "font-mono text-violet-700" "current-user") " — resolve the current authenticated user")))
|
||||
(~doc-section :title "Execution model" :id "execution"
|
||||
(p :class "text-stone-600"
|
||||
"The render tree is walked to find I/O nodes. All nodes at the same depth are gathered and executed in parallel. Results replace the I/O nodes in the tree. The walk continues until no more I/O nodes are found. This typically completes in 1-2 passes."))))
|
||||
|
||||
(defcomp ~protocol-internal-services-content ()
|
||||
(~doc-page :title "Internal Services"
|
||||
(~doc-note
|
||||
(p "Honest note: the internal service protocol is JSON, not sx. Sx is the composition layer on top. The protocols below are the plumbing underneath."))
|
||||
(~doc-section :title "HMAC-signed HTTP" :id "hmac"
|
||||
(p :class "text-stone-600"
|
||||
"Services communicate via HMAC-signed HTTP requests with short timeouts:")
|
||||
(ul :class "space-y-2 text-stone-600 font-mono text-sm"
|
||||
(li "GET /internal/data/{query} — read data (3s timeout)")
|
||||
(li "POST /internal/actions/{action} — execute write (5s timeout)")
|
||||
(li "POST /internal/inbox — ActivityPub-shaped event delivery")))
|
||||
(~doc-section :title "fetch_data / call_action" :id "fetch"
|
||||
(p :class "text-stone-600"
|
||||
"Python helpers fetch_data() and call_action() handle HMAC signing, serialization, and error handling. They resolve service URLs from environment variables (INTERNAL_URL_BLOG, etc) and fall back to public URLs in development."))))
|
||||
|
||||
(defcomp ~protocol-activitypub-content ()
|
||||
(~doc-page :title "ActivityPub"
|
||||
(~doc-note
|
||||
(p "Honest note: ActivityPub wire format is JSON-LD, not sx. This documents how AP integrates with the sx rendering layer."))
|
||||
(~doc-section :title "AP activities" :id "activities"
|
||||
(p :class "text-stone-600"
|
||||
"Rose Ash services communicate cross-domain writes via ActivityPub-shaped activities. Each service has a virtual actor. Activities are JSON-LD objects sent to /internal/inbox endpoints. RSA signatures authenticate the sender."))
|
||||
(~doc-section :title "Event bus" :id "bus"
|
||||
(p :class "text-stone-600"
|
||||
"The event bus dispatches activities to registered handlers. Handlers are async functions that process the activity and may trigger side effects. The bus runs as a background processor in each service."))))
|
||||
|
||||
(defcomp ~protocol-future-content ()
|
||||
(~doc-page :title "Future Possibilities"
|
||||
(~doc-note
|
||||
(p "This page is speculative. Nothing here is implemented. It documents ideas that may or may not happen."))
|
||||
(~doc-section :title "Custom protocol schemes" :id "schemes"
|
||||
(p :class "text-stone-600"
|
||||
"sx:// and sxs:// as custom URI schemes for content addressing or deep linking. An sx:// URI could resolve to an sx expression from a federated registry. This is technically feasible but practically unnecessary for a single-site deployment."))
|
||||
(~doc-section :title "Sx as AP serialization" :id "ap-sx"
|
||||
(p :class "text-stone-600"
|
||||
"ActivityPub objects could be serialized as s-expressions instead of JSON-LD. S-expressions are more compact and easier to parse. The practical barrier: the entire AP ecosystem expects JSON-LD."))
|
||||
(~doc-section :title "Sx-native federation" :id "federation"
|
||||
(p :class "text-stone-600"
|
||||
"Federated services could exchange sx fragments directly — render a remote user's profile card by fetching its sx source from their server. This requires trust and standardization that doesn't exist yet."))
|
||||
(~doc-section :title "Realistic assessment" :id "realistic"
|
||||
(p :class "text-stone-600"
|
||||
"The most likely near-term improvement is sx:// deep linking for client-side component resolution. Everything else requires ecosystem adoption that one project can't drive alone."))))
|
||||
51
sx/sx/reference.sx
Normal file
51
sx/sx/reference.sx
Normal file
@@ -0,0 +1,51 @@
|
||||
;; Reference page layouts — receive data from Python primitives
|
||||
|
||||
(defcomp ~reference-attrs-content (&key req-table beh-table uniq-table)
|
||||
(~doc-page :title "Attribute Reference"
|
||||
(p :class "text-stone-600 mb-6"
|
||||
"sx attributes mirror htmx where possible. This table shows all available attributes and their status.")
|
||||
(div :class "space-y-8"
|
||||
req-table
|
||||
beh-table
|
||||
uniq-table)))
|
||||
|
||||
(defcomp ~reference-headers-content (&key req-table resp-table)
|
||||
(~doc-page :title "Headers"
|
||||
(p :class "text-stone-600 mb-6"
|
||||
"sx uses custom HTTP headers to coordinate between client and server.")
|
||||
(div :class "space-y-8"
|
||||
req-table
|
||||
resp-table)))
|
||||
|
||||
(defcomp ~reference-events-content (&key table)
|
||||
(~doc-page :title "Events"
|
||||
table))
|
||||
|
||||
(defcomp ~reference-js-api-content (&key table)
|
||||
(~doc-page :title "JavaScript API"
|
||||
table))
|
||||
|
||||
(defcomp ~reference-attr-detail-content (&key title description demo
|
||||
example-code handler-code wire-placeholder-id)
|
||||
(~doc-page :title title
|
||||
(p :class "text-stone-600 mb-6" description)
|
||||
(when demo
|
||||
(~example-card :title "Demo"
|
||||
(~example-demo demo)))
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mt-6" "S-expression")
|
||||
(~example-source :code (highlight example-code "lisp"))
|
||||
(when handler-code
|
||||
(<>
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mt-6" "Server handler")
|
||||
(~example-source :code (highlight handler-code "lisp"))))
|
||||
(when wire-placeholder-id
|
||||
(<>
|
||||
(h3 :class "text-lg font-semibold text-stone-700 mt-6" "Wire response")
|
||||
(p :class "text-stone-500 text-sm mb-2"
|
||||
"Trigger the demo to see the raw response the server sends.")
|
||||
(~doc-placeholder :id wire-placeholder-id)))))
|
||||
|
||||
(defcomp ~reference-attr-not-found (&key slug)
|
||||
(~doc-page :title "Not Found"
|
||||
(p :class "text-stone-600"
|
||||
(str "No documentation found for \"" slug "\"."))))
|
||||
File diff suppressed because it is too large
Load Diff
@@ -7,24 +7,14 @@ from .essays import (
|
||||
_reference_index_sx, _reference_attr_detail_sx,
|
||||
)
|
||||
from .utils import _docs_nav_sx, _reference_nav_sx, _protocols_nav_sx, _examples_nav_sx, _essays_nav_sx
|
||||
from content.highlight import highlight
|
||||
|
||||
|
||||
def home_content_sx() -> str:
|
||||
"""Home page content as sx wire format."""
|
||||
hero_code = highlight('(div :class "p-4 bg-white rounded shadow"\n'
|
||||
' (h1 :class "text-2xl font-bold" "Hello")\n'
|
||||
' (button :sx-get "/api/data"\n'
|
||||
' :sx-target "#result"\n'
|
||||
' "Load data"))', "lisp")
|
||||
return (
|
||||
f'(section :id "main-panel"'
|
||||
f' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
|
||||
f' (div :id "main-content"'
|
||||
f' (~sx-hero {hero_code})'
|
||||
f' (~sx-philosophy)'
|
||||
f' (~sx-how-it-works)'
|
||||
f' (~sx-credits)))'
|
||||
'(section :id "main-panel"'
|
||||
' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
|
||||
' (~sx-home-content))'
|
||||
)
|
||||
|
||||
|
||||
@@ -92,25 +82,9 @@ def _register_sx_helpers() -> None:
|
||||
return label
|
||||
return None
|
||||
|
||||
def _home_content():
|
||||
"""Build home page content (uses highlight for hero code block)."""
|
||||
hero_code = _highlight(
|
||||
'(div :class "p-4 bg-white rounded shadow"\n'
|
||||
' (h1 :class "text-2xl font-bold" "Hello")\n'
|
||||
' (button :sx-get "/api/data"\n'
|
||||
' :sx-target "#result"\n'
|
||||
' "Load data"))', "lisp")
|
||||
return (
|
||||
f'(div :id "main-content"'
|
||||
f' (~sx-hero {hero_code})'
|
||||
f' (~sx-philosophy)'
|
||||
f' (~sx-how-it-works)'
|
||||
f' (~sx-credits))'
|
||||
)
|
||||
|
||||
register_page_helpers("sx", {
|
||||
# Content builders
|
||||
"home-content": _home_content,
|
||||
"home-content": lambda: "(~sx-home-content)",
|
||||
"docs-content": _docs_content_sx,
|
||||
"reference-content": _reference_content_sx,
|
||||
"reference-index-content": _reference_index_sx,
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
"""Public render/utility functions called from bp routes."""
|
||||
from __future__ import annotations
|
||||
|
||||
from content.highlight import highlight
|
||||
|
||||
|
||||
def _code(code: str, language: str = "lisp") -> str:
|
||||
"""Build a ~doc-code component with highlighted content."""
|
||||
highlighted = highlight(code, language)
|
||||
return f'(~doc-code :code {highlighted})'
|
||||
|
||||
|
||||
def _example_code(code: str, language: str = "lisp") -> str:
|
||||
"""Build an ~example-source component with highlighted content."""
|
||||
highlighted = highlight(code, language)
|
||||
return f'(~example-source :code {highlighted})'
|
||||
|
||||
|
||||
def _placeholder(div_id: str) -> str:
|
||||
"""Empty placeholder that will be filled by OOB swap on interaction."""
|
||||
from shared.sx.helpers import sx_call
|
||||
return sx_call("doc-placeholder", id=div_id)
|
||||
|
||||
|
||||
def _component_source_text(*names: str) -> str:
|
||||
"""Get defcomp source text for named components."""
|
||||
@@ -47,12 +27,6 @@ def _oob_code(target_id: str, text: str) -> str:
|
||||
return sx_call("doc-oob-code", target_id=target_id, text=text)
|
||||
|
||||
|
||||
def _clear_components_btn() -> str:
|
||||
"""Button that clears the client-side component cache (localStorage + in-memory)."""
|
||||
from shared.sx.helpers import sx_call
|
||||
return sx_call("doc-clear-cache-btn")
|
||||
|
||||
|
||||
def _full_wire_text(sx_src: str, *comp_names: str) -> str:
|
||||
"""Build the full wire response text showing component defs + CSS note + sx source.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user