@plitzi/sdk-server 0.32.16 → 0.32.18
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/CHANGELOG.md +20 -0
- package/dist/helpers/serverLog.js +3 -3
- package/dist/modules/mcp/apps/render/view.tsx +107 -3
- package/dist/modules/mcp/apps/shared/shell.ejs +19 -2
- package/dist/modules/mcp/helpers/guide.js +9 -2
- package/dist/modules/mcp/helpers/space.js +1 -1
- package/dist/modules/mcp/resources/renderGuide.js +205 -18
- package/dist/modules/mcp/server.js +1 -1
- package/dist/modules/mcp/tools/apply/dispatch.js +2 -0
- package/dist/modules/mcp/tools/apply/index.js +17 -3
- package/dist/modules/mcp/tools/operations/index.js +4 -0
- package/dist/modules/mcp/tools/operations/schema/elements/patchElement.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/elements/repeatElement.js +148 -0
- package/dist/modules/mcp/tools/operations/schema/interactions/deleteInteraction.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/interactions/patchInteractionNode.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/operations.js +2 -0
- package/dist/modules/mcp/tools/operations/schema/pages/upsertPage.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/shared.js +8 -5
- package/dist/modules/mcp/tools/operations/schemaIds.js +54 -0
- package/dist/modules/mcp/tools/operations/style/definitions/patchDefinition.js +1 -1
- package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinition.js +1 -1
- package/dist/modules/mcp/tools/operations/style/definitions/upsertDefinitions.js +40 -0
- package/dist/modules/mcp/tools/operations/style/globalStyles/patchGlobalStyle.js +1 -1
- package/dist/modules/mcp/tools/operations/style/globalStyles/upsertGlobalStyle.js +1 -1
- package/dist/modules/mcp/tools/operations/style/idStyles/patchIdStyle.js +1 -1
- package/dist/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.js +1 -1
- package/dist/modules/mcp/tools/operations/style/operations.js +2 -0
- package/dist/modules/mcp/tools/operations/style/shared.js +1 -1
- package/dist/modules/mcp/tools/render.js +70 -8
- package/dist/modules/mcp/tools/shared/expandOperations.js +44 -0
- package/dist/modules/mcp/tools/shared/validator/audit.js +3 -0
- package/dist/modules/mcp/tools/shared/validator/batch.js +13 -4
- package/dist/modules/mcp/tools/shared/validator/index.js +15 -0
- package/dist/modules/mcp/tools/validate.js +11 -3
- package/dist/modules/oauth/metadata.js +11 -4
- package/dist/src/helpers/serverLog.d.ts +1 -1
- package/dist/src/modules/mcp/apps/render/heldBatch.d.ts +10 -0
- package/dist/src/modules/mcp/apps/render/heldBatch.test.d.ts +1 -0
- package/dist/src/modules/mcp/e2e/index.d.ts +2 -2
- package/dist/src/modules/mcp/e2e/renderingHost.d.ts +16 -1
- package/dist/src/modules/mcp/helpers/guide.d.ts +1 -1
- package/dist/src/modules/mcp/helpers/space.d.ts +1 -1
- package/dist/src/modules/mcp/tests/schemaIds.test.d.ts +1 -0
- package/dist/src/modules/mcp/tools/apply/index.d.ts +50 -0
- package/dist/src/modules/mcp/tools/operations/index.d.ts +100 -0
- package/dist/src/modules/mcp/tools/operations/schema/elements/repeatElement.d.ts +55 -0
- package/dist/src/modules/mcp/tools/operations/schema/index.d.ts +1 -0
- package/dist/src/modules/mcp/tools/operations/schema/operations.d.ts +19 -0
- package/dist/src/modules/mcp/tools/operations/schema/shared.d.ts +15 -0
- package/dist/src/modules/mcp/tools/operations/schemaIds.d.ts +6 -0
- package/dist/src/modules/mcp/tools/operations/style/definitions/upsertDefinitions.d.ts +41 -0
- package/dist/src/modules/mcp/tools/operations/style/index.d.ts +1 -0
- package/dist/src/modules/mcp/tools/operations/style/operations.d.ts +33 -0
- package/dist/src/modules/mcp/tools/operations/style/shared.d.ts +4 -3
- package/dist/src/modules/mcp/tools/preview.d.ts +50 -0
- package/dist/src/modules/mcp/tools/render.d.ts +421 -363
- package/dist/src/modules/mcp/tools/screenshot.d.ts +50 -0
- package/dist/src/modules/mcp/tools/shared/expandOperations.d.ts +13 -0
- package/dist/src/modules/mcp/tools/validate.d.ts +50 -0
- package/dist/src/modules/oauth/metadata.d.ts +11 -4
- package/package.json +5 -5
|
@@ -10,7 +10,7 @@ var patchElementOp = z.object({
|
|
|
10
10
|
type: z.literal("patchElement"),
|
|
11
11
|
pageRef: z.string().describe("Page ref or id"),
|
|
12
12
|
ref: z.string().describe("Existing element ref or id"),
|
|
13
|
-
idRef: z.string().optional().describe("Assign or rename
|
|
13
|
+
idRef: z.string().optional().describe("Assign or rename this element idRef ([A-Za-z0-9_-], starting with a letter, unique in the space). Without one an element publishes no data source, so it is not bindable. A rename moves the source name with it: every binding and interaction that targeted the old one is repointed for you."),
|
|
14
14
|
label: z.string().optional(),
|
|
15
15
|
subType: z.string().optional(),
|
|
16
16
|
props: z.record(z.string(), z.unknown()).optional().describe("Merged onto existing props: listed keys change, null unsets a key, others are preserved"),
|
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
import { elementInput, elementShape, position, styleRefs } from "../shared.js";
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
//#region src/modules/mcp/tools/operations/schema/elements/repeatElement.ts
|
|
4
|
+
var item = z.record(z.string(), z.unknown());
|
|
5
|
+
var MAX_ITEMS = 100;
|
|
6
|
+
var MAX_ROWS = 500;
|
|
7
|
+
var templateElement = z.lazy(() => z.object({
|
|
8
|
+
...elementShape,
|
|
9
|
+
children: z.array(templateElement).optional(),
|
|
10
|
+
repeat: z.object({
|
|
11
|
+
items: z.string().describe("The row field holding the sub-list, as \"{{item.<field>}}\" (or just the field path)"),
|
|
12
|
+
template: elementInput.describe("Rendered once per entry of that sub-list (a plain element tree)")
|
|
13
|
+
}).optional().describe("Makes THIS element the wrapper of a nested list — a list inside each row")
|
|
14
|
+
}));
|
|
15
|
+
var repeatElementOp = z.object({
|
|
16
|
+
type: z.literal("repeatElement"),
|
|
17
|
+
pageRef: z.string().describe("Page ref or id"),
|
|
18
|
+
ref: z.string().describe("Ref of the WRAPPER element this creates; the rows become its children"),
|
|
19
|
+
elementType: z.string().optional().describe("Type of the wrapper; defaults to container"),
|
|
20
|
+
label: z.string().optional(),
|
|
21
|
+
style: styleRefs.optional().describe("Classes for the wrapper — this is where the row/grid layout goes"),
|
|
22
|
+
parentRef: z.string().optional().describe("Anchor ref/id; defaults to page root"),
|
|
23
|
+
position: position.optional(),
|
|
24
|
+
template: templateElement.describe("The subtree ONE row renders, with {{item.field}} placeholders"),
|
|
25
|
+
items: z.array(item).min(1).max(MAX_ITEMS).describe("One object per row; its fields fill the {{item.field}} placeholders of the template")
|
|
26
|
+
}).describe("Build a LIST from one template plus its `items` data, instead of repeating near-identical elements. Creates a wrapper whose children are the template rendered once per row. `{{item.<field>}}` is replaced by that row field — alone as the whole value it keeps the field type, mixed with text it interpolates. Every ref gets the row number appended (\"day\" → \"day-1\"), which is how you address a row later. A template node may carry `repeat: { items: \"{{item.<list>}}\", template: … }` to nest a list inside each row: it becomes the sub-list wrapper, refs number both levels (\"step-2-3\"), and `{{item.…}}` there reads the SUB-row. Other {{…}} names are untouched, so schema variables still work.");
|
|
27
|
+
var PLACEHOLDER = /\{\{\s*item\.([A-Za-z0-9_.-]+)\s*\}\}/g;
|
|
28
|
+
var WHOLE_PLACEHOLDER = /^\{\{\s*item\.([A-Za-z0-9_.-]+)\s*\}\}$/;
|
|
29
|
+
var lookup = (row, path) => path.split(".").reduce((value, key) => {
|
|
30
|
+
if (value === null || typeof value !== "object") return;
|
|
31
|
+
return value[key];
|
|
32
|
+
}, row);
|
|
33
|
+
/** One string of the template, with its placeholders resolved. A placeholder that IS the whole string yields the
|
|
34
|
+
* field's own value (a number stays a number, an array stays an array); mixed text interpolates. */
|
|
35
|
+
var fillString = (text, row) => {
|
|
36
|
+
const whole = WHOLE_PLACEHOLDER.exec(text);
|
|
37
|
+
if (whole) {
|
|
38
|
+
const value = lookup(row, whole[1]);
|
|
39
|
+
return value === void 0 ? {
|
|
40
|
+
value: text,
|
|
41
|
+
missing: whole[1]
|
|
42
|
+
} : { value };
|
|
43
|
+
}
|
|
44
|
+
let missing;
|
|
45
|
+
const value = text.replace(PLACEHOLDER, (match, path) => {
|
|
46
|
+
const found = lookup(row, path);
|
|
47
|
+
if (found === void 0) {
|
|
48
|
+
missing ??= path;
|
|
49
|
+
return match;
|
|
50
|
+
}
|
|
51
|
+
if (typeof found === "string") return found;
|
|
52
|
+
return typeof found === "number" || typeof found === "boolean" ? found.toString() : JSON.stringify(found);
|
|
53
|
+
});
|
|
54
|
+
return missing === void 0 ? { value } : {
|
|
55
|
+
value,
|
|
56
|
+
missing
|
|
57
|
+
};
|
|
58
|
+
};
|
|
59
|
+
var fillValue = (value, row, missing) => {
|
|
60
|
+
if (typeof value === "string") {
|
|
61
|
+
const filled = fillString(value, row);
|
|
62
|
+
if (filled.missing) missing.add(filled.missing);
|
|
63
|
+
return filled.value;
|
|
64
|
+
}
|
|
65
|
+
if (Array.isArray(value)) return value.map((entry) => fillValue(entry, row, missing));
|
|
66
|
+
if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, fillValue(entry, row, missing)]));
|
|
67
|
+
return value;
|
|
68
|
+
};
|
|
69
|
+
var listOf = (row, spec) => {
|
|
70
|
+
const whole = WHOLE_PLACEHOLDER.exec(spec);
|
|
71
|
+
const value = lookup(row, whole ? whole[1] : spec);
|
|
72
|
+
if (!Array.isArray(value)) return;
|
|
73
|
+
return value.map((entry) => entry !== null && typeof entry === "object" ? entry : { value: entry });
|
|
74
|
+
};
|
|
75
|
+
/** One template node against one row. `suffix` is the row numbering accumulated from the OUTSIDE in, so a nested
|
|
76
|
+
* row reads "step-2-3" (row 2, sub-row 3) rather than the other way round. */
|
|
77
|
+
var expandNode = (node, row, suffix, state) => {
|
|
78
|
+
const { children, repeat, ...own } = node;
|
|
79
|
+
const filled = fillValue(own, row, state.missing);
|
|
80
|
+
const element = {
|
|
81
|
+
...filled,
|
|
82
|
+
ref: `${filled.ref}${suffix}`
|
|
83
|
+
};
|
|
84
|
+
if (repeat) {
|
|
85
|
+
const rows = listOf(row, repeat.items);
|
|
86
|
+
if (!rows) {
|
|
87
|
+
state.notAList.add(repeat.items);
|
|
88
|
+
return element;
|
|
89
|
+
}
|
|
90
|
+
const capped = rows.slice(0, MAX_ITEMS);
|
|
91
|
+
state.rows += capped.length;
|
|
92
|
+
element.children = capped.map((subRow, index) => expandNode(repeat.template, subRow, `${suffix}-${index + 1}`, state));
|
|
93
|
+
return element;
|
|
94
|
+
}
|
|
95
|
+
if (children) element.children = children.map((child) => expandNode(child, row, suffix, state));
|
|
96
|
+
return element;
|
|
97
|
+
};
|
|
98
|
+
var rowError = (index, message, hint) => ({
|
|
99
|
+
path: `items[${index}]`,
|
|
100
|
+
message,
|
|
101
|
+
hint
|
|
102
|
+
});
|
|
103
|
+
var keysOf = (row) => Object.keys(row).join(", ") || "(nothing)";
|
|
104
|
+
/** Expand one repeat into the single upsertElement it stands for: the wrapper, with the template rendered once per
|
|
105
|
+
* row as its children. Returns the errors instead of throwing, so a bad row reads like every other op error. */
|
|
106
|
+
var expandRepeat = (op) => {
|
|
107
|
+
const errors = [];
|
|
108
|
+
const children = [];
|
|
109
|
+
let total = op.items.length;
|
|
110
|
+
for (const [index, row] of op.items.entries()) {
|
|
111
|
+
const state = {
|
|
112
|
+
missing: /* @__PURE__ */ new Set(),
|
|
113
|
+
notAList: /* @__PURE__ */ new Set(),
|
|
114
|
+
rows: 0
|
|
115
|
+
};
|
|
116
|
+
const expanded = expandNode(op.template, row, `-${index + 1}`, state);
|
|
117
|
+
total += state.rows;
|
|
118
|
+
if (state.missing.size > 0) {
|
|
119
|
+
const fields = [...state.missing];
|
|
120
|
+
errors.push(rowError(index, `Row ${index + 1} has no ${fields.map((field) => `"${field}"`).join(", ")}`, `The template reads it as {{item.${fields[0]}}}. This row carries: ${keysOf(row)}`));
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
if (state.notAList.size > 0) {
|
|
124
|
+
const [spec] = [...state.notAList];
|
|
125
|
+
errors.push(rowError(index, `Row ${index + 1} has no list at ${spec}`, `A nested repeat needs an ARRAY in that field. This row carries: ${keysOf(row)}`));
|
|
126
|
+
continue;
|
|
127
|
+
}
|
|
128
|
+
children.push(expanded);
|
|
129
|
+
}
|
|
130
|
+
if (errors.length > 0) return { errors };
|
|
131
|
+
if (total > MAX_ROWS) return { errors: [{
|
|
132
|
+
path: "items",
|
|
133
|
+
message: `This repeat expands to ${total} rows (max ${MAX_ROWS})`,
|
|
134
|
+
hint: "Shorten the lists, or split the widget into several repeats."
|
|
135
|
+
}] };
|
|
136
|
+
return {
|
|
137
|
+
element: {
|
|
138
|
+
ref: op.ref,
|
|
139
|
+
type: op.elementType ?? "container",
|
|
140
|
+
...op.label === void 0 ? {} : { label: op.label },
|
|
141
|
+
...op.style === void 0 ? {} : { style: op.style },
|
|
142
|
+
children
|
|
143
|
+
},
|
|
144
|
+
errors
|
|
145
|
+
};
|
|
146
|
+
};
|
|
147
|
+
//#endregion
|
|
148
|
+
export { expandRepeat, repeatElementOp };
|
|
@@ -9,7 +9,7 @@ var deleteInteractionOp = z.object({
|
|
|
9
9
|
ref: z.string().describe("Element ref or id"),
|
|
10
10
|
flowId: z.string().optional().describe("Remove the whole flow with this id (the trigger node id)"),
|
|
11
11
|
nodeId: z.string().optional().describe("Remove a single step; its neighbors are re-linked. Deleting a trigger removes its flow")
|
|
12
|
-
}).describe("DESTRUCTIVE, not undoable — remove
|
|
12
|
+
}).describe("DESTRUCTIVE, not undoable — remove a flow (by flowId) or a single step (by nodeId); provide exactly one. To turn a step OFF instead, patchInteractionNode { enabled: false }. Confirm with the user first.");
|
|
13
13
|
var removeFlow = (interactions, flowId) => {
|
|
14
14
|
let deleted = 0;
|
|
15
15
|
for (const [nodeId, node] of Object.entries(interactions)) if ((node.flowId || node.id) === flowId) {
|
|
@@ -18,7 +18,7 @@ var patchInteractionNodeOp = z.object({
|
|
|
18
18
|
when,
|
|
19
19
|
elementId,
|
|
20
20
|
preview
|
|
21
|
-
}).describe("Partially update ONE step of
|
|
21
|
+
}).describe("Partially update ONE step of a flow (found by nodeId): only the fields you pass change, the order/links are untouched. This is how you DISABLE a step without removing it — { enabled: false } keeps it in the flow. deleteInteraction is for actually removing one. Fails if the node does not exist.");
|
|
22
22
|
var patchInteractionNode = (space, env, op) => {
|
|
23
23
|
const found = resolveElement(space, env, op.pageRef, op.ref);
|
|
24
24
|
if ("error" in found) return found.error;
|
|
@@ -11,7 +11,7 @@ var upsertInteractionFlowOp = z.object({
|
|
|
11
11
|
ref: z.string().describe("Element ref or id"),
|
|
12
12
|
flowId: z.string().optional().describe("Existing flow to replace (the trigger node id). Omit to create a new flow"),
|
|
13
13
|
nodes: z.array(interactionNode).min(1).describe("Ordered steps of the flow: the FIRST must be a trigger, the rest run after it in order")
|
|
14
|
-
}).describe("Create or replace one interaction flow on an element from an ordered list of steps
|
|
14
|
+
}).describe("Create or replace one interaction flow on an element from an ordered list of steps — pass them in execution order, the beforeNode/afterNode/flowId links are computed for you. For a globalCallback OMIT elementId: it is registered on a source module, not the host element, and the source and default params are filled in for you. To edit a single step in place use patchInteractionNode.");
|
|
15
15
|
var upsertInteractionFlow = (space, env, op) => {
|
|
16
16
|
const found = resolveElement(space, env, op.pageRef, op.ref);
|
|
17
17
|
if ("error" in found) return found.error;
|
|
@@ -4,6 +4,7 @@ import { upsertBindingOp } from "./bindings/upsertBinding.js";
|
|
|
4
4
|
import { deleteElementOp } from "./elements/deleteElement.js";
|
|
5
5
|
import { moveElementOp } from "./elements/moveElement.js";
|
|
6
6
|
import { patchElementOp } from "./elements/patchElement.js";
|
|
7
|
+
import { repeatElementOp } from "./elements/repeatElement.js";
|
|
7
8
|
import { upsertElementOp } from "./elements/upsertElement.js";
|
|
8
9
|
import { deleteFolderOp } from "./folders/deleteFolder.js";
|
|
9
10
|
import { upsertFolderOp } from "./folders/upsertFolder.js";
|
|
@@ -18,6 +19,7 @@ import { upsertVariableOp } from "./variables/upsertVariable.js";
|
|
|
18
19
|
//#region src/modules/mcp/tools/operations/schema/operations.ts
|
|
19
20
|
var elementOps = {
|
|
20
21
|
upsertElement: upsertElementOp,
|
|
22
|
+
repeatElement: repeatElementOp,
|
|
21
23
|
patchElement: patchElementOp,
|
|
22
24
|
deleteElement: deleteElementOp,
|
|
23
25
|
moveElement: moveElementOp,
|
|
@@ -8,7 +8,7 @@ var upsertPageOp = z.object({
|
|
|
8
8
|
type: z.literal("upsertPage"),
|
|
9
9
|
ref: z.string().describe("Page id/slug to update, or a new id you choose to create one. On a new page it is stored as its idRef: starts with a letter, then letters, numbers, hyphens and underscores (\"pricing\"), unique across the space."),
|
|
10
10
|
label: z.string().optional(),
|
|
11
|
-
slug: z.string().optional().describe("The page URL path, RELATIVE —
|
|
11
|
+
slug: z.string().optional().describe("The page URL path, RELATIVE — never start it with \"/\" (parent folder slugs prepend it; a leading \"/\" is stripped). Set one on create for a stable route: \"pricing\", \"posts/:postId\". A \":name\" segment is a route param, readable as {{name}} and as navigation.routeParams.name. Omitted → the page ref is used."),
|
|
12
12
|
folder: z.string().nullable().optional().describe("Ref of an existing folder to place this page in; \"\" or null moves it to the root. Unknown → error"),
|
|
13
13
|
default: z.boolean().optional(),
|
|
14
14
|
enabled: z.boolean().optional().describe("false disables the page in the published SDK runtime (not routable/accessible to end users); it stays editable here. Defaults to true")
|
|
@@ -60,14 +60,17 @@ var initialStateInput = z.object({
|
|
|
60
60
|
styleVariant: styleVariantInput.optional().describe("Variant each attached class uses: { className: { base|slot: variantName | [names] } }"),
|
|
61
61
|
visibility: z.boolean().optional().describe("Initial visibility of the element")
|
|
62
62
|
});
|
|
63
|
-
var
|
|
64
|
-
ref: z.string().describe("Semantic id you choose, or an existing element ref/id. On a new element
|
|
63
|
+
var elementShape = {
|
|
64
|
+
ref: z.string().describe("Semantic id you choose, or an existing element ref/id. On a new element it becomes the idRef everything addresses it by: its data source is `<type>_<ref>`, and interactions target it by this ref. Letters, digits, `-` and `_`, starting with a letter — a dot would break the `<type>_<idRef>.<field>` grammar. Unique across the space."),
|
|
65
65
|
type: z.string().describe("Type from plitzi://types"),
|
|
66
66
|
label: z.string().optional(),
|
|
67
67
|
subType: z.string().optional(),
|
|
68
68
|
props: z.record(z.string(), z.unknown()).optional().describe("Full replacement on update"),
|
|
69
69
|
style: styleRefs.optional().describe("Definition refs per slot; style the element by attaching a definition"),
|
|
70
|
-
initialState: initialStateInput.optional().describe("Applied style variant(s) and initial visibility (see plitzi://guide styling)")
|
|
70
|
+
initialState: initialStateInput.optional().describe("Applied style variant(s) and initial visibility (see plitzi://guide styling)")
|
|
71
|
+
};
|
|
72
|
+
var elementInput = z.lazy(() => z.object({
|
|
73
|
+
...elementShape,
|
|
71
74
|
children: z.array(elementInput).optional()
|
|
72
75
|
}));
|
|
73
76
|
var position = z.enum([
|
|
@@ -112,8 +115,8 @@ var interactionNode = z.object({
|
|
|
112
115
|
params: z.record(z.string(), z.unknown()).optional(),
|
|
113
116
|
enabled: z.boolean().optional(),
|
|
114
117
|
when: ruleGroup.optional().describe("QueryBuilder RuleGroup gating this step (validated structurally)"),
|
|
115
|
-
elementId: z.string().optional().describe("Element whose callback this step invokes; defaults to this element.
|
|
118
|
+
elementId: z.string().optional().describe("Element whose callback this step invokes; defaults to this element. Its ref or raw id — a raw id is normalised to the idRef the runtime looks callbacks up by, and a target without one is given it."),
|
|
116
119
|
preview: z.record(z.string(), z.unknown()).optional()
|
|
117
120
|
});
|
|
118
121
|
//#endregion
|
|
119
|
-
export { bindingCategory, bindingInput, elementInput, initialStateInput, interactionNode, interactionNodeType, position, ruleGroup, scalar, styleRefs, styleVariantInput };
|
|
122
|
+
export { bindingCategory, bindingInput, elementInput, elementShape, initialStateInput, interactionNode, interactionNodeType, position, ruleGroup, scalar, styleRefs, styleVariantInput };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { bindingInput, elementInput, initialStateInput, interactionNode, position, ruleGroup, styleRefs, styleVariantInput } from "./schema/shared.js";
|
|
2
|
+
import { cssMap, cssPatchMap, definitionSlot, definitionSlotPatch, displayModeCss, displayModeCssPatch, themeValue } from "./style/shared.js";
|
|
3
|
+
import { z } from "zod";
|
|
4
|
+
//#region src/modules/mcp/tools/operations/schemaIds.ts
|
|
5
|
+
/** The op union is the tool input of plitzi_apply, plitzi_validate, plitzi_render AND plitzi_preview, and every
|
|
6
|
+
* host reads it as JSON Schema on tools/list — four copies of the same 30 operations in the model's context, on
|
|
7
|
+
* EVERY request of every conversation the server is connected to. It measured ~25k tokens PER TOOL (~100k in
|
|
8
|
+
* total), which dwarfs anything a widget payload ever costs.
|
|
9
|
+
*
|
|
10
|
+
* Most of that weight is one subschema pasted over and over: an element tree appears in upsertElement,
|
|
11
|
+
* patchElement and both repeat templates; a breakpoint CSS block in every style op; a rule group in every binding
|
|
12
|
+
* and interaction step. Zod emits a named `definitions` entry (and `$ref`s to it) for any schema carrying an `id`
|
|
13
|
+
* in the global registry, so registering the shared ones here collapses those copies — without touching the ops,
|
|
14
|
+
* the tools, or the MCP SDK, whose own converter honours the registry (it is given no options otherwise).
|
|
15
|
+
*
|
|
16
|
+
* An id only pays when the schema is REUSED: giving one to a subschema that appears once makes the listing bigger,
|
|
17
|
+
* because the `definitions` entry and the ref cost more than the single copy they replace (measured, and reverted).
|
|
18
|
+
*
|
|
19
|
+
* The ids are the names the MODEL reads in the refs, so they are written for it: `Element`, not `ElementInput`.
|
|
20
|
+
* This runs once, at module load of the op vocabulary, so every conversion path benefits — the MCP tools list and
|
|
21
|
+
* the co-worker's own tool-schema converter alike. */
|
|
22
|
+
var SHARED_SCHEMAS = [
|
|
23
|
+
[elementInput, "Element"],
|
|
24
|
+
[ruleGroup, "RuleGroup"],
|
|
25
|
+
[styleRefs, "StyleRefs"],
|
|
26
|
+
[initialStateInput, "InitialState"],
|
|
27
|
+
[styleVariantInput, "StyleVariant"],
|
|
28
|
+
[interactionNode, "InteractionNode"],
|
|
29
|
+
[bindingInput, "Binding"],
|
|
30
|
+
[position, "Position"],
|
|
31
|
+
[cssMap, "Css"],
|
|
32
|
+
[cssPatchMap, "CssPatch"],
|
|
33
|
+
[definitionSlot, "StyleSlot"],
|
|
34
|
+
[definitionSlotPatch, "StyleSlotPatch"],
|
|
35
|
+
[displayModeCss, "BreakpointCss"],
|
|
36
|
+
[displayModeCssPatch, "BreakpointCssPatch"],
|
|
37
|
+
[themeValue, "ThemeValue"]
|
|
38
|
+
];
|
|
39
|
+
var claimSchemaId = (schema, id) => {
|
|
40
|
+
const taken = z.globalRegistry._idmap.get(id);
|
|
41
|
+
if (taken !== void 0 && taken !== schema) {
|
|
42
|
+
z.globalRegistry.add(schema, { id: `Plitzi${id}` });
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
z.globalRegistry.add(schema, { id });
|
|
46
|
+
};
|
|
47
|
+
/** Idempotent: the module can be imported more than once in a test run, and a schema that already carries its id
|
|
48
|
+
* is left alone (re-adding the same one is harmless but pointless). Runs at module load, never per request — the
|
|
49
|
+
* MCP builds a server per request and this must not grow with them (pinned in the statelessness test). */
|
|
50
|
+
var registerSharedSchemaIds = () => {
|
|
51
|
+
for (const [schema, id] of SHARED_SCHEMAS) if (z.globalRegistry.get(schema)?.id === void 0) claimSchemaId(schema, id);
|
|
52
|
+
};
|
|
53
|
+
//#endregion
|
|
54
|
+
export { claimSchemaId, registerSharedSchemaIds };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { defUri, defsUri } from "../../../../helpers/uris.js";
|
|
2
2
|
import { empty, fail } from "../../../../helpers/opResult.js";
|
|
3
3
|
import { definitionToAI } from "../translator.js";
|
|
4
|
-
import { guardKind, mergePatch, writeStyleItem } from "../write.js";
|
|
5
4
|
import { patchCssShape } from "../shared.js";
|
|
5
|
+
import { guardKind, mergePatch, writeStyleItem } from "../write.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
//#region src/modules/mcp/tools/operations/style/definitions/patchDefinition.ts
|
|
8
8
|
var patchDefinitionOp = z.object({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { defUri, defsUri } from "../../../../helpers/uris.js";
|
|
2
2
|
import { empty } from "../../../../helpers/opResult.js";
|
|
3
|
-
import { guardKind, writeStyleItem } from "../write.js";
|
|
4
3
|
import { upsertCssShape } from "../shared.js";
|
|
4
|
+
import { guardKind, writeStyleItem } from "../write.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/modules/mcp/tools/operations/style/definitions/upsertDefinition.ts
|
|
7
7
|
var upsertDefinitionOp = z.object({
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { defUri, defsUri } from "../../../../helpers/uris.js";
|
|
2
|
+
import { empty } from "../../../../helpers/opResult.js";
|
|
3
|
+
import { upsertCssShape } from "../shared.js";
|
|
4
|
+
import { guardKind, writeStyleItem } from "../write.js";
|
|
5
|
+
import { z } from "zod";
|
|
6
|
+
//#region src/modules/mcp/tools/operations/style/definitions/upsertDefinitions.ts
|
|
7
|
+
var definitionBody = z.object(upsertCssShape);
|
|
8
|
+
var upsertDefinitionsOp = z.object({
|
|
9
|
+
type: z.literal("upsertDefinitions"),
|
|
10
|
+
definitions: z.record(z.string(), definitionBody).describe("Every class keyed by the class name you choose (kebab-case); the value is that class CSS")
|
|
11
|
+
}).describe("Create or fully replace SEVERAL style classes in ONE operation — identical to a run of upsertDefinition, minus the repeated envelope, so prefer it whenever a batch declares more than one class. Apply each by attaching its name to an element via style.base. Use patchDefinition to change only some CSS of one class.");
|
|
12
|
+
/** Writes each entry exactly as {@link upsertDefinition} would. Entries are independent: one bad class reports its
|
|
13
|
+
* own error under `definitions.<ref>` and the rest still write — nothing is persisted while the batch carries
|
|
14
|
+
* errors, so a partial write cannot escape. */
|
|
15
|
+
var upsertDefinitions = (space, env, op) => {
|
|
16
|
+
const result = {
|
|
17
|
+
...empty(),
|
|
18
|
+
staleResources: [defsUri(env)]
|
|
19
|
+
};
|
|
20
|
+
const errors = [];
|
|
21
|
+
for (const [ref, { slots, ...base }] of Object.entries(op.definitions)) {
|
|
22
|
+
const guard = guardKind(space.style, ref, "class");
|
|
23
|
+
if (guard?.errors) {
|
|
24
|
+
errors.push(...guard.errors.map((error) => ({
|
|
25
|
+
...error,
|
|
26
|
+
path: `definitions.${ref}`
|
|
27
|
+
})));
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
writeStyleItem(space.style, ref, base, slots, "class", void 0);
|
|
31
|
+
result.updated += 1;
|
|
32
|
+
result.staleResources.push(defUri(env, ref));
|
|
33
|
+
}
|
|
34
|
+
return errors.length > 0 ? {
|
|
35
|
+
...result,
|
|
36
|
+
errors
|
|
37
|
+
} : result;
|
|
38
|
+
};
|
|
39
|
+
//#endregion
|
|
40
|
+
export { upsertDefinitions, upsertDefinitionsOp };
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { globalUri, globalsUri } from "../../../../helpers/uris.js";
|
|
2
2
|
import { empty, fail } from "../../../../helpers/opResult.js";
|
|
3
3
|
import { globalStyleToAI } from "../translator.js";
|
|
4
|
-
import { guardKind, mergePatch, writeStyleItem } from "../write.js";
|
|
5
4
|
import { patchCssShape } from "../shared.js";
|
|
5
|
+
import { guardKind, mergePatch, writeStyleItem } from "../write.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
//#region src/modules/mcp/tools/operations/style/globalStyles/patchGlobalStyle.ts
|
|
8
8
|
var patchGlobalStyleOp = z.object({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { globalUri, globalsUri } from "../../../../helpers/uris.js";
|
|
2
2
|
import { empty } from "../../../../helpers/opResult.js";
|
|
3
|
-
import { guardKind, writeStyleItem } from "../write.js";
|
|
4
3
|
import { upsertCssShape } from "../shared.js";
|
|
4
|
+
import { guardKind, writeStyleItem } from "../write.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/modules/mcp/tools/operations/style/globalStyles/upsertGlobalStyle.ts
|
|
7
7
|
var upsertGlobalStyleOp = z.object({
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { idUri, idsUri } from "../../../../helpers/uris.js";
|
|
2
2
|
import { empty, fail } from "../../../../helpers/opResult.js";
|
|
3
3
|
import { idStyleToAI } from "../translator.js";
|
|
4
|
-
import { guardKind, mergePatch, writeStyleItem } from "../write.js";
|
|
5
4
|
import { patchCssShape } from "../shared.js";
|
|
5
|
+
import { guardKind, mergePatch, writeStyleItem } from "../write.js";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
//#region src/modules/mcp/tools/operations/style/idStyles/patchIdStyle.ts
|
|
8
8
|
var patchIdStyleOp = z.object({
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { idUri, idsUri } from "../../../../helpers/uris.js";
|
|
2
2
|
import { empty } from "../../../../helpers/opResult.js";
|
|
3
|
-
import { guardKind, writeStyleItem } from "../write.js";
|
|
4
3
|
import { upsertCssShape } from "../shared.js";
|
|
4
|
+
import { guardKind, writeStyleItem } from "../write.js";
|
|
5
5
|
import { z } from "zod";
|
|
6
6
|
//#region src/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.ts
|
|
7
7
|
var upsertIdStyleOp = z.object({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { deleteDefinitionOp } from "./definitions/deleteDefinition.js";
|
|
2
2
|
import { patchDefinitionOp } from "./definitions/patchDefinition.js";
|
|
3
3
|
import { upsertDefinitionOp } from "./definitions/upsertDefinition.js";
|
|
4
|
+
import { upsertDefinitionsOp } from "./definitions/upsertDefinitions.js";
|
|
4
5
|
import { deleteGlobalStyleOp } from "./globalStyles/deleteGlobalStyle.js";
|
|
5
6
|
import { patchGlobalStyleOp } from "./globalStyles/patchGlobalStyle.js";
|
|
6
7
|
import { upsertGlobalStyleOp } from "./globalStyles/upsertGlobalStyle.js";
|
|
@@ -12,6 +13,7 @@ import { upsertStyleVariableOp } from "./variables/upsertStyleVariable.js";
|
|
|
12
13
|
//#region src/modules/mcp/tools/operations/style/operations.ts
|
|
13
14
|
var styleOps = {
|
|
14
15
|
upsertDefinition: upsertDefinitionOp,
|
|
16
|
+
upsertDefinitions: upsertDefinitionsOp,
|
|
15
17
|
patchDefinition: patchDefinitionOp,
|
|
16
18
|
deleteDefinition: deleteDefinitionOp,
|
|
17
19
|
upsertGlobalStyle: upsertGlobalStyleOp,
|
|
@@ -56,4 +56,4 @@ var patchCssShape = {
|
|
|
56
56
|
slots: z.record(z.string(), definitionSlotPatch).optional()
|
|
57
57
|
};
|
|
58
58
|
//#endregion
|
|
59
|
-
export { displayModeCss, displayModeCssPatch, patchCssShape, styleCategory, themeValue, upsertCssShape };
|
|
59
|
+
export { cssMap, cssPatchMap, definitionSlot, definitionSlotPatch, displayModeCss, displayModeCssPatch, patchCssShape, styleCategory, themeValue, upsertCssShape };
|
|
@@ -2,9 +2,12 @@ import { RENDER_APP_URI } from "../apps/render/index.js";
|
|
|
2
2
|
import { emptySpace } from "../helpers/space.js";
|
|
3
3
|
import { operations } from "./operations/index.js";
|
|
4
4
|
import { applyOperations } from "./apply/dispatch.js";
|
|
5
|
+
import { expandOperations } from "./shared/expandOperations.js";
|
|
5
6
|
import { defineTool } from "./shared/tool.js";
|
|
6
7
|
import { validateOperations } from "./shared/validator/index.js";
|
|
7
8
|
import { auditResources } from "./shared/validator/audit.js";
|
|
9
|
+
import { randomUUID } from "node:crypto";
|
|
10
|
+
import { z } from "zod";
|
|
8
11
|
import { validateSchema } from "@plitzi/sdk-schema/helpers/schemaValidator";
|
|
9
12
|
import { generateCache } from "@plitzi/sdk-style/StyleHelper";
|
|
10
13
|
//#region src/modules/mcp/tools/render.ts
|
|
@@ -32,16 +35,26 @@ var seedSpace = () => {
|
|
|
32
35
|
return space;
|
|
33
36
|
};
|
|
34
37
|
var noWarnings = (warnings) => warnings.length > 0 ? warnings : void 0;
|
|
35
|
-
var renderShape = {
|
|
38
|
+
var renderShape = {
|
|
39
|
+
operations,
|
|
40
|
+
patch: z.boolean().optional().describe("Set true to CHANGE a widget you already rendered instead of rebuilding it: send its `renderId` and ONLY the operations that differ (patchDefinition, patchElement, deleteElement, a new repeatElement…). The widget merges them into the batch it was built from and reports back what it applied. The refs it already has (card-1, blk-2-3) are the ones to address. If that widget cannot be recovered — a surface that renders none, a host that keeps no storage — the answer says so and you re-send the whole batch."),
|
|
41
|
+
renderId: z.string().optional().describe("Handle returned by a previous render. Required with patch:true; it names the widget being changed.")
|
|
42
|
+
};
|
|
36
43
|
var render = (input) => {
|
|
37
44
|
const space = seedSpace();
|
|
38
|
-
const
|
|
45
|
+
const expansion = expandOperations(input.operations);
|
|
46
|
+
if (expansion.errors.length > 0) return {
|
|
47
|
+
rendered: false,
|
|
48
|
+
errors: expansion.errors
|
|
49
|
+
};
|
|
50
|
+
const ops = expansion.operations;
|
|
51
|
+
const validation = validateOperations(space, ops);
|
|
39
52
|
if (!validation.valid) return {
|
|
40
53
|
rendered: false,
|
|
41
54
|
errors: validation.errors,
|
|
42
55
|
warnings: noWarnings(validation.warnings)
|
|
43
56
|
};
|
|
44
|
-
const outcome = applyOperations(space, "main",
|
|
57
|
+
const outcome = applyOperations(space, "main", ops);
|
|
45
58
|
if (outcome.errors.length > 0) return {
|
|
46
59
|
rendered: false,
|
|
47
60
|
errors: outcome.errors,
|
|
@@ -57,7 +70,7 @@ var render = (input) => {
|
|
|
57
70
|
})),
|
|
58
71
|
warnings: noWarnings(validation.warnings)
|
|
59
72
|
};
|
|
60
|
-
const audit = auditResources(space,
|
|
73
|
+
const audit = auditResources(space, ops);
|
|
61
74
|
const warnings = [...validation.warnings, ...audit.warnings];
|
|
62
75
|
if (audit.errors.length > 0) return {
|
|
63
76
|
rendered: false,
|
|
@@ -67,6 +80,7 @@ var render = (input) => {
|
|
|
67
80
|
space.style.cache = generateCache(space.style);
|
|
68
81
|
return {
|
|
69
82
|
rendered: true,
|
|
83
|
+
operations: ops,
|
|
70
84
|
rootRef: HOST_PAGE_REF,
|
|
71
85
|
elementCount: Object.keys(space.schema.flat).length - 1,
|
|
72
86
|
offlineData: {
|
|
@@ -76,7 +90,53 @@ var render = (input) => {
|
|
|
76
90
|
warnings: noWarnings(warnings)
|
|
77
91
|
};
|
|
78
92
|
};
|
|
79
|
-
|
|
93
|
+
/** A patch is not rendered here — it CANNOT be: this server keeps nothing between calls (no session, no store,
|
|
94
|
+
* any replica answers any request), so the previous widget only exists on the host side. The delta therefore
|
|
95
|
+
* travels as a courier result: the model pays for the delta alone, the view recovers the batch that widget was
|
|
96
|
+
* built from (by renderId — see apps/render/heldBatch.ts), merges, and re-calls this same tool with the whole
|
|
97
|
+
* thing over the host bridge, never through the model's context, then reports back with ui/update-model-context.
|
|
98
|
+
*
|
|
99
|
+
* The renderId is the explicit handle the MCP RC asks for in place of implicit session state: the model carries
|
|
100
|
+
* it, so a patch names the widget it means and any replica can serve it.
|
|
101
|
+
*
|
|
102
|
+
* Validation is not skipped, only deferred: the re-call carries the full batch, so refs, integrity and the audit
|
|
103
|
+
* all run exactly as they do on a first render, and their errors reach the model through that report. */
|
|
104
|
+
var toPatchResult = (ops, renderId) => {
|
|
105
|
+
if (ops.length === 0) return { content: [{
|
|
106
|
+
type: "text",
|
|
107
|
+
text: JSON.stringify({
|
|
108
|
+
patch: false,
|
|
109
|
+
error: "A patch carries no operations",
|
|
110
|
+
hint: "Send the operations that differ from the widget on screen, or drop `patch` to render a new one."
|
|
111
|
+
})
|
|
112
|
+
}] };
|
|
113
|
+
if (renderId === void 0) return { content: [{
|
|
114
|
+
type: "text",
|
|
115
|
+
text: JSON.stringify({
|
|
116
|
+
patch: false,
|
|
117
|
+
error: "A patch needs the renderId of the widget it changes",
|
|
118
|
+
hint: "Pass the renderId the render answered with, or drop `patch` to render a new widget."
|
|
119
|
+
})
|
|
120
|
+
}] };
|
|
121
|
+
return {
|
|
122
|
+
content: [{
|
|
123
|
+
type: "text",
|
|
124
|
+
text: JSON.stringify({
|
|
125
|
+
patch: true,
|
|
126
|
+
renderId,
|
|
127
|
+
operations: ops.length,
|
|
128
|
+
note: "Handed to the widget; it will report what it applied. If nothing reports back, it could not be recovered — re-send the full batch without patch."
|
|
129
|
+
})
|
|
130
|
+
}],
|
|
131
|
+
structuredContent: {
|
|
132
|
+
patch: true,
|
|
133
|
+
renderId,
|
|
134
|
+
operations: ops
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
};
|
|
138
|
+
var newRenderId = () => `r${randomUUID().slice(0, 8)}`;
|
|
139
|
+
var toRenderResult = (res, renderId) => {
|
|
80
140
|
if (!res.rendered) return { content: [{
|
|
81
141
|
type: "text",
|
|
82
142
|
text: JSON.stringify({
|
|
@@ -87,6 +147,7 @@ var toRenderResult = (res) => {
|
|
|
87
147
|
}] };
|
|
88
148
|
const summary = {
|
|
89
149
|
rendered: true,
|
|
150
|
+
renderId,
|
|
90
151
|
rootRef: res.rootRef,
|
|
91
152
|
elementCount: res.elementCount,
|
|
92
153
|
warnings: res.warnings
|
|
@@ -98,19 +159,20 @@ var toRenderResult = (res) => {
|
|
|
98
159
|
}],
|
|
99
160
|
structuredContent: {
|
|
100
161
|
...summary,
|
|
101
|
-
offlineData: res.offlineData
|
|
162
|
+
offlineData: res.offlineData,
|
|
163
|
+
operations: res.operations
|
|
102
164
|
}
|
|
103
165
|
};
|
|
104
166
|
};
|
|
105
167
|
var renderTool = defineTool({
|
|
106
168
|
name: "plitzi_render",
|
|
107
169
|
title: "Render widget",
|
|
108
|
-
description: "Show the user a real, rendered UI widget instead of describing one — cards, hero sections, pricing tables, forms, menus, checklists, profiles, galleries. It runs the Plitzi SDK fully offline: no backend, account, or setup. Reach for it whenever a visual layout beats prose: the user asks you to design/build/show something, OR your answer is naturally visual (a recipe → a card, a comparison → a table, steps → a checklist). Prefer showing over telling.\n\nAuthor the widget as an ordered list of `operations` that build an element tree under the pre-seeded root page \"render\". Three rules:\n1. STRUCTURE — one upsertElement builds the whole tree: set pageRef:\"render\" and give element a nested `children` array. Each element is { ref (unique), type, subType?, props?, style?, children? }; children render in order. (To attach to something you already made, use a top-level parentRef:\"<existing ref>\" instead.)\n2. STYLE — declare
|
|
170
|
+
description: "Show the user a real, rendered UI widget instead of describing one — cards, hero sections, pricing tables, forms, menus, checklists, profiles, galleries. It runs the Plitzi SDK fully offline: no backend, account, or setup. Reach for it whenever a visual layout beats prose: the user asks you to design/build/show something, OR your answer is naturally visual (a recipe → a card, a comparison → a table, steps → a checklist). Prefer showing over telling.\n\nAuthor the widget as an ordered list of `operations` that build an element tree under the pre-seeded root page \"render\". Three rules:\n1. STRUCTURE — one upsertElement builds the whole tree: set pageRef:\"render\" and give element a nested `children` array. Each element is { ref (unique), type, subType?, props?, style?, children? }; children render in order. (To attach to something you already made, use a top-level parentRef:\"<existing ref>\" instead.)\n1b. REPEATS — the moment two siblings share a shape and differ only in data (list, steps, cards, table, timeline), do NOT copy-paste them: use repeatElement { pageRef, ref (wrapper), style, template, items }. The template is written once with {{item.field}} placeholders and rendered per row; refs come out numbered (\"step-1\", \"step-2\"…). A list INSIDE each row (days with their own steps) is the same op: give the wrapping node repeat:{ items:\"{{item.<list>}}\", template:… } and put the sub-rows in the row data.\n2. STYLE — declare ALL the classes in ONE upsertDefinitions { definitions: { \"<class>\": { desktop:{ …CSS in kebab-case… } }, … } }, then attach via the element style:{ base:[\"<class ref>\"] }. Lay containers out with flex/grid. Keep the call small: one class per look (not per property), and never hand-draw a scene in a data: URI — it costs more than the whole widget; use an https image, a flat colour or a gradient.\n2b. LAYOUT — it renders in a side panel, so width is free and HEIGHT is scarce. Plain containers stack children vertically, which is the tall half-empty default to avoid: put peers (metrics, plans, options, image + text) in a wrapping row — display:flex, flex-direction:row, flex-wrap:wrap, children flex-grow:\"1\" + flex-basis:\"0%\" + min-width — or a grid with grid-template-columns:\"repeat(auto-fit, minmax(160px, 1fr))\". Keep padding 12-16px and gap 8-12px, and let the outer container fill the panel. Stack only what reads in order (heading over paragraph, forms, steps, prose). Watch the SDK defaults: every container has min-width/min-height 50px (set them to \"0\" for rails, dividers, dots and any flex child that must shrink), and heading/paragraph keep the margins the browser gives them (zero them, space with the parent gap).\n2c. THEME — it is embedded in the host UI, which MAY BE DARK, so never hardcode a light palette. Take colours from the host variables with a light-dark() fallback — background-color:\"var(--color-background-secondary, light-dark(#ffffff, #1f2430))\", color:\"var(--color-text-primary, light-dark(#0f172a, #e8eaed))\", border-color:\"var(--color-border-primary, light-dark(#e2e8f0, #333a48))\" — and always set `color` wherever you set `background-color` (a brand accent states its own text colour too).\n3. CONTENT — visible copy goes in props.content (text, heading, paragraph, button); heading level is the element subType (\"h1\"..\"h6\"); image/video take props.src. An unknown prop comes back as a warning naming the right one.\n\nCommon types: container, heading, paragraph, text, button, link, image, video, list, listItem, markdown (plitzi://render/types lists every built-in type with descriptions). Widgets can also be data-driven and interactive — an apiContainer fetches at runtime, upsertBinding wires data into elements, and upsertInteractionFlow makes them react to clicks (see the guide).\nREAD the resource plitzi://render/guide first — it has the element/prop table, the style model and a full worked example, and following it is the difference between a widget that renders and repeated failed calls.\nITERATING — to change a widget you already rendered, do NOT rebuild it: call again with patch:true, the `renderId` that render answered with, and ONLY the operations that differ (patchDefinition, patchElement, deleteElement…). The widget merges them and reports back what it applied; address rows by the refs you already know. Patch ONLY to modify that widget: a different subject or a different kind of widget is a fresh render, without patch — a patch is merged into the previous batch, so patching a new idea leaves you with both.\nReturns a compact summary including the renderId (the widget itself is shown to the user); on failure it returns teachable errors (path + hint) — read them and retry.",
|
|
109
171
|
inputShape: renderShape,
|
|
110
172
|
access: "read",
|
|
111
173
|
spaceless: true,
|
|
112
174
|
ui: { resourceUri: RENDER_APP_URI },
|
|
113
|
-
run: (input) => toRenderResult(render(input))
|
|
175
|
+
run: (input) => input.patch === true ? toPatchResult(input.operations, input.renderId) : toRenderResult(render(input), input.renderId ?? newRenderId())
|
|
114
176
|
});
|
|
115
177
|
//#endregion
|
|
116
178
|
export { render, renderShape, renderTool };
|