Build tooling: updated OCaml bootstrapper, compile-modules, bundle.sh, sx-build-all. WASM browser: rebuilt sx_browser.bc.js/wasm, sx-platform-2.js, .sxbc bytecode files. CSSX/Tailwind: reworked cssx.sx templates and tw-layout, added tw-type support. Content: refreshed essays, plans, geography, reactive islands, docs, demos, handlers. New tools: bisect_sxbc.sh, test-spa.js, render-trace.sx, morph playwright spec. Tests: added test-match.sx, test-examples.sx, updated test-tw.sx and web tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
59 lines
5.9 KiB
Plaintext
59 lines
5.9 KiB
Plaintext
;; ---------------------------------------------------------------------------
|
|
;; Named Stores — page-level signal containers
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
(defcomp ~reactive-islands/named-stores/reactive-islands-named-stores-content ()
|
|
(~docs/page :title "Named Stores"
|
|
|
|
(~docs/section :title "The Problem" :id "problem"
|
|
(p "Islands are isolated by default. Signal props work when islands are adjacent, but not when they are:")
|
|
(ul (~tw :tokens "space-y-1 text-stone-600 list-disc pl-5")
|
|
(li "Distant in the DOM tree (header badge + drawer island)")
|
|
(li "Defined in different " (code ".sx") " files")
|
|
(li "Destroyed and recreated by htmx swaps"))
|
|
(p "Named stores solve all three. A store is a named collection of signals that lives at " (em "page") " scope, not island scope."))
|
|
|
|
(~docs/section :title "def-store / use-store" :id "api"
|
|
(~docs/code :src (highlight ";; Create a named store — called once at page level\n;; The init function creates signals and computeds\n(def-store \"cart\" (fn ()\n (let ((items (signal (list))))\n (dict\n :items items\n :count (computed (fn () (length (deref items))))\n :total (computed (fn () (reduce + 0\n (map (fn (i) (get i \"price\")) (deref items)))))))))\n\n;; Use the store from any island — returns the signal dict\n(defisland ~reactive-islands/named-stores/cart-badge ()\n (let ((store (use-store \"cart\")))\n (span :class \"badge bg-violet-100 text-violet-800 px-2 py-1 rounded-full\"\n (deref (get store \"count\")))))\n\n(defisland ~reactive-islands/named-stores/cart-drawer ()\n (let ((store (use-store \"cart\")))\n (div :class \"p-4\"\n (h2 \"Cart\")\n (ul (map (fn (item)\n (li :class \"flex justify-between py-1\"\n (span (get item \"name\"))\n (span :class \"text-stone-500\" \"\£\" (get item \"price\"))))\n (deref (get store \"items\"))))\n (div :class \"border-t pt-2 font-semibold\"\n \"Total: \£\" (deref (get store \"total\"))))))" "lisp"))
|
|
|
|
(p (code "def-store") " is " (strong "idempotent") " — calling it again with the same name returns the existing store. This means multiple components can call " (code "def-store") " defensively without double-creating."))
|
|
|
|
(~docs/section :title "Lifecycle" :id "lifecycle"
|
|
(ol (~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
|
|
(li (strong "Page load: ") (code "def-store") " creates the store in a global registry. Signals are initialized.")
|
|
(li (strong "Island hydration: ") "Each island calls " (code "use-store") " to get the shared signal dict. Derefs create subscriptions.")
|
|
(li (strong "Island swap: ") "An island is destroyed by htmx swap. Its effects are cleaned up. But the store " (em "persists") " — it's in the page-level registry, not the island scope.")
|
|
(li (strong "Island recreation: ") "The new island calls " (code "use-store") " again. Gets the same signals. Reconnects reactively. User state is preserved.")
|
|
(li (strong "Full page navigation: ") (code "clear-stores") " wipes the registry. Clean slate.")))
|
|
|
|
(~docs/section :title "Combining with event bridge" :id "combined"
|
|
(p "Named stores + event bridge = full lake→island→island communication:")
|
|
|
|
(~docs/code :src (highlight ";; Store persists across island lifecycle\n(def-store \"cart\" (fn () ...))\n\n;; Island 1: product page with htmx lake\n(defisland ~reactive-islands/named-stores/product-island ()\n (let ((store (use-store \"cart\")))\n ;; Bridge server-rendered \"Add\" buttons to store\n (bridge-event container \"cart:add\" (get store \"items\")\n (fn (detail) (append (deref (get store \"items\")) detail)))\n ;; Lake content swapped via sx-get\n (div :id \"product-content\" :sx-get \"/products/featured\")))\n\n;; Island 2: cart badge in header (distant in DOM)\n(defisland ~reactive-islands/named-stores/cart-badge ()\n (let ((store (use-store \"cart\")))\n (span (deref (get store \"count\")))))" "lisp"))
|
|
|
|
(p "User clicks \"Add to Cart\" in server-rendered product content. " (code "cart:add") " event fires. Product island catches it via bridge. Store's " (code "items") " signal updates. Cart badge — in a completely different island — updates reactively because it reads the same signal."))
|
|
|
|
(~docs/section :title "Spec" :id "spec"
|
|
(p "Named stores are spec'd in " (code "signals.sx") " (section 12). Three functions:")
|
|
|
|
(div (~tw :tokens "overflow-x-auto rounded border border-stone-200")
|
|
(table (~tw :tokens "w-full text-left text-sm")
|
|
(thead (tr (~tw :tokens "border-b border-stone-200 bg-stone-100")
|
|
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Function")
|
|
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Description")))
|
|
(tbody
|
|
(tr (~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "(def-store name init-fn)")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Create or return existing named store. init-fn returns a dict of signals/computeds."))
|
|
(tr (~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "(use-store name)")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Get existing store by name. Errors if store doesn't exist."))
|
|
(tr
|
|
(td (~tw :tokens "px-3 py-2 font-mono text-sm text-violet-700") "(clear-stores)")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Wipe all stores. Called on full page navigation."))))))))
|
|
|
|
|
|
;; ---------------------------------------------------------------------------
|
|
;; Plan — the full design document (moved from plans section)
|
|
;; ---------------------------------------------------------------------------
|