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

@@ -44,8 +44,8 @@ def _market_header_sx(ctx: dict, *, oob: bool = False) -> str:
return sx_call(
"menu-row-sx",
id="market-row", level=2,
link_href=link_href, link_label_content=SxExpr(label_sx),
nav=SxExpr(nav_sx) if nav_sx else None,
link_href=link_href, link_label_content=label_sx,
nav=nav_sx or None,
child_id="market-header-child", oob=oob,
)
@@ -87,7 +87,7 @@ def _desktop_category_nav_sx(ctx: dict, categories: dict, qs: str,
return sx_call("market-desktop-category-nav",
links=SxExpr(links_sx),
admin=SxExpr(admin_sx) if admin_sx else None)
admin=admin_sx or None)
def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
@@ -117,7 +117,7 @@ def _product_header_sx(ctx: dict, d: dict, *, oob: bool = False) -> str:
return sx_call(
"menu-row-sx",
id="product-row", level=3,
link_href=link_href, link_label_content=SxExpr(label_sx),
link_href=link_href, link_label_content=label_sx,
nav=SxExpr(nav_sx), child_id="product-header-child", oob=oob,
)
@@ -219,7 +219,7 @@ def _mobile_nav_panel_sx(ctx: dict) -> str:
bg_cls=bg_cls, href=cat_href, hx_select=hx_select,
select_colours=select_colours, cat_name=cat,
count_label=f"{cat_count} products", count_str=str(cat_count),
chevron=SxExpr(chevron_sx),
chevron=chevron_sx,
)
subs = data.get("subs", [])
@@ -246,8 +246,8 @@ def _mobile_nav_panel_sx(ctx: dict) -> str:
item_parts.append(sx_call(
"market-mobile-cat-details",
open=cat_active or None,
summary=SxExpr(summary_sx),
subs=SxExpr(subs_sx),
summary=summary_sx,
subs=subs_sx,
))
items_sx = "(<> " + " ".join(item_parts) + ")"
@@ -295,16 +295,16 @@ def _register_market_layouts() -> None:
async def _market_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
return await render_to_sx_with_env("market-browse-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(_market_header_sx(ctx)))
post_header=await _post_header_sx(ctx),
market_header=_market_header_sx(ctx))
async def _market_oob(ctx: dict, **kw: Any) -> str:
oob_hdr = await _oob_header_sx("post-header-child", "market-header-child",
_market_header_sx(ctx))
return sx_call("market-browse-layout-oob",
oob_header=SxExpr(oob_hdr),
post_header_oob=SxExpr(await _post_header_sx(ctx, oob=True)),
oob_header=oob_hdr,
post_header_oob=await _post_header_sx(ctx, oob=True),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child")))
@@ -317,17 +317,17 @@ async def _market_admin_full(ctx: dict, **kw: Any) -> str:
from shared.sx.helpers import render_to_sx_with_env
selected = kw.get("selected", "")
return await render_to_sx_with_env("market-admin-layout-full", {},
post_header=SxExpr(await _post_header_sx(ctx)),
market_header=SxExpr(_market_header_sx(ctx)),
admin_header=SxExpr(await _market_admin_header_sx(ctx, selected=selected)))
post_header=await _post_header_sx(ctx),
market_header=_market_header_sx(ctx),
admin_header=await _market_admin_header_sx(ctx, selected=selected))
async def _market_admin_oob(ctx: dict, **kw: Any) -> str:
selected = kw.get("selected", "")
return sx_call("market-admin-layout-oob",
market_header_oob=SxExpr(_market_header_sx(ctx, oob=True)),
admin_oob_header=SxExpr(await _oob_header_sx("market-header-child", "market-admin-header-child",
await _market_admin_header_sx(ctx, selected=selected))),
market_header_oob=_market_header_sx(ctx, oob=True),
admin_oob_header=await _oob_header_sx("market-header-child", "market-admin-header-child",
await _market_admin_header_sx(ctx, selected=selected)),
clear_oob=SxExpr(_clear_deeper_oob("post-row", "post-header-child",
"market-row", "market-header-child",
"market-admin-row", "market-admin-header-child")))