Level 2-3: lake morphing — server content flows through reactive islands

Lake tag (lake :id "name" children...) creates server-morphable slots
within islands. During morph, the engine enters hydrated islands and
updates data-sx-lake elements by ID while preserving surrounding
reactive DOM (signals, effects, event listeners).

Specced in .sx, bootstrapped to JS and Python:
- adapter-dom.sx: render-dom-lake, reactive-attr marks data-sx-reactive-attrs
- adapter-html.sx: render-html-lake SSR output
- adapter-sx.sx: lake serialized in wire format
- engine.sx: morph-island-children (lake-by-ID matching),
  sync-attrs skips reactive attributes
- ~sx-header uses lakes for logo and copyright
- Hegelian essay updated with lake code example

Also includes: lambda nil-padding for missing args, page env ordering fix

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 14:29:54 +00:00
parent d5e416e478
commit 9b9fc6b6a5
15 changed files with 351 additions and 63 deletions

View File

@@ -469,6 +469,38 @@ def _render_island(island: Island, args: list, env: dict[str, Any]) -> str:
return "".join(parts)
def _render_lake(args: list, env: dict[str, Any]) -> str:
"""Render a server-morphable lake slot.
(lake :id "name" :tag "div" children...)
→ <div data-sx-lake="name">children</div>
Lakes are server territory inside reactive islands. During morph,
the server can update lake content while surrounding reactive DOM
is preserved.
"""
lake_id = ""
lake_tag = "div"
children: list[Any] = []
i = 0
while i < len(args):
arg = args[i]
if isinstance(arg, Keyword) and i + 1 < len(args):
kname = arg.name
kval = _eval(args[i + 1], env)
if kname == "id":
lake_id = str(kval) if kval is not None and kval is not NIL else ""
elif kname == "tag":
lake_tag = str(kval) if kval is not None and kval is not NIL else "div"
i += 2
else:
children.append(arg)
i += 1
body = "".join(_render(c, env) for c in children)
return f'<{lake_tag} data-sx-lake="{_escape_attr(lake_id)}">{body}</{lake_tag}>'
def _render_list(expr: list, env: dict[str, Any]) -> str:
"""Render a list expression — could be an HTML element, special form,
component call, or data list."""
@@ -494,6 +526,10 @@ def _render_list(expr: list, env: dict[str, Any]) -> str:
if name == "<>":
return "".join(_render(child, env) for child in expr[1:])
# --- lake → server-morphable slot within island -------------------
if name == "lake":
return _render_lake(expr[1:], env)
# --- html: prefix → force tag rendering --------------------------
if name.startswith("html:"):
return _render_element(name[5:], expr[1:], env)