sbuilder-mcp 0.13.0 → 0.13.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 CHANGED
@@ -6,6 +6,12 @@ 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.13.1] - 2026-09-09
10
+
11
+ ### Fixed
12
+ - sb_store's checkout build now seeds a `form:success` event that sends the shopper to `/checkout/complete`, since the form record's own `settings.afterSubmit.action = "redirect"` is stored by the API but carried nowhere by the platform, leaving a completed order on the checkout page with every cart total reading 0.
13
+ - sb_review now reports `order_goes_nowhere` for a checkout-shaped page (a form alongside a cart total) whose form has no navigating `form:success` event, naming the sb_event call that fixes it.
14
+
9
15
  ## [0.13.0] - 2026-09-09
10
16
 
11
17
  ### Added
package/CHANGELOG.vi.md CHANGED
@@ -6,6 +6,12 @@ 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.13.1] - 2026-09-09
10
+
11
+ ### Fixed
12
+ - Việc dựng checkout của sb_store giờ tự gieo một event `form:success` đưa khách hàng tới `/checkout/complete`, vì `settings.afterSubmit.action = "redirect"` của chính form record được API lưu lại nhưng nền tảng không mang nó đi đâu cả, khiến một đơn hàng đã hoàn tất vẫn ở lại trang checkout với mọi tổng giỏ hàng hiện 0.
13
+ - sb_review giờ báo cáo `order_goes_nowhere` cho một trang có dáng checkout (có form và có cart total) mà form của nó không có event `form:success` nào điều hướng đi, nêu rõ lệnh sb_event để khắc phục.
14
+
9
15
  ## [0.13.0] - 2026-09-09
10
16
 
11
17
  ### Added
