@gemboss/gem-framework 0.19.0 → 0.20.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/README.md CHANGED
@@ -9,9 +9,10 @@ gem-framework của GemTheme đóng thành package, cho các repo **ngoài** mon
9
9
  | `…/sections` | Các hàm `headless.tsx` được import: `elementOverrides`, `imageSrcSet`, `subscriptionPlans`, `defaultPlan`; từ 0.15.0 thêm bộ đọc nhấn trong câu `inlineMarkRuns`, `phraseMarkRuns`, `markClassName`, `inlineMarkText`, `safeMarkHref`, `MARK_KINDS`; từ 0.16.0 thêm giá thẻ sản phẩm `cardPrice` (`card-price.ts`: nhiều giá thì "From" + giá thấp nhất, Sale chỉ khi biến thể mua được đang giảm, hết hàng, biến thể mà giá nói tới). `product-card-grid` quick-view-grid và featured từ section-library 1.39.6 import chúng: đồng bộ hai section đó khi đã lên 0.16.0 | `packages/gem-sections/src` |
10
10
  | `…/section-gate` | `validateSectionArtifacts`: ĐÚNG bộ kiểm section gemtheme dùng khi duyệt. Từ 0.3.0 thêm ba cổng của thư viện cho MỘT section: `layoutKnobCheck` (+ `LAYOUT_ATTRS`), `slotContractDrift` (+ `slotsReadBy`, `slotSourceFromElementTree`), `cssPairingOrphans` (+ `classesStyledBy`, truyền CSS từ `createGemThemeCss`). Từ 0.3.2 thêm hai cổng pháp lý: `fabricatedProofInSource` / `fabricatedProofInSchema` (điểm, số lượt, người, lời hứa bịa) và `inventedTestimonyIn` (hằng DEMO_*_QUOTE/REVIEW… làm giá trị khi thiếu dữ liệu). Từ 0.3.3 hai cổng này đọc được tiếng Việt ("4,8/5", "đã bán", "miễn phí vận chuyển", "199k", cả chữ NFD), tra giá trị của `DEMO.<key>` ở mọi chỗ đọc nó (`??`, `\|\|`, toán tử ba ngôi, dòng bị Prettier ngắt), và bỏ qua số lượng hiển thị (`reviews_to_show`…). Nâng từ 0.3.2 lên có thể làm đỏ section trước đó xanh: đó là lời khẳng định nằm trong DEMO. Từ 0.3.5 bắt thêm chữ ký người Việt (tên kèm nơi chốn như "Chị Lan, Hà Nội", hoặc họ + Thị/Văn như "Nguyễn Thị Hoa"; tiêu đề viết hoa từng chữ thì không), điểm sao ("4,9 ★"), `rating: 4.9` / `reviewCount: "1.284"` trong mảng sản phẩm mẫu, và hằng demo ở nhánh else của toán tử ba ngôi (`bound ? [] : DEMO_REVIEWS`). Mỗi thứ bịa chỉ báo MỘT lần: tại dòng DEMO nếu dòng đó tự lộ, không thì tại chỗ đọc. "đồng hồ" không còn bị đọc là giá. Từ 0.3.6 bắt lời hứa có thời hạn đứng sau từ khoá ("Dùng thử 30 ngày", "Bảo hành 12 tháng", "Nhận trong 2–4 ngày", "Giao 2 giờ") và "hoàn tiền" không kèm số; "Chính sách hoàn tiền" vẫn là nhãn. Từ 0.3.7 câu hỏi (kết thúc bằng "?") không bao giờ là lời hứa, "không hợp lệ"/số bước/nhịp giao không bị báo, và bắt thêm "trong vòng N ngày", "Giao hàng 2–4 ngày", "Bảo hành 1 đổi 1…", "Try it free for 30 days", "Ships in 24h". Từ 0.3.8 xuất thêm `claimIn` (câu hỏi "một chuỗi này có phải lời khẳng định, người bịa, hay lời hứa?"). Từ 0.3.9 bắt "cancel anytime", "guaranteed delivery", "huỷ bất cứ lúc nào" (chính sách huỷ gói và giao hàng). Từ 0.3.10 lời từ chối ("cannot be canceled", "Không thể hủy") không bị báo, "giao dịch/giao diện" không phải giao hàng, "Delivery not guaranteed" là lời miễn trừ, một chuỗi chỉ gồm con số viết tắt ("199k", "12k+") mang nhãn trung tính "hardcoded figure" (không phân biệt được giá với số đếm), và `stringLiteralsIn(line)` thay cho hằng `STRING_LITERAL` (đã gỡ: một regex `/g` dùng chung rò `lastIndex`; 0 chỗ dùng ở gemboss/gemfactory/gemsite). Từ 0.3.11 "Huỷ bỏ lọc…" và "Hủy đơn, gọi chúng tôi bất cứ lúc nào" không bị đọc là chính sách huỷ; "huỷ đăng ký" là bỏ nhận tin (như "Unsubscribe anytime"); `SOURCE.json` ghi `gemtheme_dirty`, và `npm pack` từ chối khi ĐẦU VÀO của gói (`packages/`, gói này trừ `package.json`, `scripts/lib/`) có thay đổi chưa commit hoặc git không trả lời được. Từ 0.3.12 bắt lời hứa hoàn tiền không có chữ "hoàn tiền" liền nhau ("nhận lại toàn bộ tiền", "Hoàn lại 100% tiền", "Trả lại tiền nếu không hài lòng"); lời từ chối ("Không được hoàn lại tiền") và tiền thừa/thối khi thanh toán không bị bắt Từ 0.4.0: `demoOutsidePreview(tsx)` (chữ mẫu DEMO chỉ hiện trong bản xem trước, tức khi `section.demo === true`; plan xuất bản không bao giờ mang cờ đó) và schema `SectionSpec` khai trường `demo` | `packages/gem-sections/src/section-artifacts.ts`, `section-contract.ts` |
