Add OOB header swaps for sx docs navigation + enable OAuth + fragments

- OOB nav updates: AJAX navigation now swaps both menu bar levels
  (main nav highlighting + sub-nav with current page) using the same
  oob_header_sx/oob_page_sx pattern as blog/market/events
- Enable OAuth for sx and test apps (removed from _NO_OAUTH, added sx
  to ALLOWED_CLIENTS, added app_urls for sx/test/orders)
- Fetch real cross-service fragments (cart-mini, auth-menu, nav-tree)
  instead of hardcoding empty values
- Add :selected param to ~menu-row-sx for white text current-page label
- Fix duplicate element IDs: use menu-row-sx child_id/child mechanism
  instead of manual header_child_sx wrappers
- Fix home page copy: "Server-rendered DOM over the wire (no HTML)"

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-02 23:22:01 +00:00
parent 3d55145e5f
commit 13bcf755f6
8 changed files with 461 additions and 316 deletions

View File

@@ -16,6 +16,9 @@ app_urls:
events: "https://events.rose-ash.com"
federation: "https://federation.rose-ash.com"
account: "https://account.rose-ash.com"
sx: "https://sx.rose-ash.com"
test: "https://test.rose-ash.com"
orders: "https://orders.rose-ash.com"
cache:
fs_root: /app/_snapshot # <- absolute path to your snapshot dir
categories:

View File

@@ -44,7 +44,7 @@ from .services import (
SESSION_USER_KEY = "uid"
ACCOUNT_SESSION_KEY = "account_sid"
ALLOWED_CLIENTS = {"blog", "market", "cart", "events", "federation", "orders", "test", "artdag", "artdag_l2"}
ALLOWED_CLIENTS = {"blog", "market", "cart", "events", "federation", "orders", "test", "sx", "artdag", "artdag_l2"}
def register(url_prefix="/auth"):

View File

@@ -145,8 +145,8 @@ def create_base_app(
errors(app)
# Auto-register OAuth client blueprint for non-account apps
# (account is the OAuth authorization server; test/sx are public dashboards)
_NO_OAUTH = {"account", "test", "sx"}
# (account is the OAuth authorization server)
_NO_OAUTH = {"account"}
if name not in _NO_OAUTH:
from shared.infrastructure.oauth import create_oauth_blueprint
app.register_blueprint(create_oauth_blueprint(name))

View File

@@ -82,9 +82,9 @@
(div :class "block md:hidden text-md font-bold"
(when auth-menu auth-menu))))
; @css bg-sky-400 bg-sky-300 bg-sky-200 bg-sky-100
; @css bg-sky-400 bg-sky-300 bg-sky-200 bg-sky-100 bg-violet-400 bg-violet-300 bg-violet-200 bg-violet-100
(defcomp ~menu-row-sx (&key id level colour link-href link-label link-label-content icon
hx-select nav child-id child oob external)
selected hx-select nav child-id child oob external)
(let* ((c (or colour "sky"))
(lv (or level 1))
(shade (str (- 500 (* lv 100)))))
@@ -102,7 +102,10 @@
:class "w-full whitespace-normal flex items-center gap-2 font-bold text-2xl px-3 py-2"
(when icon (i :class icon :aria-hidden "true"))
(if link-label-content link-label-content
(when link-label (div link-label)))))
(<>
(when link-label (div link-label))
(when selected
(span :class "text-lg text-white/80 font-normal" selected))))))
(when nav
(nav :class "hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0"
nav)))

View File

@@ -3,28 +3,41 @@ import path_setup # noqa: F401
import sxc.sx_components as sx_components # noqa: F401
from shared.infrastructure.factory import create_base_app
from shared.sx.jinja_bridge import render
from bp import register_pages
from services import register_domain_services
async def sx_docs_context() -> dict:
"""SX docs app context processor — minimal, no cross-service fragments."""
"""SX docs app context processor — fetches cross-service fragments."""
from quart import request, g
from shared.infrastructure.context import base_context
from shared.infrastructure.cart_identity import current_cart_identity
from shared.infrastructure.fragments import fetch_fragments
ctx = await base_context()
ctx["menu_items"] = []
blog_url = ctx.get("blog_url", "")
if callable(blog_url):
blog_url_str = blog_url("")
else:
blog_url_str = str(blog_url or "")
ctx["cart_mini"] = render(
"cart-mini", cart_count=0, blog_url=blog_url_str, cart_url="",
)
ctx["auth_menu"] = ""
ctx["nav_tree"] = ""
ident = current_cart_identity()
user = getattr(g, "user", None)
cart_params = {}
if ident.get("user_id"):
cart_params["user_id"] = ident["user_id"]
if ident.get("session_id"):
cart_params["session_id"] = ident["session_id"]
auth_params = {"email": user.email} if user else None
cart_mini, auth_menu, nav_tree = await fetch_fragments([
("cart", "cart-mini", cart_params or None),
("account", "auth-menu", auth_params),
("blog", "nav-tree", {"app_name": "sx", "path": request.path}),
], required=False)
ctx["cart_mini"] = cart_mini
ctx["auth_menu"] = auth_menu
ctx["nav_tree"] = nav_tree
return ctx

View File

