create-tanqory-theme 0.0.0-stage → 0.2.3

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 (879) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +55 -2
  3. package/bin/create-tanqory-theme.mjs +13 -0
  4. package/lib/agents.mjs +118 -0
  5. package/lib/create.mjs +245 -0
  6. package/package.json +39 -4
  7. package/templates/blank/README.md +13 -0
  8. package/templates/blank/config/settings.json +3 -0
  9. package/templates/blank/locales/en.json +22 -0
  10. package/templates/blank/locales/th.json +22 -0
  11. package/templates/blank/package.json +34 -0
  12. package/templates/blank/regions/footer.json +35 -0
  13. package/templates/blank/regions/header.json +13 -0
  14. package/templates/blank/scripts/build.mjs +193 -0
  15. package/templates/blank/scripts/probe.ts +4 -0
  16. package/templates/blank/src/bundle.ts +15 -0
  17. package/templates/blank/src/components/parts.tsx +32 -0
  18. package/templates/blank/src/entry.client.tsx +6 -0
  19. package/templates/blank/src/entry.server.tsx +13 -0
  20. package/templates/blank/src/sections/chrome.tsx +132 -0
  21. package/templates/blank/src/sections/commerce.tsx +242 -0
  22. package/templates/blank/src/sections/content.tsx +111 -0
  23. package/templates/blank/src/sections/editorial.tsx +107 -0
  24. package/templates/blank/src/settings.ts +15 -0
  25. package/templates/blank/src/styles/theme.css +195 -0
  26. package/templates/blank/src/templates.ts +14 -0
  27. package/templates/blank/src/theme.tsx +76 -0
  28. package/templates/blank/template.json +24 -0
  29. package/templates/blank/templates/404.json +14 -0
  30. package/templates/blank/templates/account.json +14 -0
  31. package/templates/blank/templates/article.json +14 -0
  32. package/templates/blank/templates/blog.json +14 -0
  33. package/templates/blank/templates/cart.json +14 -0
  34. package/templates/blank/templates/collection.json +14 -0
  35. package/templates/blank/templates/index.json +36 -0
  36. package/templates/blank/templates/page.contact.json +18 -0
  37. package/templates/blank/templates/page.json +14 -0
  38. package/templates/blank/templates/product.json +18 -0
  39. package/templates/blank/templates/search.json +14 -0
  40. package/templates/blank/tests/theme.test.ts +49 -0
  41. package/templates/blank/tsconfig.json +17 -0
  42. package/templates/blank/vite.client.config.ts +17 -0
  43. package/templates/blank/vite.probe.config.ts +16 -0
  44. package/templates/blank/vite.server.config.ts +32 -0
  45. package/templates/blank/vitest.config.ts +9 -0
  46. package/templates/common/.claude/settings.json +8 -0
  47. package/templates/common/.codex/config.toml +4 -0
  48. package/templates/common/.gemini/settings.json +12 -0
  49. package/templates/common/.github/workflows/theme-check.yml +44 -0
  50. package/templates/common/.mcp.json +12 -0
  51. package/templates/common/.vscode/mcp.json +9 -0
  52. package/templates/common/_gitignore +9 -0
  53. package/templates/common/scripts/check.mjs +44 -0
  54. package/templates/common/scripts/dev.mjs +30 -0
  55. package/templates/common/tanqory.config.json +6 -0
  56. package/templates/nova/.gitattributes +3 -0
  57. package/templates/nova/.tanqory/contracts/LOCK.json +66 -0
  58. package/templates/nova/.tanqory/contracts/nova-01.json +1092 -0
  59. package/templates/nova/.tanqory/contracts/nova-12A.json +2674 -0
  60. package/templates/nova/.tanqory/contracts/nova-12B.json +2285 -0
  61. package/templates/nova/.tanqory/contracts/nova-12C.json +2848 -0
  62. package/templates/nova/.tanqory/contracts/nova-12D.json +3039 -0
  63. package/templates/nova/.tanqory/contracts/nova-12E.json +1473 -0
  64. package/templates/nova/.tanqory/contracts/nova-12F.json +1670 -0
  65. package/templates/nova/.tanqory/contracts/nova-12G.json +1661 -0
  66. package/templates/nova/.tanqory/contracts/nova-13.json +301 -0
  67. package/templates/nova/.tanqory/contracts/nova-14.json +172 -0
  68. package/templates/nova/.tanqory/legacy-settings.json +1121 -0
  69. package/templates/nova/README.md +150 -0
  70. package/templates/nova/_gitignore +28 -0
  71. package/templates/nova/assets/ai-tokens.css +6 -0
  72. package/templates/nova/assets/starter/starter-art-1200x800-0b773ac4.svg +1 -0
  73. package/templates/nova/assets/starter/starter-art-1200x900-59600290.svg +1 -0
  74. package/templates/nova/assets/starter/starter-art-1200x900-c549d496.svg +1 -0
  75. package/templates/nova/assets/starter/starter-art-1600x900-8f979fcb.svg +1 -0
  76. package/templates/nova/assets/starter/starter-art-2100x900-cbb71bfd.svg +1 -0
  77. package/templates/nova/config/legacy-settings.json +438 -0
  78. package/templates/nova/config/promotion-popup.schema.ts +29 -0
  79. package/templates/nova/config/settings.json +23 -0
  80. package/templates/nova/config/settings.schema.ts +231 -0
  81. package/templates/nova/entry.abi.tsx +27 -0
  82. package/templates/nova/groups/footer.json +39 -0
  83. package/templates/nova/groups/header.json +11 -0
  84. package/templates/nova/lib/abi/abi.css +41 -0
  85. package/templates/nova/lib/bundle-core.ts +35 -0
  86. package/templates/nova/lib/bundle.client.ts +15 -0
  87. package/templates/nova/lib/bundle.ts +15 -0
  88. package/templates/nova/lib/legacy-settings.ts +104 -0
  89. package/templates/nova/locales/ar.json +590 -0
  90. package/templates/nova/locales/en.json +590 -0
  91. package/templates/nova/locales/th.json +590 -0
  92. package/templates/nova/main.abi.tsx +12 -0
  93. package/templates/nova/package.json +44 -0
  94. package/templates/nova/scripts/abi/build.mjs +145 -0
  95. package/templates/nova/scripts/abi/check-content.mjs +99 -0
  96. package/templates/nova/scripts/abi/jcs.mjs +27 -0
  97. package/templates/nova/scripts/abi/local-host.mjs +294 -0
  98. package/templates/nova/scripts/abi/manifest.mjs +392 -0
  99. package/templates/nova/scripts/abi/starter-assets.mjs +32 -0
  100. package/templates/nova/scripts/compiler/checks/check-a11y.mjs +88 -0
  101. package/templates/nova/scripts/compiler/checks/check-layout-sanity.mjs +169 -0
  102. package/templates/nova/scripts/compiler/checks/check-page-headings.mjs +67 -0
  103. package/templates/nova/scripts/compiler/checks/check-product-jsonld.mjs +80 -0
  104. package/templates/nova/scripts/compiler/checks/theme-spec/check.mjs +88 -0
  105. package/templates/nova/scripts/compiler/contracts/extract.mjs +188 -0
  106. package/templates/nova/scripts/compiler/contracts/index.ts +125 -0
  107. package/templates/nova/scripts/compiler/contracts/validate.ts +347 -0
  108. package/templates/nova/scripts/compiler/manifest/gen-manifest.mjs +156 -0
  109. package/templates/nova/scripts/compiler/manifest/manifest-entry.ts +386 -0
  110. package/templates/nova/scripts/compiler/manifest/manifest-sections.ts +247 -0
  111. package/templates/nova/scripts/compiler/migrate/migrate-content.mjs +171 -0
  112. package/templates/nova/scripts/compiler/migrate/migrate-groups.mjs +159 -0
  113. package/templates/nova/scripts/parity/case-shim.ts +14 -0
  114. package/templates/nova/scripts/parity/emit.ts +206 -0
  115. package/templates/nova/scripts/parity/index.html +12 -0
  116. package/templates/nova/scripts/parity/main.tsx +23 -0
  117. package/templates/nova/scripts/parity/pages.tsx +123 -0
  118. package/templates/nova/scripts/parity/vite.config.ts +29 -0
  119. package/templates/nova/src/components/base/Accordion.css +20 -0
  120. package/templates/nova/src/components/base/Accordion.tsx +50 -0
  121. package/templates/nova/src/components/base/Badge.css +17 -0
  122. package/templates/nova/src/components/base/Badge.tsx +26 -0
  123. package/templates/nova/src/components/base/Button.css +73 -0
  124. package/templates/nova/src/components/base/Button.tsx +64 -0
  125. package/templates/nova/src/components/base/Checkbox.tsx +18 -0
  126. package/templates/nova/src/components/base/Chip.css +20 -0
  127. package/templates/nova/src/components/base/Chip.tsx +33 -0
  128. package/templates/nova/src/components/base/Divider.css +6 -0
  129. package/templates/nova/src/components/base/Divider.tsx +28 -0
  130. package/templates/nova/src/components/base/IconButton.css +34 -0
  131. package/templates/nova/src/components/base/IconButton.tsx +41 -0
  132. package/templates/nova/src/components/base/Input.css +21 -0
  133. package/templates/nova/src/components/base/Input.tsx +24 -0
  134. package/templates/nova/src/components/base/Link.css +15 -0
  135. package/templates/nova/src/components/base/Link.tsx +23 -0
  136. package/templates/nova/src/components/base/Radio.tsx +13 -0
  137. package/templates/nova/src/components/base/Select.css +18 -0
  138. package/templates/nova/src/components/base/Select.tsx +34 -0
  139. package/templates/nova/src/components/base/Skeleton.css +17 -0
  140. package/templates/nova/src/components/base/Skeleton.tsx +23 -0
  141. package/templates/nova/src/components/base/Spinner.css +13 -0
  142. package/templates/nova/src/components/base/Spinner.tsx +8 -0
  143. package/templates/nova/src/components/base/Switch.tsx +16 -0
  144. package/templates/nova/src/components/base/Tabs.css +24 -0
  145. package/templates/nova/src/components/base/Tabs.tsx +61 -0
  146. package/templates/nova/src/components/base/Textarea.css +1 -0
  147. package/templates/nova/src/components/base/Textarea.tsx +8 -0
  148. package/templates/nova/src/components/base/choice-props.ts +9 -0
  149. package/templates/nova/src/components/base/choice.css +69 -0
  150. package/templates/nova/src/components/commerce/AddToCart.css +4 -0
  151. package/templates/nova/src/components/commerce/AddToCart.tsx +77 -0
  152. package/templates/nova/src/components/commerce/CartLine.css +26 -0
  153. package/templates/nova/src/components/commerce/CartLine.tsx +82 -0
  154. package/templates/nova/src/components/commerce/CartSummary.css +23 -0
  155. package/templates/nova/src/components/commerce/CartSummary.tsx +120 -0
  156. package/templates/nova/src/components/commerce/CollectionCard.css +66 -0
  157. package/templates/nova/src/components/commerce/CollectionCard.tsx +57 -0
  158. package/templates/nova/src/components/commerce/ComparisonTable.css +33 -0
  159. package/templates/nova/src/components/commerce/ComparisonTable.tsx +113 -0
  160. package/templates/nova/src/components/commerce/EarnHint.css +3 -0
  161. package/templates/nova/src/components/commerce/EarnHint.tsx +31 -0
  162. package/templates/nova/src/components/commerce/Hotspot.css +16 -0
  163. package/templates/nova/src/components/commerce/Hotspot.tsx +64 -0
  164. package/templates/nova/src/components/commerce/ListingParts.css +79 -0
  165. package/templates/nova/src/components/commerce/ListingParts.tsx +211 -0
  166. package/templates/nova/src/components/commerce/LookItem.css +19 -0
  167. package/templates/nova/src/components/commerce/LookItem.tsx +41 -0
  168. package/templates/nova/src/components/commerce/MiniCartLine.css +20 -0
  169. package/templates/nova/src/components/commerce/MiniCartLine.tsx +93 -0
  170. package/templates/nova/src/components/commerce/PaymentMark.css +2 -0
  171. package/templates/nova/src/components/commerce/PaymentMark.tsx +55 -0
  172. package/templates/nova/src/components/commerce/Price.css +27 -0
  173. package/templates/nova/src/components/commerce/Price.tsx +55 -0
  174. package/templates/nova/src/components/commerce/ProductCard.css +66 -0
  175. package/templates/nova/src/components/commerce/ProductCard.tsx +117 -0
  176. package/templates/nova/src/components/commerce/ProductGallery.css +72 -0
  177. package/templates/nova/src/components/commerce/ProductGallery.tsx +98 -0
  178. package/templates/nova/src/components/commerce/QuantitySelector.css +23 -0
  179. package/templates/nova/src/components/commerce/QuantitySelector.tsx +45 -0
  180. package/templates/nova/src/components/commerce/Reviews.css +34 -0
  181. package/templates/nova/src/components/commerce/Reviews.tsx +112 -0
  182. package/templates/nova/src/components/commerce/Stars.css +7 -0
  183. package/templates/nova/src/components/commerce/Stars.tsx +38 -0
  184. package/templates/nova/src/components/commerce/VariantPicker.css +31 -0
  185. package/templates/nova/src/components/commerce/VariantPicker.tsx +122 -0
  186. package/templates/nova/src/components/commerce/blocks/Badge.css +6 -0
  187. package/templates/nova/src/components/commerce/blocks/Badge.tsx +33 -0
  188. package/templates/nova/src/components/commerce/blocks/PaymentIcons.tsx +43 -0
  189. package/templates/nova/src/components/commerce/blocks/Swatches.tsx +17 -0
  190. package/templates/nova/src/components/commerce/blocks/payment.css +2 -0
  191. package/templates/nova/src/components/commerce/cart-limit.ts +11 -0
  192. package/templates/nova/src/components/commerce/money.ts +34 -0
  193. package/templates/nova/src/components/commerce/swatch.ts +54 -0
  194. package/templates/nova/src/components/content/ArticleCard.css +22 -0
  195. package/templates/nova/src/components/content/ArticleCard.tsx +53 -0
  196. package/templates/nova/src/components/content/BeforeAfterSlider.css +38 -0
  197. package/templates/nova/src/components/content/BeforeAfterSlider.tsx +87 -0
  198. package/templates/nova/src/components/content/CardRow.css +80 -0
  199. package/templates/nova/src/components/content/CardRow.tsx +140 -0
  200. package/templates/nova/src/components/content/CarouselControls.css +61 -0
  201. package/templates/nova/src/components/content/CarouselControls.tsx +90 -0
  202. package/templates/nova/src/components/content/ContentsNav.css +16 -0
  203. package/templates/nova/src/components/content/ContentsNav.tsx +55 -0
  204. package/templates/nova/src/components/content/Countdown.css +24 -0
  205. package/templates/nova/src/components/content/Countdown.tsx +115 -0
  206. package/templates/nova/src/components/content/HeroCopy.css +92 -0
  207. package/templates/nova/src/components/content/HeroCopy.tsx +148 -0
  208. package/templates/nova/src/components/content/HeroFrame.css +75 -0
  209. package/templates/nova/src/components/content/HeroFrame.tsx +49 -0
  210. package/templates/nova/src/components/content/HeroMedia.css +40 -0
  211. package/templates/nova/src/components/content/HeroMedia.tsx +170 -0
  212. package/templates/nova/src/components/content/LongForm.css +40 -0
  213. package/templates/nova/src/components/content/LongForm.tsx +56 -0
  214. package/templates/nova/src/components/content/MediaFrame.css +5 -0
  215. package/templates/nova/src/components/content/MediaFrame.tsx +52 -0
  216. package/templates/nova/src/components/content/RichText.tsx +30 -0
  217. package/templates/nova/src/components/content/SectionHead.css +8 -0
  218. package/templates/nova/src/components/content/SectionHead.tsx +34 -0
  219. package/templates/nova/src/components/content/ShareButtons.css +2 -0
  220. package/templates/nova/src/components/content/ShareButtons.tsx +40 -0
  221. package/templates/nova/src/components/content/StoreCard.css +14 -0
  222. package/templates/nova/src/components/content/StoreCard.tsx +60 -0
  223. package/templates/nova/src/components/content/TestimonialCard.css +36 -0
  224. package/templates/nova/src/components/content/TestimonialCard.tsx +102 -0
  225. package/templates/nova/src/components/content/UgcTile.css +8 -0
  226. package/templates/nova/src/components/content/UgcTile.tsx +33 -0
  227. package/templates/nova/src/components/content/blocks/Button.tsx +36 -0
  228. package/templates/nova/src/components/content/blocks/Column.tsx +30 -0
  229. package/templates/nova/src/components/content/blocks/Heading.tsx +31 -0
  230. package/templates/nova/src/components/content/blocks/Icon.tsx +25 -0
  231. package/templates/nova/src/components/content/blocks/Image.tsx +42 -0
  232. package/templates/nova/src/components/content/blocks/Text.tsx +26 -0
  233. package/templates/nova/src/components/content/blocks/Video.tsx +44 -0
  234. package/templates/nova/src/components/content/blocks/content.css +25 -0
  235. package/templates/nova/src/components/content/blocks/shared.ts +5 -0
  236. package/templates/nova/src/components/forms/ContactForm.css +19 -0
  237. package/templates/nova/src/components/forms/ContactForm.tsx +139 -0
  238. package/templates/nova/src/components/forms/FormField.css +15 -0
  239. package/templates/nova/src/components/forms/FormField.tsx +77 -0
  240. package/templates/nova/src/components/forms/GatewayFields.tsx +79 -0
  241. package/templates/nova/src/components/forms/NewsletterForm.css +9 -0
  242. package/templates/nova/src/components/forms/NewsletterForm.tsx +141 -0
  243. package/templates/nova/src/components/forms/SearchField.css +7 -0
  244. package/templates/nova/src/components/forms/SearchField.tsx +68 -0
  245. package/templates/nova/src/components/forms/blocks/Newsletter.css +5 -0
  246. package/templates/nova/src/components/forms/blocks/Newsletter.tsx +71 -0
  247. package/templates/nova/src/components/forms/validate.ts +4 -0
  248. package/templates/nova/src/components/listing/ProductListing.tsx +200 -0
  249. package/templates/nova/src/components/navigation/BrandLogo.tsx +35 -0
  250. package/templates/nova/src/components/navigation/Breadcrumb.css +16 -0
  251. package/templates/nova/src/components/navigation/Breadcrumb.tsx +59 -0
  252. package/templates/nova/src/components/navigation/LineAddFriend.css +5 -0
  253. package/templates/nova/src/components/navigation/LineAddFriend.tsx +42 -0
  254. package/templates/nova/src/components/navigation/LocaleSelector.css +33 -0
  255. package/templates/nova/src/components/navigation/LocaleSelector.tsx +64 -0
  256. package/templates/nova/src/components/navigation/Menu.tsx +26 -0
  257. package/templates/nova/src/components/navigation/Pagination.css +24 -0
  258. package/templates/nova/src/components/navigation/Pagination.tsx +74 -0
  259. package/templates/nova/src/components/navigation/RecoveryLinks.css +8 -0
  260. package/templates/nova/src/components/navigation/RecoveryLinks.tsx +23 -0
  261. package/templates/nova/src/components/navigation/SocialLinks.css +5 -0
  262. package/templates/nova/src/components/navigation/SocialLinks.tsx +51 -0
  263. package/templates/nova/src/components/navigation/blocks/SocialLinks.tsx +23 -0
  264. package/templates/nova/src/components/overlays/AccountMenu.css +11 -0
  265. package/templates/nova/src/components/overlays/AccountMenu.tsx +91 -0
  266. package/templates/nova/src/components/overlays/CartDrawer.css +17 -0
  267. package/templates/nova/src/components/overlays/CartDrawer.tsx +118 -0
  268. package/templates/nova/src/components/overlays/ConfirmationDialog.tsx +49 -0
  269. package/templates/nova/src/components/overlays/CookieConsent.css +56 -0
  270. package/templates/nova/src/components/overlays/CookieConsent.tsx +170 -0
  271. package/templates/nova/src/components/overlays/Drawer.css +38 -0
  272. package/templates/nova/src/components/overlays/Drawer.tsx +66 -0
  273. package/templates/nova/src/components/overlays/FriendOfferPanel.tsx +79 -0
  274. package/templates/nova/src/components/overlays/MarketSelector.css +111 -0
  275. package/templates/nova/src/components/overlays/MarketSelector.tsx +450 -0
  276. package/templates/nova/src/components/overlays/Menu.css +9 -0
  277. package/templates/nova/src/components/overlays/Menu.tsx +124 -0
  278. package/templates/nova/src/components/overlays/MobileNavDrawer.css +28 -0
  279. package/templates/nova/src/components/overlays/MobileNavDrawer.tsx +166 -0
  280. package/templates/nova/src/components/overlays/Modal.css +52 -0
  281. package/templates/nova/src/components/overlays/Modal.tsx +88 -0
  282. package/templates/nova/src/components/overlays/Popover.css +4 -0
  283. package/templates/nova/src/components/overlays/Popover.tsx +115 -0
  284. package/templates/nova/src/components/overlays/ProductLightbox.css +20 -0
  285. package/templates/nova/src/components/overlays/ProductLightbox.tsx +70 -0
  286. package/templates/nova/src/components/overlays/PromoPopup.css +32 -0
  287. package/templates/nova/src/components/overlays/PromoPopup.tsx +198 -0
  288. package/templates/nova/src/components/overlays/QuickView.css +31 -0
  289. package/templates/nova/src/components/overlays/QuickView.tsx +127 -0
  290. package/templates/nova/src/components/overlays/ReferralPanel.css +16 -0
  291. package/templates/nova/src/components/overlays/ReferralPanel.tsx +138 -0
  292. package/templates/nova/src/components/overlays/RewardsLauncher.css +22 -0
  293. package/templates/nova/src/components/overlays/RewardsLauncher.tsx +270 -0
  294. package/templates/nova/src/components/overlays/SearchModal.css +48 -0
  295. package/templates/nova/src/components/overlays/SearchModal.tsx +302 -0
  296. package/templates/nova/src/components/overlays/Sheet.css +16 -0
  297. package/templates/nova/src/components/overlays/Sheet.tsx +48 -0
  298. package/templates/nova/src/components/overlays/SizeGuide.css +8 -0
  299. package/templates/nova/src/components/overlays/SizeGuide.tsx +74 -0
  300. package/templates/nova/src/components/overlays/Toast.css +20 -0
  301. package/templates/nova/src/components/overlays/Toast.tsx +164 -0
  302. package/templates/nova/src/components/overlays/Tooltip.css +6 -0
  303. package/templates/nova/src/components/overlays/Tooltip.tsx +59 -0
  304. package/templates/nova/src/components/overlays/use-dialog.ts +74 -0
  305. package/templates/nova/src/components/section/shared.css +42 -0
  306. package/templates/nova/src/components/section/shared.ts +120 -0
  307. package/templates/nova/src/components/section/shell.tsx +29 -0
  308. package/templates/nova/src/components/states/Alert.css +10 -0
  309. package/templates/nova/src/components/states/Alert.tsx +50 -0
  310. package/templates/nova/src/components/states/EditorFrame.css +31 -0
  311. package/templates/nova/src/components/states/EditorFrame.tsx +43 -0
  312. package/templates/nova/src/components/states/MediaState.css +1 -0
  313. package/templates/nova/src/components/states/MediaState.tsx +49 -0
  314. package/templates/nova/src/components/states/OfflineNotice.css +4 -0
  315. package/templates/nova/src/components/states/OfflineNotice.tsx +24 -0
  316. package/templates/nova/src/components/states/SkeletonLayout.css +22 -0
  317. package/templates/nova/src/components/states/SkeletonLayout.tsx +104 -0
  318. package/templates/nova/src/components/states/StateBlock.css +22 -0
  319. package/templates/nova/src/components/states/StateBlock.tsx +39 -0
  320. package/templates/nova/src/components/states/media/MediaError.css +6 -0
  321. package/templates/nova/src/components/states/media/MediaError.tsx +49 -0
  322. package/templates/nova/src/components/states/media/MediaPlaceholder.css +24 -0
  323. package/templates/nova/src/components/states/media/MediaPlaceholder.tsx +101 -0
  324. package/templates/nova/src/components/states/media/placeholder-art.ts +142 -0
  325. package/templates/nova/src/layouts/Main.tsx +80 -0
  326. package/templates/nova/src/sections/Account/Account.tsx +109 -0
  327. package/templates/nova/src/sections/Account/index.ts +22 -0
  328. package/templates/nova/src/sections/Account/presets.ts +6 -0
  329. package/templates/nova/src/sections/Account/schema.ts +29 -0
  330. package/templates/nova/src/sections/AnnouncementBar/AnnouncementBar.css +37 -0
  331. package/templates/nova/src/sections/AnnouncementBar/AnnouncementBar.tsx +151 -0
  332. package/templates/nova/src/sections/AnnouncementBar/index.ts +20 -0
  333. package/templates/nova/src/sections/AnnouncementBar/presets.ts +10 -0
  334. package/templates/nova/src/sections/AnnouncementBar/schema.ts +26 -0
  335. package/templates/nova/src/sections/ArticleBody/ArticleBody.tsx +129 -0
  336. package/templates/nova/src/sections/ArticleBody/index.ts +21 -0
  337. package/templates/nova/src/sections/ArticleBody/presets.ts +10 -0
  338. package/templates/nova/src/sections/ArticleBody/schema.ts +40 -0
  339. package/templates/nova/src/sections/BeforeAfter/BeforeAfter.tsx +47 -0
  340. package/templates/nova/src/sections/BeforeAfter/blocks/Comparison.tsx +20 -0
  341. package/templates/nova/src/sections/BeforeAfter/index.ts +28 -0
  342. package/templates/nova/src/sections/BeforeAfter/presets.ts +10 -0
  343. package/templates/nova/src/sections/BeforeAfter/schema.ts +34 -0
  344. package/templates/nova/src/sections/BlogPosts/BlogPosts.tsx +106 -0
  345. package/templates/nova/src/sections/BlogPosts/index.ts +30 -0
  346. package/templates/nova/src/sections/BlogPosts/presets.ts +10 -0
  347. package/templates/nova/src/sections/BlogPosts/schema.ts +42 -0
  348. package/templates/nova/src/sections/Breadcrumbs/Breadcrumbs.css +10 -0
  349. package/templates/nova/src/sections/Breadcrumbs/Breadcrumbs.tsx +41 -0
  350. package/templates/nova/src/sections/Breadcrumbs/index.ts +20 -0
  351. package/templates/nova/src/sections/Breadcrumbs/presets.ts +6 -0
  352. package/templates/nova/src/sections/Breadcrumbs/schema.ts +24 -0
  353. package/templates/nova/src/sections/CartItems/CartItems.tsx +170 -0
  354. package/templates/nova/src/sections/CartItems/blocks/CartBadge.tsx +14 -0
  355. package/templates/nova/src/sections/CartItems/index.ts +37 -0
  356. package/templates/nova/src/sections/CartItems/presets.ts +8 -0
  357. package/templates/nova/src/sections/CartItems/schema.ts +34 -0
  358. package/templates/nova/src/sections/CollectionBanner/CollectionBanner.tsx +120 -0
  359. package/templates/nova/src/sections/CollectionBanner/index.ts +25 -0
  360. package/templates/nova/src/sections/CollectionBanner/presets.ts +10 -0
  361. package/templates/nova/src/sections/CollectionBanner/schema.ts +37 -0
  362. package/templates/nova/src/sections/CollectionLinks/CollectionLinks.tsx +47 -0
  363. package/templates/nova/src/sections/CollectionLinks/index.ts +20 -0
  364. package/templates/nova/src/sections/CollectionLinks/presets.ts +8 -0
  365. package/templates/nova/src/sections/CollectionLinks/schema.ts +33 -0
  366. package/templates/nova/src/sections/CollectionList/CollectionList.tsx +89 -0
  367. package/templates/nova/src/sections/CollectionList/blocks/CollectionItem.tsx +25 -0
  368. package/templates/nova/src/sections/CollectionList/index.ts +33 -0
  369. package/templates/nova/src/sections/CollectionList/presets.ts +10 -0
  370. package/templates/nova/src/sections/CollectionList/schema.ts +41 -0
  371. package/templates/nova/src/sections/ComparisonTable/ComparisonTable.tsx +52 -0
  372. package/templates/nova/src/sections/ComparisonTable/blocks/CompareItem.tsx +26 -0
  373. package/templates/nova/src/sections/ComparisonTable/index.ts +31 -0
  374. package/templates/nova/src/sections/ComparisonTable/presets.ts +8 -0
  375. package/templates/nova/src/sections/ComparisonTable/schema.ts +31 -0
  376. package/templates/nova/src/sections/ContactForm/ContactForm.tsx +83 -0
  377. package/templates/nova/src/sections/ContactForm/blocks/ContactDetail.tsx +13 -0
  378. package/templates/nova/src/sections/ContactForm/index.ts +29 -0
  379. package/templates/nova/src/sections/ContactForm/presets.ts +8 -0
  380. package/templates/nova/src/sections/ContactForm/schema.ts +34 -0
  381. package/templates/nova/src/sections/Divider/Divider.css +17 -0
  382. package/templates/nova/src/sections/Divider/Divider.tsx +21 -0
  383. package/templates/nova/src/sections/Divider/index.ts +20 -0
  384. package/templates/nova/src/sections/Divider/presets.ts +10 -0
  385. package/templates/nova/src/sections/Divider/schema.ts +24 -0
  386. package/templates/nova/src/sections/Faq/Faq.tsx +51 -0
  387. package/templates/nova/src/sections/Faq/blocks/FaqItem.tsx +13 -0
  388. package/templates/nova/src/sections/Faq/index.ts +28 -0
  389. package/templates/nova/src/sections/Faq/presets.ts +8 -0
  390. package/templates/nova/src/sections/Faq/schema.ts +32 -0
  391. package/templates/nova/src/sections/FeatureGridBlocks/FeatureGridBlocks.tsx +90 -0
  392. package/templates/nova/src/sections/FeatureGridBlocks/index.ts +35 -0
  393. package/templates/nova/src/sections/FeatureGridBlocks/presets.ts +8 -0
  394. package/templates/nova/src/sections/FeatureGridBlocks/schema.ts +35 -0
  395. package/templates/nova/src/sections/FeatureHighlights/FeatureHighlights.tsx +56 -0
  396. package/templates/nova/src/sections/FeatureHighlights/index.ts +20 -0
  397. package/templates/nova/src/sections/FeatureHighlights/presets.ts +6 -0
  398. package/templates/nova/src/sections/FeatureHighlights/schema.ts +27 -0
  399. package/templates/nova/src/sections/FeaturedCollection/FeaturedCollection.tsx +67 -0
  400. package/templates/nova/src/sections/FeaturedCollection/index.ts +21 -0
  401. package/templates/nova/src/sections/FeaturedCollection/presets.ts +10 -0
  402. package/templates/nova/src/sections/FeaturedCollection/schema.ts +39 -0
  403. package/templates/nova/src/sections/FeaturedProduct/FeaturedProduct.tsx +107 -0
  404. package/templates/nova/src/sections/FeaturedProduct/index.ts +23 -0
  405. package/templates/nova/src/sections/FeaturedProduct/presets.ts +8 -0
  406. package/templates/nova/src/sections/FeaturedProduct/schema.ts +35 -0
  407. package/templates/nova/src/sections/Footer/Footer.css +81 -0
  408. package/templates/nova/src/sections/Footer/Footer.tsx +256 -0
  409. package/templates/nova/src/sections/Footer/blocks/FooterBrand.tsx +40 -0
  410. package/templates/nova/src/sections/Footer/blocks/FooterLine.tsx +11 -0
  411. package/templates/nova/src/sections/Footer/blocks/FooterMenu.tsx +14 -0
  412. package/templates/nova/src/sections/Footer/blocks/FooterText.tsx +8 -0
  413. package/templates/nova/src/sections/Footer/blocks/footer.css +7 -0
  414. package/templates/nova/src/sections/Footer/index.ts +38 -0
  415. package/templates/nova/src/sections/Footer/presets.ts +10 -0
  416. package/templates/nova/src/sections/Footer/schema.ts +27 -0
  417. package/templates/nova/src/sections/Group/Group.css +59 -0
  418. package/templates/nova/src/sections/Group/Group.tsx +167 -0
  419. package/templates/nova/src/sections/Group/blocks/Accordion.tsx +26 -0
  420. package/templates/nova/src/sections/Group/blocks/JumboText.tsx +20 -0
  421. package/templates/nova/src/sections/Group/blocks/Spacer.tsx +16 -0
  422. package/templates/nova/src/sections/Group/index.ts +36 -0
  423. package/templates/nova/src/sections/Group/presets.ts +12 -0
  424. package/templates/nova/src/sections/Group/schema.ts +35 -0
  425. package/templates/nova/src/sections/Header/Header.css +185 -0
  426. package/templates/nova/src/sections/Header/Header.tsx +478 -0
  427. package/templates/nova/src/sections/Header/index.ts +32 -0
  428. package/templates/nova/src/sections/Header/presets.ts +10 -0
  429. package/templates/nova/src/sections/Header/schema.ts +32 -0
  430. package/templates/nova/src/sections/Hero/Hero.tsx +128 -0
  431. package/templates/nova/src/sections/Hero/index.ts +24 -0
  432. package/templates/nova/src/sections/Hero/presets.ts +16 -0
  433. package/templates/nova/src/sections/Hero/schema.ts +56 -0
  434. package/templates/nova/src/sections/ImageGallery/ImageGallery.tsx +86 -0
  435. package/templates/nova/src/sections/ImageGallery/index.ts +28 -0
  436. package/templates/nova/src/sections/ImageGallery/presets.ts +10 -0
  437. package/templates/nova/src/sections/ImageGallery/schema.ts +34 -0
  438. package/templates/nova/src/sections/ImageWithText/ImageWithText.tsx +62 -0
  439. package/templates/nova/src/sections/ImageWithText/index.ts +23 -0
  440. package/templates/nova/src/sections/ImageWithText/presets.ts +12 -0
  441. package/templates/nova/src/sections/ImageWithText/schema.ts +45 -0
  442. package/templates/nova/src/sections/LineAddFriend/LineAddFriend.tsx +25 -0
  443. package/templates/nova/src/sections/LineAddFriend/index.ts +20 -0
  444. package/templates/nova/src/sections/LineAddFriend/presets.ts +3 -0
  445. package/templates/nova/src/sections/LineAddFriend/schema.ts +25 -0
  446. package/templates/nova/src/sections/LogoList/LogoList.tsx +40 -0
  447. package/templates/nova/src/sections/LogoList/blocks/Logo.tsx +18 -0
  448. package/templates/nova/src/sections/LogoList/index.ts +29 -0
  449. package/templates/nova/src/sections/LogoList/presets.ts +8 -0
  450. package/templates/nova/src/sections/LogoList/schema.ts +29 -0
  451. package/templates/nova/src/sections/MainCollection/MainCollection.tsx +51 -0
  452. package/templates/nova/src/sections/MainCollection/index.ts +26 -0
  453. package/templates/nova/src/sections/MainCollection/presets.ts +10 -0
  454. package/templates/nova/src/sections/MainCollection/schema.ts +29 -0
  455. package/templates/nova/src/sections/Marquee/Marquee.tsx +114 -0
  456. package/templates/nova/src/sections/Marquee/index.ts +20 -0
  457. package/templates/nova/src/sections/Marquee/presets.ts +10 -0
  458. package/templates/nova/src/sections/Marquee/schema.ts +23 -0
  459. package/templates/nova/src/sections/Multicolumn/Multicolumn.tsx +56 -0
  460. package/templates/nova/src/sections/Multicolumn/index.ts +25 -0
  461. package/templates/nova/src/sections/Multicolumn/presets.ts +8 -0
  462. package/templates/nova/src/sections/Multicolumn/schema.ts +34 -0
  463. package/templates/nova/src/sections/Newsletter/Newsletter.tsx +64 -0
  464. package/templates/nova/src/sections/Newsletter/index.ts +20 -0
  465. package/templates/nova/src/sections/Newsletter/presets.ts +10 -0
  466. package/templates/nova/src/sections/Newsletter/schema.ts +36 -0
  467. package/templates/nova/src/sections/NotFound/NotFound.tsx +41 -0
  468. package/templates/nova/src/sections/NotFound/index.ts +20 -0
  469. package/templates/nova/src/sections/NotFound/presets.ts +6 -0
  470. package/templates/nova/src/sections/NotFound/schema.ts +30 -0
  471. package/templates/nova/src/sections/PageBody/PageBody.tsx +35 -0
  472. package/templates/nova/src/sections/PageBody/index.ts +21 -0
  473. package/templates/nova/src/sections/PageBody/presets.ts +6 -0
  474. package/templates/nova/src/sections/PageBody/schema.ts +29 -0
  475. package/templates/nova/src/sections/PolicyPage/PolicyPage.tsx +62 -0
  476. package/templates/nova/src/sections/PolicyPage/index.ts +23 -0
  477. package/templates/nova/src/sections/PolicyPage/presets.ts +6 -0
  478. package/templates/nova/src/sections/PolicyPage/schema.ts +27 -0
  479. package/templates/nova/src/sections/ProductDetails/ProductDetails.tsx +269 -0
  480. package/templates/nova/src/sections/ProductDetails/blocks/AddToCartBlock.tsx +15 -0
  481. package/templates/nova/src/sections/ProductDetails/blocks/ProductDescription.tsx +15 -0
  482. package/templates/nova/src/sections/ProductDetails/blocks/ProductInventory.tsx +38 -0
  483. package/templates/nova/src/sections/ProductDetails/blocks/ProductPrice.tsx +19 -0
  484. package/templates/nova/src/sections/ProductDetails/blocks/ProductSku.tsx +15 -0
  485. package/templates/nova/src/sections/ProductDetails/blocks/ProductTitle.tsx +17 -0
  486. package/templates/nova/src/sections/ProductDetails/blocks/Quantity.tsx +14 -0
  487. package/templates/nova/src/sections/ProductDetails/blocks/Tab.tsx +23 -0
  488. package/templates/nova/src/sections/ProductDetails/blocks/VariantPickerBlock.tsx +44 -0
  489. package/templates/nova/src/sections/ProductDetails/blocks/commerce-blocks.css +13 -0
  490. package/templates/nova/src/sections/ProductDetails/index.ts +44 -0
  491. package/templates/nova/src/sections/ProductDetails/presets.ts +10 -0
  492. package/templates/nova/src/sections/ProductDetails/schema.ts +44 -0
  493. package/templates/nova/src/sections/ProductGrid/ProductGrid.tsx +38 -0
  494. package/templates/nova/src/sections/ProductGrid/index.ts +25 -0
  495. package/templates/nova/src/sections/ProductGrid/presets.ts +6 -0
  496. package/templates/nova/src/sections/ProductGrid/schema.ts +37 -0
  497. package/templates/nova/src/sections/ProductRecommendations/ProductRecommendations.tsx +45 -0
  498. package/templates/nova/src/sections/ProductRecommendations/index.ts +20 -0
  499. package/templates/nova/src/sections/ProductRecommendations/presets.ts +8 -0
  500. package/templates/nova/src/sections/ProductRecommendations/schema.ts +28 -0
  501. package/templates/nova/src/sections/ProductReviews/ProductReviews.tsx +136 -0
  502. package/templates/nova/src/sections/ProductReviews/index.ts +22 -0
  503. package/templates/nova/src/sections/ProductReviews/presets.ts +6 -0
  504. package/templates/nova/src/sections/ProductReviews/schema.ts +31 -0
  505. package/templates/nova/src/sections/PromoBanner/PromoBanner.tsx +78 -0
  506. package/templates/nova/src/sections/PromoBanner/index.ts +20 -0
  507. package/templates/nova/src/sections/PromoBanner/presets.ts +10 -0
  508. package/templates/nova/src/sections/PromoBanner/schema.ts +38 -0
  509. package/templates/nova/src/sections/RecentlyViewed/RecentlyViewed.tsx +53 -0
  510. package/templates/nova/src/sections/RecentlyViewed/index.ts +20 -0
  511. package/templates/nova/src/sections/RecentlyViewed/presets.ts +6 -0
  512. package/templates/nova/src/sections/RecentlyViewed/schema.ts +26 -0
  513. package/templates/nova/src/sections/RichText/RichText.tsx +38 -0
  514. package/templates/nova/src/sections/RichText/index.ts +20 -0
  515. package/templates/nova/src/sections/RichText/presets.ts +10 -0
  516. package/templates/nova/src/sections/RichText/schema.ts +37 -0
  517. package/templates/nova/src/sections/SearchResults/SearchResults.tsx +112 -0
  518. package/templates/nova/src/sections/SearchResults/index.ts +26 -0
  519. package/templates/nova/src/sections/SearchResults/presets.ts +8 -0
  520. package/templates/nova/src/sections/SearchResults/schema.ts +30 -0
  521. package/templates/nova/src/sections/ShopTheLook/ShopTheLook.tsx +112 -0
  522. package/templates/nova/src/sections/ShopTheLook/blocks/Hotspot.tsx +22 -0
  523. package/templates/nova/src/sections/ShopTheLook/index.ts +33 -0
  524. package/templates/nova/src/sections/ShopTheLook/presets.ts +8 -0
  525. package/templates/nova/src/sections/ShopTheLook/schema.ts +31 -0
  526. package/templates/nova/src/sections/Slideshow/Slideshow.tsx +199 -0
  527. package/templates/nova/src/sections/Slideshow/blocks/Slide.tsx +93 -0
  528. package/templates/nova/src/sections/Slideshow/index.ts +30 -0
  529. package/templates/nova/src/sections/Slideshow/presets.ts +10 -0
  530. package/templates/nova/src/sections/Slideshow/schema.ts +36 -0
  531. package/templates/nova/src/sections/StoreLocator/StoreLocator.tsx +66 -0
  532. package/templates/nova/src/sections/StoreLocator/index.ts +20 -0
  533. package/templates/nova/src/sections/StoreLocator/presets.ts +6 -0
  534. package/templates/nova/src/sections/StoreLocator/schema.ts +34 -0
  535. package/templates/nova/src/sections/TeamFounders/TeamFounders.tsx +74 -0
  536. package/templates/nova/src/sections/TeamFounders/blocks/Person.tsx +28 -0
  537. package/templates/nova/src/sections/TeamFounders/index.ts +28 -0
  538. package/templates/nova/src/sections/TeamFounders/presets.ts +10 -0
  539. package/templates/nova/src/sections/TeamFounders/schema.ts +36 -0
  540. package/templates/nova/src/sections/Testimonials/Testimonials.tsx +83 -0
  541. package/templates/nova/src/sections/Testimonials/blocks/Testimonial.tsx +30 -0
  542. package/templates/nova/src/sections/Testimonials/index.ts +29 -0
  543. package/templates/nova/src/sections/Testimonials/presets.ts +10 -0
  544. package/templates/nova/src/sections/Testimonials/schema.ts +35 -0
  545. package/templates/nova/src/sections/Timeline/Timeline.tsx +43 -0
  546. package/templates/nova/src/sections/Timeline/blocks/Step.tsx +19 -0
  547. package/templates/nova/src/sections/Timeline/index.ts +28 -0
  548. package/templates/nova/src/sections/Timeline/presets.ts +8 -0
  549. package/templates/nova/src/sections/Timeline/schema.ts +31 -0
  550. package/templates/nova/src/sections/TrustBadges/TrustBadges.tsx +42 -0
  551. package/templates/nova/src/sections/TrustBadges/index.ts +29 -0
  552. package/templates/nova/src/sections/TrustBadges/presets.ts +8 -0
  553. package/templates/nova/src/sections/TrustBadges/schema.ts +29 -0
  554. package/templates/nova/src/sections/UgcGallery/UgcGallery.tsx +58 -0
  555. package/templates/nova/src/sections/UgcGallery/blocks/UgcImage.tsx +27 -0
  556. package/templates/nova/src/sections/UgcGallery/index.ts +31 -0
  557. package/templates/nova/src/sections/UgcGallery/presets.ts +8 -0
  558. package/templates/nova/src/sections/UgcGallery/schema.ts +35 -0
  559. package/templates/nova/src/sections/VideoHero/VideoHero.tsx +75 -0
  560. package/templates/nova/src/sections/VideoHero/index.ts +22 -0
  561. package/templates/nova/src/sections/VideoHero/presets.ts +8 -0
  562. package/templates/nova/src/sections/VideoHero/schema.ts +43 -0
  563. package/templates/nova/src/sections/data.ts +115 -0
  564. package/templates/nova/src/sections/shipped-copy.ts +25 -0
  565. package/templates/nova/src/sections/starter.ts +49 -0
  566. package/templates/nova/src/styles/Container.css +6 -0
  567. package/templates/nova/src/styles/Container.tsx +13 -0
  568. package/templates/nova/src/styles/Icon.css +2 -0
  569. package/templates/nova/src/styles/Icon.tsx +101 -0
  570. package/templates/nova/src/styles/NovaProvider.tsx +91 -0
  571. package/templates/nova/src/styles/base.css +142 -0
  572. package/templates/nova/src/styles/blocks.css +6 -0
  573. package/templates/nova/src/styles/fonts/OFL.txt +93 -0
  574. package/templates/nova/src/styles/fonts/geist-cyrillic-ext.woff2 +0 -0
  575. package/templates/nova/src/styles/fonts/geist-cyrillic.woff2 +0 -0
  576. package/templates/nova/src/styles/fonts/geist-latin-ext.woff2 +0 -0
  577. package/templates/nova/src/styles/fonts/geist-latin.woff2 +0 -0
  578. package/templates/nova/src/styles/fonts/geist-mono-cyrillic-ext.woff2 +0 -0
  579. package/templates/nova/src/styles/fonts/geist-mono-cyrillic.woff2 +0 -0
  580. package/templates/nova/src/styles/fonts/geist-mono-latin-ext.woff2 +0 -0
  581. package/templates/nova/src/styles/fonts/geist-mono-latin.woff2 +0 -0
  582. package/templates/nova/src/styles/fonts/geist-mono-vietnamese.woff2 +0 -0
  583. package/templates/nova/src/styles/fonts/geist-vietnamese.woff2 +0 -0
  584. package/templates/nova/src/styles/fonts.css +95 -0
  585. package/templates/nova/src/styles/global-settings.css +88 -0
  586. package/templates/nova/src/styles/index.css +6 -0
  587. package/templates/nova/src/styles/motion.ts +53 -0
  588. package/templates/nova/src/styles/scroll.ts +24 -0
  589. package/templates/nova/src/styles/sections/commerce.css +147 -0
  590. package/templates/nova/src/styles/sections/content.css +304 -0
  591. package/templates/nova/src/styles/sections/editorial.css +131 -0
  592. package/templates/nova/src/styles/sections/promotion.css +336 -0
  593. package/templates/nova/src/styles/sections/trust.css +117 -0
  594. package/templates/nova/src/styles/sections/utility.css +190 -0
  595. package/templates/nova/src/styles/strings.ts +492 -0
  596. package/templates/nova/src/styles/text.ts +4 -0
  597. package/templates/nova/src/styles/theme.ts +171 -0
  598. package/templates/nova/src/styles/tokens.css +600 -0
  599. package/templates/nova/src/styles/viewport.ts +23 -0
  600. package/templates/nova/template.json +94 -0
  601. package/templates/nova/templates/404.json +30 -0
  602. package/templates/nova/templates/account.json +15 -0
  603. package/templates/nova/templates/article.json +41 -0
  604. package/templates/nova/templates/article.longform.json +17 -0
  605. package/templates/nova/templates/blog.featured.json +22 -0
  606. package/templates/nova/templates/blog.json +29 -0
  607. package/templates/nova/templates/cart.json +67 -0
  608. package/templates/nova/templates/collection.featured.json +43 -0
  609. package/templates/nova/templates/collection.json +30 -0
  610. package/templates/nova/templates/contact.json +65 -0
  611. package/templates/nova/templates/index.json +167 -0
  612. package/templates/nova/templates/list-collections.json +19 -0
  613. package/templates/nova/templates/page.about.json +134 -0
  614. package/templates/nova/templates/page.contact.json +27 -0
  615. package/templates/nova/templates/page.json +27 -0
  616. package/templates/nova/templates/policy.json +23 -0
  617. package/templates/nova/templates/product.bundle.json +145 -0
  618. package/templates/nova/templates/product.json +119 -0
  619. package/templates/nova/templates/search.json +17 -0
  620. package/templates/nova/tests/abi-ai-contract.test.ts +99 -0
  621. package/templates/nova/tests/abi-forms.test.tsx +269 -0
  622. package/templates/nova/tests/abi-inventory.test.tsx +197 -0
  623. package/templates/nova/tests/abi-legacy-settings.test.tsx +105 -0
  624. package/templates/nova/tests/abi-opt-in.test.tsx +100 -0
  625. package/templates/nova/tests/abi-parity.test.tsx +95 -0
  626. package/templates/nova/tests/abi-r5-store-settings.test.tsx +337 -0
  627. package/templates/nova/tests/abi-render.test.tsx +178 -0
  628. package/templates/nova/tests/abi-shop-links.test.tsx +141 -0
  629. package/templates/nova/tests/abi-storefront-basics.test.tsx +233 -0
  630. package/templates/nova/tests/abi-variant-url.test.tsx +73 -0
  631. package/templates/nova/tests/add-refused-no-success.test.tsx +193 -0
  632. package/templates/nova/tests/architecture.test.ts +63 -0
  633. package/templates/nova/tests/architecture.ts +160 -0
  634. package/templates/nova/tests/audit-guards.test.ts +31 -0
  635. package/templates/nova/tests/bundle-split.test.ts +29 -0
  636. package/templates/nova/tests/cart-limit-message.test.tsx +212 -0
  637. package/templates/nova/tests/cart-link-message.test.tsx +98 -0
  638. package/templates/nova/tests/cases/components/Overview.cases.tsx +8 -0
  639. package/templates/nova/tests/cases/components/base/Accordion.cases.tsx +40 -0
  640. package/templates/nova/tests/cases/components/base/Badge.cases.tsx +30 -0
  641. package/templates/nova/tests/cases/components/base/Button.cases.tsx +73 -0
  642. package/templates/nova/tests/cases/components/base/Checkbox.cases.tsx +27 -0
  643. package/templates/nova/tests/cases/components/base/Chip.cases.tsx +37 -0
  644. package/templates/nova/tests/cases/components/base/Divider.cases.tsx +26 -0
  645. package/templates/nova/tests/cases/components/base/IconButton.cases.tsx +28 -0
  646. package/templates/nova/tests/cases/components/base/Input.cases.tsx +28 -0
  647. package/templates/nova/tests/cases/components/base/Link.cases.tsx +27 -0
  648. package/templates/nova/tests/cases/components/base/Radio.cases.tsx +27 -0
  649. package/templates/nova/tests/cases/components/base/Select.cases.tsx +26 -0
  650. package/templates/nova/tests/cases/components/base/Skeleton.cases.tsx +29 -0
  651. package/templates/nova/tests/cases/components/base/Spinner.cases.tsx +16 -0
  652. package/templates/nova/tests/cases/components/base/Switch.cases.tsx +26 -0
  653. package/templates/nova/tests/cases/components/base/Tabs.cases.tsx +39 -0
  654. package/templates/nova/tests/cases/components/base/Textarea.cases.tsx +17 -0
  655. package/templates/nova/tests/cases/components/commerce/AddToCart.cases.tsx +36 -0
  656. package/templates/nova/tests/cases/components/commerce/CartLine.cases.tsx +20 -0
  657. package/templates/nova/tests/cases/components/commerce/CartSummary.cases.tsx +16 -0
  658. package/templates/nova/tests/cases/components/commerce/CollectionCard.cases.tsx +19 -0
  659. package/templates/nova/tests/cases/components/commerce/ComparisonTable.cases.tsx +19 -0
  660. package/templates/nova/tests/cases/components/commerce/Hotspot.cases.tsx +25 -0
  661. package/templates/nova/tests/cases/components/commerce/LookItem.cases.tsx +19 -0
  662. package/templates/nova/tests/cases/components/commerce/PaymentMark.cases.tsx +16 -0
  663. package/templates/nova/tests/cases/components/commerce/Price.cases.tsx +27 -0
  664. package/templates/nova/tests/cases/components/commerce/ProductCard.cases.tsx +67 -0
  665. package/templates/nova/tests/cases/components/commerce/ProductGallery.cases.tsx +39 -0
  666. package/templates/nova/tests/cases/components/commerce/QuantitySelector.cases.tsx +31 -0
  667. package/templates/nova/tests/cases/components/commerce/Reviews.cases.tsx +35 -0
  668. package/templates/nova/tests/cases/components/commerce/Stars.cases.tsx +16 -0
  669. package/templates/nova/tests/cases/components/commerce/VariantPicker.cases.tsx +38 -0
  670. package/templates/nova/tests/cases/components/commerce/blocks/Badge.cases.tsx +17 -0
  671. package/templates/nova/tests/cases/components/commerce/blocks/PaymentIcons.cases.tsx +17 -0
  672. package/templates/nova/tests/cases/components/commerce/blocks/Swatches.cases.tsx +22 -0
  673. package/templates/nova/tests/cases/components/content/ArticleCard.cases.tsx +17 -0
  674. package/templates/nova/tests/cases/components/content/BeforeAfterSlider.cases.tsx +18 -0
  675. package/templates/nova/tests/cases/components/content/CardRow.cases.tsx +23 -0
  676. package/templates/nova/tests/cases/components/content/CarouselControls.cases.tsx +48 -0
  677. package/templates/nova/tests/cases/components/content/ContentsNav.cases.tsx +17 -0
  678. package/templates/nova/tests/cases/components/content/Countdown.cases.tsx +28 -0
  679. package/templates/nova/tests/cases/components/content/HeroCopy.cases.tsx +42 -0
  680. package/templates/nova/tests/cases/components/content/HeroFrame.cases.tsx +22 -0
  681. package/templates/nova/tests/cases/components/content/HeroMedia.cases.tsx +18 -0
  682. package/templates/nova/tests/cases/components/content/LongForm.cases.tsx +17 -0
  683. package/templates/nova/tests/cases/components/content/MediaFrame.cases.tsx +32 -0
  684. package/templates/nova/tests/cases/components/content/RichText.cases.tsx +19 -0
  685. package/templates/nova/tests/cases/components/content/SectionHead.cases.tsx +19 -0
  686. package/templates/nova/tests/cases/components/content/ShareButtons.cases.tsx +24 -0
  687. package/templates/nova/tests/cases/components/content/StoreCard.cases.tsx +17 -0
  688. package/templates/nova/tests/cases/components/content/TestimonialCard.cases.tsx +19 -0
  689. package/templates/nova/tests/cases/components/content/UgcTile.cases.tsx +18 -0
  690. package/templates/nova/tests/cases/components/content/blocks/Button.cases.tsx +20 -0
  691. package/templates/nova/tests/cases/components/content/blocks/Column.cases.tsx +17 -0
  692. package/templates/nova/tests/cases/components/content/blocks/Heading.cases.tsx +18 -0
  693. package/templates/nova/tests/cases/components/content/blocks/Icon.cases.tsx +17 -0
  694. package/templates/nova/tests/cases/components/content/blocks/Image.cases.tsx +22 -0
  695. package/templates/nova/tests/cases/components/content/blocks/Text.cases.tsx +18 -0
  696. package/templates/nova/tests/cases/components/content/blocks/Video.cases.tsx +18 -0
  697. package/templates/nova/tests/cases/components/forms/ContactForm.cases.tsx +29 -0
  698. package/templates/nova/tests/cases/components/forms/FormField.cases.tsx +61 -0
  699. package/templates/nova/tests/cases/components/forms/NewsletterForm.cases.tsx +17 -0
  700. package/templates/nova/tests/cases/components/forms/SearchField.cases.tsx +25 -0
  701. package/templates/nova/tests/cases/components/forms/blocks/Newsletter.cases.tsx +19 -0
  702. package/templates/nova/tests/cases/components/navigation/BrandLogo.cases.tsx +29 -0
  703. package/templates/nova/tests/cases/components/navigation/Breadcrumb.cases.tsx +19 -0
  704. package/templates/nova/tests/cases/components/navigation/LocaleSelector.cases.tsx +32 -0
  705. package/templates/nova/tests/cases/components/navigation/Menu.cases.tsx +27 -0
  706. package/templates/nova/tests/cases/components/navigation/Pagination.cases.tsx +25 -0
  707. package/templates/nova/tests/cases/components/navigation/RecoveryLinks.cases.tsx +16 -0
  708. package/templates/nova/tests/cases/components/navigation/SocialLinks.cases.tsx +18 -0
  709. package/templates/nova/tests/cases/components/navigation/blocks/SocialLinks.cases.tsx +17 -0
  710. package/templates/nova/tests/cases/components/overlays/AccountMenu.cases.tsx +28 -0
  711. package/templates/nova/tests/cases/components/overlays/CartDrawer.cases.tsx +30 -0
  712. package/templates/nova/tests/cases/components/overlays/ConfirmationDialog.cases.tsx +16 -0
  713. package/templates/nova/tests/cases/components/overlays/CookieConsent.cases.tsx +23 -0
  714. package/templates/nova/tests/cases/components/overlays/CookiePreferences.cases.tsx +16 -0
  715. package/templates/nova/tests/cases/components/overlays/Drawer.cases.tsx +52 -0
  716. package/templates/nova/tests/cases/components/overlays/MarketSelector.cases.tsx +82 -0
  717. package/templates/nova/tests/cases/components/overlays/Menu.cases.tsx +27 -0
  718. package/templates/nova/tests/cases/components/overlays/MobileNavDrawer.cases.tsx +32 -0
  719. package/templates/nova/tests/cases/components/overlays/Modal.cases.tsx +71 -0
  720. package/templates/nova/tests/cases/components/overlays/Popover.cases.tsx +50 -0
  721. package/templates/nova/tests/cases/components/overlays/PromoPopup.cases.tsx +19 -0
  722. package/templates/nova/tests/cases/components/overlays/QuickView.cases.tsx +18 -0
  723. package/templates/nova/tests/cases/components/overlays/SearchModal.cases.tsx +60 -0
  724. package/templates/nova/tests/cases/components/overlays/Sheet.cases.tsx +32 -0
  725. package/templates/nova/tests/cases/components/overlays/SizeGuide.cases.tsx +27 -0
  726. package/templates/nova/tests/cases/components/overlays/Toast.cases.tsx +48 -0
  727. package/templates/nova/tests/cases/components/overlays/Tooltip.cases.tsx +33 -0
  728. package/templates/nova/tests/cases/components/states/Alert.cases.tsx +27 -0
  729. package/templates/nova/tests/cases/components/states/EditorFrame.cases.tsx +36 -0
  730. package/templates/nova/tests/cases/components/states/Empty.cases.tsx +30 -0
  731. package/templates/nova/tests/cases/components/states/Error.cases.tsx +38 -0
  732. package/templates/nova/tests/cases/components/states/Loading.cases.tsx +15 -0
  733. package/templates/nova/tests/cases/components/states/MediaState.cases.tsx +29 -0
  734. package/templates/nova/tests/cases/components/states/SkeletonLayout.cases.tsx +20 -0
  735. package/templates/nova/tests/cases/components/states/Unavailable.cases.tsx +16 -0
  736. package/templates/nova/tests/cases/components/states/media/MediaError.cases.tsx +18 -0
  737. package/templates/nova/tests/cases/components/states/media/MediaPlaceholder.cases.tsx +87 -0
  738. package/templates/nova/tests/cases/sections/Overview.cases.tsx +8 -0
  739. package/templates/nova/tests/cases/sections/commerce/collection-list/CollectionList.cases.tsx +46 -0
  740. package/templates/nova/tests/cases/sections/commerce/collection-list/blocks/CollectionItem.cases.tsx +18 -0
  741. package/templates/nova/tests/cases/sections/commerce/featured-collection/FeaturedCollection.cases.tsx +46 -0
  742. package/templates/nova/tests/cases/sections/commerce/featured-product/FeaturedProduct.cases.tsx +40 -0
  743. package/templates/nova/tests/cases/sections/commerce/main-collection/MainCollection.cases.tsx +81 -0
  744. package/templates/nova/tests/cases/sections/commerce/product-details/ProductDetails.cases.tsx +110 -0
  745. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/AddToCartBlock.cases.tsx +21 -0
  746. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductDescription.cases.tsx +17 -0
  747. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductInventory.cases.tsx +17 -0
  748. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductPrice.cases.tsx +17 -0
  749. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductSku.cases.tsx +17 -0
  750. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/ProductTitle.cases.tsx +17 -0
  751. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/Quantity.cases.tsx +20 -0
  752. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/Tab.cases.tsx +29 -0
  753. package/templates/nova/tests/cases/sections/commerce/product-details/blocks/VariantPickerBlock.cases.tsx +32 -0
  754. package/templates/nova/tests/cases/sections/commerce/product-grid/ProductGrid.cases.tsx +48 -0
  755. package/templates/nova/tests/cases/sections/commerce/product-recommendations/ProductRecommendations.cases.tsx +27 -0
  756. package/templates/nova/tests/cases/sections/commerce/recently-viewed/RecentlyViewed.cases.tsx +36 -0
  757. package/templates/nova/tests/cases/sections/content/article-body/ArticleBody.cases.tsx +32 -0
  758. package/templates/nova/tests/cases/sections/content/before-after/BeforeAfter.cases.tsx +40 -0
  759. package/templates/nova/tests/cases/sections/content/before-after/blocks/Comparison.cases.tsx +17 -0
  760. package/templates/nova/tests/cases/sections/content/blog-posts/BlogPosts.cases.tsx +47 -0
  761. package/templates/nova/tests/cases/sections/content/collection-links/CollectionLinks.cases.tsx +36 -0
  762. package/templates/nova/tests/cases/sections/content/faq/Faq.cases.tsx +38 -0
  763. package/templates/nova/tests/cases/sections/content/faq/blocks/FaqItem.cases.tsx +17 -0
  764. package/templates/nova/tests/cases/sections/content/feature-grid-blocks/FeatureGridBlocks.cases.tsx +31 -0
  765. package/templates/nova/tests/cases/sections/content/feature-highlights/FeatureHighlights.cases.tsx +26 -0
  766. package/templates/nova/tests/cases/sections/content/image-gallery/ImageGallery.cases.tsx +31 -0
  767. package/templates/nova/tests/cases/sections/content/image-with-text/ImageWithText.cases.tsx +35 -0
  768. package/templates/nova/tests/cases/sections/content/multicolumn/Multicolumn.cases.tsx +32 -0
  769. package/templates/nova/tests/cases/sections/content/page-body/PageBody.cases.tsx +31 -0
  770. package/templates/nova/tests/cases/sections/content/rich-text/RichTextSection.cases.tsx +33 -0
  771. package/templates/nova/tests/cases/sections/content/team-founders/TeamFounders.cases.tsx +31 -0
  772. package/templates/nova/tests/cases/sections/content/team-founders/blocks/Person.cases.tsx +18 -0
  773. package/templates/nova/tests/cases/sections/content/timeline/Timeline.cases.tsx +27 -0
  774. package/templates/nova/tests/cases/sections/content/timeline/blocks/Step.cases.tsx +17 -0
  775. package/templates/nova/tests/cases/sections/layout/announcement-bar/AnnouncementBar.cases.tsx +119 -0
  776. package/templates/nova/tests/cases/sections/layout/breadcrumbs/Breadcrumbs.cases.tsx +73 -0
  777. package/templates/nova/tests/cases/sections/layout/divider/Divider.cases.tsx +81 -0
  778. package/templates/nova/tests/cases/sections/layout/footer/Footer.cases.tsx +116 -0
  779. package/templates/nova/tests/cases/sections/layout/footer/blocks/FooterBrand.cases.tsx +27 -0
  780. package/templates/nova/tests/cases/sections/layout/footer/blocks/FooterMenu.cases.tsx +22 -0
  781. package/templates/nova/tests/cases/sections/layout/footer/blocks/FooterText.cases.tsx +20 -0
  782. package/templates/nova/tests/cases/sections/layout/group/Group.cases.tsx +185 -0
  783. package/templates/nova/tests/cases/sections/layout/group/blocks/Accordion.cases.tsx +17 -0
  784. package/templates/nova/tests/cases/sections/layout/group/blocks/JumboText.cases.tsx +17 -0
  785. package/templates/nova/tests/cases/sections/layout/group/blocks/Spacer.cases.tsx +33 -0
  786. package/templates/nova/tests/cases/sections/layout/header/Header.cases.tsx +277 -0
  787. package/templates/nova/tests/cases/sections/marketing/collection-banner/CollectionBanner.cases.tsx +62 -0
  788. package/templates/nova/tests/cases/sections/marketing/hero/Hero.cases.tsx +138 -0
  789. package/templates/nova/tests/cases/sections/marketing/marquee/Marquee.cases.tsx +63 -0
  790. package/templates/nova/tests/cases/sections/marketing/promo-banner/PromoBanner.cases.tsx +87 -0
  791. package/templates/nova/tests/cases/sections/marketing/slideshow/Slideshow.cases.tsx +113 -0
  792. package/templates/nova/tests/cases/sections/marketing/slideshow/blocks/Slide.cases.tsx +26 -0
  793. package/templates/nova/tests/cases/sections/marketing/video-hero/VideoHero.cases.tsx +72 -0
  794. package/templates/nova/tests/cases/sections/trust/comparison-table/ComparisonTableSection.cases.tsx +42 -0
  795. package/templates/nova/tests/cases/sections/trust/comparison-table/blocks/CompareItem.cases.tsx +17 -0
  796. package/templates/nova/tests/cases/sections/trust/logo-list/LogoList.cases.tsx +29 -0
  797. package/templates/nova/tests/cases/sections/trust/logo-list/blocks/Logo.cases.tsx +17 -0
  798. package/templates/nova/tests/cases/sections/trust/product-reviews/ProductReviews.cases.tsx +51 -0
  799. package/templates/nova/tests/cases/sections/trust/shop-the-look/ShopTheLook.cases.tsx +51 -0
  800. package/templates/nova/tests/cases/sections/trust/shop-the-look/blocks/Hotspot.cases.tsx +17 -0
  801. package/templates/nova/tests/cases/sections/trust/testimonials/Testimonials.cases.tsx +61 -0
  802. package/templates/nova/tests/cases/sections/trust/testimonials/blocks/Testimonial.cases.tsx +17 -0
  803. package/templates/nova/tests/cases/sections/trust/trust-badges/TrustBadges.cases.tsx +30 -0
  804. package/templates/nova/tests/cases/sections/trust/ugc-gallery/UgcGallery.cases.tsx +32 -0
  805. package/templates/nova/tests/cases/sections/trust/ugc-gallery/blocks/UgcImage.cases.tsx +17 -0
  806. package/templates/nova/tests/cases/sections/utility/account/Account.cases.tsx +44 -0
  807. package/templates/nova/tests/cases/sections/utility/cart-items/CartItems.cases.tsx +43 -0
  808. package/templates/nova/tests/cases/sections/utility/cart-items/blocks/CartBadge.cases.tsx +16 -0
  809. package/templates/nova/tests/cases/sections/utility/contact-form/ContactFormSection.cases.tsx +52 -0
  810. package/templates/nova/tests/cases/sections/utility/contact-form/blocks/ContactDetail.cases.tsx +17 -0
  811. package/templates/nova/tests/cases/sections/utility/newsletter/NewsletterSection.cases.tsx +109 -0
  812. package/templates/nova/tests/cases/sections/utility/not-found/NotFound.cases.tsx +28 -0
  813. package/templates/nova/tests/cases/sections/utility/policy-page/PolicyPage.cases.tsx +29 -0
  814. package/templates/nova/tests/cases/sections/utility/search-results/SearchResultsSection.cases.tsx +39 -0
  815. package/templates/nova/tests/cases/sections/utility/store-locator/StoreLocator.cases.tsx +40 -0
  816. package/templates/nova/tests/cases/styles/Borders.cases.tsx +10 -0
  817. package/templates/nova/tests/cases/styles/Breakpoints.cases.tsx +10 -0
  818. package/templates/nova/tests/cases/styles/Colors.cases.tsx +10 -0
  819. package/templates/nova/tests/cases/styles/Elevation.cases.tsx +10 -0
  820. package/templates/nova/tests/cases/styles/Icon.cases.tsx +23 -0
  821. package/templates/nova/tests/cases/styles/Layout.cases.tsx +8 -0
  822. package/templates/nova/tests/cases/styles/Motion.cases.tsx +10 -0
  823. package/templates/nova/tests/cases/styles/Overview.cases.tsx +8 -0
  824. package/templates/nova/tests/cases/styles/Radius.cases.tsx +10 -0
  825. package/templates/nova/tests/cases/styles/SharedThemeSettings.cases.tsx +19 -0
  826. package/templates/nova/tests/cases/styles/Spacing.cases.tsx +10 -0
  827. package/templates/nova/tests/cases/styles/Typography.cases.tsx +10 -0
  828. package/templates/nova/tests/cases.test.tsx +38 -0
  829. package/templates/nova/tests/components/PromoPopup.test.ts +21 -0
  830. package/templates/nova/tests/components/RichText.test.tsx +65 -0
  831. package/templates/nova/tests/components/formatDate.test.ts +10 -0
  832. package/templates/nova/tests/contract-locks.test.ts +23 -0
  833. package/templates/nova/tests/contracts.test.ts +153 -0
  834. package/templates/nova/tests/cookie-consent-privacy.test.tsx +128 -0
  835. package/templates/nova/tests/empty-store.test.tsx +81 -0
  836. package/templates/nova/tests/fixtures/commerce.ts +128 -0
  837. package/templates/nova/tests/fixtures/content.ts +58 -0
  838. package/templates/nova/tests/fixtures/index.ts +8 -0
  839. package/templates/nova/tests/fixtures/merch.ts +161 -0
  840. package/templates/nova/tests/fixtures/navigation.ts +125 -0
  841. package/templates/nova/tests/fixtures/promotion.ts +43 -0
  842. package/templates/nova/tests/fixtures/sample-store.json +1674 -0
  843. package/templates/nova/tests/fixtures/shop.ts +88 -0
  844. package/templates/nova/tests/fixtures/stress.ts +21 -0
  845. package/templates/nova/tests/inert.test.tsx +285 -0
  846. package/templates/nova/tests/line-add-friend.test.tsx +108 -0
  847. package/templates/nova/tests/localized-copy.test.ts +330 -0
  848. package/templates/nova/tests/product-template-blocks.test.ts +28 -0
  849. package/templates/nova/tests/rewards-launcher.test.tsx +401 -0
  850. package/templates/nova/tests/schema.test.ts +65 -0
  851. package/templates/nova/tests/schema.typetest.ts +7 -0
  852. package/templates/nova/tests/section-groups.test.ts +189 -0
  853. package/templates/nova/tests/sections/Group.test.tsx +37 -0
  854. package/templates/nova/tests/setup.ts +30 -0
  855. package/templates/nova/tests/starter.test.ts +56 -0
  856. package/templates/nova/tests/storefront-click-path.test.tsx +157 -0
  857. package/templates/nova/tests/styles/button-brand.test.ts +24 -0
  858. package/templates/nova/tests/styles/theme.test.ts +43 -0
  859. package/templates/nova/tests/styles/tokens.test.ts +35 -0
  860. package/templates/nova/tests/support/abi-ctx.ts +136 -0
  861. package/templates/nova/tests/support/block-stories.tsx +13 -0
  862. package/templates/nova/tests/support/case.tsx +102 -0
  863. package/templates/nova/tests/support/contracts.ts +30 -0
  864. package/templates/nova/tests/support/foundation-pages.tsx +260 -0
  865. package/templates/nova/tests/support/ia-pages.tsx +154 -0
  866. package/templates/nova/tests/support/listing-demo.tsx +29 -0
  867. package/templates/nova/tests/support/presets.ts +14 -0
  868. package/templates/nova/tests/support/sections.ts +36 -0
  869. package/templates/nova/tests/support/story-content.ts +321 -0
  870. package/templates/nova/tests/support/story-helpers.tsx +37 -0
  871. package/templates/nova/tests/support/with-nova.tsx +26 -0
  872. package/templates/nova/tests/theme-config.test.ts +82 -0
  873. package/templates/nova/tests/theme-integrity.test.ts +315 -0
  874. package/templates/nova/theme.config.ts +134 -0
  875. package/templates/nova/theme.manifest.json +17495 -0
  876. package/templates/nova/tsconfig.json +19 -0
  877. package/templates/nova/vite.abi-ssr.config.ts +37 -0
  878. package/templates/nova/vite.abi.config.ts +26 -0
  879. package/templates/nova/vitest.config.ts +20 -0