11
11
  | `…/settings-schema` | `validateSettingsSchema` | `scripts/lib/settings-schema-contract.mjs` |
12
- | `…/gem-framework.css`, `…/gem-runtime.js` | CSS token mặc định + runtime mọi plugin, dựng sẵn. `gem-runtime.js` tự chạy khi tải: đăng ký và lên lịch đủ 26 plugin | build |
12
+ | `…/gem-framework.css`, `…/gem-runtime.js` | CSS token mặc định + runtime mọi plugin, dựng sẵn. `gem-runtime.js` tự chạy khi tải: đăng ký và lên lịch đủ 31 plugin (lazy chunk: chỉ stub) | build |
13
13
  | `…/tokens.css`, `…/primitives.css`, `…/sections.css`, `…/popup-shell.css` | Từ 0.8.0: `gem-framework.css` cắt ba (từ 0.8.3 cắt bốn), nối lại theo đúng thứ tự đó thì bằng `gem-framework.css` từng byte (build dừng nếu không bằng). `tokens.css`: biến `:root` + núm style. `primitives.css`: reset, phần tử nền, tiện ích, mọi primitive `.gem-*`. `sections.css`: khung header/footer, lớp section chung, profile theme. `popup-shell.css` (0.8.3): khung popup `.gem-popup-shell`, chỉ trang có popup mới cần tải, đặt sau `primitives.css` và trước `sections.css` (thứ tự nối: tokens, primitives, popup-shell, sections). CSS riêng của từng section (`section-library/**/gem.css`) KHÔNG nằm trong gói | `createGemThemeCssParts` |
14
14
  | `…/plugins/<key>` (`…/plugins/<key>.js`), `…/runtime-core` | Từ 0.8.0: mỗi plugin của runtime là một ES module, `export function mount(root = document, options?)` trả về `unmount()`, import không làm gì. Cùng dòng mã với `gem-runtime.js` (một bảng trong `runtime.ts`), chỉ khác: mọi `x.addEventListener(` thành `__gemListen(x, ` để unmount gỡ được. `runtime-core.js` là phần dùng chung: `mountPlugin`, `createGemApi`, `gemApi` | `runtime.ts`, `runtime-modules.ts`, `runtime-core.js` |
