webcake-landing-mcp 1.0.31 → 1.0.32
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/changelog.json
CHANGED
|
@@ -1,4 +1,11 @@
|
|
|
1
1
|
[
|
|
2
|
+
{
|
|
3
|
+
"v": "1.0.32",
|
|
4
|
+
"d": "08/06/2026",
|
|
5
|
+
"type": "Fixed",
|
|
6
|
+
"en": "get_element for select now marks specials.field_placeholder as required (the published renderer crashes without it), the element seed now emits a…",
|
|
7
|
+
"vi": "get_element cho select nay đánh dấu specials.field_placeholder là bắt buộc (renderer đã xuất bản sẽ crash nếu thiếu trường này), seed phần tử nay…"
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"v": "1.0.31",
|
|
4
11
|
"d": "08/06/2026",
|
|
@@ -33,12 +40,5 @@
|
|
|
33
40
|
"type": "Fixed",
|
|
34
41
|
"en": "The HTTP server's GET / route now serves the full HTML guide page to social and search crawlers (Facebook, Zalo, Twitter/X, LinkedIn, Slack,…",
|
|
35
42
|
"vi": "Route GET / của HTTP server nay phục vụ trang hướng dẫn HTML đầy đủ cho các crawler mạng xã hội và công cụ tìm kiếm (Facebook, Zalo, Twitter/X,…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.0.26",
|
|
39
|
-
"d": "07/06/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"en": "The HTTP server now serves a pre-rendered 1200×630 PNG social card at GET /og.png; the guide page's og:image and twitter:image meta tags now point…",
|
|
42
|
-
"vi": "HTTP server nay phục vụ ảnh social card PNG được render sẵn 1200×630 tại GET /og.png; các meta tag og:image và twitter:image của trang hướng dẫn nay…"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -109,19 +109,40 @@ export const FORM = [
|
|
|
109
109
|
useWhen: "Pick one from a list.",
|
|
110
110
|
keySpecials: {
|
|
111
111
|
field_name: "REQUIRED unique data key.",
|
|
112
|
-
field_placeholder: "placeholder/
|
|
112
|
+
field_placeholder: "REQUIRED — the disabled first-option label (the placeholder/prompt). The key is `field_placeholder`, NOT `placeholder`. The select renderer crashes if this is missing.",
|
|
113
113
|
default_value: "string (option id) to pre-select; 'default-none' = no pre-selection.",
|
|
114
114
|
defaultVariationId: "string — default product variation id registered on the parent form regardless of selection.",
|
|
115
115
|
defaultVariationQuantity: "number — quantity registered with defaultVariationId.",
|
|
116
116
|
ignoreOnHidden: "boolean — when CSS-hidden, remove this element's variations from the form total.",
|
|
117
117
|
isConnectSurvey: "boolean — link to a survey for conditional show/hide.",
|
|
118
118
|
connectedSurvey: "string — id of the connected survey.",
|
|
119
|
-
options: "array of
|
|
119
|
+
options: "array of option objects. The renderer builds each <option> from `id` and `name` ONLY — MINIMUM shape is {id, name} where name is BOTH the visible text and the submitted value. Do NOT use the HTML-style {label, value} — those keys are ignored and the option renders blank. Rich commerce options may also carry value, variations:[{id,quantity,price}], attrOnly/prodId/attrName/attrVal/attrs, quantityOnly/quantityProd/quantityValue, tags:[], toggleEvent, events_option:[] (show/hide, collapse, price/discount/shipping). See docs/element-specials-reference.md for the full option schema.",
|
|
120
120
|
},
|
|
121
121
|
seed: (el) => {
|
|
122
122
|
seedPosition(el);
|
|
123
123
|
setBox(el, 150, 36);
|
|
124
124
|
el.specials.field_name = `select_${el.id}`;
|
|
125
|
+
el.specials.field_placeholder = "Chọn...";
|
|
126
|
+
el.specials.default_value = "default-none";
|
|
127
|
+
},
|
|
128
|
+
example: {
|
|
129
|
+
id: "sel_attend", type: "select",
|
|
130
|
+
properties: { name: "Select", movable: true, sync: true },
|
|
131
|
+
responsive: {
|
|
132
|
+
desktop: { config: {}, styles: { top: 0, left: 0, width: 300, height: 44 } },
|
|
133
|
+
mobile: { config: {}, styles: { top: 0, left: 0, width: 280, height: 44 } },
|
|
134
|
+
},
|
|
135
|
+
// options use {id, name} — NOT {label, value}. field_placeholder is required.
|
|
136
|
+
specials: {
|
|
137
|
+
field_name: "attendance",
|
|
138
|
+
field_placeholder: "Bạn có tham dự không?",
|
|
139
|
+
default_value: "default-none",
|
|
140
|
+
options: [
|
|
141
|
+
{ id: "opt_yes", name: "Tôi sẽ tham dự" },
|
|
142
|
+
{ id: "opt_no", name: "Rất tiếc, tôi không thể đến" },
|
|
143
|
+
],
|
|
144
|
+
},
|
|
145
|
+
runtime: {}, events: [],
|
|
125
146
|
},
|
|
126
147
|
},
|
|
127
148
|
{
|
|
@@ -147,7 +168,7 @@ export const FORM = [
|
|
|
147
168
|
ignoreOnHidden: "boolean — when CSS-hidden, exclude this element's variations from the form total.",
|
|
148
169
|
isConnectSurvey: "boolean — link to a survey for conditional show/hide.",
|
|
149
170
|
connectedSurvey: "string — id of the connected survey.",
|
|
150
|
-
options: "array of
|
|
171
|
+
options: "array of option objects, MINIMUM shape {id, name} (name = visible text; the renderer crashes on options that lack a string `name`). Do NOT use {label, value}. Same rich shape as select — additionally supports the tcb_auto_banking event type in events_option (sets the storecake_tcb payment gateway). See docs/element-specials-reference.md for the full option schema.",
|
|
151
172
|
},
|
|
152
173
|
seed: (el) => {
|
|
153
174
|
seedPosition(el);
|
|
@@ -169,7 +190,7 @@ export const FORM = [
|
|
|
169
190
|
ignoreOnHidden: "boolean — when hidden, exclude this element's variations from the form total.",
|
|
170
191
|
isConnectSurvey: "boolean — link to a survey for conditional show/hide.",
|
|
171
192
|
connectedSurvey: "string — id of the connected survey.",
|
|
172
|
-
options: "array of
|
|
193
|
+
options: "array of option objects, MINIMUM shape {id, name} (name = visible text; the renderer crashes on options that lack a string `name`). Do NOT use {label, value}. Same rich shape as select — events_option additionally supports 9 payment-gateway event types (tcb_auto_banking, xendit_banking, onepay_banking, mercadopago_banking, vnpay_banking, paymongo_banking, stripe_banking, paypal_banking, momopay_banking) that set the form's payment provider. See docs/element-specials-reference.md for the full option schema.",
|
|
173
194
|
},
|
|
174
195
|
seed: (el) => {
|
|
175
196
|
seedPosition(el);
|
|
@@ -26,6 +26,11 @@ const ELEMENT_TARGET_ACTIONS = new Set([
|
|
|
26
26
|
"show_hide_element", "change_tab", "collapse",
|
|
27
27
|
]);
|
|
28
28
|
const TOP_LEVEL_TYPES = new Set(["section", "dynamic_page", "popup"]);
|
|
29
|
+
// Fields whose renderer builds each <option> from `option.name`. A missing name
|
|
30
|
+
// crashes the published renderer (radio/checkbox-group call .replace/.normalize on
|
|
31
|
+
// it; select shows a blank option). The correct option shape is {id, name} — NOT
|
|
32
|
+
// the HTML-style {label, value}.
|
|
33
|
+
const OPTION_NAME_FIELDS = new Set(["select", "radio", "checkbox-group"]);
|
|
29
34
|
// Fixed canvas reference (matches vocab CANVAS) used for the layout/bounds check.
|
|
30
35
|
const CANVAS_DESKTOP = 960;
|
|
31
36
|
const CANVAS_MOBILE = 420;
|
|
@@ -108,10 +113,36 @@ export function validatePage(input) {
|
|
|
108
113
|
}
|
|
109
114
|
// form fields need field_name
|
|
110
115
|
if (type && FIELD_TYPES.has(type)) {
|
|
111
|
-
const
|
|
116
|
+
const specials = node.specials;
|
|
117
|
+
const fn = specials?.field_name;
|
|
112
118
|
if (!fn || typeof fn !== "string" || fn.trim() === "") {
|
|
113
119
|
warnings.push(`${path} (${type}): form input should have a unique specials.field_name.`);
|
|
114
120
|
}
|
|
121
|
+
// `field_placeholder` is the ONLY placeholder key the renderer reads. A stray
|
|
122
|
+
// `placeholder` renders blank; a select with no field_placeholder crashes the
|
|
123
|
+
// published renderer (unescapeHTML(undefined)).
|
|
124
|
+
if (specials && typeof specials === "object") {
|
|
125
|
+
const hasFieldPlaceholder = typeof specials.field_placeholder === "string";
|
|
126
|
+
if (typeof specials.placeholder === "string" && !hasFieldPlaceholder) {
|
|
127
|
+
warnings.push(`${path} (${type}): uses specials.placeholder — the renderer reads specials.field_placeholder. Rename "placeholder" → "field_placeholder".`);
|
|
128
|
+
}
|
|
129
|
+
if (type === "select" && !hasFieldPlaceholder) {
|
|
130
|
+
errors.push(`${path} (select): needs a string specials.field_placeholder (the select renderer crashes without it).`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
// select/radio/checkbox-group render each option from option.name; a missing
|
|
134
|
+
// name crashes the renderer (radio/checkbox-group) or renders blank (select).
|
|
135
|
+
if (OPTION_NAME_FIELDS.has(type) && Array.isArray(specials?.options)) {
|
|
136
|
+
specials.options.forEach((opt, oi) => {
|
|
137
|
+
if (typeof opt?.name !== "string" || opt.name.trim() === "") {
|
|
138
|
+
const keys = opt && typeof opt === "object" ? Object.keys(opt) : [];
|
|
139
|
+
const hint = keys.includes("label") || keys.includes("value")
|
|
140
|
+
? ` Use {id, name} — not {label, value} (found keys: ${keys.join(", ")}).`
|
|
141
|
+
: "";
|
|
142
|
+
errors.push(`${path} (${type}): specials.options[${oi}] needs a non-empty string "name" (the visible option text).${hint}`);
|
|
143
|
+
}
|
|
144
|
+
});
|
|
145
|
+
}
|
|
115
146
|
}
|
|
116
147
|
// collect events
|
|
117
148
|
if (Array.isArray(node.events)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-landing-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.32",
|
|
4
4
|
"description": "MCP server exposing Webcake landing-page element schemas + AI usage hints, and persisting LLM-generated page sources to a Webcake backend.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|