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:
@@ -5,6 +5,11 @@
|
||||
:returns "sx-source"
|
||||
:service "sx")
|
||||
|
||||
(define-page-helper "component-source"
|
||||
:params (name)
|
||||
:returns "string"
|
||||
:service "sx")
|
||||
|
||||
(define-page-helper "primitives-data"
|
||||
:params ()
|
||||
:returns "dict"
|
||||
|
||||
@@ -2,18 +2,7 @@
|
||||
|
||||
(defcomp ~sx-home-content ()
|
||||
(div :id "main-content" :class "max-w-3xl mx-auto px-4 py-6"
|
||||
(~doc-code :code (highlight "(defcomp ~sx-header ()
|
||||
(a :href \"/\"
|
||||
:sx-get \"/\" :sx-target \"#main-panel\"
|
||||
:sx-select \"#main-panel\"
|
||||
:sx-swap \"outerHTML\" :sx-push-url \"true\"
|
||||
:class \"block max-w-3xl mx-auto px-4 pt-8 pb-4 text-center no-underline\"
|
||||
(span :class \"text-4xl font-bold font-mono text-violet-700 block mb-2\"
|
||||
\"(<sx>)\")
|
||||
(p :class \"text-lg text-stone-500 mb-1\"
|
||||
\"Framework free reactive hypermedia\")
|
||||
(p :class \"text-xs text-stone-400\"
|
||||
\"© Giles Bradshaw 2026\")))" "lisp"))))
|
||||
(~doc-code :code (highlight (component-source "~sx-header") "lisp"))))
|
||||
|
||||
(defcomp ~docs-introduction-content ()
|
||||
(~doc-page :title "Introduction"
|
||||
|
||||
@@ -50,9 +50,11 @@
|
||||
"But the crucial move — the one that makes this a genuine Hegelian synthesis rather than a mere juxtaposition — is " (strong "the morph") ". When the server sends new content and the client merges it into the existing DOM, hydrated islands are " (em "preserved") ". The server updates the lake. The islands keep their state. The server's new representation flows around the islands like water around rocks. The client's interiority survives the server's authority.")
|
||||
(p :class "text-stone-600"
|
||||
"This is " (em "Aufhebung") " in its precise meaning: cancellation, preservation, and elevation. The thesis (server authority) is " (em "cancelled") " — the server no longer has total control over the page. It is " (em "preserved") " — the server still renders the document, still determines structure, still delivers representations. It is " (em "elevated") " — the server now renders " (em "around") " reactive islands, acknowledging their autonomy. Simultaneously, the antithesis (client autonomy) is cancelled (the client no longer controls the whole page), preserved (islands keep their state), and elevated (client state now coexists with server-driven updates).")
|
||||
(~doc-code :code (highlight ";; The server sends this. The island already exists\n;; in the DOM with reactive state. The morph preserves it.\n\n(defisland ~sx-header ()\n (let ((families (list \"violet\" \"rose\" \"blue\" \"emerald\"))\n (idx (signal 0))\n (current (computed (fn ()\n (nth families (mod (deref idx) (len families)))))))\n (a :href \"/\" :sx-get \"/\" :sx-target \"#main-panel\"\n (span :style (cssx (:text (colour (deref current) 500)))\n :on-click (fn (e) (swap! idx inc))\n \"reactive\"))))\n\n;; Click: colour changes (client state)\n;; Click also triggers sx-get (server fetch)\n;; Server response morphs the page\n;; Island keeps its colour — state survives the swap" "lisp"))
|
||||
(~doc-code :code (highlight ";; The island: reactive state coexists with server lakes.\n;; Lakes are server-morphable slots — the water within the island.\n\n(defisland ~sx-header ()\n (let ((families (list \"violet\" \"rose\" \"blue\" \"emerald\"))\n (idx (signal 0))\n (current (computed (fn ()\n (nth families (mod (deref idx) (len families)))))))\n (a :href \"/\" :sx-get \"/\" :sx-target \"#main-panel\"\n ;; Lake: server can update the logo\n (lake :id \"logo\"\n (span :style (cssx ...) \"(<sx>)\"))\n ;; Reactive: signal-bound, NOT in a lake\n (span :style (cssx (:text (colour (deref current) 500)))\n :on-click (fn (e) (swap! idx inc))\n \"reactive\")\n ;; Lake: server can update the copyright\n (lake :id \"copyright\"\n (p \"© 2026\")))))\n\n;; Click: colour changes (client state)\n;; Server sends new page — morph enters the island\n;; Lakes update from server content\n;; Reactive span keeps its colour — state survives" "lisp"))
|
||||
(p :class "text-stone-600"
|
||||
"In this example, the word " (em "reactive") " cycles through colours on click. The same click triggers a server navigation. The server responds with a fresh page. The morph algorithm encounters the island, recognises it as already hydrated, and " (em "skips it") ". The signal — the " (code "idx") " that tracks which colour family we're on — survives. The client's inner life persists through the server's outer renewal."))
|
||||
"The " (code "lake") " tag is the key. Inside the island, " (code "(lake :id \"logo\" ...)") " marks a region as server territory — the server can update its content during a morph. The reactive " (code "span") " with its signal-bound style is " (em "not") " in a lake — it is island territory, untouchable by the morph. The morph enters the island, finds the lakes, updates them from the server's new representation, and " (em "flows around") " the reactive nodes like water around rocks.")
|
||||
(p :class "text-stone-600"
|
||||
"Click the word " (em "reactive") " in the header. The colour changes. Navigate to another page. The morph enters the island, updates the lakes (logo, copyright), but the reactive span — with its colour signal — " (em "persists") ". The client's inner life survives the server's outer renewal."))
|
||||
(~doc-section :title "V. Spirit: The self-knowing page" :id "spirit"
|
||||
(p :class "text-stone-600"
|
||||
"Hegel's system does not end with synthesis. Synthesis becomes a new thesis, which generates its own antithesis, and the dialectic continues. The island architecture is not a final resting place. It is a " (em "moment") " in the self-development of the web.")
|
||||
|
||||
@@ -10,6 +10,11 @@
|
||||
;; Logo + tagline + copyright — always shown at top of page area.
|
||||
;; The header itself is an island so the "reactive" word can cycle colours
|
||||
;; on click — demonstrates inline signals without a separate component.
|
||||
;;
|
||||
;; Lakes (server-morphable slots) wrap the static content: logo and copyright.
|
||||
;; The server can update these during navigation morphs without disturbing
|
||||
;; the reactive colour-cycling state. This is Level 2-3: the water (server
|
||||
;; content) flows through the island, around the rocks (reactive signals).
|
||||
(defisland ~sx-header ()
|
||||
(let ((families (list "violet" "rose" "blue" "emerald" "amber" "cyan" "red" "teal" "pink" "indigo"))
|
||||
(idx (signal 0))
|
||||
@@ -22,9 +27,12 @@
|
||||
:style (str (display "block") (max-w (get cssx-max-widths "3xl"))
|
||||
(mx-auto) (px 4) (pt 8) (pb 4) (align "center")
|
||||
(decoration "none"))
|
||||
(span :style (str (display "block") (mb 2)
|
||||
(cssx (:text (colour "violet" 699) (size "4xl") (weight "bold") (family "mono"))))
|
||||
"(<sx>)")
|
||||
;; Lake: server can update the logo text
|
||||
(lake :id "logo"
|
||||
(span :style (str (display "block") (mb 2)
|
||||
(cssx (:text (colour "violet" 699) (size "4xl") (weight "bold") (family "mono"))))
|
||||
"(<sx>)"))
|
||||
;; Reactive: colour-cycling "reactive" word (signal-bound, NOT in a lake)
|
||||
(p :style (str (mb 1) (cssx (:text (colour "stone" 500) (size "lg"))))
|
||||
"Framework free "
|
||||
(span
|
||||
@@ -37,8 +45,10 @@
|
||||
(reset! shade (+ 400 (* (mod (* (deref idx) 137) 5) 50))))))
|
||||
"reactive")
|
||||
" hypermedia")
|
||||
(p :style (cssx (:text (colour "stone" 400) (size "xs")))
|
||||
"© Giles Bradshaw 2026"))))
|
||||
;; Lake: server can update the copyright
|
||||
(lake :id "copyright"
|
||||
(p :style (cssx (:text (colour "stone" 400) (size "xs")))
|
||||
"© Giles Bradshaw 2026")))))
|
||||
|
||||
;; @css grid grid-cols-3
|
||||
|
||||
|
||||
Reference in New Issue
Block a user