@vtex/faststore 0.0.10 → 0.0.12

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.
Files changed (509) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/README.md +0 -1
  3. package/dist/_virtual/_rolldown/runtime.js +4 -0
  4. package/dist/actions.json +6 -1
  5. package/dist/catalog.json +789 -1701
  6. package/dist/cli/index.d.ts +66 -28
  7. package/dist/cli/index.js +7371 -512
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/cli/server-runtime.js +41 -60
  10. package/dist/cli/server-runtime.js.map +1 -1
  11. package/dist/client.d.ts +3 -3
  12. package/dist/client.js +2 -2
  13. package/dist/components.d.ts +63 -57
  14. package/dist/components.js +60 -54
  15. package/dist/content-types.json +5785 -754
  16. package/dist/core/dist/define-component.d.ts +1 -1
  17. package/dist/core/dist/define-content-type.d.ts +50 -2
  18. package/dist/core/dist/define-content-type.js +107 -3
  19. package/dist/core/dist/define-content-type.js.map +1 -1
  20. package/dist/core/dist/index.d.ts +2 -2
  21. package/dist/docs/access-code-field.md +1 -1
  22. package/dist/docs/access-code-sign-in.md +126 -77
  23. package/dist/docs/account-address-book.md +210 -95
  24. package/dist/docs/account-menu.md +14 -15
  25. package/dist/docs/account-saved-cards.md +44 -39
  26. package/dist/docs/alert-dialog.md +3 -1
  27. package/dist/docs/avatar.md +4 -2
  28. package/dist/docs/billing-address.md +99 -30
  29. package/dist/docs/binding-switcher.md +5 -2
  30. package/dist/docs/buy-button.md +7 -8
  31. package/dist/docs/card-entry.md +39 -26
  32. package/dist/docs/card-summary.md +18 -8
  33. package/dist/docs/cardholder-document.md +5 -3
  34. package/dist/docs/carousel.md +5 -4
  35. package/dist/docs/cart-destination.md +39 -26
  36. package/dist/docs/cart-item.md +29 -14
  37. package/dist/docs/cart-totalizer.md +7 -5
  38. package/dist/docs/cart.md +135 -41
  39. package/dist/docs/challenge-return.md +135 -0
  40. package/dist/docs/client-preferences-fieldset.md +10 -5
  41. package/dist/docs/client-preferences.md +31 -8
  42. package/dist/docs/client-profile-fieldset.md +20 -6
  43. package/dist/docs/client-profile.md +12 -3
  44. package/dist/docs/copy-text.md +9 -7
  45. package/dist/docs/document-field.md +1 -1
  46. package/dist/docs/email-field.md +2 -1
  47. package/dist/docs/email-identification.md +12 -2
  48. package/dist/docs/footer-copyright.md +4 -2
  49. package/dist/docs/form.md +54 -18
  50. package/dist/docs/free-shipping-progress.md +6 -5
  51. package/dist/docs/identity-edit.md +8 -2
  52. package/dist/docs/identity-summary.md +18 -5
  53. package/dist/docs/installments.md +46 -32
  54. package/dist/docs/my-account-profile.md +16 -11
  55. package/dist/docs/navigation-link.md +5 -3
  56. package/dist/docs/order-cancel.md +3 -1
  57. package/dist/docs/order-delivery-address.md +7 -5
  58. package/dist/docs/order-delivery-group.md +1 -3
  59. package/dist/docs/order-details-back.md +1 -1
  60. package/dist/docs/order-details-header.md +1 -4
  61. package/dist/docs/order-invoice-download.md +60 -0
  62. package/dist/docs/order-list-row.md +1 -1
  63. package/dist/docs/order-list-search.md +13 -20
  64. package/dist/docs/order-list-views.md +11 -17
  65. package/dist/docs/order-list.md +12 -5
  66. package/dist/docs/order-payment.md +9 -7
  67. package/dist/docs/order-placed-date.md +1 -1
  68. package/dist/docs/order-placed-payment-summary.md +28 -15
  69. package/dist/docs/order-placed.md +49 -14
  70. package/dist/docs/order-progress.md +1 -1
  71. package/dist/docs/order-status-badge.md +20 -9
  72. package/dist/docs/order-summary.md +2 -2
  73. package/dist/docs/order-tracking-link.md +58 -0
  74. package/dist/docs/payment-method-badges.md +30 -4
  75. package/dist/docs/payment-methods.md +1 -1
  76. package/dist/docs/payment.md +36 -17
  77. package/dist/docs/phone-field.md +4 -4
  78. package/dist/docs/place-order.md +84 -36
  79. package/dist/docs/postal-code-field.md +3 -1
  80. package/dist/docs/pricing.md +21 -23
  81. package/dist/docs/product-gallery.md +19 -20
  82. package/dist/docs/product-information.md +13 -9
  83. package/dist/docs/product-list.md +7 -6
  84. package/dist/docs/product-shelf.md +0 -1
  85. package/dist/docs/promotion.md +4 -2
  86. package/dist/docs/region.md +23 -15
  87. package/dist/docs/save-card.md +7 -6
  88. package/dist/docs/saved-cards.md +65 -36
  89. package/dist/docs/shipping-address.md +57 -21
  90. package/dist/docs/shipping-simulator.md +53 -15
  91. package/dist/docs/sign-in-disclosure.md +176 -0
  92. package/dist/docs/sign-in-form.md +78 -47
  93. package/dist/docs/sign-in-redirect.md +4 -2
  94. package/dist/docs/sign-in.md +173 -0
  95. package/dist/docs/toast.md +15 -4
  96. package/dist/docs/variant-selector.md +15 -11
  97. package/dist/domain-logic/dist/checkout/rules.js +33 -0
  98. package/dist/domain-logic/dist/checkout/rules.js.map +1 -0
  99. package/dist/graphql/types.d.ts +4 -14
  100. package/dist/index.d.ts +2 -2
  101. package/dist/internal/client.d.ts +1 -3
  102. package/dist/internal/client.js +1 -1
  103. package/dist/internal/server.d.ts +7 -4
  104. package/dist/internal/server.js +7 -4
  105. package/dist/matter/access-code-sign-in/access-code-sign-in.d.ts +250 -66
  106. package/dist/matter/access-code-sign-in/access-code-sign-in.js +459 -188
  107. package/dist/matter/access-code-sign-in/access-code-sign-in.js.map +1 -1
  108. package/dist/matter/account-address-book/account-address-book-delete.d.ts +78 -40
  109. package/dist/matter/account-address-book/account-address-book-delete.js +86 -50
  110. package/dist/matter/account-address-book/account-address-book-delete.js.map +1 -1
  111. package/dist/matter/account-address-book/account-address-book-deliverability.d.ts +21 -4
  112. package/dist/matter/account-address-book/account-address-book-deliverability.js +45 -17
  113. package/dist/matter/account-address-book/account-address-book-deliverability.js.map +1 -1
  114. package/dist/matter/account-address-book/account-address-book-form-parts.d.ts +74 -19
  115. package/dist/matter/account-address-book/account-address-book-form-parts.js +77 -26
  116. package/dist/matter/account-address-book/account-address-book-form-parts.js.map +1 -1
  117. package/dist/matter/account-address-book/account-address-book-profile-notice.d.ts +27 -6
  118. package/dist/matter/account-address-book/account-address-book-profile-notice.js +55 -28
  119. package/dist/matter/account-address-book/account-address-book-profile-notice.js.map +1 -1
  120. package/dist/matter/account-address-book/account-address-book.d.ts +67 -14
  121. package/dist/matter/account-address-book/account-address-book.js +74 -33
  122. package/dist/matter/account-address-book/account-address-book.js.map +1 -1
  123. package/dist/matter/account-address-book/account-address-country-switch.js +3 -3
  124. package/dist/matter/account-address-book/account-address-country-switch.js.map +1 -1
  125. package/dist/matter/account-address-book/account-address-country.d.ts +66 -20
  126. package/dist/matter/account-address-book/account-address-country.js +83 -49
  127. package/dist/matter/account-address-book/account-address-country.js.map +1 -1
  128. package/dist/matter/account-address-book/account-address-form-context.d.ts +8 -4
  129. package/dist/matter/account-address-book/account-address-form-context.js.map +1 -1
  130. package/dist/matter/account-address-book/account-address-form.d.ts +468 -45
  131. package/dist/matter/account-address-book/account-address-form.js +518 -155
  132. package/dist/matter/account-address-book/account-address-form.js.map +1 -1
  133. package/dist/matter/account-address-book/account-address-record.d.ts +27 -6
  134. package/dist/matter/account-address-book/account-address-record.js +32 -17
  135. package/dist/matter/account-address-book/account-address-record.js.map +1 -1
  136. package/dist/matter/account-menu/account-menu.d.ts +34 -17
  137. package/dist/matter/account-menu/account-menu.js +46 -54
  138. package/dist/matter/account-menu/account-menu.js.map +1 -1
  139. package/dist/matter/account-menu-identity/account-menu-identity.js +1 -1
  140. package/dist/matter/account-saved-cards/account-saved-cards.d.ts +108 -28
  141. package/dist/matter/account-saved-cards/account-saved-cards.js +177 -122
  142. package/dist/matter/account-saved-cards/account-saved-cards.js.map +1 -1
  143. package/dist/matter/billing-address/billing-address.d.ts +156 -74
  144. package/dist/matter/billing-address/billing-address.js +227 -156
  145. package/dist/matter/billing-address/billing-address.js.map +1 -1
  146. package/dist/matter/breadcrumb/breadcrumb.d.ts +21 -3
  147. package/dist/matter/breadcrumb/breadcrumb.js +24 -9
  148. package/dist/matter/breadcrumb/breadcrumb.js.map +1 -1
  149. package/dist/matter/buy-button/buy-button.d.ts +8 -11
  150. package/dist/matter/buy-button/buy-button.js +10 -19
  151. package/dist/matter/buy-button/buy-button.js.map +1 -1
  152. package/dist/matter/card-entry/card-capture.js +12 -4
  153. package/dist/matter/card-entry/card-capture.js.map +1 -1
  154. package/dist/matter/card-entry/card-entry.d.ts +123 -32
  155. package/dist/matter/card-entry/card-entry.js +348 -221
  156. package/dist/matter/card-entry/card-entry.js.map +1 -1
  157. package/dist/matter/cart/cart-namespace.d.ts +7 -1
  158. package/dist/matter/cart/cart-namespace.js +7 -1
  159. package/dist/matter/cart/cart-namespace.js.map +1 -1
  160. package/dist/matter/cart/cart.d.ts +402 -61
  161. package/dist/matter/cart/cart.js +624 -153
  162. package/dist/matter/cart/cart.js.map +1 -1
  163. package/dist/matter/cart-destination/cart-destination.d.ts +30 -19
  164. package/dist/matter/cart-destination/cart-destination.js +160 -70
  165. package/dist/matter/cart-destination/cart-destination.js.map +1 -1
  166. package/dist/matter/cart-item/cart-item.d.ts +144 -17
  167. package/dist/matter/cart-item/cart-item.js +166 -49
  168. package/dist/matter/cart-item/cart-item.js.map +1 -1
  169. package/dist/matter/cart-item-compositions/cart-item-compositions.js +1 -1
  170. package/dist/matter/cart-item-compositions/cart-item-compositions.js.map +1 -1
  171. package/dist/matter/cart-item-customizations/cart-item-customizations.js +1 -1
  172. package/dist/matter/cart-item-customizations/cart-item-customizations.js.map +1 -1
  173. package/dist/matter/cart-item-discounts/cart-item-discounts.js +1 -1
  174. package/dist/matter/cart-item-discounts/cart-item-discounts.js.map +1 -1
  175. package/dist/matter/cart-item-seller-info/cart-item-seller-info.js +1 -1
  176. package/dist/matter/cart-item-seller-info/cart-item-seller-info.js.map +1 -1
  177. package/dist/matter/cart-item-variations/cart-item-variations.js +1 -1
  178. package/dist/matter/cart-item-variations/cart-item-variations.js.map +1 -1
  179. package/dist/matter/cart-totalizer/cart-totalizer.d.ts +43 -9
  180. package/dist/matter/cart-totalizer/cart-totalizer.js +46 -17
  181. package/dist/matter/cart-totalizer/cart-totalizer.js.map +1 -1
  182. package/dist/matter/challenge-return/challenge-return.d.ts +62 -0
  183. package/dist/matter/challenge-return/challenge-return.js +241 -0
  184. package/dist/matter/challenge-return/challenge-return.js.map +1 -0
  185. package/dist/matter/client-preferences/client-preferences-fieldset.d.ts +86 -17
  186. package/dist/matter/client-preferences/client-preferences-fieldset.js +126 -54
  187. package/dist/matter/client-preferences/client-preferences-fieldset.js.map +1 -1
  188. package/dist/matter/client-preferences/client-preferences.d.ts +50 -7
  189. package/dist/matter/client-preferences/client-preferences.js +84 -15
  190. package/dist/matter/client-preferences/client-preferences.js.map +1 -1
  191. package/dist/matter/client-profile/client-profile.d.ts +44 -9
  192. package/dist/matter/client-profile/client-profile.js +64 -24
  193. package/dist/matter/client-profile/client-profile.js.map +1 -1
  194. package/dist/matter/client-profile-form/client-profile-fieldset.d.ts +119 -22
  195. package/dist/matter/client-profile-form/client-profile-fieldset.js +161 -73
  196. package/dist/matter/client-profile-form/client-profile-fieldset.js.map +1 -1
  197. package/dist/matter/delivery-options/delivery-options-accordion.d.ts +49 -4
  198. package/dist/matter/delivery-options/delivery-options-accordion.js +67 -14
  199. package/dist/matter/delivery-options/delivery-options-accordion.js.map +1 -1
  200. package/dist/matter/delivery-options/delivery-options-controller.d.ts +69 -9
  201. package/dist/matter/delivery-options/delivery-options-controller.js +80 -14
  202. package/dist/matter/delivery-options/delivery-options-controller.js.map +1 -1
  203. package/dist/matter/delivery-options/delivery-options.d.ts +37 -2
  204. package/dist/matter/delivery-options/delivery-options.js +75 -12
  205. package/dist/matter/delivery-options/delivery-options.js.map +1 -1
  206. package/dist/matter/delivery-options/use-delivery-option-selection.d.ts +1 -0
  207. package/dist/matter/delivery-options/use-delivery-option-selection.js +1 -1
  208. package/dist/matter/delivery-options/use-delivery-option-selection.js.map +1 -1
  209. package/dist/matter/email-identification/email-identification.d.ts +25 -3
  210. package/dist/matter/email-identification/email-identification.js +49 -12
  211. package/dist/matter/email-identification/email-identification.js.map +1 -1
  212. package/dist/matter/free-shipping-progress/free-shipping-progress.d.ts +79 -7
  213. package/dist/matter/free-shipping-progress/free-shipping-progress.js +77 -20
  214. package/dist/matter/free-shipping-progress/free-shipping-progress.js.map +1 -1
  215. package/dist/matter/identity-edit/identity-edit.d.ts +52 -10
  216. package/dist/matter/identity-edit/identity-edit.js +47 -19
  217. package/dist/matter/identity-edit/identity-edit.js.map +1 -1
  218. package/dist/matter/identity-summary/identity-summary.d.ts +76 -22
  219. package/dist/matter/identity-summary/identity-summary.js +95 -45
  220. package/dist/matter/identity-summary/identity-summary.js.map +1 -1
  221. package/dist/matter/installments/installments-select.d.ts +98 -16
  222. package/dist/matter/installments/installments-select.js +156 -48
  223. package/dist/matter/installments/installments-select.js.map +1 -1
  224. package/dist/matter/installments/installments.d.ts +96 -16
  225. package/dist/matter/installments/installments.js +115 -40
  226. package/dist/matter/installments/installments.js.map +1 -1
  227. package/dist/matter/installments/use-installments-selection.d.ts +1 -0
  228. package/dist/matter/installments/use-installments-selection.js +8 -3
  229. package/dist/matter/installments/use-installments-selection.js.map +1 -1
  230. package/dist/matter/my-account-profile/my-account-profile.d.ts +358 -103
  231. package/dist/matter/my-account-profile/my-account-profile.js +370 -174
  232. package/dist/matter/my-account-profile/my-account-profile.js.map +1 -1
  233. package/dist/matter/order-cancel/order-cancel.d.ts +65 -19
  234. package/dist/matter/order-cancel/order-cancel.js +118 -51
  235. package/dist/matter/order-cancel/order-cancel.js.map +1 -1
  236. package/dist/matter/order-delivery-address/order-delivery-address.d.ts +8 -18
  237. package/dist/matter/order-delivery-address/order-delivery-address.js +14 -23
  238. package/dist/matter/order-delivery-address/order-delivery-address.js.map +1 -1
  239. package/dist/matter/order-delivery-group/order-delivery-group.d.ts +40 -24
  240. package/dist/matter/order-delivery-group/order-delivery-group.js +64 -74
  241. package/dist/matter/order-delivery-group/order-delivery-group.js.map +1 -1
  242. package/dist/matter/order-details-header/order-details-header.d.ts +32 -15
  243. package/dist/matter/order-details-header/order-details-header.js +66 -58
  244. package/dist/matter/order-details-header/order-details-header.js.map +1 -1
  245. package/dist/matter/order-invoice-download/order-invoice-download.d.ts +78 -0
  246. package/dist/matter/order-invoice-download/order-invoice-download.js +173 -0
  247. package/dist/matter/order-invoice-download/order-invoice-download.js.map +1 -0
  248. package/dist/matter/order-list/order-list.d.ts +164 -19
  249. package/dist/matter/order-list/order-list.js +229 -87
  250. package/dist/matter/order-list/order-list.js.map +1 -1
  251. package/dist/matter/order-list-row/order-list-row.d.ts +6 -1
  252. package/dist/matter/order-list-row/order-list-row.js +3 -2
  253. package/dist/matter/order-list-row/order-list-row.js.map +1 -1
  254. package/dist/matter/order-list-search/order-list-search.d.ts +18 -26
  255. package/dist/matter/order-list-search/order-list-search.js +30 -69
  256. package/dist/matter/order-list-search/order-list-search.js.map +1 -1
  257. package/dist/matter/order-list-views/order-list-views.d.ts +15 -22
  258. package/dist/matter/order-list-views/order-list-views.js +22 -68
  259. package/dist/matter/order-list-views/order-list-views.js.map +1 -1
  260. package/dist/matter/order-payment/order-payment.d.ts +33 -10
  261. package/dist/matter/order-payment/order-payment.js +44 -39
  262. package/dist/matter/order-payment/order-payment.js.map +1 -1
  263. package/dist/matter/order-placed/order-placed-deliveries.d.ts +144 -12
  264. package/dist/matter/order-placed/order-placed-deliveries.js +115 -40
  265. package/dist/matter/order-placed/order-placed-deliveries.js.map +1 -1
  266. package/dist/matter/order-placed/order-placed.d.ts +198 -31
  267. package/dist/matter/order-placed/order-placed.js +230 -139
  268. package/dist/matter/order-placed/order-placed.js.map +1 -1
  269. package/dist/matter/order-placed-date/order-placed-date.d.ts +5 -8
  270. package/dist/matter/order-placed-date/order-placed-date.js +7 -8
  271. package/dist/matter/order-placed-date/order-placed-date.js.map +1 -1
  272. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.d.ts +90 -17
  273. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js +93 -117
  274. package/dist/matter/order-placed-payment-summary/order-placed-payment-summary.js.map +1 -1
  275. package/dist/matter/order-progress/order-progress.d.ts +19 -8
  276. package/dist/matter/order-progress/order-progress.js +37 -45
  277. package/dist/matter/order-progress/order-progress.js.map +1 -1
  278. package/dist/matter/order-summary/order-summary.d.ts +19 -9
  279. package/dist/matter/order-summary/order-summary.js +38 -44
  280. package/dist/matter/order-summary/order-summary.js.map +1 -1
  281. package/dist/matter/order-tracking-link/order-tracking-link.d.ts +43 -0
  282. package/dist/matter/order-tracking-link/order-tracking-link.js +107 -0
  283. package/dist/matter/order-tracking-link/order-tracking-link.js.map +1 -0
  284. package/dist/matter/payment/payment-card-capture.js +17 -10
  285. package/dist/matter/payment/payment-card-capture.js.map +1 -1
  286. package/dist/matter/payment/payment-controller.d.ts +4 -2
  287. package/dist/matter/payment/payment-controller.js +30 -9
  288. package/dist/matter/payment/payment-controller.js.map +1 -1
  289. package/dist/matter/payment/payment-method-badges.js +1 -1
  290. package/dist/matter/payment/payment.d.ts +69 -19
  291. package/dist/matter/payment/payment.js +101 -35
  292. package/dist/matter/payment/payment.js.map +1 -1
  293. package/dist/matter/payment/use-payment-method-selection.d.ts +25 -4
  294. package/dist/matter/payment/use-payment-method-selection.js +94 -32
  295. package/dist/matter/payment/use-payment-method-selection.js.map +1 -1
  296. package/dist/matter/payment/use-payment-submit-gate.d.ts +14 -3
  297. package/dist/matter/payment/use-payment-submit-gate.js +47 -8
  298. package/dist/matter/payment/use-payment-submit-gate.js.map +1 -1
  299. package/dist/matter/place-order/place-order.d.ts +171 -57
  300. package/dist/matter/place-order/place-order.js +376 -185
  301. package/dist/matter/place-order/place-order.js.map +1 -1
  302. package/dist/matter/pricing/pricing.d.ts +152 -13
  303. package/dist/matter/pricing/pricing.js +153 -52
  304. package/dist/matter/pricing/pricing.js.map +1 -1
  305. package/dist/matter/product-information/product-information.d.ts +27 -4
  306. package/dist/matter/product-information/product-information.js +32 -15
  307. package/dist/matter/product-information/product-information.js.map +1 -1
  308. package/dist/matter/product-list/product-list.d.ts +24 -6
  309. package/dist/matter/product-list/product-list.js +32 -26
  310. package/dist/matter/product-list/product-list.js.map +1 -1
  311. package/dist/matter/product-shelf/product-shelf.d.ts +1 -3
  312. package/dist/matter/product-shelf/product-shelf.js +2 -15
  313. package/dist/matter/product-shelf/product-shelf.js.map +1 -1
  314. package/dist/matter/region/region.d.ts +33 -16
  315. package/dist/matter/region/region.js +59 -52
  316. package/dist/matter/region/region.js.map +1 -1
  317. package/dist/matter/save-card/save-card.d.ts +43 -8
  318. package/dist/matter/save-card/save-card.js +61 -23
  319. package/dist/matter/save-card/save-card.js.map +1 -1
  320. package/dist/matter/saved-cards/saved-cards.d.ts +113 -21
  321. package/dist/matter/saved-cards/saved-cards.js +349 -77
  322. package/dist/matter/saved-cards/saved-cards.js.map +1 -1
  323. package/dist/matter/shipping-address/address-payload.js +25 -0
  324. package/dist/matter/shipping-address/address-payload.js.map +1 -0
  325. package/dist/matter/shipping-address/country-descriptors.js +71 -71
  326. package/dist/matter/shipping-address/country-descriptors.js.map +1 -1
  327. package/dist/matter/shipping-address/library-country-descriptors.js +136 -0
  328. package/dist/matter/shipping-address/library-country-descriptors.js.map +1 -0
  329. package/dist/matter/shipping-address/shipping-address.d.ts +312 -45
  330. package/dist/matter/shipping-address/shipping-address.js +443 -188
  331. package/dist/matter/shipping-address/shipping-address.js.map +1 -1
  332. package/dist/matter/shipping-simulator/shipping-simulator.d.ts +250 -24
  333. package/dist/matter/shipping-simulator/shipping-simulator.js +257 -51
  334. package/dist/matter/shipping-simulator/shipping-simulator.js.map +1 -1
  335. package/dist/matter/sign-in/sign-in.d.ts +108 -0
  336. package/dist/matter/sign-in/sign-in.js +285 -0
  337. package/dist/matter/sign-in/sign-in.js.map +1 -0
  338. package/dist/matter/sign-in-disclosure/sign-in-disclosure.d.ts +81 -0
  339. package/dist/matter/sign-in-disclosure/sign-in-disclosure.js +232 -0
  340. package/dist/matter/sign-in-disclosure/sign-in-disclosure.js.map +1 -0
  341. package/dist/matter/sign-in-form/sign-in-form.d.ts +93 -62
  342. package/dist/matter/sign-in-form/sign-in-form.js +134 -144
  343. package/dist/matter/sign-in-form/sign-in-form.js.map +1 -1
  344. package/dist/particles/binding-switcher/binding-switcher.d.ts +29 -8
  345. package/dist/particles/binding-switcher/binding-switcher.js +37 -26
  346. package/dist/particles/binding-switcher/binding-switcher.js.map +1 -1
  347. package/dist/particles/card-summary/card-summary.d.ts +38 -8
  348. package/dist/particles/card-summary/card-summary.js +55 -22
  349. package/dist/particles/card-summary/card-summary.js.map +1 -1
  350. package/dist/particles/carousel/carousel.d.ts +99 -11
  351. package/dist/particles/carousel/carousel.js +103 -35
  352. package/dist/particles/carousel/carousel.js.map +1 -1
  353. package/dist/particles/copy-text/copy-text.js +5 -15
  354. package/dist/particles/copy-text/copy-text.js.map +1 -1
  355. package/dist/particles/dialog/dialog.d.ts +14 -6
  356. package/dist/particles/dialog/dialog.js +6 -3
  357. package/dist/particles/dialog/dialog.js.map +1 -1
  358. package/dist/particles/drawer/drawer.js +1 -1
  359. package/dist/particles/drawer/drawer.js.map +1 -1
  360. package/dist/particles/email-field/email-field.d.ts +2 -1
  361. package/dist/particles/email-field/email-field.js +2 -1
  362. package/dist/particles/email-field/email-field.js.map +1 -1
  363. package/dist/particles/footer-link-group/footer-link-group.js +1 -0
  364. package/dist/particles/footer-link-group/footer-link-group.js.map +1 -1
  365. package/dist/particles/form/form.d.ts +85 -13
  366. package/dist/particles/form/form.js +91 -42
  367. package/dist/particles/form/form.js.map +1 -1
  368. package/dist/particles/gallery/gallery.d.ts +129 -12
  369. package/dist/particles/gallery/gallery.js +121 -34
  370. package/dist/particles/gallery/gallery.js.map +1 -1
  371. package/dist/particles/order-details-back/order-details-back.d.ts +5 -3
  372. package/dist/particles/order-details-back/order-details-back.js +8 -10
  373. package/dist/particles/order-details-back/order-details-back.js.map +1 -1
  374. package/dist/particles/order-status-badge/order-status-badge.d.ts +3 -3
  375. package/dist/particles/order-status-badge/order-status-badge.js +4 -20
  376. package/dist/particles/order-status-badge/order-status-badge.js.map +1 -1
  377. package/dist/particles/password-field/password-field.d.ts +1 -1
  378. package/dist/particles/password-field/password-field.js +2 -4
  379. package/dist/particles/password-field/password-field.js.map +1 -1
  380. package/dist/particles/phone-field/phone-field.d.ts +4 -4
  381. package/dist/particles/phone-field/phone-field.js +8 -11
  382. package/dist/particles/phone-field/phone-field.js.map +1 -1
  383. package/dist/particles/spinner/spinner.d.ts +24 -4
  384. package/dist/particles/spinner/spinner.js +25 -9
  385. package/dist/particles/spinner/spinner.js.map +1 -1
  386. package/dist/particles/switch/switch.d.ts +4 -1
  387. package/dist/particles/switch/switch.js +3 -1
  388. package/dist/particles/switch/switch.js.map +1 -1
  389. package/dist/particles/toast/toast.d.ts +29 -3
  390. package/dist/particles/toast/toast.js +34 -10
  391. package/dist/particles/toast/toast.js.map +1 -1
  392. package/dist/schema.graphql +164 -56
  393. package/dist/sdk/address/address-fields.js +4 -4
  394. package/dist/sdk/address/address-fields.js.map +1 -1
  395. package/dist/sdk/address/constants.js +1 -6
  396. package/dist/sdk/address/constants.js.map +1 -1
  397. package/dist/sdk/address/messages.js +44 -44
  398. package/dist/sdk/address/messages.js.map +1 -1
  399. package/dist/sdk/address/postal-code.js +15 -2
  400. package/dist/sdk/address/postal-code.js.map +1 -1
  401. package/dist/sdk/address/types.d.ts +5 -3
  402. package/dist/sdk/address/validate-address.js +3 -3
  403. package/dist/sdk/address/validate-address.js.map +1 -1
  404. package/dist/sdk/analytics/use-analytics-actions.d.ts +1 -1
  405. package/dist/sdk/analytics/use-analytics-actions.js.map +1 -1
  406. package/dist/sdk/cart/use-cart-actions.js.map +1 -1
  407. package/dist/sdk/data/index.js +2 -2
  408. package/dist/sdk/data/reactive-list-item-data-provider.js +2 -2
  409. package/dist/sdk/generated/gql.js +1 -1
  410. package/dist/sdk/generated/gql.js.map +1 -1
  411. package/dist/sdk/generated/graphql.d.ts +20 -5
  412. package/dist/sdk/generated/graphql.js +6 -0
  413. package/dist/sdk/generated/graphql.js.map +1 -1
  414. package/dist/sdk/i18n/format-error-code.js +23 -0
  415. package/dist/sdk/i18n/format-error-code.js.map +1 -0
  416. package/dist/sdk/i18n/message-formatter.d.ts +22 -0
  417. package/dist/sdk/i18n/message-formatter.js +140 -0
  418. package/dist/sdk/i18n/message-formatter.js.map +1 -0
  419. package/dist/sdk/i18n/use-message-formatter.d.ts +8 -0
  420. package/dist/sdk/i18n/use-message-formatter.js +18 -0
  421. package/dist/sdk/i18n/use-message-formatter.js.map +1 -0
  422. package/dist/sdk/identity/use-identity-actions.js.map +1 -1
  423. package/dist/sdk/navigation/redirect.d.ts +2 -0
  424. package/dist/sdk/navigation/redirect.js +19 -1
  425. package/dist/sdk/navigation/redirect.js.map +1 -1
  426. package/dist/sdk/navigation/types.d.ts +13 -0
  427. package/dist/sdk/payment/bin-format.js +21 -0
  428. package/dist/sdk/payment/bin-format.js.map +1 -0
  429. package/dist/sdk/payment/challenge-return.js +37 -0
  430. package/dist/sdk/payment/challenge-return.js.map +1 -0
  431. package/dist/sdk/payment/confirmation-last-four.js +39 -0
  432. package/dist/sdk/payment/confirmation-last-four.js.map +1 -0
  433. package/dist/sdk/payment/gateway.d.ts +1 -1
  434. package/dist/sdk/payment/gateway.js +8 -2
  435. package/dist/sdk/payment/gateway.js.map +1 -1
  436. package/dist/sdk/payment/operations.d.ts +1 -0
  437. package/dist/sdk/payment/operations.js +28 -6
  438. package/dist/sdk/payment/operations.js.map +1 -1
  439. package/dist/sdk/payment/resumed-purchase.d.ts +1 -0
  440. package/dist/sdk/payment/resumed-purchase.js +119 -0
  441. package/dist/sdk/payment/resumed-purchase.js.map +1 -0
  442. package/dist/sdk/payment/security-code-mask.js +16 -23
  443. package/dist/sdk/payment/security-code-mask.js.map +1 -1
  444. package/dist/sdk/payment/submission-registry.js +6 -2
  445. package/dist/sdk/payment/submission-registry.js.map +1 -1
  446. package/dist/sdk/payment/use-payment-actions.d.ts +18 -3
  447. package/dist/sdk/payment/use-payment-actions.js +23 -4
  448. package/dist/sdk/payment/use-payment-actions.js.map +1 -1
  449. package/dist/sdk/payment/use-place-order.d.ts +10 -2
  450. package/dist/sdk/payment/use-place-order.js +138 -76
  451. package/dist/sdk/payment/use-place-order.js.map +1 -1
  452. package/dist/sdk/provider/storefront-provider.d.ts +1 -7
  453. package/dist/sdk/provider/storefront-provider.js +4 -11
  454. package/dist/sdk/provider/storefront-provider.js.map +1 -1
  455. package/dist/sdk/seo/entity-landing.d.ts +24 -0
  456. package/dist/sdk/seo/entity-landing.js +30 -0
  457. package/dist/sdk/seo/entity-landing.js.map +1 -0
  458. package/dist/sdk/seo/page-alternates.d.ts +3 -2
  459. package/dist/sdk/seo/page-alternates.js +4 -2
  460. package/dist/sdk/seo/page-alternates.js.map +1 -1
  461. package/dist/sdk/seo/search-landing.d.ts +42 -0
  462. package/dist/sdk/seo/search-landing.js +85 -0
  463. package/dist/sdk/seo/search-landing.js.map +1 -0
  464. package/dist/sdk/session/mock-session.d.ts +9 -0
  465. package/dist/sdk/session/mock-session.js +31 -0
  466. package/dist/sdk/session/mock-session.js.map +1 -0
  467. package/dist/sdk/session/read-server-session.d.ts +3 -0
  468. package/dist/sdk/session/read-server-session.js +5 -0
  469. package/dist/sdk/session/read-server-session.js.map +1 -1
  470. package/dist/sdk/session/return-path-segment.d.ts +10 -0
  471. package/dist/sdk/session/return-path-segment.js +15 -0
  472. package/dist/sdk/session/return-path-segment.js.map +1 -0
  473. package/dist/sdk/session/use-session-actions.js.map +1 -1
  474. package/dist/sdk/shipping/use-cart-shipping-simulation.js.map +1 -1
  475. package/dist/sdk/shipping/use-shipping-actions.js.map +1 -1
  476. package/dist/sdk/shipping/use-shipping-simulation.js.map +1 -1
  477. package/dist/utils/safe-href.d.ts +2 -0
  478. package/package.json +4 -3
  479. package/skills/storefront-asset/SKILL.md +2 -2
  480. package/skills/storefront-asset/references/api-side.md +27 -14
  481. package/skills/storefront-asset/references/findings.md +5 -2
  482. package/src/styles/tailwind.css +8 -0
  483. package/dist/matter/shipping-address/country-descriptors.d.ts +0 -1
  484. package/dist/message-locales.json +0 -7
  485. package/dist/sdk/i18n/client.d.ts +0 -4
  486. package/dist/sdk/i18n/client.js +0 -4
  487. package/dist/sdk/i18n/locales.d.ts +0 -8
  488. package/dist/sdk/i18n/locales.js +0 -57
  489. package/dist/sdk/i18n/locales.js.map +0 -1
  490. package/dist/sdk/i18n/messages/en-US.js +0 -183
  491. package/dist/sdk/i18n/messages/en-US.js.map +0 -1
  492. package/dist/sdk/i18n/messages/en-US.json +0 -178
  493. package/dist/sdk/i18n/messages/en-US.json.d.ts +0 -182
  494. package/dist/sdk/i18n/messages/es-ES.js +0 -183
  495. package/dist/sdk/i18n/messages/es-ES.js.map +0 -1
  496. package/dist/sdk/i18n/messages/es-ES.json +0 -178
  497. package/dist/sdk/i18n/messages/pt-BR.js +0 -183
  498. package/dist/sdk/i18n/messages/pt-BR.js.map +0 -1
  499. package/dist/sdk/i18n/messages/pt-BR.json +0 -178
  500. package/dist/sdk/i18n/provider.d.ts +0 -1
  501. package/dist/sdk/i18n/provider.js +0 -30
  502. package/dist/sdk/i18n/provider.js.map +0 -1
  503. package/dist/sdk/i18n/server.d.ts +0 -28
  504. package/dist/sdk/i18n/server.js +0 -59
  505. package/dist/sdk/i18n/server.js.map +0 -1
  506. package/dist/utils/resolve-authored-copy.js +0 -18
  507. package/dist/utils/resolve-authored-copy.js.map +0 -1
  508. package/dist/utils/substitute-template.js +0 -33
  509. package/dist/utils/substitute-template.js.map +0 -1
