@thorprovider/blocks 0.1.3 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +228 -68
  3. package/docs/standards/data-contracts.md +13 -2
  4. package/package.json +26 -10
  5. package/scripts/fix-esm-extensions.mjs +40 -0
  6. package/src/blocks/application/login-forms/login-page.test.tsx +190 -0
  7. package/src/blocks/application/login-forms/login-page.tsx +240 -0
  8. package/src/blocks/application/password-recovery/forgot-password-page-1.tsx +6 -8
  9. package/src/blocks/application/signup-forms/signup-page-1.tsx +127 -129
  10. package/src/blocks/application/verification/verify-1.tsx +2 -2
  11. package/src/blocks/application/viewport-ownership.test.tsx +41 -0
  12. package/src/blocks/e-commerce/category-filters/category-listing-filter-fields.tsx +218 -0
  13. package/src/blocks/e-commerce/category-filters/{category-filter-2.test.tsx → category-listing.test.tsx} +287 -199
  14. package/src/blocks/e-commerce/category-filters/category-listing.tsx +934 -0
  15. package/src/blocks/e-commerce/checkout-forms/checkout-contact-step.tsx +149 -0
  16. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-country-field.tsx → checkout-country-field.tsx} +10 -8
  17. package/src/blocks/e-commerce/checkout-forms/checkout-order-summary.tsx +230 -0
  18. package/src/blocks/e-commerce/checkout-forms/checkout-payment-step.tsx +100 -0
  19. package/src/blocks/e-commerce/checkout-forms/checkout-progress.tsx +62 -0
  20. package/src/blocks/e-commerce/checkout-forms/checkout-review-step.tsx +59 -0
  21. package/src/blocks/e-commerce/checkout-forms/{checkout-form-1-shipping-step.tsx → checkout-shipping-step.tsx} +65 -41
  22. package/src/blocks/e-commerce/checkout-forms/checkout.test.tsx +478 -0
  23. package/src/blocks/e-commerce/checkout-forms/checkout.tsx +550 -0
  24. package/src/blocks/e-commerce/navigation/storefront-navbar-account-menu.test.tsx +56 -0
  25. package/src/blocks/{marketing/navbars/navbar-4-account-menu.tsx → e-commerce/navigation/storefront-navbar-account-menu.tsx} +3 -3
  26. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.test.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.test.tsx} +22 -22
  27. package/src/blocks/{marketing/navbars/navbar-4-cart-sheet.tsx → e-commerce/navigation/storefront-navbar-cart-sheet.tsx} +3 -3
  28. package/src/blocks/{marketing/navbars/navbar-4-mobile-nav.tsx → e-commerce/navigation/storefront-navbar-mobile-nav.tsx} +4 -12
  29. package/src/blocks/{marketing/navbars/navbar-4-search-bar.tsx → e-commerce/navigation/storefront-navbar-search-bar.tsx} +3 -3
  30. package/src/blocks/{marketing/navbars/navbar-4.test.tsx → e-commerce/navigation/storefront-navbar.test.tsx} +67 -36
  31. package/src/blocks/{marketing/navbars/navbar-4.tsx → e-commerce/navigation/storefront-navbar.tsx} +43 -25
  32. package/src/blocks/e-commerce/product-categories/category-card-grid.test.tsx +122 -0
  33. package/src/blocks/e-commerce/product-categories/category-card.test.tsx +68 -0
  34. package/src/blocks/e-commerce/product-categories/category-card.tsx +172 -0
  35. package/src/blocks/e-commerce/product-categories/category-grid.test.tsx +67 -0
  36. package/src/blocks/e-commerce/product-categories/category-shared.tsx +109 -0
  37. package/src/blocks/e-commerce/product-categories/product-category-grid.tsx +170 -0
  38. package/src/blocks/e-commerce/product-categories/product-category.test.tsx +266 -0
  39. package/src/blocks/e-commerce/product-categories/product-category.tsx +462 -0
  40. package/src/blocks/e-commerce/product-list/product-card.test.tsx +263 -0
  41. package/src/blocks/e-commerce/product-list/product-card.tsx +447 -0
  42. package/src/blocks/e-commerce/product-list/product-grid.test.tsx +162 -0
  43. package/src/blocks/e-commerce/product-list/product-grid.tsx +114 -0
  44. package/src/blocks/e-commerce/product-overview/product-overview-gallery.tsx +71 -0
  45. package/src/blocks/e-commerce/product-overview/{product-overview-5-lightbox-tab.tsx → product-overview-lightbox-tab.tsx} +35 -23
  46. package/src/blocks/e-commerce/product-overview/{product-overview-5-reviews-tab.tsx → product-overview-reviews-tab.tsx} +40 -17
  47. package/src/blocks/e-commerce/product-overview/{product-overview-5-tabs.tsx → product-overview-tabs.tsx} +8 -7
  48. package/src/blocks/e-commerce/product-overview/product-overview.test.tsx +283 -0
  49. package/src/blocks/e-commerce/product-overview/product-overview.tsx +1186 -0
  50. package/src/blocks/e-commerce/reviews/review-rating-2.tsx +5 -6
  51. package/src/blocks/e-commerce/shopping-carts/shopping-cart.test.tsx +399 -0
  52. package/src/blocks/e-commerce/shopping-carts/{shopping-cart-4.tsx → shopping-cart.tsx} +119 -85
  53. package/src/blocks/e-commerce/storefront-hero/storefront-hero.test.tsx +412 -0
  54. package/src/blocks/e-commerce/storefront-hero/storefront-hero.tsx +1283 -0
  55. package/src/blocks/marketing/features/feature.test.tsx +153 -0
  56. package/src/blocks/marketing/features/feature.tsx +195 -0
  57. package/src/blocks/marketing/footers/footer-section-accessibility.test.tsx +118 -0
  58. package/src/blocks/marketing/footers/footer-section-actions.test.tsx +131 -0
  59. package/src/blocks/marketing/footers/footer-section-data.test.tsx +127 -0
  60. package/src/blocks/marketing/footers/footer-section-native-links.test.tsx +100 -0
  61. package/src/blocks/marketing/footers/footer-section-public-api.test.tsx +11 -0
  62. package/src/blocks/marketing/footers/footer-section.test.tsx +46 -0
  63. package/src/blocks/marketing/footers/footer-section.tsx +532 -0
  64. package/src/blocks/marketing/navbars/navbar-public-api.test.tsx +57 -0
  65. package/src/blocks/marketing/navbars/navbar.mega.test.tsx +149 -0
  66. package/src/blocks/marketing/navbars/navbar.test.tsx +155 -0
  67. package/src/blocks/marketing/navbars/navbar.tsx +735 -0
  68. package/src/blocks/marketing/testimonials/testimonial-section.test.tsx +171 -0
  69. package/src/blocks/marketing/testimonials/testimonial-section.tsx +237 -0
  70. package/src/index.ts +156 -128
  71. package/src/lib/contracts-curated.test.ts +47 -29
  72. package/src/lib/contracts.test.ts +198 -2
  73. package/src/lib/contracts.ts +410 -16
  74. package/src/ui/button-group.tsx +1 -1
  75. package/src/ui/carousel.tsx +1 -1
  76. package/src/ui/dialog.tsx +1 -1
  77. package/src/ui/field.tsx +2 -2
  78. package/src/ui/input-group.tsx +3 -3
  79. package/src/ui/pagination.tsx +1 -1
  80. package/src/ui/sidebar.tsx +7 -7
  81. package/src/ui/toggle-group.tsx +1 -1
  82. package/tsconfig.build.json +12 -0
  83. package/tsup.config.ts +11 -7
  84. package/.turbo/turbo-build.log +0 -24
  85. package/.turbo/turbo-test.log +0 -47
  86. package/.turbo/turbo-type-check.log +0 -0
  87. package/dist/.tsbuildinfo +0 -1
  88. package/dist/contracts.d.mts +0 -1732
  89. package/dist/contracts.d.ts +0 -1732
  90. package/dist/contracts.js +0 -423
  91. package/dist/contracts.js.map +0 -1
  92. package/dist/contracts.mjs +0 -365
  93. package/dist/contracts.mjs.map +0 -1
  94. package/dist/index.d.mts +0 -3068
  95. package/dist/index.d.ts +0 -3068
  96. package/dist/index.js +0 -17834
  97. package/dist/index.js.map +0 -1
  98. package/dist/index.mjs +0 -17740
  99. package/dist/index.mjs.map +0 -1
  100. package/src/blocks/application/login-forms/login-page-1.tsx +0 -246
  101. package/src/blocks/application/login-forms/login-page-2.tsx +0 -207
  102. package/src/blocks/e-commerce/category-filters/category-filter-1.tsx +0 -260
  103. package/src/blocks/e-commerce/category-filters/category-filter-2-filter-panel.tsx +0 -222
  104. package/src/blocks/e-commerce/category-filters/category-filter-2.tsx +0 -921
  105. package/src/blocks/e-commerce/category-filters/category-filter-4-chip-rail.tsx +0 -57
  106. package/src/blocks/e-commerce/category-filters/category-filter-4-drawer.tsx +0 -124
  107. package/src/blocks/e-commerce/category-filters/category-filter-4-fields.tsx +0 -170
  108. package/src/blocks/e-commerce/category-filters/category-filter-4-sidebar.tsx +0 -52
  109. package/src/blocks/e-commerce/category-filters/category-filter-4-sort-sheet.tsx +0 -57
  110. package/src/blocks/e-commerce/category-filters/category-filter-4-state.ts +0 -68
  111. package/src/blocks/e-commerce/category-filters/category-filter-4.tsx +0 -298
  112. package/src/blocks/e-commerce/checkout-forms/card-formatting.test.ts +0 -47
  113. package/src/blocks/e-commerce/checkout-forms/card-formatting.ts +0 -9
  114. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-contact-step.tsx +0 -96
  115. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-order-summary.tsx +0 -252
  116. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-payment-step.tsx +0 -188
  117. package/src/blocks/e-commerce/checkout-forms/checkout-form-1-progress.tsx +0 -36
  118. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.test.tsx +0 -216
  119. package/src/blocks/e-commerce/checkout-forms/checkout-form-1.tsx +0 -338
  120. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-order-summary.tsx +0 -130
  121. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-payment-card.tsx +0 -175
  122. package/src/blocks/e-commerce/checkout-forms/checkout-form-2-shipping-card.tsx +0 -190
  123. package/src/blocks/e-commerce/checkout-forms/checkout-form-2.tsx +0 -312
  124. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-information-step.tsx +0 -91
  125. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-order-summary.tsx +0 -110
  126. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-payment-step.tsx +0 -137
  127. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-progress.tsx +0 -68
  128. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-review-step.tsx +0 -79
  129. package/src/blocks/e-commerce/checkout-forms/checkout-form-3-shipping-step.tsx +0 -172
  130. package/src/blocks/e-commerce/checkout-forms/checkout-form-3.tsx +0 -392
  131. package/src/blocks/e-commerce/product-categories/avatar-group.tsx +0 -86
  132. package/src/blocks/e-commerce/product-categories/product-category-1.tsx +0 -126
  133. package/src/blocks/e-commerce/product-categories/product-category-2.test.tsx +0 -51
  134. package/src/blocks/e-commerce/product-categories/product-category-2.tsx +0 -120
  135. package/src/blocks/e-commerce/product-categories/product-category-4.tsx +0 -129
  136. package/src/blocks/e-commerce/product-categories/product-category-5.tsx +0 -218
  137. package/src/blocks/e-commerce/product-list/product-card-1.test.tsx +0 -88
  138. package/src/blocks/e-commerce/product-list/product-card-1.tsx +0 -192
  139. package/src/blocks/e-commerce/product-list/product-card-2.tsx +0 -71
  140. package/src/blocks/e-commerce/product-list/product-card-3-reaction-toggle.tsx +0 -166
  141. package/src/blocks/e-commerce/product-list/product-card-3.tsx +0 -215
  142. package/src/blocks/e-commerce/product-list/product-card-5-checkbox-check.tsx +0 -98
  143. package/src/blocks/e-commerce/product-list/product-card-5-reaction-toggle.tsx +0 -171
  144. package/src/blocks/e-commerce/product-list/product-card-5.tsx +0 -367
  145. package/src/blocks/e-commerce/product-overview/product-overview-1.test.tsx +0 -182
  146. package/src/blocks/e-commerce/product-overview/product-overview-1.tsx +0 -370
  147. package/src/blocks/e-commerce/product-overview/product-overview-2.tsx +0 -272
  148. package/src/blocks/e-commerce/product-overview/product-overview-5-gallery.tsx +0 -59
  149. package/src/blocks/e-commerce/product-overview/product-overview-5.tsx +0 -274
  150. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1-spinning-counter.tsx +0 -142
  151. package/src/blocks/e-commerce/shopping-carts/shopping-cart-1.tsx +0 -291
  152. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-item-row.tsx +0 -110
  153. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-recommendations.tsx +0 -51
  154. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2-spinning-counter.tsx +0 -146
  155. package/src/blocks/e-commerce/shopping-carts/shopping-cart-2.tsx +0 -295
  156. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3-spinning-counter.tsx +0 -142
  157. package/src/blocks/e-commerce/shopping-carts/shopping-cart-3.tsx +0 -342
  158. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4-spinning-counter.tsx +0 -146
  159. package/src/blocks/e-commerce/shopping-carts/shopping-cart-4.test.tsx +0 -229
  160. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.test.tsx +0 -72
  161. package/src/blocks/e-commerce/storefront-hero/storefront-hero-1.tsx +0 -263
  162. package/src/blocks/e-commerce/storefront-hero/storefront-hero-2.tsx +0 -283
  163. package/src/blocks/e-commerce/storefront-hero/storefront-hero-5.tsx +0 -318
  164. package/src/blocks/e-commerce/storefront-hero/storefront-hero-6.tsx +0 -205
  165. package/src/blocks/marketing/features/feature-1.tsx +0 -97
  166. package/src/blocks/marketing/features/feature-2.tsx +0 -82
  167. package/src/blocks/marketing/footers/footer-section-1.test.tsx +0 -117
  168. package/src/blocks/marketing/footers/footer-section-1.tsx +0 -178
  169. package/src/blocks/marketing/footers/footer-section-3.tsx +0 -192
  170. package/src/blocks/marketing/footers/footer-section-4.tsx +0 -355
  171. package/src/blocks/marketing/navbars/navbar-1.tsx +0 -267
  172. package/src/blocks/marketing/navbars/navbar-2.tsx +0 -351
  173. package/src/blocks/marketing/navbars/navbar-3.tsx +0 -303
  174. package/src/blocks/marketing/navbars/navbar-4-account-menu.test.tsx +0 -38
  175. package/src/blocks/marketing/navbars/navbar-4-like-button.tsx +0 -177
  176. package/src/blocks/marketing/navbars/navbar-4-product-grid.tsx +0 -219
  177. package/src/blocks/marketing/testimonials/testimonial-1.tsx +0 -141
  178. package/src/blocks/marketing/testimonials/testimonial-2.tsx +0 -108
