Fix _aser_call and sx_call list serialization: use (list ...) for data arrays

Data lists (dicts, strings, numbers) were wrapped in (<> ...) fragments
which the client rendered as empty DocumentFragments instead of iterable
arrays. This broke map/filter over cards, tag_groups, and authors in
blog index and similar components.

- _aser_call: data lists → (list ...), rendered content (SxExpr) → (<> ...)
- sx_call: all list kwargs → (list ...)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-04 20:11:11 +00:00
parent 959e63d440
commit 0456b3d25c
2 changed files with 29 additions and 10 deletions

View File

@@ -1253,15 +1253,25 @@ async def _aser_call(
extra_class = val.class_name extra_class = val.class_name
else: else:
parts.append(f":{arg.name}") parts.append(f":{arg.name}")
# Plain list (e.g. from map) → wrap as fragment to # Plain list → serialize for the client.
# avoid ambiguity with function application on re-parse # Rendered items (SxExpr) → wrap in (<> ...) fragment.
# Data items (dicts, strings, numbers) → (list ...)
# so the client gets an iterable array, not a
# DocumentFragment that breaks map/filter.
if isinstance(val, list): if isinstance(val, list):
items = [serialize(v) for v in val live = [v for v in val
if v is not NIL and v is not None] if v is not NIL and v is not None]
parts.append( items = [serialize(v) for v in live]
"(<> " + " ".join(items) + ")" if items if not items:
else "nil" parts.append("nil")
) elif any(isinstance(v, SxExpr) for v in live):
parts.append(
"(<> " + " ".join(items) + ")"
)
else:
parts.append(
"(list " + " ".join(items) + ")"
)
else: else:
parts.append(serialize(val)) parts.append(serialize(val))
i += 2 i += 2

View File

@@ -404,13 +404,22 @@ def sx_call(component_name: str, **kwargs: Any) -> str:
Values are serialized: strings are quoted, None becomes nil, Values are serialized: strings are quoted, None becomes nil,
bools become true/false, numbers stay as-is. bools become true/false, numbers stay as-is.
List values use ``(list ...)`` so the client gets an iterable array
rather than a rendered fragment.
""" """
from .parser import serialize from .parser import serialize, SxExpr
name = component_name if component_name.startswith("~") else f"~{component_name}" name = component_name if component_name.startswith("~") else f"~{component_name}"
parts = [name] parts = [name]
for key, val in kwargs.items(): for key, val in kwargs.items():
parts.append(f":{key.replace('_', '-')}") parts.append(f":{key.replace('_', '-')}")
parts.append(serialize(val)) if isinstance(val, list):
items = [serialize(v) for v in val if v is not None]
if not items:
parts.append("nil")
else:
parts.append("(list " + " ".join(items) + ")")
else:
parts.append(serialize(val))
return "(" + " ".join(parts) + ")" return "(" + " ".join(parts) + ")"