Fix component body serialization: capture ALL body expressions

defcomp/defisland with multi-expression bodies (let, letrec,
freeze-scope, effect) had only the LAST expression stored as body.
The browser received a truncated defisland missing let/letrec/signal
bindings, causing "Undefined symbol: code-tokens" on hydration.

Fix: body_exprs = expr[body_start:], wrapped in (begin ...) if
multiple. Also clear stale pickle cache on code changes.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-25 16:27:33 +00:00
parent c62e7319cf
commit 9f0c541872

View File

@@ -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.