@wix/himalaya-cli 0.1310.0 → 0.1312.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 (189) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/wix-payments-contract.d.ts +20 -1
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +13 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +8 -0
  4. package/dist/cli.mjs +1321 -692
  5. package/dist/preview-assets.generated.bin +0 -0
  6. package/guides/himi-json-output.md +29 -1
  7. package/guides/iap-products.md +51 -2
  8. package/guides/sdk-reference.json +19 -1
  9. package/guides/web-page-native-sdk.md +18 -8
  10. package/guides/webview-page-sdk.md +9 -3
  11. package/package.json +2 -2
  12. package/templates/beauty-salon/dev/index.ts +25 -25
  13. package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +10 -0
  14. package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +3 -1
  15. package/templates/beauty-salon-owner/dev/index.ts +844 -510
  16. package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +10 -0
  17. package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +3 -1
  18. package/templates/beauty-salon-owner/tier5-src/_shared/detail-facts.ts +26 -0
  19. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  20. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  21. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  22. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  23. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  24. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product/index.ts +10 -0
  25. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  26. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  27. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service/index.ts +10 -0
  28. package/templates/branded-lite/dev/index.ts +120 -120
  29. package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +10 -0
  30. package/templates/branded-lite/tier5-src/_shared/collection-service.ts +3 -1
  31. package/templates/community-club/dev/index.ts +20 -20
  32. package/templates/community-club/tier5-src/_shared/collection-contract.ts +10 -0
  33. package/templates/community-club/tier5-src/_shared/collection-service.ts +3 -1
  34. package/templates/community-club-owner/dev/index.ts +957 -595
  35. package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +10 -0
  36. package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +3 -1
  37. package/templates/community-club-owner/tier5-src/_shared/detail-facts.ts +26 -0
  38. package/templates/community-club-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  39. package/templates/community-club-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  40. package/templates/community-club-owner/tier5-src/himi-owner-lite-event/index.ts +10 -0
  41. package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  42. package/templates/community-club-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  43. package/templates/community-club-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  44. package/templates/community-club-owner/tier5-src/himi-owner-lite-product/index.ts +10 -0
  45. package/templates/community-club-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  46. package/templates/community-club-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  47. package/templates/events-meetups/dev/index.ts +15 -15
  48. package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +10 -0
  49. package/templates/events-meetups/tier5-src/_shared/collection-service.ts +3 -1
  50. package/templates/events-meetups-owner/dev/index.ts +726 -433
  51. package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +10 -0
  52. package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +3 -1
  53. package/templates/events-meetups-owner/tier5-src/_shared/detail-facts.ts +26 -0
  54. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  55. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  56. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event/index.ts +10 -0
  57. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  58. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  59. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  60. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  61. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  62. package/templates/gym-fitness/dev/index.ts +30 -30
  63. package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +10 -0
  64. package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +3 -1
  65. package/templates/gym-fitness-owner/dev/index.ts +1041 -644
  66. package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +10 -0
  67. package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +3 -1
  68. package/templates/gym-fitness-owner/tier5-src/_shared/detail-facts.ts +26 -0
  69. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  70. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  71. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event/index.ts +10 -0
  72. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  73. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  74. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  75. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product/index.ts +10 -0
  76. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  77. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  78. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service/index.ts +10 -0
  79. package/templates/nonprofit-cause/dev/index.ts +20 -20
  80. package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +10 -0
  81. package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +3 -1
  82. package/templates/nonprofit-cause-owner/dev/index.ts +810 -482
  83. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +10 -0
  84. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +3 -1
  85. package/templates/nonprofit-cause-owner/tier5-src/_shared/detail-facts.ts +26 -0
  86. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  87. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  88. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event/index.ts +10 -0
  89. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  90. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  91. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  92. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  93. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  94. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service/index.ts +10 -0
  95. package/templates/owner-lite/dev/index.ts +1066 -648
  96. package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +10 -0
  97. package/templates/owner-lite/tier5-src/_shared/collection-service.ts +3 -1
  98. package/templates/owner-lite/tier5-src/_shared/detail-facts.ts +26 -0
  99. package/templates/owner-lite/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  100. package/templates/owner-lite/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  101. package/templates/owner-lite/tier5-src/himi-owner-lite-event/index.ts +10 -0
  102. package/templates/owner-lite/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  103. package/templates/owner-lite/tier5-src/himi-owner-lite-post/index.ts +10 -0
  104. package/templates/owner-lite/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  105. package/templates/owner-lite/tier5-src/himi-owner-lite-product/index.ts +10 -0
  106. package/templates/owner-lite/tier5-src/himi-owner-lite-search/index.ts +6 -0
  107. package/templates/owner-lite/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  108. package/templates/owner-lite/tier5-src/himi-owner-lite-service/index.ts +10 -0
  109. package/templates/podcast-creator/dev/index.ts +20 -20
  110. package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +10 -0
  111. package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +3 -1
  112. package/templates/podcast-creator-owner/dev/index.ts +957 -595
  113. package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +10 -0
  114. package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +3 -1
  115. package/templates/podcast-creator-owner/tier5-src/_shared/detail-facts.ts +26 -0
  116. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  117. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  118. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event/index.ts +10 -0
  119. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  120. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  121. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  122. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product/index.ts +10 -0
  123. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  124. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  125. package/templates/portfolio-creative/dev/index.ts +20 -20
  126. package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +10 -0
  127. package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +3 -1
  128. package/templates/portfolio-creative-owner/dev/index.ts +844 -510
  129. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +10 -0
  130. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +3 -1
  131. package/templates/portfolio-creative-owner/tier5-src/_shared/detail-facts.ts +26 -0
  132. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  133. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  134. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  135. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  136. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  137. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product/index.ts +10 -0
  138. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  139. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  140. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service/index.ts +10 -0
  141. package/templates/restaurant/dev/index.ts +15 -15
  142. package/templates/restaurant/tier5-src/_shared/collection-contract.ts +10 -0
  143. package/templates/restaurant/tier5-src/_shared/collection-service.ts +3 -1
  144. package/templates/restaurant-owner/dev/index.ts +1041 -644
  145. package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +10 -0
  146. package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +3 -1
  147. package/templates/restaurant-owner/tier5-src/_shared/detail-facts.ts +26 -0
  148. package/templates/restaurant-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  149. package/templates/restaurant-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  150. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event/index.ts +10 -0
  151. package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  152. package/templates/restaurant-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  153. package/templates/restaurant-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  154. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product/index.ts +10 -0
  155. package/templates/restaurant-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  156. package/templates/restaurant-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  157. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service/index.ts +10 -0
  158. package/templates/retail-store/dev/index.ts +10 -10
  159. package/templates/retail-store/tier5-src/_shared/collection-contract.ts +10 -0
  160. package/templates/retail-store/tier5-src/_shared/collection-service.ts +3 -1
  161. package/templates/retail-store-owner/dev/index.ts +760 -461
  162. package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +10 -0
  163. package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +3 -1
  164. package/templates/retail-store-owner/tier5-src/_shared/detail-facts.ts +26 -0
  165. package/templates/retail-store-owner/tier5-src/_shared/offset-page.ts +10 -0
  166. package/templates/retail-store-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  167. package/templates/retail-store-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  168. package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  169. package/templates/retail-store-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  170. package/templates/retail-store-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  171. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product/index.ts +10 -0
  172. package/templates/retail-store-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  173. package/templates/retail-store-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  174. package/templates/yoga-studio/dev/index.ts +30 -30
  175. package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +10 -0
  176. package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +3 -1
  177. package/templates/yoga-studio-owner/dev/index.ts +810 -482
  178. package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +10 -0
  179. package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +3 -1
  180. package/templates/yoga-studio-owner/tier5-src/_shared/detail-facts.ts +26 -0
  181. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-ask/blocks.ts +7 -3
  182. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-ask/index.ts +2 -2
  183. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event/index.ts +10 -0
  184. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/logic.ts +3 -0
  185. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post/index.ts +10 -0
  186. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-posts/index.ts +21 -2
  187. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-search/index.ts +6 -0
  188. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-search/vertical-sources.ts +178 -0
  189. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service/index.ts +10 -0
