@plitzi/sdk-server 0.32.15 → 0.32.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +20 -0
- package/dist/adapters/jsonAdapters.js +2 -1
- package/dist/core/createServer.js +10 -2
- package/dist/core/http/stages/middlewares.js +3 -2
- package/dist/core/http/stages/pluginAssets.js +3 -2
- package/dist/core/http/stages/static.js +3 -2
- package/dist/core/requestParser.js +1 -1
- package/dist/core/server/{ssrServer.js → pageServer.js} +15 -6
- package/dist/core/services/registry.js +2 -2
- package/dist/helpers/cache/defaults.js +3 -3
- package/dist/helpers/serverLog.js +3 -3
- package/dist/index.js +1 -1
- package/dist/modules/mcp/apps/shared/shell.ejs +19 -2
- package/dist/modules/mcp/catalogs/cssCatalog/border.js +30 -0
- package/dist/modules/mcp/catalogs/cssCatalog/box.js +31 -0
- package/dist/modules/mcp/catalogs/cssCatalog/helpers.js +185 -0
- package/dist/modules/mcp/catalogs/cssCatalog/index.js +131 -0
- package/dist/modules/mcp/catalogs/cssCatalog/layout.js +98 -0
- package/dist/modules/mcp/catalogs/cssCatalog/visual.js +144 -0
- package/dist/modules/mcp/catalogs/registry.js +1 -1
- package/dist/modules/mcp/helpers/guide.js +23 -13
- package/dist/modules/mcp/helpers/space.js +1 -1
- package/dist/modules/mcp/resources/core.js +5 -2
- package/dist/modules/mcp/resources/primer.js +2 -1
- package/dist/modules/mcp/resources/register.js +6 -3
- package/dist/modules/mcp/resources/renderGuide.js +90 -10
- package/dist/modules/mcp/server.js +1 -1
- package/dist/modules/mcp/tools/operations/schema/interactions/upsertInteractionFlow.js +3 -2
- package/dist/modules/mcp/tools/operations/style/shared.js +2 -2
- package/dist/modules/mcp/tools/operations/style/write.js +2 -2
- package/dist/modules/mcp/tools/render.js +1 -1
- package/dist/modules/mcp/tools/shared/validator/audit.js +1 -4
- package/dist/modules/mcp/tools/shared/validator/css.js +3 -11
- package/dist/modules/mcp/tools/shared/validator/elements.js +4 -1
- package/dist/modules/mcp/tools/shared/validator/index.js +15 -15
- package/dist/modules/mcp/tools/shared/validator/interactions.js +1 -4
- package/dist/modules/oauth/authorize.js +2 -1
- package/dist/modules/oauth/metadata.js +15 -2
- package/dist/modules/oauth/records.js +1 -1
- package/dist/modules/oauth/token.js +2 -2
- package/dist/modules/ssr/registerExternalPlugins.js +1 -1
- package/dist/plugins/manager.js +1 -1
- package/dist/src/core/createServer.d.ts +9 -1
- package/dist/src/core/server/pageServer.d.ts +10 -0
- package/dist/src/core/services/registry.d.ts +1 -1
- package/dist/src/helpers/serverLog.d.ts +1 -1
- package/dist/src/modules/mcp/catalogs/cssCatalog/border.d.ts +4 -0
- package/dist/src/modules/mcp/catalogs/cssCatalog/box.d.ts +4 -0
- package/dist/src/modules/mcp/catalogs/cssCatalog/helpers.d.ts +34 -0
- package/dist/src/modules/mcp/catalogs/{cssCatalog.d.ts → cssCatalog/index.d.ts} +10 -5
- package/dist/src/modules/mcp/catalogs/cssCatalog/layout.d.ts +9 -0
- package/dist/src/modules/mcp/catalogs/cssCatalog/visual.d.ts +8 -0
- package/dist/src/modules/mcp/helpers/guide.d.ts +2 -2
- package/dist/src/modules/mcp/helpers/space.d.ts +1 -1
- package/dist/src/modules/mcp/resources/index.d.ts +1 -1
- package/dist/src/modules/mcp/tests/apply.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/bindings.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/css.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/helpers.d.ts +13 -0
- package/dist/src/modules/mcp/tests/interactions.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/pages.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/reads.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/search.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/styles.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/tools.test.d.ts +1 -0
- package/dist/src/modules/mcp/tests/validation.test.d.ts +1 -0
- package/dist/src/modules/mcp/tools/screenshot.d.ts +1 -1
- package/dist/src/modules/oauth/metadata.d.ts +14 -1
- package/package.json +10 -10
- package/dist/modules/mcp/catalogs/cssCatalog.js +0 -205
- package/dist/src/core/server/ssrServer.d.ts +0 -2
- /package/dist/src/modules/mcp/{mcp.test.d.ts → catalogs/cssCatalog/cssCatalog.test.d.ts} +0 -0
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { FLEX_DIRECTIONS, FLEX_WRAPS, LENGTH_RE, NUMBER_RE, QUOTE_RE, splitOn, splitPair, splitTokens } from "./helpers.js";
|
|
2
|
+
//#region src/modules/mcp/catalogs/cssCatalog/layout.ts
|
|
3
|
+
var expandOverflow = (value, out) => {
|
|
4
|
+
const [x, y = x] = splitTokens(value);
|
|
5
|
+
out["overflow-x"] = x;
|
|
6
|
+
out["overflow-y"] = y;
|
|
7
|
+
};
|
|
8
|
+
var FLEX_KEYWORDS = /* @__PURE__ */ new Map([
|
|
9
|
+
["none", [
|
|
10
|
+
"0",
|
|
11
|
+
"0",
|
|
12
|
+
"auto"
|
|
13
|
+
]],
|
|
14
|
+
["auto", [
|
|
15
|
+
"1",
|
|
16
|
+
"1",
|
|
17
|
+
"auto"
|
|
18
|
+
]],
|
|
19
|
+
["initial", [
|
|
20
|
+
"0",
|
|
21
|
+
"1",
|
|
22
|
+
"auto"
|
|
23
|
+
]]
|
|
24
|
+
]);
|
|
25
|
+
var writeFlex = (out, grow, shrink, basis) => {
|
|
26
|
+
out["flex-grow"] = grow;
|
|
27
|
+
out["flex-shrink"] = shrink;
|
|
28
|
+
out["flex-basis"] = basis;
|
|
29
|
+
};
|
|
30
|
+
var expandFlex = (value, out) => {
|
|
31
|
+
const tokens = splitTokens(value);
|
|
32
|
+
if (tokens.length === 1) {
|
|
33
|
+
const [only] = tokens;
|
|
34
|
+
const keyword = FLEX_KEYWORDS.get(only);
|
|
35
|
+
if (keyword) writeFlex(out, ...keyword);
|
|
36
|
+
else if (NUMBER_RE.test(only)) writeFlex(out, only, "1", "0%");
|
|
37
|
+
else writeFlex(out, "1", "1", only);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
const [grow, second, third] = tokens;
|
|
41
|
+
if (!NUMBER_RE.test(grow)) return;
|
|
42
|
+
if (tokens.length === 2) if (NUMBER_RE.test(second)) writeFlex(out, grow, second, "0%");
|
|
43
|
+
else writeFlex(out, grow, "1", second);
|
|
44
|
+
else writeFlex(out, grow, second, third);
|
|
45
|
+
};
|
|
46
|
+
var expandFlexFlow = (value, out) => {
|
|
47
|
+
for (const token of splitTokens(value)) if (FLEX_DIRECTIONS.has(token)) out["flex-direction"] = token;
|
|
48
|
+
else if (FLEX_WRAPS.has(token)) out["flex-wrap"] = token;
|
|
49
|
+
};
|
|
50
|
+
var expandPlacePair = (prefix, value, out) => {
|
|
51
|
+
const [align, justify = align] = splitTokens(value);
|
|
52
|
+
const prop = prefix.slice(6);
|
|
53
|
+
out[`align-${prop}`] = align;
|
|
54
|
+
out[`justify-${prop}`] = justify;
|
|
55
|
+
};
|
|
56
|
+
var expandGridLine = (key, value, out) => {
|
|
57
|
+
const axis = key.slice(5);
|
|
58
|
+
const [start, end = "auto"] = splitOn(value, "/");
|
|
59
|
+
out[`grid-${axis}-start`] = start;
|
|
60
|
+
out[`grid-${axis}-end`] = end;
|
|
61
|
+
};
|
|
62
|
+
var expandGridArea = (value, out) => {
|
|
63
|
+
const [rowStart, colStart = rowStart, rowEnd = colStart, colEnd = rowEnd] = splitOn(value, "/");
|
|
64
|
+
out["grid-row-start"] = rowStart;
|
|
65
|
+
out["grid-column-start"] = colStart;
|
|
66
|
+
out["grid-row-end"] = rowEnd;
|
|
67
|
+
out["grid-column-end"] = colEnd;
|
|
68
|
+
};
|
|
69
|
+
var expandColumns = (value, out) => {
|
|
70
|
+
const tokens = splitTokens(value);
|
|
71
|
+
if (tokens.length === 1) {
|
|
72
|
+
const [only] = tokens;
|
|
73
|
+
if (NUMBER_RE.test(only)) out["column-count"] = only;
|
|
74
|
+
else out["column-width"] = only;
|
|
75
|
+
} else if (tokens.length >= 2) {
|
|
76
|
+
const [first, second] = tokens;
|
|
77
|
+
const [width, count] = NUMBER_RE.test(first) ? [second, first] : [first, second];
|
|
78
|
+
out["column-width"] = width;
|
|
79
|
+
out["column-count"] = count;
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
var isTrackList = (tokens) => tokens.some((token) => token === "none" || token === "auto" || token === "min-content" || token === "max-content" || token.startsWith("[") || token.startsWith("minmax(") || token.startsWith("repeat(") || token.startsWith("fit-content(") || LENGTH_RE.test(token) || /^-?[\d.]+fr$/.test(token));
|
|
83
|
+
var expandGridTemplate = (value, out) => {
|
|
84
|
+
const [first, second] = splitPair(value, "/");
|
|
85
|
+
const rowTokens = splitTokens(first);
|
|
86
|
+
const areas = rowTokens.filter((token) => QUOTE_RE.test(token));
|
|
87
|
+
const sizes = rowTokens.filter((token) => !QUOTE_RE.test(token));
|
|
88
|
+
if (second !== void 0) {
|
|
89
|
+
if (second !== "") out["grid-template-columns"] = second;
|
|
90
|
+
} else if (areas.length === 0) {
|
|
91
|
+
if (isTrackList(rowTokens)) out["grid-template-columns"] = first;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
if (areas.length > 0) out["grid-template-areas"] = areas.join(" ");
|
|
95
|
+
if (sizes.length > 0) out["grid-template-rows"] = sizes.join(" ");
|
|
96
|
+
};
|
|
97
|
+
//#endregion
|
|
98
|
+
export { expandColumns, expandFlex, expandFlexFlow, expandGridArea, expandGridLine, expandGridTemplate, expandOverflow, expandPlacePair };
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { ANIMATION_DIRECTIONS, ANIMATION_FILL_MODES, ANIMATION_PLAY_STATES, FONT_STYLE_KEYWORDS, FONT_VARIANT_KEYWORDS, FONT_WEIGHT_KEYWORDS, LENGTH_RE, LIST_STYLE_POSITIONS, LIST_STYLE_TYPES, NUMBER_RE, TEXT_DECORATION_LINES, TEXT_DECORATION_STYLES, TIME_RE, isTimingFunction, splitOn, splitPair, splitTokens } from "./helpers.js";
|
|
2
|
+
import { classifyBorderTokens } from "./border.js";
|
|
3
|
+
//#region src/modules/mcp/catalogs/cssCatalog/visual.ts
|
|
4
|
+
var expandLayers = (value, out, expandLayer) => {
|
|
5
|
+
const layers = splitOn(value, ",");
|
|
6
|
+
if (layers.length === 1) {
|
|
7
|
+
expandLayer(layers[0], out);
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
const perLayer = layers.map((layer) => {
|
|
11
|
+
const layerOut = {};
|
|
12
|
+
expandLayer(layer, layerOut);
|
|
13
|
+
return layerOut;
|
|
14
|
+
});
|
|
15
|
+
const keys = new Set(perLayer.flatMap((layer) => Object.keys(layer)));
|
|
16
|
+
for (const key of keys) out[key] = perLayer.map((layer) => String(layer[key] ?? "initial")).join(", ");
|
|
17
|
+
};
|
|
18
|
+
var expandOutline = (value, out) => {
|
|
19
|
+
for (const [kind, token] of Object.entries(classifyBorderTokens(value))) out[`outline-${kind}`] = token;
|
|
20
|
+
};
|
|
21
|
+
var expandListStyle = (value, out) => {
|
|
22
|
+
for (const token of splitTokens(value)) if (token.startsWith("url(")) out["list-style-image"] = token;
|
|
23
|
+
else if (LIST_STYLE_POSITIONS.has(token)) out["list-style-position"] = token;
|
|
24
|
+
else if (LIST_STYLE_TYPES.has(token)) out["list-style-type"] = token;
|
|
25
|
+
};
|
|
26
|
+
var expandTextDecoration = (value, out) => {
|
|
27
|
+
for (const token of splitTokens(value)) if (TEXT_DECORATION_LINES.has(token)) out["text-decoration-line"] = token;
|
|
28
|
+
else if (TEXT_DECORATION_STYLES.has(token)) out["text-decoration-style"] = token;
|
|
29
|
+
else out["text-decoration-color"] = token;
|
|
30
|
+
};
|
|
31
|
+
var expandTransitionLayer = (layer, out) => {
|
|
32
|
+
let foundDuration = false;
|
|
33
|
+
for (const token of splitTokens(layer)) if (isTimingFunction(token)) out["transition-timing-function"] = token;
|
|
34
|
+
else if (TIME_RE.test(token) || NUMBER_RE.test(token)) if (foundDuration) out["transition-delay"] = token;
|
|
35
|
+
else {
|
|
36
|
+
out["transition-duration"] = token;
|
|
37
|
+
foundDuration = true;
|
|
38
|
+
}
|
|
39
|
+
else if (!out["transition-property"]) out["transition-property"] = token;
|
|
40
|
+
};
|
|
41
|
+
var expandTransition = (value, out) => expandLayers(value, out, expandTransitionLayer);
|
|
42
|
+
var expandAnimationLayer = (layer, out) => {
|
|
43
|
+
let foundDuration = false;
|
|
44
|
+
for (const token of splitTokens(layer)) if (isTimingFunction(token)) out["animation-timing-function"] = token;
|
|
45
|
+
else if (TIME_RE.test(token)) if (foundDuration) out["animation-delay"] = token;
|
|
46
|
+
else {
|
|
47
|
+
out["animation-duration"] = token;
|
|
48
|
+
foundDuration = true;
|
|
49
|
+
}
|
|
50
|
+
else if (token === "infinite" || NUMBER_RE.test(token)) out["animation-iteration-count"] = token;
|
|
51
|
+
else if (ANIMATION_DIRECTIONS.has(token) && !out["animation-direction"]) out["animation-direction"] = token;
|
|
52
|
+
else if (ANIMATION_FILL_MODES.has(token) && !out["animation-fill-mode"]) out["animation-fill-mode"] = token;
|
|
53
|
+
else if (ANIMATION_PLAY_STATES.has(token) && !out["animation-play-state"]) out["animation-play-state"] = token;
|
|
54
|
+
else if (!out["animation-name"]) out["animation-name"] = token;
|
|
55
|
+
};
|
|
56
|
+
var expandAnimation = (value, out) => expandLayers(value, out, expandAnimationLayer);
|
|
57
|
+
var BACKGROUND_REPEATS = /* @__PURE__ */ new Set([
|
|
58
|
+
"repeat",
|
|
59
|
+
"no-repeat",
|
|
60
|
+
"repeat-x",
|
|
61
|
+
"repeat-y",
|
|
62
|
+
"space",
|
|
63
|
+
"round"
|
|
64
|
+
]);
|
|
65
|
+
var BACKGROUND_ATTACHMENTS = /* @__PURE__ */ new Set([
|
|
66
|
+
"scroll",
|
|
67
|
+
"fixed",
|
|
68
|
+
"local"
|
|
69
|
+
]);
|
|
70
|
+
var BACKGROUND_BOXES = /* @__PURE__ */ new Set([
|
|
71
|
+
"border-box",
|
|
72
|
+
"padding-box",
|
|
73
|
+
"content-box"
|
|
74
|
+
]);
|
|
75
|
+
var BACKGROUND_POSITIONS = /* @__PURE__ */ new Set([
|
|
76
|
+
"center",
|
|
77
|
+
"top",
|
|
78
|
+
"bottom",
|
|
79
|
+
"left",
|
|
80
|
+
"right"
|
|
81
|
+
]);
|
|
82
|
+
var isImage = (token) => token.startsWith("url(") || token.includes("-gradient(");
|
|
83
|
+
var isPosition = (token) => BACKGROUND_POSITIONS.has(token) || LENGTH_RE.test(token);
|
|
84
|
+
var isSize = (token) => token === "cover" || token === "contain" || token === "auto" || LENGTH_RE.test(token);
|
|
85
|
+
var expandBackgroundLayer = (layer, out) => {
|
|
86
|
+
const [beforeSlash, afterSlash] = splitPair(layer, "/");
|
|
87
|
+
const tokens = splitTokens(beforeSlash);
|
|
88
|
+
if (afterSlash !== void 0) {
|
|
89
|
+
let positionStart = tokens.length;
|
|
90
|
+
while (positionStart > 0 && isPosition(tokens[positionStart - 1])) positionStart -= 1;
|
|
91
|
+
const position = tokens.splice(positionStart, tokens.length - positionStart);
|
|
92
|
+
if (position.length > 0) out["background-position"] = position.join(" ");
|
|
93
|
+
const afterTokens = splitTokens(afterSlash);
|
|
94
|
+
let sizeEnd = 0;
|
|
95
|
+
while (sizeEnd < afterTokens.length && isSize(afterTokens[sizeEnd])) sizeEnd += 1;
|
|
96
|
+
if (sizeEnd > 0) out["background-size"] = afterTokens.slice(0, sizeEnd).join(" ");
|
|
97
|
+
tokens.push(...afterTokens.slice(sizeEnd));
|
|
98
|
+
}
|
|
99
|
+
const positions = [];
|
|
100
|
+
for (const token of tokens) if (isImage(token)) out["background-image"] = token;
|
|
101
|
+
else if (BACKGROUND_REPEATS.has(token)) out["background-repeat"] = token;
|
|
102
|
+
else if (token === "cover" || token === "contain") out["background-size"] = token;
|
|
103
|
+
else if (BACKGROUND_ATTACHMENTS.has(token)) out["background-attachment"] = token;
|
|
104
|
+
else if (BACKGROUND_BOXES.has(token)) if (out["background-origin"]) out["background-clip"] = token;
|
|
105
|
+
else out["background-origin"] = token;
|
|
106
|
+
else if (isPosition(token)) positions.push(token);
|
|
107
|
+
else if (!out["background-color"]) out["background-color"] = token;
|
|
108
|
+
if (positions.length > 0) out["background-position"] = positions.join(" ");
|
|
109
|
+
};
|
|
110
|
+
var expandBackground = (value, out) => expandLayers(value, out, expandBackgroundLayer);
|
|
111
|
+
var expandFont = (value, out) => {
|
|
112
|
+
const [beforeSlash, afterSlash] = splitPair(value, "/");
|
|
113
|
+
const tokens = splitTokens(beforeSlash);
|
|
114
|
+
if (afterSlash !== void 0) {
|
|
115
|
+
const [lineHeight, ...family] = splitTokens(afterSlash);
|
|
116
|
+
out["line-height"] = lineHeight;
|
|
117
|
+
if (family.length > 0) out["font-family"] = family.join(" ");
|
|
118
|
+
}
|
|
119
|
+
const family = [];
|
|
120
|
+
let foundSize = false;
|
|
121
|
+
for (const token of tokens) {
|
|
122
|
+
if (foundSize) {
|
|
123
|
+
family.push(token);
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
if (token === "normal") {
|
|
127
|
+
const slot = [
|
|
128
|
+
"font-style",
|
|
129
|
+
"font-variant",
|
|
130
|
+
"font-weight"
|
|
131
|
+
].find((prop) => !out[prop]);
|
|
132
|
+
if (slot) out[slot] = token;
|
|
133
|
+
} else if (FONT_STYLE_KEYWORDS.has(token) && !out["font-style"]) out["font-style"] = token;
|
|
134
|
+
else if (FONT_VARIANT_KEYWORDS.has(token) && !out["font-variant"]) out["font-variant"] = token;
|
|
135
|
+
else if (FONT_WEIGHT_KEYWORDS.has(token) && !out["font-weight"]) out["font-weight"] = token;
|
|
136
|
+
else if (LENGTH_RE.test(token)) {
|
|
137
|
+
out["font-size"] = token;
|
|
138
|
+
foundSize = true;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (family.length > 0) out["font-family"] = family.join(" ");
|
|
142
|
+
};
|
|
143
|
+
//#endregion
|
|
144
|
+
export { expandAnimation, expandBackground, expandFont, expandListStyle, expandOutline, expandTextDecoration, expandTransition };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { BUILTIN_COMPONENTS } from "./builtinComponents.js";
|
|
2
|
-
import { cssProperties } from "./cssCatalog.js";
|
|
2
|
+
import { cssProperties } from "./cssCatalog/index.js";
|
|
3
3
|
//#region src/modules/mcp/catalogs/registry.ts
|
|
4
4
|
var enrichType = (typeName, info, catalog) => {
|
|
5
5
|
const entry = catalog?.[typeName];
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/modules/mcp/helpers/guide.ts
|
|
2
|
-
var serverInstructions = "Plitzi AI server: read-then-write editing of a Plitzi space. Reads follow a filesystem model — list cheap, read one item in detail on demand; never fetch a whole tree you do not need. Workflow: (1) read plitzi://primer/{env} once — it bundles the guide, types, css-properties and page/definition/variable summaries in a single call; (2) plitzi_search with include:\"detail\" to jump to elements — each hit then carries its uri, stateVersion AND full style/resolvedStyle, so an edit needs no per-element read; open a page skeleton or element only when you need its tree/detail (the skeleton already lists the style classes of each node, and plitzi_read fetches many uris at once); (3) plitzi_apply with dryRun to preview a batch; (4) plitzi_apply to persist, passing expectedResourceVersions to guard against concurrent edits — apply and search both hand back the versions you need for the next edit. Use patchElement / patchDefinition to change only some props / CSS (the upsert variants replace them all). An element read (and search include:\"detail\") inlines the CSS of the definitions it attaches under resolvedStyle, so you rarely need a separate definition read. Refs accept a semantic idRef ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) or the raw id — the idRef is ALSO the runtime wiring key, so a provider source is `<type>_<idRef>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is kebab-case
|
|
2
|
+
var serverInstructions = "Plitzi AI server: read-then-write editing of a Plitzi space. Reads follow a filesystem model — list cheap, read one item in detail on demand; never fetch a whole tree you do not need. Workflow: (1) read plitzi://primer/{env} once — it bundles the guide, types, css-properties and page/definition/variable summaries in a single call; (2) plitzi_search with include:\"detail\" to jump to elements — each hit then carries its uri, stateVersion AND full style/resolvedStyle, so an edit needs no per-element read; open a page skeleton or element only when you need its tree/detail (the skeleton already lists the style classes of each node, and plitzi_read fetches many uris at once); (3) plitzi_apply with dryRun to preview a batch; (4) plitzi_apply to persist, passing expectedResourceVersions to guard against concurrent edits — apply and search both hand back the versions you need for the next edit. Use patchElement / patchDefinition to change only some props / CSS (the upsert variants replace them all). An element read (and search include:\"detail\") inlines the CSS of the definitions it attaches under resolvedStyle, so you rarely need a separate definition read. Refs accept a semantic idRef ([A-Za-z0-9_-] starting with a letter, unique, chosen by you) or the raw id — the idRef is ALSO the runtime wiring key, so a provider source is `<type>_<idRef>.<field>`, visible to the provider’s DESCENDANTS only (bind inside its subtree). CSS is plain kebab-case — write shorthands freely (`border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`), they are expanded to longhands for you and STORED that way, so read-back shows the longhands; style vars are var(--name), schema vars are {{name}}. READERS — do not confuse them: MCP *resources* are the browsable catalog (list them, or open one by URI); plitzi_search FINDS refs by label/type/attribute; plitzi_read BATCH-fetches URIs you already hold. Reach for search/read to work; browse resources to discover. Elements also carry applied style variants + visibility (initialState), data bindings and interaction flows: edit them with patchElement (initialState), upsertBinding/patchBinding/deleteBinding, and upsertInteractionFlow/patchInteractionNode/deleteInteraction. An element read shows all three plus availableVariants (which variant each of its classes offers). Separately, to SHOW the user a small self-contained widget (offline, no space or backend) instead of editing the space — a card, hero, pricing table, a visual answer — use plitzi_render; read plitzi://render/guide for it.";
|
|
3
3
|
var guideQuickstart = `# Plitzi AI MCP — quickstart
|
|
4
4
|
This is the condensed guide; read \`plitzi://guide\` for the full reference (every resource, op and example).
|
|
5
5
|
|
|
@@ -19,10 +19,12 @@ interactions target by it. A dot would split that path; an **underscore is fine*
|
|
|
19
19
|
from the idRef (element types have none), so \`list_food_item\` reads unambiguously as type \`list\`, idRef \`food_item\`.
|
|
20
20
|
|
|
21
21
|
**Styling:** CSS keys are **kebab-case** (\`background-color\`); \`var(--token)\` for style vars, \`{{name}}\` for schema
|
|
22
|
-
vars.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
22
|
+
vars. **Write plain CSS** — shorthands (\`border\`, \`padding\`, \`margin\`, \`gap\`, \`overflow\`, \`flex\`, \`background\`,
|
|
23
|
+
\`font\`, \`transition\`, \`animation\`, \`grid\`, \`grid-row\`/\`grid-column\`, \`place-*\`, \`outline\`, \`columns\`,
|
|
24
|
+
\`list-style\`, \`text-decoration\`) are expanded to their longhands for you, so \`border: 1px solid red\` is stored as
|
|
25
|
+
\`border-top-color\`/\`border-top-width\`/\`border-top-style\`/… That is also how you read them back. Flex layout is
|
|
26
|
+
still \`display: flex\` + \`flex-direction\`/\`align-items\`/…, not a \`flex\` value. Mind a type's \`defaultStyle\`
|
|
27
|
+
(\`text\` is \`display: inline\`). Global styles (\`button {…}\`) and id styles (\`#id\`) have their own ops.
|
|
26
28
|
|
|
27
29
|
**Data bindings** (\`upsertBinding\`, category attributes|style|initialState): connect a \`source\` to a \`to\` field.
|
|
28
30
|
A source \`<type>_<idRef>\` is scoped to the provider's **DESCENDANTS only** — bind inside the provider's subtree
|
|
@@ -202,14 +204,22 @@ pointed at the old name is repointed with it, so the element stays wired. You do
|
|
|
202
204
|
- A definition lives in the **style schema**; an element's \`style.base\` (element schema) is the link that applies
|
|
203
205
|
it. Styling an element = upsertDefinition + upsertElement with that ref in \`style.base\`, in one batch.
|
|
204
206
|
- CSS keys are **kebab-case** (\`background-color\`). camelCase is rejected — read \`plitzi://css-properties\`.
|
|
205
|
-
-
|
|
206
|
-
\`padding\`, \`margin\`, \`inset\`, \`gap\`
|
|
207
|
-
-
|
|
208
|
-
|
|
209
|
-
\`
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
\`
|
|
207
|
+
- **Write normal CSS — shorthands are accepted and expanded for you.** \`border\`, \`border-{side}\`,
|
|
208
|
+
\`border-width\`/\`-color\`/\`-style\`, \`border-radius\`, \`padding\`, \`margin\`, \`inset\`, \`gap\`, \`overflow\`,
|
|
209
|
+
\`flex\`, \`flex-flow\`, \`background\`, \`font\`, \`transition\`, \`animation\`, \`grid\`, \`grid-template\`,
|
|
210
|
+
\`grid-area\`, \`grid-row\`, \`grid-column\`, \`place-content\`, \`place-items\`, \`place-self\`, \`outline\`,
|
|
211
|
+
\`columns\`, \`list-style\`, \`text-decoration\` — the full list is in \`plitzi://css-properties\` under
|
|
212
|
+
\`shorthands\`. They **persist as longhand keys** (that is what a read gives back), so a breakpoint/state/variant
|
|
213
|
+
can still override one property at a time.
|
|
214
|
+
- \`border: 1px solid red\` → \`border-top-width: 1px\`, \`border-top-style: solid\`, \`border-top-color: red\`, …
|
|
215
|
+
for all four sides. A shorthand also RESETS what it omits, so \`border: none\` clears a width a previous
|
|
216
|
+
definition set.
|
|
217
|
+
- Comma-separated layers are kept per longhand: \`transition: opacity 200ms, transform 300ms\` →
|
|
218
|
+
\`transition-property: opacity, transform\` + \`transition-duration: 200ms, 300ms\`.
|
|
219
|
+
- In a **patch**, a shorthand replaces the longhands it controls (\`padding: 8px\` overwrites a previous
|
|
220
|
+
\`padding-left\`), and \`"border": null\` removes all twelve border longhands.
|
|
221
|
+
- **Flex layout is not a \`flex\` value**: set \`display: flex\` **plus** \`flex-direction\`, \`align-items\`,
|
|
222
|
+
\`justify-content\` as separate properties.
|
|
213
223
|
- CSS is grouped by breakpoint: \`desktop\`, \`tablet\`, \`mobile\`.
|
|
214
224
|
- Reference a style variable in CSS as \`var(--name)\`; a schema variable in a prop as \`{{name}}\`.
|
|
215
225
|
- \`element.style.base\` is a **list** of definition refs; other slots go under \`element.style.slots\`.
|
|
@@ -280,7 +280,7 @@ var pageRefOfElement = (schema, el) => {
|
|
|
280
280
|
/** Total number of descendant elements under a subtree (excluding the root). */
|
|
281
281
|
var descendantCount = (schema, rootId) => descendantIds(schema, rootId).length;
|
|
282
282
|
var emptySpaceMessage = "Space data not available";
|
|
283
|
-
var unauthorizedSpaceMessage = "This
|
|
283
|
+
var unauthorizedSpaceMessage = "This connection has no space attached (a guest or widgets-only grant, or a token that carries no space), so NOTHING in a space can be read or edited — every other space tool will fail the same way, do not retry them. Use plitzi_render instead: it builds a self-contained widget offline, with no space, backend or account (read plitzi://render/guide). To edit a real space, the user must reconnect the integration and grant access to one.";
|
|
284
284
|
var generateObjectId = () => {
|
|
285
285
|
return `${Math.floor(Date.now() / 1e3).toString(16).padStart(8, "0")}${Array.from({ length: 16 }, () => Math.floor(Math.random() * 16).toString(16)).join("")}`;
|
|
286
286
|
};
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cssProperties } from "../catalogs/cssCatalog.js";
|
|
1
|
+
import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
|
|
2
2
|
import { buildTypeRegistry } from "../catalogs/registry.js";
|
|
3
3
|
import "../helpers/uris.js";
|
|
4
4
|
import { guideText } from "../helpers/guide.js";
|
|
@@ -9,7 +9,10 @@ import { envelope } from "./envelope.js";
|
|
|
9
9
|
var readCoreResource = (space, uri) => {
|
|
10
10
|
if (uri === "plitzi://guide") return envelope(guideText);
|
|
11
11
|
if (uri === "plitzi://types") return envelope(buildTypeRegistry(space.schema, space.catalog));
|
|
12
|
-
if (uri === "plitzi://css-properties") return envelope(
|
|
12
|
+
if (uri === "plitzi://css-properties") return envelope({
|
|
13
|
+
properties: cssProperties,
|
|
14
|
+
shorthands: cssShorthands
|
|
15
|
+
});
|
|
13
16
|
};
|
|
14
17
|
//#endregion
|
|
15
18
|
export { readCoreResource };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cssProperties } from "../catalogs/cssCatalog.js";
|
|
1
|
+
import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
|
|
2
2
|
import { buildDataSourceCatalog, buildInteractionCatalog } from "../catalogs/observed.js";
|
|
3
3
|
import { buildTypeRegistry } from "../catalogs/registry.js";
|
|
4
4
|
import { guideQuickstart } from "../helpers/guide.js";
|
|
@@ -16,6 +16,7 @@ var readPrimerResource = (space, env, uri) => {
|
|
|
16
16
|
guide: guideQuickstart,
|
|
17
17
|
types: buildTypeRegistry(space.schema, space.catalog),
|
|
18
18
|
cssProperties,
|
|
19
|
+
cssShorthands,
|
|
19
20
|
pages: pageSummariesToAI(space.schema),
|
|
20
21
|
folders: foldersToAI(space.schema),
|
|
21
22
|
definitions: definitionRefs(space.style),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cssProperties } from "../catalogs/cssCatalog.js";
|
|
1
|
+
import { cssProperties, cssShorthands } from "../catalogs/cssCatalog/index.js";
|
|
2
2
|
import { guideText } from "../helpers/guide.js";
|
|
3
3
|
import { resourceErrorMessage } from "./canonical.js";
|
|
4
4
|
import { envelope, jsonContents } from "./envelope.js";
|
|
@@ -36,9 +36,12 @@ var registerResources = (server, getSpace, env, log) => {
|
|
|
36
36
|
text: guideText
|
|
37
37
|
}] })));
|
|
38
38
|
server.registerResource("CSS properties", "plitzi://css-properties", {
|
|
39
|
-
description: "Valid kebab-case CSS property keys",
|
|
39
|
+
description: "Valid kebab-case CSS property keys, plus the shorthands that are accepted and auto-expanded",
|
|
40
40
|
mimeType: "application/json"
|
|
41
|
-
}, () => emitStatic("plitzi://css-properties", () => jsonContents("plitzi://css-properties", envelope(
|
|
41
|
+
}, () => emitStatic("plitzi://css-properties", () => jsonContents("plitzi://css-properties", envelope({
|
|
42
|
+
properties: cssProperties,
|
|
43
|
+
shorthands: cssShorthands
|
|
44
|
+
}))));
|
|
42
45
|
registerRenderResources(server, log);
|
|
43
46
|
const fixed = [
|
|
44
47
|
[
|
|
@@ -68,7 +68,21 @@ Styling is separate from structure: declare a class, then attach it by ref.
|
|
|
68
68
|
- CSS properties in **kebab-case** (\`background-color\`, \`font-size\`, \`border-radius\`), values as plain strings.
|
|
69
69
|
- Attach to an element via \`style: { "base": ["card"] }\`. Stack classes: \`"base": ["card", "shadow"]\`.
|
|
70
70
|
- One \`ref\` can name both an element and its class (as above) — they live in different namespaces.
|
|
71
|
-
- Lay containers out with flexbox
|
|
71
|
+
- Lay containers out with flexbox or grid — pick the direction on purpose, see **Fit the panel** below.
|
|
72
|
+
- **You are not styling from zero.** Each type lands on the page with CSS you did not write, and it is the usual
|
|
73
|
+
reason a widget does not look like the definitions say. The SDK stylesheet resets almost nothing (\`box-sizing\`,
|
|
74
|
+
\`border: 0 solid\`, \`body\` margin), so anything you leave unset comes from one of two places:
|
|
75
|
+
- The per-type rule the SDK ships — the one that changes layouts is \`container\`, which carries
|
|
76
|
+
\`min-width: 50px; min-height: 50px\`. A rail, a divider, a dot, a spacer or a narrow cell will NOT go below
|
|
77
|
+
50px until you say \`"min-width": "0"\` (and/or \`"min-height": "0"\`) on it: a 2px timeline line renders 50px
|
|
78
|
+
wide otherwise. Any flex child that must be allowed to shrink needs it too.
|
|
79
|
+
- The BROWSER's own defaults for the rest: \`heading\` keeps its UA font-size and ~0.67em top/bottom margins,
|
|
80
|
+
\`paragraph\` ~1em margins, \`list\` a 40px \`padding-left\`, \`button\` its native chrome, \`image\` its intrinsic
|
|
81
|
+
size, \`link\` its own colour and underline. In a compact widget set these explicitly — usually
|
|
82
|
+
\`"margin-top": "0"\`, \`"margin-bottom": "0"\` — and space things with the parent's \`gap\` instead.
|
|
83
|
+
- Borders start at \`0 solid\`, so \`border-color\` alone paints nothing: give \`border-width\` (and the colour).
|
|
84
|
+
- The \`defaultStyle\` a type reports in the EDITING catalog (\`plitzi://types\`) is authoring metadata for the
|
|
85
|
+
builder — it does not paint here, so do not count on it in a widget.
|
|
72
86
|
- **Mind the intrinsic display.** Some types start non-block: \`text\` is \`display: inline\`, so to stack or size it,
|
|
73
87
|
wrap it in a \`container\` (or set \`display: block\`). \`heading\` and \`paragraph\` are already block.
|
|
74
88
|
- **Use atomic longhands.** \`padding\`, \`margin\`, \`border\`, \`border-radius\` are fine (they expand cleanly), but
|
|
@@ -80,6 +94,62 @@ Styling is separate from structure: declare a class, then attach it by ref.
|
|
|
80
94
|
\`{ "desktop": { "background-color": "#3b82f6" }, "states": { "hover": { "desktop": { "background-color": "#2563eb" } } } }\`
|
|
81
95
|
(\`hover\`, \`active\`, \`focus\`).
|
|
82
96
|
|
|
97
|
+
## Fit the panel — go wide, stay short
|
|
98
|
+
|
|
99
|
+
The widget renders in a **side panel** (Claude Desktop, ChatGPT, the Plitzi builder), so it gets a usable width but
|
|
100
|
+
very little height: everything past the first screenful costs the user a scroll. Height is the scarce resource —
|
|
101
|
+
spend width instead.
|
|
102
|
+
|
|
103
|
+
Plain containers are blocks, so doing nothing stacks children **vertically** and produces exactly the tall,
|
|
104
|
+
half-empty widget to avoid. Choose the axis every time:
|
|
105
|
+
|
|
106
|
+
- **Peers side by side** — metrics, plans, options, a comparison, an image next to its text: a row, wrapping when
|
|
107
|
+
it runs out of width, children sharing it (no fixed widths).
|
|
108
|
+
\`\`\`json
|
|
109
|
+
{ "type": "upsertDefinition", "ref": "row", "desktop": { "display": "flex", "flex-direction": "row", "flex-wrap": "wrap", "gap": "12px", "align-items": "stretch" } }
|
|
110
|
+
{ "type": "upsertDefinition", "ref": "col", "desktop": { "flex-grow": "1", "flex-basis": "0%", "min-width": "150px" } }
|
|
111
|
+
\`\`\`
|
|
112
|
+
\`flex-grow: 1\` + \`flex-basis: 0%\` splits the row evenly; \`min-width\` is the wrap threshold — under it the item
|
|
113
|
+
drops to the next line by itself, so a narrow panel degrades gracefully with no breakpoints.
|
|
114
|
+
- **Many uniform items** — cards, tiles, a gallery: one grid line does it all.
|
|
115
|
+
\`\`\`json
|
|
116
|
+
{ "type": "upsertDefinition", "ref": "grid", "desktop": { "display": "grid", "grid-template-columns": "repeat(auto-fit, minmax(160px, 1fr))", "gap": "12px" } }
|
|
117
|
+
\`\`\`
|
|
118
|
+
- **Label + value pairs** stay on one line (\`display: flex\`, \`justify-content: space-between\`) instead of two.
|
|
119
|
+
- **Vertical is right** for reading order: a heading over its paragraph, a form, a step list, long prose.
|
|
120
|
+
- Force a stack on tiny screens with the \`mobile\` block: \`"mobile": { "flex-direction": "column" }\`.
|
|
121
|
+
|
|
122
|
+
Keep it compact, and the numbers low: \`padding\` 12–16px (24+ only on a single hero card), \`gap\` 8–12px,
|
|
123
|
+
\`font-size\` 13–15px for body and 16–20px for headings. Let the outer container **fill** the panel — no \`width\` on
|
|
124
|
+
it — and reach for \`max-width\` only to stop one lone card from stretching across the whole panel.
|
|
125
|
+
|
|
126
|
+
## Match the host theme — never hardcode a light palette
|
|
127
|
+
|
|
128
|
+
The widget is embedded in the host's own UI (Claude Desktop, ChatGPT, the builder), and that UI **may be in dark
|
|
129
|
+
mode**. A widget painted with fixed light colours is the most common way to ship something unusable: a white card
|
|
130
|
+
in a dark chat glares, and — worse — text left at a dark default disappears against the host's dark background.
|
|
131
|
+
|
|
132
|
+
The host publishes its palette as CSS variables on the page, so use them for every colour, with a
|
|
133
|
+
\`light-dark(<light>, <dark>)\` fallback for hosts that send none:
|
|
134
|
+
|
|
135
|
+
\`\`\`json
|
|
136
|
+
{ "type": "upsertDefinition", "ref": "card", "desktop": {
|
|
137
|
+
"background-color": "var(--color-background-secondary, light-dark(#ffffff, #1f2430))",
|
|
138
|
+
"color": "var(--color-text-primary, light-dark(#0f172a, #e8eaed))",
|
|
139
|
+
"border-width": "1px", "border-style": "solid",
|
|
140
|
+
"border-color": "var(--color-border-primary, light-dark(#e2e8f0, #333a48))"
|
|
141
|
+
} }
|
|
142
|
+
\`\`\`
|
|
143
|
+
|
|
144
|
+
- Surfaces: \`--color-background-primary\` (the page), \`--color-background-secondary\` / \`--color-background-tertiary\`
|
|
145
|
+
(cards, raised areas). Text: \`--color-text-primary\`, \`--color-text-secondary\`, \`--color-text-tertiary\` (muted).
|
|
146
|
+
Borders: \`--color-border-primary\` / \`--color-border-secondary\`. Status pairs: \`--color-background-danger\` |
|
|
147
|
+
\`success\` | \`warning\` | \`info\` with the matching \`--color-text-…\`. Also \`--font-sans\`, \`--border-radius-md\` /
|
|
148
|
+
\`lg\` / \`full\`, \`--shadow-sm\` / \`md\`.
|
|
149
|
+
- **Set \`color\` wherever you set \`background-color\`** — the pair is what stays legible, either one alone is a
|
|
150
|
+
gamble. Same rule for a brand accent: a CTA on your own blue must state its own text colour (\`#ffffff\`).
|
|
151
|
+
- Prefer a border to a drop shadow for separation: shadows all but vanish on a dark surface.
|
|
152
|
+
|
|
83
153
|
## Element types (type → what to set)
|
|
84
154
|
|
|
85
155
|
| type | renders | set |
|
|
@@ -103,21 +173,31 @@ that is not here (lists, tabs, dialogs, forms, icons…).
|
|
|
103
173
|
\`image\`/\`video\` \`src\` accepts any \`https\` URL, or a \`data:\`/\`blob:\` URI for a fully self-contained graphic
|
|
104
174
|
(e.g. an inline SVG icon or a base64 image) — both render with no extra setup.
|
|
105
175
|
|
|
106
|
-
## Full worked example —
|
|
176
|
+
## Full worked example — two plans side by side
|
|
177
|
+
|
|
178
|
+
The cards sit in a wrapping row and split it evenly, so the widget uses the panel's width and stays short; on a
|
|
179
|
+
narrow panel \`min-width\` drops the second card under the first on its own.
|
|
107
180
|
|
|
108
181
|
\`\`\`json
|
|
109
182
|
{
|
|
110
183
|
"operations": [
|
|
111
|
-
{ "type": "upsertDefinition", "ref": "
|
|
112
|
-
{ "type": "upsertDefinition", "ref": "
|
|
113
|
-
{ "type": "upsertDefinition", "ref": "
|
|
184
|
+
{ "type": "upsertDefinition", "ref": "plans", "desktop": { "display": "flex", "flex-direction": "row", "flex-wrap": "wrap", "gap": "12px", "align-items": "stretch" } },
|
|
185
|
+
{ "type": "upsertDefinition", "ref": "card", "desktop": { "display": "flex", "flex-direction": "column", "gap": "6px", "flex-grow": "1", "flex-basis": "0%", "min-width": "150px", "padding": "16px", "background-color": "var(--color-background-secondary, light-dark(#ffffff, #1f2430))", "color": "var(--color-text-primary, light-dark(#0f172a, #e8eaed))", "border-width": "1px", "border-style": "solid", "border-color": "var(--color-border-primary, light-dark(#e2e8f0, #333a48))", "border-radius": "var(--border-radius-lg, 12px)", "text-align": "center" } },
|
|
186
|
+
{ "type": "upsertDefinition", "ref": "price", "desktop": { "font-size": "28px", "font-weight": "800", "color": "#3b82f6" } },
|
|
187
|
+
{ "type": "upsertDefinition", "ref": "cta", "desktop": { "background-color": "#3b82f6", "color": "#ffffff", "padding": "10px 16px", "border-radius": "8px", "font-weight": "600" }, "states": { "hover": { "desktop": { "background-color": "#2563eb" } } } },
|
|
114
188
|
{ "type": "upsertElement", "pageRef": "render", "element": {
|
|
115
|
-
"ref": "
|
|
189
|
+
"ref": "plans", "type": "container", "style": { "base": ["plans"] },
|
|
116
190
|
"children": [
|
|
117
|
-
{ "ref": "
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
191
|
+
{ "ref": "free", "type": "container", "style": { "base": ["card"] }, "children": [
|
|
192
|
+
{ "ref": "free-plan", "type": "heading", "subType": "h3", "props": { "content": "Starter" } },
|
|
193
|
+
{ "ref": "free-amount", "type": "text", "props": { "content": "$0" }, "style": { "base": ["price"] } },
|
|
194
|
+
{ "ref": "free-buy", "type": "button", "props": { "content": "Start free" }, "style": { "base": ["cta"] } }
|
|
195
|
+
] },
|
|
196
|
+
{ "ref": "pro", "type": "container", "style": { "base": ["card"] }, "children": [
|
|
197
|
+
{ "ref": "pro-plan", "type": "heading", "subType": "h3", "props": { "content": "Pro" } },
|
|
198
|
+
{ "ref": "pro-amount", "type": "text", "props": { "content": "$29/mo" }, "style": { "base": ["price"] } },
|
|
199
|
+
{ "ref": "pro-buy", "type": "button", "props": { "content": "Start free trial" }, "style": { "base": ["cta"] } }
|
|
200
|
+
] }
|
|
121
201
|
]
|
|
122
202
|
} }
|
|
123
203
|
]
|
|
@@ -43,7 +43,7 @@ var createMcpServer = ({ adapters, getSpaceId, preview, screenshot, logger }) =>
|
|
|
43
43
|
const getSpace = () => spacePromise ??= loadSpace();
|
|
44
44
|
const server = new McpServer({
|
|
45
45
|
name: "plitzi-mcp",
|
|
46
|
-
version: "0.32.
|
|
46
|
+
version: "0.32.17"
|
|
47
47
|
}, { instructions: serverInstructions });
|
|
48
48
|
registerResources(server, getSpace, MCP_ENV, log);
|
|
49
49
|
registerApps(server);
|
|
@@ -16,13 +16,14 @@ 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;
|
|
18
18
|
const ownerRef = ensureIdRef(space, found.el);
|
|
19
|
-
const
|
|
19
|
+
const nodes = (op.flowId && !op.nodes[0].id ? [{
|
|
20
20
|
...op.nodes[0],
|
|
21
21
|
id: op.flowId
|
|
22
22
|
}, ...op.nodes.slice(1)] : op.nodes).map((node) => node.elementId === void 0 || node.nodeType === "utility" ? node : {
|
|
23
23
|
...node,
|
|
24
24
|
elementId: resolveTargetRef(space, node.elementId)
|
|
25
|
-
})
|
|
25
|
+
});
|
|
26
|
+
const { flowId, record } = materializeFlow(nodes, ownerRef);
|
|
26
27
|
const interactions = found.el.definition.interactions ?? {};
|
|
27
28
|
const targetFlow = op.flowId ?? flowId;
|
|
28
29
|
let replaced = false;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
//#region src/modules/mcp/tools/operations/style/shared.ts
|
|
3
|
-
var cssMap = z.record(z.string(), z.union([z.string(), z.number()])).describe("kebab-case CSS
|
|
3
|
+
var cssMap = z.record(z.string(), z.union([z.string(), z.number()])).describe("Plain kebab-case CSS. Shorthands are welcome — `border: 1px solid red`, `padding: 8px 16px`, `font: bold 16px/1.5 Arial`, `transition: opacity 200ms ease` — and are stored as their longhands so a breakpoint/state/variant can override each property on its own. Use var(--name) for style tokens and {{name}} for schema vars.");
|
|
4
4
|
var displayModeCss = z.object({
|
|
5
5
|
desktop: cssMap.optional(),
|
|
6
6
|
tablet: cssMap.optional(),
|
|
@@ -14,7 +14,7 @@ var cssPatchMap = z.record(z.string(), z.union([
|
|
|
14
14
|
z.string(),
|
|
15
15
|
z.number(),
|
|
16
16
|
z.null()
|
|
17
|
-
])).describe("kebab-case CSS
|
|
17
|
+
])).describe("Plain kebab-case CSS merged onto the existing declarations; shorthands are accepted and expanded, so `padding: 8px` replaces all four sides. A value of null removes the property — null on a shorthand (`border: null`) removes every longhand it controls.");
|
|
18
18
|
var displayModeCssPatch = z.object({
|
|
19
19
|
desktop: cssPatchMap.optional(),
|
|
20
20
|
tablet: cssPatchMap.optional(),
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { expandShorthand } from "../../../catalogs/cssCatalog.js";
|
|
1
|
+
import { expandShorthand, expandShorthandPatch } from "../../../catalogs/cssCatalog/index.js";
|
|
2
2
|
import { defUri, defsUri, globalUri, globalsUri, idUri, idsUri, styleVarUri, styleVarsUri } from "../../../helpers/uris.js";
|
|
3
3
|
import { fail } from "../../../helpers/opResult.js";
|
|
4
4
|
import processSelector from "@plitzi/sdk-style/helpers/processSelector";
|
|
@@ -65,7 +65,7 @@ var writeStyleItem = (style, ref, base, slots, itemType, componentType) => {
|
|
|
65
65
|
};
|
|
66
66
|
var mergeCss = (base, patch) => {
|
|
67
67
|
const merged = { ...base };
|
|
68
|
-
for (const [key, value] of Object.entries(patch ?? {})) if (value === null) Reflect.deleteProperty(merged, key);
|
|
68
|
+
for (const [key, value] of Object.entries(expandShorthandPatch(patch ?? {}))) if (value === null) Reflect.deleteProperty(merged, key);
|
|
69
69
|
else merged[key] = value;
|
|
70
70
|
return Object.keys(merged).length > 0 ? merged : void 0;
|
|
71
71
|
};
|
|
@@ -105,7 +105,7 @@ var toRenderResult = (res) => {
|
|
|
105
105
|
var renderTool = defineTool({
|
|
106
106
|
name: "plitzi_render",
|
|
107
107
|
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 reusable classes with upsertDefinition { ref, desktop:{ …CSS props in kebab-case… } }, then attach via the element style:{ base:[\"<class ref>\"] }. Lay containers out with flex/grid.\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.\nReturns a compact summary (the widget is shown to the user); on failure it returns teachable errors (path + hint) — read them and retry.",
|
|
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 reusable classes with upsertDefinition { ref, desktop:{ …CSS props in kebab-case… } }, then attach via the element style:{ base:[\"<class ref>\"] }. Lay containers out with flex/grid.\n2b. LAYOUT — it renders in a side panel, so width is free and HEIGHT is scarce. Plain containers stack children vertically, which is the tall half-empty default to avoid: put peers (metrics, plans, options, image + text) in a wrapping row — display:flex, flex-direction:row, flex-wrap:wrap, children flex-grow:\"1\" + flex-basis:\"0%\" + min-width — or a grid with grid-template-columns:\"repeat(auto-fit, minmax(160px, 1fr))\". Keep padding 12-16px and gap 8-12px, and let the outer container fill the panel. Stack only what reads in order (heading over paragraph, forms, steps, prose). Watch the SDK defaults: every container has min-width/min-height 50px (set them to \"0\" for rails, dividers, dots and any flex child that must shrink), and heading/paragraph keep the browser's own margins (zero them and space with the parent's 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.\nReturns a compact summary (the widget is shown to the user); on failure it returns teachable errors (path + hint) — read them and retry.",
|
|
109
109
|
inputShape: renderShape,
|
|
110
110
|
access: "read",
|
|
111
111
|
spaceless: true,
|
|
@@ -83,10 +83,7 @@ var collectTouched = (ops) => {
|
|
|
83
83
|
touched.globalStyles.add(op.componentType);
|
|
84
84
|
break;
|
|
85
85
|
case "upsertIdStyle":
|
|
86
|
-
case "patchIdStyle":
|
|
87
|
-
touched.idStyles.add(op.targetId);
|
|
88
|
-
break;
|
|
89
|
-
default: break;
|
|
86
|
+
case "patchIdStyle": touched.idStyles.add(op.targetId);
|
|
90
87
|
}
|
|
91
88
|
return touched;
|
|
92
89
|
};
|