@wix/himalaya-cli 0.912.0 → 0.914.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 (46) hide show
  1. package/package.json +2 -2
  2. package/templates/beauty-salon/tier5-src/_shared/owner-load-error.ts +68 -7
  3. package/templates/beauty-salon-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  4. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  5. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  6. package/templates/branded-lite/tier5-src/_shared/owner-load-error.ts +68 -7
  7. package/templates/community-club/tier5-src/_shared/owner-load-error.ts +68 -7
  8. package/templates/community-club-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  9. package/templates/community-club-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  10. package/templates/community-club-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  11. package/templates/events-meetups/tier5-src/_shared/owner-load-error.ts +68 -7
  12. package/templates/events-meetups-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  13. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  14. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  15. package/templates/gym-fitness/tier5-src/_shared/owner-load-error.ts +68 -7
  16. package/templates/gym-fitness-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  17. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  18. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  19. package/templates/himi-chat/tier5-src/_shared/owner-load-error.ts +68 -7
  20. package/templates/nonprofit-cause/tier5-src/_shared/owner-load-error.ts +68 -7
  21. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  22. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  23. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  24. package/templates/owner-lite/tier5-src/_shared/owner-load-error.ts +68 -7
  25. package/templates/owner-lite/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  26. package/templates/owner-lite/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  27. package/templates/podcast-creator/tier5-src/_shared/owner-load-error.ts +68 -7
  28. package/templates/podcast-creator-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  29. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  30. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  31. package/templates/portfolio-creative/tier5-src/_shared/owner-load-error.ts +68 -7
  32. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  33. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  34. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  35. package/templates/restaurant/tier5-src/_shared/owner-load-error.ts +68 -7
  36. package/templates/restaurant-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  37. package/templates/restaurant-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  38. package/templates/restaurant-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  39. package/templates/retail-store/tier5-src/_shared/owner-load-error.ts +68 -7
  40. package/templates/retail-store-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  41. package/templates/retail-store-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  42. package/templates/retail-store-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
  43. package/templates/yoga-studio/tier5-src/_shared/owner-load-error.ts +68 -7
  44. package/templates/yoga-studio-owner/tier5-src/_shared/owner-load-error.ts +68 -7
  45. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
  46. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.912.0",
3
+ "version": "0.914.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": "94415d57eb1b30c06a58cc4e9a3ca8fe75629dc32db71bba525dc552"
67
+ "falconPackageHash": "dcb937a54b33218d22d5a8e54fe90c7426b937a5bf3e791eb8dc26d5"
68
68
  }
@@ -9,6 +9,23 @@ export function isOwnerPreviewLive(): boolean {
9
9
  return ownerPreviewMode() === "live";
10
10
  }
11
11
 
12
+ /**
13
+ * Whether we are in the web preview AT ALL — i.e. the host declared a mode.
14
+ *
15
+ * `isBridgeUnavailable` cannot answer this: it reads "not live" as "preview",
16
+ * and on NATIVE there is no input at all, so `undefined` satisfies it. That is
17
+ * harmless for the callers that use it to pick a demo fallback or to queue a
18
+ * write, and it is deliberately left alone for them — native write-behind
19
+ * depends on it. It is NOT harmless for choosing COPY: native workers format
20
+ * their failures the same way (`"HTTP \(code): \(body)"`, JSWorker.swift;
21
+ * `"HTTP ${e.code}: ..."`, WebViewJSWorker.kt), so a transient `HTTP 503` on a
22
+ * phone would otherwise be answered with "open this app on a device" — on the
23
+ * device. Callers that pick a SENTENCE ask this instead.
24
+ */
25
+ export function isOwnerPreviewHost(): boolean {
26
+ return ownerPreviewMode() !== undefined;
27
+ }
28
+
12
29
  export function isOwnerPreviewDemo(): boolean {
13
30
  return ownerPreviewMode() === "sample";
14
31
  }
