@wix/himalaya-cli 0.1569.0 → 0.1571.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 (159) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/l10n-strings.d.ts +3 -2
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +8 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/auth-providers/wix/src/index.d.ts +7 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/business-contact-service.d.ts +17 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/business-contact.d.ts +73 -0
  6. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/index.d.ts +3 -0
  7. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/invite-target.d.ts +1 -0
  8. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/flows/screens/src/invite.d.ts +148 -0
  9. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/blog-v3/src/extensions.d.ts +6 -3
  10. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/auth.mjs +17 -7
  11. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core/validation.mjs +17 -7
  12. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +17 -7
  13. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/screens.mjs +575 -91
  14. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/state.mjs +17 -7
  15. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-auth.mjs +7 -1
  16. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-blog.mjs +1 -0
  17. package/dist/cli.mjs +1897 -1226
  18. package/dist/preview-assets.generated.bin +0 -0
  19. package/guides/base44-app-to-himi-app.md +1 -1
  20. package/guides/capabilities.md +7 -0
  21. package/guides/getting-started.md +5 -0
  22. package/guides/himi-json-output.md +11 -0
  23. package/guides/himi-skill.md +5 -0
  24. package/guides/one-app-to-himi-app.md +267 -8
  25. package/guides/sdk-reference.json +2 -2
  26. package/guides/skill.md +5 -0
  27. package/guides/splash-screens.md +10 -1
  28. package/guides/tier-5-add-a-screen.md +20 -0
  29. package/guides/trim-a-template.md +5 -0
  30. package/guides/wix-business-context.md +11 -2
  31. package/package.json +2 -2
  32. package/templates/beauty-salon/config.ts +8 -1
  33. package/templates/beauty-salon/dev/index.ts +244 -1
  34. package/templates/beauty-salon/himalaya.content.json +3 -0
  35. package/templates/beauty-salon/tier5-src/_shared/member-read.ts +52 -0
  36. package/templates/beauty-salon/tier5-src/_shared/program-description.ts +11 -0
  37. package/templates/beauty-salon/tier5-src/business-contact/index.ts +6 -0
  38. package/templates/beauty-salon/tier5-src/himi-blog-post/index.ts +35 -12
  39. package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +3 -3
  40. package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +6 -9
  41. package/templates/beauty-salon-owner/config.ts +1 -1
  42. package/templates/branded-lite/config.ts +8 -1
  43. package/templates/branded-lite/dev/index.ts +409 -2
  44. package/templates/branded-lite/himalaya.content.json +3 -0
  45. package/templates/branded-lite/tier5-src/_shared/member-read.ts +52 -0
  46. package/templates/branded-lite/tier5-src/_shared/program-description.ts +11 -0
  47. package/templates/branded-lite/tier5-src/business-contact/index.ts +6 -0
  48. package/templates/branded-lite/tier5-src/himi-blog-post/index.ts +35 -12
  49. package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +3 -3
  50. package/templates/branded-lite/tier5-src/himi-branded-lite-notifsettings/index.ts +39 -11
  51. package/templates/branded-lite/tier5-src/himi-branded-lite-program/index.ts +16 -14
  52. package/templates/branded-lite/tier5-src/himi-branded-lite-programs/index.ts +3 -7
  53. package/templates/branded-lite/tier5-src/himi-events-my-events/index.ts +76 -42
  54. package/templates/branded-lite/tier5-src/himi-store-product/index.ts +6 -9
  55. package/templates/branded-lite/tier5-src/wix-spaces-chat-thread/index.ts +101 -34
  56. package/templates/branded-lite/tier5-src/wix-spaces-forum-post/index.ts +23 -6
  57. package/templates/branded-lite/tier5-src/wix-spaces-member-detail/index.ts +26 -11
  58. package/templates/community-club/config.ts +8 -1
  59. package/templates/community-club/dev/index.ts +278 -2
  60. package/templates/community-club/himalaya.content.json +3 -0
  61. package/templates/community-club/tier5-src/_shared/branded-load-state.ts +106 -0
  62. package/templates/community-club/tier5-src/_shared/member-read.ts +52 -0
  63. package/templates/community-club/tier5-src/_shared/program-description.ts +11 -0
  64. package/templates/community-club/tier5-src/business-contact/index.ts +6 -0
  65. package/templates/community-club/tier5-src/himi-blog-post/index.ts +35 -12
  66. package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +3 -3
  67. package/templates/community-club/tier5-src/himi-events-my-events/index.ts +76 -42
  68. package/templates/community-club-owner/config.ts +1 -1
  69. package/templates/events-meetups/config.ts +8 -1
  70. package/templates/events-meetups/dev/index.ts +278 -2
  71. package/templates/events-meetups/himalaya.content.json +3 -0
  72. package/templates/events-meetups/tier5-src/_shared/branded-load-state.ts +106 -0
  73. package/templates/events-meetups/tier5-src/_shared/member-read.ts +52 -0
  74. package/templates/events-meetups/tier5-src/_shared/program-description.ts +11 -0
  75. package/templates/events-meetups/tier5-src/business-contact/index.ts +6 -0
  76. package/templates/events-meetups/tier5-src/himi-blog-post/index.ts +35 -12
  77. package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +3 -3
  78. package/templates/events-meetups/tier5-src/himi-events-my-events/index.ts +76 -42
  79. package/templates/events-meetups-owner/config.ts +1 -1
  80. package/templates/gym-fitness/config.ts +8 -1
  81. package/templates/gym-fitness/dev/index.ts +278 -2
  82. package/templates/gym-fitness/himalaya.content.json +3 -0
  83. package/templates/gym-fitness/tier5-src/_shared/member-read.ts +52 -0
  84. package/templates/gym-fitness/tier5-src/_shared/program-description.ts +11 -0
  85. package/templates/gym-fitness/tier5-src/business-contact/index.ts +6 -0
  86. package/templates/gym-fitness/tier5-src/himi-blog-post/index.ts +35 -12
  87. package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +3 -3
  88. package/templates/gym-fitness/tier5-src/himi-events-my-events/index.ts +76 -42
  89. package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +6 -9
  90. package/templates/gym-fitness-owner/config.ts +1 -1
  91. package/templates/nonprofit-cause/config.ts +8 -1
  92. package/templates/nonprofit-cause/dev/index.ts +278 -2
  93. package/templates/nonprofit-cause/himalaya.content.json +3 -0
  94. package/templates/nonprofit-cause/tier5-src/_shared/branded-load-state.ts +106 -0
  95. package/templates/nonprofit-cause/tier5-src/_shared/member-read.ts +52 -0
  96. package/templates/nonprofit-cause/tier5-src/_shared/program-description.ts +11 -0
  97. package/templates/nonprofit-cause/tier5-src/business-contact/index.ts +6 -0
  98. package/templates/nonprofit-cause/tier5-src/himi-blog-post/index.ts +35 -12
  99. package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +3 -3
  100. package/templates/nonprofit-cause/tier5-src/himi-events-my-events/index.ts +76 -42
  101. package/templates/nonprofit-cause-owner/config.ts +1 -1
  102. package/templates/owner-lite/config.ts +1 -1
  103. package/templates/podcast-creator/config.ts +8 -1
  104. package/templates/podcast-creator/dev/index.ts +278 -2
  105. package/templates/podcast-creator/himalaya.content.json +3 -0
  106. package/templates/podcast-creator/tier5-src/_shared/branded-load-state.ts +106 -0
  107. package/templates/podcast-creator/tier5-src/_shared/member-read.ts +52 -0
  108. package/templates/podcast-creator/tier5-src/_shared/program-description.ts +11 -0
  109. package/templates/podcast-creator/tier5-src/business-contact/index.ts +6 -0
  110. package/templates/podcast-creator/tier5-src/himi-blog-post/index.ts +35 -12
  111. package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +3 -3
  112. package/templates/podcast-creator/tier5-src/himi-events-my-events/index.ts +76 -42
  113. package/templates/podcast-creator-owner/config.ts +1 -1
  114. package/templates/portfolio-creative/config.ts +8 -1
  115. package/templates/portfolio-creative/dev/index.ts +278 -2
  116. package/templates/portfolio-creative/himalaya.content.json +3 -0
  117. package/templates/portfolio-creative/tier5-src/_shared/branded-load-state.ts +106 -0
  118. package/templates/portfolio-creative/tier5-src/_shared/member-read.ts +52 -0
  119. package/templates/portfolio-creative/tier5-src/_shared/program-description.ts +11 -0
  120. package/templates/portfolio-creative/tier5-src/business-contact/index.ts +6 -0
  121. package/templates/portfolio-creative/tier5-src/himi-blog-post/index.ts +35 -12
  122. package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +3 -3
  123. package/templates/portfolio-creative/tier5-src/himi-events-my-events/index.ts +76 -42
  124. package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +6 -9
  125. package/templates/portfolio-creative-owner/config.ts +1 -1
  126. package/templates/restaurant/config.ts +8 -1
  127. package/templates/restaurant/dev/index.ts +278 -2
  128. package/templates/restaurant/himalaya.content.json +3 -0
  129. package/templates/restaurant/tier5-src/_shared/branded-load-state.ts +106 -0
  130. package/templates/restaurant/tier5-src/_shared/member-read.ts +52 -0
  131. package/templates/restaurant/tier5-src/_shared/program-description.ts +11 -0
  132. package/templates/restaurant/tier5-src/business-contact/index.ts +6 -0
  133. package/templates/restaurant/tier5-src/himi-blog-post/index.ts +35 -12
  134. package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +3 -3
  135. package/templates/restaurant/tier5-src/himi-events-my-events/index.ts +76 -42
  136. package/templates/restaurant-owner/config.ts +1 -1
  137. package/templates/retail-store/config.ts +8 -1
  138. package/templates/retail-store/dev/index.ts +244 -1
  139. package/templates/retail-store/himalaya.content.json +3 -0
  140. package/templates/retail-store/tier5-src/_shared/branded-load-state.ts +106 -0
  141. package/templates/retail-store/tier5-src/_shared/member-read.ts +52 -0
  142. package/templates/retail-store/tier5-src/_shared/program-description.ts +11 -0
  143. package/templates/retail-store/tier5-src/business-contact/index.ts +6 -0
  144. package/templates/retail-store/tier5-src/himi-blog-post/index.ts +35 -12
  145. package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +3 -3
  146. package/templates/retail-store/tier5-src/himi-store-product/index.ts +6 -9
  147. package/templates/retail-store-owner/config.ts +1 -1
  148. package/templates/yoga-studio/config.ts +8 -1
  149. package/templates/yoga-studio/dev/index.ts +306 -2
  150. package/templates/yoga-studio/himalaya.content.json +3 -0
  151. package/templates/yoga-studio/tier5-src/_shared/member-read.ts +52 -0
  152. package/templates/yoga-studio/tier5-src/_shared/program-description.ts +11 -0
  153. package/templates/yoga-studio/tier5-src/business-contact/index.ts +6 -0
  154. package/templates/yoga-studio/tier5-src/himi-blog-post/index.ts +35 -12
  155. package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +3 -3
  156. package/templates/yoga-studio/tier5-src/himi-branded-lite-program/index.ts +16 -14
  157. package/templates/yoga-studio/tier5-src/himi-branded-lite-programs/index.ts +3 -7
  158. package/templates/yoga-studio/tier5-src/himi-events-my-events/index.ts +76 -42
  159. package/templates/yoga-studio-owner/config.ts +1 -1
