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

@@ -3,14 +3,14 @@
Bootstrap compiler: reference SX evaluator → JavaScript.
Reads the .sx reference specification and emits a standalone JavaScript
evaluator (sx-ref.js) that can be compared against the hand-written sx.js.
evaluator (sx-browser.js) that runs in the browser.
The compiler translates the restricted SX subset used in eval.sx/render.sx
into idiomatic JavaScript. Platform interface functions are emitted as
native JS implementations.
Usage:
python bootstrap_js.py > sx-ref.js
python bootstrap_js.py
"""
from __future__ import annotations
@@ -2911,8 +2911,9 @@ PLATFORM_DOM_JS = """
if (!_hasDom || !el) return function() {};
// Wrap SX lambdas from runtime-evaluated island code into native fns
var wrapped = isLambda(handler)
? function(e) { invoke(handler, e); }
? function(e) { try { invoke(handler, e); } catch(err) { console.error("[sx-ref] domListen handler error:", name, err); } }
: handler;
if (name === "click") logInfo("domListen: click on <" + (el.tagName||"?").toLowerCase() + "> text=" + (el.textContent||"").substring(0,20) + " isLambda=" + isLambda(handler));
el.addEventListener(name, wrapped);
return function() { el.removeEventListener(name, wrapped); };
}
@@ -4365,8 +4366,9 @@ if __name__ == "__main__":
help="Comma-separated extensions (continuations). Default: none.")
p.add_argument("--spec-modules",
help="Comma-separated spec modules (deps). Default: none.")
p.add_argument("--output", "-o",
help="Output file (default: stdout)")
default_output = os.path.join(os.path.dirname(__file__), "..", "..", "static", "scripts", "sx-browser.js")
p.add_argument("--output", "-o", default=default_output,
help="Output file (default: shared/static/scripts/sx-browser.js)")
args = p.parse_args()
adapters = args.adapters.split(",") if args.adapters else None
@@ -4375,13 +4377,10 @@ if __name__ == "__main__":
spec_modules = args.spec_modules.split(",") if args.spec_modules else None
js = compile_ref_to_js(adapters, modules, extensions, spec_modules)
if args.output:
with open(args.output, "w") as f:
f.write(js)
included = ", ".join(adapters) if adapters else "all"
mods = ", ".join(modules) if modules else "all"
ext_label = ", ".join(extensions) if extensions else "none"
print(f"Wrote {args.output} ({len(js)} bytes, adapters: {included}, modules: {mods}, extensions: {ext_label})",
file=sys.stderr)
else:
print(js)
with open(args.output, "w") as f:
f.write(js)
included = ", ".join(adapters) if adapters else "all"
mods = ", ".join(modules) if modules else "all"
ext_label = ", ".join(extensions) if extensions else "none"
print(f"Wrote {args.output} ({len(js)} bytes, adapters: {included}, modules: {mods}, extensions: {ext_label})",
file=sys.stderr)