@@ -50,7 +50,7 @@ PaymentSectionRoot (or PaymentSectionController)
50
50
  ├── … other payment leaves (installments, save-card, billing, place-order)
51
51
  ```
52
52
 
53
- `CardEntry` renders its own four fields internally (number, holder name, expiration, CVV) — none of them are separately placeable schema elements. Holder name, expiration, and CVV each render conditionally, based on the detected payment method's validator config; the number field always renders. The CVV field is `type="text"`, same as the others, but carries an inline `-webkit-text-security: disc` style that renders its digits as dots — from the keystroke that completes the code, not from the first one. "Complete" is the detected method's own `cvvMask` length: three digits for most networks, four for Amex. Where that mask is pipe-delimited — one method id covering a 3-and-4-digit family — it is the *shortest* alternative, not the longest the field accepts: a shopper holding the 3-digit card of such a family never reaches a fourth digit, so the longer length would leave their complete, valid code legible for the rest of the flow. Below it the digits render in the clear, so a shopper can read back what they just typed and catch a typo; at it, the mask engages on that same render, never on blur and never after a delay, so a paste that delivers every digit in one event opens no legible window. The mask is derived from the value on every render rather than latched once, so backspacing below the length reveals what is left, and a network switch that widens the expectation (a 3-digit code against a newly detected Amex) reveals the code again until its fourth digit. `type="password"` was tried first and reverted: a real Chrome browser offered to save the security code to its password manager (synced to the signed-in account) the instant the field carried that type — pairing the expiration field as a fake "username" — which PCI-DSS forbids for a value that must not be retained past authorization. The CSS property obscures the digits without tripping that heuristic, since it never touches `type`. It is supported in Chromium and WebKit (and, per T70's spec, Firefox 114+ — unverified on this branch); an engine without it renders the digits in the clear, exactly what shipped before this mechanism existed — not a regression. The declaration is an inline style, which outranks a class-based rule from a compound swatch at normal importance — but not one that reaches for Tailwind's `!` important modifier: swatch tokens reach `className` verbatim with no validation, so a store that authors an arbitrary-property utility ending in `!` can still unmask the field. Nothing in this package closes that hole; it is accepted rather than fixed (masking spec, OQ-1). A helper line (`cvvDescription`) renders under the field by default, telling the shopper where to find the code; an empty string suppresses it (see Components below).
53
+ `CardEntry` renders its own four fields internally (number, holder name, expiration, CVV) — none of them are separately placeable schema elements. Holder name, expiration, and CVV each render conditionally, based on the detected payment method's validator config; the number field always renders. The CVV field is `type="text"`, same as the others, but carries an inline `-webkit-text-security: disc` style that renders its digits as dots while the field does not have focus: readable while the shopper is in the field, so they can read back what they just typed and catch a typo, and obscured the moment focus leaves it, with no length involved — a partial code is hidden on blur like a full one. Re-focusing a hidden code reveals it again, so a typo can be read and corrected. A disabled field counts as unfocused, because a browser fires no `blur` when a focused input becomes disabled; the focus flag is also reset when the card form collapses and when the input unmounts. The style is derived from the value and the focus flag on every render, never from an effect or a timer, so a paste or a browser autofill that delivers every digit while the field is unfocused, and the blur itself, open no legible window. The trade-off is deliberate: while the field has focus, a code the shopper just typed is on screen in the clear. `type="password"` was tried first and reverted: a real Chrome browser offered to save the security code to its password manager (synced to the signed-in account) the instant the field carried that type — pairing the expiration field as a fake "username" — which PCI-DSS forbids for a value that must not be retained past authorization. The CSS property obscures the digits without tripping that heuristic, since it never touches `type`. It is supported in Chromium and WebKit (and Firefox 114+, unverified on this branch); an engine without it renders the digits in the clear, exactly what shipped before this mechanism existed — not a regression. The declaration is an inline style, which outranks a class-based rule from a compound swatch at normal importance — but not one that reaches for Tailwind's `!` important modifier: swatch tokens reach `className` verbatim with no validation, so a store that authors an arbitrary-property utility ending in `!` can still unmask the field. Nothing in this package closes that hole; it is accepted rather than fixed. A helper line (`cvvDescription`) renders under the field by default, telling the shopper where to find the code; an empty string suppresses it (see Components below).
54
54
 
55
55
  **Composition rules:**
56
56
 
@@ -72,7 +72,7 @@ Collapsing clears the security code field, and only the security code. The card
72
72
 
73
73
  The authored `disabled` prop is independent of the collapse: a store that freezes the form with `disabled` sees no change, and a collapsed form may also be `disabled` — neither implies the other.
74
74
 
75
- In the checkout, the card number, holder name, expiration, and security code are cleared after every outcome in which the card target ran: the card POST resolved clean, resolved with errors or threw, or the target refused before any request. They are kept when the submission was refused before the target ran (the form's own check, a refused or untrusted transaction, the submit gate, no method selected) so the shopper can fix them.
75
+ In the checkout, the card number, holder name, expiration, and security code are cleared after every outcome in which the card target ran: the card POST resolved clean, resolved with errors or threw, or the target refused before any request. They are kept when the submission was refused before the target ran (the form's own check, a refused or untrusted transaction, the submit gate, no method selected) so the shopper can fix them. The values stay while the card request is open and clear as soon as it settles, ahead of the confirmation callback, so a callback error or timeout finds the form already empty. While the order attempt is resume-only the form accepts no input, so a card cannot be typed into a form that the next click would ignore.
76
76
 
77
77
  ---
78
78
 
@@ -115,7 +115,7 @@ The card number and security code are typed into `CardEntry`'s raw fields and PO
115
115
  {
116
116
  "routes": ["/checkout"],
117
117
  "headers": {
118
- "Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' https://acme.vtexvault.com; frame-ancestors 'none'; base-uri 'self'; object-src 'none'",
118
+ "Content-Security-Policy": "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https://acme.vtexassets.com; font-src 'self' data:; connect-src 'self' https://acme.vtexvault.com; form-action 'self'; frame-ancestors 'none'; base-uri 'self'; object-src 'none'",
119
119
  "Referrer-Policy": "strict-origin-when-cross-origin"
120
120
  }
121
121
  }
@@ -126,7 +126,7 @@ The card number and security code are typed into `CardEntry`'s raw fields and PO
126
126
  - **The vault host in `connect-src` is `https://{account}.vtexvault.com`, for the account the store serves.** That is the `NEXT_PUBLIC_ACCOUNT` the store is built with, which is not always `store.account` in `foundation.json`. Write the served account, with no wildcard: `https://*.vtexvault.com` would let a compromised script send the card to any tenant's vault.
