diff --git a/shared/sx/async_eval.py b/shared/sx/async_eval.py index bb617cf..395b10d 100644 --- a/shared/sx/async_eval.py +++ b/shared/sx/async_eval.py @@ -1018,6 +1018,31 @@ async def async_eval_to_sx( return SxExpr(serialize(result)) +async def _maybe_expand_component_result( + result: Any, + env: dict[str, Any], + ctx: RequestContext, +) -> Any: + """If *result* is a component call (SxExpr or string starting with + ``(~``), re-parse and expand it server-side. + + This ensures Python-only helpers (e.g. ``highlight``) inside the + component body are evaluated on the server rather than being + serialized for the client where they don't exist. + """ + raw = None + if isinstance(result, SxExpr): + raw = str(result).strip() + elif isinstance(result, str): + raw = result.strip() + if raw and raw.startswith("(~"): + from .parser import parse_all + parsed = parse_all(raw) + if parsed: + return await async_eval_slot_to_sx(parsed[0], env, ctx) + return result + + async def async_eval_slot_to_sx( expr: Any, env: dict[str, Any], @@ -1059,21 +1084,16 @@ async def async_eval_slot_to_sx( ) # Fall back to normal async_eval_to_sx result = await _aser(expr, env, ctx) + # If the result is a component call (from case/if/let branches or + # page helpers returning strings), re-parse and expand it server-side + # so that Python-only helpers like ``highlight`` in the component body + # get evaluated here, not on the client. + result = await _maybe_expand_component_result(result, env, ctx) if isinstance(result, SxExpr): return result if result is None or result is NIL: return SxExpr("") if isinstance(result, str): - # Page helpers return sx source strings. If the string is a - # component call (starts with "(~"), re-parse and expand it - # server-side so that Python-only helpers like ``highlight`` - # inside the component body get evaluated here, not on the client. - stripped = result.strip() - if stripped.startswith("(~"): - from .parser import parse_all - parsed = parse_all(stripped) - if parsed: - return await async_eval_slot_to_sx(parsed[0], env, ctx) return SxExpr(result) return SxExpr(serialize(result))