Files
rose-ash/sx/sx/reactive-islands/index.sx
giles d40a9c6796 sx-tools: WASM kernel updates, TW/CSSX rework, content refresh, new debugging tools
Build tooling: updated OCaml bootstrapper, compile-modules, bundle.sh, sx-build-all.
WASM browser: rebuilt sx_browser.bc.js/wasm, sx-platform-2.js, .sxbc bytecode files.
CSSX/Tailwind: reworked cssx.sx templates and tw-layout, added tw-type support.
Content: refreshed essays, plans, geography, reactive islands, docs, demos, handlers.
New tools: bisect_sxbc.sh, test-spa.js, render-trace.sx, morph playwright spec.
Tests: added test-match.sx, test-examples.sx, updated test-tw.sx and web tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 11:31:57 +00:00

1157 lines
44 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
(defcomp
~reactive-islands/index/reactive-islands-index-content
()
(~docs/page
:title "Reactive Islands"
(~docs/section
:title "Architecture"
:id "architecture"
(p "Two orthogonal bars control how an SX page works:")
(ul
(~tw :tokens "space-y-1 text-stone-600 list-disc pl-5")
(li
(strong "Render boundary")
" — where rendering happens (server HTML vs client DOM)")
(li
(strong "State flow")
" — how state flows (server state vs client signals)"))
(div
(~tw :tokens "overflow-x-auto mt-4 mb-4")
(table
(~tw :tokens "w-full text-sm text-left")
(thead
(tr
(~tw :tokens "border-b border-stone-200")
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "")
(th
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
"Server State")
(th
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
"Client State")))
(tbody
(~tw :tokens "text-stone-600")
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
"Server Rendering")
(td (~tw :tokens "py-2 px-3") "Pure hypermedia (htmx)")
(td (~tw :tokens "py-2 px-3") "SSR + hydrated islands"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
"Client Rendering")
(td (~tw :tokens "py-2 px-3") "SX wire format (current)")
(td
(~tw :tokens "py-2 px-3 font-semibold text-violet-700")
"Reactive islands (this)")))))
(p
"Most content stays pure hypermedia. Interactive regions opt into reactivity. The author controls where each component sits on both bars."))
(~docs/section
:title "Four Levels"
:id "levels"
(div
(~tw :tokens "space-y-4")
(div
(~tw :tokens "rounded border border-stone-200 p-4")
(div
(~tw :tokens "font-semibold text-stone-800")
"Level 0: Pure Hypermedia")
(p
(~tw :tokens "text-sm text-stone-600 mt-1")
"The default. "
(code "sx-get")
", "
(code "sx-post")
", "
(code "sx-swap")
". Server renders everything. No client state. 90% of a typical application."))
(div
(~tw :tokens "rounded border border-stone-200 p-4")
(div
(~tw :tokens "font-semibold text-stone-800")
"Level 1: Local DOM Operations")
(p
(~tw :tokens "text-sm text-stone-600 mt-1")
"Imperative escapes: "
(code "toggle!")
", "
(code "set-attr!")
", "
(code "on-event")
". Micro-interactions too small for a server round-trip."))
(div
(~tw :tokens "rounded border border-violet-300 bg-violet-50 p-4")
(div
(~tw :tokens "font-semibold text-violet-900")
"Level 2: Reactive Islands")
(p
(~tw :tokens "text-sm text-stone-600 mt-1")
(code "defisland")
" components with local signals. Fine-grained DOM updates "
(em "without")
" virtual DOM, diffing, or component re-renders. A signal change updates only the DOM nodes that read it."))
(div
(~tw :tokens "rounded border border-stone-200 p-4")
(div
(~tw :tokens "font-semibold text-stone-800")
"Level 3: Connected Islands")
(p
(~tw :tokens "text-sm text-stone-600 mt-1")
"Islands that share state via signal props or named stores ("
(code "def-store")
" / "
(code "use-store")
")."))))
(~docs/section
:title "Signal Primitives"
:id "signals"
(~docs/code
:src (highlight
"(signal v) ;; create a reactive container\n(deref s) ;; read value — subscribes in reactive context\n(reset! s v) ;; write new value — notifies subscribers\n(swap! s f) ;; update via function: (f old-value)\n(computed fn) ;; derived signal — auto-tracks dependencies\n(effect fn) ;; side effect — re-runs when deps change\n(batch fn) ;; group writes — one notification pass"
"lisp"))
(p
"Signals are values, not hooks. Create them anywhere — conditionals, loops, closures. No rules of hooks. Pass them as arguments, store them in dicts, share between islands."))
(~docs/section
:title "Island Lifecycle"
:id "lifecycle"
(ol
(~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
(li
(strong "Definition: ")
(code "defisland")
" registers a reactive component (like "
(code "defcomp")
" + island flag)")
(li
(strong "Server render: ")
"Body evaluated with initial values. "
(code "deref")
" returns plain value. Output wrapped in "
(code "data-sx-island")
" / "
(code "data-sx-state"))
(li
(strong "Client hydration: ")
"Finds "
(code "data-sx-island")
" elements, creates signals from serialized state, re-renders in reactive context")
(li
(strong "Updates: ")
"Signal changes update only subscribed DOM nodes. No full island re-render")
(li
(strong "Disposal: ")
"Island removed from DOM — all signals and effects cleaned up via "
(code "with-island-scope"))))
(~docs/section
:title "htmx Lakes"
:id "lakes"
(p
"An htmx lake is server-driven content "
(em "inside")
" a reactive island. The island provides the reactive boundary; the lake content is swapped via "
(code "sx-get")
"/"
(code "sx-post")
" like normal hypermedia. This works because signals live in closures, not the DOM.")
(div
(~tw :tokens "space-y-2 mt-3")
(div
(~tw :tokens "rounded border border-green-200 bg-green-50 p-3")
(div
(~tw :tokens "font-semibold text-green-800 text-sm")
"Swap inside island")
(p
(~tw :tokens "text-sm text-stone-600 mt-1")
"Lake content replaced. Signals survive. Effects rebind to new DOM."))
(div
(~tw :tokens "rounded border border-green-200 bg-green-50 p-3")
(div
(~tw :tokens "font-semibold text-green-800 text-sm")
"Swap outside island")
(p
(~tw :tokens "text-sm text-stone-600 mt-1")
"Different part of page updated. Island completely unaffected."))
(div
(~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3")
(div
(~tw :tokens "font-semibold text-amber-800 text-sm")
"Swap replaces island")
(p
(~tw :tokens "text-sm text-stone-600 mt-1")
"Island disposed. Local signals lost. Named stores persist — new island reconnects via "
(code "use-store")
"."))
(div
(~tw :tokens "rounded border border-stone-200 p-3")
(div
(~tw :tokens "font-semibold text-stone-800 text-sm")
"Full page navigation")
(p
(~tw :tokens "text-sm text-stone-600 mt-1")
"Everything cleared. "
(code "clear-stores")
" wipes the registry."))))
(~docs/section
:title "Event Bridge"
:id "event-bridge"
(p
"A lake has no access to island signals, but can communicate back via DOM custom events. Elements with "
(code "data-sx-emit")
" dispatch a "
(code "CustomEvent")
" on click; an island effect catches it and updates a signal.")
(~docs/code
:src (highlight
";; Island listens for events from server-rendered lake content\n(bridge-event container \"cart:add\" items\n (fn (detail) (append (deref items) detail)))\n\n;; Server-rendered button dispatches CustomEvent on click\n(button :data-sx-emit \"cart:add\"\n :data-sx-emit-detail (json-serialize (dict :id 42))\n \"Add to Cart\")"
"lisp"))
(p
"Three primitives: "
(code "emit-event")
" (dispatch), "
(code "on-event")
" (listen), "
(code "bridge-event")
" (listen + update signal with automatic cleanup)."))
(~docs/section
:title "Named Stores"
:id "stores"
(p
"A named store is a dict of signals at "
(em "page")
" scope — not island scope. Multiple islands share the same signals. Stores survive island destruction and recreation.")
(~docs/code
:src (highlight
";; Create once — idempotent, returns existing on second call\n(def-store \"cart\" (fn ()\n (dict :items (signal (list))\n :count (computed (fn () (length (deref items)))))))\n\n;; Use from any island, anywhere in the DOM\n(let ((store (use-store \"cart\")))\n (span (deref (get store \"count\"))))"
"lisp"))
(p
(code "def-store")
" creates, "
(code "use-store")
" retrieves, "
(code "clear-stores")
" wipes all on full page navigation."))
(~docs/section
:title "Examples"
:id "examples"
(p "Each example below shows a live island and its source.")
(~docs/section
:title "Counter"
:id "demo-counter"
(p "Signals, computed, and " (code "swap!") ".")
(~reactive-islands/index/demo-counter :initial 0)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-counter")
"lisp")))
(~docs/section
:title "Temperature Converter"
:id "demo-temperature"
(p
"Two signals, each derived from the other via "
(code "effect")
".")
(~reactive-islands/index/demo-temperature)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-temperature")
"lisp")))
(~docs/section
:title "Imperative Handlers"
:id "demo-imperative"
(p "Multi-statement " (code "(do ...)") " bodies in event handlers.")
(~reactive-islands/index/demo-imperative)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-imperative")
"lisp")))
(~docs/section
:title "Stopwatch"
:id "demo-stopwatch"
(p
(code "set-interval")
" and "
(code "clear-interval")
" with signal-driven UI.")
(~reactive-islands/index/demo-stopwatch)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-stopwatch")
"lisp")))
(~docs/section
:title "Reactive List"
:id "demo-reactive-list"
(p "Dynamic list with keyed reconciliation.")
(~reactive-islands/index/demo-reactive-list)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-reactive-list")
"lisp")))
(~docs/section
:title "Input Binding"
:id "demo-input-binding"
(p "Two-way binding via " (code ":bind") " attribute.")
(~reactive-islands/index/demo-input-binding)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-input-binding")
"lisp")))
(~docs/section
:title "Dynamic Classes"
:id "demo-dynamic-class"
(p
"Reactive class toggling with "
(code "deref")
" in attribute expressions.")
(~reactive-islands/index/demo-dynamic-class)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-dynamic-class")
"lisp")))
(~docs/section
:title "Portal"
:id "demo-portal"
(p "Render content outside the island's DOM subtree.")
(~reactive-islands/index/demo-portal)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-portal")
"lisp")))
(~docs/section
:title "Error Boundary"
:id "demo-error-boundary"
(p "Catch rendering errors without crashing the page.")
(~reactive-islands/index/demo-error-boundary)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-error-boundary")
"lisp")))
(~docs/section
:title "DOM Refs"
:id "demo-refs"
(p "Access raw DOM elements via " (code ":ref") " signal.")
(~reactive-islands/index/demo-refs)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-refs")
"lisp")))
(~docs/section
:title "Resource"
:id "demo-resource"
(p "Async data fetching with loading state.")
(~reactive-islands/index/demo-resource)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-resource")
"lisp")))
(~docs/section
:title "Transition"
:id "demo-transition"
(p
"Debounced search with "
(code "schedule-idle")
" and "
(code "batch")
".")
(~reactive-islands/index/demo-transition)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-transition")
"lisp")))
(~docs/section
:title "Named Stores"
:id "demo-stores"
(p
"Two islands sharing state via "
(code "def-store")
" / "
(code "use-store")
".")
(~reactive-islands/index/demo-store-writer)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-store-writer")
"lisp"))
(~reactive-islands/index/demo-store-reader)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-store-reader")
"lisp")))
(~docs/section
:title "Event Bridge"
:id "demo-event-bridge"
(p
"Server-rendered content communicating with an island via "
(code "bridge-event")
".")
(~reactive-islands/index/demo-event-bridge)
(~docs/code
:src (highlight
(component-source "~reactive-islands/index/demo-event-bridge")
"lisp"))))
(~docs/section
:title "Design Principles"
:id "principles"
(ol
(~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
(li
(strong "Islands are opt-in.")
" "
(code "defcomp")
" is the default. "
(code "defisland")
" adds reactivity. No overhead for static content.")
(li
(strong "Signals are values, not hooks.")
" Create anywhere — conditionals, loops, closures. No rules of hooks, no dependency arrays.")
(li
(strong "Fine-grained, not component-grained.")
" A signal change updates the specific DOM node that reads it. No virtual DOM, no diffing, no component re-renders.")
(li
(strong "The server is still the authority.")
" Islands handle client interactions. The server handles auth, data, routing.")
(li
(strong "Spec-first.")
" Signal semantics live in "
(code "signals.sx")
". Bootstrapped to JS and Python. Same primitives on future hosts.")
(li
(strong "No build step.")
" Reactive bindings created at runtime. No JSX compilation, no bundler plugins.")))
(~docs/section
:title "Implementation Status"
:id "status"
(p
(~tw :tokens "text-stone-600 mb-3")
"All signal logic lives in "
(code ".sx")
" spec files and is bootstrapped to JavaScript and Python. No SX-specific logic in host languages.")
(div
(~tw :tokens "overflow-x-auto rounded border border-stone-200")
(table
(~tw :tokens "w-full text-left text-sm")
(thead
(tr
(~tw :tokens "border-b border-stone-200 bg-stone-100")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Layer")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Files")))
(tbody
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Signal runtime spec")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"signals.sx (291 lines)"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "defisland special form")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"eval.sx, special-forms.sx, render.sx"))
(tr
(~tw :tokens "border-b border-stone-100")
(td
(~tw :tokens "px-3 py-2 text-stone-700")
"DOM adapter (reactive rendering)")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"adapter-dom.sx (+140 lines)"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "HTML adapter (SSR)")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"adapter-html.sx (+65 lines)"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS bootstrapper")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"bootstrap_js.py, sx-ref.js (4769 lines)"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Python bootstrapper")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"bootstrap_py.py, sx_ref.py"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Test suite")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "17/17")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"test-signals.sx"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Named stores (L3)")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"signals.sx: def-store, use-store, clear-stores"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Event bridge")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"signals.sx: emit-event, on-event, bridge-event"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client hydration")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"boot.sx: sx-hydrate-islands, hydrate-island, dispose-island"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Event bindings")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"adapter-dom.sx: :on-click → domListen"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "data-sx-emit processing")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"orchestration.sx: process-emit-elements"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Island disposal")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"boot.sx, orchestration.sx: dispose-islands-in pre-swap"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Reactive list")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"adapter-dom.sx: map + deref auto-upgrades"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Input binding")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"adapter-dom.sx: :bind signal, bind-input"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Keyed reconciliation")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"adapter-dom.sx: :key attr, extract-key"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Portals")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"adapter-dom.sx: portal render-dom form"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Error boundaries")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"adapter-dom.sx: error-boundary render-dom form"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Resource (async signal)")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"signals.sx: resource, promise-then"))
(tr
(~tw :tokens "border-b border-stone-100")
(td (~tw :tokens "px-3 py-2 text-stone-700") "Suspense pattern")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"resource + cond/deref (no special form)"))
(tr
(td (~tw :tokens "px-3 py-2 text-stone-700") "Transition pattern")
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
(td
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
"schedule-idle + batch (no special form)"))))))))
(defisland
~reactive-islands/index/demo-counter
(&key initial)
(let
((count (signal (or initial 0)))
(doubled (computed (fn () (* 2 (deref count))))))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
(div
(~tw :tokens "flex items-center gap-4")
(button
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
:on-click (fn (e) (swap! count dec))
"")
(span
(~tw :tokens "text-2xl font-bold text-violet-900 w-12 text-center")
(deref count))
(button
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
:on-click (fn (e) (swap! count inc))
"+"))
(p
(~tw :tokens "text-sm text-stone-500 mt-2")
"doubled: "
(span (~tw :tokens "font-mono text-violet-700") (deref doubled))))))
(defisland
~reactive-islands/index/demo-temperature
()
(let
((celsius (signal 20))
(fahrenheit (computed (fn () (+ (* (deref celsius) 1.8) 32)))))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
(div
(~tw :tokens "flex items-center gap-3")
(div
(~tw :tokens "flex items-center gap-2")
(button
(~tw :tokens "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300")
:on-click (fn (e) (swap! celsius (fn (c) (- c 5))))
"5")
(span
(~tw :tokens "font-mono text-lg font-bold text-violet-900 w-16 text-center")
(deref celsius))
(button
(~tw :tokens "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300")
:on-click (fn (e) (swap! celsius (fn (c) (+ c 5))))
"+5")
(span (~tw :tokens "text-stone-500") "°C"))
(span (~tw :tokens "text-stone-400") "=")
(span
(~tw :tokens "font-mono text-lg font-bold text-violet-900")
(deref fahrenheit))
(span (~tw :tokens "text-stone-500") "°F")))))
(defisland
~reactive-islands/index/demo-imperative
()
(let
((count (signal 0))
(text-node (create-text-node "0"))
(_eff
(effect
(fn () (dom-set-text-content text-node (str (deref count)))))))
(div
(~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-4")
(p
(~tw :tokens "text-sm text-stone-600 mb-2")
"Imperative style — explicit "
(code "effect")
" + "
(code "create-text-node")
":")
(div
(~tw :tokens "flex items-center gap-4")
(button
(~tw :tokens "px-3 py-1 rounded bg-stone-600 text-white text-sm font-medium hover:bg-stone-700")
:on-click (fn (e) (swap! count dec))
"")
(span
(~tw :tokens "text-2xl font-bold text-stone-900 w-12 text-center")
text-node)
(button
(~tw :tokens "px-3 py-1 rounded bg-stone-600 text-white text-sm font-medium hover:bg-stone-700")
:on-click (fn (e) (swap! count inc))
"+")))))
(defisland
~reactive-islands/index/demo-stopwatch
()
(let
((running (signal false))
(elapsed (signal 0))
(time-text (create-text-node "0.0s"))
(btn-text (create-text-node "Start"))
(_e1
(effect
(fn
()
(when
(deref running)
(let
((id (set-interval (fn () (swap! elapsed inc)) 100)))
(fn () (clear-interval id)))))))
(_e2
(effect
(fn
()
(let
((e (deref elapsed)))
(dom-set-text-content
time-text
(str (floor (/ e 10)) "." (mod e 10) "s"))))))
(_e3
(effect
(fn
()
(dom-set-text-content
btn-text
(if (deref running) "Stop" "Start"))))))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
(div
(~tw :tokens "flex items-center gap-4")
(span
(~tw :tokens "font-mono text-2xl font-bold text-violet-900 w-24 text-center")
time-text)
(button
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
:on-click (fn (e) (swap! running not))
btn-text)
(button
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
:on-click (fn (e) (do (reset! running false) (reset! elapsed 0)))
"Reset")))))
(defisland
~reactive-islands/index/demo-reactive-list
()
(let
((next-id (signal 1))
(items (signal (list)))
(add-item
(fn
(e)
(batch
(fn
()
(swap!
items
(fn
(old)
(append
old
(dict
"id"
(deref next-id)
"text"
(str "Item " (deref next-id))))))
(swap! next-id inc)))))
(remove-item
(fn
(id)
(swap!
items
(fn
(old)
(filter (fn (item) (not (= (get item "id") id))) old))))))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
(div
(~tw :tokens "flex items-center gap-3 mb-3")
(button
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
:on-click add-item
"Add Item")
(span
(~tw :tokens "text-sm text-stone-500")
(deref (computed (fn () (len (deref items)))))
" items"))
(ul
(~tw :tokens "space-y-1")
(map
(fn
(item)
(li
:key (str (get item "id"))
(~tw :tokens "flex items-center justify-between bg-white rounded px-3 py-2 text-sm")
(span (get item "text"))
(button
(~tw :tokens "text-stone-400 hover:text-red-500 text-xs")
:on-click (fn (e) (remove-item (get item "id")))
"✕")))
(deref items))))))
(defisland
~reactive-islands/index/demo-input-binding
()
(let
((name (signal "")) (agreed (signal false)))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3")
(div
(~tw :tokens "flex items-center gap-3")
(input
:type "text"
:bind name
:placeholder "Type your name..."
(~tw :tokens "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-48"))
(span
(~tw :tokens "text-sm text-stone-600")
"Hello, "
(strong (deref name))
"!"))
(div
(~tw :tokens "flex items-center gap-2")
(input
:type "checkbox"
:bind agreed
:id "agree-cb"
(~tw :tokens "rounded border-stone-300"))
(label
:for "agree-cb"
(~tw :tokens "text-sm text-stone-600")
"I agree to the terms"))
(when
(deref agreed)
(p (~tw :tokens "text-sm text-green-700") "Thanks for agreeing!")))))
(defisland
~reactive-islands/index/demo-portal
()
(let
((open? (signal false)))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
(button
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
:on-click (fn (e) (swap! open? not))
(if (deref open?) "Close Modal" "Open Modal"))
(portal
"#portal-root"
(when
(deref open?)
(div
(~tw :tokens "fixed inset-0 bg-black/50 flex items-center justify-center z-50")
:on-click (fn (e) (reset! open? false))
(div
(~tw :tokens "bg-white rounded-lg p-6 max-w-md shadow-xl")
:on-click (fn (e) (stop-propagation e))
(h2
(~tw :tokens "text-lg font-bold text-stone-800 mb-2")
"Portal Modal")
(p
(~tw :tokens "text-stone-600 text-sm mb-4")
"This content is rendered into "
(code "#portal-root")
" — outside the island's DOM subtree. It escapes overflow:hidden, z-index stacking, and layout constraints.")
(button
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
:on-click (fn (e) (reset! open? false))
"Close"))))))))
(defisland
~reactive-islands/index/demo-error-boundary
()
(let
((throw? (signal false)))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
(div
(~tw :tokens "flex items-center gap-3 mb-3")
(button
(~tw :tokens "px-3 py-1 rounded bg-red-600 text-white text-sm font-medium hover:bg-red-700")
:on-click (fn (e) (reset! throw? true))
"Trigger Error")
(button
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
:on-click (fn (e) (reset! throw? false))
"Reset"))
(error-boundary
(fn
(err retry-fn)
(div
(~tw :tokens "p-3 bg-red-50 border border-red-200 rounded")
(p
(~tw :tokens "text-red-700 font-medium text-sm")
"Caught: "
(error-message err))
(button
(~tw :tokens "mt-2 px-3 py-1 rounded bg-red-600 text-white text-sm hover:bg-red-700")
:on-click (fn (e) (do (reset! throw? false) (invoke retry-fn)))
"Retry")))
(do
(when (deref throw?) (error "Intentional explosion!"))
(p
(~tw :tokens "text-sm text-green-700")
"Everything is fine. Click \"Trigger Error\" to throw."))))))
(defisland
~reactive-islands/index/demo-refs
()
(let
((my-ref (dict "current" nil)) (msg (signal "")))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3")
(input
:ref my-ref
:type "text"
:placeholder "I can be focused programmatically"
(~tw :tokens "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-64"))
(div
(~tw :tokens "flex items-center gap-3")
(button
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
:on-click (fn (e) (dom-focus (get my-ref "current")))
"Focus Input")
(button
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
:on-click (fn
(e)
(let
((el (get my-ref "current")))
(reset!
msg
(str
"Tag: "
(dom-tag-name el)
", value: \""
(dom-get-prop el "value")
"\""))))
"Read Input"))
(when
(not (= (deref msg) ""))
(p (~tw :tokens "text-sm text-stone-600 font-mono") (deref msg))))))
(defisland
~reactive-islands/index/demo-dynamic-class
()
(let
((danger (signal false)) (size (signal 16)))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3")
(div
(~tw :tokens "flex items-center gap-3")
(button
(~tw :tokens "px-3 py-1 rounded bg-violet-600 text-white text-sm font-medium hover:bg-violet-700")
:on-click (fn (e) (swap! danger not))
(if (deref danger) "Safe mode" "Danger mode"))
(button
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
:on-click (fn (e) (swap! size (fn (s) (+ s 2))))
"Bigger")
(button
(~tw :tokens "px-3 py-1 rounded bg-stone-300 text-stone-700 text-sm hover:bg-stone-400")
:on-click (fn (e) (swap! size (fn (s) (max 10 (- s 2)))))
"Smaller"))
(div
:class (str
"p-3 rounded font-medium transition-colors "
(if
(deref danger)
"bg-red-100 text-red-800"
"bg-green-100 text-green-800"))
:style (str "font-size:" (deref size) "px")
"This element's class and style are reactive."))))
(defisland
~reactive-islands/index/demo-resource
()
(let
((data (resource (fn () (promise-delayed 1500 (dict "name" "Ada Lovelace" "role" "First Programmer" "year" 1843))))))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4")
(cond
(get (deref data) "loading")
(div
(~tw :tokens "flex items-center gap-2 text-stone-500")
(span
(~tw :tokens "inline-block w-4 h-4 border-2 border-stone-300 border-t-violet-600 rounded-full animate-spin"))
(span (~tw :tokens "text-sm") "Loading..."))
(get (deref data) "error")
(div
(~tw :tokens "p-3 bg-red-50 border border-red-200 rounded")
(p
(~tw :tokens "text-red-700 text-sm")
"Error: "
(get (deref data) "error")))
:else (let
((d (get (deref data) "data")))
(div
(~tw :tokens "space-y-1")
(p (~tw :tokens "font-bold text-stone-800") (get d "name"))
(p
(~tw :tokens "text-sm text-stone-600")
(get d "role")
" ("
(get d "year")
")")))))))
(defisland
~reactive-islands/index/demo-transition
()
(let
((query (signal ""))
(all-items
(list
"Signals"
"Effects"
"Computed"
"Batch"
"Stores"
"Islands"
"Portals"
"Error Boundaries"
"Resources"
"Input Binding"
"Keyed Lists"
"Event Bridge"
"Reactive Text"
"Reactive Attrs"
"Reactive Fragments"
"Disposal"
"Hydration"
"CSSX"
"Macros"
"Refs"))
(filtered (signal (list)))
(pending (signal false)))
(reset! filtered all-items)
(let
((_eff (effect (fn () (let ((q (lower (deref query)))) (if (= q "") (do (reset! pending false) (reset! filtered all-items)) (do (reset! pending true) (schedule-idle (fn () (batch (fn () (reset! filtered (filter (fn (item) (contains? (lower item) q)) all-items)) (reset! pending false))))))))))))
(div
(~tw :tokens "rounded border border-violet-200 bg-violet-50 p-4 my-4 space-y-3")
(div
(~tw :tokens "flex items-center gap-3")
(input
:type "text"
:bind query
:placeholder "Filter features..."
(~tw :tokens "px-3 py-1.5 rounded border border-stone-300 text-sm focus:outline-none focus:border-violet-400 w-48"))
(when
(deref pending)
(span (~tw :tokens "text-xs text-stone-400") "Filtering...")))
(ul
(~tw :tokens "space-y-1")
(map
(fn
(item)
(li
:key item
(~tw :tokens "text-sm text-stone-700 bg-white rounded px-3 py-1.5")
item))
(deref filtered)))))))
(defisland
~reactive-islands/index/demo-store-writer
()
(let
((store (def-store "demo-theme" (fn () (dict "color" (signal "violet") "dark" (signal false))))))
(div
(~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-2")
(p
(~tw :tokens "text-xs font-semibold text-stone-500 mb-2")
"Island A — Store Writer")
(div
(~tw :tokens "flex items-center gap-3")
(select
:bind (get store "color")
(~tw :tokens "px-2 py-1 rounded border border-stone-300 text-sm")
(option :value "violet" "Violet")
(option :value "blue" "Blue")
(option :value "green" "Green")
(option :value "red" "Red"))
(label
(~tw :tokens "flex items-center gap-1 text-sm text-stone-600")
(input
:type "checkbox"
:bind (get store "dark")
(~tw :tokens "rounded border-stone-300"))
"Dark mode")))))
(defisland
~reactive-islands/index/demo-store-reader
()
(let
((store (use-store "demo-theme")))
(div
(~tw :tokens "rounded border border-stone-200 bg-stone-50 p-4 my-2")
(p
(~tw :tokens "text-xs font-semibold text-stone-500 mb-2")
"Island B — Store Reader")
(div
:class (str
"p-3 rounded font-medium text-sm "
(if
(deref (get store "dark"))
(str
"bg-"
(deref (get store "color"))
"-900 text-"
(deref (get store "color"))
"-100")
(str
"bg-"
(deref (get store "color"))
"-100 text-"
(deref (get store "color"))
"-800")))
"Styled by signals from Island A"))))
(defisland
~reactive-islands/index/demo-event-bridge
()
(let
((messages (signal (list)))
(_eff
(effect
(fn
()
(let
((cb (host-callback (fn (e) (swap! messages (fn (old) (append old (host-get (event-detail e) "text"))))))))
(host-call (dom-document) "addEventListener" "inbox:message" cb)
(fn
()
(host-call
(dom-document)
"removeEventListener"
"inbox:message"
cb)))))))
(div
(p
(~tw :tokens "text-xs font-semibold text-stone-500 mb-2")
"Event Bridge Demo")
(p
(~tw :tokens "text-sm text-stone-600 mb-2")
"The buttons below simulate server-rendered content dispatching events into the island.")
(div
(~tw :tokens "flex gap-2 mb-3")
(button
:data-sx-emit "inbox:message"
:data-sx-emit-detail "{\"text\":\"Hello from the lake!\"}"
(~tw :tokens "px-3 py-1.5 bg-violet-600 text-white rounded text-sm hover:bg-violet-700")
"Send \"Hello\"")
(button
:data-sx-emit "inbox:message"
:data-sx-emit-detail "{\"text\":\"Another message\"}"
(~tw :tokens "px-3 py-1.5 bg-blue-600 text-white rounded text-sm hover:bg-blue-700")
"Send \"Another\"")
(button
:on-click (fn (e) (reset! messages (list)))
(~tw :tokens "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300")
"Clear"))
(div
(~tw :tokens "min-h-12 p-3 rounded bg-stone-100 border border-stone-200")
(if
(empty? (deref messages))
(p (~tw :tokens "text-stone-400 text-sm") "No messages yet.")
(ul
(~tw :tokens "space-y-1")
(map
(fn
(msg)
(li (~tw :tokens "text-sm text-stone-700") (str "→ " msg)))
(deref messages))))))))