@plitzi/sdk-server 0.32.5 → 0.32.6

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.
Files changed (168) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/dist/core/services/mcp.js +1 -1
  3. package/dist/helpers/buildServerInfo.js +3 -9
  4. package/dist/index.js +2 -1
  5. package/dist/modules/mcp/catalogs/builtinCallbacks.js +204 -0
  6. package/dist/modules/mcp/catalogs/builtinComponents.js +170 -0
  7. package/dist/modules/mcp/catalogs/builtinElementCallbacks.js +50 -0
  8. package/dist/modules/mcp/catalogs/builtinTransformers.js +148 -0
  9. package/dist/modules/mcp/catalogs/builtinUtilities.js +90 -0
  10. package/dist/modules/mcp/{tools/operations/style → catalogs}/cssCatalog.js +75 -2
  11. package/dist/modules/mcp/catalogs/observed.js +90 -0
  12. package/dist/modules/mcp/catalogs/paramSpec.js +69 -0
  13. package/dist/modules/mcp/catalogs/registry.js +89 -0
  14. package/dist/modules/mcp/handler.js +12 -2
  15. package/dist/modules/mcp/helpers/agentPrompt.js +104 -0
  16. package/dist/modules/mcp/helpers/guide.js +334 -15
  17. package/dist/modules/mcp/helpers/interactions.js +123 -0
  18. package/dist/modules/mcp/helpers/log.js +54 -0
  19. package/dist/modules/mcp/helpers/space.js +158 -33
  20. package/dist/modules/mcp/helpers/uris.js +44 -0
  21. package/dist/modules/mcp/resources/canonical.js +6 -21
  22. package/dist/modules/mcp/resources/core.js +4 -3
  23. package/dist/modules/mcp/resources/primer.js +13 -8
  24. package/dist/modules/mcp/resources/register.js +37 -5
  25. package/dist/modules/mcp/resources/schema.js +30 -20
  26. package/dist/modules/mcp/resources/style.js +18 -11
  27. package/dist/modules/mcp/server.js +21 -7
  28. package/dist/modules/mcp/tools/apply/dispatch.js +38 -18
  29. package/dist/modules/mcp/tools/apply/index.js +19 -4
  30. package/dist/modules/mcp/tools/apply/writeResult.js +2 -1
  31. package/dist/modules/mcp/tools/index.js +1 -1
  32. package/dist/modules/mcp/tools/operations/index.js +13 -2
  33. package/dist/modules/mcp/tools/operations/schema/bindings/deleteBinding.js +32 -0
  34. package/dist/modules/mcp/tools/operations/schema/bindings/patchBinding.js +36 -0
  35. package/dist/modules/mcp/tools/operations/schema/bindings/upsertBinding.js +47 -0
  36. package/dist/modules/mcp/tools/operations/schema/{deleteElement.js → elements/deleteElement.js} +10 -6
  37. package/dist/modules/mcp/tools/operations/schema/{moveElement.js → elements/moveElement.js} +8 -6
  38. package/dist/modules/mcp/tools/operations/schema/{patchElement.js → elements/patchElement.js} +19 -7
  39. package/dist/modules/mcp/tools/operations/schema/{upsertElement.js → elements/upsertElement.js} +11 -5
  40. package/dist/modules/mcp/tools/operations/schema/{deleteFolder.js → folders/deleteFolder.js} +4 -4
  41. package/dist/modules/mcp/tools/operations/schema/{upsertFolder.js → folders/upsertFolder.js} +4 -4
  42. package/dist/modules/mcp/tools/operations/schema/interactions/deleteInteraction.js +52 -0
  43. package/dist/modules/mcp/tools/operations/schema/interactions/patchInteractionNode.js +47 -0
  44. package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.js +43 -0
  45. package/dist/modules/mcp/tools/operations/schema/operations.js +25 -11
  46. package/dist/modules/mcp/tools/operations/schema/{deletePage.js → pages/deletePage.js} +8 -5
  47. package/dist/modules/mcp/tools/operations/schema/pages/upsertPage.js +75 -0
  48. package/dist/modules/mcp/tools/operations/schema/settings/patchSettings.js +45 -0
  49. package/dist/modules/mcp/tools/operations/schema/shared.js +94 -2
  50. package/dist/modules/mcp/tools/operations/schema/translator.js +77 -10
  51. package/dist/modules/mcp/tools/operations/schema/{deleteVariable.js → variables/deleteVariable.js} +3 -3
  52. package/dist/modules/mcp/tools/operations/schema/{upsertVariable.js → variables/upsertVariable.js} +4 -4
  53. package/dist/modules/mcp/tools/operations/schema/write.js +78 -13
  54. package/dist/modules/mcp/tools/operations/style/{deleteDefinition.js → definitions/deleteDefinition.js} +4 -3
  55. package/dist/modules/mcp/tools/operations/style/{patchDefinition.js → definitions/patchDefinition.js} +6 -5
  56. package/dist/modules/mcp/tools/operations/style/{upsertDefinition.js → definitions/upsertDefinition.js} +5 -4
  57. package/dist/modules/mcp/tools/operations/style/{deleteGlobalStyle.js → globalStyles/deleteGlobalStyle.js} +4 -3
  58. package/dist/modules/mcp/tools/operations/style/{patchGlobalStyle.js → globalStyles/patchGlobalStyle.js} +6 -5
  59. package/dist/modules/mcp/tools/operations/style/{upsertGlobalStyle.js → globalStyles/upsertGlobalStyle.js} +5 -4
  60. package/dist/modules/mcp/tools/operations/style/idStyles/deleteIdStyle.js +21 -0
  61. package/dist/modules/mcp/tools/operations/style/idStyles/patchIdStyle.js +28 -0
  62. package/dist/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.js +24 -0
  63. package/dist/modules/mcp/tools/operations/style/operations.js +14 -8
  64. package/dist/modules/mcp/tools/operations/style/translator.js +25 -1
  65. package/dist/modules/mcp/tools/operations/style/{deleteStyleVariable.js → variables/deleteStyleVariable.js} +4 -4
  66. package/dist/modules/mcp/tools/operations/style/{upsertStyleVariable.js → variables/upsertStyleVariable.js} +4 -4
  67. package/dist/modules/mcp/tools/operations/style/write.js +19 -11
  68. package/dist/modules/mcp/tools/read.js +1 -1
  69. package/dist/modules/mcp/tools/search.js +34 -27
  70. package/dist/modules/mcp/tools/shared/validator/audit.js +124 -0
  71. package/dist/modules/mcp/tools/shared/validator/batch.js +30 -0
  72. package/dist/modules/mcp/tools/shared/validator/bindings.js +70 -0
  73. package/dist/modules/mcp/tools/shared/validator/context.js +35 -0
  74. package/dist/modules/mcp/tools/shared/validator/css.js +47 -0
  75. package/dist/modules/mcp/tools/shared/validator/elements.js +55 -0
  76. package/dist/modules/mcp/tools/shared/validator/index.js +226 -0
  77. package/dist/modules/mcp/tools/shared/validator/interactions.js +106 -0
  78. package/dist/modules/mcp/tools/shared/validator/refs.js +28 -0
  79. package/dist/modules/mcp/tools/validate.js +23 -3
  80. package/dist/modules/ssr/preview.js +1 -1
  81. package/dist/modules/ssr/views/template.ejs +2 -0
  82. package/dist/src/index.d.ts +1 -1
  83. package/dist/src/modules/mcp/catalogs/builtinCallbacks.d.ts +23 -0
  84. package/dist/src/modules/mcp/catalogs/builtinComponents.d.ts +4 -0
  85. package/dist/src/modules/mcp/catalogs/builtinElementCallbacks.d.ts +20 -0
  86. package/dist/src/modules/mcp/catalogs/builtinTransformers.d.ts +28 -0
  87. package/dist/src/modules/mcp/catalogs/builtinTransformers.test.d.ts +1 -0
  88. package/dist/src/modules/mcp/catalogs/builtinUtilities.d.ts +18 -0
  89. package/dist/src/modules/mcp/{tools/operations/style → catalogs}/cssCatalog.d.ts +5 -1
  90. package/dist/src/modules/mcp/catalogs/index.d.ts +9 -0
  91. package/dist/src/modules/mcp/catalogs/observed.d.ts +48 -0
  92. package/dist/src/modules/mcp/catalogs/paramSpec.d.ts +42 -0
  93. package/dist/src/modules/mcp/catalogs/registry.d.ts +35 -0
  94. package/dist/src/modules/mcp/handler.d.ts +2 -2
  95. package/dist/src/modules/mcp/helpers/agentPrompt.d.ts +4 -0
  96. package/dist/src/modules/mcp/helpers/guide.d.ts +2 -1
  97. package/dist/src/modules/mcp/helpers/index.d.ts +4 -0
  98. package/dist/src/modules/mcp/helpers/interactions.d.ts +30 -0
  99. package/dist/src/modules/mcp/helpers/log.d.ts +8 -0
  100. package/dist/src/modules/mcp/helpers/log.test.d.ts +1 -0
  101. package/dist/src/modules/mcp/helpers/space.d.ts +67 -11
  102. package/dist/src/modules/mcp/helpers/uris.d.ts +28 -0
  103. package/dist/src/modules/mcp/index.d.ts +1 -1
  104. package/dist/src/modules/mcp/mcp.bench.d.ts +1 -0
  105. package/dist/src/modules/mcp/resources/canonical.d.ts +0 -1
  106. package/dist/src/modules/mcp/resources/index.d.ts +2 -3
  107. package/dist/src/modules/mcp/resources/primer.d.ts +2 -1
  108. package/dist/src/modules/mcp/resources/register.d.ts +2 -2
  109. package/dist/src/modules/mcp/server.d.ts +5 -2
  110. package/dist/src/modules/mcp/tools/apply/index.d.ts +191 -2
  111. package/dist/src/modules/mcp/tools/operations/index.d.ts +383 -4
  112. package/dist/src/modules/mcp/tools/operations/schema/bindings/deleteBinding.d.ts +17 -0
  113. package/dist/src/modules/mcp/tools/operations/schema/bindings/patchBinding.d.ts +25 -0
  114. package/dist/src/modules/mcp/tools/operations/schema/bindings/upsertBinding.d.ts +27 -0
  115. package/dist/src/modules/mcp/tools/operations/schema/{deleteElement.d.ts → elements/deleteElement.d.ts} +2 -2
  116. package/dist/src/modules/mcp/tools/operations/schema/{moveElement.d.ts → elements/moveElement.d.ts} +2 -2
  117. package/dist/src/modules/mcp/tools/operations/schema/{patchElement.d.ts → elements/patchElement.d.ts} +7 -2
  118. package/dist/src/modules/mcp/tools/operations/schema/{upsertElement.d.ts → elements/upsertElement.d.ts} +3 -3
  119. package/dist/src/modules/mcp/tools/operations/schema/{deleteFolder.d.ts → folders/deleteFolder.d.ts} +2 -2
  120. package/dist/src/modules/mcp/tools/operations/schema/{upsertFolder.d.ts → folders/upsertFolder.d.ts} +2 -2
  121. package/dist/src/modules/mcp/tools/operations/schema/index.d.ts +17 -11
  122. package/dist/src/modules/mcp/tools/operations/schema/interactions/deleteInteraction.d.ts +12 -0
  123. package/dist/src/modules/mcp/tools/operations/schema/interactions/patchInteractionNode.d.ts +18 -0
  124. package/dist/src/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.d.ts +27 -0
  125. package/dist/src/modules/mcp/tools/operations/schema/operations.d.ts +131 -0
  126. package/dist/src/modules/mcp/tools/operations/schema/{deletePage.d.ts → pages/deletePage.d.ts} +2 -2
  127. package/dist/src/modules/mcp/tools/operations/schema/{upsertPage.d.ts → pages/upsertPage.d.ts} +3 -2
  128. package/dist/src/modules/mcp/tools/operations/schema/settings/patchSettings.d.ts +34 -0
  129. package/dist/src/modules/mcp/tools/operations/schema/shared.d.ts +53 -0
  130. package/dist/src/modules/mcp/tools/operations/schema/translator.d.ts +5 -2
  131. package/dist/src/modules/mcp/tools/operations/schema/{deleteVariable.d.ts → variables/deleteVariable.d.ts} +2 -2
  132. package/dist/src/modules/mcp/tools/operations/schema/{upsertVariable.d.ts → variables/upsertVariable.d.ts} +2 -2
  133. package/dist/src/modules/mcp/tools/operations/schema/write.d.ts +38 -8
  134. package/dist/src/modules/mcp/tools/operations/style/{deleteDefinition.d.ts → definitions/deleteDefinition.d.ts} +2 -2
  135. package/dist/src/modules/mcp/tools/operations/style/{patchDefinition.d.ts → definitions/patchDefinition.d.ts} +2 -2
  136. package/dist/src/modules/mcp/tools/operations/style/{upsertDefinition.d.ts → definitions/upsertDefinition.d.ts} +2 -2
  137. package/dist/src/modules/mcp/tools/operations/style/{deleteGlobalStyle.d.ts → globalStyles/deleteGlobalStyle.d.ts} +2 -2
  138. package/dist/src/modules/mcp/tools/operations/style/{patchGlobalStyle.d.ts → globalStyles/patchGlobalStyle.d.ts} +2 -2
  139. package/dist/src/modules/mcp/tools/operations/style/{upsertGlobalStyle.d.ts → globalStyles/upsertGlobalStyle.d.ts} +2 -2
  140. package/dist/src/modules/mcp/tools/operations/style/idStyles/deleteIdStyle.d.ts +9 -0
  141. package/dist/src/modules/mcp/tools/operations/style/idStyles/patchIdStyle.d.ts +37 -0
  142. package/dist/src/modules/mcp/tools/operations/style/idStyles/upsertIdStyle.d.ts +37 -0
  143. package/dist/src/modules/mcp/tools/operations/style/index.d.ts +11 -9
  144. package/dist/src/modules/mcp/tools/operations/style/operations.d.ts +70 -2
  145. package/dist/src/modules/mcp/tools/operations/style/shared.d.ts +1 -1
  146. package/dist/src/modules/mcp/tools/operations/style/translator.d.ts +4 -1
  147. package/dist/src/modules/mcp/tools/operations/style/{deleteStyleVariable.d.ts → variables/deleteStyleVariable.d.ts} +3 -3
  148. package/dist/src/modules/mcp/tools/operations/style/{upsertStyleVariable.d.ts → variables/upsertStyleVariable.d.ts} +3 -3
  149. package/dist/src/modules/mcp/tools/operations/style/write.d.ts +2 -7
  150. package/dist/src/modules/mcp/tools/preview.d.ts +191 -2
  151. package/dist/src/modules/mcp/tools/screenshot.d.ts +191 -2
  152. package/dist/src/modules/mcp/tools/shared/validator/audit.d.ts +4 -0
  153. package/dist/src/modules/mcp/tools/shared/validator/batch.d.ts +5 -0
  154. package/dist/src/modules/mcp/tools/shared/validator/bindings.d.ts +6 -0
  155. package/dist/src/modules/mcp/tools/shared/validator/context.d.ts +34 -0
  156. package/dist/src/modules/mcp/tools/shared/validator/css.d.ts +4 -0
  157. package/dist/src/modules/mcp/tools/shared/validator/elements.d.ts +6 -0
  158. package/dist/src/modules/mcp/tools/shared/validator/index.d.ts +8 -0
  159. package/dist/src/modules/mcp/tools/shared/validator/interactions.d.ts +3 -0
  160. package/dist/src/modules/mcp/tools/shared/validator/refs.d.ts +4 -0
  161. package/dist/src/modules/mcp/tools/validate.d.ts +191 -2
  162. package/dist/src/modules/mcp/types/aiSchema.d.ts +93 -1
  163. package/package.json +12 -10
  164. package/dist/modules/mcp/tools/operations/schema/registry.js +0 -55
  165. package/dist/modules/mcp/tools/operations/schema/upsertPage.js +0 -64
  166. package/dist/modules/mcp/tools/shared/validator.js +0 -262
  167. package/dist/src/modules/mcp/tools/operations/schema/registry.d.ts +0 -22
  168. package/dist/src/modules/mcp/tools/shared/validator.d.ts +0 -4
