host: Slice 8 — typed scalar fields on types + the generic, type-driven form
The keystone: a type declares :fields [{name, value-type, widget}], an instance carries
:field-values, and the SAME edit form is generated from the type definitions — no per-type
code. 'The editor maps onto the types.'
8a (field model): host/blog-value-types (String/Text/URL/Int/Date/Bool -> default widget),
host/blog--widget-for (explicit > value-type default > text), host/blog-fields-of +
--set-fields! (on the type-post, like schema), --fields-summary. Article seeded with
subtitle:String + hero:URL. /meta gains a Fields column. host/blog-type-defs (the subtype-of
hierarchy = type DEFINITIONS, vs instances-of = is-a instances).
8b (instance form): host/blog-field-values-of + --set-field-values!; host/blog--fields-for-post
(union of the post's transitive types' fields, deduped); host/blog--field-inputs (one labelled
input per field, widget per value-type, pre-filled). edit-form injects the Fields section
(durable reads pre-fetched); edit-submit reads field-* inputs via host/field and stores them.
Verified live-path (ephemeral, SX_SERVING_JIT=1): relate is-a article -> field inputs appear
-> save -> values persist. Blog suite 132/132.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -602,6 +602,55 @@
|
||||
(contains? body "h1") (contains? body "related")
|
||||
(contains? body "symmetric")))
|
||||
(list true true true true true))
|
||||
|
||||
;; -- Slice 8: typed scalar fields on a type --
|
||||
(host-bl-test "fields-of reads a type's declared fields (seeded on article)"
|
||||
(map (fn (f) (get f :name)) (host/blog-fields-of "article"))
|
||||
(list "subtitle" "hero"))
|
||||
(host-bl-test "widget-for: explicit > value-type default > text fallback"
|
||||
(list (host/blog--widget-for {:name "a" :type "URL"})
|
||||
(host/blog--widget-for {:name "b" :type "Text"})
|
||||
(host/blog--widget-for {:name "c" :type "Nonsense"})
|
||||
(host/blog--widget-for {:name "d" :type "String" :widget "custom"}))
|
||||
(list "url" "textarea" "text" "custom"))
|
||||
(host-bl-test "set-fields! is idempotent + preserves the rest of the record"
|
||||
(begin
|
||||
(host/blog--set-fields! "article"
|
||||
(list {:name "subtitle" :type "String"} {:name "hero" :type "URL"}))
|
||||
(list (get (host/blog-get "article") :title) (len (host/blog-fields-of "article"))))
|
||||
(list "Article" 2))
|
||||
(host-bl-test "a type with no declared fields -> empty list"
|
||||
(host/blog-fields-of "tag") (list))
|
||||
(host-bl-test "/meta shows the article's typed fields"
|
||||
(contains? (dream-resp-body (host-bl-app (host-bl-req "/meta"))) "subtitle:String") true)
|
||||
|
||||
;; -- Slice 8b: field values + the generic, type-driven edit form --
|
||||
(host-bl-test "fields-for-post = union of the post's (transitive) types' fields"
|
||||
(begin
|
||||
(host/blog-put! "fpost" "F Post" "(article (h1 \"F\"))" "published")
|
||||
(host/blog-relate! "fpost" "article" "is-a")
|
||||
(map (fn (f) (get f :name)) (host/blog--fields-for-post "fpost")))
|
||||
(list "subtitle" "hero"))
|
||||
(host-bl-test "a post of no typed type has no fields"
|
||||
(host/blog--fields-for-post "hello") (list))
|
||||
(host-bl-test "set/get field-values round-trips on an instance"
|
||||
(begin
|
||||
(host/blog--set-field-values! "fpost" {"subtitle" "A subtitle" "hero" "http://x/y.png"})
|
||||
(list (get (host/blog-field-values-of "fpost") "subtitle")
|
||||
(get (host/blog-field-values-of "fpost") "hero")))
|
||||
(list "A subtitle" "http://x/y.png"))
|
||||
(host-bl-test "edit form renders one input per field for a typed post"
|
||||
(let ((body (dream-resp-body (host-bl-wapp (host-bl-send "GET" "/fpost/edit" "Bearer good" nil "")))))
|
||||
(list (contains? body "field-subtitle") (contains? body "field-hero") (contains? body "Fields")))
|
||||
(list true true true))
|
||||
(host-bl-test "edit-submit stores the typed field values from the form"
|
||||
(begin
|
||||
(host-bl-wapp (host-bl-send "POST" "/fpost/edit" "Bearer good"
|
||||
"application/x-www-form-urlencoded"
|
||||
"sx_content=(article+(h1+%22F%22))&field-subtitle=Saved+Sub&field-hero=http%3A%2F%2Fz%2Fq.png"))
|
||||
(list (get (host/blog-field-values-of "fpost") "subtitle")
|
||||
(get (host/blog-field-values-of "fpost") "hero")))
|
||||
(list "Saved Sub" "http://z/q.png"))
|
||||
(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