127
127
  - **A policy that names the legacy payments host (`vtexpayments.com.br`) makes the browser block the card POST.** The order cannot be placed and nothing else on the page reports why. The generated app calls its own GraphQL at the relative `/api/graphql`, so `'self'` covers it; a store built with an absolute `NEXT_PUBLIC_STOREFRONT_API_URL` must add that origin.
128
128
  - **A rule's `routes` must name the page that composes `CardEntry`**, as the page's `route` spells it (`/checkout`, `/reserva/checkout/payment`). Renaming that page fails the compile instead of dropping the policy. A page added later that composes `CardEntry` carries nothing until its route is added to the rule.
129
- - **`script-src 'unsafe-inline'` is a known limit, and the policy does not stop injected script from sending the card off the page.** A static header cannot carry a nonce, so the inline scripts Next.js emits need it, and script injected into the page runs with the same rights. `connect-src` closes only `fetch`, XHR, beacons and WebSocket. The policy still leaves three channels open to any https host: an image request (`img-src https:`, which the page needs for catalog images), a form submission (no `form-action`) and a navigation (CSP cannot restrict one). What today's ruled policy does give is the vault origin pinned, no `'unsafe-eval'`, a page no other site can frame (`frame-ancestors 'none'`) and no `http:` or wildcard source. It does not replace keeping untrusted script off the payment page.
129
+ - **`script-src 'unsafe-inline'` is a known limit, and the policy does not stop injected script from sending the card off the page.** A static header cannot carry a nonce, so the inline scripts Next.js emits need it, and script injected into the page runs with the same rights. `connect-src` closes only `fetch`, XHR, beacons and WebSocket. The policy still leaves one channel open to any https host: a navigation (CSP cannot restrict one). The image and form-post channels are closed: `img-src` names exactly one image origin, `https://{account}.vtexassets.com`, so a store whose page loads images from another origin must list that origin exactly (a bare `https:` fails the header check), and `form-action 'self'` stops a form from posting to another origin (the check accepts `'self'` or `'none'` and nothing else). What today's ruled policy does give is the vault origin pinned, no `'unsafe-eval'`, a page no other site can frame (`frame-ancestors 'none'`) and no `http:` or wildcard source. It does not replace keeping untrusted script off the payment page.
130
130
  - **`frame-ancestors 'none'`** blanks the page inside any iframe, including an editor preview.