@@ -0,0 +1,70 @@
1
+ import { getTransformer, suggestTransformer } from "../../../catalogs/builtinTransformers.js";
2
+ import { descendantIds, elementRefOf, findElementByRef } from "../../../helpers/space.js";
3
+ import { warnOnce } from "./context.js";
4
+ import getSourceName from "@plitzi/sdk-shared/dataSource/helpers/getSourceName";
5
+ //#region src/modules/mcp/tools/shared/validator/bindings.ts
6
+ var providerOfSource = (space, source) => {
7
+ const head = source.split(".")[0];
8
+ const sep = head.indexOf("_");
9
+ if (sep < 0) return;
10
+ const idRef = head.slice(sep + 1);
11
+ const el = findElementByRef(space.schema, idRef);
12
+ if (!el || getSourceName(el.definition.type, el) !== head) return;
13
+ return {
14
+ ref: elementRefOf(el),
15
+ id: el.id
16
+ };
17
+ };
18
+ var checkBindingTarget = (ref, category, to, path, ctx) => {
19
+ if (category !== "attributes" && category !== "initialState") return;
20
+ const type = ctx.elementType(ref);
21
+ const targets = type ? ctx.typeMeta.get(type)?.bindingTargets?.[category] : void 0;
22
+ if (!targets || targets.size === 0 || targets.has(to)) return;
23
+ warnOnce(ctx, `Binding target "${to}" at ${path} is not among the "${category}" targets the type "${type}" declares (${[...targets].sort().join(", ")}). Verify against plitzi://data-sources; it may still be valid.`);
24
+ };
25
+ var checkBindingSourceScope = (space, ctx, elementRef, source, path) => {
26
+ const provider = providerOfSource(space, source);
27
+ if (!provider) return;
28
+ const bound = findElementByRef(space.schema, elementRef);
29
+ if (!bound) return;
30
+ if (bound.id !== provider.id && descendantIds(space.schema, provider.id).includes(bound.id)) return;
31
+ ctx.errors.push({
32
+ path,
33
+ message: `Binding source "${source}" is provided by element "${provider.ref}", but "${elementRef}" is not inside its subtree`,
34
+ hint: `An element source is scoped to its DESCENDANTS only, so this binding resolves to nothing at runtime (the source is not available outside "${provider.ref}"). Move "${elementRef}" under "${provider.ref}", or bind to a source that is in scope (e.g. a global module source like state/space/navigation).`
35
+ });
36
+ };
37
+ var checkBindingTransformers = (transformers, path, ctx) => {
38
+ transformers?.forEach((transformer, i) => {
39
+ if (transformer.enabled === false) return;
40
+ const base = `${path}[${i}]`;
41
+ const spec = getTransformer(transformer.action);
42
+ if (!spec) {
43
+ const suggestion = suggestTransformer(transformer.action);
44
+ ctx.errors.push({
45
+ path: `${base}.action`,
46
+ message: `Unknown transformer action "${transformer.action}"`,
47
+ hint: `The runtime skips an unknown transformer and passes the value through unchanged.${suggestion ? ` Did you mean "${suggestion}"?` : ""} See the transformers list in plitzi://data-sources.`
48
+ });
49
+ return;
50
+ }
51
+ const params = transformer.params;
52
+ const unknown = Object.keys(params).filter((key) => !(key in spec.params));
53
+ if (spec.strictParams && unknown.length > 0) warnOnce(ctx, `Transformer "${transformer.action}" at ${base} got unknown param(s) ${unknown.map((k) => `"${k}"`).join(", ")} — the runtime ignores them. Valid params: ${Object.keys(spec.params).join(", ") || "(none)"}.`);
54
+ for (const [key, param] of Object.entries(spec.params)) {
55
+ if (param.required && !(key in params)) ctx.errors.push({
56
+ path: `${base}.params.${key}`,
57
+ message: `Transformer "${transformer.action}" is missing required param "${key}"`,
58
+ hint: param.description
59
+ });
60
+ if (param.type === "select" && param.options && key in params && !param.options.includes(params[key])) ctx.errors.push({
61
+ path: `${base}.params.${key}`,
62
+ message: `Transformer "${transformer.action}" param "${key}" is "${params[key]}", not one of its options`,
63
+ hint: `Use one of: ${param.options.join(", ")}.`,
64
+ validValues: param.options
65
+ });
66
+ }
67
+ });
68
+ };
69
+ //#endregion
70
+ export { checkBindingSourceScope, checkBindingTarget, checkBindingTransformers };
@@ -0,0 +1,35 @@
1
+ //#region src/modules/mcp/tools/shared/validator/context.ts
2
+ var VAR_REF = /\{\{\s*([A-Za-z_][\w.-]*)\s*\}\}/g;
3
+ var CSS_VAR = /var\(\s*--([A-Za-z_][\w-]*)\s*\)/g;
4
+ var RAW_CODE_TYPES = /* @__PURE__ */ new Set([
5
+ "blockJsx",
6
+ "blockHtml",
7
+ "custom"
8
+ ]);
9
+ var warnOnce = (ctx, message) => {
10
+ if (!ctx.warned.has(message)) {
11
+ ctx.warned.add(message);
12
+ ctx.warnings.push(message);
13
+ }
14
+ };
15
+ var checkVarRefs = (text, path, ctx) => {
16
+ for (const match of text.matchAll(VAR_REF)) {
17
+ const name = match[1];
18
+ if (!ctx.schemaVars.has(name)) warnOnce(ctx, `Unknown variable {{${name}}} at ${path}: not a space schema variable or a page route param. Read plitzi://schema-variables, or use a route param from the page skeleton (routeParams).`);
19
+ }
20
+ };
21
+ var checkStyleVarRefs = (text, path, ctx) => {
22
+ for (const match of text.matchAll(CSS_VAR)) {
23
+ const name = match[1];
24
+ if (!ctx.styleVars.has(name)) {
25
+ const tokens = [...ctx.styleVars];
26
+ warnOnce(ctx, `Unknown style variable var(--${name}) at ${path}: not a design token in this space. ${tokens.length > 0 ? `Available tokens: ${tokens.slice(0, 30).join(", ")}${tokens.length > 30 ? ", …" : ""}.` : "Read plitzi://style-variables for the valid token names."}`);
27
+ }
28
+ }
29
+ };
30
+ var checkObservedName = (value, observed, kind, resource, path, ctx) => {
31
+ if (!value || observed.size === 0 || observed.has(value)) return;
32
+ warnOnce(ctx, `${kind} "${value}" at ${path} was not seen in this space. Verify against ${resource}; it may still be valid.`);
33
+ };
34
+ //#endregion
35
+ export { RAW_CODE_TYPES, checkObservedName, checkStyleVarRefs, checkVarRefs, warnOnce };
@@ -0,0 +1,47 @@
1
+ import { compoundLonghands, expandShorthand, isCssProperty, suggestCssProperty } from "../../../catalogs/cssCatalog.js";
2
+ import { checkStyleVarRefs, checkVarRefs, warnOnce } from "./context.js";
3
+ //#region src/modules/mcp/tools/shared/validator/css.ts
4
+ var checkCss = (css, path, ctx) => {
5
+ if (!css) return;
6
+ const declared = {};
7
+ for (const [key, value] of Object.entries(css)) if (value !== null) declared[key] = value;
8
+ for (const [key, value] of Object.entries(expandShorthand(declared))) {
9
+ const longhands = compoundLonghands(key);
10
+ if (longhands) if (!isCssProperty(key)) ctx.errors.push({
11
+ path: `${path}.${key}`,
12
+ message: `Compound CSS shorthand "${key}" is not supported — write its atomic longhand properties`,
13
+ hint: `Use: ${longhands.join(", ")}. (Note: flex layout is display + flex-direction + flex-grow/shrink/basis, not "flex".)`,
14
+ validValues: longhands
15
+ });
16
+ else warnOnce(ctx, `CSS shorthand "${key}" at ${path}.${key} is accepted but stored as one compound value. Plitzi styling is atomic — prefer the longhands (${longhands.join(", ")}) so a breakpoint/state/variant can override each property independently.`);
17
+ else if (!isCssProperty(key)) {
18
+ const suggestion = suggestCssProperty(key);
19
+ ctx.errors.push({
20
+ path: `${path}.${key}`,
21
+ message: `Unknown CSS property "${key}"`,
22
+ hint: suggestion ? `Use the kebab-case key "${suggestion}"` : "Read plitzi://css-properties for the valid property keys"
23
+ });
24
+ }
25
+ if (typeof value === "string") {
26
+ checkStyleVarRefs(value, `${path}.${key}`, ctx);
27
+ checkVarRefs(value, `${path}.${key}`, ctx);
28
+ }
29
+ }
30
+ };
31
+ var checkSlotCss = (slot, path, ctx) => {
32
+ checkCss(slot.desktop, `${path}.desktop`, ctx);
33
+ checkCss(slot.tablet, `${path}.tablet`, ctx);
34
+ checkCss(slot.mobile, `${path}.mobile`, ctx);
35
+ for (const [state, dm] of Object.entries(slot.states ?? {})) {
36
+ checkCss(dm.desktop, `${path}.states.${state}.desktop`, ctx);
37
+ checkCss(dm.tablet, `${path}.states.${state}.tablet`, ctx);
38
+ checkCss(dm.mobile, `${path}.states.${state}.mobile`, ctx);
39
+ }
40
+ for (const [name, dm] of Object.entries(slot.variants ?? {})) {
41
+ checkCss(dm.desktop, `${path}.variants.${name}.desktop`, ctx);
42
+ checkCss(dm.tablet, `${path}.variants.${name}.tablet`, ctx);
43
+ checkCss(dm.mobile, `${path}.variants.${name}.mobile`, ctx);
44
+ }
45
+ };
46
+ //#endregion
47
+ export { checkCss, checkSlotCss };
@@ -0,0 +1,55 @@
1
+ import { definitionVariantNames } from "../../operations/style/translator.js";
2
+ import { RAW_CODE_TYPES, checkVarRefs, warnOnce } from "./context.js";
3
+ import { checkIdRef } from "./refs.js";
4
+ //#region src/modules/mcp/tools/shared/validator/elements.ts
5
+ var checkTypeProps = (type, props, path, ctx) => {
6
+ if (!props || RAW_CODE_TYPES.has(type)) return;
7
+ const meta = ctx.typeMeta.get(type);
8
+ const known = meta && meta.attributes.size > 0 ? meta.attributes : ctx.typeProps.get(type);
9
+ if (!known || known.size === 0) return;
10
+ const strict = meta?.custom === false;
11
+ for (const key of Object.keys(props)) {
12
+ if (key === "subType" || known.has(key)) continue;
13
+ if (strict) ctx.errors.push({
14
+ path,
15
+ message: `Type "${type}" has no attribute "${key}"`,
16
+ hint: `Valid attributes: ${[...known].sort().join(", ")}`,
17
+ validValues: [...known].sort()
18
+ });
19
+ else warnOnce(ctx, `Type "${type}" has no observed prop "${key}" at ${path} (known: ${[...known].sort().join(", ")}). It may still be valid — verify against plitzi://types.`);
20
+ }
21
+ };
22
+ var checkVariantApplication = (initialState, path, ctx) => {
23
+ for (const [cls, selectors] of Object.entries(initialState?.styleVariant ?? {})) {
24
+ const declared = definitionVariantNames(ctx.style, cls);
25
+ const batch = ctx.batchVariants.get(cls);
26
+ for (const [selector, variant] of Object.entries(selectors)) {
27
+ const names = Array.isArray(variant) ? variant : [variant];
28
+ for (const name of names) if (!((declared?.[selector]?.includes(name) ?? false) || (batch?.has(name) ?? false))) warnOnce(ctx, `Element applies variant "${name}" on class "${cls}" (${selector}) at ${path}, but that class defines no such variant${declared ? ` (declares: ${Object.entries(declared).map(([s, v]) => `${s}:${v.join("/")}`).join(", ")})` : ""}. Create it via upsertDefinition/patchDefinition "variants", or fix the name.`);
29
+ }
30
+ }
31
+ };
32
+ var checkElementProps = (element, path, ctx) => {
33
+ if (!element.props || RAW_CODE_TYPES.has(element.type)) return;
34
+ for (const [key, value] of Object.entries(element.props)) if (typeof value === "string") checkVarRefs(value, `${path}.props.${key}`, ctx);
35
+ checkTypeProps(element.type, element.props, path, ctx);
36
+ };
37
+ var checkElementInput = (element, path, ctx, seen) => {
38
+ checkIdRef(element.ref, `${path}.ref`, ctx);
39
+ if (seen.has(element.ref)) ctx.errors.push({
40
+ path: `${path}.ref`,
41
+ message: `Duplicate ref "${element.ref}" in this batch`,
42
+ hint: "Use a unique ref"
43
+ });
44
+ seen.add(element.ref);
45
+ if (!element.type) ctx.errors.push({
46
+ path: `${path}.type`,
47
+ message: "Element type is required",
48
+ hint: "Read plitzi://types for known types"
49
+ });
50
+ else if (!ctx.knownTypes.has(element.type)) ctx.warnings.push(`Type "${element.type}" was not seen in this space; ensure a plugin provides it (${path}.type).`);
51
+ checkElementProps(element, path, ctx);
52
+ element.children?.forEach((child, i) => checkElementInput(child, `${path}.children[${i}]`, ctx, seen));
53
+ };
54
+ //#endregion
55
+ export { checkElementInput, checkTypeProps, checkVariantApplication };
@@ -0,0 +1,226 @@
1
+ import { observedDataSources, observedInteractionActions } from "../../../catalogs/observed.js";
2
+ import { buildTypeRegistry } from "../../../catalogs/registry.js";
3
+ import { findElementByRef, findFolderByRef, findPageByRef, folderAncestorIds, getPageElements, pageFoldersOf, pageRefOf, resolveRef, routeParamNames } from "../../../helpers/space.js";
4
+ import { MAX_OPS } from "../../operations/index.js";
5
+ import { batchDeclaredFolders, batchDeclaredPages, batchDeclaredVariants, batchDeclaredVars } from "./batch.js";
6
+ import { checkObservedName, checkVarRefs, warnOnce } from "./context.js";
7
+ import { checkBindingSourceScope, checkBindingTarget, checkBindingTransformers } from "./bindings.js";
8
+ import { checkSlotCss } from "./css.js";
9
+ import { checkRef } from "./refs.js";
10
+ import { checkElementInput, checkTypeProps, checkVariantApplication } from "./elements.js";
11
+ import { checkInteractionNode } from "./interactions.js";
12
+ //#region src/modules/mcp/tools/shared/validator/index.ts
13
+ var STYLE_CATEGORIES = [
14
+ "color",
15
+ "spacing",
16
+ "shadow",
17
+ "custom"
18
+ ];
19
+ var buildTypeMeta = (catalog) => {
20
+ const meta = /* @__PURE__ */ new Map();
21
+ for (const [type, entry] of Object.entries(catalog ?? {})) {
22
+ const bindingsAllowed = entry.bindingsAllowed;
23
+ meta.set(type, {
24
+ attributes: new Set(entry.attributes ?? []),
25
+ styleSelectors: new Set(entry.styleSelectors ?? []),
26
+ custom: entry.custom ?? false,
27
+ ...bindingsAllowed ? { bindingTargets: {
28
+ attributes: new Set(bindingsAllowed.attributes ?? []),
29
+ initialState: new Set(bindingsAllowed.initialState ?? [])
30
+ } } : {}
31
+ });
32
+ }
33
+ return meta;
34
+ };
35
+ /** The shared validation context, derived from a space (+ the batch's ops, for batch-declared names). Extracted so
36
+ * the post-apply resource audit (auditResources) can run the same checks against the resulting draft. */
37
+ var buildValidationCtx = (space, ops) => {
38
+ const registry = buildTypeRegistry(space.schema, space.catalog);
39
+ return {
40
+ errors: [],
41
+ warnings: [],
42
+ warned: /* @__PURE__ */ new Set(),
43
+ knownTypes: new Set(Object.keys(registry.types)),
44
+ typeProps: new Map(Object.entries(registry.types).map(([type, info]) => [type, new Set(Object.keys(info.props))])),
45
+ typeMeta: buildTypeMeta(space.catalog),
46
+ elementType: (ref) => (findElementByRef(space.schema, ref) ?? findPageByRef(space.schema, ref))?.definition.type,
47
+ schemaVars: /* @__PURE__ */ new Set([
48
+ ...space.schema.variables.map((v) => v.name),
49
+ ...routeParamNames(space.schema),
50
+ ...batchDeclaredVars(ops)
51
+ ]),
52
+ styleVars: new Set(Object.values(space.style.variables).flatMap((group) => Object.keys(group))),
53
+ style: space.style,
54
+ batchVariants: batchDeclaredVariants(ops),
55
+ observedActions: observedInteractionActions(space.schema),
56
+ observedSources: observedDataSources(space.schema)
57
+ };
58
+ };
59
+ var validateOperations = (space, ops) => {
60
+ const batchPages = batchDeclaredPages(ops);
61
+ const batchFolders = batchDeclaredFolders(ops);
62
+ const folderRefs = () => pageFoldersOf(space.schema).map((f) => f.id);
63
+ const ctx = buildValidationCtx(space, ops);
64
+ if (ops.length > 1e3) ctx.errors.push({
65
+ path: "operations",
66
+ message: `Batch has ${ops.length} operations (max ${MAX_OPS})`,
67
+ hint: `Split into batches of at most ${MAX_OPS}`
68
+ });
69
+ ops.forEach((op, i) => {
70
+ const base = `operations[${i}]`;
71
+ if ((op.type === "upsertElement" || op.type === "patchElement" || op.type === "deleteElement" || op.type === "moveElement" || op.type === "upsertBinding" || op.type === "patchBinding" || op.type === "deleteBinding" || op.type === "upsertInteractionFlow" || op.type === "patchInteractionNode" || op.type === "deleteInteraction") && op.pageRef) {
72
+ if (!findPageByRef(space.schema, op.pageRef) && !batchPages.has(op.pageRef)) {
73
+ const validRefs = getPageElements(space.schema).map(pageRefOf);
74
+ ctx.errors.push({
75
+ path: `${base}.pageRef`,
76
+ message: `Page "${op.pageRef}" does not exist`,
77
+ hint: "Use an existing page ref, or create it with upsertPage earlier in the same batch",
78
+ validValues: validRefs
79
+ });
80
+ }
81
+ }
82
+ switch (op.type) {
83
+ case "upsertElement":
84
+ checkElementInput(op.element, `${base}.element`, ctx, /* @__PURE__ */ new Set());
85
+ checkVariantApplication(op.element.initialState, `${base}.element.initialState`, ctx);
86
+ break;
87
+ case "patchElement": {
88
+ checkRef(op.ref, `${base}.ref`, ctx);
89
+ const page = findPageByRef(space.schema, op.pageRef);
90
+ const target = page ? resolveRef(space.schema, page, op.ref) : void 0;
91
+ if (op.props) {
92
+ for (const [key, value] of Object.entries(op.props)) if (typeof value === "string") checkVarRefs(value, `${base}.props.${key}`, ctx);
93
+ if (target && target.id !== page?.id) checkTypeProps(target.definition.type, op.props, base, ctx);
94
+ }
95
+ checkVariantApplication(op.initialState, `${base}.initialState`, ctx);
96
+ break;
97
+ }
98
+ case "upsertDefinition":
99
+ case "patchDefinition": {
100
+ const { type, ref, slots, ...slot } = op;
101
+ checkRef(ref, `${base}.ref`, ctx);
102
+ checkSlotCss(slot, base, ctx);
103
+ for (const [slotName, slotDef] of Object.entries(slots ?? {})) checkSlotCss(slotDef, `${base}.slots.${slotName}`, ctx);
104
+ break;
105
+ }
106
+ case "upsertGlobalStyle":
107
+ case "patchGlobalStyle": {
108
+ const { type, componentType, slots, ...slot } = op;
109
+ checkRef(componentType, `${base}.componentType`, ctx);
110
+ checkSlotCss(slot, base, ctx);
111
+ for (const [slotName, slotDef] of Object.entries(slots ?? {})) checkSlotCss(slotDef, `${base}.slots.${slotName}`, ctx);
112
+ break;
113
+ }
114
+ case "deleteGlobalStyle":
115
+ checkRef(op.componentType, `${base}.componentType`, ctx);
116
+ break;
117
+ case "upsertPage":
118
+ checkRef(op.ref, `${base}.ref`, ctx);
119
+ if (op.slug === void 0 && !findPageByRef(space.schema, op.ref)) warnOnce(ctx, `Page "${op.ref}" is being created without a slug. Set a slug for a clean, stable URL (good practice); the page ref is used as a fallback.`);
120
+ if (typeof op.folder === "string" && op.folder !== "") {
121
+ if (!findFolderByRef(space.schema, op.folder) && !batchFolders.has(op.folder)) ctx.errors.push({
122
+ path: `${base}.folder`,
123
+ message: `Folder "${op.folder}" does not exist`,
124
+ hint: "Create it with upsertFolder earlier in the same batch, or read plitzi://folders for valid refs",
125
+ validValues: folderRefs()
126
+ });
127
+ }
128
+ break;
129
+ case "upsertFolder":
130
+ checkRef(op.ref, `${base}.ref`, ctx);
131
+ if (typeof op.parentId === "string") {
132
+ checkRef(op.parentId, `${base}.parentId`, ctx);
133
+ const parent = findFolderByRef(space.schema, op.parentId);
134
+ if (!parent && !batchFolders.has(op.parentId)) ctx.errors.push({
135
+ path: `${base}.parentId`,
136
+ message: `Parent folder "${op.parentId}" does not exist`,
137
+ hint: "Create the parent with upsertFolder first, or read plitzi://folders for valid refs",
138
+ validValues: folderRefs()
139
+ });
140
+ const selfId = findFolderByRef(space.schema, op.ref)?.id ?? op.ref;
141
+ if ((parent?.id ?? op.parentId) === selfId || parent && folderAncestorIds(pageFoldersOf(space.schema), parent.id).includes(selfId)) ctx.errors.push({
142
+ path: `${base}.parentId`,
143
+ message: `Folder "${op.ref}" cannot be nested under itself or one of its descendants`,
144
+ hint: "Choose a parent that is not this folder or below it"
145
+ });
146
+ }
147
+ break;
148
+ case "deleteDefinition":
149
+ case "deleteFolder":
150
+ case "deletePage":
151
+ case "upsertVariable":
152
+ case "deleteVariable":
153
+ checkRef("ref" in op ? op.ref : op.name, `${base}.${"ref" in op ? "ref" : "name"}`, ctx);
154
+ break;
155
+ case "upsertStyleVariable":
156
+ case "deleteStyleVariable":
157
+ if (!STYLE_CATEGORIES.includes(op.category)) ctx.errors.push({
158
+ path: `${base}.category`,
159
+ message: `Unknown style-variable category "${op.category}"`,
160
+ hint: "Use one of the valid categories",
161
+ validValues: STYLE_CATEGORIES
162
+ });
163
+ break;
164
+ case "upsertBinding":
165
+ checkRef(op.ref, `${base}.ref`, ctx);
166
+ checkObservedName(op.binding.source, ctx.observedSources, "Data source", "plitzi://data-sources", `${base}.binding.source`, ctx);
167
+ checkBindingTarget(op.ref, op.category, op.binding.to, `${base}.binding.to`, ctx);
168
+ checkBindingSourceScope(space, ctx, op.ref, op.binding.source, `${base}.binding.source`);
169
+ checkBindingTransformers(op.binding.transformers, `${base}.binding.transformers`, ctx);
170
+ break;
171
+ case "patchBinding":
172
+ checkRef(op.ref, `${base}.ref`, ctx);
173
+ checkObservedName(op.source, ctx.observedSources, "Data source", "plitzi://data-sources", `${base}.source`, ctx);
174
+ checkBindingTarget(op.ref, op.category, op.to, `${base}.to`, ctx);
175
+ if (op.source !== void 0) checkBindingSourceScope(space, ctx, op.ref, op.source, `${base}.source`);
176
+ checkBindingTransformers(op.transformers, `${base}.transformers`, ctx);
177
+ break;
178
+ case "deleteBinding":
179
+ checkRef(op.ref, `${base}.ref`, ctx);
180
+ break;
181
+ case "upsertInteractionFlow":
182
+ checkRef(op.ref, `${base}.ref`, ctx);
183
+ if (op.nodes[0] && op.nodes[0].nodeType !== "trigger") ctx.errors.push({
184
+ path: `${base}.nodes[0].nodeType`,
185
+ message: "The first node of a flow must be a trigger",
186
+ hint: "Put the trigger first; the callbacks/utilities that run after it follow in order"
187
+ });
188
+ op.nodes.forEach((node, n) => checkInteractionNode(node, `${base}.nodes[${n}]`, ctx, op.ref));
189
+ break;
190
+ case "patchInteractionNode": {
191
+ checkRef(op.ref, `${base}.ref`, ctx);
192
+ const existing = findElementByRef(space.schema, op.ref)?.definition.interactions?.[op.nodeId];
193
+ if (existing) checkInteractionNode({
194
+ id: existing.id,
195
+ title: op.title ?? existing.title,
196
+ nodeType: existing.type,
197
+ action: op.action ?? existing.action,
198
+ params: {
199
+ ...existing.params,
200
+ ...op.params ?? {}
201
+ },
202
+ enabled: op.enabled ?? existing.enabled,
203
+ elementId: op.elementId ?? existing.elementId ?? void 0
204
+ }, base, ctx, op.ref);
205
+ else checkObservedName(op.action, ctx.observedActions, "Interaction action", "plitzi://interactions", `${base}.action`, ctx);
206
+ break;
207
+ }
208
+ case "deleteInteraction":
209
+ checkRef(op.ref, `${base}.ref`, ctx);
210
+ if (Boolean(op.flowId) === Boolean(op.nodeId)) ctx.errors.push({
211
+ path: `${base}.nodeId`,
212
+ message: "Provide exactly one of flowId or nodeId",
213
+ hint: "flowId removes a whole flow; nodeId removes a single step"
214
+ });
215
+ break;
216
+ default: break;
217
+ }
218
+ });
219
+ return {
220
+ valid: ctx.errors.length === 0,
221
+ errors: ctx.errors,
222
+ warnings: ctx.warnings
223
+ };
224
+ };
225
+ //#endregion
226
+ export { buildValidationCtx, validateOperations };
@@ -0,0 +1,106 @@
1
+ import { hiddenParams, invalidParams, missingRequiredParams, reconcileParams } from "../../../catalogs/paramSpec.js";
2
+ import { getGlobalCallback } from "../../../catalogs/builtinCallbacks.js";
3
+ import { getElementCallback } from "../../../catalogs/builtinElementCallbacks.js";
4
+ import { getUtility } from "../../../catalogs/builtinUtilities.js";
5
+ import { NULLISH_ELEMENT_IDS } from "../../../helpers/interactions.js";
6
+ import { checkObservedName, warnOnce } from "./context.js";
7
+ //#region src/modules/mcp/tools/shared/validator/interactions.ts
8
+ var describeType = (type) => {
9
+ switch (type) {
10
+ case "boolean": return "a boolean";
11
+ case "number": return "a number";
12
+ case "select": return "one of its allowed values";
13
+ case "scalar": return "a string, number or boolean";
14
+ default: return "a string";
15
+ }
16
+ };
17
+ var checkParams = (label, action, params, spec, base, ctx) => {
18
+ const unknown = Object.keys(params).filter((key) => !(key in spec));
19
+ if (unknown.length > 0) warnOnce(ctx, `${label} "${action}" at ${base} got unknown param(s) ${unknown.map((k) => `"${k}"`).join(", ")} — these are dropped on apply. Valid params: ${Object.keys(spec).join(", ")}.`);
20
+ const effective = reconcileParams(params, spec, true);
21
+ const missing = missingRequiredParams(params, effective, spec);
22
+ if (missing.length > 0) warnOnce(ctx, `${label} "${action}" at ${base} is missing required param(s) ${missing.map((k) => `"${k}"`).join(", ")} — the step is malformed without them (see plitzi://interactions for what each does).`);
23
+ const hidden = hiddenParams(params, effective, spec);
24
+ if (hidden.length > 0) warnOnce(ctx, `${label} "${action}" at ${base}: param(s) ${hidden.map((k) => `"${k}"`).join(", ")} are ignored under the current params (they only apply when a companion param is set — see plitzi://interactions). Set the companion param or drop them.`);
25
+ for (const { key, expected, got, options } of invalidParams(params, effective, spec)) {
26
+ const detail = options ? ` Allowed values: ${options.join(", ")}.` : "";
27
+ ctx.errors.push({
28
+ path: `${base}.params.${key}`,
29
+ message: `${label} "${action}" at ${base}: param "${key}" must be ${describeType(expected)} but got ${got}.${detail}`,
30
+ hint: `Set "${key}" to ${describeType(expected)}.`,
31
+ ...options ? { validValues: options } : {}
32
+ });
33
+ }
34
+ };
35
+ var checkGlobalCallback = (node, base, ctx) => {
36
+ const builtin = getGlobalCallback(node.action);
37
+ if (!builtin) {
38
+ if (getUtility(node.action)) warnOnce(ctx, `"${node.action}" at ${base} is a utility, not a global callback — use nodeType "utility".`);
39
+ else if (getElementCallback(node.action)) warnOnce(ctx, `"${node.action}" at ${base} is an element callback, not a global callback — use nodeType "callback" and set elementId to the element it acts on.`);
40
+ return;
41
+ }
42
+ if (node.elementId !== void 0 && node.elementId !== builtin.source) warnOnce(ctx, `Global callback "${node.action}" at ${base} is registered on "${builtin.source}", not on "${node.elementId}". Omit elementId (the MCP sets "${builtin.source}") or set it to "${builtin.source}".`);
43
+ if (node.params && builtin.strictParams) checkParams("Global callback", node.action, node.params, builtin.params, base, ctx);
44
+ };
45
+ var checkSetStateKey = (node, base, ctx, hostRef) => {
46
+ const key = node.params?.key;
47
+ if (typeof key !== "string" || key === "") return;
48
+ const targetRef = typeof node.elementId === "string" && node.elementId !== "" ? node.elementId : hostRef;
49
+ const type = ctx.elementType(targetRef);
50
+ const meta = type ? ctx.typeMeta.get(type) : void 0;
51
+ if (!meta) return;
52
+ const state = node.params?.category === "state";
53
+ const validKeys = state ? /* @__PURE__ */ new Set(["visibility", ...[...meta.styleSelectors].map((selector) => `styleSelectors.${selector}`)]) : meta.attributes;
54
+ if (validKeys.size === 0 || validKeys.has(key)) return;
55
+ const kind = state ? "state" : "attribute";
56
+ const valid = [...validKeys].sort();
57
+ const detail = `Element setState at ${base} sets ${kind} "${key}" on type "${type}", which has no such ${kind} key`;
58
+ if (!meta.custom) ctx.errors.push({
59
+ path: base,
60
+ message: detail,
61
+ hint: `Use one of: ${valid.join(", ")}`,
62
+ validValues: valid
63
+ });
64
+ else warnOnce(ctx, `${detail} (${valid.join(", ")}). It may still be valid — verify against plitzi://types.`);
65
+ };
66
+ var checkElementCallback = (node, base, ctx, hostRef) => {
67
+ const builtin = getElementCallback(node.action);
68
+ if (!builtin) {
69
+ if (getUtility(node.action)) warnOnce(ctx, `"${node.action}" at ${base} is a utility, not an element callback — use nodeType "utility".`);
70
+ else {
71
+ const global = getGlobalCallback(node.action);
72
+ if (global) warnOnce(ctx, `"${node.action}" at ${base} is a global callback (source "${global.source}"), not an element callback — use nodeType "globalCallback" and omit elementId.`);
73
+ }
74
+ return;
75
+ }
76
+ if (node.params && builtin.strictParams) checkParams("Element callback", node.action, node.params, builtin.params, base, ctx);
77
+ if (node.action === "setState") checkSetStateKey(node, base, ctx, hostRef);
78
+ };
79
+ var checkUtility = (node, base, ctx) => {
80
+ if (node.elementId && !NULLISH_ELEMENT_IDS.has(node.elementId)) warnOnce(ctx, `Utility "${node.action}" at ${base} has elementId "${node.elementId}" — a utility takes NO element (it is resolved by its action alone). Omit elementId; the MCP stores null. Pointing it at an element is a no-op.`);
81
+ const utility = getUtility(node.action);
82
+ if (!utility) {
83
+ if (getGlobalCallback(node.action)) warnOnce(ctx, `"${node.action}" at ${base} is a global callback, not a utility — use nodeType "globalCallback".`);
84
+ else if (getElementCallback(node.action)) warnOnce(ctx, `"${node.action}" at ${base} is an element callback, not a utility — use nodeType "callback".`);
85
+ return;
86
+ }
87
+ if (node.params && utility.strictParams) checkParams("Utility", node.action, node.params, utility.params, base, ctx);
88
+ };
89
+ var checkInteractionNode = (node, base, ctx, hostRef) => {
90
+ checkObservedName(node.action, ctx.observedActions, "Interaction action", "plitzi://interactions", `${base}.action`, ctx);
91
+ if (node.elementId === "undefined" || node.elementId === "null") warnOnce(ctx, `Node "${node.action}" at ${base} has the literal string elementId "${node.elementId}" — a stringified nullish value. Re-save the step: elementId must be a real element ref, a source module, or empty for a utility.`);
92
+ switch (node.nodeType) {
93
+ case "globalCallback":
94
+ checkGlobalCallback(node, base, ctx);
95
+ break;
96
+ case "callback":
97
+ checkElementCallback(node, base, ctx, hostRef);
98
+ break;
99
+ case "utility":
100
+ checkUtility(node, base, ctx);
101
+ break;
102
+ default: break;
103
+ }
104
+ };
105
+ //#endregion
106
+ export { checkInteractionNode };
@@ -0,0 +1,28 @@
1
+ import { isValidIdRef } from "@plitzi/sdk-schema/helpers/idRef";
2
+ //#region src/modules/mcp/tools/shared/validator/refs.ts
3
+ var REF_RE = /^[a-zA-Z0-9._-]+$/;
4
+ var checkRef = (ref, path, ctx) => {
5
+ if (!ref || ref.trim().length === 0) {
6
+ ctx.errors.push({
7
+ path,
8
+ message: "Ref must not be empty",
9
+ hint: "Use a semantic name like \"hero.title\""
10
+ });
11
+ return;
12
+ }
13
+ if (!REF_RE.test(ref)) ctx.errors.push({
14
+ path,
15
+ message: `Ref "${ref}" has invalid characters`,
16
+ hint: "Allowed characters: letters, numbers, dot, hyphen, underscore"
17
+ });
18
+ };
19
+ var checkIdRef = (ref, path, ctx) => {
20
+ checkRef(ref, path, ctx);
21
+ if (ref && REF_RE.test(ref) && !isValidIdRef(ref)) ctx.errors.push({
22
+ path,
23
+ message: `Ref "${ref}" is not a valid idRef`,
24
+ hint: "Use only letters, numbers, hyphens and underscores (e.g. \"hero-cta\" or \"my_list_card\"). This ref becomes the element idRef, which the runtime embeds in source names like `apiContainer_<idRef>.field` and in interaction targets — a dot would break those paths."
25
+ });
26
+ };
27
+ //#endregion
28
+ export { REF_RE, checkIdRef, checkRef };
@@ -1,16 +1,36 @@
1
+ import { cloneSpace } from "../helpers/space.js";
1
2
  import { environment, operations } from "./operations/index.js";
