Add spread + collect primitives, rewrite ~cssx/tw as defcomp

New SX primitives for child-to-parent communication in the render tree:
- spread type: make-spread, spread?, spread-attrs — child injects attrs
  onto parent element (class joins with space, style with semicolon)
- collect!/collected/clear-collected! — render-time accumulation with
  dedup into named buckets

~cssx/tw is now a proper defcomp returning a spread value instead of a
macro wrapping children. ~cssx/flush reads collected "cssx" rules and
emits a single <style data-cssx> tag.

All four render adapters (html, async, dom, aser) handle spread values.
Both bootstraps (Python + JS) regenerated. Also fixes length→len in
cssx.sx (length was never a registered primitive).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-13 02:38:31 +00:00
parent c2efa192c5
commit 41097eeef9
15 changed files with 844 additions and 230 deletions

View File

@@ -313,3 +313,61 @@
:effects [mutation]
:doc "Group multiple signal writes. Subscribers are notified once at the end,
after all values have been updated.")
;; --------------------------------------------------------------------------
;; Tier 4: Spread + Collect — render-time attribute injection and accumulation
;;
;; `spread` is a new type: a dict of attributes that, when returned as a child
;; of an HTML element, merges its attrs onto the parent element rather than
;; rendering as content. This enables components like `~cssx/tw` to inject
;; classes and styles onto their parent from inside the child list.
;;
;; `collect!` / `collected` are render-time accumulators. Values are collected
;; into named buckets (with deduplication) during rendering and retrieved at
;; flush points (e.g. a single <style> tag for all collected CSS rules).
;; --------------------------------------------------------------------------
(declare-tier :spread :source "render.sx")
(declare-spread-primitive "make-spread"
:params (attrs)
:returns "spread"
:effects []
:doc "Create a spread value from an attrs dict. When this value appears as
a child of an HTML element, its attrs are merged onto the parent
element (class values joined, others overwritten).")
(declare-spread-primitive "spread?"
:params (x)
:returns "boolean"
:effects []
:doc "Test whether a value is a spread.")
(declare-spread-primitive "spread-attrs"
:params (s)
:returns "dict"
:effects []
:doc "Extract the attrs dict from a spread value.")
(declare-spread-primitive "collect!"
:params (bucket value)
:returns "nil"
:effects [mutation]
:doc "Add value to a named render-time accumulator bucket. Values are
deduplicated (no duplicates added). Buckets persist for the duration
of the current render pass.")
(declare-spread-primitive "collected"
:params (bucket)
:returns "list"
:effects []
:doc "Return all values collected in the named bucket during the current
render pass. Returns an empty list if the bucket doesn't exist.")
(declare-spread-primitive "clear-collected!"
:params (bucket)
:returns "nil"
:effects [mutation]
:doc "Clear a named render-time accumulator bucket. Used at flush points
after emitting collected values (e.g. after writing a <style> tag).")