@wix/himalaya-cli 0.1392.0 → 0.1394.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 (286) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/bookings-v2/src/extensions.d.ts +7 -5
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bookings.mjs +4 -4
  3. package/dist/cli.mjs +41 -27
  4. package/dist/preview-assets.generated.bin +0 -0
  5. package/guides/himi-json-output.md +1 -0
  6. package/guides/iap-products.md +5 -0
  7. package/guides/sdk-reference.json +4 -4
  8. package/package.json +2 -2
  9. package/templates/beauty-salon/tier5-src/_shared/owner-load-error.ts +115 -12
  10. package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +52 -28
  11. package/templates/beauty-salon-owner/dev/index.ts +545 -100
  12. package/templates/beauty-salon-owner/himalaya.content.json +6 -0
  13. package/templates/beauty-salon-owner/tier5-src/_shared/owner-account.ts +6 -2
  14. package/templates/beauty-salon-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  15. package/templates/beauty-salon-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  16. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  17. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-appointments/index.ts +2 -2
  18. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  19. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  20. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-order/index.ts +3 -3
  21. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-orders/index.ts +2 -2
  22. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  23. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  24. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  25. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  26. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product/index.ts +31 -32
  27. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +23 -43
  28. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-products/index.ts +2 -2
  29. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service/index.ts +18 -20
  30. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +16 -29
  31. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-services/index.ts +2 -2
  32. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  33. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  34. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  35. package/templates/branded-lite/tier5-src/_shared/owner-load-error.ts +115 -12
  36. package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +52 -28
  37. package/templates/branded-lite/tier5-src/himi-branded-lite-group/index.ts +24 -24
  38. package/templates/community-club/tier5-src/_shared/owner-load-error.ts +115 -12
  39. package/templates/community-club/tier5-src/himi-branded-lite-group/index.ts +24 -24
  40. package/templates/community-club-owner/dev/index.ts +317 -100
  41. package/templates/community-club-owner/himalaya.content.json +6 -0
  42. package/templates/community-club-owner/tier5-src/_shared/owner-account.ts +6 -2
  43. package/templates/community-club-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  44. package/templates/community-club-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  45. package/templates/community-club-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  46. package/templates/community-club-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  47. package/templates/community-club-owner/tier5-src/himi-owner-lite-event/index.ts +18 -20
  48. package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +20 -28
  49. package/templates/community-club-owner/tier5-src/himi-owner-lite-events/index.ts +2 -2
  50. package/templates/community-club-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  51. package/templates/community-club-owner/tier5-src/himi-owner-lite-order/index.ts +3 -3
  52. package/templates/community-club-owner/tier5-src/himi-owner-lite-orders/index.ts +2 -2
  53. package/templates/community-club-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  54. package/templates/community-club-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  55. package/templates/community-club-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  56. package/templates/community-club-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  57. package/templates/community-club-owner/tier5-src/himi-owner-lite-product/index.ts +31 -32
  58. package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +23 -43
  59. package/templates/community-club-owner/tier5-src/himi-owner-lite-products/index.ts +2 -2
  60. package/templates/community-club-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  61. package/templates/community-club-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  62. package/templates/community-club-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  63. package/templates/events-meetups/tier5-src/_shared/owner-load-error.ts +115 -12
  64. package/templates/events-meetups/tier5-src/himi-branded-lite-group/index.ts +24 -24
  65. package/templates/events-meetups-owner/dev/index.ts +275 -100
  66. package/templates/events-meetups-owner/himalaya.content.json +6 -0
  67. package/templates/events-meetups-owner/tier5-src/_shared/owner-account.ts +6 -2
  68. package/templates/events-meetups-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  69. package/templates/events-meetups-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  70. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  71. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  72. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event/index.ts +18 -20
  73. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +20 -28
  74. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-events/index.ts +2 -2
  75. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  76. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  77. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  78. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  79. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  80. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  81. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  82. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  83. package/templates/gym-fitness/tier5-src/_shared/owner-load-error.ts +115 -12
  84. package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +52 -28
  85. package/templates/gym-fitness-owner/dev/index.ts +545 -100
  86. package/templates/gym-fitness-owner/himalaya.content.json +6 -0
  87. package/templates/gym-fitness-owner/tier5-src/_shared/owner-account.ts +6 -2
  88. package/templates/gym-fitness-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  89. package/templates/gym-fitness-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  90. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  91. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-appointments/index.ts +2 -2
  92. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  93. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event/index.ts +18 -20
  94. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +20 -28
  95. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-events/index.ts +2 -2
  96. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  97. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-order/index.ts +3 -3
  98. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-orders/index.ts +2 -2
  99. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  100. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  101. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  102. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  103. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product/index.ts +31 -32
  104. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +23 -43
  105. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-products/index.ts +2 -2
  106. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service/index.ts +18 -20
  107. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +16 -29
  108. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-services/index.ts +2 -2
  109. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  110. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  111. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  112. package/templates/himi-chat/tier5-src/_shared/owner-load-error.ts +115 -12
  113. package/templates/nonprofit-cause/tier5-src/_shared/owner-load-error.ts +115 -12
  114. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-group/index.ts +24 -24
  115. package/templates/nonprofit-cause-owner/dev/index.ts +503 -100
  116. package/templates/nonprofit-cause-owner/himalaya.content.json +6 -0
  117. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-account.ts +6 -2
  118. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  119. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  120. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  121. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-appointments/index.ts +2 -2
  122. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  123. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event/index.ts +18 -20
  124. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +20 -28
  125. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-events/index.ts +2 -2
  126. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  127. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  128. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  129. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  130. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  131. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service/index.ts +18 -20
  132. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +16 -29
  133. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-services/index.ts +2 -2
  134. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  135. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  136. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  137. package/templates/owner-lite/dev/index.ts +545 -100
  138. package/templates/owner-lite/himalaya.content.json +6 -0
  139. package/templates/owner-lite/tier5-src/_shared/owner-account.ts +6 -2
  140. package/templates/owner-lite/tier5-src/_shared/owner-booking-respond.ts +82 -0
  141. package/templates/owner-lite/tier5-src/_shared/owner-load-error.ts +115 -12
  142. package/templates/owner-lite/tier5-src/himi-owner-lite-account/index.ts +7 -4
  143. package/templates/owner-lite/tier5-src/himi-owner-lite-appointments/index.ts +2 -2
  144. package/templates/owner-lite/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  145. package/templates/owner-lite/tier5-src/himi-owner-lite-event/index.ts +18 -20
  146. package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +20 -28
  147. package/templates/owner-lite/tier5-src/himi-owner-lite-events/index.ts +2 -2
  148. package/templates/owner-lite/tier5-src/himi-owner-lite-home/index.ts +2 -2
  149. package/templates/owner-lite/tier5-src/himi-owner-lite-order/index.ts +3 -3
  150. package/templates/owner-lite/tier5-src/himi-owner-lite-orders/index.ts +2 -2
  151. package/templates/owner-lite/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  152. package/templates/owner-lite/tier5-src/himi-owner-lite-post/index.ts +60 -43
  153. package/templates/owner-lite/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  154. package/templates/owner-lite/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  155. package/templates/owner-lite/tier5-src/himi-owner-lite-product/index.ts +31 -32
  156. package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +23 -43
  157. package/templates/owner-lite/tier5-src/himi-owner-lite-products/index.ts +2 -2
  158. package/templates/owner-lite/tier5-src/himi-owner-lite-service/index.ts +18 -20
  159. package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +16 -29
  160. package/templates/owner-lite/tier5-src/himi-owner-lite-services/index.ts +2 -2
  161. package/templates/owner-lite/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  162. package/templates/owner-lite/tier5-src/himi-owner-lite-today/index.ts +315 -0
  163. package/templates/owner-lite/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  164. package/templates/podcast-creator/tier5-src/_shared/owner-load-error.ts +115 -12
  165. package/templates/podcast-creator-owner/dev/index.ts +317 -100
  166. package/templates/podcast-creator-owner/himalaya.content.json +6 -0
  167. package/templates/podcast-creator-owner/tier5-src/_shared/owner-account.ts +6 -2
  168. package/templates/podcast-creator-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  169. package/templates/podcast-creator-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  170. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  171. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  172. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event/index.ts +18 -20
  173. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +20 -28
  174. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-events/index.ts +2 -2
  175. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  176. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-order/index.ts +3 -3
  177. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-orders/index.ts +2 -2
  178. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  179. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  180. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  181. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  182. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product/index.ts +31 -32
  183. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +23 -43
  184. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-products/index.ts +2 -2
  185. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  186. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  187. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  188. package/templates/portfolio-creative/tier5-src/_shared/owner-load-error.ts +115 -12
  189. package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +52 -28
  190. package/templates/portfolio-creative-owner/dev/index.ts +545 -100
  191. package/templates/portfolio-creative-owner/himalaya.content.json +6 -0
  192. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-account.ts +6 -2
  193. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  194. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  195. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  196. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-appointments/index.ts +2 -2
  197. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  198. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  199. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-order/index.ts +3 -3
  200. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-orders/index.ts +2 -2
  201. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  202. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  203. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  204. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  205. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product/index.ts +31 -32
  206. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +23 -43
  207. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-products/index.ts +2 -2
  208. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service/index.ts +18 -20
  209. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +16 -29
  210. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-services/index.ts +2 -2
  211. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  212. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  213. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  214. package/templates/restaurant/tier5-src/_shared/owner-load-error.ts +115 -12
  215. package/templates/restaurant-owner/dev/index.ts +545 -100
  216. package/templates/restaurant-owner/himalaya.content.json +6 -0
  217. package/templates/restaurant-owner/tier5-src/_shared/owner-account.ts +6 -2
  218. package/templates/restaurant-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  219. package/templates/restaurant-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  220. package/templates/restaurant-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  221. package/templates/restaurant-owner/tier5-src/himi-owner-lite-appointments/index.ts +2 -2
  222. package/templates/restaurant-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  223. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event/index.ts +18 -20
  224. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +20 -28
  225. package/templates/restaurant-owner/tier5-src/himi-owner-lite-events/index.ts +2 -2
  226. package/templates/restaurant-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  227. package/templates/restaurant-owner/tier5-src/himi-owner-lite-order/index.ts +3 -3
  228. package/templates/restaurant-owner/tier5-src/himi-owner-lite-orders/index.ts +2 -2
  229. package/templates/restaurant-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  230. package/templates/restaurant-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  231. package/templates/restaurant-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  232. package/templates/restaurant-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  233. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product/index.ts +31 -32
  234. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +23 -43
  235. package/templates/restaurant-owner/tier5-src/himi-owner-lite-products/index.ts +2 -2
  236. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service/index.ts +18 -20
  237. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +16 -29
  238. package/templates/restaurant-owner/tier5-src/himi-owner-lite-services/index.ts +2 -2
  239. package/templates/restaurant-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  240. package/templates/restaurant-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  241. package/templates/restaurant-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  242. package/templates/retail-store/tier5-src/_shared/owner-load-error.ts +115 -12
  243. package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +52 -28
  244. package/templates/retail-store-owner/dev/index.ts +317 -100
  245. package/templates/retail-store-owner/himalaya.content.json +6 -0
  246. package/templates/retail-store-owner/tier5-src/_shared/owner-account.ts +6 -2
  247. package/templates/retail-store-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  248. package/templates/retail-store-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  249. package/templates/retail-store-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  250. package/templates/retail-store-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  251. package/templates/retail-store-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  252. package/templates/retail-store-owner/tier5-src/himi-owner-lite-order/index.ts +3 -3
  253. package/templates/retail-store-owner/tier5-src/himi-owner-lite-orders/index.ts +2 -2
  254. package/templates/retail-store-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  255. package/templates/retail-store-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  256. package/templates/retail-store-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  257. package/templates/retail-store-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  258. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product/index.ts +31 -32
  259. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +23 -43
  260. package/templates/retail-store-owner/tier5-src/himi-owner-lite-products/index.ts +2 -2
  261. package/templates/retail-store-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  262. package/templates/retail-store-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  263. package/templates/retail-store-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
  264. package/templates/yoga-studio/tier5-src/_shared/owner-load-error.ts +115 -12
  265. package/templates/yoga-studio-owner/dev/index.ts +503 -100
  266. package/templates/yoga-studio-owner/himalaya.content.json +6 -0
  267. package/templates/yoga-studio-owner/tier5-src/_shared/owner-account.ts +6 -2
  268. package/templates/yoga-studio-owner/tier5-src/_shared/owner-booking-respond.ts +82 -0
  269. package/templates/yoga-studio-owner/tier5-src/_shared/owner-load-error.ts +115 -12
  270. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-account/index.ts +7 -4
  271. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-appointments/index.ts +2 -2
  272. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-ask/live.ts +3 -3
  273. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event/index.ts +18 -20
  274. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +20 -28
  275. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-events/index.ts +2 -2
  276. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-home/index.ts +2 -2
  277. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-overview/index.ts +2 -2
  278. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post/index.ts +60 -43
  279. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -23
  280. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-posts/index.ts +2 -2
  281. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service/index.ts +18 -20
  282. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +16 -29
  283. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-services/index.ts +2 -2
  284. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-sites/index.ts +2 -2
  285. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-today/index.ts +315 -0
  286. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-wrist/index.ts +42 -15
