@mohasinac/appkit 4.52.4 → 4.53.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (633) hide show
  1. package/dist/.build-stamp.json +4 -4
  2. package/dist/_internal/client/features/sticker-trace/canvas-codec.d.ts +2 -0
  3. package/dist/_internal/client/features/sticker-trace/canvas-codec.js +148 -0
  4. package/dist/_internal/server/features/auctions/actions.js +1 -1
  5. package/dist/_internal/server/features/categories/data.js +23 -7
  6. package/dist/_internal/server/features/checkout/actions.js +10 -3
  7. package/dist/_internal/server/features/checkout/data.d.ts +15 -0
  8. package/dist/_internal/server/features/checkout/data.js +20 -3
  9. package/dist/_internal/server/features/lottery/actions.js +9 -1
  10. package/dist/_internal/server/features/lottery/og.d.ts +40 -0
  11. package/dist/_internal/server/features/lottery/og.js +79 -0
  12. package/dist/_internal/server/features/messages/actions.js +4 -0
  13. package/dist/_internal/server/features/offers/adapters.js +17 -3
  14. package/dist/_internal/server/features/orders/actions.js +7 -2
  15. package/dist/_internal/server/features/raffle/actions.d.ts +19 -0
  16. package/dist/_internal/server/features/raffle/actions.js +25 -0
  17. package/dist/_internal/server/features/reviews/actions.js +1 -1
  18. package/dist/_internal/server/features/reviews/data.d.ts +1 -1
  19. package/dist/_internal/server/features/search/index-build/artifact-store.d.ts +89 -0
  20. package/dist/_internal/server/features/search/index-build/artifact-store.js +208 -0
  21. package/dist/_internal/server/features/search/index-build/build.d.ts +57 -0
  22. package/dist/_internal/server/features/search/index-build/build.js +285 -0
  23. package/dist/_internal/server/features/search/index-build/sources.d.ts +53 -0
  24. package/dist/_internal/server/features/search/index-build/sources.js +385 -0
  25. package/dist/_internal/server/features/search/search-sources.d.ts +0 -15
  26. package/dist/_internal/server/features/search/search-sources.js +142 -7
  27. package/dist/_internal/server/features/seo/robots.js +21 -1
  28. package/dist/_internal/server/features/seo/sitemap.js +1 -1
  29. package/dist/_internal/server/features/shared/listing-data-factory.js +16 -4
  30. package/dist/_internal/server/features/site-settings/adapters.d.ts +1 -1
  31. package/dist/_internal/server/features/site-settings/adapters.js +26 -0
  32. package/dist/_internal/server/features/sticker-trace/actions.d.ts +109 -0
  33. package/dist/_internal/server/features/sticker-trace/actions.js +172 -0
  34. package/dist/_internal/server/features/sticker-trace/adapters.d.ts +80 -0
  35. package/dist/_internal/server/features/sticker-trace/adapters.js +174 -0
  36. package/dist/_internal/server/features/sticker-trace/build-cut-file.d.ts +26 -0
  37. package/dist/_internal/server/features/sticker-trace/build-cut-file.js +127 -0
  38. package/dist/_internal/server/features/sticker-trace/coordinator.d.ts +27 -0
  39. package/dist/_internal/server/features/sticker-trace/coordinator.js +169 -0
  40. package/dist/_internal/server/features/sticker-trace/fanout.d.ts +43 -0
  41. package/dist/_internal/server/features/sticker-trace/fanout.js +180 -0
  42. package/dist/_internal/server/features/sticker-trace/finalizer.d.ts +25 -0
  43. package/dist/_internal/server/features/sticker-trace/finalizer.js +123 -0
  44. package/dist/_internal/server/features/sticker-trace/mosaic-inputs.d.ts +30 -0
  45. package/dist/_internal/server/features/sticker-trace/mosaic-inputs.js +112 -0
  46. package/dist/_internal/server/features/sticker-trace/progress.d.ts +81 -0
  47. package/dist/_internal/server/features/sticker-trace/progress.js +198 -0
  48. package/dist/_internal/server/features/sticker-trace/promote.d.ts +23 -0
  49. package/dist/_internal/server/features/sticker-trace/promote.js +193 -0
  50. package/dist/_internal/server/features/sticker-trace/shard.d.ts +50 -0
  51. package/dist/_internal/server/features/sticker-trace/shard.js +174 -0
  52. package/dist/_internal/server/features/sticker-trace/sheet-actions.d.ts +141 -0
  53. package/dist/_internal/server/features/sticker-trace/sheet-actions.js +279 -0
  54. package/dist/_internal/server/features/sticker-trace/storage.d.ts +135 -0
  55. package/dist/_internal/server/features/sticker-trace/storage.js +282 -0
  56. package/dist/_internal/server/functions/scheduled.d.ts +2 -1
  57. package/dist/_internal/server/functions/scheduled.js +25 -1
  58. package/dist/_internal/server/jobs/core/_event-ref.d.ts +51 -0
  59. package/dist/_internal/server/jobs/core/_event-ref.js +63 -0
  60. package/dist/_internal/server/jobs/core/assignSpinPrize.d.ts +18 -0
  61. package/dist/_internal/server/jobs/core/assignSpinPrize.js +77 -16
  62. package/dist/_internal/server/jobs/core/auctionSettlement.js +13 -4
  63. package/dist/_internal/server/jobs/core/autoPayoutEligibility.js +44 -15
  64. package/dist/_internal/server/jobs/core/bundleStockSync.js +3 -4
  65. package/dist/_internal/server/jobs/core/cleanupRtdbEvents.js +44 -0
  66. package/dist/_internal/server/jobs/core/event-draw-reasons.d.ts +78 -0
  67. package/dist/_internal/server/jobs/core/event-draw-reasons.js +133 -0
  68. package/dist/_internal/server/jobs/core/listingProcessor.js +28 -8
  69. package/dist/_internal/server/jobs/core/onOrderCreate.js +12 -1
  70. package/dist/_internal/server/jobs/core/onScamReportCreate.js +13 -2
  71. package/dist/_internal/server/jobs/core/onScamReportRejected.js +8 -2
  72. package/dist/_internal/server/jobs/core/onScamReportVerified.js +8 -2
  73. package/dist/_internal/server/jobs/core/onStoreWrite.js +11 -4
  74. package/dist/_internal/server/jobs/core/prizeDrawAssignWinner.js +5 -4
  75. package/dist/_internal/server/jobs/core/prizeDrawExpiryReveal.js +1 -4
  76. package/dist/_internal/server/jobs/core/prizeDrawSoldOutReveal.js +1 -2
  77. package/dist/_internal/server/jobs/core/promotions.js +7 -4
  78. package/dist/_internal/server/jobs/core/searchIndexBuild.d.ts +7 -0
  79. package/dist/_internal/server/jobs/core/searchIndexBuild.js +211 -0
  80. package/dist/_internal/server/jobs/core/triggerEventRaffle.d.ts +35 -0
  81. package/dist/_internal/server/jobs/core/triggerEventRaffle.js +213 -31
  82. package/dist/_internal/server/jobs/core/weeklyPayoutEligibility.js +20 -14
  83. package/dist/_internal/server/jobs/handlers/assignSpinPrize.d.ts +8 -0
  84. package/dist/_internal/server/jobs/handlers/assignSpinPrize.js +15 -15
  85. package/dist/_internal/server/jobs/handlers/index.d.ts +1 -0
  86. package/dist/_internal/server/jobs/handlers/index.js +1 -0
  87. package/dist/_internal/server/jobs/handlers/onCategoryWrite.js +18 -0
  88. package/dist/_internal/server/jobs/handlers/onProductWrite.js +18 -0
  89. package/dist/_internal/server/jobs/handlers/onReviewWrite.js +18 -0
  90. package/dist/_internal/server/jobs/handlers/onScamReportUpdate.js +18 -0
  91. package/dist/_internal/server/jobs/handlers/onStoreWrite.js +18 -0
  92. package/dist/_internal/server/jobs/handlers/searchIndexBuild.d.ts +2 -0
  93. package/dist/_internal/server/jobs/handlers/searchIndexBuild.js +2 -0
  94. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.d.ts +8 -0
  95. package/dist/_internal/server/jobs/handlers/triggerEventRaffle.js +15 -6
  96. package/dist/_internal/shared/actions/action-registry.js +29 -0
  97. package/dist/_internal/shared/cache/revalidate-targets.d.ts +0 -1
  98. package/dist/_internal/shared/cache/revalidate-targets.js +10 -8
  99. package/dist/_internal/shared/features/events/config.d.ts +14 -0
  100. package/dist/_internal/shared/features/events/config.js +14 -0
  101. package/dist/_internal/shared/features/notifications/action-url.js +8 -0
  102. package/dist/_internal/shared/features/site-settings/types.d.ts +11 -0
  103. package/dist/_internal/shared/features/sticker-trace/codec.d.ts +81 -0
  104. package/dist/_internal/shared/features/sticker-trace/codec.js +30 -0
  105. package/dist/_internal/shared/features/sticker-trace/config.d.ts +81 -0
  106. package/dist/_internal/shared/features/sticker-trace/config.js +177 -0
  107. package/dist/_internal/shared/features/sticker-trace/cut-contour.d.ts +20 -0
  108. package/dist/_internal/shared/features/sticker-trace/cut-contour.js +60 -0
  109. package/dist/_internal/shared/features/sticker-trace/export/build-export-set.d.ts +65 -0
  110. package/dist/_internal/shared/features/sticker-trace/export/build-export-set.js +176 -0
  111. package/dist/_internal/shared/features/sticker-trace/export/cut-svg.d.ts +56 -0
  112. package/dist/_internal/shared/features/sticker-trace/export/cut-svg.js +120 -0
  113. package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.d.ts +92 -0
  114. package/dist/_internal/shared/features/sticker-trace/export/machine-profiles.js +153 -0
  115. package/dist/_internal/shared/features/sticker-trace/export/nesting.d.ts +51 -0
  116. package/dist/_internal/shared/features/sticker-trace/export/nesting.js +110 -0
  117. package/dist/_internal/shared/features/sticker-trace/export/page-sizes.d.ts +68 -0
  118. package/dist/_internal/shared/features/sticker-trace/export/page-sizes.js +223 -0
  119. package/dist/_internal/shared/features/sticker-trace/export/part-tiling.d.ts +51 -0
  120. package/dist/_internal/shared/features/sticker-trace/export/part-tiling.js +178 -0
  121. package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.d.ts +26 -0
  122. package/dist/_internal/shared/features/sticker-trace/export/simplify-budget.js +134 -0
  123. package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.d.ts +40 -0
  124. package/dist/_internal/shared/features/sticker-trace/export/vectorise-art.js +195 -0
  125. package/dist/_internal/shared/features/sticker-trace/index.d.ts +2 -0
  126. package/dist/_internal/shared/features/sticker-trace/index.js +59 -0
  127. package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.d.ts +11 -0
  128. package/dist/_internal/shared/features/sticker-trace/math/catmull-rom.js +66 -0
  129. package/dist/_internal/shared/features/sticker-trace/math/edt.d.ts +27 -0
  130. package/dist/_internal/shared/features/sticker-trace/math/edt.js +165 -0
  131. package/dist/_internal/shared/features/sticker-trace/math/label.d.ts +15 -0
  132. package/dist/_internal/shared/features/sticker-trace/math/label.js +156 -0
  133. package/dist/_internal/shared/features/sticker-trace/math/marching-squares.d.ts +16 -0
  134. package/dist/_internal/shared/features/sticker-trace/math/marching-squares.js +242 -0
  135. package/dist/_internal/shared/features/sticker-trace/math/morphology.d.ts +46 -0
  136. package/dist/_internal/shared/features/sticker-trace/math/morphology.js +168 -0
  137. package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.d.ts +88 -0
  138. package/dist/_internal/shared/features/sticker-trace/math/rasterize-path.js +286 -0
  139. package/dist/_internal/shared/features/sticker-trace/math/rdp.d.ts +10 -0
  140. package/dist/_internal/shared/features/sticker-trace/math/rdp.js +74 -0
  141. package/dist/_internal/shared/features/sticker-trace/resume.d.ts +50 -0
  142. package/dist/_internal/shared/features/sticker-trace/resume.js +160 -0
  143. package/dist/_internal/shared/features/sticker-trace/trace/cluster.d.ts +20 -0
  144. package/dist/_internal/shared/features/sticker-trace/trace/cluster.js +132 -0
  145. package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.d.ts +28 -0
  146. package/dist/_internal/shared/features/sticker-trace/trace/cross-profile.js +120 -0
  147. package/dist/_internal/shared/features/sticker-trace/trace/flood.d.ts +21 -0
  148. package/dist/_internal/shared/features/sticker-trace/trace/flood.js +104 -0
  149. package/dist/_internal/shared/features/sticker-trace/trace/geometry.d.ts +3 -0
  150. package/dist/_internal/shared/features/sticker-trace/trace/geometry.js +140 -0
  151. package/dist/_internal/shared/features/sticker-trace/trace/line-filter.d.ts +15 -0
  152. package/dist/_internal/shared/features/sticker-trace/trace/line-filter.js +138 -0
  153. package/dist/_internal/shared/features/sticker-trace/trace/merge.d.ts +27 -0
  154. package/dist/_internal/shared/features/sticker-trace/trace/merge.js +187 -0
  155. package/dist/_internal/shared/features/sticker-trace/trace/paper.d.ts +32 -0
  156. package/dist/_internal/shared/features/sticker-trace/trace/paper.js +147 -0
  157. package/dist/_internal/shared/features/sticker-trace/trace/prefilter.d.ts +30 -0
  158. package/dist/_internal/shared/features/sticker-trace/trace/prefilter.js +125 -0
  159. package/dist/_internal/shared/features/sticker-trace/trace/quantise.d.ts +10 -0
  160. package/dist/_internal/shared/features/sticker-trace/trace/quantise.js +153 -0
  161. package/dist/_internal/shared/features/sticker-trace/trace/ridge.d.ts +88 -0
  162. package/dist/_internal/shared/features/sticker-trace/trace/ridge.js +344 -0
  163. package/dist/_internal/shared/features/sticker-trace/trace/segment.d.ts +10 -0
  164. package/dist/_internal/shared/features/sticker-trace/trace/segment.js +251 -0
  165. package/dist/_internal/shared/features/sticker-trace/types.d.ts +197 -0
  166. package/dist/_internal/shared/features/sticker-trace/types.js +49 -0
  167. package/dist/_internal/shared/listing-types/_registry.d.ts +11 -0
  168. package/dist/_internal/shared/listing-types/art/config.d.ts +1 -0
  169. package/dist/_internal/shared/listing-types/art/config.js +1 -0
  170. package/dist/_internal/shared/listing-types/auction/config.d.ts +1 -0
  171. package/dist/_internal/shared/listing-types/auction/config.js +1 -0
  172. package/dist/_internal/shared/listing-types/classified/config.d.ts +1 -0
  173. package/dist/_internal/shared/listing-types/classified/config.js +1 -0
  174. package/dist/_internal/shared/listing-types/digital-code/config.d.ts +1 -0
  175. package/dist/_internal/shared/listing-types/digital-code/config.js +1 -0
  176. package/dist/_internal/shared/listing-types/feature-flags.d.ts +7 -0
  177. package/dist/_internal/shared/listing-types/feature-flags.js +9 -0
  178. package/dist/_internal/shared/listing-types/live/config.d.ts +1 -0
  179. package/dist/_internal/shared/listing-types/live/config.js +1 -0
  180. package/dist/_internal/shared/listing-types/pre-order/config.d.ts +1 -0
  181. package/dist/_internal/shared/listing-types/pre-order/config.js +1 -0
  182. package/dist/_internal/shared/listing-types/prize-draw/config.d.ts +1 -0
  183. package/dist/_internal/shared/listing-types/prize-draw/config.js +1 -0
  184. package/dist/_internal/shared/listing-types/standard/config.d.ts +1 -0
  185. package/dist/_internal/shared/listing-types/standard/config.js +1 -0
  186. package/dist/_internal/shared/listing-types/stickers/config.d.ts +1 -0
  187. package/dist/_internal/shared/listing-types/stickers/config.js +1 -0
  188. package/dist/_internal/shared/media/limits.d.ts +12 -2
  189. package/dist/_internal/shared/media/limits.js +12 -2
  190. package/dist/_internal/shared/search/artifact-url.d.ts +35 -0
  191. package/dist/_internal/shared/search/artifact-url.js +119 -0
  192. package/dist/_internal/shared/search/index-record.d.ts +68 -0
  193. package/dist/_internal/shared/search/index-record.js +96 -0
  194. package/dist/_internal/shared/search/match.d.ts +65 -0
  195. package/dist/_internal/shared/search/match.js +263 -0
  196. package/dist/client.d.ts +8 -7
  197. package/dist/client.js +12 -5
  198. package/dist/configs/eslint.js +22 -2
  199. package/dist/constants/api-endpoints.d.ts +68 -0
  200. package/dist/constants/api-endpoints.js +32 -0
  201. package/dist/constants/field-names.d.ts +7 -1
  202. package/dist/constants/field-names.js +36 -1
  203. package/dist/contracts/repository.d.ts +8 -7
  204. package/dist/contracts/search.d.ts +2 -2
  205. package/dist/contracts/table.d.ts +3 -3
  206. package/dist/contracts/table.js +3 -3
  207. package/dist/core/contact-submissions.repository.js +2 -2
  208. package/dist/core/integration-keys.js +7 -1
  209. package/dist/core/newsletter.repository.js +2 -2
  210. package/dist/core/site-config.js +1 -1
  211. package/dist/errors/error-codes.d.ts +30 -0
  212. package/dist/errors/error-codes.js +30 -0
  213. package/dist/errors/error-display-map.js +40 -1
  214. package/dist/features/account/api/[userId]/addresses/route.js +1 -1
  215. package/dist/features/account/components/UserBidsView.js +1 -1
  216. package/dist/features/account/components/index.d.ts +0 -8
  217. package/dist/features/account/components/index.js +0 -4
  218. package/dist/features/addresses/repository/addresses.repository.d.ts +17 -6
  219. package/dist/features/addresses/repository/addresses.repository.js +65 -9
  220. package/dist/features/addresses/schemas/firestore.d.ts +17 -6
  221. package/dist/features/admin/actions/notification-actions.js +15 -6
  222. package/dist/features/admin/api/bids/route.js +1 -1
  223. package/dist/features/admin/api/coupons/route.js +1 -1
  224. package/dist/features/admin/api/products/route.js +1 -1
  225. package/dist/features/admin/api/reviews/route.js +1 -1
  226. package/dist/features/admin/components/AdminAdEditorView.js +19 -5
  227. package/dist/features/admin/components/AdminAuditLogView.js +1 -1
  228. package/dist/features/admin/components/AdminBlogEditorView.js +0 -1
  229. package/dist/features/admin/components/AdminBlogView.js +1 -1
  230. package/dist/features/admin/components/AdminBrandsView.js +17 -1
  231. package/dist/features/admin/components/AdminCategoryEditorView.js +0 -1
  232. package/dist/features/admin/components/AdminContactView.js +1 -1
  233. package/dist/features/admin/components/AdminEmployeeEditorView.js +1 -3
  234. package/dist/features/admin/components/AdminNewsletterView.js +1 -1
  235. package/dist/features/admin/components/AdminOrdersView.js +9 -2
  236. package/dist/features/admin/components/AdminPayoutsView.js +1 -1
  237. package/dist/features/admin/components/AdminPrizeDrawsView.js +11 -0
  238. package/dist/features/admin/components/AdminProductEditorView.d.ts +10 -1
  239. package/dist/features/admin/components/AdminProductEditorView.js +24 -37
  240. package/dist/features/admin/components/AdminProductsView.js +10 -3
  241. package/dist/features/admin/components/AdminScammersView.js +1 -1
  242. package/dist/features/admin/components/AdminSectionsView.js +21 -5
  243. package/dist/features/admin/components/AdminSessionsView.js +1 -1
  244. package/dist/features/admin/components/AdminSublistingCategoriesView.js +22 -1
  245. package/dist/features/admin/components/AdminTeamView.js +1 -1
  246. package/dist/features/admin/components/AdminTesterChecklistItemEditorView.js +0 -1
  247. package/dist/features/admin/components/AdminUsersView.js +1 -1
  248. package/dist/features/admin/components/DataListingView.d.ts +2 -1
  249. package/dist/features/admin/components/DataListingView.js +9 -4
  250. package/dist/features/admin/components/analytics/AdminPageViewsReportView.js +0 -2
  251. package/dist/features/admin/hooks/useAdminListing.d.ts +1 -1
  252. package/dist/features/admin/repository/site-settings.repository.js +8 -3
  253. package/dist/features/admin/schemas/firestore.d.ts +62 -1
  254. package/dist/features/admin/schemas/firestore.js +32 -0
  255. package/dist/features/admin/types/index.d.ts +1 -1
  256. package/dist/features/auctions/actions/bid-actions.d.ts +2 -2
  257. package/dist/features/auctions/actions/bid-actions.js +16 -4
  258. package/dist/features/auctions/api/route.js +1 -1
  259. package/dist/features/auctions/components/AuctionBottomActions.js +4 -0
  260. package/dist/features/auctions/repository/bid.repository.d.ts +46 -4
  261. package/dist/features/auctions/repository/bid.repository.js +114 -13
  262. package/dist/features/auctions/schemas/firestore.d.ts +24 -4
  263. package/dist/features/auctions/schemas/firestore.js +1 -0
  264. package/dist/features/auth/actions/profile-actions.js +30 -4
  265. package/dist/features/auth/components/index.d.ts +0 -1
  266. package/dist/features/auth/components/index.js +0 -1
  267. package/dist/features/auth/repository/session.repository.d.ts +17 -3
  268. package/dist/features/auth/repository/session.repository.js +46 -5
  269. package/dist/features/auth/repository/token.repository.d.ts +27 -4
  270. package/dist/features/auth/repository/token.repository.js +65 -11
  271. package/dist/features/auth/repository/user.repository.d.ts +33 -7
  272. package/dist/features/auth/repository/user.repository.js +100 -15
  273. package/dist/features/auth/schemas/firestore.d.ts +63 -12
  274. package/dist/features/auth/schemas/firestore.js +11 -0
  275. package/dist/features/before-after/api/route.js +1 -1
  276. package/dist/features/blog/components/BlogListView.js +16 -3
  277. package/dist/features/blog/components/BlogPostView.js +1 -1
  278. package/dist/features/blog/schemas/index.d.ts +2 -2
  279. package/dist/features/cart/api/route.js +1 -1
  280. package/dist/features/catalogue/repository/catalogue.repository.d.ts +7 -7
  281. package/dist/features/catalogue/repository/catalogue.repository.js +28 -10
  282. package/dist/features/catalogue/schemas/firestore.d.ts +11 -0
  283. package/dist/features/categories/api/route.js +10 -10
  284. package/dist/features/categories/components/CategoryDetailPageView.js +35 -5
  285. package/dist/features/categories/components/CategorySelectorCreate.js +0 -1
  286. package/dist/features/categories/components/index.d.ts +0 -2
  287. package/dist/features/categories/components/index.js +0 -1
  288. package/dist/features/categories/repository/categories.repository.d.ts +1 -0
  289. package/dist/features/categories/repository/categories.repository.js +31 -1
  290. package/dist/features/categories/schemas/index.d.ts +2 -2
  291. package/dist/features/categories/utils/taxonomy-visual.d.ts +21 -0
  292. package/dist/features/categories/utils/taxonomy-visual.js +58 -0
  293. package/dist/features/checkout/actions/checkout-value-otp-actions.js +8 -1
  294. package/dist/features/collections/api/[slug]/route.js +3 -3
  295. package/dist/features/collections/api/route.js +1 -1
  296. package/dist/features/consultation/api/route.js +1 -1
  297. package/dist/features/consultation/repository/consultations.repository.js +1 -1
  298. package/dist/features/consultation/types/index.d.ts +1 -1
  299. package/dist/features/contact/components/ContactForm.js +1 -3
  300. package/dist/features/corporate/api/route.js +1 -1
  301. package/dist/features/corporate/repository/corporate.repository.js +1 -1
  302. package/dist/features/corporate/types/index.d.ts +1 -1
  303. package/dist/features/cron/jobs/auction-expiry.job.js +1 -1
  304. package/dist/features/cron/jobs/preorder-reminder.job.js +1 -1
  305. package/dist/features/events/actions/event-actions.d.ts +14 -0
  306. package/dist/features/events/actions/event-actions.js +82 -3
  307. package/dist/features/events/api/[id]/route.js +2 -2
  308. package/dist/features/events/components/AdminEventEditorView.js +22 -3
  309. package/dist/features/events/components/AdminEventsView.js +32 -8
  310. package/dist/features/events/components/EventRafflesSection.d.ts +34 -1
  311. package/dist/features/events/components/EventRafflesSection.js +52 -3
  312. package/dist/features/events/components/SpinWheelView.d.ts +2 -2
  313. package/dist/features/events/components/SpinWheelView.js +41 -4
  314. package/dist/features/events/repository/event-entry.repository.d.ts +14 -2
  315. package/dist/features/events/repository/event-entry.repository.js +41 -7
  316. package/dist/features/events/schemas/firestore.d.ts +17 -4
  317. package/dist/features/events/types/index.d.ts +37 -0
  318. package/dist/features/faq/repository/faqs.repository.js +1 -1
  319. package/dist/features/faq/schemas/index.d.ts +5 -2
  320. package/dist/features/faq/schemas/index.js +6 -0
  321. package/dist/features/faq/types/index.d.ts +0 -1
  322. package/dist/features/grouped/components/GroupedListingEditorView.js +17 -2
  323. package/dist/features/homepage/api/carousel/[id]/route.js +1 -1
  324. package/dist/features/homepage/api/carousel/route.js +4 -4
  325. package/dist/features/homepage/api/route.js +2 -2
  326. package/dist/features/homepage/api/sections/route.js +1 -1
  327. package/dist/features/homepage/components/AnnouncementBar.d.ts +1 -9
  328. package/dist/features/homepage/components/AnnouncementBar.js +13 -19
  329. package/dist/features/homepage/components/MarketplaceHomepageView.js +6 -1
  330. package/dist/features/homepage/components/index.d.ts +0 -2
  331. package/dist/features/homepage/components/index.js +0 -1
  332. package/dist/features/homepage/repository/homepage.repository.js +1 -1
  333. package/dist/features/homepage/schemas/firestore.d.ts +6 -0
  334. package/dist/features/homepage/schemas/index.d.ts +10 -10
  335. package/dist/features/layout/AppLayoutShell.d.ts +12 -3
  336. package/dist/features/layout/AppLayoutShell.js +65 -13
  337. package/dist/features/layout/BottomActionsContext.d.ts +16 -0
  338. package/dist/features/layout/BottomActionsContext.js +5 -0
  339. package/dist/features/layout/BottomChrome.js +48 -9
  340. package/dist/features/layout/BottomNavbar.d.ts +14 -0
  341. package/dist/features/layout/BottomNavbar.js +43 -3
  342. package/dist/features/layout/MainNavbar.d.ts +11 -0
  343. package/dist/features/layout/NavbarLayout.d.ts +3 -0
  344. package/dist/features/layout/NavbarLayout.js +88 -11
  345. package/dist/features/layout/SideActionDock.d.ts +1 -0
  346. package/dist/features/layout/SideActionDock.js +219 -0
  347. package/dist/features/layout/TitleBarLayout.d.ts +1 -3
  348. package/dist/features/layout/TitleBarLayout.js +3 -4
  349. package/dist/features/layout/cta-anchor.d.ts +31 -0
  350. package/dist/features/layout/cta-anchor.js +31 -0
  351. package/dist/features/layout/hooks/useBottomActions.d.ts +7 -0
  352. package/dist/features/layout/hooks/useBottomActions.js +6 -1
  353. package/dist/features/layout/index.d.ts +9 -1
  354. package/dist/features/layout/index.js +9 -1
  355. package/dist/features/layout/useCtaReveal.d.ts +24 -0
  356. package/dist/features/layout/useCtaReveal.js +82 -0
  357. package/dist/features/layout/useHoldToOpen.d.ts +41 -0
  358. package/dist/features/layout/useHoldToOpen.js +84 -0
  359. package/dist/features/lottery/repository/lottery-entry.repository.d.ts +18 -3
  360. package/dist/features/lottery/repository/lottery-entry.repository.js +48 -6
  361. package/dist/features/lottery/schemas/firestore.d.ts +33 -5
  362. package/dist/features/loyalty/api/balance/route.js +1 -1
  363. package/dist/features/loyalty/api/history/route.js +1 -1
  364. package/dist/features/media/MediaImage.js +0 -1
  365. package/dist/features/media/MediaPickerModal.js +7 -1
  366. package/dist/features/media/modals/ImageEditor.js +5 -1
  367. package/dist/features/media/modals/VideoThumbnailSelector.js +17 -1
  368. package/dist/features/media/repository/media-assets.repository.d.ts +19 -0
  369. package/dist/features/media/upload/CameraCapture.js +16 -1
  370. package/dist/features/media/upload/ImageUpload.js +42 -4
  371. package/dist/features/media/upload/MediaUploadField.js +69 -13
  372. package/dist/features/media/upload/downscale-image.d.ts +56 -0
  373. package/dist/features/media/upload/downscale-image.js +150 -0
  374. package/dist/features/messages/components/ConversationThreadView.js +45 -2
  375. package/dist/features/messages/components/MessageSellerButton.d.ts +15 -1
  376. package/dist/features/messages/components/MessageSellerButton.js +10 -2
  377. package/dist/features/messages/schemas/firestore.d.ts +15 -0
  378. package/dist/features/orders/hooks/useOrders.js +4 -2
  379. package/dist/features/orders/repository/orders.repository.d.ts +15 -4
  380. package/dist/features/orders/repository/orders.repository.js +52 -20
  381. package/dist/features/orders/schemas/firestore.d.ts +15 -4
  382. package/dist/features/orders/schemas/index.d.ts +8 -8
  383. package/dist/features/orders/types/index.d.ts +2 -2
  384. package/dist/features/payments/repository/payments.repository.js +2 -2
  385. package/dist/features/payments/repository/payout.repository.d.ts +23 -6
  386. package/dist/features/payments/repository/payout.repository.js +63 -23
  387. package/dist/features/payments/repository/saved-payment-methods.repository.d.ts +17 -6
  388. package/dist/features/payments/repository/saved-payment-methods.repository.js +59 -11
  389. package/dist/features/payments/schemas/firestore.d.ts +14 -3
  390. package/dist/features/payments/schemas/saved-methods-firestore.d.ts +12 -1
  391. package/dist/features/pre-orders/api/[slug]/route.js +1 -1
  392. package/dist/features/pre-orders/api/admin/route.js +1 -1
  393. package/dist/features/pre-orders/api/route.js +1 -1
  394. package/dist/features/pre-orders/repository/pre-orders.repository.d.ts +3 -3
  395. package/dist/features/pre-orders/repository/pre-orders.repository.js +6 -6
  396. package/dist/features/products/api/[id]/route.js +1 -1
  397. package/dist/features/products/components/ListingBottomActions.js +4 -0
  398. package/dist/features/products/components/ProductDetailPageView.js +2 -1
  399. package/dist/features/products/components/ProductGalleryClient.js +2 -2
  400. package/dist/features/products/components/ProductGrid.js +12 -1
  401. package/dist/features/products/components/index.d.ts +0 -2
  402. package/dist/features/products/components/index.js +0 -1
  403. package/dist/features/products/config/listing-type-listing-config.d.ts +0 -3
  404. package/dist/features/products/config/listing-type-listing-config.js +27 -2
  405. package/dist/features/products/constants/listing-tabs.d.ts +9 -0
  406. package/dist/features/products/constants/listing-tabs.js +3 -2
  407. package/dist/features/products/hooks/useProducts.js +2 -2
  408. package/dist/features/products/repository/products.repository.js +13 -1
  409. package/dist/features/products/schemas/index.d.ts +2 -2
  410. package/dist/features/products/types/index.d.ts +1 -1
  411. package/dist/features/promotions/api/route.js +1 -1
  412. package/dist/features/reviews/actions/review-actions.d.ts +6 -6
  413. package/dist/features/reviews/actions/review-actions.js +22 -8
  414. package/dist/features/reviews/components/ReviewDetailPageView.js +13 -1
  415. package/dist/features/reviews/components/ReviewsIndexPageView.js +21 -4
  416. package/dist/features/reviews/repository/reviews.repository.d.ts +19 -7
  417. package/dist/features/reviews/repository/reviews.repository.js +63 -12
  418. package/dist/features/reviews/schemas/firestore.d.ts +13 -2
  419. package/dist/features/reviews/schemas/index.d.ts +2 -2
  420. package/dist/features/scams/actions/scam-actions.js +16 -4
  421. package/dist/features/search/components/SearchResultsPageView.js +2 -1
  422. package/dist/features/search/hooks/useSearchIndex.d.ts +34 -0
  423. package/dist/features/search/hooks/useSearchIndex.js +172 -0
  424. package/dist/features/search/repository/search.repository.js +2 -2
  425. package/dist/features/seller/actions/offer-actions.d.ts +5 -5
  426. package/dist/features/seller/actions/offer-actions.js +38 -8
  427. package/dist/features/seller/actions/seller-actions.js +34 -7
  428. package/dist/features/seller/api/coupons/route.js +1 -1
  429. package/dist/features/seller/api/products/route.js +1 -1
  430. package/dist/features/seller/api/store/route.js +1 -1
  431. package/dist/features/seller/components/SellerBidsView.js +1 -1
  432. package/dist/features/seller/components/SellerCouponsView.js +1 -1
  433. package/dist/features/seller/components/SellerListingTypeView.js +9 -1
  434. package/dist/features/seller/components/SellerOrdersView.js +1 -1
  435. package/dist/features/seller/components/SellerPayoutSettingsView.js +0 -1
  436. package/dist/features/seller/components/SellerPayoutsView.js +1 -1
  437. package/dist/features/seller/components/SellerProductShell.js +0 -1
  438. package/dist/features/seller/components/SellerProductsView.js +1 -1
  439. package/dist/features/seller/components/SellerShippingView.js +0 -1
  440. package/dist/features/seller/components/SellerStorefrontView.js +0 -1
  441. package/dist/features/seller/components/index.d.ts +0 -4
  442. package/dist/features/seller/components/index.js +0 -2
  443. package/dist/features/seller/repository/offer.repository.d.ts +15 -3
  444. package/dist/features/seller/repository/offer.repository.js +43 -7
  445. package/dist/features/seller/schemas/firestore.d.ts +14 -3
  446. package/dist/features/seller/schemas/index.d.ts +4 -4
  447. package/dist/features/seller/schemas/order-forms.d.ts +39 -0
  448. package/dist/features/seller/schemas/order-forms.js +37 -0
  449. package/dist/features/shell/FormShell.js +6 -2
  450. package/dist/features/shell/QuickFormDrawer.js +15 -1
  451. package/dist/features/shipments/components/AdminShipmentEditorView.js +0 -1
  452. package/dist/features/shipments/components/AdminShipmentsView.js +1 -1
  453. package/dist/features/sticker-trace/components/StickerCommitPanel.d.ts +30 -0
  454. package/dist/features/sticker-trace/components/StickerCommitPanel.js +93 -0
  455. package/dist/features/sticker-trace/components/StickerPrintPanel.d.ts +33 -0
  456. package/dist/features/sticker-trace/components/StickerPrintPanel.js +104 -0
  457. package/dist/features/sticker-trace/components/StickerRetryPanel.d.ts +20 -0
  458. package/dist/features/sticker-trace/components/StickerRetryPanel.js +128 -0
  459. package/dist/features/sticker-trace/components/StickerRunHistoryPanel.d.ts +45 -0
  460. package/dist/features/sticker-trace/components/StickerRunHistoryPanel.js +84 -0
  461. package/dist/features/sticker-trace/components/StickerTraceStudio.d.ts +27 -0
  462. package/dist/features/sticker-trace/components/StickerTraceStudio.js +171 -0
  463. package/dist/features/sticker-trace/constants/help-content.d.ts +23 -0
  464. package/dist/features/sticker-trace/constants/help-content.js +172 -0
  465. package/dist/features/sticker-trace/hooks/useLiveTrace.d.ts +27 -0
  466. package/dist/features/sticker-trace/hooks/useLiveTrace.js +160 -0
  467. package/dist/features/sticker-trace/hooks/useStickerTraceProgress.d.ts +22 -0
  468. package/dist/features/sticker-trace/hooks/useStickerTraceProgress.js +106 -0
  469. package/dist/features/sticker-trace/repository/sticker-sheets.repository.d.ts +113 -0
  470. package/dist/features/sticker-trace/repository/sticker-sheets.repository.js +146 -0
  471. package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.d.ts +132 -0
  472. package/dist/features/sticker-trace/repository/sticker-trace-runs.repository.js +174 -0
  473. package/dist/features/sticker-trace/schemas/firestore.d.ts +248 -0
  474. package/dist/features/sticker-trace/schemas/firestore.js +79 -0
  475. package/dist/features/sticker-trace/server.d.ts +8 -0
  476. package/dist/features/sticker-trace/server.js +51 -0
  477. package/dist/features/stores/actions/store-query-actions.d.ts +2 -2
  478. package/dist/features/stores/actions/store-query-actions.js +7 -2
  479. package/dist/features/stores/api/[storeSlug]/auctions/route.js +3 -3
  480. package/dist/features/stores/api/[storeSlug]/products/route.js +3 -3
  481. package/dist/features/stores/api/[storeSlug]/reviews/route.js +1 -1
  482. package/dist/features/stores/api/[storeSlug]/route.js +1 -1
  483. package/dist/features/stores/components/StoreNavTabs.js +17 -2
  484. package/dist/features/stores/manifest.js +8 -4
  485. package/dist/features/support/repository/support.repository.d.ts +14 -3
  486. package/dist/features/support/repository/support.repository.js +93 -13
  487. package/dist/features/support/schemas/firestore.d.ts +16 -5
  488. package/dist/features/tester/seed-data/authored/admin__events-raffles-spin.js +61 -19
  489. package/dist/features/tester/seed-data/authored/buying__browsing-search.js +19 -3
  490. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.d.ts +2 -0
  491. package/dist/features/tester/seed-data/authored/content-discovery__authenticity-checker.js +243 -0
  492. package/dist/features/tester/seed-data/authored/content-discovery__events.js +36 -10
  493. package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.d.ts +2 -0
  494. package/dist/features/tester/seed-data/authored/content-discovery__overflowing-pill-rows.js +209 -0
  495. package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.d.ts +2 -0
  496. package/dist/features/tester/seed-data/authored/design-ux__header-brand-mark-centring.js +90 -0
  497. package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.d.ts +2 -0
  498. package/dist/features/tester/seed-data/authored/design-ux__header-controls-and-messages.js +85 -0
  499. package/dist/features/tester/seed-data/authored/index.js +10 -0
  500. package/dist/features/tester/seed-data/authored/selling__media-limits.js +19 -17
  501. package/dist/features/tester/seed-data/authored/selling__product-upload-details.js +10 -5
  502. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.d.ts +2 -0
  503. package/dist/features/tester/seed-data/authored/tools__sticker-tracer.js +283 -0
  504. package/dist/features/tester/seed-data/tester-checklist-seed-data.js +235 -22
  505. package/dist/index.d.ts +10 -23
  506. package/dist/index.js +27 -19
  507. package/dist/next/api/routeHandler.js +9 -0
  508. package/dist/next/routing/require-record.d.ts +13 -0
  509. package/dist/next/routing/require-record.js +62 -0
  510. package/dist/next/routing/route-map.d.ts +4 -4
  511. package/dist/next/routing/route-map.js +25 -3
  512. package/dist/providers/db-firebase/base.d.ts +4 -2
  513. package/dist/providers/db-firebase/base.js +21 -5
  514. package/dist/providers/db-firebase/base.repository.d.ts +11 -9
  515. package/dist/providers/db-firebase/base.repository.js +19 -0
  516. package/dist/providers/db-firebase/rtdb-paths.d.ts +12 -0
  517. package/dist/providers/db-firebase/rtdb-paths.js +12 -0
  518. package/dist/react/ErrorBoundary.js +6 -1
  519. package/dist/react/hooks/realtime-event-constants.d.ts +1 -0
  520. package/dist/react/hooks/realtime-event-constants.js +8 -0
  521. package/dist/react/hooks/useEntityDelete.js +5 -1
  522. package/dist/react/hooks/useMessage.js +5 -1
  523. package/dist/repositories/index.d.ts +2 -2
  524. package/dist/repositories/index.js +6 -1
  525. package/dist/schemas/registry.d.ts +13 -20
  526. package/dist/schemas/registry.js +31 -14
  527. package/dist/schemas/webhooks/google-oauth.d.ts +2 -2
  528. package/dist/security/encrypted.d.ts +117 -0
  529. package/dist/security/encrypted.js +100 -0
  530. package/dist/security/index.d.ts +5 -2
  531. package/dist/security/index.js +16 -2
  532. package/dist/security/pii-accessors.d.ts +71 -0
  533. package/dist/security/pii-accessors.js +150 -0
  534. package/dist/security/pii-encrypt.d.ts +0 -8
  535. package/dist/security/pii-encrypt.js +22 -15
  536. package/dist/security/pii-mask.d.ts +18 -0
  537. package/dist/security/pii-mask.js +27 -0
  538. package/dist/seed/_helpers/search-txt-wrappers.d.ts +6 -6
  539. package/dist/seed/_helpers/search-txt-wrappers.js +22 -0
  540. package/dist/seed/addresses-seed-data.d.ts +2 -2
  541. package/dist/seed/authenticity-knowledge-seed-data.d.ts +34 -0
  542. package/dist/seed/authenticity-knowledge-seed-data.js +1277 -0
  543. package/dist/seed/bids-seed-data.d.ts +2 -2
  544. package/dist/seed/bids-seed-data.js +23 -2
  545. package/dist/seed/events-seed-data.d.ts +2 -2
  546. package/dist/seed/events-seed-data.js +5 -0
  547. package/dist/seed/faq-seed-data.js +175 -5
  548. package/dist/seed/homepage-sections-seed-data.js +39 -23
  549. package/dist/seed/index.d.ts +1 -0
  550. package/dist/seed/index.js +1 -0
  551. package/dist/seed/lottery-entries-seed-data.d.ts +2 -2
  552. package/dist/seed/offers-seed-data.d.ts +2 -2
  553. package/dist/seed/offers-seed-data.js +1 -1
  554. package/dist/seed/orders-seed-data.d.ts +2 -2
  555. package/dist/seed/payouts-seed-data.d.ts +2 -2
  556. package/dist/seed/payouts-seed-data.js +1 -1
  557. package/dist/seed/reviews-seed-data.d.ts +1 -2
  558. package/dist/seed/seed-purpose.js +7 -0
  559. package/dist/seed/sessions-seed-data.d.ts +2 -2
  560. package/dist/seed/sessions-seed-data.js +5 -0
  561. package/dist/seed/site-settings-seed-data.js +1 -1
  562. package/dist/seed/support-tickets-seed-data.d.ts +2 -2
  563. package/dist/seed/support-tickets-seed-data.js +1 -1
  564. package/dist/seed/users-seed-data.d.ts +2 -2
  565. package/dist/seed/users-seed-data.js +1 -1
  566. package/dist/seo/json-ld.js +5 -1
  567. package/dist/server.d.ts +18 -3
  568. package/dist/server.js +59 -3
  569. package/dist/styles.css +237 -1
  570. package/dist/tailwind-utilities.css +1 -1
  571. package/dist/ui/components/Button.js +7 -1
  572. package/dist/ui/components/FilterChipGroup.d.ts +11 -0
  573. package/dist/ui/components/FilterChipGroup.js +1 -1
  574. package/dist/ui/components/ImageGallery.js +6 -1
  575. package/dist/ui/components/ListingLayout.js +14 -8
  576. package/dist/ui/components/OverflowRow.d.ts +92 -0
  577. package/dist/ui/components/OverflowRow.js +203 -0
  578. package/dist/ui/components/OverflowRow.style.css +235 -0
  579. package/dist/ui/components/RecordDetailModal.js +14 -1
  580. package/dist/ui/components/RowActionMenu.js +5 -1
  581. package/dist/ui/components/SideDrawer.js +5 -1
  582. package/dist/ui/components/SideModal.js +9 -1
  583. package/dist/ui/components/TabBarShell.d.ts +12 -1
  584. package/dist/ui/components/TabBarShell.js +3 -2
  585. package/dist/ui/components/Tabs.js +36 -24
  586. package/dist/ui/components/Toast.d.ts +3 -24
  587. package/dist/ui/components/Toast.js +2 -19
  588. package/dist/ui/components/TopBannerPortal.d.ts +15 -0
  589. package/dist/ui/components/TopBannerPortal.js +19 -0
  590. package/dist/ui/components/UnsavedChangesModal.js +5 -1
  591. package/dist/ui/components/index.style.css +1 -0
  592. package/dist/ui/components/row-collapse.d.ts +15 -0
  593. package/dist/ui/components/row-collapse.js +127 -0
  594. package/dist/ui/components/toast-context.d.ts +63 -0
  595. package/dist/ui/components/toast-context.js +48 -0
  596. package/dist/ui/components/top-banner-slot.d.ts +37 -0
  597. package/dist/ui/components/top-banner-slot.js +45 -0
  598. package/dist/ui/icons/icon-registry.d.ts +6 -0
  599. package/dist/ui/icons/icon-registry.js +17 -1
  600. package/dist/ui/index.d.ts +2 -0
  601. package/dist/ui/index.js +1 -0
  602. package/dist/utils/array.helper.d.ts +2 -2
  603. package/dist/utils/array.helper.js +5 -5
  604. package/dist/utils/id-generators.d.ts +6 -0
  605. package/dist/utils/id-generators.js +10 -0
  606. package/dist/utils/media-url.js +0 -2
  607. package/dist/utils/search-txt-builders.d.ts +28 -4
  608. package/dist/utils/search-txt-builders.js +41 -1
  609. package/dist/validation/schemas.d.ts +8 -8
  610. package/package.json +6 -2
  611. package/scripts/seed-cli.mjs +3 -0
  612. package/dist/features/account/components/UserAddressesView.d.ts +0 -13
  613. package/dist/features/account/components/UserAddressesView.js +0 -5
  614. package/dist/features/account/components/UserNotificationsView.d.ts +0 -15
  615. package/dist/features/account/components/UserNotificationsView.js +0 -12
  616. package/dist/features/account/components/UserSettingsView.d.ts +0 -16
  617. package/dist/features/account/components/UserSettingsView.js +0 -13
  618. package/dist/features/account/components/UserSupportView.d.ts +0 -3
  619. package/dist/features/account/components/UserSupportView.js +0 -166
  620. package/dist/features/admin/components/DemoSeedView.d.ts +0 -11
  621. package/dist/features/admin/components/DemoSeedView.js +0 -5
  622. package/dist/features/auth/components/AuthStatusPanel.d.ts +0 -10
  623. package/dist/features/auth/components/AuthStatusPanel.js +0 -22
  624. package/dist/features/categories/components/CategoryProductsView.d.ts +0 -45
  625. package/dist/features/categories/components/CategoryProductsView.js +0 -21
  626. package/dist/features/homepage/components/HowItWorksInfoView.d.ts +0 -36
  627. package/dist/features/homepage/components/HowItWorksInfoView.js +0 -10
  628. package/dist/features/layout/BottomActions.d.ts +0 -1
  629. package/dist/features/layout/BottomActions.js +0 -311
  630. package/dist/features/products/components/ProductsView.d.ts +0 -6
  631. package/dist/features/products/components/ProductsView.js +0 -5
  632. package/dist/features/seller/components/SellerStoreView.d.ts +0 -8
  633. package/dist/features/seller/components/SellerStoreView.js +0 -8
