sbuilder-mcp 0.32.1 → 0.32.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 +6 -0
- package/CHANGELOG.vi.md +6 -0
- package/dist/domains/site/importmap.js +26 -0
- package/dist/domains/site/patterns.js +45 -8
- package/dist/vision/capture.js +19 -2
- package/package.json +1 -1
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.32.2] - 2026-09-11
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- A centred section built by sb_import, sb_import_site, or the built-in layout patterns applied by sb_template_use no longer renders half left-aligned: the theme's heading preset overrides inherited text alignment, so headings and paragraphs now carry `textAlign` directly on the node when the source (or pattern) centers or right-aligns them.
|
|
13
|
+
- The gallery-wall layout pattern applied by sb_template_use now frames every photo to a shared aspect ratio measured as the median of the pictures actually picked, instead of leaving each tile its own shape, so a wall of portraits or landscapes no longer renders with mismatched row heights.
|
|
14
|
+
|
|
9
15
|
## [0.32.1] - 2026-09-11
|
|
10
16
|
|
|
11
17
|
### Fixed
|
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.32.2] - 2026-09-11
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Một section được căn giữa do sb_import, sb_import_site, hoặc các layout pattern có sẵn được sb_template_use áp dụng giờ không còn hiển thị nửa căn trái nữa: preset heading của theme ghi đè cách căn chữ được kế thừa, nên heading và đoạn văn giờ mang `textAlign` trực tiếp trên node khi trang nguồn (hoặc pattern) căn giữa hay căn phải chúng.
|
|
13
|
+
- Layout pattern dạng tường ảnh do sb_template_use áp dụng giờ đóng khung mọi ảnh theo một tỉ lệ khung hình chung, tính bằng trung vị của các ảnh thực sự được chọn, thay vì để mỗi ô giữ nguyên hình dạng riêng, nên một tường ảnh chân dung hay ảnh ngang không còn hiển thị với chiều cao các hàng lệch nhau.
|
|
14
|
+
|
|
9
15
|
## [0.32.1] - 2026-09-11
|
|
10
16
|
|
|
11
17
|
### Fixed
|
|
@@ -155,6 +155,10 @@ function one(c, t) {
|
|
|
155
155
|
style: {
|
|
156
156
|
margin: '0',
|
|
157
157
|
...scale.style,
|
|
158
|
+
// ON THE NODE, never left to inheritance: the theme's heading preset
|
|
159
|
+
// declares its own textAlign, and a class rule beats an inherited
|
|
160
|
+
// value.
|
|
161
|
+
...(c.textAlign ? { textAlign: c.textAlign } : {}),
|
|
158
162
|
...(t.headingColor ? { color: t.headingColor } : {}),
|
|
159
163
|
...(t.headingWeight ? { fontWeight: t.headingWeight } : {}),
|
|
160
164
|
},
|
|
@@ -169,6 +173,7 @@ function one(c, t) {
|
|
|
169
173
|
specials: { htmlTag: 'p', text },
|
|
170
174
|
style: {
|
|
171
175
|
lineHeight: '1.7',
|
|
176
|
+
...(c.textAlign ? { textAlign: c.textAlign } : {}),
|
|
172
177
|
...(t.textColor ? { color: t.textColor } : {}),
|
|
173
178
|
...(t.textSize ? { fontSize: t.textSize } : {}),
|
|
174
179
|
},
|
|
@@ -288,6 +293,27 @@ function one(c, t) {
|
|
|
288
293
|
// a width/height ATTRIBUTE is a used height, and without this the cap below
|
|
289
294
|
// would be the thing ignored. `contain` because the source's crop is not
|
|
290
295
|
// ours to guess.
|
|
296
|
+
// A FRAME, WHEN THE CALLER MEASURED ONE. `height: auto` is what makes
|
|
297
|
+
// `aspect-ratio` work here at all — the platform writes the intrinsic
|
|
298
|
+
// width/height ATTRIBUTES onto every <img>, and a presentational height is
|
|
299
|
+
// a USED height, so the ratio would otherwise be ignored. That is the same
|
|
300
|
+
// fix the platform applied to its own media CSS.
|
|
301
|
+
//
|
|
302
|
+
// `cover` only inside a frame: filling one means cropping, which is the
|
|
303
|
+
// trade a wall of tiles makes on purpose. With no frame the answer stays
|
|
304
|
+
// `contain` — the source's crop is not ours to guess.
|
|
305
|
+
if (c.ratio) {
|
|
306
|
+
return {
|
|
307
|
+
type: 'image',
|
|
308
|
+
specials: { src: c.src, alt: c.alt ?? '' },
|
|
309
|
+
style: {
|
|
310
|
+
width: '100%',
|
|
311
|
+
height: 'auto',
|
|
312
|
+
aspectRatio: c.ratio,
|
|
313
|
+
objectFit: 'cover',
|
|
314
|
+
},
|
|
315
|
+
};
|
|
316
|
+
}
|
|
291
317
|
return {
|
|
292
318
|
type: 'image',
|
|
293
319
|
specials: { src: c.src, alt: c.alt ?? '' },
|
|
@@ -83,8 +83,17 @@ const row = (children, wrap = false, align) => ({
|
|
|
83
83
|
...(align ? { align } : {}),
|
|
84
84
|
children,
|
|
85
85
|
});
|
|
86
|
-
const h = (text, level = 2) => ({
|
|
87
|
-
|
|
86
|
+
const h = (text, level = 2, textAlign) => ({
|
|
87
|
+
kind: 'heading',
|
|
88
|
+
level,
|
|
89
|
+
text,
|
|
90
|
+
...(textAlign ? { textAlign } : {}),
|
|
91
|
+
});
|
|
92
|
+
const p = (text, textAlign) => ({
|
|
93
|
+
kind: 'text',
|
|
94
|
+
text,
|
|
95
|
+
...(textAlign ? { textAlign } : {}),
|
|
96
|
+
});
|
|
88
97
|
const cta = (text, href = '#') => ({ kind: 'button', variant: 'cta', text, href });
|
|
89
98
|
/**
|
|
90
99
|
* The picture slot: a real image when the site has one, and WORDS when it does
|
|
@@ -132,7 +141,10 @@ export const LAYOUT_PATTERNS = [
|
|
|
132
141
|
id: 'sb_hero_centered',
|
|
133
142
|
name: 'Hero — canh giữa',
|
|
134
143
|
use: 'Mở đầu trang khi chưa có ảnh: tiêu đề lớn, một câu, một nút',
|
|
135
|
-
build: (t) => section(
|
|
144
|
+
build: (t) => section(
|
|
145
|
+
// Centred on the NODES, not left to the section's inherited textAlign —
|
|
146
|
+
// the theme's heading preset declares its own and wins.
|
|
147
|
+
[h('Tiêu đề chính', 1, 'center'), p('Một câu nói rõ bạn bán gì và cho ai.', 'center'), cta('Mua ngay')], t, { alignItems: 'center', textAlign: 'center' }, { alignItems: 'center' }),
|
|
136
148
|
},
|
|
137
149
|
{
|
|
138
150
|
id: 'sb_feature_trio',
|
|
@@ -153,9 +165,9 @@ export const LAYOUT_PATTERNS = [
|
|
|
153
165
|
use: 'Ba đến bốn con số lớn với nhãn bên dưới — bằng chứng, không phải lời hứa',
|
|
154
166
|
build: (t) => section([
|
|
155
167
|
row([
|
|
156
|
-
{ kind: 'group', direction: 'column', children: [h('1.000+', 2), p('Khách hàng')] },
|
|
157
|
-
{ kind: 'group', direction: 'column', children: [h('4,9/5', 2), p('Đánh giá trung bình')] },
|
|
158
|
-
{ kind: 'group', direction: 'column', children: [h('24h', 2), p('Giao trong nội thành')] },
|
|
168
|
+
{ kind: 'group', direction: 'column', children: [h('1.000+', 2, 'center'), p('Khách hàng', 'center')] },
|
|
169
|
+
{ kind: 'group', direction: 'column', children: [h('4,9/5', 2, 'center'), p('Đánh giá trung bình', 'center')] },
|
|
170
|
+
{ kind: 'group', direction: 'column', children: [h('24h', 2, 'center'), p('Giao trong nội thành', 'center')] },
|
|
159
171
|
]),
|
|
160
172
|
], t, { alignItems: 'center', textAlign: 'center' }, { alignItems: 'center' }),
|
|
161
173
|
},
|
|
@@ -163,7 +175,11 @@ export const LAYOUT_PATTERNS = [
|
|
|
163
175
|
id: 'sb_cta_band',
|
|
164
176
|
name: 'Dải kêu gọi hành động',
|
|
165
177
|
use: 'Một câu và một nút, đặt cuối trang hoặc giữa hai band nội dung',
|
|
166
|
-
build: (t) => section(
|
|
178
|
+
build: (t) => section(
|
|
179
|
+
// CENTRED ON THE NODES. The section's own `textAlign` is inherited, and
|
|
180
|
+
// the theme's heading preset declares its own — so the band came out
|
|
181
|
+
// with its words hard left and only the button in the middle.
|
|
182
|
+
[h('Sẵn sàng bắt đầu?', 2, 'center'), p('Một câu nhắc lại lời hứa chính.', 'center'), cta('Mua ngay')], t, { alignItems: 'center', textAlign: 'center' }, { alignItems: 'center' }),
|
|
167
183
|
},
|
|
168
184
|
{
|
|
169
185
|
id: 'sb_gallery',
|
|
@@ -176,11 +192,32 @@ export const LAYOUT_PATTERNS = [
|
|
|
176
192
|
// SIX IS A WALL, THREE IS A ROW. Bounded because a gallery is a design
|
|
177
193
|
// decision and not a dump of the library — a merchant with 164 assets does
|
|
178
194
|
// not want 164 of them in one band.
|
|
195
|
+
const picked = [];
|
|
179
196
|
for (let i = 0; i < 6; i += 1) {
|
|
180
197
|
const m = pick(pool, used, 'any');
|
|
181
198
|
if (!m)
|
|
182
199
|
break;
|
|
183
|
-
|
|
200
|
+
picked.push(m);
|
|
201
|
+
}
|
|
202
|
+
// ONE FRAME FOR THE WALL, MEASURED RATHER THAN INVENTED.
|
|
203
|
+
//
|
|
204
|
+
// Rule 6 says match a frame's ratio to the ASSET, and a wall of
|
|
205
|
+
// photographs is the case it does not cover: there is no single asset.
|
|
206
|
+
// Left alone, every tile keeps its own shape and the grid's rows come out
|
|
207
|
+
// different heights — measured on a real build, four tiles at 330x220 and
|
|
208
|
+
// a fifth noticeably taller, which reads as unfinished.
|
|
209
|
+
//
|
|
210
|
+
// The MEDIAN of the pictures actually being shown is the honest frame:
|
|
211
|
+
// most crop by nothing, the outliers crop least, and a library of
|
|
212
|
+
// portraits gets a portrait wall rather than a landscape one imposed on
|
|
213
|
+
// it. A picture whose size the library did not report votes for nothing.
|
|
214
|
+
const ratios = picked
|
|
215
|
+
.map((m) => (m.width && m.height ? m.width / m.height : 0))
|
|
216
|
+
.filter((r) => r > 0)
|
|
217
|
+
.sort((a, b) => a - b);
|
|
218
|
+
const frame = ratios.length ? ratios[Math.floor(ratios.length / 2)].toFixed(2) : undefined;
|
|
219
|
+
for (const m of picked) {
|
|
220
|
+
shots.push({ kind: 'image', src: m.url, alt: m.name ?? 'Ảnh', ...(frame ? { ratio: frame } : {}) });
|
|
184
221
|
}
|
|
185
222
|
if (shots.length === 0) {
|
|
186
223
|
return section([
|
package/dist/vision/capture.js
CHANGED
|
@@ -274,6 +274,23 @@ function capturePage(limits) {
|
|
|
274
274
|
* loader is displaying until the real one arrives, which is precisely the
|
|
275
275
|
* placeholder this import must not ship.
|
|
276
276
|
*/
|
|
277
|
+
/**
|
|
278
|
+
* The SOURCE'S OWN text alignment, and only when it is not the default.
|
|
279
|
+
*
|
|
280
|
+
* A hero that centres its copy is making a decision; a copy that left-aligns
|
|
281
|
+
* it is not the same band. `start`/`left` is the browser's answer and saying
|
|
282
|
+
* it out loud would put a value on every imported paragraph for nothing —
|
|
283
|
+
* and worse, would override the target page's own centred preset with a
|
|
284
|
+
* literal.
|
|
285
|
+
*/
|
|
286
|
+
const alignOf = (el) => {
|
|
287
|
+
const a = getComputedStyle(el).textAlign;
|
|
288
|
+
if (a === 'center')
|
|
289
|
+
return { textAlign: 'center' };
|
|
290
|
+
if (a === 'right' || a === 'end')
|
|
291
|
+
return { textAlign: 'right' };
|
|
292
|
+
return {};
|
|
293
|
+
};
|
|
277
294
|
const LAZY_SRC = ['data-src', 'data-original', 'data-lazy-src', 'data-lazy', 'data-echo', 'data-url'];
|
|
278
295
|
const widest = (srcset) => {
|
|
279
296
|
let best = '';
|
|
@@ -657,7 +674,7 @@ function capturePage(limits) {
|
|
|
657
674
|
if (!text)
|
|
658
675
|
return [];
|
|
659
676
|
taken.nodes++;
|
|
660
|
-
return [{ kind: 'heading', level: Number(tag.slice(1)), text }];
|
|
677
|
+
return [{ kind: 'heading', level: Number(tag.slice(1)), text, ...alignOf(el) }];
|
|
661
678
|
}
|
|
662
679
|
if (tag === 'IMG') {
|
|
663
680
|
const src = realSrc(el);
|
|
@@ -801,7 +818,7 @@ function capturePage(limits) {
|
|
|
801
818
|
if (!text)
|
|
802
819
|
return [];
|
|
803
820
|
taken.nodes++;
|
|
804
|
-
return [{ kind: 'text', text }];
|
|
821
|
+
return [{ kind: 'text', text, ...alignOf(el) }];
|
|
805
822
|
}
|
|
806
823
|
const kids = walkChildren(el);
|
|
807
824
|
if (kids.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sbuilder-mcp",
|
|
3
|
-
"version": "0.32.
|
|
3
|
+
"version": "0.32.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",
|