@plitzi/sdk-server 0.32.17 → 0.32.19
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/README.md +18 -0
- package/dist/modules/mcp/apps/render/index.js +2 -1
- package/dist/modules/mcp/apps/render/view/heldBatch.ts +107 -0
- package/dist/modules/mcp/apps/render/view/index.tsx +212 -0
- package/dist/modules/mcp/apps/shared/assets.js +10 -0
- package/dist/modules/mcp/apps/shared/bundle.js +15 -14
- package/dist/modules/mcp/handler.js +1 -1
- package/dist/modules/mcp/helpers/guide.js +11 -3
- package/dist/modules/mcp/helpers/space.js +13 -1
- package/dist/modules/mcp/resources/register.js +8 -3
- package/dist/modules/mcp/resources/renderGuide.js +138 -24
- package/dist/modules/mcp/resources/router.js +13 -1
- package/dist/modules/mcp/server.js +31 -19
- 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/read.js +21 -3
- 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/tool.js +16 -11
- 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/src/modules/mcp/apps/shared/assets.d.ts +10 -0
- package/dist/src/modules/mcp/apps/shared/bundle.d.ts +3 -0
- package/dist/src/modules/mcp/apps/shared/index.d.ts +1 -0
- package/dist/src/modules/mcp/e2e/index.d.ts +2 -2
- package/dist/src/modules/mcp/e2e/mcpEndpoint.d.ts +6 -1
- package/dist/src/modules/mcp/e2e/renderingHost.d.ts +16 -1
- package/dist/src/modules/mcp/helpers/guide.d.ts +2 -1
- package/dist/src/modules/mcp/helpers/space.d.ts +9 -0
- package/dist/src/modules/mcp/resources/index.d.ts +2 -1
- package/dist/src/modules/mcp/resources/register.d.ts +6 -2
- package/dist/src/modules/mcp/resources/renderGuide.d.ts +5 -0
- package/dist/src/modules/mcp/resources/router.d.ts +4 -0
- package/dist/src/modules/mcp/server.d.ts +6 -5
- 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/read.d.ts +1 -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/shared/tool.d.ts +8 -0
- package/dist/src/modules/mcp/tools/validate.d.ts +50 -0
- package/package.json +6 -5
- package/skills/plitzi-render/SKILL.md +170 -0
- package/dist/modules/mcp/apps/render/view.tsx +0 -108
- /package/dist/modules/mcp/apps/example/{view.tsx → view/index.tsx} +0 -0
- /package/dist/src/modules/mcp/{apps/example/view.d.ts → tests/schemaIds.test.d.ts} +0 -0
- /package/dist/src/modules/mcp/{apps/render/view.d.ts → tests/skill.test.d.ts} +0 -0
|
@@ -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 };
|
|
@@ -1,5 +1,7 @@
|
|
|
1
|
+
import { noSpaceError, unauthorizedSpaceMessage } from "../helpers/space.js";
|
|
1
2
|
import { resourceErrorMessage } from "../resources/canonical.js";
|
|
2
|
-
import {
|
|
3
|
+
import { RENDER_GUIDE_URI, RENDER_TYPES_URI } from "../resources/renderGuide.js";
|
|
4
|
+
import { readPublicResource, readResource } from "../resources/router.js";
|
|
3
5
|
import { defineTool } from "./shared/tool.js";
|
|
4
6
|
import { z } from "zod";
|
|
5
7
|
//#region src/modules/mcp/tools/read.ts
|
|
@@ -21,13 +23,29 @@ var read = (input, space, env) => {
|
|
|
21
23
|
};
|
|
22
24
|
}) };
|
|
23
25
|
};
|
|
26
|
+
var publicHint = `Readable on this connection: ${RENDER_GUIDE_URI}, ${RENDER_TYPES_URI}, plitzi://guide, plitzi://css-properties. Everything else lives in a space this connection has none of.`;
|
|
27
|
+
var readPublic = (input, env) => ({ results: input.uris.map((uri) => {
|
|
28
|
+
const found = readPublicResource(env, uri);
|
|
29
|
+
if (found) return {
|
|
30
|
+
uri,
|
|
31
|
+
stateVersion: found.stateVersion,
|
|
32
|
+
data: found.data
|
|
33
|
+
};
|
|
34
|
+
return {
|
|
35
|
+
uri,
|
|
36
|
+
error: noSpaceError,
|
|
37
|
+
message: unauthorizedSpaceMessage,
|
|
38
|
+
hint: publicHint
|
|
39
|
+
};
|
|
40
|
+
}) });
|
|
24
41
|
var readTool = defineTool({
|
|
25
42
|
name: "plitzi_read",
|
|
26
43
|
title: "Batch read",
|
|
27
44
|
description: "Batch-fetch resource content for URIs you ALREADY HOLD (from plitzi_search or a write response) — the tool form of opening MCP resources, for many at once. Pass an array of URIs (pages, elements, definitions, variables) and get them all in one call. NOT for finding things: to locate an element by label/type use plitzi_search; to browse what exists, list the plitzi:// resources. Each result is { uri, stateVersion, data } or a teachable error, so one bad URI never fails the batch. Never hand-build a URI to guess your way to an element — search for it.",
|
|
28
45
|
inputShape: readShape,
|
|
29
46
|
access: "read",
|
|
30
|
-
run: (input, ctx) => read(input, ctx.space, ctx.env)
|
|
47
|
+
run: (input, ctx) => read(input, ctx.space, ctx.env),
|
|
48
|
+
runPublic: readPublic
|
|
31
49
|
});
|
|
32
50
|
//#endregion
|
|
33
|
-
export { read, readShape, readTool };
|
|
51
|
+
export { read, readPublic, readShape, readTool };
|
|
@@ -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). Nothing carries a minimum size, so an element with no content and no size takes none — give a spacer or a rail its own height/width — while heading/paragraph do 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 };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { expandRepeat } from "../operations/schema/elements/repeatElement.js";
|
|
2
|
+
//#region src/modules/mcp/tools/shared/expandOperations.ts
|
|
3
|
+
/** Rewrites the sugar ops into the vocabulary the rest of the pipeline knows, BEFORE validation — today that is
|
|
4
|
+
* `repeatElement`, which stands for the `upsertElement` its template + rows expand to. Everything downstream
|
|
5
|
+
* (validator, dispatch, audit, versions) therefore stays unaware of it, and a row that renders a bad element is
|
|
6
|
+
* reported by the ordinary element checks.
|
|
7
|
+
*
|
|
8
|
+
* One op in, one op out: the indices the agent sent are the indices every error path names, so `operations[3]`
|
|
9
|
+
* still means the fourth operation it wrote. */
|
|
10
|
+
var expandOperations = (ops) => {
|
|
11
|
+
if (!ops.some((op) => op.type === "repeatElement")) return {
|
|
12
|
+
operations: ops,
|
|
13
|
+
errors: []
|
|
14
|
+
};
|
|
15
|
+
const operations = [];
|
|
16
|
+
const errors = [];
|
|
17
|
+
for (const [index, op] of ops.entries()) {
|
|
18
|
+
if (op.type !== "repeatElement") {
|
|
19
|
+
operations.push(op);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const expanded = expandRepeat(op);
|
|
23
|
+
if (!expanded.element) {
|
|
24
|
+
errors.push(...expanded.errors.map((error) => ({
|
|
25
|
+
...error,
|
|
26
|
+
path: `operations[${index}].${error.path}`
|
|
27
|
+
})));
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
operations.push({
|
|
31
|
+
type: "upsertElement",
|
|
32
|
+
pageRef: op.pageRef,
|
|
33
|
+
element: expanded.element,
|
|
34
|
+
...op.parentRef === void 0 ? {} : { parentRef: op.parentRef },
|
|
35
|
+
...op.position === void 0 ? {} : { position: op.position }
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return {
|
|
39
|
+
operations,
|
|
40
|
+
errors
|
|
41
|
+
};
|
|
42
|
+
};
|
|
43
|
+
//#endregion
|
|
44
|
+
export { expandOperations };
|
|
@@ -13,16 +13,21 @@ var imageResult = (images, meta) => ({ content: [{
|
|
|
13
13
|
/** Author a tool: give it its metadata, its input shape and a typed `run`. The returned descriptor parses the
|
|
14
14
|
* raw args against the shape before handing them to `run`, so `run` is fully typed and no cast is needed.
|
|
15
15
|
* Adding a tool is: call defineTool in its own file and append it to the `tools` registry. */
|
|
16
|
-
var defineTool = (spec) =>
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
16
|
+
var defineTool = (spec) => {
|
|
17
|
+
const { runPublic } = spec;
|
|
18
|
+
const parse = (args) => z.object(spec.inputShape).parse(args);
|
|
19
|
+
return {
|
|
20
|
+
name: spec.name,
|
|
21
|
+
title: spec.title,
|
|
22
|
+
description: spec.description,
|
|
23
|
+
inputShape: spec.inputShape,
|
|
24
|
+
access: spec.access,
|
|
25
|
+
requires: spec.requires,
|
|
26
|
+
spaceless: spec.spaceless,
|
|
27
|
+
ui: spec.ui,
|
|
28
|
+
execute: (args, ctx) => spec.run(parse(args), ctx),
|
|
29
|
+
...runPublic ? { executePublic: (args, env) => runPublic(parse(args), env) } : {}
|
|
30
|
+
};
|
|
31
|
+
};
|
|
27
32
|
//#endregion
|
|
28
33
|
export { defineTool, imageResult };
|
|
@@ -78,6 +78,9 @@ var collectTouched = (ops) => {
|
|
|
78
78
|
case "patchDefinition":
|
|
79
79
|
touched.definitions.add(op.ref);
|
|
80
80
|
break;
|
|
81
|
+
case "upsertDefinitions":
|
|
82
|
+
for (const ref of Object.keys(op.definitions)) touched.definitions.add(ref);
|
|
83
|
+
break;
|
|
81
84
|
case "upsertGlobalStyle":
|
|
82
85
|
case "patchGlobalStyle":
|
|
83
86
|
touched.globalStyles.add(op.componentType);
|
|
@@ -16,13 +16,22 @@ var batchDeclaredFolders = (ops) => {
|
|
|
16
16
|
for (const op of ops) if (op.type === "upsertFolder") refs.add(op.ref);
|
|
17
17
|
return refs;
|
|
18
18
|
};
|
|
19
|
+
var variantNamesOf = (source) => {
|
|
20
|
+
const names = new Set(Object.keys(source.variants ?? {}));
|
|
21
|
+
for (const slot of Object.values(source.slots ?? {})) for (const name of Object.keys(slot.variants ?? {})) names.add(name);
|
|
22
|
+
return names;
|
|
23
|
+
};
|
|
19
24
|
var batchDeclaredVariants = (ops) => {
|
|
20
25
|
const map = /* @__PURE__ */ new Map();
|
|
26
|
+
const record = (ref, names) => {
|
|
27
|
+
if (names.size > 0) map.set(ref, /* @__PURE__ */ new Set([...map.get(ref) ?? [], ...names]));
|
|
28
|
+
};
|
|
21
29
|
for (const op of ops) {
|
|
22
|
-
if (op.type
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
30
|
+
if (op.type === "upsertDefinitions") {
|
|
31
|
+
for (const [ref, definition] of Object.entries(op.definitions)) record(ref, variantNamesOf(definition));
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
if (op.type === "upsertDefinition" || op.type === "patchDefinition") record(op.ref, variantNamesOf(op));
|
|
26
35
|
}
|
|
27
36
|
return map;
|
|
28
37
|
};
|
|
@@ -96,6 +96,21 @@ var validateOperations = (space, ops) => {
|
|
|
96
96
|
checkVariantApplication(op.initialState, `${base}.initialState`, ctx);
|
|
97
97
|
break;
|
|
98
98
|
}
|
|
99
|
+
case "upsertDefinitions": {
|
|
100
|
+
const entries = Object.entries(op.definitions);
|
|
101
|
+
if (entries.length === 0) ctx.errors.push({
|
|
102
|
+
path: `${base}.definitions`,
|
|
103
|
+
message: "This operation declares no classes",
|
|
104
|
+
hint: "Key each class by its name: { \"definitions\": { \"card\": { \"desktop\": { … } } } }"
|
|
105
|
+
});
|
|
106
|
+
for (const [ref, { slots, ...slot }] of entries) {
|
|
107
|
+
const entry = `${base}.definitions.${ref}`;
|
|
108
|
+
checkRef(ref, entry, ctx);
|
|
109
|
+
checkSlotCss(slot, entry, ctx);
|
|
110
|
+
for (const [slotName, slotDef] of Object.entries(slots ?? {})) checkSlotCss(slotDef, `${entry}.slots.${slotName}`, ctx);
|
|
111
|
+
}
|
|
112
|
+
break;
|
|
113
|
+
}
|
|
99
114
|
case "upsertDefinition":
|
|
100
115
|
case "patchDefinition": {
|
|
101
116
|
const { type, ref, slots, ...slot } = op;
|