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 +12 -0
- package/CHANGELOG.vi.md +12 -0
- package/dist/core/tree.js +12 -0
- package/dist/domains/site/readiness-fetch.js +6 -2
- package/dist/domains/site/readiness.js +3 -2
- package/dist/domains/site/review.js +25 -3
- package/package.json +1 -1
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) =>
|
|
187
|
-
|
|
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
|
-
|
|
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
|
-
|
|
717
|
-
|
|
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.
|
|
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",
|