@wix/himalaya-cli 0.945.0 → 0.947.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 +13 -0
  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
@@ -55607,6 +55607,17 @@ var TERMINAL_DIST_STATUSES = /* @__PURE__ */ new Set([
55607
55607
  "BLOCKED",
55608
55608
  "STATUS_UNKNOWN"
55609
55609
  ]);
55610
+ function shellShaDivergenceWarnings(jobs) {
55611
+ const seen = /* @__PURE__ */ new Map();
55612
+ for (const j of jobs) {
55613
+ const release = typeof j.release === "string" ? j.release : void 0;
55614
+ const built = typeof j.gitCommitSha === "string" ? j.gitCommitSha : void 0;
55615
+ if (j.shellShaSource === "release" && release && built && release !== built) seen.set(release, built);
55616
+ }
55617
+ return [...seen].map(
55618
+ ([release, built]) => `\u26A0 this build compiles the native shell at ${built.slice(0, 12)}, not release ${release.slice(0, 12)} \u2014 the release was pushed from a branch whose HEAD is not on the default branch, so the Mac pool checks out the nearest default-branch ancestor. A native-shell or tools/mobile-dist/** change on that branch is NOT in this binary even if the job reports SUCCESS; re-push from a checkout whose HEAD is on origin/master to validate it.`
55619
+ );
55620
+ }
55610
55621
  function materializeForPreview(appDir2, slug, displayName) {
55611
55622
  if (!isTemplateSourceTree(appDir2)) return appDir2;
55612
55623
  const files = readTemplateFiles(appDir2);
@@ -59821,6 +59832,7 @@ ${label2}`);
59821
59832
  return 1;
59822
59833
  }
59823
59834
  const jobs = r.json?.jobs ?? [];
59835
+ for (const line of shellShaDivergenceWarnings(jobs)) (flags.json === true ? io.error : io.log)(line);
59824
59836
  const noWait = flags["no-wait"] === true || flags.wait === false;
59825
59837
  if (noWait) {
59826
59838
  print({ status: r.status, ...r.json ?? {} });
@@ -60760,6 +60772,7 @@ export {
60760
60772
  serveUnreachableError,
60761
60773
  serveUnreachableHint,
60762
60774
  servesAll,
60775
+ shellShaDivergenceWarnings,
60763
60776
  switchFlagErrors,
60764
60777
  templatePreviewAuthError,
60765
60778
  tokenCoversApp,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wix/himalaya-cli",
3
- "version": "0.945.0",
3
+ "version": "0.947.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": "e6f6c45f77b9ba2728e1098c0c46b7d7eb3e6fb922394d52a0797dd7"
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();