Spec event bridge and named stores, move plan to reactive islands section

- signals.sx: add def-store/use-store/clear-stores (L3 named stores)
  and emit-event/on-event/bridge-event (lake→island DOM events)
- reactive-islands.sx: add event bridge, named stores, and plan pages
- Remove ~plan-reactive-islands-content from plans.sx
- Update nav-data.sx and docs.sx routing accordingly

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-08 10:59:58 +00:00
parent 0da5dc41e1
commit 5b70cd5cfc
5 changed files with 340 additions and 267 deletions

View File

@@ -288,3 +288,87 @@
(fn (disposable)
(when *island-scope*
(*island-scope* disposable))))
;; ==========================================================================
;; 12. Named stores — page-level signal containers (L3)
;; ==========================================================================
;;
;; Stores persist across island creation/destruction. They live at page
;; scope, not island scope. When an island is swapped out and re-created,
;; it reconnects to the same store instance.
;;
;; The store registry is global page-level state. It survives island
;; disposal but is cleared on full page navigation.
(define *store-registry* (dict))
(define def-store
(fn (name init-fn)
(let ((registry *store-registry*))
;; Only create the store once — subsequent calls return existing
(when (not (has? registry name))
(set! *store-registry* (assoc registry name (init-fn))))
(get *store-registry* name))))
(define use-store
(fn (name)
(if (has? *store-registry* name)
(get *store-registry* name)
(error (str "Store not found: " name
". Call (def-store ...) before (use-store ...).")))))
(define clear-stores
(fn ()
(set! *store-registry* (dict))))
;; ==========================================================================
;; 13. Event bridge — DOM event communication for lake→island
;; ==========================================================================
;;
;; Server-rendered content ("htmx lakes") inside reactive islands can
;; communicate with island signals via DOM custom events. The bridge
;; pattern:
;;
;; 1. Server renders a button/link with data-sx-emit="event-name"
;; 2. When clicked, the client dispatches a CustomEvent on the element
;; 3. The event bubbles up to the island container
;; 4. An island effect listens for the event and updates signals
;;
;; This keeps server content pure HTML — no signal references needed.
;; The island effect is the only reactive code.
;;
;; Platform interface required:
;; (dom-listen el event-name handler) → remove-fn
;; (dom-dispatch el event-name detail) → void
;; (event-detail e) → any
;;
;; These are platform primitives because they require browser DOM APIs.
(define emit-event
(fn (el event-name detail)
(dom-dispatch el event-name detail)))
(define on-event
(fn (el event-name handler)
(dom-listen el event-name handler)))
;; Convenience: create an effect that listens for a DOM event on an
;; element and writes the event detail (or a transformed value) into
;; a target signal. Returns the effect's dispose function.
;; When the effect is disposed (island teardown), the listener is
;; removed automatically via the cleanup return.
(define bridge-event
(fn (el event-name target-signal transform-fn)
(effect (fn ()
(let ((remove (dom-listen el event-name
(fn (e)
(let ((detail (event-detail e))
(new-val (if transform-fn
(transform-fn detail)
detail)))
(reset! target-signal new-val))))))
;; Return cleanup — removes listener on dispose/re-run
remove)))))