@mohasinac/appkit 4.59.0 β†’ 4.60.1

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 (532) hide show
  1. package/dist/.build-stamp.json +3 -3
  2. package/dist/_internal/client/features/layout/DashboardLayoutClient.js +62 -5
  3. package/dist/_internal/client/features/maintenance/views/MaintenanceDashboardView.js +2 -2
  4. package/dist/_internal/server/features/brands/actions.js +78 -4
  5. package/dist/_internal/server/features/checkout/actions.d.ts +36 -13
  6. package/dist/_internal/server/features/checkout/actions.js +668 -183
  7. package/dist/_internal/server/features/checkout/supplier-identity.js +18 -1
  8. package/dist/_internal/server/features/gateway-events/index.d.ts +10 -0
  9. package/dist/_internal/server/features/gateway-events/index.js +10 -0
  10. package/dist/_internal/server/features/gateway-events/ingest.d.ts +127 -0
  11. package/dist/_internal/server/features/gateway-events/ingest.js +188 -0
  12. package/dist/_internal/server/features/gateway-events/redact.d.ts +66 -0
  13. package/dist/_internal/server/features/gateway-events/redact.js +151 -0
  14. package/dist/_internal/server/features/grievance/actions.d.ts +63 -0
  15. package/dist/_internal/server/features/grievance/actions.js +173 -0
  16. package/dist/_internal/server/features/grievance/index.d.ts +8 -0
  17. package/dist/_internal/server/features/grievance/index.js +8 -0
  18. package/dist/_internal/server/features/grouped/data.js +28 -3
  19. package/dist/_internal/server/features/lottery/actions.js +99 -4
  20. package/dist/_internal/server/features/order-completion/actions.d.ts +92 -0
  21. package/dist/_internal/server/features/order-completion/actions.js +336 -0
  22. package/dist/_internal/server/features/order-completion/index.d.ts +7 -0
  23. package/dist/_internal/server/features/order-completion/index.js +7 -0
  24. package/dist/_internal/server/features/orders/actions.d.ts +13 -0
  25. package/dist/_internal/server/features/orders/actions.js +56 -2
  26. package/dist/_internal/server/features/orders/adapters.d.ts +20 -2
  27. package/dist/_internal/server/features/orders/adapters.js +44 -0
  28. package/dist/_internal/server/features/orders/cancellation-refund.d.ts +116 -0
  29. package/dist/_internal/server/features/orders/cancellation-refund.js +466 -0
  30. package/dist/_internal/server/features/orders/index.d.ts +2 -1
  31. package/dist/_internal/server/features/orders/index.js +1 -1
  32. package/dist/_internal/server/features/orders/service.d.ts +60 -0
  33. package/dist/_internal/server/features/orders/service.js +74 -0
  34. package/dist/_internal/server/features/payment-cases/disputes.d.ts +76 -0
  35. package/dist/_internal/server/features/payment-cases/disputes.js +241 -0
  36. package/dist/_internal/server/features/payment-cases/index.d.ts +9 -0
  37. package/dist/_internal/server/features/payment-cases/index.js +9 -0
  38. package/dist/_internal/server/features/payouts/actions.d.ts +28 -3
  39. package/dist/_internal/server/features/payouts/actions.js +84 -10
  40. package/dist/_internal/server/features/points/adjust.d.ts +61 -0
  41. package/dist/_internal/server/features/points/adjust.js +121 -0
  42. package/dist/_internal/server/features/points/award.d.ts +65 -0
  43. package/dist/_internal/server/features/points/award.js +145 -0
  44. package/dist/_internal/server/features/price-index/market.d.ts +33 -0
  45. package/dist/_internal/server/features/price-index/market.js +54 -1
  46. package/dist/_internal/server/features/products/data.js +40 -22
  47. package/dist/_internal/server/features/protection-claims/actions.d.ts +72 -0
  48. package/dist/_internal/server/features/protection-claims/actions.js +228 -0
  49. package/dist/_internal/server/features/protection-claims/index.d.ts +8 -0
  50. package/dist/_internal/server/features/protection-claims/index.js +8 -0
  51. package/dist/_internal/server/features/refunds/actions.d.ts +20 -0
  52. package/dist/_internal/server/features/refunds/actions.js +259 -17
  53. package/dist/_internal/server/features/refunds/capacity.d.ts +29 -0
  54. package/dist/_internal/server/features/refunds/capacity.js +37 -0
  55. package/dist/_internal/server/features/reviews/actions.js +3 -2
  56. package/dist/_internal/server/features/search/index-build/sources.js +20 -1
  57. package/dist/_internal/server/features/shipping/reconcile.d.ts +10 -1
  58. package/dist/_internal/server/features/shipping/reconcile.js +34 -3
  59. package/dist/_internal/server/features/shipping/reimbursement.d.ts +117 -0
  60. package/dist/_internal/server/features/shipping/reimbursement.js +223 -0
  61. package/dist/_internal/server/features/stores/tabs.d.ts +19 -4
  62. package/dist/_internal/server/features/stores/tabs.js +24 -4
  63. package/dist/_internal/server/functions/https.d.ts +31 -1
  64. package/dist/_internal/server/functions/https.js +52 -1
  65. package/dist/_internal/server/functions/scheduled.d.ts +72 -1
  66. package/dist/_internal/server/functions/scheduled.js +124 -1
  67. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +62 -16
  68. package/dist/_internal/server/jobs/core/boxRollupReconcile.d.ts +46 -0
  69. package/dist/_internal/server/jobs/core/boxRollupReconcile.js +142 -0
  70. package/dist/_internal/server/jobs/core/dailyDataCleanup.js +149 -1
  71. package/dist/_internal/server/jobs/core/dailyStatusDigest.d.ts +14 -0
  72. package/dist/_internal/server/jobs/core/dailyStatusDigest.js +79 -1
  73. package/dist/_internal/server/jobs/core/index.d.ts +2 -0
  74. package/dist/_internal/server/jobs/core/index.js +2 -0
  75. package/dist/_internal/server/jobs/core/marketPriceRollup.d.ts +64 -0
  76. package/dist/_internal/server/jobs/core/marketPriceRollup.js +153 -0
  77. package/dist/_internal/server/jobs/core/obligationSweep.d.ts +36 -0
  78. package/dist/_internal/server/jobs/core/obligationSweep.js +503 -0
  79. package/dist/_internal/server/jobs/core/paymentReconcile.d.ts +79 -0
  80. package/dist/_internal/server/jobs/core/paymentReconcile.js +614 -0
  81. package/dist/_internal/server/jobs/core/paymentWindowTimeout.js +34 -0
  82. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +35 -4
  83. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.d.ts +2 -0
  84. package/dist/_internal/server/jobs/handlers/boxRollupReconcile.js +15 -0
  85. package/dist/_internal/server/jobs/handlers/countersReconcileNow.d.ts +65 -0
  86. package/dist/_internal/server/jobs/handlers/countersReconcileNow.js +89 -0
  87. package/dist/_internal/server/jobs/handlers/index.d.ts +5 -0
  88. package/dist/_internal/server/jobs/handlers/index.js +5 -0
  89. package/dist/_internal/server/jobs/handlers/marketPriceRollup.d.ts +2 -0
  90. package/dist/_internal/server/jobs/handlers/marketPriceRollup.js +2 -0
  91. package/dist/_internal/server/jobs/handlers/obligationSweep.d.ts +2 -0
  92. package/dist/_internal/server/jobs/handlers/obligationSweep.js +15 -0
  93. package/dist/_internal/server/jobs/handlers/onShipmentAllocationSync.js +30 -1
  94. package/dist/_internal/server/jobs/handlers/paymentReconcile.d.ts +2 -0
  95. package/dist/_internal/server/jobs/handlers/paymentReconcile.js +6 -0
  96. package/dist/_internal/shared/actions/action-registry.d.ts +1 -1
  97. package/dist/_internal/shared/actions/action-registry.js +71 -0
  98. package/dist/_internal/shared/checkout/coupon-discount.d.ts +73 -0
  99. package/dist/_internal/shared/checkout/coupon-discount.js +103 -0
  100. package/dist/_internal/shared/checkout/lanes.d.ts +42 -0
  101. package/dist/_internal/shared/checkout/lanes.js +46 -0
  102. package/dist/_internal/shared/checkout/order-math.d.ts +149 -1
  103. package/dist/_internal/shared/checkout/order-math.js +251 -3
  104. package/dist/_internal/shared/checkout/refundable-base.d.ts +116 -0
  105. package/dist/_internal/shared/checkout/refundable-base.js +122 -0
  106. package/dist/_internal/shared/features/auctions/config.d.ts +14 -1
  107. package/dist/_internal/shared/features/auctions/config.js +30 -1
  108. package/dist/_internal/shared/features/brands/schema.d.ts +42 -0
  109. package/dist/_internal/shared/features/brands/schema.js +18 -0
  110. package/dist/_internal/shared/features/orders/refund-copy.d.ts +22 -0
  111. package/dist/_internal/shared/features/orders/refund-copy.js +34 -0
  112. package/dist/_internal/shared/features/print/label-profiles.d.ts +198 -0
  113. package/dist/_internal/shared/features/print/label-profiles.js +350 -0
  114. package/dist/_internal/shared/features/products/to-product-item.js +33 -5
  115. package/dist/_internal/shared/fees/calculator.d.ts +31 -2
  116. package/dist/_internal/shared/fees/calculator.js +103 -5
  117. package/dist/_internal/shared/fees/india-states.d.ts +195 -0
  118. package/dist/_internal/shared/fees/india-states.js +190 -0
  119. package/dist/_internal/shared/fees/payout-hold.d.ts +80 -0
  120. package/dist/_internal/shared/fees/payout-hold.js +74 -0
  121. package/dist/_internal/shared/obligations/deadlines.d.ts +94 -0
  122. package/dist/_internal/shared/obligations/deadlines.js +155 -0
  123. package/dist/_internal/shared/shipping/quote.d.ts +96 -0
  124. package/dist/_internal/shared/shipping/quote.js +67 -0
  125. package/dist/_internal/shared/time/business-days.d.ts +91 -0
  126. package/dist/_internal/shared/time/business-days.js +185 -0
  127. package/dist/cli/index.js +0 -2
  128. package/dist/client.d.ts +10 -4
  129. package/dist/client.js +29 -3
  130. package/dist/configs/tailwind.d.ts +0 -12
  131. package/dist/configs/tailwind.js +28 -15
  132. package/dist/constants/api-endpoints.d.ts +51 -9
  133. package/dist/constants/api-endpoints.js +23 -7
  134. package/dist/constants/index.d.ts +1 -1
  135. package/dist/constants/index.js +1 -1
  136. package/dist/contracts/index.d.ts +1 -1
  137. package/dist/contracts/payment.d.ts +131 -0
  138. package/dist/features/about/components/AboutView.js +1 -1
  139. package/dist/features/about/components/DeveloperView.js +5 -2
  140. package/dist/features/about/components/FAQPageView.js +2 -1
  141. package/dist/features/about/components/FeesView.js +2 -1
  142. package/dist/features/about/components/HelpPageView.js +2 -1
  143. package/dist/features/about/components/HowAuctionsWorkView.js +2 -1
  144. package/dist/features/about/components/HowCheckoutWorksView.js +2 -1
  145. package/dist/features/about/components/HowEmiWorksView.js +2 -1
  146. package/dist/features/about/components/HowOffersWorkView.js +2 -1
  147. package/dist/features/about/components/HowOrdersWorkView.js +2 -1
  148. package/dist/features/about/components/HowPayoutsWorkView.js +2 -1
  149. package/dist/features/about/components/HowPreOrdersWorkView.js +2 -1
  150. package/dist/features/about/components/HowReviewsWorkView.js +2 -1
  151. package/dist/features/about/components/PolicyPageView.js +2 -1
  152. package/dist/features/about/components/ProseHero.d.ts +73 -0
  153. package/dist/features/about/components/ProseHero.js +18 -0
  154. package/dist/features/about/components/SecurityPrivacyView.js +2 -1
  155. package/dist/features/about/components/ShippingPolicyView.js +2 -1
  156. package/dist/features/about/components/TrackOrderView.js +2 -1
  157. package/dist/features/about/components/UnauthorizedView.js +1 -1
  158. package/dist/features/about/index.d.ts +2 -0
  159. package/dist/features/about/index.js +5 -0
  160. package/dist/features/account/actions/address-actions.d.ts +26 -2
  161. package/dist/features/account/actions/address-actions.js +25 -1
  162. package/dist/features/account/components/BuyerShoppingGuideView.js +1 -1
  163. package/dist/features/account/components/NotificationPreferencesPanel.js +1 -1
  164. package/dist/features/account/components/UserOrdersView.js +17 -0
  165. package/dist/features/account/components/UserSidebar.js +1 -1
  166. package/dist/features/account/hooks/useNotifications.js +29 -0
  167. package/dist/features/admin/actions/admin-coupon-actions.js +36 -0
  168. package/dist/features/admin/components/AdminBeysView.js +2 -2
  169. package/dist/features/admin/components/AdminBlogEditorView.js +2 -2
  170. package/dist/features/admin/components/AdminBrandEditorView.js +32 -2
  171. package/dist/features/admin/components/AdminBundleEditorView.js +1 -1
  172. package/dist/features/admin/components/AdminCarouselEditorView.js +1 -1
  173. package/dist/features/admin/components/AdminCategoriesView.js +1 -1
  174. package/dist/features/admin/components/AdminCategoryEditorView.js +73 -17
  175. package/dist/features/admin/components/AdminContactEditorView.js +1 -1
  176. package/dist/features/admin/components/AdminCouponEditorView.js +26 -1
  177. package/dist/features/admin/components/AdminFaqEditorView.js +1 -1
  178. package/dist/features/admin/components/AdminFaqsView.js +2 -2
  179. package/dist/features/admin/components/AdminGatewayEventsView.d.ts +54 -0
  180. package/dist/features/admin/components/AdminGatewayEventsView.js +209 -0
  181. package/dist/features/admin/components/AdminGuideHubView.js +1 -1
  182. package/dist/features/admin/components/AdminMoneyCasesView.d.ts +60 -0
  183. package/dist/features/admin/components/AdminMoneyCasesView.js +287 -0
  184. package/dist/features/admin/components/AdminPartsView.js +2 -2
  185. package/dist/features/admin/components/AdminProductsView.js +75 -4
  186. package/dist/features/admin/components/AdminSidebar.js +2 -2
  187. package/dist/features/admin/components/AdminSiteSettingsView.js +65 -7
  188. package/dist/features/admin/components/AdminSublistingCategoriesView.js +2 -0
  189. package/dist/features/admin/components/AdminSupportTicketDetailView.js +1 -1
  190. package/dist/features/admin/components/AdminSupportTicketsView.js +2 -2
  191. package/dist/features/admin/components/AdminTesterChecklistView.js +2 -2
  192. package/dist/features/admin/components/AdminUsersView.js +72 -1
  193. package/dist/features/admin/components/AdminViewCards.d.ts +3 -1
  194. package/dist/features/admin/components/AdminViewCards.js +4 -4
  195. package/dist/features/admin/components/DataListingView.d.ts +28 -0
  196. package/dist/features/admin/components/DataListingView.js +175 -14
  197. package/dist/features/admin/components/DataTable.d.ts +12 -1
  198. package/dist/features/admin/components/DataTable.js +3 -3
  199. package/dist/features/admin/components/OwnerQuickActions.d.ts +1 -1
  200. package/dist/features/admin/components/OwnerQuickActions.js +60 -2
  201. package/dist/features/admin/components/RecordOwnerQuickActions.d.ts +1 -1
  202. package/dist/features/admin/components/RecordOwnerQuickActions.js +61 -14
  203. package/dist/features/admin/components/analytics/AdminStatCard.d.ts +35 -1
  204. package/dist/features/admin/components/analytics/AdminStatCard.js +77 -2
  205. package/dist/features/admin/components/index.d.ts +4 -0
  206. package/dist/features/admin/components/index.js +6 -0
  207. package/dist/features/admin/constants/filter-tabs.d.ts +156 -0
  208. package/dist/features/admin/constants/filter-tabs.js +106 -0
  209. package/dist/features/admin/repository/site-settings.repository.d.ts +22 -0
  210. package/dist/features/admin/repository/site-settings.repository.js +82 -1
  211. package/dist/features/admin/schemas/admin-editor-forms.d.ts +21 -0
  212. package/dist/features/admin/schemas/admin-editor-forms.js +44 -0
  213. package/dist/features/admin/schemas/firestore.js +5 -1
  214. package/dist/features/admin/schemas/site-settings-form.js +30 -1
  215. package/dist/features/auctions/actions/bid-actions.js +13 -1
  216. package/dist/features/auctions/components/AuctionBottomActions.js +2 -2
  217. package/dist/features/auctions/components/AuctionDetailPageView.js +3 -1
  218. package/dist/features/auctions/components/MarketplaceAuctionCard.d.ts +11 -0
  219. package/dist/features/auctions/components/MarketplaceAuctionCard.js +5 -3
  220. package/dist/features/auctions/components/PlaceBidFormClient.d.ts +8 -1
  221. package/dist/features/auctions/components/PlaceBidFormClient.js +2 -2
  222. package/dist/features/audit-log/schemas/firestore.d.ts +28 -0
  223. package/dist/features/audit-log/schemas/firestore.js +14 -0
  224. package/dist/features/auth/permissions/constants.d.ts +17 -1
  225. package/dist/features/auth/permissions/constants.js +18 -0
  226. package/dist/features/blog/components/BlogPostView.js +1 -1
  227. package/dist/features/catalogue/components/AdminCatalogueApprovalsView.js +1 -1
  228. package/dist/features/categories/api/[id]/description-template/route.js +107 -1
  229. package/dist/features/categories/components/BrandDetailPageView.js +2 -2
  230. package/dist/features/categories/components/BreadcrumbTrail.js +7 -1
  231. package/dist/features/categories/components/BundleDetailView.js +1 -1
  232. package/dist/features/categories/components/CategoryDetailPageView.js +2 -2
  233. package/dist/features/categories/components/CategoryForm.js +12 -1
  234. package/dist/features/categories/components/CategoryTemplatePicker.js +38 -5
  235. package/dist/features/categories/repository/market-price-index.repository.d.ts +123 -0
  236. package/dist/features/categories/repository/market-price-index.repository.js +105 -0
  237. package/dist/features/categories/repository/price-observation.repository.d.ts +166 -0
  238. package/dist/features/categories/repository/price-observation.repository.js +132 -0
  239. package/dist/features/events/components/EventSaleBanner.js +1 -1
  240. package/dist/features/events/components/EventStatusBadge.js +1 -1
  241. package/dist/features/faq/components/FAQAccordion.js +2 -1
  242. package/dist/features/faq/components/FAQCategorySidebar.js +2 -2
  243. package/dist/features/faq/components/FAQHelpfulButtons.js +4 -4
  244. package/dist/features/faq/components/RelatedFAQs.js +1 -1
  245. package/dist/features/filters/FilterFacetSection.js +1 -1
  246. package/dist/features/filters/SwitchFilter.js +1 -1
  247. package/dist/features/gateway-events/repository/gateway-events.repository.d.ts +117 -0
  248. package/dist/features/gateway-events/repository/gateway-events.repository.js +274 -0
  249. package/dist/features/gateway-events/schemas/firestore.d.ts +217 -0
  250. package/dist/features/gateway-events/schemas/firestore.js +125 -0
  251. package/dist/features/gateway-events/schemas/index.d.ts +81 -0
  252. package/dist/features/gateway-events/schemas/index.js +79 -0
  253. package/dist/features/grouped/components/GroupedListingDetailView.js +1 -1
  254. package/dist/features/homepage/components/AnnouncementBar.js +37 -1
  255. package/dist/features/homepage/components/AnnouncementBar.style.css +30 -0
  256. package/dist/features/homepage/components/BrandsSection.js +1 -1
  257. package/dist/features/homepage/components/CTABannerSection.js +1 -1
  258. package/dist/features/homepage/components/FeaturedBundlesSection.js +14 -24
  259. package/dist/features/homepage/components/HeroCarousel.js +2 -2
  260. package/dist/features/homepage/components/SocialPostCard.js +1 -1
  261. package/dist/features/inventory/components/AdminMoveLedgerView.d.ts +7 -0
  262. package/dist/features/inventory/components/AdminMoveLedgerView.js +157 -0
  263. package/dist/features/inventory/components/AdminStorageBoxesView.d.ts +11 -0
  264. package/dist/features/inventory/components/AdminStorageBoxesView.js +168 -0
  265. package/dist/features/inventory/components/ReceiveStationView.d.ts +39 -0
  266. package/dist/features/inventory/components/ReceiveStationView.js +413 -0
  267. package/dist/features/inventory/components/operator-message.d.ts +29 -0
  268. package/dist/features/inventory/components/operator-message.js +41 -0
  269. package/dist/features/inventory/index.d.ts +13 -0
  270. package/dist/features/inventory/index.js +28 -0
  271. package/dist/features/inventory/repository/inventory-movements.repository.d.ts +97 -0
  272. package/dist/features/inventory/repository/inventory-movements.repository.js +186 -0
  273. package/dist/features/inventory/repository/inventory-placements.repository.d.ts +179 -0
  274. package/dist/features/inventory/repository/inventory-placements.repository.js +345 -0
  275. package/dist/features/inventory/repository/storage-boxes.repository.d.ts +83 -0
  276. package/dist/features/inventory/repository/storage-boxes.repository.js +127 -0
  277. package/dist/features/inventory/schemas/firestore.d.ts +197 -0
  278. package/dist/features/inventory/schemas/firestore.js +159 -0
  279. package/dist/features/layout/AppLayoutShell.d.ts +12 -1
  280. package/dist/features/layout/AppLayoutShell.js +25 -12
  281. package/dist/features/layout/AutoBreadcrumbs.js +162 -3
  282. package/dist/features/layout/AutoBreadcrumbs.style.css +45 -0
  283. package/dist/features/layout/BottomNavbar.d.ts +1 -1
  284. package/dist/features/layout/BottomNavbar.js +2 -2
  285. package/dist/features/media/MediaAvatar.js +1 -1
  286. package/dist/features/media/MediaImage.d.ts +24 -4
  287. package/dist/features/media/MediaImage.js +44 -4
  288. package/dist/features/media/MediaLightbox.js +1 -1
  289. package/dist/features/media/modals/ImageCropModal.js +1 -1
  290. package/dist/features/media/modals/ImageEditor.js +1 -1
  291. package/dist/features/media/upload/MediaUploadField.js +91 -3
  292. package/dist/features/obligations/constants/policies.d.ts +92 -0
  293. package/dist/features/obligations/constants/policies.js +378 -0
  294. package/dist/features/obligations/repository/obligations.repository.d.ts +68 -0
  295. package/dist/features/obligations/repository/obligations.repository.js +158 -0
  296. package/dist/features/obligations/schemas/firestore.d.ts +160 -0
  297. package/dist/features/obligations/schemas/firestore.js +87 -0
  298. package/dist/features/orders/actions/order-actions.d.ts +43 -4
  299. package/dist/features/orders/actions/order-actions.js +60 -3
  300. package/dist/features/orders/actions/refund-actions.js +70 -10
  301. package/dist/features/orders/components/OrderCompletionPanel.d.ts +27 -0
  302. package/dist/features/orders/components/OrderCompletionPanel.js +192 -0
  303. package/dist/features/orders/components/OrdersList.js +2 -2
  304. package/dist/features/orders/components/RefundHistoryTable.js +3 -1
  305. package/dist/features/orders/components/index.d.ts +2 -0
  306. package/dist/features/orders/components/index.js +6 -0
  307. package/dist/features/orders/constants/transitions.d.ts +59 -0
  308. package/dist/features/orders/constants/transitions.js +104 -0
  309. package/dist/features/orders/repository/orders.repository.d.ts +109 -6
  310. package/dist/features/orders/repository/orders.repository.js +464 -21
  311. package/dist/features/orders/schemas/firestore.d.ts +270 -0
  312. package/dist/features/orders/schemas/firestore.js +24 -0
  313. package/dist/features/orders/schemas/index.d.ts +61 -0
  314. package/dist/features/orders/schemas/index.js +22 -0
  315. package/dist/features/orders/types/index.d.ts +79 -0
  316. package/dist/features/parts/components/PartsIndexPageView.js +8 -1
  317. package/dist/features/payment-cases/repository/payment-cases.repository.d.ts +170 -0
  318. package/dist/features/payment-cases/repository/payment-cases.repository.js +342 -0
  319. package/dist/features/payment-cases/schemas/firestore.d.ts +229 -0
  320. package/dist/features/payment-cases/schemas/firestore.js +105 -0
  321. package/dist/features/payment-cases/schemas/resolve-form.d.ts +97 -0
  322. package/dist/features/payment-cases/schemas/resolve-form.js +114 -0
  323. package/dist/features/payments/repository/payout.repository.d.ts +0 -1
  324. package/dist/features/payments/repository/payout.repository.js +0 -1
  325. package/dist/features/points/repository/points.repository.d.ts +122 -0
  326. package/dist/features/points/repository/points.repository.js +304 -1
  327. package/dist/features/points/schemas/adjust-form.d.ts +58 -0
  328. package/dist/features/points/schemas/adjust-form.js +63 -0
  329. package/dist/features/points/schemas/firestore.d.ts +108 -1
  330. package/dist/features/points/schemas/firestore.js +84 -0
  331. package/dist/features/pre-orders/components/MarketplacePreorderCard.js +3 -3
  332. package/dist/features/pre-orders/components/PreOrderBadge.js +1 -1
  333. package/dist/features/pre-orders/components/PreOrderDetailPageView.js +1 -1
  334. package/dist/features/price-index/components/PriceIndexToolView.d.ts +23 -4
  335. package/dist/features/price-index/components/PriceIndexToolView.js +71 -6
  336. package/dist/features/price-index/market-stats.d.ts +82 -0
  337. package/dist/features/price-index/market-stats.js +230 -0
  338. package/dist/features/print/components/BoxLabelPrintView.d.ts +25 -0
  339. package/dist/features/print/components/BoxLabelPrintView.js +66 -0
  340. package/dist/features/print/components/LabelProfilePicker.d.ts +13 -0
  341. package/dist/features/print/components/LabelProfilePicker.js +38 -0
  342. package/dist/features/print/components/labels.d.ts +117 -0
  343. package/dist/features/print/components/labels.js +258 -0
  344. package/dist/features/print/components/labels.style.css +46 -0
  345. package/dist/features/print/index.d.ts +6 -0
  346. package/dist/features/print/index.js +16 -0
  347. package/dist/features/products/columns/productTableColumns.js +5 -4
  348. package/dist/features/products/components/MarketplaceBundleCard.js +2 -2
  349. package/dist/features/products/components/MarketplacePrizeDrawCard.js +6 -6
  350. package/dist/features/products/components/PrizeDrawCollage.js +1 -1
  351. package/dist/features/products/components/PrizeDrawItemsEditor.js +1 -1
  352. package/dist/features/products/components/PrizeRevealModal.js +2 -2
  353. package/dist/features/products/components/ProductDetailPageView.js +18 -4
  354. package/dist/features/products/components/ProductForm.js +34 -23
  355. package/dist/features/products/components/ProductGrid.js +86 -62
  356. package/dist/features/products/constants/card-badges.d.ts +97 -0
  357. package/dist/features/products/constants/card-badges.js +100 -0
  358. package/dist/features/products/repository/products.repository.js +34 -1
  359. package/dist/features/products/schemas/firestore.d.ts +46 -3
  360. package/dist/features/products/schemas/firestore.js +21 -0
  361. package/dist/features/promotions/components/PromotionsView.js +1 -1
  362. package/dist/features/promotions/repository/claimed-coupons.repository.d.ts +32 -0
  363. package/dist/features/promotions/repository/claimed-coupons.repository.js +58 -0
  364. package/dist/features/promotions/schemas/firestore.js +30 -3
  365. package/dist/features/promotions/schemas/index.d.ts +6 -6
  366. package/dist/features/protection-claims/repository/protection-claims.repository.d.ts +54 -0
  367. package/dist/features/protection-claims/repository/protection-claims.repository.js +174 -0
  368. package/dist/features/protection-claims/schemas/firestore.d.ts +168 -0
  369. package/dist/features/protection-claims/schemas/firestore.js +130 -0
  370. package/dist/features/reviews/actions/review-actions.js +3 -2
  371. package/dist/features/reviews/components/ReviewModal.js +1 -1
  372. package/dist/features/reviews/components/ReviewsList.js +1 -1
  373. package/dist/features/reviews/schemas/firestore.d.ts +20 -1
  374. package/dist/features/scams/constants/scam-types.d.ts +2 -0
  375. package/dist/features/scams/constants/scam-types.js +18 -0
  376. package/dist/features/scams/schemas/report-form.d.ts +3 -3
  377. package/dist/features/scams/schemas/report-form.js +26 -1
  378. package/dist/features/search/components/Search.js +9 -9
  379. package/dist/features/search/components/SearchResultsClient.js +18 -3
  380. package/dist/features/search/components/SearchResultsPageView.d.ts +1 -6
  381. package/dist/features/search/components/SearchResultsPageView.js +2 -2
  382. package/dist/features/seller/actions/offer-actions.js +16 -1
  383. package/dist/features/seller/actions/seller-actions.js +14 -3
  384. package/dist/features/seller/components/CategoryInlineSelect.js +75 -11
  385. package/dist/features/seller/components/PrintCenterView.d.ts +5 -0
  386. package/dist/features/seller/components/PrintCenterView.js +41 -30
  387. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  388. package/dist/features/seller/components/SellerPayoutRequestView.js +16 -5
  389. package/dist/features/seller/components/SellerPayoutSettingsView.js +1 -1
  390. package/dist/features/seller/components/SellerProductShell.d.ts +16 -0
  391. package/dist/features/seller/components/SellerProductShell.js +123 -13
  392. package/dist/features/seller/components/SellerReviewsView.js +4 -2
  393. package/dist/features/seller/components/SellerSidebar.js +1 -1
  394. package/dist/features/seller/schemas/coupon-form.d.ts +26 -0
  395. package/dist/features/seller/schemas/coupon-form.js +27 -0
  396. package/dist/features/seller/schemas/product-form.d.ts +3 -0
  397. package/dist/features/seller/schemas/product-form.js +5 -0
  398. package/dist/features/shell/FormShell.js +2 -2
  399. package/dist/features/shell/QuickFormDrawer.js +14 -0
  400. package/dist/features/shell/SectionForm.d.ts +19 -0
  401. package/dist/features/shell/SectionForm.js +133 -9
  402. package/dist/features/shell/SectionForm.style.css +54 -0
  403. package/dist/features/shipments/components/AdminShipmentsView.js +16 -1
  404. package/dist/features/shipments/repository/shipment-items.repository.d.ts +9 -0
  405. package/dist/features/shipments/repository/shipment-items.repository.js +14 -0
  406. package/dist/features/site-settings/components/ActionPermissionsManager.js +1 -1
  407. package/dist/features/sticker-trace/components/StickerCommitPanel.js +1 -1
  408. package/dist/features/store-extensions/schemas/report-review-form.d.ts +8 -8
  409. package/dist/features/stores/components/StoreGuideHubView.js +1 -1
  410. package/dist/features/stores/components/StoreHeader.js +1 -1
  411. package/dist/features/support/schemas/firestore.d.ts +73 -2
  412. package/dist/features/support/schemas/firestore.js +59 -0
  413. package/dist/index.d.ts +16 -24
  414. package/dist/index.js +38 -47
  415. package/dist/next/ErrorBoundary.js +1 -1
  416. package/dist/next/routing/route-map.d.ts +24 -0
  417. package/dist/next/routing/route-map.js +40 -0
  418. package/dist/providers/payment-manual/index.d.ts +15 -1
  419. package/dist/providers/payment-manual/index.js +37 -0
  420. package/dist/providers/payment-phonepe/index.d.ts +14 -1
  421. package/dist/providers/payment-phonepe/index.js +36 -0
  422. package/dist/providers/payment-razorpay/index.d.ts +44 -1
  423. package/dist/providers/payment-razorpay/index.js +99 -0
  424. package/dist/providers/shipping-shiprocket/index.d.ts +18 -0
  425. package/dist/providers/shipping-shiprocket/index.js +18 -0
  426. package/dist/schemas/registry.d.ts +44 -11
  427. package/dist/server-entry.d.ts +2 -0
  428. package/dist/server-entry.js +27 -0
  429. package/dist/server.d.ts +43 -0
  430. package/dist/server.js +98 -0
  431. package/dist/styles.css +418 -149
  432. package/dist/tailwind-utilities.css +1 -1
  433. package/dist/tokens/themes/default-dark.js +15 -12
  434. package/dist/tokens/themes/default-light.js +14 -11
  435. package/dist/tokens/tokens.css +106 -23
  436. package/dist/ui/DataTable.style.css +4 -4
  437. package/dist/ui/columns/cell-renderers.d.ts +20 -0
  438. package/dist/ui/columns/cell-renderers.js +26 -1
  439. package/dist/ui/columns/index.d.ts +1 -1
  440. package/dist/ui/columns/index.js +4 -1
  441. package/dist/ui/components/Badge.style.css +1 -1
  442. package/dist/ui/components/Breadcrumb.js +19 -1
  443. package/dist/ui/components/BulkActionBar.style.css +4 -4
  444. package/dist/ui/components/Button.style.css +1 -1
  445. package/dist/ui/components/Checkbox.style.css +1 -1
  446. package/dist/ui/components/FallbackShell.js +1 -1
  447. package/dist/ui/components/Form.d.ts +14 -1
  448. package/dist/ui/components/Form.js +2 -2
  449. package/dist/ui/components/FormField.js +18 -4
  450. package/dist/ui/components/FormField.style.css +43 -3
  451. package/dist/ui/components/GatedPrice.style.css +1 -1
  452. package/dist/ui/components/IconButton.style.css +1 -1
  453. package/dist/ui/components/Input.js +22 -1
  454. package/dist/ui/components/Input.style.css +38 -22
  455. package/dist/ui/components/ListingFilterDrawer.js +1 -1
  456. package/dist/ui/components/ListingToolbar.js +1 -1
  457. package/dist/ui/components/PaginatedSelect.js +0 -0
  458. package/dist/ui/components/Radio.style.css +1 -1
  459. package/dist/ui/components/Repeater.d.ts +53 -0
  460. package/dist/ui/components/Repeater.js +7 -0
  461. package/dist/ui/components/RichTextEditor.d.ts +20 -1
  462. package/dist/ui/components/RichTextEditor.js +23 -2
  463. package/dist/ui/components/Select.style.css +7 -21
  464. package/dist/ui/components/Semantic.js +27 -3
  465. package/dist/ui/components/TagInput.style.css +4 -9
  466. package/dist/ui/components/Textarea.style.css +4 -18
  467. package/dist/ui/components/Toggle.style.css +1 -1
  468. package/dist/ui/components/Typography.d.ts +3 -3
  469. package/dist/ui/components/Typography.js +15 -15
  470. package/dist/ui/components/ViewToggle.style.css +4 -4
  471. package/dist/ui/forms/AddAnotherToggle.d.ts +16 -0
  472. package/dist/ui/forms/AddAnotherToggle.js +56 -0
  473. package/dist/ui/forms/ColorPickerField.js +15 -2
  474. package/dist/ui/forms/FieldCheckbox.js +15 -2
  475. package/dist/ui/forms/FieldInput.d.ts +22 -1
  476. package/dist/ui/forms/FieldInput.js +19 -5
  477. package/dist/ui/forms/FieldLabel.d.ts +35 -0
  478. package/dist/ui/forms/FieldLabel.js +70 -0
  479. package/dist/ui/forms/FieldSelect.d.ts +4 -1
  480. package/dist/ui/forms/FieldSelect.js +27 -5
  481. package/dist/ui/forms/FieldTextarea.d.ts +4 -1
  482. package/dist/ui/forms/FieldTextarea.js +22 -5
  483. package/dist/ui/forms/FormShell.d.ts +17 -1
  484. package/dist/ui/forms/FormShell.js +36 -2
  485. package/dist/ui/forms/FormShell.style.css +41 -33
  486. package/dist/ui/forms/index.d.ts +5 -1
  487. package/dist/ui/forms/index.js +2 -0
  488. package/dist/ui/icons/icon-registry.d.ts +1 -0
  489. package/dist/ui/icons/icon-registry.js +17 -1
  490. package/dist/utils/id-generators.d.ts +80 -0
  491. package/dist/utils/id-generators.js +93 -0
  492. package/dist/utils/search-txt-builders.d.ts +12 -0
  493. package/dist/utils/search-txt-builders.js +17 -0
  494. package/package.json +1 -1
  495. package/scripts/seed-cli.mjs +106 -5
  496. package/dist/features/loyalty/api/admin/loyalty/grant/route.d.ts +0 -12
  497. package/dist/features/loyalty/api/admin/loyalty/grant/route.js +0 -38
  498. package/dist/features/loyalty/api/balance/route.d.ts +0 -12
  499. package/dist/features/loyalty/api/balance/route.js +0 -41
  500. package/dist/features/loyalty/api/earn/route.d.ts +0 -12
  501. package/dist/features/loyalty/api/earn/route.js +0 -38
  502. package/dist/features/loyalty/api/history/route.d.ts +0 -12
  503. package/dist/features/loyalty/api/history/route.js +0 -58
  504. package/dist/features/loyalty/api/redeem/route.d.ts +0 -12
  505. package/dist/features/loyalty/api/redeem/route.js +0 -38
  506. package/dist/features/loyalty/columns/index.d.ts +0 -17
  507. package/dist/features/loyalty/columns/index.js +0 -40
  508. package/dist/features/loyalty/components/CoinsDisplay.d.ts +0 -12
  509. package/dist/features/loyalty/components/CoinsDisplay.js +0 -10
  510. package/dist/features/loyalty/components/index.d.ts +0 -1
  511. package/dist/features/loyalty/components/index.js +0 -1
  512. package/dist/features/loyalty/hooks/useLoyaltyBalance.d.ts +0 -4
  513. package/dist/features/loyalty/hooks/useLoyaltyBalance.js +0 -25
  514. package/dist/features/loyalty/index.d.ts +0 -6
  515. package/dist/features/loyalty/index.js +0 -6
  516. package/dist/features/loyalty/manifest.d.ts +0 -2
  517. package/dist/features/loyalty/manifest.js +0 -16
  518. package/dist/features/loyalty/messages/en.json +0 -19
  519. package/dist/features/loyalty/repository/loyalty.repository.d.ts +0 -8
  520. package/dist/features/loyalty/repository/loyalty.repository.js +0 -22
  521. package/dist/features/loyalty/schemas/index.d.ts +0 -85
  522. package/dist/features/loyalty/schemas/index.js +0 -40
  523. package/dist/features/loyalty/server.d.ts +0 -6
  524. package/dist/features/loyalty/server.js +0 -6
  525. package/dist/features/loyalty/types/index.d.ts +0 -31
  526. package/dist/features/loyalty/types/index.js +0 -1
  527. package/dist/features/loyalty/types/loyalty-config.d.ts +0 -7
  528. package/dist/features/loyalty/types/loyalty-config.js +0 -1
  529. package/dist/features/loyalty/types/loyalty-math.d.ts +0 -9
  530. package/dist/features/loyalty/types/loyalty-math.js +0 -27
  531. package/dist/features/products/components/ProductOwnerQuickActions.d.ts +0 -11
  532. package/dist/features/products/components/ProductOwnerQuickActions.js +0 -120
