sbuilder-mcp 0.76.0 → 0.76.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 CHANGED
@@ -6,6 +6,18 @@ 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.76.2] - 2026-09-28
10
+
11
+ ### Fixed
12
+ - `sb_review`'s `productImages` gap now reads "1 active product has no image" instead of "1 active product have no image" when only a single active product is missing `images`.
13
+
14
+ ## [0.76.1] - 2026-09-28
15
+
16
+ ### Fixed
17
+ - `sb_review`'s `unreachablePage` gap now reads a page's `/source` response under its `source.document` envelope instead of a bare `document`, so a link found in another page's body is actually counted; previously every page appeared to carry no links and every candidate page was reported unreachable.
18
+ - `sb_review`'s `no_h1` finding no longer points at a shared header's brand line when that header is composed onto every page; it now recognizes a shared section by either its stored `globalRef` stamp or its composed `globalId` stamp, and fires only on a page's own heading, staying silent when a page has no heading of its own.
19
+ - `sb_review`'s `pageSeo` gap now reads "1 page carries" instead of "1 page carry" when only a single page is missing `settings.title` or `settings.description`.
20
+
9
21
  ## [0.76.0] - 2026-09-28
10
22
 
11
23
  ### Added
package/CHANGELOG.vi.md CHANGED
@@ -6,6 +6,18 @@ 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.76.2] - 2026-09-28
10
+
11
+ ### Fixed
12
+ - Gap `productImages` của `sb_review` giờ hiển thị "1 active product has no image" thay vì "1 active product have no image" khi chỉ có một sản phẩm active thiếu `images`.
13
+
14
+ ## [0.76.1] - 2026-09-28
15
+
16
+ ### Fixed
17
+ - Gap `unreachablePage` của `sb_review` giờ đọc response `/source` của trang theo đúng envelope `source.document` thay vì `document` trần trụi, nên một link tìm thấy trong thân một trang khác giờ được tính đúng; trước đó mọi trang đều bị coi là không mang link nào và mọi trang ứng viên đều bị báo là không thể tới được.
18
+ - Finding `no_h1` của `sb_review` không còn trỏ vào dòng thương hiệu của header dùng chung khi header đó được dựng vào mọi trang; giờ nhận diện một section dùng chung qua cả stamp `globalRef` đã lưu lẫn stamp `globalId` được dựng, và chỉ báo trên heading của chính trang đó, im lặng khi trang không có heading riêng nào.
19
+ - Gap `pageSeo` của `sb_review` giờ hiển thị "1 page carries" thay vì "1 page carry" khi chỉ có một trang thiếu `settings.title` hoặc `settings.description`.
20
+
9
21
  ## [0.76.0] - 2026-09-28
10
22
 
11
23
  ### Added
package/dist/core/tree.js CHANGED
@@ -14,6 +14,18 @@ export const SPEC_GLOBAL_KIND = 'globalKind';
14
14
  export const SPEC_GLOBAL_REV = 'globalRev';
15
15
  export const SPEC_OVERLAY_REV = 'overlayRev';
16
16
  export const SPEC_GLOBAL_REF = 'globalRef';
17
+ /**
18
+ * Is this node a shared section, by EITHER stamp: the stored reference
19
+ * (`globalRef`, what a client writes) or the composed one (`globalId`, what a
20
+ * page read back from the platform carries). A check knowing only one misses
21
+ * the other on every read — measured: a header composed as `globalId` on all
22
+ * six pages of a store, and a rule keyed on `globalRef` treating it as the
23
+ * page's own.
24
+ */
25
+ export function isSharedSection(doc, id) {
26
+ const s = doc.nodes[id]?.specials ?? {};
27
+ return typeof s[SPEC_GLOBAL_ID] === 'string' || typeof s[SPEC_GLOBAL_REF] === 'string';
28
+ }
17
29
  /** The specials stamps a composed APP BLOCK carries (appblocks.go:97-100). */
18
30
  export const SPEC_APP_BLOCK_ID = 'appBlockId';
19
31
  export const SPEC_APP_BLOCK_REF = 'appBlockRef';
@@ -183,8 +183,12 @@ export async function gatherReadiness(ctx, siteId, pageNodes, openPageId) {
183
183
  const others = (input.pages ?? [])
184
184
  .filter((p) => p.status === 'published' && typeof p.id === 'string' && p.id !== openPageId)
185
185
  .slice(0, 40);
186
- const sources = await Promise.all(others.map((p) => get(`/api/sites/${site}/pages/${encodeURIComponent(p.id)}/source`)));
187
- const nodes = sources.flatMap((src) => Object.values(src?.document?.nodes ?? {}));
186
+ const sources = await Promise.all(others.map((p) =>
187
+ // `{ source: { document } }` — the envelope transport/pages.ts loadSource
188
+ // unwraps; read bare, this pass saw no link in any page (measured on a
189
+ // local store, every page's document present and every href missed).
190
+ get(`/api/sites/${site}/pages/${encodeURIComponent(p.id)}/source`)));
191
+ const nodes = sources.flatMap((src) => Object.values(src?.source?.document?.nodes ?? {}));
188
192
  input.menuLinks.hrefs.push(...hrefsIn(nodes));
189
193
  }
