Add Cyst and Reactive Expressions demo pages under reactive islands

Two new demo pages with live interactive islands:

Cyst (/geography/reactive/examples/cyst):
  Parent island with a cyst inside. Parent + button increments parent
  count, cyst + button increments cyst count. Cyst state survives
  parent re-renders. Shows the isolation boundary in action.

Reactive Expressions (/geography/reactive/examples/reactive-expressions):
  Temperature signal with 5 expression types all updating live:
  bare deref, str+deref, arithmetic+deref, full conversion string,
  conditional. Demonstrates that any expression containing deref
  auto-tracks signal dependencies.

Both verified reactive with Playwright clicks on the live server.
Nav entries added to reactive-examples-nav-items.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-26 08:44:49 +00:00
parent dc72aac5b1
commit 6e2696ca20
3 changed files with 118 additions and 495 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,41 @@
;; Cyst demo — isolated reactive subtree inside a parent island.
(defisland ~reactive-islands/index/demo-cyst ()
(let ((parent-count (signal 0))
(render-count (signal 0)))
;; Increment render-count each time the parent re-renders
(swap! render-count inc)
(div :class "rounded border border-violet-200 bg-violet-50/30 p-4 space-y-4"
(div :class "space-y-2"
(div :class "text-xs text-stone-500" (str "Parent render count: " (deref render-count)))
(div :class "flex items-center gap-3"
(span :class "text-sm text-stone-600" "Parent signal:")
(button :class "px-2 py-1 rounded bg-stone-200 text-stone-700 text-sm hover:bg-stone-300"
:on-click (fn (e) (swap! parent-count inc))
"+")
(span :class "font-mono text-lg font-bold text-stone-700" (deref parent-count))))
;; The cyst — its state survives parent re-renders
(cyst :key "demo-cyst"
(let ((child-count (signal 0)))
(div :class "rounded border border-emerald-200 bg-emerald-50/30 p-3 space-y-2"
(div :class "text-xs text-emerald-600 font-semibold" "Inside cyst (isolated)")
(div :class "flex items-center gap-3"
(span :class "text-sm text-stone-600" "Cyst signal:")
(button :class "px-2 py-1 rounded bg-emerald-200 text-emerald-700 text-sm hover:bg-emerald-300"
:on-click (fn (e) (swap! child-count inc))
"+")
(span :class "font-mono text-lg font-bold text-emerald-700" (deref child-count)))))))))
(defcomp ~reactive-islands/demo/example-cyst ()
(~docs/page :title "Cyst"
(p "A " (code "cyst") " is an isolated reactive subtree inside a parent island. When the parent re-renders, the cyst's DOM is preserved — event handlers, signal subscriptions, and state all survive.")
(~reactive-islands/index/demo-cyst)
(~docs/code :src (highlight "(cyst :key \"demo-cyst\"
(let ((child-count (signal 0)))
(div
(button :on-click (fn (e) (swap! child-count inc)) \"+\")
(span (deref child-count)))))" "lisp"))
(p "Click the parent + button — the parent count increments but the cyst's count is unaffected. Click the cyst's + button — it increments independently. The cyst's DOM persists across parent re-renders because " (code "render-dom") " returns the cached DOM node instead of recreating it.")
(p "Without " (code "cyst") ", a reactive island nested inside another island's render tree gets destroyed and recreated on every parent re-render. Event handlers are lost, signals reset to initial values. " (code "cyst") " solves this by caching the rendered DOM keyed by the " (code ":key") " parameter and returning it when the node is still connected to the document.")
(p "Usage: " (code "(cyst :key \"unique-id\" body...)") ". The key must be unique within the page. The body is evaluated in a fresh " (code "with-island-scope") " with its own disposer list.")))

View File

@@ -0,0 +1,46 @@
;; Reactive Expressions demo — compound expressions with deref auto-track signals.
(defisland ~reactive-islands/index/demo-reactive-expressions ()
(let ((celsius (signal 20))
(name (signal "World")))
(div :class "rounded border border-violet-200 bg-violet-50/30 p-4 space-y-4"
(div :class "space-y-3"
(div :class "flex items-center gap-3"
(span :class "text-sm text-stone-600" "Celsius:")
(button :class "px-2 py-1 rounded bg-stone-200 text-sm hover:bg-stone-300"
:on-click (fn (e) (swap! celsius (fn (c) (- c 5)))) "5")
(span :class "font-mono text-lg font-bold text-violet-700" (deref celsius))
(button :class "px-2 py-1 rounded bg-stone-200 text-sm hover:bg-stone-300"
:on-click (fn (e) (swap! celsius (fn (c) (+ c 5)))) "+5"))
;; All of these are reactive — they update when celsius changes
(div :class "space-y-1 text-sm"
(div (span :class "text-stone-500 w-32 inline-block" "Bare deref: ") (span :class "font-mono text-violet-700" (deref celsius)))
(div (span :class "text-stone-500 w-32 inline-block" "str + deref: ") (span :class "font-mono text-violet-700" (str (deref celsius) "°C")))
(div (span :class "text-stone-500 w-32 inline-block" "Math + deref: ") (span :class "font-mono text-violet-700" (+ (* (deref celsius) 1.8) 32)))
(div (span :class "text-stone-500 w-32 inline-block" "Full conversion: ") (span :class "font-mono text-violet-700" (str (deref celsius) "°C = " (+ (* (deref celsius) 1.8) 32) "°F")))
(div (span :class "text-stone-500 w-32 inline-block" "Conditional: ") (span :class "font-mono text-violet-700" (if (> (deref celsius) 30) "Hot!" (if (< (deref celsius) 0) "Freezing!" "Moderate")))))))))
(defcomp ~reactive-islands/demo/example-reactive-expressions ()
(~docs/page :title "Reactive Expressions"
(p "Any expression containing " (code "(deref sig)") " inside an island scope is automatically reactive. The DOM adapter wraps it in a " (code "computed") " signal for dependency tracking.")
(~reactive-islands/index/demo-reactive-expressions)
(~docs/code :src (highlight ";; All of these update live when celsius changes:
;; Bare deref
(span (deref celsius))
;; String interpolation
(span (str (deref celsius) \"°C\"))
;; Arithmetic
(span (+ (* (deref celsius) 1.8) 32))
;; Complex expression
(span (str (deref celsius) \"°C = \"
(+ (* (deref celsius) 1.8) 32) \"°F\"))
;; Conditional
(span (if (> (deref celsius) 30) \"Hot!\" \"Moderate\"))" "lisp"))
(p "Before this feature, only bare " (code "(deref sig)") " was reactive. Compound expressions like " (code "(str (deref celsius) \"°C\")") " were evaluated once and never updated. Now the " (code "contains-deref?") " predicate in " (code "adapter-dom.sx") " detects " (code "deref") " calls at any depth and wraps the expression in " (code "(reactive-text (computed (fn () (eval-expr expr env))))") ".")
(p "This is a language-level feature — every island on the site benefits automatically. No opt-in needed.")))