Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 35s
The cards-as-OBJECTS model (plans/composition-objects.md): an object's :body is a tiny UI language over content-addressed object refs; the render-fold is its interpreter. Four combinators — seq (sequence) / row,grid (layout/par) / alt+when (conditional/or) / each (iteration/loop) — plus field/text/card leaves, ref (transclude), and tmpl (recursion). The two fundamentals designed IN: (1) recursion via self-referential named templates (tmpl) + each over (children) + a depth guard — renders trees (verified: a nested type hierarchy -> [Types[Article][Card[Image][Callout]]]); (2) the context is an extensible ENVIRONMENT — reads it, extends it (:item, :depth) — so behaviour (Slice 9) and reactivity (signals) plug in via the context with no new combinators. and/or/choice fall out of one axis ( on forks) x the container strategy (render-all vs render-first), so Alt isn't a new node — it's 'first'. The unifying property, proven: the object's CID is its DEFINITION (query/template/every when-variant); render is the EXECUTION (which items/branch/context). One object renders two ways by context (anon -> 'Please log in', authed -> 'Members area'). Render-fold and the Slice-9 behaviour interpreter are the same shape — interpreters over content-addressed objects. lib/host/compose.sx is self-contained (no blog deps); verified via sx_eval (every combinator + a recursive tree + a full composed doc across two contexts). Roadmap: wire :body into host/blog-render, each-source=graph-query, live context, Lexical->card-objects import, block editor. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
99 lines
4.8 KiB
Plaintext
99 lines
4.8 KiB
Plaintext
;; lib/host/compose.sx — the composition / object render-fold (plans/composition-objects.md).
|
|
;;
|
|
;; An object's :body is a composition node — a tiny UI language over object refs. The
|
|
;; render-fold below is its interpreter. Four combinators (seq/row/alt/each) + leaves
|
|
;; (field/text/card) + ref + recursion (tmpl). The context is an EXTENSIBLE ENVIRONMENT:
|
|
;; `when` reads it, `each` extends it (:item, :depth). Same predicate set as the type
|
|
;; guards. The object's CID is its DEFINITION; render is the EXECUTION (per context+data).
|
|
;; Self-contained (no blog deps) so the model can be proven in isolation.
|
|
|
|
;; ── predicates for `when` (over the context environment) ────────────
|
|
(define host/comp--pred?
|
|
(fn (pred ctx)
|
|
(let ((op (str (first pred))))
|
|
(cond
|
|
((= op "has") (not (nil? (get ctx (str (first (rest pred)))))))
|
|
((= op "eq") (= (str (get ctx (str (first (rest pred))))) (str (first (rest (rest pred))))))
|
|
((= op "not") (not (host/comp--pred? (first (rest pred)) ctx)))
|
|
(else false)))))
|
|
|
|
;; the value of a leaf (field): the current :item's key, else the context's key.
|
|
(define host/comp--field
|
|
(fn (k ctx)
|
|
(let ((item (get ctx "item")) (key (str k)))
|
|
(if (and item (not (nil? (get item key))))
|
|
(str (get item key))
|
|
(str (or (get ctx key) ""))))))
|
|
|
|
;; the source collection for `each`: literal items, the :item's :children (trees), or a
|
|
;; named list field on the :item. (A graph-query source is wiring step 3, plan roadmap.)
|
|
(define host/comp--source
|
|
(fn (src ctx)
|
|
(let ((op (str (first src))) (item (get ctx "item")))
|
|
(cond
|
|
((= op "items") (rest src))
|
|
((= op "children") (if item (or (get item "children") (list)) (list)))
|
|
((= op "field") (if item (or (get item (str (first (rest src)))) (list)) (list)))
|
|
(else (list))))))
|
|
|
|
;; ── template registry (recursion: a template may reference itself by name) ──
|
|
(define host/comp--tmpls (dict))
|
|
(define host/comp--def-tmpl! (fn (name node) (dict-set! host/comp--tmpls name node)))
|
|
|
|
;; ── the render-fold (the interpreter) ───────────────────────────────
|
|
(define host/comp--render-all
|
|
(fn (nodes ctx) (reduce (fn (acc n) (str acc (host/comp--render n ctx))) "" nodes)))
|
|
|
|
;; alt: render the FIRST branch whose `when` holds (or `else`) — recursive first-match so
|
|
;; a branch that legitimately renders empty isn't skipped.
|
|
(define host/comp--alt-pick
|
|
(fn (branches ctx)
|
|
(if (empty? branches)
|
|
""
|
|
(let ((br (first branches)) (bh (str (first (first branches)))))
|
|
(cond
|
|
((= bh "else") (host/comp--render (first (rest br)) ctx))
|
|
((= bh "when") (if (host/comp--pred? (first (rest br)) ctx)
|
|
(host/comp--render (first (rest (rest br))) ctx)
|
|
(host/comp--alt-pick (rest branches) ctx)))
|
|
(else (host/comp--alt-pick (rest branches) ctx)))))))
|
|
|
|
;; each: eval source -> items; render template per item with :item bound + :depth+1
|
|
;; (depth guard backstops runaway recursion; trees terminate naturally on empty source).
|
|
(define host/comp--each
|
|
(fn (src tmpl ctx)
|
|
(let ((depth (or (get ctx "depth") 0)))
|
|
(if (> depth 40)
|
|
"<em>(max depth)</em>"
|
|
(reduce
|
|
(fn (acc item)
|
|
(str acc (host/comp--render tmpl (merge ctx {"item" item "depth" (+ depth 1)}))))
|
|
"" (host/comp--source src ctx))))))
|
|
|
|
;; card leaf (proof: a labelled box; in the host this renders via the card-type's :template).
|
|
(define host/comp--card
|
|
(fn (ctype fields)
|
|
(str "<div class=\"card card-" ctype "\">"
|
|
(reduce (fn (acc k) (str acc "<b>" k ":</b> " (str (get fields k)) " ")) "" (keys fields))
|
|
"</div>")))
|
|
|
|
(define host/comp--render
|
|
(fn (node ctx)
|
|
(if (not (= (type-of node) "list"))
|
|
(str node)
|
|
(let ((h (str (first node))) (args (rest node)))
|
|
(cond
|
|
((= h "seq") (host/comp--render-all args ctx))
|
|
((= h "row") (str "<div class=\"row\" style=\"display:flex;gap:1em\">" (host/comp--render-all args ctx) "</div>"))
|
|
((= h "grid") (str "<div class=\"grid\" style=\"display:grid;gap:1em\">" (host/comp--render-all args ctx) "</div>"))
|
|
((= h "alt") (host/comp--alt-pick args ctx))
|
|
((= h "each") (host/comp--each (first args) (first (rest args)) ctx))
|
|
((= h "field") (str "<span>" (host/comp--field (first args) ctx) "</span>"))
|
|
((= h "text") (str (first args)))
|
|
((= h "card") (host/comp--card (str (first args)) (first (rest args))))
|
|
((= h "tmpl") (host/comp--render (get host/comp--tmpls (str (first args))) ctx))
|
|
(else ""))))))
|
|
|
|
;; public entry: render a composition node against a context environment.
|
|
(define host/comp-render (fn (node ctx) (host/comp--render node ctx)))
|