@wix/himalaya-cli 0.1039.0 → 0.1041.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 (202) hide show
  1. package/dist/cli.mjs +31 -19
  2. package/dist/preview-assets.generated.bin +0 -0
  3. package/package.json +2 -2
  4. package/templates/beauty-salon/dev/index.ts +42 -42
  5. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +2 -0
  6. package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +6 -1
  7. package/templates/beauty-salon-owner/config.ts +2 -2
  8. package/templates/beauty-salon-owner/dev/index.ts +794 -295
  9. package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +237 -4
  10. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +2 -0
  11. package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +6 -1
  12. package/templates/beauty-salon-owner/tier5-src/_shared/owner-account.ts +45 -0
  13. package/templates/beauty-salon-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  14. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  15. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  16. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  17. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  18. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  19. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  20. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
  21. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
  22. package/templates/branded-lite/dev/index.ts +175 -175
  23. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +2 -0
  24. package/templates/branded-lite/tier5-src/_shared/collection-service.ts +6 -1
  25. package/templates/community-club/dev/index.ts +28 -28
  26. package/templates/community-club/tier5-src/_shared/collection-contract.ts +2 -0
  27. package/templates/community-club/tier5-src/_shared/collection-service.ts +6 -1
  28. package/templates/community-club-owner/config.ts +2 -2
  29. package/templates/community-club-owner/dev/index.ts +794 -295
  30. package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +237 -4
  31. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +2 -0
  32. package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +6 -1
  33. package/templates/community-club-owner/tier5-src/_shared/owner-account.ts +45 -0
  34. package/templates/community-club-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  35. package/templates/community-club-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  36. package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  37. package/templates/community-club-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  38. package/templates/community-club-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  39. package/templates/community-club-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  40. package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
  41. package/templates/community-club-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  42. package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
  43. package/templates/events-meetups/dev/index.ts +21 -21
  44. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +2 -0
  45. package/templates/events-meetups/tier5-src/_shared/collection-service.ts +6 -1
  46. package/templates/events-meetups-owner/config.ts +2 -2
  47. package/templates/events-meetups-owner/dev/index.ts +766 -243
  48. package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +237 -4
  49. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +2 -0
  50. package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +6 -1
  51. package/templates/events-meetups-owner/tier5-src/_shared/owner-account.ts +45 -0
  52. package/templates/events-meetups-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  53. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  54. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  55. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  56. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  57. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  58. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
  59. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  60. package/templates/gym-fitness/dev/index.ts +49 -49
  61. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +2 -0
  62. package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +6 -1
  63. package/templates/gym-fitness-owner/config.ts +2 -2
  64. package/templates/gym-fitness-owner/dev/index.ts +801 -320
  65. package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +237 -4
  66. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +2 -0
  67. package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +6 -1
  68. package/templates/gym-fitness-owner/tier5-src/_shared/owner-account.ts +45 -0
  69. package/templates/gym-fitness-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  70. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  71. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  72. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  73. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  74. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  75. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
  76. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  77. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
  78. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
  79. package/templates/nonprofit-cause/dev/index.ts +28 -28
  80. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +2 -0
  81. package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +6 -1
  82. package/templates/nonprofit-cause-owner/config.ts +2 -2
  83. package/templates/nonprofit-cause-owner/dev/index.ts +773 -268
  84. package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +237 -4
  85. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +2 -0
  86. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +6 -1
  87. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-account.ts +45 -0
  88. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  89. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  90. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  91. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  92. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  93. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  94. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
  95. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  96. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
  97. package/templates/owner-lite/config.ts +2 -2
  98. package/templates/owner-lite/dev/index.ts +802 -322
  99. package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +237 -4
  100. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +2 -0
  101. package/templates/owner-lite/tier5-src/_shared/collection-service.ts +6 -1
  102. package/templates/owner-lite/tier5-src/_shared/owner-account.ts +45 -0
  103. package/templates/owner-lite/tier5-src/_shared/owner-preview-demo.ts +21 -0
  104. package/templates/owner-lite/tier5-src/himi-owner-lite-account/index.ts +68 -0
  105. package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  106. package/templates/owner-lite/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  107. package/templates/owner-lite/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  108. package/templates/owner-lite/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  109. package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
  110. package/templates/owner-lite/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  111. package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
  112. package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
  113. package/templates/podcast-creator/dev/index.ts +28 -28
  114. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +2 -0
  115. package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +6 -1
  116. package/templates/podcast-creator-owner/config.ts +2 -2
  117. package/templates/podcast-creator-owner/dev/index.ts +794 -295
  118. package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +237 -4
  119. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +2 -0
  120. package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +6 -1
  121. package/templates/podcast-creator-owner/tier5-src/_shared/owner-account.ts +45 -0
  122. package/templates/podcast-creator-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  123. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  124. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  125. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  126. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  127. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  128. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
  129. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  130. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
  131. package/templates/portfolio-creative/dev/index.ts +35 -35
  132. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +2 -0
  133. package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +6 -1
  134. package/templates/portfolio-creative-owner/config.ts +2 -2
  135. package/templates/portfolio-creative-owner/dev/index.ts +794 -295
  136. package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +237 -4
  137. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +2 -0
  138. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +6 -1
  139. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-account.ts +45 -0
  140. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  141. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  142. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  143. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  144. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  145. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  146. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  147. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
  148. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
  149. package/templates/restaurant/dev/index.ts +21 -21
  150. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +2 -0
  151. package/templates/restaurant/tier5-src/_shared/collection-service.ts +6 -1
  152. package/templates/restaurant-owner/config.ts +2 -2
  153. package/templates/restaurant-owner/dev/index.ts +801 -320
  154. package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +237 -4
  155. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +2 -0
  156. package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +6 -1
  157. package/templates/restaurant-owner/tier5-src/_shared/owner-account.ts +45 -0
  158. package/templates/restaurant-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  159. package/templates/restaurant-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  160. package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  161. package/templates/restaurant-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  162. package/templates/restaurant-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  163. package/templates/restaurant-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  164. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
  165. package/templates/restaurant-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  166. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
  167. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
  168. package/templates/retail-store/dev/index.ts +21 -21
  169. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +2 -0
  170. package/templates/retail-store/tier5-src/_shared/collection-service.ts +6 -1
  171. package/templates/retail-store-owner/config.ts +2 -2
  172. package/templates/retail-store-owner/dev/index.ts +787 -270
  173. package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +237 -4
  174. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +2 -0
  175. package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +6 -1
  176. package/templates/retail-store-owner/tier5-src/_shared/owner-account.ts +45 -0
  177. package/templates/retail-store-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  178. package/templates/retail-store-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  179. package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  180. package/templates/retail-store-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  181. package/templates/retail-store-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  182. package/templates/retail-store-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  183. package/templates/retail-store-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  184. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +29 -13
  185. package/templates/yoga-studio/dev/index.ts +42 -42
  186. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +2 -0
  187. package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +6 -1
  188. package/templates/yoga-studio-owner/config.ts +2 -2
  189. package/templates/yoga-studio-owner/dev/index.ts +773 -268
  190. package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +237 -4
  191. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +2 -0
  192. package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +6 -1
  193. package/templates/yoga-studio-owner/tier5-src/_shared/owner-account.ts +45 -0
  194. package/templates/yoga-studio-owner/tier5-src/_shared/owner-preview-demo.ts +21 -0
  195. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-account/index.ts +68 -0
  196. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +28 -0
  197. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-ask/index.ts +4 -3
  198. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-drawer/index.ts +34 -48
  199. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-drawer/logic.ts +102 -0
  200. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +9 -3
  201. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post-form/index.ts +10 -4
  202. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +9 -3
