Files
rose-ash/shared/sx/ref/demo-signals.html
giles fe289287ec Transpile signals.sx to JS and Python via bootstrappers
Both bootstrappers now handle the full signal runtime:
- &rest lambda params → JS arguments.slice / Python *args
- Signal/Island/TrackingContext platform functions in both hosts
- RENAMES for all signal, island, tracking, and reactive DOM identifiers
- signals auto-included with DOM adapter (JS) and HTML adapter (Python)
- Signal API exports on Sx object (signal, deref, reset, swap, computed, effect, batch)
- New DOM primitives: createComment, domRemove, domChildNodes, domRemoveChildrenAfter, domSetData
- jsonSerialize/isEmptyDict for island state serialization
- Demo HTML page exercising all signal primitives

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-08 10:17:16 +00:00

183 lines
6.8 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>SX Reactive Islands Demo</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body { font-family: system-ui, sans-serif; max-width: 640px; margin: 40px auto; padding: 0 20px; color: #1a1a2e; background: #f8f8fc; }
h1 { margin-bottom: 8px; font-size: 1.5rem; }
.subtitle { color: #666; margin-bottom: 32px; font-size: 0.9rem; }
.demo { background: white; border: 1px solid #e2e2ea; border-radius: 8px; padding: 20px; margin-bottom: 20px; }
.demo h2 { font-size: 1.1rem; margin-bottom: 12px; color: #2d2d4e; }
.demo-row { display: flex; align-items: center; gap: 12px; margin-bottom: 8px; }
button { background: #4a3f8a; color: white; border: none; border-radius: 4px; padding: 6px 16px; cursor: pointer; font-size: 0.9rem; }
button:hover { background: #5b4fa0; }
button:active { background: #3a2f7a; }
.value { font-size: 1.4rem; font-weight: 600; min-width: 3ch; text-align: center; }
.derived { color: #666; font-size: 0.85rem; }
.effect-log { background: #f0f0f8; border-radius: 4px; padding: 8px 12px; font-family: monospace; font-size: 0.8rem; max-height: 120px; overflow-y: auto; white-space: pre-wrap; }
.batch-indicator { display: inline-block; background: #e8f5e9; color: #2e7d32; padding: 2px 8px; border-radius: 3px; font-size: 0.8rem; }
code { background: #f0f0f8; padding: 2px 6px; border-radius: 3px; font-size: 0.85rem; }
.note { color: #888; font-size: 0.8rem; margin-top: 8px; }
</style>
</head>
<body>
<h1>SX Reactive Islands</h1>
<p class="subtitle">Signals transpiled from <code>signals.sx</code> spec via <code>bootstrap_js.py</code></p>
<!-- Demo 1: Basic signal -->
<div class="demo" id="demo-counter">
<h2>1. Signal: Counter</h2>
<div class="demo-row">
<button onclick="decr()">-</button>
<span class="value" id="count-display">0</span>
<button onclick="incr()">+</button>
</div>
<div class="derived" id="doubled-display"></div>
<p class="note"><code>signal</code> + <code>computed</code> + <code>effect</code></p>
</div>
<!-- Demo 2: Batch -->
<div class="demo" id="demo-batch">
<h2>2. Batch: Two signals, one notification</h2>
<div class="demo-row">
<span>first: <strong id="first-display">0</strong></span>
<span>second: <strong id="second-display">0</strong></span>
<span class="batch-indicator" id="render-count"></span>
</div>
<div class="demo-row">
<button onclick="batchBoth()">Batch increment both</button>
<button onclick="noBatchBoth()">No-batch increment both</button>
</div>
<p class="note"><code>batch</code> coalesces writes: 2 updates, 1 re-render</p>
</div>
<!-- Demo 3: Effect with cleanup -->
<div class="demo" id="demo-effect">
<h2>3. Effect: Auto-tracking + Cleanup</h2>
<div class="demo-row">
<button onclick="togglePolling()">Toggle polling</button>
<span id="poll-status"></span>
</div>
<div class="effect-log" id="effect-log"></div>
<p class="note"><code>effect</code> returns cleanup fn; dispose stops tracking</p>
</div>
<!-- Demo 4: Computed chains -->
<div class="demo" id="demo-chain">
<h2>4. Computed chain: base &rarr; doubled &rarr; quadrupled</h2>
<div class="demo-row">
<button onclick="chainDecr()">-</button>
<span>base: <strong id="chain-base">1</strong></span>
<button onclick="chainIncr()">+</button>
</div>
<div class="derived">
doubled: <strong id="chain-doubled"></strong> &nbsp;
quadrupled: <strong id="chain-quad"></strong>
</div>
<p class="note">Three-level computed dependency graph, auto-propagation</p>
</div>
<script src="sx-ref.js"></script>
<script>
// Grab signal primitives from transpiled runtime
var S = window.Sx;
var signal = S.signal;
var deref = S.deref;
var reset = S.reset;
var swap = S.swap;
var computed = S.computed;
var effect = S.effect;
var batch = S.batch;
// ---- Demo 1: Counter ----
var count = signal(0);
var doubled = computed(function() { return deref(count) * 2; });
effect(function() {
document.getElementById("count-display").textContent = deref(count);
});
effect(function() {
document.getElementById("doubled-display").textContent = "doubled: " + deref(doubled);
});
function incr() { swap(count, function(n) { return n + 1; }); }
function decr() { swap(count, function(n) { return n - 1; }); }
// ---- Demo 2: Batch ----
var first = signal(0);
var second = signal(0);
var renders = signal(0);
effect(function() {
document.getElementById("first-display").textContent = deref(first);
document.getElementById("second-display").textContent = deref(second);
swap(renders, function(n) { return n + 1; });
});
effect(function() {
document.getElementById("render-count").textContent = "renders: " + deref(renders);
});
function batchBoth() {
batch(function() {
swap(first, function(n) { return n + 1; });
swap(second, function(n) { return n + 1; });
});
}
function noBatchBoth() {
swap(first, function(n) { return n + 1; });
swap(second, function(n) { return n + 1; });
}
// ---- Demo 3: Effect with cleanup ----
var polling = signal(false);
var pollDispose = null;
var logEl = document.getElementById("effect-log");
function log(msg) {
logEl.textContent += msg + "\n";
logEl.scrollTop = logEl.scrollHeight;
}
effect(function() {
var active = deref(polling);
document.getElementById("poll-status").textContent = active ? "polling..." : "stopped";
if (active) {
var n = 0;
var id = setInterval(function() {
n++;
log("poll #" + n);
}, 500);
log("effect: started interval");
// Return cleanup function
return function() {
clearInterval(id);
log("cleanup: cleared interval");
};
}
});
function togglePolling() { swap(polling, function(v) { return !v; }); }
// ---- Demo 4: Computed chain ----
var base = signal(1);
var chainDoubled = computed(function() { return deref(base) * 2; });
var quadrupled = computed(function() { return deref(chainDoubled) * 2; });
effect(function() {
document.getElementById("chain-base").textContent = deref(base);
});
effect(function() {
document.getElementById("chain-doubled").textContent = deref(chainDoubled);
});
effect(function() {
document.getElementById("chain-quad").textContent = deref(quadrupled);
});
function chainIncr() { swap(base, function(n) { return n + 1; }); }
function chainDecr() { swap(base, function(n) { return n - 1; }); }
</script>
</body>
</html>