Two bugs caused code blocks to render empty across the site: 1. ~docs/code component had parameter named `code` which collided with the HTML <code> tag name. Renamed to `src` and updated all 57 callers. Added font-mono class for explicit monospace. 2. Batched IO dispatch in ocaml_bridge.py only skipped one leading number (batch ID) but the format has two (epoch + ID): (io-request EPOCH ID "name" args...). Changed to skip all leading numbers so the string name is correctly found. This fixes highlight and other batchable helpers returning empty results. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
195 lines
10 KiB
Plaintext
195 lines
10 KiB
Plaintext
;; ---------------------------------------------------------------------------
|
|
;; Scopes — the unified primitive beneath provide, collect!, and spreads
|
|
;; ---------------------------------------------------------------------------
|
|
|
|
|
|
;; ---- Demo components ----
|
|
|
|
(defcomp ~geography/demo-scope-basic ()
|
|
(div :class "space-y-2"
|
|
(scope "demo-theme" :value "violet"
|
|
(div :class "rounded-lg p-3 bg-violet-50 border border-violet-200"
|
|
(p :class "text-sm text-violet-800 font-semibold"
|
|
(str "Inside scope: theme = " (context "demo-theme")))
|
|
(p :class "text-xs text-stone-500" "scope creates a named scope. context reads it.")))
|
|
(div :class "rounded-lg p-3 bg-stone-50 border border-stone-200"
|
|
(p :class "text-sm text-stone-600" "Outside scope: no context available."))))
|
|
|
|
(defcomp ~geography/demo-scope-emit ()
|
|
(div :class "space-y-2"
|
|
(scope "demo-deps"
|
|
(div :class "rounded-lg p-3 bg-stone-50 border border-stone-200"
|
|
(p :class "text-sm text-stone-700"
|
|
(emit! "demo-deps" "lodash")
|
|
(emit! "demo-deps" "react")
|
|
"Components emit their dependencies upward."))
|
|
(div :class "rounded-lg p-3 bg-violet-50 border border-violet-200"
|
|
(p :class "text-sm text-violet-800 font-semibold" "Emitted:")
|
|
(ul :class "text-xs text-stone-600 list-disc pl-5"
|
|
(map (fn (d) (li (code d))) (emitted "demo-deps")))))))
|
|
|
|
(defcomp ~geography/demo-scope-dedup ()
|
|
(div :class "space-y-2"
|
|
(div :class "rounded-lg p-3 bg-stone-50 border border-stone-200"
|
|
(p :class "text-sm text-stone-700"
|
|
(collect! "demo-css-dedup" ".card { padding: 1rem }")
|
|
(collect! "demo-css-dedup" ".card { padding: 1rem }")
|
|
(collect! "demo-css-dedup" ".btn { color: blue }")
|
|
"Three collect! calls, two identical. Only unique values kept."))
|
|
(div :class "rounded-lg p-3 bg-violet-50 border border-violet-200"
|
|
(p :class "text-sm text-violet-800 font-semibold"
|
|
(str "Collected: " (len (collected "demo-css-dedup")) " rules"))
|
|
(ul :class "text-xs text-stone-600 list-disc pl-5"
|
|
(map (fn (r) (li (code r))) (collected "demo-css-dedup"))))))
|
|
|
|
|
|
;; ---- Layout helper ----
|
|
|
|
(defcomp ~geography/scopes-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/scopes-content ()
|
|
(~docs/page :title "Scopes"
|
|
|
|
(p :class "text-stone-500 text-sm italic mb-8"
|
|
"The unified primitive. " (code "scope") " creates a named scope with an optional value "
|
|
"and an accumulator. " (code "provide") ", " (code "collect!") ", spreads, islands — "
|
|
"they all resolve to scope operations at the platform level.")
|
|
|
|
;; =====================================================================
|
|
;; I. The primitive
|
|
;; =====================================================================
|
|
|
|
(~docs/section :title "The primitive" :id "primitive"
|
|
|
|
(p (code "scope") " is a special form that pushes a named scope, evaluates its body, "
|
|
"then pops it. The scope has three properties: a name, a downward value, and an "
|
|
"upward accumulator.")
|
|
|
|
(~docs/code :src (highlight "(scope name body...) ;; scope with no value\n(scope name :value v body...) ;; scope with downward value" "lisp"))
|
|
|
|
(p "Within the body, " (code "context") " reads the value, " (code "emit!") " appends "
|
|
"to the accumulator, and " (code "emitted") " reads what was accumulated.")
|
|
|
|
(~geography/scopes-demo-example
|
|
:demo (~geography/demo-scope-basic)
|
|
:code (highlight "(scope \"theme\" :value \"violet\"\n (context \"theme\")) ;; → \"violet\"\n\n;; Nested scopes shadow:\n(scope \"x\" :value \"outer\"\n (scope \"x\" :value \"inner\"\n (context \"x\")) ;; → \"inner\"\n (context \"x\")) ;; → \"outer\"" "lisp")))
|
|
|
|
;; =====================================================================
|
|
;; II. Sugar forms
|
|
;; =====================================================================
|
|
|
|
(~docs/section :title "Sugar forms" :id "sugar"
|
|
|
|
(p "Nobody writes " (code "scope") " directly. The sugar forms are the API:")
|
|
|
|
(~docs/table
|
|
:headers (list "Sugar" "Expands to" "Used for")
|
|
:rows (list
|
|
(list "provide" "(scope name :value v body...)" "Downward context passing")
|
|
(list "collect!" "Lazy root scope + dedup emit" "CSS rule accumulation")
|
|
(list "Spreads" "(scope \"element-attrs\" ...)" "Child-to-parent attrs (implicit)")))
|
|
|
|
(~docs/subsection :title "provide — scope with a value"
|
|
(p (code "(provide name value body...)") " is exactly "
|
|
(code "(scope name :value value body...)") ". It exists because "
|
|
"the two-arg form is the common case.")
|
|
(~docs/code :src (highlight ";; These are equivalent:\n(provide \"theme\" {:primary \"violet\"}\n (h1 \"hello\"))\n\n(scope \"theme\" :value {:primary \"violet\"}\n (h1 \"hello\"))" "lisp")))
|
|
|
|
(~docs/subsection :title "collect! — lazy root scope with dedup"
|
|
(p (code "collect!") " is the most interesting sugar. When called, if no scope exists "
|
|
"for that name, it lazily creates a root scope with deduplication enabled. "
|
|
"Then it emits into it.")
|
|
(~geography/scopes-demo-example
|
|
:demo (~geography/demo-scope-dedup)
|
|
:code (highlight ";; collect! creates a lazy root scope:\n(collect! \"css\" \".card { pad: 1rem }\")\n(collect! \"css\" \".card { pad: 1rem }\") ;; deduped!\n(collect! \"css\" \".btn { color: blue }\")\n(collected \"css\") ;; → 2 rules\n\n;; Equivalent to:\n(scope \"css\" ;; with dedup\n (emit! \"css\" ...)\n (emitted \"css\"))" "lisp"))
|
|
(p (code "collected") " is an alias for " (code "emitted") ". "
|
|
(code "clear-collected!") " clears the accumulator."))
|
|
|
|
(~docs/subsection :title "Spreads — implicit element scope"
|
|
(p "Every element rendering function wraps its children in "
|
|
(code "(scope-push! \"element-attrs\" nil)") ". Spread children "
|
|
(code "emit!") " their attrs into this scope. After rendering, the element "
|
|
"merges the emitted attrs.")
|
|
(p "See the "
|
|
(a :href "/sx/(geography.(spreads))" :class "text-violet-600 hover:underline" "spreads article")
|
|
" for the full mechanism.")))
|
|
|
|
;; =====================================================================
|
|
;; III. Accumulator: upward data flow
|
|
;; =====================================================================
|
|
|
|
(~docs/section :title "Upward data flow" :id "upward"
|
|
|
|
(~geography/scopes-demo-example
|
|
:demo (~geography/demo-scope-emit)
|
|
:code (highlight "(scope \"deps\"\n (emit! \"deps\" \"lodash\")\n (emit! \"deps\" \"react\")\n (emitted \"deps\")) ;; → (\"lodash\" \"react\")" "lisp"))
|
|
|
|
(p "Accumulation always goes to the " (em "nearest") " enclosing scope with that name. "
|
|
"This is what makes nested elements work — a spread inside a nested "
|
|
(code "span") " emits to the " (code "span") "'s scope, not an outer "
|
|
(code "div") "'s scope."))
|
|
|
|
;; =====================================================================
|
|
;; IV. Platform implementation
|
|
;; =====================================================================
|
|
|
|
(~docs/section :title "Platform implementation" :id "platform"
|
|
|
|
(p "Each platform (Python, JavaScript) maintains a single data structure:")
|
|
|
|
(~docs/code :src (highlight "_scope_stacks = {} ;; {name: [{value, emitted: [], dedup: bool}]}" "python"))
|
|
|
|
(p "Six operations on this structure:")
|
|
|
|
(~docs/table
|
|
:headers (list "Operation" "Purpose")
|
|
:rows (list
|
|
(list "scope-push!(name, value)" "Push {value, emitted: [], dedup: false}")
|
|
(list "scope-pop!(name)" "Pop the most recent scope")
|
|
(list "context(name, default?)" "Read value from nearest scope")
|
|
(list "emit!(name, value)" "Append to nearest scope's accumulator (respects dedup)")
|
|
(list "emitted(name)" "Read accumulated values from nearest scope")
|
|
(list "collect!(name, value)" "Lazy push root scope with dedup, then emit")))
|
|
|
|
(p (code "provide-push!") " and " (code "provide-pop!") " are aliases for "
|
|
(code "scope-push!") " and " (code "scope-pop!") ". "
|
|
"All adapter code uses " (code "scope-push!") "/" (code "scope-pop!") " directly."))
|
|
|
|
;; =====================================================================
|
|
;; V. Unification
|
|
;; =====================================================================
|
|
|
|
(~docs/section :title "What scope unifies" :id "unification"
|
|
|
|
(p "Before scopes, the platform had two separate mechanisms:")
|
|
|
|
(~docs/code :src (highlight ";; Before: two mechanisms\n_provide_stacks = {} ;; {name: [{value, emitted: []}]}\n_collect_buckets = {} ;; {name: [values...]}\n\n;; After: one mechanism\n_scope_stacks = {} ;; {name: [{value, emitted: [], dedup: bool}]}" "python"))
|
|
|
|
(p "The unification is not just code cleanup. It means:")
|
|
(ul :class "space-y-1"
|
|
(li (code "collect!") " can be nested inside " (code "provide") " scopes — "
|
|
"they share the same stack.")
|
|
(li "A component can " (code "emit!") " and " (code "collect!") " into the same scope — "
|
|
"they use the same accumulator.")
|
|
(li "The dedup flag is per-scope, not per-mechanism — a " (code "provide") " scope "
|
|
"has no dedup, a " (code "collect!") " root scope has dedup."))
|
|
|
|
(p "See the "
|
|
(a :href "/sx/(etc.(plan.scoped-effects))" :class "text-violet-600 hover:underline" "scoped effects plan")
|
|
" for the full design rationale and future phases (reactive scopes, morph scopes).")
|
|
|
|
(~docs/note
|
|
(p (strong "Spec: ") "The " (code "scope") " special form is in "
|
|
(a :href "/sx/(language.(spec.(explore.evaluator)))" :class "font-mono text-violet-600 hover:underline text-sm" "eval.sx")
|
|
". 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: Scoped effects).")))))
|