@wix/himalaya-cli 0.1446.0 → 0.1447.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 (93) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/state/src/index.d.ts +2 -1
  2. package/dist/cli.mjs +38 -27
  3. package/dist/preview-assets.generated.bin +0 -0
  4. package/guides/sdk-reference.json +1 -1
  5. package/package.json +2 -2
  6. package/templates/beauty-salon/tier5-src/_shared/collection-query.ts +43 -9
  7. package/templates/beauty-salon/tier5-src/_shared/collection-service.ts +15 -3
  8. package/templates/beauty-salon/tier5-src/_shared/iap-presentation.ts +3 -1
  9. package/templates/beauty-salon-owner/dev/index.ts +3 -14
  10. package/templates/beauty-salon-owner/tier5-src/_shared/collection-query.ts +43 -9
  11. package/templates/beauty-salon-owner/tier5-src/_shared/collection-service.ts +15 -3
  12. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  13. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  14. package/templates/branded-lite/tier5-src/_shared/collection-query.ts +43 -9
  15. package/templates/branded-lite/tier5-src/_shared/collection-service.ts +15 -3
  16. package/templates/branded-lite/tier5-src/_shared/iap-presentation.ts +3 -1
  17. package/templates/community-club/tier5-src/_shared/collection-query.ts +43 -9
  18. package/templates/community-club/tier5-src/_shared/collection-service.ts +15 -3
  19. package/templates/community-club/tier5-src/_shared/iap-presentation.ts +3 -1
  20. package/templates/community-club-owner/dev/index.ts +3 -14
  21. package/templates/community-club-owner/tier5-src/_shared/collection-query.ts +43 -9
  22. package/templates/community-club-owner/tier5-src/_shared/collection-service.ts +15 -3
  23. package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  24. package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  25. package/templates/events-meetups/tier5-src/_shared/collection-query.ts +43 -9
  26. package/templates/events-meetups/tier5-src/_shared/collection-service.ts +15 -3
  27. package/templates/events-meetups/tier5-src/_shared/iap-presentation.ts +3 -1
  28. package/templates/events-meetups-owner/dev/index.ts +3 -14
  29. package/templates/events-meetups-owner/tier5-src/_shared/collection-query.ts +43 -9
  30. package/templates/events-meetups-owner/tier5-src/_shared/collection-service.ts +15 -3
  31. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  32. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  33. package/templates/gym-fitness/tier5-src/_shared/collection-query.ts +43 -9
  34. package/templates/gym-fitness/tier5-src/_shared/collection-service.ts +15 -3
  35. package/templates/gym-fitness/tier5-src/_shared/iap-presentation.ts +3 -1
  36. package/templates/gym-fitness-owner/dev/index.ts +3 -14
  37. package/templates/gym-fitness-owner/tier5-src/_shared/collection-query.ts +43 -9
  38. package/templates/gym-fitness-owner/tier5-src/_shared/collection-service.ts +15 -3
  39. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  40. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  41. package/templates/nonprofit-cause/tier5-src/_shared/collection-query.ts +43 -9
  42. package/templates/nonprofit-cause/tier5-src/_shared/collection-service.ts +15 -3
  43. package/templates/nonprofit-cause/tier5-src/_shared/iap-presentation.ts +3 -1
  44. package/templates/nonprofit-cause-owner/dev/index.ts +3 -14
  45. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-query.ts +43 -9
  46. package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-service.ts +15 -3
  47. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  48. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  49. package/templates/owner-lite/dev/index.ts +3 -14
  50. package/templates/owner-lite/tier5-src/_shared/collection-query.ts +43 -9
  51. package/templates/owner-lite/tier5-src/_shared/collection-service.ts +15 -3
  52. package/templates/owner-lite/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  53. package/templates/owner-lite/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  54. package/templates/podcast-creator/tier5-src/_shared/collection-query.ts +43 -9
  55. package/templates/podcast-creator/tier5-src/_shared/collection-service.ts +15 -3
  56. package/templates/podcast-creator/tier5-src/_shared/iap-presentation.ts +3 -1
  57. package/templates/podcast-creator-owner/dev/index.ts +3 -14
  58. package/templates/podcast-creator-owner/tier5-src/_shared/collection-query.ts +43 -9
  59. package/templates/podcast-creator-owner/tier5-src/_shared/collection-service.ts +15 -3
  60. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  61. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  62. package/templates/portfolio-creative/tier5-src/_shared/collection-query.ts +43 -9
  63. package/templates/portfolio-creative/tier5-src/_shared/collection-service.ts +15 -3
  64. package/templates/portfolio-creative/tier5-src/_shared/iap-presentation.ts +3 -1
  65. package/templates/portfolio-creative-owner/dev/index.ts +3 -14
  66. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-query.ts +43 -9
  67. package/templates/portfolio-creative-owner/tier5-src/_shared/collection-service.ts +15 -3
  68. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  69. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  70. package/templates/restaurant/tier5-src/_shared/collection-query.ts +43 -9
  71. package/templates/restaurant/tier5-src/_shared/collection-service.ts +15 -3
  72. package/templates/restaurant/tier5-src/_shared/iap-presentation.ts +3 -1
  73. package/templates/restaurant-owner/dev/index.ts +3 -14
  74. package/templates/restaurant-owner/tier5-src/_shared/collection-query.ts +43 -9
  75. package/templates/restaurant-owner/tier5-src/_shared/collection-service.ts +15 -3
  76. package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  77. package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  78. package/templates/retail-store/tier5-src/_shared/collection-query.ts +43 -9
  79. package/templates/retail-store/tier5-src/_shared/collection-service.ts +15 -3
  80. package/templates/retail-store/tier5-src/_shared/iap-presentation.ts +3 -1
  81. package/templates/retail-store-owner/dev/index.ts +3 -14
  82. package/templates/retail-store-owner/tier5-src/_shared/collection-query.ts +43 -9
  83. package/templates/retail-store-owner/tier5-src/_shared/collection-service.ts +15 -3
  84. package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  85. package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
  86. package/templates/yoga-studio/tier5-src/_shared/collection-query.ts +43 -9
  87. package/templates/yoga-studio/tier5-src/_shared/collection-service.ts +15 -3
  88. package/templates/yoga-studio/tier5-src/_shared/iap-presentation.ts +3 -1
  89. package/templates/yoga-studio-owner/dev/index.ts +3 -14
  90. package/templates/yoga-studio-owner/tier5-src/_shared/collection-query.ts +43 -9
  91. package/templates/yoga-studio-owner/tier5-src/_shared/collection-service.ts +15 -3
  92. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/index.ts +17 -12
  93. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/logic.ts +18 -5
