sbuilder-mcp 0.46.1 → 0.46.3

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,16 @@ 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.46.3] - 2026-09-14
10
+
11
+ ### Fixed
12
+ - The layout-overlap check now reads each element's computed position before reporting a collision, so an absolutely-, fixed-, or sticky-positioned decoration sitting over a band — a quickview badge, a sale ribbon, a wishlist heart — is no longer flagged as a defect; a relative element overlapping a sibling still is, since it still takes up space in the flow.
13
+
14
+ ## [0.46.2] - 2026-09-14
15
+
16
+ ### Fixed
17
+ - A page save that was refused for a reason it did not cause used to still apply and publish its patches locally before the refusal surfaced, so the next successful save on that page silently committed the earlier refused edit as well; a save is now refused before anything is applied whenever it would leave the page still unstorable, while a save that repairs the page's pre-existing problems still goes through.
18
+
9
19
  ## [0.46.1] - 2026-09-14
10
20
 
11
21
  ### Fixed
package/CHANGELOG.vi.md CHANGED
@@ -6,6 +6,16 @@ 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.46.3] - 2026-09-14
10
+
11
+ ### Fixed
12
+ - Kiểm tra chồng lấn bố cục giờ đọc thuộc tính position đã tính toán của từng phần tử trước khi báo va chạm, nên một chi tiết trang trí đặt ở vị trí absolute, fixed hoặc sticky đè lên một khối — huy hiệu quickview, ruy băng khuyến mãi, trái tim yêu thích — không còn bị báo là lỗi; một phần tử relative đè lên phần tử anh em bên cạnh thì vẫn bị báo, vì nó vẫn chiếm chỗ trong luồng bố cục.
13
+
14
+ ## [0.46.2] - 2026-09-14
15
+
16
+ ### Fixed
17
+ - Một lần lưu trang bị từ chối vì lý do nó không gây ra trước đây vẫn áp dụng và publish các patch cục bộ trước khi việc từ chối được báo, nên lần lưu thành công tiếp theo trên trang đó âm thầm gộp luôn cả chỉnh sửa đã bị từ chối trước đó; giờ đây việc lưu bị từ chối trước khi áp dụng bất cứ gì mỗi khi nó khiến trang vẫn không thể lưu được, còn một lần lưu khắc phục được vấn đề đã tồn tại sẵn của trang thì vẫn được thực hiện bình thường.
18
+
9
19
  ## [0.46.1] - 2026-09-14
10
20
 
11
21
  ### Fixed
