Add provide/emit! geography article, update spreads article, fix foundations rendering

- New geography article (provide.sx): four primitives, demos, nested scoping,
  adapter comparison, spec explorer links
- Updated spreads article section VI: provide/emit! is now implemented, not planned
- Fixed foundations.sx: ~docs/code-block → ~docs/code (undefined component
  was causing the page to silently fail to render)
- Added nav entry and defpage route for provide/emit! article

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 16:04:52 +00:00
parent 04d3b2ecaf
commit aef990735f
5 changed files with 303 additions and 38 deletions

View File

@@ -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;; → <div class=\"card\">hello</div>\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")
".")))))