sbuilder-mcp 0.17.0 → 0.17.1
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 +7 -0
- package/CHANGELOG.vi.md +7 -0
- package/dist/core/patch.js +32 -2
- package/dist/vision/capture.js +46 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,13 @@ 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.17.1] - 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- sb_add and sb_import no longer store a nested node's children twice: a patch batch that gets applied more than once (as every write already is, to validate it before it lands) mutated itself on the first pass by carrying an added node by reference, so the second pass re-inserted its children into a node that already held them.
|
|
13
|
+
- sb_import and sb_import_site now cap a flattened row at 12 columns and treat a grid container as wrapping rather than single-line, so a page whose content wrapper is a CSS grid (a documentation site, for example) no longer imports as one row of hundreds of slivered columns.
|
|
14
|
+
- sb_import and sb_import_site now capture a `<pre>`/`<code>` block as one node instead of one node per syntax-highlighting `<span>`, so a code sample no longer arrives broken into dozens of single-token fragments.
|
|
15
|
+
|
|
9
16
|
## [0.17.0] - 2026-09-10
|
|
10
17
|
|
|
11
18
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,13 @@ 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.17.1] - 2026-09-10
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- sb_add và sb_import giờ không còn lưu nhân đôi các con của một node lồng nhau: một batch patch bị áp dụng nhiều hơn một lần (như mọi lần ghi vẫn vậy, để kiểm tra trước khi áp thật) trước đây tự làm thay đổi chính nó ngay ở lượt đầu vì mang theo một node vừa thêm bằng tham chiếu, khiến lượt thứ hai chèn lại các con vào một node đã sẵn có chúng.
|
|
13
|
+
- sb_import và sb_import_site giờ giới hạn một row được làm phẳng ở tối đa 12 cột và coi container dạng grid là tự xuống dòng thay vì nằm trên một hàng, để một trang có content wrapper là CSS grid (chẳng hạn một trang tài liệu kỹ thuật) không còn bị import thành một row hàng trăm cột mỏng dính.
|
|
14
|
+
- sb_import và sb_import_site giờ lấy một khối `<pre>`/`<code>` thành một node duy nhất thay vì một node cho mỗi `<span>` tô cú pháp, để một đoạn code không còn bị vỡ thành hàng chục mảnh một-token-một-node.
|
|
15
|
+
|
|
9
16
|
## [0.17.0] - 2026-09-10
|
|
10
17
|
|
|
11
18
|
### Added
|
package/dist/core/patch.js
CHANGED
|
@@ -78,6 +78,16 @@ function parentOf(state, path) {
|
|
|
78
78
|
* half-edited with nothing anywhere to say so — which is the exact failure shape
|
|
79
79
|
* this whole repo exists to rule out.
|
|
80
80
|
*/
|
|
81
|
+
/**
|
|
82
|
+
* A patch's value, detached from whatever the caller still holds.
|
|
83
|
+
*
|
|
84
|
+
* Primitives are returned as they are — a style key is a string, and cloning
|
|
85
|
+
* one on every `sb_set` would be pure cost. Only a structure can be aliased,
|
|
86
|
+
* and only an alias can be mutated behind the batch's back.
|
|
87
|
+
*/
|
|
88
|
+
function clone(value) {
|
|
89
|
+
return value !== null && typeof value === 'object' ? structuredClone(value) : value;
|
|
90
|
+
}
|
|
81
91
|
export function applyPatches(state, patches) {
|
|
82
92
|
for (const p of patches) {
|
|
83
93
|
if (!isSyncablePatch(p)) {
|
|
@@ -86,7 +96,27 @@ export function applyPatches(state, patches) {
|
|
|
86
96
|
const { holder, key } = parentOf(state, p.path);
|
|
87
97
|
switch (p.op) {
|
|
88
98
|
case 'set':
|
|
89
|
-
|
|
99
|
+
// THE VALUE IS NEVER ALIASED INTO THE DOCUMENT.
|
|
100
|
+
//
|
|
101
|
+
// `addSubtree` builds a node, emits `set nodes/<id>` carrying THAT
|
|
102
|
+
// object, and then emits `insert` patches that push child ids into
|
|
103
|
+
// `data.nodes`. Assigning the reference means the first apply MUTATES
|
|
104
|
+
// the patch's own value, so the batch is no longer the thing it was: a
|
|
105
|
+
// second apply re-establishes a node that already holds its children and
|
|
106
|
+
// then inserts them again.
|
|
107
|
+
//
|
|
108
|
+
// Applying a batch twice is not hypothetical — `applyAndSave` does it by
|
|
109
|
+
// design, once through `preview` to judge the write and once for real.
|
|
110
|
+
// Between v0.16.1 (which introduced that check) and this fix, EVERY
|
|
111
|
+
// nested `sb_add` stored each child twice, and `sb_import` three times.
|
|
112
|
+
// It is silent: the tree is well-formed, every id resolves, the save is
|
|
113
|
+
// accepted, and the page simply renders its content twice. Measured on
|
|
114
|
+
// a real import — 106 of 231 containers listing one child id three
|
|
115
|
+
// times.
|
|
116
|
+
//
|
|
117
|
+
// A copy makes the batch idempotent for this shape: the re-`set` puts a
|
|
118
|
+
// pristine node back, and the inserts that follow rebuild the same list.
|
|
119
|
+
holder[key] = clone(p.value);
|
|
90
120
|
break;
|
|
91
121
|
case 'unset':
|
|
92
122
|
delete holder[key];
|
|
@@ -95,7 +125,7 @@ export function applyPatches(state, patches) {
|
|
|
95
125
|
const arr = holder[key];
|
|
96
126
|
if (!Array.isArray(arr))
|
|
97
127
|
break;
|
|
98
|
-
arr.splice(p.index, 0, p.value);
|
|
128
|
+
arr.splice(p.index, 0, clone(p.value));
|
|
99
129
|
break;
|
|
100
130
|
}
|
|
101
131
|
case 'remove': {
|
package/dist/vision/capture.js
CHANGED
|
@@ -183,6 +183,25 @@ function capturePage(limits) {
|
|
|
183
183
|
taken.nodes++;
|
|
184
184
|
return [{ kind: 'list', items }];
|
|
185
185
|
}
|
|
186
|
+
// A CODE BLOCK IS ONE THING, and walking into it produces rubble.
|
|
187
|
+
//
|
|
188
|
+
// Every syntax highlighter wraps each token in its own <span>, so the leaf
|
|
189
|
+
// walk took them one at a time: a twenty-line JSON config arrived as forty
|
|
190
|
+
// separate text nodes — `{`, `"mcpServers"`, `: {` — each its own block on
|
|
191
|
+
// its own line. Measured on a real import; it also ate forty of the node
|
|
192
|
+
// budget to say what one node says.
|
|
193
|
+
//
|
|
194
|
+
// Whitespace is collapsed like any other text because this platform has no
|
|
195
|
+
// code element to preserve it in — the six kinds are what can cross — so
|
|
196
|
+
// the honest translation is one paragraph the merchant can then restyle,
|
|
197
|
+
// not a shredded imitation of a listing.
|
|
198
|
+
if (tag === 'PRE' || tag === 'CODE') {
|
|
199
|
+
const text = clean(el.textContent);
|
|
200
|
+
if (!text)
|
|
201
|
+
return [];
|
|
202
|
+
taken.nodes++;
|
|
203
|
+
return [{ kind: 'text', text }];
|
|
204
|
+
}
|
|
186
205
|
if (tag === 'P' || tag === 'BLOCKQUOTE') {
|
|
187
206
|
const text = clean(el.textContent);
|
|
188
207
|
if (!text)
|
|
@@ -197,12 +216,34 @@ function capturePage(limits) {
|
|
|
197
216
|
cs.display === 'inline-flex' || cs.display === 'inline-grid';
|
|
198
217
|
// A ROW is worth keeping; a column is what the page already is, so
|
|
199
218
|
// wrapping one in a group would add a level that renders identically.
|
|
200
|
-
const
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
219
|
+
const grid = cs.display.indexOf('grid') >= 0;
|
|
220
|
+
const row = grid || cs.flexDirection === 'row' || cs.flexDirection === 'row-reverse';
|
|
221
|
+
// A ROW OF 279 IS NOT A ROW.
|
|
222
|
+
//
|
|
223
|
+
// Measured on a real import of a documentation site: its content wrapper
|
|
224
|
+
// is a GRID, every grid was read as a single row, and the whole page came
|
|
225
|
+
// back as one flex row of 279 columns — each column a sliver, every line
|
|
226
|
+
// of prose broken to one word, and 80 nodes hanging past the viewport.
|
|
227
|
+
// The page was structurally perfect and visually destroyed.
|
|
228
|
+
//
|
|
229
|
+
// A design row is a feature trio, a card shelf, a logo wall: a handful of
|
|
230
|
+
// columns, chosen. Past that the container is not arranging things side
|
|
231
|
+
// by side, it is the page's own content column and the browser is
|
|
232
|
+
// wrapping it — so the honest translation is the stack it already reads
|
|
233
|
+
// as. Twelve is above any real row seen here and far below a content
|
|
234
|
+
// grid.
|
|
235
|
+
const ROW_MAX = 12;
|
|
236
|
+
if (lays && row && kids.length >= 2 && kids.length <= ROW_MAX) {
|
|
204
237
|
taken.nodes++;
|
|
205
|
-
return [{
|
|
238
|
+
return [{
|
|
239
|
+
kind: 'group',
|
|
240
|
+
direction: 'row',
|
|
241
|
+
// A GRID ALWAYS WRAPS — that is what a grid IS — and `flexWrap` reads
|
|
242
|
+
// `nowrap` on one because the property does not apply. Carrying that
|
|
243
|
+
// literally gave the columns nowhere to go at any width.
|
|
244
|
+
wrap: grid || cs.flexWrap === 'wrap',
|
|
245
|
+
children: kids,
|
|
246
|
+
}];
|
|
206
247
|
}
|
|
207
248
|
return kids;
|
|
208
249
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.17.
|
|
3
|
+
"version": "0.17.1",
|
|
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",
|