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

@@ -36,7 +36,7 @@ def _ticket_widget_html(entry, qty: int, ticket_url: str, *, ctx: dict) -> str:
return sx_call("events-tw-form",
ticket_url=ticket_url, target=tgt,
csrf=csrf_token_val, entry_id=str(eid),
count_val=str(count_val), btn=SxExpr(btn_html))
count_val=str(count_val), btn=btn_html)
if qty == 0:
inner = _tw_form(1, sx_call("events-tw-cart-plus"))
@@ -80,7 +80,7 @@ def _tickets_main_panel_html(ctx: dict, tickets: list) -> str:
type_name=tt.name if tt else None,
time_str=time_str or None,
cal_name=cal.name if cal else None,
badge=SxExpr(_ticket_state_badge_html(state)),
badge=_ticket_state_badge_html(state),
code_prefix=ticket.code[:8]))
cards_html = "".join(ticket_cards)
@@ -193,7 +193,7 @@ def _ticket_admin_main_panel_html(ctx: dict, tickets: list, stats: dict) -> str:
entry_name=entry.name if entry else "\u2014",
date=SxExpr(date_html),
type_name=tt.name if tt else "\u2014",
badge=SxExpr(_ticket_state_badge_html(state)),
badge=_ticket_state_badge_html(state),
action=SxExpr(action_html))
return sx_call("events-ticket-admin-panel",
@@ -238,7 +238,7 @@ def render_checkin_result(success: bool, error: str | None, ticket) -> str:
entry_name=entry.name if entry else "\u2014",
date=SxExpr(date_html),
type_name=tt.name if tt else "\u2014",
badge=SxExpr(_ticket_state_badge_html("checked_in")),
badge=_ticket_state_badge_html("checked_in"),
time_str=time_str)
@@ -275,7 +275,7 @@ def render_lookup_result(ticket, error: str | None) -> str:
if cal:
info_html += sx_call("events-lookup-cal", cal_name=cal.name)
info_html += sx_call("events-lookup-status",
badge=SxExpr(_ticket_state_badge_html(state)), code=code)
badge=_ticket_state_badge_html(state), code=code)
if checked_in_at:
info_html += sx_call("events-lookup-checkin-time",
date_str=checked_in_at.strftime("%B %d, %Y at %H:%M"))
@@ -328,7 +328,7 @@ def render_entry_tickets_admin(entry, tickets: list) -> str:
rows_html += sx_call("events-entry-tickets-admin-row",
code=code, code_short=code[:12] + "...",
type_name=tt.name if tt else "\u2014",
badge=SxExpr(_ticket_state_badge_html(state)),
badge=_ticket_state_badge_html(state),
action=SxExpr(action_html))
if tickets:
@@ -340,7 +340,7 @@ def render_entry_tickets_admin(entry, tickets: list) -> str:
return sx_call("events-entry-tickets-admin-panel",
entry_name=entry.name,
count_label=f"{count} ticket{suffix}",
body=SxExpr(body_html))
body=body_html)
# ---------------------------------------------------------------------------
@@ -519,16 +519,16 @@ def render_buy_form(entry, ticket_remaining, ticket_sold_count,
cost_str = f"\u00a3{tt.cost:.2f}" if tt.cost is not None else "\u00a30.00"
type_items += sx_call("events-buy-type-item",
type_name=tt.name, cost_str=cost_str,
adjust_controls=SxExpr(_ticket_adjust_controls(csrf, adjust_url, target, eid, type_count, ticket_type_id=tt.id)))
adjust_controls=_ticket_adjust_controls(csrf, adjust_url, target, eid, type_count, ticket_type_id=tt.id))
body_html = sx_call("events-buy-types-wrapper", items=SxExpr(type_items))
else:
qty = user_ticket_count or 0
body_html = sx_call("events-buy-default",
price_str=f"\u00a3{tp:.2f}",
adjust_controls=SxExpr(_ticket_adjust_controls(csrf, adjust_url, target, eid, qty)))
adjust_controls=_ticket_adjust_controls(csrf, adjust_url, target, eid, qty))
return sx_call("events-buy-panel",
entry_id=eid_s, info=SxExpr(info_html), body=SxExpr(body_html))
entry_id=eid_s, info=SxExpr(info_html), body=body_html)
def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket_type_id=None):
@@ -543,8 +543,8 @@ def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket
return sx_call("events-adjust-form",
adjust_url=adjust_url, target=target,
extra_cls=extra_cls, csrf=csrf,
entry_id=eid_s, tt=SxExpr(tt_html) if tt_html else None,
count_val=str(count_val), btn=SxExpr(btn_html))
entry_id=eid_s, tt=tt_html or None,
count_val=str(count_val), btn=btn_html)
if count == 0:
return _adj_form(1, sx_call("events-adjust-cart-plus"),
@@ -557,7 +557,7 @@ def _ticket_adjust_controls(csrf, adjust_url, target, entry_id, count, *, ticket
plus = _adj_form(count + 1, sx_call("events-adjust-plus"))
return sx_call("events-adjust-controls",
minus=SxExpr(minus), cart_icon=SxExpr(cart_icon), plus=SxExpr(plus))
minus=minus, cart_icon=cart_icon, plus=plus)
# ---------------------------------------------------------------------------
@@ -603,7 +603,7 @@ def _ticket_types_header_html(ctx: dict, *, oob: bool = False) -> str:
return sx_call("menu-row-sx", id="ticket_types-row", level=7,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="ticket_type-header-child", oob=oob)
nav=nav_html or None, child_id="ticket_type-header-child", oob=oob)
@@ -639,7 +639,7 @@ def _ticket_type_header_html(ctx: dict, *, oob: bool = False) -> str:
return sx_call("menu-row-sx", id="ticket_type-row", level=8,
link_href=link_href, link_label_content=SxExpr(label_html),
nav=SxExpr(nav_html) if nav_html else None, child_id="ticket_type-header-child-inner", oob=oob)
nav=nav_html or None, child_id="ticket_type-header-child-inner", oob=oob)
# ---------------------------------------------------------------------------