@@ -0,0 +1,84 @@
1
+ "use client";
2
+ import { useCallback, useEffect, useRef } from "react";
3
+ /**
4
+ * How long a press has to last before it counts as "hold to open" rather than
5
+ * "tap to follow the link". 400ms is the usual long-press threshold — short
6
+ * enough not to feel like a stall, long enough that an ordinary tap never
7
+ * trips it.
8
+ */
9
+ export const HOLD_TO_OPEN_MS = 400;
10
+ /**
11
+ * useHoldToOpen — press-and-hold a navigation link to open its sub-menu
12
+ * instead of following it.
13
+ *
14
+ * The shared half of the same affordance on two surfaces: the desktop navbar's
15
+ * dropdown and the mobile bottom nav's sheet. Both wrap a REAL link whose tap
16
+ * must still navigate, and both need the `click` that a long press still fires
17
+ * to be swallowed — otherwise the menu opens and the browser immediately
18
+ * navigates away from it, which reads as the gesture doing nothing.
19
+ *
20
+ * 🛑 `held` is a ref, not state. The click handler reads it in the same tick
21
+ * the timer wrote it, and a state update would not have landed yet — the link
22
+ * would follow through on every hold.
23
+ *
24
+ * @param onHold called once the press passes the threshold (open the menu)
25
+ * @param enabled false for a link with no children — then every handler is a
26
+ * no-op and the link behaves exactly as it did before
27
+ */
28
+ export function useHoldToOpen(onHold, enabled = true) {
29
+ const timerRef = useRef(null);
30
+ const heldRef = useRef(false);
31
+ const onHoldRef = useRef(onHold);
32
+ // Keep the callback current without re-creating the handlers (and therefore
33
+ // without restarting a press in progress on every parent re-render).
34
+ useEffect(() => {
35
+ onHoldRef.current = onHold;
36
+ }, [onHold]);
37
+ const cancel = useCallback(() => {
38
+ if (timerRef.current) {
39
+ clearTimeout(timerRef.current);
40
+ timerRef.current = null;
41
+ }
42
+ }, []);
43
+ // Clear a pending timer if the item unmounts mid-press — the nav re-renders
44
+ // its item list whenever the listing-type flags or the session resolve.
45
+ useEffect(() => cancel, [cancel]);
46
+ const start = useCallback(() => {
47
+ heldRef.current = false;
48
+ cancel();
49
+ timerRef.current = setTimeout(() => {
50
+ heldRef.current = true;
51
+ onHoldRef.current();
52
+ }, HOLD_TO_OPEN_MS);
53
+ }, [cancel]);
54
+ /**
55
+ * Spread onto the element that wraps (or is) the link. `onClickCapture` is
56
+ * deliberate rather than `onClick`: on the bottom nav the handlers sit on a
57
+ * wrapper around `<NavItem>`, and by the time a bubbled click arrives the
58
+ * anchor's own default is already queued — capture runs before the anchor
59
+ * sees it, so one set of handlers works whether it is spread onto the link
60
+ * itself or onto a container.
61
+ */
62
+ const handlers = enabled
63
+ ? {
64
+ onPointerDown: start,
65
+ onPointerUp: cancel,
66
+ onPointerLeave: cancel,
67
+ onPointerCancel: cancel,
68
+ onClickCapture: (e) => {
69
+ if (!heldRef.current)
70
+ return;
71
+ e.preventDefault();
72
+ e.stopPropagation();
73
+ heldRef.current = false;
74
+ },
75
+ // A long press on touch otherwise raises the OS link callout over the
76
+ // menu the same gesture just opened.
77
+ onContextMenu: (e) => {
78
+ if (heldRef.current)
79
+ e.preventDefault();
80
+ },
81
+ }
82
+ : {};
83
+ return handlers;
84
+ }
@@ -1,10 +1,25 @@
1
1
  import type { FirebaseSieveFields, FirebaseSieveResult, SieveModel } from "../../../providers/db-firebase";
