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:
2026-03-05 00:22:17 +00:00
parent 4298d5be16
commit 36a0bd8577
8 changed files with 718 additions and 2500 deletions

115
sx/sx/docs-content.sx Normal file
View 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

File diff suppressed because one or more lines are too long

58
sx/sx/examples.sx Normal file
View 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
View 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
View 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

View File

@@ -7,24 +7,14 @@ from .essays import (
_reference_index_sx, _reference_attr_detail_sx, _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 .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: def home_content_sx() -> str:
"""Home page content as sx wire format.""" """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 ( return (
f'(section :id "main-panel"' '(section :id "main-panel"'
f' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"' ' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
f' (div :id "main-content"' ' (~sx-home-content))'
f' (~sx-hero {hero_code})'
f' (~sx-philosophy)'
f' (~sx-how-it-works)'
f' (~sx-credits)))'
) )
@@ -92,25 +82,9 @@ def _register_sx_helpers() -> None:
return label return label
return None 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", { register_page_helpers("sx", {
# Content builders # Content builders
"home-content": _home_content, "home-content": lambda: "(~sx-home-content)",
"docs-content": _docs_content_sx, "docs-content": _docs_content_sx,
"reference-content": _reference_content_sx, "reference-content": _reference_content_sx,
"reference-index-content": _reference_index_sx, "reference-index-content": _reference_index_sx,

View File

@@ -1,26 +1,6 @@
"""Public render/utility functions called from bp routes.""" """Public render/utility functions called from bp routes."""
from __future__ import annotations 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: def _component_source_text(*names: str) -> str:
"""Get defcomp source text for named components.""" """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) 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: def _full_wire_text(sx_src: str, *comp_names: str) -> str:
"""Build the full wire response text showing component defs + CSS note + sx source. """Build the full wire response text showing component defs + CSS note + sx source.