sbuilder-mcp 0.4.0 → 0.4.2
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 +11 -0
- package/CHANGELOG.vi.md +11 -0
- package/dist/domains/site/review.js +30 -6
- package/dist/tools/api.js +13 -1
- package/dist/vision/shoot.js +49 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,17 @@ 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.4.2] - 2026-09-08
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- sb_look now waits for the page to stop changing (a MutationObserver, 250 ms quiet / 2000 ms cap) instead of waiting on a networkidle timeout that a storefront's polling cart island and session checks could never satisfy, cutting a typical shot from ~3.2s to under 1s while still photographing a page that never settles rather than holding the shot forever.
|
|
13
|
+
|
|
14
|
+
## [0.4.1] - 2026-09-08
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- sb_review now reports findings inside the cart drawer and other site overlays, tagged with overlay: true, instead of silently skipping them on the belief that an overlay is not the page's to fix; the review notice explains that an overlay:true finding is fixed the same way but is site-wide, so it only needs fixing once.
|
|
18
|
+
- sb_api_call's path_params now matches a parameter name case-insensitively as a fallback, so a call using the common siteId spelling no longer fails against the 8 operations that spell it siteID; an exact match still wins and a genuinely missing parameter is still refused.
|
|
19
|
+
|
|
9
20
|
## [0.4.0] - 2026-09-08
|
|
10
21
|
|
|
11
22
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,17 @@ 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.4.2] - 2026-09-08
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- sb_look giờ chờ trang ngừng thay đổi (dùng MutationObserver, 250 ms yên tĩnh / giới hạn 2000 ms) thay vì chờ timeout networkidle mà cart island liên tục polling và các kiểm tra session của storefront không bao giờ thỏa mãn được, rút ngắn một lần chụp điển hình từ ~3.2s xuống dưới 1s, đồng thời vẫn chụp ảnh một trang không bao giờ ổn định thay vì giữ mãi không chụp.
|
|
13
|
+
|
|
14
|
+
## [0.4.1] - 2026-09-08
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- sb_review giờ báo cáo các finding bên trong cart drawer và các overlay khác của site, gắn cờ overlay: true, thay vì âm thầm bỏ qua chúng vì cho rằng một overlay không phải trách nhiệm của trang; thông báo review giải thích rằng một finding có overlay: true được sửa theo cùng cách nhưng có phạm vi toàn site, nên chỉ cần sửa một lần.
|
|
18
|
+
- path_params của sb_api_call giờ khớp tên tham số không phân biệt hoa thường như một phương án dự phòng, nên một lời gọi dùng cách viết phổ biến siteId không còn thất bại với 8 operation viết là siteID; một khớp chính xác vẫn được ưu tiên và một tham số thực sự thiếu vẫn bị từ chối.
|
|
19
|
+
|
|
9
20
|
## [0.4.0] - 2026-09-08
|
|
10
21
|
|
|
11
22
|
### Added
|
|
@@ -12,7 +12,9 @@ import { fill } from './findings.js';
|
|
|
12
12
|
export const REVIEW_NOTICE = 'FIX THESE. Each one is a defect a visitor will see on the published page, not a ' +
|
|
13
13
|
'suggestion — a blank band, a placeholder sentence, a broken image. Apply the fix each ' +
|
|
14
14
|
'finding names, then review again until the list is empty. Do not report the page as done ' +
|
|
15
|
-
'while findings stand; if you believe one is a false positive, say which and why.'
|
|
15
|
+
'while findings stand; if you believe one is a false positive, say which and why. ' +
|
|
16
|
+
'A finding marked overlay:true is in the cart drawer or a pop-up — fix it the same way ' +
|
|
17
|
+
'(sb_set lands there), but it is SITE-WIDE, so fix it once rather than once per page.';
|
|
16
18
|
/** The specials keys an element seeds that hold its visible content. */
|
|
17
19
|
function contentKeys(type) {
|
|
18
20
|
const seeded = ELEMENTS[type]?.defaults.specials ?? {};
|
|
@@ -56,8 +58,19 @@ function drawsItsOwnContent(type) {
|
|
|
56
58
|
/**
|
|
57
59
|
* Everything wrong with this page that a person would notice.
|
|
58
60
|
*
|
|
59
|
-
*
|
|
60
|
-
* this page's to fix
|
|
61
|
+
* OVERLAYS ARE WALKED, and used not to be — on the stated reasoning that the
|
|
62
|
+
* cart drawer "is not this page's to fix", which is false. An overlay's content
|
|
63
|
+
* reaches storage THROUGH THE PAGE SAVE (`overlays/rest/rest.go`: content is
|
|
64
|
+
* "deliberately NOT written here"), so `sb_set` on a drawer node lands, and the
|
|
65
|
+
* skip meant nothing ever reported what shipped inside one. Measured: a
|
|
66
|
+
* rose-and-ink storefront whose drawer carried a static mock row reading
|
|
67
|
+
* "Product name / 0₫", a duplicate cart list, and English copy — none of it
|
|
68
|
+
* mentioned by any check, on a site that reviewed clean ten pages running.
|
|
69
|
+
*
|
|
70
|
+
* Their findings carry `overlay: true`, because the master is SHARED: without
|
|
71
|
+
* the flag the same drawer defect reads as ten problems on a ten-page site.
|
|
72
|
+
*
|
|
73
|
+
* The INSIDE of an app block is still skipped (trap 5): its
|
|
61
74
|
* placeholders are the app's, and no fix this page could apply would survive a
|
|
62
75
|
* save. The block root itself is still walked — it is a node the page owns and
|
|
63
76
|
* can be an empty container. Findings are ordered by document order so a caller working
|
|
@@ -84,10 +97,14 @@ export function reviewDesign(doc) {
|
|
|
84
97
|
// itself belongs to the scope OUTSIDE it, which is why this is set before the
|
|
85
98
|
// scope for the children is computed.
|
|
86
99
|
const inRepeater = new Map();
|
|
87
|
-
|
|
88
|
-
|
|
100
|
+
// Which nodes sit inside an overlay, so their findings can say so.
|
|
101
|
+
const inOverlay = new Set();
|
|
102
|
+
const go = (id, repeater, overlay = false) => {
|
|
103
|
+
if (seen.has(id))
|
|
89
104
|
return;
|
|
90
105
|
seen.add(id);
|
|
106
|
+
if (overlay)
|
|
107
|
+
inOverlay.add(id);
|
|
91
108
|
walkOrder.push(id);
|
|
92
109
|
if (repeater)
|
|
93
110
|
inRepeater.set(id, repeater);
|
|
@@ -95,7 +112,7 @@ export function reviewDesign(doc) {
|
|
|
95
112
|
return;
|
|
96
113
|
const inner = repeats(d.nodes[id]?.data.type ?? '') ? (repeater ?? id) : repeater;
|
|
97
114
|
for (const k of childrenOf(d, id))
|
|
98
|
-
go(k, inner);
|
|
115
|
+
go(k, inner, overlay || overlayIds.has(k));
|
|
99
116
|
};
|
|
100
117
|
go(d.root_node_id);
|
|
101
118
|
for (const id of walkOrder) {
|
|
@@ -301,5 +318,12 @@ export function reviewDesign(doc) {
|
|
|
301
318
|
}
|
|
302
319
|
}
|
|
303
320
|
}
|
|
321
|
+
// Tagged in ONE place rather than at eight push sites: whether a node sits
|
|
322
|
+
// inside an overlay is a fact about where it is, not about what is wrong with
|
|
323
|
+
// it, and threading it through every rule would put the same argument in eight
|
|
324
|
+
// signatures.
|
|
325
|
+
for (const f of out)
|
|
326
|
+
if (inOverlay.has(f.nodeId))
|
|
327
|
+
f.overlay = true;
|
|
304
328
|
return out;
|
|
305
329
|
}
|
package/dist/tools/api.js
CHANGED
|
@@ -171,7 +171,19 @@ export async function callOperation(ctx, args) {
|
|
|
171
171
|
let path = op.path;
|
|
172
172
|
for (const m of op.path.matchAll(/\{([^}]+)\}/g)) {
|
|
173
173
|
const name = m[1];
|
|
174
|
-
|
|
174
|
+
// CASE-INSENSITIVE, because the platform spells one parameter two ways:
|
|
175
|
+
// `{siteId}` in 281 operations and `{siteID}` in 8. A caller who learned the
|
|
176
|
+
// common spelling passes the wrong key on those eight and is refused for a
|
|
177
|
+
// difference of one letter — a distinction no reader of the call sheet has
|
|
178
|
+
// any reason to notice, and one this tool has nothing to gain by enforcing.
|
|
179
|
+
// The exact name still wins; the fold is only a fallback.
|
|
180
|
+
const given = args.path_params ?? {};
|
|
181
|
+
let value = given[name];
|
|
182
|
+
if (value === undefined) {
|
|
183
|
+
const folded = Object.keys(given).find((k) => k.toLowerCase() === name.toLowerCase());
|
|
184
|
+
if (folded !== undefined)
|
|
185
|
+
value = given[folded];
|
|
186
|
+
}
|
|
175
187
|
if (value === undefined) {
|
|
176
188
|
// NAME THE ARGUMENT, not just the parameter. The call sheet lists these
|
|
177
189
|
// under `params` while the call takes them in `path_params`, and a caller
|
package/dist/vision/shoot.js
CHANGED
|
@@ -138,6 +138,54 @@ export async function shoot(url, opts = {}) {
|
|
|
138
138
|
}
|
|
139
139
|
}));
|
|
140
140
|
}
|
|
141
|
+
/**
|
|
142
|
+
* WAIT FOR THE PAGE TO STOP CHANGING, not for the network to go quiet.
|
|
143
|
+
*
|
|
144
|
+
* This used to be `waitForLoadState('networkidle', { timeout: 2_500 })`, with a
|
|
145
|
+
* comment explaining that a storefront never goes idle — the cart island polls,
|
|
146
|
+
* a session endpoint answers 401 forever. That was correct, and it meant the
|
|
147
|
+
* wait ALWAYS ran to its cap: measured at 2502 ms on every single look, three
|
|
148
|
+
* runs out of three, against a 2847 ms total. Eighty-eight per cent of a
|
|
149
|
+
* screenshot was a timeout the code already knew would never resolve, paid after
|
|
150
|
+
* every edit of a vision loop.
|
|
151
|
+
*
|
|
152
|
+
* A MutationObserver answers the question actually being asked — has the page
|
|
153
|
+
* finished rendering — and answers it the moment it is true. Measured on the
|
|
154
|
+
* same three pages: 400-460 ms, with identical content on screen (images,
|
|
155
|
+
* prices, no empty states). The storefront renders its lists SERVER-side, so
|
|
156
|
+
* everything is present a few hundred ms after `load`; the old wait bought
|
|
157
|
+
* nothing but latency.
|
|
158
|
+
*
|
|
159
|
+
* Bounded twice over, and both bounds matter: `quiet` is how long nothing may
|
|
160
|
+
* change before the page counts as settled, and `cap` stops an animation or a
|
|
161
|
+
* polling widget from holding the shot forever. A page that never settles is
|
|
162
|
+
* photographed anyway — a late picture beats none.
|
|
163
|
+
*/
|
|
164
|
+
async function settleDom(page) {
|
|
165
|
+
await page
|
|
166
|
+
.evaluate(({ quiet, cap }) => new Promise((resolve) => {
|
|
167
|
+
const start = Date.now();
|
|
168
|
+
let last = Date.now();
|
|
169
|
+
const mo = new MutationObserver(() => {
|
|
170
|
+
last = Date.now();
|
|
171
|
+
});
|
|
172
|
+
mo.observe(document.documentElement, {
|
|
173
|
+
subtree: true,
|
|
174
|
+
childList: true,
|
|
175
|
+
attributes: true,
|
|
176
|
+
characterData: true,
|
|
177
|
+
});
|
|
178
|
+
const tick = setInterval(() => {
|
|
179
|
+
const now = Date.now();
|
|
180
|
+
if (now - last >= quiet || now - start >= cap) {
|
|
181
|
+
clearInterval(tick);
|
|
182
|
+
mo.disconnect();
|
|
183
|
+
resolve();
|
|
184
|
+
}
|
|
185
|
+
}, 50);
|
|
186
|
+
}), { quiet: 250, cap: 2_000 })
|
|
187
|
+
.catch(() => { });
|
|
188
|
+
}
|
|
141
189
|
/**
|
|
142
190
|
* WALK THE PAGE so its lazy images load, then come back to the top.
|
|
143
191
|
*
|
|
@@ -187,7 +235,7 @@ async function shootOne(page, url, width, format, opts) {
|
|
|
187
235
|
// storefront. The settle is best-effort: if the page does go quiet, the shot
|
|
188
236
|
// waits for it; if it never does, the shot happens anyway.
|
|
189
237
|
await page.goto(url, { waitUntil: 'load', timeout: 30_000 });
|
|
190
|
-
await page
|
|
238
|
+
await settleDom(page);
|
|
191
239
|
await settleLazyImages(page);
|
|
192
240
|
// A RENDERED page carries its node ids as the HTML `id` attribute — not as
|
|
193
241
|
// `data-node-id`, which is the editor CANVAS's hook and never reaches the
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
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",
|