131
131
  - **reCAPTCHA needs the anti-bot origins, and the ruled policy above does not declare them yet.** The challenge frame and script come from `https://www.google.com` and `https://www.gstatic.com`, the origins `sdk/anti-bot` declares. The frame is blocked by `default-src 'self'` (no `frame-src`) and the script by `script-src 'self' 'unsafe-inline'`, so both need the anti-bot origins. The stores add them together with whoever wires `getAntiBotToken`; the header check accepts a `frame-src` that names only those two origins and rejects any other.
132
132
  - **Under `next dev` the served `script-src` also carries `'unsafe-eval'`; production does not.** React Refresh evaluates code in the browser, so the generated `next.config.ts` adds `'unsafe-eval'` to the `script-src` of every Content-Security-Policy it serves only when `NODE_ENV` is `development` (what `next dev` sets), which is what `pnpm dev:example` runs. Any build whose `NODE_ENV` is not `development` (the default for `next build` and `next start`) serves the declared policy byte for byte, so the live header check still runs against them: a dev response shows a `script-src` the store never declared, which `scripts/assert-payment-page-headers.mjs` rejects by design, so run that script against `next start`.
@@ -143,33 +143,44 @@ The only component. No internal sub-parts are separately schema-addressable.
143
143
 
144
144
  | Prop | Type | Default | Notes |
145
145
  |------|------|---------|-------|
146
- | `legend` | string | `"Card details"` | Fieldset legend/title. |
147
- | `numberLabel` | string | `"Card number"` | Card-number field label. |
148
146
  | `numberPlaceholder` | string | — | Card-number field placeholder. |
149
- | `holderNameLabel` | string | `"Cardholder name"` | Holder-name field label. Same noun as `holderNameRequiredMessage`, deliberately. |
150
147
  | `holderNamePlaceholder` | string | — | Holder-name field placeholder. |
151
- | `expirationLabel` | string | `"Expiration date"` | Expiration field label. |
152
- | `expirationPlaceholder` | string | `"MM/YY"` | Expiration field placeholder. |
153
- | `cvvLabel` | string | `"Security code"` | CVV field label. |
154
148
  | `cvvPlaceholder` | string | — | CVV field placeholder. |
155
- | `cvvDescription` | string | `"The 3-4 digit number on your card"` | Helper text under the CVV field. Empty string renders nothing. |
156
- | `numberRequiredMessage` | string | `"Card number is required"` | Shown when the card-number field is left blank. |
157
- | `numberInvalidMessage` | string | `"Enter a valid card number"` | Shown for a Luhn-invalid number, or one a configured network claims only as a prefix (the wrong length, or two networks at once). |
158
- | `networkNotAcceptedMessage` | string | `"This card is not from a network the store accepts"` | Shown for a Luhn-valid number that no configured network claims at all. |
159
- | `holderNameRequiredMessage` | string | `"Cardholder name is required"` | Shown when the holder-name field is left blank and the detected method requires it. |
160
- | `expirationRequiredMessage` | string | `"Expiration date is required"` | Shown when the expiration field is left blank and the detected method requires it. |
161
- | `expirationInvalidMessage` | string | `"Enter a valid expiration date"` | Shown for a structurally malformed `MM/YY`. |
162
- | `expirationExpiredMessage` | string | `"This card has expired"` | Shown for a structurally valid `MM/YY` that has already passed. |
163
- | `cvvRequiredMessage` | string | `"Security code is required"` | Shown when the CVV field is left blank and the detected method requires it. |
164
- | `cvvInvalidMessage` | string | `"Enter a valid security code"` | Shown for a CVV that fails the detected method's format. |
165
149
  | `disabled` | boolean | `false` | Disables every field. |
166
150
 
151
+ **Content model** (`card-entry`), every field read only from `$content` (a missing message renders empty and logs `message.missing`):
152
+
153
+ | Field | Source copy | Notes |
154
+ |-------|-------------|-------|
155
+ | `legend` | "Card details" | Fieldset legend/title. |
156
+ | `numberLabel` | "Card number" | Card-number field label. |
157
+ | `holderNameLabel` | "Cardholder name" | Holder-name field label. Same noun as `holderNameRequiredMessage`, deliberately. |
158
+ | `expirationLabel` | "Expiration date" | Expiration field label. |
159
+ | `expirationPlaceholder` | "MM/YY" | Expiration field placeholder. |
160
+ | `cvvLabel` | "Security code" | CVV field label. |
161
+ | `cvvDescription` | "The 3-4 digit number on your card" | Helper text under the CVV field. A blank message renders nothing. |
162
+ | `numberRequiredMessage` | "Card number is required" | Shown when the card-number field is left blank. |
163
+ | `numberInvalidMessage` | "Enter a valid card number" | Shown for a Luhn-invalid number, or one a configured network claims only as a prefix (the wrong length, or two networks at once). |
164
+ | `networkNotAcceptedMessage` | "This card is not from a network the store accepts" | Shown for a Luhn-valid number that no configured network claims at all. |
165
+ | `holderNameRequiredMessage` | "Cardholder name is required" | Shown when the holder-name field is left blank and the detected method requires it. |
166
+ | `expirationRequiredMessage` | "Expiration date is required" | Shown when the expiration field is left blank and the detected method requires it. |
167
+ | `expirationInvalidMessage` | "Enter a valid expiration date" | Shown for a structurally malformed `MM/YY`. |
168
+ | `expirationExpiredMessage` | "This card has expired" | Shown for a structurally valid `MM/YY` that has already passed. |
169
+ | `cvvRequiredMessage` | "Security code is required" | Shown when the CVV field is left blank and the detected method requires it. |
170
+ | `cvvInvalidMessage` | "Enter a valid security code" | Shown for a CVV that fails the detected method's format. |
171
+
167
172
  **Data:** declares no fragment and no actions. Card masks, required-ness, and network regexes come from the `card-capture` context — at checkout, the payment section's own methods. It reads no cart, so it works where no purchase is underway; the `add_payment_info` event and the cart total it needs belong to the payment section root.
168
173
 
169
174
  **Never a schema prop:** the card number and CVV. They exist only in this component's local state, for the lifetime of the mounted instance, and are never exposed as a bindable prop, a fragment field, or a value another component can read.