@@ -127,12 +127,35 @@ export class PageSession {
127
127
  async applyAndSave(patches) {
128
128
  const d = this.current();
129
129
  const before = new Set(validateForSave(d));
130
- const introduced = validateForSave(d.preview(patches)).filter((p) => !before.has(p));
130
+ const after = validateForSave(d.preview(patches));
131
+ const introduced = after.filter((p) => !before.has(p));
131
132
  if (introduced.length > 0) {
132
133
  throw new Error(`sbuilder: refusing to save — ${introduced.join(' ')}`);
133
134
  }
135
+ // AND REFUSE BEFORE APPLYING WHEN THE PAGE STILL CANNOT BE STORED.
136
+ //
137
+ // This used to apply, publish, and only then let `save()` discover the
138
+ // inherited damage — which left the patches in the draft AND on the wire,
139
+ // so the next save that did pass committed an edit the caller had been told
140
+ // was refused. MEASURED on a live page: removing an empty section was
141
+ // refused over four orphans it had nothing to do with, and the next
142
+ // successful remove then stored a document missing FIVE nodes — the four it
143
+ // was asked for, plus the section whose removal had been refused.
144
+ //
145
+ // `6980ebb` named this exact defect ("a refused write left its patches in
146
+ // the draft and blamed the next command") and closed half of it: it stopped
147
+ // the pre-check refusing INHERITED damage, and left the inherited damage
148
+ // being discovered one step too late.
149
+ //
150
+ // An edit that REPAIRS the page still passes, which is what keeps a broken
151
+ // page editable: `after` is the state this write would leave behind, so a
152
+ // write that clears the damage leaves it empty and goes through.
153
+ if (after.length > 0) {
154
+ throw new Error(`sbuilder: refusing to save — this page already cannot be stored, and this write does ` +
155
+ `not repair it, so nothing was applied: ${after.join(' ')}`);
156
+ }
134
157
  this.applyAndPublish(patches);
135
- await this.save();
158
+ await this.save(before);
136
159
  }
137
160
  applyRemote(patches) {
138
161
  this.doc?.apply(patches);
@@ -182,7 +205,7 @@ export class PageSession {
182
205
  * autosave later means the agent has spent the interval editing a tree nobody
183
206
  * will ever store.
184
207
  */
185
- async save() {
208
+ async save(inherited = new Set()) {
186
209
  if (this.stale) {
187
210
  // THE YIELD RULE. The room moved in a way this client cannot reconcile, so
188
211
  // it must not write its copy over whatever is there now. Re-pull, and make
@@ -197,7 +220,28 @@ export class PageSession {
197
220
  const d = this.current();
198
221
  if (d.rev === this.savedRev)
199
222
  return;
200
- const problems = validateForSave(d);
223
+ // THE SAME BASELINE `applyAndSave` USES, and the two gates disagreeing is
224
+ // precisely the defect `6980ebb` named and half-closed.
225
+ //
226
+ // That commit — "a refused write left its patches in the draft and blamed
227
+ // the next command" — taught `applyAndSave` to refuse only what an edit
228
+ // INTRODUCES, so a page that arrived broken can still be edited. It left
229
+ // this gate re-deriving the whole list from scratch, so the order became:
230
+ // applyAndSave passes, `applyAndPublish` APPLIES THE PATCHES LOCALLY, and
231
+ // then this throws over a problem that was already there. The patches stay
232
+ // in the draft, and the next save that does pass commits them.
233
+ //
234
+ // MEASURED ON A LIVE PAGE. Removing an empty section was refused here over
235
+ // four orphans it had nothing to do with; the next successful remove then
236
+ // saved a document missing FIVE nodes — the four it was asked for and the
237
+ // section whose removal had been refused. `rev: 3` after one command is the
238
+ // same fact from the other side.
239
+ //
240
+ // Rolling back after a failed save was the other candidate and is worse:
241
+ // `applyAndPublish` has already broadcast the patches over the live socket,
242
+ // so a local rollback would leave every watching editor showing an edit
243
+ // this session no longer has.
244
+ const problems = validateForSave(d).filter((p) => !inherited.has(p));
201
245
  if (problems.length > 0) {
202
246
  throw new Error(`sbuilder: refusing to save — ${problems.join(' ')}`);
203
247
  }
@@ -73,6 +73,24 @@ export function measureShot(shot, skip = new Set()) {
73
73
  continue;
74
74
  if (a.w === 0 || a.h === 0 || b.w === 0 || b.h === 0)
75
75
  continue;
76
+ // AND THE COMMENT ABOVE IS NOW IMPLEMENTED. It has always said that "an
77
+ // absolutely-positioned decoration over a band is a design choice", and
78
+ // nothing checked: `Box` carried no `position`, so the rule could not tell
79
+ // a deliberate overlay from a collision. Every quickview badge, sale
80
+ // ribbon and wishlist heart on a product card is an overlay BY
81
+ // CONSTRUCTION, so this fired on storefronts that were built correctly —
82
+ // measured on a live home page, a `collection-media` reported against the
83
+ // `icon` sitting in its own corner.
84
+ //
85
+ // `static` is the only value that means "in the flow", which is what the
86
+ // rest of this rule is about: two boxes the LAYOUT put on top of each
87
+ // other. `relative` counts as in-flow too — it still takes its space, and
88
+ // a relative element overlapping a sibling is the negative-margin defect
89
+ // this check exists to find.
90
+ if (a.position && a.position !== 'static' && a.position !== 'relative')
91
+ continue;
92
+ if (b.position && b.position !== 'static' && b.position !== 'relative')
93
+ continue;
76
94
  const pairKey = `${a.id}|${b.id}`;
77
95
  if (seen.has(pairKey))
78
96
  continue;
@@ -354,6 +354,7 @@ async function shootOne(page, url, width, format, opts) {
354
354
  h: Math.round(r.height),
355
355
  fontPx: Math.round(parseFloat(cs.fontSize) || 0),
356
356
  hasText: own.length > 0,
357
+ position: cs.position,
357
358
  };
358
359
  })));
359
360
  // ZOOM. A designer does not judge a card by looking at the whole page, and
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sbuilder-mcp",
3
- "version": "0.46.1",
3
+ "version": "0.46.3",
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",