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:
File diff suppressed because one or more lines are too long
41
sx/sx/reactive-islands/demo-cyst.sx
Normal file
41
sx/sx/reactive-islands/demo-cyst.sx
Normal 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.")))
|
||||
46
sx/sx/reactive-islands/demo-reactive-expressions.sx
Normal file
46
sx/sx/reactive-islands/demo-reactive-expressions.sx
Normal 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.")))
|
||||
Reference in New Issue
Block a user