@@ -142,6 +142,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
142
142
  // the query controller carries a recoverable failure. Treat each shape as
143
143
  // one collection state so the shared UI never renders a blank catalog.
144
144
  const backendState = backend?.controller.getState();
145
+ const provisional = backendState?.provisional === true;
145
146
  const errored = s.status === 'error' || s.isErrored === true || s.errored === true || backendState?.status === 'error';
146
147
  const domainError = typeof s.error === 'string' ? s.error.trim() : '';
147
148
  const queryError = typeof backendState?.error === 'string' ? backendState.error.trim() : '';
@@ -177,13 +178,17 @@ export function decorateCollection(name: string, def: { state: State; actions?:
177
178
  // un-narrowed failure. A re-query while typing keeps it (and the search field's focus).
178
179
  const hideChrome = (!narrowed && !s.nextCursor && projected.loaded < COLLECTION_CHROME_MIN_ITEMS)
179
180
  || sourceEmpty || ((loading || errored) && rows.length === 0 && !narrowed);
180
- return { ...s, collection: { ...prefs, ...projected,
181
+ // Kept with the state, so a cold boot restored from disk shows its last page and revalidates behind it.
182
+ const collectionShown = backend?.controller.shownPage();
183
+ return { ...s, ...(backend ? { collectionShown: collectionShown ?? null } : {}), collection: { ...prefs, ...projected,
181
184
  hideChrome, noActiveCriteria: !narrowed,
182
185
  viewName, viewMessage, saveMessage, noSavedViews: prefs.savedViews.length === 0,
183
186
  cursor: s.pageError === true || s.loadingMore === true ? '' : s.nextCursor,
184
187
  noMore: !s.nextCursor || s.pageError === true || s.loadingMore === true,
185
188
  noPageError: s.pageError !== true,
186
- noOffline: s.fromCache !== true,
189
+ // Rows read from the device ahead of the live read are shown as they are: "saved offline" is what stays on
190
+ // screen when the live read failed, not a first visit that is still loading.
191
+ noOffline: s.fromCache !== true || provisional,
187
192
  noError: !errored,
188
193
  noRetry: !canRetry || signInRequired,
189
194
  noSignIn: !signInRequired,
@@ -202,7 +207,7 @@ export function decorateCollection(name: string, def: { state: State; actions?:
202
207
  filtersPresented, previewPresented: !!previewId, preview: projected.items.filter(row => row.collectionId === previewId),
203
208
  persistenceLabel: persisted.available ? copyParam('collection.onDevice', 'Saved on this device') : copyParam('collection.sessionOnly', 'Choices last for this session'),
204
209
  noEmpty: loading || errored || sourceEmpty || domainNarrowed || projected.items.length > 0,
205
- summary: backend && config.backendQuery && s.fromCache
210
+ summary: backend && config.backendQuery && s.fromCache && !provisional
206
211
  ? copyParam('collection.cached', 'Saved offline · may be outdated')
207
212
  : collectionSummary({ shown: projected.items.length, loaded: projected.loaded, backend: !!(backend && config.backendQuery),
208
213
  total: typeof s.total === 'number' ? s.total : undefined, noun: config.countNoun
@@ -345,6 +350,13 @@ export function defineBackendCollection<S extends object, Row extends object>(na
345
350
  apply: (state, page) => options.apply(state as S, page as unknown as CollectionQueryState<Row>) as State,
346
351
  });
347
352
  const handle = (options.register ?? register)(name, definition);
353
+ // Registration restored the state a cold boot saved: the page it showed is this run's, before the first load. Only
354
+ // when the service's own rows came back with it: a bundle that keeps them out of the restore (its `notHydrated`)
355
+ // starts its first load as before, never from rows it chose not to restore.
356
+ const restored = (handle as { getState?: () => State }).getState?.();
357
+ const rowsKey = COLLECTIONS[name]?.rows;
358
+ const restoredRows = rowsKey ? restored?.[rowsKey] : undefined;
359
+ if (Array.isArray(restoredRows) && restoredRows.length > 0) controller.seed(restored?.collectionShown);
348
360
  publish = () => handle.collectionDataChanged();
349
361
  return handle;
350
362
  }
@@ -17,7 +17,7 @@ import { OWNER_MODULES, ownerModuleId } from '../_shared/owner-modules.js';
17
17
  import { localPreferences } from '../_shared/local-preferences.js';
18
18
  import { stashCopy, copyParam, copyTemplate } from '../_shared/himi-copy.js';
19
19
  import { fallbackNameFrom, selectedSiteName } from '../_shared/owner-selected-site.js';
20
- import { DASHBOARD_PATHS, deriveManage, parseAreas, recentAreas, rememberArea, withMetrics, type ManageArea, type Metric } from './logic.js';
20
+ import { DASHBOARD_PATHS, deriveManage, parseAreas, recentAreas, metricFailure, rememberArea, withMetrics, type ManageArea, type Metric, type MetricFailure } from './logic.js';
21
21
 
22
22
  interface State {
23
23
  /** The business the header names: the one picked on Sites, else the descriptor's
@@ -43,7 +43,6 @@ interface State {
43
43
  noSections: boolean;
44
44
  isLoading: boolean;
45
45
  notLoading: boolean;
46
- summaryNote: string;
47
46
  handoffError: string;
48
47
  }
49
48
 
@@ -242,20 +241,26 @@ async function loadMetrics(state: State): Promise<State> {
242
241
  return newestStarted ?? settled;
243
242
  }
244
243
 
244
+ /** A row's status when its summary could not load: why, so a missing app or a denied read is not taken for a fault. */
245
+ function failureText(failure: MetricFailure): string {
246
+ switch (failure) {
247
+ case 'notSetUp': return copyParam('ownerManage.notSetUp', 'Not set up on this site');
248
+ case 'noAccess': return copyParam('ownerManage.noAccess', 'No access');
249
+ default: return copyParam('ownerManage.summaryUnavailable', 'Couldn’t load summary');
250
+ }
251
+ }
252
+
245
253
  async function readMetrics(state: State): Promise<State> {
246
254
  const keys = [...new Set(state.areas.map(area => area.metricKey))];
247
255
  const settled = await Promise.allSettled(keys.map(metricFor));
248
256
  const metrics: Record<string, Metric | undefined> = {};
249
- const unavailable = new Set<string>();
257
+ const failures = new Map<string, string>();
250
258
  settled.forEach((result, index) => {
251
259
  if (result.status === 'fulfilled') metrics[keys[index]] = result.value;
252
- else unavailable.add(keys[index]);
260
+ else failures.set(keys[index], failureText(metricFailure(result.reason)));
253
261
  });
254
- const areas = withMetrics(state.areas, metrics, unavailable);
255
- const note = unavailable.size
256
- ? copyParam('ownerManage.partialSummary', 'Some summaries couldn’t load. You can still open every business area.')
257
- : '';
258
- return derive({ ...state, areas, isLoading: false, notLoading: true, summaryNote: note, handoffError: '' }, areas);
262
+ const areas = withMetrics(state.areas, metrics, failures);
263
+ return derive({ ...state, areas, isLoading: false, notLoading: true, handoffError: '' }, areas);
259
264
  }
260
265
 
261
266
  function findArea(state: State, arg: unknown): ManageArea | undefined {
@@ -294,7 +299,7 @@ const svc = defineService<State, {
294
299
  // 'All' is always a valid filter, so the chip row paints from the first frame
295
300
  // instead of appearing (and shifting the layout) when the areas arrive.
296
301
  groups: [{ id: 'all', label: allLabel() }], summary: '', noEmpty: true,
297
- hasRecents: false, noRecents: true, noSections: true, isLoading: true, notLoading: false, summaryNote: '', handoffError: '',
302
+ hasRecents: false, noRecents: true, noSections: true, isLoading: true, notLoading: false, handoffError: '',
298
303
  },
299
304
  actions: {
300
305
  load: async (state, arg) => {
@@ -313,7 +318,7 @@ const svc = defineService<State, {
313
318
  const parsed = (() => { try { return JSON.parse(str(arg, 'areas') || '[]'); } catch { return []; } })();
314
319
  const areas = parseAreas(parsed);
315
320
  if (state.status === 'ready' && sameAreas(state.areas, areas)) return loadMetrics(revalidating(state, areas));
316
- return loadMetrics(derive({ ...state, status: 'ready', areas, isLoading: true, notLoading: false, summaryNote: '', handoffError: '' }, areas));
321
+ return loadMetrics(derive({ ...state, status: 'ready', areas, isLoading: true, notLoading: false, handoffError: '' }, areas));
317
322
  },
318
323
  // Refresh re-reads summaries for KNOWN areas. Before the descriptor has
319
324
  // delivered them there is nothing to refresh, and settling here would open
@@ -321,7 +326,7 @@ const svc = defineService<State, {
321
326
  // it is re-read either way.
322
327
  refresh: async state => {
323
328
  currentBusinessName = selectedSiteName(fallbackBusinessName);
324
- return state.status !== 'ready' ? { ...state, businessName: currentBusinessName } : loadMetrics(derive({ ...state, isLoading: true, notLoading: false, summaryNote: '', handoffError: '' }));
329
+ return state.status !== 'ready' ? { ...state, businessName: currentBusinessName } : loadMetrics(derive({ ...state, isLoading: true, notLoading: false, handoffError: '' }));
325
330
  },
326
331
  search: (state, arg) => derive({ ...state, query: str(arg, 'value').slice(0, 256) }),
327
332
  filter: (state, arg) => {
@@ -110,13 +110,26 @@ export function countText(metric: Metric, label: string): string {
110
110
  return `${count}${metric.hasMore ? "+" : ""} ${label}`;
111
111
  }
112
112
 
113
- /** Apply independently loaded metrics. A failed source stays visibly unknown;
114
- * it never becomes a misleading zero. */
115
- export function withMetrics(areas: ManageArea[], metrics: Record<string, Metric | undefined>, unavailable: Set<string>): ManageArea[] {
113
+ /** Why a summary could not load: the area's app is not on the site, the owner may not read it, or the read failed.
114
+ * From the LEADING status only, as Today reads it (`isSectionUnavailable`): a transient
115
+ * `HTTP 503: {"message":"upstream returned 404"}` is a failure, not a missing app. */
116
+ export type MetricFailure = "notSetUp" | "noAccess" | "failed";
117
+
118
+ export function metricFailure(error: unknown): MetricFailure {
119
+ const text = String((error as Error)?.message ?? error);
120
+ if (/^HTTP (?:404|428)\b/.test(text) || /^HTTP \d{3} \[FAILED_PRECONDITION\]/.test(text)) return "notSetUp";
121
+ if (/^HTTP 403\b/.test(text) || /^HTTP \d{3} \[PERMISSION_DENIED\]/.test(text)) return "noAccess";
122
+ return "failed";
123
+ }
124
+
125
+ /** Apply independently loaded metrics. A failed source stays visibly unknown, saying why on its own row
126
+ * ([failures]: metric key → that row's status); it never becomes a misleading zero. */
127
+ export function withMetrics(areas: ManageArea[], metrics: Record<string, Metric | undefined>, failures: Map<string, string>): ManageArea[] {
116
128
  return areas.map(area => {
117
129
  const metric = metrics[area.metricKey];
118
- if (!metric || unavailable.has(area.metricKey)) {
119
- return { ...area, metricText: "—", statusText: "Couldn’t load summary", unavailable: true };
130
+ const failure = failures.get(area.metricKey);
131
+ if (!metric || failure !== undefined) {
132
+ return { ...area, metricText: "—", statusText: failure ?? "", unavailable: true };
120
133
  }
121
134
  return {
122
135
  ...area,