Replace sx_call() with render_to_sx() across all services
All checks were successful
Build and Deploy / build-and-deploy (push) Successful in 2m6s

Python no longer generates s-expression strings. All SX rendering now
goes through render_to_sx() which builds AST from native Python values
and evaluates via async_eval_to_sx() — no SX string literals in Python.

- Add render_to_sx()/render_to_html() infrastructure in shared/sx/helpers.py
- Add (abort status msg) IO primitive in shared/sx/primitives_io.py
- Convert all 9 services: ~650 sx_call() invocations replaced
- Convert shared helpers (root_header_sx, full_page_sx, etc.) to async
- Fix likes service import bug (likes.models → models)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 00:08:33 +00:00
parent 0554f8a113
commit e085fe43b4
51 changed files with 1824 additions and 1742 deletions

View File

@@ -34,30 +34,30 @@ def _register_sx_layouts() -> None:
register_custom_layout("sx-section", _sx_section_full_headers, _sx_section_oob_headers, _sx_section_mobile)
def _sx_full_headers(ctx: dict, **kw: Any) -> str:
async def _sx_full_headers(ctx: dict, **kw: Any) -> str:
"""Full headers for sx home page: root + sx menu row."""
from shared.sx.helpers import root_header_sx
from sxc.sx_components import _sx_header_sx, _main_nav_sx
main_nav = _main_nav_sx(kw.get("section"))
root_hdr = root_header_sx(ctx)
sx_row = _sx_header_sx(main_nav)
main_nav = await _main_nav_sx(kw.get("section"))
root_hdr = await root_header_sx(ctx)
sx_row = await _sx_header_sx(main_nav)
return "(<> " + root_hdr + " " + sx_row + ")"
def _sx_oob_headers(ctx: dict, **kw: Any) -> str:
async def _sx_oob_headers(ctx: dict, **kw: Any) -> str:
"""OOB headers for sx home page."""
from shared.sx.helpers import root_header_sx, oob_header_sx
from sxc.sx_components import _sx_header_sx, _main_nav_sx
root_hdr = root_header_sx(ctx)
main_nav = _main_nav_sx(kw.get("section"))
sx_row = _sx_header_sx(main_nav)
root_hdr = await root_header_sx(ctx)
main_nav = await _main_nav_sx(kw.get("section"))
sx_row = await _sx_header_sx(main_nav)
rows = "(<> " + root_hdr + " " + sx_row + ")"
return oob_header_sx("root-header-child", "sx-header-child", rows)
return await oob_header_sx("root-header-child", "sx-header-child", rows)
def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
async def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
"""Full headers for sx section pages: root + sx row + sub row."""
from shared.sx.helpers import root_header_sx
from sxc.sx_components import (
@@ -70,14 +70,14 @@ def _sx_section_full_headers(ctx: dict, **kw: Any) -> str:
sub_nav = kw.get("sub_nav", "")
selected = kw.get("selected", "")
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)
root_hdr = await root_header_sx(ctx)
main_nav = await _main_nav_sx(section)
sub_row = await _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = await _sx_header_sx(main_nav, child=sub_row)
return "(<> " + root_hdr + " " + sx_row + ")"
def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
async def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
"""OOB headers for sx section pages."""
from shared.sx.helpers import root_header_sx, oob_header_sx
from sxc.sx_components import (
@@ -90,34 +90,34 @@ def _sx_section_oob_headers(ctx: dict, **kw: Any) -> str:
sub_nav = kw.get("sub_nav", "")
selected = kw.get("selected", "")
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)
root_hdr = await root_header_sx(ctx)
main_nav = await _main_nav_sx(section)
sub_row = await _sub_row_sx(sub_label, sub_href, sub_nav, selected)
sx_row = await _sx_header_sx(main_nav, child=sub_row)
rows = "(<> " + root_hdr + " " + sx_row + ")"
return oob_header_sx("root-header-child", "sx-header-child", rows)
return await oob_header_sx("root-header-child", "sx-header-child", rows)
def _sx_mobile(ctx: dict, **kw: Any) -> str:
async def _sx_mobile(ctx: dict, **kw: Any) -> str:
"""Mobile menu for sx home page: main nav + root."""
from shared.sx.helpers import (
mobile_menu_sx, mobile_root_nav_sx, sx_call, SxExpr,
mobile_menu_sx, mobile_root_nav_sx, render_to_sx, SxExpr,
)
from sxc.sx_components import _main_nav_sx
main_nav = _main_nav_sx(kw.get("section"))
main_nav = await _main_nav_sx(kw.get("section"))
return mobile_menu_sx(
sx_call("mobile-menu-section",
await render_to_sx("mobile-menu-section",
label="sx", href="/", level=1, colour="violet",
items=SxExpr(main_nav)),
mobile_root_nav_sx(ctx),
await mobile_root_nav_sx(ctx),
)
def _sx_section_mobile(ctx: dict, **kw: Any) -> str:
async def _sx_section_mobile(ctx: dict, **kw: Any) -> str:
"""Mobile menu for sx section pages: sub nav + main nav + root."""
from shared.sx.helpers import (
mobile_menu_sx, mobile_root_nav_sx, sx_call, SxExpr,
mobile_menu_sx, mobile_root_nav_sx, render_to_sx, SxExpr,
)
from sxc.sx_components import _main_nav_sx
@@ -125,17 +125,17 @@ def _sx_section_mobile(ctx: dict, **kw: Any) -> str:
sub_label = kw.get("sub_label", section)
sub_href = kw.get("sub_href", "/")
sub_nav = kw.get("sub_nav", "")
main_nav = _main_nav_sx(section)
main_nav = await _main_nav_sx(section)
parts = []
if sub_nav:
parts.append(sx_call("mobile-menu-section",
parts.append(await render_to_sx("mobile-menu-section",
label=sub_label, href=sub_href, level=2, colour="violet",
items=SxExpr(sub_nav)))
parts.append(sx_call("mobile-menu-section",
parts.append(await render_to_sx("mobile-menu-section",
label="sx", href="/", level=1, colour="violet",
items=SxExpr(main_nav)))
parts.append(mobile_root_nav_sx(ctx))
parts.append(await mobile_root_nav_sx(ctx))
return mobile_menu_sx(*parts)

View File

@@ -5,7 +5,7 @@ import os
from shared.sx.jinja_bridge import load_sx_dir, watch_sx_dir
from shared.sx.helpers import (
sx_call, SxExpr, get_asset_url,
render_to_sx, SxExpr, get_asset_url,
)
from content.highlight import highlight
@@ -108,11 +108,11 @@ def _full_wire_text(sx_src: str, *comp_names: str) -> str:
# Navigation helpers
# ---------------------------------------------------------------------------
def _nav_items_sx(items: list[tuple[str, str]], current: str | None = None) -> str:
async def _nav_items_sx(items: list[tuple[str, str]], current: str | None = None) -> str:
"""Build nav link items as sx."""
parts = []
for label, href in items:
parts.append(sx_call("nav-link",
parts.append(await render_to_sx("nav-link",
href=href, label=label,
is_selected="true" if current == label else None,
select_colours="aria-selected:bg-violet-200 aria-selected:text-violet-900",
@@ -120,9 +120,9 @@ 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, *, child: str | None = None) -> str:
async 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",
return await render_to_sx("menu-row-sx",
id="sx-row", level=1, colour="violet",
link_href="/", link_label="sx",
link_label_content=SxExpr('(span :class "font-mono" "(</>) sx")'),
@@ -132,40 +132,40 @@ def _sx_header_sx(nav: str | None = None, *, child: str | None = None) -> str:
)
def _docs_nav_sx(current: str | None = None) -> str:
async def _docs_nav_sx(current: str | None = None) -> str:
from content.pages import DOCS_NAV
return _nav_items_sx(DOCS_NAV, current)
return await _nav_items_sx(DOCS_NAV, current)
def _reference_nav_sx(current: str | None = None) -> str:
async def _reference_nav_sx(current: str | None = None) -> str:
from content.pages import REFERENCE_NAV
return _nav_items_sx(REFERENCE_NAV, current)
return await _nav_items_sx(REFERENCE_NAV, current)
def _protocols_nav_sx(current: str | None = None) -> str:
async def _protocols_nav_sx(current: str | None = None) -> str:
from content.pages import PROTOCOLS_NAV
return _nav_items_sx(PROTOCOLS_NAV, current)
return await _nav_items_sx(PROTOCOLS_NAV, current)
def _examples_nav_sx(current: str | None = None) -> str:
async def _examples_nav_sx(current: str | None = None) -> str:
from content.pages import EXAMPLES_NAV
return _nav_items_sx(EXAMPLES_NAV, current)
return await _nav_items_sx(EXAMPLES_NAV, current)
def _essays_nav_sx(current: str | None = None) -> str:
async def _essays_nav_sx(current: str | None = None) -> str:
from content.pages import ESSAYS_NAV
return _nav_items_sx(ESSAYS_NAV, current)
return await _nav_items_sx(ESSAYS_NAV, current)
def _main_nav_sx(current_section: str | None = None) -> str:
async def _main_nav_sx(current_section: str | None = None) -> str:
from content.pages import MAIN_NAV
return _nav_items_sx(MAIN_NAV, current_section)
return await _nav_items_sx(MAIN_NAV, current_section)
def _sub_row_sx(sub_label: str, sub_href: str, sub_nav: str,
async 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",
return await render_to_sx("menu-row-sx",
id="sx-sub-row", level=2, colour="violet",
link_href=sub_href, link_label=sub_label,
selected=selected or None,
@@ -178,22 +178,22 @@ def _sub_row_sx(sub_label: str, sub_href: str, sub_nav: str,
# Content builders — return sx source strings
# ---------------------------------------------------------------------------
def _doc_nav_sx(items: list[tuple[str, str]], current: str) -> str:
async def _doc_nav_sx(items: list[tuple[str, str]], current: str) -> str:
"""Build the in-page doc navigation pills."""
items_sx = " ".join(
f'(list "{label}" "{href}")'
for label, href in items
)
return sx_call("doc-nav", items=SxExpr(f"(list {items_sx})"), current=current)
return await render_to_sx("doc-nav", items=SxExpr(f"(list {items_sx})"), current=current)
def _attr_table_sx(title: str, attrs: list[tuple[str, str, bool]]) -> str:
async def _attr_table_sx(title: str, attrs: list[tuple[str, str, bool]]) -> str:
"""Build an attribute reference table."""
from content.pages import ATTR_DETAILS
rows = []
for attr, desc, exists in attrs:
href = f"/reference/attributes/{attr}" if exists and attr in ATTR_DETAILS else None
rows.append(sx_call("doc-attr-row", attr=attr, description=desc,
rows.append(await render_to_sx("doc-attr-row", attr=attr, description=desc,
exists="true" if exists else None,
href=href))
return (
@@ -209,13 +209,13 @@ def _attr_table_sx(title: str, attrs: list[tuple[str, str, bool]]) -> str:
)
def _primitives_section_sx() -> str:
async def _primitives_section_sx() -> str:
"""Build the primitives section."""
from content.pages import PRIMITIVES
parts = []
for category, prims in PRIMITIVES.items():
prims_sx = " ".join(f'"{p}"' for p in prims)
parts.append(sx_call("doc-primitives-table",
parts.append(await render_to_sx("doc-primitives-table",
category=category,
primitives=SxExpr(f"(list {prims_sx})")))
return " ".join(parts)
@@ -245,8 +245,9 @@ def _headers_table_sx(title: str, headers: list[tuple[str, str, str]]) -> str:
def _docs_content_sx(slug: str) -> str:
async def _docs_content_sx(slug: str) -> str:
"""Route to the right docs content builder."""
import inspect
builders = {
"introduction": _docs_introduction_sx,
"getting-started": _docs_getting_started_sx,
@@ -257,7 +258,8 @@ def _docs_content_sx(slug: str) -> str:
"server-rendering": _docs_server_rendering_sx,
}
builder = builders.get(slug, _docs_introduction_sx)
return builder()
result = builder()
return await result if inspect.isawaitable(result) else result
def _docs_introduction_sx() -> str:
@@ -379,8 +381,8 @@ def _docs_evaluator_sx() -> str:
)
def _docs_primitives_sx() -> str:
prims = _primitives_section_sx()
async def _docs_primitives_sx() -> str:
prims = await _primitives_section_sx()
return (
f'(~doc-page :title "Primitives"'
f' (~doc-section :title "Built-in functions" :id "builtins"'
@@ -471,14 +473,16 @@ def _docs_server_rendering_sx() -> str:
# Reference pages
# ---------------------------------------------------------------------------
def _reference_content_sx(slug: str) -> str:
async def _reference_content_sx(slug: str) -> str:
import inspect
builders = {
"attributes": _reference_attrs_sx,
"headers": _reference_headers_sx,
"events": _reference_events_sx,
"js-api": _reference_js_api_sx,
}
return builders.get(slug or "", _reference_attrs_sx)()
result = builders.get(slug or "", _reference_attrs_sx)()
return await result if inspect.isawaitable(result) else result
def _reference_index_sx() -> str:
@@ -573,18 +577,22 @@ def _reference_attr_detail_sx(slug: str) -> str:
)
def _reference_attrs_sx() -> str:
async def _reference_attrs_sx() -> str:
from content.pages import REQUEST_ATTRS, BEHAVIOR_ATTRS, SX_UNIQUE_ATTRS, HTMX_MISSING_ATTRS
req = await _attr_table_sx("Request Attributes", REQUEST_ATTRS)
beh = await _attr_table_sx("Behavior Attributes", BEHAVIOR_ATTRS)
uniq = await _attr_table_sx("Unique to sx", SX_UNIQUE_ATTRS)
missing = await _attr_table_sx("htmx features not yet in sx", HTMX_MISSING_ATTRS)
return (
f'(~doc-page :title "Attribute Reference"'
f' (p :class "text-stone-600 mb-6"'
f' "sx attributes mirror htmx where possible. This table shows what exists, '
f'what\'s unique to sx, and what\'s not yet implemented.")'
f' (div :class "space-y-8"'
f' {_attr_table_sx("Request Attributes", REQUEST_ATTRS)}'
f' {_attr_table_sx("Behavior Attributes", BEHAVIOR_ATTRS)}'
f' {_attr_table_sx("Unique to sx", SX_UNIQUE_ATTRS)}'
f' {_attr_table_sx("htmx features not yet in sx", HTMX_MISSING_ATTRS)}))'
f' {req}'
f' {beh}'
f' {uniq}'
f' {missing}))'
)
@@ -2066,9 +2074,9 @@ def home_content_sx() -> str:
)
def docs_content_partial_sx(slug: str) -> str:
async def docs_content_partial_sx(slug: str) -> str:
"""Docs content as sx wire format."""
inner = _docs_content_sx(slug)
inner = await _docs_content_sx(slug)
return (
f'(section :id "main-panel"'
f' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
@@ -2076,8 +2084,8 @@ def docs_content_partial_sx(slug: str) -> str:
)
def reference_content_partial_sx(slug: str) -> str:
inner = _reference_content_sx(slug)
async def reference_content_partial_sx(slug: str) -> str:
inner = await _reference_content_sx(slug)
return (
f'(section :id "main-panel"'
f' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
@@ -2085,8 +2093,8 @@ def reference_content_partial_sx(slug: str) -> str:
)
def protocol_content_partial_sx(slug: str) -> str:
inner = _protocol_content_sx(slug)
async def protocol_content_partial_sx(slug: str) -> str:
inner = await _protocol_content_sx(slug)
return (
f'(section :id "main-panel"'
f' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
@@ -2094,8 +2102,8 @@ def protocol_content_partial_sx(slug: str) -> str:
)
def examples_content_partial_sx(slug: str) -> str:
inner = _examples_content_sx(slug)
async def examples_content_partial_sx(slug: str) -> str:
inner = await _examples_content_sx(slug)
return (
f'(section :id "main-panel"'
f' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'
@@ -2103,8 +2111,8 @@ def examples_content_partial_sx(slug: str) -> str:
)
def essay_content_partial_sx(slug: str) -> str:
inner = _essay_content_sx(slug)
async def essay_content_partial_sx(slug: str) -> str:
inner = await _essay_content_sx(slug)
return (
f'(section :id "main-panel"'
f' :class "flex-1 md:h-full md:min-h-0 overflow-y-auto overscroll-contain js-grid-viewport"'