@wix/himalaya-cli 0.1582.0 → 0.1584.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 (336) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/builder.d.ts +8 -0
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/schema.d.ts +26 -2
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/validator.d.ts +18 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +2 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/comments-v1/src/extensions.d.ts +2 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/events/src/index.d.ts +3 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/groups/src/extensions.d.ts +15 -0
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/loyalty/src/extensions.d.ts +13 -0
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/members-v1/src/index.d.ts +19 -4
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +5 -1
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +5 -1
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-comments.mjs +3 -0
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-events.mjs +4 -2
  14. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-groups.mjs +5 -0
  15. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-loyalty.mjs +4 -0
  16. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-members.mjs +10 -3
  17. package/dist/cli.mjs +1098 -702
  18. package/dist/preview-assets.generated.bin +0 -0
  19. package/fixtures/wix-canned.json +25 -0
  20. package/guides/component-reference.json +4 -3
  21. package/guides/component-reference.md +24 -2
  22. package/guides/one-app-to-himi-app.md +52 -1
  23. package/guides/sdk-reference.json +37 -9
  24. package/guides/wix-api-usage.md +9 -0
  25. package/package.json +2 -2
  26. package/templates/beauty-salon/config.ts +27 -1
  27. package/templates/beauty-salon/dev/index.ts +2285 -757
  28. package/templates/beauty-salon/himalaya.content.json +3 -0
  29. package/templates/beauty-salon/tier5-src/_shared/booking-location.ts +147 -0
  30. package/templates/beauty-salon/tier5-src/_shared/booking-session.ts +222 -0
  31. package/templates/beauty-salon/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  32. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +19 -3
  33. package/templates/beauty-salon/tier5-src/_shared/himi-restaurants.ts +2 -1
  34. package/templates/beauty-salon/tier5-src/_shared/owner-editor-value.ts +164 -0
  35. package/templates/beauty-salon/tier5-src/_shared/store-product-info.ts +50 -0
  36. package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +21 -8
  37. package/templates/beauty-salon/tier5-src/himi-booking-service/index.ts +277 -14
  38. package/templates/beauty-salon/tier5-src/himi-booking-services/index.ts +351 -92
  39. package/templates/beauty-salon/tier5-src/himi-booking-time/index.ts +25 -5
  40. package/templates/beauty-salon/tier5-src/himi-branded-lite-agenda/index.ts +219 -220
  41. package/templates/beauty-salon/tier5-src/himi-branded-lite-category/index.ts +3 -2
  42. package/templates/beauty-salon/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  43. package/templates/beauty-salon/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  44. package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +14 -3
  45. package/templates/beauty-salon/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  46. package/templates/beauty-salon/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  47. package/templates/beauty-salon-owner/dev/index.ts +129 -66
  48. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +19 -3
  49. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  50. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  51. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  52. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  53. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  54. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  55. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  56. package/templates/branded-lite/config.ts +29 -1
  57. package/templates/branded-lite/dev/index.ts +8371 -5565
  58. package/templates/branded-lite/himalaya.content.json +3 -0
  59. package/templates/branded-lite/l10n/en.json +2 -2
  60. package/templates/branded-lite/tier5-src/_shared/booking-location.ts +147 -0
  61. package/templates/branded-lite/tier5-src/_shared/booking-session.ts +222 -0
  62. package/templates/branded-lite/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  63. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +19 -3
  64. package/templates/branded-lite/tier5-src/_shared/event-registration.ts +86 -0
  65. package/templates/branded-lite/tier5-src/_shared/himi-restaurants.ts +2 -1
  66. package/templates/branded-lite/tier5-src/_shared/owner-editor-value.ts +164 -0
  67. package/templates/branded-lite/tier5-src/_shared/store-product-info.ts +50 -0
  68. package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +21 -8
  69. package/templates/branded-lite/tier5-src/himi-booking-service/index.ts +277 -14
  70. package/templates/branded-lite/tier5-src/himi-booking-services/index.ts +351 -92
  71. package/templates/branded-lite/tier5-src/himi-booking-time/index.ts +25 -5
  72. package/templates/branded-lite/tier5-src/himi-branded-lite-agenda/index.ts +219 -220
  73. package/templates/branded-lite/tier5-src/himi-branded-lite-badges/index.ts +2 -1
  74. package/templates/branded-lite/tier5-src/himi-branded-lite-category/index.ts +3 -2
  75. package/templates/branded-lite/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  76. package/templates/branded-lite/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
  77. package/templates/branded-lite/tier5-src/himi-branded-lite-loyalty/index.ts +115 -49
  78. package/templates/branded-lite/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  79. package/templates/branded-lite/tier5-src/himi-branded-lite-podcasts/index.ts +2 -1
  80. package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio/index.ts +2 -1
  81. package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -2
  82. package/templates/branded-lite/tier5-src/himi-branded-lite-program/facts.ts +52 -0
  83. package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +21 -11
  84. package/templates/branded-lite/tier5-src/himi-branded-lite-video/index.ts +2 -1
  85. package/templates/branded-lite/tier5-src/himi-events-detail/index.ts +12 -86
  86. package/templates/branded-lite/tier5-src/himi-events-list/index.ts +47 -6
  87. package/templates/branded-lite/tier5-src/himi-store-product/index.ts +13 -3
  88. package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +3 -2
  89. package/templates/branded-lite/tier5-src/wix-spaces-member-detail/index.ts +53 -37
  90. package/templates/branded-lite/tier5-src/wix-spaces-members/index.ts +100 -34
  91. package/templates/branded-lite/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  92. package/templates/branded-lite/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  93. package/templates/community-club/config.ts +36 -1
  94. package/templates/community-club/dev/index.ts +1930 -401
  95. package/templates/community-club/himalaya.content.json +6 -0
  96. package/templates/community-club/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  97. package/templates/community-club/tier5-src/_shared/collection-contract.ts +19 -3
  98. package/templates/community-club/tier5-src/_shared/event-registration.ts +86 -0
  99. package/templates/community-club/tier5-src/_shared/himi-restaurants.ts +2 -1
  100. package/templates/community-club/tier5-src/himi-blog-post/index.ts +21 -8
  101. package/templates/community-club/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  102. package/templates/community-club/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
  103. package/templates/community-club/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  104. package/templates/community-club/tier5-src/himi-events-detail/index.ts +12 -86
  105. package/templates/community-club/tier5-src/himi-events-list/index.ts +47 -6
  106. package/templates/community-club/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  107. package/templates/community-club-owner/dev/index.ts +53 -1
  108. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +19 -3
  109. package/templates/community-club-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  110. package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  111. package/templates/community-club-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  112. package/templates/community-club-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  113. package/templates/community-club-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  114. package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  115. package/templates/community-club-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  116. package/templates/events-meetups/config.ts +36 -1
  117. package/templates/events-meetups/dev/index.ts +1930 -401
  118. package/templates/events-meetups/himalaya.content.json +6 -0
  119. package/templates/events-meetups/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  120. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +19 -3
  121. package/templates/events-meetups/tier5-src/_shared/event-registration.ts +86 -0
  122. package/templates/events-meetups/tier5-src/_shared/himi-restaurants.ts +2 -1
  123. package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +21 -8
  124. package/templates/events-meetups/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  125. package/templates/events-meetups/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
  126. package/templates/events-meetups/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  127. package/templates/events-meetups/tier5-src/himi-events-detail/index.ts +12 -86
  128. package/templates/events-meetups/tier5-src/himi-events-list/index.ts +47 -6
  129. package/templates/events-meetups/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  130. package/templates/events-meetups-owner/dev/index.ts +6 -0
  131. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +19 -3
  132. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  133. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  134. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  135. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  136. package/templates/gym-fitness/config.ts +27 -1
  137. package/templates/gym-fitness/dev/index.ts +2803 -844
  138. package/templates/gym-fitness/himalaya.content.json +3 -0
  139. package/templates/gym-fitness/tier5-src/_shared/booking-location.ts +147 -0
  140. package/templates/gym-fitness/tier5-src/_shared/booking-session.ts +222 -0
  141. package/templates/gym-fitness/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  142. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +19 -3
  143. package/templates/gym-fitness/tier5-src/_shared/event-registration.ts +86 -0
  144. package/templates/gym-fitness/tier5-src/_shared/himi-restaurants.ts +2 -1
  145. package/templates/gym-fitness/tier5-src/_shared/owner-editor-value.ts +164 -0
  146. package/templates/gym-fitness/tier5-src/_shared/store-product-info.ts +50 -0
  147. package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +21 -8
  148. package/templates/gym-fitness/tier5-src/himi-booking-service/index.ts +277 -14
  149. package/templates/gym-fitness/tier5-src/himi-booking-services/index.ts +351 -92
  150. package/templates/gym-fitness/tier5-src/himi-booking-time/index.ts +25 -5
  151. package/templates/gym-fitness/tier5-src/himi-branded-lite-agenda/index.ts +219 -220
  152. package/templates/gym-fitness/tier5-src/himi-branded-lite-category/index.ts +3 -2
  153. package/templates/gym-fitness/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  154. package/templates/gym-fitness/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  155. package/templates/gym-fitness/tier5-src/himi-events-detail/index.ts +12 -86
  156. package/templates/gym-fitness/tier5-src/himi-events-list/index.ts +47 -6
  157. package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +14 -3
  158. package/templates/gym-fitness/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  159. package/templates/gym-fitness/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  160. package/templates/gym-fitness-owner/dev/index.ts +132 -66
  161. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +19 -3
  162. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  163. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  164. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  165. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  166. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  167. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  168. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  169. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  170. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  171. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  172. package/templates/nonprofit-cause/config.ts +36 -1
  173. package/templates/nonprofit-cause/dev/index.ts +1925 -396
  174. package/templates/nonprofit-cause/himalaya.content.json +6 -0
  175. package/templates/nonprofit-cause/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  176. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +19 -3
  177. package/templates/nonprofit-cause/tier5-src/_shared/event-registration.ts +86 -0
  178. package/templates/nonprofit-cause/tier5-src/_shared/himi-restaurants.ts +2 -1
  179. package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +21 -8
  180. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  181. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-group-about/index.ts +243 -0
  182. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  183. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  184. package/templates/nonprofit-cause/tier5-src/himi-events-detail/index.ts +12 -86
  185. package/templates/nonprofit-cause/tier5-src/himi-events-list/index.ts +47 -6
  186. package/templates/nonprofit-cause/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  187. package/templates/nonprofit-cause/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  188. package/templates/nonprofit-cause-owner/dev/index.ts +85 -65
  189. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +19 -3
  190. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-rich-guard.ts +27 -0
  191. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  192. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  193. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  194. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  195. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  196. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  197. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  198. package/templates/owner-lite/dev/index.ts +132 -66
  199. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +19 -3
  200. package/templates/owner-lite/tier5-src/himi-owner-lite-event/index.ts +16 -8
  201. package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  202. package/templates/owner-lite/tier5-src/himi-owner-lite-events/index.ts +4 -2
  203. package/templates/owner-lite/tier5-src/himi-owner-lite-post/index.ts +7 -0
  204. package/templates/owner-lite/tier5-src/himi-owner-lite-product/index.ts +39 -6
  205. package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  206. package/templates/owner-lite/tier5-src/himi-owner-lite-products/index.ts +7 -4
  207. package/templates/owner-lite/tier5-src/himi-owner-lite-service/index.ts +38 -7
  208. package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  209. package/templates/owner-lite/tier5-src/himi-owner-lite-services/index.ts +4 -2
  210. package/templates/podcast-creator/config.ts +27 -1
  211. package/templates/podcast-creator/dev/index.ts +1051 -158
  212. package/templates/podcast-creator/himalaya.content.json +3 -0
  213. package/templates/podcast-creator/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  214. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +19 -3
  215. package/templates/podcast-creator/tier5-src/_shared/event-registration.ts +86 -0
  216. package/templates/podcast-creator/tier5-src/_shared/himi-restaurants.ts +2 -1
  217. package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +21 -8
  218. package/templates/podcast-creator/tier5-src/himi-branded-lite-feed/index.ts +4 -3
  219. package/templates/podcast-creator/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  220. package/templates/podcast-creator/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  221. package/templates/podcast-creator/tier5-src/himi-branded-lite-podcasts/index.ts +2 -1
  222. package/templates/podcast-creator/tier5-src/himi-events-detail/index.ts +12 -86
  223. package/templates/podcast-creator/tier5-src/himi-events-list/index.ts +47 -6
  224. package/templates/podcast-creator/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  225. package/templates/podcast-creator/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  226. package/templates/podcast-creator-owner/dev/index.ts +53 -1
  227. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +19 -3
  228. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  229. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  230. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  231. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  232. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  233. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  234. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  235. package/templates/portfolio-creative/config.ts +27 -1
  236. package/templates/portfolio-creative/dev/index.ts +1305 -468
  237. package/templates/portfolio-creative/himalaya.content.json +3 -0
  238. package/templates/portfolio-creative/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  239. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +19 -3
  240. package/templates/portfolio-creative/tier5-src/_shared/event-registration.ts +86 -0
  241. package/templates/portfolio-creative/tier5-src/_shared/himi-restaurants.ts +2 -1
  242. package/templates/portfolio-creative/tier5-src/_shared/owner-editor-value.ts +164 -0
  243. package/templates/portfolio-creative/tier5-src/_shared/store-product-info.ts +50 -0
  244. package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +21 -8
  245. package/templates/portfolio-creative/tier5-src/himi-branded-lite-category/index.ts +3 -2
  246. package/templates/portfolio-creative/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  247. package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio/index.ts +2 -1
  248. package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -2
  249. package/templates/portfolio-creative/tier5-src/himi-events-detail/index.ts +12 -86
  250. package/templates/portfolio-creative/tier5-src/himi-events-list/index.ts +47 -6
  251. package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +14 -3
  252. package/templates/portfolio-creative/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  253. package/templates/portfolio-creative-owner/dev/index.ts +129 -66
  254. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +19 -3
  255. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  256. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  257. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  258. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  259. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  260. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  261. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  262. package/templates/restaurant/config.ts +27 -1
  263. package/templates/restaurant/dev/index.ts +1051 -158
  264. package/templates/restaurant/himalaya.content.json +3 -0
  265. package/templates/restaurant/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  266. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +19 -3
  267. package/templates/restaurant/tier5-src/_shared/event-registration.ts +86 -0
  268. package/templates/restaurant/tier5-src/_shared/himi-restaurants.ts +2 -1
  269. package/templates/restaurant/tier5-src/himi-blog-post/index.ts +21 -8
  270. package/templates/restaurant/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  271. package/templates/restaurant/tier5-src/himi-events-detail/index.ts +12 -86
  272. package/templates/restaurant/tier5-src/himi-events-list/index.ts +47 -6
  273. package/templates/restaurant/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  274. package/templates/restaurant-owner/dev/index.ts +132 -66
  275. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +19 -3
  276. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  277. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  278. package/templates/restaurant-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  279. package/templates/restaurant-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  280. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  281. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  282. package/templates/restaurant-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  283. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  284. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  285. package/templates/restaurant-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
  286. package/templates/retail-store/config.ts +27 -1
  287. package/templates/retail-store/dev/index.ts +597 -191
  288. package/templates/retail-store/himalaya.content.json +3 -0
  289. package/templates/retail-store/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  290. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +19 -3
  291. package/templates/retail-store/tier5-src/_shared/himi-restaurants.ts +2 -1
  292. package/templates/retail-store/tier5-src/_shared/owner-editor-value.ts +164 -0
  293. package/templates/retail-store/tier5-src/_shared/store-product-info.ts +50 -0
  294. package/templates/retail-store/tier5-src/himi-blog-post/index.ts +21 -8
  295. package/templates/retail-store/tier5-src/himi-branded-lite-category/index.ts +3 -2
  296. package/templates/retail-store/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  297. package/templates/retail-store/tier5-src/himi-store-product/index.ts +13 -3
  298. package/templates/retail-store/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  299. package/templates/retail-store-owner/dev/index.ts +50 -1
  300. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +19 -3
  301. package/templates/retail-store-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  302. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product/index.ts +39 -6
  303. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +14 -8
  304. package/templates/retail-store-owner/tier5-src/himi-owner-lite-products/index.ts +7 -4
  305. package/templates/yoga-studio/config.ts +27 -1
  306. package/templates/yoga-studio/dev/index.ts +2996 -877
  307. package/templates/yoga-studio/himalaya.content.json +3 -0
  308. package/templates/yoga-studio/tier5-src/_shared/booking-location.ts +147 -0
  309. package/templates/yoga-studio/tier5-src/_shared/booking-session.ts +222 -0
  310. package/templates/yoga-studio/tier5-src/_shared/branded-lite-hosted.ts +1 -1
  311. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +19 -3
  312. package/templates/yoga-studio/tier5-src/_shared/event-registration.ts +86 -0
  313. package/templates/yoga-studio/tier5-src/_shared/himi-restaurants.ts +2 -1
  314. package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +21 -8
  315. package/templates/yoga-studio/tier5-src/himi-booking-service/index.ts +277 -14
  316. package/templates/yoga-studio/tier5-src/himi-booking-services/index.ts +351 -92
  317. package/templates/yoga-studio/tier5-src/himi-booking-time/index.ts +25 -5
  318. package/templates/yoga-studio/tier5-src/himi-branded-lite-agenda/index.ts +219 -220
  319. package/templates/yoga-studio/tier5-src/himi-branded-lite-notifsettings/index.ts +173 -0
  320. package/templates/yoga-studio/tier5-src/himi-branded-lite-plans/index.ts +2 -1
  321. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/facts.ts +52 -0
  322. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +21 -11
  323. package/templates/yoga-studio/tier5-src/himi-events-detail/index.ts +12 -86
  324. package/templates/yoga-studio/tier5-src/himi-events-list/index.ts +47 -6
  325. package/templates/yoga-studio/tier5-src/wix-spaces-plan-detail/index.ts +2 -1
  326. package/templates/yoga-studio/tier5-src/wix-spaces-whats-new/index.ts +2 -1
  327. package/templates/yoga-studio-owner/dev/index.ts +85 -65
  328. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +19 -3
  329. package/templates/yoga-studio-owner/tier5-src/_shared/owner-rich-guard.ts +27 -0
  330. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event/index.ts +16 -8
  331. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +16 -7
  332. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-events/index.ts +4 -2
  333. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post/index.ts +7 -0
  334. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service/index.ts +38 -7
  335. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +53 -7
  336. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-services/index.ts +4 -2