170
175
 
171
176
  **Validation timing:** the draft is validated before the place-order sequence opens a transaction, not only when the order is submitted — an invalid number, holder name, expiration, or CVV refuses with an inline field message before anything is charged or opened. The submission itself re-validates the same way, so the two can never disagree.
172
177
 
178
+ **Field timing:** each field validates when the shopper leaves it. A blur writes or clears only that field's error, and editing a field clears its own error. A blur never moves focus: pulling focus back to a half-typed expiration would trap a shopper who tabs away. The first invalid field takes focus only when Place order refuses the draft.
179
+
180
+ **Keyboard hint:** the number, expiration, and security code inputs carry `inputmode="numeric"`; the holder name input carries none. The attribute is advisory, so a keyboard that ignores it still shows letters.
181
+
182
+ **A card from a network the store does not accept:** a Luhn-valid number that no configured network claims, exactly or as a prefix, is told apart from a malformed one. The number field shows `networkNotAcceptedMessage` when the shopper leaves it, and Place order refuses with the code `UNSUPPORTED_CARD_NETWORK`, rendered through `PlaceOrder`'s `unsupportedNetworkMessage`, whether or not a payment method is selected. The refusal comes before any transaction opens. With no method selected, the form gives the place-order sequence a side-effect-free explanation, asked only at the missing-method step, so the order of the checks (path, method, submit gate, card check) does not change; that click sets no field error and moves no focus, because the blur message is already on the field. With a method selected, the card check itself refuses with the same code, shows the field error, and focuses the number. A prefix claimed at the wrong length, a prefix two networks both claim, a Luhn-invalid number, and a blank number keep their existing refusals, and so does a store whose methods carry no number pattern, where detection never ran. The code and both messages are fixed copy: nothing derived from the typed number appears in them or in a log line.
183
+
173
184
  **Selection timing:** network detection, the network mark, and the number's grouping follow every keystroke, but the detected method and the card's BIN (its first eight digits) reach the server only once the number field holds eight digits — never as a shorter prefix. Below eight digits nothing is sent, so the installments control and the persisted selection keep whatever was last recorded. The first digit typed off the new-card path does record that path, locally and with nothing sent, so a saved card that was checked stops being the selection as soon as the shopper starts a new number.
174
185
 
175
186
  ---
@@ -234,14 +245,16 @@ Style `CardEntry`'s root as a vertical stack of fields, matching a typical card
234
245
  | Network mark slot (`data-fs-card-entry-networks`) | Positions the single mark inside the input's box; absent entirely until a network is recognized | `absolute right-[12px] top-1/2 -translate-y-1/2 flex items-center` |
235
246
  | Network mark (`data-fs-card-entry-network`) | The recognized network itself — **at most one**, never a row. Carries `data-fs-card-entry-network-id` with the detected method's id | `flex items-center justify-center` |
236
247
  | Network icon (`data-fs-card-entry-network-icon`) | The icon, when the recognized network's name maps to one | `w-[20px] h-[20px]` |
237
- | Network fallback text (`data-fs-card-entry-network-fallback`) | The recognized network's name as text, when it maps to no icon; mutually exclusive with the icon | `text-[13px] font-medium text-[#333333]` |
238
- | Field labels (`data-fs-field-label`) | Small, muted label above each input | `text-[13px] font-medium text-[#333333] mb-[4px] block` |
239
- | Field inputs (`data-fs-field-control`) | The bordered text input | `border border-[#CCCCCC] rounded-[6px] px-[12px] py-[10px] text-[15px] w-full` |
240
- | Field errors (`data-fs-field-error`) | Inline validation message, small and red | `text-[12px] text-[#C8102E] mt-[4px] block` |
241
- | CVV helper text (`data-fs-card-entry-cvv-description`) | Small muted line under the CVV field | `text-[12px] text-[#666666] mt-[4px] block` |
248
+ | Network fallback text (`data-fs-card-entry-network-fallback`) | The recognized network's name as text, when it maps to no icon; mutually exclusive with the icon | `text-[13px] font-medium text-[var(--color-ink)]` |
249
+ | Field labels (`data-fs-field-label`) | Small, muted label above each input | `text-[13px] font-medium text-[var(--color-ink)] mb-[4px] block` |
250
+ | Field inputs (`data-fs-field-control`) | The bordered text input | `border border-[color-mix(in_srgb,var(--color-ink)_20%,transparent)] rounded-[var(--radius-base)] px-[12px] py-[10px] text-[15px] w-full` |
251
+ | Field errors (`data-fs-field-error`) | Inline validation message, small | `text-[12px] mt-[4px] block` |
252
+ | CVV helper text (`data-fs-card-entry-cvv-description`) | Small muted line under the CVV field | `text-[12px] text-[color-mix(in_srgb,var(--color-ink)_60%,transparent)] mt-[4px] block` |
242
253
  | Placing (`data-fs-card-entry-placing`) | Present while an order-placement sequence is in flight (T28), and while the order attempt is resume-only (its card may already have been sent, so the fields do not accept input) until the attempt is freed; the whole root is also `inert` and `aria-disabled` at that point, so style this hook rather than relying on pointer events alone | `opacity-60 cursor-not-allowed` |
243
254
  | Collapsed (`data-fs-card-entry-collapsed`) | Present on the styled root while the card payment is collapsed — the persisted selection is a method this section does not serve or one that is not a card, and a picker is composed to un-hide it (see Reveal and collapse); the root is also `inert`. **No transition or animation can run on it**: the actual hide is a `hidden` attribute one level up, on an unstyled wrapper this node cannot reach with any class, and an ancestor's `display:none` removes the whole subtree from rendering the instant it applies, before any transition on a descendant could ever paint. Use this hook only for one-shot conditional styling of the (already-unrendered) subtree, never for an entrance/exit animation | `opacity-60` |
244
255
 
256
+ A status color is a `visualIdentity` feedback role of the foundation: when the store declares them, color the error row `text-[var(--color-feedback-danger)]`, the success row `text-[var(--color-feedback-success)]` and the notice row `text-[var(--color-feedback-warning)]`, never with a hex.
257
+
245
258
  Styled minimal example:
246
259
 
