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

@@ -144,7 +144,7 @@ def _render_page_search_results(pages, query, page, has_more) -> str:
items_sx = "(<> " + " ".join(items) + ")"
return sx_call("page-search-results",
items=SxExpr(items_sx),
sentinel=SxExpr(sentinel) if sentinel else None)
sentinel=sentinel or None)
def _render_menu_items_nav_oob(menu_items) -> str:
@@ -191,12 +191,12 @@ def _render_menu_items_nav_oob(menu_items) -> str:
if item_slug != "cart":
item_parts.append(sx_call("blog-nav-item-link",
href=href, hx_get=f"/{item_slug}/", selected=selected,
nav_cls=nav_button_cls, img=SxExpr(img_sx), label=label,
nav_cls=nav_button_cls, img=img_sx, label=label,
))
else:
item_parts.append(sx_call("blog-nav-item-plain",
href=href, selected=selected, nav_cls=nav_button_cls,
img=SxExpr(img_sx), label=label,
img=img_sx, label=label,
))
items_sx = "(<> " + " ".join(item_parts) + ")" if item_parts else ""

View File

@@ -226,7 +226,7 @@ def _render_associated_entries(all_calendars, associated_entry_ids, post_slug: s
confirm_text=f"This will remove {e_name} from this post",
toggle_url=toggle_url,
hx_headers=f'{{"X-CSRFToken": "{csrf}"}}',
img=SxExpr(img_sx), name=e_name,
img=img_sx, name=e_name,
date_str=f"{cal_name} \u2022 {date_str}",
))
@@ -237,7 +237,7 @@ def _render_associated_entries(all_calendars, associated_entry_ids, post_slug: s
else:
content_sx = sx_call("blog-associated-entries-empty")
return sx_call("blog-associated-entries-panel", content=SxExpr(content_sx))
return sx_call("blog-associated-entries-panel", content=content_sx)
def _render_nav_entries_oob(associated_entries, calendars, post: dict) -> str: