@wix/himalaya-cli 0.945.0 → 0.946.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 (36) hide show
  1. package/dist/cli.mjs +0 -2
  2. package/package.json +2 -2
  3. package/templates/beauty-salon/tier5-src/_shared/owner-load-error.ts +68 -24
  4. package/templates/beauty-salon-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  5. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  6. package/templates/branded-lite/tier5-src/_shared/owner-load-error.ts +68 -24
  7. package/templates/community-club/tier5-src/_shared/owner-load-error.ts +68 -24
  8. package/templates/community-club-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  9. package/templates/community-club-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  10. package/templates/events-meetups/tier5-src/_shared/owner-load-error.ts +68 -24
  11. package/templates/events-meetups-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  12. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  13. package/templates/gym-fitness/tier5-src/_shared/owner-load-error.ts +68 -24
  14. package/templates/gym-fitness-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  15. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  16. package/templates/himi-chat/tier5-src/_shared/owner-load-error.ts +68 -24
  17. package/templates/nonprofit-cause/tier5-src/_shared/owner-load-error.ts +68 -24
  18. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  19. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  20. package/templates/owner-lite/tier5-src/_shared/owner-load-error.ts +68 -24
  21. package/templates/owner-lite/tier5-src/himi-owner-lite-home/index.ts +7 -3
  22. package/templates/podcast-creator/tier5-src/_shared/owner-load-error.ts +68 -24
  23. package/templates/podcast-creator-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  24. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  25. package/templates/portfolio-creative/tier5-src/_shared/owner-load-error.ts +68 -24
  26. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  27. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  28. package/templates/restaurant/tier5-src/_shared/owner-load-error.ts +68 -24
  29. package/templates/restaurant-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  30. package/templates/restaurant-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  31. package/templates/retail-store/tier5-src/_shared/owner-load-error.ts +68 -24
  32. package/templates/retail-store-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  33. package/templates/retail-store-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
  34. package/templates/yoga-studio/tier5-src/_shared/owner-load-error.ts +68 -24
  35. package/templates/yoga-studio-owner/tier5-src/_shared/owner-load-error.ts +68 -24
  36. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-home/index.ts +7 -3
