webcake-storefront-mcp 1.16.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/builder/guide.js +20 -1
- package/dist/builder/page.js +103 -1
- package/dist/changelog.json +7 -7
- package/dist/tools/builder.js +24 -1
- package/package.json +1 -1
package/dist/builder/guide.js
CHANGED
|
@@ -36,7 +36,26 @@ children with a grid:
|
|
|
36
36
|
- each child config also has \`rowStart/rowEnd\` (1-based grid lines),
|
|
37
37
|
\`constraintX\` (['left'|'right'|'centerLeft']), \`constraintY\` (['top'|'bottom'|'centerTop']).
|
|
38
38
|
new_section does ALL of this for you: pass children and they are stacked one row each in
|
|
39
|
-
the centre column.
|
|
39
|
+
the centre column.
|
|
40
|
+
|
|
41
|
+
## Multi-column rows (cards side by side) — USE THIS, real pages are full of them
|
|
42
|
+
A plain vertical stack looks like a blog post, not a designed page. Feature cards,
|
|
43
|
+
category tiles, footer columns, a text+image hero — all are HORIZONTAL rows. Two ways:
|
|
44
|
+
- new_row(children=[colA, colB, colC]) → a container whose children sit SIDE BY SIDE in
|
|
45
|
+
equal columns. Place the returned node as a child of a section.
|
|
46
|
+
- Inside new_section, add \`layout:"row"\` to a CONTAINER child spec and its children become
|
|
47
|
+
columns: \`{ type:"container", layout:"row", children:[ {..}, {..}, {..} ] }\`.
|
|
48
|
+
Rows are RESPONSIVE automatically: finalizeForRender collapses them on small screens
|
|
49
|
+
(default tablet=2 columns, mobile=1) so cards never shrink to slivers — override with
|
|
50
|
+
\`collapse:{bp3:2,bp4:1}\`. Uneven columns via \`colWidths\` (e.g. a 2:1 text/image split:
|
|
51
|
+
\`colWidths:[{unit:'fr',value:2},{unit:'fr',value:1}]\`), spacing via \`columnGap\`/\`rowGap\`.
|
|
52
|
+
Each column is usually a \`container\` holding its own stacked children (image + title +
|
|
53
|
+
text). Example feature row:
|
|
54
|
+
\`new_row(children=[
|
|
55
|
+
{ type:"container", children:[ {type:"image",opts:{...}}, {type:"text",opts:{text:"Giao nhanh"}} ] },
|
|
56
|
+
{ type:"container", children:[ {type:"image",opts:{...}}, {type:"text",opts:{text:"Chất lượng"}} ] },
|
|
57
|
+
{ type:"container", children:[ {type:"image",opts:{...}}, {type:"text",opts:{text:"Bảo hành"}} ] },
|
|
58
|
+
])\`
|
|
40
59
|
|
|
41
60
|
## Where layout/style live: per-breakpoint keys (NOT \`runtime\`)
|
|
42
61
|
new_section / new_element emit a temporary \`runtime: { style, config }\`. That is a
|
package/dist/builder/page.js
CHANGED
|
@@ -84,6 +84,68 @@ export function stackChildren(container, children, opts = {}) {
|
|
|
84
84
|
container.children = children;
|
|
85
85
|
return container;
|
|
86
86
|
}
|
|
87
|
+
const ROW_PLACEHOLDER_ROW = () => ({ unit: "min/max", min: { unit: "px", absValue: 50 }, max: { unit: "max-c" } });
|
|
88
|
+
/** Default responsive collapse for a multi-column row: full columns on desktop/laptop,
|
|
89
|
+
* 2 columns on tablet, 1 column on mobile. `null` = keep the full column count. */
|
|
90
|
+
const ROW_COLLAPSE_DEFAULT = { bp1: null, bp2: null, bp3: 2, bp4: 1 };
|
|
91
|
+
/**
|
|
92
|
+
* Lay children out HORIZONTALLY — side by side in one grid row — the way real BuilderX
|
|
93
|
+
* pages build feature cards, category tiles, footer columns, 2-col hero, etc. The grid is
|
|
94
|
+
* `Nx1` with N equal `fr` columns and each child placed in its own column. Values go to
|
|
95
|
+
* `runtime` plus `__row`/`__cell` markers that finalizeForRender() reads to emit a
|
|
96
|
+
* RESPONSIVE grid per breakpoint (it auto-collapses to fewer columns on tablet/mobile so
|
|
97
|
+
* the row never renders as cramped slivers).
|
|
98
|
+
*/
|
|
99
|
+
export function rowChildren(container, children, opts = {}) {
|
|
100
|
+
const cols = children.length || 1;
|
|
101
|
+
const colWidths = opts.colWidths && opts.colWidths.length === cols
|
|
102
|
+
? opts.colWidths
|
|
103
|
+
: Array.from({ length: cols }, () => ({ unit: "fr", value: 1 }));
|
|
104
|
+
const collapse = { ...ROW_COLLAPSE_DEFAULT, ...(opts.collapse || {}) };
|
|
105
|
+
const columnGap = opts.columnGap ?? 24;
|
|
106
|
+
const rowGap = opts.rowGap ?? 24;
|
|
107
|
+
const meta = { cols, count: children.length, collapse, columnGap, rowGap };
|
|
108
|
+
container.runtime = container.runtime || {};
|
|
109
|
+
container.runtime.config = {
|
|
110
|
+
...(container.runtime.config || {}),
|
|
111
|
+
grid: `${cols}x1`,
|
|
112
|
+
columns: colWidths,
|
|
113
|
+
rows: [ROW_PLACEHOLDER_ROW()],
|
|
114
|
+
columnGap,
|
|
115
|
+
rowGap,
|
|
116
|
+
heightUnit: "auto",
|
|
117
|
+
__row: meta,
|
|
118
|
+
};
|
|
119
|
+
children.forEach((child, i) => {
|
|
120
|
+
child.runtime = child.runtime || {};
|
|
121
|
+
child.runtime.config = {
|
|
122
|
+
...(child.runtime.config || {}),
|
|
123
|
+
columnStart: i + 1,
|
|
124
|
+
columnEnd: i + 2,
|
|
125
|
+
rowStart: 1,
|
|
126
|
+
rowEnd: 2,
|
|
127
|
+
constraintX: (child.runtime.config && child.runtime.config.constraintX) || ["centerLeft"],
|
|
128
|
+
constraintY: (child.runtime.config && child.runtime.config.constraintY) || ["top"],
|
|
129
|
+
loaded: true,
|
|
130
|
+
__cell: { index: i, ...meta },
|
|
131
|
+
};
|
|
132
|
+
});
|
|
133
|
+
container.children = children;
|
|
134
|
+
return container;
|
|
135
|
+
}
|
|
136
|
+
/** Build a standalone multi-column ROW container from child specs (each laid side by side).
|
|
137
|
+
* Used by the new_row tool; inside new_section pass `layout:"row"` on a container spec. */
|
|
138
|
+
export function buildRow(childSpecs = [], opts = {}) {
|
|
139
|
+
const container = buildElement("container", opts.containerOpts || {});
|
|
140
|
+
const children = childSpecs.map((spec) => buildFromSpec(spec));
|
|
141
|
+
return rowChildren(container, children, opts);
|
|
142
|
+
}
|
|
143
|
+
/** How many columns a row shows at a given breakpoint (clamped to its real column count). */
|
|
144
|
+
function colsForBp(meta, bp) {
|
|
145
|
+
const c = meta.collapse ? meta.collapse[bp] : null;
|
|
146
|
+
const k = c == null ? meta.cols : Math.min(c, meta.cols);
|
|
147
|
+
return Math.max(1, k);
|
|
148
|
+
}
|
|
87
149
|
/**
|
|
88
150
|
* Build a ready-to-place section from a list of child specs.
|
|
89
151
|
* Each spec: { type, opts?, children? } where children is a nested array of specs.
|
|
@@ -118,7 +180,19 @@ function buildFromSpec(spec) {
|
|
|
118
180
|
const node = buildElement(spec.type, spec.opts || {});
|
|
119
181
|
if (Array.isArray(spec.children) && spec.children.length) {
|
|
120
182
|
const kids = spec.children.map((c) => buildFromSpec(c));
|
|
121
|
-
|
|
183
|
+
// `layout:"row"` lays the children out side by side (responsive); default is a
|
|
184
|
+
// top-to-bottom vertical stack. rowGap/columnGap/colWidths/collapse tune the layout.
|
|
185
|
+
if (spec.layout === "row") {
|
|
186
|
+
rowChildren(node, kids, {
|
|
187
|
+
columnGap: spec.columnGap,
|
|
188
|
+
rowGap: spec.rowGap,
|
|
189
|
+
colWidths: spec.colWidths,
|
|
190
|
+
collapse: spec.collapse,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
stackChildren(node, kids, { rowGap: spec.rowGap });
|
|
195
|
+
}
|
|
122
196
|
}
|
|
123
197
|
return node;
|
|
124
198
|
}
|
|
@@ -193,9 +267,14 @@ function expandNodeToBreakpoints(node) {
|
|
|
193
267
|
const baseStyle = rt.style || {};
|
|
194
268
|
const baseConfig = { ...(rt.config || {}), loaded: true };
|
|
195
269
|
const isSection = node.type === "section";
|
|
270
|
+
const rowMeta = baseConfig.__row; // this node is a multi-column row container
|
|
271
|
+
const cellMeta = baseConfig.__cell; // this node is a cell inside a row
|
|
196
272
|
for (const [bp, [minW]] of Object.entries(BREAKPOINTS)) {
|
|
197
273
|
const style = clone(baseStyle);
|
|
198
274
|
const config = clone(baseConfig);
|
|
275
|
+
// Internal build-time markers never get persisted.
|
|
276
|
+
delete config.__row;
|
|
277
|
+
delete config.__cell;
|
|
199
278
|
if (isSection) {
|
|
200
279
|
const g = genGridByBp(minW);
|
|
201
280
|
const sectionRows = baseConfig.rows && baseConfig.rows.length ? clone(baseConfig.rows) : clone(g.rows);
|
|
@@ -204,6 +283,29 @@ function expandNodeToBreakpoints(node) {
|
|
|
204
283
|
config.grid = `3x${sectionRows.length}`;
|
|
205
284
|
config.heightUnit = config.heightUnit || "auto";
|
|
206
285
|
}
|
|
286
|
+
// A multi-column row collapses to fewer columns on smaller screens so cards never
|
|
287
|
+
// shrink to slivers: recompute the grid + this container's columns per breakpoint.
|
|
288
|
+
if (rowMeta) {
|
|
289
|
+
const k = colsForBp(rowMeta, bp);
|
|
290
|
+
const nrows = Math.ceil(rowMeta.count / k);
|
|
291
|
+
config.columns =
|
|
292
|
+
k === rowMeta.cols && Array.isArray(baseConfig.columns) && baseConfig.columns.length === k
|
|
293
|
+
? clone(baseConfig.columns)
|
|
294
|
+
: Array.from({ length: k }, () => ({ unit: "fr", value: 1 }));
|
|
295
|
+
config.rows = Array.from({ length: nrows }, () => ROW_PLACEHOLDER_ROW());
|
|
296
|
+
config.grid = `${k}x${nrows}`;
|
|
297
|
+
config.heightUnit = config.heightUnit || "auto";
|
|
298
|
+
}
|
|
299
|
+
// A cell repositions itself into the collapsed grid (wraps to a new row as needed).
|
|
300
|
+
if (cellMeta) {
|
|
301
|
+
const k = colsForBp(cellMeta, bp);
|
|
302
|
+
const c = cellMeta.index % k;
|
|
303
|
+
const r = Math.floor(cellMeta.index / k);
|
|
304
|
+
config.columnStart = c + 1;
|
|
305
|
+
config.columnEnd = c + 2;
|
|
306
|
+
config.rowStart = r + 1;
|
|
307
|
+
config.rowEnd = r + 2;
|
|
308
|
+
}
|
|
207
309
|
node[bp] = { style, config };
|
|
208
310
|
}
|
|
209
311
|
delete node.runtime;
|
package/dist/changelog.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.17.0",
|
|
4
|
+
"d": "24/06/2026",
|
|
5
|
+
"type": "Added",
|
|
6
|
+
"en": "New new_row tool builds a multi-column responsive row container where child elements are placed side by side (left to right); the row auto-collapses…",
|
|
7
|
+
"vi": "Tool mới new_row tạo một container đa cột responsive với các phần tử con được đặt nằm ngang cạnh nhau (từ trái sang phải); hàng tự động thu gọn…"
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"v": "1.16.0",
|
|
4
11
|
"d": "24/06/2026",
|
|
@@ -33,12 +40,5 @@
|
|
|
33
40
|
"type": "Added",
|
|
34
41
|
"en": "New restore_file_version tool rolls a CMS file back to a saved version in one step: it reads the chosen version's content (via get_file_versions)…",
|
|
35
42
|
"vi": "Tool mới restore_file_version khôi phục CMS file về một phiên bản đã lưu chỉ trong một bước: đọc nội dung phiên bản được chọn (qua…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.12.1",
|
|
39
|
-
"d": "24/06/2026",
|
|
40
|
-
"type": "Fixed",
|
|
41
|
-
"en": "save_file_version and get_file_versions both sent the parameter as cms_file_id, but the backend reads file_id; versions were saved unlinked to any…",
|
|
42
|
-
"vi": "save_file_version và get_file_versions đều gửi tham số dưới key cms_file_id, trong khi backend đọc file_id; các phiên bản được lưu mà không liên kết…"
|
|
43
43
|
}
|
|
44
44
|
]
|
package/dist/tools/builder.js
CHANGED
|
@@ -3,12 +3,17 @@ import { BUILD_GUIDE } from "../builder/guide.js";
|
|
|
3
3
|
import { listElements, getElement, buildElement } from "../builder/catalog.js";
|
|
4
4
|
import { describeEventsCatalog } from "../builder/events.js";
|
|
5
5
|
import { describeBindingsCatalog } from "../builder/bindings.js";
|
|
6
|
-
import { buildSection, newPageSkeleton, validatePage, finalizeForRender, reassignIds, } from "../builder/page.js";
|
|
6
|
+
import { buildSection, buildRow, newPageSkeleton, validatePage, finalizeForRender, reassignIds, } from "../builder/page.js";
|
|
7
7
|
// Recursive spec for new_section / build_page children.
|
|
8
8
|
const elementSpec = z.object({
|
|
9
9
|
type: z.string().describe("Element type (see list_elements)"),
|
|
10
10
|
opts: z.record(z.any()).optional().describe("Factory opts: { style, config, specials, text, src, width, height, ... }"),
|
|
11
11
|
children: z.array(z.any()).optional().describe("Nested child specs (same shape) for container types"),
|
|
12
|
+
layout: z.enum(["row", "column"]).optional().describe("How this node's children are laid out: 'row' = side by side (responsive columns, auto-collapses on tablet/mobile), 'column' = stacked top-to-bottom (default). Use 'row' on a container to make feature cards / category tiles / footer columns / 2-col hero."),
|
|
13
|
+
rowGap: z.number().optional().describe("Vertical gap (px) between stacked children / wrapped rows"),
|
|
14
|
+
columnGap: z.number().optional().describe("Horizontal gap (px) between columns (layout:'row' only)"),
|
|
15
|
+
colWidths: z.array(z.any()).optional().describe("layout:'row' only — explicit per-column unit objects (length = #children), e.g. [{unit:'fr',value:2},{unit:'fr',value:1}] for a 2:1 split. Default: equal columns."),
|
|
16
|
+
collapse: z.record(z.number()).optional().describe("layout:'row' only — columns to show per breakpoint, e.g. {bp3:2,bp4:1}. Default: tablet 2, mobile 1."),
|
|
12
17
|
});
|
|
13
18
|
function parseSource(src) {
|
|
14
19
|
if (src == null)
|
|
@@ -48,6 +53,24 @@ Example children: [{ "type":"text", "opts":{"text":"Welcome","style":{"fontSize"
|
|
|
48
53
|
children: z.array(elementSpec).default([]).describe("Child element specs, stacked vertically in the section"),
|
|
49
54
|
section_opts: z.record(z.any()).optional().describe("Optional factory opts for the section node itself"),
|
|
50
55
|
}, ({ children, section_opts }) => handle(async () => buildSection(children || [], section_opts || {})));
|
|
56
|
+
server.tool("new_row", `Build a multi-column ROW container: children laid out SIDE BY SIDE (not stacked).
|
|
57
|
+
This is how real pages build feature cards, category tiles, footer columns, a 2-col hero, etc.
|
|
58
|
+
The row is RESPONSIVE — it auto-collapses to fewer columns on tablet/mobile (default tablet 2, mobile 1) so cards never become cramped slivers.
|
|
59
|
+
Place the returned node as a child inside a section (section children still stack vertically; nest a row for horizontal layout).
|
|
60
|
+
Example children: [{ "type":"container", "children":[{"type":"image","opts":{...}},{"type":"text","opts":{...}}] }, { ... }, { ... }]`, {
|
|
61
|
+
children: z.array(elementSpec).default([]).describe("Child specs, one per column (laid out left-to-right)"),
|
|
62
|
+
column_gap: z.number().optional().describe("Horizontal gap (px) between columns (default 24)"),
|
|
63
|
+
row_gap: z.number().optional().describe("Vertical gap (px) between wrapped rows (default 24)"),
|
|
64
|
+
col_widths: z.array(z.any()).optional().describe("Explicit per-column unit objects (length = #children), e.g. [{unit:'fr',value:2},{unit:'fr',value:1}]. Default: equal columns."),
|
|
65
|
+
collapse: z.record(z.number()).optional().describe("Columns to show per breakpoint, e.g. {bp3:2,bp4:1}. Default: tablet 2, mobile 1."),
|
|
66
|
+
container_opts: z.record(z.any()).optional().describe("Optional factory opts for the row container node itself (e.g. { style:{...} })"),
|
|
67
|
+
}, ({ children, column_gap, row_gap, col_widths, collapse, container_opts }) => handle(async () => buildRow(children || [], {
|
|
68
|
+
columnGap: column_gap,
|
|
69
|
+
rowGap: row_gap,
|
|
70
|
+
colWidths: col_widths,
|
|
71
|
+
collapse,
|
|
72
|
+
containerOpts: container_opts || {},
|
|
73
|
+
})));
|
|
51
74
|
server.tool("new_page_skeleton", "Return an empty but valid page source: { sections: [] }. Add sections built with new_section, then save with build_page.", {}, () => handle(async () => newPageSkeleton()));
|
|
52
75
|
server.tool("validate_page", "Validate a page source ({ sections: [...] }). Returns errors (block saving: duplicate/missing ids, missing types) and warnings (unknown types, form fields without field_name, dangling event targets) plus stats. Always run this before build_page.", {
|
|
53
76
|
source: z.any().describe("Page source object or JSON string"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-storefront-mcp",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.17.0",
|
|
4
4
|
"description": "MCP server for the WebCake/StoreCake storefront builder — page CRUD, page authoring, products, orders, and more",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/webcake-storefront-mcp",
|
|
6
6
|
"license": "MIT",
|