@@ -31,6 +48,11 @@ export function shouldQueueOwnerWrite(e: unknown): boolean {
31
48
  const STATUS = /HTTP (\d{3})\b/;
32
49
  /** A wire body that is an HTML DOCUMENT, not a message. */
33
50
  const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
51
+ /** A wire body that is a JSON ENVELOPE, not a message. The serve bridge answers
52
+ * its typed refusals this way — `HTTP 501: {"error":"webwix_owner_no_session",
53
+ * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
+ * never caught it and the owner read the machine's word for it. */
55
+ const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
34
56
  /** Longest raw upstream text worth showing. Beyond this it stops being a
35
57
  * message and starts being a payload dump. */
36
58
  const MAX_RAW = 200;
@@ -62,16 +84,55 @@ function humanWireError(msg: string): string {
62
84
  return msg.length > MAX_RAW ? `${msg.slice(0, MAX_RAW).trimEnd()}…` : msg;
63
85
  }
64
86
 
65
- export function ownerLoadError(e: unknown): string {
87
+ /**
88
+ * The one sentence every owner screen gives for the DESIGNED bridge boundary.
89
+ *
90
+ * Copy-as-configuration (himi-copy): the key lives under the `ownerCommon.`
91
+ * prefix the dev module forwards to all owner screens, so one pack retitles it
92
+ * everywhere. With no pack this returns the stock copy verbatim.
93
+ */
94
+ export function ownerPreviewUnavailable(): string {
95
+ return copyParam(
96
+ "ownerCommon.bridgeUnavailable",
97
+ "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
98
+ );
99
+ }
100
+
101
+ /**
102
+ * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
+ *
104
+ * Two things the default cannot do, each for a reason:
105
+ *
106
+ * 1. It only names the web preview when there IS one. `isBridgeUnavailable`
107
+ * reads "not live" as "preview", and native injects no mode at all, so the
108
+ * default would answer a device-side `HTTP 503` with "open this app on a
109
+ * device" — on the device. That predicate is deliberately left alone:
110
+ * native write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
+ *
112
+ * 2. It reduces a JSON ENVELOPE to its status. The default must NOT: its
113
+ * output is PARSED downstream — the products worker matches
114
+ * `CATALOG_V3_CALLING_CATALOG_V1_API` in it to choose its v1 fallback, and
115
+ * `owner-load-error.test.ts` pins that verbatim. A display-only caller has
116
+ * no such reader, and the bridge's typed refusals are JSON and short enough
117
+ * (68 chars) to slip under MAX_RAW, so nothing else was catching them.
118
+ *
119
+ * So: use this ONLY where the value is rendered and never matched on.
120
+ */
121
+ export function ownerDisplayError(e: unknown): string {
122
+ if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
66
123
  const msg = String((e as Error)?.message ?? e);
67
- if (isBridgeUnavailable(e)) {
68
- // Copy-as-configuration (himi-copy): shared across every owner-lite worker,
69
- // so the key lives under the ownerCommon. prefix the dev module forwards to
70
- // all owner screens. With no pack this returns the stock copy verbatim.
124
+ const status = STATUS.exec(msg)?.[1];
125
+ if (JSON_BODY.test(msg) && status) {
71
126
  return copyParam(
72
- "ownerCommon.bridgeUnavailable",
73
- "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
127
+ "ownerCommon.upstreamStatus",
128
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
74
129
  );
75
130
  }
76
131
  return humanWireError(msg);
77
132
  }
133
+
134
+ export function ownerLoadError(e: unknown): string {
135
+ const msg = String((e as Error)?.message ?? e);
136
+ if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();
137
+ return humanWireError(msg);
138
+ }
@@ -9,6 +9,23 @@ export function isOwnerPreviewLive(): boolean {
9
9
  return ownerPreviewMode() === "live";
10
10
  }
11
11
 
12
+ /**
13
+ * Whether we are in the web preview AT ALL — i.e. the host declared a mode.
14
+ *
15
+ * `isBridgeUnavailable` cannot answer this: it reads "not live" as "preview",
16
+ * and on NATIVE there is no input at all, so `undefined` satisfies it. That is
17
+ * harmless for the callers that use it to pick a demo fallback or to queue a
18
+ * write, and it is deliberately left alone for them — native write-behind
19
+ * depends on it. It is NOT harmless for choosing COPY: native workers format
20
+ * their failures the same way (`"HTTP \(code): \(body)"`, JSWorker.swift;
21
+ * `"HTTP ${e.code}: ..."`, WebViewJSWorker.kt), so a transient `HTTP 503` on a
22
+ * phone would otherwise be answered with "open this app on a device" — on the
23
+ * device. Callers that pick a SENTENCE ask this instead.
24
+ */
25
+ export function isOwnerPreviewHost(): boolean {
26
+ return ownerPreviewMode() !== undefined;
27
+ }
28
+
12
29
  export function isOwnerPreviewDemo(): boolean {
13
30
  return ownerPreviewMode() === "sample";
14
31
  }
@@ -31,6 +48,11 @@ export function shouldQueueOwnerWrite(e: unknown): boolean {
31
48
  const STATUS = /HTTP (\d{3})\b/;
32
49
  /** A wire body that is an HTML DOCUMENT, not a message. */
33
50
  const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
51
+ /** A wire body that is a JSON ENVELOPE, not a message. The serve bridge answers
52
+ * its typed refusals this way — `HTTP 501: {"error":"webwix_owner_no_session",
53
+ * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
+ * never caught it and the owner read the machine's word for it. */
55
+ const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
34
56
  /** Longest raw upstream text worth showing. Beyond this it stops being a
35
57
  * message and starts being a payload dump. */
36
58
  const MAX_RAW = 200;
@@ -62,16 +84,55 @@ function humanWireError(msg: string): string {
62
84
  return msg.length > MAX_RAW ? `${msg.slice(0, MAX_RAW).trimEnd()}…` : msg;
63
85
  }
64
86
 
65
- export function ownerLoadError(e: unknown): string {
87
+ /**
88
+ * The one sentence every owner screen gives for the DESIGNED bridge boundary.
89
+ *
90
+ * Copy-as-configuration (himi-copy): the key lives under the `ownerCommon.`
91
+ * prefix the dev module forwards to all owner screens, so one pack retitles it
92
+ * everywhere. With no pack this returns the stock copy verbatim.
93
+ */
94
+ export function ownerPreviewUnavailable(): string {
95
+ return copyParam(
96
+ "ownerCommon.bridgeUnavailable",
97
+ "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
98
+ );
99
+ }
100
+
101
+ /**
102
+ * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
+ *
104
+ * Two things the default cannot do, each for a reason:
105
+ *
106
+ * 1. It only names the web preview when there IS one. `isBridgeUnavailable`
107
+ * reads "not live" as "preview", and native injects no mode at all, so the
108
+ * default would answer a device-side `HTTP 503` with "open this app on a
109
+ * device" — on the device. That predicate is deliberately left alone:
110
+ * native write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
+ *
112
+ * 2. It reduces a JSON ENVELOPE to its status. The default must NOT: its
113
+ * output is PARSED downstream — the products worker matches
114
+ * `CATALOG_V3_CALLING_CATALOG_V1_API` in it to choose its v1 fallback, and
115
+ * `owner-load-error.test.ts` pins that verbatim. A display-only caller has
116
+ * no such reader, and the bridge's typed refusals are JSON and short enough
117
+ * (68 chars) to slip under MAX_RAW, so nothing else was catching them.
118
+ *
119
+ * So: use this ONLY where the value is rendered and never matched on.
120
+ */
121
+ export function ownerDisplayError(e: unknown): string {
122
+ if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
66
123
  const msg = String((e as Error)?.message ?? e);
67
- if (isBridgeUnavailable(e)) {
68
- // Copy-as-configuration (himi-copy): shared across every owner-lite worker,
69
- // so the key lives under the ownerCommon. prefix the dev module forwards to
70
- // all owner screens. With no pack this returns the stock copy verbatim.
124
+ const status = STATUS.exec(msg)?.[1];
125
+ if (JSON_BODY.test(msg) && status) {
71
126
  return copyParam(
72
- "ownerCommon.bridgeUnavailable",
73
- "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
127
+ "ownerCommon.upstreamStatus",
128
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
74
129
  );
75
130
  }
76
131
  return humanWireError(msg);
77
132
  }
133
+
134
+ export function ownerLoadError(e: unknown): string {
135
+ const msg = String((e as Error)?.message ?? e);
136
+ if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();
137
+ return humanWireError(msg);
138
+ }
@@ -31,6 +31,7 @@ import { defineService } from "@himalaya/state";
31
31
  import { open } from "@himalaya/open-url";
32
32
  import { formatNumber } from "../_shared/engine-intl.js";
33
33
  import { stashCopy, copyParam } from "../_shared/himi-copy.js";
34
+ import { ownerDisplayError, isOwnerPreviewDemo, ownerPreviewUnavailable } from "../_shared/owner-load-error.js";
34
35
  import {
35
36
  getSiteAnalytics,
36
37
  getSiteTimeZone,
@@ -353,9 +354,41 @@ function withDetail(s: AnalyticsState, key: string): AnalyticsState {
353
354
  };
354
355
  }
355
356
 
357
+ /**
358
+ * The error arm, shaped once.
359
+ *
360
+ * `message` is ALWAYS human-readable copy, never a wire body. This screen used
361
+ * to pass `String(e.message)` straight through, which is how an owner opening
362
+ * the web preview met `HTTP 501: {"error":"webwix_bridge_no_client_id",…}` in
363
+ * the card where every sibling screen says the same thing in a sentence.
364
+ */
365
+ function errored(n: number, message: string): AnalyticsState {
366
+ return {
367
+ ...empty,
368
+ status: "error",
369
+ days: n,
370
+ rangeId: String(n),
371
+ range: rangeLabel(n),
372
+ notLoading: true,
373
+ notErrored: false,
374
+ hideContent: true,
375
+ noEmpty: true,
376
+ noCompare: true,
377
+ emptyText: "",
378
+ error: message,
379
+ handoffError: "",
380
+ };
381
+ }
382
+
356
383
  /** Fetch + shape the metrics for a window of `days`. */
357
384
  async function compute(days: number): Promise<AnalyticsState> {
358
385
  const n = RANGES.includes(days) ? days : 30;
386
+ // SAMPLE preview mode, and this screen deliberately has no sample arm (see the
387
+ // descriptor's "NO demo banner" note — fabricated traffic and revenue would be
388
+ // indistinguishable from the real thing). So refuse HERE, in one sentence,
389
+ // instead of firing a request the host has already told us cannot be served
390
+ // and rendering the bridge's refusal at the owner.
391
+ if (isOwnerPreviewDemo()) return errored(n, ownerPreviewUnavailable());
359
392
  try {
360
393
  // A site with no zone set resolves undefined and legitimately falls back to
361
394
  // UTC — the API's own default. A FAILED request rejects and lands in the
@@ -426,22 +459,7 @@ async function compute(days: number): Promise<AnalyticsState> {
426
459
  handoffError: "",
427
460
  };
428
461
  } catch (e) {
429
- const message = String((e as Error)?.message ?? e);
430
- return {
431
- ...empty,
432
- status: "error",
433
- days: n,
434
- rangeId: String(n),
435
- range: rangeLabel(n),
436
- notLoading: true,
437
- notErrored: false,
438
- hideContent: true,
439
- noEmpty: true,
440
- noCompare: true,
441
- emptyText: "",
442
- error: message,
443
- handoffError: "",
444
- };
462
+ return errored(n, ownerDisplayError(e));
445
463
  }
446
464
  }
447
465
 
@@ -46,6 +46,7 @@
46
46
  import { defineService } from "@himalaya/state";
47
47
  import { formatNumber } from "../_shared/engine-intl.js";
48
48
  import { copyParam, stashCopy } from "../_shared/himi-copy.js";
49
+ import { ownerDisplayError, isOwnerPreviewDemo, ownerPreviewUnavailable } from "../_shared/owner-load-error.js";
49
50
  import {
50
51
  getSiteTimeZone,
51
52
  intervalForDays,
@@ -472,7 +473,7 @@ async function runQuery(s: ReportState, days: number): Promise<ReportState | Dec
472
473
  return out;
473
474
  } catch (e) {
474
475
  if (mine !== seq) return latest ?? s;
475
- const out = errorState(s, String((e as Error)?.message ?? e));
476
+ const out = errorState(s, ownerDisplayError(e));
476
477
  latest = out;
477
478
  return out;
478
479
  }
@@ -494,6 +495,16 @@ const svc = defineService<ReportState, {
494
495
  */
495
496
  load: async (s, arg) => {
496
497
  stashCopy(arg);
498
+ // SAMPLE preview mode. Like the highlights service this screen has no
499
+ // sample arm on purpose — an invented subject area with invented rows is
500
+ // the "confident wrong number" this screen family exists to prevent — so
501
+ // it refuses in a sentence rather than issuing a call the host already
502
+ // knows cannot be served and surfacing the bridge's own refusal.
503
+ if (isOwnerPreviewDemo()) {
504
+ const out = errorState(s, ownerPreviewUnavailable());
505
+ latest = out;
506
+ return out;
507
+ }
497
508
  const mine = ++seq;
498
509
  try {
499
510
  const models = await listSemanticModels();
@@ -533,7 +544,7 @@ const svc = defineService<ReportState, {
533
544
  // made the failure path inconsistent with the success path right beside
534
545
  // it. (AI-review finding on #2533.)
535
546
  if (mine !== seq) return latest ?? s ?? { ...empty };
536
- const out = errorState(s, String((e as Error)?.message ?? e));
547
+ const out = errorState(s, ownerDisplayError(e));
537
548
  latest = out;
538
549
  return out;
539
550
  }
@@ -607,7 +618,7 @@ const svc = defineService<ReportState, {
607
618
  // Same hole, same reason: tap subject A then B, and A's rejection
608
619
  // arriving late must not replace B's selection with the error arm.
609
620
  if (mine !== seq) return latest ?? s;
610
- const out = errorState(s, String((e as Error)?.message ?? e));
621
+ const out = errorState(s, ownerDisplayError(e));
611
622
  latest = out;
612
623
  return out;
613
624
  } finally {
@@ -9,6 +9,23 @@ export function isOwnerPreviewLive(): boolean {
9
9
  return ownerPreviewMode() === "live";
10
10
  }
11
11
 
12
+ /**
13
+ * Whether we are in the web preview AT ALL — i.e. the host declared a mode.
14
+ *
15
+ * `isBridgeUnavailable` cannot answer this: it reads "not live" as "preview",
16
+ * and on NATIVE there is no input at all, so `undefined` satisfies it. That is
17
+ * harmless for the callers that use it to pick a demo fallback or to queue a
18
+ * write, and it is deliberately left alone for them — native write-behind
19
+ * depends on it. It is NOT harmless for choosing COPY: native workers format
20
+ * their failures the same way (`"HTTP \(code): \(body)"`, JSWorker.swift;
21
+ * `"HTTP ${e.code}: ..."`, WebViewJSWorker.kt), so a transient `HTTP 503` on a
22
+ * phone would otherwise be answered with "open this app on a device" — on the
23
+ * device. Callers that pick a SENTENCE ask this instead.
24
+ */
25
+ export function isOwnerPreviewHost(): boolean {
26
+ return ownerPreviewMode() !== undefined;
27
+ }
28
+
12
29
  export function isOwnerPreviewDemo(): boolean {
13
30
  return ownerPreviewMode() === "sample";
14
31
  }
@@ -31,6 +48,11 @@ export function shouldQueueOwnerWrite(e: unknown): boolean {
31
48
  const STATUS = /HTTP (\d{3})\b/;
32
49
  /** A wire body that is an HTML DOCUMENT, not a message. */
33
50
  const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
51
+ /** A wire body that is a JSON ENVELOPE, not a message. The serve bridge answers
52
+ * its typed refusals this way — `HTTP 501: {"error":"webwix_owner_no_session",
53
+ * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
+ * never caught it and the owner read the machine's word for it. */
55
+ const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
34
56
  /** Longest raw upstream text worth showing. Beyond this it stops being a
35
57
  * message and starts being a payload dump. */
36
58
  const MAX_RAW = 200;
@@ -62,16 +84,55 @@ function humanWireError(msg: string): string {
62
84
  return msg.length > MAX_RAW ? `${msg.slice(0, MAX_RAW).trimEnd()}…` : msg;
63
85
  }
64
86
 
65
- export function ownerLoadError(e: unknown): string {
87
+ /**
88
+ * The one sentence every owner screen gives for the DESIGNED bridge boundary.
89
+ *
90
+ * Copy-as-configuration (himi-copy): the key lives under the `ownerCommon.`
91
+ * prefix the dev module forwards to all owner screens, so one pack retitles it
92
+ * everywhere. With no pack this returns the stock copy verbatim.
93
+ */
94
+ export function ownerPreviewUnavailable(): string {
95
+ return copyParam(
96
+ "ownerCommon.bridgeUnavailable",
97
+ "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
98
+ );
99
+ }
100
+
101
+ /**
102
+ * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
+ *
104
+ * Two things the default cannot do, each for a reason:
105
+ *
106
+ * 1. It only names the web preview when there IS one. `isBridgeUnavailable`
107
+ * reads "not live" as "preview", and native injects no mode at all, so the
108
+ * default would answer a device-side `HTTP 503` with "open this app on a
109
+ * device" — on the device. That predicate is deliberately left alone:
110
+ * native write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
+ *
112
+ * 2. It reduces a JSON ENVELOPE to its status. The default must NOT: its
113
+ * output is PARSED downstream — the products worker matches
114
+ * `CATALOG_V3_CALLING_CATALOG_V1_API` in it to choose its v1 fallback, and
115
+ * `owner-load-error.test.ts` pins that verbatim. A display-only caller has
116
+ * no such reader, and the bridge's typed refusals are JSON and short enough
117
+ * (68 chars) to slip under MAX_RAW, so nothing else was catching them.
118
+ *
119
+ * So: use this ONLY where the value is rendered and never matched on.
120
+ */
121
+ export function ownerDisplayError(e: unknown): string {
122
+ if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
66
123
  const msg = String((e as Error)?.message ?? e);
67
- if (isBridgeUnavailable(e)) {
68
- // Copy-as-configuration (himi-copy): shared across every owner-lite worker,
69
- // so the key lives under the ownerCommon. prefix the dev module forwards to
70
- // all owner screens. With no pack this returns the stock copy verbatim.
124
+ const status = STATUS.exec(msg)?.[1];
125
+ if (JSON_BODY.test(msg) && status) {
71
126
  return copyParam(
72
- "ownerCommon.bridgeUnavailable",
73
- "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
127
+ "ownerCommon.upstreamStatus",
128
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
74
129
  );
75
130
  }
76
131
  return humanWireError(msg);
77
132
  }
133
+
134
+ export function ownerLoadError(e: unknown): string {
135
+ const msg = String((e as Error)?.message ?? e);
136
+ if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();
137
+ return humanWireError(msg);
138
+ }
@@ -9,6 +9,23 @@ export function isOwnerPreviewLive(): boolean {
9
9
  return ownerPreviewMode() === "live";
10
10
  }
11
11
 
12
+ /**
13
+ * Whether we are in the web preview AT ALL — i.e. the host declared a mode.
14
+ *
15
+ * `isBridgeUnavailable` cannot answer this: it reads "not live" as "preview",
16
+ * and on NATIVE there is no input at all, so `undefined` satisfies it. That is
17
+ * harmless for the callers that use it to pick a demo fallback or to queue a
18
+ * write, and it is deliberately left alone for them — native write-behind
19
+ * depends on it. It is NOT harmless for choosing COPY: native workers format
20
+ * their failures the same way (`"HTTP \(code): \(body)"`, JSWorker.swift;
21
+ * `"HTTP ${e.code}: ..."`, WebViewJSWorker.kt), so a transient `HTTP 503` on a
22
+ * phone would otherwise be answered with "open this app on a device" — on the
23
+ * device. Callers that pick a SENTENCE ask this instead.
24
+ */
25
+ export function isOwnerPreviewHost(): boolean {
26
+ return ownerPreviewMode() !== undefined;
27
+ }
28
+
12
29
  export function isOwnerPreviewDemo(): boolean {
13
30
  return ownerPreviewMode() === "sample";
14
31
  }
@@ -31,6 +48,11 @@ export function shouldQueueOwnerWrite(e: unknown): boolean {
31
48
  const STATUS = /HTTP (\d{3})\b/;
32
49
  /** A wire body that is an HTML DOCUMENT, not a message. */
33
50
  const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
51
+ /** A wire body that is a JSON ENVELOPE, not a message. The serve bridge answers
52
+ * its typed refusals this way — `HTTP 501: {"error":"webwix_owner_no_session",
53
+ * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
+ * never caught it and the owner read the machine's word for it. */
55
+ const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
34
56
  /** Longest raw upstream text worth showing. Beyond this it stops being a
35
57
  * message and starts being a payload dump. */
36
58
  const MAX_RAW = 200;
@@ -62,16 +84,55 @@ function humanWireError(msg: string): string {
62
84
  return msg.length > MAX_RAW ? `${msg.slice(0, MAX_RAW).trimEnd()}…` : msg;
63
85
  }
64
86
 
65
- export function ownerLoadError(e: unknown): string {
87
+ /**
88
+ * The one sentence every owner screen gives for the DESIGNED bridge boundary.
89
+ *
90
+ * Copy-as-configuration (himi-copy): the key lives under the `ownerCommon.`
91
+ * prefix the dev module forwards to all owner screens, so one pack retitles it
92
+ * everywhere. With no pack this returns the stock copy verbatim.
93
+ */
94
+ export function ownerPreviewUnavailable(): string {
95
+ return copyParam(
96
+ "ownerCommon.bridgeUnavailable",
97
+ "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
98
+ );
99
+ }
100
+
101
+ /**
102
+ * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
+ *
104
+ * Two things the default cannot do, each for a reason:
105
+ *
106
+ * 1. It only names the web preview when there IS one. `isBridgeUnavailable`
107
+ * reads "not live" as "preview", and native injects no mode at all, so the
108
+ * default would answer a device-side `HTTP 503` with "open this app on a
109
+ * device" — on the device. That predicate is deliberately left alone:
110
+ * native write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
+ *
112
+ * 2. It reduces a JSON ENVELOPE to its status. The default must NOT: its
113
+ * output is PARSED downstream — the products worker matches
114
+ * `CATALOG_V3_CALLING_CATALOG_V1_API` in it to choose its v1 fallback, and
115
+ * `owner-load-error.test.ts` pins that verbatim. A display-only caller has
116
+ * no such reader, and the bridge's typed refusals are JSON and short enough
117
+ * (68 chars) to slip under MAX_RAW, so nothing else was catching them.
118
+ *
119
+ * So: use this ONLY where the value is rendered and never matched on.
120
+ */
121
+ export function ownerDisplayError(e: unknown): string {
122
+ if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
66
123
  const msg = String((e as Error)?.message ?? e);
67
- if (isBridgeUnavailable(e)) {
68
- // Copy-as-configuration (himi-copy): shared across every owner-lite worker,
69
- // so the key lives under the ownerCommon. prefix the dev module forwards to
70
- // all owner screens. With no pack this returns the stock copy verbatim.
124
+ const status = STATUS.exec(msg)?.[1];
125
+ if (JSON_BODY.test(msg) && status) {
71
126
  return copyParam(
72
- "ownerCommon.bridgeUnavailable",
73
- "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
127
+ "ownerCommon.upstreamStatus",
128
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
74
129
  );
75
130
  }
76
131
  return humanWireError(msg);
77
132
  }
133
+
134
+ export function ownerLoadError(e: unknown): string {
135
+ const msg = String((e as Error)?.message ?? e);
136
+ if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();
137
+ return humanWireError(msg);
138
+ }
@@ -9,6 +9,23 @@ export function isOwnerPreviewLive(): boolean {
9
9
  return ownerPreviewMode() === "live";
10
10
  }
11
11
 
12
+ /**
13
+ * Whether we are in the web preview AT ALL — i.e. the host declared a mode.
14
+ *
15
+ * `isBridgeUnavailable` cannot answer this: it reads "not live" as "preview",
16
+ * and on NATIVE there is no input at all, so `undefined` satisfies it. That is
17
+ * harmless for the callers that use it to pick a demo fallback or to queue a
18
+ * write, and it is deliberately left alone for them — native write-behind
19
+ * depends on it. It is NOT harmless for choosing COPY: native workers format
20
+ * their failures the same way (`"HTTP \(code): \(body)"`, JSWorker.swift;
21
+ * `"HTTP ${e.code}: ..."`, WebViewJSWorker.kt), so a transient `HTTP 503` on a
22
+ * phone would otherwise be answered with "open this app on a device" — on the
23
+ * device. Callers that pick a SENTENCE ask this instead.
24
+ */
25
+ export function isOwnerPreviewHost(): boolean {
26
+ return ownerPreviewMode() !== undefined;
27
+ }
28
+
12
29
  export function isOwnerPreviewDemo(): boolean {
13
30
  return ownerPreviewMode() === "sample";
14
31
  }
@@ -31,6 +48,11 @@ export function shouldQueueOwnerWrite(e: unknown): boolean {
31
48
  const STATUS = /HTTP (\d{3})\b/;
32
49
  /** A wire body that is an HTML DOCUMENT, not a message. */
33
50
  const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
51
+ /** A wire body that is a JSON ENVELOPE, not a message. The serve bridge answers
52
+ * its typed refusals this way — `HTTP 501: {"error":"webwix_owner_no_session",
53
+ * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
+ * never caught it and the owner read the machine's word for it. */
55
+ const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
34
56
  /** Longest raw upstream text worth showing. Beyond this it stops being a
35
57
  * message and starts being a payload dump. */
36
58
  const MAX_RAW = 200;
@@ -62,16 +84,55 @@ function humanWireError(msg: string): string {
62
84
  return msg.length > MAX_RAW ? `${msg.slice(0, MAX_RAW).trimEnd()}…` : msg;
63
85
  }
64
86
 
65
- export function ownerLoadError(e: unknown): string {
87
+ /**
88
+ * The one sentence every owner screen gives for the DESIGNED bridge boundary.
89
+ *
90
+ * Copy-as-configuration (himi-copy): the key lives under the `ownerCommon.`
91
+ * prefix the dev module forwards to all owner screens, so one pack retitles it
92
+ * everywhere. With no pack this returns the stock copy verbatim.
93
+ */
94
+ export function ownerPreviewUnavailable(): string {
95
+ return copyParam(
96
+ "ownerCommon.bridgeUnavailable",
97
+ "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
98
+ );
99
+ }
100
+
101
+ /**
102
+ * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
+ *
104
+ * Two things the default cannot do, each for a reason:
105
+ *
106
+ * 1. It only names the web preview when there IS one. `isBridgeUnavailable`
107
+ * reads "not live" as "preview", and native injects no mode at all, so the
108
+ * default would answer a device-side `HTTP 503` with "open this app on a
109
+ * device" — on the device. That predicate is deliberately left alone:
110
+ * native write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
+ *
112
+ * 2. It reduces a JSON ENVELOPE to its status. The default must NOT: its
113
+ * output is PARSED downstream — the products worker matches
114
+ * `CATALOG_V3_CALLING_CATALOG_V1_API` in it to choose its v1 fallback, and
115
+ * `owner-load-error.test.ts` pins that verbatim. A display-only caller has
116
+ * no such reader, and the bridge's typed refusals are JSON and short enough
117
+ * (68 chars) to slip under MAX_RAW, so nothing else was catching them.
118
+ *
119
+ * So: use this ONLY where the value is rendered and never matched on.
120
+ */
121
+ export function ownerDisplayError(e: unknown): string {
122
+ if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
66
123
  const msg = String((e as Error)?.message ?? e);
67
- if (isBridgeUnavailable(e)) {
68
- // Copy-as-configuration (himi-copy): shared across every owner-lite worker,
69
- // so the key lives under the ownerCommon. prefix the dev module forwards to
70
- // all owner screens. With no pack this returns the stock copy verbatim.
124
+ const status = STATUS.exec(msg)?.[1];
125
+ if (JSON_BODY.test(msg) && status) {
71
126
  return copyParam(
72
- "ownerCommon.bridgeUnavailable",
73
- "Owner data isn’t available in the web preview — open this app on a device (▦ Open on phone) and sign in as the site owner.",
127
+ "ownerCommon.upstreamStatus",
128
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
74
129
  );
75
130
  }
76
131
  return humanWireError(msg);
77
132
  }
133
+
134
+ export function ownerLoadError(e: unknown): string {
135
+ const msg = String((e as Error)?.message ?? e);
136
+ if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();
137
+ return humanWireError(msg);
138
+ }