@@ -0,0 +1,173 @@
1
+ import { withOnlineWrites } from "../_shared/online-actions.js";
2
+ // himi-branded-lite-notifsettings — reads the site's notification topics + the member's preferences.
3
+ // a failed query is an error with Retry (or Sign in when Wix refuses the
4
+ // visitor) and an empty one is a real empty state — never sample rows presented as
5
+ // the customer's own. Samples appear only in the host's explicit sample preview
6
+ // (_shared/branded-load-state.ts).
7
+ // any exist. INTERACTIVE: tapping a row toggles that topic On/Off — optimistic +
8
+ // best-effort upsertMyNotificationPreferences (a no-op on sample rows).
9
+ import { defineService as registerService } from "@himalaya/state";
10
+ import { withEmptyFlags } from "../_shared/empty-flags.js";
11
+ import { stashCopy } from "../_shared/himi-copy.js";
12
+ import { memberAccess, assertMemberAccess, type MemberAccess } from "../_shared/member-access.js";
13
+ import { LOADING, loaded, loadFailed, type LoadCopy, type LoadFlags } from "../_shared/branded-load-state.js";
14
+ import {
15
+ getNotificationTopics,
16
+ getMyNotificationPreferences,
17
+ upsertMyNotificationPreferences,
18
+ muteStatusFor,
19
+ readTopics,
20
+ readPreferences,
21
+ isDeliveredNow,
22
+ isLocked,
23
+ preferenceFor,
24
+ type NotificationTopicDefinition,
25
+ type PreferenceInfo,
26
+ } from "@himalaya/wix-notification-preferences";
27
+
28
+ /** The channel this surface delivers on — read for the row state, written on toggle.
29
+ * One constant, so the read and the write cannot drift apart. */
30
+ const CHANNEL = "MOBILE";
31
+
32
+ /** `note` is the line under a row's switch — only a locked topic has one. */
33
+ interface Row { id: string; name: string; subtitle: string; note: string; on: boolean; locked: boolean;
34
+ /** Inverse of `locked` (hiddenWhen hides on TRUE) and the plain On/Off a locked row shows. */
35
+ notLocked: boolean; stateLabel: string }
36
+ interface S extends LoadFlags {
37
+ preferencesMember: MemberAccess | null;
38
+ topics: Row[]; emptyText: string; actionMessage: string;
39
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
40
+ * shows only when the site really has none, and the unavailable one only when the
41
+ * query failed. A failed query must never render the sentence that claims the
42
+ * customer has no content. */
43
+ notMockEmpty: boolean; notMockUnavailable: boolean;
44
+ }
45
+
46
+ /** Whether this topic is actually being DELIVERED — not merely what the recipient
47
+ * asked for. `isDeliveredNow` prefers the server's `granted` verdict, so a topic the
48
+ * member unmuted under a channel-wide mute renders OFF instead of promising a
49
+ * notification that will never arrive. No preference row at all ⇒ the topic's default,
50
+ * which is delivery. */
51
+ function isOn(topicId: string, prefs: PreferenceInfo[]): boolean {
52
+ const p = preferenceFor(topicId, prefs, CHANNEL);
53
+ if (!p) return true;
54
+ return isDeliveredNow(p.stateData);
55
+ }
56
+
57
+ /** A row the server says cannot be changed here (a broader rule overrides it). */
58
+ function locked(topicId: string, prefs: PreferenceInfo[]): boolean {
59
+ return isLocked(preferenceFor(topicId, prefs, CHANNEL)?.stateData);
60
+ }
61
+
62
+ /** Row subtitle doubles as an affordance: current state + how to change it. */
63
+ function subtitleFor(on: boolean): string {
64
+ return on ? "On · tap to mute" : "Off · tap to enable";
65
+ }
66
+
67
+ function row(id: string, name: string, on: boolean): Row {
68
+ return { id, name, subtitle: subtitleFor(on), note: "", on, locked: false, notLocked: true, stateLabel: on ? "On" : "Off" };
69
+ }
70
+
71
+ function norm(t: NotificationTopicDefinition, prefs: PreferenceInfo[]): Row {
72
+ const name = (t.displayName || "Notification").trim() || "Notification";
73
+ const id = t.topicId || name;
74
+ const r = row(id, name, isOn(id, prefs));
75
+ // An overridden row says so instead of offering a toggle that cannot take effect.
76
+ return locked(id, prefs)
77
+ ? { ...r, locked: true, notLocked: false, subtitle: "Managed by your channel settings", note: "Managed by your channel settings" }
78
+ : r;
79
+ }
80
+
81
+ const MOCK: Row[] = [
82
+ row("sample-orders", "Order updates", true),
83
+ row("sample-bookings", "Bookings & reminders", true),
84
+ row("sample-promos", "Promotions & offers", false),
85
+ row("sample-community", "Community activity", true),
86
+ ];
87
+
88
+ /** sample rows reach the screen ONLY in the host's explicit sample preview;
89
+ * a failed or empty query is an honest error / empty state (branded-load-state.ts). */
90
+ const COPY: LoadCopy<Row> = {
91
+ prefix: "notifsettings",
92
+ empty: "No notification topics to manage yet.",
93
+ error: "Notification settings couldn't be loaded.",
94
+ signIn: "Sign in to manage your notifications.",
95
+ sample: () => MOCK.map((r) => ({ ...r })),
96
+ };
97
+
98
+ const defineService = withOnlineWrites(registerService, ["toggle"], { messageFields: ["actionMessage"] });
99
+ const INITIAL = { ...LOADING, topics: [] as Row[], emptyText: "", actionMessage: "", preferencesMember: null };
100
+ const signInRequired = () => new Error("Unauthenticated: sign in to manage notifications");
101
+ let loadVersion = 0;
102
+ const svc = defineService<S, {
103
+ prepare: (s: S) => S;
104
+ load: (s: S, arg?: unknown) => Promise<S | undefined>;
105
+ toggle: (s: S, p?: { id?: string; value?: unknown }) => Promise<S | undefined>;
106
+ }>("himiBrandedNotifSettings", withEmptyFlags({
107
+ state: INITIAL,
108
+ actions: {
109
+ prepare: (s) => ({ ...s, ...INITIAL }),
110
+ load: async (s, arg): Promise<S | undefined> => {
111
+ stashCopy(arg);
112
+ const version = ++loadVersion;
113
+ await svc.prepare();
114
+ try {
115
+ const member = await memberAccess();
116
+ if (!member.email) throw signInRequired();
117
+ const [topicsRes, prefsRes] = await Promise.all([
118
+ getNotificationTopics(),
119
+ getMyNotificationPreferences(),
120
+ ]);
121
+ await assertMemberAccess(member);
122
+ const prefs = readPreferences(prefsRes);
123
+ const topics = readTopics(topicsRes).map((t) => norm(t, prefs));
124
+ return version === loadVersion ? { ...s, ...loaded("topics", topics, COPY), preferencesMember: member, actionMessage: "" } : undefined;
125
+ } catch {
126
+ const member = await memberAccess();
127
+ // An authenticated denial is not repaired by an endless sign-in loop.
128
+ const failure = member.email ? new Error(COPY.error) : signInRequired();
129
+ return version === loadVersion ? { ...s, ...loadFailed("topics", failure, COPY), preferencesMember: null, actionMessage: "" } : undefined;
130
+ }
131
+ },
132
+ // Flip one topic's On/Off. Optimistic (updates the row immediately); the real
133
+ // upsert is best-effort and a no-op on sample rows.
134
+ toggle: async (s, p): Promise<S | undefined> => {
135
+ const id = p?.id;
136
+ const target = s.topics.find((r) => r.id === id);
137
+ if (s.busy || !id || !target || target.locked) return undefined;
138
+ if (!s.isMock) {
139
+ try {
140
+ if (!s.preferencesMember) return undefined;
141
+ await assertMemberAccess(s.preferencesMember);
142
+ if (svc.getState().preferencesMember !== s.preferencesMember) return undefined;
143
+ } catch {
144
+ await svc.load();
145
+ return undefined;
146
+ }
147
+ }
148
+ let nextOn = true;
149
+ const topics = s.topics.map((r) => {
150
+ if (r.id !== id) return r;
151
+ // A Toggle sends the NEXT state as `value` (action-payload contract); honour it so
152
+ // a repeated event can never flip the row the wrong way. A bare tap still flips.
153
+ nextOn = typeof p?.value === "boolean" ? p.value : !r.on;
154
+ return { ...r, on: nextOn, subtitle: subtitleFor(nextOn), stateLabel: nextOn ? "On" : "Off" };
155
+ });
156
+ if (!s.isMock && !id.startsWith("sample-")) {
157
+ try {
158
+ // `stateData.state` is a SCALAR enum — the old nested `{ muteStatus }` was a
159
+ // shape this service does not have, so every write was malformed.
160
+ await upsertMyNotificationPreferences([
161
+ { topicId: id, channel: CHANNEL, stateData: { state: muteStatusFor(nextOn) } },
162
+ ]);
163
+ } catch { /* keep the optimistic state */ }
164
+ try { await assertMemberAccess(s.preferencesMember!); }
165
+ catch { await svc.load(); return undefined; }
166
+ }
167
+ return svc.getState().preferencesMember === s.preferencesMember ? { ...svc.getState(), topics } : undefined;
168
+ },
169
+ },
170
+ }, { topics: "noTopics" }));
171
+ svc.prepare();
172
+ svc.load();
173
+ export { svc };
@@ -1,3 +1,4 @@
1
+ import { descriptionBody } from "../_shared/rich-body.js";
1
2
  import { paymentSetupMessage } from "../_shared/payment-setup.js";