@@ -7,25 +7,24 @@
7
7
 
8
8
  import { defineService } from "@himalaya/state";
9
9
  import { getService, deleteService, type WixService } from "@himalaya/wix-bookings";
10
- import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
10
+ import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback, ownerWriteError, isNotFound } from "../_shared/owner-load-error.js";
11
11
  import { DEMO_SERVICES, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
12
12
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
13
- import { get as dbGet, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
13
+ import { get as dbGet, remove as dbRemove, type DbEntity } from "../_shared/owner-local-db.js";
14
14
  import { wixMediaUrl, WIX_DETAIL_IMAGE } from "@himalaya/wix-media";
15
15
  import { blankFlags } from "../_shared/detail-facts.js";
16
16
 
17
17
  // Offline-first (data-offline): the service DETAIL falls back to the last-synced
18
- // local `owner_services` row (put by the service form's write-behind) on a failed
19
- // fetch, and its delete is WRITE-BEHIND — the local row is dropped OPTIMISTICALLY,
20
- // then deleteService is attempted; if the bridge/network is unreachable the delete
21
- // is ENQUEUED for replay on reconnect. Every local-db call is wrapped so a build
22
- // without the `local_database` grant behaves exactly as before (online-only).
18
+ // local `owner_services` row on a failed fetch. Its delete is SERVER-CONFIRMED: the
19
+ // local row is dropped only after Wix deletes the service; a failed delete leaves it
20
+ // and tells the owner why. No host replays a queued write, so nothing is queued.
21
+ // Every local-db call is wrapped so a build without the `local_database` grant
22
+ // behaves exactly as before (online-only).
23
23
  const ENTITY = "owner_services";
24
24
 
25
25
 
26
26
  async function cacheDrop(id: string): Promise<void> { try { await dbRemove(ENTITY, id); } catch { /* capability not granted */ } }
27
27
  async function cacheGet(id: string): Promise<DbEntity | null> { try { return await dbGet(ENTITY, id); } catch { return null; } }
28
- async function queueWrite(actionId: string, params: Record<string, string>): Promise<void> { try { await dbEnqueue(actionId, params); } catch { /* capability not granted */ } }
29
28
 
30
29
  interface ServiceState {
31
30
  id: string;
@@ -238,9 +237,10 @@ const svc = defineService<ServiceState, {
238
237
  return await refresh(shape({ ...s, id, status: "loading" }), id);
239
238
  } catch (e) {
240
239
  // Offline: fall back to the last-synced local row before the demo/error paths.
241
- const cached = await cacheGet(id);
240
+ // Wix says it is gone (e.g. a delete whose reply was lost): drop the row, never show it.
241
+ const cached = isNotFound(e) ? (await cacheDrop(id), null) : await cacheGet(id);
242
242
  if (cached) return cachedDetail(s, cached);
243
- if (isBridgeUnavailable(e)) {
243
+ if (ownerDemoFallback(e)) {
244
244
  const demo = demoDetail(s, id);
245
245
  if (demo) return demo;
246
246
  }
@@ -258,9 +258,10 @@ const svc = defineService<ServiceState, {
258
258
  try {
259
259
  return await refresh(shape({ ...s, status: "loading" }), s.id);
260
260
  } catch (e) {
261
- const cached = await cacheGet(s.id);
261
+ // Wix says it is gone (e.g. a delete whose reply was lost): drop the row, never show it.
262
+ const cached = isNotFound(e) ? (await cacheDrop(s.id), null) : await cacheGet(s.id);
262
263
  if (cached) return cachedDetail(s, cached);
263
- if (isBridgeUnavailable(e)) {
264
+ if (ownerDemoFallback(e)) {
264
265
  const demo = demoDetail(s, s.id);
265
266
  if (demo) return demo;
266
267
  }
@@ -270,18 +271,15 @@ const svc = defineService<ServiceState, {
270
271
  delete: async (s) => {
271
272
  if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
272
273
  if (!s.id) return shape({ ...s, actionMessage: copyParam("ownerService.noIdMessage", "No service id.") });
273
- // Optimistic local delete — the list drops the row immediately.
274
- await cacheDrop(s.id);
275
274
  try {
276
275
  await deleteService(s.id);
277
- return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerService.deletedEmpty", "Service deleted."), actionMessage: "" });
278
276
  } catch (e) {
279
- if (shouldQueueOwnerWrite(e)) {
280
- await queueWrite("tier5.himiOwnerLiteService.delete", { id: s.id });
281
- return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerService.deletedEmpty", "Service deleted."), actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
282
- }
283
- return shape({ ...s, actionMessage: ownerLoadError(e) });
277
+ // Nothing replays a queued write: the record stays, and the owner is told why.
278
+ return shape({ ...s, actionMessage: ownerWriteError(e) });
284
279
  }
280
+ // Only a delete Wix confirmed leaves the local list.
281
+ await cacheDrop(s.id);
282
+ return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerService.deletedEmpty", "Service deleted."), actionMessage: "" });
285
283
  },
286
284
  },
287
285
  });
@@ -6,22 +6,20 @@
6
6
 
7
7
  import { defineService } from "@himalaya/state";
8
8
  import { getService, updateService, type WixService } from "@himalaya/wix-bookings";
9
- import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
9
+ import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback, ownerWriteError } from "../_shared/owner-load-error.js";
10
10
  import { DEMO_SERVICES, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
11
11
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
12
- import { get as dbGet, upsert as dbUpsert, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
12
+ import { get as dbGet, upsert as dbUpsert, type DbEntity } from "../_shared/owner-local-db.js";
13
13
 
14
- // Offline-first (data-offline): the service edit is WRITE-BEHIND against the
15
- // encrypted `owner_services` entity — the local store is updated OPTIMISTICALLY so
16
- // the detail/list repaint from cache immediately, then updateService is attempted;
17
- // if it can't reach the bridge/network the write is ENQUEUED for replay on
18
- // reconnect, and load prefills from the last-synced local row when offline. Every
19
- // local-db call is wrapped (try/catch) so a build that ships without the
20
- // `local_database` grant behaves exactly as before (online-only, no cache/queue).
14
+ // Offline-first (data-offline): load prefills from the last-synced local
15
+ // `owner_services` row when offline, and the edit is SERVER-CONFIRMED — the local
16
+ // row changes only after Wix accepts it; a failed save leaves it alone, keeps the
17
+ // form, and tells the owner why. No host replays a queued write, so nothing is queued.
18
+ // Every local-db call is wrapped (try/catch) so a build that ships without the
19
+ // `local_database` grant behaves exactly as before (online-only, no cache).
21
20
  const ENTITY = "owner_services";
22
21
  async function cachePut(row: DbEntity): Promise<void> { try { await dbUpsert(ENTITY, row); } catch { /* capability not granted */ } }
23
22
  async function cacheGet(id: string): Promise<DbEntity | null> { try { return await dbGet(ENTITY, id); } catch { return null; } }
24
- async function queueWrite(actionId: string, params: Record<string, string>): Promise<void> { try { await dbEnqueue(actionId, params); } catch { /* capability not granted */ } }
25
23
 
26
24
  interface ServiceFormState {
27
25
  mode: "edit";
@@ -156,7 +154,7 @@ const svc = defineService<ServiceFormState, {
156
154
  status: "idle", error: "", savedMessage: "",
157
155
  });
158
156
  }
159
- if (isBridgeUnavailable(e)) {
157
+ if (ownerDemoFallback(e)) {
160
158
  const d = DEMO_SERVICES.find((x) => x.id === id);
161
159
  if (d) {
162
160
  return shape({
@@ -178,10 +176,8 @@ const svc = defineService<ServiceFormState, {
178
176
  if (!name) return shape({ ...s, status: "error", error: copyParam("ownerServiceForm.nameRequired", "Name is required"), savedMessage: "" });
179
177
  if (!targetId) return shape({ ...s, status: "error", error: copyParam("ownerServiceForm.errorNoService", "No service to edit."), savedMessage: "" });
180
178
 
181
- // Optimistic local write — the detail/list repaint from cache immediately (offline or not).
182
179
  const cached = !s.id ? await cacheGet(targetId) : null;
183
180
  const localRow: DbEntity = { ...(cached ?? {}), id: targetId, name, title: name, description, category: s.category || String(cached?.category ?? "") };
184
- await cachePut(localRow);
185
181
  try {
186
182
  // v2 PATCH is a masked update requiring the current `revision` (optimistic
187
183
  // concurrency), captured from the getService on load. The response wraps
@@ -190,6 +186,8 @@ const svc = defineService<ServiceFormState, {
190
186
  // load-time revision and reject.
191
187
  const res = await updateService(targetId, { name, ...(description ? { description } : {}), ...(s.revision ? { revision: s.revision } : {}) });
192
188
  const bumped = String((res?.service as { revision?: unknown } | undefined)?.revision ?? "");
189
+ // The local row follows only what Wix confirmed.
190
+ await cachePut(localRow);
193
191
  return shape({
194
192
  ...s, id: targetId, name, description,
195
193
  revision: bumped || s.revision,
@@ -197,22 +195,11 @@ const svc = defineService<ServiceFormState, {
197
195
  savedId: targetId,
198
196
  });
199
197
  } catch (e) {
200
- if (shouldQueueOwnerWrite(e)) {
201
- // Demo / web-preview (no owner bridge; the record came from demo sample
202
- // data, marked by demoNote): a queued write could never replay, so answer
203
- // with the demo save note instead of the real-offline write-behind.
204
- if (s.demoNote) return demoRefusal(s);
205
- // Real device offline: the optimistic cache row already shows; queue the
206
- // write to replay (WriteQueue, idempotent) when connectivity returns.
207
- await queueWrite("tier5.himiOwnerLiteServiceForm.submit", { id: targetId, name, description });
208
- return shape({
209
- ...s, id: targetId, name, description,
210
- status: "saved", error: "",
211
- savedMessage: copyParam("ownerServiceForm.savedOffline", "Saved offline — will sync when you reconnect."),
212
- savedId: targetId,
213
- });
214
- }
215
- return shape({ ...s, status: "error", error: ownerLoadError(e), savedMessage: "" });
198
+ // Never "saved offline": nothing replays a queued write on any platform. The cache was
199
+ // not touched, and the typed values stay in the form so a retry is one tap.
200
+ // Demo / web-preview: the record came from demo sample data (marked by demoNote).
201
+ if (s.demoNote) return demoRefusal(s);
202
+ return shape({ ...s, name, description, status: "error", error: ownerWriteError(e), savedMessage: "" });
216
203
  }
217
204
  },
218
205
  },
@@ -5,7 +5,7 @@
5
5
  import { defineBackendCollection } from "../_shared/collection-service.js";
6
6
  import { offsetPage } from "../_shared/offset-page.js";
7
7
  import { queryServices, type WixService } from "@himalaya/wix-bookings";
8
- import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo } from "../_shared/owner-load-error.js";
8
+ import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback } from "../_shared/owner-load-error.js";
9
9
  import { DEMO_SERVICES, DEMO_NOTE } from "../_shared/owner-preview-demo.js";
10
10
  import { copyParam } from "../_shared/himi-copy.js";
11
11
  import { getAll, upsert, type DbEntity } from "../_shared/owner-local-db.js";
@@ -171,7 +171,7 @@ const svc = defineBackendCollection("himiOwnerLiteServices", {
171
171
  const cached = await readCachedRows();
172
172
  if (cached.length) return { items: cached, nextCursor: "", complete: false, metadata: { fromCache: true } };
173
173
  }
174
- if (!cursor && isBridgeUnavailable(error)) return demo();
174
+ if (!cursor && ownerDemoFallback(error)) return demo();
175
175
  throw error;
176
176
  }
177
177
  },
@@ -8,7 +8,7 @@
8
8
 
9
9
  import { defineService } from "@himalaya/state";
10
10
  import { querySites, type WixSite } from "@himalaya/wix-site-list";
11
- import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo } from "../_shared/owner-load-error.js";
11
+ import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback } from "../_shared/owner-load-error.js";
12
12
  import { DEMO_SITES, DEMO_NOTE } from "../_shared/owner-preview-demo.js";
13
13
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
14
14
  import { getAll, upsert, type DbEntity } from "../_shared/owner-local-db.js";
@@ -165,7 +165,7 @@ async function loadList(s: SitesState): Promise<SitesState> {
165
165
  } catch (e) {
166
166
  // Offline / fetch failure: keep the last-synced cache BEFORE the demo/error fallbacks.
167
167
  if (repairedCache.length) return base;
168
- if (isBridgeUnavailable(e)) return demoList(s);
168
+ if (ownerDemoFallback(e)) return demoList(s);
169
169
  return shape({ ...s, status: "error", allSites: [], sites: [], error: ownerLoadError(e) });
170
170
  }
171
171
  }
@@ -0,0 +1,315 @@
1
+ // Tier-5 bundle: __HIMI_APP_NAME__ — TODAY. The worker behind the Home Screen widget.
2
+ //
3
+ // A WidgetKit extension (and a Glance receiver) runs no JS and holds no Wix session, and the OTA
4
+ // widget producer has no credential either, so neither can read an owner's business. The PHONE
5
+ // app runs this worker headlessly with its owner Wix bridge, resolves the widget descriptors'
6
+ // `$service:ownerToday.*` leaves to literals (CompanionDescriptorResolver, the same pattern the
7
+ // watch companion uses), and writes the frozen tree to the App Group for the widget to render.
8
+ //
9
+ // One owner glance: "what's next, and what is waiting for me" — the next booking, the booking
10
+ // requests waiting on the owner, and the orders that need attention. Read-only: a widget never
11
+ // writes to the business.
12
+ //
13
+ // Each source degrades on its own. A site without Bookings answers 428/404 on the bookings
14
+ // query, and that must not cost the order count (or the reverse).
15
+
16
+ import { defineService } from "@himalaya/state";
17
+ import { queryBookings } from "@himalaya/wix-bookings";
18
+ import { searchOrders } from "@himalaya/wix-ecom";
19
+ import { ownerOrderAttentionFilter } from "../_shared/owner-order-attention.js";
20
+ import { readSelectedSite } from "../_shared/owner-selected-site.js";
21
+
22
+ type Rec = Record<string, unknown>;
23
+
24
+ /** One booking as the widget shows it. Client FIRST name only: a Home Screen is visible to
25
+ * anyone near the phone. */
26
+ export interface TodayBooking {
27
+ bookingId: string;
28
+ name: string;
29
+ service: string;
30
+ timeLabel: string; // "10:30"
31
+ dayLabel: string; // "Today" / "Tomorrow" / "Thu 16 Oct"
32
+ /** "Today 10:30" — the absolute moment, never a countdown: a frozen "in 21 min" is wrong a
33
+ * minute later, and nothing re-renders the widget between app foregrounds. */
34
+ whenLabel: string;
35
+ }
36
+
37
+ export interface TodayState {
38
+ status: "loading" | "ready" | "error" | "signedOut";
39
+ siteName: string;
40
+ /** Header line: "Today · <site>", or "Today" when the site has no name yet. */
41
+ header: string;
42
+ next: TodayBooking;
43
+ hasNext: boolean;
44
+ /** Hide the next-booking block: nothing is booked, or Bookings cannot be read. */
45
+ hideNext: boolean;
46
+ /** "No upcoming bookings" — shown in place of the block when Bookings has nothing ahead. */
47
+ nextNote: string;
48
+ hideNextNote: boolean;
49
+ requestCount: number;
50
+ /** "2 requests" / "1 request" / "No requests". At the read cap: "50+ requests". */
51
+ requestLabel: string;
52
+ orderCount: number;
53
+ /** "3 orders to handle" / "1 order to handle" / "No orders waiting". */
54
+ orderLabel: string;
55
+ /** Compact counts for the small widget: "2 req · 3 orders". */
56
+ countsShort: string;
57
+ bookingsReady: boolean;
58
+ ordersReady: boolean;
59
+ hideRequests: boolean;
60
+ hideOrders: boolean;
61
+ /** One-line state copy for a degrade (signed out, read failed, nothing installed). */
62
+ message: string;
63
+ hideMessage: boolean;
64
+ /** Epoch SECONDS after which this frame's labels are wrong: the next local midnight ("Today" /
65
+ * "Tomorrow" shift) or the next booking's start (it is no longer "next"), whichever is first.
66
+ * The phone caps the snapshot's lifetime at it, so a frozen frame never outlives its words. */
67
+ validUntil: number;
68
+ }
69
+
70
+ /** Which verticals THIS build provisions — baked into the widget descriptors' `input`. A vertical
71
+ * the build does not show is never read: its count must not reach the compact line, and its
72
+ * transient failure must not withhold a frame about the sections the build does show. */
73
+ export interface Verticals {
74
+ bookings: boolean;
75
+ store: boolean;
76
+ }
77
+
78
+ export const COPY = {
79
+ signedOut: "Sign in to Owner to see today",
80
+ error: "Open Owner to refresh",
81
+ nothing: "Open Owner to see your business",
82
+ noneUpcoming: "No upcoming bookings",
83
+ } as const;
84
+
85
+ const PENDING = ["PENDING", "PENDING_APPROVAL", "WAITING_LIST"];
86
+ /** A glance, not a list: a count at the cap reads as "50+". */
87
+ export const LIMIT = 50;
88
+
89
+ const WEEKDAYS = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
90
+ const MONTHS = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
91
+
92
+ function str(r: unknown, ...paths: string[]): string {
93
+ for (const path of paths) {
94
+ let v: unknown = r;
95
+ for (const k of path.split(".")) v = v && typeof v === "object" ? (v as Rec)[k] : undefined;
96
+ if (typeof v === "string" && v) return v;
97
+ if (typeof v === "number") return String(v);
98
+ }
99
+ return "";
100
+ }
101
+
102
+ function hhmm(d: Date): string {
103
+ return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
104
+ }
105
+
106
+ function sameLocalDay(a: Date, b: Date): boolean {
107
+ return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();
108
+ }
109
+
110
+ /** The day a booking falls on, as an owner would say it. */
111
+ export function dayLabel(start: Date, now: Date): string {
112
+ if (sameLocalDay(start, now)) return "Today";
113
+ const tomorrow = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1);
114
+ if (sameLocalDay(start, tomorrow)) return "Tomorrow";
115
+ return `${WEEKDAYS[start.getDay()]} ${start.getDate()} ${MONTHS[start.getMonth()]}`;
116
+ }
117
+
118
+ function startOf(b: Rec): string {
119
+ return str(b, "startDate", "bookedEntity.slot.startDate");
120
+ }
121
+
122
+ function booking(b: Rec, now: Date): TodayBooking {
123
+ const start = new Date(startOf(b));
124
+ const day = dayLabel(start, now);
125
+ return {
126
+ bookingId: str(b, "id"),
127
+ name: str(b, "contactDetails.firstName") || "Client",
128
+ service: str(b, "bookedEntity.title", "bookedEntity.slot.serviceName") || "Appointment",
129
+ timeLabel: hhmm(start),
130
+ dayLabel: day,
131
+ whenLabel: `${day} ${hhmm(start)}`,
132
+ };
133
+ }
134
+
135
+ const EMPTY_BOOKING: TodayBooking = { bookingId: "", name: "", service: "", timeLabel: "", dayLabel: "", whenLabel: "" };
136
+
137
+ function counted(n: number, capped: boolean, one: string, many: string): string {
138
+ return `${capped ? `${LIMIT}+` : n} ${n === 1 && !capped ? one : many}`;
139
+ }
140
+
141
+ export interface Sources {
142
+ /** Upcoming CONFIRMED bookings, or null when Bookings could not be read. */
143
+ bookings: Rec[] | null;
144
+ /** Upcoming booking REQUESTS (pending statuses), read on their own: one page of every status
145
+ * would fill with a busy week's confirmed bookings and hide next week's request. */
146
+ requests: Rec[] | null;
147
+ /** Orders needing attention, or null when Store could not be read. */
148
+ orders: Rec[] | null;
149
+ }
150
+
151
+ function upcoming(rows: Rec[] | null, statuses: string[], now: Date): Rec[] {
152
+ return (rows ?? [])
153
+ .filter((b) => str(b, "id") && statuses.includes(str(b, "status").toUpperCase()))
154
+ .filter((b) => Date.parse(startOf(b)) >= now.getTime())
155
+ .sort((a, b) => Date.parse(startOf(a)) - Date.parse(startOf(b)) || str(a, "id").localeCompare(str(b, "id")));
156
+ }
157
+
158
+ /** Pure: shape what was read into the widget state. `now` is injectable for tests. */
159
+ export function shape(sources: Sources, now: Date, siteName: string): TodayState {
160
+ const bookingsReady = sources.bookings !== null && sources.requests !== null;
161
+ const ordersReady = sources.orders !== null;
162
+ // Re-filtered even though the queries filter: a server (or a fixture) that returns a
163
+ // cancelled or past row must not become an appointment the owner is told to expect.
164
+ const confirmed = bookingsReady ? upcoming(sources.bookings, ["CONFIRMED"], now) : [];
165
+ // Today's next CONFIRMED booking first: a request is not an appointment yet. With none left
166
+ // today, the first confirmed one after today, so "what's next" still has an answer.
167
+ const next = confirmed.find((b) => sameLocalDay(new Date(startOf(b)), now)) ?? confirmed[0];
168
+ const requestCount = bookingsReady ? upcoming(sources.requests, PENDING, now).length : 0;
169
+ const orderCount = (sources.orders ?? []).length;
170
+ const requestsCapped = (sources.requests ?? []).length >= LIMIT;
171
+ const ordersCapped = orderCount >= LIMIT;
172
+
173
+ const shortParts = [
174
+ ...(bookingsReady ? [`${requestsCapped ? `${LIMIT}+` : requestCount} req`] : []),
175
+ ...(ordersReady ? [counted(orderCount, ordersCapped, "order", "orders")] : []),
176
+ ];
177
+ const nothing = !bookingsReady && !ordersReady;
178
+ const nextNote = bookingsReady && !next ? COPY.noneUpcoming : "";
179
+ const midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate() + 1).getTime();
180
+ const nextStart = next ? Date.parse(startOf(next)) : Infinity;
181
+ return {
182
+ validUntil: Math.floor(Math.min(midnight, nextStart) / 1000),
183
+ status: "ready",
184
+ siteName,
185
+ header: siteName ? `Today · ${siteName}` : "Today",
186
+ next: next ? booking(next, now) : EMPTY_BOOKING,
187
+ hasNext: !!next,
188
+ hideNext: !next,
189
+ nextNote,
190
+ hideNextNote: !nextNote,
191
+ requestCount,
192
+ requestLabel: requestCount ? counted(requestCount, requestsCapped, "request", "requests") : "No requests",
193
+ orderCount,
194
+ orderLabel: orderCount ? `${counted(orderCount, ordersCapped, "order", "orders")} to handle` : "No orders waiting",
195
+ countsShort: shortParts.join(" · "),
196
+ bookingsReady,
197
+ ordersReady,
198
+ hideRequests: !bookingsReady,
199
+ hideOrders: !ordersReady,
200
+ message: nothing ? COPY.nothing : "",
201
+ hideMessage: !nothing,
202
+ };
203
+ }
204
+
205
+ /** A degrade frame: ONE line, no rows. After a sign-out or a business switch the last rows
206
+ * belong to someone else's clients, so none of them survive into it. */
207
+ export function degraded(status: "error" | "signedOut" | "loading", siteName: string): TodayState {
208
+ // A FIXED instant, not the clock: a degrade frame has no rows, so nothing in it depends on
209
+ // the time — and the initial state must be deterministic (template contract: no worker's
210
+ // initial state may read the clock).
211
+ const base = shape({ bookings: null, requests: null, orders: null }, new Date(0), siteName);
212
+ const message = status === "signedOut" ? COPY.signedOut : status === "error" ? COPY.error : "";
213
+ // validUntil pinned too: shape() derives it from LOCAL midnight, so even at a fixed instant it
214
+ // would vary with the build machine's timezone and change the baked services hash.
215
+ return { ...base, status, message, hideMessage: !message, validUntil: 0 };
216
+ }
217
+
218
+ /** No usable owner session behind the bridge (the bridge refusing, or a 401). A transient
219
+ * 5xx is an ordinary error, not a sign-out — and a 403 is a missing PERMISSION for one
220
+ * vertical (a collaborator whose role lacks Bookings), not a sign-out either. */
221
+ export function isSignedOut(e: unknown): boolean {
222
+ const text = String((e as Error)?.message ?? e);
223
+ return /^HTTP 401\b/.test(text) || /wixApi not enabled|session unavailable|not signed in|webwix_bridge/i.test(text);
224
+ }
225
+
226
+ /** This section cannot exist for this owner: the vertical is not installed on the site (its
227
+ * API answers 428, or 404 for the route), or the owner's role may not read it (403). Hides
228
+ * that one section; every other failure is an error for the whole frame.
229
+ *
230
+ * Read from the LEADING status only — the bridges format a failure as `HTTP <status>: <body>`
231
+ * or `HTTP <status> [<code>]: <message>` — never from anywhere in the message: a transient
232
+ * `HTTP 503: {"message":"upstream returned 404"}` must stay an error, not hide the section. */
233
+ export function isSectionUnavailable(e: unknown): boolean {
234
+ const text = String((e as Error)?.message ?? e);
235
+ return /^HTTP (?:403|404|428)\b/.test(text) || /^HTTP \d{3} \[(?:FAILED_PRECONDITION|PERMISSION_DENIED)\]/.test(text);
236
+ }
237
+
238
+ function siteName(): string {
239
+ return readSelectedSite()?.name ?? "";
240
+ }
241
+
242
+ type Read = { ok: true; value: unknown } | { ok: false; error: unknown };
243
+
244
+ async function attempt(fn: () => Promise<unknown>): Promise<Read> {
245
+ try {
246
+ return { ok: true, value: await fn() };
247
+ } catch (error) {
248
+ return { ok: false, error };
249
+ }
250
+ }
251
+
252
+ function rows(r: Read, key: string): Rec[] | null {
253
+ if (!r.ok) return null;
254
+ const list = (r.value as Rec | undefined)?.[key];
255
+ return Array.isArray(list) ? (list as Rec[]) : [];
256
+ }
257
+
258
+ /** The build's verticals from the descriptor's baked `input.verticals` ("bookings,store"). A
259
+ * host that passes none (an older descriptor) reads both, as before. */
260
+ export function buildVerticals(): Verticals {
261
+ const raw = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input?.verticals;
262
+ if (typeof raw !== "string") return { bookings: true, store: true };
263
+ const set = new Set(raw.split(",").map((v) => v.trim()));
264
+ return { bookings: set.has("bookings"), store: set.has("store") };
265
+ }
266
+
267
+ /** A section this build does not provision: never read, and indistinguishable from "not
268
+ * installed" downstream (hidden, out of the compact line, out of the error rule). */
269
+ const NOT_PROVISIONED: Read = { ok: false, error: new Error("HTTP 404: not provisioned in this build") };
270
+
271
+ async function read(): Promise<TodayState> {
272
+ const nowIso = new Date().toISOString();
273
+ const verticals = buildVerticals();
274
+ const sort = [{ fieldName: "startDate", order: "ASC" }, { fieldName: "id", order: "ASC" }];
275
+ const bookingsBy = (statuses: string[]) => verticals.bookings ? attempt(() => queryBookings({ query: {
276
+ filter: { startDate: { $gte: nowIso }, status: { $in: statuses } },
277
+ sort,
278
+ paging: { limit: LIMIT, offset: 0 },
279
+ } })) : Promise.resolve(NOT_PROVISIONED);
280
+ const reads = await Promise.all([
281
+ bookingsBy(["CONFIRMED"]),
282
+ bookingsBy(PENDING),
283
+ verticals.store ? attempt(() => searchOrders({ search: {
284
+ filter: ownerOrderAttentionFilter(),
285
+ sort: [{ fieldName: "createdDate", order: "DESC" }, { fieldName: "id", order: "ASC" }],
286
+ cursorPaging: { limit: LIMIT },
287
+ } })) : Promise.resolve(NOT_PROVISIONED),
288
+ ]);
289
+ const failures = reads.flatMap((r) => (r.ok ? [] : [r.error]));
290
+ if (failures.some(isSignedOut)) return degraded("signedOut", "");
291
+ // A section that cannot exist (not installed, no permission) is hidden. ANY other failure —
292
+ // a transient 5xx on one read included — makes this an error frame, which the phone does
293
+ // not publish: the last good frame stays, rather than a frame that silently drops a
294
+ // section the owner does have and reads as "nothing waiting".
295
+ if (failures.some((e) => !isSectionUnavailable(e))) return degraded("error", siteName());
296
+ const [bookings, requests, orders] = reads;
297
+ return shape({
298
+ bookings: rows(bookings, "bookings"),
299
+ requests: rows(requests, "bookings"),
300
+ orders: rows(orders, "orders"),
301
+ }, new Date(), siteName());
302
+ }
303
+
304
+ const svc = defineService<TodayState, {
305
+ load: (s: TodayState) => Promise<TodayState>;
306
+ refresh: (s: TodayState) => Promise<TodayState>;
307
+ }>("ownerToday", {
308
+ state: degraded("loading", ""),
309
+ actions: {
310
+ load: async () => read(),
311
+ refresh: async () => read(),
312
+ },
313
+ });
314
+
315
+ export { svc };
@@ -13,7 +13,10 @@
13
13
  // render and tap must never confirm another business's booking.
14
14
 
15
15
  import { defineService } from "@himalaya/state";
16
- import { queryBookings, confirmBooking, declineBooking } from "@himalaya/wix-bookings";
16
+ import { getBooking, queryBookings } from "@himalaya/wix-bookings";
17
+ import {
18
+ PENDING_BOOKING_STATUSES as PENDING, isForActiveBusiness, isStaleBookingWrite as isStale, writeBookingResponse, type BookingResponse,
19
+ } from "../_shared/owner-booking-respond.js";
17
20
  import { isBridgeUnavailable } from "../_shared/owner-load-error.js";
18
21
  import { readSelectedSite, selectedSiteName } from "../_shared/owner-selected-site.js";
19
22
 
@@ -80,9 +83,9 @@ export const COPY = {
80
83
  changed: "This request changed — check it on your iPhone",
81
84
  error: "Open Owner on your iPhone",
82
85
  writeFailed: "Couldn't update it — check it on your iPhone",
86
+ busy: "Still updating the last one — try again",
83
87
  } as const;
84
88
 
85
- const PENDING = new Set(["PENDING", "PENDING_APPROVAL", "WAITING_LIST"]);
86
89
  const LIMIT = 50;
87
90
 
88
91
  function str(r: Rec | undefined, ...paths: string[]): string {
@@ -257,25 +260,39 @@ async function fetchInto(s: WristState): Promise<WristState> {
257
260
  return shape([...byId.values()], new Date(), site(), s.requestIndex);
258
261
  }
259
262
 
260
- function isStale(e: unknown): boolean {
261
- return /revision|\b409\b|ABORTED|conflict/i.test(String((e as Error)?.message ?? e));
262
- }
263
-
264
- type Write = (bookingId: string, revision: string) => Promise<unknown>;
265
-
266
- async function respond(s: WristState, params: unknown, write: Write): Promise<WristState> {
263
+ async function respond(s: WristState, params: unknown, kind: BookingResponse): Promise<WristState> {
267
264
  if (s.inFlight) return s;
268
265
  const p = (params && typeof params === "object" ? params : {}) as Rec;
269
266
  const bookingId = str(p, "bookingId");
270
267
  const siteId = str(p, "siteId");
271
- // A press rendered for another business, or for a request that is no longer pending.
272
- if (siteId !== site().id) return { ...s, actionError: COPY.changed };
273
- const target = s.requests.find((r) => r.bookingId === bookingId);
268
+ // A press rendered for another business — or naming none, or made while no business is
269
+ // active (two empty ids are not the same business) — or for a request no longer pending.
270
+ if (!isForActiveBusiness(siteId, site().id)) return { ...s, actionError: COPY.changed };
271
+ let target = s.requests.find((r) => r.bookingId === bookingId);
272
+ if (!target && bookingId) {
273
+ // Not in the queue this state was read with: a press relayed from a NOTIFICATION
274
+ // can name a request that arrived after the last read, or one past the queue's first page.
275
+ // The booking itself, read by id from the business the press names, decides — one that is
276
+ // not pending there is not written. Guarded like the write, so a second press during the
277
+ // read cannot start a second one.
278
+ inFlightNow = true;
279
+ inFlightBooking = bookingId;
280
+ try {
281
+ const booking = ((await getBooking(bookingId, { site: siteId })) as unknown as { booking?: Rec } | undefined)?.booking;
282
+ if (booking && str(booking, "revision") && PENDING.has(str(booking, "status"))
283
+ && handled.get(bookingId) !== str(booking, "revision")) target = request({ ...booking, id: bookingId });
284
+ } catch (e) {
285
+ inFlightNow = false;
286
+ return isSignedOut(e) ? failure(s, e) : { ...s, actionError: COPY.changed };
287
+ }
288
+ inFlightNow = false;
289
+ }
274
290
  if (!target) return { ...s, actionError: COPY.changed };
275
291
  inFlightNow = true;
292
+ inFlightBooking = bookingId;
276
293
  writeGeneration += 1;
277
294
  try {
278
- await write(target.bookingId, target.revision);
295
+ await writeBookingResponse(kind, target.bookingId, target.revision, siteId);
279
296
  handled.set(target.bookingId, target.revision);
280
297
  } catch (e) {
281
298
  inFlightNow = false;
@@ -319,6 +336,16 @@ async function read(s: WristState): Promise<WristState> {
319
336
  // The state a concurrent action sees is the one it was dispatched with, so a double tap
320
337
  // would read `inFlight: false` twice; this module flag is the guard that actually holds.
321
338
  let inFlightNow = false;
339
+ /** The request that write is for. */
340
+ let inFlightBooking = "";
341
+
342
+ /** A press that lands while a write is running. The same request again (a double tap) is that
343
+ * write; ANOTHER request — a second notification's Accept relayed from the watch — was not
344
+ * written, and must say so rather than come back looking handled. */
345
+ function whileBusy(s: WristState, params: unknown): WristState {
346
+ const bookingId = str((params && typeof params === "object" ? params : {}) as Rec, "bookingId");
347
+ return bookingId && bookingId !== inFlightBooking ? { ...s, actionError: COPY.busy } : s;
348
+ }
322
349
 
323
350
  const svc = defineService<WristState, {
324
351
  load: (s: WristState) => Promise<WristState>;
@@ -333,8 +360,8 @@ const svc = defineService<WristState, {
333
360
  actions: {
334
361
  load: async (s) => read(s),
335
362
  refresh: async (s) => read(s),
336
- confirm: async (s, params) => (inFlightNow ? s : respond(s, params, (id, rev) => confirmBooking(id, rev))),
337
- decline: async (s, params) => (inFlightNow ? s : respond(s, params, (id, rev) => declineBooking(id, rev))),
363
+ confirm: async (s, params) => (inFlightNow ? whileBusy(s, params) : respond(s, params, "confirm")),
364
+ decline: async (s, params) => (inFlightNow ? whileBusy(s, params) : respond(s, params, "decline")),
338
365
  askDecline: async (s) => (s.current.bookingId ? { ...s, confirmingDecline: true, notConfirmingDecline: false, actionError: "" } : s),
339
366
  cancelDecline: async (s) => ({ ...s, confirmingDecline: false, notConfirmingDecline: true }),
340
367
  nextRequest: async (s) => {