package/dist/cli.mjs CHANGED
@@ -18546,7 +18546,6 @@ function shouldRevealError(args) {
18546
18546
  var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
18547
18547
  var init_validation = __esm({
18548
18548
  "../../core/ts/src/validation.ts"() {
18549
- "use strict";
18550
18549
  EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
18551
18550
  URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
18552
18551
  INTEGER_RE = /^[+-]?[0-9]+$/;
@@ -21970,7 +21969,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
21970
21969
  var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
21971
21970
  var init_worker_subtree = __esm({
21972
21971
  "../../core/ts/src/worker-subtree.ts"() {
21973
- "use strict";
21974
21972
  init_worker_subtree_safe_types_generated();
21975
21973
  WORKER_SUBTREE_CEILINGS = Object.freeze({
21976
21974
  maxEncodedBytes: 65536,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.945.0",
3
+ "version": "0.946.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": "7c47343c4a5027aabc50c963502aa5034b2828f8bf4e1e133fec1b94"
67
+ "falconPackageHash": "3f6060102b6c94b2c8e6ed84fd14f6c0e0091b8c611331ef95f97e2a"
68
68
  }
@@ -53,6 +53,17 @@ const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
53
53
  * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
54
  * never caught it and the owner read the machine's word for it. */
55
55
  const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
56
+ /** `HTTP <status> [SERVICE_CODE]: message` — the shape a Wix service uses when
57
+ * it refuses with a named reason. */
58
+ const CODED = /HTTP (\d{3}) \[([A-Z0-9_]+)\]/;
59
+ /**
60
+ * The codes that all mean one thing to an owner: the session cannot address this
61
+ * business. Found on a real session whose account did not own the business the app
62
+ * had persisted — Home rendered
63
+ * `HTTP 428 [MISSING_METASITE_ID]: Couldn't extract metasiteId from the request
64
+ * context` where "6 orders" belongs.
65
+ */
66
+ const SITE_CONTEXT_CODES = ["MISSING_METASITE_ID", "MISSING_REQUEST_SITE_CONTEXT", "MISSING_SITE_CONTEXT"];
56
67
  /** Longest raw upstream text worth showing. Beyond this it stops being a
57
68
  * message and starts being a payload dump. */
58
69
  const MAX_RAW = 200;
@@ -69,6 +80,29 @@ const MAX_RAW = 200;
69
80
  */
70
81
  function humanWireError(msg: string): string {
71
82
  const status = STATUS.exec(msg)?.[1];
83
+ // A JSON ENVELOPE or a NAMED REASON CODE is the machine's word for it, and the
84
+ // owner gets neither. Both reductions used to live in `ownerDisplayError` only,
85
+ // on the belief that this function's output was parsed downstream — see the note
86
+ // on `ownerLoadError`, which records why that was wrong and how it was checked.
87
+ if (JSON_BODY.test(msg) && status) {
88
+ return copyParam(
89
+ "ownerCommon.upstreamStatus",
90
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
91
+ );
92
+ }
93
+ const coded = CODED.exec(msg);
94
+ if (coded) {
95
+ if (SITE_CONTEXT_CODES.includes(coded[2])) {
96
+ return copyParam(
97
+ "ownerCommon.noSiteContext",
98
+ "This business isn’t available to your Wix account right now. Switch business, or sign in again as its owner.",
99
+ );
100
+ }
101
+ return copyParam(
102
+ "ownerCommon.upstreamStatus",
103
+ `Wix returned an error (HTTP ${coded[1]}). Try again in a moment.`,
104
+ );
105
+ }
72
106
  if (HTML_BODY.test(msg)) {
73
107
  if (status === "403") {
74
108
  return copyParam(
@@ -101,36 +135,46 @@ export function ownerPreviewUnavailable(): string {
101
135
  /**
102
136
  * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
137
  *
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.
138
+ * One difference left, and it is about WHERE the app is rather than what the
139
+ * error says: this names the web preview when there IS one. `isBridgeUnavailable`
140
+ * reads "not live" as "preview", and native injects no mode at all, so the
141
+ * default would answer a device-side `HTTP 503` with "open this app on a device"
142
+ * — on the device. That predicate is deliberately left alone: native
143
+ * write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
144
  *
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.
145
+ * The reductions this used to add are now in `humanWireError`, so every owner
146
+ * screen gets them rather than the two that happened to call this.
120
147
  */
121
148
  export function ownerDisplayError(e: unknown): string {
122
149
  if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
123
- const msg = String((e as Error)?.message ?? e);
124
- const status = STATUS.exec(msg)?.[1];
125
- if (JSON_BODY.test(msg) && status) {
126
- return copyParam(
127
- "ownerCommon.upstreamStatus",
128
- `Wix returned an error (HTTP ${status}). Try again in a moment.`,
129
- );
130
- }
131
- return humanWireError(msg);
150
+ return humanWireError(String((e as Error)?.message ?? e));
132
151
  }
133
152
 
153
+ /**
154
+ * The default. Reduces the same shapes as `ownerDisplayError`.
155
+ *
156
+ * It used to reduce LESS, on the stated grounds that its output is parsed
157
+ * downstream — "the products worker matches `CATALOG_V3_CALLING_CATALOG_V1_API`
158
+ * in it to choose its v1 fallback". **That was not true**, and it cost 17 owner
159
+ * screens their error copy: they printed `HTTP 428 [MISSING_METASITE_ID]: …`
160
+ * verbatim for a contract that does not exist.
161
+ *
162
+ * Checked before changing it, and worth recording so nobody restores the belief:
163
+ *
164
+ * • the catalog v1/v3 decision is made INSIDE the Wix stores SDK, on the RAW
165
+ * thrown error — its `catalogVersionFromMismatch` reads the error's own
166
+ * `message`, and only that module calls it. It never sees this function's
167
+ * output.
168
+ * • `CATALOG_V3_CALLING_CATALOG_V1_API` appears nowhere else in the codebase
169
+ * outside comments, tests and that one SDK module.
170
+ * • the collection framework's `clearOnError` is handed the RAW error
171
+ * (`error => /401|403|forbidden/i.test(String(error))`), separately from
172
+ * `errorMessage: ownerLoadError` — so the one place that does match on a
173
+ * failure is unaffected by what this returns.
174
+ *
175
+ * So: the raw error remains available to logic everywhere it was; this function's
176
+ * result is display copy, and now reads like it.
177
+ */
134
178
  export function ownerLoadError(e: unknown): string {
135
179
  const msg = String((e as Error)?.message ?? e);
136
180
  if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();
@@ -53,6 +53,17 @@ const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
53
53
  * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
54
  * never caught it and the owner read the machine's word for it. */
55
55
  const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
56
+ /** `HTTP <status> [SERVICE_CODE]: message` — the shape a Wix service uses when
57
+ * it refuses with a named reason. */
58
+ const CODED = /HTTP (\d{3}) \[([A-Z0-9_]+)\]/;
59
+ /**
60
+ * The codes that all mean one thing to an owner: the session cannot address this
61
+ * business. Found on a real session whose account did not own the business the app
62
+ * had persisted — Home rendered
63
+ * `HTTP 428 [MISSING_METASITE_ID]: Couldn't extract metasiteId from the request
64
+ * context` where "6 orders" belongs.
65
+ */
66
+ const SITE_CONTEXT_CODES = ["MISSING_METASITE_ID", "MISSING_REQUEST_SITE_CONTEXT", "MISSING_SITE_CONTEXT"];
56
67
  /** Longest raw upstream text worth showing. Beyond this it stops being a
57
68
  * message and starts being a payload dump. */
58
69
  const MAX_RAW = 200;
@@ -69,6 +80,29 @@ const MAX_RAW = 200;
69
80
  */
70
81
  function humanWireError(msg: string): string {
71
82
  const status = STATUS.exec(msg)?.[1];
83
+ // A JSON ENVELOPE or a NAMED REASON CODE is the machine's word for it, and the
84
+ // owner gets neither. Both reductions used to live in `ownerDisplayError` only,
85
+ // on the belief that this function's output was parsed downstream — see the note
86
+ // on `ownerLoadError`, which records why that was wrong and how it was checked.
87
+ if (JSON_BODY.test(msg) && status) {
88
+ return copyParam(
89
+ "ownerCommon.upstreamStatus",
90
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
91
+ );
92
+ }
93
+ const coded = CODED.exec(msg);
94
+ if (coded) {
95
+ if (SITE_CONTEXT_CODES.includes(coded[2])) {
96
+ return copyParam(
97
+ "ownerCommon.noSiteContext",
98
+ "This business isn’t available to your Wix account right now. Switch business, or sign in again as its owner.",
99
+ );
100
+ }
101
+ return copyParam(
102
+ "ownerCommon.upstreamStatus",
103
+ `Wix returned an error (HTTP ${coded[1]}). Try again in a moment.`,
104
+ );
105
+ }
72
106
  if (HTML_BODY.test(msg)) {
73
107
  if (status === "403") {
74
108
  return copyParam(
@@ -101,36 +135,46 @@ export function ownerPreviewUnavailable(): string {
101
135
  /**
102
136
  * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
137
  *
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.
138
+ * One difference left, and it is about WHERE the app is rather than what the
139
+ * error says: this names the web preview when there IS one. `isBridgeUnavailable`
140
+ * reads "not live" as "preview", and native injects no mode at all, so the
141
+ * default would answer a device-side `HTTP 503` with "open this app on a device"
142
+ * — on the device. That predicate is deliberately left alone: native
143
+ * write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
144
  *
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.
145
+ * The reductions this used to add are now in `humanWireError`, so every owner
146
+ * screen gets them rather than the two that happened to call this.
120
147
  */
121
148
  export function ownerDisplayError(e: unknown): string {
122
149
  if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
123
- const msg = String((e as Error)?.message ?? e);
124
- const status = STATUS.exec(msg)?.[1];
125
- if (JSON_BODY.test(msg) && status) {
126
- return copyParam(
127
- "ownerCommon.upstreamStatus",
128
- `Wix returned an error (HTTP ${status}). Try again in a moment.`,
129
- );
130
- }
131
- return humanWireError(msg);
150
+ return humanWireError(String((e as Error)?.message ?? e));
132
151
  }
133
152
 
153
+ /**
154
+ * The default. Reduces the same shapes as `ownerDisplayError`.
155
+ *
156
+ * It used to reduce LESS, on the stated grounds that its output is parsed
157
+ * downstream — "the products worker matches `CATALOG_V3_CALLING_CATALOG_V1_API`
158
+ * in it to choose its v1 fallback". **That was not true**, and it cost 17 owner
159
+ * screens their error copy: they printed `HTTP 428 [MISSING_METASITE_ID]: …`
160
+ * verbatim for a contract that does not exist.
161
+ *
162
+ * Checked before changing it, and worth recording so nobody restores the belief:
163
+ *
164
+ * • the catalog v1/v3 decision is made INSIDE the Wix stores SDK, on the RAW
165
+ * thrown error — its `catalogVersionFromMismatch` reads the error's own
166
+ * `message`, and only that module calls it. It never sees this function's
167
+ * output.
168
+ * • `CATALOG_V3_CALLING_CATALOG_V1_API` appears nowhere else in the codebase
169
+ * outside comments, tests and that one SDK module.
170
+ * • the collection framework's `clearOnError` is handed the RAW error
171
+ * (`error => /401|403|forbidden/i.test(String(error))`), separately from
172
+ * `errorMessage: ownerLoadError` — so the one place that does match on a
173
+ * failure is unaffected by what this returns.
174
+ *
175
+ * So: the raw error remains available to logic everywhere it was; this function's
176
+ * result is display copy, and now reads like it.
177
+ */
134
178
  export function ownerLoadError(e: unknown): string {
135
179
  const msg = String((e as Error)?.message ?? e);
136
180
  if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();
@@ -13,7 +13,11 @@ import { queryMembers } from "@himalaya/wix-members";
13
13
  import { queryReviews } from "@himalaya/wix-reviews";
14
14
  import { queryReservations } from "@himalaya/wix-table-reservations";
15
15
  import { getSiteAnalytics, getSiteTimeZone, localDateRange, localDateOf, localMidnightUtc } from "@himalaya/wix-analytics";
16
- import { ownerLoadError, isBridgeUnavailable, isOwnerPreviewDemo } from "../_shared/owner-load-error.js";
16
+ // `ownerDisplayError`, not `ownerLoadError`: every string below is bound into a
17
+ // `text(...)` and never matched on — the RAW reason is kept beside it in
18
+ // `<part>Reason` for anything that needs it — so the display-only reduction is
19
+ // both safe here and the reason a 428 reason code stopped reaching the owner.
20
+ import { ownerDisplayError, isBridgeUnavailable, isOwnerPreviewDemo } from "../_shared/owner-load-error.js";
17
21
  import { DEMO_DASHBOARD, DEMO_NOTE, DEMO_TODAY } from "../_shared/owner-preview-demo.js";
18
22
  import { stashCopy, copyParam, copyTemplate } from "../_shared/himi-copy.js";
19
23
  import { formatCurrency } from "../_shared/engine-intl.js";
@@ -566,7 +570,7 @@ async function loadSource(source: HomeSourceId, parts = sourceParts(source)): Pr
566
570
  const item = settled[index];
567
571
  if (item.status === "fulfilled") result[part] = item.value.payload;
568
572
  else {
569
- result[`${part}Error`] = ownerLoadError(item.reason);
573
+ result[`${part}Error`] = ownerDisplayError(item.reason);
570
574
  result[`${part}Reason`] = item.reason;
571
575
  }
572
576
  }
@@ -798,7 +802,7 @@ async function loadDashboard(s: HomeState, sources: HomeSourceId[]): Promise<Hom
798
802
  if (failedReasons.length > 0 && failedReasons.every((reason) => isBridgeUnavailable(reason))) {
799
803
  return demoDashboard(shape({ ...s, sourceStates: [], sourceFailures: [] }));
800
804
  }
801
- return shape({ ...composeState(), status: "error", error: ownerLoadError(failedReasons[0]) });
805
+ return shape({ ...composeState(), status: "error", error: ownerDisplayError(failedReasons[0]) });
802
806
  }
803
807
  return filteredSchedule(composeState(), activeScheduleFilter);
804
808
  }
@@ -53,6 +53,17 @@ const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
53
53
  * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
54
  * never caught it and the owner read the machine's word for it. */
55
55
  const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
56
+ /** `HTTP <status> [SERVICE_CODE]: message` — the shape a Wix service uses when
57
+ * it refuses with a named reason. */
58
+ const CODED = /HTTP (\d{3}) \[([A-Z0-9_]+)\]/;
59
+ /**
60
+ * The codes that all mean one thing to an owner: the session cannot address this
61
+ * business. Found on a real session whose account did not own the business the app
62
+ * had persisted — Home rendered
63
+ * `HTTP 428 [MISSING_METASITE_ID]: Couldn't extract metasiteId from the request
64
+ * context` where "6 orders" belongs.
65
+ */
66
+ const SITE_CONTEXT_CODES = ["MISSING_METASITE_ID", "MISSING_REQUEST_SITE_CONTEXT", "MISSING_SITE_CONTEXT"];
56
67
  /** Longest raw upstream text worth showing. Beyond this it stops being a
57
68
  * message and starts being a payload dump. */
58
69
  const MAX_RAW = 200;
@@ -69,6 +80,29 @@ const MAX_RAW = 200;
69
80
  */
70
81
  function humanWireError(msg: string): string {
71
82
  const status = STATUS.exec(msg)?.[1];
83
+ // A JSON ENVELOPE or a NAMED REASON CODE is the machine's word for it, and the
84
+ // owner gets neither. Both reductions used to live in `ownerDisplayError` only,
85
+ // on the belief that this function's output was parsed downstream — see the note
86
+ // on `ownerLoadError`, which records why that was wrong and how it was checked.
87
+ if (JSON_BODY.test(msg) && status) {
88
+ return copyParam(
89
+ "ownerCommon.upstreamStatus",
90
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
91
+ );
92
+ }
93
+ const coded = CODED.exec(msg);
94
+ if (coded) {
95
+ if (SITE_CONTEXT_CODES.includes(coded[2])) {
96
+ return copyParam(
97
+ "ownerCommon.noSiteContext",
98
+ "This business isn’t available to your Wix account right now. Switch business, or sign in again as its owner.",
99
+ );
100
+ }
101
+ return copyParam(
102
+ "ownerCommon.upstreamStatus",
103
+ `Wix returned an error (HTTP ${coded[1]}). Try again in a moment.`,
104
+ );
105
+ }
72
106
  if (HTML_BODY.test(msg)) {
73
107
  if (status === "403") {
74
108
  return copyParam(
@@ -101,36 +135,46 @@ export function ownerPreviewUnavailable(): string {
101
135
  /**
102
136
  * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
137
  *
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.
138
+ * One difference left, and it is about WHERE the app is rather than what the
139
+ * error says: this names the web preview when there IS one. `isBridgeUnavailable`
140
+ * reads "not live" as "preview", and native injects no mode at all, so the
141
+ * default would answer a device-side `HTTP 503` with "open this app on a device"
142
+ * — on the device. That predicate is deliberately left alone: native
143
+ * write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
144
  *
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.
145
+ * The reductions this used to add are now in `humanWireError`, so every owner
146
+ * screen gets them rather than the two that happened to call this.
120
147
  */
121
148
  export function ownerDisplayError(e: unknown): string {
122
149
  if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
123
- const msg = String((e as Error)?.message ?? e);
124
- const status = STATUS.exec(msg)?.[1];
125
- if (JSON_BODY.test(msg) && status) {
126
- return copyParam(
127
- "ownerCommon.upstreamStatus",
128
- `Wix returned an error (HTTP ${status}). Try again in a moment.`,
129
- );
130
- }
131
- return humanWireError(msg);
150
+ return humanWireError(String((e as Error)?.message ?? e));
132
151
  }
133
152
 
153
+ /**
154
+ * The default. Reduces the same shapes as `ownerDisplayError`.
155
+ *
156
+ * It used to reduce LESS, on the stated grounds that its output is parsed
157
+ * downstream — "the products worker matches `CATALOG_V3_CALLING_CATALOG_V1_API`
158
+ * in it to choose its v1 fallback". **That was not true**, and it cost 17 owner
159
+ * screens their error copy: they printed `HTTP 428 [MISSING_METASITE_ID]: …`
160
+ * verbatim for a contract that does not exist.
161
+ *
162
+ * Checked before changing it, and worth recording so nobody restores the belief:
163
+ *
164
+ * • the catalog v1/v3 decision is made INSIDE the Wix stores SDK, on the RAW
165
+ * thrown error — its `catalogVersionFromMismatch` reads the error's own
166
+ * `message`, and only that module calls it. It never sees this function's
167
+ * output.
168
+ * • `CATALOG_V3_CALLING_CATALOG_V1_API` appears nowhere else in the codebase
169
+ * outside comments, tests and that one SDK module.
170
+ * • the collection framework's `clearOnError` is handed the RAW error
171
+ * (`error => /401|403|forbidden/i.test(String(error))`), separately from
172
+ * `errorMessage: ownerLoadError` — so the one place that does match on a
173
+ * failure is unaffected by what this returns.
174
+ *
175
+ * So: the raw error remains available to logic everywhere it was; this function's
176
+ * result is display copy, and now reads like it.
177
+ */
134
178
  export function ownerLoadError(e: unknown): string {
135
179
  const msg = String((e as Error)?.message ?? e);
136
180
  if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();
@@ -53,6 +53,17 @@ const HTML_BODY = /<!DOCTYPE html|<html[\s>]|<head[\s>]/i;
53
53
  * "app":"…"}` — and it is short enough to slip under MAX_RAW, so truncation
54
54
  * never caught it and the owner read the machine's word for it. */
55
55
  const JSON_BODY = /HTTP \d{3}:\s*[[{]/;
56
+ /** `HTTP <status> [SERVICE_CODE]: message` — the shape a Wix service uses when
57
+ * it refuses with a named reason. */
58
+ const CODED = /HTTP (\d{3}) \[([A-Z0-9_]+)\]/;
59
+ /**
60
+ * The codes that all mean one thing to an owner: the session cannot address this
61
+ * business. Found on a real session whose account did not own the business the app
62
+ * had persisted — Home rendered
63
+ * `HTTP 428 [MISSING_METASITE_ID]: Couldn't extract metasiteId from the request
64
+ * context` where "6 orders" belongs.
65
+ */
66
+ const SITE_CONTEXT_CODES = ["MISSING_METASITE_ID", "MISSING_REQUEST_SITE_CONTEXT", "MISSING_SITE_CONTEXT"];
56
67
  /** Longest raw upstream text worth showing. Beyond this it stops being a
57
68
  * message and starts being a payload dump. */
58
69
  const MAX_RAW = 200;
@@ -69,6 +80,29 @@ const MAX_RAW = 200;
69
80
  */
70
81
  function humanWireError(msg: string): string {
71
82
  const status = STATUS.exec(msg)?.[1];
83
+ // A JSON ENVELOPE or a NAMED REASON CODE is the machine's word for it, and the
84
+ // owner gets neither. Both reductions used to live in `ownerDisplayError` only,
85
+ // on the belief that this function's output was parsed downstream — see the note
86
+ // on `ownerLoadError`, which records why that was wrong and how it was checked.
87
+ if (JSON_BODY.test(msg) && status) {
88
+ return copyParam(
89
+ "ownerCommon.upstreamStatus",
90
+ `Wix returned an error (HTTP ${status}). Try again in a moment.`,
91
+ );
92
+ }
93
+ const coded = CODED.exec(msg);
94
+ if (coded) {
95
+ if (SITE_CONTEXT_CODES.includes(coded[2])) {
96
+ return copyParam(
97
+ "ownerCommon.noSiteContext",
98
+ "This business isn’t available to your Wix account right now. Switch business, or sign in again as its owner.",
99
+ );
100
+ }
101
+ return copyParam(
102
+ "ownerCommon.upstreamStatus",
103
+ `Wix returned an error (HTTP ${coded[1]}). Try again in a moment.`,
104
+ );
105
+ }
72
106
  if (HTML_BODY.test(msg)) {
73
107
  if (status === "403") {
74
108
  return copyParam(
@@ -101,36 +135,46 @@ export function ownerPreviewUnavailable(): string {
101
135
  /**
102
136
  * `ownerLoadError` for a screen that does nothing but RENDER the string.
103
137
  *
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.
138
+ * One difference left, and it is about WHERE the app is rather than what the
139
+ * error says: this names the web preview when there IS one. `isBridgeUnavailable`
140
+ * reads "not live" as "preview", and native injects no mode at all, so the
141
+ * default would answer a device-side `HTTP 503` with "open this app on a device"
142
+ * — on the device. That predicate is deliberately left alone: native
143
+ * write-behind (`shouldQueueOwnerWrite`) depends on exactly it.
111
144
  *
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.
145
+ * The reductions this used to add are now in `humanWireError`, so every owner
146
+ * screen gets them rather than the two that happened to call this.
120
147
  */
121
148
  export function ownerDisplayError(e: unknown): string {
122
149
  if (isOwnerPreviewHost() && isBridgeUnavailable(e)) return ownerPreviewUnavailable();
123
- const msg = String((e as Error)?.message ?? e);
124
- const status = STATUS.exec(msg)?.[1];
125
- if (JSON_BODY.test(msg) && status) {
126
- return copyParam(
127
- "ownerCommon.upstreamStatus",
128
- `Wix returned an error (HTTP ${status}). Try again in a moment.`,
129
- );
130
- }
131
- return humanWireError(msg);
150
+ return humanWireError(String((e as Error)?.message ?? e));
132
151
  }
133
152
 
153
+ /**
154
+ * The default. Reduces the same shapes as `ownerDisplayError`.
155
+ *
156
+ * It used to reduce LESS, on the stated grounds that its output is parsed
157
+ * downstream — "the products worker matches `CATALOG_V3_CALLING_CATALOG_V1_API`
158
+ * in it to choose its v1 fallback". **That was not true**, and it cost 17 owner
159
+ * screens their error copy: they printed `HTTP 428 [MISSING_METASITE_ID]: …`
160
+ * verbatim for a contract that does not exist.
161
+ *
162
+ * Checked before changing it, and worth recording so nobody restores the belief:
163
+ *
164
+ * • the catalog v1/v3 decision is made INSIDE the Wix stores SDK, on the RAW
165
+ * thrown error — its `catalogVersionFromMismatch` reads the error's own
166
+ * `message`, and only that module calls it. It never sees this function's
167
+ * output.
168
+ * • `CATALOG_V3_CALLING_CATALOG_V1_API` appears nowhere else in the codebase
169
+ * outside comments, tests and that one SDK module.
170
+ * • the collection framework's `clearOnError` is handed the RAW error
171
+ * (`error => /401|403|forbidden/i.test(String(error))`), separately from
172
+ * `errorMessage: ownerLoadError` — so the one place that does match on a
173
+ * failure is unaffected by what this returns.
174
+ *
175
+ * So: the raw error remains available to logic everywhere it was; this function's
176
+ * result is display copy, and now reads like it.
177
+ */
134
178
  export function ownerLoadError(e: unknown): string {
135
179
  const msg = String((e as Error)?.message ?? e);
136
180
  if (isBridgeUnavailable(e)) return ownerPreviewUnavailable();