Fix defisland→Component bug in jinja_bridge; add island reactivity test
jinja_bridge.py was creating Component objects for both defcomp AND defisland forms. Islands need Island objects so the serializer emits defisland (not defcomp) in the client component bundle. Without this, client-side islands don't get data-sx-island attributes, hydration fails, and all reactive signals (colour cycling, stepper) stop working. Add Playwright test: islands hydrate, stepper buttons update count, reactive colour cycling works on click. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -396,6 +396,40 @@ def load_handler_dir(directory: str, service_name: str) -> None:
|
||||
_load(directory, service_name)
|
||||
|
||||
|
||||
def _parse_defcomp_params(param_form: list) -> tuple[list[str], bool]:
|
||||
"""Extract keyword param names and has_children from a defcomp param list.
|
||||
|
||||
Handles: (&key p1 p2 &rest children), (&key (p1 :as type) &rest children),
|
||||
(p1 p2), () etc.
|
||||
|
||||
Returns (param_names, has_children).
|
||||
"""
|
||||
if not isinstance(param_form, list):
|
||||
return [], False
|
||||
|
||||
params: list[str] = []
|
||||
has_children = False
|
||||
in_key = False
|
||||
i = 0
|
||||
while i < len(param_form):
|
||||
item = param_form[i]
|
||||
if isinstance(item, Symbol):
|
||||
sname = item.name
|
||||
if sname == "&key":
|
||||
in_key = True
|
||||
elif sname == "&rest":
|
||||
has_children = True
|
||||
i += 1 # skip the rest-param name (e.g. 'children')
|
||||
else:
|
||||
params.append(sname)
|
||||
elif isinstance(item, list):
|
||||
# Typed param: (name :as type)
|
||||
if item and isinstance(item[0], Symbol):
|
||||
params.append(item[0].name)
|
||||
i += 1
|
||||
return params, has_children
|
||||
|
||||
|
||||
def register_components(sx_source: str, *, _defer_postprocess: bool = False) -> None:
|
||||
"""Parse and evaluate s-expression component definitions into the
|
||||
shared environment.
|
||||
@@ -410,7 +444,7 @@ def register_components(sx_source: str, *, _defer_postprocess: bool = False) ->
|
||||
existing = set(_COMPONENT_ENV.keys())
|
||||
|
||||
# Evaluate definitions — OCaml kernel handles everything.
|
||||
# Python-side component registry is populated minimally for CSS/deps.
|
||||
# Python-side component registry is populated with parsed params for CSS/deps.
|
||||
exprs = parse_all(sx_source)
|
||||
for expr in exprs:
|
||||
if (isinstance(expr, list) and expr and isinstance(expr[0], Symbol)
|
||||
@@ -420,9 +454,11 @@ def register_components(sx_source: str, *, _defer_postprocess: bool = False) ->
|
||||
name_sym = expr[1] if len(expr) > 1 else None
|
||||
name = name_sym.name if hasattr(name_sym, 'name') else str(name_sym) if name_sym else None
|
||||
if name and expr[0].name in ("defcomp", "defisland"):
|
||||
_COMPONENT_ENV[name] = Component(
|
||||
params, has_children = _parse_defcomp_params(expr[2] if len(expr) > 3 else [])
|
||||
cls = Island if expr[0].name == "defisland" else Component
|
||||
_COMPONENT_ENV[name] = cls(
|
||||
name=name.lstrip("~"),
|
||||
params=[], has_children=False,
|
||||
params=params, has_children=has_children,
|
||||
body=expr[-1], closure={},
|
||||
)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user