@@ -0,0 +1,44 @@
1
+ {
2
+ "name": "__PACKAGE_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "description": "A Tanqory storefront theme forked from Nova V3 (Theme ABI v2) on @tanqory/theme-kit.",
7
+ "engines": {
8
+ "node": ">=22.6.0"
9
+ },
10
+ "scripts": {
11
+ "dev": "node scripts/dev.mjs",
12
+ "build": "node scripts/abi/build.mjs",
13
+ "check": "node scripts/check.mjs",
14
+ "typecheck": "tsc --noEmit",
15
+ "test": "vitest run",
16
+ "manifest": "node --experimental-strip-types scripts/compiler/manifest/gen-manifest.mjs",
17
+ "build:abi": "node scripts/abi/build.mjs",
18
+ "verify": "npm run build:abi && npm run typecheck && npm run test",
19
+ "host": "node scripts/abi/local-host.mjs"
20
+ },
21
+ "dependencies": {
22
+ "@tanqory/theme-kit": "2.0.0-alpha.3",
23
+ "react": "18.3.1",
24
+ "react-dom": "18.3.1"
25
+ },
26
+ "devDependencies": {
27
+ "@tanqory/cli": "2.0.0-alpha.3",
28
+ "@tanqory/theme-abi": "2.0.0-alpha.14",
29
+ "@tanqory/theme-conformance": "0.1.0-alpha.15",
30
+ "@testing-library/jest-dom": "^6.9.1",
31
+ "@testing-library/react": "^16.3.0",
32
+ "@testing-library/user-event": "^14.6.1",
33
+ "@types/node": "^20.0.0",
34
+ "@types/react": "^18.3.12",
35
+ "@types/react-dom": "^18.3.1",
36
+ "@vitejs/plugin-react": "^4.3.4",
37
+ "axe-core": "^4.13.0",
38
+ "jsdom": "^25.0.1",
39
+ "playwright-core": "^1.63.0",
40
+ "typescript": "^5.6.3",
41
+ "vite": "^5.4.10",
42
+ "vitest": "^2.1.9"
43
+ }
44
+ }
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Build Nova as a Theme ABI v2 theme directory — the layout @tanqory/theme-conformance runs and
3
+ * the platform build-service packages into an artifact (theme-artifact-v1.md §1):
4
+ *
5
+ * dist/abi/manifest.json ThemeManifest v2, JCS + "\n"
6
+ * dist/abi/server/entry.mjs render(request, ctx) + manifest — one file, zero imports
7
+ * dist/abi/client/client-manifest.json neutral asset manifest, sha256 digest + SRI per file
8
+ * dist/abi/client/assets/* content-hashed browser files
9
+ * dist/abi/defaults/{settings.json, regions/*.json, templates/*.json}
10
+ * dist/abi/locales/<lang>.json
11
+ *
12
+ * `artifact.json`, the SBOM and the signed statement are the build-service's and signer's (they
13
+ * bind builder identity and inputs this script cannot attest), so they are not written here.
14
+ *
15
+ * node scripts/abi/build.mjs build + validate; exit 1 on any contract problem
16
+ */
17
+ import { execFileSync } from 'node:child_process'
18
+ import { createRequire } from 'node:module'
19
+ import { createHash } from 'node:crypto'
20
+ import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, rmSync, statSync, writeFileSync } from 'node:fs'
21
+ import { dirname, join, relative } from 'node:path'
22
+ import { fileURLToPath } from 'node:url'
23
+ import { gzipSync } from 'node:zlib'
24
+ import { LIMITS } from '@tanqory/theme-abi/limits'
25
+ import { PLATFORM_ROUTES } from '@tanqory/theme-abi'
26
+ import { buildManifest, STARTER_ASSET_DIR } from './manifest.mjs'
27
+ import { canonicalize } from './jcs.mjs'
28
+
29
+ const theme = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
30
+ const out = join(theme, 'dist', 'abi')
31
+ // Vite as the theme resolves it (its own node_modules, a workspace's hoisted one, or a standalone
32
+ // checkout), run from that package's own bin — never a path that assumes the monorepo layout.
33
+ const vitePkgJson = createRequire(join(theme, 'package.json')).resolve('vite/package.json')
34
+ const vite = join(dirname(vitePkgJson), JSON.parse(readFileSync(vitePkgJson, 'utf8')).bin.vite)
35
+ const fail = (msg, list = []) => {
36
+ console.error(`✗ ${msg}`)
37
+ for (const l of list) console.error(` - ${l}`)
38
+ process.exit(1)
39
+ }
40
+ const writeJcs = (path, value) => {
41
+ mkdirSync(dirname(path), { recursive: true })
42
+ writeFileSync(path, `${canonicalize(value)}\n`)
43
+ }
44
+
45
+ // The kit's wire client (@tanqory/theme-kit, abi/wire.ts) hard-codes the two browser prefixes; they must be the contract's.
46
+ if (PLATFORM_ROUTES.data !== '/_tq/data/' || PLATFORM_ROUTES.actions !== '/_tq/actions/') fail('PLATFORM_ROUTES changed — update @tanqory/theme-kit abi/wire.ts')
47
+
48
+ // 1. the v1 catalogue is generated from the defineSection schemas — the one source of truth
49
+ execFileSync(process.execPath, [join(theme, 'scripts', 'compiler', 'manifest', 'gen-manifest.mjs')], { cwd: theme, stdio: ['ignore', 'ignore', 'inherit'] })
50
+
51
+ // 2. manifest v2 + defaults, every stored value through validateFieldValue
52
+ const { manifest, defaults, problems, starterFiles } = buildManifest(theme)
53
+ if (problems.length) fail(`${problems.length} contract problem(s) in the theme's schemas or starter content`, problems)
54
+ writeJcs(join(theme, 'lib', 'abi', 'generated', 'manifest.json'), manifest)
55
+
56
+ // 3. browser bundle, then the serve entry (which imports the generated manifest)
57
+ rmSync(out, { recursive: true, force: true })
58
+ execFileSync(process.execPath, [vite, 'build', '--config', 'vite.abi.config.ts', '--logLevel', 'warn'], { cwd: theme, stdio: 'inherit' })
59
+ execFileSync(process.execPath, [vite, 'build', '--config', 'vite.abi-ssr.config.ts', '--logLevel', 'warn'], { cwd: theme, stdio: 'inherit' })
60
+
61
+ // 4. server/entry.mjs
62
+ mkdirSync(join(out, 'server'), { recursive: true })
63
+ cpSync(join(theme, 'dist', 'abi-ssr', 'entry.mjs'), join(out, 'server', 'entry.mjs'))
64
+ writeJcs(join(out, 'manifest.json'), manifest)
65
+
66
+ // 5. client manifest: every emitted file, digest + SRI; one isEntry script with its css + imports
67
+ const clientDir = join(out, 'client')
68
+ // Starter images the defaults/presets name as `asset:client/assets/<file>` (ABI Appendix B.2): the
69
+ // files must be in the artifact (the signer refuses an `asset:` with no matching file).
70
+ for (const f of starterFiles) cpSync(join(theme, STARTER_ASSET_DIR, f), join(clientDir, 'assets', f))
71
+ const viteManifestPath = join(clientDir, '.vite', 'manifest.json')
72
+ const viteManifest = JSON.parse(readFileSync(viteManifestPath, 'utf8'))
73
+ rmSync(join(clientDir, '.vite'), { recursive: true, force: true })
74
+ const files = []
75
+ const walk = (dir) => {
76
+ for (const name of readdirSync(dir)) {
77
+ const p = join(dir, name)
78
+ if (statSync(p).isDirectory()) walk(p)
79
+ else files.push(relative(clientDir, p).split('\\').join('/'))
80
+ }
81
+ }
82
+ walk(clientDir)
83
+ const kindOf = (f) => (/\.m?js$/.test(f) ? 'script' : /\.css$/.test(f) ? 'style' : /\.(woff2?|ttf|otf)$/.test(f) ? 'font' : /\.(png|jpe?g|gif|svg|webp|avif|ico)$/.test(f) ? 'image' : 'other')
84
+ const entryKey = Object.keys(viteManifest).find((k) => viteManifest[k].isEntry)
85
+ if (!entryKey) fail('vite produced no entry chunk')
86
+ const importsOf = (key, seen = new Set()) => {
87
+ for (const imp of viteManifest[key]?.imports ?? []) {
88
+ if (seen.has(imp)) continue
89
+ seen.add(imp)
90
+ importsOf(imp, seen)
91
+ }
92
+ return seen
93
+ }
94
+ const entryImports = [...importsOf(entryKey)].map((k) => viteManifest[k].file)
95
+ const entryCss = [...new Set([entryKey, ...importsOf(entryKey)].flatMap((k) => viteManifest[k].css ?? []))]
96
+ const clientManifest = {}
97
+ for (const f of files.sort()) {
98
+ const bytes = readFileSync(join(clientDir, f))
99
+ const sha = createHash('sha256').update(bytes).digest()
100
+ const isEntry = f === viteManifest[entryKey].file
101
+ clientManifest[isEntry ? 'main' : f] = {
102
+ file: f,
103
+ kind: kindOf(f),
104
+ ...(isEntry ? { isEntry: true, css: entryCss, imports: entryImports } : {}),
105
+ size: bytes.length,
106
+ digest: `sha256:${sha.toString('hex')}`,
107
+ integrity: `sha256-${sha.toString('base64')}`,
108
+ }
109
+ }
110
+ writeJcs(join(clientDir, 'client-manifest.json'), clientManifest)
111
+
112
+ // 6. defaults/ + locales/
113
+ writeJcs(join(out, 'defaults', 'settings.json'), defaults.settings)
114
+ for (const [name, doc] of Object.entries(defaults.regions)) writeJcs(join(out, 'defaults', 'regions', `${name}.json`), doc)
115
+ for (const [name, doc] of Object.entries(defaults.templates)) writeJcs(join(out, 'defaults', 'templates', `${name}.json`), doc)
116
+ for (const f of readdirSync(join(theme, 'locales')).filter((n) => n.endsWith('.json'))) {
117
+ const map = JSON.parse(readFileSync(join(theme, 'locales', f), 'utf8'))
118
+ const nonString = Object.entries(map).filter(([, v]) => typeof v !== 'string').map(([k]) => k)
119
+ if (nonString.length) fail(`locales/${f} is not a flat string map`, nonString)
120
+ writeJcs(join(out, 'locales', f), map)
121
+ }
122
+
123
+ // 7. sizes against the limits table (§8): budget warns, hard fails
124
+ const entry = readFileSync(join(out, 'server', 'entry.mjs'))
125
+ const gz = gzipSync(entry, { level: 9 }).length
126
+ const manifestBytes = readFileSync(join(out, 'manifest.json')).length
127
+ const initial = [viteManifest[entryKey].file, ...entryImports, ...entryCss].reduce((n, f) => n + gzipSync(readFileSync(join(clientDir, f)), { level: 9 }).length, 0)
128
+ const totalRaw = files.reduce((n, f) => n + statSync(join(clientDir, f)).size, 0)
129
+ const rows = [
130
+ ['ssrEntryRawBytes', entry.length],
131
+ ['ssrEntryGzipBytes', gz],
132
+ ['manifestBytes', manifestBytes],
133
+ ['clientInitialGzipBytes', initial],
134
+ ['clientTotalRawBytes', totalRaw],
135
+ ]
136
+ let over = false
137
+ for (const [k, v] of rows) {
138
+ const { budget, hard } = LIMITS[k]
139
+ const verdict = v > hard ? 'OVER HARD' : budget !== null && v > budget ? 'over budget' : 'ok'
140
+ if (v > hard) over = true
141
+ console.log(`${k.padEnd(24)} ${String(v).padStart(10)} budget ${String(budget).padStart(10)} hard ${String(hard).padStart(10)} ${verdict}`)
142
+ }
143
+ if (over) fail('a hard size limit is exceeded')
144
+ if (!existsSync(join(out, 'server', 'entry.mjs'))) fail('no server entry')
145
+ console.log(`✓ ${relative(process.cwd(), out) || out}`)
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Will a store's existing content carry over to Nova v3 unchanged?
3
+ *
4
+ * node scripts/abi/check-content.mjs <content.json | defaults-dir> [--manifest dist/abi/manifest.json]
5
+ *
6
+ * Input: a ContentRevision ({ settings: { values }, regions, templates }) — e.g. a store's live revision —
7
+ * or an ABI `defaults/` directory (another theme's signed starter content, read as if a store had saved
8
+ * all of it). Every setting key and value is checked against Nova v3's ABI manifest with the rules
9
+ * storefront-control's publish validator applies (content-document.ts checkFieldValue: declared key,
10
+ * value type, option membership, colour / URL / media rules, '' per type). Content carries over 1:1 —
11
+ * no key or value is rewritten — so "nothing the shop set is lost" means: zero issues here.
12
+ *
13
+ * Prints a summary and exits 1 when any value would be refused.
14
+ */
15
+ import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs'
16
+ import { join, dirname } from 'node:path'
17
+ import { fileURLToPath } from 'node:url'
18
+
19
+ const theme = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
20
+ const arg = (n, d) => { const i = process.argv.indexOf(n); return i > 0 ? process.argv[i + 1] : d }
21
+ const input = process.argv[2]
22
+ if (!input) { console.error('usage: check-content.mjs <content.json | defaults-dir>'); process.exit(2) }
23
+ const manifest = JSON.parse(readFileSync(arg('--manifest', join(theme, 'dist', 'abi', 'manifest.json')), 'utf8'))
24
+ const readJson = (p) => JSON.parse(readFileSync(p, 'utf8'))
25
+
26
+ function contentOf(path) {
27
+ if (!statSync(path).isDirectory()) return readJson(path)
28
+ const out = { settings: { values: readJson(join(path, 'settings.json')).values ?? {} }, regions: {}, templates: {} }
29
+ for (const f of readdirSync(join(path, 'regions'))) out.regions[f.replace(/\.json$/, '')] = { sections: readJson(join(path, 'regions', f)).sections }
30
+ for (const f of readdirSync(join(path, 'templates'))) out.templates[f.replace(/\.json$/, '')] = { regions: readJson(join(path, 'templates', f)).regions }
31
+ return out
32
+ }
33
+
34
+ const COLOR = /^#(?:[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$/
35
+ const urlOk = (raw, kind) => {
36
+ const v = raw.trim()
37
+ const scheme = /^([A-Za-z][A-Za-z0-9+.-]*):/.exec(v)?.[1]?.toLowerCase()
38
+ if (kind === 'media') return scheme === 'https' || v.startsWith('asset:client/assets/')
39
+ if (scheme) return ['https', 'http', 'mailto', 'tel'].includes(scheme)
40
+ return v === '/' || (v.length >= 2 && v[0] === '/' && v[1] !== '/' && v[1] !== '\\')
41
+ }
42
+ /** storefront-control content-document.ts checkFieldValue, as a predicate (null = cleared = ok). */
43
+ function valueIssue(spec, v) {
44
+ if (v === null) return null
45
+ if (spec.dynamic === true && v && typeof v === 'object') return null
46
+ const s = typeof v === 'string'
47
+ switch (spec.type) {
48
+ case 'text': case 'textarea': case 'richtext': case 'html': case 'collection': case 'product': case 'page': case 'blog': case 'article': case 'menu':
49
+ return s ? null : 'must be text'
50
+ case 'number': case 'range':
51
+ if (typeof v !== 'number' || !Number.isFinite(v)) return 'must be a number'
52
+ if (typeof spec.min === 'number' && v < spec.min) return `below ${spec.min}`
53
+ if (typeof spec.max === 'number' && v > spec.max) return `above ${spec.max}`
54
+ return null
55
+ case 'boolean': return typeof v === 'boolean' ? null : 'must be true/false'
56
+ case 'color': return s && COLOR.test(v) ? null : `"${v}" is not a colour`
57
+ case 'select': case 'radio': return (spec.options ?? []).some((o) => o.value === v) ? null : `"${v}" is not an option`
58
+ case 'text_alignment': return ['left', 'center', 'right', 'start', 'end'].includes(v) ? null : `"${v}" is not an alignment`
59
+ case 'url': return s && urlOk(v, 'url') ? null : `"${v}" is not a link`
60
+ case 'image': case 'video': return s && urlOk(v, 'media') ? null : `"${v}" is not https media`
61
+ default: return null
62
+ }
63
+ }
64
+
65
+ const settingSpecs = new Map(manifest.settingsSchema.flatMap((g) => g.items.map((i) => [i.key, i])))
66
+ const issues = []
67
+ const stats = { settings: 0, nodes: 0, values: 0 }
68
+ const content = contentOf(input)
69
+ for (const [k, v] of Object.entries(content.settings?.values ?? {})) {
70
+ stats.settings++
71
+ const spec = settingSpecs.get(k)
72
+ if (!spec) issues.push(`settings.${k}: not a theme setting of Nova v3`)
73
+ else { const why = valueIssue(spec, v); if (why) issues.push(`settings.${k}: ${why}`) }
74
+ }
75
+ function nodes(list, at, region) {
76
+ for (const [i, n] of (list ?? []).entries()) {
77
+ stats.nodes++
78
+ const c = manifest.components[n.type]
79
+ const p = `${at}[${i}] ${n.type}#${n.id}`
80
+ if (!c) { issues.push(`${p}: no component "${n.type}" in Nova v3`); continue }
81
+ if (region && c.regions && !c.regions.includes(region) && !c.parents) issues.push(`${p}: not accepted in region ${region}`)
82
+ for (const [k, v] of Object.entries(n.settings ?? {})) {
83
+ stats.values++
84
+ const spec = c.attributes?.[k]
85
+ if (!spec) issues.push(`${p}.${k}: not a setting of "${n.type}" in Nova v3`)
86
+ else { const why = valueIssue(spec, v); if (why) issues.push(`${p}.${k}: ${why}`) }
87
+ }
88
+ nodes(n.blocks, `${p}.blocks`, null)
89
+ }
90
+ }
91
+ for (const [r, doc] of Object.entries(content.regions ?? {})) nodes(doc.sections, `regions.${r}`, r)
92
+ for (const [t, doc] of Object.entries(content.templates ?? {})) {
93
+ if (!manifest.templates[t]) { issues.push(`templates.${t}: not a template of Nova v3`); continue }
94
+ for (const [r, rd] of Object.entries(doc.regions ?? {})) nodes(rd.sections, `templates.${t}.${r}`, r)
95
+ }
96
+ console.log(`checked ${stats.settings} global settings, ${stats.nodes} nodes, ${stats.values} node values against Nova v3 (${manifest.name}@${manifest.version})`)
97
+ if (issues.length) { console.log(`✗ ${issues.length} value(s) Nova v3 would refuse:`); for (const i of issues) console.log(` - ${i}`); process.exit(1) }
98
+ console.log('✓ every key and value carries over unchanged')
99
+ void existsSync
@@ -0,0 +1,27 @@
1
+ /**
2
+ * RFC 8785 JSON Canonicalization Scheme for the values a manifest and its defaults can hold
3
+ * (null, boolean, string, finite number, array, plain object). theme-artifact-v1.md §1 requires
4
+ * manifest.json to be JCS + "\n"; the runner compares the `manifest` export the same way (S3).
5
+ *
6
+ * Keys are sorted by UTF-16 code units (JCS §3.2.3), which is what Array.prototype.sort does on
7
+ * strings. Numbers use ECMAScript Number::toString, which is what JCS §3.2.2.3 prescribes.
8
+ */
9
+ export function canonicalize(value) {
10
+ if (value === null) return 'null'
11
+ switch (typeof value) {
12
+ case 'boolean':
13
+ return value ? 'true' : 'false'
14
+ case 'number':
15
+ if (!Number.isFinite(value)) throw new TypeError(`JCS: non-finite number ${value}`)
16
+ return JSON.stringify(value)
17
+ case 'string':
18
+ return JSON.stringify(value)
19
+ case 'object': {
20
+ if (Array.isArray(value)) return `[${value.map(canonicalize).join(',')}]`
21
+ const keys = Object.keys(value).filter((k) => value[k] !== undefined).sort()
22
+ return `{${keys.map((k) => `${JSON.stringify(k)}:${canonicalize(value[k])}`).join(',')}}`
23
+ }
24
+ default:
25
+ throw new TypeError(`JCS: unsupported ${typeof value}`)
26
+ }
27
+ }
@@ -0,0 +1,294 @@
1
+ /**
2
+ * A laptop host for the built ABI theme (dist/abi): renders pages through the real
3
+ * `server/entry.mjs` with a RenderContext built the way the platform builds it, serves the client
4
+ * bundle, answers the browser wire (`/_tq/data/*`, `/_tq/actions/*`) and sends the platform's CSP
5
+ * (`style-src-attr 'none'`, hashed `style-src-elem`). For looking at the theme in a browser and for
6
+ * the parity screenshots — not a conformance runner (scripts/abi/conformance.mjs is).
7
+ *
8
+ * node scripts/abi/local-host.mjs --data <dir> [--origin https://<store host>] [--content <file.json>] [--cart <lines.json>] [--port 4401] [--preview]
9
+ *
10
+ * --data a directory of recorded wire answers (`<op>-<base64url args>.json`, envelope form)
11
+ * --origin a live storefront to fill a missing answer from (its public /_tq/data wire, paced)
12
+ * --content a ContentRevision ({ settings: { values }, regions, templates }); default: the theme's own defaults
13
+ * --preview render with ctx.mode = 'preview' (the editor canvas)
14
+ * --cart a JSON file of ABI cart lines the in-memory cart starts with (`{ id, quantity, merchandise, attributes }`)
15
+ */
16
+ import { createServer } from 'node:http'
17
+ import { createHash } from 'node:crypto'
18
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs'
19
+ import { extname, join, dirname, normalize } from 'node:path'
20
+ import { fileURLToPath, pathToFileURL } from 'node:url'
21
+
22
+ const theme = join(dirname(fileURLToPath(import.meta.url)), '..', '..')
23
+ const abi = join(theme, 'dist', 'abi')
24
+ const arg = (name, dflt = null) => {
25
+ const i = process.argv.indexOf(`--${name}`)
26
+ return i > 0 ? process.argv[i + 1] : dflt
27
+ }
28
+ const DATA = arg('data')
29
+ const ORIGIN = arg('origin')
30
+ const PORT = Number(arg('port', '4401'))
31
+ const PREVIEW = process.argv.includes('--preview')
32
+ if (!DATA) throw new Error('--data <dir> is required')
33
+ mkdirSync(DATA, { recursive: true })
34
+
35
+ const entry = await import(pathToFileURL(join(abi, 'server', 'entry.mjs')).href)
36
+ const manifest = JSON.parse(readFileSync(join(abi, 'manifest.json'), 'utf8'))
37
+ const clientManifest = JSON.parse(readFileSync(join(abi, 'client', 'client-manifest.json'), 'utf8'))
38
+ const readDefaults = (rel) => JSON.parse(readFileSync(join(abi, 'defaults', rel), 'utf8'))
39
+ const content = arg('content') ? JSON.parse(readFileSync(arg('content'), 'utf8')) : {}
40
+
41
+ // ── the wire: recorded answers, else (with --origin) the live store's, paced and recorded ─────────
42
+ const canon = (v) => (v === null || typeof v !== 'object' ? JSON.stringify(v) : Array.isArray(v) ? `[${v.map(canon).join(',')}]` : `{${Object.keys(v).sort().map((k) => `${JSON.stringify(k)}:${canon(v[k])}`).join(',')}}`)
43
+ const b64 = (s) => Buffer.from(s).toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '')
44
+ const fileOf = (op, args) => join(DATA, `${op}${args.length ? '-' + b64(canon(args)) : ''}.json`.replace(/[^A-Za-z0-9._-]/g, '_'))
45
+ let live = null
46
+ async function liveSession() {
47
+ const r = await fetch(ORIGIN + '/')
48
+ live = { cookie: (r.headers.getSetCookie?.() ?? []).map((c) => c.split(';')[0]).join('; '), token: /name="tq-csrf" content="([^"]*)"/.exec(await r.text())?.[1] ?? '', last: 0 }
49
+ }
50
+ async function wire(op, args = []) {
51
+ const file = fileOf(op, args)
52
+ if (existsSync(file)) return JSON.parse(readFileSync(file, 'utf8'))
53
+ if (!ORIGIN) return { ok: true, data: null, dataVersion: 'local' }
54
+ if (!live) await liveSession()
55
+ for (let i = 0; i < 6; i++) {
56
+ const wait = Math.max(0, live.last + 3500 - Date.now())
57
+ if (wait) await new Promise((r) => setTimeout(r, wait))
58
+ live.last = Date.now()
59
+ const qs = args.length ? `?a=${b64(canon(args))}` : ''
60
+ const res = await fetch(`${ORIGIN}/_tq/data/${op}${qs}`, { headers: { cookie: live.cookie, 'x-tq-csrf': live.token, 'sec-fetch-site': 'same-origin', 'sec-fetch-mode': 'cors', origin: ORIGIN, referer: ORIGIN + '/', accept: 'application/json' } })
61
+ if (res.status === 429) { await new Promise((r) => setTimeout(r, Number(res.headers.get('retry-after') ?? 30) * 1000 + 500)); continue }
62
+ const env = await res.json()
63
+ if (!env.ok && env.error?.code === 'csrf_failed') { await liveSession(); continue }
64
+ writeFileSync(file, JSON.stringify(env))
65
+ return env
66
+ }
67
+ return { ok: false, error: { code: 'unavailable', requestId: 'local' } }
68
+ }
69
+ const get = async (op, args = []) => {
70
+ const env = await wire(op, args)
71
+ return env.ok ? env.data : null
72
+ }
73
+
74
+ // ── routing (the platform's built-in kinds, theme-abi-v2 §2.1) ────────────────────────────────
75
+ function route(url) {
76
+ const p = decodeURIComponent(url.pathname).replace(/\/+$/, '') || '/'
77
+ const m = (re) => re.exec(p)
78
+ let r
79
+ if (p === '/') r = { kind: 'home' }
80
+ else if (m(/^\/products\/([^/]+)$/)) r = { kind: 'product', handle: m(/^\/products\/([^/]+)$/)[1] }
81
+ else if (p === '/collections') r = { kind: 'collections' }
82
+ else if (m(/^\/collections\/([^/]+)$/)) r = { kind: 'collection', handle: m(/^\/collections\/([^/]+)$/)[1] }
83
+ else if (m(/^\/pages\/([^/]+)$/)) r = { kind: 'page', handle: m(/^\/pages\/([^/]+)$/)[1] }
84
+ else if (m(/^\/blogs\/([^/]+)\/([^/]+)$/)) { const x = m(/^\/blogs\/([^/]+)\/([^/]+)$/); r = { kind: 'article', blogHandle: x[1], handle: x[2] } }
85
+ else if (m(/^\/blogs\/([^/]+)$/)) r = { kind: 'blog', handle: m(/^\/blogs\/([^/]+)$/)[1] }
86
+ else if (p === '/search') r = { kind: 'search' }
87
+ else if (p === '/cart') r = { kind: 'cart' }
88
+ else if (p === '/account' || p.startsWith('/account/')) r = { kind: 'customer' }
89
+ else if (m(/^\/policies\/([^/]+)$/)) r = { kind: 'policy', handle: m(/^\/policies\/([^/]+)$/)[1] }
90
+ else if (p === '/contact') r = { kind: 'custom:contact' }
91
+ else r = { kind: 'not_found' }
92
+ const params = {}
93
+ if (r.kind === 'search' && url.searchParams.has('q')) params.q = url.searchParams.get('q')
94
+ r.template = manifest.routes[r.kind] ?? '404'
95
+ if (r.kind === 'page' && manifest.templates[`page.${r.handle}`]) r.template = `page.${r.handle}`
96
+ return { ...r, params, canonical: `${url.origin}${p === '/' ? '/' : p}` }
97
+ }
98
+
99
+ // ── content: the store's revision over the theme's signed defaults; asset: → this host's URL ──────
100
+ const ASSET_BASE = (origin) => `${origin}/__assets`
101
+ function resolveAssets(v, origin) {
102
+ if (typeof v === 'string' && v.startsWith('asset:client/')) return `${ASSET_BASE(origin)}/${v.slice('asset:client/'.length)}`
103
+ if (Array.isArray(v)) return v.map((x) => resolveAssets(x, origin))
104
+ if (v && typeof v === 'object') return Object.fromEntries(Object.entries(v).map(([k, x]) => [k, resolveAssets(x, origin)]))
105
+ return v
106
+ }
107
+ function pageDoc(template, origin) {
108
+ const t = manifest.templates[template]
109
+ const regions = []
110
+ for (const id of t.regions) {
111
+ const def = manifest.regions[id]
112
+ let sections
113
+ if (def.scope === 'shared') sections = content.regions?.[id]?.sections ?? readDefaults(`regions/${id}.json`).sections
114
+ else {
115
+ const own = content.templates?.[template]?.regions?.[id]?.sections
116
+ sections = own ?? readDefaults(`templates/${template}.json`).regions?.[id]?.sections
117
+ }
118
+ if (sections) regions.push({ region: id, sections: resolveAssets(sections, origin) })
119
+ }
120
+ return { contentVersion: manifest.contentVersion, template, regions }
121
+ }
122
+ const settingValues = () => ({ ...readDefaults('settings.json').values, ...(content.settings?.values ?? {}) })
123
+
124
+ // ── ctx.data: the snapshot the platform composes, from the wire ─────────────────────────────────
125
+ async function snapshot(r, page) {
126
+ const [shop, menuList] = await Promise.all([get('shop.get'), get('shop.menus')])
127
+ const menus = Object.fromEntries((menuList ?? []).map((m) => [m.handle, m]))
128
+ let resource = { kind: r.kind }
129
+ if (r.kind === 'product') resource = { kind: 'product', product: await get('catalog.product', [r.handle]) }
130
+ else if (r.kind === 'collection') resource = { kind: 'collection', collection: await get('catalog.collection', [r.handle, { productsFirst: 24 }]) }
131
+ else if (r.kind === 'collections') resource = { kind: 'collections', collections: await get('catalog.collections', [{ first: 50 }]) }
132
+ else if (r.kind === 'page') resource = { kind: 'page', page: await get('content.page', [r.handle]) }
133
+ else if (r.kind === 'blog') resource = { kind: 'blog', blog: await get('content.blog', [r.handle]) }
134
+ else if (r.kind === 'article') resource = { kind: 'article', article: await get('content.article', [r.blogHandle, r.handle]), blog: await get('content.blog', [r.blogHandle]) }
135
+ else if (r.kind === 'policy') resource = { kind: 'policy', policy: await get('content.policy', [r.handle]) }
136
+ else if (r.kind === 'search') resource = { kind: 'search', result: await get('catalog.search', [{ query: r.params.q ?? '', first: 48, types: ['product', 'page', 'article'] }]) }
137
+ else if (r.kind === 'cart') resource = { kind: 'cart', cart: cartView() }
138
+ const refs = {}
139
+ const walk = (nodes) => {
140
+ for (const n of nodes ?? []) {
141
+ const attrs = manifest.components[n.type]?.attributes ?? {}
142
+ for (const [k, spec] of Object.entries(attrs)) {
143
+ if (!spec.resolve) continue
144
+ const v = n.settings?.[k] ?? spec.default
145
+ if (typeof v === 'string' && v.trim()) refs[`${n.id}.${k}`] = { type: spec.type, handle: v.trim() }
146
+ }
147
+ walk(n.blocks)
148
+ }
149
+ }
150
+ for (const reg of page.regions) walk(reg.sections)
151
+ for (const [key, { type, handle }] of Object.entries(refs)) {
152
+ refs[key] =
153
+ type === 'collection' ? await get('catalog.collection', [handle, { productsFirst: 24 }])
154
+ : type === 'product' ? await get('catalog.product', [handle])
155
+ : type === 'menu' ? menus[handle] ?? null
156
+ : type === 'page' ? await get('content.page', [handle])
157
+ : type === 'blog' ? await get('content.blog', [handle])
158
+ : null
159
+ }
160
+ return { shop, menus, resource, refs, pixels: [], denied: [] }
161
+ }
162
+
163
+ // ── a cart the actions change (in memory, one shopper) ─────────────────────────────────────────
164
+ let cart = { id: 'cart-local', version: 1, lines: arg('cart') ? JSON.parse(readFileSync(arg('cart'), 'utf8')) : [], note: null, discountCodes: [] }
165
+ const money = (n, c) => ({ amount: n.toFixed(2), currencyCode: c })
166
+ function cartView() {
167
+ const cur = cart.lines[0]?.merchandise.price.currencyCode ?? 'USD'
168
+ const sub = cart.lines.reduce((s, l) => s + Number(l.merchandise.price.amount) * l.quantity, 0)
169
+ return {
170
+ ...cart,
171
+ checkoutUrl: '/checkout',
172
+ totalQuantity: cart.lines.reduce((s, l) => s + l.quantity, 0),
173
+ attributes: {},
174
+ lines: cart.lines.map((l) => ({ ...l, cost: { subtotalAmount: money(Number(l.merchandise.price.amount) * l.quantity, cur), totalAmount: money(Number(l.merchandise.price.amount) * l.quantity, cur) } })),
175
+ cost: { subtotalAmount: money(sub, cur), totalAmount: money(sub, cur), totalTaxAmount: money(0, cur) },
176
+ }
177
+ }
178
+ async function variantById(id) {
179
+ for (const f of (await import('node:fs')).readdirSync(DATA).filter((x) => x.startsWith('catalog.product-'))) {
180
+ const p = JSON.parse(readFileSync(join(DATA, f), 'utf8')).data
181
+ const v = p?.variants?.nodes?.find((x) => x.id === id)
182
+ if (v) return { ...v, product: { id: p.id, handle: p.handle, title: p.title, featuredImage: p.featuredImage } }
183
+ }
184
+ return null
185
+ }
186
+ async function cartAction(name, args) {
187
+ const addLines = async (lines) => {
188
+ for (const l of lines ?? []) {
189
+ const v = await variantById(l.merchandiseId)
190
+ if (!v) continue
191
+ const have = cart.lines.find((x) => x.merchandise.id === v.id)
192
+ if (have) have.quantity += l.quantity ?? 1
193
+ else cart.lines.push({ id: `line-${cart.lines.length + 1}`, quantity: l.quantity ?? 1, merchandise: { id: v.id, title: v.title, price: v.price, image: v.image ?? null, product: v.product, selectedOptions: v.selectedOptions ?? [] }, attributes: {} })
194
+ }
195
+ }
196
+ if (name === 'cart.create') await addLines(args[0]?.lines)
197
+ else if (name === 'cart.addLines') await addLines(args[0])
198
+ else if (name === 'cart.updateLines') { for (const l of args[0] ?? []) { const x = cart.lines.find((y) => y.id === l.id); if (x) x.quantity = l.quantity } cart.lines = cart.lines.filter((l) => l.quantity > 0) }
199
+ else if (name === 'cart.removeLines') { const ids = new Set(args[0] ?? []); cart.lines = cart.lines.filter((l) => !ids.has(l.id)) }
200
+ else if (name === 'cart.updateNote') cart.note = args[0] ?? null
201
+ cart.version++
202
+ return { ok: true, data: { ok: true, cart: cartView() }, dataVersion: 'local' }
203
+ }
204
+
205
+ // ── the platform's response policy ─────────────────────────────────────────────────────────────
206
+ function csp(styleHashes) {
207
+ return [
208
+ "default-src 'self'", "script-src 'self'", `style-src-elem 'self' ${styleHashes.map((h) => `'${h}'`).join(' ')}`, "style-src-attr 'none'",
209
+ "connect-src 'self'", 'img-src https: data: blob: http://localhost:*', 'media-src https: data: blob:', "font-src 'self' https: data:",
210
+ "frame-src 'none'", "object-src 'none'", "base-uri 'self'", "form-action 'self'",
211
+ ].join('; ')
212
+ }
213
+ const TYPES = { '.js': 'text/javascript', '.css': 'text/css', '.woff2': 'font/woff2', '.svg': 'image/svg+xml', '.png': 'image/png', '.json': 'application/json' }
214
+
215
+ createServer(async (req, res) => {
216
+ try {
217
+ const origin = `http://${req.headers.host}`
218
+ const url = new URL(req.url, origin)
219
+ if (url.pathname.startsWith('/__assets/')) {
220
+ const rel = normalize(url.pathname.slice('/__assets/'.length))
221
+ const file = join(abi, 'client', rel)
222
+ if (rel.startsWith('..') || !existsSync(file)) return res.writeHead(404).end()
223
+ res.writeHead(200, { 'content-type': TYPES[extname(file)] ?? 'application/octet-stream', 'cache-control': 'no-cache' })
224
+ return res.end(readFileSync(file))
225
+ }
226
+ if (url.pathname.startsWith('/_tq/data/')) {
227
+ const op = url.pathname.slice('/_tq/data/'.length)
228
+ const a = url.searchParams.get('a')
229
+ const args = a ? JSON.parse(Buffer.from(a.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8')) : []
230
+ const env = op === 'cart.get' ? { ok: true, data: cartView(), dataVersion: 'local' } : op === 'consent.get' ? { ok: true, data: { known: false, analytics: false, marketing: false, functional: true, bannerRequired: false }, dataVersion: 'local' } : await wire(op, args)
231
+ res.writeHead(200, { 'content-type': 'application/json' })
232
+ return res.end(JSON.stringify(env))
233
+ }
234
+ if (url.pathname.startsWith('/_tq/actions/')) {
235
+ let body = ''
236
+ for await (const c of req) body += c
237
+ const name = url.pathname.slice('/_tq/actions/'.length)
238
+ const args = JSON.parse(body || '{}').args ?? []
239
+ const env = name.startsWith('cart.') ? await cartAction(name, args) : { ok: true, data: null, dataVersion: 'local' }
240
+ res.writeHead(200, { 'content-type': 'application/json' })
241
+ return res.end(JSON.stringify(env))
242
+ }
243
+ const r = route(url)
244
+ const page = pageDoc(r.template, origin)
245
+ const logs = []
246
+ const ctx = {
247
+ abi: '2.0',
248
+ mode: PREVIEW ? 'preview' : 'serve',
249
+ request: { method: req.method === 'POST' ? 'POST' : 'GET', url: url.href, headers: {}, hasCart: cart.lines.length > 0, isSignedIn: false, isPreview: PREVIEW },
250
+ route: { kind: r.kind, ...(r.handle ? { handle: r.handle } : {}), ...(r.blogHandle ? { blogHandle: r.blogHandle } : {}), template: r.template, params: r.params, canonical: r.canonical },
251
+ locale: { language: 'en', country: 'US', market: 'us', currency: 'USD', availableLanguages: ['en'], timeZone: 'UTC' },
252
+ consent: { known: false, analytics: false, marketing: false, functional: true },
253
+ content: { revision: 'sha256:local', contentVersion: manifest.contentVersion, page, settings: resolveAssets(settingValues(), origin) },
254
+ assets: { base: ASSET_BASE(origin), manifest: clientManifest },
255
+ signal: new AbortController().signal,
256
+ data: {
257
+ dataVersion: 'local',
258
+ snapshot: () => snapshot(r, page),
259
+ shop: { get: () => get('shop.get'), menus: async (h) => { const all = (await get('shop.menus')) ?? []; return h ? all.filter((m) => h.includes(m.handle)) : all }, policies: async () => [], pixels: async () => [] },
260
+ localization: { strings: async () => ({}) },
261
+ catalog: {
262
+ product: (h) => get('catalog.product', [h]),
263
+ products: () => get('catalog.products', [{}]),
264
+ collection: (h, o) => get('catalog.collection', [h, o ?? { productsFirst: 24 }]),
265
+ collections: (a) => get('catalog.collections', [a ?? { first: 50 }]),
266
+ search: (a) => get('catalog.search', [a]),
267
+ predictive: (q, l) => get('catalog.predictive', [q, l ?? 4]),
268
+ recommendations: async (h, i, l) => (await get('catalog.recommendations', [h, i ?? 'related', l ?? 8])) ?? [],
269
+ },
270
+ content: { page: (h) => get('content.page', [h]), blog: (h) => get('content.blog', [h]), article: (b, h) => get('content.article', [b, h]), policy: (h) => get('content.policy', [h]), template: async () => null, metaobjects: async () => ({ nodes: [], pageInfo: { hasNextPage: false } }) },
271
+ cart: { get: async () => cartView() },
272
+ customer: { current: async () => null, loginUrl: () => '/account/login', logoutUrl: () => '/account/logout', accountUrl: (p) => `/account${p ? '/' + p : ''}`, registerUrl: () => '/account/register', recoverUrl: () => '/account/recover' },
273
+ },
274
+ actions: undefined,
275
+ cache: { hint() {} },
276
+ log: { debug() {}, info: (m, f) => logs.push(['info', m, f]), warn: (m, f) => logs.push(['warn', m, f]), error: (m, f) => logs.push(['error', m, f]) },
277
+ now: () => Date.UTC(2026, 9, 13, 12),
278
+ release: { id: 'local', artifactDigest: 'local' },
279
+ capabilities: new Set(manifest.capabilities.server),
280
+ }
281
+ const out = await entry.render(new Request(url.href), ctx)
282
+ for (const l of logs) if (l[0] !== 'info') console.error(`[${l[0]}] ${url.pathname}: ${l[1]} ${JSON.stringify(l[2] ?? '')}`)
283
+ const hashes = out.headers.getAll ? out.headers.getAll('x-tq-csp-style-hash') : (out.headers.get('x-tq-csp-style-hash') ?? '').split(',').map((s) => s.trim()).filter(Boolean)
284
+ let html = await out.text()
285
+ html = html.replace('<head>', '<head>\n <meta name="tq-csrf" content="local">')
286
+ res.writeHead(out.status, { 'content-type': 'text/html; charset=utf-8', 'content-security-policy': csp(hashes), 'cache-control': 'no-store' })
287
+ res.end(html)
288
+ } catch (e) {
289
+ console.error(e)
290
+ res.writeHead(500, { 'content-type': 'text/plain' }).end(String(e?.stack ?? e))
291
+ }
292
+ }).listen(PORT, () => console.log(`nova-v3 local host on http://localhost:${PORT} (${PREVIEW ? 'preview' : 'serve'})`))
293
+
294
+ export const _sha = (s) => createHash('sha256').update(s).digest('base64')