@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
@@ -230,6 +230,15 @@ export interface AriaChatState {
230
230
  * Stop that cannot stop anything is worse than no Stop, because the owner
231
231
  * taps it, the answer arrives anyway, and the control is what they blame. */
232
232
  stopHidden: boolean;
233
+ /** The microphone is open for composer dictation. Set by `dictate`, cleared by
234
+ * `finishDictation` and by the result landing. */
235
+ listening: boolean;
236
+ /** Gates for the composer's mic control and its "Listening…" row, in the
237
+ * `hiddenPath` polarity. Derived in `gated`, like `stopHidden`: the mic is
238
+ * collapsed for a consumer with no `dictation` configured and while it is
239
+ * already listening; the listening row is collapsed whenever it is not. */
240
+ micHidden: boolean;
241
+ listeningHidden: boolean;
233
242
  /** Intentionally never true: a `form` submit that returns `ok` makes the host
234
243
  * POP the screen. A chat composer must stay put, so the whole surface leaves
235
244
  * this false and the bound message list simply re-renders. */
@@ -317,6 +326,13 @@ export interface AriaChatLabels {
317
326
  * all — it carries no Wix app binding to sign the chat-service call with.
318
327
  * See `isWixAccountUnreachable`, and note what it is NOT: a signed-out user. */
319
328
  unavailable?: Resolvable<string>;
329
+ /** Neutral line when dictation ended with nothing to insert. The recognizer
330
+ * reports silence, a denied microphone and a missing engine the SAME way
331
+ * (`cancelled`), so this cannot say which — it names both likely causes. */
332
+ dictationEmpty?: Resolvable<string>;
333
+ /** Error line when the host could not start dictation at all (the speech
334
+ * capability is not registered, or refused the call). */
335
+ dictationFailed?: Resolvable<string>;
320
336
  /** The mention picker's copy. See `MentionLabels`. */
321
337
  mentionTitle?: Resolvable<string>;
322
338
  mentionNoProviders?: Resolvable<string>;
@@ -349,6 +365,41 @@ export interface AriaChatLabels {
349
365
  toolFailed?: Resolvable<string>;
350
366
  }
351
367
 
368
+ /** One dictation outcome, handed back to the service by `AriaDictationOptions.deliver`.
369
+ * `session` names the `dictate` tap it answers, so a result for a session that has
370
+ * since been replaced is dropped instead of landing in the composer. */
371
+ export interface AriaDictationResult {
372
+ session: number;
373
+ transcript?: string;
374
+ cancelled?: boolean;
375
+ /** The host refused or threw — as opposed to hearing nothing. */
376
+ failed?: boolean;
377
+ }
378
+
379
+ /** Composer dictation, over whatever speech binding the consumer has.
380
+ *
381
+ * `listen` is NOT awaited by the action that starts it: an action's state only
382
+ * paints when it returns, so awaiting a whole utterance would leave the mic tap
383
+ * looking dead until the owner stopped talking. `dictate` returns "Listening…"
384
+ * at once, and the result re-enters as a FRESH dispatch through `deliver` —
385
+ * which is the bundle's own `svc.dictated`, since only the bundle holds `svc`. */
386
+ export interface AriaDictationOptions {
387
+ /** Record one utterance and resolve with what was heard. Must REJECT when the
388
+ * host cannot dictate at all, so that is told apart from hearing nothing. */
389
+ listen(): Promise<{ transcript: string; cancelled: boolean }>;
390
+ /** Close the microphone. On iOS this also resolves `listen` with what had been
391
+ * heard so far, which is what makes "Done" keep the owner's words. */
392
+ stop(): Promise<void>;
393
+ /** Re-enter the service with the outcome — `(r) => { void svc.dictated(r); }`. */
394
+ deliver(result: AriaDictationResult): void;
395
+ /** Can this host recognize speech at all? Asked once, on the first `load`.
396
+ * `false` (or a rejection) collapses the mic: a control that can only ever
397
+ * answer "Didn't catch that" — a browser with no SpeechRecognition, a macOS
398
+ * build whose recognizer is a stub — sends the owner to fix a microphone
399
+ * permission that is not the problem. Omitted ⇒ assumed available. */
400
+ available?(): Promise<boolean>;
401
+ }
402
+
352
403
  /** What the composer needs from the device to attach a photo. Supplied by the
353
404
  * app's adapter, because both are capabilities the SCREEN has to declare — the
354
405
  * shared core imports neither, so a consumer without attachments (the clone)
@@ -441,6 +492,9 @@ export interface AriaChatOptions {
441
492
  * authoritative and account-scoped by the signed instance; losing the offline
442
493
  * fallback is a far smaller cost than that. */
443
494
  localHistory?: boolean;
495
+ /** Composer dictation. Omit it and `dictate` is inert and `micHidden`
496
+ * stays true, so a descriptor that never adds a mic is unaffected. */
497
+ dictation?: AriaDictationOptions;
444
498
  }
445
499
 
446
500
  /** The argument shape every action tolerates. The clone's generic per-item
@@ -1235,6 +1289,16 @@ export interface AriaChatCore {
1235
1289
  widgetPick(s: AriaChatState, arg?: AriaActionArg): AriaChatState;
1236
1290
  /** Answer one QUESTIONNAIRE question. */
1237
1291
  widgetAnswer(s: AriaChatState, arg?: AriaActionArg): AriaChatState;
1292
+ /** The composer's mic: open the recognizer and show "Listening…". Returns at
1293
+ * once; the transcript arrives later through `dictated`. */
1294
+ dictate(s: AriaChatState): AriaChatState;
1295
+ /** "Done" on the listening row: close the microphone. The session stays live,
1296
+ * so what the recognizer had already heard is still inserted when it lands. */
1297
+ finishDictation(s: AriaChatState): AriaChatState;
1298
+ /** A dictation outcome (see `AriaDictationOptions.deliver`). Resolves to
1299
+ * `undefined` — leave the state untouched — for a session that is no longer
1300
+ * the live one. */
1301
+ dictated(s: AriaChatState, arg?: unknown): AriaChatState | undefined;
1238
1302
  /** Open the mention picker at the category level, listing the site's
1239
1303
  * installed providers the first time (per business). `undefined` when the
1240
1304
  * business switched while the list was being fetched. */
@@ -1300,6 +1364,10 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
1300
1364
  const unavailableText = () => res(
1301
1365
  o.labels?.unavailable ?? `${assistant()} can't reach your Wix account from here.`,
1302
1366
  );
1367
+ const dictationEmptyText = () => res(
1368
+ o.labels?.dictationEmpty ?? "Didn't catch that. Check microphone access and try again.",
1369
+ );
1370
+ const dictationFailedText = () => res(o.labels?.dictationFailed ?? "Dictation isn't available right now.");
1303
1371
  const attachUploadingText = () => res(o.labels?.attachUploading ?? "Uploading…");
1304
1372
  const attachFailedText = () => res(o.labels?.attachFailed ?? "Couldn't upload");
1305
1373
  const chipLabels = () => ({ uploading: attachUploadingText(), failed: attachFailedText() });
@@ -1434,6 +1502,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
1434
1502
  messages: [], prompts: [], _convId: "", _siteId: "", _pending: "", _pendingRefs: "", _pendingAttach: [],
1435
1503
  _pendingAfter: 0, _pendingId: "", _replyAfter: 0, _srvUsers: 0, _srvAssistants: 0,
1436
1504
  _abortId: "", _turnId: "", stopHidden: true, ok: false,
1505
+ listening: false, micHidden: !o.dictation, listeningHidden: true,
1437
1506
  widget: NO_WIDGET, mention: mentionViewNow,
1438
1507
  attachments: [], attachmentsHidden: true,
1439
1508
  };
