sbuilder-mcp 0.38.0 → 0.38.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/catalog/elements.generated.js +2 -1
- package/dist/vision/capture.js +18 -1
- package/dist/vision/shoot.js +37 -0
- package/package.json +2 -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.38.2] - 2026-09-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- sb_traits_for's config_values for list-dataset now lists a sixth config.collectionType value, page_collection, which renders the collection the page itself resolves to (the one a /collections/{slug} URL names), so one collection template can serve every collection explicitly instead of relying on all_products to narrow itself.
|
|
13
|
+
|
|
14
|
+
## [0.38.1] - 2026-09-11
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- sb_look no longer photographs a reveal-on-scroll band (config.animation trigger:"view") as blank; every animation is now stopped before the shutter opens, since the lazy-image walk returns to the top of the page and would otherwise catch the band back at its opacity:0 starting keyframe.
|
|
18
|
+
- sb_import and sb_import_site no longer drop a `<video>` element that carries a poster image and no explicit width/height, which previously measured as a zero-size box and was skipped like a genuinely hidden element.
|
|
19
|
+
|
|
9
20
|
## [0.38.0] - 2026-09-11
|
|
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.38.2] - 2026-09-11
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- config_values của sb_traits_for dành cho list-dataset giờ liệt kê thêm giá trị thứ sáu của config.collectionType, page_collection, giá trị này render đúng collection mà trang tự phân giải tới (collection được đặt tên bởi một URL /collections/{slug}), nhờ đó một collection template có thể phục vụ mọi collection một cách tường minh thay vì dựa vào all_products tự thu hẹp phạm vi.
|
|
13
|
+
|
|
14
|
+
## [0.38.1] - 2026-09-11
|
|
15
|
+
|
|
16
|
+
### Fixed
|
|
17
|
+
- sb_look không còn chụp một band hiệu ứng xuất hiện khi cuộn (config.animation trigger:"view") thành ảnh trống nữa; mọi animation giờ được dừng lại trước khi chụp, vì bước quét ảnh lazy-load quay về đầu trang và trước đây sẽ bắt band đó đứng yên ở keyframe khởi đầu opacity:0.
|
|
18
|
+
- sb_import và sb_import_site không còn bỏ sót phần tử `<video>` có ảnh poster nhưng không khai báo width/height tường minh, trước đây phần tử này đo được kích thước bằng 0 nên bị bỏ qua như một phần tử thực sự ẩn.
|
|
19
|
+
|
|
9
20
|
## [0.38.0] - 2026-09-11
|
|
10
21
|
|
|
11
22
|
### Added
|
|
@@ -32993,7 +32993,7 @@ export const ELEMENTS = {
|
|
|
32993
32993
|
"Always add exactly one dataset-block child as the repeated item template; every row clones that exact structure and styling",
|
|
32994
32994
|
"The dataset-block template — and every dataset node inside it — must carry the SAME config.datasetSource as this repeater. Each node reads its OWN key, so a collection list whose card omits it leaves that card on the product default: it binds product.url, no product context reaches a collection row, and the card publishes with no link, so clicking it does nothing",
|
|
32995
32995
|
"config.datasetSource picks the ENTITY: \"product\" for a product list, \"category\" for a collection list. It is what derives the node's binding target (product::product_list / category::collection_list)",
|
|
32996
|
-
"Product lists: config.collectionType \"all_products\" renders the whole catalog, \"collection\" renders the one named by config.collectionId, \"related\" renders the PAGE PRODUCT's neighbours — the products sharing one of its collections, minus itself",
|
|
32996
|
+
"Product lists: config.collectionType \"page_collection\" renders THE COLLECTION THE PAGE IS (the one /collections/{slug} named) so a single collection template serves every collection, \"all_products\" renders the whole catalog and narrows to the page collection on a collection template, \"collection\" renders the one named by config.collectionId, \"related\" renders the PAGE PRODUCT's neighbours — the products sharing one of its collections, minus itself",
|
|
32997
32997
|
"Use \"related\" for a cross-sell shelf on a product page (\"Sản phẩm liên quan\" / \"You may also like\"). It reads no collectionId — its axis is whatever collections the page product is filed under — and it renders nothing on a page that names no product, so pair it with an emptyStateId if the section must never be blank",
|
|
32998
32998
|
"Collection lists: config.collectionListType \"all_collections\" renders every collection; \"custom_collections\" renders specials.collectionIds, in that order",
|
|
32999
32999
|
"config.quantity caps how many records render; config.itemsPerRow is the row size (grid columns / slide page size) and style.gap is the space between cards — both are per-breakpoint",
|
|
@@ -36284,6 +36284,7 @@ export const CONFIG_VALUES = {
|
|
|
36284
36284
|
"all_products",
|
|
36285
36285
|
"collection",
|
|
36286
36286
|
"featured",
|
|
36287
|
+
"page_collection",
|
|
36287
36288
|
"related",
|
|
36288
36289
|
"slot"
|
|
36289
36290
|
],
|
package/dist/vision/capture.js
CHANGED
|
@@ -22,7 +22,24 @@ function capturePage(limits) {
|
|
|
22
22
|
if (cs.display === 'none' || cs.visibility === 'hidden' || cs.opacity === '0')
|
|
23
23
|
return false;
|
|
24
24
|
const r = el.getBoundingClientRect();
|
|
25
|
-
|
|
25
|
+
if (r.width > 0 && r.height > 0)
|
|
26
|
+
return true;
|
|
27
|
+
// A ZERO BOX MEANS "HIDDEN" ONLY FOR AN ELEMENT THAT SIZES ITSELF FROM ITS
|
|
28
|
+
// CONTENT. A `<video>` sizes itself from its MEDIA, and a `poster` is what
|
|
29
|
+
// it measures before any media loads — so a video whose poster has not
|
|
30
|
+
// resolved measures 0×0 while being perfectly present, and Chrome only
|
|
31
|
+
// falls back to 300×150 once that load has actually failed.
|
|
32
|
+
//
|
|
33
|
+
// MEASURED: `<video src poster>` with no explicit width/height was dropped
|
|
34
|
+
// with nothing but a `hidden` count, while the identical element carrying
|
|
35
|
+
// `width`/`height` — or carrying NO poster — came through. So the one video
|
|
36
|
+
// most worth importing, the one with a still frame on it, was the one that
|
|
37
|
+
// disappeared, and only on the pages slow enough for the race to be lost.
|
|
38
|
+
//
|
|
39
|
+
// Narrow on purpose: a `<video>` alone, and only when it names something to
|
|
40
|
+
// play or to show. An empty `<video></video>` is a genuinely empty box and
|
|
41
|
+
// goes on being skipped.
|
|
42
|
+
return el.tagName === 'VIDEO' && !!(el.getAttribute('src') || el.getAttribute('poster'));
|
|
26
43
|
};
|
|
27
44
|
const clean = (s) => (s ?? '').replace(/\s+/g, ' ').trim();
|
|
28
45
|
// ABSOLUTE where it can be, RAW where it cannot. `new URL(rel, base)` throws
|
package/dist/vision/shoot.js
CHANGED
|
@@ -250,6 +250,42 @@ async function settleLazyImages(page) {
|
|
|
250
250
|
.waitForFunction(() => [...document.querySelectorAll('img')].every((i) => i.complete === true), undefined, { timeout: 3_000 })
|
|
251
251
|
.catch(() => { });
|
|
252
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* SETTLE EVERY ENTRANCE ANIMATION, or photograph a band that is not there.
|
|
255
|
+
*
|
|
256
|
+
* A still picture wants the page a visitor ends up looking at, and the settled
|
|
257
|
+
* state of every entrance animation is "visible". Two ways a correct page
|
|
258
|
+
* photographs BLANK without this, both of them new since the platform shipped
|
|
259
|
+
* 46 effects and a scroll trigger, and both silent:
|
|
260
|
+
*
|
|
261
|
+
* - `trigger: "view"` compiles to `animation-timeline: view()`, whose progress
|
|
262
|
+
* is a function of where the element sits in the scrollport. The walk above
|
|
263
|
+
* scrolls and then RETURNS TO THE TOP, so every revealed section is back at
|
|
264
|
+
* its `from` keyframe — `opacity: 0` — at the moment the shutter opens.
|
|
265
|
+
* MEASURED: a four-band page photographed with the third band entirely
|
|
266
|
+
* empty, the other three correct.
|
|
267
|
+
* - `fill: both` applies the `from` keyframe during a `delay`, so a section
|
|
268
|
+
* with a delay long enough to outlast the settle photographs blank too.
|
|
269
|
+
*
|
|
270
|
+
* The honest reading of either picture is "this band is broken", which sends
|
|
271
|
+
* the caller to fix a page that works — the same failure the lazy-image walk
|
|
272
|
+
* above exists to prevent, arriving by a different route that the walk cannot
|
|
273
|
+
* fix because the walk's own return to the top is what causes it.
|
|
274
|
+
*
|
|
275
|
+
* `animation: none` rather than forcing the timeline to `auto`: that one
|
|
276
|
+
* RESTARTS the animation against the document timeline and the shot catches it
|
|
277
|
+
* mid-flight (measured at opacity 0.317). Removing the animation drops the
|
|
278
|
+
* element to its own declared style, which for every entrance effect here is
|
|
279
|
+
* exactly the end state — measured back at opacity 1, with the band's text on
|
|
280
|
+
* screen.
|
|
281
|
+
*
|
|
282
|
+
* Best-effort: a page that refuses a stylesheet is still worth photographing.
|
|
283
|
+
*/
|
|
284
|
+
async function settleAnimations(page) {
|
|
285
|
+
await page
|
|
286
|
+
.addStyleTag({ content: '*,*::before,*::after{animation:none !important}' })
|
|
287
|
+
.catch(() => { });
|
|
288
|
+
}
|
|
253
289
|
async function shootOne(page, url, width, format, opts) {
|
|
254
290
|
// LOAD, then a BOUNDED settle — never `networkidle` alone.
|
|
255
291
|
//
|
|
@@ -287,6 +323,7 @@ async function shootOne(page, url, width, format, opts) {
|
|
|
287
323
|
}
|
|
288
324
|
await settleDom(page);
|
|
289
325
|
await settleLazyImages(page);
|
|
326
|
+
await settleAnimations(page);
|
|
290
327
|
// A RENDERED page carries its node ids as the HTML `id` attribute — not as
|
|
291
328
|
// `data-node-id`, which is the editor CANVAS's hook and never reaches the
|
|
292
329
|
// renderer. Selecting the canvas attribute here returned an empty box list
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.38.
|
|
3
|
+
"version": "0.38.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",
|
|
@@ -28,6 +28,7 @@
|
|
|
28
28
|
"start": "node dist/index.js",
|
|
29
29
|
"smoke": "node dist/smoke.js",
|
|
30
30
|
"test": "vitest run",
|
|
31
|
+
"test:browser": "SB_BROWSER_TEST=1 vitest run",
|
|
31
32
|
"codegen": "tsx scripts/gen-catalog.ts",
|
|
32
33
|
"prepublishOnly": "npm run build && npm run smoke",
|
|
33
34
|
"release": "node scripts/release.mjs",
|