@@ -60,6 +60,11 @@ export const FIX = {
60
60
  '{ "overflowX": "visible", "overflowY": "visible" } — or move the pinned node outside it. ' +
61
61
  'Sticky resolves against its nearest SCROLLING ancestor, so a clipping one becomes that ' +
62
62
  'ancestor and the node pins inside a box that never scrolls.',
63
+ order_goes_nowhere: 'Send the shopper on: sb_event id "<id>", trigger "form:success", action "go_to_url", ' +
64
+ 'payload { "url": "/checkout/complete" }. That path resolves by page TYPE and serves a ' +
65
+ 'built-in receipt even with no completion page, so it is never a link to a 404. Do NOT ' +
66
+ 'reach for the form record\'s settings.afterSubmit "redirect" — the API stores it and the ' +
67
+ 'platform carries it nowhere.',
63
68
  hover_dead: 'Rewrite it with the same values: sb_set id "<id>", namespace style, state "hover", ' +
64
69
  'keys { … } — read them off the node first, because that one call both writes them where ' +
65
70
  'the renderer looks AND clears the slot nobody reads. Nothing else to do afterwards.',
@@ -225,6 +225,39 @@ export function reviewDesign(doc) {
225
225
  styled.states?.[STUCK_STATE],
226
226
  ...Object.values(styled.responsive ?? {}).map((r) => r?.states?.[STUCK_STATE]),
227
227
  ];
228
+ // AN ORDER FORM THAT LEAVES THE SHOPPER WHERE THEY WERE.
229
+ //
230
+ // Measured on a store built entirely with these tools: the order is created
231
+ // and the page stays put, showing a receipt-shaped list of totals that all
232
+ // read 0 because the cart it was summing has just been emptied. Nothing
233
+ // errors, and the shopper cannot tell a completed order from a failed one.
234
+ //
235
+ // Checked on the FORM NODE rather than on the form record, because the
236
+ // record's own `afterSubmit: "redirect"` is stored by the API and carried
237
+ // nowhere (`forms/pagesource.go`) — reading it would report a page as fine
238
+ // on the strength of a setting the platform ignores.
239
+ if (type === 'form') {
240
+ const events = n.events ?? [];
241
+ const navigates = events.some((e) => e.name === 'form:success' &&
242
+ (e.action === 'go_to_url' || e.action === 'open_page' || e.action === 'go_to_checkout'));
243
+ // AN ORDER-SHAPED PAGE, read off the document rather than off the page
244
+ // record. The record's `type` would need a second round trip on every
245
+ // open, and the form's own type a third; a page that carries BOTH a form
246
+ // and a cart total is the checkout shape and nothing else is — a contact
247
+ // page has no cart to total.
248
+ const sellsFromCart = Object.values(d.nodes).some((x) => x.data?.type === 'cart-total');
249
+ if (!navigates && sellsFromCart) {
250
+ out.push({
251
+ code: 'order_goes_nowhere',
252
+ nodeId: id,
253
+ type,
254
+ problem: 'This is the checkout form and nothing sends the shopper anywhere once the order ' +
255
+ 'is placed. They stay on this page, where every cart total now reads 0 because the ' +
256
+ 'cart was just emptied — a completed order that looks like a failed one.',
257
+ fix: fill('order_goes_nowhere', { id }),
258
+ });
259
+ }
260
+ }
228
261
  // A HOVER STORED WHERE NOBODY READS IT. The universal hover state is not the
229
262
  // only home: an element whose meta declares a Hover variant keeps it
230
263
  // somewhere its own renderer looks, and for a `button` that is the flat
@@ -132,8 +132,54 @@ function pageDocumentFor(formId, headline) {
132
132
  .join(formId)
133
133
  .split(HEADLINE_SENTINEL)
134
134
  .join(headline);
135
- return JSON.parse(json);
135
+ const doc = JSON.parse(json);
136
+ sendTheShopperOn(doc);
137
+ return doc;
138
+ }
139
+ /**
140
+ * WHERE THE SHOPPER GOES AFTER THE ORDER, which nothing else supplies.
141
+ *
142
+ * Measured on a store built entirely with these tools: press "Đặt hàng" and the
143
+ * order is created — and the shopper stays on the checkout page, looking at a
144
+ * receipt-shaped list of totals that all read 0 ₫ because the cart it was
145
+ * summing has just been emptied, under an inline confirmation whose default
146
+ * wording runs the order number into the total ("Đơn hàng #1003129.000 ₫").
147
+ * Nothing is broken; nothing sent them anywhere.
148
+ *
149
+ * The form record HAS a setting for this and it does not work:
150
+ * `settings.afterSubmit.action = "redirect"` is accepted by the API, stored, and
151
+ * carried nowhere — `forms/pagesource.go` says so outright ("Only the MESSAGE
152
+ * behaviour is carried today"), and `page.FormRef.RedirectPath` is assigned by
153
+ * nothing in the platform. Writing `specials.sentRedirect` on the node does not
154
+ * survive either: the composer overwrites that special from the same empty
155
+ * field on every render. So the platform's seed cannot carry this, and neither
156
+ * can any setting an agent can reach.
157
+ *
158
+ * What does work is the form's own success chain, which the island runs BEFORE
159
+ * afterSubmit and which `form`'s meta declares `go_to_url` on. `/checkout/complete`
160
+ * is the right destination unconditionally: it resolves by page TYPE, and it
161
+ * backstops itself with a built-in receipt when the store has no completion page
162
+ * of its own, so this is never a link to a 404.
163
+ */
164
+ function sendTheShopperOn(doc) {
165
+ for (const node of Object.values(doc.nodes)) {
166
+ if (node.data?.type !== 'form')
167
+ continue;
168
+ const events = (node.events ?? []);
169
+ if (events.some((e) => e.name === 'form:success'))
170
+ continue;
171
+ events.unshift({
172
+ id: 'ev_go_to_url',
173
+ name: 'form:success',
174
+ action: 'go_to_url',
175
+ payload: { url: COMPLETE_PATH },
176
+ });
177
+ node.events = events;
178
+ }
136
179
  }
180
+ /** The path a completed order lands on. Resolves by page TYPE, and serves a
181
+ * built-in receipt when the store has authored no completion page. */
182
+ const COMPLETE_PATH = '/checkout/complete';
137
183
  /**
138
184
  * THE FORM TEMPLATES, as a tuple zod can turn into an enum.
139
185
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sbuilder-mcp",
3
- "version": "0.13.0",
3
+ "version": "0.13.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",