247
260
  ```json
@@ -260,7 +273,7 @@ Styled minimal example:
260
273
  ## Accessibility Contract
261
274
 
262
275
  **What the platform promises:** the four fields sit inside a native `<fieldset>`/`<legend>` pair
263
- (`legend`, default `"Card details"`) — the pattern every other grouped payment control now follows
276
+ (the `legend` message) — the pattern every other grouped payment control now follows
264
277
  (T38, Decision 1). Each field carries its own `Field.Label`, `aria-invalid`, and an error message
265
278
  associated through `aria-describedby` once it fails validation; the CVV field's helper text
266
279
  (`cvvDescription`) is linked the same way. There is no composite keyboard widget here — every rendered
@@ -59,7 +59,15 @@ Code only: it has no catalog entry and is never placed in a schema, because a sc
59
59
 
60
60
  ### CardSummary
61
61
 
62
- The wrapper, `<span data-fs-card-summary>`. Carries `data-fs-card-summary-network="<icon>"` when the network has a mark and `data-fs-card-summary-labeled` when a label is present. No props.
62
+ The wrapper, `<span data-fs-card-summary>`. Carries `data-fs-card-summary-network="<icon>"` when the network has a mark and `data-fs-card-summary-labeled` when a label is present. No props besides `$content`.
63
+
64
+ It formats its `endingIn` message and hands it to the `CardSummaryDigits` inside it. A missing message logs an error, and the digits are then read bare. A `CardSummary` has `$content` only when the store places it or a parent forwards a message to it, as `AccountSavedCardRemoveDescription` forwards its `cardEndingIn`.
65
+
66
+ Content type `card-summary`:
67
+
68
+ | Content field | Description |
69
+ |---|---|
70
+ | endingIn | The last digits as read to assistive technology, with the `lastDigits` argument |
63
71
 
64
72
  ### CardSummaryMark
65
73
 
@@ -90,7 +98,7 @@ Decoration for the hidden digits, always `aria-hidden`. Nothing when empty or wh
90
98
 
91
99
  ### CardSummaryDigits
92
100
 
93
- The last four digits. Screen readers hear "ending in 1234" (message id `fs/card.ending-in`, in en-US, es-ES, and pt-BR); the visible digits are hidden from them. Nothing when the card has no digits. No props.
101
+ The last four digits. Screen readers hear the `endingIn` message of the `CardSummary` around them, such as "ending in 1234"; the visible digits are hidden from them. Outside a `CardSummary`, or with its message missing, screen readers hear the bare digits. Nothing when the card has no digits. No props.
94
102
 
95
103
  ---
96
104
 
@@ -150,9 +158,9 @@ Digits first, with another mask:
150
158
  |---|---|---|
151
159
  | `CardSummary` | Lines the parts up | `inline-flex items-center gap-[8px]` |
152
160
  | `CardSummaryMark` | Mark box; size the glyph through it | `inline-flex items-center [&_svg]:h-[24px] [&_svg]:w-[32px]` |
153
- | `CardSummaryName` | Name typography | `text-[14px] font-medium text-[#1F1F1F]` |
154
- | `CardSummaryMask` | Mask color and spacing | `text-[14px] tracking-[2px] text-[#6B6B6B]` |
155
- | `CardSummaryDigits` | Digit typography | `text-[14px] tabular-nums text-[#1F1F1F]` |
161
+ | `CardSummaryName` | Name typography | `text-[14px] font-medium text-[var(--color-text)]` |
162
+ | `CardSummaryMask` | Mask color and spacing | `text-[14px] tracking-[2px] text-[var(--color-text-muted)]` |
163
+ | `CardSummaryDigits` | Digit typography | `text-[14px] tabular-nums text-[var(--color-text)]` |
156
164
 
157
165
  `CardSummaryRoot` renders no element and is not styled.
158
166
 
@@ -160,12 +168,14 @@ Digits first, with another mask:
160
168
  {
161
169
  "card-summary": { "type": "CardSummary", "style": ["inline-flex", "items-center", "gap-[8px]"], "children": ["card-mark", "card-name", "card-mask", "card-digits"] },
162
170
  "card-mark": { "type": "CardSummaryMark", "style": ["inline-flex", "items-center", "[&_svg]:h-[24px]", "[&_svg]:w-[32px]"] },
163
- "card-name": { "type": "CardSummaryName", "style": ["text-[14px]", "font-medium", "text-[#1F1F1F]"] },
164
- "card-mask": { "type": "CardSummaryMask", "style": ["text-[14px]", "tracking-[2px]", "text-[#6B6B6B]"] },
165
- "card-digits": { "type": "CardSummaryDigits", "style": ["text-[14px]", "tabular-nums", "text-[#1F1F1F]"] }
171
+ "card-name": { "type": "CardSummaryName", "style": ["text-[14px]", "font-medium", "text-[var(--color-text)]"] },
172
+ "card-mask": { "type": "CardSummaryMask", "style": ["text-[14px]", "tracking-[2px]", "text-[var(--color-text-muted)]"] },
173
+ "card-digits": { "type": "CardSummaryDigits", "style": ["text-[14px]", "tabular-nums", "text-[var(--color-text)]"] }
166
174
  }
167
175
  ```
168
176
 
177
+ The colors that several elements share are variables of the page or layout `theme`, which declares them: `{"--color-text": "#1F1F1F", "--color-text-muted": "#6B6B6B"}`. A color used once stays a hex.
178
+
169
179
  ---
170
180
 
171
181
  ## Related
@@ -69,6 +69,8 @@ The field itself. Renders the `DocumentField` particle for the type `usePayment(
69
69
  | `errorMessage` | `string` | `'Invalid '` + the type's name | Shown inline when the typed value fails the type's validation on blur. |
70
70
  | `disabled` | `boolean` | `false` | Disables the input without hiding it — independent of the data-driven presence gate. |
71
71
 
72
+ The method decides whether the field shows and whether it is mandatory; the store's country decides which document it is. The type is the first displayable document the store's country row lists that this build knows, and a country row's own `required` flag is read by nobody. The SSN and SIN inputs carry `autocomplete="off"` so a browser does not offer them for autofill; the CPF and RFC inputs carry no `autocomplete` attribute, and every type stays `type="text"`.
73
+
72
74
  No `documentType` prop — the store's country decides it. No `required` prop — requiredness is always read from the selected method's validator, never authored.
73
75
 
74
76
  **Data:** declares no `fragment` of its own. It reads `selectedMethod`, `countryDocumentType`, `draft`, and `setDraftDocument` off the ambient `payment` context that `PaymentSectionRoot` / `PaymentSectionController` already fetches for.
@@ -109,8 +111,8 @@ No `documentType` prop — the store's country decides it. No `required` prop
109
111
 
110
112
  ## Known limitations
111
113
 
112
- - **Four person documents, by store country.** The country comes from `Payment.storeCountryCode` and `Payment.countries`. A store whose country resolves to no document type shows no field, refuses nothing and sends no document. A document typed while the field was shown is posted only while the selected method still asks for one and the country still resolves a type; the draft itself is not cleared. `label`, `placeholder` and `errorMessage` have no catalog default (the right copy depends on the country), so leave them unset unless the store serves one country.
113
- - **Server-driven requiredness renders the field correctly and is also enforced at submit time — but not by this component or by `CardEntry`.** This component marks the field required when the validator says so; a required-but-blank or malformed value (whether or not required) blocks Place Order client-side, before any transaction opens, **while the selected method's validator also says the document is displayable**. That gate lives in `matter/payment/use-payment-submit-gate.ts` (T24) and is consumed by `PlaceOrder`, not by this component or by `CardEntry`'s own `validateDraft` — this component's only job stays publishing `draft.document` via `setDraftDocument`. When the *currently* selected method's validator says `documentDisplayable: false`, the gate treats `draft.document` as inert either way (fix pass, findings R1/S4/S5): there is no field on screen for the shopper to read a leftover value from or correct it in, whether that value is left over from a previously selected, displayable method or simply blank. See `specs/cardholder-document-submit-gate.md` for the gate's full contract.
114
+ - **Four person documents, by store country.** The country comes from `Payment.storeCountryCode` and `Payment.countries`. A store whose country resolves to no document type shows no field, refuses nothing and sends no document, even when the selected method says a document is required, so the order is not refused client-side for it. A document typed while the field was shown is posted only while the selected method still asks for one and the country still resolves a type; the draft itself is not cleared. `label`, `placeholder` and `errorMessage` have no catalog default (the right copy depends on the country), so leave them unset unless the store serves one country.
115
+ - **Server-driven requiredness renders the field correctly and is also enforced at submit time — but not by this component or by `CardEntry`.** This component marks the field required when the validator says so; a required-but-blank or malformed value (whether or not required) blocks Place Order client-side, before any transaction opens, **while the selected method's validator also says the document is displayable**. That gate lives in `matter/payment/use-payment-submit-gate.ts` (T24) and is consumed by `PlaceOrder`, not by this component or by `CardEntry`'s own `validateDraft` — this component's only job stays publishing `draft.document` via `setDraftDocument`. The gate checks the value against the same country-resolved type the field shows. When the *currently* selected method's validator says `documentDisplayable: false`, the gate treats `draft.document` as inert either way (fix pass, findings R1/S4/S5): there is no field on screen for the shopper to read a leftover value from or correct it in, whether that value is left over from a previously selected, displayable method or simply blank.
114
116
 
115
117
  ---
116
118
 
@@ -120,7 +122,7 @@ No `documentType` prop — the store's country decides it. No `required` prop
120
122
 
121
123
  | Component | What you style | Plausible classes |
122
124
  |---|---|---|
123
- | `CardholderDocument` | The field's own label, input box, and inline error — same shape as a `Field.Root` | `flex flex-col gap-[4px] w-full` on the root; `text-[14px] font-medium text-[#1A1A1A]` for the label; `border border-[#CCCCCC] rounded-[6px] px-[12px] py-[8px] text-[14px]` for the input |
125
+ | `CardholderDocument` | The field's own label, input box, and inline error — same shape as a `Field.Root` | `flex flex-col gap-[4px] w-full` on the root; `text-[14px] font-medium text-[var(--color-ink)]` for the label; `border border-[color-mix(in_srgb,var(--color-ink)_20%,transparent)] rounded-[var(--radius-base)] px-[12px] py-[8px] text-[14px]` for the input |
124
126
  | Placing (`data-fs-cardholder-document-placing`) | Present while an order-placement sequence is in flight (T28); the field is also `inert` and `aria-disabled` at that point | `opacity-60 cursor-not-allowed` |
125
127
 
126
128
  Styled example:
@@ -35,7 +35,8 @@
35
35
  | `variant` | `'slide' \| 'scroll'` | `'slide'` | Transform slide vs. native overflow scroll |
36
36
  | `itemsPerPage` | number | `1` | How many children are visible per page |
37
37
  | `defaultPage` | number | `0` | Page selected on first render |
38
- | `label` | string | `'Carousel'` | `aria-label` for the carousel region |
38
+
39
+ The region's `aria-label` comes from the `$content` field `label` (content type `carousel-root`).
39
40
 
40
41
  ### CarouselTrack
41
42
 
@@ -43,16 +44,16 @@ Lays out and counts the children. No props beyond standard `div` attributes. Pla
43
44
 
44
45
  ### CarouselPrevious / CarouselNext
45
46
 
46
- Buttons that move one page. No props beyond standard `button` attributes; accept any children (Icon, text). Disabled at the respective end when `infiniteMode` is off.
47
+ Buttons that move one page. No props beyond standard `button` attributes; accept any children (Icon, text). Disabled at the respective end when `infiniteMode` is off. Their `aria-label` comes from the `$content` field `label` (content types `carousel-previous` and `carousel-next`).
47
48
 
48
49
  ### CarouselBullets
49
50
 
50
51
  | Prop | Type | Default | Notes |
51
52
  |------|------|---------|-------|
52
- | `ariaLabel` | string | `'Choose page'` | Label for the bullets group |
53
- | `bulletLabel` | string | `'Go to page {page}'` | Per-bullet label; `{page}` is the 1-based page number |
54
53
  | `hideWhenSingle` | boolean | `true` | Render nothing when there is a single page |
55
54
 
55
+ The labels come from `$content` (content type `carousel-bullets`): `ariaLabel` names the bullets group, and `bulletLabel` names each bullet, an ICU message whose `{page}` argument is the 1-based page number.
56
+
56
57
  Bullets support `ArrowLeft` / `ArrowRight` / `Home` / `End` keyboard navigation and mark the active page with `data-fs-carousel-bullet-selected="true"` and `aria-current`.
57
58
 
58
59
  ---
@@ -10,8 +10,8 @@
10
10
  Look in the **top of the cart page's main column**, above the free-shipping bar and the line items:
11
11
 
12
12
  - A single line reading **"Entregando em"** (or "Delivering to") followed by a **postal code and city, state**, beside a link such as **"Trocar localização?"**.
13
- - With no destination yet, the line reads a prompt such as **"Informe seu CEP"** beside the same link.
14
- - Pressing the link swaps the line for **one masked postal code field**, an **"Atualizar"** button, a **"Cancelar"** button and a short help line.
13
+ - With no destination yet, the bar shows just a prompt such as **"Informe seu CEP"**, often with a pin icon. Pressing it opens the field.
14
+ - Pressing the link (or the prompt) swaps the line for **one postal code field**, an **"Atualizar"** button and a **"Cancelar"** button.
15
15
 
16
16
  The image is the source of truth for layout.
17
17
 
@@ -40,14 +40,14 @@ A postal code control in the **site header** is not this component: that is `Reg
40
40
  CartDestinationRoot (provides 'cartDestination'; starts in read mode)
41
41
  ├── CartDestinationRead (read mode only)
42
42
  │ ├── CartDestinationValue (the destination, once set)
43
- │ ├── CartDestinationUnset (the store's invitation, while none is set)
43
+ │ ├── CartDestinationUnset (button: the invitation, while none is set; opens edit mode)
44
+ │ │ ├── Icon (optional pin)
44
45
  │ │ └── Text ("Informe seu CEP")
45
46
  │ └── CartDestinationChange (opens edit mode)
46
47
  └── CartDestinationForm (edit mode only)
47
48
  ├── CartDestinationPostalCodeControl
48
49
  ├── CartDestinationSubmit
49
50
  ├── CartDestinationCancel
50
- ├── Text (the help line, placed by the store)
51
51
  └── CartDestinationError
52
52
  ```
53
53
 
@@ -85,7 +85,7 @@ The destination as prefix, postal code, city and state, each in its own element.
85
85
 
86
86
  ### CartDestinationUnset
87
87
 
88
- Renders its children only once the destination resolved and none is set. Place the invitation inside it, within `CartDestinationRead`. No props.
88
+ A button that opens edit mode, with `aria-expanded` tied to the mode, exactly like `CartDestinationChange`. It renders only once the destination resolved and none is set. Its children are the invitation content (a `Text`, an `Icon`). Place it within `CartDestinationRead`. Its props are the props of a button; a consumer `onClick` runs before edit mode opens. When edit mode closes with no destination set, focus returns to this button. It ships no style of its own: like the change link, the store gives it a pointer cursor and an underline so it reads as clickable.
89
89
 
90
90
  ### CartDestinationChange
91
91
 
@@ -103,18 +103,23 @@ Renders only in edit mode. Enter in the field runs the submit, which flags an in
103
103
 
104
104
  ### CartDestinationPostalCodeControl
105
105
 
106
- A `PostalCodeField` bound to the draft, empty every time edit mode opens.
106
+ A plain text field bound to the draft, empty every time edit mode opens. Its limits (`maxLength`, `inputMode`) and its placeholder, the country's example postal code (`01310-100` for BRA), come from the postal code rules of the cart's country (`@vtex/country-forms`), which load when edit mode opens. There is no input mask: the shopper types the code as they know it, and Submit validates it against the country's rules.
107
+
108
+ While the rules load, the field and `CartDestinationSubmit` are disabled, and the field takes focus once it is enabled. If the load fails, the field is enabled as free text and nothing validates it. A country the rules do not cover (anything but ARG, BRA, CAN, CHL, COL, DEU, ECU, ESP, FRA, GBR, IRL, ITA, MEX, PER and USA) is free text too: no mask, no limit and no validation beyond a non-blank value.
109
+
110
+ The component keeps no mask table and does not read the shared country descriptors that `BillingAddress` and `ShippingAddress` use, so a Canadian or Mexican cart address is judged by the library's own rules for those countries and never by the Brazilian `99999-999` mask. For `CAN` the field takes at most 7 characters, shows the example `K1A 0A9`, accepts `k1a0b1` and writes it as `K1A 0B1`; the library refuses a first letter of D, F, I, O, Q, U or W and is looser than Canada Post elsewhere (`Z1A 0A9` passes). For `MEX` the field takes at most 5 characters on a numeric keyboard and refuses `00999` and `6600`; there is no mask. No case in `cart-destination.test.tsx` uses `CAN` or `MEX`: the behaviour comes from the same `@vtex/country-forms` rules and `sdk/address` functions the billing and shipping suites pin for those countries.
111
+
112
+ Because the rules come from the library, the cart can disagree with the other two forms where their descriptor patterns differ from the library. A US ZIP+4 typed without the hyphen (`021011234`) is refused here (`cart-destination.test.tsx` pins it) and accepted by billing and shipping.
107
113
 
108
114
  | Prop | Type | Default | Notes |
109
115
  |------|------|---------|-------|
110
- | `mask` | string | the country's mask | `99999-999` for BRA; also the mask Submit checks the draft against, and the typed value must still match the country's pattern, so a mask the pattern rejects (9 digits in BRA, for example) keeps Submit disabled |
111
116
  | `label` | string | — | Field label |
112
- | `placeholder` | string | — | Field placeholder |
113
- | `invalidMessage` | string | — | Shown for a draft that is incomplete or fails the country's pattern |
117
+ | `placeholder` | string | the country's example postal code | Field placeholder; wins over the example the rules give |
118
+ | `invalidMessage` | string | — | Shown for a typed code the country's rules refuse, once the field loses focus or Submit is attempted |
114
119
 
115
120
  ### CartDestinationSubmit
116
121
 
117
- Writes the typed postal code. Disabled until the draft is complete for the field's mask (the control's `mask`, else the country's), matches its pattern and differs from the current postal code, and while a write is pending (`aria-busy`). A pending write also waits for any cart line write in flight.
122
+ Writes the typed postal code. Disabled until the country's rules have loaded, the draft passes the country's postal code validation (any non-blank text when the rules failed to load or the country is not covered) and differs from the current postal code, and while a write is pending (`aria-busy`). The write sends the code in the country's own form (`04538132` becomes `04538-132`). A pending write also waits for any cart line write in flight.
118
123
 
119
124
  | Prop | Type | Default | Notes |
120
125
  |------|------|---------|-------|
@@ -135,13 +140,16 @@ Closes edit mode, discards the draft and any error, and writes nothing. Disabled
135
140
 
136
141
  ### CartDestinationError
137
142
 
138
- An alert announcing a failed write; renders nothing otherwise. A refused postal code and a network failure reach the client as the same error, so author one message that covers both.
143
+ An alert announcing a failed write; renders nothing otherwise. It words the error's `code` from its own content and never shows the server's text. A code it does not know (a newer server) shows `genericMessage` and logs `message.unknown-code`.
139
144
 
140
- | Prop | Type | Default | Notes |
141
- |------|------|---------|-------|
142
- | `genericMessage` | string | — | Authored message; the server's own is the fallback |
145
+ | Code | Message field |
146
+ |------|---------------|
147
+ | `INVALID_POSTAL_CODE` | `invalidPostalCodeMessage` |
148
+ | `NETWORK_ERROR` and every other `ShippingAddressUpdateErrorCode` | `genericMessage` |
149
+
150
+ A postal code the platform refuses upstream reaches the client as `NETWORK_ERROR` today, so write `genericMessage` to cover it.
143
151
 
144
- **Content model** (`cart-destination-error`): `genericMessage`.
152
+ **Content model** (`cart-destination-error`): `genericMessage`, `invalidPostalCodeMessage` (messages, no arguments).
145
153
 
146
154
  ---
147
155
 
@@ -160,7 +168,12 @@ An alert announcing a failed write; renders nothing otherwise. A refused postal
160
168
  "children": ["cart-destination-value", "cart-destination-unset", "cart-destination-change"]
161
169
  },
162
170
  "cart-destination-value": { "type": "CartDestinationValue", "props": { "prefix": "Entregando em" }, "style": ["flex", "gap-1"] },
163
- "cart-destination-unset": { "type": "CartDestinationUnset", "children": ["cart-destination-invitation"] },
171
+ "cart-destination-unset": {
172
+ "type": "CartDestinationUnset",
173
+ "style": ["flex", "items-center", "gap-1", "cursor-pointer", "underline"],
174
+ "children": ["cart-destination-unset-icon", "cart-destination-invitation"]
175
+ },
176
+ "cart-destination-unset-icon": { "type": "Icon", "props": { "name": "map-pin" } },
164
177
  "cart-destination-invitation": { "type": "Text", "props": { "as": "span", "children": "Informe seu CEP" } },
165
178
  "cart-destination-change": {
166
179
  "type": "CartDestinationChange",
@@ -174,20 +187,18 @@ An alert announcing a failed write; renders nothing otherwise. A refused postal
174
187
  "cart-destination-control",
175
188
  "cart-destination-submit",
176
189
  "cart-destination-cancel",
177
- "cart-destination-help",
178
190
  "cart-destination-error"
179
191
  ]
180
192
  },