190
194
  return input;
@@ -333,7 +333,8 @@ export function readinessGaps(input) {
333
333
  id: 'pageSeo',
334
334
  draft: false,
335
335
  problem: pageCount(bare) +
336
- ' carry no settings.title or settings.description. Each serves the SITE NAME as its ' +
336
+ (bare.length === 1 ? ' carries' : ' carry') +
337
+ ' no settings.title or settings.description. Each serves the SITE NAME as its ' +
337
338
  '<title> and no meta description — a search result cannot tell them apart, and ' +
338
339
  'nothing on the canvas or in a screenshot shows it.',
339
340
  fix: 'PATCH /api/v1/pages/{id} with {settings: {...existing, title, description}} — ' +
@@ -594,7 +595,7 @@ export function readinessGaps(input) {
594
595
  gaps.push({
595
596
  id: 'productImages',
596
597
  draft: false,
597
- problem: `${n} of ${input.products.active} active products have no image, so every card, cart ` +
598
+ problem: `${n} of ${input.products.active} active products ${n === 1 ? 'has' : 'have'} no image, so every card, cart ` +
598
599
  "line and product page shows the platform's grey placeholder where the photo goes.",
599
600
  fix: 'Get a picture into the library (sb_media_upload: a file, a url, or query + pick), then ' +
600
601
  'PUT the whole product back with images: [url, …] — first is the primary. POST ' +
@@ -1,4 +1,4 @@
1
- import { childrenOf, childrenWithSatellites, isOverlay, pageChildren, appBlockRoot, SPEC_GLOBAL_REF, SPEC_APP_BLOCK_REF } from '../../core/tree.js';
1
+ import { childrenOf, childrenWithSatellites, isOverlay, isSharedSection, pageChildren, appBlockRoot, SPEC_GLOBAL_REF, SPEC_APP_BLOCK_REF } from '../../core/tree.js';
2
2
  import { STUCK_STATE, stickyBlockedBy, stuckHostOf, isPinnedNode } from './sticky.js';
3
3
  import { bandOf } from './traps.js';
4
4
  import { HOVER_STATE, hoverHome } from './hover.js';
@@ -704,7 +704,28 @@ opts = {}) {
704
704
  // level but the one a search engine and a screen reader look for first.
705
705
  // Nothing on the canvas shows the tag. Page bands only (`band` excludes the
706
706
  // overlays): a drawer heading is not the page's.
707
+ //
708
+ // NAMED ON THE PAGE'S OWN HEADING, never a shared section's. The draft
709
+ // composes the global header in, so its brand line is the first heading on
710
+ // EVERY page — measured on a local store: one node id reported on six pages,
711
+ // and the fix would have made the site name the h1 everywhere. A shared h1
712
+ // still counts as present (that is a site's own decision); a page whose only
713
+ // headings are shared has none of its own and is left alone.
707
714
  {
715
+ const shared = new Set();
716
+ for (const sectionId of pageChildren(d)) {
717
+ if (!isSharedSection(d, sectionId))
718
+ continue;
719
+ const stack = [sectionId];
720
+ while (stack.length) {
721
+ const id = stack.pop();
722
+ if (shared.has(id))
723
+ continue;
724
+ shared.add(id);
725
+ for (const k of childrenWithSatellites(d, id))
726
+ stack.push(k);
727
+ }
728
+ }
708
729
  const level = (id) => {
709
730
  const n = d.nodes[id];
710
731
  const tag = n.specials?.htmlTag;
@@ -713,8 +734,9 @@ opts = {}) {
713
734
  return n.data.type === 'heading' ? 'h2' : null;
714
735
  };
715
736
  const headings = walkOrder.filter((id) => band.has(id) && level(id) !== null);
716
- if (headings.length > 0 && !headings.some((id) => level(id) === 'h1')) {
717
- const first = headings[0];
737
+ const own = headings.filter((id) => !shared.has(id));
738
+ if (own.length > 0 && !headings.some((id) => level(id) === 'h1')) {
739
+ const first = own[0];
718
740
  out.push({
719
741
  code: 'no_h1',
720
742
  nodeId: first,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sbuilder-mcp",
3
- "version": "0.76.0",
3
+ "version": "0.76.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",