@@ -7,75 +7,61 @@
7
7
  // attention today — and none of that is knowable when the config is authored.
8
8
  //
9
9
  // Hosting the panel lets the worker answer it at runtime: the live site, per-site
10
- // counts on the rows that have them, and a "needs attention" line that simply is
11
- // not there when the count is zero.
10
+ // counts on the rows that have them, and a "needs attention" section that simply
11
+ // is not there when nothing is waiting.
12
+ //
13
+ // The derived fields (initials, the attention total and header, each row's badge
14
+ // and spoken label, the clear-confirmation flags) live in ./logic.ts so they are
15
+ // unit-tested and can only ever agree with each other.
12
16
  //
13
17
  // Build: node core/dev-server/tier5-bundles-src/build-all.mjs himi-owner-lite-drawer
14
18
  // → core/dev-server/tier5-bundles/himi-owner-lite-drawer.bundle.js
15
19
 
16
20
  import { defineService } from "@himalaya/state";
17
21
  import { serializeForAgent } from "@himalaya/agent";
22
+ import { activeScreenFromInput, attentionRow, navRow, withDerived, type DrawerState } from "./logic.js";
18
23
 
19
- interface Row {
20
- id: string;
21
- title: string;
22
- icon: string;
23
- screenId: string;
24
- /** Rendered only when non-empty — an owner does not need a "0". */
25
- badge: string;
26
- }
27
-
28
- interface DrawerState {
29
- siteName: string;
30
- siteInitials: string;
31
- nav: Row[];
32
- /** Rows an owner should look at today; empty ⇒ the section renders nothing. */
33
- attention: Row[];
34
- attentionLabel: string;
35
- /** TRUE when there is nothing to chase. Feeds `hiddenPath`, which hides on a
36
- * TRUTHY value — so the flag is phrased negatively on purpose. */
37
- noAttention: boolean;
38
- }
39
-
40
- const nav = (id: string, title: string, icon: string, screenId: string, badge = ""): Row =>
41
- ({ id, title, icon, screenId, badge });
42
-
43
- /** Recompute the derived attention summary from the rows. */
44
- function withAttention(s: Omit<DrawerState, "attentionLabel" | "noAttention">): DrawerState {
45
- const n = s.attention.length;
46
- return {
47
- ...s,
48
- noAttention: n === 0,
49
- attentionLabel: n === 0 ? "All caught up" : `${n} need${n === 1 ? "s" : ""} attention`,
50
- };
51
- }
24
+ /** The shell passes the screen it is showing as worker input, so the drawer can mark
25
+ * that destination selected the way the declarative drawer does. */
26
+ const hostActiveScreen = () => activeScreenFromInput((globalThis as { himalaya?: { input?: unknown } }).himalaya?.input);
52
27
 
