Make SxExpr a str subclass, sx_call/render functions return SxExpr

SxExpr is now a str subclass so it works everywhere a plain string
does (join, isinstance, f-strings) while serialize() still emits it
unquoted. sx_call() and all internal render functions (_render_to_sx,
async_eval_to_sx, etc.) return SxExpr, eliminating the "forgot to
wrap" bug class that caused the sx_content leak and list serialization
bugs.

- Phase 0: SxExpr(str) with .source property, __add__/__radd__
- Phase 1: sx_call returns SxExpr (drop-in, all 200+ sites unchanged)
- Phase 2: async_eval_to_sx, async_eval_slot_to_sx, _render_to_sx,
  mobile_menu_sx return SxExpr; remove isinstance(str) workaround
- Phase 3: Remove ~150 redundant SxExpr() wrappings across 45 files
- Phase 4: serialize() docstring, handler return docs, ;; returns: sx

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 21:47:00 +00:00
parent ad75798ab7
commit 278ae3e8f6
45 changed files with 378 additions and 379 deletions

View File

@@ -1,4 +1,5 @@
;; Cart account-nav-item fragment handler
;; returns: sx
;;
;; Renders the "orders" link for the account dashboard nav.

View File

@@ -1,4 +1,5 @@
;; Cart cart-mini fragment handler
;; returns: sx
;;
;; Renders the cart icon with badge (or logo when empty).

View File

@@ -3,8 +3,6 @@ from __future__ import annotations
from typing import Any
from shared.sx.parser import SxExpr
def _register_cart_layouts() -> None:
from shared.sx.layouts import register_custom_layout
@@ -80,8 +78,8 @@ def _page_cart_header_sx(ctx: dict, page_post: Any, *, oob: bool = False) -> str
"menu-row-sx",
id="page-cart-row", level=2, colour="sky",
link_href=call_url(ctx, "cart_url", f"/{slug}/"),
link_label_content=SxExpr(label_sx),
nav=SxExpr(nav_sx), oob=oob,
link_label_content=label_sx,
nav=nav_sx, oob=oob,
)
@@ -102,8 +100,8 @@ async def _cart_page_full(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post")
env = {}
return await render_to_sx_with_env("cart-page-layout-full", env,
cart_row=SxExpr(_cart_header_sx(ctx)),
page_cart_row=SxExpr(_page_cart_header_sx(ctx, page_post)),
cart_row=_cart_header_sx(ctx),
page_cart_row=_page_cart_header_sx(ctx, page_post),
)
@@ -112,9 +110,9 @@ async def _cart_page_oob(ctx: dict, **kw: Any) -> str:
page_post = ctx.get("page_post")
env = {}
return await render_to_sx_with_env("cart-page-layout-oob", env,
root_header_oob=SxExpr(await root_header_sx(ctx, oob=True)),
cart_row_oob=SxExpr(_cart_header_sx(ctx, oob=True)),
page_cart_row=SxExpr(_page_cart_header_sx(ctx, page_post)),
root_header_oob=await root_header_sx(ctx, oob=True),
cart_row_oob=_cart_header_sx(ctx, oob=True),
page_cart_row=_page_cart_header_sx(ctx, page_post),
)
@@ -124,8 +122,8 @@ async def _cart_admin_full(ctx: dict, **kw: Any) -> str:
selected = kw.get("selected", "")
env = {}
return await render_to_sx_with_env("cart-admin-layout-full", env,
post_header=SxExpr(await _post_header_sx(ctx, page_post)),
admin_header=SxExpr(await _cart_page_admin_header_sx(ctx, page_post, selected=selected)),
post_header=await _post_header_sx(ctx, page_post),
admin_header=await _cart_page_admin_header_sx(ctx, page_post, selected=selected),
)

View File

@@ -20,7 +20,7 @@ async def render_orders_page(ctx, orders, page, total_pages, search, search_coun
header_rows = await render_to_sx_with_env("cart-orders-layout-full", {},
list_url=list_url,
)
filt = sx_call("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
filt = sx_call("order-list-header", search_mobile=await search_mobile_sx(ctx))
return await full_page_sx(ctx, header_rows=header_rows, filter=filt,
aside=await search_desktop_sx(ctx), content=content)
@@ -44,7 +44,7 @@ def render_orders_rows(ctx, orders, page, total_pages, url_for_fn, qs_fn):
next_scroll = sx_call("order-end-row")
return sx_call("cart-orders-rows",
rows=SxExpr("(<> " + " ".join(parts) + ")"),
next_scroll=SxExpr(next_scroll),
next_scroll=next_scroll,
)
@@ -62,7 +62,7 @@ async def render_orders_oob(ctx, orders, page, total_pages, search, search_count
oobs = await render_to_sx_with_env("cart-orders-layout-oob", {},
list_url=list_url,
)
filt = sx_call("order-list-header", search_mobile=SxExpr(await search_mobile_sx(ctx)))
filt = sx_call("order-list-header", search_mobile=await search_mobile_sx(ctx))
return await oob_page_sx(oobs=oobs, filter=filt, aside=await search_desktop_sx(ctx), content=content)
@@ -116,7 +116,7 @@ async def render_checkout_error_page(ctx, error=None, order=None):
hdr = await render_to_sx_with_env("layout-root-full", {})
filt = sx_call("checkout-error-header")
content = sx_call("checkout-error-content", msg=err_msg,
order=SxExpr(order_sx) if order_sx else None, back_url=cart_url("/"))
order=order_sx or None, back_url=cart_url("/"))
return await full_page_sx(ctx, header_rows=hdr, filter=filt, content=content)