sbuilder-mcp 0.40.0 → 0.41.0
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 +15 -0
- package/CHANGELOG.vi.md +15 -0
- package/README.md +2 -2
- package/README.vi.md +2 -2
- package/dist/catalog/elements.generated.js +256 -11
- package/dist/catalog/search.js +28 -0
- package/dist/domains/site/discover.js +89 -2
- package/dist/domains/site/importmap.js +53 -14
- package/dist/domains/site/inert.js +153 -0
- package/dist/domains/site/patterns.js +144 -0
- package/dist/domains/site/shape.js +11 -1
- package/dist/tools/importpage.js +52 -10
- package/dist/vision/capture.js +174 -24
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,21 @@ 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.41.0] - 2026-09-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_templates` / `sb_template_use` gain four new built-in layout patterns — `sb_product_shelf` and `sb_category_strip` are real `list-dataset` repeaters bound to the catalogue rather than static tiles, `sb_brand_wall` frames library logos with `contain` instead of a photograph's median-ratio crop, and `sb_trust_band` composes icon-plus-line reassurances from exact platform icon ids.
|
|
13
|
+
- `sb_api_find` and `sb_api_call` now attach a `product_traps` note to the four operations that create or replace a whole product, naming three facts a `ProductInput` body cannot show on its own: price lives on the variant rather than the product, a colliding slug is silently renamed rather than refused, and an image can be ingested straight from a URL in one hop.
|
|
14
|
+
- The inert-element table grew from 3 to 20 entries after an audit of all 113 catalog elements, adding `quickview`, `bundle-items`, `chat-widget`, `currency-switcher`, `theme-switcher`, `form-step-count`, `form-step-button`, `order-receipt`, `payment-status`, `points-card`, `points-prompt`, `list-empty`, `list-loading`, `rating-stars`, `accordion-content`, `menu-drawer` and `menu-panel` — elements that render convincingly on the first write while doing nothing useful, so `sb_add` can now warn about them before publish.
|
|
15
|
+
- `sb_import_site` now reads a sitemap index's own child filenames to tell record pages (products, categories, brands, tags) from page-shaped ones, and excludes the record kinds from the import plan by default instead of picking whichever URLs happen to win the page cap.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- `sb_import` and `sb_import_site` no longer turn an `<svg>`'s wrapper or utility class into a confident wrong icon; a class candidate is only considered when it carries a prefix a real icon set actually stamps, ranked by length only as a tie-break among those.
|
|
19
|
+
- `sb_import` and `sb_import_site` now build a captured list as a real `list` of `list-item` nodes carrying the platform's own marker icon, instead of a column of text nodes each with a literal bullet glyph glued on.
|
|
20
|
+
- The `structure` fidelity score no longer reports arrangement loss on pages with lists; the shape comparison now counts a captured list's rows as real children instead of scoring the list as an empty leaf.
|
|
21
|
+
- `sb_import_site`'s theme write and its per-node color/font-size stamping no longer fight each other; the four token fields a default style preset already resolves through the theme are dropped from the stamped literals once the theme write actually lands, so an imported page's colors stay controlled by the theme instead of a literal that silently outranks it forever.
|
|
22
|
+
- The `coverage` score reported by `sb_import` and `sb_import_site` no longer counts text the importer deliberately declines (chrome, hidden elements, form controls, decorative content) as lost content, and no longer misses text in a bare top-level `<nav>` or a hidden `<select>` that the page-section walk never visits at all.
|
|
23
|
+
|
|
9
24
|
## [0.40.0] - 2026-09-13
|
|
10
25
|
|
|
11
26
|
### Added
|
package/CHANGELOG.vi.md
CHANGED
|
@@ -6,6 +6,21 @@ 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.41.0] - 2026-09-13
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
- `sb_templates` / `sb_template_use` có thêm bốn layout dựng sẵn — `sb_product_shelf` và `sb_category_strip` là repeater `list-dataset` thật, gắn vào catalogue thay vì các ô tĩnh; `sb_brand_wall` đóng khung logo lấy từ thư viện bằng `contain` thay vì crop theo tỉ lệ trung vị của ảnh chụp; `sb_trust_band` ghép các dòng cam kết icon-kèm-chữ từ đúng các id icon của nền tảng.
|
|
13
|
+
- `sb_api_find` và `sb_api_call` giờ đính kèm ghi chú `product_traps` vào bốn operation tạo hoặc thay thế toàn bộ một sản phẩm, nêu rõ ba điều mà body `ProductInput` không tự nói lên được: giá nằm ở variant chứ không phải ở product, một slug trùng bị đổi tên âm thầm chứ không bị từ chối, và có thể nạp ảnh thẳng từ URL trong một bước thay vì hai.
|
|
14
|
+
- Bảng phần tử "inert" tăng từ 3 lên 20 mục sau khi rà soát toàn bộ 113 phần tử trong catalog, thêm `quickview`, `bundle-items`, `chat-widget`, `currency-switcher`, `theme-switcher`, `form-step-count`, `form-step-button`, `order-receipt`, `payment-status`, `points-card`, `points-prompt`, `list-empty`, `list-loading`, `rating-stars`, `accordion-content`, `menu-drawer` và `menu-panel` — những phần tử trông có vẻ hoàn chỉnh ngay ở lần ghi đầu tiên nhưng không làm được gì cả, nên `sb_add` giờ có thể cảnh báo về chúng trước khi publish.
|
|
15
|
+
- `sb_import_site` giờ đọc tên file của từng sitemap con trong một sitemap index để phân biệt trang dữ liệu (sản phẩm, danh mục, thương hiệu, tag) với trang nội dung thông thường, và mặc định loại các trang dữ liệu khỏi kế hoạch import thay vì chọn đại URL nào thắng giới hạn số trang.
|
|
16
|
+
|
|
17
|
+
### Fixed
|
|
18
|
+
- `sb_import` và `sb_import_site` không còn biến class wrapper hay class tiện ích của một `<svg>` thành một icon sai nhưng trông chắc chắn đúng; một class chỉ được xét khi mang tiền tố mà một bộ icon thật sự dùng, và chỉ xếp hạng theo độ dài như một cách phân định giữa các ứng viên đó.
|
|
19
|
+
- `sb_import` và `sb_import_site` giờ dựng một danh sách đã capture thành `list` thật gồm các `list-item`, mang icon đánh dấu riêng của nền tảng, thay vì một cột các node văn bản mỗi cái gắn cứng một ký tự bullet.
|
|
20
|
+
- Điểm `structure` trong đo lường fidelity không còn báo mất cấu trúc trên các trang có danh sách; phép so sánh hình dạng giờ tính các dòng của một danh sách đã capture là con thật thay vì tính danh sách đó như một lá rỗng.
|
|
21
|
+
- Bản ghi theme của `sb_import_site` và việc gán màu/cỡ chữ literal lên từng node không còn triệt tiêu lẫn nhau; bốn field token mà một style preset mặc định vốn đã phân giải qua theme sẽ bị bỏ khỏi các literal được gán ngay khi bản ghi theme thực sự có hiệu lực, nhờ đó màu sắc của trang được import vẫn do theme kiểm soát thay vì một literal âm thầm đè lên nó mãi mãi.
|
|
22
|
+
- Điểm `coverage` mà `sb_import` và `sb_import_site` báo cáo không còn tính phần chữ mà importer chủ động bỏ qua (chrome, phần tử ẩn, control của form, nội dung trang trí) là nội dung bị mất, và không còn bỏ sót chữ nằm trong một `<nav>` cấp cao nhất hay một `<select>` ẩn mà lượt duyệt theo section chưa bao giờ chạm tới.
|
|
23
|
+
|
|
9
24
|
## [0.40.0] - 2026-09-13
|
|
10
25
|
|
|
11
26
|
### Added
|
package/README.md
CHANGED
|
@@ -94,7 +94,7 @@ in this client: one would put the very key the platform exists to hold back into
|
|
|
94
94
|
| `sb_move` | Move a node to another parent |
|
|
95
95
|
| `sb_remove` | Remove a node and its subtree |
|
|
96
96
|
| `sb_duplicate` | Copy a node and its subtree under fresh ids, right after the original |
|
|
97
|
-
| `sb_templates` | The store's saved section templates, plus the BUILT-IN layouts — hero, feature trio, stats, FAQ, CTA band — composed against the page's own tokens |
|
|
97
|
+
| `sb_templates` | The store's saved section templates, plus the BUILT-IN layouts — hero, feature trio, stats, FAQ, CTA band, product shelf, category strip, brand wall, trust band — composed against the page's own tokens |
|
|
98
98
|
| `sb_template_use` | Instantiate a template into a page |
|
|
99
99
|
| `sb_page_list` | Every page on the site |
|
|
100
100
|
| `sb_page_create` | Create a page — a store type arrives with the editor's own starting document; `type` is the route for checkout, product, category, post, course |
|
|
@@ -113,7 +113,7 @@ in this client: one would put the very key the platform exists to hold back into
|
|
|
113
113
|
| `sb_undo` | Put back what a PUT replaced. The SECOND answer for a page, not the only one: the platform has versions, history and restore (`sb_api_find` "page versions"), which outlive this process — reach for those first and use this for every other shaped PUT |
|
|
114
114
|
|
|
115
115
|
Twenty-eight tools, **524 API operations** (175 of the 234 writes carrying a body shape read
|
|
116
|
-
off the handler),
|
|
116
|
+
off the handler), 113 elements, 78 binding sources. `sb_api_find`
|
|
117
117
|
is an index rather than a tool per endpoint, so the tool list stays short while everything
|
|
118
118
|
the platform can do stays reachable — and operations added to the platform arrive with the
|
|
119
119
|
next `npm run codegen`.
|
package/README.vi.md
CHANGED
|
@@ -91,7 +91,7 @@ nhét ngược lại vào mọi bản cài.
|
|
|
91
91
|
| `sb_move` | Chuyển node sang cha khác |
|
|
92
92
|
| `sb_remove` | Xoá node và cả cây con |
|
|
93
93
|
| `sb_duplicate` | Nhân bản một node và cả cây con dưới id mới, ngay sau bản gốc |
|
|
94
|
-
| `sb_templates` | Section template đã lưu của cửa hàng, cộng bộ layout DỰNG SẴN — hero, ba lợi ích, số liệu, FAQ, dải kêu gọi — soạn theo token của chính trang |
|
|
94
|
+
| `sb_templates` | Section template đã lưu của cửa hàng, cộng bộ layout DỰNG SẴN — hero, ba lợi ích, số liệu, FAQ, dải kêu gọi, kệ sản phẩm, dải danh mục, dải logo, dải cam kết — soạn theo token của chính trang |
|
|
95
95
|
| `sb_template_use` | Thả một template vào trang |
|
|
96
96
|
| `sb_page_list` | Mọi trang của site |
|
|
97
97
|
| `sb_page_create` | Tạo một trang — trang cửa hàng sinh ra đã có sẵn tài liệu như trong editor; `type` là đường đi cho checkout, product, category, post, course |
|
|
@@ -110,7 +110,7 @@ nhét ngược lại vào mọi bản cài.
|
|
|
110
110
|
| `sb_undo` | Trả lại thứ mà một lệnh PUT đã ghi đè. Với TRANG thì đây là đường về thứ hai chứ không phải duy nhất: nền tảng có version, history và restore (`sb_api_find` "page versions"), và chúng sống lâu hơn tiến trình này — hãy dùng chúng trước, còn tool này cho mọi PUT có hình dạng khác |
|
|
111
111
|
|
|
112
112
|
Hai mươi tám tool, **524 operation API** (175 trong 234 lệnh ghi có hình dạng body đọc thẳng
|
|
113
|
-
từ handler),
|
|
113
|
+
từ handler), 113 element, 78 nguồn binding. `sb_api_find` là
|
|
114
114
|
một chỉ mục chứ không phải mỗi endpoint một tool, nên danh sách tool vẫn ngắn trong khi mọi
|
|
115
115
|
thứ nền tảng làm được vẫn với tới — và operation mới thêm bên nền tảng sẽ tự có sau lần
|
|
116
116
|
`npm run codegen` kế tiếp.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export const ELEMENT_SOURCE = {
|
|
2
|
-
"count":
|
|
2
|
+
"count": 113,
|
|
3
3
|
"docSchemaVersion": 2
|
|
4
4
|
};
|
|
5
5
|
export const ELEMENTS = {
|
|
@@ -689,7 +689,9 @@ export const ELEMENTS = {
|
|
|
689
689
|
"close_dropdown",
|
|
690
690
|
"toggle_dropdown",
|
|
691
691
|
"logout_customer",
|
|
692
|
-
"toggle_wishlist"
|
|
692
|
+
"toggle_wishlist",
|
|
693
|
+
"quick_view",
|
|
694
|
+
"close_quick_view"
|
|
693
695
|
]
|
|
694
696
|
},
|
|
695
697
|
"bindingEvents": {
|
|
@@ -1592,7 +1594,9 @@ export const ELEMENTS = {
|
|
|
1592
1594
|
"change_tab",
|
|
1593
1595
|
"open_dropdown",
|
|
1594
1596
|
"close_dropdown",
|
|
1595
|
-
"toggle_dropdown"
|
|
1597
|
+
"toggle_dropdown",
|
|
1598
|
+
"quick_view",
|
|
1599
|
+
"close_quick_view"
|
|
1596
1600
|
]
|
|
1597
1601
|
},
|
|
1598
1602
|
"defaults": {
|
|
@@ -1762,7 +1766,9 @@ export const ELEMENTS = {
|
|
|
1762
1766
|
"close_dropdown",
|
|
1763
1767
|
"toggle_dropdown",
|
|
1764
1768
|
"logout_customer",
|
|
1765
|
-
"toggle_wishlist"
|
|
1769
|
+
"toggle_wishlist",
|
|
1770
|
+
"quick_view",
|
|
1771
|
+
"close_quick_view"
|
|
1766
1772
|
]
|
|
1767
1773
|
},
|
|
1768
1774
|
"defaults": {
|
|
@@ -3688,7 +3694,7 @@ export const ELEMENTS = {
|
|
|
3688
3694
|
},
|
|
3689
3695
|
"spline-scene": {
|
|
3690
3696
|
"type": "spline-scene",
|
|
3691
|
-
"label": "
|
|
3697
|
+
"label": "3D scene",
|
|
3692
3698
|
"category": "media",
|
|
3693
3699
|
"isContainer": false,
|
|
3694
3700
|
"isRootOnly": false,
|
|
@@ -3697,6 +3703,7 @@ export const ELEMENTS = {
|
|
|
3697
3703
|
"childAllows": [],
|
|
3698
3704
|
"defaults": {
|
|
3699
3705
|
"specials": {
|
|
3706
|
+
"source": "effect",
|
|
3700
3707
|
"sceneUrl": "https://prod.spline.design/HqdfCmOueigtautT/scene.splinecode",
|
|
3701
3708
|
"posterUrl": "",
|
|
3702
3709
|
"sceneControls": []
|
|
@@ -3708,13 +3715,28 @@ export const ELEMENTS = {
|
|
|
3708
3715
|
"config": {
|
|
3709
3716
|
"background": "transparent",
|
|
3710
3717
|
"eventsTarget": "local",
|
|
3711
|
-
"mobileMode": "scene"
|
|
3718
|
+
"mobileMode": "scene",
|
|
3719
|
+
"effect": "gradient-mesh",
|
|
3720
|
+
"speed": "normal",
|
|
3721
|
+
"intensity": "normal",
|
|
3722
|
+
"grain": false,
|
|
3723
|
+
"effectColors": "theme",
|
|
3724
|
+
"effectColor1": "#171717",
|
|
3725
|
+
"effectColor2": "#ffffff",
|
|
3726
|
+
"effectColor3": "#6b7280"
|
|
3712
3727
|
}
|
|
3713
3728
|
},
|
|
3714
3729
|
"inspector": [
|
|
3715
3730
|
{
|
|
3716
3731
|
"tab": "general",
|
|
3717
3732
|
"groups": [
|
|
3733
|
+
{
|
|
3734
|
+
"key": "scene_source",
|
|
3735
|
+
"label": "Source",
|
|
3736
|
+
"controls": [
|
|
3737
|
+
"scene_source"
|
|
3738
|
+
]
|
|
3739
|
+
},
|
|
3718
3740
|
{
|
|
3719
3741
|
"key": "size",
|
|
3720
3742
|
"label": "Size",
|
|
@@ -3730,6 +3752,13 @@ export const ELEMENTS = {
|
|
|
3730
3752
|
"spline_source"
|
|
3731
3753
|
]
|
|
3732
3754
|
},
|
|
3755
|
+
{
|
|
3756
|
+
"key": "scene_effect",
|
|
3757
|
+
"label": "Effect",
|
|
3758
|
+
"controls": [
|
|
3759
|
+
"scene_effect"
|
|
3760
|
+
]
|
|
3761
|
+
},
|
|
3733
3762
|
{
|
|
3734
3763
|
"key": "scene_display",
|
|
3735
3764
|
"label": "Display",
|
|
@@ -3790,9 +3819,11 @@ export const ELEMENTS = {
|
|
|
3790
3819
|
}
|
|
3791
3820
|
],
|
|
3792
3821
|
"controls": [
|
|
3822
|
+
"scene_source",
|
|
3793
3823
|
"width_select",
|
|
3794
3824
|
"size_bounds",
|
|
3795
3825
|
"spline_source",
|
|
3826
|
+
"scene_effect",
|
|
3796
3827
|
"scene_display",
|
|
3797
3828
|
"scene_controls",
|
|
3798
3829
|
"border",
|
|
@@ -3803,25 +3834,35 @@ export const ELEMENTS = {
|
|
|
3803
3834
|
"animation",
|
|
3804
3835
|
"class_css"
|
|
3805
3836
|
],
|
|
3806
|
-
"description": "
|
|
3837
|
+
"description": "A 3D block whose source the merchant picks: a generated WebGL effect that needs no file and no account (~15 KB), or a Spline scene embedded from its viewer link (~600 KB).",
|
|
3807
3838
|
"useWhen": [
|
|
3839
|
+
"For a moving background behind a hero or a section — pick the effect source, which costs ~15 KB and needs nothing but the element",
|
|
3808
3840
|
"For a hero or product moment that should move in 3D as the visitor scrolls or hovers",
|
|
3809
|
-
"When the merchant already designs in Spline and has a .splinecode link"
|
|
3841
|
+
"When the merchant already designs in Spline and has a .splinecode link — pick the Spline source"
|
|
3810
3842
|
],
|
|
3811
3843
|
"avoidWhen": [
|
|
3812
3844
|
"For a plain product photo — use image",
|
|
3813
3845
|
"For a video — use video / youtube / vimeo",
|
|
3814
|
-
"
|
|
3846
|
+
"For a still gradient or a flat colour behind text — a background style on the section is free; this element runs a shader",
|
|
3847
|
+
"More than one Spline scene on a page (each costs ~600 KB of JS once it scrolls into view). Several effects are cheap by comparison, but they each run their own animation loop"
|
|
3815
3848
|
],
|
|
3816
3849
|
"contentTips": [
|
|
3817
|
-
"
|
|
3818
|
-
"
|
|
3850
|
+
"Choose the source first — the rest of the panel changes with it, and the budget line under the picker says what the page will download",
|
|
3851
|
+
"Effect: four shaders (gradient mesh, floating particles, waves, aurora) with speed, intensity and a grain toggle. Its colours follow the site theme until you switch them to custom",
|
|
3852
|
+
"Spline: paste the link from Spline — Export → Viewer → copy link (…/scene.splinecode)",
|
|
3853
|
+
"Add a poster image so the box is not blank while a Spline scene loads; an effect draws immediately and needs none",
|
|
3854
|
+
"Scene controls (rotate on scroll, tilt with the mouse) drive named objects, so they apply to a Spline scene and not to an effect, which has no objects"
|
|
3819
3855
|
],
|
|
3820
3856
|
"semantics": [
|
|
3821
3857
|
"3d",
|
|
3822
3858
|
"spline",
|
|
3823
3859
|
"scene",
|
|
3824
3860
|
"webgl",
|
|
3861
|
+
"shader",
|
|
3862
|
+
"effect",
|
|
3863
|
+
"gradient",
|
|
3864
|
+
"particles",
|
|
3865
|
+
"animated background",
|
|
3825
3866
|
"animation",
|
|
3826
3867
|
"interactive"
|
|
3827
3868
|
]
|
|
@@ -19567,6 +19608,190 @@ export const ELEMENTS = {
|
|
|
19567
19608
|
"input-skin"
|
|
19568
19609
|
]
|
|
19569
19610
|
},
|
|
19611
|
+
"quickview": {
|
|
19612
|
+
"type": "quickview",
|
|
19613
|
+
"label": "Quick view",
|
|
19614
|
+
"category": "basic",
|
|
19615
|
+
"isContainer": true,
|
|
19616
|
+
"isRootOnly": false,
|
|
19617
|
+
"locked": true,
|
|
19618
|
+
"hideInLayer": false,
|
|
19619
|
+
"childAllows": [],
|
|
19620
|
+
"defaults": {
|
|
19621
|
+
"style": {
|
|
19622
|
+
"display": "flex",
|
|
19623
|
+
"flexDirection": "column",
|
|
19624
|
+
"gap": "16px",
|
|
19625
|
+
"width": "720px",
|
|
19626
|
+
"maxWidth": "calc(100% - 32px)",
|
|
19627
|
+
"maxHeight": "calc(100vh - 64px)",
|
|
19628
|
+
"padding": "24px",
|
|
19629
|
+
"backgroundColor": "#ffffff",
|
|
19630
|
+
"borderTopLeftRadius": "10px",
|
|
19631
|
+
"borderTopRightRadius": "10px",
|
|
19632
|
+
"borderBottomLeftRadius": "10px",
|
|
19633
|
+
"borderBottomRightRadius": "10px",
|
|
19634
|
+
"overflowX": "hidden",
|
|
19635
|
+
"overflowY": "auto"
|
|
19636
|
+
},
|
|
19637
|
+
"config": {
|
|
19638
|
+
"showOverlay": true,
|
|
19639
|
+
"overlayColor": "#000000",
|
|
19640
|
+
"clickToClose": true
|
|
19641
|
+
}
|
|
19642
|
+
},
|
|
19643
|
+
"inspector": [
|
|
19644
|
+
{
|
|
19645
|
+
"tab": "general",
|
|
19646
|
+
"groups": [
|
|
19647
|
+
{
|
|
19648
|
+
"key": "panel",
|
|
19649
|
+
"label": "Panel",
|
|
19650
|
+
"controls": [
|
|
19651
|
+
"quickview_name"
|
|
19652
|
+
]
|
|
19653
|
+
},
|
|
19654
|
+
{
|
|
19655
|
+
"key": "size",
|
|
19656
|
+
"label": "Size",
|
|
19657
|
+
"controls": [
|
|
19658
|
+
"width_select",
|
|
19659
|
+
"height_select",
|
|
19660
|
+
"size_bounds"
|
|
19661
|
+
]
|
|
19662
|
+
},
|
|
19663
|
+
{
|
|
19664
|
+
"key": "layout",
|
|
19665
|
+
"label": "Layout",
|
|
19666
|
+
"controls": [
|
|
19667
|
+
"direction",
|
|
19668
|
+
"gap",
|
|
19669
|
+
"padding",
|
|
19670
|
+
"margin",
|
|
19671
|
+
"vertical",
|
|
19672
|
+
"horizontal"
|
|
19673
|
+
]
|
|
19674
|
+
},
|
|
19675
|
+
{
|
|
19676
|
+
"key": "quickview_position",
|
|
19677
|
+
"label": "Position",
|
|
19678
|
+
"controls": [
|
|
19679
|
+
"popup_placement"
|
|
19680
|
+
]
|
|
19681
|
+
},
|
|
19682
|
+
{
|
|
19683
|
+
"key": "quickview_overlay",
|
|
19684
|
+
"label": "Overlay",
|
|
19685
|
+
"controls": [
|
|
19686
|
+
"quickview_scrim"
|
|
19687
|
+
]
|
|
19688
|
+
},
|
|
19689
|
+
{
|
|
19690
|
+
"key": "background",
|
|
19691
|
+
"label": "Background",
|
|
19692
|
+
"controls": [
|
|
19693
|
+
"bg_color",
|
|
19694
|
+
"bg_image"
|
|
19695
|
+
]
|
|
19696
|
+
},
|
|
19697
|
+
{
|
|
19698
|
+
"key": "shape",
|
|
19699
|
+
"label": "Shape",
|
|
19700
|
+
"controls": [
|
|
19701
|
+
"border",
|
|
19702
|
+
"corner",
|
|
19703
|
+
"shadow"
|
|
19704
|
+
]
|
|
19705
|
+
}
|
|
19706
|
+
]
|
|
19707
|
+
},
|
|
19708
|
+
{
|
|
19709
|
+
"tab": "advanced",
|
|
19710
|
+
"groups": [
|
|
19711
|
+
{
|
|
19712
|
+
"key": "state",
|
|
19713
|
+
"label": "state",
|
|
19714
|
+
"controls": []
|
|
19715
|
+
},
|
|
19716
|
+
{
|
|
19717
|
+
"key": "spacing",
|
|
19718
|
+
"label": "Spacing",
|
|
19719
|
+
"controls": [
|
|
19720
|
+
"padding_margin"
|
|
19721
|
+
]
|
|
19722
|
+
},
|
|
19723
|
+
{
|
|
19724
|
+
"key": "display",
|
|
19725
|
+
"label": "Display",
|
|
19726
|
+
"controls": [
|
|
19727
|
+
"display"
|
|
19728
|
+
]
|
|
19729
|
+
},
|
|
19730
|
+
{
|
|
19731
|
+
"key": "animation",
|
|
19732
|
+
"label": "Animation",
|
|
19733
|
+
"controls": [
|
|
19734
|
+
"animation"
|
|
19735
|
+
]
|
|
19736
|
+
},
|
|
19737
|
+
{
|
|
19738
|
+
"key": "class_css",
|
|
19739
|
+
"label": "Class",
|
|
19740
|
+
"controls": [
|
|
19741
|
+
"class_css"
|
|
19742
|
+
]
|
|
19743
|
+
}
|
|
19744
|
+
]
|
|
19745
|
+
}
|
|
19746
|
+
],
|
|
19747
|
+
"controls": [
|
|
19748
|
+
"quickview_name",
|
|
19749
|
+
"width_select",
|
|
19750
|
+
"height_select",
|
|
19751
|
+
"size_bounds",
|
|
19752
|
+
"direction",
|
|
19753
|
+
"gap",
|
|
19754
|
+
"padding",
|
|
19755
|
+
"margin",
|
|
19756
|
+
"vertical",
|
|
19757
|
+
"horizontal",
|
|
19758
|
+
"popup_placement",
|
|
19759
|
+
"quickview_scrim",
|
|
19760
|
+
"bg_color",
|
|
19761
|
+
"bg_image",
|
|
19762
|
+
"border",
|
|
19763
|
+
"corner",
|
|
19764
|
+
"shadow",
|
|
19765
|
+
"padding_margin",
|
|
19766
|
+
"display",
|
|
19767
|
+
"animation",
|
|
19768
|
+
"class_css"
|
|
19769
|
+
],
|
|
19770
|
+
"description": "A QUICK VIEW panel: the surface a shopper opens from a product card to see enough to decide — gallery, title, price, variants, quantity, add to cart — without leaving the list they were browsing. Belongs to the site, not to a page; a product list points at one and several lists may share the same panel.",
|
|
19771
|
+
"useWhen": [
|
|
19772
|
+
"a catalogue page where shoppers compare several products and losing their scroll position costs a sale",
|
|
19773
|
+
"a store whose products need one or two choices (size, colour) before they can be added to the cart",
|
|
19774
|
+
"a site that wants a second, shorter product surface without maintaining a second product page"
|
|
19775
|
+
],
|
|
19776
|
+
"avoidWhen": [
|
|
19777
|
+
"as a standalone block: this panel only renders through the list that names it, hidden until a shopper opens it",
|
|
19778
|
+
"as a replacement for the product page — search engines index the page, not this panel, and a long description belongs there",
|
|
19779
|
+
"for products that need no decision at all: a one-click add-to-cart button on the card is faster than any panel"
|
|
19780
|
+
],
|
|
19781
|
+
"contentTips": [
|
|
19782
|
+
"show what the card could not: the other photos, the variants, the stock, the short description",
|
|
19783
|
+
"keep an obvious way through to the full product page — a shopper who wants the detail should not have to close and hunt",
|
|
19784
|
+
"put the add-to-cart control where it is visible without scrolling on a phone"
|
|
19785
|
+
],
|
|
19786
|
+
"semantics": [
|
|
19787
|
+
"quick-view",
|
|
19788
|
+
"modal",
|
|
19789
|
+
"product",
|
|
19790
|
+
"listing",
|
|
19791
|
+
"commerce",
|
|
19792
|
+
"container"
|
|
19793
|
+
]
|
|
19794
|
+
},
|
|
19570
19795
|
"product-variants": {
|
|
19571
19796
|
"type": "product-variants",
|
|
19572
19797
|
"label": "Product variants",
|
|
@@ -32802,6 +33027,14 @@ export const ELEMENTS = {
|
|
|
32802
33027
|
"loadingStateId"
|
|
32803
33028
|
]
|
|
32804
33029
|
},
|
|
33030
|
+
{
|
|
33031
|
+
"key": "quickview",
|
|
33032
|
+
"label": "Quick view",
|
|
33033
|
+
"controls": [
|
|
33034
|
+
"list_quickview",
|
|
33035
|
+
"quickviewId"
|
|
33036
|
+
]
|
|
33037
|
+
},
|
|
32805
33038
|
{
|
|
32806
33039
|
"key": "narrowing",
|
|
32807
33040
|
"label": "Narrowing",
|
|
@@ -32963,6 +33196,8 @@ export const ELEMENTS = {
|
|
|
32963
33196
|
"list_loading_state",
|
|
32964
33197
|
"emptyStateId",
|
|
32965
33198
|
"loadingStateId",
|
|
33199
|
+
"list_quickview",
|
|
33200
|
+
"quickviewId",
|
|
32966
33201
|
"list_searchable",
|
|
32967
33202
|
"list_filterable",
|
|
32968
33203
|
"width_select",
|
|
@@ -36111,6 +36346,11 @@ export const SATELLITE_RULES = {
|
|
|
36111
36346
|
"type": "list-loading",
|
|
36112
36347
|
"configKey": "loadingStateId",
|
|
36113
36348
|
"optional": true
|
|
36349
|
+
},
|
|
36350
|
+
{
|
|
36351
|
+
"type": "quickview",
|
|
36352
|
+
"configKey": "quickviewNodeId",
|
|
36353
|
+
"optional": true
|
|
36114
36354
|
}
|
|
36115
36355
|
]
|
|
36116
36356
|
};
|
|
@@ -36336,6 +36576,11 @@ export const BASE_ONLY_CONFIG = [
|
|
|
36336
36576
|
"collectionId",
|
|
36337
36577
|
"collectionType",
|
|
36338
36578
|
"quantity",
|
|
36579
|
+
"effect",
|
|
36580
|
+
"speed",
|
|
36581
|
+
"intensity",
|
|
36582
|
+
"grain",
|
|
36583
|
+
"effectColors",
|
|
36339
36584
|
"rowLimit"
|
|
36340
36585
|
];
|
|
36341
36586
|
/**
|
package/dist/catalog/search.js
CHANGED
|
@@ -13,6 +13,20 @@ import { translationCallSheet } from '../domains/site/translate.js';
|
|
|
13
13
|
const WEIGHT = { tag: 5, path: 3, summary: 2 };
|
|
14
14
|
/** Eight is enough to choose from; twelve was measured at 44 KB once the schemas rode along. */
|
|
15
15
|
export const DEFAULT_FIND_LIMIT = 8;
|
|
16
|
+
/**
|
|
17
|
+
* The four operations that create or replace a whole product, on both
|
|
18
|
+
* credential surfaces — never a list, a delete, or the bulk import/categories
|
|
19
|
+
* routes beside them. Listed rather than matched by path pattern, because
|
|
20
|
+
* `/products/import` and `/products/{productId}/categories` sit right next to
|
|
21
|
+
* these and are not this trap: an import posts a file, and filing a product
|
|
22
|
+
* under a category touches neither its price, its slug, nor its photos.
|
|
23
|
+
*/
|
|
24
|
+
const PRODUCT_WRITE_IDS = new Set([
|
|
25
|
+
'post:/api/sites/{siteId}/products',
|
|
26
|
+
'put:/api/sites/{siteId}/products/{id}',
|
|
27
|
+
'post:/api/v1/products',
|
|
28
|
+
'put:/api/v1/products/{id}',
|
|
29
|
+
]);
|
|
16
30
|
export function findOperation(id) {
|
|
17
31
|
return API_OPERATIONS.find((o) => o.id === id);
|
|
18
32
|
}
|
|
@@ -139,6 +153,20 @@ export function describeOperation(op) {
|
|
|
139
153
|
'/apps/blocks again for what it contributed.',
|
|
140
154
|
};
|
|
141
155
|
}
|
|
156
|
+
// FILLING A CATALOGUE WAS REACHABLE AND THREE FACTS ABOUT IT WERE NOT, each
|
|
157
|
+
// failing silently. Attached to the call sheet for the same reason the
|
|
158
|
+
// translation table is: this is where the agent is when it decides what to
|
|
159
|
+
// send, on both credential surfaces a product can be created or replaced on.
|
|
160
|
+
if (PRODUCT_WRITE_IDS.has(op.id)) {
|
|
161
|
+
out.product_traps = {
|
|
162
|
+
price: 'Price lives on the VARIANT, not the product — variants[].priceCents. A product ' +
|
|
163
|
+
'posted with no variant renders a catalogue entry nobody can buy.',
|
|
164
|
+
slug: 'A colliding slug is RENAMED, not refused, and the write still answers 200/201. ' +
|
|
165
|
+
'Re-running an import does not error — it DOUBLES the catalogue in silence.',
|
|
166
|
+
images: 'POST /api/media/{siteId}/from-url ingests a photo straight from its URL, one hop ' +
|
|
167
|
+
'instead of downloading and re-uploading it.',
|
|
168
|
+
};
|
|
169
|
+
}
|
|
142
170
|
const shape = REQUEST_SHAPES[op.id];
|
|
143
171
|
if (shape) {
|
|
144
172
|
out.body_shape = shape;
|
|
@@ -18,6 +18,82 @@
|
|
|
18
18
|
* what counts as a page are the half worth arguing over, and they can be argued
|
|
19
19
|
* over offline. Fetching belongs to the caller.
|
|
20
20
|
*/
|
|
21
|
+
/**
|
|
22
|
+
* RECORD-shaped kinds: individual entries in a catalogue this platform serves
|
|
23
|
+
* through ONE bound template plus real data, never as N static pages. Their
|
|
24
|
+
* words, checked FIRST — see `sitemapKind` below for why the order matters.
|
|
25
|
+
*/
|
|
26
|
+
const ENTITY_KIND_WORDS = [
|
|
27
|
+
['product', ['product', 'products']],
|
|
28
|
+
['category', ['category', 'categories']],
|
|
29
|
+
['collection', ['collection', 'collections']],
|
|
30
|
+
['brand', ['brand', 'brands']],
|
|
31
|
+
['tag', ['tag', 'tags']],
|
|
32
|
+
];
|
|
33
|
+
/** PAGE-shaped kinds: ordinary content, one static page each. */
|
|
34
|
+
const PAGE_KIND_WORDS = [
|
|
35
|
+
['article', ['article', 'articles']],
|
|
36
|
+
['post', ['post', 'posts']],
|
|
37
|
+
['blog', ['blog', 'blogs']],
|
|
38
|
+
['page', ['page', 'pages']],
|
|
39
|
+
];
|
|
40
|
+
/** The kinds `choosePages` excludes from a plan by default. */
|
|
41
|
+
export const ENTITY_KINDS = new Set(ENTITY_KIND_WORDS.map(([k]) => k));
|
|
42
|
+
/** How to name an entity kind's count in a sentence — "categories", not "categorys". */
|
|
43
|
+
export const ENTITY_KIND_LABEL = {
|
|
44
|
+
product: 'products',
|
|
45
|
+
category: 'categories',
|
|
46
|
+
collection: 'collections',
|
|
47
|
+
brand: 'brands',
|
|
48
|
+
tag: 'tags',
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* What kind of `<loc>`s a child sitemap holds, read off its OWN FILENAME —
|
|
52
|
+
* never a guess about the URLs themselves. ttgshop.vn spells it
|
|
53
|
+
* `sitemap_product.xml`; Yoast writes `product-sitemap.xml`; Shopify numbers
|
|
54
|
+
* its shards `sitemap_products_1.xml`. All three are covered by testing
|
|
55
|
+
* whether the filename's own WORDS include one of the terms above.
|
|
56
|
+
*
|
|
57
|
+
* TOKENIZED, not a raw substring test. `"category"` as a plain substring can
|
|
58
|
+
* hit a coincidental run of letters inside an unrelated word (this repo has
|
|
59
|
+
* already paid once for exactly that mistake — `isPlumbing`'s boundary bug,
|
|
60
|
+
* above), and this catalog of kinds is short enough to name outright, so the
|
|
61
|
+
* filename is split on anything that is not a letter or digit and matched as
|
|
62
|
+
* whole TOKENS: a name has to actually SPELL "category" as its own word to
|
|
63
|
+
* read as one. A page sitemap never does.
|
|
64
|
+
*
|
|
65
|
+
* ENTITY words are checked before PAGE words. A filename naming both — a
|
|
66
|
+
* WordPress "blog categories" taxonomy archive, say — resolves to the
|
|
67
|
+
* entity: excluding a few thousand catalogue records is the safe direction
|
|
68
|
+
* to be wrong in, and importing them as static pages is the exact defect
|
|
69
|
+
* this function exists to prevent.
|
|
70
|
+
*
|
|
71
|
+
* A name with NEITHER — `sitemap.xml`, `sitemap1.xml`, `sitemap_index.xml` —
|
|
72
|
+
* returns `undefined`, which is what keeps every site shaped like that
|
|
73
|
+
* planning exactly as it always has.
|
|
74
|
+
*/
|
|
75
|
+
export function sitemapKind(sitemapUrl) {
|
|
76
|
+
let path;
|
|
77
|
+
try {
|
|
78
|
+
path = new URL(sitemapUrl).pathname;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
path = sitemapUrl;
|
|
82
|
+
}
|
|
83
|
+
const file = path.split('/').filter(Boolean).pop() ?? '';
|
|
84
|
+
const tokens = new Set(file
|
|
85
|
+
.toLowerCase()
|
|
86
|
+
.replace(/\.xml(?:\.gz)?$/, '')
|
|
87
|
+
.split(/[^a-z0-9]+/)
|
|
88
|
+
.filter(Boolean));
|
|
89
|
+
for (const [kind, words] of ENTITY_KIND_WORDS)
|
|
90
|
+
if (words.some((w) => tokens.has(w)))
|
|
91
|
+
return kind;
|
|
92
|
+
for (const [kind, words] of PAGE_KIND_WORDS)
|
|
93
|
+
if (words.some((w) => tokens.has(w)))
|
|
94
|
+
return kind;
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
21
97
|
/** Anything whose extension says it is a file rather than a page. */
|
|
22
98
|
const ASSET = /\.(?:pdf|jpe?g|png|gif|webp|svg|ico|avif|css|js|mjs|json|xml|rss|atom|zip|rar|gz|tgz|tar|mp3|mp4|m4a|webm|mov|avi|woff2?|ttf|otf|eot|docx?|xlsx?|pptx?|csv|txt)$/i;
|
|
23
99
|
/**
|
|
@@ -370,6 +446,7 @@ export function choosePages(entry, urls, opts = {}) {
|
|
|
370
446
|
const exclude = (opts.exclude ?? []).map((s) => s.toLowerCase());
|
|
371
447
|
const origin = originOf(entry);
|
|
372
448
|
const skipped = {};
|
|
449
|
+
const kinds = {};
|
|
373
450
|
const skip = (why) => {
|
|
374
451
|
skipped[why] = (skipped[why] ?? 0) + 1;
|
|
375
452
|
};
|
|
@@ -405,6 +482,16 @@ export function choosePages(entry, urls, opts = {}) {
|
|
|
405
482
|
// about a stranger's site, and a caller who names `/account` knows something
|
|
406
483
|
// this module does not.
|
|
407
484
|
if (!wanted && norm !== entry) {
|
|
485
|
+
// RECORD-SHAPED KINDS ARE NOT PAGES HERE. Read off the sitemap that
|
|
486
|
+
// named it, never guessed at from the URL — see `sitemapKind`. A
|
|
487
|
+
// kindless `Found` (no child sitemap named it, or discovery fell
|
|
488
|
+
// through to the link crawl) is unaffected: this fires only when the
|
|
489
|
+
// site's own sitemap said so.
|
|
490
|
+
if (f.kind && ENTITY_KINDS.has(f.kind)) {
|
|
491
|
+
skip('entity-kind');
|
|
492
|
+
kinds[f.kind] = (kinds[f.kind] ?? 0) + 1;
|
|
493
|
+
continue;
|
|
494
|
+
}
|
|
408
495
|
if (ASSET.test(path)) {
|
|
409
496
|
skip('asset');
|
|
410
497
|
continue;
|
|
@@ -441,7 +528,7 @@ export function choosePages(entry, urls, opts = {}) {
|
|
|
441
528
|
continue;
|
|
442
529
|
}
|
|
443
530
|
seen.add(norm);
|
|
444
|
-
kept.push({ url: norm, from: norm === entry ? 'entry' : f.from });
|
|
531
|
+
kept.push({ url: norm, from: norm === entry ? 'entry' : f.from, ...(f.kind ? { kind: f.kind } : {}) });
|
|
445
532
|
}
|
|
446
533
|
const folded = foldLocales(kept, entry, skip);
|
|
447
534
|
// GROUPS ARE COUNTED BEFORE THE CAP, because the whole point of reporting them
|
|
@@ -472,7 +559,7 @@ export function choosePages(entry, urls, opts = {}) {
|
|
|
472
559
|
const slug = slugFor(f.url, taken);
|
|
473
560
|
return { ...f, slug, name: nameFor(slug), depth: depthOf(f.url) };
|
|
474
561
|
});
|
|
475
|
-
return { pages, skipped, groups };
|
|
562
|
+
return { pages, skipped, groups, kinds };
|
|
476
563
|
}
|
|
477
564
|
/**
|
|
478
565
|
* The filter a link crawl needs, as one function: a raw href in, its canonical
|