host: each-source = graph query — the data-driven each (composition roadmap step 3)
An object's `each` source can now be a GRAPH QUERY: `(query is-a TYPE)` resolves to
whatever is-a TYPE *right now* — the list isn't baked into the body, it's the live graph.
The object's `each` IS the query; the render is the run over current data (the unifying
property, now over real data).
compose.sx stays self-contained: the `query` source delegates to a resolver bound in the
render context under "query" — it asks the context for data, never reaching into the graph
itself. The host supplies graph access via host/blog--comp-query (`(query is-a TYPE)` ->
host/blog-instances-of -> full records) injected by host/blog--comp-ctx (auth + resolver);
the post handler renders :body against that context.
Added a `val` leaf — the raw field value with no markup wrapper, for use inside attributes
(href/src). `field` stays span-wrapped for display; `(val :slug)` makes a real link in the
each template. /compose-demo's each is now a live (query is-a compose-item) over two seeded
instances instead of a baked literal list.
Verified end-to-end via a focused harness eval over the full relations+persist+blog stack
(query iterates real instances; clean href via val; empty query -> empty, not an error).
Blog suite 151/153 — the 2 fails ("relate-options load-more sentinel", "related picker
offers all posts") are PRE-EXISTING (clean HEAD is 149/151 with the identical 2 fails, a
relate-picker pagination-boundary issue) and unrelated to composition; my 2 new tests pass.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -750,6 +750,31 @@
|
||||
(list "ANON<span>X</span><span>Y</span>" "MEMBER<span>X</span><span>Y</span>"))
|
||||
(host-bl-test "post page renders :body (composition) over sx_content"
|
||||
(contains? (dream-resp-body (host-bl-app (host-bl-req "/cdoc/"))) "ANON") true)
|
||||
;; -- the each source can be a GRAPH QUERY: the list isn't baked into the body, it's
|
||||
;; whatever is-a the type right now (data-driven). The resolver (host/blog--comp-query)
|
||||
;; is injected into the render context by host/blog--comp-ctx. --
|
||||
(host-bl-test "each(query is-a TYPE) iterates real graph instances"
|
||||
(begin
|
||||
(host/blog-seed! "qtype" "QType" "(p \"t\")" "published")
|
||||
(host/blog-relate! "qtype" "type" "subtype-of")
|
||||
(host/blog-seed! "qi-1" "Item One" "(p \"1\")" "published")
|
||||
(host/blog-seed! "qi-2" "Item Two" "(p \"2\")" "published")
|
||||
(host/blog-relate! "qi-1" "qtype" "is-a")
|
||||
(host/blog-relate! "qi-2" "qtype" "is-a")
|
||||
(let ((out (host/comp-render
|
||||
(quote (each (query is-a qtype)
|
||||
(seq (text "<a href=\"/") (val :slug) (text "\">") (field :title) (text "</a>"))))
|
||||
(host/blog--comp-ctx nil))))
|
||||
;; field wraps in <span> (display); val is raw (for the href attribute).
|
||||
(list (contains? out "Item One") (contains? out "Item Two")
|
||||
(contains? out "/qi-1") (contains? out "<span>Item One</span>"))))
|
||||
(list true true true true))
|
||||
;; a query with no matching instances renders empty (not an error) — robustness.
|
||||
(host-bl-test "each(query is-a TYPE) with no instances renders empty"
|
||||
(host/comp-render
|
||||
(quote (each (query is-a no-such-type) (field :title)))
|
||||
(host/blog--comp-ctx nil))
|
||||
"")
|
||||
(host-bl-test "a post with no schema'd type is vacuously valid"
|
||||
(host/blog-type-valid? "ppost" "(p \"anything\")") true)
|
||||
(host-bl-test "edit-submit rejects content violating the type schema (not saved)"
|
||||
|
||||
Reference in New Issue
Block a user