Files
rose-ash/sx/sx/reactive-islands/named-stores.sx
giles d40a9c6796 sx-tools: WASM kernel updates, TW/CSSX rework, content refresh, new debugging tools
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>
2026-04-02 11:31:57 +00:00

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)
;; ---------------------------------------------------------------------------