diff --git a/shared/sx/jinja_bridge.py b/shared/sx/jinja_bridge.py index 8ffeffde..f552590a 100644 --- a/shared/sx/jinja_bridge.py +++ b/shared/sx/jinja_bridge.py @@ -52,6 +52,11 @@ _COMPONENT_HASH: str = "" # available for client-side evaluation (e.g. cssx colour/spacing functions). _CLIENT_LIBRARY_SOURCES: list[str] = [] +# Raw source text per component name — used to send the ORIGINAL source +# to the browser instead of re-serializing the evaluated AST (which loses +# let/letrec/signal bindings that the client needs for hydration). +_COMPONENT_RAW_SOURCE: dict[str, str] = {} + def get_component_env() -> dict[str, Any]: """Return the shared component environment.""" @@ -456,10 +461,19 @@ def register_components(sx_source: str, *, _defer_postprocess: bool = False) -> if name and expr[0].name in ("defcomp", "defisland"): params, has_children = _parse_defcomp_params(expr[2] if len(expr) > 3 else []) cls = Island if expr[0].name == "defisland" else Component + # Body may be multiple expressions (let, letrec, freeze-scope, etc.) + # Skip name + params; also skip :effects keyword pair if present + body_start = 3 + if (len(expr) > 4 + and hasattr(expr[3], 'name') + and isinstance(expr[3], Keyword)): + body_start = 5 # skip :effects [list] + body_exprs = expr[body_start:] + body = body_exprs[0] if len(body_exprs) == 1 else [Symbol("begin")] + body_exprs _COMPONENT_ENV[name] = cls( name=name.lstrip("~"), params=params, has_children=has_children, - body=expr[-1], closure={}, + body=body, closure={}, ) # Pre-scan CSS classes for newly registered components.