Unicode escapes, variadic infix fix, spreads demos, scoped-effects + foundations plans
- Add \uXXXX unicode escape support to parser.py and parser.sx spec - Add char-from-code primitive (Python chr(), JS String.fromCharCode()) - Fix variadic infix operators in both bootstrappers (js.sx, py.sx) — (+ a b c d) was silently dropping terms, now left-folds correctly - Rebootstrap sx_ref.py and sx-browser.js with all fixes - Fix 3 pre-existing map-dict test failures in shared/sx/tests/run.py - Add live demos alongside examples in spreads essay (side-by-side layout) - Add scoped-effects plan: algebraic effects as unified foundation for spread/collect/island/lake/signal/context - Add foundations plan: CEK machine, the computational floor, three-axis model (depth/topology/linearity), Curry-Howard correspondence - Route both plans in page-functions.sx and nav-data.sx Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
126
sx/sx/spreads.sx
126
sx/sx/spreads.sx
@@ -2,6 +2,87 @@
|
||||
;; Spreads — child-to-parent communication across render boundaries
|
||||
;; ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
;; ---- Layout helper ----
|
||||
|
||||
(defcomp ~geography/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)))))
|
||||
|
||||
|
||||
;; ---- Demo components ----
|
||||
|
||||
(defcomp ~geography/demo-callout (&key type)
|
||||
(make-spread
|
||||
(cond
|
||||
(= type "info") {"style" "border-left:4px solid #3b82f6;padding:0.5rem 0.75rem;background:#eff6ff;border-radius:0.25rem"
|
||||
"data-callout" "info"}
|
||||
(= type "warning") {"style" "border-left:4px solid #f59e0b;padding:0.5rem 0.75rem;background:#fffbeb;border-radius:0.25rem"
|
||||
"data-callout" "warning"}
|
||||
(= type "success") {"style" "border-left:4px solid #10b981;padding:0.5rem 0.75rem;background:#ecfdf5;border-radius:0.25rem"
|
||||
"data-callout" "success"}
|
||||
:else {"style" "border-left:4px solid #78716c;padding:0.5rem 0.75rem;background:#fafaf9;border-radius:0.25rem"
|
||||
"data-callout" "default"})))
|
||||
|
||||
(defcomp ~geography/demo-spread-basic ()
|
||||
(div :class "space-y-3"
|
||||
(div (~geography/demo-callout :type "info")
|
||||
(p :class "text-sm" "Info — styled by its child."))
|
||||
(div (~geography/demo-callout :type "warning")
|
||||
(p :class "text-sm" "Warning — same component, different type."))
|
||||
(div (~geography/demo-callout :type "success")
|
||||
(p :class "text-sm" "Success — child tells parent how to look."))))
|
||||
|
||||
(defcomp ~geography/demo-cssx-tw ()
|
||||
(div :class "space-y-3"
|
||||
(div (~cssx/tw :tokens "bg-violet-100 rounded-lg p-4")
|
||||
(h4 (~cssx/tw :tokens "text-violet-800 font-semibold text-lg") "Styled via ~cssx/tw")
|
||||
(p (~cssx/tw :tokens "text-stone-600 text-sm mt-1")
|
||||
"Classes injected from spread child."))
|
||||
(div (~cssx/tw :tokens "bg-rose-50 rounded-lg p-4 border border-rose-200")
|
||||
(h4 (~cssx/tw :tokens "text-rose-700 font-semibold text-lg") "Different tokens")
|
||||
(p (~cssx/tw :tokens "text-stone-600 text-sm mt-1")
|
||||
"CSS rules JIT-generated. No build step."))))
|
||||
|
||||
(defcomp ~geography/demo-semantic-vars ()
|
||||
(let ((card (~cssx/tw :tokens "bg-stone-50 rounded-lg p-4 shadow-sm border border-stone-200"))
|
||||
(heading (~cssx/tw :tokens "text-violet-700 text-lg font-bold"))
|
||||
(body-text (~cssx/tw :tokens "text-stone-600 text-sm mt-1")))
|
||||
(div :class "space-y-3"
|
||||
(div card
|
||||
(h4 heading "First Card")
|
||||
(p body-text "Named spreads bound with let."))
|
||||
(div card
|
||||
(h4 heading "Second Card")
|
||||
(p body-text "Same variables, consistent look.")))))
|
||||
|
||||
(defisland ~geography/demo-reactive-spread ()
|
||||
(let ((colour (signal "violet")))
|
||||
(div (~cssx/tw :tokens (str "rounded-lg p-4 transition-all duration-300 bg-" (deref colour) "-100 border border-" (deref colour) "-300"))
|
||||
(h4 (~cssx/tw :tokens (str "text-" (deref colour) "-800 font-semibold text-lg"))
|
||||
(str "Theme: " (deref colour)))
|
||||
(p (~cssx/tw :tokens "text-stone-600 text-sm mt-2 mb-3")
|
||||
"Click to change theme. Reactive spreads surgically update classes.")
|
||||
(div :class "flex gap-2 flex-wrap"
|
||||
(button :on-click (fn (e) (reset! colour "violet"))
|
||||
(~cssx/tw :tokens "bg-violet-500 text-white px-3 py-1.5 rounded text-sm font-medium")
|
||||
"Violet")
|
||||
(button :on-click (fn (e) (reset! colour "rose"))
|
||||
(~cssx/tw :tokens "bg-rose-500 text-white px-3 py-1.5 rounded text-sm font-medium")
|
||||
"Rose")
|
||||
(button :on-click (fn (e) (reset! colour "amber"))
|
||||
(~cssx/tw :tokens "bg-amber-500 text-white px-3 py-1.5 rounded text-sm font-medium")
|
||||
"Amber")
|
||||
(button :on-click (fn (e) (reset! colour "emerald"))
|
||||
(~cssx/tw :tokens "bg-emerald-500 text-white px-3 py-1.5 rounded text-sm font-medium")
|
||||
"Emerald")))))
|
||||
|
||||
|
||||
;; ---- Page content ----
|
||||
|
||||
(defcomp ~geography/spreads-content ()
|
||||
(~docs/page :title "Spreads"
|
||||
|
||||
@@ -22,9 +103,11 @@
|
||||
(p "A spread is a value type. " (code "make-spread") " creates one from a dict of "
|
||||
"attributes. When the renderer encounters a spread as a child of an element, "
|
||||
"it merges the attrs onto the parent element instead of appending a DOM node.")
|
||||
(~docs/code :code (highlight "(defcomp ~highlight (&key colour)\n (make-spread {\"class\" (str \"highlight-\" colour)\n \"data-highlight\" colour}))" "lisp"))
|
||||
(p "Use it as a child of any element:")
|
||||
(~docs/code :code (highlight "(div (~highlight :colour \"yellow\")\n \"This div gets class=highlight-yellow\")" "lisp"))
|
||||
|
||||
(~geography/demo-example
|
||||
:demo (~geography/demo-spread-basic)
|
||||
:code (highlight "(defcomp ~callout (&key type)\n (make-spread\n (cond\n (= type \"info\")\n {\"style\" \"border-left:4px solid\n #3b82f6; background:#eff6ff\"}\n (= type \"warning\")\n {\"style\" \"border-left:4px solid\n #f59e0b; background:#fffbeb\"}\n (= type \"success\")\n {\"style\" \"border-left:4px solid\n #10b981; background:#ecfdf5\"})))\n\n;; Child injects attrs onto parent:\n(div (~callout :type \"info\")\n \"This div gets the callout style.\")" "lisp"))
|
||||
|
||||
(p (code "class") " values are appended (space-joined). "
|
||||
(code "style") " values are appended (semicolon-joined). "
|
||||
"All other attributes overwrite."))
|
||||
@@ -32,7 +115,7 @@
|
||||
(~docs/subsection :title "2. collect! / collected / clear-collected!"
|
||||
(p "Render-time accumulators. Values are collected into named buckets "
|
||||
"during rendering and retrieved at flush points. Deduplication is automatic.")
|
||||
(~docs/code :code (highlight ";; Deep inside a component tree:\n(collect! \"cssx\" \".sx-bg-red-500{background-color:hsl(0,72%,53%)}\")\n\n;; At the flush point (once, in the layout):\n(let ((rules (collected \"cssx\")))\n (clear-collected! \"cssx\")\n (raw! (str \"<style>\" (join \"\" rules) \"</style>\")))" "lisp"))
|
||||
(~docs/code :code (highlight ";; Deep inside a component tree:\n(collect! \"cssx\" \".sx-bg-red-500{background:red}\")\n\n;; At the flush point (once, in the layout):\n(let ((rules (collected \"cssx\")))\n (clear-collected! \"cssx\")\n (raw! (str \"<style>\" (join \"\" rules) \"</style>\")))" "lisp"))
|
||||
(p "This is upward communication through the render tree: "
|
||||
"a deeply nested component contributes a CSS rule, and the layout "
|
||||
"emits all accumulated rules as a single " (code "<style>") " tag. "
|
||||
@@ -42,12 +125,15 @@
|
||||
(p "Inside an island, when a spread's value depends on signals, "
|
||||
(code "reactive-spread") " tracks signal dependencies and surgically "
|
||||
"updates the parent element's attributes when signals change.")
|
||||
(~docs/code :code (highlight "(defisland ~themed-card ()\n (let ((theme (signal \"violet\")))\n (div (~cssx/tw :tokens (str \"bg-\" (deref theme) \"-500 p-4\"))\n (button :on-click (fn (e) (reset! theme \"rose\"))\n \"change theme\"))))" "lisp"))
|
||||
(p "When " (code "theme") " changes from " (code "\"violet\"") " to "
|
||||
(code "\"rose\"") ":")
|
||||
|
||||
(~geography/demo-example
|
||||
:demo (~geography/demo-reactive-spread)
|
||||
:code (highlight "(defisland ~themed-card ()\n (let ((theme (signal \"violet\")))\n (div (~cssx/tw :tokens\n (str \"bg-\" (deref theme)\n \"-100 p-4\"))\n (button\n :on-click\n (fn (e) (reset! theme \"rose\"))\n \"change theme\"))))" "lisp"))
|
||||
|
||||
(p "When " (code "theme") " changes:")
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-600"
|
||||
(li "Old classes (" (code "sx-bg-violet-500") ") are removed from the element")
|
||||
(li "New classes (" (code "sx-bg-rose-500") ") are added")
|
||||
(li "Old classes are removed from the element")
|
||||
(li "New classes are added")
|
||||
(li "New CSS rules are JIT-generated and flushed to the live stylesheet")
|
||||
(li "No re-render. No VDOM. No diffing. Just attr surgery."))))
|
||||
|
||||
@@ -98,11 +184,15 @@
|
||||
|
||||
(~docs/section :title "CSSX: the first application" :id "cssx"
|
||||
(p (code "~cssx/tw") " is a component that uses all three primitives:")
|
||||
(~docs/code :code (highlight "(defcomp ~cssx/tw (tokens)\n (let ((token-list (filter (fn (t) (not (= t \"\")))\n (split (or tokens \"\") \" \")))\n (results (map cssx-process-token token-list))\n (valid (filter (fn (r) (not (nil? r))) results))\n (classes (map (fn (r) (get r \"cls\")) valid))\n (rules (map (fn (r) (get r \"rule\")) valid))\n (_ (for-each (fn (rule) (collect! \"cssx\" rule)) rules)))\n (if (empty? classes)\n nil\n (make-spread {\"class\" (join \" \" classes)\n \"data-tw\" (or tokens \"\")}))))" "lisp"))
|
||||
(p "It's a regular " (code "defcomp") ". It uses " (code "make-spread") " to "
|
||||
"inject classes onto its parent, " (code "collect!") " to accumulate CSS rules "
|
||||
"for batch flushing, and when called inside an island with signal-dependent "
|
||||
"tokens, " (code "reactive-spread") " makes it live.")
|
||||
|
||||
(~geography/demo-example
|
||||
:demo (~geography/demo-cssx-tw)
|
||||
:code (highlight "(defcomp ~cssx/tw (tokens)\n (let ((token-list\n (filter (fn (t) (not (= t \"\")))\n (split (or tokens \"\") \" \")))\n (results\n (map cssx-process-token\n token-list))\n (classes (map (fn (r)\n (get r \"cls\")) results))\n (rules (map (fn (r)\n (get r \"rule\")) results)))\n (for-each (fn (rule)\n (collect! \"cssx\" rule)) rules)\n (make-spread\n {\"class\" (join \" \" classes)})))" "lisp"))
|
||||
|
||||
(p "It uses " (code "make-spread") " to inject classes, "
|
||||
(code "collect!") " to accumulate CSS rules for batch flushing, and "
|
||||
"when called inside an island with signal-dependent tokens, "
|
||||
(code "reactive-spread") " makes it live.")
|
||||
(p "But " (code "~cssx/tw") " is just one instance. The same primitives enable:")
|
||||
(ul :class "list-disc pl-5 space-y-1 text-stone-600"
|
||||
(li (code "~aria") " — reactive accessibility attributes driven by UI state")
|
||||
@@ -117,9 +207,13 @@
|
||||
(~docs/section :title "Semantic style variables" :id "variables"
|
||||
(p "Because " (code "~cssx/tw") " returns a spread, and spreads are values, "
|
||||
"you can bind them to names:")
|
||||
(~docs/code :code (highlight ";; Define once\n(define heading-style (~cssx/tw :tokens \"text-violet-700 text-2xl font-bold\"))\n(define nav-link (~cssx/tw :tokens \"text-stone-500 text-sm\"))\n(define card-base (~cssx/tw :tokens \"bg-stone-50 rounded-lg p-4\"))\n\n;; Use everywhere\n(div card-base\n (h1 heading-style \"Title\")\n (a nav-link :href \"/\" \"Home\"))" "lisp"))
|
||||
|
||||
(~geography/demo-example
|
||||
:demo (~geography/demo-semantic-vars)
|
||||
:code (highlight "(let ((card (~cssx/tw :tokens\n \"bg-stone-50 rounded-lg p-4\n shadow-sm border\"))\n (heading (~cssx/tw :tokens\n \"text-violet-700 text-lg\n font-bold\"))\n (body (~cssx/tw :tokens\n \"text-stone-600 text-sm\")))\n ;; Reuse everywhere:\n (div card\n (h4 heading \"First Card\")\n (p body \"Same variables.\"))\n (div card\n (h4 heading \"Second Card\")\n (p body \"Consistent look.\")))" "lisp"))
|
||||
|
||||
(p "These are semantic names wrapping utility tokens. Change the definition, "
|
||||
"every use updates. No build step, no CSS-in-JS runtime. Just " (code "define") ".")
|
||||
"every use updates. No build step, no CSS-in-JS runtime. Just " (code "let") ".")
|
||||
(p "Namespacing prevents clashes — " (code "~app/heading") " vs "
|
||||
(code "~admin/heading") " are different components in different namespaces."))
|
||||
|
||||
|
||||
Reference in New Issue
Block a user