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