- Rename /reactive-islands/ → /reactive/, /reference/ → /hypermedia/reference/,
/examples/ → /hypermedia/examples/ across all .sx and .py files
- Add 404 error page (not-found.sx) working on both server refresh and
client-side SX navigation via orchestration.sx error response handling
- Add trailing slash redirect (GET only, excludes /api/, /static/, /internal/)
- Remove blue sky-500 header bar from SX docs layout (conditional on header-rows)
- Fix 405 on API endpoints from trailing slash redirect hitting POST/PUT/DELETE
- Fix client-side 404: orchestration.sx now swaps error response content
instead of silently dropping it
- Add new plan files and home page component
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
(li (strong "Swap inside island: ") "Signals survive. The lake content is replaced but the island's signal closures are untouched. Effects re-bind to new DOM nodes if needed.")
(li (strong "Swap outside island: ") "Signals survive. The island is not affected by swaps to other parts of the page.")
(li (strong "Swap replaces island: ") "Signals are " (em "lost") ". The island is disposed. This is where " (a :href "/reactive-islands/named-stores" :sx-get "/reactive-islands/named-stores" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "named stores") " come in — they persist at page level, surviving island destruction.")))
(li (strong "Swap replaces island: ") "Signals are " (em "lost") ". The island is disposed. This is where " (a :href "/reactive/named-stores" :sx-get "/reactive/named-stores" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "named stores") " come in — they persist at page level, surviving island destruction.")))
(~doc-section :title "Spec" :id "spec"
(p "The event bridge is spec'd in " (code "signals.sx") " (sections 12-13). Three functions:")
(p "Islands separated client state from server content. Lakes let server content flow through islands. Marshes dissolve the boundary entirely — the same zone is simultaneously server-authored and reactively interpreted.")
(p "This is the next turn of the Hegelian spiral. The thesis (pure hypermedia) posited the server as sole authority. The antithesis (reactive islands) gave the client its own inner life. The first synthesis (islands + lakes) maintained the boundary between them. The second synthesis (marshes) " (em "sublates the boundary itself") ".")
(p "In a marsh, you can't point to a piece of DOM and say \"this is server territory\" or \"this is client territory.\" It's both. The server sent it. The client transformed it. The server can update it. The client will re-transform it. The signal reads the server data. The server data feeds the signal. Subject and substance are one.")
(p "The practical consequence: an SX application can handle " (em "any") " interaction pattern without breaking its architecture. Pure content → hypermedia. Micro-interactions → L1 DOM ops. Reactive UI → islands. Server slots → lakes. And now, for the places where reactivity and hypermedia must truly merge — marshes."))))
(p "The practical consequence: an SX application can handle " (em "any") " interaction pattern without breaking its architecture. Pure content → hypermedia. Micro-interactions → L1 DOM ops. Reactive UI → islands. Server slots → lakes. And now, for the places where reactivity and hypermedia must truly merge — marshes."))
(p "Click \"Fetch Price\" to hit a real server endpoint. The response is " (em "hypermedia") " — SX content swapped into the page. But a " (code "data-init") " script in the response also writes to the " (code "\"demo-price\"") " store signal. The island's reactive UI — total, savings, price display — updates instantly from the signal change.")
(p "This is the marsh pattern: " (strong "the server response is both content and a signal write") ". Hypermedia and reactivity aren't separate — the same response does both.")
(~demo-marsh-product)
(~doc-code :code (highlight ";; Island with a store-backed price signal\n(defisland ~demo-marsh-product ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99))))\n (qty (signal 1))\n (total (computed (fn () (* (deref price) (deref qty))))))\n (div\n ;; Reactive price display — updates when store changes\n (span \"$\" (deref price))\n (span \"Qty:\") (button \"-\") (span (deref qty)) (button \"+\")\n (span \"Total: $\" (deref total))\n\n ;; Fetch from server — response arrives as hypermedia\n (button :sx-get \"/reactive/api/flash-sale\"\n :sx-target \"#marsh-server-msg\"\n :sx-swap \"innerHTML\"\n \"Fetch Price\")\n ;; Server response lands here:\n (div :id \"marsh-server-msg\"))))" "lisp"))
(~doc-code :code (highlight ";; Server returns SX content + a data-init script:\n;;\n;; (<>\n;; (p \"Flash sale! Price: $14.99\")\n;; (script :type \"text/sx\" :data-init\n;; \"(reset! (use-store \\\"demo-price\\\") 14.99)\"))\n;;\n;; The <p> is swapped in as normal hypermedia content.\n;; The script writes to the store signal.\n;; The island's (deref price), total, and savings\n;; all update reactively — no re-render, no diffing." "lisp"))
(p "Two things happen from one server response: content appears in the swap target (hypermedia) and the price signal updates (reactivity). The island didn't fetch the price. The server didn't call a signal API. The response " (em "is") " both."))
(~doc-subsection :title "Demo 2: Server writes to signals"
(p "Two separate islands share a named store " (code "\"demo-price\"") ". Island A creates the store and has control buttons. Island B reads it. Signal changes propagate instantly across island boundaries.")
(div :class "space-y-3"
(~demo-marsh-store-writer)
(~demo-marsh-store-reader))
(p :class "mt-3 text-sm text-stone-500" "The \"Flash Sale\" buttons call " (code "(reset! price 14.99)") " — exactly what " (code "data-sx-signal=\"demo-price:14.99\"") " does during morph.")
(~doc-code :code (highlight ";; Island A — creates the store, has control buttons\n(defisland ~demo-marsh-store-writer ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99)))))\n ;; (reset! price 14.99) is what data-sx-signal does during morph\n (button :on-click (fn (e) (reset! price 14.99))\n \"Flash Sale $14.99\")))\n\n;; Island B — reads the same store, different island\n(defisland ~demo-marsh-store-reader ()\n (let ((price (def-store \"demo-price\" (fn () (signal 19.99)))))\n (span \"$\" (deref price))))\n\n;; Server returns: data-sx-signal writes to the store during morph\n;; (div :data-sx-signal \"demo-price:14.99\"\n;; (p \"Flash sale! Price updated.\"))" "lisp"))
(p "In production, the server response includes " (code "data-sx-signal=\"demo-price:14.99\"") ". The morph algorithm processes this attribute, calls " (code "(reset! (use-store \"demo-price\") 14.99)") ", and removes the attribute from the DOM. Every island reading that store updates instantly — fine-grained, no re-render."))
(p "After a swap settles, the trigger element's " (code "sx-on-settle") " attribute is parsed and evaluated as SX. This runs " (em "after") " the content is in the DOM — so you can update reactive state based on what the server returned.")
(p "Click \"Fetch Item\" to load server content. The response is pure hypermedia. But " (code "sx-on-settle") " on the button increments a fetch counter signal " (em "after") " the swap. The counter updates reactively.")
(p "The server knows nothing about signals or counters. It returns plain content. The " (code "sx-on-settle") " hook is a client-side concern — it runs in the global SX environment with access to all primitives."))
(p "Inside an island, " (em "all") " attributes are reactive — including " (code "sx-get") ". When an attribute value contains " (code "deref") ", the DOM adapter wraps it in an effect that re-sets the attribute when signals change.")
(p "Select a search category. The " (code "sx-get") " URL on the search button changes reactively. Click \"Search\" to fetch from the current endpoint. The URL was computed from the " (code "mode") " signal at render time and updates whenever the mode changes.")
(p "No custom plumbing. The same " (code "reactive-attr") " mechanism that makes " (code ":class") " reactive also makes " (code ":sx-get") " reactive. " (code "get-verb-info") " reads " (code "dom-get-attr") " at trigger time — it sees the current URL because the effect already updated the DOM attribute."))
(p "A view-mode signal controls how items are displayed. Click \"Fetch Catalog\" to load items from the server, then toggle the view mode. The " (em "same") " data re-renders differently based on client state — no server round-trip for view changes.")
(~demo-marsh-view-transform)
(~doc-code :code (highlight ";; View mode transforms display without refetch\n(defisland ~demo-marsh-view-transform ()\n (let ((view (signal \"list\"))\n (items (signal nil)))\n (div\n ;; View toggle\n (div :class \"flex gap-2\"\n (button :on-click (fn (e) (reset! view \"list\")) \"List\")\n (button :on-click (fn (e) (reset! view \"grid\")) \"Grid\")\n (button :on-click (fn (e) (reset! view \"compact\")) \"Compact\"))\n\n ;; Fetch from server — stores raw data in signal\n (button :sx-get \"/reactive/api/catalog\"\n :sx-target \"#catalog-raw\"\n :sx-swap \"innerHTML\"\n \"Fetch Catalog\")\n\n ;; Raw server content (hidden, used as data source)\n (div :id \"catalog-raw\" :class \"hidden\")\n\n ;; Reactive display — re-renders when view changes\n (div (computed (fn () (render-view (deref view) (deref items))))))))" "lisp"))
(p "The view signal doesn't just toggle CSS classes — it fundamentally reshapes the DOM. List view shows description. Grid view arranges in columns. Compact view shows names only. All from the same server data, transformed by client state."))
(p "An htmx lake is server-driven content " (em "inside") " a reactive island. The island provides the reactive boundary; the lake content is swapped via " (code "sx-get") "/" (code "sx-post") " like normal hypermedia.")
(p "This works because signals live in JavaScript closures, not in the DOM. When a swap replaces lake content, the island's signals are unaffected. The lake can communicate back to the island via the " (a :href "/reactive-islands/event-bridge" :sx-get "/reactive-islands/event-bridge" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "event bridge") ".")
(p "This works because signals live in JavaScript closures, not in the DOM. When a swap replaces lake content, the island's signals are unaffected. The lake can communicate back to the island via the " (a :href "/reactive/event-bridge" :sx-get "/reactive/event-bridge" :sx-target "#main-panel" :sx-select "#main-panel" :sx-swap "outerHTML" :sx-push-url "true" :class "text-violet-700 underline" "event bridge") ".")
(~doc-subsection :title "Navigation scenarios"
(div :class "space-y-3"
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.