@@ -598,13 +598,13 @@ export async function CategoryDetailPageView({ slug }) {
598
598
  const ldFaq = category?.faqs && category.faqs.length > 0
599
599
  ? faqJsonLd(category.faqs.map((f) => ({ question: f.question, answer: f.answer })))
600
600
  : null;
601
- return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && hasMosaic && (_jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", padding: "x-md", children: _jsx(Div, { padding: "t-lg", children: _jsx(ImageMosaic, { images: mosaicImages, alt: `${category?.name ?? slug} on LetItRip` }) }) })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsx(BreadcrumbTrail, { crumbs: trail, className: "mb-4", tone: hasCover ? "on-media" : "default" }), category?.id && _jsx(LastCategoryBeacon, { categoryId: category.id }), category?.id && (_jsx(RecordOwnerQuickActions, { resource: "CATEGORY", recordId: category.id, placement: "detail",
601
+ return (_jsxs(Main, { children: [_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldBreadcrumb) } }), _jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldCollection) } }), ldFaq && (_jsx("script", { type: "application/ld+json", dangerouslySetInnerHTML: { __html: JSON.stringify(ldFaq) } })), _jsxs(Section, { className: `relative ${__O.hidden} ${hasCover ? "min-h-[220px] md:min-h-[280px]" : "bg-[var(--appkit-color-bg)]"}`, children: [hasCover && (_jsxs(_Fragment, { children: [_jsx(Div, { className: "absolute inset-0", children: _jsx(MediaImage, { src: coverImage, alt: "", size: "banner" }) }), _jsx(Div, { surface: "overlay-md", className: "absolute inset-0" })] })), !hasCover && hasMosaic && (_jsx(Div, { className: "relative z-10 max-w-7xl mx-auto", padding: "x-md", children: _jsx(Div, { padding: "t-lg", children: _jsx(ImageMosaic, { images: mosaicImages, alt: `${category?.name ?? slug} on LetItRip` }) }) })), _jsxs(Div, { className: "relative z-10 max-w-7xl mx-auto", paddingY: hasCover ? "y-3xl" : "y-xl", padding: "x-md", children: [_jsx(BreadcrumbTrail, { crumbs: trail, className: "mb-4", tone: hasCover ? "on-media" : "default" }), category?.id && _jsx(LastCategoryBeacon, { categoryId: category.id }), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.id && (_jsx(RecordOwnerQuickActions, { resource: "CATEGORY", recordId: category.id, placement: "detail",
602
602
  /*
603
603
  * Same flag the rest of this hero branches on. Without it the
604
604
  * pencil renders `ghost` (zinc-600) on the dark cover and is
605
605
  * invisible β€” see Root Cause #67 and `OwnerQuickActions.onMedia`.
606
606
  */
607
- onMedia: hasCover, className: "mb-3", recordLabel: category.name ?? slug })), _jsx(Heading, { color: "inverse", level: 1, className: `mb-2 ${hasCover ? "" : "text-[var(--appkit-color-text)]"}`, size: "3xl", mdSize: "4xl", weight: "bold", children: category?.name ?? slug }), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: `max-w-2xl mb-4 ${hasCover ? "/80" : ""}`, size: "base", children: category.description })), _jsxs(Row, { wrap: true, gap: "sm", children: [productCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400", rounded: "full", padding: "pill-sm-tall", children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), showCountSplit && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "muted", size: "xs", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [ownHere.toLocaleString(), " here \u00B7 ", inSubcategories.toLocaleString(), " in subcategories"] })), auctionCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "success", surface: hasCover ? undefined : "success-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), category?.hasbroName && (_jsx(Section, { padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Row, { gap: "md", align: "center", wrap: true, children: [category.hasbroCoverImage && (_jsx(MediaImage, { src: category.hasbroCoverImage, alt: `${category.hasbroName} logo`, size: "thumbnail", className: "h-12 w-auto flex-shrink-0" })), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", transform: "uppercase", weight: "medium", children: "Also known as" }), _jsx(Text, { weight: "semibold", children: category.hasbroName }), _jsxs(Text, { size: "xs", color: "muted", children: ["The same wave, named differently outside Japan \u2014 Takara-Tomy releases it as ", category.name, ", Hasbro as ", category.hasbroName, ". Both names find these listings."] })] })] }) }) })), contentBody && contentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: contentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: category?.highlights, faqs: category?.faqs }), pairedBeys.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: __W, padding: "section", children: _jsxs(Stack, { gap: "sm", className: "min-w-0", children: [_jsx(Heading, { level: 2, size: "lg", children: pairedBeys.length === 1 ? "Specifications" : "Releases" }), _jsx(Stack, { gap: "xs", className: "min-w-0", children: pairedBeys.map((bey) => (_jsxs(Row, { gap: "sm", align: "center", wrap: true, className: "min-w-0", children: [_jsx(TextLink, { href: String(ROUTES.PUBLIC.BEY_DETAIL(bey.slug)), children: bey.name }), [bey.system, bey.productCodes?.[0]]
607
+ onMedia: hasCover, className: "mb-3", recordLabel: category.name ?? slug })), category?.description && typeof category.description === "string" && !category.description.startsWith("{") && (_jsx(Text, { color: hasCover ? "inverse" : "muted", className: "max-w-2xl mb-4", size: "base", children: category.description })), _jsxs(Row, { wrap: true, gap: "sm", children: [productCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: "inverse", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "bg-primary/10 text-primary-700 dark:text-primary-400", rounded: "full", padding: "pill-sm-tall", children: [productCount.toLocaleString(), " ", productCount === 1 ? "product" : "products"] })), showCountSplit && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "muted", size: "xs", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [ownHere.toLocaleString(), " here \u00B7 ", inSubcategories.toLocaleString(), " in subcategories"] })), auctionCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "warning", surface: hasCover ? undefined : "warning-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [auctionCount.toLocaleString(), " ", auctionCount === 1 ? "auction" : "auctions"] })), preOrderCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "info", surface: hasCover ? undefined : "info-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [preOrderCount.toLocaleString(), " ", preOrderCount === 1 ? "pre-order" : "pre-orders"] })), storeCount > 0 && (_jsxs(Span, { layout: "inline-flex", gap: "xs", color: hasCover ? "inverse" : "success", surface: hasCover ? undefined : "success-surface", size: "xs", weight: "medium", className: hasCover ? "bg-[rgba(255,255,255,0.2)] backdrop-blur-sm" : "", rounded: "full", padding: "pill-sm-tall", children: [storeCount.toLocaleString(), " ", storeCount === 1 ? "store" : "stores"] }))] })] })] }), category?.hasbroName && (_jsx(Section, { padding: "y-md", children: _jsx(Container, { size: "xl", children: _jsxs(Row, { gap: "md", align: "center", wrap: true, children: [category.hasbroCoverImage && (_jsx(MediaImage, { src: category.hasbroCoverImage, alt: `${category.hasbroName} logo`, size: "thumbnail", className: "h-12 w-auto flex-shrink-0" })), _jsxs(Stack, { gap: "xs", children: [_jsx(Text, { size: "xs", color: "muted", transform: "uppercase", weight: "medium", children: "Also known as" }), _jsx(Text, { weight: "semibold", children: category.hasbroName }), _jsxs(Text, { size: "xs", color: "muted", children: ["The same wave, named differently outside Japan \u2014 Takara-Tomy releases it as ", category.name, ", Hasbro as ", category.hasbroName, ". Both names find these listings."] })] })] }) }) })), contentBody && contentBody.trim().length > 0 && (_jsx(Section, { padding: "y-lg", children: _jsx(Container, { size: "xl", children: _jsx(RichTextRenderer, { html: contentBody }) }) })), _jsx(CategoryHighlightsAndFaqSection, { highlights: category?.highlights, faqs: category?.faqs }), pairedBeys.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: __W, padding: "section", children: _jsxs(Stack, { gap: "sm", className: "min-w-0", children: [_jsx(Heading, { level: 2, size: "lg", children: pairedBeys.length === 1 ? "Specifications" : "Releases" }), _jsx(Stack, { gap: "xs", className: "min-w-0", children: pairedBeys.map((bey) => (_jsxs(Row, { gap: "sm", align: "center", wrap: true, className: "min-w-0", children: [_jsx(TextLink, { href: String(ROUTES.PUBLIC.BEY_DETAIL(bey.slug)), children: bey.name }), [bey.system, bey.productCodes?.[0]]
608
608
  .filter(Boolean)
609
609
  .map((bit) => (_jsx(Text, { size: "sm", color: "muted", children: String(bit) }, String(bit))))] }, bey.id))) })] }) }) })), compositions.length > 0 && (_jsx(Section, { border: "subtle", surface: "default", className: "border-b", children: _jsx(Div, { className: __W, padding: "section", children: _jsxs(Stack, { gap: "md", className: "min-w-0", children: [_jsx(Heading, { level: 2, size: "lg", children: "What it is made of" }), _jsx(ModelCompositionPanel, { compositions: compositions, hrefForPart: (slot, tail) => String(ROUTES.PUBLIC.PART_DETAIL(slot, tail)) })] }) }) })), (() => {
610
610
  if (category?.tier !== 0)
@@ -1,7 +1,9 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
3
+ import { useId } from "react";
3
4
  import { Div, FormGroup } from "../../../ui/index.js";
4
5
  import { Label, Checkbox, RichTextEditor, Text } from "../../../ui/index.js";
6
+ import { FieldLabel } from "../../../ui/forms/index.js";
5
7
  import { ImageUpload, MediaImage, useMediaUpload } from "../../media/index.js";
6
8
  import { FormField } from "../../../ui/index.js";
7
9
  import { flattenCategories } from "../types/index.js";
@@ -25,6 +27,15 @@ const DEFAULT_LABELS = {
25
27
  export function CategoryForm({ category, allCategories, onChange, isReadonly = false, labels = {}, stackClassName = "space-y-4", }) {
26
28
  const { upload } = useMediaUpload();
27
29
  const L = { ...DEFAULT_LABELS, ...labels };
30
+ /*
31
+ * Unique per instance via `useId`, not a literal. Two CategoryForms on one
32
+ * page would otherwise both emit the same label id, and `aria-labelledby`
33
+ * resolves the FIRST match β€” so the second editor would be named by the
34
+ * first form’s label. The same defect FieldInput’s own note records
35
+ * fixing for `htmlFor`.
36
+ */
37
+ const reactId = useId();
38
+ const descriptionLabelId = `category-description-label-${reactId}`;
28
39
  const update = (partial) => {
29
40
  onChange({ ...category, ...partial });
30
41
  };
@@ -35,7 +46,7 @@ export function CategoryForm({ category, allCategories, onChange, isReadonly = f
35
46
  const handleNameChange = (value) => {
36
47
  update({ name: value, slug: makeSlug(value, category.isBrand ?? false) });
37
48
  };
38
- return (_jsxs(Div, { className: stackClassName, children: [_jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "name", label: L.name, type: "text", value: category.name || "", onChange: handleNameChange, disabled: isReadonly }), _jsx(FormField, { name: "slug", label: L.slug, type: "text", value: category.slug || "", onChange: (value) => update({ slug: value }), disabled: isReadonly })] }), _jsxs(Div, { className: "appkit-form-field", children: [_jsx(Label, { className: "appkit-form-field__label", children: L.description }), _jsx(RichTextEditor, { value: normalizeRichTextHtml(category.description || ""), onChange: (value) => update({ description: value }), disabled: isReadonly, minHeightClassName: "min-h-[140px]", placeholder: "Enter category description" }), _jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: "Rich text is supported for category descriptions." })] }), !isReadonly && (_jsx(ImageUpload, { currentImage: category.display?.coverImage, onUpload: (file) => upload(file, "categories", true, {
49
+ return (_jsxs(Div, { className: stackClassName, children: [_jsxs(FormGroup, { columns: 2, children: [_jsx(FormField, { name: "name", label: L.name, type: "text", value: category.name || "", onChange: handleNameChange, disabled: isReadonly }), _jsx(FormField, { name: "slug", label: L.slug, type: "text", value: category.slug || "", onChange: (value) => update({ slug: value }), disabled: isReadonly })] }), _jsxs(Div, { className: "appkit-form-field", children: [_jsx(FieldLabel, { id: descriptionLabelId, children: L.description }), _jsx(RichTextEditor, { value: normalizeRichTextHtml(category.description || ""), onChange: (value) => update({ description: value }), disabled: isReadonly, minHeightClassName: "min-h-[140px]", placeholder: "Enter category description", ariaLabelledBy: descriptionLabelId }), _jsx(Text, { size: "sm", variant: "secondary", className: "appkit-form-field__hint", children: "Rich text is supported for category descriptions." })] }), !isReadonly && (_jsx(ImageUpload, { currentImage: category.display?.coverImage, onUpload: (file) => upload(file, "categories", true, {
39
50
  type: "category-image",
40
51
  name: category.name || "category",
41
52
  }), onChange: (url) => update({ display: { ...category.display, coverImage: url } }), label: L.categoryImage, helperText: L.imageRecommended })), category.display?.coverImage && isReadonly && (_jsxs(Div, { children: [_jsx(Label, { className: "block mb-2", size: "sm", weight: "medium", children: L.categoryImage }), _jsx(Div, { className: `relative h-32 w-40 ${__O.hidden}`, rounded: "default", children: _jsx(MediaImage, { src: category.display.coverImage, alt: category.name || "", size: "card" }) })] })), _jsx(FormField, { name: "parentId", label: L.parentCategory, type: "select", value: category.parentId || "", onChange: (value) => update({ parentId: value || null }), disabled: isReadonly, options: [
@@ -70,12 +70,45 @@ function unwrap(res) {
70
70
  const body = res?.data ?? res;
71
71
  return body && typeof body === "object" && "template" in body ? body : null;
72
72
  }
73
- /** "2 of 9 slots could not be filled: {{year}}, {{type}}" */
74
- function unresolvedNote(unresolved, total) {
73
+ /**
74
+ * `camelCase` / `snake_case` slot name -> words a seller reads.
75
+ *
76
+ * `categoryPath` -> "category path", `partBreakdown` -> "part breakdown".
77
+ * Lower-cased throughout because the note reads these mid-sentence.
78
+ */
79
+ function humaniseSlot(name) {
80
+ return name
81
+ .replace(/[_-]+/g, " ")
82
+ .replace(/([a-z\d])([A-Z])/g, "$1 $2")
83
+ .trim()
84
+ .toLowerCase();
85
+ }
86
+ /**
87
+ * "2 details were left blank and are highlighted below: year, type"
88
+ *
89
+ * πŸ›‘ NO `{{…}}` IN USER-FACING COPY. This printed the slot names back wrapped
90
+ * in braces β€” `2 of 9 slots could not be filled: {{year}}, {{type}}` β€” which
91
+ * is where the reported "raw {{slot}} syntax" came from. It was not a failed
92
+ * interpolation leaking through; the note RE-MANUFACTURED the template syntax
93
+ * to describe it, so the seller saw the same thing either way and no amount of
94
+ * fixing the templating would have removed it.
95
+ *
96
+ * "slots" went with the braces. It is the word this codebase uses for a
97
+ * placeholder; a seller filling in a listing has no reason to know it, and
98
+ * "could not be filled" reads as a system failure rather than as the three
99
+ * boxes they need to type in. The count stays β€” it is the actionable part β€”
100
+ * but it counts what is MISSING rather than reporting it as a fraction, since
101
+ * "2 of 9" invites the reader to wonder what the other seven were β€” and the
102
+ * denominator is why the `total` argument is gone.
103
+ */
104
+ function unresolvedNote(unresolved) {
75
105
  if (unresolved.length === 0)
76
106
  return null;
77
- const named = unresolved.map((n) => `{{${n}}}`).join(", ");
78
- return `${unresolved.length} of ${total} slots could not be filled: ${named}`;
107
+ const named = unresolved.map(humaniseSlot).join(", ");
108
+ const count = unresolved.length;
109
+ return count === 1
110
+ ? `One detail was left blank β€” add the ${named} before publishing.`
111
+ : `${count} details were left blank β€” add the ${named} before publishing.`;
79
112
  }
80
113
  /** "adds 2 specification rows Β· sets condition to Used" */
81
114
  function describeDefaults(defaults) {
@@ -165,7 +198,7 @@ export function CategoryTemplatePicker({ categoryId, condition, context, isDescr
165
198
  if (!data || !template || !filled) {
166
199
  return (_jsx(Div, { className: PANEL_CLASS, children: _jsx(Text, { size: "xs", color: "muted", children: "No description template for this category yet \u2014 write your own, and an admin can add one so the next listing starts from it." }) }));
167
200
  }
168
- const note = unresolvedNote(filled.unresolved, template.placeholders?.length ?? filled.unresolved.length);
201
+ const note = unresolvedNote(filled.unresolved);
169
202
  const extras = describeDefaults(data.defaults);
170
203
  const source = data.inheritedFromName
171
204
  ? `Inherited from ${data.inheritedFromName}`
@@ -0,0 +1,123 @@
1
+ /**
2
+ * Market Price Index β€” the COMPUTED asking-price band for one leaf.
3
+ *
4
+ * The third collection in a deliberate trio, and the three are not
5
+ * interchangeable:
6
+ *
7
+ * `priceObservations` one dated sighting (raw, append-only)
8
+ * `marketPriceIndex` what asks cluster on (derived, this file)
9
+ * `priceIndex` what SOLD here (derived, our own orders)
10
+ *
11
+ * πŸ›‘ The second and third are never blended, and the rule predates this file:
12
+ * *"They are rendered side by side and never averaged. The gap between them is
13
+ * the most useful thing we can tell a seller."* An ask is not a sale β€” 76–93%
14
+ * of crawled competitor listings are sold out without ever converting β€” so a
15
+ * blended figure would describe neither event and could not be unpicked later.
16
+ *
17
+ * ## πŸ›‘ Why this is not written onto `categories`
18
+ *
19
+ * Same reason as its two siblings, and it is a measured one rather than a
20
+ * stylistic preference: `onCategoryWrite` is a `documentWritten` trigger on
21
+ * `categories`, and Root Cause #92 is a trigger that wrote back to the
22
+ * collection it watched producing **1,017,548 invocations in 24 hours**.
23
+ * `notifyRevalidate` also fires on every category write, so a nightly band
24
+ * write would drop each category page's ISR entry on a project that has
25
+ * already been paused once for ISR-write overage.
26
+ *
27
+ * ## πŸ›‘ It does NOT replace the seeded band β€” it OUTRANKS it
28
+ *
29
+ * `CategoryPriceGuidance.market` stays exactly as seeded. A computed band wins
30
+ * where one exists, and where one does not the seeded generation band still
31
+ * shows, clearly labelled as the generation's. Overwriting the seed would mean
32
+ * a single bad import could erase the only evidence ~60 leaves have, with no
33
+ * way back β€” the same trap as `priceIndexRollup` being forbidden from touching
34
+ * `market`.
35
+ *
36
+ * @tag domain:price-index
37
+ * @tag layer:repository
38
+ * @tag pattern:repository
39
+ * @tag access:server-only
40
+ * @tag consumers:marketPriceRollup,price-guidance-resolve,price-index/market
41
+ * @tag sideEffects:writes marketPriceIndex
42
+ */
43
+ import { BaseRepository } from "../../../providers/db-firebase";
44
+ import type { PriceObservationKind } from "./price-observation.repository";
45
+ /** One computed band. Mirrors `MarketStats` plus its provenance. */
46
+ export interface MarketPriceBand {
47
+ /**
48
+ * The price sightings cluster on β€” the headline.
49
+ *
50
+ * πŸ›‘ Optional, and the renderer must branch. Absent means no bucket held two
51
+ * or more sightings, i.e. every observed price was different, so there is no
52
+ * "most common" to report and `median` is shown under a different label. A
53
+ * mode synthesised from a single sighting would be that sighting wearing a
54
+ * word that implies consensus.
55
+ */
56
+ mode?: number;
57
+ modeCount?: number;
58
+ median: number;
59
+ low: number;
60
+ high: number;
61
+ sampleSize: number;
62
+ /**
63
+ * πŸ›‘ FALSE means low/high/mode describe ALL TIME, not the last six months β€”
64
+ * the window held too little to stand alone. The renderer must relabel. A
65
+ * figure drawn from a two-year-old crawl is indistinguishable from a current
66
+ * one once printed, which is why this travels with the band rather than being
67
+ * inferred from `computedAt`.
68
+ */
69
+ basedOnRecent: boolean;
70
+ windowDays: number;
71
+ /** Newest sighting in the set the band came from. Not the rollup time. */
72
+ lastSeenAt: Date;
73
+ }
74
+ export interface MarketPriceIndexDocument {
75
+ /** The leaf id β€” model, era, part group, whatever was observed. */
76
+ id: string;
77
+ currency: "INR";
78
+ /**
79
+ * Bands kept APART by event kind, never merged.
80
+ *
81
+ * Keyed by `PriceObservationKind`. `ask` is what shops list at; `sale` is
82
+ * what we have evidence changed hands at elsewhere. Most leaves will only
83
+ * ever have `ask`, and that is an honest state rather than a gap.
84
+ */
85
+ byKind: Partial<Record<PriceObservationKind, MarketPriceBand>>;
86
+ computedAt: Date;
87
+ }
88
+ export declare const MARKET_PRICE_INDEX_COLLECTION = "marketPriceIndex";
89
+ export declare class MarketPriceIndexRepository extends BaseRepository<MarketPriceIndexDocument> {
90
+ constructor();
91
+ /**
92
+ * Bands for several leaves in ONE round trip.
93
+ *
94
+ * πŸ›‘ `db.getAll`, not N `findById` calls β€” the same decision, for the same
95
+ * reason, as `PriceIndexRepository.getManyForLeaves`: the tool resolves up to
96
+ * `PRICE_INDEX_SEARCH_LIMIT` leaves per lookup and N sequential reads is N
97
+ * round trips against Rule #6's ~3-per-request budget.
98
+ *
99
+ * A missing document is OMITTED, never returned as an empty band β€” "no
100
+ * computed band here" and "this leaf does not exist" are different answers,
101
+ * and a zeroed band renders as a confident β‚Ή0.
102
+ */
103
+ getManyForLeaves(leafIds: readonly string[]): Promise<Map<string, MarketPriceIndexDocument>>;
104
+ /**
105
+ * Write one leaf's bands.
106
+ *
107
+ * `byKind` is replaced WHOLESALE rather than merged: `update` merges, so a
108
+ * kind whose observations were all retracted would keep its stale band
109
+ * forever β€” the same reason `PriceIndexRepository.setForLeaf` passes the full
110
+ * map rather than a patch.
111
+ */
112
+ setForLeaf(leafId: string, byKind: MarketPriceIndexDocument["byKind"]): Promise<void>;
113
+ /**
114
+ * Drop a leaf's bands entirely.
115
+ *
116
+ * Reachable state, not defensive coding: a leaf whose observations are all
117
+ * retracted, or all fall below `MIN_BAND_OBSERVATIONS` after a retraction,
118
+ * must stop publishing a band. Leaving the last computed one in place would
119
+ * publish a figure derived from evidence that no longer exists.
120
+ */
121
+ clearForLeaf(leafId: string): Promise<void>;
122
+ }
123
+ export declare const marketPriceIndexRepository: MarketPriceIndexRepository;
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Market Price Index β€” the COMPUTED asking-price band for one leaf.
3
+ *
4
+ * The third collection in a deliberate trio, and the three are not
5
+ * interchangeable:
6
+ *
7
+ * `priceObservations` one dated sighting (raw, append-only)
8
+ * `marketPriceIndex` what asks cluster on (derived, this file)
9
+ * `priceIndex` what SOLD here (derived, our own orders)
10
+ *
11
+ * πŸ›‘ The second and third are never blended, and the rule predates this file:
12
+ * *"They are rendered side by side and never averaged. The gap between them is
13
+ * the most useful thing we can tell a seller."* An ask is not a sale β€” 76–93%
14
+ * of crawled competitor listings are sold out without ever converting β€” so a
15
+ * blended figure would describe neither event and could not be unpicked later.
16
+ *
17
+ * ## πŸ›‘ Why this is not written onto `categories`
18
+ *
19
+ * Same reason as its two siblings, and it is a measured one rather than a
20
+ * stylistic preference: `onCategoryWrite` is a `documentWritten` trigger on
21
+ * `categories`, and Root Cause #92 is a trigger that wrote back to the
22
+ * collection it watched producing **1,017,548 invocations in 24 hours**.
23
+ * `notifyRevalidate` also fires on every category write, so a nightly band
24
+ * write would drop each category page's ISR entry on a project that has
25
+ * already been paused once for ISR-write overage.
26
+ *
27
+ * ## πŸ›‘ It does NOT replace the seeded band β€” it OUTRANKS it
28
+ *
29
+ * `CategoryPriceGuidance.market` stays exactly as seeded. A computed band wins
30
+ * where one exists, and where one does not the seeded generation band still
31
+ * shows, clearly labelled as the generation's. Overwriting the seed would mean
32
+ * a single bad import could erase the only evidence ~60 leaves have, with no
33
+ * way back β€” the same trap as `priceIndexRollup` being forbidden from touching
34
+ * `market`.
35
+ *
36
+ * @tag domain:price-index
37
+ * @tag layer:repository
38
+ * @tag pattern:repository
39
+ * @tag access:server-only
40
+ * @tag consumers:marketPriceRollup,price-guidance-resolve,price-index/market
41
+ * @tag sideEffects:writes marketPriceIndex
42
+ */
43
+ import { BaseRepository } from "../../../providers/db-firebase/index.js";
44
+ export const MARKET_PRICE_INDEX_COLLECTION = "marketPriceIndex";
45
+ export class MarketPriceIndexRepository extends BaseRepository {
46
+ constructor() {
47
+ super(MARKET_PRICE_INDEX_COLLECTION);
48
+ }
49
+ /**
50
+ * Bands for several leaves in ONE round trip.
51
+ *
52
+ * πŸ›‘ `db.getAll`, not N `findById` calls β€” the same decision, for the same
53
+ * reason, as `PriceIndexRepository.getManyForLeaves`: the tool resolves up to
54
+ * `PRICE_INDEX_SEARCH_LIMIT` leaves per lookup and N sequential reads is N
55
+ * round trips against Rule #6's ~3-per-request budget.
56
+ *
57
+ * A missing document is OMITTED, never returned as an empty band β€” "no
58
+ * computed band here" and "this leaf does not exist" are different answers,
59
+ * and a zeroed band renders as a confident β‚Ή0.
60
+ */
61
+ async getManyForLeaves(leafIds) {
62
+ const ids = [...new Set(leafIds.filter(Boolean))];
63
+ const out = new Map();
64
+ if (ids.length === 0)
65
+ return out;
66
+ const refs = ids.map((id) => this.db.collection(MARKET_PRICE_INDEX_COLLECTION).doc(id));
67
+ const snaps = await this.db.getAll(...refs);
68
+ for (const snap of snaps) {
69
+ if (!snap.exists)
70
+ continue;
71
+ out.set(snap.id, { ...snap.data(), id: snap.id });
72
+ }
73
+ return out;
74
+ }
75
+ /**
76
+ * Write one leaf's bands.
77
+ *
78
+ * `byKind` is replaced WHOLESALE rather than merged: `update` merges, so a
79
+ * kind whose observations were all retracted would keep its stale band
80
+ * forever β€” the same reason `PriceIndexRepository.setForLeaf` passes the full
81
+ * map rather than a patch.
82
+ */
83
+ async setForLeaf(leafId, byKind) {
84
+ const payload = { currency: "INR", byKind, computedAt: new Date() };
85
+ const existing = await this.findById(leafId);
86
+ if (existing)
87
+ await this.update(leafId, payload);
88
+ else
89
+ await this.createWithId(leafId, payload);
90
+ }
91
+ /**
92
+ * Drop a leaf's bands entirely.
93
+ *
94
+ * Reachable state, not defensive coding: a leaf whose observations are all
95
+ * retracted, or all fall below `MIN_BAND_OBSERVATIONS` after a retraction,
96
+ * must stop publishing a band. Leaving the last computed one in place would
97
+ * publish a figure derived from evidence that no longer exists.
98
+ */
99
+ async clearForLeaf(leafId) {
100
+ const existing = await this.findById(leafId);
101
+ if (existing)
102
+ await this.delete(leafId);
103
+ }
104
+ }
105
+ export const marketPriceIndexRepository = new MarketPriceIndexRepository();
@@ -0,0 +1,166 @@
1
+ /**
2
+ * Price Observations β€” one DATED sighting of one price for one leaf.
3
+ *
4
+ * ## Why this collection has to exist at all
5
+ *
6
+ * The competitor crawl of 2026-10 survives only as a summarised markdown table:
7
+ * p25/median/p75 per GENERATION, and a single `asOf` for the whole thing. That
8
+ * shape makes three questions permanently unanswerable, and all three were
9
+ * asked:
10
+ *
11
+ * "most common price" a mode needs the observations, not a summary of them
12
+ * "last six months" one crawl date cannot yield a window
13
+ * "what does THIS bey go a generation band cannot distinguish Dragoon MS
14
+ * for" from Strata Dragoon MS β€” different beys, one number
15
+ *
16
+ * None of those is a rendering problem, which is why no amount of relabelling
17
+ * fixed them. They are the shape of the stored data, and this is that shape
18
+ * changed: the row is the unit, the summary is derived, and the derivation can
19
+ * be re-run with a different window whenever the question changes.
20
+ *
21
+ * ## πŸ›‘ A sibling collection, never a field on `categories`
22
+ *
23
+ * Identical reasoning to `price-index.repository.ts`, and it is not theoretical.
24
+ * `onCategoryWrite` is a `documentWritten` trigger on `categories`, so a job
25
+ * writing a band there re-triggers it per document β€” Root Cause #92's shape,
26
+ * which produced **1,017,548 invocations in 24 hours** against a 2M/month
27
+ * allowance and blocked `firebase deploy` with a billing denial. Worse here:
28
+ * `notifyRevalidate` fires on every category write, so each rolled-up band
29
+ * would drop that category page's ISR entry, on a project already paused once
30
+ * for exceeding ISR writes by 15.5Γ—.
31
+ *
32
+ * ## πŸ›‘ `sourceKey` is OPAQUE, by instruction
33
+ *
34
+ * The owner's requirement is that competitors are not named on the surface. The
35
+ * honest way to meet that is to not carry the name into the record a renderer
36
+ * can reach β€” not to carry it and remember never to print it, which is one
37
+ * careless `{...row}` away from publishing (Root Cause #70: a public payload
38
+ * built by spreading a document publishes every field nobody thought to delete).
39
+ *
40
+ * So the stored value is a stable opaque key (`src-a`, `src-b`, …). It still
41
+ * does the two jobs provenance needs β€” telling two sources apart, and letting
42
+ * one be re-crawled or retracted wholesale β€” without the name being present to
43
+ * leak. The key↔name mapping is operator knowledge, kept out of the database.
44
+ *
45
+ * ## Shape
46
+ *
47
+ * priceObservations/{observationId}
48
+ *
49
+ * One flat collection, no subcollections. Rows are queried by `categoryId` and
50
+ * `seenAt` and never listed wholesale, so the composite index
51
+ * `(categoryId, seenAt desc)` is the only one it needs.
52
+ *
53
+ * @tag domain:price-index
54
+ * @tag layer:repository
55
+ * @tag pattern:repository
56
+ * @tag access:server-only
57
+ * @tag consumers:marketPriceRollup,price-observations CLI
58
+ * @tag sideEffects:writes priceObservations
59
+ */
60
+ import { BaseRepository } from "../../../providers/db-firebase";
61
+ /**
62
+ * What kind of event the sighting was.
63
+ *
64
+ * πŸ›‘ The distinction `price-bands.md` is built around: *"a sold-out row proves
65
+ * the item is GONE, not that it sold at that price"*, with 76–93% of crawled
66
+ * listings sold out. An ask and a sale are different events and a band that
67
+ * blends them describes neither β€” so the kind is recorded per row and the
68
+ * rollup groups on it rather than averaging across it.
69
+ */
70
+ export type PriceObservationKind = "ask" | "sale";
71
+ export interface PriceObservationDocument {
72
+ id: string;
73
+ /**
74
+ * The leaf this prices β€” a MODEL leaf wherever the sighting names a model.
75
+ *
76
+ * πŸ›‘ This is the field that makes the whole collection worth building. The
77
+ * seeded bands hang on ~60 generation leaves, so every one of the 1,904 model
78
+ * leaves inherits a number measured for its entire era. Recording the model
79
+ * id is what lets Dragoon MS and Strata Dragoon MS hold different prices,
80
+ * which they do in the real market.
81
+ */
82
+ categoryId: string;
83
+ /** Decimal rupees, tax-inclusive β€” `PRICE_GUIDANCE_TAX_BASIS`. */
84
+ amount: number;
85
+ currency: "INR";
86
+ kind: PriceObservationKind;
87
+ /**
88
+ * When the price was SEEN, not when the row was written.
89
+ *
90
+ * πŸ›‘ These come apart and the gap is the entire point. Back-filling a
91
+ * two-year-old crawl writes rows today that describe 2024 β€” using
92
+ * `createdAt` as the date would file every one of them inside the six-month
93
+ * window and report stale prices as current, which is the failure this
94
+ * collection exists to make impossible.
95
+ */
96
+ seenAt: Date;
97
+ /**
98
+ * `ProductDocument.condition` where the sighting stated one.
99
+ *
100
+ * Absent means NOT STATED β€” never "new". Condition is the dominant price
101
+ * variable in collectibles (hobson's "Without Box" group runs β‚Ή599 against
102
+ * β‚Ή4,499 for the same figures boxed), so guessing it would be guessing the
103
+ * price. Captured now even though the v1 rollup does not slice on it, because
104
+ * a column nobody recorded cannot be back-filled later.
105
+ */
106
+ condition?: string;
107
+ /** True when the listing was already sold out when seen. */
108
+ soldOut?: boolean;
109
+ /** Opaque, stable, never a competitor name. See the header. */
110
+ sourceKey: string;
111
+ /** Free-text operator note. Never rendered publicly. */
112
+ note?: string;
113
+ createdAt: Date;
114
+ }
115
+ export declare const PRICE_OBSERVATION_COLLECTION = "priceObservations";
116
+ /**
117
+ * Hard ceiling on one leaf's observation read.
118
+ *
119
+ * A leaf with more sightings than this has plenty for a stable band, so the cap
120
+ * costs no accuracy β€” it exists so a mis-tagged import cannot turn one rollup
121
+ * iteration into an unbounded read (Rule #6).
122
+ */
123
+ export declare const PRICE_OBSERVATION_SCAN_LIMIT = 500;
124
+ export declare class PriceObservationRepository extends BaseRepository<PriceObservationDocument> {
125
+ constructor();
126
+ /**
127
+ * Every sighting for one leaf, newest first, bounded.
128
+ *
129
+ * πŸ›‘ Ordered by `seenAt` and NOT filtered by it. The rollup needs both the
130
+ * recent window and the full history β€” it decides between them itself
131
+ * (`computeMarketStats`), because that decision has to be made once, in one
132
+ * place, or the job and the renderer will disagree about which window a
133
+ * figure came from. Pushing a date filter down here would make the historic
134
+ * fallback impossible without a second query.
135
+ *
136
+ * πŸ›‘ `orderBy("seenAt")` excludes any document LACKING the field β€” the
137
+ * optional-field trap of Root Cause #100, where a store's reviews read as
138
+ * zero because `itemsSold` had never been written. `seenAt` is required by
139
+ * the write path below precisely so that exclusion can never bite.
140
+ */
141
+ findForLeaf(categoryId: string, limit?: number): Promise<PriceObservationDocument[]>;
142
+ /**
143
+ * Every leaf that has at least one observation.
144
+ *
145
+ * πŸ›‘ Enumerates the ENTITIES, not the events β€” Root Cause #102's lesson, from
146
+ * a reconciler that built its tally from products that exist rather than
147
+ * categories that exist and so could only ever add. The rollup iterates this
148
+ * list, so a leaf whose last observation was retracted is still visited and
149
+ * its stale band cleared.
150
+ *
151
+ * `.select()` fetches no field data; only the ids are needed to group.
152
+ */
153
+ listLeafIdsWithObservations(scanLimit?: number): Promise<string[]>;
154
+ /**
155
+ * Record a sighting.
156
+ *
157
+ * πŸ›‘ `seenAt` is required from the caller and is NEVER defaulted to the write
158
+ * time. `BaseRepository.create` stamps `createdAt` itself β€” which is why the
159
+ * two fields exist separately and why this signature cannot accept one in
160
+ * place of the other. Back-filling an old crawl writes rows today that
161
+ * describe last year; conflating the two dates would file every one of them
162
+ * inside the six-month window.
163
+ */
164
+ record(input: Omit<PriceObservationDocument, "id" | "createdAt" | "updatedAt" | "currency">): Promise<PriceObservationDocument>;
165
+ }
166
+ export declare const priceObservationRepository: PriceObservationRepository;