Reactive island preservation across server-driven morphs

Islands survive hypermedia swaps: morph-node skips hydrated
data-sx-island elements when the same island exists in new content.
dispose-islands-in skips hydrated islands to prevent premature cleanup.

- @client directive: .sx files marked ;; @client send define forms to browser
- CSSX client-side: cssxgroup renamed (no hyphen) to avoid isRenderExpr
  matching it as a custom element — was producing [object HTMLElement]
- Island wrappers: div→span to avoid block-in-inline HTML parse breakage
- ~sx-header is now a defisland with inline reactive colour cycling
- bootstrap_js.py defaults output to shared/static/scripts/sx-browser.js
- Deleted stale sx-ref.js (sx-browser.js is the canonical browser build)
- Hegelian Synthesis essay: dialectic of hypertext and reactivity
- component-source helper handles Island types for docs pretty-printing

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-10 14:10:35 +00:00
parent 8a5c115557
commit d5e416e478
15 changed files with 458 additions and 5440 deletions

View File

@@ -221,6 +221,7 @@
"no-alternative" (~essay-no-alternative)
"zero-tooling" (~essay-zero-tooling)
"react-is-hypermedia" (~essay-react-is-hypermedia)
"hegelian-synthesis" (~essay-hegelian-synthesis)
:else (~essays-index-content))))
;; ---------------------------------------------------------------------------
@@ -516,6 +517,8 @@
"sx-forge" (~plan-sx-forge-content)
"sx-swarm" (~plan-sx-swarm-content)
"sx-proxy" (~plan-sx-proxy-content)
"async-eval-convergence" (~plan-async-eval-convergence-content)
"wasm-bytecode-vm" (~plan-wasm-bytecode-vm-content)
:else (~plans-index-content))))
;; ---------------------------------------------------------------------------

View File

@@ -13,6 +13,7 @@ def _register_sx_helpers() -> None:
register_page_helpers("sx", {
"highlight": _highlight,
"component-source": _component_source,
"primitives-data": _primitives_data,
"special-forms-data": _special_forms_data,
"reference-data": _reference_data,
@@ -35,6 +36,33 @@ def _register_sx_helpers() -> None:
})
def _component_source(name: str) -> str:
"""Return the pretty-printed defcomp/defisland source for a named component."""
from shared.sx.jinja_bridge import get_component_env
from shared.sx.parser import serialize
from shared.sx.types import Component, Island
comp = get_component_env().get(name)
if isinstance(comp, Island):
param_strs = list(comp.params)
if comp.has_children:
param_strs.extend(["&rest", "children"])
params_sx = "(" + " ".join(param_strs) + ")"
body_sx = serialize(comp.body, pretty=True)
return f"(defisland {name} {params_sx}\n {body_sx})"
if not isinstance(comp, Component):
return f";; component {name} not found"
param_strs = ["&key"] + list(comp.params)
if comp.has_children:
param_strs.extend(["&rest", "children"])
params_sx = "(" + " ".join(param_strs) + ")"
body_sx = serialize(comp.body, pretty=True)
affinity = ""
if comp.render_target == "server":
affinity = " :affinity :server"
return f"(defcomp {name} {params_sx}{affinity}\n {body_sx})"
def _primitives_data() -> dict:
"""Return the PRIMITIVES dict for the primitives docs page."""
from content.pages import PRIMITIVES