host: Slice 8c render-template-per-type + metamodel create-type form
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 24s

Closes the 'types define the UI' loop and adds the editor's create half.

8c (render template): a type declares a :template — a parameterised SX tree (stored as
source) with (field "name") placeholders that resolve to the instance's field-values at
render. host/blog-template-of / --set-template! / --instantiate (pure tree-walk) /
--typed-block (per the post's types, parse+instantiate, pre-fetched in the handler).
host/blog-post renders it above the body. Article seeded a subtitle standfirst template.
So ONE field definition now drives BOTH the edit form AND the rendered page.

create-type (metamodel editor surface 1): POST /meta/new-type creates a published post
subtype-of "type" -> appears in host/blog-type-defs / the /meta Types list, ready to be
given fields/schema/template. Guarded (unauthed -> login, not created). /meta gains a
'+ Type' form. You can now DEFINE A TYPE THROUGH THE UI.

Verified live-path: typed post's subtitle renders on its page; create 'Recipe' via the
form -> Types(4). Blog suite 140/140.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-30 12:40:27 +00:00
parent bbb8528352
commit 536bb8b76b
2 changed files with 99 additions and 1 deletions

View File

@@ -651,6 +651,39 @@
(list (get (host/blog-field-values-of "fpost") "subtitle")
(get (host/blog-field-values-of "fpost") "hero")))
(list "Saved Sub" "http://z/q.png"))
;; -- Slice 8c: render template per type (fields drive the page too) --
(host-bl-test "instantiate resolves (field name), replacing the placeholder"
(list (contains? (str (host/blog--instantiate (parse-safe "(p (field \"subtitle\"))") {"subtitle" "Hi"})) "Hi")
(contains? (str (host/blog--instantiate (parse-safe "(p (field \"x\"))") {})) "field"))
(list true false))
(host-bl-test "template-of reads the article's seeded render template"
(contains? (host/blog-template-of "article") "field") true)
(host-bl-test "typed-block renders a typed post's field values"
(begin
(host/blog--set-field-values! "fpost" {"subtitle" "My Standfirst" "hero" ""})
(contains? (str (host/blog--typed-block "fpost")) "My Standfirst"))
true)
(host-bl-test "typed-block is empty for an untyped post"
(host/blog--typed-block "hello") "")
(host-bl-test "post page renders the typed template standfirst"
(contains? (dream-resp-body (host-bl-app (host-bl-req "/fpost/"))) "My Standfirst") true)
;; -- metamodel editor: define a type through the UI (POST /meta/new-type) --
(host-bl-test "/meta has the create-type form"
(contains? (dream-resp-body (host-bl-app (host-bl-req "/meta"))) "/meta/new-type") true)
(host-bl-test "POST /meta/new-type creates a type (subtype-of type) in type-defs"
(begin
(host-bl-wapp (host-bl-send "POST" "/meta/new-type" "Bearer good"
"application/x-www-form-urlencoded" "title=Recipe"))
(list (host/blog-exists? "recipe") (contains? (host/blog-type-defs) "recipe")))
(list true true))
(host-bl-test "create-type requires auth (unauthed -> not created)"
(begin
(host-bl-wapp (host-bl-send "POST" "/meta/new-type" nil
"application/x-www-form-urlencoded" "title=Sneaky Type"))
(host/blog-exists? "sneaky-type"))
false)
(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)"