host: Part C — edit the TYPE DEFINITION (its grammar) on the type's own page

"It's just more composition": a type post's edit page now shows a Type-definition editor —
each field as name:type, and each Composition field with a GRAMMAR CHECKLIST (a checkbox per
card kind = permitted, + conditional/repeater toggles). Editing it changes what the type's
instances may contain. host/blog--{is-type?, set-field-grammar!, own-field, checkbox,
grammar-form, type-def-editor}; POST /<type>/grammar reads the checklist (uniquely-named
blk-<ct> / allow-<ctrl> boxes, since form fields are single-value) → set-field-grammar!.
Shown only when host/blog--is-type? (declares fields, or subtype-of type) — a type's page has
it, an instance's doesn't.

blog 184/184, full conformance 413/413 (+ Part C tests: is-type?, set-field-grammar!, the
checklist renders, POST /grammar sets it, appears on a type page not an instance's).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-01 14:03:43 +00:00
parent 10243113dc
commit 30a23d4dae
2 changed files with 125 additions and 0 deletions

View File

@@ -971,6 +971,41 @@
(host/blog-block-add! "grm1" "body" "card-image" {"src" "x.jpg"}) ;; model add bypasses the handler guard
(> (len (host/blog--comp-violations "grm1" "body")) 0))
true)
;; -- Part C: the TYPE DEFINITION is itself displayed + edited (as composition) on the type's
;; own edit page. is-type? gates it; the grammar checklist edits what instances may contain. --
(host-bl-test "is-type? recognises type posts (article, card-image) but not a plain instance"
(begin
(host/blog-put! "plainpost" "PP" "(p \"x\")" "published")
(list (host/blog--is-type? "article") (host/blog--is-type? "card-image") (host/blog--is-type? "plainpost")))
(list true true false))
(host-bl-test "set-field-grammar! updates a Composition field's :blocks + :allow"
(begin
(host/blog-seed! "dt" "DT" "(p)" "published") (host/blog-relate! "dt" "type" "subtype-of")
(host/blog--set-fields! "dt" (list {:name "body" :type "Composition"}))
(host/blog--set-field-grammar! "dt" "body" (list "card-text") (list "each"))
(let ((f (host/blog--own-field "dt" "body")))
(list (get f :blocks) (get f :allow))))
(list (list "card-text") (list "each")))
(host-bl-test "the type-def editor renders a grammar checklist for a Composition field"
(let ((html (render-page (host/blog--type-def-editor "article"))))
(list (contains? html "Type definition") (contains? html "may contain")
(contains? html "blk-card-image") (contains? html "allow-cond")))
(list true true true true))
(host-bl-test "POST /<type>/grammar sets the grammar from the checklist"
(begin
(host/blog-seed! "dt2" "DT2" "(p)" "published") (host/blog-relate! "dt2" "type" "subtype-of")
(host/blog--set-fields! "dt2" (list {:name "body" :type "Composition"}))
(host-bl-wapp (host-bl-send "POST" "/dt2/grammar" "Bearer good"
"application/x-www-form-urlencoded" "field=body&blk-card-text=on&blk-card-heading=on&allow-cond=on"))
(let ((f (host/blog--own-field "dt2" "body")))
(list (contains? (get f :blocks) "card-text") (contains? (get f :blocks) "card-heading")
(contains? (get f :blocks) "card-image") (get f :allow))))
(list true true false (list "cond")))
;; a type's edit page SHOWS the type-def editor; an instance's does not.
(host-bl-test "the type-def editor appears on a type's edit page, not an instance's"
(list (contains? (dream-resp-body (host-bl-wapp (host-bl-send "GET" "/article/edit" "Bearer good" "" ""))) "Type definition")
(contains? (dream-resp-body (host-bl-wapp (host-bl-send "GET" "/my-first-post/edit" "Bearer good" "" ""))) "Type definition"))
(list true false))
;; the editor renders a HAND-AUTHORED composition (text/row/alt-with-text) WITHOUT falling
;; through to "(unknown block)" — every node kind gets a labelled row (the compose-demo case).
(host-bl-test "the block editor renders text/layout/inline-alt nodes (no unknown block)"