@@ -20,8 +20,8 @@ def register(url_prefix: str = "/") -> Blueprint:
async def index():
if _is_sx_request():
from shared.sx.helpers import sx_response
from sxc.sx_components import home_content_sx
return sx_response(home_content_sx())
from sxc.sx_components import home_oob_sx
return sx_response(await home_oob_sx())
from shared.sx.page import get_template_context
from sxc.sx_components import render_home_page_sx
@@ -42,8 +42,8 @@ def register(url_prefix: str = "/") -> Blueprint:
async def docs_page(slug: str):
if _is_sx_request():
from shared.sx.helpers import sx_response
from sxc.sx_components import docs_content_partial_sx
return sx_response(docs_content_partial_sx(slug))
from sxc.sx_components import docs_oob_sx
return sx_response(await docs_oob_sx(slug))
from shared.sx.page import get_template_context
from sxc.sx_components import render_docs_page_sx
@@ -59,8 +59,8 @@ def register(url_prefix: str = "/") -> Blueprint:
async def reference_index():
if _is_sx_request():
from shared.sx.helpers import sx_response
from sxc.sx_components import reference_content_partial_sx
return sx_response(reference_content_partial_sx(""))
from sxc.sx_components import reference_oob_sx
return sx_response(await reference_oob_sx(""))
from shared.sx.page import get_template_context
from sxc.sx_components import render_reference_page_sx
@@ -72,8 +72,8 @@ def register(url_prefix: str = "/") -> Blueprint:
async def reference_page(slug: str):
if _is_sx_request():
from shared.sx.helpers import sx_response
from sxc.sx_components import reference_content_partial_sx
return sx_response(reference_content_partial_sx(slug))
from sxc.sx_components import reference_oob_sx
return sx_response(await reference_oob_sx(slug))
from shared.sx.page import get_template_context
from sxc.sx_components import render_reference_page_sx
@@ -94,8 +94,8 @@ def register(url_prefix: str = "/") -> Blueprint:
async def protocol_page(slug: str):
if _is_sx_request():
from shared.sx.helpers import sx_response
from sxc.sx_components import protocol_content_partial_sx
return sx_response(protocol_content_partial_sx(slug))
from sxc.sx_components import protocol_oob_sx
return sx_response(await protocol_oob_sx(slug))
from shared.sx.page import get_template_context
from sxc.sx_components import render_protocol_page_sx
@@ -116,8 +116,8 @@ def register(url_prefix: str = "/") -> Blueprint:
async def examples_page(slug: str):
if _is_sx_request():
from shared.sx.helpers import sx_response
from sxc.sx_components import examples_content_partial_sx
return sx_response(examples_content_partial_sx(slug))
from sxc.sx_components import examples_oob_sx
return sx_response(await examples_oob_sx(slug))
from shared.sx.page import get_template_context
from sxc.sx_components import render_examples_page_sx
@@ -205,8 +205,8 @@ def register(url_prefix: str = "/") -> Blueprint:
async def essay_page(slug: str):
if _is_sx_request():
from shared.sx.helpers import sx_response
from sxc.sx_components import essay_content_partial_sx
return sx_response(essay_content_partial_sx(slug))
from sxc.sx_components import essay_oob_sx
return sx_response(await essay_oob_sx(slug))
from shared.sx.page import get_template_context
from sxc.sx_components import render_essay_page_sx

View File