@@ -1658,6 +1727,77 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
1658
1727
  let draft = "";
1659
1728
  let draftSeq = 0;
1660
1729
 
1730
+ /** Dictation sessions: a counter minted per `dictate` tap, and the one whose
1731
+ * result may still land (0 = none).
1732
+ *
1733
+ * A latch rather than state for the same reason `inFlightSend` is one: the
1734
+ * result arrives as a fresh dispatch handed the CURRENT state, and the only
1735
+ * way to tell it is the answer to an older tap — one the recognizer is still
1736
+ * finishing after the owner started another — is to remember which tap is
1737
+ * live here. Without it a slow recognizer's late result lands in the
1738
+ * composer on top of the one the owner is dictating now. */
1739
+ let dictationSession = 0;
1740
+ let dictationLive = 0;
1741
+ /** The live session was ended by the owner's own "Done". Its result is still
1742
+ * inserted, but an EMPTY one is not reported: they stopped it, and a
1743
+ * recognizer that does not answer a stop (Android's resolves only at its
1744
+ * safety cap, 30s later) would otherwise put "Didn't catch that" on screen
1745
+ * long after anyone was asking. */
1746
+ let dictationStopped = false;
1747
+ /** Sessions the owner ended with "Done" and then SUPERSEDED with a new tap
1748
+ * before their answers landed. Their words are still theirs: Done promises
1749
+ * to keep what was heard, and quick re-taps must not break that — on Android
1750
+ * the new `recognizeOnce` is exactly what settles the old recognizer, so its
1751
+ * answer arrives right as the new session opens. A SET, not one slot: Done →
1752
+ * Dictate → Done → Dictate leaves two outstanding, and a single slot dropped
1753
+ * the first. Each is appended as it lands, silently, without touching the
1754
+ * live session's `listening`, and leaves the set when it does. */
1755
+ const dictationFinishing = new Set<number>();
1756
+ /** The last dictated insertion: the draft it was built on and the draft it
1757
+ * produced — see `insertHeard`. Forgotten on any edit, send or fresh chat,
1758
+ * so it can only ever bridge answers that have not committed yet. */
1759
+ let lastInsert: { from: string; to: string } | null = null;
1760
+ /** The host's answer to `dictation.available`, once asked. `null` until then. */
1761
+ let dictationAvailable: boolean | null = null;
1762
+ const probeDictation = async (): Promise<void> => {
1763
+ if (!o.dictation?.available || dictationAvailable !== null) return;
1764
+ try { dictationAvailable = (await o.dictation.available()) === true; } catch { dictationAvailable = false; }
1765
+ };
1766
+ /** Heard words INTO the draft, never sent: the owner reads what the
1767
+ * recognizer made of them before it goes anywhere. Appended to what they had
1768
+ * already typed. Dictation is an edit to the draft, so it moves the draft
1769
+ * latch exactly as a keystroke does — `withDraft` must not put the old text
1770
+ * back. */
1771
+ const insertHeard = (s: AriaChatState, heard: string): string => {
1772
+ const current = s.input ?? "";
1773
+ // Two answers can land in the SAME turn — parked Done sessions settling
1774
+ // together — and each `dictated` is handed the state as it was before
1775
+ // either committed. Built on that snapshot, the second would overwrite the
1776
+ // first's words. So when the snapshot is still exactly what the previous
1777
+ // insertion started from, build on what that insertion produced instead.
1778
+ const base = lastInsert && current === lastInsert.from ? lastInsert.to : current;
1779
+ const typed = base.replace(/\s+$/, "");
1780
+ const input = typed ? `${typed} ${heard}` : heard;
1781
+ lastInsert = { from: current, to: input };
1782
+ draft = input;
1783
+ draftSeq += 1;
1784
+ return input;
1785
+ };
1786
+ /** A send or a fresh chat ends dictation. The words belong to the draft the
1787
+ * owner was writing THEN; a transcript that lands after they sent it, or in
1788
+ * another conversation's composer, reads as a duplicate draft ready to send
1789
+ * twice. So the live session is retired (its late answer will be dropped)
1790
+ * and a still-open microphone is closed. Returns the state fields to spread. */
1791
+ const retireDictation = (): { listening: false } => {
1792
+ dictationFinishing.clear();
1793
+ lastInsert = null;
1794
+ if (dictationLive) {
1795
+ dictationLive = 0;
1796
+ o.dictation?.stop().catch(() => { /* already closed */ });
1797
+ }
1798
+ return { listening: false };
1799
+ };
1800
+
1661
1801
  /** The client-tool exchange the current turn is in the middle of, if any.
1662
1802
  *
1663
1803
  * While it runs, the agent is WAITING ON US — for the owner's consent, say —
@@ -2037,6 +2177,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
2037
2177
  // tap that answers a question the owner has already moved past — the same
2038
2178
  // reason a scrollback widget keeps its prose and loses its controls.
2039
2179
  widget: NO_WIDGET,
2180
+ ...retireDictation(),
2040
2181
  };
2041
2182
  inFlightSend = content;
2042
2183
  // Held outside the try so a FAILED send still reports the conversation it
@@ -2313,9 +2454,16 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
2313
2454
  // `_pending` rather than `turnInFlight` — see `regenerate` for why `status`
2314
2455
  // is not trusted to have been cleared.
2315
2456
  const replyActionsHidden = !(lastReply(s) && !s._pending);
2457
+ // The dictation pair is derived here for the same reason: two exact
2458
+ // negations each maintained by hand is how a screen ends up with both the
2459
+ // mic and "Listening…" on it, or neither.
2460
+ const listening = s.listening === true;
2461
+ const micHidden = !o.dictation || dictationAvailable === false || listening;
2462
+ const listeningHidden = !listening;
2316
2463
  const actioned = s.stopHidden === hidden && s.replyActionsHidden === replyActionsHidden
2464
+ && s.micHidden === micHidden && s.listeningHidden === listeningHidden
2317
2465
  ? s
2318
- : { ...s, stopHidden: hidden, replyActionsHidden };
2466
+ : { ...s, stopHidden: hidden, replyActionsHidden, micHidden, listeningHidden };
2319
2467
  // The attachment projection rides the same exit, for the same reason: one
2320
2468
  // place that every action's result passes through, so no reducer can hand
2321
2469
  // back a stale copy of the chips. Seeded first, so the very first action of
@@ -2557,6 +2705,9 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
2557
2705
  // still happened.
2558
2706
  draft = next;
2559
2707
  draftSeq += 1;
2708
+ // The owner's own edit: whatever a previous insertion produced is theirs
2709
+ // to change now, and must not be built on again.
2710
+ lastInsert = null;
2560
2711
  return next === s.input ? s : { ...s, input: next };
2561
2712
  },
2562
2713
 
@@ -2609,6 +2760,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
2609
2760
  // cancel a poll, so the only way to stop the 2s cadence is to re-arm it
2610
2761
  // slow — do that whenever this interrupted something.
2611
2762
  const wasMidTurn = !!s._pending || s.status === "sending";
2763
+ const retired = retireDictation();
2612
2764
  const slow = (next: AriaChatState): AriaChatState =>
2613
2765
  wasMidTurn && o.poll ? cadence(next, o.poll.idleMs, null) : next;
2614
2766
  try {
@@ -2622,11 +2774,11 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
2622
2774
  // and the old observed counts back — the fresh chat snapping back to the
2623
2775
  // previous one, which reads as ➕ having done nothing.
2624
2776
  turnSeq += 1;
2625
- return slow({ ...s, status: "ready", _convId: conversationIdOf(res), _siteId: site, input: "", statusText: "", error: "",
2777
+ return slow({ ...s, ...retired, status: "ready", _convId: conversationIdOf(res), _siteId: site, input: "", statusText: "", error: "",
2626
2778
  _pending: "", _abortId: "", _turnId: "", copyNote: "", messages: greetingOnly(), ...observed([]), prompts: promptList(), ok: false });
2627
2779
  } catch (e) {
2628
2780
  turnSeq += 1; // see above — a failed create commits state too
2629
- return slow({ ...s, status: "ready", _convId: "", _siteId: site, input: "", ...failureSlots(e, unavailableText),
2781
+ return slow({ ...s, ...retired, status: "ready", _convId: "", _siteId: site, input: "", ...failureSlots(e, unavailableText),
2630
2782
  _pending: "", _abortId: "", _turnId: "", copyNote: "", messages: greetingOnly(), ...observed([]), prompts: promptList(), ok: false });
2631
2783
  }
2632
2784
  },
@@ -2960,6 +3112,77 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
2960
3112
  ? openWidgetUrl(s, s.widget.secondaryUrl)
2961
3113
  : impl.widgetRespond(s, { value: "secondary" }),
2962
3114
 
3115
+ dictate: (s) => {
3116
+ if (!o.dictation || s.listening) return s;
3117
+ const session = ++dictationSession;
3118
+ // Only a session the owner closed with Done can still be live here
3119
+ // (`listening` refuses a tap otherwise), and its answer is still owed.
3120
+ if (dictationLive && dictationStopped) dictationFinishing.add(dictationLive);
3121
+ dictationLive = session;
3122
+ dictationStopped = false;
3123
+ const deliver = (r: Omit<AriaDictationResult, "session">) => {
3124
+ try { o.dictation!.deliver({ session, ...r }); } catch { /* the bundle is gone */ }
3125
+ };
3126
+ // Deliberately not awaited — see `AriaDictationOptions`.
3127
+ o.dictation.listen().then(
3128
+ (r) => deliver({ transcript: r.transcript, cancelled: r.cancelled }),
3129
+ () => deliver({ failed: true }),
3130
+ );
3131
+ // A new attempt retires the previous attempt's own messages, and nothing
3132
+ // else: the status line may be saying "Aria is typing…", which is the
3133
+ // turn's, and the error slot may hold a failed send the owner still needs.
3134
+ return {
3135
+ ...s,
3136
+ listening: true,
3137
+ statusText: s.statusText === dictationEmptyText() ? "" : s.statusText,
3138
+ error: s.error === dictationFailedText() ? "" : s.error,
3139
+ };
3140
+ },
3141
+
3142
+ finishDictation: (s) => {
3143
+ if (!s.listening) return s;
3144
+ // Close the mic without retiring the session: the recognizer answers with
3145
+ // what it had already heard, and dropping that would make "Done" throw the
3146
+ // owner's words away.
3147
+ dictationStopped = true;
3148
+ if (o.dictation) o.dictation.stop().catch(() => { /* already closed */ });
3149
+ return { ...s, listening: false };
3150
+ },
3151
+
3152
+ dictated: (s, arg) => {
3153
+ const r = (arg && typeof arg === "object" ? arg : {}) as Partial<AriaDictationResult>;
3154
+ const session = Number(r.session);
3155
+ // A Done session superseded by a newer tap: keep its words, and nothing
3156
+ // else. Silent on nothing-heard or failure — the owner has moved on to the
3157
+ // next utterance — and `listening` belongs to the live session.
3158
+ if (session && dictationFinishing.has(session)) {
3159
+ dictationFinishing.delete(session);
3160
+ const words = !r.failed && !r.cancelled && typeof r.transcript === "string" ? r.transcript.trim() : "";
3161
+ return words ? { ...s, input: insertHeard(s, words) } : undefined;
3162
+ }
3163
+ // Not the live session: a retired tap's recognizer finishing late (a send
3164
+ // or a new chat ended it), or a result for a worker instance that no
3165
+ // longer exists. Leave everything alone.
3166
+ if (!session || session !== dictationLive) return undefined;
3167
+ dictationLive = 0;
3168
+ const done: AriaChatState = { ...s, listening: false };
3169
+ // Into the error slot only when it is free (or already ours): a failed send
3170
+ // the owner still has to act on outranks a mic that did not start.
3171
+ if (r.failed) return s.error && s.error !== dictationFailedText() ? done : { ...done, error: dictationFailedText() };
3172
+ const heard = typeof r.transcript === "string" ? r.transcript.trim() : "";
3173
+ if (r.cancelled || !heard) {
3174
+ // Neutral, and only when the status line is free: "Aria is typing…"
3175
+ // belongs to a turn in flight and is not ours to overwrite. Silent
3176
+ // after the owner's own Done — see `dictationStopped`.
3177
+ return turnInFlight(s) || dictationStopped ? done : { ...done, statusText: dictationEmptyText() };
3178
+ }
3179
+ return {
3180
+ ...done,
3181
+ input: insertHeard(s, heard),
3182
+ statusText: s.statusText === dictationEmptyText() ? "" : s.statusText,
3183
+ };
3184
+ },
3185
+
2963
3186
  openLink: async (s, arg) => {
2964
3187
  const url = typeof arg === "string" ? arg.trim() : (arg?.url ?? arg?.id ?? "").trim();
2965
3188
  if (!url) return s;
@@ -3215,7 +3438,14 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
3215
3438
  // Every action's result goes through the Stop gate on its way out.
3216
3439
  return {
3217
3440
  initialState,
3218
- load: (s) => impl.load(s).then(gated),
3441
+ // A `listening: true` that no session in THIS worker owns was persisted by
3442
+ // an earlier process: the recognizer it described is gone, and leaving the
3443
+ // flag up would hide the mic for good.
3444
+ load: async (s) => {
3445
+ const r = await impl.load(s);
3446
+ await probeDictation();
3447
+ return gated(r.listening && !dictationLive ? { ...r, listening: false } : r);
3448
+ },
3219
3449
  submit: (s, arg) => impl.submit(s, arg).then(gated),
3220
3450
  submitPrompt: (s, arg) => impl.submitPrompt(s, arg).then(gated),
3221
3451
  newChat: (s) => impl.newChat(s).then(gated),
@@ -3235,6 +3465,9 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
3235
3465
  widgetRespond: (s, arg) => impl.widgetRespond(s, arg).then(gated),
3236
3466
  widgetPrimary: (s) => impl.widgetPrimary(s).then(gated),
3237
3467
  widgetSecondary: (s) => impl.widgetSecondary(s).then(gated),
3468
+ dictate: (s) => gated(impl.dictate(s)),
3469
+ finishDictation: (s) => gated(impl.finishDictation(s)),
3470
+ dictated: (s, arg) => { const r = impl.dictated(s, arg); return r ? gated(r) : r; },
3238
3471
  mentionOpen: (s) => impl.mentionOpen(s).then((r) => (r ? gated(r) : r)),
3239
3472
  mentionClose: (s) => gated(impl.mentionClose(s)),
3240
3473
  mentionBack: (s) => gated(impl.mentionBack(s)),
@@ -168,6 +168,8 @@ export function projectCollection(source: CollectionRow[], config: CollectionCon
168
168
  items: visible.map(row => ({ ...row, collectionId: collectionId(row), collectionTitle: collectionTitle(row, config),
169
169
  collectionMeta: [...new Set((config.metadata ?? ['total', 'statusLabel', 'price', 'priceLine', 'dateLine', 'meta']).filter(key => key !== config.subtitle).map(key => scalar(row[key])).filter(Boolean))].join(' · '),
170
170
  collectionSelectLabel: prefs.selectedIds.includes(collectionId(row)) ? 'Selected' : 'Select',
171
+ // The save toggle names the action it WILL take (the service localizes it).
172
+ collectionSaveLabel: prefs.savedIds.includes(collectionId(row)) ? 'Remove from saved' : 'Save',
171
173
  collectionSubtitle: scalar(row[config.subtitle ?? '']) || scalar(row.subtitle) || scalar(row.description) || scalar(row.meta),
172
174
  collectionImage: scalar(row[config.image ?? '']), collectionNoImage: !scalar(row[config.image ?? '']), collectionHasImage: !!scalar(row[config.image ?? '']),
173
175
  collectionSaved: prefs.savedIds.includes(collectionId(row)), collectionNotSaved: !prefs.savedIds.includes(collectionId(row)), collectionSelected: prefs.selectedIds.includes(collectionId(row)),
@@ -123,7 +123,12 @@ export function decorateCollection(name: string, def: { state: State; actions?:
123
123
  pendingFilter = '';
124
124
  }
125
125
  const projected = projectCollection(rows, config, prefs, !!backend && config.backendQuery === true, known);
126
- projected.items.forEach(row => { row.collectionSelectLabel = row.collectionSelected ? copyParam('collection.selected', 'Selected') : copyParam('collection.select', 'Select'); });
126
+ projected.items.forEach(row => {
127
+ row.collectionSelectLabel = row.collectionSelected ? copyParam('collection.selected', 'Selected') : copyParam('collection.select', 'Select');
128
+ // the save toggle names the action it WILL take for this row. The
129
+ // old static "Save / unsave" made the owner work out the row's state.
130
+ row.collectionSaveLabel = row.collectionSaved ? copyParam('collection.unsave', 'Remove from saved') : copyParam('collection.save', 'Save');
131
+ });
127
132
  if (previewId && !rows.some(row => collectionId(row) === previewId)) previewId = '';
128
133
  const filterChoices = [{ id: 'all', label: copyParam('collection.all', 'All') }, { id: 'saved', label: copyParam('collection.saved', 'Saved') },
129
134
  ...projected.filters.map(label => ({ id: `value:${label}`, label: copyParam(`collection.filter.${label}`, label) }))];
@@ -0,0 +1,45 @@
1
+ // Pure seams of the owner-lite Account worker — no @himalaya/* imports, so a
2
+ // plain node test can load them without the bundle build's module aliases.
3
+
4
+ export interface AccountState {
5
+ status: "loading" | "ready";
6
+ /** "Name · email", the email alone, or "" (the line collapses). */
7
+ ownerLine: string;
8
+ /** "Version 1.2.0 (34)", or "" when the host cannot say. */
9
+ versionLine: string;
10
+ /** Why the owner line is empty, when the identity read failed; "" otherwise. */
11
+ error: string;
12
+ supportUrl: string;
13
+ privacyUrl: string;
14
+ termsUrl: string;
15
+ /** hiddenPath flags — TRUE hides, so each is phrased negatively. */
16
+ noSupport: boolean;
17
+ noPrivacy: boolean;
18
+ noTerms: boolean;
19
+ }
20
+
21
+ /** Only absolute http(s) URLs are openable; anything else is treated as absent. */
22
+ export function safeUrl(v: unknown): string {
23
+ if (typeof v !== "string") return "";
24
+ const t = v.trim();
25
+ return /^https?:\/\/[^\s]+$/i.test(t) ? t : "";
26
+ }
27
+
28
+ /** "Name · email" when both are known, whichever one is when only one is, else "". */
29
+ export function ownerLineOf(name: string, email: string): string {
30
+ const n = name.trim(), e = email.trim();
31
+ if (n && e && n !== e) return `${n} · ${e}`;
32
+ return n || e;
33
+ }
34
+
35
+ export function shapeAccount(s: Partial<AccountState>): AccountState {
36
+ const supportUrl = safeUrl(s.supportUrl), privacyUrl = safeUrl(s.privacyUrl), termsUrl = safeUrl(s.termsUrl);
37
+ return {
38
+ status: s.status ?? "loading",
39
+ ownerLine: s.ownerLine ?? "",
40
+ versionLine: s.versionLine ?? "",
41
+ error: s.error ?? "",
42
+ supportUrl, privacyUrl, termsUrl,
43
+ noSupport: !supportUrl, noPrivacy: !privacyUrl, noTerms: !termsUrl,
44
+ };
45
+ }
@@ -18,6 +18,27 @@ export const DEMO_NOTE =
18
18
 
19
19
  export const DEMO_SAVE_NOTE = "Demo mode — changes aren’t saved in this preview.";
20
20
 
21
+ /** The state patch for a write refused because this is the demo preview. A demo
22
+ * refusal is a MODE, not an error: no red error line — the neutral info banner
23
+ * (already on screen before the owner typed anything) switches to the save
24
+ * note, so the tap still visibly answers. `saveNote` is the (copy-pack) text. */
25
+ export function demoRefusalPatch(saveNote: string = DEMO_SAVE_NOTE): { status: "idle"; error: ""; savedMessage: ""; demoNote: string } {
26
+ return { status: "idle", error: "", savedMessage: "", demoNote: saveNote || DEMO_SAVE_NOTE };
27
+ }
28
+
29
+ /** "€1,234.50" / "12,50" / " 10 " → "1234.50" / "12.50" / "10": digits and one
30
+ * decimal point — what an editable price field accepts and a price write needs.
31
+ * A separator followed by exactly 1–2 digits is the decimal; any other groups. */
32
+ export function editablePrice(raw: string): string {
33
+ const t = raw.replace(/[^\d.,]/g, "");
34
+ if (!t) return "";
35
+ const lastSep = Math.max(t.lastIndexOf("."), t.lastIndexOf(","));
36
+ if (lastSep >= 0 && /^\d{1,2}$/.test(t.slice(lastSep + 1))) {
37
+ return `${t.slice(0, lastSep).replace(/[.,]/g, "")}.${t.slice(lastSep + 1)}`;
38
+ }
39
+ return t.replace(/[.,]/g, "");
40
+ }
41
+
21
42
  export const DEMO_IDS = ["demo-1", "demo-2", "demo-3"] as const;
22
43
 
23
44
  export interface DemoProduct {
@@ -0,0 +1,68 @@
1
+ // Tier-5 bundle: __HIMI_APP_NAME__ — Account.
2
+ //
3
+ // The Account screen used to be a static SDUI descriptor whose subtitle was the
4
+ // literal "Wix owner (user)" — a role string, not a person. A descriptor cannot
5
+ // know who is signed in; a worker can. This one reads:
6
+ // • the signed-in Wix user (`getMyData`, the same users.wix.com read
7
+ // wix-owner-profile uses) → "Name · email" under the title;
8
+ // • the app's real version through @himalaya/app-info (never a baked literal,
9
+ // which goes stale the moment the binary is bumped);
10
+ // • the help / privacy / terms links, which arrive as onAppear params baked
11
+ // from provisioning.json `identity` — so a branded template ships ITS links,
12
+ // and `openLink` can only ever open one of those three URLs.
13
+ //
14
+ // Every read is best-effort: an identity or version the host cannot answer
15
+ // collapses its line rather than rendering an error on a settings screen.
16
+
17
+ import { defineService } from "@himalaya/state";
18
+ import { getMyData } from "@himalaya/wix-users";
19
+ import { displayVersion } from "@himalaya/app-info";
20
+ import { open } from "@himalaya/open-url";
21
+ import { stashCopy, copyTemplate } from "../_shared/himi-copy.js";
22
+ import { ownerLineOf, shapeAccount, type AccountState } from "../_shared/owner-account.js";
23
+
24
+ type LinkKind = "support" | "privacy" | "terms";
25
+
26
+ const obj = (v: unknown): Record<string, unknown> => (v && typeof v === "object" ? v as Record<string, unknown> : {});
27
+
28
+ const svc = defineService<AccountState, {
29
+ load: (s: AccountState, arg?: unknown) => Promise<AccountState>;
30
+ openLink: (s: AccountState, arg?: unknown) => Promise<AccountState>;
31
+ }>("himiOwnerLiteAccount", {
32
+ state: shapeAccount({}),
33
+ actions: {
34
+ load: async (s, arg) => {
35
+ stashCopy(arg);
36
+ const p = obj(arg);
37
+ const links = { supportUrl: String(p.supportUrl ?? s.supportUrl), privacyUrl: String(p.privacyUrl ?? s.privacyUrl), termsUrl: String(p.termsUrl ?? s.termsUrl) };
38
+ let ownerLine = "";
39
+ // Why the identity line is missing, when it is. Kept in state (the failure is
40
+ // modelled, not swallowed) but not painted: a settings screen that cannot name
41
+ // its owner still works, so the line collapses rather than showing an error.
42
+ let error: string = "";
43
+ try {
44
+ const d = await getMyData();
45
+ // The PROFILE name only — never the userName handle, an account nickname
46
+ // ("apptestercoowner"): exactly the kind of placeholder this line replaces.
47
+ const name = [d.profile?.firstName, d.profile?.lastName].filter(Boolean).join(" ");
48
+ ownerLine = ownerLineOf(name, String(d.email || d.contactEmail || ""));
49
+ } catch (e) {
50
+ error = String((e as Error)?.message ?? e);
51
+ }
52
+ let versionLine = "";
53
+ try {
54
+ const v = await displayVersion();
55
+ versionLine = v ? copyTemplate("ownerAccount.version", "Version {version}", { version: v }) : "";
56
+ } catch { /* host without app_info — the row collapses */ }
57
+ return shapeAccount({ ...s, ...links, status: "ready", ownerLine, versionLine, error });
58
+ },
59
+ openLink: async (s, arg) => {
60
+ const kind = String(obj(arg).kind ?? arg ?? "") as LinkKind;
61
+ const url = kind === "support" ? s.supportUrl : kind === "privacy" ? s.privacyUrl : kind === "terms" ? s.termsUrl : "";
62
+ if (url) await open(url);
63
+ return s;
64
+ },
65
+ },
66
+ });
67
+
68
+ export { svc };
@@ -27,6 +27,7 @@
27
27
  import { defineService } from "@himalaya/state";
28
28
  import { open } from "@himalaya/open-url";
29
29
  import { writeText } from "@himalaya/clipboard";
30
+ import { availability, recognizeOnce, stop as stopSpeech } from "@himalaya/speech";
30
31
  import { capturePhoto } from "@himalaya/camera";
31
32
  import { put } from "@himalaya/presigned-upload";
32
33
  import { alert } from "@himalaya/ui";
@@ -35,6 +36,7 @@ import {
35
36
  createAriaChat,
36
37
  type AriaActionArg,
37
38
  type AriaChatState,
39
+ type AriaDictationResult,
38
40
  type AriaPromptChip,
39
41
  } from "../_shared/aria-chat.js";
40
42
  import { WIDGET_SUBTREE_SLOT } from "../_shared/aria-generic-widget.js";
@@ -201,6 +203,8 @@ const core = createAriaChat({
201
203
  // English. Says no "sign in": the owner already is, and the missing piece is
202
204
  // the app's binding, so there is no action for them to take.
203
205
  unavailable: () => copyParam("ownerAria.unavailable", "Aria can't reach your Wix account from here."),
206
+ dictationEmpty: () => copyParam("ownerAria.dictationEmpty", "Didn't catch that. Check microphone access and try again."),
207
+ dictationFailed: () => copyParam("ownerAria.dictationFailed", "Dictation isn't available right now."),
204
208
  // The mention picker. Through copyParam for the same reason as the rest.
205
209
  // The CATEGORY labels are not here, deliberately: they are what the
206
210
  // reference's `data.type` carries to the model, so they stay English.
@@ -243,6 +247,19 @@ const core = createAriaChat({
243
247
  return screenId ? { screenId } : null;
244
248
  },
245
249
  poll: { action: `${SERVICE}.tick`, fastMs: FAST_MS, idleMs: IDLE_MS },
250
+ // Composer dictation, on the platform recognizer: SFSpeechRecognizer,
251
+ // Android SpeechRecognizer, Web Speech. No audio crosses the bridge — only the
252
+ // final transcript. An empty language means the device's own locale, which is
253
+ // the owner's, rather than a hardcoded en-US. The screen must declare "speech".
254
+ //
255
+ // `svc` is referenced lazily: it is defined below, and `deliver` only ever
256
+ // runs after an utterance, long after this module has finished evaluating.
257
+ dictation: {
258
+ listen: () => recognizeOnce(),
259
+ stop: () => stopSpeech(),
260
+ deliver: (r) => { void svc.dictated(r); },
261
+ available: async () => (await availability()).recognition !== "unavailable",
262
+ },
246
263
  // Photo attachments. The two device capabilities live HERE rather than
247
264
  // in the shared core so that only a screen which declares them imports them:
248
265
  // `camera` for the picker (it returns the image already re-encoded as JPEG and
@@ -293,6 +310,10 @@ const svc = defineService<AriaChatState, {
293
310
  widgetPick: (s: AriaChatState, arg?: AriaActionArg) => AriaChatState;
294
311
  widgetQuery: (s: AriaChatState, arg?: AriaActionArg) => AriaChatState;
295
312
  widgetAnswer: (s: AriaChatState, arg?: AriaActionArg) => AriaChatState;
313
+ dictate: (s: AriaChatState) => AriaChatState;
314
+ finishDictation: (s: AriaChatState) => AriaChatState;
315
+ // `undefined` for a result whose session is no longer the live one — see the core.
316
+ dictated: (s: AriaChatState, arg?: AriaDictationResult) => AriaChatState | undefined;
296
317
  mentionOpen: (s: AriaChatState) => Promise<AriaChatState | undefined>;
297
318
  mentionClose: (s: AriaChatState) => AriaChatState;
298
319
  mentionBack: (s: AriaChatState) => AriaChatState;
@@ -340,6 +361,13 @@ const svc = defineService<AriaChatState, {
340
361
  widgetPick: core.widgetPick,
341
362
  widgetQuery: core.widgetQuery,
342
363
  widgetAnswer: core.widgetAnswer,
364
+ // Composer dictation: the mic, the listening row's Done, and the result
365
+ // re-entering from `dictation.deliver` above. Exported for the same reason
366
+ // as the widget actions — an action the entry does not name is a control
367
+ // that renders and does nothing.
368
+ dictate: core.dictate,
369
+ finishDictation: core.finishDictation,
370
+ dictated: core.dictated,
343
371
  // Mentions: the "@ Mention" control, the two-level picker and the
344
372
  // chips above the composer. Every one is named by the descriptor, so every
345
373
  // one has to be exported here — see the note on the picker family above.
@@ -111,7 +111,8 @@ const STARTER: Node = {
111
111
  props: {
112
112
  title: "Ask about your business",
113
113
  subtitle: "Try “which products are out of stock”, “unpaid orders”, or “what's coming up”.",
114
- icon: "search",
114
+ // Not `search`: Ask is not search, and a magnifier says it is.
115
+ icon: "ai",
115
116
  },
116
117
  },
117
118
  },
@@ -129,7 +130,7 @@ const enc = (n: Node): string => encodeSubtree(n as unknown as Parameters<typeof
129
130
  const initialState: AskState = {
130
131
  status: "idle",
131
132
  intent: "",
132
- headline: "Ask",
133
+ headline: "",
133
134
  note: "",
134
135
  customerNotice: "",
135
136
  repairs: "",
@@ -267,7 +268,7 @@ function failed(state: AskState, headline: string, note: string): AskState {
267
268
  body: {
268
269
  package: "himalaya",
269
270
  type: "EmptyState",
270
- props: { title: headline, subtitle: note, icon: "search" },
271
+ props: { title: headline, subtitle: note, icon: "ai" },
271
272
  },
272
273
  }],
273
274
  },