@@ -5,6 +5,8 @@ import { queryEvents, type WixEvent } from "@himalaya/wix-events";
5
5
  import { getAll, upsert, type DbEntity } from "@himalaya/local-db";
6
6
  import { confirmedRsvpIds, eventStartMillis, normalizeEvent, type EventRow } from "../_shared/himi-events";
7
7
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
8
+ import { memberAccess } from "../_shared/member-access.js";
9
+ import { readAccess, assertReadAccess, readAccessDenied, readSignInFlags, NO_READ_SIGN_IN } from "../_shared/member-read.js";
8
10
 
9
11
  // Offline-first (data-offline): My Events is a read-through over the same `events`
10
12
  // catalog the list caches — paint the last-synced rows (filtered to this device's
@@ -27,6 +29,7 @@ async function writeCached(rows: CachedEventRow[]): Promise<void> {
27
29
  }
28
30
 
29
31
  interface S {
32
+ signInRequired: boolean; noSignIn: boolean; signInScreenId: string;
30
33
  status: "loading" | "ready";
31
34
  events: EventRow[];
32
35
  emptyText: string;
@@ -98,11 +101,10 @@ function unverifiedEvent(event: CachedEventRow): CachedEventRow {
98
101
  };
99
102
  }
100
103
 
101
- /** Keep only the candidate rows the member ACTUALLY still holds an RSVP for, asking
102
- * Wix per event. Fail-open per row: a network/permission blip must not blank a row the
103
- * member really has (that would look like their RSVP vanished), so an errored probe
104
- * keeps the row and only an explicit "no active RSVP" drops it. */
105
- async function confirmRsvps(candidates: CachedEventRow[]): Promise<{ events: CachedEventRow[]; verificationUnavailable: boolean }> {
104
+ /** Verify device-local candidates against the current member. Member-only mode
105
+ * requires successful verification; standalone mode retains failed probes as
106
+ * explicitly unverified device-local history. */
107
+ async function confirmRsvps(candidates: CachedEventRow[], memberOnly = false): Promise<{ events: CachedEventRow[]; verificationUnavailable: boolean }> {
106
108
  // Raw wixApi chokepoint rather than a generated client: the member RSVP read lives in
107
109
  // events **v2**, and `@himalaya/wix-events` is the v1 domain module. Adding a
108
110
  // `wix-events-v2` specifier is not an option — the umbrella package deliberately
@@ -122,7 +124,9 @@ async function confirmRsvps(candidates: CachedEventRow[]): Promise<{ events: Cac
122
124
  event: held ? { ...event, status: [event.status, copyParam("myEvents.rsvpConfirmed", "RSVP confirmed")].filter(Boolean).join(" · ") } : null,
123
125
  verificationUnavailable: false,
124
126
  };
125
- } catch {
127
+ } catch (error) {
128
+ // Device candidates carry no member ownership proof until this read succeeds.
129
+ if (memberOnly) throw error;
126
130
  // Fail-open, but never claim backend confirmation while its member-readable
127
131
  // probe is unavailable. The device id remains a candidate, explicitly labeled.
128
132
  return { event: unverifiedEvent(event), verificationUnavailable: true };
@@ -134,8 +138,8 @@ async function confirmRsvps(candidates: CachedEventRow[]): Promise<{ events: Cac
134
138
  };
135
139
  }
136
140
 
137
- const svc = defineService<S, { load: (s: S, arg?: unknown) => Promise<S> }>("himiMyEvents", {
138
- state: derive({
141
+ const INITIAL: S = derive({
142
+ ...NO_READ_SIGN_IN, signInScreenId: "himi_account",
139
143
  status: "loading",
140
144
  events: [],
141
145
  emptyText: "",
@@ -147,45 +151,75 @@ const svc = defineService<S, { load: (s: S, arg?: unknown) => Promise<S> }>("him
147
151
  isLoading: true,
148
152
  notLoading: false,
149
153
  noEmpty: true,
150
- }),
154
+ });
155
+ let memberOnlyRequested = false;
156
+ let loadVersion = 0;
157
+ const svc = defineService<S, { prepare: (s: S) => S; load: (s: S, arg?: unknown) => Promise<S | undefined> }>("himiMyEvents", {
158
+ state: INITIAL,
151
159
  actions: {
152
- load: async (s, arg) => {
160
+ prepare: (s) => ({ ...s, ...INITIAL }),
161
+ load: async (s, arg): Promise<S | undefined> => {
153
162
  stashCopy(arg);
154
- const confirmed = new Set(confirmedRsvpIds());
155
- const identity = await identityLine();
156
- // 1) Instant paint from the on-device cache (offline-safe; empty on first-ever run).
157
- const cached = await readCached();
158
- const cachedEvents = cached.filter((event) => confirmed.has(event.id)).map(displayEventRow);
159
- const base = cached.length
160
- ? derive({ ...s, status: "ready", errored: false, events: cachedEvents, identityLine: identity, verificationUnavailable: cachedEvents.length > 0 })
161
- : s;
162
- // 2) Revalidate from Wix and refresh the cache.
163
+ const version = ++loadVersion;
164
+ const options = arg as { memberOnly?: unknown; signInScreenId?: unknown } | undefined;
165
+ if (options?.memberOnly !== undefined) memberOnlyRequested = options.memberOnly === "1";
166
+ const memberOnly = memberOnlyRequested;
167
+ const clean = { ...INITIAL, signInScreenId: options?.signInScreenId === "himi_sign_in" ? "himi_sign_in" : s.signInScreenId };
168
+ await svc.prepare();
163
169
  try {
164
- const res = await queryEvents({ limit: 50 });
165
- const all = ((res as unknown as { events?: WixEvent[] }).events ?? []).map(cachedEventRow);
166
- await writeCached(all);
167
- // Phase 3c — make each row AUTHORITATIVE. The device list is only a candidate
168
- // set: it never forgets, so an RSVP cancelled elsewhere (web, or by the
169
- // business) would otherwise show here forever. Confirm each candidate against
170
- // Wix and drop the ones the member no longer holds.
171
- //
172
- // Per-event by necessity, not by choice: a member CANNOT enumerate their own
173
- // RSVPs. Verified live against a real member token — `queryRsvps` (cross-event)
174
- // 403s "WIX_EVENTS.READ_RSVP permission required", `listOrders` 403s
175
- // READ_ORDERS, and `/rsvps/current` 400s without an eventId. Only the
176
- // single-event read is member-readable, so the fan-out below IS the ceiling
177
- // until those member scopes exist server-side.
178
- const candidates = all.filter((event) => confirmed.has(event.id)).map(displayEventRow);
179
- const confirmation = await confirmRsvps(candidates);
180
- return derive({ ...base, status: "ready", errored: false, events: confirmation.events, identityLine: identity, verificationUnavailable: confirmation.verificationUnavailable });
181
- } catch {
182
- // Offline / fetch failure: keep the cached rows instead of a blank error state.
183
- return cached.length
184
- ? base
185
- : derive({ ...s, status: "ready", errored: true, events: [], identityLine: identity });
170
+ const member = memberOnly ? await readAccess() : null;
171
+ if (memberOnly && !member?.member.email) throw new Error("Unauthenticated: sign in to see your events");
172
+ const confirmed = new Set(confirmedRsvpIds());
173
+ const identity = await identityLine();
174
+ // 1) Instant paint from the on-device cache (offline-safe; empty on first-ever run).
175
+ // Device-local candidates have no member ownership proof. Branded must
176
+ // verify them online rather than show a previous member's cached RSVP list.
177
+ const cached = memberOnly ? [] : await readCached();
178
+ const cachedEvents = cached.filter((event) => confirmed.has(event.id)).map(displayEventRow);
179
+ const base = cached.length
180
+ ? derive({ ...clean, status: "ready", errored: false, events: cachedEvents, identityLine: identity, verificationUnavailable: cachedEvents.length > 0 })
181
+ : clean;
182
+ // 2) Revalidate from Wix and refresh the cache.
183
+ try {
184
+ const res = await queryEvents({ limit: 50 });
185
+ const all = ((res as unknown as { events?: WixEvent[] }).events ?? []).map(cachedEventRow);
186
+ if (member) await assertReadAccess(member);
187
+ if (version !== loadVersion) return undefined;
188
+ await writeCached(all);
189
+ // Phase 3c — make each row AUTHORITATIVE. The device list is only a candidate
190
+ // set: it never forgets, so an RSVP cancelled elsewhere (web, or by the
191
+ // business) would otherwise show here forever. Confirm each candidate against
192
+ // Wix and drop the ones the member no longer holds.
193
+ //
194
+ // Per-event by necessity, not by choice: a member CANNOT enumerate their own
195
+ // RSVPs. Verified live against a real member token — `queryRsvps` (cross-event)
196
+ // 403s "WIX_EVENTS.READ_RSVP permission required", `listOrders` 403s
197
+ // READ_ORDERS, and `/rsvps/current` 400s without an eventId. Only the
198
+ // single-event read is member-readable, so the fan-out below IS the ceiling
199
+ // until those member scopes exist server-side.
200
+ const candidates = all.filter((event) => confirmed.has(event.id)).map(displayEventRow);
201
+ const confirmation = await confirmRsvps(candidates, memberOnly);
202
+ if (member) await assertReadAccess(member);
203
+ if (version !== loadVersion) return undefined;
204
+ return derive({ ...base, status: "ready", errored: false, events: confirmation.events, identityLine: identity, verificationUnavailable: confirmation.verificationUnavailable });
205
+ } catch (error) {
206
+ if (memberOnly && readAccessDenied(error)) throw error;
207
+ if (member) await assertReadAccess(member);
208
+ if (version !== loadVersion) return undefined;
209
+ // Offline / fetch failure: keep the cached rows instead of a blank error state.
210
+ return cached.length
211
+ ? base
212
+ : derive({ ...clean, status: "ready", errored: true, events: [], identityLine: identity });
213
+ }
214
+ } catch (error) {
215
+ const failure = memberOnly && !(await memberAccess()).email ? new Error("Unauthenticated: sign in to see your events") : error;
216
+ const flags = await readSignInFlags(failure);
217
+ return version === loadVersion ? derive({ ...clean, ...flags, status: "ready", errored: true, events: [] }) : undefined;
186
218
  }
187
219
  },
188
220
  },
189
221
  });
190
- svc.load();
222
+ // Wait for the descriptor's load action to select member-only or device-local scope.
223
+ // An eager unconfigured read could briefly expose guest KV before Branded opts in.
224
+ svc.prepare();
191
225
  export { svc };
@@ -37,7 +37,7 @@ export default defineApp({
37
37
  actions: "http://localhost:8190/v1/actions",
38
38
  sync: "http://localhost:8190/v1/sync",
39
39
  },
40
- capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","photos","file_picker","files","presigned_upload","app_info","biometrics","wix_platform","animation"],
40
+ capabilities: ["live_activity","push","share","secure_storage","open_url","local_database","clipboard","ui","speech","camera","photos","file_picker","files","presigned_upload","app_info","biometrics","wix_platform","stream","wix_api"],
41
41
  designTokensPath: "./tokens.json",
42
42
  // Native fallback; the T5 splash descriptor owns the editable launch appearance.
43
43
  splash: {
@@ -37,7 +37,7 @@ export default defineApp({
37
37
  actions: "http://localhost:8190/v1/actions",
38
38
  sync: "http://localhost:8190/v1/sync",
39
39
  },
40
- capabilities: ["live_activity","push","share","secure_storage","open_url","calendar","local_database","wix_api","wix_platform","app_info","iap","release_control","clipboard","theme"],
40
+ capabilities: ["push","share","secure_storage","open_url","calendar","local_database","wix_api","wix_platform","app_info","iap","release_control","clipboard","theme","wix_cashier"],
41
41
  cache: {
42
42
  "rules": [
43
43
  {
@@ -81,6 +81,13 @@ export default defineApp({
81
81
  "parentScreenId": "himi_account",
82
82
  "tabId": "profile"
83
83
  },
84
+ {
85
+ "screenId": "himi_business_contact",
86
+ "isPublic": true,
87
+ "paramKeys": [],
88
+ "parentScreenId": "himi_account",
89
+ "tabId": "profile"
90
+ },
84
91
  {
85
92
  "screenId": "himi_appearance",
86
93
  "isPublic": true,
@@ -6132,6 +6132,219 @@ const SCREENS = runtimeSetting({
6132
6132
  "bundleVersion": "0.0.1"
6133
6133
  }
6134
6134
  },
6135
+ "himi_business_contact": {
6136
+ "screenId": "himi_business_contact",
6137
+ "title": "Business contact",
6138
+ "renderer": "sdui_intent",
6139
+ "schemaVersion": 1,
6140
+ "cacheControl": {
6141
+ "maxAge": 0,
6142
+ "staleWhileRevalidate": 0
6143
+ },
6144
+ "payload": {
6145
+ "id": "himi_business_contact.root",
6146
+ "body": {
6147
+ "package": "himalaya",
6148
+ "type": "VStack",
6149
+ "props": {
6150
+ "spacing": 16,
6151
+ "alignment": "leading",
6152
+ "children": [
6153
+ {
6154
+ "id": "himi_business_contact.section",
6155
+ "body": {
6156
+ "package": "himalaya",
6157
+ "type": "VStack",
6158
+ "props": {
6159
+ "spacing": 12,
6160
+ "alignment": "leading",
6161
+ "children": [
6162
+ {
6163
+ "id": "himi_business_contact.section.directions_copy",
6164
+ "body": {
6165
+ "package": "himalaya",
6166
+ "type": "Text",
6167
+ "props": {
6168
+ "text": "Directions",
6169
+ "style": "body",
6170
+ "collapseWhenEmpty": true
6171
+ }
6172
+ },
6173
+ "modifiers": {
6174
+ "hidden": true
6175
+ }
6176
+ },
6177
+ {
6178
+ "id": "himi_business_contact.section.unavailable",
6179
+ "body": {
6180
+ "package": "himalaya",
6181
+ "type": "Text",
6182
+ "props": {
6183
+ "text": "Contact details unavailable",
6184
+ "style": "body",
6185
+ "collapseWhenEmpty": true
6186
+ }
6187
+ }
6188
+ },
6189
+ {
6190
+ "id": "himi_business_contact.section.member_count",
6191
+ "body": {
6192
+ "package": "himalaya",
6193
+ "type": "FragmentSlot",
6194
+ "props": {
6195
+ "casePath": "$service:businessContact.memberCountCase",
6196
+ "defaultCase": "unavailable",
6197
+ "cases": [
6198
+ {
6199
+ "id": "one",
6200
+ "content": {
6201
+ "id": "himi_business_contact.section.members_one",
6202
+ "body": {
6203
+ "package": "himalaya",
6204
+ "type": "HStack",
6205
+ "props": {
6206
+ "spacing": 4,
6207
+ "alignment": "center",
6208
+ "children": [
6209
+ {
6210
+ "id": "himi_business_contact.section.count_one",
6211
+ "body": {
6212
+ "package": "himalaya",
6213
+ "type": "Text",
6214
+ "props": {
6215
+ "text": "$service:businessContact.memberCount",
6216
+ "style": "footnote",
6217
+ "collapseWhenEmpty": true
6218
+ }
6219
+ }
6220
+ },
6221
+ {
6222
+ "id": "himi_business_contact.section.unit_one",
6223
+ "body": {
6224
+ "package": "himalaya",
6225
+ "type": "Text",
6226
+ "props": {
6227
+ "text": "member",
6228
+ "style": "footnote",
6229
+ "collapseWhenEmpty": true
6230
+ }
6231
+ }
6232
+ }
6233
+ ]
6234
+ }
6235
+ }
6236
+ }
6237
+ },
6238
+ {
6239
+ "id": "other",
6240
+ "content": {
6241
+ "id": "himi_business_contact.section.members_other",
6242
+ "body": {
6243
+ "package": "himalaya",
6244
+ "type": "HStack",
6245
+ "props": {
6246
+ "spacing": 4,
6247
+ "alignment": "center",
6248
+ "children": [
6249
+ {
6250
+ "id": "himi_business_contact.section.count_other",
6251
+ "body": {
6252
+ "package": "himalaya",
6253
+ "type": "Text",
6254
+ "props": {
6255
+ "text": "$service:businessContact.memberCount",
6256
+ "style": "footnote",
6257
+ "collapseWhenEmpty": true
6258
+ }
6259
+ }
6260
+ },
6261
+ {
6262
+ "id": "himi_business_contact.section.unit_other",
6263
+ "body": {
6264
+ "package": "himalaya",
6265
+ "type": "Text",
6266
+ "props": {
6267
+ "text": "members",
6268
+ "style": "footnote",
6269
+ "collapseWhenEmpty": true
6270
+ }
6271
+ }
6272
+ }
6273
+ ]
6274
+ }
6275
+ }
6276
+ }
6277
+ },
6278
+ {
6279
+ "id": "unavailable",
6280
+ "content": {
6281
+ "id": "himi_business_contact.section.members_unavailable",
6282
+ "body": {
6283
+ "package": "himalaya",
6284
+ "type": "Text",
6285
+ "props": {
6286
+ "text": "Member count unavailable",
6287
+ "style": "footnote",
6288
+ "collapseWhenEmpty": true
6289
+ }
6290
+ }
6291
+ }
6292
+ }
6293
+ ]
6294
+ }
6295
+ },
6296
+ "modifiers": {
6297
+ "hidden": true
6298
+ }
6299
+ },
6300
+ {
6301
+ "id": "himi_business_contact.section.feedback",
6302
+ "body": {
6303
+ "package": "himalaya",
6304
+ "type": "Text",
6305
+ "props": {
6306
+ "text": "$service:businessContact.message",
6307
+ "style": "footnote",
6308
+ "collapseWhenEmpty": true
6309
+ }
6310
+ }
6311
+ }
6312
+ ]
6313
+ }
6314
+ }
6315
+ }
6316
+ ]
6317
+ }
6318
+ },
6319
+ "modifiers": {
6320
+ "background": {
6321
+ "token": "surfacePrimary"
6322
+ },
6323
+ "padding": {
6324
+ "leading": 20,
6325
+ "trailing": 20,
6326
+ "top": 18,
6327
+ "bottom": 28
6328
+ },
6329
+ "onAppear": {
6330
+ "actionId": "tier5.businessContact.load",
6331
+ "args": {
6332
+ "showMemberCount": false
6333
+ }
6334
+ }
6335
+ }
6336
+ },
6337
+ "logicBundle": {
6338
+ "url": "http://localhost:8190/v1/bundles/business-contact?integrity=",
6339
+ "integrity": "",
6340
+ "contentType": "application/javascript+ts-bundle",
6341
+ "declaredCapabilities": [
6342
+ "open_url"
6343
+ ],
6344
+ "minBinaryVersion": "0.1.0",
6345
+ "bundleVersion": "0.0.1"
6346
+ }
6347
+ },
6135
6348
  "himi_appearance": {
6136
6349
  "screenId": "himi_appearance",
6137
6350
  "renderer": "sdui_intent",
@@ -11233,6 +11446,9 @@ const SCREENS = runtimeSetting({
11233
11446
  "style": "body",
11234
11447
  "color": "textSecondary"
11235
11448
  }
11449
+ },
11450
+ "modifiers": {
11451
+ "hiddenPath": "$service:himiMyEvents.signInRequired"
11236
11452
  }
11237
11453
  },
11238
11454
  {
@@ -11247,6 +11463,32 @@ const SCREENS = runtimeSetting({
11247
11463
  "actionId": "tier5.himiMyEvents.load"
11248
11464
  }
11249
11465
  }
11466
+ },
11467
+ "modifiers": {
11468
+ "hiddenPath": "$service:himiMyEvents.signInRequired"
11469
+ }
11470
+ },
11471
+ {
11472
+ "id": "himi_my_events.retry.sign_in",
11473
+ "body": {
11474
+ "package": "himalaya",
11475
+ "type": "Button",
11476
+ "props": {
11477
+ "title": "Sign in",
11478
+ "style": "primary",
11479
+ "action": {
11480
+ "actionId": "himi.open",
11481
+ "params": {
11482
+ "screenId": "himi_sign_in",
11483
+ "mode": "signin",
11484
+ "modal": "1"
11485
+ },
11486
+ "presentation": "sheet"
11487
+ }
11488
+ }
11489
+ },
11490
+ "modifiers": {
11491
+ "hiddenPath": "$service:himiMyEvents.noSignIn"
11250
11492
  }
11251
11493
  }
11252
11494
  ]
@@ -11677,7 +11919,11 @@ const SCREENS = runtimeSetting({
11677
11919
  "bottom": 24
11678
11920
  },
11679
11921
  "onAppear": {
11680
- "actionId": "tier5.himiMyEvents.load"
11922
+ "actionId": "tier5.himiMyEvents.load",
11923
+ "params": {
11924
+ "signInScreenId": "himi_sign_in",
11925
+ "memberOnly": "1"
11926
+ }
11681
11927
  }
11682
11928
  }
11683
11929
  },
@@ -20884,6 +21130,9 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
20884
21130
  "style": "body",
20885
21131
  "color": "textSecondary"
20886
21132
  }
21133
+ },
21134
+ "modifiers": {
21135
+ "hiddenPath": "$service:himiBlogPost.signInRequired"
20887
21136
  }
20888
21137
  },
20889
21138
  {
@@ -20898,6 +21147,32 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
20898
21147
  "actionId": "tier5.himiBlogPost.retry"
20899
21148
  }
20900
21149
  }
21150
+ },
21151
+ "modifiers": {
21152
+ "hiddenPath": "$service:himiBlogPost.signInRequired"
21153
+ }
21154
+ },
21155
+ {
21156
+ "id": "himi_blog_post.retry.sign_in",
21157
+ "body": {
21158
+ "package": "himalaya",
21159
+ "type": "Button",
21160
+ "props": {
21161
+ "title": "Sign in",
21162
+ "style": "primary",
21163
+ "action": {
21164
+ "actionId": "himi.open",
21165
+ "params": {
21166
+ "screenId": "himi_sign_in",
21167
+ "mode": "signin",
21168
+ "modal": "1"
21169
+ },
21170
+ "presentation": "sheet"
21171
+ }
21172
+ }
21173
+ },
21174
+ "modifiers": {
21175
+ "hiddenPath": "$service:himiBlogPost.noSignIn"
20901
21176
  }
20902
21177
  }
20903
21178
  ]
@@ -21602,7 +21877,8 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
21602
21877
  "onAppear": {
21603
21878
  "actionId": "tier5.himiBlogPost.load",
21604
21879
  "params": {
21605
- "id": "__HIMI_ID__"
21880
+ "id": "__HIMI_ID__",
21881
+ "signInScreenId": "himi_sign_in"
21606
21882
  }
21607
21883
  }
21608
21884
  }
@@ -28,6 +28,9 @@
28
28
  "himi_branded_splash": {
29
29
  "displayName": "Splash"
30
30
  },
31
+ "himi_business_contact": {
32
+ "displayName": "Business contact"
33
+ },
31
34
  "himi_create_post": {
32
35
  "displayName": "Create Post"
33
36
  },
@@ -0,0 +1,106 @@
1
+ // Load-state honesty for the branded-lite vertical workers.
2
+ //
3
+ // These workers used to answer every failed or empty query with representative SAMPLE
4
+ // rows — discount codes, gift-card balances, invoices, reservations, inbox messages —
5
+ // under a "Sample content" banner. On a customer build that is invented data presented
6
+ // as the customer's own: a code that does not exist, a balance in the wrong currency, an
7
+ // "Overdue" invoice, a booking labelled "tap to cancel". The banner did not make it
8
+ // honest; the rows were still the first thing a visitor read.
9
+ //
10
+ // The rule now:
11
+ // • query FAILED → status "error" with a customer sentence, a Retry, and — when
12
+ // Wix refused an anonymous visitor (401) — a Sign in path instead;
13
+ // • query was EMPTY → status "ready", no rows, the vertical's own empty sentence;
14
+ // • sample rows → ONLY when the host explicitly asked for sample data (the web
15
+ // preview's sample data mode, `__himiOwnerPreview: "sample"`).
16
+ // Never on a device, never as a fallback.
17
+ //
18
+ // Every field here is flat and boolean-gated because descriptors can only
19
+ // `hiddenWhen(<path>)` on a TRUE value: `notErrored` hides the error card, `noSignIn`
20
+ // hides the sign-in CTA, `signInRequired` hides Retry when retrying cannot help.
21
+ import { copyParam } from "./himi-copy.js";
22
+
23
+ export interface LoadFlags {
24
+ status: "loading" | "ready" | "error";
25
+ /** The customer-facing failure sentence; "" unless status is "error". */
26
+ error: string;
27
+ signInRequired: boolean;
28
+ notErrored: boolean;
29
+ /** hiddenWhen gate for a loading skeleton. */
30
+ notLoading: boolean;
31
+ /** Loading or failed — gates content that has nothing to say in either. */
32
+ busy: boolean;
33
+ noSignIn: boolean;
34
+ isMock: boolean;
35
+ notMock: boolean;
36
+ notMockEmpty: boolean;
37
+ notMockUnavailable: boolean;
38
+ }
39
+
40
+ export const LOADING: LoadFlags = {
41
+ status: "loading", error: "", signInRequired: false, notErrored: true, notLoading: false, busy: true, noSignIn: true,
42
+ isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true,
43
+ };
44
+
45
+ const READY: LoadFlags = { ...LOADING, status: "ready", notLoading: true, busy: false };
46
+
47
+ /** Did the HOST ask for sample data? Only the web preview's sample mode does; a native
48
+ * build, a visitor preview and a live owner preview never carry it. */
49
+ export function samplesRequested(): boolean {
50
+ const input = (globalThis as { himalaya?: { input?: Record<string, unknown> } }).himalaya?.input;
51
+ return input?.__himiOwnerPreview === "sample";
52
+ }
53
+
54
+ /** Wix refused an ANONYMOUS visitor: signing in is the fix, not retrying. A 403 is not
55
+ * this — it also answers "feature not installed" and a signed-in member without the
56
+ * permission, where a Sign in button leads nowhere — so it stays a Retry-able failure. */
57
+ export function needsSignIn(err: unknown): boolean {
58
+ const msg = String((err as { message?: unknown })?.message ?? err ?? "");
59
+ return /\b401\b|unauthori[sz]ed|unauthenticated/i.test(msg);
60
+ }
61
+
62
+ export interface LoadCopy<T> {
63
+ /** The copy namespace (`giftCards`, `coupons`, …) — keys resolve through copyParam. */
64
+ prefix: string;
65
+ /** Shown when the site genuinely has none. */
66
+ empty: string;
67
+ /** Shown when the query failed. */
68
+ error: string;
69
+ /** Shown when Wix refused the visitor (member-only data). */
70
+ signIn: string;
71
+ /** Sample rows for the explicit sample preview ONLY. */
72
+ sample: () => T[];
73
+ }
74
+
75
+ type Rows<K extends string, T> = { [P in K]: T[] } & { emptyText: string } & LoadFlags;
76
+
77
+ /** A query answered. Rows win; an empty answer is a real empty state. */
78
+ export function loaded<K extends string, T>(key: K, rows: T[], copy: LoadCopy<T>): Rows<K, T> {
79
+ if (rows.length) return { ...READY, [key]: rows, emptyText: "" } as Rows<K, T>;
80
+ if (samplesRequested()) return sampleRows(key, copy, "empty");
81
+ return { ...READY, [key]: [], emptyText: copyParam(`${copy.prefix}.emptyText`, copy.empty) } as Rows<K, T>;
82
+ }
83
+
84
+ /** A query threw. Never rows the customer did not create. */
85
+ export function loadFailed<K extends string, T>(key: K, err: unknown, copy: LoadCopy<T>): Rows<K, T> {
86
+ if (samplesRequested()) return sampleRows(key, copy, "unavailable");
87
+ const signIn = needsSignIn(err);
88
+ return {
89
+ ...READY,
90
+ status: "error",
91
+ [key]: [],
92
+ emptyText: "",
93
+ error: signIn ? copyParam(`${copy.prefix}.signInText`, copy.signIn) : copyParam(`${copy.prefix}.errorText`, copy.error),
94
+ signInRequired: signIn,
95
+ notErrored: false,
96
+ busy: true,
97
+ noSignIn: !signIn,
98
+ } as Rows<K, T>;
99
+ }
100
+
101
+ function sampleRows<K extends string, T>(key: K, copy: LoadCopy<T>, reason: "empty" | "unavailable"): Rows<K, T> {
102
+ return {
103
+ ...READY, [key]: copy.sample(), emptyText: "",
104
+ isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable",
105
+ } as Rows<K, T>;
106
+ }