2
3
  import { offlineWriteMessage, offlineWritesEnabled, withOnlineWrites } from "../_shared/online-actions.js";
3
4
  import { connectivity } from "@himalaya/state";
@@ -140,7 +141,7 @@ function planRow(plan: WixPlan): PlanRow {
140
141
  return {
141
142
  id: plan.id || "",
142
143
  name,
143
- description: (plan.description ?? "").trim(),
144
+ description: descriptionBody(plan.description).bodyText,
144
145
  trialBadge,
145
146
  noTrialBadge: !trialBadge,
146
147
  priceLine: money(plan),
@@ -0,0 +1,52 @@
1
+ import type { WixProgramFull } from "@himalaya/wix-online-programs";
2
+ import { copyParam } from "../_shared/himi-copy.js";
3
+ import { formatDateMedium } from "../_shared/engine-intl.js";
4
+
5
+ export interface ProgramFact { id: string; icon: string; title: string; body: string }
6
+
7
+ /** Only explicit Program fields establish a fact. Missing access is never public. */
8
+ export function programFacts(program: WixProgramFull, locale: string): ProgramFact[] {
9
+ const facts: ProgramFact[] = [];
10
+ const timeline = program.timeline;
11
+ if (timeline?.selfPaced === true) facts.push({
12
+ id: "pace", icon: "clock",
13
+ title: copyParam("program.facts.selfPacedTitle", "Self-paced"),
14
+ body: copyParam("program.facts.selfPacedBody", "Complete steps at your own pace."),
15
+ });
16
+ else if (timeline?.selfPaced === false) facts.push({
17
+ id: "pace", icon: "clock",
18
+ title: copyParam("program.facts.scheduledTitle", "Scheduled"),
19
+ body: copyParam("program.facts.scheduledBody", "Follow the program schedule."),
20
+ });
21
+
22
+ const rawStart = timeline?.startDate;
23
+ // A date-only field is a calendar day, not midnight in the viewer's time zone. Build it at LOCAL noon and
24
+ // format it in the local zone: that reads the same day with ECMA-402 and on engines without it (watch/Wear),
25
+ // whose fallback ignores a timeZone argument and reads local getters.
26
+ const dateOnly = typeof rawStart === "string" ? /^(\d{4})-(\d{2})-(\d{2})$/.exec(rawStart.trim()) : null;
27
+ const date = dateOnly ? new Date(Number(dateOnly[1]), Number(dateOnly[2]) - 1, Number(dateOnly[3]), 12)
28
+ : typeof rawStart === "string" && rawStart.trim() ? new Date(rawStart) : undefined;
29
+ if (date && Number.isFinite(date.getTime())) {
30
+ let body: string;
31
+ try { body = formatDateMedium(date, locale); }
32
+ catch { body = formatDateMedium(date, "en"); }
33
+ facts.push({ id: "start", icon: "calendar", title: copyParam("program.facts.fixedStartTitle", "Fixed start date"), body });
34
+ } else if (timeline?.selfPaced === true && (rawStart === undefined || rawStart === "")) facts.push({
35
+ id: "start", icon: "calendar",
36
+ title: copyParam("program.facts.flexibleStartTitle", "Flexible start date"),
37
+ body: copyParam("program.facts.flexibleStartBody", "Start whenever you're ready."),
38
+ });
39
+
40
+ const access = program.restrictions?.accessType?.toUpperCase();
41
+ if (access === "PUBLIC") facts.push({
42
+ id: "access", icon: "globe",
43
+ title: copyParam("program.facts.publicTitle", "Public"),
44
+ body: copyParam("program.facts.publicBody", "Anyone can join."),
45
+ });
46
+ else if (access === "PRIVATE") facts.push({
47
+ id: "access", icon: "lock",
48
+ title: copyParam("program.facts.privateTitle", "Private"),
49
+ body: copyParam("program.facts.privateBody", "Approval is required to join."),
50
+ });
51
+ return facts;
52
+ }
@@ -40,7 +40,8 @@ import {
40
40
  } from "@himalaya/wix-online-programs";
41
41
  import { createRedirectSession } from "@himalaya/wix-redirects";
42
42
  import { getMyMember } from "@himalaya/wix-members";
43
- import { session as wixPlatformSession } from "@himalaya/wix-platform";
43
+ import { session as wixPlatformSession, i18n } from "@himalaya/wix-platform";
44
+ import { programFacts, type ProgramFact } from "./facts.js";
44
45
  import { cleanTitle, initials } from "../_shared/himi-text.js";
45
46
  import { copyParam, copyTemplate, stashCopy } from "../_shared/himi-copy.js";
46
47
  import { isPaymentSetupFailure, payCashierOrder } from "@himalaya/checkout";
@@ -62,8 +63,10 @@ interface State {
62
63
  description: string;
63
64
  descriptionJson: string;
64
65
  hasDescriptionJson: boolean;
65
- /** "N sections · M steps" / "Self-paced" — structure meta line. */
66
+ /** "N sections · M steps" — structure meta line; pace has its own fact row. */
66
67
  meta: string;
68
+ facts: ProgramFact[];
69
+ hideFacts: boolean;
67
70
  /** Formatted price when the payload carries one ("" for free/absent). */
68
71
  priceLine: string;
69
72
  image: string;
@@ -105,8 +108,11 @@ function inputId(): string {
105
108
 
106
109
  function derive(state: State): State {
107
110
  const empty = state.status === "ready" && !state.name;
111
+ const facts = Array.isArray(state.facts) ? state.facts : [];
108
112
  return {
109
113
  ...state,
114
+ facts,
115
+ hideFacts: facts.length === 0,
110
116
  hasDescriptionJson: state.descriptionJson !== "",
111
117
  isLoading: state.status === "loading",
112
118
  notLoading: state.status !== "loading",
@@ -132,9 +138,7 @@ function metaOf(p: WixProgram): string {
132
138
  const steps = Number(summary?.stepCount ?? 0);
133
139
  if (sections) bits.push(`${sections} ${sections === 1 ? "section" : "sections"}`);
134
140
  if (steps) bits.push(`${steps} ${steps === 1 ? "step" : "steps"}`);
135
- if (bits.length) return bits.join(" · ");
136
- const selfPaced = (p as { timeline?: { selfPaced?: boolean } }).timeline?.selfPaced;
137
- return selfPaced ? copyParam("program.selfPacedLabel", "Self-paced") : "";
141
+ return bits.join(" · ");
138
142
  }
139
143
 
140
144
  function priceOf(p: WixProgram): string {
@@ -149,10 +153,10 @@ function priceOf(p: WixProgram): string {
149
153
  }
150
154
  }
151
155
 
152
- function ready(state: State, p: WixProgram | undefined): State {
156
+ function ready(state: State, p: WixProgram | undefined, locale: string): State {
153
157
  const stateOf = ((p?.state ?? p?.status ?? "") as string).toUpperCase();
154
158
  if (!p || stateOf === "DRAFT") {
155
- return derive({ ...state, status: "ready", name: "", description: "", descriptionJson: "", meta: "", priceLine: "", image: "", initial: "", emptyText: copyParam("program.emptyText", "This program is no longer available.") });
159
+ return derive({ ...state, status: "ready", name: "", description: "", descriptionJson: "", meta: "", facts: [], priceLine: "", image: "", initial: "", emptyText: copyParam("program.emptyText", "This program is no longer available.") });
156
160
  }
157
161
  const name = titleOf(p);
158
162
  const image = programImage(p, WIX_DETAIL_IMAGE);
@@ -164,6 +168,7 @@ function ready(state: State, p: WixProgram | undefined): State {
164
168
  description: body.bodyText,
165
169
  descriptionJson: body.bodyJson,
166
170
  meta: metaOf(p),
171
+ facts: programFacts(p, locale),
167
172
  priceLine: priceOf(p),
168
173
  image,
169
174
  initial: initials(name),
@@ -274,7 +279,7 @@ const pricing = commerce.pricing;
274
279
  const PRICING_NOT_HYDRATED = [
275
280
  "priceLine", "priceNote", "hidePriceRetry", "enrollDisabled", "enrollNote", "enrollHidden", "buyHidden", "buyLabel", "buyDisabled", "buyNote",
276
281
  ] as const;
277
- const CONTENT_KEYS = ["status", "name", "description", "descriptionJson", "meta", "image", "initial", "emptyText"] as const;
282
+ const CONTENT_KEYS = ["status", "name", "description", "descriptionJson", "meta", "facts", "image", "initial", "emptyText"] as const;
278
283
  type Content = Pick<State, (typeof CONTENT_KEYS)[number]>;
279
284
  const NO_PRICE: IapPriceFields = { priceText: "", priceNote: "", priceLoading: false, hidePriceRetry: true, actionDisabled: true, actionNote: "" };
280
285
  const NOT_APPLE: IapPurchaseFields = { appleRoute: false, accessReady: false, buyLabel: "", buyDisabled: true, buyBusy: false, buyNote: "" };
@@ -307,9 +312,12 @@ async function load(state: State, arg?: unknown): Promise<undefined> {
307
312
  if (presentation === "sheet" || presentation === "full_screen") signInPresentation = presentation;
308
313
  const id = inputId();
309
314
  const seq = ++loadSeq;
310
- const found = await fetchProgram(inputId()).then((program) => ({ program }), () => undefined);
311
- if (seq !== loadSeq) return undefined;
312
- content = found ? contentOf(ready(state, found.program)) : { ...content, status: "error" };
315
+ const [found, locale] = await Promise.all([
316
+ fetchProgram(id).then((program) => ({ program }), () => undefined),
317
+ i18n.locale().catch(() => "en"),
318
+ ]);
319
+ if (seq !== loadSeq || inputId() !== id) return undefined;
320
+ content = found ? contentOf(ready(state, found.program, locale)) : { ...content, status: "error" };
313
321
  pricedItem = found?.program ? onlineProgramItem({ id: inputId() }, priceOf(found.program)) : pricedItem;
314
322
  void svc.pricingPublish();
315
323
  const prices = found?.program ? priceProgram() : Promise.resolve();
@@ -467,6 +475,8 @@ const INITIAL_STATE: State = derive({
467
475
  descriptionJson: "",
468
476
  hasDescriptionJson: false,
469
477
  meta: "",
478
+ facts: [],
479
+ hideFacts: true,
470
480
  priceLine: "",
471
481
  image: "",
472
482
  initial: "",
@@ -35,8 +35,10 @@ import {
35
35
  type EventRow,
36
36
  } from "../_shared/himi-events";
37
37
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
38
+ import { eventPageUrlOf, eventStatus, registrationKindOf, registrationUi, registrationUrlOf, type RegistrationKind } from "../_shared/event-registration.js";
39
+ export { eventPageUrlOf, registrationKindOf, registrationUrlOf, type RegistrationKind };
38
40
  import { openRichLink } from "../_shared/rich-content-link.js";
39
- import { richBody } from "../_shared/rich-body.js";
41
+ import { descriptionBody } from "../_shared/rich-body.js";
40
42
  import { mayUseRegularTicketCheckout } from "../_shared/iap-attendance.js";
41
43
  import { ATTENDANCE_INITIAL, ATTENDANCE_NOT_HYDRATED, createAttendancePurchase, type AttendancePurchaseFields } from "../_shared/iap-attendance-purchase.js";
42
44
  import { resetUnhydratedKeysOnOlderRuntime } from "../_shared/iap-worker.js";
@@ -88,9 +90,6 @@ interface TicketTierRow {
88
90
  notAvailable?: boolean;
89
91
  }
90
92
 
91
- /** Registration CTA kind derived from the event's registration/eventType. */
92
- export type RegistrationKind = "rsvp" | "tickets" | "external" | "none";
93
-
94
93
  // Offline-first (data-offline): the event detail is a read-through single-item read —
95
94
  // it shares the `events` entity written by the list, and also write-throughs its own
96
95
  // fetched row so a deep-linked detail is cached for the next offline open. On a failed
@@ -302,7 +301,7 @@ function loadedTiers(defs: WixAvailableTicket[]): { rows: TicketTierRow[]; ticke
302
301
  const ticket = tickets.get(row.ticketDefinitionId ?? row.id);
303
302
  if (!ticket) return { ...row, quantity: 0, description: "", feeNote: "", policy: "", noPolicy: true, notAvailable: !row.available, notGuestPrice: !row.isGuestPrice, maxQuantity: 0 };
304
303
  const policy = (ticket as unknown as { policy?: string; policyText?: string }).policyText || (ticket as unknown as { policy?: string }).policy || "";
305
- return { ...row, quantity: 0, description: ticket.description || "", feeNote: ((ticket as unknown as { wixFeeConfig?: { type?: string }; feeType?: string }).wixFeeConfig?.type ?? (ticket as unknown as { feeType?: string }).feeType) === "FEE_ADDED_AT_CHECKOUT" ? detailCopy("event.serviceFee", "+ ticket service fee") : "", policy, noPolicy: !policy, notAvailable: !row.available, notGuestPrice: !row.isGuestPrice, priceAmount: row.priceAmount ?? Number(ticket.price?.amount ?? 0), rowLimit: row.available ? ticket.limitPerCheckout ?? 99 : 0, maxQuantity: row.available ? ticket.limitPerCheckout ?? 99 : 0 };
304
+ return { ...row, quantity: 0, description: descriptionBody(ticket.description).bodyText, feeNote: ((ticket as unknown as { wixFeeConfig?: { type?: string }; feeType?: string }).wixFeeConfig?.type ?? (ticket as unknown as { feeType?: string }).feeType) === "FEE_ADDED_AT_CHECKOUT" ? detailCopy("event.serviceFee", "+ ticket service fee") : "", policy, noPolicy: !policy, notAvailable: !row.available, notGuestPrice: !row.isGuestPrice, priceAmount: row.priceAmount ?? Number(ticket.price?.amount ?? 0), rowLimit: row.available ? ticket.limitPerCheckout ?? 99 : 0, maxQuantity: row.available ? ticket.limitPerCheckout ?? 99 : 0 };
306
305
  });
307
306
  return { rows, tickets };
308
307
  }
@@ -451,29 +450,6 @@ function calendarFallbackUrl(s: S, end: number): string {
451
450
  * shortDescription: REGISTRATION fills eventType, which skips the query enrichment that used to supply them. */
452
451
  export const EVENT_DETAIL_FIELDSETS: WixEventFieldset[] = ["DETAILS", "TEXTS", "REGISTRATION", "URLS", "FORM", "AGENDA"];
453
452
 
454
- /** Map registration status and decoded eventType onto the CTA variant. The decoded
455
- * `eventType` already folds registration.type / registration.initialType /
456
- * the legacy flat `type` (decodeEvent, #478). Without a recognized type or
457
- * status there is no registration action to offer. */
458
- export function registrationKindOf(e: WixEvent): RegistrationKind {
459
- const raw = e as unknown as { registration?: { type?: string; initialType?: string; status?: string } };
460
- const value = (raw.registration?.type ?? e.eventType ?? raw.registration?.initialType ?? "").toUpperCase();
461
- if (value === "NONE" || value.includes("NO_REGISTRATION")) return "none";
462
- // Open statuses select the footer action, so they must also select the
463
- // inventory read and action guards when type metadata is absent or stale.
464
- switch (raw.registration?.status?.toUpperCase()) {
465
- case "OPEN_TICKETS": return "tickets";
466
- case "OPEN_EXTERNAL": return "external";
467
- case "OPEN_RSVP":
468
- case "OPEN_RSVP_WAITLIST_ONLY":
469
- case "SCHEDULED_RSVP": return "rsvp";
470
- }
471
- if (value.includes("EXTERNAL")) return "external";
472
- if (value.includes("TICKET")) return "tickets";
473
- if (value.includes("RSVP")) return "rsvp";
474
- return "none";
475
- }
476
-
477
453
  function detailCopy(key: string, fallback: string): string { return copyParam(key, fallback); }
478
454
 
479
455
  function detailDate(e: WixEvent): { line: string; zone: string; start: number; end: number } {
@@ -536,69 +512,17 @@ function detailPlace(e: WixEvent): { value: string; mapUrl: string; hideMapTap:
536
512
  return { value, mapUrl: mapped, hideMapTap: !mapped, icon: "location" };
537
513
  }
538
514
 
539
- function eventStatus(e: WixEvent): string {
540
- const raw = e as unknown as { status?: string };
541
- return (raw.status ?? e.eventStatus ?? "").toUpperCase();
542
- }
543
-
544
- function registrationUi(e: WixEvent, now: number): { state: string; label: string; action: string; caption: string; notice: string } {
545
- const raw = e as unknown as { registration?: { status?: string; registrationPaused?: boolean; registrationDisabled?: boolean; allowedGuestTypes?: string; rsvp?: { startDate?: string }; tickets?: { soldOut?: boolean }; external?: { url?: string } }; form?: { messages?: { registrationClosed?: { message?: string; exploreEventsActionLabel?: string }; ticketsUnavailable?: { message?: string; exploreEventsActionLabel?: string } } } };
546
- const r = raw.registration ?? {};
547
- const status = eventStatus(e);
548
- const type = registrationKindOf(e);
549
- if (status === "CANCELED") return { state: "notice", label: "", action: "", caption: "", notice: detailCopy("event.canceledNotice", "This event was canceled.") };
550
- if (status === "ENDED") return { state: "notice", label: "", action: "", caption: "", notice: detailCopy("event.endedNotice", "This event has ended.") };
551
- if (type === "none" || r.registrationDisabled) return { state: "none", label: "", action: "", caption: "", notice: "" };
552
- const ownerClosed = raw.form?.messages?.registrationClosed?.message;
553
- const ownerSold = raw.form?.messages?.ticketsUnavailable?.message;
554
- if (["CLOSED_AUTOMATICALLY", "CLOSED_MANUALLY"].includes(r.status || "") || r.registrationPaused) return { state: "notice", label: "", action: "", caption: "", notice: ownerClosed || detailCopy("event.closedNotice", "Registration is closed.") };
555
- if (type === "tickets" && r.tickets?.soldOut) return { state: "notice", label: "", action: "", caption: "", notice: ownerSold || detailCopy("event.soldOutNotice", "Tickets are sold out.") };
556
- if (r.status === "SCHEDULED_RSVP") return { state: "scheduled", label: detailCopy("event.rsvpCta", "RSVP"), action: "", caption: detailCopy("event.opensCaption", "RSVP opens {date}.").replace("{date}", r.rsvp?.startDate ? formatWeekdayDateTime(new Date(r.rsvp.startDate)) : ""), notice: "" };
557
- if (r.status === "OPEN_RSVP_WAITLIST_ONLY") return { state: "waitlist", label: detailCopy("event.waitlistCta", "Join Waitlist"), action: "himi.open", caption: detailCopy("event.fullCaption", "This event is full."), notice: "" };
558
- if (r.status === "OPEN_TICKETS" || type === "tickets") return { state: "tickets", label: detailCopy("event.ticketsCta", "Buy Tickets"), action: "openTickets", caption: "", notice: "" };
559
- if (r.status === "OPEN_EXTERNAL" || type === "external") return registrationUrlOf(e)
560
- ? { state: "external", label: detailCopy("event.externalCta", "Register"), action: "openRegistration", caption: "", notice: "" }
561
- : { state: "none", label: "", action: "", caption: "", notice: "" };
562
- if (type === "rsvp" && r.status === "OPEN_RSVP") return { state: "rsvp", label: detailCopy("event.rsvpCta", "RSVP"), action: "himi.open", caption: "", notice: "" };
563
- if (r.status === "OPEN_RSVP" || (type === "rsvp" && !r.status)) return { state: "rsvp", label: detailCopy("event.rsvpCta", "RSVP"), action: "himi.open", caption: "", notice: "" };
564
- void now;
565
- return { state: "none", label: "", action: "", caption: "", notice: "" };
566
- }
567
-
568
- /** External registration URL / event page URL from the raw payload ("" when absent). */
569
- export function registrationUrlOf(e: WixEvent): string {
570
- const raw = e as unknown as {
571
- registration?: { external?: { registration?: string; url?: string } };
572
- eventPageUrl?: { base?: string; path?: string } | string;
573
- };
574
- const external = raw.registration?.external?.registration ?? raw.registration?.external?.url;
575
- if (typeof external === "string" && /^https?:\/\//i.test(external)) return external;
576
- return eventPageUrlOf(e);
577
- }
578
-
579
515
  /** Event slug for createRedirectSession({ eventsCheckout }) — "" when absent. */
580
516
  export function eventSlugOf(e: WixEvent): string {
581
517
  const slug = e.slug ?? (e as unknown as { slug?: string }).slug;
582
518
  return typeof slug === "string" ? slug.trim() : "";
583
519
  }
584
520
 
585
- /** The event's public page URL when the payload carries one ("" otherwise). */
586
- export function eventPageUrlOf(e: WixEvent): string {
587
- const raw = e as unknown as { eventPageUrl?: { base?: string; path?: string } | string };
588
- const page = raw.eventPageUrl;
589
- if (typeof page === "string" && /^https?:\/\//i.test(page)) return page;
590
- if (page && typeof page === "object" && typeof page.base === "string" && page.base) {
591
- const path = typeof page.path === "string" ? page.path : "";
592
- return `${page.base.replace(/\/$/, "")}${path && !path.startsWith("/") ? "/" : ""}${path}`;
593
- }
594
- return "";
595
- }
596
-
597
521
  /** The description pair: the v3 Ricos `description` for RichContent, and its plain text. A string description
598
522
  * (or none) keeps `eventDescription`'s legacy read, with no document. */
599
523
  function eventBody(e: WixEvent): { description: string; descriptionJson: string } {
600
- const legacy = eventDescription(e);
601
- const { bodyJson, bodyText } = richBody((e as unknown as { description?: unknown }).description, legacy);
524
+ const legacy = eventDescription({ ...e, description: undefined });
525
+ const { bodyJson, bodyText } = descriptionBody((e as unknown as { description?: unknown }).description, descriptionBody(legacy).bodyText);
602
526
  return { description: bodyText, descriptionJson: bodyJson };
603
527
  }
604
528
 
@@ -1132,6 +1056,8 @@ const svc: ReturnType<typeof defineService<S, DetailActions>> = defineService<S,
1132
1056
  }).sort((a, b) => (eventStartMillis(a) ?? 0) - (eventStartMillis(b) ?? 0)).map((item) => ({ id: item.id ?? "", date: eventWhen(item) })).filter((item) => !!item.id);
1133
1057
  } catch { moreDates = []; optionalReadFailed = true; }
1134
1058
  }
1059
+ const body = eventBody(e);
1060
+ const shortDescription = descriptionBody(rawEvent.shortDescription).bodyText;
1135
1061
  const readyState = derive({
1136
1062
  ...s,
1137
1063
  status: "ready",
@@ -1153,9 +1079,9 @@ const svc: ReturnType<typeof defineService<S, DetailActions>> = defineService<S,
1153
1079
  price,
1154
1080
  zone: dates.zone,
1155
1081
  mapUrl: place.mapUrl,
1156
- shortDescription: rawEvent.shortDescription || "",
1157
- noShortDescription: !rawEvent.shortDescription || rawEvent.shortDescription === eventBody(e).description,
1158
- noDescription: !eventBody(e).description && !eventBody(e).descriptionJson,
1082
+ shortDescription,
1083
+ noShortDescription: !shortDescription || shortDescription === body.description,
1084
+ noDescription: !body.description && !body.descriptionJson,
1159
1085
  noDate: !dates.line,
1160
1086
  registrationState: registration.state,
1161
1087
  footerLabel: registration.label,
@@ -1177,7 +1103,7 @@ const svc: ReturnType<typeof defineService<S, DetailActions>> = defineService<S,
1177
1103
  noMoreDates: moreDates.length === 0,
1178
1104
  // The Ricos description when the event has one (its own text is the plain copy); the legacy
1179
1105
  // string fields otherwise.
1180
- ...eventBody(e),
1106
+ ...body,
1181
1107
  image,
1182
1108
  hasImage: !!image,
1183
1109
  noImage: !image,
@@ -5,6 +5,8 @@ import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
5
5
  import { normalizeEvent, type EventRow } from "../_shared/himi-events";
6
6
  import { offsetPage } from "../_shared/offset-page.js";
7
7
  import { copyParam } from "../_shared/himi-copy.js";
8
+ import { eventPageUrlOf, registrationUi } from "../_shared/event-registration.js";
9
+ import { share as shareSheet } from "@himalaya/share";
8
10
 
9
11
  // Offline-first (data-offline): the public events list is a read-through catalog —
10
12
  // paint the last-synced rows from the on-device store first (instant, works offline),
@@ -13,8 +15,37 @@ import { copyParam } from "../_shared/himi-copy.js";
13
15
  // (+ declared the `events` entity in its dataSchema) just falls back to today's
14
16
  // online-only path — the try/catch is the seam that keeps this shared bundle safe.
15
17
  const ENTITY = "events";
16
- async function readCached(): Promise<EventRow[]> {
17
- try { return (await getAll(ENTITY)) as unknown as EventRow[]; } catch { return []; }
18
+ interface ListEventRow extends EventRow {
19
+ dateBadge: string; noDateBadge: boolean;
20
+ /** This card's verb (Register / RSVP / Buy Tickets / Join Waitlist) from the SAME rule the detail uses; hidden when none applies. */
21
+ actionLabel: string; noAction: boolean;
22
+ /** The event page URL (when the payload carries one) for Share. */
23
+ shareUrl: string;
24
+ }
25
+
26
+ // The day monogram is a title initial for undated events. Only an actual calendar
27
+ // date becomes a badge; derive it for old cached rows too, without a cache migration.
28
+ // The month must be the three-letter abbreviation: rows cached by an older worker on an
29
+ // Intl-less engine can hold a whole "11/10/2030, 12:00:00 PM" there.
30
+ function withDateBadge(row: EventRow): ListEventRow {
31
+ const dated = typeof row.month === "string" && /^[A-Z]{3}$/.test(row.month)
32
+ && /^(?:[1-9]|[12]\d|3[01])$/.test(row.day);
33
+ const known = row as Partial<ListEventRow>;
34
+ // Rows cached by an older build carry no registration data: they show no verb until a refresh, never a wrong one.
35
+ return { ...row, dateBadge: dated ? `${row.day}\n${row.month}` : "", noDateBadge: !dated,
36
+ actionLabel: known.actionLabel ?? "", noAction: known.noAction ?? true, shareUrl: known.shareUrl ?? "" };
37
+ }
38
+
39
+ /** The card's action, from the event's own registration (REGISTRATION + URLS fieldsets). Cancelled, ended,
40
+ * closed, sold-out and registration-less events offer none, so a card never promises "Register" the detail refuses. */
41
+ function withRegistration(raw: WixEvent, row: ListEventRow): ListEventRow {
42
+ const ui = registrationUi(raw, Date.now());
43
+ const offers = ui.state !== "notice" && ui.state !== "none" && ui.state !== "scheduled" && !!ui.label;
44
+ return { ...row, actionLabel: offers ? ui.label : "", noAction: !offers, shareUrl: eventPageUrlOf(raw) };
45
+ }
46
+
47
+ async function readCached(): Promise<ListEventRow[]> {
48
+ try { return ((await getAll(ENTITY)) as unknown as EventRow[]).map(withDateBadge); } catch { return []; }
18
49
  }
19
50
  async function writeCached(rows: EventRow[]): Promise<void> {
20
51
  try { for (const r of rows) await upsert(ENTITY, r as unknown as DbEntity); } catch { /* capability not granted → online-only */ }
@@ -22,12 +53,12 @@ async function writeCached(rows: EventRow[]): Promise<void> {
22
53
 
23
54
  interface S {
24
55
  status: "loading" | "ready";
25
- query: string; all: EventRow[]; results: EventRow[]; emptyText: string; countLabel: string;
56
+ query: string; all: ListEventRow[]; results: ListEventRow[]; emptyText: string; countLabel: string;
26
57
  errored: boolean; notErrored: boolean; isLoading: boolean; notLoading: boolean; noEmpty: boolean;
27
58
  nextCursor?: string; complete?: boolean; total?: number; loadingMore?: boolean; pageError?: boolean; fromCache?: boolean;
28
59
  }
29
60
  let queryNow = new Date().toISOString();
30
- const svc = defineBackendCollection<S, EventRow>("himiEvents", {
61
+ const svc = defineBackendCollection<S, ListEventRow>("himiEvents", {
31
62
  restorePagesOnLoad: true,
32
63
  state: { status: "loading", query: "", all: [], results: [], emptyText: "", countLabel: "", errored: false, notErrored: true,
33
64
  isLoading: true, notLoading: false, noEmpty: true },
@@ -62,10 +93,10 @@ const svc = defineBackendCollection<S, EventRow>("himiEvents", {
62
93
  ];
63
94
  }
64
95
  if (criteria.filter === "value:Past") filter["dateAndTimeSettings.startDate"] = { $lt: queryNow };
65
- const res = await queryEvents({ limit: 50, offset: Number(cursor || 0), query: { filter,
96
+ const res = await queryEvents({ limit: 50, offset: Number(cursor || 0), fields: ["DETAILS", "REGISTRATION", "URLS"], query: { filter,
66
97
  sort: [{ fieldName: criteria.sort === "default" ? "dateAndTimeSettings.startDate" : "title", order: criteria.sort === "za" ? "DESC" : "ASC" }, { fieldName: "id", order: "ASC" }] } });
67
98
  const raw = res.events ?? [];
68
- const items = raw.filter(row => !!row.id).map(normalizeEvent);
99
+ const items = raw.filter(row => !!row.id).map(row => withRegistration(row, withDateBadge(normalizeEvent(row))));
69
100
  await writeCached(items);
70
101
  const metadata = (res as { pagingMetadata?: { total?: number } }).pagingMetadata;
71
102
  return { items, ...offsetPage(cursor, raw.length, 50, metadata), metadata: { fromCache: false } };
@@ -78,6 +109,16 @@ const svc = defineBackendCollection<S, EventRow>("himiEvents", {
78
109
  fromCache: page.metadata?.fromCache === true, errored, notErrored: !errored, isLoading: loading, notLoading: !loading,
79
110
  noEmpty: true, countLabel: "", emptyText: errored ? copyParam("events.errorText", "Events could not be loaded.") : "" };
80
111
  },
112
+ actions: {
113
+ // Share a card's event: its title and date line, plus the event page when the payload carried one.
114
+ share: async (s, arg) => {
115
+ const id = typeof arg === "string" ? arg : String((arg as { id?: unknown } | undefined)?.id ?? "");
116
+ const row = s.results.find(item => item.id === id);
117
+ if (!row) return undefined;
118
+ try { await shareSheet({ subject: row.title, text: [row.title, row.dateLine].filter(Boolean).join(" — "), ...(row.shareUrl ? { url: row.shareUrl } : {}) }); } catch { /* the sheet is best-effort: nothing to undo */ }
119
+ return undefined;
120
+ },
121
+ },
81
122
  });
82
123
  svc.load();
83
124
  export { svc };
@@ -1,3 +1,4 @@
1
+ import { descriptionBody } from "../_shared/rich-body.js";
1
2
  // Tier-5 bundle: Wix Spaces clone — Pricing plan detail (ON-DEVICE, site-scoped).
2
3
  // himalaya.input.id carries the plan id.
3
4
  //
@@ -53,7 +54,7 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesPlanDetail",
53
54
  const p = await readPlan(id);
54
55
  if (!p) return { ...s, status: "ready", ...presence("missing") };
55
56
  const perks = (p.perks?.values ?? []).map((v) => `• ${v}`).join("\n");
56
- return { status: "ready", name: p.name || "Plan", price: priceOf(p), description: p.description || "", perks, ...presence("found") };
57
+ return { status: "ready", name: p.name || "Plan", price: priceOf(p), description: descriptionBody(p.description).bodyText, perks, ...presence("found") };
57
58
  } catch (e) { return { ...s, status: "ready", ...presenceAfterError(e) }; }
58
59
  },
59
60
  },
@@ -1,3 +1,4 @@
1
+ import { descriptionBody } from "../_shared/rich-body.js";
1
2
  // Tier-5 bundle: Wix Spaces clone — What's New (ON-DEVICE, account-level).
2
3
  import { defineService } from "@himalaya/state";
3
4
  import { withEmptyFlags } from "../_shared/empty-flags.js";
@@ -8,7 +9,7 @@ interface S { status: "loading" | "ready"; items: Row[]; emptyText: string; /**
8
9
 
9
10
  function norm(a: WixAnnouncement, i: number): Row {
10
11
  const aa = a as unknown as { id?: string; title?: string; bodyText?: string; description?: string };
11
- return { id: aa.id || `a${i}`, name: aa.title || "Announcement", subtitle: aa.description || aa.bodyText || "" };
12
+ return { id: aa.id || `a${i}`, name: aa.title || "Announcement", subtitle: descriptionBody(aa.description, descriptionBody(aa.bodyText).bodyText).bodyText };
12
13
  }
13
14
 
14
15
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("spacesWhatsNew", withEmptyFlags({