2
2
  import { BaseRepository } from "../../../providers/db-firebase";
3
- import { type LotteryEntryDocument } from "../schemas/firestore";
3
+ import { type LotteryEntryDocument, type LotteryEntryPlain } from "../schemas/firestore";
4
4
  declare class LotteryEntryRepository extends BaseRepository<LotteryEntryDocument> {
5
5
  static readonly SIEVE_FIELDS: FirebaseSieveFields;
6
6
  constructor();
7
- protected mapDoc<D = LotteryEntryDocument>(snap: import("../../../providers/db-firebase").DocumentSnapshot): D;
7
+ /**
8
+ * Plaintext PII. **Opt-in and named** — for the admin entries table and the
9
+ * draw notification, where the real address is the point.
10
+ */
11
+ revealEntry(doc: LotteryEntryDocument): LotteryEntryPlain;
12
+ /** As `revealEntry`, for a list read. */
13
+ revealEntries(docs: readonly LotteryEntryDocument[]): LotteryEntryPlain[];
14
+ /**
15
+ * Masked PII — **the default for any surface that only needs to identify a
16
+ * row to a human.** Decrypts first, then masks: handing a stored field
17
+ * straight to `maskName` would return "Anonymous" for every row, because
18
+ * those helpers refuse ciphertext by design.
19
+ */
20
+ maskedEntry(doc: LotteryEntryDocument): LotteryEntryPlain;
21
+ /** As `maskedEntry`, for a list read. */
22
+ maskedEntries(docs: readonly LotteryEntryDocument[]): LotteryEntryPlain[];
8
23
  /**
9
24
  * Get the next sequential userLotteryNumber for a source atomically.
10
25
  * Returns current count + 1.
@@ -25,7 +40,7 @@ declare class LotteryEntryRepository extends BaseRepository<LotteryEntryDocument
25
40
  /** List entries for a specific user across all sources. */
26
41
  listForUser(userId: string, model: SieveModel): Promise<FirebaseSieveResult<LotteryEntryDocument>>;
27
42
  /** Create a lottery entry with PII encryption. */
28
- createEntry(data: Omit<LotteryEntryDocument, "id">): Promise<LotteryEntryDocument>;
43
+ createEntry(data: Omit<LotteryEntryPlain, "id">): Promise<LotteryEntryDocument>;
29
44
  /** Flag a lottery entry as fraudulent. */
30
45
  flagEntry(entryId: string, flagNote: string, flaggedBy: string): Promise<LotteryEntryDocument>;
31
46
  }
