diff --git a/sx/sx/nav-data.sx b/sx/sx/nav-data.sx index 5f28913..ab5f8e2 100644 --- a/sx/sx/nav-data.sx +++ b/sx/sx/nav-data.sx @@ -374,8 +374,10 @@ :children (list {:label "Reference" :href "/sx/(geography.(hypermedia.(reference)))" :children reference-nav-items} {:label "Examples" :href "/sx/(geography.(hypermedia.(example)))" :children examples-nav-items})} + {:label "Provide / Emit!" :href "/sx/(geography.(provide))" + :summary "Render-time dynamic scope — the substrate beneath spreads, CSSX, and script collection. Downward context, upward accumulation, one mechanism."} {:label "Spreads" :href "/sx/(geography.(spreads))" - :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, and the path to provide/context/emit!."} + :summary "Child-to-parent communication across render boundaries — spread, collect!, reactive-spread, built on provide/emit!."} {:label "Marshes" :href "/sx/(geography.(marshes))" :summary "Where reactivity and hypermedia interpenetrate — server writes to signals, reactive transforms reshape server content, client state modifies how hypermedia is interpreted."} {:label "Isomorphism" :href "/sx/(geography.(isomorphism))" :children isomorphism-nav-items})} diff --git a/sx/sx/plans/foundations.sx b/sx/sx/plans/foundations.sx index be5a4c6..e987eb2 100644 --- a/sx/sx/plans/foundations.sx +++ b/sx/sx/plans/foundations.sx @@ -20,7 +20,7 @@ (p "Every layer is definable in terms of the one below. " "No layer can be decomposed without the layer beneath it.") - (~docs/code-block :code + (~docs/code :code (str "Layer 0: CEK machine (expression + environment + continuation)\n" "Layer 1: Continuations (shift / reset \u2014 delimited capture)\n" "Layer 2: Algebraic effects (operations + handlers)\n" @@ -69,7 +69,7 @@ (p "SX already implements CEK. It just doesn't name it:") - (~docs/code-block :code + (~docs/code :code (str ";; eval-expr IS the CEK transition function\n" ";; C = expr, E = env, K = implicit (call stack / trampoline)\n" "(define eval-expr\n" @@ -105,7 +105,7 @@ (p "Delimited continuations (Felleisen 1988, Danvy & Filinski 1990) " "expose the K register as a first-class value:") - (~docs/code-block :code + (~docs/code :code (str ";; reset marks a point in the continuation\n" "(reset\n" " (+ 1 (shift k ;; k = \"the rest up to reset\"\n" @@ -148,7 +148,7 @@ "an operation (\"perform this effect\") and a handler (\"here's what that effect means\"). " "The handler receives the operation's argument and a continuation to resume the program.") - (~docs/code-block :code + (~docs/code :code (str ";; Pseudocode \u2014 algebraic effect style\n" "(handle\n" " (fn () (+ 1 (perform :ask \"what number?\")))\n" @@ -317,7 +317,7 @@ (p "The deepest primitive is not a single thing. " "It's a point in a three-dimensional space:") - (~docs/code-block :code + (~docs/code :code (str "depth: CEK \u2192 continuations \u2192 algebraic effects \u2192 scoped effects\n" "topology: sequential \u2192 concurrent \u2192 distributed\n" "linearity: unrestricted \u2192 affine \u2192 linear")) @@ -378,7 +378,7 @@ (p "If C, E, and K are all data structures (not host stack frames), " "the entire computation state is serializable:") - (~docs/code-block :code + (~docs/code :code (str ";; Freeze a computation mid-flight\n" "(let ((state (capture-cek)))\n" " (send-to-worker state) ;; ship to another machine\n" @@ -454,7 +454,7 @@ (p "Add optional effect annotations to function definitions:") - (~docs/code-block :code + (~docs/code :code (str ";; Declare what effects a function uses\n" "(define fetch-user :effects [io auth]\n" " (fn (id) ...))\n" @@ -477,7 +477,7 @@ (p "Refactor eval.sx to expose the CEK registers as data:") - (~docs/code-block :code + (~docs/code :code (str ";; The CEK state is a value\n" "(define-record CEK\n" " :control expr ;; the expression\n" @@ -514,7 +514,7 @@ (p "Extend the CEK machine to support multiple concurrent computations:") - (~docs/code-block :code + (~docs/code :code (str ";; Fork: create two CEK states from one\n" "(define fork :effects [concurrency]\n" " (fn (cek)\n" @@ -539,7 +539,7 @@ (p "Add resource-safety constraints:") - (~docs/code-block :code + (~docs/code :code (str ";; Linear scope: must be entered, must complete\n" "(define-linear open-file :effects [io linear]\n" " (fn (path)\n" diff --git a/sx/sx/provide.sx b/sx/sx/provide.sx new file mode 100644 index 0000000..ba5e48e --- /dev/null +++ b/sx/sx/provide.sx @@ -0,0 +1,247 @@ +;; --------------------------------------------------------------------------- +;; Provide / Context / Emit! — render-time dynamic scope +;; --------------------------------------------------------------------------- + + +;; ---- Demo components ---- + +(defcomp ~geography/demo-provide-basic () + (div :class "space-y-2" + (provide "theme" {:primary "violet" :accent "rose"} + (div :class "rounded-lg p-3 bg-violet-50 border border-violet-200" + (p :class "text-sm text-violet-800 font-semibold" "Inside provider: theme.primary = violet") + (p :class "text-xs text-stone-500" "Child reads context value without prop threading."))) + (div :class "rounded-lg p-3 bg-stone-50 border border-stone-200" + (p :class "text-sm text-stone-600" "Outside provider: no theme context.")))) + +(defcomp ~geography/demo-emit-collect () + (div :class "space-y-2" + (provide "scripts" nil + (div :class "rounded-lg p-3 bg-stone-50 border border-stone-200" + (p :class "text-sm text-stone-700" + (emit! "scripts" "analytics.js") + (emit! "scripts" "charts.js") + "Page content renders here. Scripts emitted silently.")) + (div :class "rounded-lg p-3 bg-violet-50 border border-violet-200" + (p :class "text-sm text-violet-800 font-semibold" "Emitted scripts:") + (ul :class "text-xs text-stone-600 list-disc pl-5" + (map (fn (s) (li (code s))) (emitted "scripts"))))))) + +(defcomp ~geography/demo-spread-mechanism () + (div :class "space-y-2" + (div (make-spread {:class "rounded-lg p-3 bg-rose-50 border border-rose-200"}) + (p :class "text-sm text-rose-800 font-semibold" "Spread child styled this div") + (p :class "text-xs text-stone-500" "The spread emitted into the element-attrs provider.")) + (let ((card (make-spread {:class "rounded-lg p-3 bg-amber-50 border border-amber-200"}))) + (div card + (p :class "text-sm text-amber-800 font-semibold" "Stored spread, same mechanism") + (p :class "text-xs text-stone-500" "Bound to a let variable, applied when rendered as child."))))) + +(defcomp ~geography/demo-nested-provide () + (div :class "space-y-2" + (provide "level" "outer" + (div :class "rounded-lg p-3 bg-stone-50 border border-stone-200" + (p :class "text-sm text-stone-700" + (str "Level: " (context "level"))) + (provide "level" "inner" + (div :class "rounded-lg p-3 bg-violet-50 border border-violet-200 ml-4" + (p :class "text-sm text-violet-700" + (str "Level: " (context "level"))))) + (p :class "text-sm text-stone-500 mt-1" + (str "Back to: " (context "level"))))))) + + +;; ---- Layout helper (reuse from spreads article) ---- + +(defcomp ~geography/provide-demo-example (&key demo code) + (div :class "grid grid-cols-1 lg:grid-cols-2 gap-4 my-6 items-start" + (div :class "border border-dashed border-stone-300 rounded-lg p-4 bg-stone-50 min-h-[80px]" + demo) + (div :class "not-prose bg-stone-100 rounded-lg p-4 overflow-x-auto" + (pre :class "text-sm leading-relaxed whitespace-pre-wrap break-words" (code code))))) + + +;; ---- Page content ---- + +(defcomp ~geography/provide-content () + (~docs/page :title "Provide / Context / Emit!" + + (p :class "text-stone-500 text-sm italic mb-8" + "Render-time dynamic scope. " (code "provide") " creates a named scope with a value " + "and an accumulator. " (code "context") " reads the value downward. " + (code "emit!") " appends to the accumulator upward. " (code "emitted") " retrieves what was emitted. " + "This is the substrate that spreads, CSSX, and script collection are built on.") + + ;; ===================================================================== + ;; I. The four primitives + ;; ===================================================================== + + (~docs/section :title "Four primitives" :id "primitives" + + (~docs/subsection :title "provide (special form)" + (p (code "provide") " creates a named scope with a value and an empty accumulator. " + "The body expressions execute with the scope active. When the body completes, " + "the scope is popped.") + (~docs/code :code (highlight "(provide name value\n body...)\n\n;; Example: theme context\n(provide \"theme\" {:primary \"violet\"}\n (h1 \"Title\") ;; can read (context \"theme\")\n (p \"Body\")) ;; scope active for all children" "lisp")) + (p (code "provide") " is a special form, not a function — the body is evaluated " + "inside the scope, not before it.")) + + (~docs/subsection :title "context" + (p "Reads the value from the nearest enclosing " (code "provide") " with the given name. " + "Errors if no provider and no default given.") + (~docs/code :code (highlight "(provide \"theme\" {:primary \"violet\" :font \"serif\"}\n (get (context \"theme\") :primary)) ;; → \"violet\"\n\n;; With default (no error when missing):\n(context \"theme\" {:primary \"stone\"}) ;; → {:primary \"stone\"}" "lisp"))) + + (~docs/subsection :title "emit!" + (p "Appends a value to the nearest enclosing provider's accumulator. " + "Tolerant: returns nil silently when no provider exists.") + (~docs/code :code (highlight "(provide \"scripts\" nil\n (emit! \"scripts\" \"analytics.js\")\n (emit! \"scripts\" \"charts.js\")\n ;; accumulator now has both scripts\n )\n\n;; Outside any provider — silently does nothing:\n(emit! \"scripts\" \"orphan.js\") ;; → nil, no error" "lisp")) + (p "Tolerance is critical. Spreads emit into " (code "\"element-attrs\"") + " — but a spread might be evaluated in a fragment, a " (code "begin") " block, or a " + (code "map") " call where no element provider exists. " + "Tolerant " (code "emit!") " means these cases silently vanish instead of crashing.")) + + (~docs/subsection :title "emitted" + (p "Returns the list of values emitted into the nearest provider with the given name. " + "Empty list if no provider.") + (~docs/code :code (highlight "(provide \"scripts\" nil\n (emit! \"scripts\" \"a.js\")\n (emit! \"scripts\" \"b.js\")\n (emitted \"scripts\")) ;; → (\"a.js\" \"b.js\")" "lisp")))) + + ;; ===================================================================== + ;; II. Two directions, one mechanism + ;; ===================================================================== + + (~docs/section :title "Two directions, one mechanism" :id "directions" + (p (code "provide") " serves both downward and upward communication through a single scope.") + + (~docs/table + :headers (list "Direction" "Read with" "Write with" "Example") + :rows (list + (list "Downward (scope → child)" "context" "provide value" "Theme, config, locale") + (list "Upward (child → scope)" "emitted" "emit!" "Script collection, spread attrs"))) + + (~geography/provide-demo-example + :demo (~geography/demo-provide-basic) + :code (highlight ";; Downward: theme context\n(provide \"theme\"\n {:primary \"violet\" :accent \"rose\"}\n (h1 :style (str \"color:\"\n (get (context \"theme\") :primary))\n \"Themed heading\")\n (p \"inherits theme context\"))" "lisp")) + + (~geography/provide-demo-example + :demo (~geography/demo-emit-collect) + :code (highlight ";; Upward: script accumulation\n(provide \"scripts\" nil\n (div\n (emit! \"scripts\" \"analytics.js\")\n (div\n (emit! \"scripts\" \"charts.js\")\n \"chart\"))\n ;; Collect at the boundary:\n (for-each (fn (s)\n (script :src s))\n (emitted \"scripts\")))" "lisp"))) + + ;; ===================================================================== + ;; III. How spreads use it + ;; ===================================================================== + + (~docs/section :title "How spreads use provide/emit!" :id "spreads" + (p "Every element rendering function wraps its children in a provider scope " + "named " (code "\"element-attrs\"") ". When the adapter encounters a spread child, " + "it emits the spread's attrs into this scope. After all children render, the " + "element collects and merges the emitted attrs.") + + (~geography/provide-demo-example + :demo (~geography/demo-spread-mechanism) + :code (highlight ";; Spread = emit! into element-attrs\n(div (make-spread {:class \"card\"})\n \"hello\")\n\n;; Internally:\n;; 1. div opens provider:\n;; (provide-push! \"element-attrs\" nil)\n;; 2. spread child emits:\n;; (emit! \"element-attrs\"\n;; {:class \"card\"})\n;; 3. div collects + merges:\n;; (emitted \"element-attrs\")\n;; → ({:class \"card\"})\n;; 4. (provide-pop! \"element-attrs\")\n;; Result:
hello
" "lisp")) + + (~docs/subsection :title "Why this matters" + (p "Before the refactor, every intermediate form in the render pipeline — " + "fragments, " (code "let") ", " (code "begin") ", " (code "map") ", " + (code "for-each") ", " (code "when") ", " (code "cond") ", component children — " + "needed an explicit " (code "(filter (fn (r) (not (spread? r))) ...)") " to strip " + "spread values from rendered output. Over 25 such filters existed across the four adapters.") + (p "With provide/emit!, all of these disappear. Spreads emit into the nearest element's " + "scope regardless of how many layers of control flow they pass through. Non-element " + "contexts have no provider, so " (code "emit!") " is a silent no-op."))) + + ;; ===================================================================== + ;; IV. Nested scoping + ;; ===================================================================== + + (~docs/section :title "Nested scoping" :id "nesting" + (p "Providers stack. Each " (code "provide") " pushes onto a per-name stack; " + "the closest one wins. This gives lexical-style scoping at render time.") + + (~geography/provide-demo-example + :demo (~geography/demo-nested-provide) + :code (highlight "(provide \"level\" \"outer\"\n (context \"level\") ;; → \"outer\"\n (provide \"level\" \"inner\"\n (context \"level\")) ;; → \"inner\"\n (context \"level\")) ;; → \"outer\" again" "lisp")) + + (p "For " (code "emit!") ", this means emissions go to the " (em "nearest") " provider. " + "A spread inside a nested element emits to that element, not an ancestor.") + (~docs/code :code (highlight ";; Nested elements = nested providers\n(div ;; provider A\n (span ;; provider B\n (make-spread {:class \"inner\"})) ;; emits to B\n (make-spread {:class \"outer\"})) ;; emits to A\n;; →
" "lisp"))) + + ;; ===================================================================== + ;; V. Across all adapters + ;; ===================================================================== + + (~docs/section :title "Across all adapters" :id "adapters" + (p "The provide/emit! mechanism works identically across all four rendering adapters. " + "The element rendering pattern is the same; only the output format differs.") + + (~docs/table + :headers (list "Adapter" "Element render" "Spread dispatch") + :rows (list + (list "HTML (server)" "provide-push! → render children → merge emitted → provide-pop!" "(emit! \"element-attrs\" (spread-attrs expr)) → \"\"") + (list "Async (server)" "Same pattern, with await on child rendering" "Same dispatch") + (list "SX wire (aser)" "provide-push! → serialize children → merge emitted as :key attrs → provide-pop!" "(emit! \"element-attrs\" (spread-attrs expr)) → nil") + (list "DOM (browser)" "provide-push! → reduce children → merge emitted onto DOM element → provide-pop!" "emit! + keep value for reactive-spread detection"))) + + (~docs/subsection :title "DOM adapter: reactive-spread preserved" + (p "In the DOM adapter, spread children inside islands are still checked individually " + "for signal dependencies. " (code "reactive-spread") " tracks signal deps and " + "surgically updates attributes when signals change. The static path uses provide/emit!; " + "the reactive path wraps it in an effect.") + (p "See the " + (a :href "/sx/(geography.(spreads))" :class "text-violet-600 hover:underline" "spreads article") + " for reactive-spread details."))) + + ;; ===================================================================== + ;; VI. Comparison with collect! + ;; ===================================================================== + + (~docs/section :title "Comparison with collect! / collected" :id "comparison" + (~docs/table + :headers (list "" "provide / emit!" "collect! / collected") + :rows (list + (list "Scope" "Lexical (nearest enclosing provide)" "Global (render-wide)") + (list "Deduplication" "None — every emit! appends" "Automatic (same value skipped)") + (list "Multiple scopes" "Yes — nested provides shadow" "No — single global bucket per name") + (list "Downward data" "Yes (context)" "No") + (list "Used by" "Spreads (element-attrs)" "CSSX rule accumulation"))) + + (p (code "collect!") " remains the right tool for CSS rule accumulation — deduplication " + "matters there, and rules need to reach the layout root regardless of nesting depth. " + (code "emit!") " is right for spread attrs — no dedup needed, and each element only " + "wants attrs from its direct children.")) + + ;; ===================================================================== + ;; VII. Platform implementation + ;; ===================================================================== + + (~docs/section :title "Platform implementation" :id "platform" + (p "Each platform (Python, JavaScript) must provide five operations. " + "The platform manages per-name stacks — each stack entry has a value and an " + "emitted list.") + + (~docs/table + :headers (list "Platform primitive" "Purpose") + :rows (list + (list "provide-push!(name, value)" "Push a new scope with value and empty emitted list") + (list "provide-pop!(name)" "Pop the most recent scope") + (list "context(name, ...default)" "Read value from nearest scope (error if missing and no default)") + (list "emit!(name, value)" "Append to nearest scope's emitted list (tolerant: no-op if missing)") + (list "emitted(name)" "Return list of emitted values from nearest scope"))) + + (p (code "provide") " itself is a special form in " + (a :href "/sx/(language.(spec.(explore.evaluator)))" :class "font-mono text-violet-600 hover:underline text-sm" "eval.sx") + " — it calls " (code "provide-push!") ", evaluates the body, " + "then calls " (code "provide-pop!") ". The five platform primitives are declared in " + (a :href "/sx/(language.(spec.(explore.boundary)))" :class "font-mono text-violet-600 hover:underline text-sm" "boundary.sx") + " (Tier 5: Dynamic scope).") + + (~docs/note + (p (strong "Spec explorer: ") "See the provide/emit! primitives in " + (a :href "/sx/(language.(spec.(explore.boundary)))" :class "font-mono text-violet-600 hover:underline text-sm" "boundary.sx explorer") + ". The " (code "provide") " special form is in " + (a :href "/sx/(language.(spec.(explore.evaluator)))" :class "font-mono text-violet-600 hover:underline text-sm" "eval.sx explorer") + ". Element rendering with provide/emit! is visible in " + (a :href "/sx/(language.(spec.(explore.adapter-html)))" :class "font-mono text-violet-600 hover:underline text-sm" "adapter-html") + " and " + (a :href "/sx/(language.(spec.(explore.adapter-async)))" :class "font-mono text-violet-600 hover:underline text-sm" "adapter-async") + "."))))) diff --git a/sx/sx/spreads.sx b/sx/sx/spreads.sx index ab9c5f9..774a14b 100644 --- a/sx/sx/spreads.sx +++ b/sx/sx/spreads.sx @@ -244,37 +244,43 @@ (p "Spread and collect are both instances of the same pattern: " (strong "child communicates upward through the render tree") ". " "The general form is " (code "provide") "/" (code "context") "/" (code "emit!") - " — render-time dynamic scope.") + " — render-time dynamic scope. Spreads are now implemented on top of this mechanism.") + + (~docs/subsection :title "How spreads use provide/emit! internally" + (p "Every element rendering wraps its children in a provider scope named " + (code "\"element-attrs\"") ". When the renderer encounters a spread child, it " + "emits the spread's attrs into this scope instead of returning a DOM node. " + "After all children render, the element collects emitted attrs and merges them.") + (~docs/code :code (highlight ";; What happens inside element rendering:\n;; 1. Element creates a provider scope\n(provide-push! \"element-attrs\" nil)\n\n;; 2. Children render — spreads emit into scope\n;; (make-spread {:class \"card\"}) → (emit! \"element-attrs\" {:class \"card\"})\n\n;; 3. Element collects emitted attrs\n(for-each\n (fn (spread-dict) (merge-spread-attrs attrs spread-dict))\n (emitted \"element-attrs\"))\n(provide-pop! \"element-attrs\")" "lisp")) + (p (code "emit!") " is tolerant — when no provider exists (a spread outside any element), " + "it silently returns nil. This means spreads in non-element contexts " + "(fragments, " (code "begin") ", bare " (code "map") ") vanish without error.") + (p "Stored spreads work naturally:") + (~docs/code :code (highlight ";; Spread value stored in a let binding\n(let ((card (make-spread {:class \"card\"})))\n (div card \"hello\"))\n;; →
hello
\n;;\n;; `card` holds a _Spread object.\n;; When div renders it, the adapter sees type \"spread\"\n;; → emits into div's \"element-attrs\" provider\n;; → returns \"\" (no content)\n;; div merges the emitted attrs after children." "lisp"))) (~docs/subsection :title "The unification" (~docs/table - :headers (list "Current" "General form" "Direction") + :headers (list "Mechanism" "General form" "Direction") :rows (list (list "collect! / collected" "emit! / emitted" "upward (child → scope)") - (list "make-spread" "emit! into implicit parent-attrs provider" "upward (child → parent)") - (list "(nothing yet)" "context" "downward (scope → child)"))) - (p (code "provide") " creates a named scope with a value (downward) and an accumulator (upward). " - (code "context") " reads the value. " (code "emit!") " appends to the accumulator. " - (code "emitted") " retrieves accumulated values.") - (~docs/code :code (highlight ";; Downward: theme context\n(provide \"theme\" {:primary \"violet\" :font \"serif\"}\n (h1 :style (str \"color:\" (get (context \"theme\") :primary))\n \"Themed heading\"))\n\n;; Upward: script accumulation (like collect!)\n(provide \"scripts\" nil\n (div\n (emit! \"scripts\" \"analytics.js\")\n (div (emit! \"scripts\" \"charts.js\") \"chart\"))\n (for-each (fn (s) (script :src s)) (emitted \"scripts\")))\n\n;; Both at once\n(provide \"page\" {:title \"Home\"}\n (h1 (context \"page\" :title))\n (emit! \"page\" {:meta \"og:title\" :content \"Home\"})\n (for-each (fn (m) (meta :name (get m :meta) :content (get m :content)))\n (emitted \"page\")))" "lisp"))) - - (~docs/subsection :title "What this means" - (p "Three mechanisms collapse into one:") - (ul :class "list-disc pl-5 space-y-1 text-stone-600" - (li (code "collect!") " = " (code "emit!") " with deduplication") - (li (code "spread") " = " (code "emit!") " into implicit parent-attrs provider") - (li (code "collected") " = " (code "emitted")) - (li (code "context") " = downward data flow (new capability — no current equivalent)")) - (p "The reactive-spread we just built would naturally follow — the effect tracks " - "signal deps in the " (code "emit!") " call, the provider accumulates, the element applies.") - (p :class "text-stone-500 italic" - "This is the planned next step. The current primitives (spread, collect, reactive-spread) " - "work and are fully orthogonal. " (code "provide/context/emit!") " will be the deeper " - "foundation they are reimplemented on top of.")) + (list "spread" "emit! into element-attrs provider" "upward (child → parent)") + (list "theme / config" "context" "downward (scope → child)"))) + (p "Three mechanisms, one substrate. " + (a :href "/sx/(geography.(provide))" :class "text-violet-600 hover:underline" + "See the full provide/context/emit! article") + " for the general primitive and its other uses.")) (~docs/note - (p (strong "Plan: ") (code "provide") "/" (code "context") "/" (code "emit!") " is specced " - "and ready to implement. Per-name stacks. Each entry has a value and an emitted list. " - "Four primitives: " (code "provide") " (special form), " (code "context") ", " - (code "emit!") ", " (code "emitted") ". Platform provides " (code "provide-push!") - "/" (code "provide-pop!") ". See the implementation plan for details."))))) + (p (strong "Spec: ") "The provide/emit! primitives are declared in " + (a :href "/sx/(language.(spec.(explore.boundary)))" :class "font-mono text-violet-600 hover:underline text-sm" "boundary.sx") + " (Tier 5: Dynamic scope). The " (code "provide") " special form is in " + (a :href "/sx/(language.(spec.(explore.evaluator)))" :class "font-mono text-violet-600 hover:underline text-sm" "eval.sx") + ". Element rendering with provide/emit! is visible in all four adapter specs: " + (a :href "/sx/(language.(spec.(explore.adapter-html)))" :class "font-mono text-violet-600 hover:underline text-sm" "adapter-html") + ", " + (a :href "/sx/(language.(spec.(explore.adapter-async)))" :class "font-mono text-violet-600 hover:underline text-sm" "adapter-async") + ", " + (a :href "/sx/(language.(spec.(explore.adapter-sx)))" :class "font-mono text-violet-600 hover:underline text-sm" "adapter-sx") + ", " + (a :href "/sx/(language.(spec.(explore.adapter-dom)))" :class "font-mono text-violet-600 hover:underline text-sm" "adapter-dom") + "."))))) diff --git a/sx/sxc/pages/docs.sx b/sx/sxc/pages/docs.sx index 12a7854..32c8511 100644 --- a/sx/sxc/pages/docs.sx +++ b/sx/sxc/pages/docs.sx @@ -613,6 +613,16 @@ "phase2" (~reactive-islands/phase2/reactive-islands-phase2-content) :else (~reactive-islands/index/reactive-islands-index-content)))) +;; --------------------------------------------------------------------------- +;; Provide / Emit! section (under Geography) +;; --------------------------------------------------------------------------- + +(defpage provide-index + :path "/geography/provide/" + :auth :public + :layout :sx-docs + :content (~layouts/doc :path "/sx/(geography.(provide))" (~geography/provide-content))) + ;; --------------------------------------------------------------------------- ;; Spreads section (under Geography) ;; ---------------------------------------------------------------------------