Fix highlight undefined symbol by expanding component results server-side

When defpage content expressions use case/if branches that resolve to
component calls (e.g. `(case slug "intro" (~docs-intro-content) ...)`),
_aser serializes them for the client. Components containing Python-only
helpers like `highlight` then fail with "Undefined symbol" on the client.

Add _maybe_expand_component_result() which detects when the evaluated
result (SxExpr or string) is a component call starting with "(~" and
re-parses + expands it through async_eval_slot_to_sx server-side.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-05 01:52:45 +00:00
parent dfccd113fc
commit aed4c03537

View File

@@ -1018,6 +1018,31 @@ async def async_eval_to_sx(
return SxExpr(serialize(result)) 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( async def async_eval_slot_to_sx(
expr: Any, expr: Any,
env: dict[str, Any], env: dict[str, Any],
@@ -1059,21 +1084,16 @@ async def async_eval_slot_to_sx(
) )
# Fall back to normal async_eval_to_sx # Fall back to normal async_eval_to_sx
result = await _aser(expr, env, ctx) 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): if isinstance(result, SxExpr):
return result return result
if result is None or result is NIL: if result is None or result is NIL:
return SxExpr("") return SxExpr("")
if isinstance(result, str): 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(result)
return SxExpr(serialize(result)) return SxExpr(serialize(result))