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

@@ -111,16 +111,19 @@ async def execute_handler(
service_name: str,
args: dict[str, str] | None = None,
) -> str:
"""Execute a declarative handler and return rendered sx/HTML string.
"""Execute a declarative handler and return SX wire format (``SxExpr``).
Uses the async evaluator+renderer so I/O primitives (``query``,
``service``, ``request-arg``, etc.) are awaited inline within
control flow — no collect-then-substitute limitations.
Uses the async evaluator so I/O primitives (``query``, ``service``,
``request-arg``, etc.) are awaited inline within control flow.
Returns ``SxExpr`` — pre-built sx source. Callers like
``fetch_fragment`` check ``content-type: text/sx`` and wrap the
response in ``SxExpr`` when consuming cross-service fragments.
1. Build env from component env + handler closure
2. Bind handler params from args (typically request.args)
3. Evaluate + render via async_render (handles I/O inline)
4. Return rendered string
3. Evaluate via ``async_eval_to_sx`` (I/O inline, components serialized)
4. Return ``SxExpr`` wire format
"""
from .jinja_bridge import get_component_env, _get_request_context
from .async_eval import async_eval_to_sx