@caelo-cms/shared 0.10.72 → 0.11.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/ai-tools.d.ts +103 -142
- package/dist/ai-tools.d.ts.map +1 -1
- package/dist/ai-tools.js +79 -167
- package/dist/ai-tools.js.map +1 -1
- package/dist/build-page.d.ts +115 -35
- package/dist/build-page.d.ts.map +1 -1
- package/dist/build-page.js +31 -12
- package/dist/build-page.js.map +1 -1
- package/dist/content.d.ts +214 -102
- package/dist/content.d.ts.map +1 -1
- package/dist/content.js +219 -84
- package/dist/content.js.map +1 -1
- package/dist/css-scope-scan.js +1 -1
- package/dist/css-var-aliases.d.ts +20 -0
- package/dist/css-var-aliases.d.ts.map +1 -0
- package/dist/css-var-aliases.js +95 -0
- package/dist/css-var-aliases.js.map +1 -0
- package/dist/css-var-scan.d.ts +0 -16
- package/dist/css-var-scan.d.ts.map +1 -1
- package/dist/css-var-scan.js +24 -0
- package/dist/css-var-scan.js.map +1 -1
- package/dist/edit-markers.d.ts +41 -5
- package/dist/edit-markers.d.ts.map +1 -1
- package/dist/edit-markers.js +47 -50
- package/dist/edit-markers.js.map +1 -1
- package/dist/escaped-line-breaks.d.ts +44 -0
- package/dist/escaped-line-breaks.d.ts.map +1 -0
- package/dist/escaped-line-breaks.js +203 -0
- package/dist/escaped-line-breaks.js.map +1 -0
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -1
- package/dist/index.js.map +1 -1
- package/dist/interactions.js +6 -6
- package/dist/interactions.js.map +1 -1
- package/dist/link-canonicalise.d.ts.map +1 -1
- package/dist/link-canonicalise.js +7 -1
- package/dist/link-canonicalise.js.map +1 -1
- package/dist/link-refs.d.ts +1 -1
- package/dist/link-refs.js +3 -3
- package/dist/lists/address.d.ts +34 -0
- package/dist/lists/address.d.ts.map +1 -0
- package/dist/lists/address.js +43 -0
- package/dist/lists/address.js.map +1 -0
- package/dist/lists/default-types.d.ts +22 -0
- package/dist/lists/default-types.d.ts.map +1 -0
- package/dist/lists/default-types.js +127 -0
- package/dist/lists/default-types.js.map +1 -0
- package/dist/lists/fields.d.ts +152 -0
- package/dist/lists/fields.d.ts.map +1 -0
- package/dist/lists/fields.js +259 -0
- package/dist/lists/fields.js.map +1 -0
- package/dist/lists/index.d.ts +14 -0
- package/dist/lists/index.d.ts.map +1 -0
- package/dist/lists/index.js +15 -0
- package/dist/lists/index.js.map +1 -0
- package/dist/lists/item-attr.d.ts +26 -0
- package/dist/lists/item-attr.d.ts.map +1 -0
- package/dist/lists/item-attr.js +31 -0
- package/dist/lists/item-attr.js.map +1 -0
- package/dist/lists/items.d.ts +105 -0
- package/dist/lists/items.d.ts.map +1 -0
- package/dist/lists/items.js +304 -0
- package/dist/lists/items.js.map +1 -0
- package/dist/lists/list-type.d.ts +401 -0
- package/dist/lists/list-type.d.ts.map +1 -0
- package/dist/lists/list-type.js +275 -0
- package/dist/lists/list-type.js.map +1 -0
- package/dist/lists/model.d.ts +14 -0
- package/dist/lists/model.d.ts.map +1 -0
- package/dist/lists/model.js +15 -0
- package/dist/lists/model.js.map +1 -0
- package/dist/lists/page-type.d.ts +115 -0
- package/dist/lists/page-type.d.ts.map +1 -0
- package/dist/lists/page-type.js +66 -0
- package/dist/lists/page-type.js.map +1 -0
- package/dist/lists/refs.d.ts +13 -0
- package/dist/lists/refs.d.ts.map +1 -0
- package/dist/lists/refs.js +18 -0
- package/dist/lists/refs.js.map +1 -0
- package/dist/lists/render-data.d.ts +123 -0
- package/dist/lists/render-data.d.ts.map +1 -0
- package/dist/lists/render-data.js +3 -0
- package/dist/lists/render-data.js.map +1 -0
- package/dist/lists/render-deps.d.ts +69 -0
- package/dist/lists/render-deps.d.ts.map +1 -0
- package/dist/lists/render-deps.js +93 -0
- package/dist/lists/render-deps.js.map +1 -0
- package/dist/lists/resolve-values.d.ts +63 -0
- package/dist/lists/resolve-values.d.ts.map +1 -0
- package/dist/lists/resolve-values.js +181 -0
- package/dist/lists/resolve-values.js.map +1 -0
- package/dist/lists/resolve.d.ts +119 -0
- package/dist/lists/resolve.d.ts.map +1 -0
- package/dist/lists/resolve.js +970 -0
- package/dist/lists/resolve.js.map +1 -0
- package/dist/lists/scope.d.ts +81 -0
- package/dist/lists/scope.d.ts.map +1 -0
- package/dist/lists/scope.js +3 -0
- package/dist/lists/scope.js.map +1 -0
- package/dist/markup-structure.d.ts +8 -0
- package/dist/markup-structure.d.ts.map +1 -1
- package/dist/markup-structure.js +2 -0
- package/dist/markup-structure.js.map +1 -1
- package/dist/media.d.ts +1 -1
- package/dist/nested-module-render.d.ts +38 -20
- package/dist/nested-module-render.d.ts.map +1 -1
- package/dist/nested-module-render.js +156 -31
- package/dist/nested-module-render.js.map +1 -1
- package/dist/page-log.d.ts +1 -1
- package/dist/preview-compose.d.ts +24 -21
- package/dist/preview-compose.d.ts.map +1 -1
- package/dist/preview-compose.js +34 -118
- package/dist/preview-compose.js.map +1 -1
- package/dist/redirect-plan.d.ts +13 -0
- package/dist/redirect-plan.d.ts.map +1 -1
- package/dist/redirect-plan.js +22 -0
- package/dist/redirect-plan.js.map +1 -1
- package/dist/seo.d.ts +21 -0
- package/dist/seo.d.ts.map +1 -1
- package/dist/seo.js +32 -0
- package/dist/seo.js.map +1 -1
- package/dist/source-text-transport.d.ts +43 -0
- package/dist/source-text-transport.d.ts.map +1 -0
- package/dist/source-text-transport.js +79 -0
- package/dist/source-text-transport.js.map +1 -0
- package/dist/template-engine.d.ts +44 -31
- package/dist/template-engine.d.ts.map +1 -1
- package/dist/template-engine.js +75 -174
- package/dist/template-engine.js.map +1 -1
- package/dist/template-sections.d.ts +69 -0
- package/dist/template-sections.d.ts.map +1 -0
- package/dist/template-sections.js +436 -0
- package/dist/template-sections.js.map +1 -0
- package/dist/version.d.ts +2 -2
- package/dist/version.d.ts.map +1 -1
- package/dist/version.js +1 -1
- package/dist/version.js.map +1 -1
- package/package.json +8 -1
- package/src/__tests__/redos-hardening.test.ts +51 -16
- package/src/ai-tools-add-module-modes.test.ts +51 -1
- package/src/ai-tools.test.ts +7 -10
- package/src/ai-tools.ts +90 -180
- package/src/bug-intake.test.ts +1 -1
- package/src/build-page.test.ts +34 -3
- package/src/build-page.ts +34 -11
- package/src/content.test.ts +137 -4
- package/src/content.ts +230 -85
- package/src/css-scope-scan.ts +1 -1
- package/src/css-var-aliases.test.ts +61 -0
- package/src/css-var-aliases.ts +109 -0
- package/src/css-var-scan.ts +9 -0
- package/src/edit-markers.test.ts +37 -21
- package/src/edit-markers.ts +52 -20
- package/src/escaped-line-breaks.ts +221 -0
- package/src/index.ts +4 -1
- package/src/interactions.ts +6 -6
- package/src/link-canonicalise.test.ts +36 -6
- package/src/link-canonicalise.ts +6 -1
- package/src/link-refs.test.ts +67 -42
- package/src/link-refs.ts +3 -3
- package/src/lists/address.ts +75 -0
- package/src/lists/default-types.ts +141 -0
- package/src/lists/fields.ts +286 -0
- package/src/lists/index.ts +16 -0
- package/src/lists/item-attr.test.ts +36 -0
- package/src/lists/item-attr.ts +49 -0
- package/src/lists/items.ts +402 -0
- package/src/lists/list-fixtures.test.ts +308 -0
- package/src/lists/list-type.ts +306 -0
- package/src/lists/lists.test.ts +328 -0
- package/src/lists/model.ts +16 -0
- package/src/lists/page-type.ts +71 -0
- package/src/lists/refs.ts +21 -0
- package/src/lists/render-data.ts +134 -0
- package/src/lists/render-deps.test.ts +132 -0
- package/src/lists/render-deps.ts +147 -0
- package/src/lists/resolve-values.ts +224 -0
- package/src/lists/resolve.test.ts +472 -0
- package/src/lists/resolve.ts +1246 -0
- package/src/lists/scope.ts +69 -0
- package/src/lists-compose-parity.test.ts +262 -0
- package/src/markup-structure.ts +10 -0
- package/src/nested-module-render.test.ts +147 -74
- package/src/nested-module-render.ts +120 -48
- package/src/preview-compose.test.ts +55 -19
- package/src/preview-compose.ts +59 -154
- package/src/redirect-plan.test.ts +25 -0
- package/src/redirect-plan.ts +24 -0
- package/src/seo.test.ts +58 -0
- package/src/seo.ts +49 -0
- package/src/source-text-transport.test.ts +158 -0
- package/src/source-text-transport.ts +81 -0
- package/src/template-engine-lists.test.ts +608 -0
- package/src/template-engine.test.ts +62 -80
- package/src/template-engine.ts +102 -245
- package/src/template-sections.ts +514 -0
- package/src/version.ts +1 -1
- package/dist/structured-sets.d.ts +0 -70
- package/dist/structured-sets.d.ts.map +0 -1
- package/dist/structured-sets.js +0 -92
- package/dist/structured-sets.js.map +0 -1
- package/src/structured-sets.ts +0 -114
- package/src/template-engine-data-lists.test.ts +0 -90
package/src/ai-tools.ts
CHANGED
|
@@ -17,16 +17,18 @@ import {
|
|
|
17
17
|
contentInstanceDeleteSchema,
|
|
18
18
|
contentInstanceUpdateSchema,
|
|
19
19
|
forkPlacementContentSchema,
|
|
20
|
-
|
|
21
|
-
MODULE_HTML_MAX,
|
|
22
|
-
MODULE_JS_MAX,
|
|
20
|
+
type ModuleField,
|
|
23
21
|
moduleFieldSchema,
|
|
22
|
+
pageShortTitleSchema,
|
|
23
|
+
pageTypeSlugSchema,
|
|
24
24
|
setPlacementContentSchema,
|
|
25
25
|
slugSchema,
|
|
26
26
|
} from "./content.js";
|
|
27
|
+
import type { SourceLanguage } from "./escaped-line-breaks.js";
|
|
28
|
+
import { LIST_ITEM_ATTR_RE } from "./lists/item-attr.js";
|
|
27
29
|
import { pluginPreviewSelectionSchema } from "./plugin-preview.js";
|
|
28
30
|
import { redirectTargetPathSchema } from "./redirect-target.js";
|
|
29
|
-
import {
|
|
31
|
+
import { decodeSourceTextTransport } from "./source-text-transport.js";
|
|
30
32
|
|
|
31
33
|
/**
|
|
32
34
|
* v0.6.2 — `position` argument shared across the three `add_module_to_*`
|
|
@@ -61,15 +63,17 @@ function stripSurroundingQuotes(v: unknown): unknown {
|
|
|
61
63
|
}
|
|
62
64
|
/**
|
|
63
65
|
* An AI tool argument holding source text (html / css / js): the given
|
|
64
|
-
* string schema, with
|
|
65
|
-
*
|
|
66
|
-
*
|
|
67
|
-
*
|
|
66
|
+
* string schema, with whole-value transport encodings decoded first — a
|
|
67
|
+
* CDATA envelope, a JSON string literal, HTML-escaped markup (see
|
|
68
|
+
* `decodeSourceTextTransport`). Every AI authoring surface for module,
|
|
69
|
+
* template and layout source uses it, so one encoding habit of the model
|
|
70
|
+
* is handled in one place instead of refused per tool.
|
|
68
71
|
*
|
|
69
72
|
* @param schema - the string schema (length caps, min) the decoded value must satisfy.
|
|
73
|
+
* @param language - the grammar of the value (`html`, `css`, `js`).
|
|
70
74
|
*/
|
|
71
|
-
export function sourceTextArg<T extends z.ZodType<string>>(schema: T) {
|
|
72
|
-
return z.preprocess(
|
|
75
|
+
export function sourceTextArg<T extends z.ZodType<string>>(schema: T, language: SourceLanguage) {
|
|
76
|
+
return z.preprocess((value) => decodeSourceTextTransport(value, language), schema);
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
export const positionInputSchema = z.preprocess(
|
|
@@ -83,55 +87,6 @@ export const positionInputSchema = z.preprocess(
|
|
|
83
87
|
]),
|
|
84
88
|
);
|
|
85
89
|
|
|
86
|
-
export const editModuleToolInput = z
|
|
87
|
-
.object({
|
|
88
|
-
moduleId: z.string().uuid(),
|
|
89
|
-
displayName: z.string().min(1).max(128).optional(),
|
|
90
|
-
/**
|
|
91
|
-
* v0.12.0 — rewrite the module's purpose. Optional; passing it
|
|
92
|
-
* updates the `list_modules` catalog your future self will read.
|
|
93
|
-
*/
|
|
94
|
-
description: z.string().max(1000).optional(),
|
|
95
|
-
/** v0.12.0 — re-classify the module's role tag. */
|
|
96
|
-
kind: z.enum(["chrome", "hero", "content", "cta", "utility"]).optional(),
|
|
97
|
-
/**
|
|
98
|
-
* v0.12.3 (issue #106) — re-classify the module's stable `type` (the
|
|
99
|
-
* class a parent's `allowedModuleTypes` whitelist matches against,
|
|
100
|
-
* e.g. `button`). Rarely needed; usually derived from displayName at
|
|
101
|
-
* create time. Set it to make this module satisfy a parent's allowlist.
|
|
102
|
-
*/
|
|
103
|
-
type: slugSchema.optional(),
|
|
104
|
-
html: sourceTextArg(z.string().max(MODULE_HTML_MAX)).optional(),
|
|
105
|
-
css: sourceTextArg(z.string().max(MODULE_CSS_MAX)).optional(),
|
|
106
|
-
js: sourceTextArg(z.string().max(MODULE_JS_MAX)).optional(),
|
|
107
|
-
/**
|
|
108
|
-
* v0.4.0 — module field schema. Each field declares one substitution
|
|
109
|
-
* slot referenced in the module HTML as `{{name}}`. Page placements
|
|
110
|
-
* fill these via `set_page_module_content`. Optional on edits — pass
|
|
111
|
-
* to replace the declared schema.
|
|
112
|
-
*/
|
|
113
|
-
fields: z.array(moduleFieldSchema).max(64).optional(),
|
|
114
|
-
/** issue #164 slice 2 — see addModuleToolInput.bindThemeLiterals. */
|
|
115
|
-
bindThemeLiterals: z.boolean().optional(),
|
|
116
|
-
})
|
|
117
|
-
.strict();
|
|
118
|
-
|
|
119
|
-
/**
|
|
120
|
-
* v0.4.0 — `set_page_module_content` AI tool. Fills the content values for
|
|
121
|
-
* a module placement on a specific page. Page-bound + branch-isolated per
|
|
122
|
-
* chat until publish (unlike `edit_module` which is global + immediate).
|
|
123
|
-
*/
|
|
124
|
-
export const setPageModuleContentToolInput = z
|
|
125
|
-
.object({
|
|
126
|
-
pageId: z.string().uuid(),
|
|
127
|
-
blockName: z.string().min(1).max(64),
|
|
128
|
-
position: z.number().int().nonnegative(),
|
|
129
|
-
/** Map of module field name → value. Fully replaces existing values. */
|
|
130
|
-
contentValues: z.record(z.string(), z.unknown()),
|
|
131
|
-
})
|
|
132
|
-
.strict();
|
|
133
|
-
export type SetPageModuleContentToolInput = z.infer<typeof setPageModuleContentToolInput>;
|
|
134
|
-
|
|
135
90
|
export const siteMemoryProposeToolInput = z
|
|
136
91
|
.object({
|
|
137
92
|
slot: z.enum(["purpose", "brand-voice", "tone", "banned-phrases", "instructions", "glossary"]),
|
|
@@ -206,9 +161,9 @@ export const addModuleToolInput = z
|
|
|
206
161
|
description: z.string().max(1000).optional(),
|
|
207
162
|
kind: z.enum(["chrome", "hero", "content", "cta", "utility"]).optional(),
|
|
208
163
|
type: slugSchema.optional(),
|
|
209
|
-
html: sourceTextArg(z.string().min(1).max(50_000)).optional(),
|
|
210
|
-
css: sourceTextArg(z.string().max(50_000)).optional(),
|
|
211
|
-
js: sourceTextArg(z.string().max(50_000)).optional(),
|
|
164
|
+
html: sourceTextArg(z.string().min(1).max(50_000), "html").optional(),
|
|
165
|
+
css: sourceTextArg(z.string().max(50_000), "css").optional(),
|
|
166
|
+
js: sourceTextArg(z.string().max(50_000), "js").optional(),
|
|
212
167
|
fields: z.array(moduleFieldSchema).max(64).optional(),
|
|
213
168
|
bindThemeLiterals: z.boolean().optional(),
|
|
214
169
|
/**
|
|
@@ -221,8 +176,8 @@ export const addModuleToolInput = z
|
|
|
221
176
|
* stores the values as the minted module's `fields[].default` —
|
|
222
177
|
* which requires explicit `fields` (moduleize field names are
|
|
223
178
|
* unknowable up front) and is rejected for `moduleId` reuse (a
|
|
224
|
-
* shared module renders its stored defaults;
|
|
225
|
-
* them).
|
|
179
|
+
* shared module renders its stored defaults; update_modules_many
|
|
180
|
+
* changes them).
|
|
226
181
|
*/
|
|
227
182
|
values: z.record(z.string(), z.unknown()).optional(),
|
|
228
183
|
})
|
|
@@ -233,7 +188,7 @@ export const addModuleToolInput = z
|
|
|
233
188
|
ctx.addIssue({
|
|
234
189
|
code: z.ZodIssueCode.custom,
|
|
235
190
|
message:
|
|
236
|
-
"Reusing a module on a layout renders the module's STORED field defaults — `values` has nothing to bind to. Drop `values` (the chrome shows the module as-is), or change the shared copy via
|
|
191
|
+
"Reusing a module on a layout renders the module's STORED field defaults — `values` has nothing to bind to. Drop `values` (the chrome shows the module as-is), or change the shared copy via update_modules_many.",
|
|
237
192
|
});
|
|
238
193
|
} else if (input.fields === undefined || input.fields.length === 0) {
|
|
239
194
|
ctx.addIssue({
|
|
@@ -257,7 +212,7 @@ export const addModuleToolInput = z
|
|
|
257
212
|
// (§1A/§11), so we do NOT reject a moduleId call that also carries authoring
|
|
258
213
|
// fields. Those fields are not applied here; the tool HANDLER surfaces an
|
|
259
214
|
// INFO for any ignored field whose value DIFFERS from the module's stored
|
|
260
|
-
// one, pointing at
|
|
215
|
+
// one, pointing at update_modules_many — so a dropped change is never silent (§2).
|
|
261
216
|
if (input.moduleId !== undefined) return;
|
|
262
217
|
if (input.displayName === undefined || input.html === undefined) {
|
|
263
218
|
ctx.addIssue({
|
|
@@ -274,17 +229,25 @@ export const addModuleToolInput = z
|
|
|
274
229
|
// the exact two-step build_page was created to kill. Only enforced
|
|
275
230
|
// when the caller authored explicit fields; the raw-HTML path goes
|
|
276
231
|
// through moduleize, which enforces defaults itself.
|
|
232
|
+
// A `list` field bound to a shared list the module fixes (`list`), or
|
|
233
|
+
// fed by a source, already has its content: requiring a default there
|
|
234
|
+
// refused the very footer-bound-to-the-menu call the manage-menu skill
|
|
235
|
+
// prescribes (PR #1121 e2e-livedit run 40297574140).
|
|
236
|
+
const carriesContent = (f: ModuleField): boolean =>
|
|
237
|
+
("default" in f && f.default !== undefined) ||
|
|
238
|
+
(f.kind === "list" && (f.bind === "source" || (f.bind === "shared" && f.list !== undefined)));
|
|
277
239
|
if (
|
|
278
240
|
input.fields !== undefined &&
|
|
279
241
|
input.fields.length > 0 &&
|
|
280
242
|
input.values === undefined &&
|
|
281
|
-
!input.fields.some(
|
|
243
|
+
!input.fields.some(carriesContent)
|
|
282
244
|
) {
|
|
283
245
|
ctx.addIssue({
|
|
284
246
|
code: z.ZodIssueCode.custom,
|
|
285
247
|
message:
|
|
286
248
|
"A minted module needs its initial content IN THIS CALL: pass `values` " +
|
|
287
249
|
"({fieldName: value, …}) for this placement, or give the fields `default`s. " +
|
|
250
|
+
'A list field bound to a site list carries its content through `list` ({kind:"list", bind:"shared", list:"caelo:list/<id>"} — create the list first with upsert_lists_many). ' +
|
|
288
251
|
"Without either, the module renders empty placeholders until a second call.",
|
|
289
252
|
});
|
|
290
253
|
}
|
|
@@ -341,45 +304,6 @@ export const deleteContentInstanceToolInput = contentInstanceDeleteSchema;
|
|
|
341
304
|
export const setPlacementContentToolInput = setPlacementContentSchema;
|
|
342
305
|
export const forkPlacementContentToolInput = forkPlacementContentSchema;
|
|
343
306
|
|
|
344
|
-
export const AI_TOOLS = [
|
|
345
|
-
"edit_module",
|
|
346
|
-
"site_memory_propose",
|
|
347
|
-
"add_module",
|
|
348
|
-
"create_page",
|
|
349
|
-
// audit #3 — page metadata (name/title/slug/template/status) is one tool for
|
|
350
|
-
// 1..200 pages: `update_pages_many`. The former rename_page / set_page_title
|
|
351
|
-
// / change_page_slug were single-field wrappers over the same pages.update.
|
|
352
|
-
"update_pages_many",
|
|
353
|
-
// audit #4 — one delete tool for 1..200 pages, disposition per page.
|
|
354
|
-
// The former singular delete_page folded in (n=1 is a one-item array).
|
|
355
|
-
"delete_pages_many",
|
|
356
|
-
// one remove-module tool routed by target (page|layout).
|
|
357
|
-
"remove_module_from",
|
|
358
|
-
"set_structured_set",
|
|
359
|
-
"update_theme",
|
|
360
|
-
"set_template_layout",
|
|
361
|
-
"create_layout",
|
|
362
|
-
"set_site_defaults",
|
|
363
|
-
"duplicate_page",
|
|
364
|
-
"repoint_page_template",
|
|
365
|
-
"move_module",
|
|
366
|
-
"reorder_module",
|
|
367
|
-
"set_nav_menu",
|
|
368
|
-
"add_plugin_to_page",
|
|
369
|
-
// v0.12.0 — content_instances + placement binding
|
|
370
|
-
"list_content_instances",
|
|
371
|
-
"get_content_instance",
|
|
372
|
-
"create_content_instance",
|
|
373
|
-
"set_content_instance_values",
|
|
374
|
-
"delete_content_instance",
|
|
375
|
-
"set_placement_content",
|
|
376
|
-
"fork_placement_content",
|
|
377
|
-
// issue #299 — bulk-first build path (§11)
|
|
378
|
-
"build_page",
|
|
379
|
-
"create_content_instances",
|
|
380
|
-
"set_page_module_content_many",
|
|
381
|
-
] as const;
|
|
382
|
-
export type AiToolName = (typeof AI_TOOLS)[number];
|
|
383
307
|
export type AddPluginToPageToolInput = z.infer<typeof addPluginToPageToolInput>;
|
|
384
308
|
export type ListContentInstancesToolInput = z.infer<typeof listContentInstancesToolInput>;
|
|
385
309
|
export type GetContentInstanceToolInput = z.infer<typeof getContentInstanceToolInput>;
|
|
@@ -451,9 +375,14 @@ export const CHAT_MAX_ATTACHMENTS = 4;
|
|
|
451
375
|
* `context` come from a right-click / text selection in the live-edit
|
|
452
376
|
* preview (#916): what the operator selected and the text of the block
|
|
453
377
|
* around it, so the AI knows exactly what was meant. `blockName` /
|
|
454
|
-
* `position` / `field`
|
|
378
|
+
* `position` / `field` (#917) — plus `item` / `key` for an entry of a
|
|
379
|
+
* local list field (epic #1107) — name the placement field the text
|
|
455
380
|
* comes from when the preview marked it, so the AI can address it in
|
|
456
|
-
* `
|
|
381
|
+
* `set_page_module_content_many` without inspecting the page first.
|
|
382
|
+
* `listItem` is the `data-caelo-list-item` of the list entry the element
|
|
383
|
+
* sits in (epic #1107, `docs/lists.md` §13): the chip then names that
|
|
384
|
+
* entry — of a shared list, or of a module's own list — not just the
|
|
385
|
+
* module around it.
|
|
457
386
|
*/
|
|
458
387
|
export const chatChipSchema = z
|
|
459
388
|
.object({
|
|
@@ -465,7 +394,12 @@ export const chatChipSchema = z
|
|
|
465
394
|
blockName: z.string().min(1).max(80).optional(),
|
|
466
395
|
position: z.number().int().nonnegative().optional(),
|
|
467
396
|
field: z.string().min(1).max(200).optional(),
|
|
468
|
-
|
|
397
|
+
item: z.string().uuid().optional(),
|
|
398
|
+
key: z
|
|
399
|
+
.string()
|
|
400
|
+
.regex(/^[a-z][a-z0-9_]{0,63}$/)
|
|
401
|
+
.optional(),
|
|
402
|
+
listItem: z.string().max(200).regex(LIST_ITEM_ATTR_RE).optional(),
|
|
469
403
|
})
|
|
470
404
|
.strict();
|
|
471
405
|
export type ChatChip = z.infer<typeof chatChipSchema>;
|
|
@@ -555,11 +489,8 @@ export const chatPublishInput = z
|
|
|
555
489
|
.array(
|
|
556
490
|
z
|
|
557
491
|
.object({
|
|
558
|
-
// v0.9.0 —
|
|
559
|
-
//
|
|
560
|
-
// helper accepted "structuredSet" via the underlying SQL
|
|
561
|
-
// query but the input schema rejected it — minor gap closed
|
|
562
|
-
// alongside the branched-create work.
|
|
492
|
+
// v0.9.0 — "layout" joined the enum so the Stage picker can
|
|
493
|
+
// include/exclude it.
|
|
563
494
|
kind: z.enum([
|
|
564
495
|
"module",
|
|
565
496
|
"template",
|
|
@@ -567,7 +498,10 @@ export const chatPublishInput = z
|
|
|
567
498
|
"pageLayout",
|
|
568
499
|
"pageModuleContent",
|
|
569
500
|
"layout",
|
|
570
|
-
|
|
501
|
+
// Epic #1107 — shared lists, list types, page types.
|
|
502
|
+
"listType",
|
|
503
|
+
"list",
|
|
504
|
+
"pageType",
|
|
571
505
|
// v0.11.0 (#45) — themes primitive joins the publish/Stage
|
|
572
506
|
// surface so chat-branched theme edits replay into live
|
|
573
507
|
// on chat.publish.
|
|
@@ -647,9 +581,11 @@ export const createTemplateToolInput = z
|
|
|
647
581
|
.object({
|
|
648
582
|
slug: slugInputSchema,
|
|
649
583
|
displayName: z.string().min(1).max(256),
|
|
650
|
-
html: sourceTextArg(z.string().min(1).max(2_000_000)),
|
|
651
|
-
css: sourceTextArg(z.string().max(2_000_000)).default(""),
|
|
584
|
+
html: sourceTextArg(z.string().min(1).max(2_000_000), "html"),
|
|
585
|
+
css: sourceTextArg(z.string().max(2_000_000), "css").default(""),
|
|
652
586
|
layoutId: z.string().uuid().optional(),
|
|
587
|
+
/** Epic #1107 — the page type (slug) this template serves; omit for the site default. */
|
|
588
|
+
pageType: pageTypeSlugSchema.optional(),
|
|
653
589
|
// Optional block-set metadata, symmetric with create_layout. Omit to
|
|
654
590
|
// auto-derive blocks from <caelo-slot> tags; pass to set displayName /
|
|
655
591
|
// position (each name must match a slot). Enforced op-side in
|
|
@@ -682,25 +618,9 @@ export const removeModuleFromToolInput = z
|
|
|
682
618
|
.strict();
|
|
683
619
|
export type RemoveModuleFromToolInput = z.infer<typeof removeModuleFromToolInput>;
|
|
684
620
|
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
// from any AI tool — the kind-specific wrappers covered only
|
|
689
|
-
// nav-menu + theme).
|
|
690
|
-
kind: z.enum(["nav-menu", "taxonomy", "tags", "link-list"]),
|
|
691
|
-
slug: slugInputSchema,
|
|
692
|
-
displayName: z.string().min(1).max(200),
|
|
693
|
-
items: z.array(z.unknown()),
|
|
694
|
-
})
|
|
695
|
-
.strict();
|
|
696
|
-
|
|
697
|
-
// v0.10.22 — `updateThemeToolInput` removed. The kind-specific `update_theme`
|
|
698
|
-
// wrapper was replaced by the unified `set_structured_set` surface.
|
|
699
|
-
// v0.11.0 (#45) — theme moved out of structured_sets entirely into its
|
|
700
|
-
// own `themes` primitive (DTCG-shaped jsonb). Theme edits are now
|
|
701
|
-
// `set_theme_tokens` (loose-name patch) / `propose_create_theme`
|
|
702
|
-
// (gated mint) / `propose_activate_theme` (gated flip) — see
|
|
703
|
-
// packages/admin-core/src/ai/tools/{update-theme-tokens,get-theme,…}.ts.
|
|
621
|
+
// v0.11.0 (#45) — theme edits are `set_theme_tokens` (loose-name patch) /
|
|
622
|
+
// `propose_create_theme` (gated mint) / `propose_activate_theme` (gated
|
|
623
|
+
// flip) — see packages/admin-core/src/ai/tools/{update-theme-tokens,get-theme,…}.ts.
|
|
704
624
|
|
|
705
625
|
export const setTemplateLayoutToolInput = z
|
|
706
626
|
.object({
|
|
@@ -713,8 +633,8 @@ export const createLayoutToolInput = z
|
|
|
713
633
|
.object({
|
|
714
634
|
slug: slugInputSchema,
|
|
715
635
|
displayName: z.string().min(1).max(200),
|
|
716
|
-
html: sourceTextArg(z.string().min(1).max(50_000)),
|
|
717
|
-
css: sourceTextArg(z.string().max(50_000)).optional(),
|
|
636
|
+
html: sourceTextArg(z.string().min(1).max(50_000), "html"),
|
|
637
|
+
css: sourceTextArg(z.string().max(50_000), "css").optional(),
|
|
718
638
|
blocks: z
|
|
719
639
|
.array(
|
|
720
640
|
z
|
|
@@ -750,8 +670,9 @@ export const duplicatePageToolInput = z
|
|
|
750
670
|
.object({
|
|
751
671
|
sourcePageId: z.string().uuid(),
|
|
752
672
|
newSlug: slugInputSchema,
|
|
753
|
-
newName: z.string().min(1).max(256).optional(),
|
|
754
673
|
newTitle: z.string().min(1).max(256).optional(),
|
|
674
|
+
/** #1142 — the copy's breadcrumb label; omitted = newTitle when given, else the source's. */
|
|
675
|
+
newShortTitle: pageShortTitleSchema.optional(),
|
|
755
676
|
targetTemplateId: z.string().uuid().optional(),
|
|
756
677
|
/** Where the copy sits: a parent page id, or null for the top level.
|
|
757
678
|
* Omitted = next to the source (same parent). */
|
|
@@ -779,32 +700,40 @@ export const repointPageTemplateToolInput = z
|
|
|
779
700
|
})
|
|
780
701
|
.strict();
|
|
781
702
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
703
|
+
/**
|
|
704
|
+
* `move_module` position: `top` | `bottom` | a 0-based index in the target
|
|
705
|
+
* block, plus `up` / `down` (one slot within the module's CURRENT block).
|
|
706
|
+
* Same quote-stripping + numeric-string coercion as `positionInputSchema`.
|
|
707
|
+
*/
|
|
708
|
+
export const movePositionInputSchema = z.preprocess(
|
|
709
|
+
stripSurroundingQuotes,
|
|
710
|
+
z.union([
|
|
711
|
+
z.enum(["top", "bottom", "up", "down"]),
|
|
712
|
+
z.preprocess(
|
|
713
|
+
(v) => (typeof v === "string" && /^\d+$/.test(v) ? Number.parseInt(v, 10) : v),
|
|
714
|
+
z.number().int().min(0).max(1000),
|
|
715
|
+
),
|
|
716
|
+
]),
|
|
717
|
+
);
|
|
791
718
|
|
|
792
|
-
|
|
719
|
+
/**
|
|
720
|
+
* `move_module` — moves a placement to `toBlockName` (omitted = the block it
|
|
721
|
+
* is in now, i.e. a reorder) at `position`. `up` / `down` only make sense
|
|
722
|
+
* inside the current block; the handler refuses them with another block.
|
|
723
|
+
*/
|
|
724
|
+
export const moveModuleToolInput = z
|
|
793
725
|
.object({
|
|
794
726
|
pageId: z.string().uuid(),
|
|
795
727
|
moduleId: z.string().uuid(),
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
* target position within the same block.
|
|
799
|
-
*/
|
|
800
|
-
direction: z.union([z.enum(["up", "down"]), z.number().int().min(0).max(1000)]),
|
|
728
|
+
toBlockName: z.string().min(1).max(80).optional(),
|
|
729
|
+
position: movePositionInputSchema,
|
|
801
730
|
})
|
|
802
731
|
.strict();
|
|
803
732
|
|
|
804
733
|
/**
|
|
805
734
|
* P7 — `find_media`. Searches the media library by alt-text /
|
|
806
735
|
* filename / mime. Each row carries the media UUID (what
|
|
807
|
-
* `set_theme_asset` / `
|
|
736
|
+
* `set_theme_asset` / `set_media_alt_many` require) plus a pre-resolved
|
|
808
737
|
* variant URL. The AI's only media-inventory surface — the `## Media`
|
|
809
738
|
* system-prompt block was removed by the context diet (#300), so every
|
|
810
739
|
* asset lookup ("an image of a sunlit office") goes through this tool.
|
|
@@ -849,7 +778,7 @@ export const regenerateMediaVariantsToolInput = z
|
|
|
849
778
|
export type RegenerateMediaVariantsToolInput = z.infer<typeof regenerateMediaVariantsToolInput>;
|
|
850
779
|
|
|
851
780
|
/**
|
|
852
|
-
* P7 — `
|
|
781
|
+
* P7 — one item of `set_media_alt_many`. AI may improve a11y on an existing asset
|
|
853
782
|
* without a human round-trip (e.g. when the editor uploaded an image
|
|
854
783
|
* with the default alt and the AI reads its content). Updates only
|
|
855
784
|
* the alt field on `media_assets`.
|
|
@@ -879,7 +808,7 @@ export const setNotFoundPageToolInput = z.object({ pageId: z.string().uuid().nul
|
|
|
879
808
|
export type SetNotFoundPageToolInput = z.infer<typeof setNotFoundPageToolInput>;
|
|
880
809
|
|
|
881
810
|
/**
|
|
882
|
-
* 0181 — `
|
|
811
|
+
* 0181 — one item of `set_media_source_many`. AI records an asset's provenance (source
|
|
883
812
|
* kind + origin detail) and licence when known. All fields but the
|
|
884
813
|
* assetId are optional; the op COALESCEs so omitted fields stay
|
|
885
814
|
* unchanged.
|
|
@@ -895,7 +824,7 @@ export const setMediaSourceToolInput = z
|
|
|
895
824
|
export type SetMediaSourceToolInput = z.infer<typeof setMediaSourceToolInput>;
|
|
896
825
|
|
|
897
826
|
/**
|
|
898
|
-
* P8 — `
|
|
827
|
+
* P8 — one item of `set_page_seo_many`. Manual / panel writes to the per-page SEO
|
|
899
828
|
* sidecar. AI calls this only on explicit user intent
|
|
900
829
|
* ("set the home meta description to ..."). Doesn't bump fingerprints
|
|
901
830
|
* (autofilled_at / optimized_at).
|
|
@@ -916,7 +845,7 @@ export const setPageSeoToolInput = z
|
|
|
916
845
|
export type SetPageSeoToolInput = z.infer<typeof setPageSeoToolInput>;
|
|
917
846
|
|
|
918
847
|
/**
|
|
919
|
-
* P8 —
|
|
848
|
+
* P8 — one item of `autofill_page_seo_many`.
|
|
920
849
|
* Fill-once. Refuses when the page's SEO was already autofilled. The AI
|
|
921
850
|
* calls it before a page's first Stage; staging and production builds
|
|
922
851
|
* refuse pages without a meta description (deploy/missing-seo.ts).
|
|
@@ -930,22 +859,6 @@ export const autofillPageSeoToolInput = z
|
|
|
930
859
|
.strict();
|
|
931
860
|
export type AutofillPageSeoToolInput = z.infer<typeof autofillPageSeoToolInput>;
|
|
932
861
|
|
|
933
|
-
/**
|
|
934
|
-
* P8 — `optimize_page_seo`. Explicit re-optimization. Always allowed.
|
|
935
|
-
* Takes optional context (keyword analysis / intent shifts / branding
|
|
936
|
-
* changes). The AI can call this across N pages in one chat turn —
|
|
937
|
-
* the resulting changes batch into one Publish-pill confirm.
|
|
938
|
-
*/
|
|
939
|
-
export const optimizePageSeoToolInput = z
|
|
940
|
-
.object({
|
|
941
|
-
pageId: z.string().uuid(),
|
|
942
|
-
metaDescription: z.string().min(1).max(320),
|
|
943
|
-
ogImageAssetId: z.string().uuid().nullable().optional(),
|
|
944
|
-
context: z.string().max(4000).optional(),
|
|
945
|
-
})
|
|
946
|
-
.strict();
|
|
947
|
-
export type OptimizePageSeoToolInput = z.infer<typeof optimizePageSeoToolInput>;
|
|
948
|
-
|
|
949
862
|
/**
|
|
950
863
|
* P8 AI-first review pass — bulk variants. Per CLAUDE.md §11, bulk
|
|
951
864
|
* tools save round-trips when the AI knows it has N changes to make
|
|
@@ -965,7 +878,7 @@ export type FindRedirectsToolInput = z.infer<typeof findRedirectsToolInput>;
|
|
|
965
878
|
|
|
966
879
|
export const bulkCreateRedirectsToolInput = z
|
|
967
880
|
.object({
|
|
968
|
-
|
|
881
|
+
items: z
|
|
969
882
|
.array(
|
|
970
883
|
z
|
|
971
884
|
.object({
|
|
@@ -1017,7 +930,7 @@ export type BulkDeleteRedirectsToolInput = z.infer<typeof bulkDeleteRedirectsToo
|
|
|
1017
930
|
|
|
1018
931
|
export const bulkOptimizeSeoToolInput = z
|
|
1019
932
|
.object({
|
|
1020
|
-
|
|
933
|
+
items: z
|
|
1021
934
|
.array(
|
|
1022
935
|
z
|
|
1023
936
|
.object({
|
|
@@ -1034,10 +947,8 @@ export const bulkOptimizeSeoToolInput = z
|
|
|
1034
947
|
.strict();
|
|
1035
948
|
export type BulkOptimizeSeoToolInput = z.infer<typeof bulkOptimizeSeoToolInput>;
|
|
1036
949
|
|
|
1037
|
-
export type EditModuleToolInput = z.infer<typeof editModuleToolInput>;
|
|
1038
950
|
export type SiteMemoryProposeToolInput = z.infer<typeof siteMemoryProposeToolInput>;
|
|
1039
951
|
export type CreateTemplateToolInput = z.infer<typeof createTemplateToolInput>;
|
|
1040
|
-
export type SetStructuredSetToolInput = z.infer<typeof setStructuredSetToolInput>;
|
|
1041
952
|
// v0.10.22 — `UpdateThemeToolInput` removed alongside `update_theme` tool.
|
|
1042
953
|
export type ChatCreateSessionInput = z.infer<typeof chatCreateSessionInput>;
|
|
1043
954
|
export type ChatSendMessageInput = z.infer<typeof chatSendMessageInput>;
|
|
@@ -1053,7 +964,6 @@ export type SetSiteDefaultsToolInput = z.infer<typeof setSiteDefaultsToolInput>;
|
|
|
1053
964
|
export type DuplicatePageToolInput = z.infer<typeof duplicatePageToolInput>;
|
|
1054
965
|
export type RepointPageTemplateToolInput = z.infer<typeof repointPageTemplateToolInput>;
|
|
1055
966
|
export type MoveModuleToolInput = z.infer<typeof moveModuleToolInput>;
|
|
1056
|
-
export type ReorderModuleToolInput = z.infer<typeof reorderModuleToolInput>;
|
|
1057
967
|
// v0.10.22 — `SetNavMenuToolInput` removed alongside `set_nav_menu` tool.
|
|
1058
968
|
|
|
1059
969
|
/**
|
package/src/bug-intake.test.ts
CHANGED
|
@@ -32,7 +32,7 @@ describe("computeBugFingerprint", () => {
|
|
|
32
32
|
});
|
|
33
33
|
|
|
34
34
|
it("keeps different tools and different quoted field names apart", async () => {
|
|
35
|
-
expect(await auto("boom", "build_page")).not.toBe(await auto("boom", "
|
|
35
|
+
expect(await auto("boom", "build_page")).not.toBe(await auto("boom", "update_modules_many"));
|
|
36
36
|
expect(await auto('Invalid input at "page.slug"')).not.toBe(
|
|
37
37
|
await auto('Invalid input at "page.title"'),
|
|
38
38
|
);
|
package/src/build-page.test.ts
CHANGED
|
@@ -39,6 +39,14 @@ describe("buildPageInputSchema — page target modes", () => {
|
|
|
39
39
|
expect(r.success).toBe(true);
|
|
40
40
|
});
|
|
41
41
|
|
|
42
|
+
it("accepts parentPageId: null as top level, like pages.create (PR #1121 e2e)", () => {
|
|
43
|
+
const r = buildPageInputSchema.safeParse({
|
|
44
|
+
page: { slug: "pricing", title: "Pricing", parentPageId: null },
|
|
45
|
+
modules: [mintModule],
|
|
46
|
+
});
|
|
47
|
+
expect(r.success).toBe(true);
|
|
48
|
+
});
|
|
49
|
+
|
|
42
50
|
it("accepts existing mode (pageId only)", () => {
|
|
43
51
|
const r = buildPageInputSchema.safeParse({
|
|
44
52
|
page: { pageId: UUID },
|
|
@@ -140,10 +148,10 @@ describe("buildPageInputSchema — module entry modes name the failing index", (
|
|
|
140
148
|
{
|
|
141
149
|
blockName: "content",
|
|
142
150
|
displayName: "Nav",
|
|
143
|
-
html: "<nav>{{#nav_links}}<a href='{{
|
|
151
|
+
html: "<nav>{{#nav_links}}<a href='{{link}}'>{{label}}</a>{{/nav_links}}</nav>",
|
|
144
152
|
fields: [
|
|
145
|
-
{ name: "nav_links", kind: "
|
|
146
|
-
{ name: "tags", kind: "
|
|
153
|
+
{ name: "nav_links", kind: "list", listType: "links", label: "Nav links" },
|
|
154
|
+
{ name: "tags", kind: "list", listType: "texts", label: "Tags", min: 0, max: 12 },
|
|
147
155
|
],
|
|
148
156
|
},
|
|
149
157
|
],
|
|
@@ -190,6 +198,29 @@ describe("buildPageInputSchema — module entry modes name the failing index", (
|
|
|
190
198
|
});
|
|
191
199
|
});
|
|
192
200
|
|
|
201
|
+
describe("buildPageInputSchema — ref handles", () => {
|
|
202
|
+
// e2e-livedit run 40336739359 (PR #1143): the AI named three cards
|
|
203
|
+
// `cardA` / `cardB` / `cardC` and the lowercase-only rule refused the
|
|
204
|
+
// whole call for a handle that never leaves it.
|
|
205
|
+
it("accepts mixed-case handles and refuses ones that do not start with a letter", () => {
|
|
206
|
+
const { blockName: _b, ...detached } = mintModule;
|
|
207
|
+
for (const ref of ["cardA", "card_b", "feat-1"]) {
|
|
208
|
+
const r = buildPageInputSchema.safeParse({
|
|
209
|
+
page: { pageId: UUID },
|
|
210
|
+
modules: [{ ...detached, ref }],
|
|
211
|
+
});
|
|
212
|
+
expect(r.success).toBe(true);
|
|
213
|
+
}
|
|
214
|
+
for (const ref of ["1card", "$card", "card a"]) {
|
|
215
|
+
const r = buildPageInputSchema.safeParse({
|
|
216
|
+
page: { pageId: UUID },
|
|
217
|
+
modules: [{ ...detached, ref }],
|
|
218
|
+
});
|
|
219
|
+
expect(r.success).toBe(false);
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
});
|
|
223
|
+
|
|
193
224
|
describe("buildPageContentSchema — the three sources", () => {
|
|
194
225
|
it("inline requires values", () => {
|
|
195
226
|
expect(buildPageContentSchema.safeParse({ source: "inline" }).success).toBe(false);
|
package/src/build-page.ts
CHANGED
|
@@ -28,6 +28,7 @@ import {
|
|
|
28
28
|
MODULE_JS_MAX,
|
|
29
29
|
moduleFieldSchema,
|
|
30
30
|
moduleKindSchema,
|
|
31
|
+
pageCreateSchema,
|
|
31
32
|
pageStatusSchema,
|
|
32
33
|
slugSchema,
|
|
33
34
|
syncModeSchema,
|
|
@@ -42,10 +43,10 @@ const contentValuesSchema = z.record(z.string(), z.unknown());
|
|
|
42
43
|
* existing content paths so build_page adds NO new semantics, only batching:
|
|
43
44
|
*
|
|
44
45
|
* - `inline` → mint a private (unsynced) content_instance carrying
|
|
45
|
-
* `values`, exactly what `
|
|
46
|
+
* `values`, exactly what `set_page_module_content_many` produces for a
|
|
46
47
|
* fresh placement.
|
|
47
48
|
* - `shared` → mint a reusable content_instance (purpose required —
|
|
48
|
-
* same decision-support contract as `
|
|
49
|
+
* same decision-support contract as `create_content_instances`) and
|
|
49
50
|
* bind this placement to it, `synced` by default.
|
|
50
51
|
* - `existing` → bind an already-minted content_instance (reuse-first
|
|
51
52
|
* per CLAUDE.md §1A) — the batched form of `set_placement_content`.
|
|
@@ -93,7 +94,7 @@ export type BuildPageContent = z.infer<typeof buildPageContentSchema>;
|
|
|
93
94
|
* extra authoring keys are TOLERATED (§1A/§11 — a placement that can
|
|
94
95
|
* succeed must not fail over an extra field) but NOT applied; the op
|
|
95
96
|
* surfaces an info naming any carried field whose value differs from the
|
|
96
|
-
* module's stored one, pointing at
|
|
97
|
+
* module's stored one, pointing at update_modules_many (§2 — no silent drop).
|
|
97
98
|
*
|
|
98
99
|
* The element-level superRefine reports issues WITH the element's array
|
|
99
100
|
* index in the Zod path, so a mode error fails as `modules[3]: …`.
|
|
@@ -130,7 +131,10 @@ export const buildPageModuleSchema = z
|
|
|
130
131
|
*/
|
|
131
132
|
ref: z
|
|
132
133
|
.string()
|
|
133
|
-
.regex(
|
|
134
|
+
.regex(
|
|
135
|
+
/^[A-Za-z][A-Za-z0-9_-]{0,31}$/,
|
|
136
|
+
"ref must be a short handle: a letter, then letters, digits, _ or -",
|
|
137
|
+
)
|
|
134
138
|
.optional(),
|
|
135
139
|
/**
|
|
136
140
|
* Place mode: an existing module from `list_modules` (UUID), or a
|
|
@@ -155,9 +159,9 @@ export const buildPageModuleSchema = z
|
|
|
155
159
|
description: z.string().max(1000).optional(),
|
|
156
160
|
kind: moduleKindSchema.optional(),
|
|
157
161
|
type: slugSchema.optional(),
|
|
158
|
-
html: sourceTextArg(z.string().min(1).max(MODULE_HTML_MAX)).optional(),
|
|
159
|
-
css: sourceTextArg(z.string().max(MODULE_CSS_MAX)).optional(),
|
|
160
|
-
js: sourceTextArg(z.string().max(MODULE_JS_MAX)).optional(),
|
|
162
|
+
html: sourceTextArg(z.string().min(1).max(MODULE_HTML_MAX), "html").optional(),
|
|
163
|
+
css: sourceTextArg(z.string().max(MODULE_CSS_MAX), "css").optional(),
|
|
164
|
+
js: sourceTextArg(z.string().max(MODULE_JS_MAX), "js").optional(),
|
|
161
165
|
fields: z.array(moduleFieldSchema).max(64).optional(),
|
|
162
166
|
/** issue #164 slice 2 — opt-in mechanical token binding (tool layer). */
|
|
163
167
|
bindThemeLiterals: z.boolean().optional(),
|
|
@@ -188,7 +192,7 @@ export const buildPageModuleSchema = z
|
|
|
188
192
|
// (build_page places; it does not re-author a shared module). To avoid a
|
|
189
193
|
// SILENT dropped change (§2), the HANDLER surfaces an INFO in the result
|
|
190
194
|
// naming any ignored authoring field whose value DIFFERS from the module's
|
|
191
|
-
// stored one, and points at
|
|
195
|
+
// stored one, and points at update_modules_many. The schema can't do that check (no
|
|
192
196
|
// DB), so it just allows the entry through.
|
|
193
197
|
if (entry.moduleId !== undefined) return;
|
|
194
198
|
if (entry.displayName === undefined || entry.html === undefined) {
|
|
@@ -216,7 +220,7 @@ export const buildPageTargetSchema = z
|
|
|
216
220
|
pageId: z.string().uuid().optional(),
|
|
217
221
|
slug: slugSchema.optional(),
|
|
218
222
|
title: z.string().min(1).max(256).optional(),
|
|
219
|
-
|
|
223
|
+
shortTitle: pageCreateSchema.shape.shortTitle,
|
|
220
224
|
templateId: z.string().uuid().optional(),
|
|
221
225
|
status: pageStatusSchema.optional(),
|
|
222
226
|
/**
|
|
@@ -224,7 +228,16 @@ export const buildPageTargetSchema = z
|
|
|
224
228
|
* "<parent's path>/<slug>". Create mode only (an existing page moves
|
|
225
229
|
* with `pages.move_many`).
|
|
226
230
|
*/
|
|
227
|
-
|
|
231
|
+
// null = top level, same as pages.create (sibling tools must agree — #106 class).
|
|
232
|
+
parentPageId: z.string().uuid().nullable().optional(),
|
|
233
|
+
/**
|
|
234
|
+
* Epic #1107 — what the new page IS (page type slug), its properties
|
|
235
|
+
* and terms; same semantics as `pages.create` (create mode only — an
|
|
236
|
+
* existing page changes type with `pages.update`).
|
|
237
|
+
*/
|
|
238
|
+
pageType: pageCreateSchema.shape.pageType,
|
|
239
|
+
properties: pageCreateSchema.shape.properties,
|
|
240
|
+
terms: pageCreateSchema.shape.terms,
|
|
228
241
|
/**
|
|
229
242
|
* Migration linkage (issue #278 flow): the `import_pages` row this
|
|
230
243
|
* page rebuilds. The op stamps `import_pages.accepted_page_id` with
|
|
@@ -252,7 +265,17 @@ export const buildPageTargetSchema = z
|
|
|
252
265
|
.strict()
|
|
253
266
|
.superRefine((page, ctx) => {
|
|
254
267
|
const createKeys = (
|
|
255
|
-
[
|
|
268
|
+
[
|
|
269
|
+
"slug",
|
|
270
|
+
"title",
|
|
271
|
+
"shortTitle",
|
|
272
|
+
"templateId",
|
|
273
|
+
"status",
|
|
274
|
+
"parentPageId",
|
|
275
|
+
"pageType",
|
|
276
|
+
"properties",
|
|
277
|
+
"terms",
|
|
278
|
+
] as const
|
|
256
279
|
).filter((k) => page[k] !== undefined);
|
|
257
280
|
if (page.pageId !== undefined) {
|
|
258
281
|
if (createKeys.length > 0) {
|