Build tooling: updated OCaml bootstrapper, compile-modules, bundle.sh, sx-build-all. WASM browser: rebuilt sx_browser.bc.js/wasm, sx-platform-2.js, .sxbc bytecode files. CSSX/Tailwind: reworked cssx.sx templates and tw-layout, added tw-type support. Content: refreshed essays, plans, geography, reactive islands, docs, demos, handlers. New tools: bisect_sxbc.sh, test-spa.js, render-trace.sx, morph playwright spec. Tests: added test-match.sx, test-examples.sx, updated test-tw.sx and web tests. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
303 lines
11 KiB
Plaintext
303 lines
11 KiB
Plaintext
(defcomp
|
|
~geography/demo-scope-basic
|
|
()
|
|
(div
|
|
(~tw :tokens "space-y-2")
|
|
(scope
|
|
"demo-theme"
|
|
:value "violet"
|
|
(div
|
|
(~tw :tokens "rounded-lg p-3 bg-violet-50 border border-violet-200")
|
|
(p
|
|
(~tw :tokens "text-sm text-violet-800 font-semibold")
|
|
(str "Inside scope: theme = " (context "demo-theme")))
|
|
(p
|
|
(~tw :tokens "text-xs text-stone-500")
|
|
"scope creates a named scope. context reads it.")))
|
|
(div
|
|
(~tw :tokens "rounded-lg p-3 bg-stone-50 border border-stone-200")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-600")
|
|
"Outside scope: no context available."))))
|
|
|
|
(defcomp
|
|
~geography/demo-scope-emit
|
|
()
|
|
(div
|
|
(~tw :tokens "space-y-2")
|
|
(scope
|
|
"demo-deps"
|
|
(div
|
|
(~tw :tokens "rounded-lg p-3 bg-stone-50 border border-stone-200")
|
|
(p
|
|
(~tw :tokens "text-sm text-stone-700")
|
|
(emit! "demo-deps" "lodash")
|
|
(emit! "demo-deps" "react")
|
|
"Components emit their dependencies upward."))
|
|
(div
|
|
(~tw :tokens "rounded-lg p-3 bg-violet-50 border border-violet-200")
|
|
(p (~tw :tokens "text-sm text-violet-800 font-semibold") "Emitted:")
|
|
(ul
|
|
(~tw :tokens "text-xs text-stone-600 list-disc pl-5")
|
|
(map (fn (d) (li (code d))) (emitted "demo-deps")))))))
|
|
|
|
(defcomp
|
|
~geography/demo-scope-dedup
|
|
()
|
|
(div
|
|
(~tw :tokens "space-y-2")
|
|
(div
|
|
(~tw :tokens "rounded-lg p-3 bg-stone-50 border border-stone-200")
|
|
(p
|
|
(~tw :tokens "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
|
|
(~tw :tokens "rounded-lg p-3 bg-violet-50 border border-violet-200")
|
|
(p
|
|
(~tw :tokens "text-sm text-violet-800 font-semibold")
|
|
(str "Collected: " (len (collected "demo-css-dedup")) " rules"))
|
|
(ul
|
|
(~tw :tokens "text-xs text-stone-600 list-disc pl-5")
|
|
(map (fn (r) (li (code r))) (collected "demo-css-dedup"))))))
|
|
|
|
(defcomp
|
|
~geography/scopes-demo-example
|
|
(&key demo src)
|
|
(div
|
|
(~tw :tokens "grid grid-cols-1 lg:grid-cols-2 gap-4 my-6 items-start")
|
|
(div
|
|
(~tw :tokens "border border-dashed border-stone-300 rounded-lg p-4 bg-stone-50 min-h-[80px]")
|
|
demo)
|
|
(div
|
|
(~tw :tokens "not-prose bg-stone-100 rounded-lg p-4 overflow-x-auto")
|
|
(pre
|
|
(~tw :tokens "text-sm leading-relaxed whitespace-pre-wrap break-words")
|
|
(code src)))))
|
|
|
|
(defcomp
|
|
~geography/scopes-content
|
|
()
|
|
(~docs/page
|
|
:title "Scopes"
|
|
(p
|
|
(~tw :tokens "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.")
|
|
(~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)
|
|
:src (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")))
|
|
(~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)
|
|
:src (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))"
|
|
(~tw :tokens "text-violet-600 hover:underline")
|
|
"spreads article")
|
|
" for the full mechanism.")))
|
|
(~docs/section
|
|
:title "Upward data flow"
|
|
:id "upward"
|
|
(~geography/scopes-demo-example
|
|
:demo (~geography/demo-scope-emit)
|
|
:src (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."))
|
|
(~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."))
|
|
(~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
|
|
(~tw :tokens "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))"
|
|
(~tw :tokens "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)))"
|
|
(~tw :tokens "font-mono text-violet-600 hover:underline text-sm")
|
|
"eval.sx")
|
|
". Platform primitives are declared in "
|
|
(a
|
|
:href "/sx/(language.(spec.(explore.boundary)))"
|
|
(~tw :tokens "font-mono text-violet-600 hover:underline text-sm")
|
|
"boundary.sx")
|
|
" (Tier 5: Scoped effects).")))))
|