package/CHANGELOG.md ADDED
@@ -0,0 +1,155 @@
1
+ # @thorprovider/blocks
2
+
3
+ ## 2.0.0
4
+
5
+ ### Major Changes
6
+
7
+ - dc82f71: Consolidate the category filter family into one canonical `CategoryListing` block.
8
+
9
+ - Adds `CategoryListing` (export + `CategoryListingProps` and the `CategoryListing*` option, facet, status, filter and active-filter types). It is the single controlled listing controller: products, pagination and the selected query are props; `on*Change` / `onClearFilters` / `onRepair` / `onRetry` report intent; the block never fetches, parses the URL, or keeps a private query.
10
+ - Two bounded presentations share one contract: `sidebar` (default; persistent desktop panel plus mobile sheet) and `toolbar` (sticky mobile toolbar, filter/sort drawers and chip rail). Presentation changes chrome only.
11
+ - Results render through the canonical `ProductCard` (`minimal`), so the listing owns no card markup. Facets are generic option-only groups supplied by the host; a group renders only from real metadata, a real `0` count is displayed, and an absent/invalid count is omitted. Price renders only from a real finite range, sort only from advertised options, and no result total is fabricated.
12
+ - `ProductCard` gains `hideAddToCart` for listing cells and renders `data-slot='product-image-placeholder'` instead of an image with an empty source.
13
+ - Runtime: `toFilterProduct` → `toListingProduct` (returns canonical `ProductPresentation` with `Money`), `CategoryFilterPresentation(Input)` → `CategoryListingPresentation(Input)`, `mapCatalogResultsToFilterProps` → `mapCatalogResultsToCategoryListing`, and `resolveFilterPresentationSupport` → `resolveCategoryListingSupport` plus a separate `resolveCategoryFacetSupport`. The old numbered presentation metadata is gone.
14
+
15
+ BREAKING: removes the `CategoryFilter1`, `CategoryFilter2` and `CategoryFilter4` exports, their types, and the `category-filter-4-*` helpers. Consumers must migrate to `CategoryListing`:
16
+
17
+ | Removed | Migration |
18
+ | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
19
+ | `CategoryFilter1` | `CategoryListing` with `presentation='sidebar'`; replace counted categories/price bands with real `categories` / `priceRange` metadata. |
20
+ | `CategoryFilter2` | `CategoryListing` with `presentation='sidebar'` (the default). Products become canonical `ProductPresentation` items with `Money` prices; `filterPrice` string prices are no longer accepted. |
21
+ | `CategoryFilter4` | `CategoryListing` with `presentation='toolbar'`; replace bike-specific facets/frame/model fields with generic `facets` groups and remove its private grid/state. |
22
+
23
+ - 28a3bd7: Consolidate the numbered checkout-form family into one canonical page-level `Checkout` block.
24
+
25
+ - Adds `Checkout` (export plus `CheckoutProps`, `CheckoutValues`, `CheckoutPresentation`, `CheckoutStepId`, `CheckoutLabels`, `CheckoutValuesFieldErrors`, `CheckoutFieldDescriptions`, `CheckoutChangeHandler`, `CheckoutOrderSummary`, `CheckoutOrderLine`, `CheckoutTotals`, `CheckoutTotalsState`, `CheckoutCountryOption`, `CheckoutShippingOption`) with a required bounded `presentation` (`single-page` | `stepper`) and an orthogonal `showReviewStep` flow option. `single-page` is the former `CheckoutForm2` structure; `stepper` is the former `CheckoutForm1` wizard; `stepper` plus the review option is the former `CheckoutForm3`. Field values are controlled (`values` + `onChange`), the step index is ephemeral block state, and `validateStep(step: CheckoutStepId)` is injected — the block contains no validation logic.
26
+ - Totals are canonical `Money` only and render verbatim from the host; the legacy numeric `price`/`shipping`/`tax`/`discount`/`promoDiscount` fields and their fallback summation are removed. A missing or malformed required total renders as unavailable with recovery, never as zero.
27
+ - Removes raw card capture: `cardNumber`, `expiryDate`, `cvv`, `cardName`, the `collectCardFields` escape hatch, the CVV visibility toggle, and `card-formatting.ts`. Payment methods remain data (`PaymentMethod[]`); card collection belongs to the host payment-provider surface.
28
+ - Removes the numbered exports `CheckoutForm1`, `CheckoutForm2`, `CheckoutForm3` and their `CheckoutForm1*`, `CheckoutForm2*`, `CheckoutForm3*` types.
29
+
30
+ BREAKING: consumers importing a numbered checkout must migrate to `Checkout` with the `presentation` prop (and `showReviewStep` for the review flow). Values lose the card fields and `sameAsBilling`; totals must be supplied as backend `Money`; `validateStep` now receives a `CheckoutStepId` instead of a step number.
31
+
32
+ - 06333a8: Consolidate the navigation family into two canonical components and retire the numbered navbars.
33
+
34
+ - `Navbar` is the canonical page-level header with bounded `bar`, `mega`, and `floating` presentations. Content is serializable (`links`, `items`, `featured`, `signIn`, `cta`, `logoText`, `logoHref`, `currentPath`); icons resolve through a host-bound `iconMap`; `floating` uses `sticky` so it reserves its own space.
35
+ - `StorefrontNavbar` is the canonical commerce shell (formerly `Navbar4`) and keeps its full storefront contract: utility strip, category navigation, optional search, account states, and the controlled cart sheet with canonical `Money`.
36
+ - Removed identifiers: `Navbar1`, `Navbar2`, `Navbar3`, `Navbar4`, `navbar4PropsSchema`, `Navbar4SpecProps`, `Navbar4LikeButton`, `Navbar4ProductGrid`, and the `navbar-4-*` helper modules (now `storefront-navbar-cart-sheet`, `storefront-navbar-account-menu`, `storefront-navbar-search-bar`, `storefront-navbar-mobile-nav`).
37
+ - The page-level header no longer owns cart state: `cartCount` and `onOpenCart` are gone. Use the commerce shell for the cart affordance.
38
+
39
+ - 65fac4b: Remove the numbered compatibility adapters `Testimonial1`, `Testimonial2`, `LoginPage1`, `LoginPage2`, `FooterSection1`, `FooterSection3`, and `FooterSection4`, including their deep module paths and legacy prop types. Migrate testimonials to `TestimonialSection` with `carousel` or `bento`, login pages to `LoginPage` with `card` or `social`, and footers to `FooterSection` with `brand-columns`, `newsletter-grid`, or `contact`. Select native footer link behavior explicitly where browser navigation is intended.
40
+
41
+ ### Minor Changes
42
+
43
+ - 5d32763: The auth blocks render into the host page instead of owning it.
44
+
45
+ `LoginPage` (both `card` and `social`), `SignUpPage1`, `ForgotPasswordPage1` and `Verify1` no longer paint a full-viewport surface. They drop the `min-h-dvh` canvas, the page landmark (the `social` presentation no longer renders a root `<main>`), the `bg-linear-to-br` backdrop with `isolate`/`overflow-hidden`, the vertical centring and the page-level padding. Each block now renders a neutral, content-sized root that keeps its width cap (`max-w-md` for login and recovery, `max-w-lg` for registration) and its card styling, including the shadow.
46
+
47
+ Why: a generated storefront mounts these blocks inside the default chrome (notice + banner + navbar + footer). A viewport-height canvas inside that chrome centres the form roughly 100 px low and, for the `social` presentation, emits a second `<main>` landmark inside the page's own `<main>`.
48
+
49
+ No prop, type or export changed, so existing call sites keep compiling. What changes is visual ownership: a host that relied on the block's canvas must now supply one (a route shell, a modal, a split screen, or a template the host owns), using the same theme tokens. `@thorprovider/storefront` does exactly that for the generated `account` view, which keeps the shopper-visible result unchanged.
50
+
51
+ ## 1.0.0
52
+
53
+ ### Major Changes
54
+
55
+ - f011717: Consolidate the `home.benefits` feature family into one canonical `Feature` block.
56
+
57
+ - Adds `Feature` (export + `featurePropsSchema` / `FeatureProps` in the contracts surface) with a bounded `presentation` prop (`cards` | `split`, default `cards`). `cards` is the former header-badge card grid with per-item learn-more and an optional section CTA; `split` is the former split layout with the optional grayscale image column. `split` deliberately ignores `badge` and per-item learn-more; an absent or empty image `src` renders no image column (never a blank `src`).
58
+ - The storefront catalog declares the two presentations as the `home.benefits` variants with `legacyAliases` (`Feature1` → `cards`, `Feature2` → `split`), so existing manifests storing the numbered names keep regenerating to the same presentation; the benefits feature's `compact` / `detailed` configuration values are unchanged and map onto `cards` / `split`.
59
+
60
+ BREAKING: removes the `Feature1` and `Feature2` exports (components and their `Feature1Props`/`Feature1Item`/`Feature2Props`/`Feature2Item` types) that this canonical block supersedes. Consumers must migrate to `Feature` with the `presentation` prop.
61
+
62
+ - f011717: Consolidate the product-card family into one canonical `ProductCard` block.
63
+
64
+ - Adds `ProductCard` (export + `productCardPropsSchema` / `ProductCardSpecProps` in the contracts surface) with a bounded `presentation` prop (`classic` | `minimal` | `rich`, default `classic`). `classic` is the former `ProductCard1` structure (badge, optional rating, compare-at, add-to-cart footer, reduced-motion-safe motion); `minimal` is the former `ProductCard2` lean look; `rich` is the former `ProductCard3` overlay actions (wishlist heart with host-owned controlled state or uncontrolled visual fallback, and view-similar rendered only when a handler is supplied). The comparison-table surface of the former `ProductCard5` is deliberately retired (a listing controller, not an item cell).
65
+ - Items are the canonical `ProductPresentation` contract only (identity, title, optional image with alt, canonical `Money` price, optional `compareAtPrice`/`rating`/`badge`). An unresolved price renders an unavailable state with add-to-cart disabled — never a zero. Ratings, badges and crossed-out prices render only from host-supplied data; the block invents no evidence. Prices render through the canonical `formatMoney` path (no hardcoded currency symbol) and all user-facing copy is overridable with the previous Spanish defaults.
66
+ - Removes the numbered exports `ProductCard1`, `ProductCard2`, `ProductCard3`, `ProductCard5`, their types `ProductCard1Product` / `ProductCard1Props`, the contracts surface `productCard1PropsSchema` / `ProductCard1SpecProps`, and the `product-card-3-reaction-toggle` / `product-card-5-reaction-toggle` / `product-card-5-checkbox-check` subcomponents.
67
+ - Migration: supply item data in the canonical presentation contract (canonical `Money` amounts as decimal major-unit strings plus `currencyCode`). The former numeric-price props (`price: number`, `originalPrice?: number`, mandatory demo merchandising fields) are not retained; map them to `Money` at your boundary. Consumer code importing a numbered card selects the equivalent bounded presentation (`1` → `classic`, `2` → `minimal`, `3` → `rich`); the former comparison table has no replacement in this block.
68
+
69
+ - f011717: Consolidate the `home.categories` family into one canonical `ProductCategory` block.
70
+
71
+ - Adds `ProductCategory` (export + `productCategoryPropsSchema` / `ProductCategoryProps`) built on the canonical `CategoryCard` shape. It exposes a bounded `presentation` prop (`grid` | `icons` | `list`); optional per-category data (`icon` as a serializable key, `subcategories`, `featured`, `itemCount`) degrades gracefully when absent.
72
+ - Interaction is controlled: `onBrowse` / `onViewAll` / `onQueryChange` report from handlers with no private query state; a category count is omitted when absent or invalid and a real zero is preserved; a missing image renders a neutral placeholder, never `src=""`; no copy, currency, or demo metrics are hardcoded.
73
+
74
+ BREAKING: removes the `ProductCategory1`, `ProductCategory2`, `ProductCategory4`, and `ProductCategory5` exports (and the `product-categories/avatar-group` helper) that this canonical block supersedes. Consumers must migrate to `ProductCategory` with the `presentation` prop.
75
+
76
+ - ece154d: Consolidate the `product.overview` family into one canonical `ProductOverview` block.
77
+
78
+ - Adds `ProductOverview` (export + `productOverviewPropsSchema` / `ProductOverviewProps`) built on the canonical `ProductDetailProduct` projection and canonical `Money`. It exposes a bounded `presentation` prop (`standard` | `gallery` | `rich`); `rich` accepts optional `specifications` / `features` / `reviews` props and degrades gracefully when they are absent.
79
+ - Selection and quantity are controlled; `onAddToCart` emits the explicitly selected backend variant id and quantity and never substitutes the first variant. Missing price renders as unavailable, never `$0`; currency is never hardcoded.
80
+
81
+ BREAKING: removes the `ProductOverview1`, `ProductOverview2`, and `ProductOverview5` exports (and the `product-overview-5-*` subcomponents) that this canonical block supersedes. Consumers must migrate to `ProductOverview` with the `presentation` prop.
82
+
83
+ - b41cc50: Consolidate the numbered shopping-cart family into one canonical `ShoppingCart` block.
84
+
85
+ - Adds `ShoppingCart` (export + `shoppingCartPropsSchema` / `ShoppingCartSpecProps` in the contracts surface) with a bounded `presentation` prop (`compact` | `page`). `compact` is the former `ShoppingCart4` review card; `page` is the same contract with the page-level structure. The optional promo-entry affordance reports the entered code via `onApplyPromo(code)` and renders only authoritative promotion/discount data — it is hidden when the callback is absent, and the block never validates promotion codes itself. Host-supplied `recentlyViewed` suggestions replace the former `ShoppingCart2` recommendations, priced with canonical `Money`.
86
+ - Items are the canonical contract only: identity, title, quantity, optional image (omitted when absent, never `src=""`), and canonical `Money` amounts. Each line carries the backend-authoritative `lineTotal` (rendered verbatim; a malformed amount renders as unavailable — never a zero or free-shipping claim) and an optional `unitPrice` carried verbatim but never used for local arithmetic. `totals` come from the backend and are rendered verbatim; no local tax/shipping/threshold math, no fabricated merchandising, no hardcoded currency.
87
+ - Removes the numbered exports `ShoppingCart1`, `ShoppingCart2`, `ShoppingCart3`, `ShoppingCart4` (and their `ShoppingCart1CartItem`, `ShoppingCart2Props`/`ShoppingCart2Item`, `ShoppingCart3Props`/`ShoppingCart3CartItem`, `ShoppingCart4Props`/`ShoppingCart4Item` types), the contracts surface `shoppingCart4PropsSchema` / `ShoppingCart4SpecProps`, and the `shopping-cart-{1,2,3,4}-spinning-counter`, `shopping-cart-2-recommendations`, and `shopping-cart-2-item-row` subcomponents.
88
+
89
+ BREAKING: consumers importing a numbered cart must migrate to `ShoppingCart` with the `presentation` prop. The former numeric-price props (`price: number`, local `taxRate`/shipping-threshold arithmetic) are not retained; map item amounts to canonical `Money` (`{ amount, currencyCode }`) at your boundary and pass backend `totals` verbatim.
90
+
91
+ - f011717: Consolidate the `home.hero` family into one canonical `StorefrontHero` block.
92
+
93
+ - Adds `StorefrontHero` (export + `storefrontHeroPropsSchema` / `StorefrontHeroProps` / `StorefrontHeroProduct` in the contracts surface) with a bounded `presentation` prop (`compact` | `showcase` | `editorial` | `promotional`) reusing the registry's archetype intent vocabulary. `compact` is the former `StorefrontHero1` text-forward hero with product grid and editorial image seam; `showcase` the former `StorefrontHero2` search + rotating product carousel (the search input renders only when `onSearch` is supplied); `editorial` the former `StorefrontHero5` department grid with optional featured deals and store locator; `promotional` the former `StorefrontHero6` deals bar + category grid.
94
+ - Truthful evidence only: ratings, review counts, category/department counts, deal descriptions, and countdowns render only from host-supplied data; discount treatments render only from a real `originalPrice > price` pair (`isOnSale` is dropped); `currency` (ISO 4217) formats all amounts via `Intl.NumberFormat` — no hardcoded `$`. Rotation respects reduced motion and is presentation-internal.
95
+ - The storefront catalog declares `StorefrontHero` as the single `home.hero` variant with `legacyAliases` (`StorefrontHero1`/`2`/`5`/`6` → the canonical), so existing manifests storing the numbered names keep regenerating to a working composition; `archetypes.json` intents map each intent to its matching presentation.
96
+
97
+ BREAKING: removes the `StorefrontHero1`, `StorefrontHero2`, `StorefrontHero5`, and `StorefrontHero6` exports (and the `StorefrontHero1Props` type) that this canonical block supersedes. The `FeaturedProduct` type is now exported from the canonical source and its evidence fields (`brand`, `colors`, `rating`, `reviewCount`, `originalPrice`) are optional. Consumers must migrate to `StorefrontHero` with the `presentation` prop.
98
+
99
+ - dc2b11b: Split the category collection into an item cell and a listing.
100
+
101
+ - `CategoryCard` is the item cell: one `category` at a time, with the existing `grid`, `icons` and `list` presentations. It renders no section, heading, search, CTA or empty state.
102
+ - `CategoryGrid` is the listing: it owns the collection, `maxItems`, section copy, controlled search (list presentation), loading skeletons and the empty state, and renders `CategoryCard` for every item.
103
+ - BREAKING: `ProductCategory` (collection) is replaced by `CategoryGrid`; the single-item surface is `CategoryCard`. `ProductCategoryProps` is replaced by `CategoryGridProps` / `CategoryCardProps`.
104
+
105
+ Migration: replace `<ProductCategory categories={items} ... />` with `<CategoryGrid categories={items} ... />`. For a single category, render `<CategoryCard category={item} ... />`.
106
+
107
+ - 3e43eb0: BREAKING: `ProductCard` is now an item cell and a new `ProductGrid` listing owns the collection.
108
+
109
+ - `ProductCard` (`packages/blocks/src/blocks/e-commerce/product-list/product-card.tsx`) now takes a single `item: ProductPresentation` (plus `presentation?` and `wishlisted?`) and renders exactly one cell — no section wrapper, no section heading, no grid, and no item cap. The `items`, `heading`, and `maxItems` props and the `wishlistedIds` collection prop are removed; `wishlisted` is now a single host-resolved boolean.
110
+ - The serializable contract `productCardPropsSchema` / `ProductCardSpecProps` changes accordingly (`{ item, presentation?, wishlisted? }`), still without callbacks or label strings.
111
+ - Adds `ProductGrid` (export + `productGridPropsSchema` / `ProductGridSpecProps`) as the canonical listing: it takes `items: ProductPresentation[]`, an optional `presentation`, `heading`, `maxItems`, `wishlistedIds`, and owns the `loading` (skeletons), `empty` (overridable copy), and ready states. It renders `ProductCard` cells (single source of cell markup) and reports actions through the same callbacks. `error` remains the host's responsibility.
112
+ - Migration: pass a listing's data to `ProductGrid` with `items`, `heading`, and `maxItems`; pass a single item to `ProductCard` via `item`. Resolve rich-presentation wishlist state per cell through `wishlistedIds` on the grid or `wishlisted` on the cell. The delimited showcase `ProductShowcase1` is unchanged.
113
+
114
+ - b41cc50: Require an image for the `Feature` block's `split` presentation.
115
+
116
+ - `featureSplitSpecSchema` now requires `presentation: 'split'` and a non-empty, trimmed `image.src`; `featureCardsSpecSchema` keeps `image` out of its contract. `featurePropsSchema` is the union of both branches, so TypeScript narrows `image` only after checking `presentation`.
117
+ - The component throws a configuration error when an untyped caller renders `split` without a non-empty image, instead of silently dropping the media column. `cards` is unchanged.
118
+ - Editorial fallbacks belong to the consumer, not the block library. The showcase now owns `public/demo/editorial-fallback.svg`; the storefront template seeds a neutral image in user-owned `content.ts`.
119
+
120
+ Migration: pass `{ image: { src, alt } }` when using `presentation="split"`, or switch to `presentation="cards"` if the section is icon-only. Replace the storefront's neutral seed with real editorial imagery.
121
+
122
+ ### Minor Changes
123
+
124
+ - 80d4137: Add optional, backward-compatible brand customization to existing surfaces (M5):
125
+
126
+ - `@thorprovider/blocks`: `Navbar4` and `FooterSection1` accept an optional wordmark accent `{ text, className }` (trailing segment of the brand name) and an optional surface `className`; a supplied surface `className` overrides the default surface paint (default background/backdrop not emitted) so a branded surface paints correctly. `StorefrontHero1` accepts an optional editorial `heroImage { src, alt }` rendered with a native, guarded image. `ProductCategory2` accepts an optional `itemCount`: an unknown count is omitted (no fabricated `0`), a real `0` is preserved, and non-finite/negative values render no count. All props are optional and omit to the current rendering, so existing consumers are unchanged; no new block type or variant is added, and the blocks stay framework-, backend-, and data-free.
127
+ - `@thorprovider/storefront`: the scaffold generates a user-owned `lib/<name>/shell/{content,props}.ts` seam (brand accents, surface classes, footer description/links, newsletter) that the derived spec composes last, so `storefront sync` regenerates only derived wiring and preserves user-owned edits. The scaffold ships no newsletter submit callback, so the newsletter is omitted by default rather than rendering a fake form; default footer copy makes no delivery/shipping promise and links only to routes the storefront serves (no assumed route). No new identity CLI command is added — `create --site-name` already covers the site name.
128
+
129
+ This changeset adds release metadata only; it does not bump versions or publish packages.
130
+
131
+ - 3b3c24c: Add validated single-store commerce contracts, explicit real/mock policy, directed variant pricing, persistent guest carts, backend-authoritative totals, manual TEST checkout, and capability-authorized receipt reconciliation in generated storefronts.
132
+
133
+ Breaking migration: `@thorprovider/types` is now entirely type-only. Import `SupportedProviderType`, `PROVIDER_METADATA`, `isSupportedProviderType`, `getProviderMetadata`, and `StorefrontConfigError` from `@thorprovider/adapters` instead. The pre-1.0 curated blocks/runtime APIs also change: product detail uses canonical options/variants/Money and explicit variant selection; confirmation totals and lines use Money; `formatMoney` returns `null` for unavailable values. Real-mode failures no longer silently return mock data.
134
+
135
+ The local packed MVP candidate retains its exact current version pins until a separate release updates the compatible toolset. This changeset does not publish packages or establish public-sales readiness.
136
+
137
+ - b41cc50: Deliver the global shopper account experience (M5, change `storefront-navbar-account-menu`):
138
+
139
+ - `@thorprovider/blocks`: `Navbar4` renders a session-driven account affordance from consumer props only. An authenticated `user` (optional `avatar`) with `accountMenuItems` gets an account menu (identity + configured items + consumer-supplied sign-out); no `user` with a configured `accountHref` gets a signed-out entry point on desktop and mobile; neither configured renders no account affordance or empty container. Optional `accountPending`/`accountUnavailable` drive a pending/disabled state and an accessible unavailable/retry state (`onAccountRetry`) without presenting either as anonymous, and sign-out is suppressed while pending. Absent avatar uses the initials fallback (no empty image `src`). All callbacks stay consumer bindings outside the serializable JSON prop schema; the block never authenticates.
140
+ - `@thorprovider/storefront`: a mounted `customer-auth` resolves the shopper server-side per request and seeds an allowlisted display identity (`id`, `email`, `firstName`, `lastName`; the auth token stays in its httpOnly cookie). One shared client lifecycle (`account-lifecycle.client.tsx`) owns identity, guarded claims, login/register/logout, and retry with no identity fetch on mount or navigation; it reconciles same-store tabs through non-secret store-scoped signals (`BroadcastChannel` with a storage-event fallback) and on foreground/resume, discards stale generations, and shows a neutral pending state instead of stale identity/orders or a fake sign-out. `/account` is the only account surface — sign-in, registration, identity, sign-out, and real paginated orders + detail (route-scoped commerce, no direct HTTP); no address book/profile/wishlist/social/recovery/cart-merge. Account wiring is delivered through derived files (`account-session*.ts`, `account-lifecycle/account-boundary/account-mutation/account-reconcile.client.*`, `account-private.core.ts`, `account-user.core.ts`, `projection-ownership*.ts`, `registry.tsx`, `schema.ts`, `spec/index.ts`); user-owned files (`views/*/{schema,content,props,data}.ts`, `mappers.ts`, `icon-map.ts`, `shell/**`) survive `sync`. `storefront feature add|remove customer-auth` toggles the `/account` route, session reads, listeners, and navbar chrome together; without the feature the generated registry carries no account-lifecycle/account-reconcile reference and reads no session.
141
+ - `@thorprovider/adapters`: `getCurrentCustomer()` resolves `null` only for a confirmed unauthenticated response (HTTP 401 / `"Unauthorized"`) and throws a typed `ProviderAPIError` (`GET_CURRENT_CUSTOMER_FAILED`) on operational failures (network, timeout, 5xx), so a host can distinguish an anonymous shopper from a backend outage and render the unavailable state instead of a false sign-out. `isAuthenticated()` delegates to it and can reject. Signature and framework-agnostic boundary are unchanged.
142
+
143
+ This changeset adds release metadata only; it does not bump versions or publish packages.
144
+
145
+ - ece154d: Gate the `ProductOverview` wishlist affordance on a wired handler.
146
+
147
+ - `ProductOverview` renders its wishlist button only when `onToggleWishlist` is supplied; an unwired button is a dead control and is no longer rendered in any presentation (`standard`, `gallery`, `rich`).
148
+ - The storefront product template and the demo/electrostore registries drop their no-op `onToggleWishlist` wiring, so a storefront without wishlist backend support no longer shows a non-functional heart.
149
+
150
+ ### Patch Changes
151
+
152
+ - ece154d: Align the `Breadcrumb` block with the page content: the trail now renders inside the shared responsive container (`mx-auto w-full max-w-7xl px-4 py-4 sm:px-6 lg:px-8`) that sibling blocks use, instead of sitting flush against the viewport edge. The container is merged with the incoming `className` via `cn`, so a custom `className` can still override it.
153
+ - Updated dependencies [23f876f]
154
+ - Updated dependencies [3b3c24c]
155
+ - @thorprovider/types@6.0.0
package/README.md CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  title: "@thorprovider/blocks"
3
3
  purpose: "Reusable, framework-agnostic storefront blocks built from ShadcnStore UI, decoupled from data and backend"
