Fix process-bindings scope loss and async-invoke arity, bootstrap async adapter

Two bugs fixed:
1. process-bindings used merge(env) which returns {} for Env objects
   (Env is not a dict subclass). Changed to env-extend in render.sx
   and adapter-async.sx. This caused "Undefined symbol: theme" etc.
2. async-aser-eval-call passed evaled-args list to async-invoke(&rest),
   double-wrapping it. Changed to inline apply + coroutine check.

Also: bootstrap define-async into sx_ref.py (Phase 6), replace ~1000 LOC
hand-written async_eval_ref.py with 24-line thin re-export shim.

Test runner now uses Env (not flat dict) for render envs to catch scope bugs.
8 new regression tests (4 scope chain, 2 native callable arity, 2 render).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-11 16:38:47 +00:00
parent e843602ac9
commit ff6c1fab71
17 changed files with 1402 additions and 1203 deletions

View File

@@ -414,10 +414,10 @@ def _render_component(comp: Component, args: list, env: dict[str, Any]) -> str:
def _render_island(island: Island, args: list, env: dict[str, Any]) -> str:
"""Render an island as static HTML with hydration attributes.
Produces: <span data-sx-island="name" data-sx-state='{"k":"v",...}'>body HTML</span>
The client hydrates this into a reactive island.
Produces: <span data-sx-island="name" data-sx-state="{:k &quot;v&quot;}">body HTML</span>
The client hydrates this into a reactive island via sx-parse (not JSON).
"""
import json as _json
from .parser import serialize as _sx_serialize
kwargs: dict[str, Any] = {}
children: list[Any] = []
@@ -443,26 +443,13 @@ def _render_island(island: Island, args: list, env: dict[str, Any]) -> str:
body_html = _render(island.body, local)
# Serialize state for hydration — only keyword args
state = {}
for k, v in kwargs.items():
if isinstance(v, (str, int, float, bool)):
state[k] = v
elif v is NIL or v is None:
state[k] = None
elif isinstance(v, list):
state[k] = v
elif isinstance(v, dict):
state[k] = v
else:
state[k] = str(v)
state_json = _escape_attr(_json.dumps(state, separators=(",", ":"))) if state else ""
# Serialize state for hydration — SX format (not JSON)
state_sx = _escape_attr(_sx_serialize(kwargs)) if kwargs else ""
island_name = _escape_attr(island.name)
parts = [f'<span data-sx-island="{island_name}"']
if state_json:
parts.append(f' data-sx-state="{state_json}"')
if state_sx:
parts.append(f' data-sx-state="{state_sx}"')
parts.append(">")
parts.append(body_html)
parts.append("</span>")