181
193
  "cart-destination-control": { "type": "CartDestinationPostalCodeControl", "props": { "label": "CEP" } },
182
194
  "cart-destination-submit": { "type": "CartDestinationSubmit", "props": { "label": "Atualizar", "pendingLabel": "Atualizando…" } },
183
195
  "cart-destination-cancel": { "type": "CartDestinationCancel", "props": { "label": "Cancelar" } },
184
- "cart-destination-help": {
185
- "type": "Text",
186
- "props": { "as": "span", "children": "Após confirmar, a barra volta ao modo leitura com o novo CEP e cidade." }
187
- },
188
196
  "cart-destination-error": {
189
197
  "type": "CartDestinationError",
190
- "props": { "genericMessage": "Não foi possível usar esse CEP. Confira o número e tente de novo." },
198
+ "props": {
199
+ "genericMessage": "Não foi possível usar esse CEP. Confira o número e tente de novo.",
200
+ "invalidPostalCodeMessage": "CEP inválido. Confira o número e tente de novo."
201
+ },
191
202
  "style": ["text-sm", "text-red-700"]
192
203
  }
193
204
  }
@@ -205,7 +216,7 @@ An alert announcing a failed write; renders nothing otherwise. A refused postal
205
216
  | `data-fs-cart-destination-read` | `CartDestinationRead` | Read mode |
206
217
  | `data-fs-cart-destination-value` | `CartDestinationValue` | A destination is set |
207
218
  | `data-fs-cart-destination-value-prefix`, `-postal-code`, `-city`, `-state` | The piece's element | The piece exists |
208
- | `data-fs-cart-destination-unset` | `CartDestinationUnset` | No destination is set |
219
+ | `data-fs-cart-destination-unset` | `CartDestinationUnset` (a button) | No destination is set |
209
220
  | `data-fs-cart-destination-change` | `CartDestinationChange` | Always |
210
221
  | `data-fs-cart-destination-form` | `CartDestinationForm` | Edit mode |
211
222
  | `data-fs-cart-destination-postal-code-control` | `CartDestinationPostalCodeControl` | Always |
@@ -225,14 +236,16 @@ No part ships CSS of its own. Spacing, the link look of the change control and t
225
236
  - **Placing the postal code control, `CartDestinationSubmit` or `CartDestinationCancel` outside `CartDestinationForm`.** Only the form is bound to edit mode: outside it they render in read mode too, and Enter in the field no longer reaches the submit.
226
237
  - **Looking for an `open` prop or a controller.** The root owns edit mode; nothing outside it can open it.
227
238
  - **Omitting `CartDestinationUnset`.** With no destination the page then shows only the change link and no invitation.
239
+ - **Leaving `CartDestinationUnset` unstyled.** It is a button with no style of its own, so without a pointer cursor and an underline the invitation does not read as clickable. Give it the same look as `CartDestinationChange`.
240
+ - **Placing `CartDestinationChange` inside `CartDestinationUnset`.** The invitation is already a button, and a button cannot hold another. With no destination the regular change link repeats the invitation, so the store usually hides it while unset (for example with a `[&:has([data-fs-cart-destination-unset])>[data-fs-cart-destination-change]]:hidden` rule on `CartDestinationRead`).
228
241
  - **Expecting the Value to show what the shopper just typed.** It shows what the cart confirms.
229
- - **Authoring a "CEP não encontrado" message.** A refused code and a network failure are the same error today: author one message in `CartDestinationError`.
242
+ - **Passing a `mask` to the postal code control.** There is none: the field is plain text and the country's rules validate it.
243
+ - **Authoring only `invalidPostalCodeMessage` for "CEP não encontrado".** A postal code the platform refuses upstream reaches the client as a network failure today, so `genericMessage` must cover it too.
230
244
 
231
245
  ---
232
246
 
233
247
  ## Related
234
248
 
235
249
  - [[../free-shipping-progress/free-shipping-progress]] — the bar beside this component; it follows the destination write through the cache with no wiring.
236
- - [[../region/region]] — the header's region control; same postal code field, but a cookie and no cart write.
250
+ - [[../region/region]] — the header's region control; it keeps the masked postal code field and sets a cookie, with no cart write.
237
251
  - [[../shipping-address/shipping-address]] — the full delivery address on checkout, which this component's write shares with.
238
- - [[../../particles/postal-code-field/postal-code-field]] — the masked input this component composes.
@@ -15,10 +15,11 @@ A **cart line** — a repeated row in a cart or mini-cart list: a product thumbn
15
15
 
16
16
  ## Composition
17
17
 
18
- **`CartItemRoot` wraps the line-item template inside `CartList`.** The nesting is always:
18
+ **`CartItemRoot` wraps the line-item template inside `CartList`, `CartAvailableItems` or `CartUnavailableItems`.** The nesting is always:
19
19
 
20
20
  ```text
21
- CartList (iterator — renders its children once per cart line)
21
+ CartList (iterator — renders its children once per cart line; CartAvailableItems and
22
+ CartUnavailableItems iterate the purchasable and the unavailable lines the same way)
22
23
  └─ CartItemRoot (per-line data provider — owns the core slice)
23
24
  ├─ CartItemImage (optionally wrapped in CartItemProductLink; see below)
24
25
  ├─ CartItemName
@@ -37,7 +38,7 @@ CartList (iterator — renders its children once per cart line)
37
38
  ```
38
39
 
39
40
  - **`CartItemRoot` is required.** Every `CartItem*` core particle reads its data from the nearest `CartItemRoot` (build-time `requiresContext: 'cart-item-data'`). Placing a core particle directly inside `CartList` with no `CartItemRoot` between them is a build error. When authoring a cart or mini-cart line template, always insert a `CartItemRoot` between the `CartList` and the row's particles — it carries no visible styling of its own, so put the row layout classes on it (or on a `Container` inside it).
40
- - **`CartItemRoot` must be inside `CartList`.** It requires the `cart-item` iteration context that `CartList` provides.
41
+ - **`CartItemRoot` must be inside `CartList`, `CartAvailableItems` or `CartUnavailableItems`.** It requires the `cart-item` iteration context that each of the three provides.
41
42
 
42
43
  ## Image placeholder
43
44
 
@@ -66,7 +67,7 @@ CartItemRoot
66
67
 
67
68
  Navigation is client-side through the `NavigationLink` particle, which also owns `rel` hardening. The link is same-tab, and `target` is omitted from the props type rather than merely left out of the schema, because an image-only link cannot carry the "(opens in new tab)" cue a `_blank` destination needs.
68
69
 
69
- **Known gap:** a mini-cart drawer does **not** dismiss itself when a client-side navigation leaves it, so the overlay stays open over the product page. This is not specific to this leaf — `CartGoCheckout` behaves the same way — and it belongs to the drawer composition, not to a line leaf.
70
+ **Known gap:** a mini-cart drawer does **not** dismiss itself when a client-side navigation leaves it, so the overlay stays open over the product page. The product link behaves that way, and it belongs to the drawer composition, not to a line leaf. `CartGoCheckout` closes the drawer itself when it leaves for the checkout.
70
71
 
71
72
  Renders `a[data-fs-cart-item-product-link]`, which also carries the particle's `data-fs-navigation-link`.
72
73
 
@@ -81,7 +82,7 @@ CartItemRoot
81
82
  └─ CartItemRemove (removes this line from within its alert)
82
83
  ```
83
84
 
84
- **Label-vs-children nuance:** passing children to `CartItemUnavailable` **replaces** its built-in state label (`children ?? unavailableLabel(availability)`). A remove-on-alert composition therefore renders the label as an explicit child (e.g. a `Text`) next to the `CartItemRemove`; nesting the remove control alone leaves the alert without a label. The whole subtree is availability-gated: when the line is `AVAILABLE`, neither the label nor the nested remove control renders.
85
+ **Label-vs-children nuance:** passing children to `CartItemUnavailable` **replaces** its state label, which is one `$content` message per state (`outOfStockLabel` for `OUT_OF_STOCK`, `cannotBeDeliveredLabel` for `UNAVAILABLE`, `unavailableLabel` for any other state; content type `cart-item-unavailable`), formatted only when no children are given. A remove-on-alert composition therefore renders the label as an explicit child (e.g. a `Text`) next to the `CartItemRemove`; nesting the remove control alone leaves the alert without a label. The whole subtree is availability-gated: when the line is `AVAILABLE`, neither the label nor the nested remove control renders.
85
86
 
86
87
  For the cart-level counterparts (the "N items unavailable" summary and the bulk "remove all unavailable" button), see `cart.md` — Unavailable-item recovery.
87
88
 
@@ -90,8 +91,8 @@ For the cart-level counterparts (the "N items unavailable" summary and the bulk
90
91
  `CartItemRemove` awaits the removal and tells the shopper when it went through:
91
92
 
92
93
  - **Disabled while pending.** The button carries the native `disabled` attribute from the click until `removeItem` settles, so a second click sends no second request. A caller-passed `disabled` is kept.
93
- - **Toast on a clean result only.** When `removeItem` resolves with an empty `userErrors` list, the button raises one `success` toast titled `removedMessage` (default `Item removed`, editable per store). A non-empty list, a missing `removeItem`, or a rejection raises no toast; failures keep surfacing through `CartError`.
94
- - **`removedMessage`** is the only schema prop. The toast carries the title alone: no description, no action.
94
+ - **Toast on a clean result only.** When `removeItem` resolves with an empty `userErrors` list, the button raises one `success` toast titled by the `removedMessage` message (content type `cart-item-remove`). A non-empty list, a missing `removeItem`, or a rejection raises no toast; failures keep surfacing through `CartError`.
95
+ - **`removedMessage`** is a `$content` field, not a prop. The toast carries the title alone: no description, no action.
95
96
  - **Needs a `ToastProvider`.** The toast shows through the layout's `ToastList`. Without a `ToastProvider` above the line, no toast shows and the removal still works.
96
97
 
97
98
  ## Gift lines
@@ -101,17 +102,29 @@ A promotion can add a line the shopper never chose. `CartItem.gift` marks it, an
101
102
  ```text
102
103
  CartItemRoot (row carries data-fs-cart-item-gift-line on a gift line)
