@bettercms-ai/mcp 0.72.2 → 0.73.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js
CHANGED
|
@@ -2989,6 +2989,7 @@ var PARAM_TEXT = {
|
|
|
2989
2989
|
recommendedMax: "a SOFT limit (characters, items or value): the editor warns past it, never refuses",
|
|
2990
2990
|
openMode: "object/array/group/repeater: how the value (or each row) opens: 'inline', 'drill', 'popover' or 'dialog'",
|
|
2991
2991
|
fromBuiltin: "a builtin:* id from list_components: copies it into the project; fields you send win",
|
|
2992
|
+
componentPreview: "how ONE instance reads as a ROW in a parent's slot list (a /kit schema's document-level `preview`): {title?, subtitle?, media?} each a TOP-LEVEL prop key (422 preview_key_unknown otherwise), fallback? a literal row label (\u2264120), icon? a dock icon name. null clears it",
|
|
2992
2993
|
describes: "for editors: what it is and holds; omitted: written for you",
|
|
2993
2994
|
runtimeBundle: "instead of pageId: the server-app bundle cleanup",
|
|
2994
2995
|
pageRef: "page id or path ('/de/about'); a bare slug only when one page has it",
|
|
@@ -3034,7 +3035,7 @@ var DETACHED_COPY_NOTE = "{overrides?}: a rich-text embed's separate-copy `field
|
|
|
3034
3035
|
var UNPROPPED_COPY_NOTE = "A `warnings` entry UNPROPPED_COPY names copy no prop targets, which nobody can edit: add a prop with a `target` for each leaf it lists.";
|
|
3035
3036
|
var UPDATE_PAGE_IF_MATCH_NOTE = "get_page's `optimisticVersion`: the write applies only at that version, and a 409 means the page changed since, so read it again. Send it whenever you pass blockJson, which REPLACES the whole composition.";
|
|
3036
3037
|
var SLOT_MULTIPLE_NOTE = "'slot' with config.multiple holds a list [{componentId, overrides?}] (config.maxItems, default 12, at most 50): target.path 'props.children' on a section. V2 slots, 'table' mode 'types': playbook \xA75.";
|
|
3037
|
-
var PROP_V2_NOTE = "Props take the fields' V2 keys (\xA714): 'object' = ONE {child: value} from config.fields, \u226410 levels (field_depth_exceeded); datetime|slug|email|phone|json|location|tags|rating|code|reference|multi-reference as that field (a reference names contentModelId, or polymorphic + allowedModelSlugs; page reads hydrate its entries); helpMore, advanced, recommendedMax, openMode, config.scale, ui.layout rows|chips|gallery, ui.preview.fallback, showIf notEmpty|empty.";
|
|
3038
|
+
var PROP_V2_NOTE = "Props take the fields' V2 keys (\xA714): 'object' = ONE {child: value} from config.fields, \u226410 levels (field_depth_exceeded); datetime|slug|email|phone|json|location|tags|rating|code|reference|multi-reference as that field (a reference names contentModelId, or polymorphic + allowedModelSlugs; page reads hydrate its entries); helpMore, advanced, recommendedMax, openMode, config.scale, ui.layout rows|chips|gallery, ui.preview.fallback, showIf notEmpty|empty. Send helpText: null for a prop whose /kit schema has no description (omitted, the platform writes one).";
|
|
3038
3039
|
var LAYOUT_LOCK_NOTE = "A Global field with `overridable:false` + `lockReason` (add-field `field`, update-field `patch`) refuses page overrides (LAYOUT_FIELD_NOT_OVERRIDABLE); get_layout scope page lists stored ones as `shadowed`.";
|
|
3039
3040
|
|
|
3040
3041
|
// src/entry-query.ts
|
|
@@ -3265,7 +3266,13 @@ var propUiObject = uiObject.extend({
|
|
|
3265
3266
|
collapsible: z.boolean().optional(),
|
|
3266
3267
|
layout: z.enum(["stack", "breakpoints"]).optional(),
|
|
3267
3268
|
order: z.number().int().min(0).max(99).optional()
|
|
3268
|
-
}).optional().describe("the group this prop (or sub-field) sits in. The FIRST member defines {id, label, kind?, icon?, collapsed?, description?, columns?, collapsible?, layout?, order?}; later ones send only {id}.")
|
|
3269
|
+
}).optional().describe("the group this prop (or sub-field) sits in. The FIRST member defines {id, label, kind?, icon?, collapsed?, description?, columns?, collapsible?, layout?, order?}; later ones send only {id}."),
|
|
3270
|
+
tab: z.strictObject({
|
|
3271
|
+
id: z.string().min(1).max(40),
|
|
3272
|
+
label: z.string().min(1).max(40).optional(),
|
|
3273
|
+
icon: uiObject.shape.icon,
|
|
3274
|
+
order: z.number().int().min(0).max(99).optional()
|
|
3275
|
+
}).optional().describe("TOP-LEVEL props only: the dock tab this prop sits in (a V2 kit schema `groups` entry: Content, Design, \u2026). The FIRST prop in a tab defines {id, label, icon?, order?}; later ones send only {id}. \u22648 tabs, labels \u226440 chars and unique; a group's members share one tab.")
|
|
3269
3276
|
});
|
|
3270
3277
|
var choiceOption = z.union([z.string(), z.object({ label: z.string(), value: z.string(), icon: z.string().optional(), description: z.string().optional() })]);
|
|
3271
3278
|
var fieldShape = {
|
|
@@ -3284,7 +3291,7 @@ var fieldShape = {
|
|
|
3284
3291
|
"choices when type is 'select'. A plain string, or {label, value, icon?, description?} when the stored value differs from what the editor shows (e.g. {label:'Sold out', value:'soldOut'}); icon a dock name, description one line \u2264140."
|
|
3285
3292
|
),
|
|
3286
3293
|
config: z.record(z.string(), z.unknown()).optional().describe(
|
|
3287
|
-
"per-type config: reference {contentModelId}, multi-reference {contentModelId,min,max}, array {itemType: text|number|date|image|link|richtext}, date {includeTime, timezone}, modular|slot {blockSlugs: ['quote','gallery'], minItems?, maxItems?} (slot: playbook \xA75) \u2014 blockSlugs is REQUIRED, non-empty, and each slug must name an existing kind:'block' model; richtext {inline:true, hostTag:'h2', accept:['h2','p','b','i','u','link'], features:['bold','italic','underline','link','headings'], allowMultipleParagraphs:false} \u2014 for a field bound to ONE element on the page: `hostTag` is that element's tag and `accept` must name it, so the editor's block-style control opens already showing 'Heading 2' instead of 'Paragraph'. Name exactly ONE heading level: publish refuses an <h3> value bound to an <h2> rather than nesting one heading inside the other. Keep 'p' in `accept` or the sanitiser strips a block demoted to Paragraph; limits: text/longtext {minLength,maxLength,pattern,patternMessage}, richtext {minLength,maxLength}, number {validation:{min,max}}, array {minItems,maxItems}, tags {min,max}, rating {min,max,step}"
|
|
3294
|
+
"per-type config: reference {contentModelId}, multi-reference {contentModelId,min,max}, repeater {minItems, maxItems} (the list's item limits; the editor shows 'max N' and stops Add there), array {itemType: text|number|date|image|link|richtext}, date {includeTime, timezone}, modular|slot {blockSlugs: ['quote','gallery'], minItems?, maxItems?} (slot: playbook \xA75) \u2014 blockSlugs is REQUIRED, non-empty, and each slug must name an existing kind:'block' model; richtext {inline:true, hostTag:'h2', accept:['h2','p','b','i','u','link'], features:['bold','italic','underline','link','headings'], allowMultipleParagraphs:false} \u2014 for a field bound to ONE element on the page: `hostTag` is that element's tag and `accept` must name it, so the editor's block-style control opens already showing 'Heading 2' instead of 'Paragraph'. Name exactly ONE heading level: publish refuses an <h3> value bound to an <h2> rather than nesting one heading inside the other. Keep 'p' in `accept` or the sanitiser strips a block demoted to Paragraph; limits: text/longtext {minLength,maxLength,pattern,patternMessage}, richtext {minLength,maxLength}, number {validation:{min,max}}, array {minItems,maxItems}, tags {min,max}, rating {min,max,step}"
|
|
3288
3295
|
),
|
|
3289
3296
|
fields: z.array(z.lazy(() => fieldObject)).optional().describe(
|
|
3290
3297
|
"NESTED child fields \u2014 REQUIRED for type 'group' (one nested object, a Non-Repeatable Zone like blog_hero \u2192 heading, description, hero_image) and type 'repeater' (a repeatable array of such objects, a Repeatable Zone / section-list like testimonials \u2192 quote, author). A section with repeating items is a 'repeater'; a fixed grouped block is a 'group'. Recurse to any depth \u2014 do NOT flatten zones into separate top-level fields."
|
|
@@ -3455,7 +3462,8 @@ function toField(f) {
|
|
|
3455
3462
|
return { ...base, type: "array", config: { zones: { nonRepeatable: toFields(f.fields) } } };
|
|
3456
3463
|
}
|
|
3457
3464
|
if (f.type === "repeater") {
|
|
3458
|
-
|
|
3465
|
+
const { minItems, maxItems } = f.config ?? {};
|
|
3466
|
+
return { ...base, type: "array", config: { zones: { repeatable: { fields: toFields(f.fields), ...minItems !== void 0 ? { minItems } : {}, ...maxItems !== void 0 ? { maxItems } : {} } } } };
|
|
3459
3467
|
}
|
|
3460
3468
|
if (f.type === "array" && f.config && typeof f.config === "object" && "zones" in f.config) {
|
|
3461
3469
|
const zones = f.config.zones ?? {};
|
|
@@ -3488,8 +3496,8 @@ var UPDATE_MODEL_DESCRIPTION = "Edit a content model: rename it, change descript
|
|
|
3488
3496
|
var FIELD_REMOVAL_NOTE = "top-level field keys (from get_content_model) to DELETE from this model with every entry's value for them; needs allowFieldRemoval: true. An unknown key refuses the whole call. Never for a rename: rename the label instead.";
|
|
3489
3497
|
var ALLOW_FIELD_REMOVAL_NOTE = "true = the user accepts that removeFields discards those fields' data in every entry. There is no dry run on this route: confirm first.";
|
|
3490
3498
|
var MODEL_IF_MATCH_NOTE = "optimisticVersion from get_content_model (with pageId: get_page backingModel). The write applies only at that version; a 409 version-conflict means it changed since: read it again and re-apply, never retry blindly. REQUIRED with field*, override or a fieldset kind/tab/icon/collapsed (428 without); otherwise, omitted, the last write wins.";
|
|
3491
|
-
var COMPONENT_IF_MATCH_NOTE = "optimisticVersion from get_component. The write applies only at that version; a 409 version-conflict means it changed since: read it again and re-apply. REQUIRED with prop*, override or a prop ui icon/itemLabel/group (428 without).";
|
|
3492
|
-
var PROP_UI_ARG_NOTE = "top-level prop key \u2192 ui patch, the same vocabulary as a prop's `ui
|
|
3499
|
+
var COMPONENT_IF_MATCH_NOTE = "optimisticVersion from get_component. The write applies only at that version; a 409 version-conflict means it changed since: read it again and re-apply. REQUIRED with prop*, override or a prop ui icon/itemLabel/group/tab (428 without).";
|
|
3500
|
+
var PROP_UI_ARG_NOTE = "top-level prop key \u2192 ui patch, the same vocabulary as a prop's `ui` (group and tab included): each key merges, a null key clears it, null clears the whole ui. Patches keep every other prop as stored; sending `props` instead REPLACES the whole array, so echo each prop's stored `ui` (its `ui.group` AND `ui.tab`) or the prop leaves its tab";
|
|
3493
3501
|
var OVERRIDE_NOTE = "replace chrome a person set (otherwise 409 ui_human_owned names those paths). Send it only on the user's word.";
|
|
3494
3502
|
var FIELD_UI_ARG_NOTE = "path \u2192 ui patch, a field's `ui` vocabulary: each key merges, a null key clears it, null clears the whole ui";
|
|
3495
3503
|
var MODEL_UPDATE_KEYS = [
|
|
@@ -3657,7 +3665,7 @@ var withProjectId = (def) => ({
|
|
|
3657
3665
|
return currentProject.run(arg("projectId"), () => currentBranch.run(arg("branchId"), () => def.handler(args)));
|
|
3658
3666
|
}
|
|
3659
3667
|
});
|
|
3660
|
-
var autoHelpNote = (args) => typeof args.helpText === "string" && args.helpText.trim() ? "" : " No helpText was sent, so the platform wrote it (helpSource: auto); send helpText with update_field to write your own.";
|
|
3668
|
+
var autoHelpNote = (args) => args.helpText === null || typeof args.helpText === "string" && args.helpText.trim() ? "" : " No helpText was sent, so the platform wrote it (helpSource: auto); send helpText with update_field to write your own.";
|
|
3661
3669
|
function buildToolDefs(deps) {
|
|
3662
3670
|
async function withClient(fn) {
|
|
3663
3671
|
const token = await deps.auth.getAccessToken();
|
|
@@ -3964,6 +3972,7 @@ function buildToolDefs(deps) {
|
|
|
3964
3972
|
// 'select' — required, choices
|
|
3965
3973
|
// 'group'/'table'/'object' sub-shape. Typed loosely here (the server validates the full
|
|
3966
3974
|
// recursive shape, 10 levels at most) so a deep tree needs no Zod mirror in the client.
|
|
3975
|
+
// A sub-field may be a 'slot' (its value at its key in the object or row), with the slot keys above.
|
|
3967
3976
|
fields: z.array(z.record(z.string(), z.unknown())).optional(),
|
|
3968
3977
|
min: z.number().optional(),
|
|
3969
3978
|
// 'number'
|
|
@@ -3975,7 +3984,7 @@ function buildToolDefs(deps) {
|
|
|
3975
3984
|
// override that omits it still saves — so it is a marker the dashboard renders and
|
|
3976
3985
|
// get_site_composition counts, never a refusal. Parity: componentPropDefSchema on the server.
|
|
3977
3986
|
required: z.boolean().optional().describe("mark the field required in the editor. An editor HINT: the API still accepts a placement that leaves it empty, and get_site_composition is what reports one."),
|
|
3978
|
-
helpText: z.string().max(200).optional().describe("hint shown under the control, muted (\u2264 200 chars)"),
|
|
3987
|
+
helpText: z.string().max(200).nullable().optional().describe("hint shown under the control, muted (\u2264 200 chars). null = no help on purpose (a /kit schema with no description); omitted = the platform writes it"),
|
|
3979
3988
|
placeholder: z.string().max(120).optional().describe("ghost text inside a text-ish control (\u2264 120 chars)"),
|
|
3980
3989
|
// Stored markers on a prop: nothing refuses a prop value for them. Declared so an echo keeps them.
|
|
3981
3990
|
readOnly: z.union([z.literal(true), z.literal("developer")]).optional(),
|
|
@@ -4064,6 +4073,13 @@ function buildToolDefs(deps) {
|
|
|
4064
4073
|
"section family, e.g. 'Hero' \u2014 set this with a library category to make the component selectable in the page editor's 'Add a section' picker. Components sharing one are layout variants of one section."
|
|
4065
4074
|
);
|
|
4066
4075
|
const allowedOn = z.array(z.string().regex(/^(\*|slug:[a-z0-9-]+|type:(singleton|dynamic|template))$/)).max(100).describe("placement allowlist: *, slug:<page-slug>, and/or type:singleton|dynamic|template");
|
|
4076
|
+
const componentPreviewArg = z.strictObject({
|
|
4077
|
+
title: z.string().min(1).max(64).optional(),
|
|
4078
|
+
subtitle: z.string().min(1).max(64).optional(),
|
|
4079
|
+
media: z.string().min(1).max(64).optional(),
|
|
4080
|
+
fallback: z.string().min(1).max(120).optional(),
|
|
4081
|
+
icon: dockIconArg.optional()
|
|
4082
|
+
}).nullable().describe(PARAM_TEXT.componentPreview);
|
|
4067
4083
|
const createComponentInput = z.object({
|
|
4068
4084
|
name: z.string().min(1),
|
|
4069
4085
|
slug: slug.describe("url-safe unique slug (lowercase letters/numbers/hyphens)"),
|
|
@@ -4075,6 +4091,7 @@ function buildToolDefs(deps) {
|
|
|
4075
4091
|
projectId: z.string().nullable().optional().describe("owning project id; null creates a workspace-wide global component"),
|
|
4076
4092
|
allowedOn: allowedOn.optional(),
|
|
4077
4093
|
description: z.string().optional().describe(PARAM_TEXT.describes),
|
|
4094
|
+
preview: componentPreviewArg.optional(),
|
|
4078
4095
|
blockJson: z.array(blockObject).default([]).describe("the component's block tree"),
|
|
4079
4096
|
props: z.array(componentPropObject).default([]).describe(
|
|
4080
4097
|
"overridable fields. DECLARE A PROP \u2014 WITH A `target` \u2014 for every string, link and image a marketer will ever touch: only the leaves a prop targets are click-to-edit. Each one may carry `ui` to say how its control presents in the inspector \u2014 a collapsed group, what titles and thumbnails a table's rows, whether that table's rows can be dragged at all. 'button' is ONE unit {label, href, variant?, size?, arrow?, icon?, newTab?} on a button block's 'props', never a text prop plus a url prop. 'link' an href, 'date' YYYY-MM-DD, 'color' a hex, 'file' {url, name?}, 'icon' an icon name or an image, 'multiselect' a string[] from config.options (required). " + SLOT_MULTIPLE_NOTE + " " + PROP_V2_NOTE
|
|
@@ -4091,14 +4108,16 @@ function buildToolDefs(deps) {
|
|
|
4091
4108
|
variantOf: z.string().min(1).max(255).optional().describe("see create_component"),
|
|
4092
4109
|
allowedOn: allowedOn.optional().describe("REPLACES the placement allowlist"),
|
|
4093
4110
|
description: z.string().optional(),
|
|
4111
|
+
preview: componentPreviewArg.optional(),
|
|
4094
4112
|
blockJson: z.array(blockObject).optional().describe("REPLACES the block tree"),
|
|
4095
4113
|
props: z.array(componentPropObject).optional().describe(
|
|
4096
4114
|
"REPLACES the prop array \u2014 include every prop you want to keep, WITH its stored `ui`, or that prop's authoring chrome is dropped along with the prop. Read get_component first."
|
|
4097
4115
|
),
|
|
4098
4116
|
propUi: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe(PROP_UI_ARG_NOTE),
|
|
4099
4117
|
propGroup: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe("prop key \u2192 its ui.group ({id, label?, kind?, icon?, collapsed?}), null takes it out"),
|
|
4118
|
+
propTab: z.record(z.string(), z.record(z.string(), z.unknown()).nullable()).optional().describe("prop key \u2192 its ui.tab ({id, label?, icon?, order?}), the V2 schema group it sits in. The FIRST top-level prop in a tab defines {id, label, icon?, order?}, later ones send {id}; \u22648 tabs; one group's members share one tab. null takes it out"),
|
|
4100
4119
|
propLabel: z.record(z.string(), z.string().min(1).max(255)).optional().describe("prop key \u2192 label"),
|
|
4101
|
-
propHelpText: z.record(z.string(), z.string().max(200).nullable()).optional().describe("prop key \u2192 help text
|
|
4120
|
+
propHelpText: z.record(z.string(), z.string().max(200).nullable()).optional().describe("prop key \u2192 help text; null = no help on purpose (stays empty, never refilled)"),
|
|
4102
4121
|
override: z.boolean().optional().describe(OVERRIDE_NOTE),
|
|
4103
4122
|
ifMatch: z.number().int().min(0).optional().describe(COMPONENT_IF_MATCH_NOTE),
|
|
4104
4123
|
confirmed: z.boolean().optional().describe(PARAM_TEXT.confirmedInput)
|
|
@@ -6521,8 +6540,11 @@ Then declare what an editor may change, via \`props\`:
|
|
|
6521
6540
|
]
|
|
6522
6541
|
|
|
6523
6542
|
Each prop also takes \`required\`, \`helpText\` (\u2264 200 chars) and \`placeholder\` (\u2264 120), which the
|
|
6524
|
-
dashboard's prop editor renders.
|
|
6543
|
+
dashboard's prop editor renders. \`helpText\` is three-state: a string is yours, \`null\` is no help on
|
|
6544
|
+
purpose (send it for a prop whose /kit schema has no description; nothing refills it), and omitted
|
|
6545
|
+
lets the platform write one. They are HINTS: nothing on the write path refuses a placement
|
|
6525
6546
|
that leaves a required prop empty, so \`get_site_composition\` is what reports one.
|
|
6547
|
+
A /kit schema's document-level \`preview\` \u2192 the component's \`preview\` ({title?, subtitle?, media?} are top-level prop keys, plus \`fallback?\`, \`icon?\`); slot rows read it.
|
|
6526
6548
|
|
|
6527
6549
|
\u{1F534} **On a components-first page, \`props\` is the ONLY editing surface.** Click-to-edit binds
|
|
6528
6550
|
\`heading\`/\`text\`/\`button\`/\`image\` blocks; it does **not** bind a \`component\` block, because
|
|
@@ -6574,7 +6596,12 @@ A \`slot\` field (V2 blocks inside a block) takes the same \`config.blockSlugs\`
|
|
|
6574
6596
|
but stores each child INLINE: \`[{ _key, _type: '<block slug>', _variant?, _settings?, ...values }]\`,
|
|
6575
6597
|
its values judged by its block's fields. A component's \`slot\` prop with \`config.multiple\`
|
|
6576
6598
|
takes the same child shape with \`_type\` the COMPONENT id (no \`_variant\`: a variant is its own
|
|
6577
|
-
component). A
|
|
6599
|
+
component). A \`slot\` may also be a SUB-FIELD: inside a \`group\` / \`object\` prop's
|
|
6600
|
+
\`config.fields\` or a \`table\` row (an item type's \`fields\` too), with the slot prop's
|
|
6601
|
+
config keys (\`componentIds\`, \`multiple\`, \`minItems\`/\`maxItems\`, \`slotSettings\`). Its value
|
|
6602
|
+
sits at its key in the object or row, \`{ componentId, overrides? }\` or a list with
|
|
6603
|
+
\`config.multiple\`, judged like a slot prop's: the same allowlist, depth cap and cycle refusal.
|
|
6604
|
+
A list of several item shapes on a component is a \`table\` prop with
|
|
6578
6605
|
\`config.mode: 'types'\` and \`config.itemTypes: [{ name, label?, fields }]\`, rows
|
|
6579
6606
|
\`[{ _key?, _type: '<name>', ...values }]\`.
|
|
6580
6607
|
|
|
@@ -7030,8 +7057,10 @@ hints the dashboard renders \u2014 they are NOT enforced at write time, so an ov
|
|
|
7030
7057
|
required prop still saves; \`get_site_composition\` is what reports those. Use \`select\` with
|
|
7031
7058
|
\`config.options\` for layout / theme / alignment / spacing variants that change only styling,
|
|
7032
7059
|
and a SEPARATE variant component when the markup differs. Repeatable list \u2192 \`table\`; fixed
|
|
7033
|
-
cluster \u2192 \`group\`. Nesting
|
|
7034
|
-
\`table
|
|
7060
|
+
cluster \u2192 \`group\`. Nesting goes up to 10 levels counting the prop itself. A \`table\` row may hold a
|
|
7061
|
+
\`table\` (rows), a data table (\`config.mode: "data"\`) or a polymorphic list
|
|
7062
|
+
(\`config.mode: "types"\`), so a size guide's rows can each carry a chart and a multi-step
|
|
7063
|
+
form's steps can each carry their fields. Deeper is refused (\`field_depth_exceeded\`).
|
|
7035
7064
|
|
|
7036
7065
|
**5. Design, responsiveness and accessibility.** \`componentize_sections\` and \`npx
|
|
7037
7066
|
@bettercms-ai/convert --componentize\` move the section's markup VERBATIM \u2014 classes, responsive
|
|
@@ -7396,8 +7425,13 @@ them \u2014 except on a \`slot\`, whose children belong to whichever component f
|
|
|
7396
7425
|
cannot be resolved at save time. \`update_component\` REPLACES the whole \`props\` array, so echo
|
|
7397
7426
|
back each prop's stored \`ui\` or you delete it along with the prop. Props have no fieldsets, so a
|
|
7398
7427
|
prop joins a group with \`ui.group\`: the FIRST prop of a group defines \`{ id, label, kind?, icon?,
|
|
7399
|
-
collapsed? }\` (kind \`group\` or \`row\`, never
|
|
7400
|
-
|
|
7428
|
+
collapsed? }\` (kind \`group\` or \`row\`; a tab is \`ui.tab\`, never a group kind), and every later prop
|
|
7429
|
+
sends \`{ id }\` only. A prop joins a TAB with \`ui.tab\` the same way: the FIRST top-level prop in a tab
|
|
7430
|
+
defines \`{ id, label, icon?, order? }\`, later props send \`{ id }\`; at most 8 tabs, labels \u226440 characters
|
|
7431
|
+
and unique; top-level props only (never a sub-field); all members of one \`ui.group\` share one tab. A V2
|
|
7432
|
+
kit entry's schema \`groups\` and each field's \`group\` map 1:1 to \`ui.tab\`, so its \`design\` group is a
|
|
7433
|
+
tab named Design holding the theme, sticky and similar props. A FIELD never takes \`ui.group\` or
|
|
7434
|
+
\`ui.tab\`; it joins a fieldset with \`fieldsetId\`.
|
|
7401
7435
|
|
|
7402
7436
|
\`\`\`jsonc
|
|
7403
7437
|
{ "key": "slides", "label": "Slides", "type": "table",
|
|
@@ -7466,13 +7500,13 @@ Importance orders a screen: required fields first, then array order.
|
|
|
7466
7500
|
| A star rating | \`type: 'rating'\`: a number from 0 to \`config.max\` (5), \`config.step\` 1 or 0.5 (\`rating_value_invalid\`) |
|
|
7467
7501
|
| A code snippet | \`type: 'code'\`: \`{ language?, code }\` or a string; \`config.language\` default, \`config.languages\` allowlist (\`code_value_invalid\`) |
|
|
7468
7502
|
| "Add question" instead of "Add item" | \`ui.itemLabel: 'Question'\` (V2 \`itemNoun\`; there is no itemNoun key) |
|
|
7469
|
-
| The same section as a component (the dock edits a page section through its props) | the same keys on its \`props\`: \`type: 'object'\` with \`config.fields\` (\`group\` and \`table\` too, 10 levels), \`datetime\`, \`slug\`, \`email\`, \`phone\`, \`json\`, \`location\`, \`tags\`, \`rating\`, \`code\`, plus the two CMS-item types (\`reference\`, \`multi-reference\`: \`config.contentModelId\` or \`polymorphic\` + \`allowedModelSlugs\`; page reads hydrate them, a text slot shows the title), and every key above except fieldsets (props group with \`ui.group\`) |
|
|
7503
|
+
| The same section as a component (the dock edits a page section through its props) | the same keys on its \`props\`: \`type: 'object'\` with \`config.fields\` (\`group\` and \`table\` too, 10 levels), \`datetime\`, \`slug\`, \`email\`, \`phone\`, \`json\`, \`location\`, \`tags\`, \`rating\`, \`code\`, plus the two CMS-item types (\`reference\`, \`multi-reference\`: \`config.contentModelId\` or \`polymorphic\` + \`allowedModelSlugs\`; page reads hydrate them, a text slot shows the title), and every key above except fieldsets (props group with \`ui.group\` and tab with \`ui.tab\`) |
|
|
7470
7504
|
|
|
7471
7505
|
An unknown \`openMode\` is refused with \`openMode_invalid\`, one on a leaf \`openMode_leaf\`; a bad \`config.scale\` with \`scale_invalid\`; more than 10 nested field levels with \`field_depth_exceeded\`. update_field sets and clears (null) helpMore, advanced, recommendedMax and openMode on an existing field.
|
|
7472
7506
|
|
|
7473
7507
|
**Tabs.** A \`kind: 'tab'\` fieldset is a tab:
|
|
7474
|
-
- at most **3** tabs, names at most
|
|
7475
|
-
- top-level fields only: tabs never nest
|
|
7508
|
+
- at most **3** tabs, names at most 40 characters and unique;
|
|
7509
|
+
- top-level fields only: tabs never nest (a component's props take tabs through \`ui.tab\`, its own rules);
|
|
7476
7510
|
- untabbed fields stay PINNED above the strip;
|
|
7477
7511
|
- a group or row fieldset joins a tab with \`tab: '<tab id>'\`; groups in a non-first tab default open;
|
|
7478
7512
|
- with fewer than 2 non-empty tabs no strip renders, so a tab for one fieldset is noise.
|
|
@@ -7491,7 +7525,7 @@ An unknown \`openMode\` is refused with \`openMode_invalid\`, one on a leaf \`op
|
|
|
7491
7525
|
\`fieldLabel\`, \`fieldHelpText\` (keyed by dotted key path, \`'hero.title'\`) and \`fieldsets\`. A page's
|
|
7492
7526
|
schema: the same call with \`pageId\` instead of \`modelId\`, and the version from \`get_page\`'s
|
|
7493
7527
|
\`backingModel\`.
|
|
7494
|
-
- A component's props: \`update_component\` with \`propUi\`, \`propGroup\`, \`propLabel\`, \`propHelpText\` (keyed by prop key).
|
|
7528
|
+
- A component's props: \`update_component\` with \`propUi\`, \`propGroup\`, \`propTab\`, \`propLabel\`, \`propHelpText\` (keyed by prop key).
|
|
7495
7529
|
Name props from the code (\`plan.cents\` \u2192 Cents); help text guides ("Whole number, no currency symbol."), never repeats
|
|
7496
7530
|
the value; repeated cards are one \`table\` prop. An icon (\`config.media: "icon"\`) is content: group it, never prune it.
|
|
7497
7531
|
They write the DRAFT props; no publish is needed for the editor to show them.
|
|
@@ -7504,6 +7538,8 @@ An unknown \`openMode\` is refused with \`openMode_invalid\`, one on a leaf \`op
|
|
|
7504
7538
|
- **409** \`ui_human_owned\` \u2014 a person shaped those paths. Leave them, or send \`override: true\`, and
|
|
7505
7539
|
only when the user said so.
|
|
7506
7540
|
- \`tab_unknown\`, \`tab_nested\`, \`tab_props\`, \`tab_max\`, \`tab_name\`, \`tab_depth\` \u2014 the tab rules above.
|
|
7541
|
+
- \`prop_tab_unknown\`, \`prop_tab_redefined\`, \`prop_group_split_tabs\` \u2014 a prop's \`ui.tab\` rules (\`tab_max\`,
|
|
7542
|
+
\`tab_name\` and \`tab_nested\` too); \`tab_props\` is a \`ui.group\` with \`kind: 'tab'\` \u2014 use \`ui.tab\`.
|
|
7507
7543
|
- \`fieldset_unknown\` / \`fieldset_orphan\` \u2014 a \`fieldsetId\` naming no fieldset, or a list that drops a
|
|
7508
7544
|
fieldset its fields still name.
|
|
7509
7545
|
- \`row_member_type\`, \`row_member_count\`, \`row_member_control\` \u2014 the row rule.
|
|
@@ -7629,7 +7665,7 @@ shape, then ask me, once and batched). Icon names are in bettercms://icons/dock.
|
|
|
7629
7665
|
- fields that exist: \`update_content_model\` fieldUi / fieldFieldset / fieldOrder / fieldLabel /
|
|
7630
7666
|
fieldHelpText by field path, plus \`fieldsets\`. A singleton page: send \`pageId\` instead of
|
|
7631
7667
|
modelId, with its backingModel's version;
|
|
7632
|
-
- component props: \`update_component\` propUi / propGroup / propLabel / propHelpText;
|
|
7668
|
+
- component props: \`update_component\` propUi / propGroup / propTab / propLabel / propHelpText;
|
|
7633
7669
|
- a NEW field: ui / helpText / showIf in the same \`add_field\` call, with \`after\`.
|
|
7634
7670
|
A 409 \`ui_human_owned\` names chrome a person set: leave it, or ask me and send \`override: true\`
|
|
7635
7671
|
only on my word.
|
|
@@ -7751,7 +7787,7 @@ Read \`bettercms://playbook/schema\` \xA712 first \u2014 this is the short form
|
|
|
7751
7787
|
4. **Fields** = \`props\`: text, richtext, image, url, boolean, number, select, group, table, slot.
|
|
7752
7788
|
Always a \`defaultValue\`; \`required\` / \`helpText\` / \`placeholder\` are editor hints, not write
|
|
7753
7789
|
-time enforcement. \`select\` for styling-only variants, a separate component when the markup
|
|
7754
|
-
differs. Nesting
|
|
7790
|
+
differs. Nesting goes up to 10 levels; a \`table\` row may hold a data table or a polymorphic list.
|
|
7755
7791
|
5. **Quality.** The componentize lane and \`npx @bettercms-ai/convert --componentize\` move markup
|
|
7756
7792
|
VERBATIM \u2014 classes, responsive utilities, aria attributes, alt text all survive. Never
|
|
7757
7793
|
restyle. Dry-run first.
|