Files
rose-ash/sx/content/pages.py
giles fd67f202c2
Some checks failed
Build and Deploy / build-and-deploy (push) Has been cancelled
Add Continuations essay to SX docs
Covers server-side (suspendable rendering, streaming, error boundaries),
client-side (linear async flows, wizard forms, cooperative scheduling,
undo), and implementation path from the existing TCO trampoline. Updates
TCO essay's continuations section to link to the new essay instead of
dismissing the idea. Fixes "What sx is not" to acknowledge macros + TCO.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-04 10:41:22 +00:00

723 lines
30 KiB
Python

"""Documentation content for the sx docs site.
All page content as Python data structures, consumed by sx_components.py
to build s-expression page trees.
"""
from __future__ import annotations
# ---------------------------------------------------------------------------
# Navigation
# ---------------------------------------------------------------------------
DOCS_NAV = [
("Introduction", "/docs/introduction"),
("Getting Started", "/docs/getting-started"),
("Components", "/docs/components"),
("Evaluator", "/docs/evaluator"),
("Primitives", "/docs/primitives"),
("CSS", "/docs/css"),
("Server Rendering", "/docs/server-rendering"),
]
REFERENCE_NAV = [
("Attributes", "/reference/attributes"),
("Headers", "/reference/headers"),
("Events", "/reference/events"),
("JS API", "/reference/js-api"),
]
PROTOCOLS_NAV = [
("Wire Format", "/protocols/wire-format"),
("Fragments", "/protocols/fragments"),
("Resolver I/O", "/protocols/resolver-io"),
("Internal Services", "/protocols/internal-services"),
("ActivityPub", "/protocols/activitypub"),
("Future", "/protocols/future"),
]
EXAMPLES_NAV = [
("Click to Load", "/examples/click-to-load"),
("Form Submission", "/examples/form-submission"),
("Polling", "/examples/polling"),
("Delete Row", "/examples/delete-row"),
("Inline Edit", "/examples/inline-edit"),
("OOB Swaps", "/examples/oob-swaps"),
("Lazy Loading", "/examples/lazy-loading"),
("Infinite Scroll", "/examples/infinite-scroll"),
("Progress Bar", "/examples/progress-bar"),
("Active Search", "/examples/active-search"),
("Inline Validation", "/examples/inline-validation"),
("Value Select", "/examples/value-select"),
("Reset on Submit", "/examples/reset-on-submit"),
("Edit Row", "/examples/edit-row"),
("Bulk Update", "/examples/bulk-update"),
("Swap Positions", "/examples/swap-positions"),
("Select Filter", "/examples/select-filter"),
("Tabs", "/examples/tabs"),
("Animations", "/examples/animations"),
("Dialogs", "/examples/dialogs"),
("Keyboard Shortcuts", "/examples/keyboard-shortcuts"),
("PUT / PATCH", "/examples/put-patch"),
("JSON Encoding", "/examples/json-encoding"),
("Vals & Headers", "/examples/vals-and-headers"),
("Loading States", "/examples/loading-states"),
("Request Abort", "/examples/sync-replace"),
("Retry", "/examples/retry"),
]
ESSAYS_NAV = [
("sx sucks", "/essays/sx-sucks"),
("Why S-Expressions", "/essays/why-sexps"),
("The htmx/React Hybrid", "/essays/htmx-react-hybrid"),
("On-Demand CSS", "/essays/on-demand-css"),
("Client Reactivity", "/essays/client-reactivity"),
("SX Native", "/essays/sx-native"),
("The SX Manifesto", "/essays/sx-manifesto"),
("Tail-Call Optimization", "/essays/tail-call-optimization"),
("Continuations", "/essays/continuations"),
]
MAIN_NAV = [
("Docs", "/docs/introduction"),
("Reference", "/reference/"),
("Protocols", "/protocols/wire-format"),
("Examples", "/examples/click-to-load"),
("Essays", "/essays/sx-sucks"),
]
# ---------------------------------------------------------------------------
# Reference: Attributes
# ---------------------------------------------------------------------------
REQUEST_ATTRS = [
("sx-get", "Issue a GET request to the given URL", True),
("sx-post", "Issue a POST request to the given URL", True),
("sx-put", "Issue a PUT request to the given URL", True),
("sx-delete", "Issue a DELETE request to the given URL", True),
("sx-patch", "Issue a PATCH request to the given URL", True),
]
BEHAVIOR_ATTRS = [
("sx-trigger", "Specifies the event that triggers the request. Modifiers: once, changed, delay:<time>, from:<selector>, intersect, revealed, load, every:<time>", True),
("sx-target", "CSS selector for the target element to update", True),
("sx-swap", "How to swap the response: outerHTML, innerHTML, afterend, beforeend, afterbegin, beforebegin, delete, none", True),
("sx-swap-oob", "Out-of-band swap — update elements elsewhere in the DOM by ID", True),
("sx-select", "CSS selector to pick a fragment from the response", True),
("sx-confirm", "Shows a confirmation dialog before issuing the request", True),
("sx-push-url", "Push the request URL into the browser location bar", True),
("sx-sync", "Synchronization strategy for requests from this element", True),
("sx-encoding", "Set the encoding for the request (e.g. multipart/form-data)", True),
("sx-headers", "Add headers to the request as a JSON string", True),
("sx-include", "Include additional element values in the request", True),
("sx-vals", "Add values to the request as a JSON string", True),
("sx-media", "Only enable this element when the media query matches", True),
("sx-disable", "Disable sx processing on this element and its children", True),
("sx-on:*", "Inline event handler — e.g. sx-on:click runs JavaScript on event", True),
]
SX_UNIQUE_ATTRS = [
("sx-retry", "Exponential backoff retry on request failure", True),
("data-sx", "Client-side rendering — evaluate the sx source in this attribute and render into the element", True),
("data-sx-env", "Provide environment variables as JSON for data-sx rendering", True),
]
HTMX_MISSING_ATTRS = [
("hx-boost", "Progressively enhance links and forms (not yet implemented)", False),
("hx-preload", "Preload content on hover/focus (not yet implemented)", False),
("hx-preserve", "Preserve element across swaps (not yet implemented)", False),
("hx-optimistic", "Optimistic UI updates (not yet implemented)", False),
("hx-indicator", "sx uses .sx-request CSS class instead — no dedicated attribute (not yet implemented)", False),
("hx-validate", "Custom validation (not yet implemented — sx has sx-disable)", False),
("hx-ignore", "Ignore element (not yet implemented — sx has sx-disable)", False),
]
# ---------------------------------------------------------------------------
# Reference: Headers
# ---------------------------------------------------------------------------
REQUEST_HEADERS = [
("SX-Request", "true", "Set on every sx-initiated request"),
("SX-Current-URL", "URL", "The current URL of the browser"),
("SX-Target", "CSS selector", "The target element for the response"),
("SX-Components", "~comp1,~comp2,...", "Component names the client already has cached"),
("SX-Css", "hash or class list", "CSS classes/hash the client already has"),
("SX-History-Restore", "true", "Set when restoring from browser history"),
("SX-Css-Hash", "8-char hash", "Hash of the client's known CSS class set"),
]
RESPONSE_HEADERS = [
("SX-Css-Hash", "8-char hash", "Hash of the cumulative CSS class set after this response"),
("SX-Css-Add", "class1,class2,...", "New CSS classes added by this response"),
]
# ---------------------------------------------------------------------------
# Reference: Events
# ---------------------------------------------------------------------------
EVENTS = [
("sx:beforeRequest", "Fired before an sx request is issued. Call preventDefault() to cancel."),
("sx:afterRequest", "Fired after a successful sx response is received."),
("sx:afterSwap", "Fired after the response has been swapped into the DOM."),
("sx:afterSettle", "Fired after the DOM has settled (scripts executed, etc)."),
("sx:responseError", "Fired on HTTP error responses (4xx, 5xx)."),
("sx:sendError", "Fired when the request fails to send (network error)."),
]
# ---------------------------------------------------------------------------
# Reference: JS API
# ---------------------------------------------------------------------------
JS_API = [
("Sx.parse(text)", "Parse a single s-expression from text"),
("Sx.parseAll(text)", "Parse multiple s-expressions from text"),
("Sx.eval(expr, env)", "Evaluate an expression in the given environment"),
("Sx.render(expr, env)", "Render an expression to DOM nodes"),
("Sx.renderToString(expr, env)", "Render an expression to an HTML string (requires sx-test.js)"),
("Sx.renderComponent(name, kwargs, env)", "Render a named component with keyword arguments"),
("Sx.loadComponents(text)", "Parse and register component definitions"),
("Sx.getEnv()", "Get the current component environment"),
("Sx.mount(target, expr, env)", "Mount an expression into a DOM element"),
("Sx.update(target, newEnv)", "Re-render an element with new environment data"),
("Sx.hydrate(root)", "Find and render all [data-sx] elements within root"),
("SxEngine.process(root)", "Process all sx attributes in the DOM subtree"),
("SxEngine.executeRequest(elt, verb, url)", "Programmatically trigger an sx request"),
]
# ---------------------------------------------------------------------------
# Primitives
# ---------------------------------------------------------------------------
PRIMITIVES = {
"Arithmetic": ["+", "-", "*", "/", "mod", "sqrt", "pow", "abs", "floor", "ceil", "round", "min", "max"],
"Comparison": ["=", "!=", "<", ">", "<=", ">="],
"Logic": ["not", "and", "or"],
"String": ["str", "upper", "lower", "trim", "split", "join", "starts-with?", "ends-with?", "replace", "substring"],
"Collections": ["list", "dict", "len", "first", "last", "rest", "nth", "cons", "append", "keys", "vals", "merge", "assoc", "range", "concat", "reverse", "sort", "flatten", "zip"],
"Higher-Order": ["map", "map-indexed", "filter", "reduce", "some", "every?", "for-each"],
"Predicates": ["nil?", "number?", "string?", "list?", "dict?", "empty?", "contains?", "odd?", "even?", "zero?"],
"Type Conversion": ["int", "float", "number"],
}
# ---------------------------------------------------------------------------
# Example items for delete demo
# ---------------------------------------------------------------------------
DELETE_DEMO_ITEMS = [
("1", "Implement dark mode"),
("2", "Fix login bug"),
("3", "Write documentation"),
("4", "Deploy to production"),
("5", "Add unit tests"),
]
# ---------------------------------------------------------------------------
# Static data for new examples
# ---------------------------------------------------------------------------
SEARCH_LANGUAGES = [
"Python", "JavaScript", "TypeScript", "Rust", "Go", "Java", "C", "C++",
"Ruby", "Elixir", "Haskell", "Clojure", "Scala", "Kotlin", "Swift",
"Zig", "OCaml", "Lua", "Perl", "PHP",
]
PROFILE_DEFAULT = {"name": "Ada Lovelace", "email": "ada@example.com", "role": "Engineer"}
BULK_USERS = [
{"id": "1", "name": "Alice Chen", "email": "alice@example.com", "status": "active"},
{"id": "2", "name": "Bob Rivera", "email": "bob@example.com", "status": "inactive"},
{"id": "3", "name": "Carol Zhang", "email": "carol@example.com", "status": "active"},
{"id": "4", "name": "Dan Okafor", "email": "dan@example.com", "status": "inactive"},
{"id": "5", "name": "Eve Larsson", "email": "eve@example.com", "status": "active"},
]
VALUE_SELECT_DATA = {
"Languages": ["Python", "JavaScript", "Rust", "Go"],
"Frameworks": ["Quart", "FastAPI", "React", "Svelte"],
"Databases": ["PostgreSQL", "Redis", "SQLite", "MongoDB"],
}
EDIT_ROW_DATA = [
{"id": "1", "name": "Widget A", "price": "19.99", "stock": "142"},
{"id": "2", "name": "Widget B", "price": "24.50", "stock": "89"},
{"id": "3", "name": "Widget C", "price": "12.00", "stock": "305"},
{"id": "4", "name": "Widget D", "price": "45.00", "stock": "67"},
]
# ---------------------------------------------------------------------------
# Reference: Attribute detail pages
# ---------------------------------------------------------------------------
ATTR_DETAILS: dict[str, dict] = {
# --- Request Attributes ---
"sx-get": {
"description": (
"Issues a GET request to the given URL when triggered. "
"The response HTML is swapped into the target element. "
"This is the most common sx attribute — use it for loading content, "
"navigation, and any read operation."
),
"demo": "ref-get-demo",
"example": (
'(button :sx-get "/reference/api/time"\n'
' :sx-target "#ref-get-result"\n'
' :sx-swap "innerHTML"\n'
' "Load server time")'
),
"handler": (
'(defhandler ref-time (&key)\n'
' (let ((now (format-time (now) "%H:%M:%S")))\n'
' (span :class "text-stone-800 text-sm"\n'
' "Server time: " (strong now))))'
),
},
"sx-post": {
"description": (
"Issues a POST request to the given URL. "
"Form values from the enclosing form (or sx-include target) are sent as the request body. "
"Use for creating resources, submitting forms, and any write operation."
),
"demo": "ref-post-demo",
"example": (
'(form :sx-post "/reference/api/greet"\n'
' :sx-target "#ref-post-result"\n'
' :sx-swap "innerHTML"\n'
' (input :type "text" :name "name"\n'
' :placeholder "Your name")\n'
' (button :type "submit" "Greet"))'
),
"handler": (
'(defhandler ref-greet (&key)\n'
' (let ((name (or (form-data "name") "stranger")))\n'
' (span :class "text-stone-800 text-sm"\n'
' "Hello, " (strong name) "!")))'
),
},
"sx-put": {
"description": (
"Issues a PUT request to the given URL. "
"Used for full replacement updates of a resource. "
"Form values are sent as the request body."
),
"demo": "ref-put-demo",
"example": (
'(button :sx-put "/reference/api/status"\n'
' :sx-target "#ref-put-view"\n'
' :sx-swap "innerHTML"\n'
' :sx-vals "{\\"status\\": \\"published\\"}"\n'
' "Publish")'
),
"handler": (
'(defhandler ref-status (&key)\n'
' (let ((status (or (form-data "status") "unknown")))\n'
' (span :class "text-stone-700 text-sm"\n'
' "Status: " (strong status) " — updated via PUT")))'
),
},
"sx-delete": {
"description": (
"Issues a DELETE request to the given URL. "
"Commonly paired with sx-confirm for a confirmation dialog, "
'and sx-swap "delete" to remove the element from the DOM.'
),
"demo": "ref-delete-demo",
"example": (
'(button :sx-delete "/reference/api/item/1"\n'
' :sx-target "#ref-del-1"\n'
' :sx-swap "delete"\n'
' "Remove")'
),
"handler": (
'(defhandler ref-delete (&key item-id)\n'
' ;; Empty response — swap "delete" removes the target\n'
' "")'
),
},
"sx-patch": {
"description": (
"Issues a PATCH request to the given URL. "
"Used for partial updates — only changed fields are sent. "
"Form values are sent as the request body."
),
"demo": "ref-patch-demo",
"example": (
'(button :sx-patch "/reference/api/theme"\n'
' :sx-vals "{\\"theme\\": \\"dark\\"}"\n'
' :sx-target "#ref-patch-val"\n'
' :sx-swap "innerHTML"\n'
' "Dark")'
),
"handler": (
'(defhandler ref-theme (&key)\n'
' (let ((theme (or (form-data "theme") "unknown")))\n'
' (str theme)))'
),
},
# --- Behavior Attributes ---
"sx-trigger": {
"description": (
"Specifies which DOM event triggers the request. "
"Defaults to 'click' for most elements and 'submit' for forms. "
"Supports modifiers: once, changed, delay:<time>, from:<selector>, "
"intersect, revealed, load, every:<time>. "
"Multiple triggers can be comma-separated."
),
"demo": "ref-trigger-demo",
"example": (
'(input :type "text" :name "q"\n'
' :placeholder "Type to search..."\n'
' :sx-get "/reference/api/trigger-search"\n'
' :sx-trigger "input changed delay:300ms"\n'
' :sx-target "#ref-trigger-result"\n'
' :sx-swap "innerHTML")'
),
"handler": (
'(defhandler ref-trigger-search (&key)\n'
' (let ((q (or (request-arg "q") "")))\n'
' (if (empty? q)\n'
' (span "Start typing to trigger a search.")\n'
' (span "Results for: " (strong q)))))'
),
},
"sx-target": {
"description": (
"CSS selector identifying which element receives the response content. "
'Defaults to the element itself. Use "closest <selector>" to find '
"the nearest ancestor matching the selector."
),
"demo": "ref-target-demo",
"example": (
';; Two buttons targeting different elements\n'
'(button :sx-get "/reference/api/time"\n'
' :sx-target "#ref-target-a"\n'
' :sx-swap "innerHTML"\n'
' "Update Box A")\n'
'\n'
'(button :sx-get "/reference/api/time"\n'
' :sx-target "#ref-target-b"\n'
' :sx-swap "innerHTML"\n'
' "Update Box B")'
),
},
"sx-swap": {
"description": (
"Controls how the response is swapped into the target element. "
"Values: innerHTML (default), outerHTML, afterend, beforeend, "
"afterbegin, beforebegin, delete, none."
),
"demo": "ref-swap-demo",
"example": (
';; Append to the end of a list\n'
'(button :sx-get "/reference/api/swap-item"\n'
' :sx-target "#ref-swap-list"\n'
' :sx-swap "beforeend"\n'
' "beforeend")\n'
'\n'
';; Prepend to the start\n'
'(button :sx-get "/reference/api/swap-item"\n'
' :sx-target "#ref-swap-list"\n'
' :sx-swap "afterbegin"\n'
' "afterbegin")'
),
"handler": (
'(defhandler ref-swap-item (&key)\n'
' (let ((now (format-time (now) "%H:%M:%S")))\n'
' (div :class "text-sm text-violet-700"\n'
' "New item (" now ")")))'
),
},
"sx-swap-oob": {
"description": (
"Out-of-band swap — updates elements elsewhere in the DOM by ID, "
"outside the normal target. The server includes extra elements in "
"the response with sx-swap-oob attributes, and they are swapped "
"into matching elements in the page."
),
"demo": "ref-oob-demo",
"example": (
'(button :sx-get "/reference/api/oob"\n'
' :sx-target "#ref-oob-main"\n'
' :sx-swap "innerHTML"\n'
' "Update both boxes")'
),
"handler": (
'(defhandler ref-oob (&key)\n'
' (let ((now (format-time (now) "%H:%M:%S")))\n'
' (<>\n'
' (span "Main updated at " now)\n'
' (div :id "ref-oob-side"\n'
' :sx-swap-oob "innerHTML"\n'
' (span "OOB updated at " now)))))'
),
},
"sx-select": {
"description": (
"CSS selector to pick a fragment from the response HTML. "
"Only the matching element is swapped into the target. "
"Useful for extracting part of a full-page response."
),
"demo": "ref-select-demo",
"example": (
'(button :sx-get "/reference/api/select-page"\n'
' :sx-target "#ref-select-result"\n'
' :sx-select "#the-content"\n'
' :sx-swap "innerHTML"\n'
' "Load (selecting #the-content)")'
),
"handler": (
'(defhandler ref-select-page (&key)\n'
' (let ((now (format-time (now) "%H:%M:%S")))\n'
' (<>\n'
' (div :id "the-header" (h3 "Page header — not selected"))\n'
' (div :id "the-content"\n'
' (span "Selected fragment. Time: " now))\n'
' (div :id "the-footer" (p "Page footer — not selected")))))'
),
},
"sx-confirm": {
"description": (
"Shows a browser confirmation dialog before issuing the request. "
"The request is cancelled if the user clicks Cancel. "
"The value is the message shown in the dialog."
),
"demo": "ref-confirm-demo",
"example": (
'(button :sx-delete "/reference/api/item/confirm"\n'
' :sx-target "#ref-confirm-item"\n'
' :sx-swap "delete"\n'
' :sx-confirm "Are you sure you want to delete this file?"\n'
' "Delete")'
),
},
"sx-push-url": {
"description": (
'Push the request URL into the browser location bar, enabling '
'back/forward navigation. Set to "true" to push the request URL, '
'or provide a custom URL string.'
),
"demo": "ref-pushurl-demo",
"example": (
'(a :href "/reference/attributes/sx-get"\n'
' :sx-get "/reference/attributes/sx-get"\n'
' :sx-target "#main-panel"\n'
' :sx-select "#main-panel"\n'
' :sx-swap "outerHTML"\n'
' :sx-push-url "true"\n'
' "sx-get page")'
),
},
"sx-sync": {
"description": (
"Controls synchronization of concurrent requests from the same element. "
'Strategies: "drop" (ignore new while in-flight), '
'"replace" (abort in-flight, send new), '
'"queue" (queue and send after current completes).'
),
"demo": "ref-sync-demo",
"example": (
'(input :type "text" :name "q"\n'
' :placeholder "Type quickly..."\n'
' :sx-get "/reference/api/slow-echo"\n'
' :sx-trigger "input changed delay:100ms"\n'
' :sx-sync "replace"\n'
' :sx-target "#ref-sync-result"\n'
' :sx-swap "innerHTML")'
),
"handler": (
'(defhandler ref-slow-echo (&key)\n'
' (sleep 800)\n'
' (let ((q (or (request-arg "q") "")))\n'
' (span "Echo: " (strong q))))'
),
},
"sx-encoding": {
"description": (
"Sets the encoding type for the request body. "
'Use "multipart/form-data" for file uploads. '
"Defaults to application/x-www-form-urlencoded for forms."
),
"demo": "ref-encoding-demo",
"example": (
'(form :sx-post "/reference/api/upload-name"\n'
' :sx-encoding "multipart/form-data"\n'
' :sx-target "#ref-encoding-result"\n'
' :sx-swap "innerHTML"\n'
' (input :type "file" :name "file")\n'
' (button :type "submit" "Upload"))'
),
"handler": (
'(defhandler ref-upload-name (&key)\n'
' (let ((name (or (file-name "file") "(no file)")))\n'
' (span "Received: " (strong name))))'
),
},
"sx-headers": {
"description": (
"Adds custom headers to the request as a JSON object string. "
"Useful for passing metadata like API keys or content types."
),
"demo": "ref-headers-demo",
"example": (
'(button :sx-get "/reference/api/echo-headers"\n'
' :sx-headers \'{"X-Custom-Token": "abc123", "X-Request-Source": "demo"}\'\n'
' :sx-target "#ref-headers-result"\n'
' :sx-swap "innerHTML"\n'
' "Send with custom headers")'
),
"handler": (
'(defhandler ref-echo-headers (&key)\n'
' (let ((headers (request-headers :prefix "X-")))\n'
' (if (empty? headers)\n'
' (span "No custom headers received.")\n'
' (ul (map (fn (h)\n'
' (li (strong (first h)) ": " (last h)))\n'
' headers)))))'
),
},
"sx-include": {
"description": (
"Include values from additional elements in the request. "
"Takes a CSS selector. The matched element's form values "
"(inputs, selects, textareas) are added to the request."
),
"demo": "ref-include-demo",
"example": (
'(select :id "ref-inc-cat" :name "category"\n'
' (option :value "all" "All")\n'
' (option :value "books" "Books")\n'
' (option :value "tools" "Tools"))\n'
'\n'
'(button :sx-get "/reference/api/echo-vals"\n'
' :sx-include "#ref-inc-cat"\n'
' :sx-target "#ref-include-result"\n'
' :sx-swap "innerHTML"\n'
' "Filter")'
),
"handler": (
'(defhandler ref-echo-vals (&key)\n'
' (let ((vals (request-args)))\n'
' (if (empty? vals)\n'
' (span "No values received.")\n'
' (ul (map (fn (v)\n'
' (li (strong (first v)) ": " (last v)))\n'
' vals)))))'
),
},
"sx-vals": {
"description": (
"Adds extra values to the request as a JSON object string. "
"These are merged with any form values. "
"Useful for passing additional data without hidden inputs."
),
"demo": "ref-vals-demo",
"example": (
'(button :sx-post "/reference/api/echo-vals"\n'
' :sx-vals \'{"source": "demo", "page": "3"}\'\n'
' :sx-target "#ref-vals-result"\n'
' :sx-swap "innerHTML"\n'
' "Send with extra values")'
),
},
"sx-media": {
"description": (
"Only enables the sx attributes on this element when the given "
"CSS media query matches. When the media query does not match, "
"the element behaves as a normal HTML element."
),
"demo": "ref-media-demo",
"example": (
'(a :href "/reference/attributes/sx-get"\n'
' :sx-get "/reference/attributes/sx-get"\n'
' :sx-target "#main-panel"\n'
' :sx-select "#main-panel"\n'
' :sx-swap "outerHTML"\n'
' :sx-push-url "true"\n'
' :sx-media "(min-width: 768px)"\n'
' "sx navigation (desktop only)")'
),
},
"sx-disable": {
"description": (
"Disables sx processing on this element and all its children. "
"The element renders as normal HTML without any sx behavior. "
"Useful for opting out of sx in specific subtrees."
),
"demo": "ref-disable-demo",
"example": (
';; Left box: sx works normally\n'
';; Right box: sx-disable prevents any sx behavior\n'
'(div :sx-disable "true"\n'
' (button :sx-get "/reference/api/time"\n'
' :sx-target "#ref-dis-b"\n'
' :sx-swap "innerHTML"\n'
' "Load")\n'
' ;; This button will NOT fire an sx request\n'
' )'
),
},
"sx-on:*": {
"description": (
"Inline event handler — attaches JavaScript to a DOM event. "
'The * is replaced by the event name (e.g. sx-on:click, sx-on:keydown). '
"The handler code runs as inline JavaScript with 'this' bound to the element."
),
"demo": "ref-on-demo",
"example": (
'(button\n'
' :sx-on:click "document.getElementById(\'ref-on-result\')\n'
' .textContent = \'Clicked at \' + new Date()\n'
' .toLocaleTimeString()"\n'
' "Click me")'
),
},
# --- Unique to sx ---
"sx-retry": {
"description": (
"Enables exponential backoff retry on request failure. "
'Set to "true" for default retry behavior (3 attempts, 1s/2s/4s delays) '
"or provide a custom retry count."
),
"demo": "ref-retry-demo",
"example": (
'(button :sx-get "/reference/api/flaky"\n'
' :sx-target "#ref-retry-result"\n'
' :sx-swap "innerHTML"\n'
' :sx-retry "true"\n'
' "Call flaky endpoint")'
),
"handler": (
'(defhandler ref-flaky (&key)\n'
' (let ((n (inc-counter "ref-flaky")))\n'
' (if (!= (mod n 3) 0)\n'
' (error 503)\n'
' (span "Success on attempt " n "!"))))'
),
},
"data-sx": {
"description": (
"Client-side rendering — evaluates the s-expression source in this "
"attribute and renders the result into the element. No server request "
"is made. Useful for purely client-side UI and interactive components."
),
"demo": "ref-data-sx-demo",
"example": (
'(div :data-sx "(div :class \\"p-3 bg-violet-50 rounded\\"\n'
' (h3 :class \\"font-semibold\\" \\"Client-rendered\\")\n'
' (p \\"Evaluated in the browser.\\")")'
),
},
"data-sx-env": {
"description": (
"Provides environment variables as a JSON object for data-sx rendering. "
"These values are available as variables in the s-expression."
),
"demo": "ref-data-sx-env-demo",
"example": (
'(div\n'
' :data-sx "(div (h3 title) (p message))"\n'
' :data-sx-env \'{"title": "Dynamic", "message": "From env"}\')'
),
},
}