@wix/himalaya-cli 0.941.0 → 0.942.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.
package/dist/cli.mjs CHANGED
@@ -18541,6 +18541,7 @@ function shouldRevealError(args) {
18541
18541
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
18542
18542
  var init_validation = __esm({
18543
18543
  "../../core/ts/src/validation.ts"() {
18544
+ "use strict";
18544
18545
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
18545
18546
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
18546
18547
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -21964,6 +21965,7 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
21964
21965
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
21965
21966
  var init_worker_subtree = __esm({
21966
21967
  "../../core/ts/src/worker-subtree.ts"() {
21968
+ "use strict";
21967
21969
  init_worker_subtree_safe_types_generated();
21968
21970
  WORKER_SUBTREE_CEILINGS = Object.freeze({
21969
21971
  maxEncodedBytes: 65536,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.941.0",
3
+ "version": "0.942.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": [
@@ -64,5 +64,5 @@
64
64
  "artifactId": "himalaya-cli"
65
65
  }
66
66
  },
67
- "falconPackageHash": "f6ef221b31d3e6cb04fc7f80011d74de1d766b5da1d85c61f7a68530"
67
+ "falconPackageHash": "8700bed97461c826fd04a33c50c3cc26d349fdeaad788594d2f598c1"
68
68
  }
@@ -27533,7 +27533,22 @@ const SCREENS = runtimeSetting({
27533
27533
  }
27534
27534
  },
27535
27535
  "modifiers": {
27536
- "hiddenPath": "$service:himiBrandedCoupons.notMock"
27536
+ "hiddenPath": "$service:himiBrandedCoupons.notMockEmpty"
27537
+ }
27538
+ },
27539
+ {
27540
+ "id": "himi_coupons.unavailable_banner",
27541
+ "body": {
27542
+ "package": "himalaya",
27543
+ "type": "Banner",
27544
+ "props": {
27545
+ "severity": "warning",
27546
+ "title": "Sample content",
27547
+ "message": "Sample content — this site's offers couldn't be loaded."
27548
+ }
27549
+ },
27550
+ "modifiers": {
27551
+ "hiddenPath": "$service:himiBrandedCoupons.notMockUnavailable"
27537
27552
  }
27538
27553
  },
27539
27554
  {
@@ -29751,7 +29766,22 @@ const SCREENS = runtimeSetting({
29751
29766
  }
29752
29767
  },
29753
29768
  "modifiers": {
29754
- "hiddenPath": "$service:himiBrandedNotifications.notMock"
29769
+ "hiddenPath": "$service:himiBrandedNotifications.notMockEmpty"
29770
+ }
29771
+ },
29772
+ {
29773
+ "id": "himi_notifications.unavailable_banner",
29774
+ "body": {
29775
+ "package": "himalaya",
29776
+ "type": "Banner",
29777
+ "props": {
29778
+ "severity": "warning",
29779
+ "title": "Sample content",
29780
+ "message": "Sample content — your notifications couldn't be loaded."
29781
+ }
29782
+ },
29783
+ "modifiers": {
29784
+ "hiddenPath": "$service:himiBrandedNotifications.notMockUnavailable"
29755
29785
  }
29756
29786
  },
29757
29787
  {
@@ -31913,7 +31943,22 @@ const SCREENS = runtimeSetting({
31913
31943
  }
31914
31944
  },
31915
31945
  "modifiers": {
31916
- "hiddenPath": "$service:himiBrandedLoyalty.notMock"
31946
+ "hiddenPath": "$service:himiBrandedLoyalty.notMockEmpty"
31947
+ }
31948
+ },
31949
+ {
31950
+ "id": "himi_loyalty.unavailable_banner",
31951
+ "body": {
31952
+ "package": "himalaya",
31953
+ "type": "Banner",
31954
+ "props": {
31955
+ "severity": "warning",
31956
+ "title": "Sample content",
31957
+ "message": "Sample content — the rewards program couldn't be loaded."
31958
+ }
31959
+ },
31960
+ "modifiers": {
31961
+ "hiddenPath": "$service:himiBrandedLoyalty.notMockUnavailable"
31917
31962
  }
31918
31963
  },
31919
31964
  {
@@ -34154,7 +34199,22 @@ const SCREENS = runtimeSetting({
34154
34199
  }
34155
34200
  },
34156
34201
  "modifiers": {
34157
- "hiddenPath": "$service:himiBrandedReviews.notMock"
34202
+ "hiddenPath": "$service:himiBrandedReviews.notMockEmpty"
34203
+ }
34204
+ },
34205
+ {
34206
+ "id": "himi_reviews.unavailable_banner",
34207
+ "body": {
34208
+ "package": "himalaya",
34209
+ "type": "Banner",
34210
+ "props": {
34211
+ "severity": "warning",
34212
+ "title": "Sample content",
34213
+ "message": "Sample content — this business's reviews couldn't be loaded."
34214
+ }
34215
+ },
34216
+ "modifiers": {
34217
+ "hiddenPath": "$service:himiBrandedReviews.notMockUnavailable"
34158
34218
  }
34159
34219
  },
34160
34220
  {
@@ -39102,7 +39162,22 @@ const SCREENS = runtimeSetting({
39102
39162
  }
39103
39163
  },
39104
39164
  "modifiers": {
39105
- "hiddenPath": "$service:himiBrandedReservations.notMock"
39165
+ "hiddenPath": "$service:himiBrandedReservations.notMockEmpty"
39166
+ }
39167
+ },
39168
+ {
39169
+ "id": "himi_reservations.unavailable_banner",
39170
+ "body": {
39171
+ "package": "himalaya",
39172
+ "type": "Banner",
39173
+ "props": {
39174
+ "severity": "warning",
39175
+ "title": "Sample content",
39176
+ "message": "Sample content — your reservations couldn't be loaded."
39177
+ }
39178
+ },
39179
+ "modifiers": {
39180
+ "hiddenPath": "$service:himiBrandedReservations.notMockUnavailable"
39106
39181
  }
39107
39182
  },
39108
39183
  {
@@ -39241,7 +39316,22 @@ const SCREENS = runtimeSetting({
39241
39316
  }
39242
39317
  },
39243
39318
  "modifiers": {
39244
- "hiddenPath": "$service:himiBrandedInvoices.notMock"
39319
+ "hiddenPath": "$service:himiBrandedInvoices.notMockEmpty"
39320
+ }
39321
+ },
39322
+ {
39323
+ "id": "himi_invoices.unavailable_banner",
39324
+ "body": {
39325
+ "package": "himalaya",
39326
+ "type": "Banner",
39327
+ "props": {
39328
+ "severity": "warning",
39329
+ "title": "Sample content",
39330
+ "message": "Sample content — your invoices couldn't be loaded."
39331
+ }
39332
+ },
39333
+ "modifiers": {
39334
+ "hiddenPath": "$service:himiBrandedInvoices.notMockUnavailable"
39245
39335
  }
39246
39336
  },
39247
39337
  {
@@ -41400,7 +41490,22 @@ const SCREENS = runtimeSetting({
41400
41490
  }
41401
41491
  },
41402
41492
  "modifiers": {
41403
- "hiddenPath": "$service:himiBrandedBadges.notMock"
41493
+ "hiddenPath": "$service:himiBrandedBadges.notMockEmpty"
41494
+ }
41495
+ },
41496
+ {
41497
+ "id": "himi_badges.unavailable_banner",
41498
+ "body": {
41499
+ "package": "himalaya",
41500
+ "type": "Banner",
41501
+ "props": {
41502
+ "severity": "warning",
41503
+ "title": "Sample content",
41504
+ "message": "Sample content — this community's badges couldn't be loaded."
41505
+ }
41506
+ },
41507
+ "modifiers": {
41508
+ "hiddenPath": "$service:himiBrandedBadges.notMockUnavailable"
41404
41509
  }
41405
41510
  },
41406
41511
  {
@@ -43641,7 +43746,22 @@ const SCREENS = runtimeSetting({
43641
43746
  }
43642
43747
  },
43643
43748
  "modifiers": {
43644
- "hiddenPath": "$service:himiBrandedNotifSettings.notMock"
43749
+ "hiddenPath": "$service:himiBrandedNotifSettings.notMockEmpty"
43750
+ }
43751
+ },
43752
+ {
43753
+ "id": "himi_notif_settings.unavailable_banner",
43754
+ "body": {
43755
+ "package": "himalaya",
43756
+ "type": "Banner",
43757
+ "props": {
43758
+ "severity": "warning",
43759
+ "title": "Sample content",
43760
+ "message": "Sample content — your notification settings couldn't be loaded."
43761
+ }
43762
+ },
43763
+ "modifiers": {
43764
+ "hiddenPath": "$service:himiBrandedNotifSettings.notMockUnavailable"
43645
43765
  }
43646
43766
  },
43647
43767
  {
@@ -55810,7 +55930,22 @@ const SCREENS = runtimeSetting({
55810
55930
  }
55811
55931
  },
55812
55932
  "modifiers": {
55813
- "hiddenPath": "$service:himiBrandedDonations.notMock"
55933
+ "hiddenPath": "$service:himiBrandedDonations.notMockEmpty"
55934
+ }
55935
+ },
55936
+ {
55937
+ "id": "himi_donations.unavailable_banner",
55938
+ "body": {
55939
+ "package": "himalaya",
55940
+ "type": "Banner",
55941
+ "props": {
55942
+ "severity": "warning",
55943
+ "title": "Sample content",
55944
+ "message": "Sample content — this site's donation campaigns couldn't be loaded."
55945
+ }
55946
+ },
55947
+ "modifiers": {
55948
+ "hiddenPath": "$service:himiBrandedDonations.notMockUnavailable"
55814
55949
  }
55815
55950
  },
55816
55951
  {
@@ -58051,7 +58186,22 @@ const SCREENS = runtimeSetting({
58051
58186
  }
58052
58187
  },
58053
58188
  "modifiers": {
58054
- "hiddenPath": "$service:himiBrandedGiftCards.notMock"
58189
+ "hiddenPath": "$service:himiBrandedGiftCards.notMockEmpty"
58190
+ }
58191
+ },
58192
+ {
58193
+ "id": "himi_gift_cards.unavailable_banner",
58194
+ "body": {
58195
+ "package": "himalaya",
58196
+ "type": "Banner",
58197
+ "props": {
58198
+ "severity": "warning",
58199
+ "title": "Sample content",
58200
+ "message": "Sample content — your gift cards couldn't be loaded."
58201
+ }
58202
+ },
58203
+ "modifiers": {
58204
+ "hiddenPath": "$service:himiBrandedGiftCards.notMockUnavailable"
58055
58205
  }
58056
58206
  },
58057
58207
  {
@@ -60292,7 +60442,22 @@ const SCREENS = runtimeSetting({
60292
60442
  }
60293
60443
  },
60294
60444
  "modifiers": {
60295
- "hiddenPath": "$service:himiBrandedVideos.notMock"
60445
+ "hiddenPath": "$service:himiBrandedVideos.notMockEmpty"
60446
+ }
60447
+ },
60448
+ {
60449
+ "id": "himi_videos.unavailable_banner",
60450
+ "body": {
60451
+ "package": "himalaya",
60452
+ "type": "Banner",
60453
+ "props": {
60454
+ "severity": "warning",
60455
+ "title": "Sample content",
60456
+ "message": "Sample content — this site's video channels couldn't be loaded."
60457
+ }
60458
+ },
60459
+ "modifiers": {
60460
+ "hiddenPath": "$service:himiBrandedVideos.notMockUnavailable"
60296
60461
  }
60297
60462
  },
60298
60463
  {
@@ -65307,7 +65472,22 @@ const SCREENS = runtimeSetting({
65307
65472
  }
65308
65473
  },
65309
65474
  "modifiers": {
65310
- "hiddenPath": "$service:himiBrandedForms.notMock"
65475
+ "hiddenPath": "$service:himiBrandedForms.notMockEmpty"
65476
+ }
65477
+ },
65478
+ {
65479
+ "id": "himi_forms.unavailable_banner",
65480
+ "body": {
65481
+ "package": "himalaya",
65482
+ "type": "Banner",
65483
+ "props": {
65484
+ "severity": "warning",
65485
+ "title": "Sample content",
65486
+ "message": "Sample content — this site's forms couldn't be loaded."
65487
+ }
65488
+ },
65489
+ "modifiers": {
65490
+ "hiddenPath": "$service:himiBrandedForms.notMockUnavailable"
65311
65491
  }
65312
65492
  },
65313
65493
  {
@@ -67452,7 +67632,22 @@ const SCREENS = runtimeSetting({
67452
67632
  }
67453
67633
  },
67454
67634
  "modifiers": {
67455
- "hiddenPath": "$service:himiBrandedGalleries.notMock"
67635
+ "hiddenPath": "$service:himiBrandedGalleries.notMockEmpty"
67636
+ }
67637
+ },
67638
+ {
67639
+ "id": "himi_galleries.unavailable_banner",
67640
+ "body": {
67641
+ "package": "himalaya",
67642
+ "type": "Banner",
67643
+ "props": {
67644
+ "severity": "warning",
67645
+ "title": "Sample content",
67646
+ "message": "Sample content — this site's galleries couldn't be loaded."
67647
+ }
67648
+ },
67649
+ "modifiers": {
67650
+ "hiddenPath": "$service:himiBrandedGalleries.notMockUnavailable"
67456
67651
  }
67457
67652
  },
67458
67653
  {
@@ -69969,7 +70164,22 @@ const SCREENS = runtimeSetting({
69969
70164
  }
69970
70165
  },
69971
70166
  "modifiers": {
69972
- "hiddenPath": "$service:himiBrandedInbox.notMock"
70167
+ "hiddenPath": "$service:himiBrandedInbox.notMockEmpty"
70168
+ }
70169
+ },
70170
+ {
70171
+ "id": "himi_inbox.unavailable_banner",
70172
+ "body": {
70173
+ "package": "himalaya",
70174
+ "type": "Banner",
70175
+ "props": {
70176
+ "severity": "warning",
70177
+ "title": "Sample content",
70178
+ "message": "Sample content — your messages couldn't be loaded."
70179
+ }
70180
+ },
70181
+ "modifiers": {
70182
+ "hiddenPath": "$service:himiBrandedInbox.notMockUnavailable"
69973
70183
  }
69974
70184
  },
69975
70185
  {
@@ -13,7 +13,11 @@ interface Row { id: string; title: string; subtitle: string }
13
13
  interface S {
14
14
  status: "loading" | "ready";
15
15
  badges: Row[]; emptyText: string;
16
- isMock: boolean; notMock: boolean; mockBanner: string;
16
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
17
+ * shows only when the site really has none, and the unavailable one only when the
18
+ * query failed. A failed query must never render the sentence that claims the
19
+ * customer has no content. */
20
+ notMockEmpty: boolean; notMockUnavailable: boolean;
17
21
  }
18
22
 
19
23
  function norm(b: WixBadge): Row {
@@ -21,7 +25,6 @@ function norm(b: WixBadge): Row {
21
25
  return { id: b.id || b.slug || "badge", title, subtitle: (b.description || "").trim() };
22
26
  }
23
27
 
24
- const MOCK_BANNER = "Sample content — this community has no badges yet.";
25
28
  const MOCK: Row[] = [
26
29
  { id: "sample-founder", title: "Founding Member", subtitle: "One of the first to join the community." },
27
30
  { id: "sample-top", title: "Top Contributor", subtitle: "Recognized for consistently helpful posts." },
@@ -29,21 +32,21 @@ const MOCK: Row[] = [
29
32
  { id: "sample-vip", title: "VIP", subtitle: "A valued long-standing customer." },
30
33
  ];
31
34
 
32
- function mockState(s: S): S {
33
- return { ...s, status: "ready", badges: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
35
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
36
+ return { ...s, status: "ready", badges: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
34
37
  }
35
38
 
36
39
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedBadges", {
37
- state: { status: "loading", badges: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
40
+ state: { status: "loading", badges: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
38
41
  actions: {
39
42
  load: async (s) => {
40
43
  try {
41
44
  const r = await queryBadges({ query: { cursorPaging: { limit: 50 } } });
42
45
  const badges = (r.badges ?? []).map(norm);
43
- if (badges.length) return { ...s, status: "ready", badges, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
44
- return mockState(s);
46
+ if (badges.length) return { ...s, status: "ready", badges, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
47
+ return mockState(s, "empty");
45
48
  } catch {
46
- return mockState(s);
49
+ return mockState(s, "unavailable");
47
50
  }
48
51
  },
49
52
  },
@@ -11,7 +11,11 @@ interface Row { id: string; name: string; subtitle: string }
11
11
  interface S {
12
12
  status: "loading" | "ready";
13
13
  coupons: Row[]; emptyText: string;
14
- isMock: boolean; notMock: boolean; mockBanner: string;
14
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
15
+ * shows only when the site really has none, and the unavailable one only when the
16
+ * query failed. A failed query must never render the sentence that claims the
17
+ * customer has no content. */
18
+ notMockEmpty: boolean; notMockUnavailable: boolean;
15
19
  }
16
20
 
17
21
  function norm(c: WixCoupon): Row {
@@ -19,7 +23,6 @@ function norm(c: WixCoupon): Row {
19
23
  return { id: cc.id || cc.code || "coupon", name: cc.name || cc.code || "Coupon", subtitle: cc.code ? `Code: ${cc.code}` : "" };
20
24
  }
21
25
 
22
- const MOCK_BANNER = "Sample content — your site has no coupons yet.";
23
26
  const MOCK: Row[] = [
24
27
  { id: "sample-welcome15", name: "Welcome — 15% off your first order", subtitle: "Code: WELCOME15 · ends Aug 31" },
25
28
  { id: "sample-freeship", name: "Free shipping", subtitle: "Code: FREESHIP · orders over $50" },
@@ -27,17 +30,25 @@ const MOCK: Row[] = [
27
30
  { id: "sample-loyal10", name: "Loyalty reward — $10 off", subtitle: "Code: LOYAL10 · members only" },
28
31
  ];
29
32
 
33
+ /** The sample fallback, shared by both arms so neither can drift from the other — this
34
+ * worker inlined it twice, which is how a rename reached the constants and missed the
35
+ * uses. */
36
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
37
+ return { ...s, status: "ready", coupons: MOCK, emptyText: "", isMock: true, notMock: false,
38
+ notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable" };
39
+ }
40
+
30
41
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedCoupons", {
31
- state: { status: "loading", coupons: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
42
+ state: { status: "loading", coupons: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
32
43
  actions: {
33
44
  load: async (s) => {
34
45
  try {
35
46
  const r = await queryCoupons({ paging: { limit: 30 } } as never);
36
47
  const coupons = (((r as unknown as { coupons?: WixCoupon[] }).coupons) ?? []).map(norm);
37
- if (coupons.length) return { ...s, status: "ready", coupons, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
38
- return { ...s, status: "ready", coupons: MOCK, emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
48
+ if (coupons.length) return { ...s, status: "ready", coupons, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
49
+ return mockState(s, "empty");
39
50
  } catch {
40
- return { ...s, status: "ready", coupons: MOCK, emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
51
+ return mockState(s, "unavailable");
41
52
  }
42
53
  },
43
54
  },
@@ -11,7 +11,11 @@ interface Row { id: string; title: string; subtitle: string }
11
11
  interface S {
12
12
  status: "loading" | "ready";
13
13
  campaigns: Row[]; emptyText: string;
14
- isMock: boolean; notMock: boolean; mockBanner: string;
14
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
15
+ * shows only when the site really has none, and the unavailable one only when the
16
+ * query failed. A failed query must never render the sentence that claims the
17
+ * customer has no content. */
18
+ notMockEmpty: boolean; notMockUnavailable: boolean;
15
19
  }
16
20
 
17
21
  const STATUS_LABEL: Record<string, string> = {
@@ -30,28 +34,27 @@ function norm(c: WixDonationCampaign): Row {
30
34
  return { id: c.id || "campaign", title: (c.name || "Campaign").trim() || "Campaign", subtitle: subtitleFor(c) };
31
35
  }
32
36
 
33
- const MOCK_BANNER = "Sample content — this site has no donation campaigns yet.";
34
37
  const MOCK: Row[] = [
35
38
  { id: "sample-reef", title: "Save the Coral Reef", subtitle: "Goal: $10,000 · Active" },
36
39
  { id: "sample-school", title: "Build a Village School", subtitle: "Goal: $25,000 · Active" },
37
40
  { id: "sample-relief", title: "Winter Relief Fund", subtitle: "Goal: $5,000 · Goal reached" },
38
41
  ];
39
42
 
40
- function mockState(s: S): S {
41
- return { ...s, status: "ready", campaigns: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
43
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
44
+ return { ...s, status: "ready", campaigns: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
42
45
  }
43
46
 
44
47
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedDonations", {
45
- state: { status: "loading", campaigns: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
48
+ state: { status: "loading", campaigns: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
46
49
  actions: {
47
50
  load: async (s) => {
48
51
  try {
49
52
  const r = await queryDonationCampaigns({ query: { cursorPaging: { limit: 20 } } });
50
53
  const campaigns = (r.donationCampaigns ?? []).filter((c) => c.archived !== true).map(norm);
51
- if (campaigns.length) return { ...s, status: "ready", campaigns, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
52
- return mockState(s);
54
+ if (campaigns.length) return { ...s, status: "ready", campaigns, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
55
+ return mockState(s, "empty");
53
56
  } catch {
54
- return mockState(s);
57
+ return mockState(s, "unavailable");
55
58
  }
56
59
  },
57
60
  },
@@ -8,14 +8,17 @@
8
8
  // there would state "this site has none yet", which the app never established.
9
9
  // Real forms always win the moment any exist.
10
10
  import { defineService } from "../_shared/collection-service.js";
11
- import { copyParam } from "../_shared/himi-copy.js";
12
11
  import { queryForms, type WixForm } from "@himalaya/wix-forms";
13
12
 
14
13
  interface Row { id: string; title: string; subtitle: string }
15
14
  interface S {
16
15
  status: "loading" | "ready" | "error";
17
16
  forms: Row[]; emptyText: string;
18
- isMock: boolean; notMock: boolean; mockBanner: string;
17
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
18
+ * shows only when the site really has none, and the unavailable one only when the
19
+ * query failed. A failed query must never render the sentence that claims the
20
+ * customer has no content. */
21
+ notMockEmpty: boolean; notMockUnavailable: boolean;
19
22
  }
20
23
 
21
24
  const STATUS_LABEL: Record<string, string> = { PUBLISHED: "Published", DRAFT: "Draft" };
@@ -41,32 +44,30 @@ function norm(f: WixForm): Row {
41
44
  * which keeps these verticals showing labelled sample content rather than a blank screen.
42
45
  * So what was wrong was never the samples — it was claiming to know something the app had
43
46
  * not established. */
44
- const MOCK_BANNER_EMPTY = "Sample content — this site has no forms yet.";
45
- const MOCK_BANNER_UNAVAILABLE = "Sample content — this site's forms couldn't be loaded.";
46
47
  const MOCK: Row[] = [
47
48
  { id: "sample-contact", title: "Contact Us", subtitle: "4 fields · Published" },
48
49
  { id: "sample-subscribe", title: "Newsletter Signup", subtitle: "2 fields · Published" },
49
50
  { id: "sample-feedback", title: "Feedback", subtitle: "5 fields · Published" },
50
51
  ];
51
52
 
52
- function mockState(s: S, banner: string): S {
53
- return { ...s, status: "ready", forms: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: banner };
53
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
54
+ return { ...s, status: "ready", forms: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
54
55
  }
55
56
 
56
57
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedForms", {
57
- state: { status: "loading", forms: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
58
+ state: { status: "loading", forms: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
58
59
  actions: {
59
60
  load: async (s) => {
60
61
  try {
61
62
  const r = await queryForms({ filter: { namespace: FORMS_NAMESPACE }, paging: { limit: 50 } });
62
63
  const forms = (r.forms ?? []).map(norm);
63
- if (forms.length) return { ...s, status: "ready", forms, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
64
- return mockState(s, copyParam("forms.emptyBanner", MOCK_BANNER_EMPTY));
64
+ if (forms.length) return { ...s, status: "ready", forms, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
65
+ return mockState(s, "empty");
65
66
  } catch {
66
67
  // The samples still render — a blank screen helps nobody and this vertical
67
68
  // deliberately ships a fallback — but the banner no longer asserts the site is
68
69
  // empty, because a failed query established no such thing.
69
- return mockState(s, copyParam("forms.unavailableBanner", MOCK_BANNER_UNAVAILABLE));
70
+ return mockState(s, "unavailable");
70
71
  }
71
72
  },
72
73
  },
@@ -25,7 +25,11 @@ interface Row { id: string; title: string; subtitle: string }
25
25
  interface S {
26
26
  status: "loading" | "ready";
27
27
  galleries: Row[]; emptyText: string;
28
- isMock: boolean; notMock: boolean; mockBanner: string;
28
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
29
+ * shows only when the site really has none, and the unavailable one only when the
30
+ * query failed. A failed query must never render the sentence that claims the
31
+ * customer has no content. */
32
+ notMockEmpty: boolean; notMockUnavailable: boolean;
29
33
  }
30
34
 
31
35
  function norm(g: RawGallery, i: number): Row {
@@ -35,24 +39,23 @@ function norm(g: RawGallery, i: number): Row {
35
39
  return { id: g.id || `gallery-${i}`, title, subtitle };
36
40
  }
37
41
 
38
- const MOCK_BANNER = "Sample content — this site has no galleries yet.";
39
42
  const MOCK: Row[] = [
40
43
  { id: "sample-work", title: "Our Work", subtitle: "24 photos" },
41
44
  { id: "sample-events", title: "Events", subtitle: "48 photos" },
42
45
  { id: "sample-team", title: "The Team", subtitle: "9 photos" },
43
46
  ];
44
47
 
45
- function mockState(s: S): S {
46
- return { ...s, status: "ready", galleries: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
48
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
49
+ return { ...s, status: "ready", galleries: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
47
50
  }
48
51
 
49
52
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedGalleries", {
50
- state: { status: "loading", galleries: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
53
+ state: { status: "loading", galleries: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
51
54
  actions: {
52
55
  load: async (s) => {
53
56
  try {
54
57
  const api = wixApi();
55
- if (!api) return mockState(s);
58
+ if (!api) return mockState(s, "unavailable");
56
59
  const res = (await api.call("GET", GALLERIES_URL)) as
57
60
  { galleries?: RawGallery[] } | RawGallery[] | { data?: { galleries?: RawGallery[] } };
58
61
  // Accept {galleries:[...]}, a top-level array, or a {data:{galleries}} envelope.
@@ -61,10 +64,10 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedGalleri
61
64
  (Array.isArray(res) ? (res as RawGallery[]) : undefined) ??
62
65
  (res as { data?: { galleries?: RawGallery[] } })?.data?.galleries ?? [];
63
66
  const galleries = raw.map(norm);
64
- if (galleries.length) return { ...s, status: "ready", galleries, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
65
- return mockState(s);
67
+ if (galleries.length) return { ...s, status: "ready", galleries, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
68
+ return mockState(s, "empty");
66
69
  } catch {
67
- return mockState(s);
70
+ return mockState(s, "unavailable");
68
71
  }
69
72
  },
70
73
  },
@@ -11,7 +11,11 @@ interface Row { id: string; title: string; subtitle: string }
11
11
  interface S {
12
12
  status: "loading" | "ready";
13
13
  cards: Row[]; emptyText: string;
14
- isMock: boolean; notMock: boolean; mockBanner: string;
14
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
15
+ * shows only when the site really has none, and the unavailable one only when the
16
+ * query failed. A failed query must never render the sentence that claims the
17
+ * customer has no content. */
18
+ notMockEmpty: boolean; notMockUnavailable: boolean;
15
19
  }
16
20
 
17
21
  const CURRENCY_SYMBOL: Record<string, string> = { USD: "$", EUR: "€", GBP: "£", ILS: "₪", AUD: "A$", CAD: "C$" };
@@ -34,28 +38,27 @@ function norm(c: WixGiftCard): Row {
34
38
  return { id: c.id || c.code || "gift-card", title, subtitle };
35
39
  }
36
40
 
37
- const MOCK_BANNER = "Sample content — you have no gift cards yet.";
38
41
  const MOCK: Row[] = [
39
42
  { id: "sample-1", title: "Gift card ····4821", subtitle: "Balance $50.00" },
40
43
  { id: "sample-2", title: "Gift card ····9075", subtitle: "Balance $18.50" },
41
44
  { id: "sample-3", title: "Gift card ····3312", subtitle: "Balance $0.00" },
42
45
  ];
43
46
 
44
- function mockState(s: S): S {
45
- return { ...s, status: "ready", cards: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
47
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
48
+ return { ...s, status: "ready", cards: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
46
49
  }
47
50
 
48
51
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedGiftCards", {
49
- state: { status: "loading", cards: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
52
+ state: { status: "loading", cards: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
50
53
  actions: {
51
54
  load: async (s) => {
52
55
  try {
53
56
  const r = await queryGiftCards({ query: { cursorPaging: { limit: 20 } } });
54
57
  const cards = (r.giftCards ?? []).map(norm);
55
- if (cards.length) return { ...s, status: "ready", cards, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
56
- return mockState(s);
58
+ if (cards.length) return { ...s, status: "ready", cards, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
59
+ return mockState(s, "empty");
57
60
  } catch {
58
- return mockState(s);
61
+ return mockState(s, "unavailable");
59
62
  }
60
63
  },
61
64
  },
@@ -13,7 +13,11 @@ interface Row { id: string; title: string; subtitle: string }
13
13
  interface S {
14
14
  status: "loading" | "ready";
15
15
  conversations: Row[]; emptyText: string;
16
- isMock: boolean; notMock: boolean; mockBanner: string;
16
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
17
+ * shows only when the site really has none, and the unavailable one only when the
18
+ * query failed. A failed query must never render the sentence that claims the
19
+ * customer has no content. */
20
+ notMockEmpty: boolean; notMockUnavailable: boolean;
17
21
  }
18
22
 
19
23
  // businessDisplayData / participantDisplayData are open maps; pull a human name from
@@ -33,28 +37,27 @@ function norm(c: WixConversation, i: number): Row {
33
37
  return { id: c.id || `conv-${i}`, title, subtitle: preview };
34
38
  }
35
39
 
36
- const MOCK_BANNER = "Sample content — this site has no messages yet.";
37
40
  const MOCK: Row[] = [
38
41
  { id: "sample-orders", title: "Orders", subtitle: "Your order is on its way — track it here." },
39
42
  { id: "sample-support", title: "Support", subtitle: "Hi! Let us know how we can help." },
40
43
  { id: "sample-team", title: "The Team", subtitle: "Welcome 👋 Thanks for joining us!" },
41
44
  ];
42
45
 
43
- function mockState(s: S): S {
44
- return { ...s, status: "ready", conversations: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
46
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
47
+ return { ...s, status: "ready", conversations: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
45
48
  }
46
49
 
47
50
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedInbox", {
48
- state: { status: "loading", conversations: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
51
+ state: { status: "loading", conversations: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
49
52
  actions: {
50
53
  load: async (s) => {
51
54
  try {
52
55
  const r = await listConversations({ paging: { limit: 50 } });
53
56
  const conversations = (r.conversations ?? []).map(norm);
54
- if (conversations.length) return { ...s, status: "ready", conversations, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
55
- return mockState(s);
57
+ if (conversations.length) return { ...s, status: "ready", conversations, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
58
+ return mockState(s, "empty");
56
59
  } catch {
57
- return mockState(s);
60
+ return mockState(s, "unavailable");
58
61
  }
59
62
  },
60
63
  },
@@ -11,7 +11,11 @@ interface Row { id: string; title: string; subtitle: string }
11
11
  interface S {
12
12
  status: "loading" | "ready";
13
13
  invoices: Row[]; emptyText: string;
14
- isMock: boolean; notMock: boolean; mockBanner: string;
14
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
15
+ * shows only when the site really has none, and the unavailable one only when the
16
+ * query failed. A failed query must never render the sentence that claims the
17
+ * customer has no content. */
18
+ notMockEmpty: boolean; notMockUnavailable: boolean;
15
19
  }
16
20
 
17
21
  const STATUS_LABEL: Record<string, string> = {
@@ -35,28 +39,27 @@ function norm(v: WixInvoice): Row {
35
39
  return { id: v.id || "invoice", title, subtitle };
36
40
  }
37
41
 
38
- const MOCK_BANNER = "Sample content — you have no invoices yet.";
39
42
  const MOCK: Row[] = [
40
43
  { id: "sample-1024", title: "Invoice #1024", subtitle: "Paid · $120.00" },
41
44
  { id: "sample-1031", title: "Invoice #1031", subtitle: "Sent · $80.00" },
42
45
  { id: "sample-1042", title: "Invoice #1042", subtitle: "Overdue · $45.00" },
43
46
  ];
44
47
 
45
- function mockState(s: S): S {
46
- return { ...s, status: "ready", invoices: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
48
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
49
+ return { ...s, status: "ready", invoices: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
47
50
  }
48
51
 
49
52
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedInvoices", {
50
- state: { status: "loading", invoices: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
53
+ state: { status: "loading", invoices: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
51
54
  actions: {
52
55
  load: async (s) => {
53
56
  try {
54
57
  const r = await queryInvoices({ query: { paging: { limit: 20 } } });
55
58
  const invoices = (r.invoices ?? []).filter((v) => v.archived !== true).map(norm);
56
- if (invoices.length) return { ...s, status: "ready", invoices, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
57
- return mockState(s);
59
+ if (invoices.length) return { ...s, status: "ready", invoices, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
60
+ return mockState(s, "empty");
58
61
  } catch {
59
- return mockState(s);
62
+ return mockState(s, "unavailable");
60
63
  }
61
64
  },
62
65
  },
@@ -13,7 +13,11 @@ interface S {
13
13
  status: "loading" | "ready";
14
14
  programName: string; unit: string;
15
15
  rewards: Row[]; emptyText: string;
16
- isMock: boolean; notMock: boolean; mockBanner: string;
16
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
17
+ * shows only when the site really has none, and the unavailable one only when the
18
+ * query failed. A failed query must never render the sentence that claims the
19
+ * customer has no content. */
20
+ notMockEmpty: boolean; notMockUnavailable: boolean;
17
21
  }
18
22
 
19
23
  /** "500 points" / "Free" from a reward's requiredPoints. */
@@ -28,7 +32,6 @@ function norm(r: WixLoyaltyReward, unit: string): Row {
28
32
  return { id: r.id || r.name || "reward", name: r.name || "Reward", subtitle: pointsLabel(r, unit) };
29
33
  }
30
34
 
31
- const MOCK_BANNER = "Sample content — this business hasn't set up a rewards program yet.";
32
35
  const MOCK: Row[] = [
33
36
  { id: "sample-5off", name: "$5 off your next order", subtitle: "500 points" },
34
37
  { id: "sample-ship", name: "Free shipping", subtitle: "300 points" },
@@ -36,12 +39,12 @@ const MOCK: Row[] = [
36
39
  { id: "sample-gift", name: "Free welcome gift", subtitle: "1,000 points" },
37
40
  ];
38
41
 
39
- function mockState(s: S): S {
40
- return { ...s, status: "ready", programName: "Rewards", unit: "points", rewards: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
42
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
43
+ return { ...s, status: "ready", programName: "Rewards", unit: "points", rewards: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
41
44
  }
42
45
 
43
46
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedLoyalty", {
44
- state: { status: "loading", programName: "Rewards", unit: "points", rewards: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
47
+ state: { status: "loading", programName: "Rewards", unit: "points", rewards: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
45
48
  actions: {
46
49
  load: async (s) => {
47
50
  try {
@@ -51,10 +54,10 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedLoyalty
51
54
  const programName = (p?.name || "Rewards").trim() || "Rewards";
52
55
  const r = await listLoyaltyRewards();
53
56
  const rewards = (r.rewards ?? []).filter((x) => x.active !== false).map((x) => norm(x, unit));
54
- if (rewards.length) return { ...s, status: "ready", programName, unit, rewards, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
55
- return mockState(s);
57
+ if (rewards.length) return { ...s, status: "ready", programName, unit, rewards, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
58
+ return mockState(s, "empty");
56
59
  } catch {
57
- return mockState(s);
60
+ return mockState(s, "unavailable");
58
61
  }
59
62
  },
60
63
  },
@@ -25,7 +25,12 @@ interface Row {
25
25
  interface S {
26
26
  status: "loading" | "ready";
27
27
  items: Row[]; emptyText: string;
28
- isMock: boolean; notMock: boolean; mockBanner: string;
28
+ isMock: boolean; notMock: boolean;
29
+ /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
30
+ * shows only when the site really has none, and the unavailable one only when the
31
+ * query failed. A failed query must never render the sentence that claims the
32
+ * customer has no content. */
33
+ notMockEmpty: boolean; notMockUnavailable: boolean;
29
34
  }
30
35
 
31
36
  /** Human "2h ago" / "Yesterday" from an ISO date. */
@@ -79,7 +84,6 @@ function norm(n: MfvNotification): Row {
79
84
  };
80
85
  }
81
86
 
82
- const MOCK_BANNER = "Sample content — you have no notifications yet.";
83
87
  const MOCK: Row[] = [
84
88
  { id: "sample-like", name: "Maya Levin liked your post", subtitle: "“Weekend pop-up” · 2h ago", unread: true, unreadDot: "●", photo: "", initial: "M", dest: "" },
85
89
  { id: "sample-comment", name: "Daniel Cohen commented on your post", subtitle: "“Love this — count me in!” · 5h ago", unread: true, unreadDot: "●", photo: "", initial: "D", dest: "" },
@@ -88,8 +92,9 @@ const MOCK: Row[] = [
88
92
  { id: "sample-offer", name: "New offer just for you", subtitle: "15% off your next order · 3d ago", unread: false, unreadDot: "", photo: "", initial: "%", dest: "" },
89
93
  ];
90
94
 
91
- function mockState(s: S): S {
92
- return { ...s, status: "ready", items: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
95
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
96
+ return { ...s, status: "ready", items: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false,
97
+ notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable" };
93
98
  }
94
99
 
95
100
  const svc = defineService<S, {
@@ -97,15 +102,15 @@ const svc = defineService<S, {
97
102
  markOneRead: (s: S, id: string) => Promise<S>;
98
103
  markAll: (s: S) => Promise<S>;
99
104
  }>("himiBrandedNotifications", {
100
- state: { status: "loading", items: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
105
+ state: { status: "loading", items: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true },
101
106
  actions: {
102
107
  load: async (s) => {
103
108
  try {
104
109
  const items = (await queryNotifications(25)).map(norm);
105
- if (items.length) return { ...s, status: "ready", items, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
106
- return mockState(s);
110
+ if (items.length) return { ...s, status: "ready", items, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true };
111
+ return mockState(s, "empty");
107
112
  } catch {
108
- return mockState(s);
113
+ return mockState(s, "unavailable");
109
114
  }
110
115
  },
111
116
  // Mark one read; optimistically clear its dot. Real write is best-effort and
@@ -21,7 +21,11 @@ interface Row { id: string; name: string; subtitle: string; on: boolean }
21
21
  interface S {
22
22
  status: "loading" | "ready";
23
23
  topics: Row[]; emptyText: string;
24
- isMock: boolean; notMock: boolean; mockBanner: string;
24
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
25
+ * shows only when the site really has none, and the unavailable one only when the
26
+ * query failed. A failed query must never render the sentence that claims the
27
+ * customer has no content. */
28
+ notMockEmpty: boolean; notMockUnavailable: boolean;
25
29
  }
26
30
 
27
31
  /** A topic delivers unless an explicit MUTED preference silences it. The status
@@ -45,7 +49,6 @@ function norm(t: NotificationTopic, prefs: PreferenceInfo[]): Row {
45
49
  return row(t.id || name, name, isOn(t.id, prefs));
46
50
  }
47
51
 
48
- const MOCK_BANNER = "Sample content — notification settings will appear here once configured.";
49
52
  const MOCK: Row[] = [
50
53
  row("sample-orders", "Order updates", true),
51
54
  row("sample-bookings", "Bookings & reminders", true),
@@ -53,15 +56,15 @@ const MOCK: Row[] = [
53
56
  row("sample-community", "Community activity", true),
54
57
  ];
55
58
 
56
- function mockState(s: S): S {
57
- return { ...s, status: "ready", topics: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
59
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
60
+ return { ...s, status: "ready", topics: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
58
61
  }
59
62
 
60
63
  const svc = defineService<S, {
61
64
  load: (s: S) => Promise<S>;
62
65
  toggle: (s: S, p?: { id?: string }) => Promise<S>;
63
66
  }>("himiBrandedNotifSettings", {
64
- state: { status: "loading", topics: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
67
+ state: { status: "loading", topics: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
65
68
  actions: {
66
69
  load: async (s) => {
67
70
  try {
@@ -71,10 +74,10 @@ const svc = defineService<S, {
71
74
  ]);
72
75
  const prefs = prefsRes.preferences?.preferencesInfo ?? [];
73
76
  const topics = (topicsRes.topics ?? []).map((t) => norm(t, prefs));
74
- if (topics.length) return { ...s, status: "ready", topics, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
75
- return mockState(s);
77
+ if (topics.length) return { ...s, status: "ready", topics, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
78
+ return mockState(s, "empty");
76
79
  } catch {
77
- return mockState(s);
80
+ return mockState(s, "unavailable");
78
81
  }
79
82
  },
80
83
  // Flip one topic's On/Off. Optimistic (updates the row immediately); the real
@@ -24,7 +24,11 @@ interface S {
24
24
  /** The Agenda wire shape, derived from `reservations` on every transition. */
25
25
  events: CalendarEvent[];
26
26
  emptyText: string;
27
- isMock: boolean; notMock: boolean; mockBanner: string;
27
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
28
+ * shows only when the site really has none, and the unavailable one only when the
29
+ * query failed. A failed query must never render the sentence that claims the
30
+ * customer has no content. */
31
+ notMockEmpty: boolean; notMockUnavailable: boolean;
28
32
  }
29
33
 
30
34
  const STATUS_LABEL: Record<string, string> = {
@@ -93,7 +97,6 @@ function withEvents(s: S, reservations: Row[]): S {
93
97
  return { ...s, reservations, events: eventsOf(reservations) };
94
98
  }
95
99
 
96
- const MOCK_BANNER = "Sample content — you have no reservations yet.";
97
100
  /** Sample rows sit on days RELATIVE to now, not on frozen "Aug 8" strings: an
98
101
  * Agenda groups by real calendar day, and hardcoded past dates would pile the
99
102
  * whole sample under one stale heading. */
@@ -119,8 +122,8 @@ function mockRows(): Row[] {
119
122
  ];
120
123
  }
121
124
 
122
- function mockState(s: S): S {
123
- return withEvents({ ...s, status: "ready", emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER }, mockRows());
125
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
126
+ return withEvents({ ...s, status: "ready", emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"}, mockRows());
124
127
  }
125
128
 
126
129
  /** The reservation id out of a tap. `Agenda.onSelectEvent` dispatches
@@ -138,7 +141,7 @@ const svc = defineService<S, {
138
141
  load: (s: S) => Promise<S>;
139
142
  cancel: (s: S, p?: unknown) => Promise<S>;
140
143
  }>("himiBrandedReservations", {
141
- state: { status: "loading", reservations: [], events: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
144
+ state: { status: "loading", reservations: [], events: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
142
145
  actions: {
143
146
  load: async (s) => {
144
147
  try {
@@ -147,11 +150,11 @@ const svc = defineService<S, {
147
150
  const r = await queryReservations({ cursorPaging: { limit: 20 }, sort: [{ fieldName: "details.startDate", order: "ASC" }] });
148
151
  const reservations = (r.reservations ?? []).map(norm);
149
152
  if (reservations.length) {
150
- return withEvents({ ...s, status: "ready", emptyText: "", isMock: false, notMock: true, mockBanner: "" }, reservations);
153
+ return withEvents({ ...s, status: "ready", emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true}, reservations);
151
154
  }
152
- return mockState(s);
155
+ return mockState(s, "empty");
153
156
  } catch {
154
- return mockState(s);
157
+ return mockState(s, "unavailable");
155
158
  }
156
159
  },
157
160
  // Cancel one upcoming reservation. Optimistic (marks it Canceled immediately);
@@ -12,7 +12,11 @@ interface Row { id: string; author: string; stars: string; title: string; body:
12
12
  interface S {
13
13
  status: "loading" | "ready";
14
14
  reviews: Row[]; emptyText: string;
15
- isMock: boolean; notMock: boolean; mockBanner: string;
15
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
16
+ * shows only when the site really has none, and the unavailable one only when the
17
+ * query failed. A failed query must never render the sentence that claims the
18
+ * customer has no content. */
19
+ notMockEmpty: boolean; notMockUnavailable: boolean;
16
20
  }
17
21
 
18
22
  /** "★★★★☆" from a 0–5 rating (clamped). */
@@ -32,7 +36,6 @@ function norm(v: WixReview): Row {
32
36
  };
33
37
  }
34
38
 
35
- const MOCK_BANNER = "Sample content — this business has no reviews yet.";
36
39
  const MOCK: Row[] = [
37
40
  { id: "sample-1", author: "Maya L.", stars: "★★★★★", title: "Fantastic experience", body: "Best in town — friendly staff and top quality. Highly recommend!" },
38
41
  { id: "sample-2", author: "Daniel C.", stars: "★★★★☆", title: "Great value", body: "Really enjoyed it and will definitely be coming back." },
@@ -40,22 +43,22 @@ const MOCK: Row[] = [
40
43
  { id: "sample-4", author: "Amit L.", stars: "★★★★☆", title: "Solid", body: "Good quality and quick service. Would order again." },
41
44
  ];
42
45
 
43
- function mockState(s: S): S {
44
- return { ...s, status: "ready", reviews: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
46
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
47
+ return { ...s, status: "ready", reviews: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
45
48
  }
46
49
 
47
50
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedReviews", {
48
- state: { status: "loading", reviews: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
51
+ state: { status: "loading", reviews: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
49
52
  actions: {
50
53
  load: async (s) => {
51
54
  try {
52
55
  const r = await queryReviews({ cursorPaging: { limit: 20 } });
53
56
  const raw = (r.reviews ?? r.results ?? []) as WixReview[];
54
57
  const reviews = raw.map(norm).filter((x) => x.title || x.body);
55
- if (reviews.length) return { ...s, status: "ready", reviews, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
56
- return mockState(s);
58
+ if (reviews.length) return { ...s, status: "ready", reviews, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
59
+ return mockState(s, "empty");
57
60
  } catch {
58
- return mockState(s);
61
+ return mockState(s, "unavailable");
59
62
  }
60
63
  },
61
64
  },
@@ -37,7 +37,11 @@ interface Row { id: string; title: string; subtitle: string }
37
37
  interface S {
38
38
  status: "loading" | "ready";
39
39
  channels: Row[]; emptyText: string;
40
- isMock: boolean; notMock: boolean; mockBanner: string;
40
+ isMock: boolean; notMock: boolean; /** #2849 — hiddenWhen() hides on TRUE, so these are inverted: the empty-state banner
41
+ * shows only when the site really has none, and the unavailable one only when the
42
+ * query failed. A failed query must never render the sentence that claims the
43
+ * customer has no content. */
44
+ notMockEmpty: boolean; notMockUnavailable: boolean;
41
45
  }
42
46
 
43
47
  function norm(c: VodChannel): Row {
@@ -47,24 +51,23 @@ function norm(c: VodChannel): Row {
47
51
  return { id: c.id || "channel", title, subtitle: parts.join(" · ") };
48
52
  }
49
53
 
50
- const MOCK_BANNER = "Sample content — this site has no Wix Video channels yet.";
51
54
  const MOCK: Row[] = [
52
55
  { id: "sample-tutorials", title: "Tutorials", subtitle: "12 videos" },
53
56
  { id: "sample-behind", title: "Behind the Scenes", subtitle: "6 videos" },
54
57
  { id: "sample-masterclass", title: "Masterclass", subtitle: "8 videos · Premium" },
55
58
  ];
56
59
 
57
- function mockState(s: S): S {
58
- return { ...s, status: "ready", channels: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, mockBanner: MOCK_BANNER };
60
+ function mockState(s: S, reason: "empty" | "unavailable"): S {
61
+ return { ...s, status: "ready", channels: MOCK.map((r) => ({ ...r })), emptyText: "", isMock: true, notMock: false, notMockEmpty: reason !== "empty", notMockUnavailable: reason !== "unavailable"};
59
62
  }
60
63
 
61
64
  const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedVideos", {
62
- state: { status: "loading", channels: [], emptyText: "", isMock: false, notMock: true, mockBanner: "" },
65
+ state: { status: "loading", channels: [], emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true},
63
66
  actions: {
64
67
  load: async (s) => {
65
68
  try {
66
69
  const api = wixApi();
67
- if (!api) return mockState(s);
70
+ if (!api) return mockState(s, "unavailable");
68
71
  const res = (await api.call("POST", VOD_PUBLIC_GRAPHQL, { query: CHANNEL_LIST_QUERY, variables: {} })) as
69
72
  { data?: { channels?: { entities?: VodChannel[] } } } | { channels?: { entities?: VodChannel[] } };
70
73
  // The gateway may return the GraphQL envelope ({data:{...}}) or the bridge may
@@ -73,10 +76,10 @@ const svc = defineService<S, { load: (s: S) => Promise<S> }>("himiBrandedVideos"
73
76
  (res as { data?: { channels?: { entities?: VodChannel[] } } })?.data?.channels?.entities ??
74
77
  (res as { channels?: { entities?: VodChannel[] } })?.channels?.entities ?? [];
75
78
  const channels = entities.map(norm);
76
- if (channels.length) return { ...s, status: "ready", channels, emptyText: "", isMock: false, notMock: true, mockBanner: "" };
77
- return mockState(s);
79
+ if (channels.length) return { ...s, status: "ready", channels, emptyText: "", isMock: false, notMock: true, notMockEmpty: true, notMockUnavailable: true};
80
+ return mockState(s, "empty");
78
81
  } catch {
79
- return mockState(s);
82
+ return mockState(s, "unavailable");
80
83
  }
81
84
  },
82
85
  },