Level 2-3: lake morphing — server content flows through reactive islands
Lake tag (lake :id "name" children...) creates server-morphable slots within islands. During morph, the engine enters hydrated islands and updates data-sx-lake elements by ID while preserving surrounding reactive DOM (signals, effects, event listeners). Specced in .sx, bootstrapped to JS and Python: - adapter-dom.sx: render-dom-lake, reactive-attr marks data-sx-reactive-attrs - adapter-html.sx: render-html-lake SSR output - adapter-sx.sx: lake serialized in wire format - engine.sx: morph-island-children (lake-by-ID matching), sync-attrs skips reactive attributes - ~sx-header uses lakes for logo and copyright - Hegelian essay updated with lake code example Also includes: lambda nil-padding for missing args, page env ordering fix Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,10 @@
|
||||
(= name "<>")
|
||||
(render-dom-fragment args env ns)
|
||||
|
||||
;; lake — server-morphable slot within an island
|
||||
(= name "lake")
|
||||
(render-dom-lake args env ns)
|
||||
|
||||
;; html: prefix → force element rendering
|
||||
(starts-with? name "html:")
|
||||
(render-dom-element (slice name 5) args env ns)
|
||||
@@ -649,6 +653,50 @@
|
||||
container))))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; render-dom-lake — server-morphable slot within an island
|
||||
;; --------------------------------------------------------------------------
|
||||
;;
|
||||
;; (lake :id "name" children...)
|
||||
;;
|
||||
;; Renders as <div data-sx-lake="name">children</div>.
|
||||
;; During morph, the server can replace lake content while the surrounding
|
||||
;; reactive island DOM is preserved. This is the "water around the rocks" —
|
||||
;; server substance flowing through client territory.
|
||||
;;
|
||||
;; Supports :tag keyword to change wrapper element (default "div").
|
||||
|
||||
(define render-dom-lake
|
||||
(fn (args env ns)
|
||||
(let ((lake-id nil)
|
||||
(lake-tag "div")
|
||||
(children (list)))
|
||||
(reduce
|
||||
(fn (state arg)
|
||||
(let ((skip (get state "skip")))
|
||||
(if skip
|
||||
(assoc state "skip" false "i" (inc (get state "i")))
|
||||
(if (and (= (type-of arg) "keyword")
|
||||
(< (inc (get state "i")) (len args)))
|
||||
(let ((kname (keyword-name arg))
|
||||
(kval (trampoline (eval-expr (nth args (inc (get state "i"))) env))))
|
||||
(cond
|
||||
(= kname "id") (set! lake-id kval)
|
||||
(= kname "tag") (set! lake-tag kval))
|
||||
(assoc state "skip" true "i" (inc (get state "i"))))
|
||||
(do
|
||||
(append! children arg)
|
||||
(assoc state "i" (inc (get state "i"))))))))
|
||||
(dict "i" 0 "skip" false)
|
||||
args)
|
||||
(let ((el (dom-create-element lake-tag nil)))
|
||||
(dom-set-attr el "data-sx-lake" (or lake-id ""))
|
||||
(for-each
|
||||
(fn (c) (dom-append el (render-to-dom c env ns)))
|
||||
children)
|
||||
el))))
|
||||
|
||||
|
||||
;; --------------------------------------------------------------------------
|
||||
;; Reactive DOM rendering helpers
|
||||
;; --------------------------------------------------------------------------
|
||||
@@ -668,8 +716,14 @@
|
||||
|
||||
;; reactive-attr — bind an element attribute to a signal expression
|
||||
;; Used when an attribute value contains (deref sig) inside an island.
|
||||
;; Marks the attribute name on the element via data-sx-reactive-attrs so
|
||||
;; the morph algorithm knows not to overwrite it with server content.
|
||||
(define reactive-attr
|
||||
(fn (el attr-name compute-fn)
|
||||
;; Mark this attribute as reactively managed
|
||||
(let ((existing (or (dom-get-attr el "data-sx-reactive-attrs") ""))
|
||||
(updated (if (empty? existing) attr-name (str existing "," attr-name))))
|
||||
(dom-set-attr el "data-sx-reactive-attrs" updated))
|
||||
(effect (fn ()
|
||||
(let ((val (compute-fn)))
|
||||
(cond
|
||||
|
||||
Reference in New Issue
Block a user