15
+ | `…/pod/<file>`, `…/pod-buybox.css` | Từ 0.20.0: buy box Boss Theme POD. `pod/core.js`, `pod/personalize.js`, `pod/print-preview.js`, `pod/delivery-estimate.js` (minify, kèm `.d.ts`) là mã các chunk `plugins/personalize`, `print-preview`, `delivery-estimate` import; `pod/delivery-estimate` có `deliveryEstimate` / `formatDeliveryRange` thuần cho server. `pod-buybox.css` là dáng bốn element POD lúc server vẽ, nạp cạnh element | `packages/gem-framework/src/pod/`, `pod-engine.ts` |
15
16
 
16
17
  ## Cài
17
18
 
@@ -93,6 +94,34 @@ Từ 0.8.2 mỗi module có `.d.ts` cạnh nó (`mount(root?: ParentNode | null,
93
94
 
94
95
  ## Nhật ký thay đổi
95
96
 
97
+ - **0.20.0** (30/10/2026): **buy box Boss Theme POD** (research gemboss `docs/research/2026-10-30-pod-boss-theme`): ba plugin lười
98
+ theo đúng khuôn chunk của motion engine (#952), cho bốn element gemsite vẽ (seam 1.0.115: `product-personalization` v2,
99
+ `product-print-preview`, `product-delivery-estimate`, `product-design-switcher`).
100
+ - **`personalize`** (`[data-gem-personalize]`, một wrapper mỗi ô; `data-gem-personalize-name|type|required|max|when|accept|max-mb|
101
+ endpoint|message-<lý do>`): ô bắt buộc CHẶN lệnh thêm giỏ của chính sản phẩm đó (form `/cart/add` submit, `gem:cart:add`,
102
+ `gem:cart:add-many`; lệnh gửi từ `window` khớp sản phẩm theo `variantId` với `data-gem-variant-id`), báo lỗi ngay dưới ô, focus
103
+ ô đầu, bắn `gem:personalize:blocked` và trả `gem:cart:add-failed { reason: "personalization" }` sau một nhịp; đủ thì ghi
104
+ `properties` (form Liquid: input ẩn `properties[<tên>]`; sự kiện: `detail.properties`, add-many: dòng của chính sản phẩm),
105
+ ảnh tải lên = URL + `_<tên>_upload` (khoá lưu trữ). Ô điều kiện `data-gem-personalize-when` (`Tên`, `!Tên`, `Tên = a|b`,
106
+ `Tên != a`): ẩn thì disabled, không bắt buộc, không ghi. Bộ đếm theo max_length. Tải ảnh: kiểm `accept` / `max_mb` ở trình
107
+ duyệt, rồi HAI BƯỚC (hợp đồng gemsite 30/10, thân hàm serverless tối đa 4,5 MB): POST JSON `{ type, size, max_mb }` tới endpoint
108
+ → `{ url, key, upload: { url, method, headers } }`, PUT file thẳng tới `upload.url` đúng `headers`, không credentials; 415 / 413
109
+ báo loại / dung lượng, 404 hoặc không có endpoint = ô không dùng được (ẩn, không bao giờ chặn); host không trả `upload` thì
110
+ POST multipart file như cũ. Đang tải thì chặn thêm giỏ. Mọi thay đổi bắn `gem:personalize:change { name, value, type, color,
111
+ preview }` từ ô. Chữ cho người mua là MỘT bộ: `PERSONALIZE_MESSAGES` (required = "Fill this in to add to cart."), gemsite
112
+ import cùng bộ này.
113
+ - **`print-preview`** (`[data-gem-print-preview]`): theo `gem:personalize:change` trong cùng sản phẩm, vẽ chữ (`textContent`,
114
+ co bằng `transform` khi tràn) và ảnh khách trong vùng in `--gem-print-preview-x|y|w|h` (% ảnh) trên ảnh hiện tại; màu hex hoặc
115
+ `from:<thuộc tính>` theo ô màu (chỉ hex tới được CSS). Chưa nhập thì không vẽ gì. Nạp sau khi khách đã gõ vẫn vẽ đúng.
116
+ - **`delivery-estimate`** (`[data-gem-delivery-estimate]`): tính lại "Đặt · Gửi · Nhận" theo ngày của người mua (ngày làm việc,
117
+ giờ chốt theo múi giờ store), `Intl.DateTimeFormat` theo `lang` của trang; `deliveryEstimate()` / `formatDeliveryRange()`
118
+ thuần, server dùng chung được (`@gemboss/gem-framework/pod/delivery-estimate`).
119
+ - **Lười:** `gem-runtime.js` chỉ thêm ba dòng stub (bộ nạp là của motion engine); chunk `plugins/<key>.js` + `pod/*.js` (minify;
120
+ personalize và print-preview chung `pod/core.js`) chỉ tải khi trang có hook. **CSS:** `pod-buybox.css` (`GEM_POD_BUYBOX_CSS`,
121
+ dáng bốn element lúc server vẽ, đi cùng element) và CSS lúc chạy trong chunk; không dòng nào vào `gem-framework.css`. Lớp
122
+ `gem-el-product-personalization*`, `-print-preview*`, `-delivery-estimate*`, `-design-switcher*` đăng ký nhóm `pod`.
123
+ Plugin mới `personalize`, `print-preview`, `delivery-estimate` (31 plugin).
124
+
96
125
  - **0.19.0** (30/10/2026): **motion engine**, nền tảng chuyển động cho section (Chris chọn 30/10: "Motion engine trong gem-framework",
97
126
  như engine bundle #41 và reviews #44), port từ gemmotion. Ba effect đầu tiên: `orbit`, `rail`, `quick-view`.
98
127
  - **Nền tảng** (`dist/motion/core.js`, `@gemboss/gem-framework/motion/core`): `defineEffect({ key, hook, settings, renderer, tier,
package/dist/SOURCE.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "package": "@gemboss/gem-framework",
3
- "version": "0.19.0",
4
- "gemtheme_sha": "4e993003a8085fa5548a91d742930cb783d8168d",
3
+ "version": "0.20.0",
4
+ "gemtheme_sha": "ec9e8ce0438f487143b562d9f8c8809adf547de0",
5
5
  "gemtheme_dirty": false,
6
- "framework_version": "0.19.0"
6
+ "framework_version": "0.20.0"
7
7
  }
@@ -1,5 +1,5 @@
1
1
  (() => {
2
- const config = {"planId":"gem-framework-package","base":"gem-native","dawnAdapter":false,"plugins":[{"key":"cart-drawer","loadStrategy":"interaction","dependsOn":[]},{"key":"mobile-menu","loadStrategy":"interaction","dependsOn":[]},{"key":"quantity-input","loadStrategy":"interaction","dependsOn":[]},{"key":"variant-selects","loadStrategy":"interaction","dependsOn":[]},{"key":"predictive-search","loadStrategy":"critical","dependsOn":[]},{"key":"localization","loadStrategy":"interaction","dependsOn":[]},{"key":"disclosure","loadStrategy":"interaction","dependsOn":[]},{"key":"tabs","loadStrategy":"interaction","dependsOn":[]},{"key":"media-gallery","loadStrategy":"viewport","dependsOn":[]},{"key":"carousel","loadStrategy":"viewport","dependsOn":[]},{"key":"hero-slideshow","loadStrategy":"idle","dependsOn":[]},{"key":"quick-view","loadStrategy":"idle","dependsOn":[]},{"key":"promo-popup","loadStrategy":"idle","dependsOn":[]},{"key":"before_after","loadStrategy":"viewport","dependsOn":[]},{"key":"runtime-slot-loader","loadStrategy":"idle","dependsOn":[]},{"key":"sticky-atc","loadStrategy":"viewport","dependsOn":[]},{"key":"recently-viewed","loadStrategy":"idle","dependsOn":[]},{"key":"countdown","loadStrategy":"viewport","dependsOn":[]},{"key":"reveal-on-scroll","loadStrategy":"viewport","dependsOn":[]},{"key":"img-in","loadStrategy":"critical","dependsOn":[]},{"key":"seasonal-effects","loadStrategy":"idle","dependsOn":[]},{"key":"context-adapter","loadStrategy":"idle","dependsOn":[]},{"key":"offer-engine","loadStrategy":"interaction","dependsOn":[]},{"key":"reviews","loadStrategy":"idle","dependsOn":[]},{"key":"bundle","loadStrategy":"idle","dependsOn":[]},{"key":"size-guide","loadStrategy":"idle","dependsOn":[]},{"key":"orbit","loadStrategy":"idle","dependsOn":[]},{"key":"rail","loadStrategy":"idle","dependsOn":[]}]};
2
+ const config = {"planId":"gem-framework-package","base":"gem-native","dawnAdapter":false,"plugins":[{"key":"cart-drawer","loadStrategy":"interaction","dependsOn":[]},{"key":"mobile-menu","loadStrategy":"interaction","dependsOn":[]},{"key":"quantity-input","loadStrategy":"interaction","dependsOn":[]},{"key":"variant-selects","loadStrategy":"interaction","dependsOn":[]},{"key":"predictive-search","loadStrategy":"critical","dependsOn":[]},{"key":"localization","loadStrategy":"interaction","dependsOn":[]},{"key":"disclosure","loadStrategy":"interaction","dependsOn":[]},{"key":"tabs","loadStrategy":"interaction","dependsOn":[]},{"key":"media-gallery","loadStrategy":"viewport","dependsOn":[]},{"key":"carousel","loadStrategy":"viewport","dependsOn":[]},{"key":"hero-slideshow","loadStrategy":"idle","dependsOn":[]},{"key":"quick-view","loadStrategy":"idle","dependsOn":[]},{"key":"promo-popup","loadStrategy":"idle","dependsOn":[]},{"key":"before_after","loadStrategy":"viewport","dependsOn":[]},{"key":"runtime-slot-loader","loadStrategy":"idle","dependsOn":[]},{"key":"sticky-atc","loadStrategy":"viewport","dependsOn":[]},{"key":"recently-viewed","loadStrategy":"idle","dependsOn":[]},{"key":"countdown","loadStrategy":"viewport","dependsOn":[]},{"key":"reveal-on-scroll","loadStrategy":"viewport","dependsOn":[]},{"key":"img-in","loadStrategy":"critical","dependsOn":[]},{"key":"seasonal-effects","loadStrategy":"idle","dependsOn":[]},{"key":"context-adapter","loadStrategy":"idle","dependsOn":[]},{"key":"offer-engine","loadStrategy":"interaction","dependsOn":[]},{"key":"reviews","loadStrategy":"idle","dependsOn":[]},{"key":"bundle","loadStrategy":"idle","dependsOn":[]},{"key":"size-guide","loadStrategy":"idle","dependsOn":[]},{"key":"orbit","loadStrategy":"idle","dependsOn":[]},{"key":"rail","loadStrategy":"idle","dependsOn":[]},{"key":"personalize","loadStrategy":"idle","dependsOn":[]},{"key":"print-preview","loadStrategy":"idle","dependsOn":[]},{"key":"delivery-estimate","loadStrategy":"idle","dependsOn":[]}]};
3
3
  const target = window;
4
4
  const existing = target.GemTheme || {};
5
5
  const listeners = existing.listeners || {};
@@ -2126,6 +2126,9 @@
2126
2126
  });
2127
2127
  registerPlugin("orbit", ({ api }) => loadChunk("orbit", "[data-gem-orbit]", "viewport", api));
2128
2128
  registerPlugin("rail", ({ api }) => loadChunk("rail", "[data-gem-rail]", "viewport", api));
2129
+ registerPlugin("personalize", ({ api }) => loadChunk("personalize", "[data-gem-personalize]", "viewport", api));
2130
+ registerPlugin("print-preview", ({ api }) => loadChunk("print-preview", "[data-gem-print-preview]", "interaction", api));
2131
+ registerPlugin("delivery-estimate", ({ api }) => loadChunk("delivery-estimate", "[data-gem-delivery-estimate]", "viewport", api));
2129
2132
  registerNoopPlugin("seasonal-effects", "gem:seasonal:ready");
2130
2133
  registerNoopPlugin("context-adapter", "gem:context:ready");
2131
2134
  const mark = (element, key) => {