webcake-landing-mcp 1.0.45 → 1.0.46
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.46",
|
|
4
|
+
"d": "09/06/2026",
|
|
5
|
+
"type": "Changed",
|
|
6
|
+
"en": "validate_page now emits an advisory warning when no section, button, or text on the page carries a non-neutral color (white, black, or grey),…",
|
|
7
|
+
"vi": "validate_page nay phát cảnh báo tư vấn khi không có section, button hay text nào trên trang mang màu thực sự (không phải trắng, đen hoặc xám), giúp…"
|
|
8
|
+
},
|
|
2
9
|
{
|
|
3
10
|
"v": "1.0.45",
|
|
4
11
|
"d": "09/06/2026",
|
|
@@ -33,12 +40,5 @@
|
|
|
33
40
|
"type": "Changed",
|
|
34
41
|
"en": "get_generation_guide and server instructions now require the agent to write all page copy in the same language the user is chatting in, with full,…",
|
|
35
42
|
"vi": "get_generation_guide và hướng dẫn server nay yêu cầu agent viết toàn bộ nội dung trang bằng cùng ngôn ngữ người dùng đang nhắn tin, với đầy đủ dấu…"
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
"v": "1.0.40",
|
|
39
|
-
"d": "09/06/2026",
|
|
40
|
-
"type": "Added",
|
|
41
|
-
"en": "New ingest_html tool parses an HTML string into a compact reference AST (~2–5KB) that classifies sections by role (header, hero, features, form,…",
|
|
42
|
-
"vi": "Công cụ ingest_html mới phân tích cú pháp một chuỗi HTML thành AST tham chiếu thu gọn (~2–5KB) phân loại các section theo vai trò (header, hero,…"
|
|
43
43
|
}
|
|
44
44
|
]
|
|
@@ -84,7 +84,7 @@ PREMIUM CRAFT (what separates a polished page from an amateur one — apply to E
|
|
|
84
84
|
- CTA WEIGHT: the primary button is the heaviest thing on its band — accent background, bold label, generous padding (height ~46–52), the same radius used everywhere else.
|
|
85
85
|
|
|
86
86
|
SECTION BUILD HINTS (apply to whichever sections the chosen archetype uses)
|
|
87
|
-
- Each section is one visual band: height that comfortably holds its content (taller on mobile since things stack), background that contrasts its text.
|
|
87
|
+
- Each section is one visual band: height that comfortably holds its content (taller on mobile since things stack), and a background that contrasts its text. SET responsive.<bp>.styles.background ON EVERY SECTION (both breakpoints) — the section factory default has NO background, so a section you leave unset renders transparent/white and the whole page looks colorless. Pull each band's background from the locked PALETTE and alternate them (light / tinted / dark) so consecutive bands read as distinct, never a flat white wall.
|
|
88
88
|
- HEADER — logo/brand at the page's LEFT margin (left=80 desktop / 20 mobile), nav/CTA flush to the RIGHT margin (its left = canvas − margin − width, so its right edge lands on 880 desktop / 400 mobile). Use the SAME margin as every section below — do NOT center the logo or invent a new left; a header on a different axis than the bands under it is the #1 header defect. Put every header child on ONE shared vertical centerline: match top + height/2 across the logo, brand text, and CTA button so nothing sits higher/lower than the rest.
|
|
89
89
|
- HERO — always a clear H1, a short supporting line, and the primary CTA visible without scrolling.
|
|
90
90
|
- FEATURES / BENEFITS — a row of equal cards (icon + title + text) or a 2-column list. Center the row with the ROW math; on mobile shrink to one canvas width or stack.
|
|
@@ -126,12 +126,15 @@ WORKFLOW (recommended)
|
|
|
126
126
|
0. INTAKE (never skip — even for a quick/test page): ask the essentials above, WAIT for the answers, restate a short outline (sections + CTA + colors), and get the user's "yes" BEFORE any new_page_skeleton / create_page. Do not generate on the same turn as the request.
|
|
127
127
|
0b. LOCK THE DESIGN SYSTEM (after the customer confirms): commit the exact palette, type scale, spacing scale, and component specs (see DESIGN SYSTEM) — these are your tokens for every element below. Set settings.fontGeneral to the chosen font.
|
|
128
128
|
1. Call get_generation_guide (this) once, then new_page_skeleton for the top-level shape.
|
|
129
|
-
2.
|
|
130
|
-
3.
|
|
131
|
-
|
|
129
|
+
2. For each element type you'll use, call get_element to learn its specials & see an example.
|
|
130
|
+
3. Optionally call new_element to get a correct skeleton, then fill specials + coordinates.
|
|
131
|
+
3b. For every image the page needs (hero, product, about, feature, gallery), call search_images and put a returned URL into specials.src / gallery item.link. Use placehold.co ONLY when search_images returns ok:false.
|
|
132
|
+
4. Assemble { page, popup, settings, options, cartConfigs }.
|
|
133
|
+
5. Call validate_page and fix every error.
|
|
134
|
+
6. To save: call list_organizations, show the orgs to the user and ask which to use (default to is_default). Then create_page (dry_run first, then dry_run:false with the chosen organization_id).
|
|
132
135
|
|
|
133
136
|
EDITING an existing page
|
|
134
137
|
- list_pages → let the user pick (or take a page_id from a URL).
|
|
135
138
|
- get_page(page_id) → you get the live { page, popup, settings, ... }. Edit it surgically: change only the elements the user asked for (text/styles/specials/events); keep every other element, its id, and coordinates intact. Never regenerate the whole tree for a small change.
|
|
136
139
|
- To add an element: build it with new_element, give it a unique id, set top/left/width/height inside the right section's children.
|
|
137
|
-
- update_page(page_id, source)
|
|
140
|
+
- validate_page → update_page(page_id, source) (dry_run first, then dry_run:false).`;
|
|
@@ -9,9 +9,9 @@ RULES (follow for every request):
|
|
|
9
9
|
- INTAKE FIRST — do this EVERY time, even for a "quick"/"test" page. Do NOT jump straight to new_page_skeleton/create_page on the same turn as the request: ask the essentials, restate an outline, get a "yes", THEN build. Ask ONE short batch (3–6, with sensible defaults so the user answers fast) enough to understand the page's PURPOSE, name, look and layout: page purpose/goal, brand/page name, what they sell + price (sales/ads pages), primary color + logo/branding, sections & layout in order, primary CTA + destination, desktop+mobile or mobile-only, which organization. CONSULT, don't interrogate: SUGGEST so the user reacts to something concrete — propose a section flow (pick the archetype matching the page type) + a look (hero treatment + color/tone), and when the user is vague offer 2–3 directions to choose from; proactively suggest sections that fit their goal (social-proof, FAQ, countdown), but ask, don't silently add. Then restate the proposed design (section flow + CTA + color/tone) and WAIT for the user's confirmation, iterating until it matches their intent, before generating. Never assume or silently placeholder the page name, product, price, or colors — ask; only placeholder a core fact when the user explicitly declines to give it.
|
|
10
10
|
- ASK for any real data the page will display — never invent it, and don't silently placeholder it. This includes: phone/hotline/Zalo, price (+ original price), address, shop/brand name, links/URLs, email, opening hours, and exact stats/social-proof numbers. If a value the page needs is missing, ASK the user for it (in intake, or pause and ask before generating). Use a clearly-labelled placeholder ONLY when the user explicitly says to skip it — then tell them exactly what to fill in.
|
|
11
11
|
- LANGUAGE: write ALL page copy in the SAME language the user is chatting in, with FULL, CORRECT diacritics/accents. For Vietnamese, every word MUST carry its proper dấu (e.g. "Trân Trọng Kính Mời", "Ngày 15 Tháng 08 Năm 2025") — NEVER emit accent-stripped "không dấu" text. Never romanize or drop accent marks from any language.
|
|
12
|
-
-
|
|
12
|
+
- ALWAYS call validate_page and fix every error before create_page / update_page.
|
|
13
13
|
- BUILD THE SOURCE IN ONE PASS — gather everything you need BEFORE assembling the source, then build the FULL tree once. BATCH the reads: when a section needs several element types (section + text-block + image-block + button + form + input), call get_element({types:[…]}) ONCE instead of one call per type — same for images, call search_images({queries:[…]}) ONCE with one query per image slot (it dedups + parallelizes and returns one best photo per query). Do NOT interleave get_element calls between create_page previews and rebuild. create_page/update_page take the entire source as input, so each call re-ships the whole page — re-previewing repeatedly wastes the request.
|
|
14
|
-
- create_page and update_page DEFAULT to dry_run=true (a safety net for ambiguous requests). When the user's intent is clear
|
|
14
|
+
- create_page and update_page DEFAULT to dry_run=true (a safety net for ambiguous requests). When the user's intent is clear AND validate_page already passed (no errors), SKIP the dry-run and call with dry_run=false directly — saves one round-trip. Use dry_run=true only when (a) the request is ambiguous about target/content, (b) the user explicitly asks to "preview" or "xem trước", (c) this is an update_page that overwrites significant existing content, or (d) you genuinely need to inspect the redacted payload. Never loop dry-runs to "check" the source — validate_page is the validator. Do not run dry-run then dry-run again before the real write.
|
|
15
15
|
- LARGE PAGES (4+ sections) — build INCREMENTALLY to avoid the giant single create_page payload that can drop the connection: create_page with a SMALL skeleton (empty/near-empty page) to get a page_id, then call add_section once per section (each call ships ONLY that section; the backend appends it server-side and rejects duplicate ids — no whole-source get+put). Small pages can still go in one create_page pass.
|
|
16
16
|
- EDIT existing pages surgically: find_pages (locate the page by name/domain/id when you don't already have a page_id) → get_page → change ONLY what was asked → keep every other element, its id, and coordinates → validate_page → update_page. Never regenerate the whole tree for a small change.
|
|
17
17
|
- Organizations: call list_organizations and ask which to use; default to the is_default org. Endpoints are owner-scoped (only the account's own pages).
|
|
@@ -57,6 +57,45 @@ function num(v) {
|
|
|
57
57
|
}
|
|
58
58
|
return undefined;
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* True when a CSS color string carries real hue — i.e. NOT white/black/grey/
|
|
62
|
+
* transparent. Used to flag a page that ships with no color at all (every band
|
|
63
|
+
* white/neutral, no accent), which renders flat/"colorless". A gradient or image
|
|
64
|
+
* background counts as color. Neutrals (white/black/grey) have ~0 channel spread.
|
|
65
|
+
*/
|
|
66
|
+
function isVividColor(v) {
|
|
67
|
+
if (typeof v !== "string")
|
|
68
|
+
return false;
|
|
69
|
+
const s = v.trim().toLowerCase();
|
|
70
|
+
if (!s || s === "transparent" || s === "none" || s === "inherit")
|
|
71
|
+
return false;
|
|
72
|
+
if (s.includes("gradient") || s.startsWith("url("))
|
|
73
|
+
return true;
|
|
74
|
+
let r, g, b, a = 1;
|
|
75
|
+
const rgba = s.match(/rgba?\(([^)]+)\)/);
|
|
76
|
+
if (rgba) {
|
|
77
|
+
const parts = rgba[1].split(",").map((x) => parseFloat(x.trim()));
|
|
78
|
+
[r, g, b] = parts;
|
|
79
|
+
if (parts.length >= 4 && Number.isFinite(parts[3]))
|
|
80
|
+
a = parts[3];
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
const hex = s.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/);
|
|
84
|
+
if (!hex)
|
|
85
|
+
return false;
|
|
86
|
+
let h = hex[1];
|
|
87
|
+
if (h.length === 3)
|
|
88
|
+
h = h[0] + h[0] + h[1] + h[1] + h[2] + h[2];
|
|
89
|
+
r = parseInt(h.slice(0, 2), 16);
|
|
90
|
+
g = parseInt(h.slice(2, 4), 16);
|
|
91
|
+
b = parseInt(h.slice(4, 6), 16);
|
|
92
|
+
}
|
|
93
|
+
if (![r, g, b].every((n) => Number.isFinite(n)))
|
|
94
|
+
return false;
|
|
95
|
+
if (a <= 0.05)
|
|
96
|
+
return false; // fully transparent
|
|
97
|
+
return Math.max(r, g, b) - Math.min(r, g, b) >= 16; // channel spread ⇒ has hue
|
|
98
|
+
}
|
|
60
99
|
/** Accept an object or a JSON string. Returns the parsed page or throws. */
|
|
61
100
|
export function coercePage(input) {
|
|
62
101
|
if (typeof input === "string")
|
|
@@ -90,6 +129,9 @@ export function validatePage(input) {
|
|
|
90
129
|
// form nodes — used to check field_name uniqueness within each form's scope
|
|
91
130
|
const forms = [];
|
|
92
131
|
let elementCount = 0;
|
|
132
|
+
// Whether ANY element (section, button, text…) carries real color on either
|
|
133
|
+
// breakpoint. A page where this stays false renders flat/colorless (warned once).
|
|
134
|
+
let anyVividColor = false;
|
|
93
135
|
const topList = Array.isArray(page?.page)
|
|
94
136
|
? page.page
|
|
95
137
|
: page?.page
|
|
@@ -117,6 +159,20 @@ export function validatePage(input) {
|
|
|
117
159
|
if (!node.responsive?.desktop || !node.responsive?.mobile) {
|
|
118
160
|
errors.push(`${path} (${type}): must have responsive.desktop AND responsive.mobile.`);
|
|
119
161
|
}
|
|
162
|
+
// does this element put any real color on the page? (background / text / border)
|
|
163
|
+
if (!anyVividColor) {
|
|
164
|
+
for (const bp of ["desktop", "mobile"]) {
|
|
165
|
+
const st = node.responsive?.[bp]?.styles;
|
|
166
|
+
if (st &&
|
|
167
|
+
(isVividColor(st.background) ||
|
|
168
|
+
isVividColor(st.backgroundColor) ||
|
|
169
|
+
isVividColor(st.color) ||
|
|
170
|
+
isVividColor(st.borderColor))) {
|
|
171
|
+
anyVividColor = true;
|
|
172
|
+
break;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
120
176
|
// children only on containers
|
|
121
177
|
if (Array.isArray(node.children) && node.children.length > 0 && type && !CONTAINER_TYPES.has(type)) {
|
|
122
178
|
errors.push(`${path} (${type}): has children but "${type}" is not a container type.`);
|
|
@@ -383,6 +439,12 @@ export function validatePage(input) {
|
|
|
383
439
|
warnings.push(`Sections start on different left margins (${list}). Put every band's left-anchored content (the header logo included) on ONE shared left axis — e.g. left=${minEdge} desktop — so the page reads aligned, not ragged. This is the #1 header-misalignment defect.`);
|
|
384
440
|
}
|
|
385
441
|
}
|
|
442
|
+
// 3c) Colorless page — nothing on the page carries real color (every band/button/
|
|
443
|
+
// heading is white/black/grey). Sections have NO default background, so a page
|
|
444
|
+
// that never sets one renders as a flat white wall. Advisory only.
|
|
445
|
+
if (topList.length >= 2 && elementCount >= 3 && !anyVividColor) {
|
|
446
|
+
warnings.push(`Page has no color — no section background, button, or text uses a non-neutral color, so it renders as a flat white/grey wall. Set responsive.<bp>.styles.background on each section (alternate light/tinted/dark from the palette) and give the primary CTA an accent background. If a stark black-and-white look is intentional, ignore this.`);
|
|
447
|
+
}
|
|
386
448
|
popups.forEach((p, i) => {
|
|
387
449
|
const ds = p?.responsive?.desktop?.styles ?? {};
|
|
388
450
|
const ms = p?.responsive?.mobile?.styles ?? {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "webcake-landing-mcp",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.46",
|
|
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
|
"mcpName": "io.github.vuluu2k/webcake-landing-mcp",
|
|
6
6
|
"type": "module",
|