sbuilder-mcp 0.9.0 → 0.9.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 +13 -0
- package/CHANGELOG.vi.md +13 -0
- package/dist/domains/site/builder.js +7 -1
- package/dist/domains/site/sticky.js +40 -0
- package/dist/tools/live.js +21 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,19 @@ 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.9.2] - 2026-09-09
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- sb_look now reports `stuck_note` once per process when the page pins something (`position: sticky` or `fixed`), explaining that a screenshot cannot show whether the element is actually stuck and pointing at a browser server (Playwright MCP or Chrome DevTools MCP) to scroll and check for the `wb-stuck` class.
|
|
13
|
+
|
|
14
|
+
### Internal
|
|
15
|
+
- docs/tools.md and the sbuilder-site-design skill now document which of the other MCP servers (Figma, Google Stitch, Chrome DevTools, Playwright) answers which question, alongside sb_look, covering six cases a screenshot alone cannot settle: a sticky header actually engaging, a style that "did not apply", a cart drawer's click trigger, a checkout submission, an entity template previewed with a real record, and layout stability (CLS).
|
|
16
|
+
|
|
17
|
+
## [0.9.1] - 2026-09-09
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- sb_set now refuses a `config.stuckAfter` write the renderer would drop on the floor — a negative, non-finite, or empty value, or one set on a node that cannot pin — instead of silently storing, saving, and publishing a threshold the runtime island never reads and quietly falling back to its automatic answer.
|
|
21
|
+
|
|
9
22
|
## [0.9.0] - 2026-09-08
|
|
10
23
|
|
|
11
24
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,19 @@ 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.9.2] - 2026-09-09
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- sb_look giờ báo cáo `stuck_note` một lần mỗi tiến trình khi trang có ghim một phần tử (`position: sticky` hoặc `fixed`), giải thích rằng một ảnh chụp màn hình không thể cho biết phần tử đó có thực sự bị "stuck" hay không, và chỉ ra một trình duyệt server (Playwright MCP hoặc Chrome DevTools MCP) để cuộn trang và kiểm tra class `wb-stuck`.
|
|
13
|
+
|
|
14
|
+
### Internal
|
|
15
|
+
- docs/tools.md và skill sbuilder-site-design giờ ghi lại server MCP nào (Figma, Google Stitch, Chrome DevTools, Playwright) trả lời câu hỏi nào, bên cạnh sb_look, bao quát sáu trường hợp mà chỉ riêng ảnh chụp màn hình không thể trả lời: một header sticky có thực sự ghim hay không, một style "không được áp dụng", nút bấm mở giỏ hàng, việc submit checkout, xem trước một template entity với dữ liệu thật, và độ ổn định bố cục (CLS).
|
|
16
|
+
|
|
17
|
+
## [0.9.1] - 2026-09-09
|
|
18
|
+
|
|
19
|
+
### Fixed
|
|
20
|
+
- sb_set giờ từ chối ghi `config.stuckAfter` nếu giá trị sẽ bị renderer âm thầm bỏ qua — số âm, không hữu hạn, rỗng, hoặc được đặt trên một node không thể ghim — thay vì lưu, save rồi publish một ngưỡng mà runtime island không bao giờ đọc và lặng lẽ quay về đáp án tự động của nó.
|
|
21
|
+
|
|
9
22
|
## [0.9.0] - 2026-09-08
|
|
10
23
|
|
|
11
24
|
### Added
|
|
@@ -2,7 +2,7 @@ import { isOverlay, subtreeIds, ancestors, appBlockRoot, SPEC_GLOBAL_ID, SPEC_GL
|
|
|
2
2
|
import { ELEMENTS, ELEMENT_SEEDS, SATELLITE_RULES } from '../../catalog/elements.generated.js';
|
|
3
3
|
import { bindingsForConfig, createNode, mintSatellites } from './node.js';
|
|
4
4
|
import { refuseSecondTemplate } from './traps.js';
|
|
5
|
-
import { STUCK_STATE, refuseStuckConfig, requireStuckHost, stickySeeds } from './sticky.js';
|
|
5
|
+
import { STUCK_STATE, refuseStuckAfter, refuseStuckConfig, requireStuckHost, stickySeeds, } from './sticky.js';
|
|
6
6
|
import { genId } from './ids.js';
|
|
7
7
|
/** Append sentinel: splice clamps a too-large index, and `isSyncablePatch`
|
|
8
8
|
* deliberately allows one — an append is a legitimate thing to describe. */
|
|
@@ -182,6 +182,12 @@ export function setKeys(doc, id, keys, opts) {
|
|
|
182
182
|
// the same silent shape as an unbound element, which cost a whole page of
|
|
183
183
|
// "$0.00" cards to find once.
|
|
184
184
|
const rebind = namespace === 'config' ? rebindPatch(doc, id, keys) : null;
|
|
185
|
+
// The scroll THRESHOLD is a plain config key, and the renderer drops it in two
|
|
186
|
+
// different ways without a word — see sticky.ts. Checked here rather than in
|
|
187
|
+
// the state branch because it is written on the node that PINS, at base or at
|
|
188
|
+
// a breakpoint, never inside a stuck slot.
|
|
189
|
+
if (namespace === 'config')
|
|
190
|
+
refuseStuckAfter(doc.doc, id, keys);
|
|
185
191
|
if (namespace === 'specials') {
|
|
186
192
|
refuseComposedStamp(keys);
|
|
187
193
|
// `specials` is content and identity, base-only by definition, and states
|
|
@@ -36,6 +36,17 @@ import { ancestors } from '../../core/tree.js';
|
|
|
36
36
|
*/
|
|
37
37
|
/** The state key, in `node.states` and `responsive[bp].states`. */
|
|
38
38
|
export const STUCK_STATE = 'stuck';
|
|
39
|
+
/**
|
|
40
|
+
* The config key holding "pinned from WHEN?", in px of page scroll.
|
|
41
|
+
*
|
|
42
|
+
* It reaches the runtime island as a CSS custom property (`--wb-stuck-after`)
|
|
43
|
+
* rather than through `wb:props`, because it is per breakpoint and the cascade
|
|
44
|
+
* is what picks the right number at the right width. Unset is the common case:
|
|
45
|
+
* the island then decides from the element itself — a sticky one the moment it
|
|
46
|
+
* is held away from its place in the flow, a fixed one the moment the page
|
|
47
|
+
* scrolls past where it would have sat.
|
|
48
|
+
*/
|
|
49
|
+
export const STUCK_AFTER = 'stuckAfter';
|
|
39
50
|
/**
|
|
40
51
|
* The positions that HAVE a pinned moment.
|
|
41
52
|
*
|
|
@@ -206,3 +217,32 @@ export function stickyWarning(doc, id, bp) {
|
|
|
206
217
|
`scrolls. The node will not move. Clear overflowX/overflowY on ${blocker}, or pin a node ` +
|
|
207
218
|
'outside it.');
|
|
208
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* Refuse a `stuckAfter` that the renderer would drop on the floor.
|
|
222
|
+
*
|
|
223
|
+
* `stuckAfterCss` emits the custom property only for a node that CAN pin and
|
|
224
|
+
* that carries a usable number, and returns an empty string otherwise — so both
|
|
225
|
+
* mistakes here are stored, saved, published and read by nothing. The value
|
|
226
|
+
* rules are the platform's own: zero is honoured ("as soon as the page moves at
|
|
227
|
+
* all" is a real answer, and dropping it would silently fall back to the
|
|
228
|
+
* automatic one); negative and non-finite are not, because no scroll position
|
|
229
|
+
* can satisfy them.
|
|
230
|
+
*/
|
|
231
|
+
export function refuseStuckAfter(doc, id, keys) {
|
|
232
|
+
if (!(STUCK_AFTER in keys))
|
|
233
|
+
return;
|
|
234
|
+
const raw = keys[STUCK_AFTER];
|
|
235
|
+
const n = typeof raw === 'number' ? raw : Number(raw);
|
|
236
|
+
if (raw == null || raw === '' || !Number.isFinite(n) || n < 0) {
|
|
237
|
+
throw new Error(`sbuilder: config.${STUCK_AFTER} is px of page scroll — a finite number, zero or more ` +
|
|
238
|
+
`(0 means "as soon as the page moves at all"). ${JSON.stringify(raw)} is dropped by ` +
|
|
239
|
+
"the renderer, which then falls back to the island's automatic answer without saying " +
|
|
240
|
+
'so. To go back to automatic, remove the key.');
|
|
241
|
+
}
|
|
242
|
+
if (!isPinnedNode(doc.nodes[id])) {
|
|
243
|
+
throw new Error(`sbuilder: config.${STUCK_AFTER} says WHEN a pinned element counts as stuck, and ${id} ` +
|
|
244
|
+
'cannot pin — the renderer emits the threshold only for a node that can ' +
|
|
245
|
+
'(stickyState.ts stuckAfterCss), so this would be stored and read by nothing. ' +
|
|
246
|
+
`Pin it first: sb_set ${id} style { position: "sticky" }.`);
|
|
247
|
+
}
|
|
248
|
+
}
|
package/dist/tools/live.js
CHANGED
|
@@ -7,6 +7,7 @@ import { uploadMedia } from '../transport/media.js';
|
|
|
7
7
|
import { request } from '../transport/http.js';
|
|
8
8
|
import { shoot, DEFAULT_WIDTHS } from '../vision/shoot.js';
|
|
9
9
|
import { measure, MEASURE_NOTICE } from '../vision/measure.js';
|
|
10
|
+
import { isPinnedNode } from '../domains/site/sticky.js';
|
|
10
11
|
import { compactFindings } from '../domains/site/findings.js';
|
|
11
12
|
import { reviewField } from './page.js';
|
|
12
13
|
import { boxesForResponse, BOXES_FORMAT } from '../vision/boxes.js';
|
|
@@ -340,10 +341,30 @@ export function registerLiveTools(server, ctx, session) {
|
|
|
340
341
|
'("Color / Size", "Red / S") rather than the product\'s own. That is the preview, ' +
|
|
341
342
|
'not the page. Pass a published storefront URL as `url` to judge a template.')
|
|
342
343
|
: undefined;
|
|
344
|
+
// A STILL PICTURE CANNOT SHOW A PINNED ELEMENT ENGAGING, and this is the
|
|
345
|
+
// one tool a caller would expect to. `position: sticky` looks identical at
|
|
346
|
+
// rest and while stuck — that is the whole reason the platform needs a
|
|
347
|
+
// runtime class for it — so a page carrying a pinned node has a look this
|
|
348
|
+
// tool is structurally unable to photograph, however many widths it shoots.
|
|
349
|
+
//
|
|
350
|
+
// Gated on the document actually carrying one, and said once, for the same
|
|
351
|
+
// reason `preview_note` is: a directive that fires on pages it cannot
|
|
352
|
+
// apply to is noise, and noise is what makes the real notes unread.
|
|
353
|
+
const pinned = Object.values(session.current().doc.nodes).some((n) => isPinnedNode(n));
|
|
354
|
+
const stuckNote = pinned
|
|
355
|
+
? ctx.notices.once('stuck-scroll', 'This page pins something (position sticky/fixed). A screenshot is ONE scroll ' +
|
|
356
|
+
'position, so nothing here can show whether it engages or what it looks like ' +
|
|
357
|
+
'once it does — the platform styles that moment through a class a runtime island ' +
|
|
358
|
+
'toggles, "wb-stuck", and CSS alone cannot express it. To check: open the ' +
|
|
359
|
+
'PUBLISHED page in a browser server (Playwright or Chrome DevTools MCP), scroll, ' +
|
|
360
|
+
'and read classList for "wb-stuck". If it never appears, every stuck override on ' +
|
|
361
|
+
'the page is stored and never painted.')
|
|
362
|
+
: undefined;
|
|
343
363
|
return images(shots.map((s) => ({ dataBase64: s.imageBase64, mimeType: s.mimeType })), {
|
|
344
364
|
widths: shots.map((s) => s.width),
|
|
345
365
|
...(url ? { shot: url } : {}),
|
|
346
366
|
...(previewNote ? { preview_note: previewNote } : {}),
|
|
367
|
+
...(stuckNote ? { stuck_note: stuckNote } : {}),
|
|
347
368
|
...(node_id ? { framed: node_id } : {}),
|
|
348
369
|
...(with_boxes === false
|
|
349
370
|
? {}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.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",
|