@@ -1,6 +1,6 @@
;; SX docs — home page components
(defcomp ~sx-hero ()
(defcomp ~sx-hero (&key &rest children)
(div :class "max-w-4xl mx-auto px-6 py-16 text-center"
(h1 :class "text-5xl font-bold text-stone-900 mb-4"
(span :class "text-violet-600" "sx"))
@@ -10,9 +10,7 @@
"A hypermedia-driven UI engine that combines htmx's server-first philosophy "
"with React's component model. All rendered via s-expressions over the wire.")
(div :class "bg-stone-50 border border-stone-200 rounded-lg p-6 text-left font-mono text-sm overflow-x-auto"
(pre
(code :class "language-lisp"
"(defcomp ~greeting (&key name)\n (div :class \"p-4 rounded bg-violet-50\"\n (h2 :class \"text-lg font-bold\"\n (str \"Hello, \" name \"!\"))\n (button\n :sx-get \"/api/greet\"\n :sx-target \"closest div\"\n :sx-swap \"outerHTML\"\n :class \"mt-2 px-4 py-2 bg-violet-600 text-white rounded\"\n \"Refresh\")))")))))
(pre :class "leading-relaxed" children))))
(defcomp ~sx-philosophy ()
(div :class "max-w-4xl mx-auto px-6 py-12"
@@ -21,7 +19,7 @@
(div :class "space-y-4"
(h3 :class "text-xl font-semibold text-violet-700" "From htmx")
(ul :class "space-y-2 text-stone-600"
(li "Server-rendered HTML over the wire")
(li "Server-rendered DOM over the wire (no HTML)")
(li "Hypermedia attributes on any element (sx-get, sx-post, ...)")
(li "Target/swap model for partial page updates")
(li "No client-side routing, no virtual DOM")

View File

@@ -5,9 +5,11 @@ import os
from shared.sx.jinja_bridge import load_sx_dir, watch_sx_dir
from shared.sx.helpers import (
sx_call, SxExpr,
root_header_sx, full_page_sx, header_child_sx,
sx_call, SxExpr, get_asset_url,
root_header_sx, full_page_sx,
oob_header_sx, oob_page_sx,
)
from content.highlight import highlight
# Load .sx components from sxc/ directory (not sx/ to avoid name collision)
_sxc_dir = os.path.dirname(__file__)
@@ -15,6 +17,23 @@ load_sx_dir(_sxc_dir)
watch_sx_dir(_sxc_dir)
def _full_page(ctx: dict, **kwargs) -> str:
"""full_page_sx wrapper."""
return full_page_sx(ctx, **kwargs)
def _code(code: str, language: str = "lisp") -> str:
"""Build a ~doc-code component with highlighted content."""
highlighted = highlight(code, language)
return f'(~doc-code {highlighted})'
def _example_code(code: str) -> str:
"""Build an ~example-source component with highlighted content."""
highlighted = highlight(code, "lisp")
return f'(~example-source {highlighted})'
# ---------------------------------------------------------------------------
# Navigation helpers
# ---------------------------------------------------------------------------
@@ -31,13 +50,14 @@ def _nav_items_sx(items: list[tuple[str, str]], current: str | None = None) -> s
return "(<> " + " ".join(parts) + ")"
def _sx_header_sx(nav: str | None = None) -> str:
def _sx_header_sx(nav: str | None = None, *, child: str | None = None) -> str:
"""Build the sx docs menu-row."""
return sx_call("menu-row-sx",
id="sx-row", level=1, colour="violet",
link_href="/", link_label="sx", icon="fa fa-code",
nav=SxExpr(nav) if nav else None,
child_id="sx-header-child",
child=SxExpr(child) if child else None,
)
@@ -74,24 +94,118 @@ def _main_nav_sx(current_section: str | None = None) -> str:
def _header_stack_sx(ctx: dict, section_nav: str | None = None) -> str:
"""Full header stack: root header + sx menu row."""
hdr = root_header_sx(ctx)
inner = _sx_header_sx(section_nav)
child = header_child_sx(inner)
return "(<> " + hdr + " " + child + ")"
sx_row = _sx_header_sx(section_nav)
return "(<> " + hdr + " " + sx_row + ")"
def _sub_row_sx(sub_label: str, sub_href: str, sub_nav: str,
selected: str = "") -> str:
"""Build the level-2 sub-section menu-row."""
return sx_call("menu-row-sx",
id="sx-sub-row", level=2, colour="violet",
link_href=sub_href, link_label=sub_label,
selected=selected or None,
nav=SxExpr(sub_nav),
)
def _section_header_stack_sx(ctx: dict, main_nav: str, sub_nav: str,
sub_label: str, sub_href: str) -> str:
sub_label: str, sub_href: str,
selected: str = "") -> str:
"""Header stack with main nav + sub-section nav row."""
hdr = root_header_sx(ctx)
sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = _sx_header_sx(main_nav, child=sub_row)
return "(<> " + hdr + " " + sx_row + ")"
# ---------------------------------------------------------------------------
# OOB helpers — rebuild header rows for AJAX navigation
# ---------------------------------------------------------------------------
async def _section_oob_sx(section: str, sub_label: str, sub_href: str,
sub_nav: str, content: str,
selected: str = "") -> str:
"""Generic OOB response: rebuild both header rows + content."""
from shared.sx.page import get_template_context
ctx = await get_template_context()
root_hdr = root_header_sx(ctx)
main_nav = _main_nav_sx(section)
sub_row = _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = _sx_header_sx(main_nav, child=sub_row)
rows = "(<> " + root_hdr + " " + sx_row + ")"
header_oob = oob_header_sx("root-header-child", "sx-header-child", rows)
return oob_page_sx(oobs=header_oob, content=content)
async def home_oob_sx() -> str:
"""OOB response for home page navigation."""
from shared.sx.page import get_template_context
ctx = await get_template_context()
root_hdr = root_header_sx(ctx)
main_nav = _main_nav_sx()
sx_row = _sx_header_sx(main_nav)
sub_row = sx_call("menu-row-sx",
id="sx-sub-row", level=2, colour="violet",
link_href=sub_href, link_label=sub_label,
nav=SxExpr(sub_nav),
rows = "(<> " + root_hdr + " " + sx_row + ")"
header_oob = oob_header_sx("root-header-child", "sx-header-child", rows)
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")
content = (
f'(div :id "main-content"'
f' (~sx-hero {hero_code})'
f' (~sx-philosophy)'
f' (~sx-how-it-works)'
f' (~sx-credits))'
)
inner = "(<> " + sx_row + " " + header_child_sx(sub_row, id="sx-header-child") + ")"
child = header_child_sx(inner)
return "(<> " + hdr + " " + child + ")"
return oob_page_sx(oobs=header_oob, content=content)
async def docs_oob_sx(slug: str) -> str:
"""OOB response for docs section navigation."""
from content.pages import DOCS_NAV
current = next((label for label, href in DOCS_NAV if href.endswith(slug)), None)
sub_nav = _docs_nav_sx(current)
return await _section_oob_sx("Docs", "Docs", "/docs/introduction", sub_nav,
_docs_content_sx(slug), selected=current or "")
async def reference_oob_sx(slug: str) -> str:
"""OOB response for reference section navigation."""
from content.pages import REFERENCE_NAV
current = next((label for label, href in REFERENCE_NAV
if href.rstrip("/").endswith(slug or "reference")), "Attributes")
sub_nav = _reference_nav_sx(current)
return await _section_oob_sx("Reference", "Reference", "/reference/", sub_nav,
_reference_content_sx(slug), selected=current or "")
async def protocol_oob_sx(slug: str) -> str:
"""OOB response for protocols section navigation."""
from content.pages import PROTOCOLS_NAV
current = next((label for label, href in PROTOCOLS_NAV if href.endswith(slug)), None)
sub_nav = _protocols_nav_sx(current)
return await _section_oob_sx("Protocols", "Protocols", "/protocols/wire-format", sub_nav,
_protocol_content_sx(slug), selected=current or "")
async def examples_oob_sx(slug: str) -> str:
"""OOB response for examples section navigation."""
from content.pages import EXAMPLES_NAV
current = next((label for label, href in EXAMPLES_NAV if href.endswith(slug)), None)
sub_nav = _examples_nav_sx(current)
return await _section_oob_sx("Examples", "Examples", "/examples/click-to-load", sub_nav,
_examples_content_sx(slug), selected=current or "")
async def essay_oob_sx(slug: str) -> str:
"""OOB response for essays section navigation."""
from content.pages import ESSAYS_NAV
current = next((label for label, href in ESSAYS_NAV if href.endswith(slug)), None)
sub_nav = _essays_nav_sx(current)
return await _section_oob_sx("Essays", "Essays", "/essays/sx-sucks", sub_nav,
_essay_content_sx(slug), selected=current or "")
# ---------------------------------------------------------------------------
@@ -169,14 +283,19 @@ async def render_home_page_sx(ctx: dict) -> str:
"""Full page: home."""
main_nav = _main_nav_sx()
hdr = _header_stack_sx(ctx, main_nav)
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")
content = (
'(div :id "main-content"'
' (~sx-hero)'
' (~sx-philosophy)'
' (~sx-how-it-works)'
' (~sx-credits))'
f'(div :id "main-content"'
f' (~sx-hero {hero_code})'
f' (~sx-philosophy)'
f' (~sx-how-it-works)'
f' (~sx-credits))'
)
return full_page_sx(ctx, header_rows=hdr, content=content)
return _full_page(ctx, header_rows=hdr, content=content)
async def render_docs_page_sx(ctx: dict, slug: str) -> str:
@@ -185,9 +304,10 @@ async def render_docs_page_sx(ctx: dict, slug: str) -> str:
current = next((label for label, href in DOCS_NAV if href.endswith(slug)), None)
main_nav = _main_nav_sx("Docs")
sub_nav = _docs_nav_sx(current)
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Docs", "/docs/introduction")
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Docs", "/docs/introduction",
selected=current or "")
content = _docs_content_sx(slug)
return full_page_sx(ctx, header_rows=hdr, content=content)
return _full_page(ctx, header_rows=hdr, content=content)
def _docs_content_sx(slug: str) -> str:
@@ -235,98 +355,92 @@ def _docs_introduction_sx() -> str:
def _docs_getting_started_sx() -> str:
c1 = _code('(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."))')
c2 = _code('(button\n :sx-get "/api/data"\n :sx-target "#result"\n :sx-swap "innerHTML"\n "Load data")')
return (
'(~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 :language "lisp" :code'
' "(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.\\"))")'
' (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 :language "lisp" :code'
' "(button\\n'
' :sx-get \\"/api/data\\"\\n'
' :sx-target \\"#result\\"\\n'
' :sx-swap \\"innerHTML\\"\\n'
' \\"Load data\\")")'
' (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.")))'
f'(~doc-page :title "Getting Started"'
f' (~doc-section :title "Minimal example" :id "minimal"'
f' (p :class "text-stone-600"'
f' "An sx response is s-expression source code with content type text/sx:")'
f' {c1}'
f' (p :class "text-stone-600"'
f' "Add sx-get to any element to make it fetch and render sx:"))'
f' (~doc-section :title "Hypermedia attributes" :id "attrs"'
f' (p :class "text-stone-600"'
f' "Like htmx, sx adds attributes to HTML elements to trigger HTTP requests:")'
f' {c2}'
f' (p :class "text-stone-600"'
f' "sx-get, sx-post, sx-put, sx-delete, sx-patch — all work the same way. '
f'The response is parsed as sx and rendered into the target element.")))'
)
def _docs_components_sx() -> str:
c1 = _code('(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)))')
c2 = _code('(~card :title "My Card" :subtitle "A description"\n'
' (p "First child")\n'
' (p "Second child"))')
return (
'(~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 :language "lisp" :code'
' "(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)))")'
' (p :class "text-stone-600"'
' "Use components with the ~ prefix:") '
' (~doc-code :language "lisp" :code'
' "(~card :title \\"My Card\\" :subtitle \\"A description\\"\\n'
' (p \\"First child\\")\\n'
' (p \\"Second child\\"))"))'
' (~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.")))'
f'(~doc-page :title "Components"'
f' (~doc-section :title "defcomp" :id "defcomp"'
f' (p :class "text-stone-600"'
f' "Components are defined with defcomp. They take keyword parameters and optional children:")'
f' {c1}'
f' (p :class "text-stone-600"'
f' "Use components with the ~ prefix:")'
f' {c2})'
f' (~doc-section :title "Component caching" :id "caching"'
f' (p :class "text-stone-600"'
f' "Component definitions are sent in a <script type=\\"text/sx\\" data-components> block. '
f'The client caches them in localStorage keyed by a content hash. '
f'On subsequent page loads, the client sends an SX-Components header listing what it has. '
f'The server only sends definitions the client is missing.")'
f' (p :class "text-stone-600"'
f' "This means the first page load sends all component definitions (~5-15KB). '
f'Subsequent navigations send zero component bytes — just the page content."))'
f' (~doc-section :title "Parameters" :id "params"'
f' (p :class "text-stone-600"'
f' "&key declares keyword parameters. &rest children captures remaining positional arguments. '
f'Missing parameters evaluate to nil. Components always receive all declared parameters — '
f'use (when param ...) or (if param ... ...) to handle optional values.")))'
)
def _docs_evaluator_sx() -> str:
c1 = _code(';; 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))')
c2 = _code('(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')
return (
'(~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 :language "lisp" :code'
' ";; 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))"))'
' (~doc-section :title "Higher-order forms" :id "higher"'
' (p :class "text-stone-600"'
' "These operate on collections with function arguments:")'
' (~doc-code :language "lisp" :code'
' "(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")))'
f'(~doc-page :title "Evaluator"'
f' (~doc-section :title "Special forms" :id "special"'
f' (p :class "text-stone-600"'
f' "Special forms have lazy evaluation — arguments are not evaluated before the form runs:")'
f' {c1})'
f' (~doc-section :title "Higher-order forms" :id "higher"'
f' (p :class "text-stone-600"'
f' "These operate on collections with function arguments:")'
f' {c2}))'
)
@@ -343,78 +457,78 @@ def _docs_primitives_sx() -> str:
def _docs_css_sx() -> str:
c1 = _code('# First page load:\n'
'GET / HTTP/1.1\n\n'
'HTTP/1.1 200 OK\n'
'Content-Type: text/html\n'
'# Full CSS in <style id="sx-css"> + hash in <meta name="sx-css-classes">\n\n'
'# Subsequent navigation:\n'
'GET /about HTTP/1.1\n'
'SX-Css: a1b2c3d4\n\n'
'HTTP/1.1 200 OK\n'
'Content-Type: text/sx\n'
'SX-Css-Hash: e5f6g7h8\n'
'SX-Css-Add: bg-blue-500,text-white,rounded-lg\n'
'# Only new rules in <style data-sx-css>', "bash")
return (
'(~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 :language "bash" :code'
' "# First page load:\\n'
'GET / HTTP/1.1\\n\\n'
'HTTP/1.1 200 OK\\n'
'Content-Type: text/html\\n'
'# Full CSS in <style id=\\"sx-css\\"> + hash in <meta name=\\"sx-css-classes\\">\\n\\n'
'# Subsequent navigation:\\n'
'GET /about HTTP/1.1\\n'
'SX-Css: a1b2c3d4\\n\\n'
'HTTP/1.1 200 OK\\n'
'Content-Type: text/sx\\n'
'SX-Css-Hash: e5f6g7h8\\n'
'SX-Css-Add: bg-blue-500,text-white,rounded-lg\\n'
'# Only new rules in <style data-sx-css>"))'
' (~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"))))'
f'(~doc-page :title "On-Demand CSS"'
f' (~doc-section :title "How it works" :id "how"'
f' (p :class "text-stone-600"'
f' "sx scans every response for CSS class names used in :class attributes. '
f'It looks up only those classes in a pre-parsed Tailwind CSS registry and ships '
f'just the rules that are needed. No build step. No purging. No unused CSS.")'
f' (p :class "text-stone-600"'
f' "On the first page load, the full set of used classes is embedded in a <style> block. '
f'A hash of the class set is stored. On subsequent navigations, the client sends the hash '
f'in the SX-Css header. The server computes the diff and sends only new rules via '
f'SX-Css-Add and a <style data-sx-css> block."))'
f' (~doc-section :title "The protocol" :id "protocol"'
f' {c1})'
f' (~doc-section :title "Advantages" :id "advantages"'
f' (ul :class "space-y-2 text-stone-600"'
f' (li "Zero build step — no Tailwind CLI, no PostCSS, no purging")'
f' (li "Exact CSS — never ships a rule that isn\'t used on the page")'
f' (li "Incremental — subsequent navigations only ship new rules")'
f' (li "Component-aware — pre-scans component definitions at registration time")))'
f' (~doc-section :title "Disadvantages" :id "disadvantages"'
f' (ul :class "space-y-2 text-stone-600"'
f' (li "Requires the full Tailwind CSS file loaded in memory at startup (~4MB parsed)")'
f' (li "Regex-based class scanning — can miss dynamically constructed class names")'
f' (li "No @apply support — classes must be used directly")'
f' (li "Tied to Tailwind\'s utility class naming conventions"))))'
)
def _docs_server_rendering_sx() -> str:
c1 = _code('from shared.sx.helpers import sx_page, sx_response, sx_call\n'
'from shared.sx.parser import SxExpr\n\n'
'# Build a component call from Python kwargs\n'
'sx_call("card", title="Hello", subtitle="World")\n\n'
'# Return an sx wire-format response\n'
'return sx_response(sx_call("card", title="Hello"))\n\n'
'# Return a full HTML page shell with sx boot\n'
'return sx_page(ctx, page_sx)', "python")
return (
'(~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 :language "python" :code'
' "from shared.sx.helpers import sx_page, sx_response, sx_call\\n'
'from shared.sx.parser import SxExpr\\n\\n'
'# Build a component call from Python kwargs\\n'
'sx_call(\\"card\\", title=\\"Hello\\", subtitle=\\"World\\")\\n\\n'
'# Return an sx wire-format response\\n'
'return sx_response(sx_call(\\"card\\", title=\\"Hello\\"))\\n\\n'
'# Return a full HTML page shell with sx boot\\n'
'return sx_page(ctx, page_sx)"))'
' (~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.")))'
f'(~doc-page :title "Server Rendering"'
f' (~doc-section :title "Python API" :id "python"'
f' (p :class "text-stone-600"'
f' "The server-side sx library provides several entry points for rendering:")'
f' {c1})'
f' (~doc-section :title "sx_call" :id "sx-call"'
f' (p :class "text-stone-600"'
f' "sx_call converts Python kwargs to an s-expression component call. '
f'Snake_case becomes kebab-case. SxExpr values are inlined without quoting. '
f'None becomes nil. Bools become true/false."))'
f' (~doc-section :title "sx_response" :id "sx-response"'
f' (p :class "text-stone-600"'
f' "sx_response returns a Quart Response with content type text/sx. '
f'It prepends missing component definitions, scans for CSS classes, '
f'and sets SX-Css-Hash and SX-Css-Add headers."))'
f' (~doc-section :title "sx_page" :id "sx-page"'
f' (p :class "text-stone-600"'
f' "sx_page returns a minimal HTML document that boots the page from sx source. '
f'The browser loads component definitions and page sx from inline <script> tags, '
f'then sx.js renders everything client-side. CSS rules are pre-scanned and injected.")))'
)
@@ -429,9 +543,10 @@ async def render_reference_page_sx(ctx: dict, slug: str) -> str:
if href.rstrip("/").endswith(slug or "reference")), "Attributes")
main_nav = _main_nav_sx("Reference")
sub_nav = _reference_nav_sx(current)
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Reference", "/reference/")
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Reference", "/reference/",
selected=current or "")
content = _reference_content_sx(slug)
return full_page_sx(ctx, header_rows=hdr, content=content)
return _full_page(ctx, header_rows=hdr, content=content)
def _reference_content_sx(slug: str) -> str:
@@ -526,9 +641,10 @@ async def render_protocol_page_sx(ctx: dict, slug: str) -> str:
current = next((label for label, href in PROTOCOLS_NAV if href.endswith(slug)), None)
main_nav = _main_nav_sx("Protocols")
sub_nav = _protocols_nav_sx(current)
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Protocols", "/protocols/wire-format")
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Protocols", "/protocols/wire-format",
selected=current or "")
content = _protocol_content_sx(slug)
return full_page_sx(ctx, header_rows=hdr, content=content)
return _full_page(ctx, header_rows=hdr, content=content)
def _protocol_content_sx(slug: str) -> str:
@@ -544,18 +660,18 @@ def _protocol_content_sx(slug: str) -> str:
def _protocol_wire_format_sx() -> str:
c1 = _code('HTTP/1.1 200 OK\n'
'Content-Type: text/sx\n'
'SX-Css-Hash: a1b2c3d4\n\n'
'(div :class "p-4"\n'
' (~card :title "Hello"))', "bash")
return (
'(~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 :language "bash" :code'
' "HTTP/1.1 200 OK\\n'
'Content-Type: text/sx\\n'
'SX-Css-Hash: a1b2c3d4\\n\\n'
'(div :class \\"p-4\\"\\n'
' (~card :title \\"Hello\\"))"))'
f'(~doc-page :title "Wire Format"'
f' (~doc-section :title "The text/sx content type" :id "content-type"'
f' (p :class "text-stone-600"'
f' "sx responses use content type text/sx. The body is s-expression source code. '
f'The client parses and evaluates it, then renders the result into the DOM.")'
f' {c1})'
' (~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.")'
@@ -578,16 +694,16 @@ def _protocol_wire_format_sx() -> str:
def _protocol_fragments_sx() -> str:
c1 = _code('(frag "blog" "link-card" :slug "hello")')
return (
'(~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 :language "lisp" :code'
' "(frag \\"blog\\" \\"link-card\\" :slug \\"hello\\")"))'
f'(~doc-page :title "Cross-Service Fragments"'
f' (~doc-section :title "Fragment protocol" :id "protocol"'
f' (p :class "text-stone-600"'
f' "Rose Ash runs as independent microservices. Each service can expose HTML or sx fragments '
f'that other services compose into their pages. Fragment endpoints return text/sx or text/html.")'
f' (p :class "text-stone-600"'
f' "The frag resolver is an I/O primitive in the render tree:")'
f' {c1})'
' (~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 '
@@ -700,9 +816,10 @@ async def render_examples_page_sx(ctx: dict, slug: str) -> str:
current = next((label for label, href in EXAMPLES_NAV if href.endswith(slug)), None)
main_nav = _main_nav_sx("Examples")
sub_nav = _examples_nav_sx(current)
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Examples", "/examples/click-to-load")
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Examples", "/examples/click-to-load",
selected=current or "")
content = _examples_content_sx(slug)
return full_page_sx(ctx, header_rows=hdr, content=content)
return _full_page(ctx, header_rows=hdr, content=content)
def _examples_content_sx(slug: str) -> str:
@@ -718,60 +835,66 @@ def _examples_content_sx(slug: str) -> str:
def _example_click_to_load_sx() -> str:
c1 = _example_code('(button\n'
' :sx-get "/examples/api/click"\n'
' :sx-target "#click-result"\n'
' :sx-swap "innerHTML"\n'
' "Load content")')
return (
'(~doc-page :title "Click to Load"'
' (p :class "text-stone-600 mb-6"'
' "The simplest sx interaction: click a button, fetch content from the server, swap it in.")'
' (~example-card :title "Demo"'
' :description "Click the button to load server-rendered content."'
' (~example-demo (~click-to-load-demo)))'
' (~example-source :code'
' "(button\\n'
' :sx-get \\"/examples/api/click\\"\\n'
' :sx-target \\"#click-result\\"\\n'
' :sx-swap \\"innerHTML\\"\\n'
' \\"Load content\\")"))'
f'(~doc-page :title "Click to Load"'
f' (p :class "text-stone-600 mb-6"'
f' "The simplest sx interaction: click a button, fetch content from the server, swap it in.")'
f' (~example-card :title "Demo"'
f' :description "Click the button to load server-rendered content."'
f' (~example-demo (~click-to-load-demo)))'
f' {c1})'
)
def _example_form_submission_sx() -> str:
c1 = _example_code('(form\n'
' :sx-post "/examples/api/form"\n'
' :sx-target "#form-result"\n'
' :sx-swap "innerHTML"\n'
' (input :type "text" :name "name")\n'
' (button :type "submit" "Submit"))')
return (
'(~doc-page :title "Form Submission"'
' (p :class "text-stone-600 mb-6"'
' "Forms with sx-post submit via AJAX and swap the response into a target.")'
' (~example-card :title "Demo"'
' :description "Enter a name and submit."'
' (~example-demo (~form-demo)))'
' (~example-source :code'
' "(form\\n'
' :sx-post \\"/examples/api/form\\"\\n'
' :sx-target \\"#form-result\\"\\n'
' :sx-swap \\"innerHTML\\"\\n'
' (input :type \\"text\\" :name \\"name\\")\\n'
' (button :type \\"submit\\" \\"Submit\\"))"))'
f'(~doc-page :title "Form Submission"'
f' (p :class "text-stone-600 mb-6"'
f' "Forms with sx-post submit via AJAX and swap the response into a target.")'
f' (~example-card :title "Demo"'
f' :description "Enter a name and submit."'
f' (~example-demo (~form-demo)))'
f' {c1})'
)
def _example_polling_sx() -> str:
c1 = _example_code('(div\n'
' :sx-get "/examples/api/poll"\n'
' :sx-trigger "load, every 2s"\n'
' :sx-swap "innerHTML"\n'
' "Loading...")')
return (
'(~doc-page :title "Polling"'
' (p :class "text-stone-600 mb-6"'
' "Use sx-trigger with \\"every\\\" to poll the server at regular intervals.")'
' (~example-card :title "Demo"'
' :description "This div polls the server every 2 seconds."'
' (~example-demo (~polling-demo)))'
' (~example-source :code'
' "(div\\n'
' :sx-get \\"/examples/api/poll\\"\\n'
' :sx-trigger \\"load, every 2s\\"\\n'
' :sx-swap \\"innerHTML\\"\\n'
' \\"Loading...\\")"))'
f'(~doc-page :title "Polling"'
f' (p :class "text-stone-600 mb-6"'
f' "Use sx-trigger with \\"every\\" to poll the server at regular intervals.")'
f' (~example-card :title "Demo"'
f' :description "This div polls the server every 2 seconds."'
f' (~example-demo (~polling-demo)))'
f' {c1})'
)
def _example_delete_row_sx() -> str:
from content.pages import DELETE_DEMO_ITEMS
items_sx = " ".join(f'(list "{id}" "{name}")' for id, name in DELETE_DEMO_ITEMS)
c1 = _example_code('(button\n'
' :sx-delete "/api/delete/1"\n'
' :sx-target "#row-1"\n'
' :sx-swap "outerHTML"\n'
' :sx-confirm "Delete this item?"\n'
' "delete")')
return (
f'(~doc-page :title "Delete Row"'
f' (p :class "text-stone-600 mb-6"'
@@ -779,53 +902,47 @@ def _example_delete_row_sx() -> str:
f' (~example-card :title "Demo"'
f' :description "Click delete to remove a row. Uses sx-confirm for confirmation."'
f' (~example-demo (~delete-demo :items (list {items_sx}))))'
f' (~example-source :code'
f' "(button\\n'
f' :sx-delete \\"/api/delete/1\\"\\n'
f' :sx-target \\"#row-1\\"\\n'
f' :sx-swap \\"outerHTML\\"\\n'
f' :sx-confirm \\"Delete this item?\\"\\n'
f' \\"delete\\")"))'
f' {c1})'
)
def _example_inline_edit_sx() -> str:
c1 = _example_code(';; View mode\n'
'(button :sx-get "/api/edit?value=text"\n'
' :sx-target "#edit-target" :sx-swap "innerHTML"\n'
' "edit")\n\n'
';; Edit mode (returned by server)\n'
'(form :sx-post "/api/edit"\n'
' :sx-target "#edit-target" :sx-swap "innerHTML"\n'
' (input :type "text" :name "value")\n'
' (button :type "submit" "save"))')
return (
'(~doc-page :title "Inline Edit"'
' (p :class "text-stone-600 mb-6"'
' "Click edit to swap a display view for an edit form. Save swaps back.")'
' (~example-card :title "Demo"'
' :description "Click edit, modify the text, save or cancel."'
' (~example-demo (~inline-edit-demo)))'
' (~example-source :code'
' ";; View mode\\n'
'(button :sx-get \\"/api/edit?value=text\\"\\n'
' :sx-target \\"#edit-target\\" :sx-swap \\"innerHTML\\"\\n'
' \\"edit\\")\\n\\n'
';; Edit mode (returned by server)\\n'
'(form :sx-post \\"/api/edit\\"\\n'
' :sx-target \\"#edit-target\\" :sx-swap \\"innerHTML\\"\\n'
' (input :type \\"text\\" :name \\"value\\")\\n'
' (button :type \\"submit\\" \\"save\\"))"))'
f'(~doc-page :title "Inline Edit"'
f' (p :class "text-stone-600 mb-6"'
f' "Click edit to swap a display view for an edit form. Save swaps back.")'
f' (~example-card :title "Demo"'
f' :description "Click edit, modify the text, save or cancel."'
f' (~example-demo (~inline-edit-demo)))'
f' {c1})'
)
def _example_oob_swaps_sx() -> str:
c1 = _example_code(';; Response body updates the target (Box A)\n'
';; OOB element updates Box B by ID\n\n'
'(<>\n'
' (div :class "text-center"\n'
' (p "Box A updated!")))\n'
' (div :id "oob-box-b" :sx-swap-oob "innerHTML"\n'
' (p "Box B updated via OOB!")))')
return (
'(~doc-page :title "Out-of-Band Swaps"'
' (p :class "text-stone-600 mb-6"'
' "sx-swap-oob lets a single response update multiple elements anywhere in the DOM.")'
' (~example-card :title "Demo"'
' :description "One request updates both Box A (via sx-target) and Box B (via sx-swap-oob)."'
' (~example-demo (~oob-demo)))'
' (~example-source :code'
' ";; Response body updates the target (Box A)\\n'
';; OOB element updates Box B by ID\\n\\n'
'(<>\\n'
' (div :class \\"text-center\\"\\n'
' (p \\"Box A updated!\\")))\\n'
' (div :id \\"oob-box-b\\" :sx-swap-oob \\"innerHTML\\"\\n'
' (p \\"Box B updated via OOB!\\"))))"))'
f'(~doc-page :title "Out-of-Band Swaps"'
f' (p :class "text-stone-600 mb-6"'
f' "sx-swap-oob lets a single response update multiple elements anywhere in the DOM.")'
f' (~example-card :title "Demo"'
f' :description "One request updates both Box A (via sx-target) and Box B (via sx-swap-oob)."'
f' (~example-demo (~oob-demo)))'
f' {c1})'
)
@@ -839,9 +956,10 @@ async def render_essay_page_sx(ctx: dict, slug: str) -> str:
current = next((label for label, href in ESSAYS_NAV if href.endswith(slug)), None)
main_nav = _main_nav_sx("Essays")
sub_nav = _essays_nav_sx(current)
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Essays", "/essays/sx-sucks")
hdr = _section_header_stack_sx(ctx, main_nav, sub_nav, "Essays", "/essays/sx-sucks",
selected=current or "")
content = _essay_content_sx(slug)
return full_page_sx(ctx, header_rows=hdr, content=content)
return _full_page(ctx, header_rows=hdr, content=content)
def _essay_content_sx(slug: str) -> str:
@@ -878,7 +996,7 @@ def _essay_sx_sucks() -> str:
'sx looked at this landscape and said: you know what this needs? A Lisp dialect. '
'For HTML. Over HTTP.")'
f' {p}'
' "Nobody was asking for this. The zero GitHub stars confirm it."))'
' "Nobody was asking for this. The zero GitHub stars confirm it. It is not even on GitHub."))'
' (~doc-section :title "The author has never written a line of LISP" :id "no-lisp"'
f' {p}'
@@ -898,6 +1016,11 @@ def _essay_sx_sucks() -> str:
'This is not artisanal hand-crafted software. This is the software equivalent of '
'a microwave dinner presented on a nice plate.")'
f' {p}'
' "He adds features by typing stuff like '
'\\"is there rom for macros within sx.js? what benefits m,ight that bring?\\", '
'skim-reading the response, and then entering \\"crack on then!\\" '
'This is not software engineering. This is improv comedy with a compiler.")'
f' {p}'
' "Is that bad? Maybe. Is it honest? Yes. Is this paragraph also AI-generated? '
'You will never know."))'
@@ -1029,14 +1152,19 @@ def _essay_on_demand_css() -> str:
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 (
'(section :id "main-panel"'
' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
' (div :id "main-content"'
' (~sx-hero)'
' (~sx-philosophy)'
' (~sx-how-it-works)'
' (~sx-credits)))'
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)))'
)