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:
@@ -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))
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user