4
- lastReviewed: "2026-09-23"
4
+ lastReviewed: "2026-10-21"
5
5
  ---
6
6
 
7
7
  # @thorprovider/blocks
@@ -30,7 +30,7 @@ src/
30
30
  │ └── hero-sections/hero-section-1.tsx
31
31
  └── e-commerce/
32
32
  ├── product-list/product-card-1.tsx
33
- └── storefront-hero/storefront-hero-1.tsx
33
+ └── storefront-hero/storefront-hero.tsx
34
34
  ```
35
35
 
36
36
  Blocks follow `src/blocks/<category>/<section>/<block>.tsx`. Each block co-locates its own prop types unless a contract is shared by two or more blocks.
@@ -39,72 +39,159 @@ Blocks follow `src/blocks/<category>/<section>/<block>.tsx`. Each block co-locat
39
39
 
40
40
  | Export | Category | Description |
41
41
  | --- | --- | --- |
42
- | `Navbar1` | Marketing | Simple navbar with logo, links, sign-in, CTA and cart count |
43
- | `Navbar2` | Marketing | Mega-menu navbar with grouped flyouts and featured card |
44
- | `Navbar3` | Marketing | Floating navbar that condenses into a rounded pill on scroll |
45
- | `Navbar4` | Marketing | Storefront navbar: utility bar, category links, search, account menu, cart sheet |
42
+ | `Navbar` | Marketing | Page-level navigation header: `bar`, `mega` (grouped flyouts plus optional featured promo) or `floating` (self-reserving pinned pill) |
43
+ | `StorefrontNavbar` | E-commerce | Commerce navigation shell: utility strip, category mega menu, optional search, account menu, cart sheet |
46
44
  | `Banner1` | Marketing | Announcement banner (bar or pill) with dismiss |
47
45
  | `HeroSection1` | Marketing | Hero with badge, headline, CTA, reviewer avatars, rating and image |
48
46
  | `CtaSection1` | Marketing | Full-width call-to-action with badge, headline and two CTAs |
49
- | `Feature1` | Marketing | Icon feature grid |
50
- | `Feature2` | Marketing | Split features with image |
51
- | `Testimonial1` | Marketing | Testimonial carousel with rating stars |
52
- | `Testimonial2` | Marketing | Bento testimonials grid |
47
+ | `Feature` | Marketing | Canonical benefits section: `cards` (icon feature grid) or `split` (editorial split with a required image) presentation |
48
+ | `TestimonialSection` | Marketing | Canonical testimonial collection section with `carousel` and `bento` presentations |
53
49
  | `FaqBlock1` | Marketing | FAQ accordion (single or multiple open) |
54
50
  | `PricingSection1` | Marketing | Pricing tiers with popular badge |
55
51
  | `Statistic1` | Marketing | Metrics grid with progress bars |
56
52
  | `LogoCloud1` | Marketing | Logo cloud with skeleton fallback |
57
- | `FooterSection1` | Marketing | Footer with brand, link columns and newsletter |
58
- | `FooterSection3` | Marketing | Newsletter + six-column footer with social links |
59
- | `ProductCard1` | E-commerce | Deals grid of product cards with add-to-cart |
60
- | `ProductCard2` | E-commerce | Clean three-up grid with ratings |
61
- | `ProductCard3` | E-commerce | Deals grid with badges, low-stock and wishlist |
62
- | `ProductCard5` | E-commerce | Table-style comparison with sorting and filtering |
53
+ | `FooterSection` | Marketing | Canonical page-level footer with `brand-columns`, `newsletter-grid` or `contact` presentation |
54
+ | `ProductCard` | E-commerce | Canonical product card (item cell of a listing); `presentation` `classic`/`minimal`/`rich`, canonical `ProductPresentation` items with `Money` prices, evidence-only merchandising |
63
55
  | `ProductShowcase1` | E-commerce | Combined grid-and-carousel of product cards with manual paging |
64
- | `ProductOverview1` | E-commerce | Product detail with gallery, variants and add-to-cart |
56
+ | `ProductOverview` | E-commerce | Canonical product detail; bounded `presentation` `standard`/`gallery`/`rich` (rich adds optional specs/features/reviews tabs) |
65
57
  | `ProductQuickView` | E-commerce | Quick-view dialog with gallery, variants and quantity |
66
58
  | `ReviewRating1` | E-commerce | Review list with rating summary |
67
59
  | `ReviewRating2` | E-commerce | Rating summary with distribution and review carousel |
68
- | `ProductCategory1` | E-commerce | Category grid with view-all |
69
- | `ProductCategory2` | E-commerce | Image-based category cards |
70
- | `ProductCategory4` | E-commerce | Icon grid with customer/product counts |
71
- | `ProductCategory5` | E-commerce | Category list with search and subcategories |
72
- | `CategoryFilter1` | E-commerce | Category filter bar |
73
- | `CategoryFilter2` | E-commerce | Filter sidebar with mobile sheet |
74
- | `ShoppingCart1` | E-commerce | Detailed cart summary |
75
- | `ShoppingCart2` | E-commerce | Cart with promo code and shipping |
76
- | `ShoppingCart4` | E-commerce | Cart review with backend-authoritative totals; `presentation` `compact` (collapsible card) or `page` (full-width review) |
77
- | `CheckoutForm1` | E-commerce | Essential checkout fields |
78
- | `CheckoutForm2` | E-commerce | Single-page checkout with order summary beside |
60
+ | `ProductCategory` | E-commerce | Canonical category block; bounded `presentation` `grid` (image cards) / `icons` (compact icon grid) / `list` (rows with subcategory chips and optional controlled search) |
61
+ | `CategoryListing` | E-commerce | Canonical category listing (results + pagination) with a controlled query and bounded `presentation` `sidebar` (persistent desktop panel + mobile sheet) / `toolbar` (sticky mobile toolbar + drawers); generic facet groups render only from real metadata |
62
+ | `ShoppingCart` | E-commerce | Canonical cart; backend-authoritative `Money` totals and item lines, optional promo entry and recently-viewed suggestions; `presentation` `compact` (collapsible card) or `page` (full-width review) |
63
+ | `Checkout` | E-commerce | Canonical page-level checkout; controlled values, backend-authoritative `Money` totals, `PaymentMethod[]` data with no raw card input; `presentation` `single-page` or `stepper`, optional `showReviewStep` |
79
64
  | `CheckoutConfirmation` | E-commerce | Order confirmation with fulfilment timeline |
80
65
  | `OrderHistory1` | E-commerce | Orders list |
81
66
  | `OrderHistory2` | E-commerce | Order cards with carousel items |
82
- | `StorefrontHero1` | E-commerce | Storefront hero with gradient headline, CTAs and a product grid |
83
- | `StorefrontHero2` | E-commerce | Search field above a trending product carousel |
84
- | `StorefrontHero5` | E-commerce | Departments grid with featured deals |
85
- | `StorefrontHero6` | E-commerce | Rotating deals bar beside category cards |
67
+ | `StorefrontHero` | E-commerce | Canonical storefront hero; `presentation` `compact` (gradient headline, CTAs, product grid), `showcase` (search + rotating product carousel), `editorial` (departments, featured deals, store locator) or `promotional` (rotating deals bar, category cards) |
86
68
  | `AppShell1` | Application | Application shell with navigation slots |
87
- | `LoginPage1` | Application | Login card with remember-me and social buttons |
88
- | `LoginPage2` | Application | Login with prominent social options |
89
- | `SignUpPage1` | Application | Registration card with social options |
69
+ | `LoginPage` | Application | Canonical sign-in form with explicit `card` or `social` presentation |
70
+ | `SignUpPage1` | Application | Registration form with social options (same profile as `LoginPage`) |
90
71
  | `ForgotPasswordPage1` | Application | Four-step password recovery wizard |
91
72
  | `Verify1` | Application | Two-factor verification (OTP or backup code) |
92
73
 
93
- Interactions are exposed as callbacks (e.g. `onAddToCart`, `onBuyNow`, `onSelectColor`). Elements stay in the DOM with no-op behavior when a callback is absent.
74
+ Interactions are exposed as callbacks (e.g. `onAddToCart`, `onBuyNow`, `onSelectColor`). `StorefrontHero` and `LoginPage` render optional affordances only when their handlers are supplied; other blocks may keep elements in the DOM with no-op behavior when a callback is absent.
75
+
76
+ ### Category listing
77
+
78
+ `CategoryListing` is the single controlled listing controller. Products, pagination, the selected query values, active-filter chips and the status are props; the block emits `on*Change`/`onClearFilters`/`onRepair`/`onRetry` and never fetches, parses the URL, or keeps a private query. `presentation` changes only the chrome: `sidebar` keeps a persistent desktop panel plus a mobile sheet, `toolbar` adds a sticky mobile toolbar with filter/sort drawers. Results render through the canonical `ProductCard`, so the listing never owns card markup. Facets are option-only groups (`{ id, label, options: [{ value, label, count? }] }`) supplied by the host; a group renders only when real metadata provides it, a real `0` count is displayed, and an absent/invalid count is omitted rather than invented.
79
+
80
+ ### Footer sections
81
+
82
+ `FooterSection` is the canonical page-level footer. Its required `presentation`
83
+ is `brand-columns`, `newsletter-grid`, or `contact`; provide serializable text,
84
+ links, and social records as props. Social records use `{ label, href?, icon }`
85
+ with an optional `ariaLabel`; callbacks and visible fallback text keep using the
86
+ raw `label`. Bind icon IDs to `LucideIcon` components through `socialIcons`, and
87
+ keep that map, `logo` nodes, and callbacks outside JSON fixtures.
88
+
89
+ Column/legal and social links have separate `linkBehavior` and
90
+ `socialLinkBehavior` props, both defaulting to `host`. Host mode requires the
91
+ matching callback and renders callback-less destinations as text; `native`
92
+ preserves browser navigation. Contact address, phone, and email hrefs are native.
93
+ Newsletter controls stay visible but disabled without `onNewsletterSubmit`;
94
+ the callback receives the entered email and the block performs no network request.
95
+
96
+ The numbered `FooterSection1`, `FooterSection3`, and `FooterSection4` exports,
97
+ deep module paths, and legacy prop contracts have been removed. Migrate to
98
+ `FooterSection` by supplying an explicit presentation and canonical serializable
99
+ content props. Select `linkBehavior="native"` or
100
+ `socialLinkBehavior="native"` when browser navigation is intended; both default
101
+ to host-controlled behavior. Canonical link types are exported as
102
+ `CanonicalFooterLink` and `CanonicalFooterLinkColumn` to avoid collisions with
103
+ other package contracts.
104
+
105
+ ### Login page
106
+
107
+ `LoginPage` is a sign-in composition with one shared credential form and a
108
+ required `presentation` of `card` or `social`. Both presentations submit the
109
+ same `{ email, password, remember }` payload. `remember` starts as `false` and
110
+ is reported to the host; the block does not decide session persistence.
111
+
112
+ The block renders into the host page and does not own it: no viewport height, no
113
+ page landmark, no page backdrop, no vertical centring. `card` caps its width at
114
+ `max-w-md` and `social` at the same cap, and the mounting page supplies the
115
+ canvas — a route shell, a modal, a split screen, or a template the host owns.
116
+ Before the shell was removed, each of these blocks painted a full-viewport
117
+ surface; a host that relied on that canvas must now provide it.
118
+
119
+ Provider data stays serializable. Pass provider IDs and labels separately from
120
+ the icon components, and provide a selection callback so provider buttons are
121
+ only rendered when they can act. IDs must be unique within the list; an
122
+ unmapped icon falls back to the visible provider label:
123
+
124
+ ```tsx
125
+ import { AtSign } from 'lucide-react'
126
+ import { LoginPage } from '@thorprovider/blocks'
127
+
128
+ <LoginPage
129
+ presentation='social'
130
+ providers={[{ id: 'email', label: 'Email' }]}
131
+ providerIcons={{ email: AtSign }}
132
+ onProviderSelect={providerId => startProviderLogin(providerId)}
133
+ onSubmit={({ email, password, remember }) => signIn({ email, password, remember })}
134
+ />
135
+ ```
136
+
137
+ The numbered `LoginPage1` and `LoginPage2` exports, deep module paths, and legacy
138
+ prop contracts have been removed. Migrate to `LoginPage` and select `card` or
139
+ `social` explicitly; both presentations use the canonical
140
+ `{ email, password, remember }` host callback contract.
141
+
142
+ ## Testimonials
143
+
144
+ `TestimonialSection` renders a collection of testimonials in one of two explicit
145
+ presentations: `carousel` or `bento`. Each item requires a stable, collection-unique
146
+ `id`, `name`, and `content`. `role`, `avatar`, and `rating` are optional and are
147
+ rendered only when supplied; the block never invents attribution or ratings and
148
+ does not emit an empty image source.
149
+
150
+ ```tsx
151
+ import { TestimonialSection } from '@thorprovider/blocks'
152
+
153
+ const testimonials = [
154
+ { id: 't-1', name: 'Rosa Díaz', role: 'Propietaria', content: 'Excelente servicio.' },
155
+ ]
156
+
157
+ <TestimonialSection
158
+ presentation="carousel"
159
+ items={testimonials}
160
+ title="Lo que dicen nuestros clientes"
161
+ carouselLabels={{
162
+ previous: 'Testimonio anterior',
163
+ next: 'Testimonio siguiente',
164
+ slide: 'Ir al testimonio {n}',
165
+ }}
166
+ />
167
+ ```
168
+
169
+ For `carousel`, `carouselLabels` is required and supplies localized previous,
170
+ next, and slide-button labels; `{n}` is replaced with the one-based item number.
171
+ The native buttons support keyboard activation, and the selected item is exposed
172
+ with `aria-current`. For `bento`, omit `carouselLabels`; one-to-three items render
173
+ without empty cards, and collections larger than four repeat the four-tile visual
174
+ rhythm without dropping entries. An empty `items` array renders no section or
175
+ controls. `title`, `description`, and `className` are optional for either
176
+ presentation.
177
+
178
+ The numbered `Testimonial1` and `Testimonial2` exports, deep module paths, and
179
+ legacy prop contracts have been removed. Migrate to `TestimonialSection` with
180
+ shared testimonial items and select `carousel` or `bento` explicitly.
94
181
 
95
182
  ## Optional brand props
96
183
 
97
184
  Three existing blocks expose an optional brand seam. Omitting the prop renders
98
185
  the block's current output unchanged; no demo content is added.
99
186
 
100
- - `Navbar4` — `logoAccent?: { text: string; className?: string }` accents a
187
+ - `StorefrontNavbar` — `logoAccent?: { text: string; className?: string }` accents a
101
188
  trailing segment of `logoText`. With `logoText="ElectroCuba"` and
102
189
  `logoAccent={{ text: 'Cuba', className: 'text-brand-promo' }}`, the block
103
190
  renders `Electro` plus an accented `Cuba` span. If `text` is not a trailing
104
191
  segment of `logoText`, the wordmark renders unchanged.
105
- - `FooterSection1` — `brandAccent?: { text: string; className?: string }` applies
192
+ - `FooterSection` — `brandAccent?: { text: string; className?: string }` applies
106
193
  the same trailing-segment accent to `brandName`.
107
- - `StorefrontHero1` — `heroImage?: { src: string; alt: string }` renders a native
194
+ - `StorefrontHero` — `heroImage?: { src: string; alt: string }` renders a native
108
195
  `<img>` when `src` is a non-empty string, and nothing when the prop is absent
109
196
  or `src` is blank (never an empty `src`). The product grid is unaffected.
110
197
 
@@ -113,13 +200,13 @@ the library hardcodes no brand color.
113
200
 
114
201
  ## Custom surface `className`
115
202
 
116
- `Navbar4` (outer `<header>`) and `FooterSection1` (outer `<footer>`) accept an
203
+ `Navbar4` (outer `<header>`) and `FooterSection` (outer `<footer>`) accept an
117
204
  optional `className`, merged with the existing `cn` helper. It is purely
118
205
  presentational: no layout, structure, or behavior prop, and no hardcoded color.
119
206
 
120
207
  - **Omitted `className`** — the block renders its default surface unchanged.
121
208
  `Navbar4` keeps `bg-background/95` **and** its
122
- `supports-backdrop-filter:bg-background/80` variant; `FooterSection1` keeps
209
+ `supports-backdrop-filter:bg-background/80` variant; `FooterSection` keeps
123
210
  `bg-secondary`.
124
211
  - **Supplied `className`** — the consumer owns the surface. The default
125
212
  background **and its backdrop-filter variant** are not applied, so a semantic
@@ -128,31 +215,50 @@ presentational: no layout, structure, or behavior prop, and no hardcoded color.
128
215
  it). Layout, border, sticky, and `backdrop-blur-md` classes stay internal.
129
216
 
130
217
  The consumer is responsible for the text, transparency, and context tokens of a
131
- supplied surface. `Navbar4` adapts its primary nav links: they inherit the
218
+ supplied surface. `StorefrontNavbar` adapts its primary nav links: they inherit the
132
219
  surface text color and add `hover:text-foreground` so `hover:bg-muted` never
133
220
  paints white-on-light. No magic colors are introduced by the block.
134
221
 
222
+ ## StorefrontNavbar account affordance
223
+
224
+ `StorefrontNavbar` renders a **session-driven** account affordance exclusively from
225
+ consumer-supplied props — it never resolves a session or authenticates itself.
226
+ Given a `user` (`StoreUser`; `avatar` is optional) and `accountMenuItems`, it
227
+ renders an authenticated account menu with the identity, the configured items,
228
+ and the consumer's sign-out action on desktop and mobile. Given no `user` but a
229
+ configured `accountHref`, it renders a signed-out account entry point on both
230
+ viewports; with neither configured it renders no account affordance and no empty
231
+ container. An absent `avatar` uses the initials fallback and emits no empty image
232
+ `src`. Optional `accountPending` and `accountUnavailable` props drive a
233
+ pending/disabled state and an accessible unavailable/retry state (via
234
+ `onAccountRetry`) without presenting either as an anonymous session, and sign-out
235
+ is suppressed while pending. All callbacks (`onSignOut`, `onAccountRetry`,
236
+ `onHelp`) are consumer bindings kept outside the serializable JSON prop schema;
237
+ the sign-out action is supplied by the consumer, so the block never performs
238
+ authentication.
239
+
135
240
  ## Footer newsletter
136
241
 
137
- `FooterSection1` renders the interactive newsletter form (input + submit button)
138
- **only** when `newsletter.onSubmit` is supplied. A `newsletter` object without
139
- `onSubmit` renders no form, input, or button — never a fake form. Omit
140
- `newsletter` entirely to hide the newsletter block.
242
+ `FooterSection` renders the newsletter form disabled when
243
+ `onNewsletterSubmit` is absent. When supplied, the callback receives the entered
244
+ email; the block performs no network request. Omit `newsletter` to hide the
245
+ newsletter block.
141
246
 
142
247
  ## Cart presentation (controlled)
143
248
 
144
249
  The cart is presented in two shapes that share one data contract, so a change in
145
250
  either is visible in the other.
146
251
 
147
- - `ShoppingCart4` is the cart block. Its optional `presentation` prop selects
252
+ - `ShoppingCart` is the cart block. Its optional `presentation` prop selects
148
253
  `'compact'` (default: a collapsible `max-w-md` card with an internal scroller —
149
254
  the navbar lateral) or `'page'` (always expanded, full-width `max-w-3xl`, no
150
255
  collapse control, ordinary page scrolling — the `/cart` review route).
151
256
  Omitting `presentation` renders the compact card byte-identically to before.
152
- - Line amounts prefer the canonical `lineTotal?: Money` and fall back to the
153
- legacy numeric `price * quantity` only when `lineTotal` is absent. A missing or
154
- malformed `Money` renders the `amountUnavailableLabel` (never a fabricated
155
- zero or a currency fallback). `unitPrice?: Money` is carried verbatim.
257
+ - Line amounts render the authoritative `lineTotal: Money` verbatim (backend
258
+ line total, never a local `unitPrice × quantity` computation); a malformed
259
+ amount renders the `amountUnavailableLabel` (never a fabricated zero or a
260
+ currency fallback). An optional `unitPrice: Money` is carried verbatim but
261
+ never used for any local arithmetic.
156
262
  - `totalsState` (`'idle' | 'pending' | 'error'`) plus `onRetryTotals` make the
157
263
  totals controlled: pending retains the last confirmed totals and disables
158
264
  conflicting mutations; error shows the error label and a retry.
@@ -160,28 +266,78 @@ either is visible in the other.
160
266
  by backend line id) and `onRemove(id)` removes by the same id.
161
267
  `checkoutUnavailable` hides the checkout action (e.g. no checkout installed)
162
268
  and `checkoutDisabled` disables it (e.g. pending or invalid totals).
163
-
164
- `Navbar4` owns the lateral. Pass `cartOpen` + `onCartOpenChange` to control the
269
+ - Promo entry is an optional affordance: with `onApplyPromo(code)` it renders an
270
+ input that reports the entered code and shows only the authoritative applied
271
+ promotion/discount already present in `totals`; without the callback it is not
272
+ rendered, and the block never validates or resolves a code itself.
273
+ `onApplyPromo` never gates `recentlyViewed`, an optional host-supplied
274
+ suggestion list (name, optional image, optional `Money` price) rendered only
275
+ when supplied.
276
+
277
+ `StorefrontNavbar` owns the lateral. Pass `cartOpen` + `onCartOpenChange` to control the
165
278
  sheet's visibility from host state (omit both to keep the legacy uncontrolled
166
279
  sheet); the block keeps `aria-expanded`/`data-state` in sync. Its cart sheet
167
- (`Navbar4CartSheet`) takes an optional `canonical` mode where each line shows the
280
+ (`StorefrontNavbarCartSheet`) takes an optional `canonical` mode where each line shows the
168
281
  canonical `lineTotal` (never the placeholder numeric price), plus
169
282
  `checkoutUnavailable`/`viewCartUnavailable` to hide actions the store cannot
170
283
  serve, and a `state` (`'idle' | 'pending' | 'error'`) with retry for read/mutation
171
284
  feedback.
172
285
 
173
- ## Checkout blocks (controlled)
286
+ ## Product card (canonical)
287
+
288
+ `ProductCard` is the item cell of a product listing and the canonical member of
289
+ the product-card family. Its items are `ProductPresentation` only (canonical
290
+ `Money`): a missing price renders the `unavailablePriceLabel` with add-to-cart
291
+ disabled — never a zero amount — and optional image, rating, badge, and
292
+ compare-at price are omitted when absent (no empty `src`, no empty stars, no
293
+ empty chip). A compare-at price renders only when it is valid and strictly
294
+ higher than the selling price.
295
+
296
+ The bounded `presentation` option selects the structure — `classic` (boxed card
297
+ with an add-to-cart footer), `minimal` (lean image-forward tile with an
298
+ icon-only cart action), `rich` (overlay actions: wishlist heart and
299
+ view-similar) — while the data and action contract stays identical. Ratings,
300
+ badges, and scarcity copy render only from host-supplied data (the `badge`
301
+ copy and `compareAtPrice` are the evidence channels); the block never invents
302
+ stars, chips, or urgency.
303
+
304
+ The wishlist heart is host-owned: pass `wishlistedIds` to control it, and
305
+ `onToggleWishlist(item, nextState)` receives every toggle from the click
306
+ handler (never from an effect). Without host state the heart keeps an
307
+ uncontrolled visual fallback and still reports. `onViewSimilar` renders its
308
+ affordance only when supplied. Prices render through the canonical
309
+ `formatMoney` path (no hardcoded currency), and every label is overridable
310
+ with Spanish defaults (`Agregar al carrito`, `Precio no disponible`, …).
311
+
312
+ ## Checkout block (page-level, controlled)
313
+
314
+ `Checkout` is the single public checkout surface; it replaces the numbered `CheckoutForm1`, `CheckoutForm2`, and `CheckoutForm3` blocks. It is a **controlled component**: field values arrive through `values` and every edit is reported through `onChange`, so the host owns all commercial state. The block owns only the ephemeral step index of the `stepper` presentation.
315
+
316
+ - `presentation: 'single-page' | 'stepper'` (required) — `single-page` renders every section visible at once (summary first in DOM order on mobile); `stepper` shows one step at a time with an accessible progress indicator. Both accept the same props and emit the same callbacks.
317
+ - `showReviewStep?: boolean` — appends a confirmation step (`stepper`) or section (`single-page`) before submission.
318
+ - `values: CheckoutValues` — `email`, `firstName`, `lastName`, `phone`, `address`, `city`, `state`, `zipCode`, `country`, `paymentMethod`, `saveInfo`, `newsletter`, `promoCode`. There are no card fields.
319
+ - `onChange: (field, value) => void` — reported per edit. The block holds no private copy of the form data and applies no masking.
320
+ - `orderSummary?: { items, totals? }` — line identity, name, quantity, optional variant/image, optional canonical `Money` `lineTotal` and `unitPrice`, plus backend `totals` (`subtotal`, optional `discount`/`tax`/`shipping`, `total`). Amounts render verbatim; required totals that are absent or malformed render as unavailable with the retry action, never as zero. No local arithmetic, tax rate, shipping cost, threshold, or hardcoded currency.
321
+ - `totalsState?: 'idle' | 'pending' | 'error'` and `onRetryTotals?` — controlled reads; pending and failed states render status/alert copy without inventing amounts.
322
+ - `countries?: { value, label }[]`, `shippingOptions?`, `selectedShippingOptionId?`, `onSelectShippingOption?` — host-owned options and selection. With no country list the block renders a text input; with no shipping options it renders no method section; nothing is pre-selected.
323
+ - `paymentMethods?: PaymentMethod[]` — payment methods as data (canonical `@thorprovider/types`). One option per method, the selected method's description, an empty state for an empty list, and a non-selectable rendering for `isEnabled === false`. The block renders **no raw card fields**: card collection belongs to the host payment-provider surface.
324
+ - `labels?: Partial<CheckoutLabels>` and `formatMoney?: (money: Money) => string` — every user-facing string is overridable (neutral English defaults) and amounts format through canonical `Money` with no hardcoded symbol.
325
+ - `fieldErrors?` / `fieldDescriptions?` — per-field messages and helper text, rendered with `role="alert"` and `aria-invalid`/`aria-describedby` wiring.
326
+ - `validateStep?: (step: CheckoutStepId) => boolean` and `onStepChange?` — the block consults `validateStep` before advancing and for every applicable step before submitting; `onStepChange` is a notification only, never a controlled value. Without the callback the block advances and leaves submit protection to the host.
327
+ - `onSubmit?`, `submitting?`, `submitError?`, `onRetrySubmit?` — submission is reported, with a pending guard and a recoverable error state; the block never creates an order, clears a cart, or claims success.
328
+
329
+ Validation itself is **not** implemented in the block. Declare checks in your store spec's validation configuration (built-in `required`/`email`/`pattern`/`minLength`/`maxLength`/cross-field `requiredIf`/`equalTo`/`lessThan`/`greaterThan`, plus catalog-registered custom functions) and evaluate them with the renderer; the messages live in the spec so they are per-store and per-language.
330
+
331
+ The order summary is an internal projection of `Checkout`, not a separate block. Order receipt and fulfilment progress remain `CheckoutConfirmation`.
332
+
333
+ ### Migrating from the numbered checkout blocks
334
+
335
+ | Retired | Replacement |
336
+ | --- | --- |
337
+ | `CheckoutForm1` | `<Checkout presentation="stepper" />` — same controlled values minus `cardNumber`/`expiryDate`/`cvv`/`cardName`/`sameAsBilling`; `validateStep` now receives a `CheckoutStepId` (`'contact' \| 'shipping' \| 'payment' \| 'review'`) instead of a number |
338
+ | `CheckoutForm2` | `<Checkout presentation="single-page" />` — the host supplies `Money` totals; the block no longer computes tax, shipping, or free-shipping thresholds |
339
+ | `CheckoutForm3` | `<Checkout presentation="stepper" showReviewStep />` — values become controlled; `apartment`, card fields, per-step icons, and internal `isStepComplete` state are gone |
174
340
 
175
- `CheckoutForm1` (3-step wizard) and `CheckoutForm2` (single page) are **controlled components**: they receive the current form values through a `values` prop and report every edit through `onChange`. They hold no internal form state, so a renderer's validation context can read field values from its own state model. `CheckoutForm3` (4-step wizard) keeps the same contract with internal step state.
176
-
177
- - `paymentMethods?: PaymentMethod[]` — available payment methods as data (canonical `@thorprovider/types` `PaymentMethod`). The blocks render a radio list from this prop, show the selected method's name + `description`, and only render the card fields (`cardNumber`, `expiryDate`, `cvv`, `cardName`) when the selected method's `type === 'card'`. No payment method is hardcoded; pass an empty list to trigger the empty state.
178
- - `values: CheckoutForm1FormData` / `CheckoutForm2Data` — current field values (including `cardNumber`, `expiryDate`, `cvv`, `cardName`, and `paymentMethod` for `CheckoutForm1`/`CheckoutForm3`).
179
- - `onChange: (field, value) => void` — reported per edit; card grouping and MM/YY masking are applied by the block before the value reaches `onChange` (presentation only, no validation logic in the block).
180
- - `fieldErrors?: Partial<Record<FieldKey, Array<{ message?: string }>>>` — per-field error messages rendered via `FieldError` (`role="alert"`, `aria-invalid`/`aria-describedby` wiring). The same channel carries async/server errors (e.g. payment decline, invalid promo).
181
- - `fieldDescriptions?: Partial<Record<FieldKey, string>>` — helper text rendered via `FieldDescription`.
182
- - `validateStep?: (step: number) => boolean` (wizard only) — the block calls it on "Next"; advance only happens when it returns `true`, and the final submit only fires when every step validates.
183
-
184
- Validation itself is **not** implemented in the blocks. Declare checks in your store spec's validation configuration (built-in `required`/`email`/`pattern`/`minLength`/`maxLength`/cross-field `requiredIf`/`equalTo`/`lessThan`/`greaterThan`, plus catalog-registered custom functions like `luhn`/`cvv`/`expiryDate`) and evaluate them with the renderer; the messages live in the spec so they are per-store and per-language. Without a validation provider the blocks render with no inline errors.
185
341
 
186
342
  ## Usage
187
343
 
@@ -229,11 +385,11 @@ unique by contract, not by convention. See
229
385
  rules and the review checklist.
230
386
 
231
387
  Optional numeric projections are omitted rather than fabricated: e.g.
232
- `ProductCategory2Category.itemCount` renders a count only when it is a supplied
388
+ `ProductCategoryItem.itemCount` renders a count only when it is a supplied
233
389
  finite non-negative number (a real `0` is preserved); an unknown count renders
234
390
  no count text, never a fabricated `0`.
235
391
 
236
- Product presentations (`ProductCard1`, `ProductShowcase1`) accept either the
392
+ Product presentations (`ProductGrid`, `ProductShowcase1`) accept either the
237
393
  legacy numeric-price props or a canonical `ProductPresentation` (`price`/
238
394
  `compareAtPrice` as `Money`, `{ amount: string, currencyCode: string }` in
239
395
  decimal major units). The canonical path formats with the store currency and
@@ -242,6 +398,10 @@ locale, renders a truthful "unavailable" state for an unresolved price (never
242
398
  valid and strictly higher. Optional rating/badge/image appear only when
243
399
  supplied; a missing image is omitted, never `src=""`.
244
400
 
401
+ The canonical `ProductCard` accepts only the canonical `ProductPresentation`
402
+ contract — the legacy numeric-price props of the numbered cards are retired
403
+ (migrate item data to canonical `Money`).
404
+
245
405
  ## Block interaction contracts
246
406
 
247
407
  State and events between a block, its host registry, and the store must follow
@@ -251,4 +411,4 @@ callbacks (`useCallback`); a fresh object literal always notifies a reference-on
251
411
  store guard; and every registered action completes its round-trip (handler → set +
252
412
  emit → spec `on` → recompute → bind). See
253
413
  [`docs/standards/interaction-contracts.md`](docs/standards/interaction-contracts.md)
254
- for the rules and the review checklist.
414
+ for the rules and the review checklist.