3
+ import { applyOperations } from "./apply/dispatch.js";
2
4
  import { defineTool } from "./shared/tool.js";
3
- import { validateOperations } from "./shared/validator.js";
5
+ import { validateOperations } from "./shared/validator/index.js";
6
+ import { auditResources } from "./shared/validator/audit.js";
4
7
  //#region src/modules/mcp/tools/validate.ts
5
8
  var validateShape = {
6
9
  environment,
7
10
  operations
8
11
  };
9
- var validate = (input, space) => validateOperations(space, input.operations);
12
+ var validate = (input, space) => {
13
+ const validation = validateOperations(space, input.operations);
14
+ if (!validation.valid) return validation;
15
+ const env = input.environment ?? "main";
16
+ const draft = cloneSpace(space);
17
+ const outcome = applyOperations(draft, env, input.operations);
18
+ if (outcome.errors.length > 0) return {
19
+ valid: false,
20
+ errors: outcome.errors,
21
+ warnings: validation.warnings
22
+ };
23
+ const audit = auditResources(draft, input.operations);
24
+ return {
25
+ valid: audit.errors.length === 0,
26
+ errors: audit.errors,
27
+ warnings: [...validation.warnings, ...audit.warnings]
28
+ };
29
+ };
10
30
  var validateTool = defineTool({
11
31
  name: "plitzi_validate",
12
32
  title: "Validate",
13
- description: "Check a batch of operations without executing them. Returns teachable errors and warnings.",
33
+ description: "Check a batch of operations without executing them. Returns teachable errors and warnings — including any PRE-EXISTING malformation in a resource the batch touches (which blocks a save until fixed).",
14
34
  inputShape: validateShape,
15
35
  access: "read",
16
36
  run: (input, ctx) => validate(input, ctx.space)
@@ -1,7 +1,7 @@
1
1
  import { computeVersion } from "../mcp/helpers/computeVersion.js";
2
2
  import { cloneSpace, findPageByRef, getPageElements } from "../mcp/helpers/space.js";
3
3
  import { applyOperations } from "../mcp/tools/apply/dispatch.js";
4
- import { validateOperations } from "../mcp/tools/shared/validator.js";
4
+ import { validateOperations } from "../mcp/tools/shared/validator/index.js";
5
5
  import { PREVIEW_TOKEN_PARAM } from "../mcp/constants.js";
6
6
  import { buildBody } from "./buildBody.js";
7
7
  import { randomUUID } from "node:crypto";
@@ -10,6 +10,7 @@
10
10
  <link rel="preconnect" href="https://fonts.googleapis.com" />
11
11
  <link rel="preconnect" href="https://cdn.plitzi.com" />
12
12
 
13
+ <% if (!locals.ssrOnly) { %>
13
14
  <link rel="modulepreload" href="<%= locals.react || '' %>" crossorigin />
14
15
  <link rel="modulepreload" href="<%= locals.reactDom || '' %>" crossorigin />
15
16
 
@@ -25,6 +26,7 @@
25
26
  }
26
27
  }
27
28
  </script>
29
+ <% } %>
28
30
 
29
31
  <link
30
32
  rel="preload"
@@ -3,7 +3,7 @@ export { registerHealthCheck, buildHealthPayload } from './core/health';
3
3
  export type { HealthCheckApp, HealthIdentity } from './core/health';
4
4
  export { createJsonAdapters } from './adapters/jsonAdapters';
5
5
  export { AIEngine, toolResponseOk, toolResponseErr, zodToJsonSchema, getAllowedModes, bindTools, isToolActive, resolveToolHandler, isCallToolResult, toolResponseFromResult } from './modules/ai';
6
- export { createMcpServer, handleMcp, serveMcp, readMcpBody, createHttpPreviewClient, createHttpScreenshotClient } from './modules/mcp';
6
+ export { createMcpServer, handleMcp, serveMcp, readMcpBody, buildAgentGuide, createHttpPreviewClient, createHttpScreenshotClient } from './modules/mcp';
7
7
  export { createPreview, createMemoryDraftStore, PREVIEW_TOKEN_PARAM } from './modules/ssr/preview';
8
8
  export { apply, search, read, validate, applyShape, searchShape, readShape, validateShape, operation, tools } from './modules/mcp/tools';
9
9
  export type { JsonAdaptersConfig } from './adapters/jsonAdapters';