Files
mono/sx/sxc/pages/__init__.py
giles e085fe43b4 Replace sx_call() with render_to_sx() across all services
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>
2026-03-04 00:08:33 +00:00

214 lines
7.6 KiB
Python

"""SX docs defpage setup — registers layouts and page helpers."""
from __future__ import annotations
from typing import Any
def setup_sx_pages() -> None:
"""Register sx-specific layouts, page helpers, and load page definitions.
Called during app startup before mount_pages().
"""
_register_sx_layouts()
_register_sx_helpers()
_load_sx_page_files()
def _load_sx_page_files() -> None:
"""Load defpage definitions from sx/sxc/pages/*.sx."""
import os
from shared.sx.pages import load_page_dir
pages_dir = os.path.dirname(__file__)
load_page_dir(pages_dir, "sx")
# ---------------------------------------------------------------------------
# Layouts
# ---------------------------------------------------------------------------
def _register_sx_layouts() -> None:
"""Register the sx docs layout presets."""
from shared.sx.layouts import register_custom_layout
register_custom_layout("sx", _sx_full_headers, _sx_oob_headers, _sx_mobile)
register_custom_layout("sx-section", _sx_section_full_headers, _sx_section_oob_headers, _sx_section_mobile)
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 = 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 + ")"
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 = 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 await oob_header_sx("root-header-child", "sx-header-child", rows)
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 (
_sx_header_sx, _main_nav_sx, _sub_row_sx,
)
section = kw.get("section", "")
sub_label = kw.get("sub_label", section)
sub_href = kw.get("sub_href", "/")
sub_nav = kw.get("sub_nav", "")
selected = kw.get("selected", "")
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 + ")"
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 (
_sx_header_sx, _main_nav_sx, _sub_row_sx,
)
section = kw.get("section", "")
sub_label = kw.get("sub_label", section)
sub_href = kw.get("sub_href", "/")
sub_nav = kw.get("sub_nav", "")
selected = kw.get("selected", "")
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 await oob_header_sx("root-header-child", "sx-header-child", rows)
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, render_to_sx, SxExpr,
)
from sxc.sx_components import _main_nav_sx
main_nav = await _main_nav_sx(kw.get("section"))
return mobile_menu_sx(
await render_to_sx("mobile-menu-section",
label="sx", href="/", level=1, colour="violet",
items=SxExpr(main_nav)),
await mobile_root_nav_sx(ctx),
)
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, render_to_sx, SxExpr,
)
from sxc.sx_components import _main_nav_sx
section = kw.get("section", "")
sub_label = kw.get("sub_label", section)
sub_href = kw.get("sub_href", "/")
sub_nav = kw.get("sub_nav", "")
main_nav = await _main_nav_sx(section)
parts = []
if sub_nav:
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(await render_to_sx("mobile-menu-section",
label="sx", href="/", level=1, colour="violet",
items=SxExpr(main_nav)))
parts.append(await mobile_root_nav_sx(ctx))
return mobile_menu_sx(*parts)
# ---------------------------------------------------------------------------
# Page helpers — Python functions callable from defpage content expressions
# ---------------------------------------------------------------------------
def _register_sx_helpers() -> None:
"""Register Python content builder functions as page helpers."""
from shared.sx.pages import register_page_helpers
from sxc.sx_components import (
_docs_content_sx, _reference_content_sx,
_reference_index_sx, _reference_attr_detail_sx,
_protocol_content_sx, _examples_content_sx,
_essay_content_sx,
_docs_nav_sx, _reference_nav_sx,
_protocols_nav_sx, _examples_nav_sx, _essays_nav_sx,
)
from content.highlight import highlight as _highlight
from content.pages import (
DOCS_NAV, REFERENCE_NAV, PROTOCOLS_NAV,
EXAMPLES_NAV, ESSAYS_NAV,
)
def _find_current(nav_list, slug, match_fn=None):
"""Find the current nav label for a slug."""
if match_fn:
return match_fn(nav_list, slug)
for label, href in nav_list:
if href.endswith(slug):
return label
return None
def _home_content():
"""Build home page content (uses highlight for hero code block)."""
hero_code = _highlight(
'(div :class "p-4 bg-white rounded shadow"\n'
' (h1 :class "text-2xl font-bold" "Hello")\n'
' (button :sx-get "/api/data"\n'
' :sx-target "#result"\n'
' "Load data"))', "lisp")
return (
f'(div :id "main-content"'
f' (~sx-hero {hero_code})'
f' (~sx-philosophy)'
f' (~sx-how-it-works)'
f' (~sx-credits))'
)
register_page_helpers("sx", {
# Content builders
"home-content": _home_content,
"docs-content": _docs_content_sx,
"reference-content": _reference_content_sx,
"reference-index-content": _reference_index_sx,
"reference-attr-detail": _reference_attr_detail_sx,
"protocol-content": _protocol_content_sx,
"examples-content": _examples_content_sx,
"essay-content": _essay_content_sx,
"highlight": _highlight,
# Nav builders
"docs-nav": _docs_nav_sx,
"reference-nav": _reference_nav_sx,
"protocols-nav": _protocols_nav_sx,
"examples-nav": _examples_nav_sx,
"essays-nav": _essays_nav_sx,
# Nav data (for current label lookup)
"DOCS_NAV": DOCS_NAV,
"REFERENCE_NAV": REFERENCE_NAV,
"PROTOCOLS_NAV": PROTOCOLS_NAV,
"EXAMPLES_NAV": EXAMPLES_NAV,
"ESSAYS_NAV": ESSAYS_NAV,
# Utility
"find-current": _find_current,
})