sbuilder-mcp 0.87.0 → 0.88.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/CHANGELOG.md +10 -0
- package/CHANGELOG.vi.md +10 -0
- package/dist/catalog/deadkeys.generated.js +2 -2
- package/dist/catalog/source.generated.js +2 -2
- package/dist/domains/site/readiness.js +7 -6
- package/dist/domains/site/review.js +18 -0
- package/dist/domains/site/seedcopy.js +28 -0
- package/dist/domains/site/storepage.js +26 -3
- package/dist/tools/page.js +25 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,16 @@ All notable changes to this project are documented in this file.
|
|
|
6
6
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.88.0] - 2026-10-12
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_review` reports `default_seed_copy` for pages built earlier that still carry the English seed copy, such as "Add To Cart", on a Vietnamese site, with the Vietnamese wording to use instead.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- `sb_page_create` writes the store page seeds in Vietnamese for a Vietnamese site, taking the locale from the site's settings when none is passed.
|
|
16
|
+
- `sb_page_create` uses English thank-you copy on the completed-order page of an `en-US` site.
|
|
17
|
+
- `sb_review` words its `categoryImages` note to say that a collection tile without an image now shows a placeholder instead of a broken image.
|
|
18
|
+
|
|
9
19
|
## [0.87.0] - 2026-10-12
|
|
10
20
|
|
|
11
21
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,16 @@ Mọi thay đổi đáng chú ý của dự án được ghi lại trong file n
|
|
|
6
6
|
Định dạng dựa trên [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
7
7
|
và dự án tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
8
8
|
|
|
9
|
+
## [0.88.0] - 2026-10-12
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_review` báo `default_seed_copy` cho trang dựng trước đó còn mang chữ mẫu tiếng Anh, như "Add To Cart", trên site tiếng Việt, kèm bản tiếng Việt nên dùng.
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- `sb_page_create` viết seed trang cửa hàng bằng tiếng Việt cho site tiếng Việt, lấy locale từ cài đặt của site khi không truyền.
|
|
16
|
+
- `sb_page_create` dùng câu cảm ơn tiếng Anh cho trang hoàn tất đơn hàng của site `en-US`.
|
|
17
|
+
- `sb_review` sửa câu báo `categoryImages`: ô danh mục thiếu ảnh giờ hiện placeholder thay vì ảnh vỡ.
|
|
18
|
+
|
|
9
19
|
## [0.87.0] - 2026-10-12
|
|
10
20
|
|
|
11
21
|
### Added
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// GENERATED by scripts/gen-catalog.ts — do not edit by hand.
|
|
2
2
|
// The platform commit every other generated file here describes.
|
|
3
3
|
export const PLATFORM_SOURCE = {
|
|
4
|
-
"commit": "
|
|
5
|
-
"committedAt": "2026-10-
|
|
4
|
+
"commit": "74ca59dd2340133e4ecf7f2faaf0947874ec3554",
|
|
5
|
+
"committedAt": "2026-10-12T10:37:24+07:00",
|
|
6
6
|
"dirty": false
|
|
7
7
|
};
|
|
@@ -612,11 +612,11 @@ export function readinessGaps(input) {
|
|
|
612
612
|
'/api/media/{siteId}/from-url ingests straight from a url in one hop.',
|
|
613
613
|
});
|
|
614
614
|
}
|
|
615
|
-
// A COLLECTION TILE WITH NO PICTURE
|
|
616
|
-
// render/nodes/collection-media
|
|
617
|
-
//
|
|
618
|
-
//
|
|
619
|
-
// Asked only where a
|
|
615
|
+
// A COLLECTION TILE WITH NO PICTURE. Measured on the sb_category_strip a store
|
|
616
|
+
// built here put on its home page: render/nodes/collection-media wrote
|
|
617
|
+
// `<img src="">`, two broken icons above the category names. web_builder PR
|
|
618
|
+
// #118 (74ca59dd2) gives it the image element's grey placeholder instead —
|
|
619
|
+
// better, still not the category. Asked only where a tile is on screen.
|
|
620
620
|
if ((input.categoriesUnpictured ?? 0) > 0 &&
|
|
621
621
|
[...input.pageNodes, ...(input.globalNodes ?? [])].some((n) => n.data?.type === 'collection-media')) {
|
|
622
622
|
const n = input.categoriesUnpictured;
|
|
@@ -624,7 +624,8 @@ export function readinessGaps(input) {
|
|
|
624
624
|
id: 'categoryImages',
|
|
625
625
|
draft: false,
|
|
626
626
|
problem: `${n} product categor${n === 1 ? 'y has' : 'ies have'} no image, and this page shows category ` +
|
|
627
|
-
|
|
627
|
+
"tiles — each draws the platform's grey placeholder (a broken-image icon on a platform " +
|
|
628
|
+
'older than web_builder 74ca59dd2) where the picture goes.',
|
|
628
629
|
fix: 'Upload a picture (sb_media_upload), then PUT the category back with image: url ' +
|
|
629
630
|
'(sb_api_call put:/api/sites/{siteId}/product-categories/{id}, merge:true).',
|
|
630
631
|
});
|
|
@@ -3,6 +3,7 @@ import { childrenOf, childrenWithSatellites, isOverlay, isSharedSection, pageChi
|
|
|
3
3
|
import { STUCK_STATE, stickyBlockedBy, stuckHostOf, isPinnedNode } from './sticky.js';
|
|
4
4
|
import { bandOf, isGlobal } from './traps.js';
|
|
5
5
|
import { exampleCopy } from './examplecopy.js';
|
|
6
|
+
import { SEED_COPY_VI, langOf } from './seedcopy.js';
|
|
6
7
|
import { HOVER_STATE, hoverHome } from './hover.js';
|
|
7
8
|
import { ELEMENTS, BINDING_SOURCES, BOUND_SPECIALS, FIRST_CHILD_ONLY, SATELLITE_RULES, ELEMENT_SEEDS } from '../../catalog/elements.generated.js';
|
|
8
9
|
import { fill } from './findings.js';
|
|
@@ -867,6 +868,23 @@ opts = {}) {
|
|
|
867
868
|
fix: fill('default_seed_copy', { id, key }),
|
|
868
869
|
});
|
|
869
870
|
}
|
|
871
|
+
// THE STORE SEEDS' ENGLISH COPY ON A VIETNAMESE SITE ("Add To Cart") —
|
|
872
|
+
// fine on an English site, so asked only when the site's language is
|
|
873
|
+
// known. sb_page_create now seeds it in Vietnamese; this is for pages
|
|
874
|
+
// made before that, or with locale:"en" on a Vietnamese site.
|
|
875
|
+
else if (typeof value === 'string' &&
|
|
876
|
+
SEED_COPY_VI[value] &&
|
|
877
|
+
langOf(opts.siteLocale) === 'vi') {
|
|
878
|
+
out.push({
|
|
879
|
+
code: 'default_seed_copy',
|
|
880
|
+
nodeId: id,
|
|
881
|
+
type,
|
|
882
|
+
problem: `Still the platform's English seed copy (${JSON.stringify(value)}) on a Vietnamese site — ` +
|
|
883
|
+
`"${SEED_COPY_VI[value]}" is what this storefront should say.`,
|
|
884
|
+
key,
|
|
885
|
+
fix: fill('default_seed_copy', { id, key }),
|
|
886
|
+
});
|
|
887
|
+
}
|
|
870
888
|
// A BUILT-IN LAYOUT'S EXAMPLE COPY — see examplecopy.ts.
|
|
871
889
|
else if (typeof value === 'string' && exampleCopy().has(value)) {
|
|
872
890
|
out.push({
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE STORE SEEDS' ENGLISH COPY, IN VIETNAMESE — applied when the page is made
|
|
3
|
+
* for a Vietnamese site.
|
|
4
|
+
*
|
|
5
|
+
* The platform seeds in English on purpose (editor/src/element/emptyState.ts:
|
|
6
|
+
* "a seed is a seed", decided 2026-09-17) and a table per language was offered
|
|
7
|
+
* there and declined. Here the agent IS the author, and it knows the site's
|
|
8
|
+
* language: measured on a store built end to end, the product page published
|
|
9
|
+
* "Add To Cart" / "Buy It Now" to a Vietnamese storefront and every page
|
|
10
|
+
* reviewed clean. sb_page_create seeds through this; sb_review (which reads the
|
|
11
|
+
* site's locale) reports a page still carrying the English. Exact strings only; a seed string
|
|
12
|
+
* this table does not carry is left as it was — and test/store-page-seeds
|
|
13
|
+
* fails when codegen brings one in, so the table cannot fall behind silently.
|
|
14
|
+
*/
|
|
15
|
+
export const SEED_COPY_VI = {
|
|
16
|
+
'Add To Cart': 'Thêm vào giỏ',
|
|
17
|
+
'Buy It Now': 'Mua ngay',
|
|
18
|
+
'This item is unavailable': 'Sản phẩm tạm hết',
|
|
19
|
+
'It may have been removed. Have a look at the rest of the shop.': 'Sản phẩm có thể đã ngừng bán. Mời bạn xem các sản phẩm khác.',
|
|
20
|
+
'No products yet': 'Chưa có sản phẩm',
|
|
21
|
+
'New arrivals will show up here. Check back soon.': 'Sản phẩm mới sẽ sớm có ở đây — mời bạn quay lại sau.',
|
|
22
|
+
'No posts yet': 'Chưa có bài viết',
|
|
23
|
+
'New stories will show up here. Check back soon.': 'Bài viết mới sẽ sớm có ở đây.',
|
|
24
|
+
};
|
|
25
|
+
/** The language part of a locale tag — "vi-VN", "vi_VN" and "VI" are all "vi". */
|
|
26
|
+
export function langOf(locale) {
|
|
27
|
+
return String(locale ?? '').toLowerCase().split(/[-_]/)[0];
|
|
28
|
+
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { SEED_COPY_VI, langOf } from './seedcopy.js';
|
|
1
2
|
import { COMPLETION_HEADLINE, COMPLETION_HEADLINE_SENTINEL, PAGE_LAYOUT_SEEDS, PAGE_TYPES, STORE_PAGE_SEEDS } from '../../catalog/storepages.generated.js';
|
|
2
3
|
import { BINDING_SOURCES } from '../../catalog/elements.generated.js';
|
|
3
4
|
import { withFreshIds } from './ids.js';
|
|
@@ -40,22 +41,44 @@ export function seededTypes() {
|
|
|
40
41
|
* An explicit `headline` wins over both — a merchant's own wording is not the
|
|
41
42
|
* client's to overrule.
|
|
42
43
|
*/
|
|
44
|
+
/** Rewrite every exact seed string the table carries, in place. */
|
|
45
|
+
function localizeSeed(doc, table) {
|
|
46
|
+
for (const n of Object.values(doc.nodes)) {
|
|
47
|
+
const sp = n.specials;
|
|
48
|
+
if (!sp)
|
|
49
|
+
continue;
|
|
50
|
+
for (const k of ['text', 'label']) {
|
|
51
|
+
const v = sp[k];
|
|
52
|
+
if (typeof v === 'string' && table[v])
|
|
53
|
+
sp[k] = table[v];
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export { SEED_COPY_VI } from './seedcopy.js';
|
|
43
58
|
export function seedDocument(type, opts = {}) {
|
|
44
59
|
const seed = STORE_PAGE_SEEDS[type];
|
|
45
60
|
if (!seed)
|
|
46
61
|
return null;
|
|
47
62
|
const raw = JSON.stringify(seed);
|
|
63
|
+
const vi = langOf(opts.locale ?? 'vi') === 'vi';
|
|
64
|
+
const out = (doc) => {
|
|
65
|
+
if (vi)
|
|
66
|
+
localizeSeed(doc, SEED_COPY_VI);
|
|
67
|
+
return withFreshIds(doc);
|
|
68
|
+
};
|
|
48
69
|
// Fresh ids per create, so two pages from one seed share nothing but ROOT.
|
|
49
70
|
if (!raw.includes(COMPLETION_HEADLINE_SENTINEL)) {
|
|
50
|
-
return
|
|
71
|
+
return out(JSON.parse(raw));
|
|
51
72
|
}
|
|
52
|
-
|
|
73
|
+
// Normalized: a site whose locale is "en-US" reads its own language here too.
|
|
74
|
+
const tag = langOf(opts.locale);
|
|
75
|
+
const lang = tag && tag in COMPLETION_HEADLINE ? tag : 'vi';
|
|
53
76
|
const headline = opts.headline || COMPLETION_HEADLINE[lang];
|
|
54
77
|
// JSON.stringify already escaped the seed; the substitute has to be escaped
|
|
55
78
|
// the same way or an apostrophe in a merchant's own headline breaks the
|
|
56
79
|
// document. Slicing the quotes off a stringified string is that escaping.
|
|
57
80
|
const escaped = JSON.stringify(headline).slice(1, -1);
|
|
58
|
-
return
|
|
81
|
+
return out(JSON.parse(raw.split(COMPLETION_HEADLINE_SENTINEL).join(escaped)));
|
|
59
82
|
}
|
|
60
83
|
/**
|
|
61
84
|
* What to put on an ENTITY TEMPLATE that opens empty, or null.
|
package/dist/tools/page.js
CHANGED
|
@@ -47,8 +47,8 @@ import { projectList, PAGE_FIELDS, TEMPLATE_FIELDS } from './project.js';
|
|
|
47
47
|
* copies of a directive is how one of them quietly loses it. The fix for each
|
|
48
48
|
* KIND is sent once under `fixes`, and the directive once per process.
|
|
49
49
|
*/
|
|
50
|
-
export function reviewField(ctx, doc, formTypes, pageType, formDocType, formRedirects) {
|
|
51
|
-
const all = reviewDesign(doc, { formTypes, pageType, formDocType, formRedirects });
|
|
50
|
+
export function reviewField(ctx, doc, formTypes, pageType, formDocType, formRedirects, siteLocale) {
|
|
51
|
+
const all = reviewDesign(doc, { formTypes, pageType, formDocType, formRedirects, siteLocale });
|
|
52
52
|
if (all.length === 0)
|
|
53
53
|
return {};
|
|
54
54
|
// MAINTENANCE IS ADVICE, kept out of `findings`: it works for a visitor, so
|
|
@@ -1807,6 +1807,7 @@ export function registerPageTools(server, ctx) {
|
|
|
1807
1807
|
// The page's TYPE says which record it supplies — `no_data_context` stays
|
|
1808
1808
|
// silent without it rather than guessing.
|
|
1809
1809
|
let pageType;
|
|
1810
|
+
let siteLocale;
|
|
1810
1811
|
try {
|
|
1811
1812
|
const { siteId, pageId } = session.location();
|
|
1812
1813
|
const input = await gatherReadiness(ctx, siteId, Object.values(doc.doc.nodes), pageId);
|
|
@@ -1817,6 +1818,7 @@ export function registerPageTools(server, ctx) {
|
|
|
1817
1818
|
return f.id && a?.action === 'redirect' && (a.url || a.pageId) ? [f.id] : [];
|
|
1818
1819
|
}));
|
|
1819
1820
|
pageType = input.pages?.find((p) => p.id === pageId)?.type;
|
|
1821
|
+
siteLocale = input.siteLocale;
|
|
1820
1822
|
const gaps = readinessGaps(input);
|
|
1821
1823
|
const lang = siteLanguage(input.siteLocale, input.pageNodes);
|
|
1822
1824
|
const said = lang ? ctx.notices.once(`site-language:${siteId}`, lang) : undefined;
|
|
@@ -1830,7 +1832,7 @@ export function registerPageTools(server, ctx) {
|
|
|
1830
1832
|
catch {
|
|
1831
1833
|
// Readiness is additional information, never the reason a review fails.
|
|
1832
1834
|
}
|
|
1833
|
-
const field = reviewField(ctx, doc, formTypes, pageType, session.formType(), formRedirects);
|
|
1835
|
+
const field = reviewField(ctx, doc, formTypes, pageType, session.formType(), formRedirects, siteLocale);
|
|
1834
1836
|
// Clean = no finding a visitor meets; `advice` (maintenance) rides beside the verdict.
|
|
1835
1837
|
const clean = !field.findings;
|
|
1836
1838
|
return text({
|
|
@@ -2237,7 +2239,7 @@ export function registerPageTools(server, ctx) {
|
|
|
2237
2239
|
settings: z.record(z.unknown()).optional(),
|
|
2238
2240
|
seed: z.boolean().optional().describe('Default true; false creates a blank page.'),
|
|
2239
2241
|
chrome: z.boolean().optional().describe("Carry the site's header and footer, default true"),
|
|
2240
|
-
locale: z.string().optional().describe("vi
|
|
2242
|
+
locale: z.string().optional().describe("vi or en — a store seed's wording; defaults to the site's language, else vi."),
|
|
2241
2243
|
headline: z.string().optional().describe("The complete page's thank-you line."),
|
|
2242
2244
|
dry_run: z.boolean().optional(),
|
|
2243
2245
|
},
|
|
@@ -2420,7 +2422,25 @@ export function registerPageTools(server, ctx) {
|
|
|
2420
2422
|
let seeded = null;
|
|
2421
2423
|
const newId = res.page?.id;
|
|
2422
2424
|
if ((willSeed || layout) && typeof newId === 'string' && newId) {
|
|
2423
|
-
|
|
2425
|
+
// The SITE's language when the caller named none — the seed's English
|
|
2426
|
+
// copy is rewritten for a Vietnamese site (SEED_COPY_VI). Unreadable
|
|
2427
|
+
// settings fall back to the old default rather than block the create.
|
|
2428
|
+
const lang = locale ??
|
|
2429
|
+
(layout
|
|
2430
|
+
? undefined
|
|
2431
|
+
: await request({
|
|
2432
|
+
base: ctx.base,
|
|
2433
|
+
method: 'GET',
|
|
2434
|
+
path: `/api/sites/${encodeURIComponent(site_id)}/settings`,
|
|
2435
|
+
token: siteToken(ctx),
|
|
2436
|
+
fetchImpl: ctx.fetchImpl,
|
|
2437
|
+
})
|
|
2438
|
+
.then((r) => {
|
|
2439
|
+
const l = r?.settings?.locale;
|
|
2440
|
+
return typeof l === 'string' && l ? l : undefined;
|
|
2441
|
+
})
|
|
2442
|
+
.catch(() => undefined));
|
|
2443
|
+
const document = layout ? layoutDocument(layout) : seedDocument(type ?? '', { locale: lang, headline });
|
|
2424
2444
|
if (document) {
|
|
2425
2445
|
try {
|
|
2426
2446
|
await request({
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.88.0",
|
|
4
4
|
"description": "MCP server that designs and operates a Store Builder site — pages, data, theme and publish — through the platform's own API and live-edit protocol.",
|
|
5
5
|
"mcpName": "io.github.vuluu2k/sbuilder-mcp",
|
|
6
6
|
"type": "module",
|