Binary file
@@ -6,7 +6,7 @@ repurposed when human wording changes. New codes may be added; consumers must
6
6
  handle an unknown code as a failure, without parsing prose.
7
7
 
8
8
  **Rollout:** this contract is enforced for **`himi push`**, including `--only`,
9
- first. Other subcommands still have legacy failures without codes and are a
9
+ first, and for **`himi store iap connect`**. Other subcommands still have legacy failures without codes and are a
10
10
  follow-up migration. This is an additive change: existing `error`, `errors`,
11
11
  `message`, `hint`, status, body, and gate-specific fields stay as they were.
12
12
  Some legacy envelopes have no singular `error` or `hint`; consumers must not
@@ -78,6 +78,34 @@ The `ux_evidence_*` codes map to the existing typed `uxEvidence.state`; a succes
78
78
  `recorded` state has no refusal code. Successful pushes and warnings do not gain
79
79
  failure codes. A forced push still follows the existing override rules.
80
80
 
81
+ ## `himi store iap connect` codes
82
+
83
+ `connect` prints one envelope with the whole report. When the setup is not complete it is
84
+ `ok: false` with the first step's code; each step also carries its own `code`.
85
+
86
+ | Code | Meaning |
87
+ |---|---|
88
+ | `bundle_registered_elsewhere` | The bundle id is registered to another purchases app (another site's). Nothing was changed; only that registration's owner can remove it. |
89
+ | `apple_key_missing` | No In-App Purchase key was given and the app has none stored. |
90
+ | `invalid_usage` | The flags are incomplete or malformed (for example `--iap-key` without `--iap-key-id`). Nothing was read or written. |
91
+ | `iap_key_unreadable` | `--iap-key` is missing, unreadable, inside the repository, or not a PEM private key. Nothing was written. |
92
+ | `provider_route_unavailable` | The Serve target has no IAP routes, so the appleIAP provider was not connected. |
93
+ | `manifest_invalid` | `--manifest` is not a valid IAP manifest; `problems` lists why. Nothing was read or written. |
94
+ | `credentials_unavailable` | The `--profile` App Store Connect credentials could not be resolved. Nothing was read or written. |
95
+ | `provider_not_connected` | wix-iap answered, but reported the provider account as not connected. |
96
+ | `mapping_conflict` | The Wix item is already mapped to a different Apple product; left as it is. |
97
+ | `mapping_inactive` | The mapping exists but is inactive; left as it is. |
98
+ | `mapping_type_mismatch` | The mapping exists as a product type the purchase flow cannot sell (programs sell only as `NON_CONSUMABLE`); left as it is. |
99
+ | `sku_in_use` | The Apple product is already mapped to another Wix item; left as it is. |
100
+ | `mapping_unsupported` | The purchase flow cannot map this item yet (a vertical other than Online Programs, a price tier, or a program declared as a consumable). |
101
+ | `no_purchases_app` | A step did not run because the site has no purchases app for the bundle. |
102
+ | `request_failed` | A purchases-service call failed for another reason; the original message is preserved. |
103
+ | `setup_incomplete` | Steps are only planned (a dry run) or otherwise not yet in place. |
104
+ | `confirmation_required` | Writes were planned, but there was no terminal to ask and no `--yes`. Nothing was written. |
105
+ | `config_conflict` | The backend setup is complete, but the app's config names a different purchases app, site or bundle. |
106
+ | `config_not_applied` | `--content` was given, but the binding is not in its `config.ts` (no config file, a dynamic or symlinked config, an edit `himi validate` rolled back, or no validator); the `config` report or `note` says what to add by hand. |
107
+ | `connect_failed` | An otherwise unclassified error stopped connect (for example an unreadable manifest, or a failed `--out` write). The original message is preserved. |
108
+
81
109
  ## Follow-ups
82
110
 
83
111
  - Migrate failure envelopes in subcommands other than `push`, keeping this contract.
@@ -149,6 +149,8 @@ himi store iap draft --site <msid> --profile fit-club-owner --apple-app 6450000
149
149
  himi store iap plan --manifest ./iap-products.json --profile fit-club-owner --out ./iap-plan.json
150
150
  himi store iap apply --plan ./iap-plan.json --profile fit-club-owner --out ./iap-result.json
151
151
  himi store iap status --manifest ./iap-products.json --profile fit-club-owner --json
152
+ himi store iap connect --manifest ./iap-products.json --profile fit-club-owner \
153
+ --iap-key ~/keys/SubscriptionKey_ABCD123456.p8 --iap-key-id ABCD123456 --content . --dry-run
152
154
  ```
153
155
 
154
156
  **`plan` is read-only.** It:
@@ -222,6 +224,53 @@ schedule, even without the local journal. Nothing is ever deleted or rolled back
222
224
  **`status` is read-only.** It reports what Apple has for each product. Add `--out <file>`
223
225
  to save it in the same shape as the result file.
224
226
 
227
+ ## Connecting the Wix site: `connect`
228
+
229
+ `connect` makes the manifest's Wix site ready to sell those products, from the same manifest,
230
+ as the site owner (your Wix login, like `draft`). To run it against a Serve deploy preview, pass
231
+ `--preview-tag dp-<tag>` (or set `HIMI_SERVE_PREVIEW_TAG`). Only the Serve calls (the appleIAP
232
+ provider connection) are routed to the preview; the purchases service and App Store Connect are
233
+ called as usual. In order:
234
+
235
+ 1. **The purchases app.** It reads the site's apps and reuses the one for the manifest's bundle
236
+ id. Only when the site has none does it register one. A bundle id belongs to one purchases
237
+ app platform-wide: if another app holds it (another site's, or another account's), that is a
238
+ `bundle_registered_elsewhere` conflict. Only that registration's owner can remove it; `connect`
239
+ never does.
240
+ 2. **The In-App Purchase key.** It stores `--iap-key` / `--iap-key-id` once, server-side. This is
241
+ an App Store Connect **In-App Purchase** key (Users and Access → Integrations → In-App
242
+ Purchase), not the App Store Connect API key `--profile` names; the service rejects that kind.
243
+ The issuer comes from the profile. Skipped when a key is already stored.
244
+ 3. **The appleIAP payment provider** on the site, through Serve's IAP route to wix-iap
245
+ (`ConnectProviderAccount`). Reported as `provider_route_unavailable` only when the Serve target
246
+ has no IAP routes; a refusal from the service behind it is `request_failed`. There is no read
247
+ of the provider's state, so a rerun connects it again (the call is safe to repeat).
248
+ 4. **One mapping per Wix item → Apple product.** Online Programs map by program id (the id the
249
+ purchase flow looks up), and only as non-consumables. Other verticals, price tiers and
250
+ consumable programs are skipped with `mapping_unsupported`. An item already mapped to another
251
+ product (`mapping_conflict`), a mapping of another product type (`mapping_type_mismatch`), an
252
+ inactive mapping (`mapping_inactive`), or an Apple product mapped to another item
253
+ (`sku_in_use`) is reported and left exactly as it is.
254
+ 5. **The app's config.** With `--content <dir>`, it records `wixIap: { purchasesAppId }` (and the
255
+ `iap` capability) in the package's `config.ts`, keeping everything else byte for byte. The edit
256
+ is backed up under `.himi/store-iap-backup/`, checked with `himi validate`, and rolled back if it
257
+ introduces an error. A different purchases app already there, a member login on another
258
+ site, or another app's bundle is a conflict and the file is not touched. The bundle that must match
259
+ the manifest is the package's `store/store.config.yaml` `ios.bundleId` when it sets one;
260
+ otherwise `bundleId` itself or, for a dev bundle, the same without `.dev`. Whenever `--content` was given and the binding
261
+ did not land, the run is `ok: false` (`config_not_applied`) with what to add by hand.
262
+
263
+ **Review first.** `--dry-run` reads everything and writes nothing. Without `--dry-run` it prints
264
+ the plan and asks; a shell with no terminal (or `--json`) needs `--yes`. A rerun reuses every
265
+ record and writes nothing new. Adding products later means adding them to the manifest and
266
+ rerunning: only their mappings are added, and the purchases-app id (the one thing a build bakes)
267
+ does not change, so no new build is needed.
268
+
269
+ **What it does not do.** It never removes, replaces or reactivates a record. The app reads the
270
+ mappings it writes through Serve's member and visitor route (`POST /himalaya-serve/v1/iap/mappings`;
271
+ see [the backend integration](iap-backend-integration.md)); a build still needs the binding baked
272
+ for its screens to use them.
273
+
225
274
  ## Local state
226
275
 
227
276
  Local state lives in `~/.himalaya/store-iap/<appleAppId>/`:
@@ -261,7 +310,7 @@ future importer must authenticate ownership and re-read Apple before activating
261
310
 
262
311
  | Code | Meaning |
263
312
  |---|---|
264
- | `0` | everything requested is in place (`apply`, `status`), or the plan has no conflicts (`plan`) |
313
+ | `0` | everything requested is in place (`apply`, `status`, `connect`), or the plan has no conflicts (`plan`) |
265
314
  | `1` | partial, conflict, drift, or a declined prompt |
266
315
  | `2` | usage, manifest, credential, target or access error; nothing was written |
267
316
 
@@ -284,4 +333,4 @@ Not in this version:
284
333
  - review screenshot upload and submission
285
334
  - changing a live price
286
335
  - Google Play
287
- - backend import and checkout activation
336
+ - the build-time binding bake and the screens' checkout (the companion PR)
@@ -1932,7 +1932,7 @@
1932
1932
  "name": "AppBindingSource",
1933
1933
  "kind": "interface",
1934
1934
  "jsdoc": "",
1935
- "decl": "export interface AppBindingSource {\n /** Null when the app was never registered. */\n current(): Promise<PurchasesAppBinding | null>;\n}"
1935
+ "decl": "export interface AppBindingSource {\n /** Null when the app was never registered; rejects when the host could not say. */\n current(): Promise<PurchasesAppBinding | null>;\n}"
1936
1936
  },
1937
1937
  {
1938
1938
  "name": "IapAppHost",
@@ -1946,6 +1946,24 @@
1946
1946
  "jsdoc": "/** Where the Payments service is reachable through the Wix gateway. */",
1947
1947
  "decl": "export interface WixIapRoute {\n /** The gateway path that maps to the service's `/v1`, without a trailing slash. No public route\n * was in the generated gateway metadata on 2026-10-08, so there is deliberately no default. */\n prefix: string;\n}"
1948
1948
  },
1949
+ {
1950
+ "name": "HIMALAYA_SERVE_IAP_ROUTE",
1951
+ "kind": "const",
1952
+ "jsdoc": "/**\n * Serve's Apple-IAP routes on the public gateway (`www.wixapis.com/himalaya-serve/v1/iap`), where the\n * native bridge sends the member's or visitor's own token. One prefix for the wix-iap payment calls, the\n * cashier charge and the mapping read; Serve forwards each to the internal service that owns it.\n */",
1953
+ "decl": "export const HIMALAYA_SERVE_IAP_ROUTE: WixIapRoute = { prefix: \"/himalaya-serve/v1/iap\" };"
1954
+ },
1955
+ {
1956
+ "name": "AppleSaleRule",
1957
+ "kind": "interface",
1958
+ "jsdoc": "/** How the App Store sells one vertical's Wix item: the purchases catalog's `appProductId` for it, and\n * the only product type the purchase flow sells it as. */",
1959
+ "decl": "export interface AppleSaleRule {\n appProductId(entityId: string): string;\n productType: \"NON_CONSUMABLE\";\n}"
1960
+ },
1961
+ {
1962
+ "name": "APPLE_SALE_RULES",
1963
+ "kind": "const",
1964
+ "jsdoc": "/**\n * The one definition `himi store iap connect` writes mappings by and the mapping source reads them by,\n * keyed by catalog kind. wix-iap StartPayment maps the cashier order's single item id, and Online\n * Programs builds that order with the program id as the item id; programs are one-time unlocks.\n * Other verticals' order item ids are not established yet, so their items keep their own commerce.\n */",
1965
+ "decl": "export const APPLE_SALE_RULES: Readonly<Record<string, AppleSaleRule>> = {\n onlineProgram: { appProductId: (id) => id, productType: \"NON_CONSUMABLE\" },\n};"
1966
+ },
1949
1967
  {
1950
1968
  "name": "HostCallExpectation",
1951
1969
  "kind": "interface",
@@ -15,13 +15,13 @@ your page → window.HimalayaWorker → the app's companion worker → native iO
15
15
  ## Before you start: what the app owner must enable
16
16
 
17
17
  Your page can only use capabilities the **app owner** turned on for your site. In a Brandy app
18
- that is the `webSdk` block of the app's provisioning file:
18
+ (an app that wraps a website) that is the `webSdk` block of the app's provisioning — its
19
+ `provisioning.json`:
19
20
 
20
21
  ```json
21
22
  {
22
23
  "webSdk": {
23
- "bundle": "my-site-capabilities",
24
- "capabilities": ["health", "haptic"],
24
+ "capabilities": ["health", "haptic", "share"],
25
25
  "permissionCopy": {
26
26
  "health": "Show your daily steps and save the workouts you finish."
27
27
  }
@@ -33,8 +33,13 @@ that is the `webSdk` block of the app's provisioning file:
33
33
  [Web page SDK reference](https://apps.wix.com/_api/himi-serve/docs/web-page-sdk-reference.md) for every id and method).
34
34
  - `permissionCopy` — the sentence the phone shows when it asks the user for permission. Write it
35
35
  for the end user, about what *they* get.
36
- - `bundle` — the app's companion worker, which allowlists exactly which methods your page may
37
- call. A health-only site can omit it; any other set needs one.
36
+ - No worker code is needed. Brandy generates the companion worker that answers your page from
37
+ `capabilities`, allowing every method the capability's page lists — except
38
+ `presigned_upload.putFile`, which reads a device file and is allowed only when `files` is enabled
39
+ too. (An owner who wants a
40
+ narrower allowlist can still name their own worker in `bundle`.)
41
+ - Enabling a capability changes the app's native permissions, so the owner ships a **new app
42
+ build** with it before your page can call it. Until then the call times out.
38
43
 
39
44
  When you hand work to the app owner, give them the list as `capability.method` pairs, for example
40
45
  `health.availability`, `health.requestPermission`, `health.query`, `haptic.play`. A method that is
@@ -209,7 +214,10 @@ export function StepsCard() {
209
214
  if (state === "off") return null; // no health store, or Health not enabled for this site — hide it
210
215
  if (state === "denied") return <p>Health access is off. You can turn it on in your phone's Settings.</p>;
211
216
  if (state === "failed") return <button onClick={connect}>Couldn't load your steps — try again</button>;
212
- return steps === null ? <button onClick={connect}>Show today's steps</button> : <p>{steps} steps today</p>;
217
+ if (steps === null) return <button onClick={connect}>Show today's steps</button>;
218
+ // iOS never says a Health read was denied: a denied read returns no samples, like a day with no data.
219
+ if (steps === 0) return <p>No steps yet today. If you turned off Health access, enable it in the Health app.</p>;
220
+ return <p>{steps} steps today</p>;
213
221
  }
214
222
  ```
215
223
 
@@ -221,7 +229,9 @@ The same page must keep working in an ordinary browser (and in Base44 / Lovable
221
229
  - **Ask for permission only from a user action** (a button tap), after explaining the benefit on
222
230
  screen. Never prompt on page load.
223
231
  - **Handle "no" and "not available".** Every permission can be denied; Health may not exist on the
224
- device (`availability()` tells you). Show a calm fallback, not an error.
232
+ device (`availability()` tells you). Show a calm fallback, not an error. On iOS a denied Health
233
+ **read** is not reported as `not_authorized`: the read succeeds with no samples, exactly like a
234
+ day with no data. Word an empty result so it also covers "access is off".
225
235
  - **Hide features the app doesn't enable.** Probe once with `isEnabled(...)` on a read-only method
226
236
  and hide the UI when it is `false`. `true` only means the method is enabled — still read its
227
237
  answer (`availability()` can say `{ available: false }`).
@@ -333,7 +343,7 @@ if (photo) {
333
343
 
334
344
  For larger files, or to keep a copy on your server, upload straight from the device with
335
345
  `presigned_upload.putFile(url, handle)` and use the uploaded URL. Going the other way, a page
336
- cannot send large content in: see the size rules below.
346
+ cannot send large content in: see *Keep what you send small* under *Rules that keep a page working*.
337
347
 
338
348
  ## Reference
339
349
 
@@ -76,7 +76,8 @@ binary. One app-specific worker imports exactly the app’s selected subset.
76
76
 
77
77
  ## Brandy
78
78
 
79
- Health has a built-in proof worker, so it needs no custom bundle name:
79
+ Name the capabilities; Brandy supplies the companion worker. Health has a hand-written proof
80
+ worker:
80
81
 
81
82
  ```json
82
83
  {
@@ -89,12 +90,13 @@ Health has a built-in proof worker, so it needs no custom bundle name:
89
90
  }
90
91
  ```
91
92
 
92
- For another subset, add the companion bundle to the app and name it:
93
+ Any other set gets a worker generated from the set — it imports only those capabilities' SDKs
94
+ and allowlists every method a page can call on them (the methods each `web-sdk/<id>` page lists;
95
+ `presigned_upload.putFile` only when `files` is in the set too, since it reads the files substrate):
93
96
 
94
97
  ```json
95
98
  {
96
99
  "webSdk": {
97
- "bundle": "customer-page-capabilities",
98
100
  "capabilities": ["camera", "share"],
99
101
  "permissionCopy": {
100
102
  "camera": "Take a photo to attach it to your request."
@@ -103,6 +105,10 @@ For another subset, add the companion bundle to the app and name it:
103
105
  }
104
106
  ```
105
107
 
108
+ Preparing the customer build (`npm run prepare:customer`) writes and builds it; for local
109
+ previews, run `npm run web-sdk:companion` first. To expose fewer methods, write your own
110
+ companion and name it in `bundle` — a named bundle is used as is.
111
+
106
112
  Brandy accepts capabilities `himi dist` can add to an extensible shell. Capabilities requiring
107
113
  app-specific boot wiring remain refused until that shell supplies the wiring. Base44 is accepted
108
114
  only when the same Brandy contract contains a Base44 binding.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.1310.0",
3
+ "version": "0.1312.0",
4
4
  "type": "module",
5
5
  "description": "himi — the Himalaya authoring CLI. Build a Tier-5 content package on your own machine, push it to the production serve as a draft, preview it on web + Himi Player, and read worker logs back — the remote dev loop. Wraps @himalaya/serve-cli for transport.",
6
6
  "keywords": [
@@ -71,5 +71,5 @@
71
71
  "artifactId": "himalaya-cli"
72
72
  }
73
73
  },
74
- "falconPackageHash": "aed0d12edf97361177bd9f7e6ba033b9cce8f83615824cd195882dbf"
74
+ "falconPackageHash": "780fb2ce1d71508d01c3075d8f29dc587c79a5f5064c526a60dcb0b0"
75
75
  }
@@ -10085,7 +10085,7 @@ const SCREENS = runtimeSetting({
10085
10085
  "modifiers": {
10086
10086
  "width": 44,
10087
10087
  "height": 44,
10088
- "accessibilityLabel": "Item actions"
10088
+ "accessibilityLabel": "$item.collectionActionsLabel"
10089
10089
  }
10090
10090
  },
10091
10091
  "items": [
@@ -10365,7 +10365,7 @@ const SCREENS = runtimeSetting({
10365
10365
  "modifiers": {
10366
10366
  "width": 44,
10367
10367
  "height": 44,
10368
- "accessibilityLabel": "Item actions"
10368
+ "accessibilityLabel": "$item.collectionActionsLabel"
10369
10369
  }
10370
10370
  },
10371
10371
  "items": [
@@ -10655,7 +10655,7 @@ const SCREENS = runtimeSetting({
10655
10655
  "modifiers": {
10656
10656
  "width": 44,
10657
10657
  "height": 44,
10658
- "accessibilityLabel": "Item actions"
10658
+ "accessibilityLabel": "$item.collectionActionsLabel"
10659
10659
  }
10660
10660
  },
10661
10661
  "items": [
@@ -10935,7 +10935,7 @@ const SCREENS = runtimeSetting({
10935
10935
  "modifiers": {
10936
10936
  "width": 44,
10937
10937
  "height": 44,
10938
- "accessibilityLabel": "Item actions"
10938
+ "accessibilityLabel": "$item.collectionActionsLabel"
10939
10939
  }
10940
10940
  },
10941
10941
  "items": [
@@ -11540,7 +11540,7 @@ const SCREENS = runtimeSetting({
11540
11540
  "modifiers": {
11541
11541
  "width": 44,
11542
11542
  "height": 44,
11543
- "accessibilityLabel": "Item actions"
11543
+ "accessibilityLabel": "$item.collectionActionsLabel"
11544
11544
  }
11545
11545
  },
11546
11546
  "items": [
@@ -12652,7 +12652,7 @@ const SCREENS = runtimeSetting({
12652
12652
  "modifiers": {
12653
12653
  "width": 44,
12654
12654
  "height": 44,
12655
- "accessibilityLabel": "Item actions"
12655
+ "accessibilityLabel": "$item.collectionActionsLabel"
12656
12656
  }
12657
12657
  },
12658
12658
  "items": [
@@ -13008,7 +13008,7 @@ const SCREENS = runtimeSetting({
13008
13008
  "modifiers": {
13009
13009
  "width": 44,
13010
13010
  "height": 44,
13011
- "accessibilityLabel": "Item actions"
13011
+ "accessibilityLabel": "$item.collectionActionsLabel"
13012
13012
  }
13013
13013
  },
13014
13014
  "items": [
@@ -13327,7 +13327,7 @@ const SCREENS = runtimeSetting({
13327
13327
  "modifiers": {
13328
13328
  "width": 44,
13329
13329
  "height": 44,
13330
- "accessibilityLabel": "Item actions"
13330
+ "accessibilityLabel": "$item.collectionActionsLabel"
13331
13331
  }
13332
13332
  },
13333
13333
  "items": [
@@ -13683,7 +13683,7 @@ const SCREENS = runtimeSetting({
13683
13683
  "modifiers": {
13684
13684
  "width": 44,
13685
13685
  "height": 44,
13686
- "accessibilityLabel": "Item actions"
13686
+ "accessibilityLabel": "$item.collectionActionsLabel"
13687
13687
  }
13688
13688
  },
13689
13689
  "items": [
@@ -14343,7 +14343,7 @@ const SCREENS = runtimeSetting({
14343
14343
  "modifiers": {
14344
14344
  "width": 44,
14345
14345
  "height": 44,
14346
- "accessibilityLabel": "Item actions"
14346
+ "accessibilityLabel": "$item.collectionActionsLabel"
14347
14347
  }
14348
14348
  },
14349
14349
  "items": [
@@ -15560,7 +15560,7 @@ const SCREENS = runtimeSetting({
15560
15560
  "modifiers": {
15561
15561
  "width": 44,
15562
15562
  "height": 44,
15563
- "accessibilityLabel": "Item actions"
15563
+ "accessibilityLabel": "$item.collectionActionsLabel"
15564
15564
  }
15565
15565
  },
15566
15566
  "items": [
@@ -15901,7 +15901,7 @@ const SCREENS = runtimeSetting({
15901
15901
  "modifiers": {
15902
15902
  "width": 44,
15903
15903
  "height": 44,
15904
- "accessibilityLabel": "Item actions"
15904
+ "accessibilityLabel": "$item.collectionActionsLabel"
15905
15905
  }
15906
15906
  },
15907
15907
  "items": [
@@ -16172,7 +16172,7 @@ const SCREENS = runtimeSetting({
16172
16172
  "modifiers": {
16173
16173
  "width": 44,
16174
16174
  "height": 44,
16175
- "accessibilityLabel": "Item actions"
16175
+ "accessibilityLabel": "$item.collectionActionsLabel"
16176
16176
  }
16177
16177
  },
16178
16178
  "items": [
@@ -16513,7 +16513,7 @@ const SCREENS = runtimeSetting({
16513
16513
  "modifiers": {
16514
16514
  "width": 44,
16515
16515
  "height": 44,
16516
- "accessibilityLabel": "Item actions"
16516
+ "accessibilityLabel": "$item.collectionActionsLabel"
16517
16517
  }
16518
16518
  },
16519
16519
  "items": [
@@ -17139,7 +17139,7 @@ const SCREENS = runtimeSetting({
17139
17139
  "modifiers": {
17140
17140
  "width": 44,
17141
17141
  "height": 44,
17142
- "accessibilityLabel": "Item actions"
17142
+ "accessibilityLabel": "$item.collectionActionsLabel"
17143
17143
  }
17144
17144
  },
17145
17145
  "items": [
@@ -18353,7 +18353,7 @@ const SCREENS = runtimeSetting({
18353
18353
  "modifiers": {
18354
18354
  "width": 44,
18355
18355
  "height": 44,
18356
- "accessibilityLabel": "Item actions"
18356
+ "accessibilityLabel": "$item.collectionActionsLabel"
18357
18357
  }
18358
18358
  },
18359
18359
  "items": [
@@ -18707,7 +18707,7 @@ const SCREENS = runtimeSetting({
18707
18707
  "modifiers": {
18708
18708
  "width": 44,
18709
18709
  "height": 44,
18710
- "accessibilityLabel": "Item actions"
18710
+ "accessibilityLabel": "$item.collectionActionsLabel"
18711
18711
  }
18712
18712
  },
18713
18713
  "items": [
@@ -18981,7 +18981,7 @@ const SCREENS = runtimeSetting({
18981
18981
  "modifiers": {
18982
18982
  "width": 44,
18983
18983
  "height": 44,
18984
- "accessibilityLabel": "Item actions"
18984
+ "accessibilityLabel": "$item.collectionActionsLabel"
18985
18985
  }
18986
18986
  },
18987
18987
  "items": [
@@ -19335,7 +19335,7 @@ const SCREENS = runtimeSetting({
19335
19335
  "modifiers": {
19336
19336
  "width": 44,
19337
19337
  "height": 44,
19338
- "accessibilityLabel": "Item actions"
19338
+ "accessibilityLabel": "$item.collectionActionsLabel"
19339
19339
  }
19340
19340
  },
19341
19341
  "items": [
@@ -19934,7 +19934,7 @@ const SCREENS = runtimeSetting({
19934
19934
  "modifiers": {
19935
19935
  "width": 44,
19936
19936
  "height": 44,
19937
- "accessibilityLabel": "Item actions"
19937
+ "accessibilityLabel": "$item.collectionActionsLabel"
19938
19938
  }
19939
19939
  },
19940
19940
  "items": [
@@ -21849,7 +21849,7 @@ const SCREENS = runtimeSetting({
21849
21849
  "modifiers": {
21850
21850
  "width": 44,
21851
21851
  "height": 44,
21852
- "accessibilityLabel": "Item actions"
21852
+ "accessibilityLabel": "$item.collectionActionsLabel"
21853
21853
  }
21854
21854
  },
21855
21855
  "items": [
@@ -22196,7 +22196,7 @@ const SCREENS = runtimeSetting({
22196
22196
  "modifiers": {
22197
22197
  "width": 44,
22198
22198
  "height": 44,
22199
- "accessibilityLabel": "Item actions"
22199
+ "accessibilityLabel": "$item.collectionActionsLabel"
22200
22200
  }
22201
22201
  },
22202
22202
  "items": [
@@ -22486,7 +22486,7 @@ const SCREENS = runtimeSetting({
22486
22486
  "modifiers": {
22487
22487
  "width": 44,
22488
22488
  "height": 44,
22489
- "accessibilityLabel": "Item actions"
22489
+ "accessibilityLabel": "$item.collectionActionsLabel"
22490
22490
  }
22491
22491
  },
22492
22492
  "items": [
@@ -22833,7 +22833,7 @@ const SCREENS = runtimeSetting({
22833
22833
  "modifiers": {
22834
22834
  "width": 44,
22835
22835
  "height": 44,
22836
- "accessibilityLabel": "Item actions"
22836
+ "accessibilityLabel": "$item.collectionActionsLabel"
22837
22837
  }
22838
22838
  },
22839
22839
  "items": [
@@ -23474,7 +23474,7 @@ const SCREENS = runtimeSetting({
23474
23474
  "modifiers": {
23475
23475
  "width": 44,
23476
23476
  "height": 44,
23477
- "accessibilityLabel": "Item actions"
23477
+ "accessibilityLabel": "$item.collectionActionsLabel"
23478
23478
  }
23479
23479
  },
23480
23480
  "items": [
@@ -58,6 +58,13 @@ export interface CollectionConfig {
58
58
  /** At or below this many records, with no query/filter narrowing them and no further
59
59
  * page, the list/grid toggle, Refine, chips, search and count are noise. */
60
60
  export const COLLECTION_CHROME_MIN_ITEMS = 4;
61
+ /** The accessible name of a row's overflow menu: `template` with `{title}` filled, or the
62
+ * generic `fallback` for a record with no title. */
63
+ export function collectionActionsLabel(title: string, template: string, fallback: string): string {
64
+ const t = title.trim();
65
+ return t ? template.replace('{title}', () => t) : fallback;
66
+ }
67
+
61
68
  export const COLLECTIONS: Record<string, CollectionConfig> = {
62
69
  himiStoreShop: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', view: 'grid', backendQuery: true, countNoun: ['product', 'products'], favoriteToggle: true },
63
70
  himiBookingServices: { rows: 'results', title: 'name', subtitle: 'subtitle', image: 'image', filter: 'category', view: 'grid', backendQuery: true, searchPlaceholder: 'Search services', countNoun: ['service', 'services'], primaryVerb: { label: 'Book', screenId: 'himi_time' } },
@@ -184,6 +191,9 @@ export function projectCollection(source: CollectionRow[], config: CollectionCon
184
191
  collectionSelectLabel: prefs.selectedIds.includes(collectionId(row)) ? 'Selected' : 'Select',
185
192
  // The save toggle names the action it WILL take (the service localizes it).
186
193
  collectionSaveLabel: prefs.savedIds.includes(collectionId(row)) ? 'Remove from saved' : 'Save',
194
+ // The row's overflow menu, named for its record: out of context (a rotor, a voice
195
+ // command) a list of identical "Item actions" cannot be told apart.
196
+ collectionActionsLabel: collectionActionsLabel(collectionTitle(row, config), 'Actions for {title}', 'Item actions'),
187
197
  collectionSubtitle: scalar(row[config.subtitle ?? '']) || scalar(row.subtitle) || scalar(row.description) || scalar(row.meta),
188
198
  collectionImage: scalar(row[config.image ?? '']), collectionNoImage: !scalar(row[config.image ?? '']), collectionHasImage: !!scalar(row[config.image ?? '']),
189
199
  collectionSaved: prefs.savedIds.includes(collectionId(row)), collectionNotSaved: !prefs.savedIds.includes(collectionId(row)), collectionSelected: prefs.selectedIds.includes(collectionId(row)),
@@ -1,5 +1,5 @@
1
1
  import { defineService as register, type ServiceDefinition } from '@himalaya/state';
2
- import { COLLECTIONS, COLLECTION_CHROME_MIN_ITEMS, initialCollection, normalizeCollection, projectCollection, collectionId, type CollectionRow } from './collection-contract.js';
2
+ import { COLLECTIONS, COLLECTION_CHROME_MIN_ITEMS, initialCollection, normalizeCollection, projectCollection, collectionId, collectionActionsLabel, type CollectionRow } from './collection-contract.js';
3
3
  import { localPreferences, type PreferenceRuntime } from './local-preferences.js';
4
4
  import { copyParam, stashCopy } from './himi-copy.js';
5
5
  import { createCollectionQuery, type CollectionQuerySource, type CollectionQueryState } from './collection-query.js';
@@ -130,6 +130,8 @@ export function decorateCollection(name: string, def: { state: State; actions?:
130
130
  // the save toggle names the action it WILL take for this row. The
131
131
  // old static "Save / unsave" made the owner work out the row's state.
132
132
  row.collectionSaveLabel = row.collectionSaved ? copyParam('collection.unsave', 'Remove from saved') : copyParam('collection.save', 'Save');
133
+ row.collectionActionsLabel = collectionActionsLabel(String(row.collectionTitle ?? ''),
134
+ copyParam('collection.itemActionsFor', 'Actions for {title}'), copyParam('collection.itemActions', 'Item actions'));
133
135
  });
134
136
  if (previewId && !rows.some(row => collectionId(row) === previewId)) previewId = '';
135
137
  const filterChoices = [{ id: 'all', label: copyParam('collection.all', 'All') }, { id: 'saved', label: copyParam('collection.saved', 'Saved') },