Parser: skip unit suffix when next ident is a comparison keyword (starts, ends, contains, matches, is, does, in, precedes, follows). Fixes "123 starts with '12'" returning "123starts" instead of true. eval-hs: use hs-compile directly instead of hs-to-sx-from-source with "return " prefix, which was causing the parser to consume the comparison as a string suffix. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
589 lines
25 KiB
Plaintext
589 lines
25 KiB
Plaintext
(defcomp
|
|
()
|
|
(~docs/page
|
|
:title "Reactive Islands"
|
|
(~docs/section
|
|
:title "Architecture"
|
|
:id "architecture"
|
|
(p "Two orthogonal bars control how an SX page works:")
|
|
(ul
|
|
(~tw :tokens "space-y-1 text-stone-600 list-disc pl-5")
|
|
(li
|
|
(strong "Render boundary")
|
|
" — where rendering happens (server HTML vs client DOM)")
|
|
(li
|
|
(strong "State flow")
|
|
" — how state flows (server state vs client signals)"))
|
|
(div
|
|
(~tw :tokens "overflow-x-auto mt-4 mb-4")
|
|
(table
|
|
(~tw :tokens "w-full text-sm text-left")
|
|
(thead
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-200")
|
|
(th (~tw :tokens "py-2 px-3 font-semibold text-stone-700") "")
|
|
(th
|
|
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
|
"Server State")
|
|
(th
|
|
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
|
"Client State")))
|
|
(tbody
|
|
(~tw :tokens "text-stone-600")
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td
|
|
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
|
"Server Rendering")
|
|
(td (~tw :tokens "py-2 px-3") "Pure hypermedia (htmx)")
|
|
(td (~tw :tokens "py-2 px-3") "SSR + hydrated islands"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td
|
|
(~tw :tokens "py-2 px-3 font-semibold text-stone-700")
|
|
"Client Rendering")
|
|
(td (~tw :tokens "py-2 px-3") "SX wire format (current)")
|
|
(td
|
|
(~tw :tokens "py-2 px-3 font-semibold text-violet-700")
|
|
"Reactive islands (this)")))))
|
|
(p
|
|
"Most content stays pure hypermedia. Interactive regions opt into reactivity. The author controls where each component sits on both bars."))
|
|
(~docs/section
|
|
:title "Four Levels"
|
|
:id "levels"
|
|
(div
|
|
(~tw :tokens "space-y-4")
|
|
(div
|
|
(~tw :tokens "rounded border border-stone-200 p-4")
|
|
(div
|
|
(~tw :tokens "font-semibold text-stone-800")
|
|
"Level 0: Pure Hypermedia")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600 mt-1")
|
|
"The default. "
|
|
(code "sx-get")
|
|
", "
|
|
(code "sx-post")
|
|
", "
|
|
(code "sx-swap")
|
|
". Server renders everything. No client state. 90% of a typical application."))
|
|
(div
|
|
(~tw :tokens "rounded border border-stone-200 p-4")
|
|
(div
|
|
(~tw :tokens "font-semibold text-stone-800")
|
|
"Level 1: Local DOM Operations")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600 mt-1")
|
|
"Imperative escapes: "
|
|
(code "toggle!")
|
|
", "
|
|
(code "set-attr!")
|
|
", "
|
|
(code "on-event")
|
|
". Micro-interactions too small for a server round-trip."))
|
|
(div
|
|
(~tw :tokens "rounded border border-violet-300 bg-violet-50 p-4")
|
|
(div
|
|
(~tw :tokens "font-semibold text-violet-900")
|
|
"Level 2: Reactive Islands")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600 mt-1")
|
|
(code "defisland")
|
|
" components with local signals. Fine-grained DOM updates "
|
|
(em "without")
|
|
" virtual DOM, diffing, or component re-renders. A signal change updates only the DOM nodes that read it."))
|
|
(div
|
|
(~tw :tokens "rounded border border-stone-200 p-4")
|
|
(div
|
|
(~tw :tokens "font-semibold text-stone-800")
|
|
"Level 3: Connected Islands")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600 mt-1")
|
|
"Islands that share state via signal props or named stores ("
|
|
(code "def-store")
|
|
" / "
|
|
(code "use-store")
|
|
")."))))
|
|
(~docs/section
|
|
:title "Signal Primitives"
|
|
:id "signals"
|
|
(~docs/code
|
|
:src (highlight
|
|
"(signal v) ;; create a reactive container\n(deref s) ;; read value — subscribes in reactive context\n(reset! s v) ;; write new value — notifies subscribers\n(swap! s f) ;; update via function: (f old-value)\n(computed fn) ;; derived signal — auto-tracks dependencies\n(effect fn) ;; side effect — re-runs when deps change\n(batch fn) ;; group writes — one notification pass"
|
|
"lisp"))
|
|
(p
|
|
"Signals are values, not hooks. Create them anywhere — conditionals, loops, closures. No rules of hooks. Pass them as arguments, store them in dicts, share between islands."))
|
|
(~docs/section
|
|
:title "Island Lifecycle"
|
|
:id "lifecycle"
|
|
(ol
|
|
(~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
|
|
(li
|
|
(strong "Definition: ")
|
|
(code "defisland")
|
|
" registers a reactive component (like "
|
|
(code "defcomp")
|
|
" + island flag)")
|
|
(li
|
|
(strong "Server render: ")
|
|
"Body evaluated with initial values. "
|
|
(code "deref")
|
|
" returns plain value. Output wrapped in "
|
|
(code "data-sx-island")
|
|
" / "
|
|
(code "data-sx-state"))
|
|
(li
|
|
(strong "Client hydration: ")
|
|
"Finds "
|
|
(code "data-sx-island")
|
|
" elements, creates signals from serialized state, re-renders in reactive context")
|
|
(li
|
|
(strong "Updates: ")
|
|
"Signal changes update only subscribed DOM nodes. No full island re-render")
|
|
(li
|
|
(strong "Disposal: ")
|
|
"Island removed from DOM — all signals and effects cleaned up via "
|
|
(code "with-island-scope"))))
|
|
(~docs/section
|
|
:title "htmx Lakes"
|
|
:id "lakes"
|
|
(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. This works because signals live in closures, not the DOM.")
|
|
(div
|
|
(~tw :tokens "space-y-2 mt-3")
|
|
(div
|
|
(~tw :tokens "rounded border border-green-200 bg-green-50 p-3")
|
|
(div
|
|
(~tw :tokens "font-semibold text-green-800 text-sm")
|
|
"Swap inside island")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600 mt-1")
|
|
"Lake content replaced. Signals survive. Effects rebind to new DOM."))
|
|
(div
|
|
(~tw :tokens "rounded border border-green-200 bg-green-50 p-3")
|
|
(div
|
|
(~tw :tokens "font-semibold text-green-800 text-sm")
|
|
"Swap outside island")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600 mt-1")
|
|
"Different part of page updated. Island completely unaffected."))
|
|
(div
|
|
(~tw :tokens "rounded border border-amber-200 bg-amber-50 p-3")
|
|
(div
|
|
(~tw :tokens "font-semibold text-amber-800 text-sm")
|
|
"Swap replaces island")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600 mt-1")
|
|
"Island disposed. Local signals lost. Named stores persist — new island reconnects via "
|
|
(code "use-store")
|
|
"."))
|
|
(div
|
|
(~tw :tokens "rounded border border-stone-200 p-3")
|
|
(div
|
|
(~tw :tokens "font-semibold text-stone-800 text-sm")
|
|
"Full page navigation")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600 mt-1")
|
|
"Everything cleared. "
|
|
(code "clear-stores")
|
|
" wipes the registry."))))
|
|
(~docs/section
|
|
:title "Event Bridge"
|
|
:id "event-bridge"
|
|
(p
|
|
"A lake has no access to island signals, but can communicate back via DOM custom events. Elements with "
|
|
(code "data-sx-emit")
|
|
" dispatch a "
|
|
(code "CustomEvent")
|
|
" on click; an island effect catches it and updates a signal.")
|
|
(~docs/code
|
|
:src (highlight
|
|
";; Island listens for events from server-rendered lake content\n(bridge-event container \"cart:add\" items\n (fn (detail) (append (deref items) detail)))\n\n;; Server-rendered button dispatches CustomEvent on click\n(button :data-sx-emit \"cart:add\"\n :data-sx-emit-detail (json-serialize (dict :id 42))\n \"Add to Cart\")"
|
|
"lisp"))
|
|
(p
|
|
"Three primitives: "
|
|
(code "emit-event")
|
|
" (dispatch), "
|
|
(code "on-event")
|
|
" (listen), "
|
|
(code "bridge-event")
|
|
" (listen + update signal with automatic cleanup)."))
|
|
(~docs/section
|
|
:title "Named Stores"
|
|
:id "stores"
|
|
(p
|
|
"A named store is a dict of signals at "
|
|
(em "page")
|
|
" scope — not island scope. Multiple islands share the same signals. Stores survive island destruction and recreation.")
|
|
(~docs/code
|
|
:src (highlight
|
|
";; Create once — idempotent, returns existing on second call\n(def-store \"cart\" (fn ()\n (dict :items (signal (list))\n :count (computed (fn () (length (deref items)))))))\n\n;; Use from any island, anywhere in the DOM\n(let ((store (use-store \"cart\")))\n (span (deref (get store \"count\"))))"
|
|
"lisp"))
|
|
(p
|
|
(code "def-store")
|
|
" creates, "
|
|
(code "use-store")
|
|
" retrieves, "
|
|
(code "clear-stores")
|
|
" wipes all on full page navigation."))
|
|
(~docs/section
|
|
:title "Examples"
|
|
:id "examples"
|
|
(p "Each example below shows a live island and its source.")
|
|
(~docs/section
|
|
:title "Counter"
|
|
:id "demo-counter"
|
|
(p "Signals, computed, and " (code "swap!") ".")
|
|
(~reactive-islands/index/demo-counter :initial 0)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-counter")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Temperature Converter"
|
|
:id "demo-temperature"
|
|
(p
|
|
"Two signals, each derived from the other via "
|
|
(code "effect")
|
|
".")
|
|
(~reactive-islands/index/demo-temperature)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-temperature")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Imperative Handlers"
|
|
:id "demo-imperative"
|
|
(p "Multi-statement " (code "(do ...)") " bodies in event handlers.")
|
|
(~reactive-islands/index/demo-imperative)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-imperative")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Stopwatch"
|
|
:id "demo-stopwatch"
|
|
(p
|
|
(code "set-interval")
|
|
" and "
|
|
(code "clear-interval")
|
|
" with signal-driven UI.")
|
|
(~reactive-islands/index/demo-stopwatch)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-stopwatch")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Reactive List"
|
|
:id "demo-reactive-list"
|
|
(p "Dynamic list with keyed reconciliation.")
|
|
(~reactive-islands/index/demo-reactive-list)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-reactive-list")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Input Binding"
|
|
:id "demo-input-binding"
|
|
(p "Two-way binding via " (code ":bind") " attribute.")
|
|
(~reactive-islands/index/demo-input-binding)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-input-binding")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Dynamic Classes"
|
|
:id "demo-dynamic-class"
|
|
(p
|
|
"Reactive class toggling with "
|
|
(code "deref")
|
|
" in attribute expressions.")
|
|
(~reactive-islands/index/demo-dynamic-class)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-dynamic-class")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Portal"
|
|
:id "demo-portal"
|
|
(p "Render content outside the island's DOM subtree.")
|
|
(~reactive-islands/index/demo-portal)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-portal")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Error Boundary"
|
|
:id "demo-error-boundary"
|
|
(p "Catch rendering errors without crashing the page.")
|
|
(~reactive-islands/index/demo-error-boundary)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-error-boundary")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "DOM Refs"
|
|
:id "demo-refs"
|
|
(p "Access raw DOM elements via " (code ":ref") " signal.")
|
|
(~reactive-islands/index/demo-refs)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-refs")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Resource"
|
|
:id "demo-resource"
|
|
(p "Async data fetching with loading state.")
|
|
(~reactive-islands/index/demo-resource)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-resource")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Transition"
|
|
:id "demo-transition"
|
|
(p
|
|
"Debounced search with "
|
|
(code "schedule-idle")
|
|
" and "
|
|
(code "batch")
|
|
".")
|
|
(~reactive-islands/index/demo-transition)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-transition")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Named Stores"
|
|
:id "demo-stores"
|
|
(p
|
|
"Two islands sharing state via "
|
|
(code "def-store")
|
|
" / "
|
|
(code "use-store")
|
|
".")
|
|
(~reactive-islands/index/demo-store-writer)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-store-writer")
|
|
"lisp"))
|
|
(~reactive-islands/index/demo-store-reader)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-store-reader")
|
|
"lisp")))
|
|
(~docs/section
|
|
:title "Event Bridge"
|
|
:id "demo-event-bridge"
|
|
(p
|
|
"Server-rendered content communicating with an island via "
|
|
(code "bridge-event")
|
|
".")
|
|
(~reactive-islands/index/demo-event-bridge)
|
|
(~docs/code
|
|
:src (highlight
|
|
(component-source "~reactive-islands/index/demo-event-bridge")
|
|
"lisp"))))
|
|
(~docs/section
|
|
:title "Design Principles"
|
|
:id "principles"
|
|
(ol
|
|
(~tw :tokens "space-y-2 text-stone-600 list-decimal list-inside")
|
|
(li
|
|
(strong "Islands are opt-in.")
|
|
" "
|
|
(code "defcomp")
|
|
" is the default. "
|
|
(code "defisland")
|
|
" adds reactivity. No overhead for static content.")
|
|
(li
|
|
(strong "Signals are values, not hooks.")
|
|
" Create anywhere — conditionals, loops, closures. No rules of hooks, no dependency arrays.")
|
|
(li
|
|
(strong "Fine-grained, not component-grained.")
|
|
" A signal change updates the specific DOM node that reads it. No virtual DOM, no diffing, no component re-renders.")
|
|
(li
|
|
(strong "The server is still the authority.")
|
|
" Islands handle client interactions. The server handles auth, data, routing.")
|
|
(li
|
|
(strong "Spec-first.")
|
|
" Signal semantics live in "
|
|
(code "signals.sx")
|
|
". Bootstrapped to JS and Python. Same primitives on future hosts.")
|
|
(li
|
|
(strong "No build step.")
|
|
" Reactive bindings created at runtime. No JSX compilation, no bundler plugins.")))
|
|
(~docs/section
|
|
:title "Implementation Status"
|
|
:id "status"
|
|
(p
|
|
(~tw :tokens "text-stone-600 mb-3")
|
|
"All signal logic lives in "
|
|
(code ".sx")
|
|
" spec files and is bootstrapped to JavaScript and Python. No SX-specific logic in host languages.")
|
|
(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") "Layer")
|
|
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Status")
|
|
(th (~tw :tokens "px-3 py-2 font-medium text-stone-600") "Files")))
|
|
(tbody
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Signal runtime spec")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"signals.sx (291 lines)"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "defisland special form")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"eval.sx, special-forms.sx, render.sx"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 text-stone-700")
|
|
"DOM adapter (reactive rendering)")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"adapter-dom.sx (+140 lines)"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "HTML adapter (SSR)")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"adapter-html.sx (+65 lines)"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "JS bootstrapper")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"bootstrap_js.py, sx-ref.js (4769 lines)"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Python bootstrapper")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"bootstrap_py.py, sx_ref.py"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Test suite")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "17/17")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"test-signals.sx"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Named stores (L3)")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"signals.sx: def-store, use-store, clear-stores"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Event bridge")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"signals.sx: emit-event, on-event, bridge-event"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Client hydration")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"boot.sx: sx-hydrate-islands, hydrate-island, dispose-island"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Event bindings")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"adapter-dom.sx: :on-click → domListen"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "data-sx-emit processing")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Spec'd")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"orchestration.sx: process-emit-elements"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Island disposal")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"boot.sx, orchestration.sx: dispose-islands-in pre-swap"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Reactive list")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"adapter-dom.sx: map + deref auto-upgrades"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Input binding")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"adapter-dom.sx: :bind signal, bind-input"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Keyed reconciliation")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"adapter-dom.sx: :key attr, extract-key"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Portals")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"adapter-dom.sx: portal render-dom form"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Error boundaries")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"adapter-dom.sx: error-boundary render-dom form"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Resource (async signal)")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"signals.sx: resource, promise-then"))
|
|
(tr
|
|
(~tw :tokens "border-b border-stone-100")
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Suspense pattern")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"resource + cond/deref (no special form)"))
|
|
(tr
|
|
(td (~tw :tokens "px-3 py-2 text-stone-700") "Transition pattern")
|
|
(td (~tw :tokens "px-3 py-2 text-green-700 font-medium") "Done")
|
|
(td
|
|
(~tw :tokens "px-3 py-2 font-mono text-xs text-stone-500")
|
|
"schedule-idle + batch (no special form)"))))))))
|