diff --git a/shared/sx/async_eval.py b/shared/sx/async_eval.py index 9c25fbf..49f4d1f 100644 --- a/shared/sx/async_eval.py +++ b/shared/sx/async_eval.py @@ -1253,15 +1253,25 @@ async def _aser_call( extra_class = val.class_name else: parts.append(f":{arg.name}") - # Plain list (e.g. from map) → wrap as fragment to - # avoid ambiguity with function application on re-parse + # Plain list → serialize for the client. + # 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): - items = [serialize(v) for v in val - if v is not NIL and v is not None] - parts.append( - "(<> " + " ".join(items) + ")" if items - else "nil" - ) + live = [v for v in val + if v is not NIL and v is not None] + items = [serialize(v) for v in live] + if not items: + parts.append("nil") + elif any(isinstance(v, SxExpr) for v in live): + parts.append( + "(<> " + " ".join(items) + ")" + ) + else: + parts.append( + "(list " + " ".join(items) + ")" + ) else: parts.append(serialize(val)) i += 2 diff --git a/shared/sx/helpers.py b/shared/sx/helpers.py index 84982de..cfe5a5d 100644 --- a/shared/sx/helpers.py +++ b/shared/sx/helpers.py @@ -404,13 +404,22 @@ def sx_call(component_name: str, **kwargs: Any) -> str: Values are serialized: strings are quoted, None becomes nil, 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}" parts = [name] for key, val in kwargs.items(): 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) + ")"