diff --git a/sx/sx/nav-data.sx b/sx/sx/nav-data.sx index 7bfbfe5..a6d2f5f 100644 --- a/sx/sx/nav-data.sx +++ b/sx/sx/nav-data.sx @@ -253,18 +253,14 @@ :summary "SX as its own compiler. OCaml as substrate (closest to CEK), Koka as alternative (compile-time linearity), ultimately self-hosting. One language, every target."))) (define reactive-islands-nav-items (list - (dict :label "Overview" :href "/sx/(geography.(reactive))" - :summary "Architecture, four levels (L0-L3), and current implementation status.") - (dict :label "Demo" :href "/sx/(geography.(reactive.demo))" - :summary "Live demonstration of signals, computed, effects, batch, and defisland — all transpiled from spec.") + (dict :label "Examples" :href "/sx/(geography.(reactive.examples))" + :summary "Live interactive islands — counter, temperature, stopwatch, lists, input binding, portals, error boundaries, stores, event bridge.") (dict :label "Event Bridge" :href "/sx/(geography.(reactive.event-bridge))" :summary "DOM events for htmx lake → island communication. Server-rendered buttons dispatch custom events that island effects listen for.") (dict :label "Named Stores" :href "/sx/(geography.(reactive.named-stores))" :summary "Page-level signal containers via def-store/use-store — persist across island destruction/recreation.") - (dict :label "Plan" :href "/sx/(geography.(reactive.plan))" - :summary "The full design document — rendering boundary, state flow, signal primitives, island lifecycle.") - (dict :label "Phase 2" :href "/sx/(geography.(reactive.phase2))" - :summary "Input binding, keyed lists, reactive class/style, refs, portals, error boundaries, suspense, transitions."))) + (dict :label "Marshes" :href "/sx/(geography.(reactive.marshes))" + :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms, client state modifies hypermedia."))) (define bootstrappers-nav-items (list (dict :label "Overview" :href "/sx/(language.(bootstrapper))") diff --git a/sx/sx/reactive-islands/demo.sx b/sx/sx/reactive-islands/demo.sx index c92d8ea..b52abc2 100644 --- a/sx/sx/reactive-islands/demo.sx +++ b/sx/sx/reactive-islands/demo.sx @@ -1,13 +1,13 @@ ;; --------------------------------------------------------------------------- -;; Demo page — shows what's been implemented +;; Examples page — live interactive islands, one per section ;; --------------------------------------------------------------------------- (defcomp ~reactive-islands/demo/reactive-islands-demo-content () - (~docs/page :title "Reactive Islands Demo" + (~docs/page :title "Reactive Islands — Examples" - (~docs/section :title "What this demonstrates" :id "what" - (p (strong "These are live interactive islands") " — not static code snippets. Click the buttons. The signal runtime is defined in " (code "signals.sx") " (374 lines of s-expressions), then bootstrapped to JavaScript by " (code "bootstrap_js.py") ". No hand-written signal logic in JavaScript.") - (p "The transpiled " (code "sx-browser.js") " registers " (code "signal") ", " (code "deref") ", " (code "reset!") ", " (code "swap!") ", " (code "computed") ", " (code "effect") ", and " (code "batch") " as SX primitives — callable from " (code "defisland") " bodies defined in " (code ".sx") " files.")) + (~docs/section :title "Live interactive islands" :id "intro" + (p (strong "Every example below is a live interactive island") " — not a static code snippet. Click the buttons, type in the inputs. The signal runtime is defined in " (code "signals.sx") ", bootstrapped to JavaScript. No hand-written signal logic.") + (p "Each island uses " (code "defisland") " with signals (" (code "signal") ", " (code "deref") ", " (code "reset!") ", " (code "swap!") "), derived values (" (code "computed") "), side effects (" (code "effect") "), and batch updates (" (code "batch") ").")) (~docs/section :title "1. Signal + Computed + Effect" :id "demo-counter" (p "A signal holds a value. A computed derives from it. Click the buttons — the counter and doubled value update instantly, no server round-trip.") @@ -88,12 +88,18 @@ (~docs/code :code (highlight ";; Island A — creates/writes the store\n(defisland ~reactive-islands/demo/store-writer ()\n (let ((store (def-store \"theme\" (fn ()\n (dict \"color\" (signal \"violet\")\n \"dark\" (signal false))))))\n (select :bind (get store \"color\")\n (option :value \"violet\" \"Violet\")\n (option :value \"blue\" \"Blue\"))\n (input :type \"checkbox\" :bind (get store \"dark\"))))\n\n;; Island B — reads the same store, different island\n(defisland ~reactive-islands/demo/store-reader ()\n (let ((store (use-store \"theme\")))\n (div :class (str \"bg-\" (deref (get store \"color\")) \"-100\")\n \"Styled by signals from Island A\")))" "lisp")) (p "React equivalent: " (code "createContext") " + " (code "useContext") " or Redux/Zustand. Stores are simpler — just named dicts of signals at page scope. " (code "def-store") " creates once, " (code "use-store") " retrieves. Stores survive island disposal but clear on full page navigation.")) - (~docs/section :title "14. How defisland Works" :id "how-defisland" + (~docs/section :title "14. Event Bridge" :id "demo-event-bridge" + (p "Server-rendered content inside an island (an htmx \"lake\") can communicate with island signals via DOM custom events. Buttons with " (code "data-sx-emit") " dispatch events that island effects catch.") + (~reactive-islands/index/demo-event-bridge) + (~docs/code :code (highlight ";; Island listens for custom events from server-rendered content\n(defisland ~reactive-islands/demo/event-bridge ()\n (let ((messages (signal (list))))\n ;; Bridge: auto-listen for \"inbox:message\" events\n (bridge-event container \"inbox:message\" messages\n (fn (detail) (append (deref messages) (get detail \"text\"))))\n (div\n ;; Lake content (server-rendered) has data-sx-emit buttons\n (div :id \"lake\"\n :sx-get \"/my-content\"\n :sx-swap \"innerHTML\"\n :sx-trigger \"load\")\n ;; Island reads the signal reactively\n (ul (map (fn (msg) (li msg)) (deref messages))))))" "lisp")) + (p "The " (code "data-sx-emit") " attribute is processed by the client engine — it adds a click handler that dispatches a CustomEvent with the JSON from " (code "data-sx-emit-detail") ". The event bubbles up to the island container where " (code "bridge-event") " catches it.")) + + (~docs/section :title "15. How defisland Works" :id "how-defisland" (p (code "defisland") " creates a reactive component. Same calling convention as " (code "defcomp") " — keyword args, rest children — but with a reactive boundary. Inside an island, " (code "deref") " subscribes DOM nodes to signals.") (~docs/code :code (highlight ";; Definition — same syntax as defcomp\n(defisland ~reactive-islands/demo/counter (&key initial)\n (let ((count (signal (or initial 0))))\n (div\n (span (deref count)) ;; reactive text node\n (button :on-click (fn (e) (swap! count inc)) ;; event handler\n \"+\"))))\n\n;; Usage — same as any component\n(~reactive-islands/demo/counter :initial 42)\n\n;; Server-side rendering:\n;;
\n;; 42\n;;
\n;;\n;; Client hydrates: signals + effects + event handlers attach" "lisp")) (p "Each " (code "deref") " call registers the enclosing DOM node as a subscriber. Signal changes update " (em "only") " the subscribed nodes — no virtual DOM, no diffing, no component re-renders.")) - (~docs/section :title "15. Test suite" :id "demo-tests" + (~docs/section :title "16. Test suite" :id "demo-tests" (p "17 tests verify the signal runtime against the spec. All pass in the Python test runner (which uses the hand-written evaluator with native platform primitives).") (~docs/code :code (highlight ";; Signal basics (6 tests)\n(assert-true (signal? (signal 42)))\n(assert-equal 42 (deref (signal 42)))\n(assert-equal 5 (deref 5)) ;; non-signal passthrough\n\n;; reset! changes value\n(let ((s (signal 0)))\n (reset! s 10)\n (assert-equal 10 (deref s)))\n\n;; reset! does NOT notify when value unchanged (identical? check)\n\n;; Computed (3 tests)\n(let ((a (signal 3)) (b (signal 4))\n (sum (computed (fn () (+ (deref a) (deref b))))))\n (assert-equal 7 (deref sum))\n (reset! a 10)\n (assert-equal 14 (deref sum)))\n\n;; Effects (4 tests) — immediate run, re-run on change, dispose, cleanup\n;; Batch (1 test) — defers notifications, deduplicates subscribers\n;; defisland (3 tests) — creates island, callable, accepts children" "lisp")) (p :class "mt-2 text-sm text-stone-500" "Run: " (code "python3 shared/sx/tests/run.py signals"))) diff --git a/sx/sx/reactive-islands/index.sx b/sx/sx/reactive-islands/index.sx index e94c244..826aa66 100644 --- a/sx/sx/reactive-islands/index.sx +++ b/sx/sx/reactive-islands/index.sx @@ -483,6 +483,45 @@ "Styled by signals from Island A")))) +;; 14. Event bridge — lake→island communication via custom DOM events +(defisland ~reactive-islands/index/demo-event-bridge () + (let ((messages (signal (list))) + (container nil)) + ;; Bridge: listen for "inbox:message" events from server-rendered content + (effect (fn () + (when container + (on-event container "inbox:message" + (fn (e) + (swap! messages (fn (old) + (append old (get (event-detail e) "text"))))))))) + (div :ref (dict "current" nil) + (p :class "text-xs font-semibold text-stone-500 mb-2" "Event Bridge Demo") + (p :class "text-sm text-stone-600 mb-2" + "The buttons below simulate server-rendered content dispatching events into the island.") + (div :class "flex gap-2 mb-3" + (button + :data-sx-emit "inbox:message" + :data-sx-emit-detail "{\"text\":\"Hello from the lake!\"}" + :class "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\"}" + :class "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))) + :class "px-3 py-1.5 bg-stone-200 text-stone-700 rounded text-sm hover:bg-stone-300" + "Clear")) + (div :class "min-h-12 p-3 rounded bg-stone-100 border border-stone-200" + (if (empty? (deref messages)) + (p :class "text-stone-400 text-sm" "No messages yet.") + (ul :class "space-y-1" + (map (fn (msg) + (li :class "text-sm text-stone-700" (str "→ " msg))) + (deref messages)))))))) + + ;; --------------------------------------------------------------------------- -;; Demo page — shows what's been implemented +;; Examples page — shows what's been implemented ;; --------------------------------------------------------------------------- diff --git a/sx/sxc/pages/docs.sx b/sx/sxc/pages/docs.sx index 8010152..0d6dbe2 100644 --- a/sx/sxc/pages/docs.sx +++ b/sx/sxc/pages/docs.sx @@ -611,11 +611,11 @@ :layout :sx-docs :content (~layouts/doc :path (str "/sx/(geography.(reactive." slug "))") (case slug + "examples" (~reactive-islands/demo/reactive-islands-demo-content) "demo" (~reactive-islands/demo/reactive-islands-demo-content) "event-bridge" (~reactive-islands/event-bridge/reactive-islands-event-bridge-content) "named-stores" (~reactive-islands/named-stores/reactive-islands-named-stores-content) - "plan" (~reactive-islands/plan/reactive-islands-plan-content) - "phase2" (~reactive-islands/phase2/reactive-islands-phase2-content) + "marshes" (~reactive-islands/marshes/reactive-islands-marshes-content) :else (~reactive-islands/index/reactive-islands-index-content)))) ;; ---------------------------------------------------------------------------