53
28
  const drawer = defineService<DrawerState, {
54
- /** Re-read the live site + counts. */
55
- refresh: (s: DrawerState) => DrawerState;
56
- /** Clear the attention list — proves the section disappears, not just empties. */
29
+ /** Re-read the active screen from host input (root onAppear). An explicit
30
+ * `{ screenId }` wins, for a host that pushes the change instead. */
31
+ syncActive: (s: DrawerState, args?: { screenId?: string }) => DrawerState;
32
+ /** First tap on "Mark all handled": ask before hiding anything. */
33
+ askClearAttention: (s: DrawerState) => DrawerState;
34
+ /** The owner backed out of the confirmation. */
35
+ cancelClearAttention: (s: DrawerState) => DrawerState;
36
+ /** Confirmed: clear the attention list — the section disappears, not just empties. */
57
37
  clearAttention: (s: DrawerState) => DrawerState;
58
38
  }>("drawer", {
59
- state: withAttention({
39
+ state: withDerived({
60
40
  siteName: "My Site",
61
- siteInitials: "MS",
62
41
  nav: [
63
- nav("drawer_home", "Home", "home", "himi_owner_lite_home"),
42
+ navRow("drawer_home", "Home", "home", "himi_owner_lite_home"),
64
43
  // Between Home and Manage deliberately: analytics answers "how is the
65
44
  // business doing", which sits with the dashboard, not with the per-entity
66
45
  // management areas below it.
67
- nav("drawer_analytics", "Analytics", "chart", "himi_owner_lite_analytics"),
68
- nav("drawer_manage", "Manage", "grid", "himi_owner_lite_manage"),
69
- nav("drawer_account", "Account", "person", "himi_owner_lite_account"),
46
+ navRow("drawer_analytics", "Analytics", "chart", "himi_owner_lite_analytics"),
47
+ navRow("drawer_manage", "Manage", "grid", "himi_owner_lite_manage"),
48
+ navRow("drawer_account", "Account", "person", "himi_owner_lite_account"),
70
49
  ],
71
50
  attention: [
72
- nav("att_orders", "Orders", "cart", "himi_owner_lite_orders", "3"),
73
- nav("att_appts", "Appointments", "calendar", "himi_owner_lite_appointments", "1"),
51
+ attentionRow("att_orders", "Orders", "cart", "himi_owner_lite_orders", 3),
52
+ attentionRow("att_appts", "Appointments", "calendar", "himi_owner_lite_appointments", 1),
74
53
  ],
54
+ confirmingClear: false,
55
+ activeScreenId: hostActiveScreen(),
75
56
  }),
76
57
  actions: {
77
- refresh: (s) => snap(withAttention({ ...s, attention: [...s.attention] })),
78
- clearAttention: (s) => snap(withAttention({ ...s, attention: [] })),
58
+ syncActive: (s, args) => snap(withDerived({
59
+ ...s,
60
+ activeScreenId: typeof args?.screenId === "string" ? args.screenId : hostActiveScreen(),
61
+ })),
62
+ askClearAttention: (s) => snap(withDerived({ ...s, confirmingClear: true })),
63
+ cancelClearAttention: (s) => snap(withDerived({ ...s, confirmingClear: false })),
64
+ clearAttention: (s) => snap(withDerived({ ...s, attention: [], confirmingClear: false })),
79
65
  },
80
66
  });
81
67
 
@@ -0,0 +1,102 @@
1
+ // himi-owner-lite DRAWER — pure derivations, kept out of index.ts so they are
2
+ // unit-testable without booting a worker.
3
+ //
4
+ // Everything a reader of the panel compares lives here, so the pieces can only ever
5
+ // agree: the header counts ITEMS summed from the same numbers the badges show (it
6
+ // used to count rows, and read "2 need attention" over badges of 3 and 1), and each
7
+ // row's spoken label is built from those same numbers.
8
+
9
+ export interface Row {
10
+ id: string;
11
+ title: string;
12
+ /** Design-system icon name for the row's leading glyph. */
13
+ icon: string;
14
+ screenId: string;
15
+ /** Items waiting on this row; 0 for a plain destination. */
16
+ count: number;
17
+ /** The badge text — the count, or "" (a destination shows no badge). */
18
+ badge: string;
19
+ /** The badge read on its own, for a screen reader that keeps a labelled row's
20
+ * children reachable (iOS): "3 need attention". "" when there is no badge. */
21
+ badgeLabel: string;
22
+ /** What a screen reader announces for the whole row. */
23
+ a11yLabel: string;
24
+ /** This row is the screen the owner is on. Only destinations can be. */
25
+ selected: boolean;
26
+ /** `!selected`, for `hiddenPath` (which hides on a TRUTHY value). */
27
+ notSelected: boolean;
28
+ }
29
+
30
+ /** The authored part of the state; everything else is derived by `withDerived`. */
31
+ export interface DrawerCore {
32
+ siteName: string;
33
+ nav: Row[];
34
+ attention: Row[];
35
+ /** The owner tapped "Mark all handled" and has not confirmed or cancelled yet. */
36
+ confirmingClear: boolean;
37
+ /** The screen the shell is showing, passed in as worker input; "" when unknown. */
38
+ activeScreenId: string;
39
+ }
40
+
41
+ export interface DrawerState extends DrawerCore {
42
+ siteInitials: string;
43
+ attentionTotal: number;
44
+ attentionLabel: string;
45
+ /** TRUE when there is nothing to chase. Feeds `hiddenPath`, which hides on a
46
+ * TRUTHY value — so the flag is phrased negatively on purpose. */
47
+ noAttention: boolean;
48
+ /** Hides the inline confirmation (same negative phrasing, same reason). */
49
+ noConfirmClear: boolean;
50
+ /** The confirmation question, naming how much will be hidden. */
51
+ clearPrompt: string;
52
+ }
53
+
54
+ const needs = (n: number) => `${n} need${n === 1 ? "s" : ""} attention`;
55
+
56
+ export const navRow = (id: string, title: string, icon: string, screenId: string): Row =>
57
+ ({ id, title, icon, screenId, count: 0, badge: "", badgeLabel: "", a11yLabel: title, selected: false, notSelected: true });
58
+
59
+ export const attentionRow = (id: string, title: string, icon: string, screenId: string, count: number): Row => {
60
+ const n = Math.max(0, Math.floor(count));
61
+ return { id, title, icon, screenId, count: n, badge: n > 0 ? String(n) : "", badgeLabel: n > 0 ? needs(n) : "", a11yLabel: n > 0 ? `${title}, ${needs(n)}` : title, selected: false, notSelected: true };
62
+ };
63
+
64
+ /** Up to two initials from the site name: "Pottery Studio" → "PS". */
65
+ export function initialsOf(name: string): string {
66
+ const words = name.trim().split(/\s+/).filter(Boolean);
67
+ if (words.length === 0) return "?";
68
+ return words.slice(0, 2).map((w) => [...w][0]!.toUpperCase()).join("");
69
+ }
70
+
71
+ /** Recompute every derived field. Rows with nothing waiting are dropped, so a
72
+ * cleared or all-zero section hides instead of listing zeros. */
73
+ export function withDerived(core: DrawerCore): DrawerState {
74
+ const attention = core.attention.filter((row) => row.count > 0);
75
+ const active = core.activeScreenId;
76
+ const nav = core.nav.map((row) => {
77
+ const selected = active !== "" && row.screenId === active;
78
+ return { ...row, selected, notSelected: !selected };
79
+ });
80
+ const total = attention.reduce((sum, row) => sum + row.count, 0);
81
+ const confirmingClear = core.confirmingClear && total > 0;
82
+ return {
83
+ ...core,
84
+ nav,
85
+ attention,
86
+ confirmingClear,
87
+ siteInitials: initialsOf(core.siteName),
88
+ attentionTotal: total,
89
+ attentionLabel: total === 0
90
+ ? "All caught up"
91
+ : `${total} item${total === 1 ? "" : "s"} ${total === 1 ? "needs" : "need"} attention`,
92
+ noAttention: total === 0,
93
+ noConfirmClear: !confirmingClear,
94
+ clearPrompt: total === 1 ? "Hide this reminder?" : `Hide these ${total} reminders?`,
95
+ };
96
+ }
97
+
98
+ /** The active screen from the worker's host input (`himalaya.input.activeScreenId`). */
99
+ export function activeScreenFromInput(input: unknown): string {
100
+ const v = (input as { activeScreenId?: unknown } | undefined)?.activeScreenId;
101
+ return typeof v === "string" ? v : "";
102
+ }
@@ -9,7 +9,7 @@ import { wixMediaUrl, WIX_DETAIL_IMAGE } from "../_shared/himi-store.js";
9
9
  import { defineService } from "@himalaya/state";
10
10
  import { getDraftPost, createDraftPost, updateDraftPost, type WixDraftPostWrite, type WixRichContent } from "@himalaya/wix-blog";
11
11
  import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
12
- import { DEMO_POSTS, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
12
+ import { DEMO_POSTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
13
13
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
14
14
  import { get as dbGet, upsert as dbUpsert, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
15
15
 
@@ -127,6 +127,12 @@ function shape(s: Partial<PostFormState>): PostFormState {
127
127
 
128
128
  const startMode: Mode = initialInput.mode === "edit" || initialInput.id ? "edit" : "create";
129
129
 
130
+
131
+ /** A demo-preview write refusal: see demoRefusalPatch (a mode, not an error). */
132
+ function demoRefusal(s: Parameters<typeof shape>[0]): ReturnType<typeof shape> {
133
+ return shape({ ...s, ...demoRefusalPatch(copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE)) });
134
+ }
135
+
130
136
  const svc = defineService<PostFormState, {
131
137
  load: (s: PostFormState, arg?: unknown) => Promise<PostFormState>;
132
138
  setCover: (s: PostFormState, arg?: unknown) => PostFormState;
@@ -140,7 +146,7 @@ const svc = defineService<PostFormState, {
140
146
  // bakes no id/mode); the onAppear `id` param is the #570 suffixed-build
141
147
  // fallback. An id ⇒ edit; no id anywhere ⇒ the create shell.
142
148
  const id = navInputId() || idFrom(arg, s.id);
143
- if (!id) return shape({ ...s, mode: "create", id: "", status: "idle", error: "" });
149
+ if (!id) return shape({ ...s, mode: "create", id: "", status: "idle", error: "", demoNote: isOwnerPreviewDemo() ? copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) : "" });
144
150
  if (isOwnerPreviewDemo()) {
145
151
  const d = DEMO_POSTS.find((x) => x.id === id);
146
152
  if (d) {
@@ -203,7 +209,7 @@ const svc = defineService<PostFormState, {
203
209
  return shape({ ...s, coverUrl, error: "" });
204
210
  },
205
211
  submit: async (s, values) => {
206
- if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, status: "error", error: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
212
+ if (isOwnerPreviewDemo() || s.demoNote) return demoRefusal(s);
207
213
  const v = values ?? {};
208
214
  const title = String(v.title ?? s.title).trim();
209
215
  const excerpt = String(v.excerpt ?? s.excerpt).trim();
@@ -272,7 +278,7 @@ const svc = defineService<PostFormState, {
272
278
  // Demo / web-preview (no owner bridge; the record came from demo sample
273
279
  // data, marked by demoNote): a queued write could never replay, so answer
274
280
  // with the demo save note instead of the real-offline write-behind.
275
- if (s.demoNote) return shape({ ...s, status: "error", error: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE), savedMessage: "" });
281
+ if (s.demoNote) return demoRefusal(s);
276
282
  // Real device offline: cache and queue the edit for reconnect.
277
283
  await cachePut(localRow(localId));
278
284
  await queueWrite("tier5.himiOwnerLitePostForm.submit", { id: targetId, title, excerpt, body, coverUrl, coverChanged: String(coverChanged), preserveBody: String(preserveBody), isFeatured: String(isFeatured), mode: editing ? "edit" : "create" });
@@ -18,7 +18,7 @@ import {
18
18
  import { get as dbGet, upsert as dbUpsert, remove as dbRemove, enqueue as dbEnqueue, type DbEntity } from "../_shared/owner-local-db.js";
19
19
  import { plainText } from "../_shared/himi-store.js";
20
20
  import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo, shouldQueueOwnerWrite } from "../_shared/owner-load-error.js";
21
- import { DEMO_PRODUCTS, DEMO_NOTE, DEMO_SAVE_NOTE } from "../_shared/owner-preview-demo.js";
21
+ import { DEMO_PRODUCTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch, editablePrice } from "../_shared/owner-preview-demo.js";
22
22
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
23
23
 
24
24
  type Mode = "create" | "edit";
@@ -95,9 +95,14 @@ function boolString(value: unknown, fallback: string): string {
95
95
  // fall back to the raw numeric if the formatted string is missing.
96
96
  function priceOf(p: WixProduct): string {
97
97
  const pr = (p as any).price;
98
- return (pr?.formatted?.price || (pr?.price != null ? String(pr.price) : "")) as string;
98
+ // The field is EDITABLE and renderer-validated as an amount, so prefill the
99
+ // raw number — never the display string ("€10.00"), which would fail the rule
100
+ // and disable Save before the owner typed anything.
101
+ if (typeof pr?.price === "number" && Number.isFinite(pr.price)) return pr.price.toFixed(2);
102
+ return editablePrice(String(pr?.formatted?.price ?? pr?.price ?? ""));
99
103
  }
100
104
 
105
+
101
106
  function shape(s: Partial<ProductFormState>): ProductFormState {
102
107
  const mode: Mode = s.mode ?? "create";
103
108
  const id = typeof s.id === "string" ? s.id : "";
@@ -128,6 +133,12 @@ function shape(s: Partial<ProductFormState>): ProductFormState {
128
133
 
129
134
  const startMode: Mode = initialInput.mode === "edit" || initialInput.id ? "edit" : "create";
130
135
 
136
+
137
+ /** A demo-preview write refusal: see demoRefusalPatch (a mode, not an error). */
138
+ function demoRefusal(s: Parameters<typeof shape>[0]): ReturnType<typeof shape> {
139
+ return shape({ ...s, ...demoRefusalPatch(copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE)) });
140
+ }
141
+
131
142
  const svc = defineService<ProductFormState, {
132
143
  load: (s: ProductFormState, arg?: unknown) => Promise<ProductFormState>;
133
144
  submit: (s: ProductFormState, values?: Record<string, unknown>) => Promise<ProductFormState>;
@@ -141,13 +152,13 @@ const svc = defineService<ProductFormState, {
141
152
  // bakes no id/mode); the onAppear `id` param is the #570 suffixed-build
142
153
  // fallback. An id ⇒ edit; no id anywhere ⇒ the create shell.
143
154
  const id = navInputId() || idFrom(arg, s.id);
144
- if (!id) return shape({ ...s, mode: "create", id: "", status: "idle", error: "" });
155
+ if (!id) return shape({ ...s, mode: "create", id: "", status: "idle", error: "", demoNote: isOwnerPreviewDemo() ? copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) : "" });
145
156
  if (isOwnerPreviewDemo()) {
146
157
  const d = DEMO_PRODUCTS.find((p) => p.id === id);
147
158
  if (d) {
148
159
  return shape({
149
160
  ...s, mode: "edit", id, heading: copyParam("ownerProductForm.headingEdit", "Edit product"),
150
- name: d.name, price: d.price.replace(/[^\d.]/g, ""), sku: d.sku, description: d.description,
161
+ name: d.name, price: editablePrice(d.price), sku: d.sku, description: d.description,
151
162
  visible: d.visible ? "true" : "false",
152
163
  status: "idle", error: "", savedMessage: "", demoNote: copyParam("ownerCommon.demoNote", DEMO_NOTE),
153
164
  });
@@ -178,7 +189,7 @@ const svc = defineService<ProductFormState, {
178
189
  if (cached) {
179
190
  return shape({
180
191
  ...s, mode: "edit", id, heading: "Edit product",
181
- name: String(cached.name ?? ""), price: String(cached.price ?? ""), sku: String(cached.sku ?? ""),
192
+ name: String(cached.name ?? ""), price: editablePrice(String(cached.price ?? "")), sku: String(cached.sku ?? ""),
182
193
  visible: cached.visible === "false" ? "false" : "true",
183
194
  status: "idle", error: "", savedMessage: "",
184
195
  });
@@ -188,7 +199,7 @@ const svc = defineService<ProductFormState, {
188
199
  if (d) {
189
200
  return shape({
190
201
  ...s, mode: "edit", id, heading: copyParam("ownerProductForm.headingEdit", "Edit product"),
191
- name: d.name, price: d.price.replace(/[^\d.]/g, ""), sku: d.sku, description: d.description,
202
+ name: d.name, price: editablePrice(d.price), sku: d.sku, description: d.description,
192
203
  visible: d.visible ? "true" : "false",
193
204
  status: "idle", error: "", savedMessage: "", demoNote: copyParam("ownerCommon.demoNote", DEMO_NOTE),
194
205
  });
@@ -198,17 +209,22 @@ const svc = defineService<ProductFormState, {
198
209
  }
199
210
  },
200
211
  submit: async (s, values) => {
201
- if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, status: "error", error: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
212
+ if (isOwnerPreviewDemo() || s.demoNote) return demoRefusal(s);
202
213
  const v = values ?? {};
203
214
  const name = String(v.name ?? s.name).trim();
204
- const price = String(v.price ?? s.price).trim();
215
+ const rawPrice = String(v.price ?? s.price).trim();
216
+ // "12,50" (comma-decimal keypad) is accepted and written as 12.50.
217
+ const price = editablePrice(rawPrice);
205
218
  const sku = String(v.sku ?? s.sku).trim();
206
219
  const description = String(v.description ?? s.description).trim();
207
220
  const visible = boolString(v.visible, s.visible) === "true";
208
221
  if (!name) return shape({ ...s, status: "error", error: copyParam("ownerProductForm.nameRequired", "Name is required"), savedMessage: "" });
209
222
  const priceNum = Number(price);
210
- if (!price || !Number.isFinite(priceNum)) {
211
- return shape({ ...s, name, price, sku, description, visible: visible ? "true" : "false", status: "error", error: copyParam("ownerProductForm.priceInvalid", "Enter a valid price (a number)."), savedMessage: "" });
223
+ // The renderer already blocks these (declarative `validation`); this is the
224
+ // worker's own guard for a host or queue replay that skips it. Checked on the
225
+ // RAW value, so "-5" is refused rather than normalized into "5".
226
+ if (!/^\d+([.,]\d{1,2})?$/.test(rawPrice) || !Number.isFinite(priceNum)) {
227
+ return shape({ ...s, name, price, sku, description, visible: visible ? "true" : "false", status: "error", error: copyParam("ownerProductForm.priceInvalid", "Enter a price of 0 or more."), savedMessage: "" });
212
228
  }
213
229
 
214
230
  const write: WixProductWrite = {
@@ -254,7 +270,7 @@ const svc = defineService<ProductFormState, {
254
270
  // Demo / web-preview (no owner bridge; the record came from DEMO_PRODUCTS,
255
271
  // marked by demoNote): a queued write could never replay, so answer with
256
272
  // the demo save note instead of the real-offline write-behind.
257
- if (s.demoNote) return shape({ ...s, status: "error", error: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE), savedMessage: "" });
273
+ if (s.demoNote) return demoRefusal(s);
258
274
  // Real device offline: the optimistic cache row already shows; queue the
259
275
  // write to replay (WriteQueue, idempotent) when connectivity returns.
260
276
  await queueWrite("tier5.himiOwnerLiteProductForm.submit", { id: s.id, name, price, sku, visible: String(visible), mode: s.mode });
@@ -269,7 +285,7 @@ const svc = defineService<ProductFormState, {
269
285
  }
270
286
  },
271
287
  remove: async (s) => {
272
- if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, status: "error", error: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
288
+ if (isOwnerPreviewDemo() || s.demoNote) return demoRefusal(s);
273
289
  if (!s.id) return shape({ ...s, status: "error", error: copyParam("ownerProductForm.errorNoDelete", "No product to delete."), savedMessage: "" });
274
290
  // Optimistic local delete — the list drops the row immediately.
275
291
  await cacheDrop(s.id);
@@ -278,7 +294,7 @@ const svc = defineService<ProductFormState, {
278
294
  return shape({ ...s, id: "", status: "saved", error: "", savedMessage: copyParam("ownerProductForm.deletedMessage", "Product deleted."), savedId: "" });
279
295
  } catch (e) {
280
296
  if (shouldQueueOwnerWrite(e)) {
281
- if (s.demoNote) return shape({ ...s, status: "error", error: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE), savedMessage: "" });
297
+ if (s.demoNote) return demoRefusal(s);
282
298
  await queueWrite("tier5.himiOwnerLiteProductForm.remove", { id: s.id });
283
299
  return shape({ ...s, id: "", status: "saved", error: "", savedMessage: copyParam("ownerProductForm.deletedOffline", "Deleted offline — will sync when you reconnect."), savedId: "" });
284
300
  }