103
104
  ├─ CartItemListPrice (renders: a gift's sellingPrice is 0, so listPrice > sellingPrice)
104
- ├─ CartItemGift (renders "Free")
105
+ ├─ CartItemGift (renders its label message)
105
106
  └─ CartItemRemove (renders nothing)
106
107
  ```
107
108
 
108
- - **`CartItemGift`** is the free marker: a `span[data-fs-cart-item-gift][data-fs-text]` that renders **only** on a gift line, with the built-in English label `Free`. Passing children **replaces** the label (`children ?? 'Free'`), the same nuance `CartItemUnavailable` has. It is an **inline marker, not a wrapper** — its children are the label, so do not nest a block subtree in it. Place it beside `CartItemPrice`/`CartItemListPrice`.
109
+ - **`CartItemGift`** is the free marker: a `span[data-fs-cart-item-gift][data-fs-text]` that renders **only** on a gift line, with the `label` message (content type `cart-item-gift`). Passing children **replaces** the label, which is then not formatted, the same nuance `CartItemUnavailable` has. It is an **inline marker, not a wrapper** — its children are the label, so do not nest a block subtree in it. Place it beside `CartItemPrice`/`CartItemListPrice`.
109
110
  - **`CartItemRemove` suppresses itself** on a gift line. A gift is not the shopper's to discard in this phase, and since one template serves every row, the rule lives in the component rather than in the composition. This covers the remove-on-alert composition too: an unavailable gift renders its alert with no remove control, and recovery is the cart-level `CartRemoveUnavailable` batch (which still batches gift lines) plus server-side cart validation.
110
111
  - **`CartItemQuantity` suppresses itself** on a gift line too, the same way `CartItemRemove` does: a gift's quantity is promotion-controlled, and a `removeOnDecrement` stepper would otherwise let a shopper discard the gift by stepping it below the minimum — the one path the remove-control suppression did not cover. A store showing a gift's fixed quantity composes the read-only `CartItemQuantityValue`, which dispatches nothing.
111
112
  - **The row is marked** with `data-fs-cart-item-gift-line`, so a swatch can restyle a gift row without a component per variation — that is how a store hides the `R$ 0,00` selling price on a gift row.
112
113
 
113
114
  A gift whose `listPrice` is also 0 renders no list price (`CartItemListPrice`'s existing `listPrice <= sellingPrice` rule) and the marker carries the row alone.
114
115
 
116
+ ## Copy
117
+
118
+ Every copy-bearing leaf reads its text only from its own `$content`, whose fields are ICU messages. An unauthored message renders empty and logs `message.missing`. A store without CMS authors the fields on the element; a CMS-bound store keeps them in content.
119
+
120
+ | Component | Content type | Fields |
121
+ |---|---|---|
122
+ | `CartItemDiscountPercentage` | `cart-item-discount-percentage` | `template` (`percentage`, the integer percent) |
123
+ | `CartItemQuantity` | `cart-item-quantity` | `validationMessage` (`value`, the corrected amount) |
124
+ | `CartItemRemove` | `cart-item-remove` | `removedMessage` |
125
+ | `CartItemUnavailable` | `cart-item-unavailable` | `outOfStockLabel`, `cannotBeDeliveredLabel`, `unavailableLabel` |
126
+ | `CartItemGift` | `cart-item-gift` | `label` |
127
+
115
128
  ## Owned data
116
129
 
117
130
  `CartItemRoot`'s core fragment owns each line's `id`, `quantity`, `price`/`listPrice`/`sellingPrice`, `gift`, `ownTotal`/`total`, `variant { variantId, sku, name, image, availability }`, and the `product` core. `variant.availability` stays here (not a sub-matter) because it is read cross-cuttingly: `CartItemAvailable`/`CartItemUnavailable` gate on it, and `CartCheckoutGuard` reads it across all lines. `gift` stays here for the same reason — `CartItemGift`, `CartItemRemove`'s suppression, and the row's state hook all read it.
@@ -137,7 +150,9 @@ The stepper shows the line's quantity **in the unit the merchant sells in**, not
137
150
 
138
151
  Enforcement happens **on commit** — blur after typing, pointer release on the buttons, or a keyboard interaction — not per keystroke. A committed amount that is out of bounds or off the multiplier grid is clamped and snapped to the nearest valid amount (ties round up), then the remaining delta is dispatched as `increaseQuantity` / `decreaseQuantity` in purchasable units. No non-integer quantity can reach a mutation. Nothing is corrected at render time: a quantity already outside the authored bounds is displayed as-is and fires no write.
139
152
 
140
- A snap renders a notice as the last child of the stepper: `div[role="alert"][data-fs-cart-item-quantity-validation]`, carrying the `validationMessage` prop, whose `{value}` placeholder is replaced with the corrected displayed amount (default `Quantity adjusted to {value}.`). The root also carries `data-fs-cart-item-quantity-adjusted` plus `data-fs-cart-item-quantity-adjusted-reason="min" | "max" | "multiple"` for per-reason styling, and `data-fs-cart-item-quantity-multiplier="<value>"` whenever the multiplier is not `1`, so a measured line can be styled differently from a counted one.
153
+ A snap renders a notice as the last child of the stepper: `div[role="alert"][data-fs-cart-item-quantity-validation]`, carrying the `validationMessage` message (content type `cart-item-quantity`), whose `value` argument is the corrected displayed amount, formatted in the locale. The root also carries `data-fs-cart-item-quantity-adjusted` plus `data-fs-cart-item-quantity-adjusted-reason="min" | "max" | "multiple" | "platform"` for per-reason styling, and `data-fs-cart-item-quantity-multiplier="<value>"` whenever the multiplier is not `1`, so a measured line can be styled differently from a counted one.
154
+
155
+ `"platform"` appears when the line lands below the amount the stepper asked for, after the write succeeds: the platform stores the quantity it has in stock (or the per-item limit) instead of the one requested. The notice reuses the `validationMessage` message and reports the quantity the line holds, replaces a local snap from the same commit, and goes away when the shopper types again, commits again, or the line reaches the requested amount. A write that fails (`userErrors`) raises no platform notice, though a local snap from the same commit still shows. The stepper remembers the requested amount until the next commit or typing, so a line the platform lowers on cart load shows none, but a line that later drops below an amount this stepper already asked for shows the notice again.
141
156
 
142
157
  `removeOnDecrement`: a committed amount below the configured minimum removes the line instead of clamping, and no notice is raised. In that mode the decrement control is **not** disabled at the minimum — removal is the intended outcome there — and `data-fs-cart-item-at-min` still keys off the purchasable count. This never applies to a gift line: the stepper renders nothing there, so `removeOnDecrement` cannot discard a gift.
143
158
 
@@ -151,7 +166,7 @@ The measurement unit itself (`kg`, `un`) is not fetched, so a measured line rend
151
166
 
152
167
  `CartItemDiscounts` is another **optional** per-line data owner: it owns the line's `discounts` subtree via its own `reactive-list-item` fragment and requires only `cart-item`, so its data is fetched **only** on pages that place it. It renders one labeled row per featured promotion applied to the current cart line — the promotion `name` plus its amount (e.g. `Black Friday −R$ 20,00`, `Cupom −10%`) — so a shopper sees *which* promotion took *how much* off *this* item. It is the itemized companion to the aggregated `Discounts` totalizer line in the footer.
153
168
 
154
- Each row branches on `isPercentual`: a currency discount (`false`) formats the amount from the active storefront context; a percentual discount (`true`) renders `−N%` and is **not** currency-formatted. Every amount carries a presentational leading `−` — the source `value` is a positive amount. It **renders null when the line has no discounts** (empty or unresolved), so it appears only where a promotion actually fired. Place it under the line, near the price area. Must be inside `CartList`.
169
+ Each row branches on `isPercentual`: a currency discount (`false`) formats the amount from the active storefront context; a percentual discount (`true`) renders `−N%` and is **not** currency-formatted. Every amount carries a presentational leading `−` — the source `value` is a positive amount. It **renders null when the line has no discounts** (empty or unresolved), so it appears only where a promotion actually fired. Place it under the line, near the price area. Must be inside `CartList`, `CartAvailableItems` or `CartUnavailableItems`.
155
170
 
156
171
  **Distinct from `CartItemDiscountPercentage`.** `CartItemDiscountPercentage` is a single derived `−N%` **badge** computed from `listPrice`/`sellingPrice` with **no** promotion name; `CartItemDiscounts` is the **named list** of promotions sourced from the line's `discounts` field. They cover different concerns and may coexist on a line — the badge next to the price, the named list beneath it.
157
172
 
@@ -159,7 +174,7 @@ Renders a `div[data-fs-cart-item-discounts]` container with one `span[data-fs-ca
159
174
 
160
175
  ### CartItemCustomizations
161
176
 
162
- `CartItemCustomizations` is an **optional** per-line data owner: it owns the line's `customizations` subtree via its own `reactive-list-item` fragment and requires only `cart-item`, so its data is fetched **only** on pages that place it. It renders the form-based personalization a shopper applied to the line — engraving text, a size selection, an uploaded file's URL — as one group per assembly, each group listing its `entries` as `name` / `value` rows (e.g. `Text: MARIA`). It **renders null when the line has no customizations** (empty or unresolved). Must be inside `CartList`.
177
+ `CartItemCustomizations` is an **optional** per-line data owner: it owns the line's `customizations` subtree via its own `reactive-list-item` fragment and requires only `cart-item`, so its data is fetched **only** on pages that place it. It renders the form-based personalization a shopper applied to the line — engraving text, a size selection, an uploaded file's URL — as one group per assembly, each group listing its `entries` as `name` / `value` rows (e.g. `Text: MARIA`). It **renders null when the line has no customizations** (empty or unresolved). Must be inside `CartList`, `CartAvailableItems` or `CartUnavailableItems`.
163
178
 
164
179
  The group heading is the raw assembly `id` (e.g. `Engraving`); pretty display names are deferred (836 Decision 2). An uploaded-file value surfaces as its URL string and is rendered verbatim — there is no file-preview/download widget in v0 (836 exposes no file metadata; at parity with fastcheckout).
165
180
 
@@ -167,7 +182,7 @@ Renders a `div[data-fs-cart-item-customizations]` container with one `div[data-f
167
182
 
168
183
  ### CartItemCompositions
169
184
 
170
- `CartItemCompositions` is an **optional** per-line data owner: it owns the line's `compositions` subtree via its own `reactive-list-item` fragment and requires only `cart-item`, so its data is fetched **only** on pages that place it. It renders the nested bundle/assembly tree a shopper built under the line — each child row shows the child's `name`, `quantity` (`×N`), and formatted unit `price`. It **renders null when the line has no compositions** (empty or unresolved). Must be inside `CartList`.
185
+ `CartItemCompositions` is an **optional** per-line data owner: it owns the line's `compositions` subtree via its own `reactive-list-item` fragment and requires only `cart-item`, so its data is fetched **only** on pages that place it. It renders the nested bundle/assembly tree a shopper built under the line — each child row shows the child's `name`, `quantity` (`×N`), and formatted unit `price`. It **renders null when the line has no compositions** (empty or unresolved). Must be inside `CartList`, `CartAvailableItems` or `CartUnavailableItems`.
171
186
 
172
187
  **Recursion.** A composition child is itself an order-form line, so it can carry its own nested `compositions` (a sub-bundle) and its own `customizations` (836 Decision 8). The matter recurses over the in-slice `compositions` array with an internal render function: each node renders its row, its own customization groups (using the same `name`/`value` anatomy as `CartItemCustomizations`, so one swatch styles both), then its nested children at `depth + 1`. Recursion is over finite data — the server guarantees acyclic compositions (836 visited-index) and depth is bounded by the assembly config. React keys use the node `id`.
173
188
 
@@ -175,6 +190,6 @@ Renders a `div[data-fs-cart-item-compositions]` container with one `div[data-fs-
175
190
 
176
191
  ### CartItemVariations
177
192
 
178
- `CartItemVariations` is another **optional** per-line data owner: it owns the line's `variant.selectedOptions` subtree via its own `reactive-list-item` fragment and requires only `cart-item`, so its data is fetched **only** on pages that place it. It renders one labeled row per variation selected on the current cart line (e.g. `Cor: Vermelho`, `Tamanho: M`), so a shopper can tell two lines of the same product apart. It **renders null when the line has no selected options** (empty or unresolved), so it appears only where the SKU actually has variations. Place it near the product name. Must be inside `CartList`.
193
+ `CartItemVariations` is another **optional** per-line data owner: it owns the line's `variant.selectedOptions` subtree via its own `reactive-list-item` fragment and requires only `cart-item`, so its data is fetched **only** on pages that place it. It renders one labeled row per variation selected on the current cart line (e.g. `Cor: Vermelho`, `Tamanho: M`), so a shopper can tell two lines of the same product apart. It **renders null when the line has no selected options** (empty or unresolved), so it appears only where the SKU actually has variations. Place it near the product name. Must be inside `CartList`, `CartAvailableItems` or `CartUnavailableItems`.
179
194
 
180
195
  Renders a `div[data-fs-cart-item-variations]` container with one `span[data-fs-cart-item-variation]` per option; each row wraps a `span[data-fs-cart-item-variation-name]` (e.g. `Cor`), a `span[data-fs-cart-item-variation-separator]` holding the `": "` delimiter, and a `span[data-fs-cart-item-variation-value]` (e.g. `Vermelho`). The separator is a hook of its own so a store can restyle or hide the delimiter without the punctuation riding along with the label; with no swatch applied a row still reads `Cor: Vermelho`. No props beyond the standard `div` attributes; the list is data-driven and `children` is not used to override it.