@@ -2,17 +2,48 @@ import { normalizeError } from "../../../errors/normalize.js";
2
2
  import { DatabaseError } from "../../../errors/index.js";
3
3
  import { serverLogger } from "../../../monitoring/index.js";
4
4
  import { BaseRepository, prepareForFirestore, parseSieveDateValue, } from "../../../providers/db-firebase/index.js";
5
- import { decryptPiiFields, encryptPiiFields, LOTTERY_ENTRY_PII_FIELDS, } from "../../../security/index.js";
5
+ import { encryptPiiFields, maskPii, revealPii, LOTTERY_ENTRY_PII_FIELDS, } from "../../../security/index.js";
6
6
  import { LOTTERY_ENTRIES_COLLECTION, LOTTERY_ENTRY_FIELDS, } from "../schemas/firestore.js";
7
7
  class LotteryEntryRepository extends BaseRepository {
8
8
  constructor() {
9
9
  super(LOTTERY_ENTRIES_COLLECTION);
10
10
  }
11
- mapDoc(snap) {
12
- const raw = super.mapDoc(snap);
13
- return decryptPiiFields(raw, [
14
- ...LOTTERY_ENTRY_PII_FIELDS,
15
- ]);
11
+ /*
12
+ * 🛑 `mapDoc` is NOT overridden, and that is the whole change.
13
+ *
14
+ * It used to call `decryptPiiFields` on every read, so ciphertext never left
15
+ * the database and all five consumers handled plaintext without ever saying
16
+ * so. Following `SiteSettingsRepository`: reads return what Firestore holds,
17
+ * and plaintext requires one of the two named methods below.
18
+ *
19
+ * The compiler enforces it rather than a convention doing so —
20
+ * `LotteryEntryDocument`'s PII fields are `Encrypted<string>`, assignable to
21
+ * nothing that wants a plain `string`, so a reader that skips both accessors
22
+ * is a compile error naming the file and line.
23
+ */
24
+ /**
25
+ * Plaintext PII. **Opt-in and named** — for the admin entries table and the
26
+ * draw notification, where the real address is the point.
27
+ */
28
+ revealEntry(doc) {
29
+ return revealPii(doc, LOTTERY_ENTRY_PII_FIELDS);
30
+ }
31
+ /** As `revealEntry`, for a list read. */
32
+ revealEntries(docs) {
33
+ return docs.map((d) => this.revealEntry(d));
34
+ }
35
+ /**
36
+ * Masked PII — **the default for any surface that only needs to identify a
37
+ * row to a human.** Decrypts first, then masks: handing a stored field
38
+ * straight to `maskName` would return "Anonymous" for every row, because
39
+ * those helpers refuse ciphertext by design.
40
+ */
41
+ maskedEntry(doc) {
42
+ return maskPii(doc, LOTTERY_ENTRY_PII_FIELDS);
43
+ }
44
+ /** As `maskedEntry`, for a list read. */
45
+ maskedEntries(docs) {
46
+ return docs.map((d) => this.maskedEntry(d));
16
47
  }
17
48
  /**
18
49
  * Get the next sequential userLotteryNumber for a source atomically.
@@ -78,6 +109,17 @@ class LotteryEntryRepository extends BaseRepository {
78
109
  });
79
110
  }
80
111
  /** Create a lottery entry with PII encryption. */
112
+ /*
113
+ * 🛑 Takes PLAINTEXT (`LotteryEntryPlain`), returns CIPHERTEXT
114
+ * (`LotteryEntryDocument`), and the asymmetry is the point.
115
+ *
116
+ * The caller holds a real email and phone; this method encrypts them. Typing
117
+ * the parameter `Omit<LotteryEntryDocument, "id">` would have demanded the
118
+ * caller hand over `Encrypted<string>` it does not have, and the only way to
119
+ * satisfy that is `asEncrypted(email)` — which labels plaintext as ciphertext
120
+ * and is exactly the misuse `asEncrypted`'s own docstring forbids. The
121
+ * encryption happens here; so does the type change.
122
+ */
81
123
  async createEntry(data) {
82
124
  try {
83
125
  const encrypted = encryptPiiFields(data, [...LOTTERY_ENTRY_PII_FIELDS]);
@@ -2,7 +2,23 @@
2
2
  * LotteryEntry Firestore Document Type & Constants
3
3
  */
4
4
  import type { LotteryEntryStatus } from "../types";
5
+ import type { Encrypted, Plaintext } from "../../../security/encrypted";
5
6
  export declare const LOTTERY_ENTRIES_COLLECTION: "lotteryEntries";
7
+ /**
8
+ * The same entry with its PII as plain `string` — what the two named
9
+ * repository accessors return.
10
+ *
11
+ * 🛑 Derived with a mapped type, never hand-written. A second literal listing
12
+ * the same ~18 fields would drift the first time one was added to only one of
13
+ * them, and the drift is invisible: both compile, and the one nobody updated
14
+ * silently drops the new field from every reveal/mask call site.
15
+ *
16
+ * It is deliberately NOT distinguishable from a MASKED entry. `maskPii` also
17
+ * returns plain strings, and a type cannot express "these characters are
18
+ * asterisks" — the distinction lives in WHICH named method you called, which
19
+ * is why they are two methods rather than one taking a boolean.
20
+ */
21
+ export type LotteryEntryPlain = Plaintext<LotteryEntryDocument>;
6
22
  export type LotteryEntryDocument = {
7
23
  id: string;
8
24
  sourceType: "event" | "product";
@@ -11,11 +27,23 @@ export type LotteryEntryDocument = {
11
27
  /** Set when sourceType === "product" */
12
28
  productId?: string;
13
29
  userId: string;
14
- userDisplayName?: string;
15
- /** PII — encrypted at rest */
16
- userEmail?: string;
17
- /** PII — encrypted at rest; required */
18
- userPhone: string;
30
+ /**
31
+ * PII — CIPHERTEXT as it sits here.
32
+ *
33
+ * 🛑 `Encrypted<string>` is a promise about the VALUE, kept by
34
+ * `LotteryEntryRepository` no longer decrypting in `mapDoc`. Branding a field
35
+ * that is still decrypted on read asserts something false, and `decryptValue`
36
+ * passes a non-`enc:v1:` string straight through — so the lie would silently
37
+ * "work" and surface only on the rows that were genuinely encrypted.
38
+ *
39
+ * Readers call `lotteryEntryRepository.maskedEntry(doc)` (the UI default) or
40
+ * `revealEntry(doc)` (opt-in, named, greppable).
41
+ */
42
+ userDisplayName?: Encrypted<string>;
43
+ /** PII — ciphertext at rest; see `userDisplayName` */
44
+ userEmail?: Encrypted<string>;
45
+ /** PII — ciphertext at rest; required. See `userDisplayName` */
46
+ userPhone: Encrypted<string>;
19
47
  /** User-provided external payment TX ID */
20
48
  transactionId: string;
21
49
  /** User-provided time of payment */
@@ -24,7 +24,7 @@ export async function GET(request) {
24
24
  const repo = db.getRepository("loyalty_balance");
25
25
  const result = await repo.findAll({
26
26
  filters: `userId==${userId}`,
27
- perPage: 1,
27
+ pageSize: 1,
28
28
  });
29
29
  return NextResponse.json({
30
30
  success: true,
@@ -33,7 +33,7 @@ export async function GET(request) {
33
33
  filters: `userId==${userId}`,
34
34
  sort: "-createdAt",
35
35
  page,
36
- perPage: pageSize,
36
+ pageSize: pageSize,
37
37
  });
38
38
  const totalPages = Math.max(1, Math.ceil(result.total / pageSize));
39
39
  return NextResponse.json({
@@ -89,7 +89,6 @@ export function MediaImage({ src, alt, size = "card", priority = false, loading,
89
89
  retryTimeoutRef.current = null;
90
90
  }
91
91
  };
92
- // eslint-disable-next-line react-hooks/exhaustive-deps
93
92
  }, [resolvedSrc]);
94
93
  const wrapperExtra = [
95
94
  rounded ? ROUNDED_MAP[rounded] : "",
@@ -11,7 +11,13 @@ import { Row, Stack } from "../../ui/components/Layout.js";
11
11
  import { Alert } from "../../ui/components/Alert.js";
12
12
  import { MediaImage } from "./MediaImage.js";
13
13
  import { MediaUploadField } from "./upload/MediaUploadField.js";
14
- import { useMediaUpload, useMediaCleanup } from "./index.js";
14
+ /*
15
+ * The DEFINING module, not `./index` — Root Cause #18. The barrel re-exports
16
+ * this very component, so importing back through it made a two-module import
17
+ * cycle (`audit-import-cycles` component 4), and a cycle makes "which module
18
+ * owns this symbol" unanswerable for every tool that walks the graph.
19
+ */
20
+ import { useMediaUpload, useMediaCleanup } from "./hooks/useMedia.js";
15
21
  import { ADMIN_ENDPOINTS } from "../../constants/api-endpoints.js";
16
22
  const MEDIA_LIST_ENDPOINT = ADMIN_ENDPOINTS.MEDIA_LIST;
17
23
  const EXISTING_GRID_MAX_HEIGHT = "max-h-96";
@@ -9,6 +9,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  import { useState, useRef, useCallback } from "react";
10
10
  import { useTranslations } from "next-intl";
11
11
  import { Cropper } from "react-advanced-cropper";
12
+ import { IMAGE_DOWNSCALE_FORMAT, IMAGE_DOWNSCALE_MAX_EDGE, IMAGE_DOWNSCALE_QUALITY, } from "../upload/downscale-image.js";
12
13
  /*
13
14
  * 🛑 NO `import "react-advanced-cropper/dist/style.css"` HERE.
14
15
  *
@@ -27,7 +28,10 @@ import { Cropper } from "react-advanced-cropper";
27
28
  */
28
29
  import { Button, Div, Modal, Row, Stack, Text, useToast } from "../../../ui/index.js";
29
30
  import { normalizeError } from "../../../errors/normalize.js";
30
- export function ImageEditor({ isOpen, imageUrl, onClose, onSave, aspectRatio, outputFormat = "image/webp", outputQuality = 0.9, maxOutputWidth = 2048, }) {
31
+ export function ImageEditor({ isOpen, imageUrl, onClose, onSave, aspectRatio,
32
+ // Shared with the always-on upload downscaler so the crop path and the
33
+ // non-crop path cannot produce differently-sized masters.
34
+ outputFormat = IMAGE_DOWNSCALE_FORMAT, outputQuality = IMAGE_DOWNSCALE_QUALITY, maxOutputWidth = IMAGE_DOWNSCALE_MAX_EDGE, }) {
31
35
  const t = useTranslations("mediaEditor");
32
36
  const { showToast } = useToast();
33
37
  const cropperRef = useRef(null);
@@ -8,7 +8,23 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
8
8
  */
9
9
  import { useState, useRef } from "react";
10
10
  import { useTranslations } from "next-intl";
11
- import { Alert, Button, Div, Modal, Row, Span, Spinner, Stack, Text } from "../../../ui/index.js";
11
+ /*
12
+ * Defining modules, not the `ui` barrel — Root Cause #18.
13
+ *
14
+ * `ui/index.ts` re-exports `FormField`, `PaginatedSelect` and
15
+ * `RecordDetailModal`, and those three import back INTO `features/media` and
16
+ * `features/shell`. So reaching a primitive through the barrel closed a
17
+ * ten-module cycle (`audit-import-cycles` component 1) — while the deep paths
18
+ * below have one direction only. `ui → features/media` is the real dependency
19
+ * and it stays; the barrel hop is what did not need to exist.
20
+ */
21
+ import { Alert } from "../../../ui/components/Alert.js";
22
+ import { Button } from "../../../ui/components/Button.js";
23
+ import { Div } from "../../../ui/components/Div.js";
24
+ import { Row, Stack } from "../../../ui/components/Layout.js";
25
+ import { Modal } from "../../../ui/components/Modal.js";
26
+ import { Spinner } from "../../../ui/components/Spinner.js";
27
+ import { Span, Text } from "../../../ui/components/Typography.js";
12
28
  import { normalizeError } from "../../../errors/normalize.js";
13
29
  import { toUserMessage } from "../../../errors/error-display-map.js";
14
30
  const __O = {
@@ -21,6 +21,25 @@ export interface MediaAssetDocument {
21
21
  * any other upload without parsing filename strings.
22
22
  */
23
23
  contextType?: string;
24
+ /**
25
+ * When the watermark was composited INTO the stored bytes, at upload time.
26
+ *
27
+ * 🛑 Absent means "legacy" and is the load-bearing default. Until 2026-10-19
28
+ * `/api/media/[...slug]` sharp-decoded, composited and re-encoded EVERY image
29
+ * response — the cost profile behind the HTTP 402 Fast Origin Transfer
30
+ * suspension (Root Cause #104). Now `/api/media/finalize` bakes the mark once
31
+ * and stamps this field, and the proxy skips the composite when it is set.
32
+ *
33
+ * So an asset uploaded before this existed has no field, the proxy watermarks
34
+ * it on read exactly as it always did, and **no backfill is required** for the
35
+ * 504 rows that predate it. A backfill can run later via the Async Job
36
+ * Primitive, or never.
37
+ *
38
+ * It is written in the SAME operation that stores the watermarked bytes, so a
39
+ * row can never claim a mark the object does not carry — which would show an
40
+ * unwatermarked image, or double-watermark it the other way round.
41
+ */
42
+ watermarkedAt?: Date;
24
43
  createdAt: Date;
25
44
  }
26
45
  export type MediaAssetCreateInput = Omit<MediaAssetDocument, "id" | "createdAt">;
@@ -9,7 +9,22 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
9
9
  import { useEffect, useState } from "react";
10
10
  import { useTranslations } from "next-intl";
11
11
  import { useCamera } from "../../../react/index.js";
12
- import { Alert, Button, Div, Row, Span, Spinner, Stack } from "../../../ui/index.js";
12
+ /*
13
+ * Defining modules, not the `ui` barrel — Root Cause #18.
14
+ *
15
+ * `ui/index.ts` re-exports `FormField`, `PaginatedSelect` and
16
+ * `RecordDetailModal`, and those three import back INTO `features/media` and
17
+ * `features/shell`. So reaching a primitive through the barrel closed a
18
+ * ten-module cycle (`audit-import-cycles` component 1) — while the deep paths
19
+ * below have one direction only. `ui → features/media` is the real dependency
20
+ * and it stays; the barrel hop is what did not need to exist.
21
+ */
22
+ import { Alert } from "../../../ui/components/Alert.js";
23
+ import { Button } from "../../../ui/components/Button.js";
24
+ import { Div } from "../../../ui/components/Div.js";
25
+ import { Row, Stack } from "../../../ui/components/Layout.js";
26
+ import { Spinner } from "../../../ui/components/Spinner.js";
27
+ import { Span } from "../../../ui/components/Typography.js";
13
28
  export default function CameraCapture({ mode, facingMode = "environment", onCapture, onError, className, }) {
14
29
  const t = useTranslations("camera");
15
30
  const camera = useCamera();
@@ -1,6 +1,7 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
- import { IMAGE_ACCEPT_ATTR } from "../../../_internal/shared/media/limits.js";
3
+ import { IMAGE_ACCEPT_ATTR, MAX_IMAGE_BYTES, MEGABYTE, } from "../../../_internal/shared/media/limits.js";
4
+ import { downscaleImageFile } from "./downscale-image.js";
4
5
  import { normalizeError } from "../../../errors/normalize.js";
5
6
  /**
6
7
  * ImageUpload — canonical image upload component for @mohasinac/feat-media.
@@ -13,7 +14,24 @@ import { useState, useRef } from "react";
13
14
  import dynamic from "next/dynamic.js";
14
15
  import { useTranslations } from "next-intl";
15
16
  import { useCamera } from "../../../react/index.js";
16
- import { Alert, Button, Div, Label, Progress, Row, Spinner, Stack, Text, useToast } from "../../../ui/index.js";
17
+ /*
18
+ * Defining modules, not the `ui` barrel — Root Cause #18.
19
+ *
20
+ * `ui/index.ts` re-exports `FormField`, `PaginatedSelect` and
21
+ * `RecordDetailModal`, and those three import back INTO `features/media` and
22
+ * `features/shell`. So reaching a primitive through the barrel closed a
23
+ * ten-module cycle (`audit-import-cycles` component 1) — while the deep paths
24
+ * below have one direction only. `ui → features/media` is the real dependency
25
+ * and it stays; the barrel hop is what did not need to exist.
26
+ */
27
+ import { Alert } from "../../../ui/components/Alert.js";
28
+ import { Button } from "../../../ui/components/Button.js";
29
+ import { Div } from "../../../ui/components/Div.js";
30
+ import { Row, Stack } from "../../../ui/components/Layout.js";
31
+ import { Progress } from "../../../ui/components/Progress.js";
32
+ import { Spinner } from "../../../ui/components/Spinner.js";
33
+ import { Label, Text } from "../../../ui/components/Typography.js";
34
+ import { useToast } from "../../../ui/components/toast-context.js";
17
35
  import { MediaImage } from "../MediaImage.js";
18
36
  import CameraCapture from "./CameraCapture.js";
19
37
  // Crop/editor modals pull in image-manipulation libraries (react-advanced-cropper
@@ -40,7 +58,12 @@ function matchesMimeAccept(fileType, accept) {
40
58
  return pSub === "*" || pSub === fSub;
41
59
  });
42
60
  }
43
- export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/*", maxSizeMB = 10, label = "Upload Image", helperText, captureSource = "both", enableCrop = true, enableAdvancedCrop = false, cropAspectRatio, onCropDataChange, }) {
61
+ export function ImageUpload({ currentImage, onUpload, onChange,
62
+ // 🛑 Never `image/*` — that wildcard matches `image/svg+xml`, which
63
+ // `ALLOWED_IMAGE_MIMES` deliberately excludes as an XSS surface and which the
64
+ // server 422s only after the crop editor has already opened on it. See
65
+ // `IMAGE_ACCEPT_ATTR`'s own docstring in `_internal/shared/media/limits.ts`.
66
+ accept = IMAGE_ACCEPT_ATTR, maxSizeMB = MAX_IMAGE_BYTES / MEGABYTE, label = "Upload Image", helperText, captureSource = "both", enableCrop = true, enableAdvancedCrop = false, cropAspectRatio, onCropDataChange, }) {
44
67
  const [uploading, setUploading] = useState(false);
45
68
  const [preview, setPreview] = useState(currentImage || "");
46
69
  const [error, setError] = useState("");
@@ -65,7 +88,22 @@ export function ImageUpload({ currentImage, onUpload, onChange, accept = "image/
65
88
  try {
66
89
  setUploading(true);
67
90
  setProgress(30);
68
- const url = await onUpload(file);
91
+ /*
92
+ * The single choke point for all six upload paths (direct, crop-save,
93
+ * crop-close, advanced-save, advanced-close, camera), so bounding the
94
+ * master here covers every one.
95
+ *
96
+ * 🛑 Safe to apply even when `cropData` is present: `ImageCropData` is
97
+ * `{ position, zoom }` in CSS PERCENTAGES — the modal's own header says
98
+ * "Returns CSS position/zoom percentages" and applies them as
99
+ * `left: ${position.x}%` — so it is scale-invariant and a uniform
100
+ * downscale cannot misalign a crop. Pixel-rect crop data would not be.
101
+ *
102
+ * A file already produced by `ImageEditor` comes back untouched (it is
103
+ * webp within 2048 already), so there is no double re-encode.
104
+ */
105
+ const prepared = await downscaleImageFile(file);
106
+ const url = await onUpload(prepared);
69
107
  setProgress(100);
70
108
  setPreview(url);
71
109
  onChange?.(url);
@@ -9,14 +9,32 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
9
9
  import { useState, useRef, useEffect } from "react";
10
10
  import { useTranslations } from "next-intl";
11
11
  import { useCamera } from "../../../react/index.js";
12
- import { Alert, Anchor, Button, Div, Label, Row, Span, Spinner, Stack, Text } from "../../../ui/index.js";
12
+ /*
13
+ * Defining modules, not the `ui` barrel — Root Cause #18.
14
+ *
15
+ * `ui/index.ts` re-exports `FormField`, `PaginatedSelect` and
16
+ * `RecordDetailModal`, and those three import back INTO `features/media` and
17
+ * `features/shell`. So reaching a primitive through the barrel closed a
18
+ * ten-module cycle (`audit-import-cycles` component 1) — while the deep paths
19
+ * below have one direction only. `ui → features/media` is the real dependency
20
+ * and it stays; the barrel hop is what did not need to exist.
21
+ */
22
+ import { Alert } from "../../../ui/components/Alert.js";
23
+ import { Anchor } from "../../../ui/components/Anchor.js";
24
+ import { Button } from "../../../ui/components/Button.js";
25
+ import { Div } from "../../../ui/components/Div.js";
26
+ import { Row, Stack } from "../../../ui/components/Layout.js";
27
+ import { Spinner } from "../../../ui/components/Spinner.js";
28
+ import { Label, Span, Text } from "../../../ui/components/Typography.js";
13
29
  import { MediaImage } from "../MediaImage.js";
14
30
  import { MediaVideo } from "../MediaVideo.js";
15
31
  import dynamic from "next/dynamic.js";
16
32
  import { VideoThumbnailSelector } from "../modals/VideoThumbnailSelector.js";
17
33
  import CameraCapture from "./CameraCapture.js";
18
34
  import { extractVideoPosterFrame, posterBlobAsFile } from "./video-poster.js";
35
+ import { downscaleImageFile } from "./downscale-image.js";
19
36
  import { inferMediaTypeFromMime } from "../types/index.js";
37
+ import { ALLOWED_DOC_MIMES, ALLOWED_MIMES, ALLOWED_VIDEO_MIMES, IMAGE_ACCEPT_ATTR, MAX_IMAGE_BYTES, MAX_PDF_BYTES, MAX_VIDEO_BYTES, MEGABYTE, } from "../../../_internal/shared/media/limits.js";
20
38
  import { normalizeError } from "../../../errors/normalize.js";
21
39
  // Pulls in video-processing libraries most upload sessions never touch —
22
40
  // lazy-load rather than bundle into every page rendering a MediaUploadField.
@@ -24,14 +42,35 @@ const VideoTrimModal = dynamic(() => import("../modals/VideoTrimModal.js").then(
24
42
  const CLS_PDF_BADGE = "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-error-surface text-error dark:bg-error-surface dark:text-error";
25
43
  const CLS_PDF_NAME = "text-[length:var(--appkit-text-sm)] font-medium text-info underline break-all dark:text-info";
26
44
  const CLS_PDF_LINK = "text-[length:var(--appkit-text-sm)] underline break-all text-info dark:text-info";
45
+ /*
46
+ * 🛑 DERIVED from the canonical registry, not restated.
47
+ *
48
+ * The `kind` docstring above has always claimed these "auto-derive … from the
49
+ * canonical media-limits registry". They did not — they were hand-written, and
50
+ * every one of them was MORE PERMISSIVE than the server, which enforces
51
+ * `MAX_BYTES[kind]` in BOTH `/api/media/sign` and `/api/media/finalize`. So the
52
+ * picker accepted work the upload could never complete:
53
+ *
54
+ * image 25 MB → server 10 MB
55
+ * video 200 MB → server 50 MB
56
+ * accept "image/*" → matches `image/svg+xml`, which `ALLOWED_IMAGE_MIMES`
57
+ * deliberately excludes as an XSS surface, and which the
58
+ * server 422s only AFTER the crop editor has opened on it
59
+ * (limits.ts's own `IMAGE_ACCEPT_ATTR` docstring).
60
+ *
61
+ * Deriving makes the refusal happen in the file picker, where it is a rule,
62
+ * rather than at the end, where it reads as a failure.
63
+ */
27
64
  const KIND_DEFAULTS = {
28
- image: { accept: "image/*", maxSizeMB: 25 },
29
- video: { accept: "video/*", maxSizeMB: 200 },
30
- pdf: { accept: "application/pdf", maxSizeMB: 10 },
65
+ image: { accept: IMAGE_ACCEPT_ATTR, maxSizeMB: MAX_IMAGE_BYTES / MEGABYTE },
66
+ video: { accept: ALLOWED_VIDEO_MIMES.join(","), maxSizeMB: MAX_VIDEO_BYTES / MEGABYTE },
67
+ pdf: { accept: ALLOWED_DOC_MIMES.join(","), maxSizeMB: MAX_PDF_BYTES / MEGABYTE },
31
68
  };
32
69
  const AUTO_KIND_DEFAULTS = {
33
- accept: "image/*,video/*,application/pdf",
34
- maxSizeMB: 200,
70
+ accept: ALLOWED_MIMES.join(","),
71
+ // The most permissive of the three, because `auto` genuinely does not know
72
+ // the kind yet — the per-kind cap is re-checked server-side once it does.
73
+ maxSizeMB: Math.max(MAX_IMAGE_BYTES, MAX_VIDEO_BYTES, MAX_PDF_BYTES) / MEGABYTE,
35
74
  };
36
75
  function isVideo(url) {
37
76
  return /\.(mp4|webm|ogg|mov|avi)(\?|$)/i.test(url);
@@ -277,14 +316,24 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
277
316
  setPendingVideoUrl(null);
278
317
  };
279
318
  const uploadSingleFile = async (file) => {
280
- const fileSizeMB = file.size / 1024 / 1024;
319
+ /*
320
+ * Bound the pixel dimensions BEFORE the byte check, deliberately.
321
+ *
322
+ * The cap is about what we STORE, and a 9 MB phone photo becomes a few
323
+ * hundred KB here — so checking first would refuse an upload we were about
324
+ * to make acceptable. Non-images and anything undecodable come back
325
+ * untouched (see `downscale-image.ts`), so the check below still does its
326
+ * job for video and PDF.
327
+ */
328
+ const prepared = await downscaleImageFile(file);
329
+ const fileSizeMB = prepared.size / 1024 / 1024;
281
330
  if (fileSizeMB > maxSizeMB) {
282
- setError(`${file.name}: exceeds ${maxSizeMB}MB limit`);
331
+ setError(`${prepared.name}: exceeds ${maxSizeMB}MB limit`);
283
332
  return null;
284
333
  }
285
- const url = await onUpload(file);
334
+ const url = await onUpload(prepared);
286
335
  stageUrl(url);
287
- return { url, type: file.type };
336
+ return { url, type: prepared.type };
288
337
  };
289
338
  /**
290
339
  * Client-side first-frame extraction for video uploads. Runs after the
@@ -373,10 +422,17 @@ kind, accept: acceptProp, maxSizeMB: maxSizeMBProp, disabled = false, helperText
373
422
  setError(null);
374
423
  setIsLoading(true);
375
424
  try {
376
- const url = await onUpload(file);
377
- stageUrl(url);
425
+ /*
426
+ * Routed through `uploadSingleFile` rather than calling `onUpload`
427
+ * directly, which is what this did until 2026-10-19 — so a camera capture
428
+ * skipped BOTH the `maxSizeMB` check and (once added) the downscale. A
429
+ * full-sensor capture is precisely the large image both exist for.
430
+ */
431
+ const result = await uploadSingleFile(file);
432
+ if (!result)
433
+ return;
378
434
  const poster = await maybeCaptureVideoPoster(file);
379
- afterUpload(url, blob.type || mimeType, poster?.posterUrl, poster?.duration);
435
+ afterUpload(result.url, result.type, poster?.posterUrl, poster?.duration);
380
436
  }
381
437
  catch (err) {
382
438
  void normalizeError(err);
@@ -0,0 +1,56 @@
1
+ /**
2
+ * downscaleImageFile — bound an image's pixel dimensions and re-encode it
3
+ * before it ever leaves the browser.
4
+ *
5
+ * ## Why this exists
6
+ *
7
+ * Until 2026-10-19 the ONLY client-side resize in the whole upload path was
8
+ * inside `ImageEditor` (`modals/ImageEditor.tsx`), which runs only when the
9
+ * user opens the crop editor. Everything else stored the original bytes: a 9 MB
10
+ * phone photo was stored at 9 MB, watermarked at 9 MB, and served at 9 MB into
11
+ * a 64px thumbnail, because `/api/media/[...slug]` resizes nothing and
12
+ * `next/image` optimisation was off.
13
+ *
14
+ * That is the shape of the cost that suspended this project with HTTP 402 for
15
+ * Fast Origin Transfer at 5.25x its cap (Root Cause #104). Bounding the master
16
+ * at upload is the one fix that helps every downstream consumer at once.
17
+ *
18
+ * ## Why 2048
19
+ *
20
+ * Measured (docs/research/competitor-crawl-2026-10/images.md): worldhobbyshop
21
+ * serves a 2048x2048 master to photoswipe for zoom, and WordPress's own
22
+ * `-scaled` threshold is 2560. 2048 survives our lightbox's 300% zoom over a
23
+ * ~680px display box with detail to spare, and is 1/4 the pixels of a 4096px
24
+ * phone capture.
25
+ *
26
+ * ## Four cases where it deliberately returns the ORIGINAL file
27
+ *
28
+ * 1. **Not a raster image we own the pipeline for** — anything outside
29
+ * `CONVERTIBLE_MIMES`. SVG is never rasterised (it is also not in
30
+ * `ALLOWED_IMAGE_MIMES`), and GIF is never touched because re-encoding an
31
+ * animated GIF to a still WebP silently destroys the animation.
32
+ * 2. **Already small enough AND already a compact format** — re-encoding a
33
+ * 1200px WebP buys nothing and loses a generation of quality.
34
+ * 3. **The re-encode came out BIGGER.** A small, already-optimised JPEG can
35
+ * grow under WebP q0.9. Shipping the larger file would make this helper a
36
+ * pessimisation, so the original wins.
37
+ * 4. **Anything threw.** 🛑 Fail OPEN. A canvas decode failure, a tainted
38
+ * canvas, a browser without `createImageBitmap` — none of those are a reason
39
+ * to block a seller's upload. The server still enforces `MAX_BYTES[kind]`,
40
+ * so the worst case is the pre-2026-10-19 behaviour, not a hole.
41
+ */
42
+ /** Longest edge, in CSS pixels, of the stored master. */
43
+ export declare const IMAGE_DOWNSCALE_MAX_EDGE = 2048;
44
+ /** Output MIME for a re-encoded master. */
45
+ export declare const IMAGE_DOWNSCALE_FORMAT = "image/webp";
46
+ /** Output quality for a re-encoded master. */
47
+ export declare const IMAGE_DOWNSCALE_QUALITY = 0.9;
48
+ export interface DownscaleImageOptions {
49
+ /** Longest edge of the output. Default `IMAGE_DOWNSCALE_MAX_EDGE`. */
50
+ maxEdge?: number;
51
+ /** Output MIME. Default `IMAGE_DOWNSCALE_FORMAT`. */
52
+ format?: string;
53
+ /** Output quality, 0..1. Default `IMAGE_DOWNSCALE_QUALITY`. */
54
+ quality?: number;
55
+ }
56
+ export declare function downscaleImageFile(file: File, options?: DownscaleImageOptions): Promise<File>;