sbuilder-mcp 0.13.1 → 0.14.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 CHANGED
@@ -6,6 +6,11 @@ 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.14.0] - 2026-09-09
10
+
11
+ ### Added
12
+ - sb_review now reports `categoryScope` when a store has more than one product category and none of them is linked to a page of its own, since `/collections/{slug}` then falls back to the category type's default template for every category, and nothing on that shared template narrows the product feed to the category in the URL, so a shopper sees the whole catalogue (or another category's products) no matter which one they open; the finding names the fix — a page per category with its repeater set to `{ "collectionType": "collection", "collectionId": "<category id>" }`, linked with `sb_api_call post:/api/sites/{siteId}/page-links/bulk`.
13
+
9
14
  ## [0.13.1] - 2026-09-09
10
15
 
11
16
  ### Fixed
package/CHANGELOG.vi.md CHANGED
@@ -6,6 +6,11 @@ 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.14.0] - 2026-09-09
10
+
11
+ ### Added
12
+ - sb_review giờ báo cáo `categoryScope` khi một store có nhiều hơn một product category mà không category nào được link tới một page riêng, vì khi đó `/collections/{slug}` sẽ rơi về default template của loại category cho mọi category, và không có gì trên template dùng chung đó thu hẹp product feed theo category trong URL, nên khách hàng sẽ thấy toàn bộ catalogue (hoặc sản phẩm của category khác) dù mở category nào; finding nêu rõ cách khắc phục — mỗi category một page riêng với repeater đặt `{ "collectionType": "collection", "collectionId": "<id category>" }`, liên kết bằng `sb_api_call post:/api/sites/{siteId}/page-links/bulk`.
13
+
9
14
  ## [0.13.1] - 2026-09-09
10
15
 
11
16
  ### Fixed
@@ -24,7 +24,7 @@ export async function gatherReadiness(ctx, siteId, pageNodes) {
24
24
  }
25
25
  };
26
26
  const site = encodeURIComponent(siteId);
27
- const [pageList, gateways, shipping, globals, productList] = await Promise.all([
27
+ const [pageList, gateways, shipping, globals, productList, categoryList, pageLinks] = await Promise.all([
28
28
  get(`/api/sites/${site}/pages`),
29
29
  get(`/api/sites/${site}/payment-gateways`),
30
30
  get(`/api/sites/${site}/shipping-methods`),
@@ -34,6 +34,14 @@ export async function gatherReadiness(ctx, siteId, pageNodes) {
34
34
  // key-only one. A page is enough to tell empty from not; `total` carries the
35
35
  // real count when the platform sends it.
36
36
  get(`/api/sites/${site}/products?limit=200`),
37
+ // THE CATEGORIES, and the pages they point at. `/collections/{slug}` resolves
38
+ // through PublishedForEntity: the category's OWN page when a page-link names
39
+ // one, else the DEFAULT TEMPLATE for the `category` type — and nothing on
40
+ // that shared template narrows the product feed to the category in the URL.
41
+ // So two categories with no page-links means at most one of them can be
42
+ // right, and the rest list the whole catalogue.
43
+ get(`/api/sites/${site}/product-categories`),
44
+ get(`/api/sites/${site}/page-links`),
37
45
  ]);
38
46
  // A gateway counts only when it is BOTH enabled and configured — the editor's
39
47
  // `live` getter also filters by the store's currency, which is a narrowing:
@@ -44,6 +52,12 @@ export async function gatherReadiness(ctx, siteId, pageNodes) {
44
52
  : null;
45
53
  const methods = shipping?.shippingMethods ?? shipping?.methods;
46
54
  const shippingMethods = Array.isArray(methods) ? methods.length : null;
55
+ const cats = categoryList?.categories ?? categoryList?.productCategories;
56
+ const categories = Array.isArray(cats) ? (categoryList?.total ?? cats.length) : null;
57
+ const links = pageLinks?.links ?? pageLinks?.pageLinks;
58
+ const categoryPageLinks = Array.isArray(links)
59
+ ? links.filter((l) => l?.linkType === 'productCategory').length
60
+ : null;
47
61
  const globalNodes = globals?.globalSections
48
62
  ? globals.globalSections.flatMap((g) => Object.values(g.document?.nodes ?? {}))
49
63
  : null;
@@ -64,5 +78,7 @@ export async function gatherReadiness(ctx, siteId, pageNodes) {
64
78
  shippingMethods,
65
79
  pageNodes,
66
80
  globalNodes,
81
+ categories,
82
+ categoryPageLinks,
67
83
  };
68
84
  }
@@ -190,6 +190,39 @@ export function readinessGaps(input) {
190
190
  fix: 'Put a control with the open_cart event in the header global section.',
191
191
  });
192
192
  }
193
+ // EVERY CATEGORY SHOWS EVERY PRODUCT, which is what a shared template does.
194
+ //
195
+ // `/collections/{slug}` resolves through PublishedForEntity: the category's
196
+ // OWN page when a page-link names one, else the DEFAULT TEMPLATE for the
197
+ // `category` type. Nothing on that shared template narrows the product feed to
198
+ // the category in the URL — `entityScope` threads the entity into the article
199
+ // feed for a blogCategory and the review feed for a product, and into nothing
200
+ // at all for a productCategory — so a repeater on `all_products` repeats the
201
+ // whole catalogue, and one on `collection` names ONE fixed id. Either way at
202
+ // most one category can be right.
203
+ //
204
+ // Reported on the COUNTS rather than by reading the template's document: two
205
+ // categories and no page-links is already the defect, whatever the template
206
+ // says, and a document fetch per review to confirm it would cost every store
207
+ // that has this right. Silent when either count could not be read, like every
208
+ // other check here.
209
+ if ((input.categories ?? 0) > 1 && input.categoryPageLinks === 0) {
210
+ gaps.push({
211
+ id: 'categoryScope',
212
+ draft: false,
213
+ problem: `${input.categories} product categories share ONE page — none of them points at a page ` +
214
+ 'of its own, so /collections/{slug} serves the default template for every one. Nothing ' +
215
+ 'on that template narrows the product feed to the category in the URL (the blog twin ' +
216
+ 'auto-scopes; this one does not), so a shopper who picks a category sees the whole ' +
217
+ 'catalogue, or one other category, on all of them.',
218
+ fix: 'Give each category its own page and set its product repeater to config ' +
219
+ '{ "collectionType": "collection", "collectionId": "<that category id>" }, then link ' +
220
+ 'them in one call: sb_api_call post:/api/sites/{siteId}/page-links/bulk with body ' +
221
+ '{ "linkType": "productCategory", "linkIds": ["<category ids>"], "pageId": "<page id>" } ' +
222
+ '— one call per page, since each page is one category. sb_duplicate the template you ' +
223
+ 'already have rather than rebuilding it.',
224
+ });
225
+ }
193
226
  return gaps;
194
227
  }
195
228
  export const READINESS_NOTICE = 'These are STORE gaps, not page defects: each one survives publish without a warning and is ' +
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sbuilder-mcp",
3
- "version": "0.13.1",
3
+ "version": "0.14.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",