@wix/himalaya-cli 0.911.0 → 0.913.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.mjs +5 -7
- package/guides/getting-started.md +3 -3
- package/guides/index.json +1 -1
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/branded-lite/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/community-club/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/community-club-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/community-club-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/community-club-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/events-meetups/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/events-meetups-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/gym-fitness/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/himi-chat/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/nonprofit-cause/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/owner-lite/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/owner-lite/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/owner-lite/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/podcast-creator/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/portfolio-creative/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/restaurant/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/restaurant-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/retail-store/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/retail-store-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
- package/templates/yoga-studio/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-load-error.ts +68 -7
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-analytics/index.ts +34 -16
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-analytics/report.ts +14 -3
package/dist/cli.mjs
CHANGED
|
@@ -18526,7 +18526,6 @@ function shouldRevealError(args) {
|
|
|
18526
18526
|
var EMAIL_RE, URL_HOST_RE, INTEGER_RE, NUMBER_RE;
|
|
18527
18527
|
var init_validation = __esm({
|
|
18528
18528
|
"../../core/ts/src/validation.ts"() {
|
|
18529
|
-
"use strict";
|
|
18530
18529
|
EMAIL_RE = /^[A-Za-z0-9._%+-]+@[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*\.[A-Za-z]{2,}$/;
|
|
18531
18530
|
URL_HOST_RE = /^[A-Za-z0-9-]+(\.[A-Za-z0-9-]+)*(:[0-9]+)?$/;
|
|
18532
18531
|
INTEGER_RE = /^[+-]?[0-9]+$/;
|
|
@@ -21950,7 +21949,6 @@ function validateWorkerSubtree(raw, slotId, policy, actionCatalog) {
|
|
|
21950
21949
|
var WORKER_SUBTREE_CEILINGS, ID_PART, ACTION_ID;
|
|
21951
21950
|
var init_worker_subtree = __esm({
|
|
21952
21951
|
"../../core/ts/src/worker-subtree.ts"() {
|
|
21953
|
-
"use strict";
|
|
21954
21952
|
init_worker_subtree_safe_types_generated();
|
|
21955
21953
|
WORKER_SUBTREE_CEILINGS = Object.freeze({
|
|
21956
21954
|
maxEncodedBytes: 65536,
|
|
@@ -53930,7 +53928,7 @@ ${guardrails.map((g) => `- ${g}`).join("\n")}
|
|
|
53930
53928
|
descriptor pair; shows the *wiring* (state, \`$service\` bindings, actions).
|
|
53931
53929
|
- \`examples/stocks-dashboard.descriptor.json\` \u2014 a fully-composed **real screen** (a Portfolio
|
|
53932
53930
|
dashboard) to study for layout, spacing rhythm, and semantic color. This is what "good" looks like.
|
|
53933
|
-
- Full guides: run \`himi docs\` (list) / \`himi docs <topic>\`, or
|
|
53931
|
+
- Full guides: run \`himi docs\` (list) / \`himi docs <topic>\`, or read them in a browser:
|
|
53934
53932
|
${idx.portalUrl}
|
|
53935
53933
|
`;
|
|
53936
53934
|
}
|
|
@@ -54119,7 +54117,7 @@ var PRODUCED_HINTS = {
|
|
|
54119
54117
|
unbuilt: "this package has no dist/bundles, so there is nothing to measure \u2014 run `himi build` first. A package with no Tier-5 bundle can declare no widget at all.",
|
|
54120
54118
|
"producer-unavailable": "the widget producer is not resolvable in this install, so production cannot be measured here at all \u2014 building this package will not change that. Ask a running app instead: GET /v1/widgets."
|
|
54121
54119
|
};
|
|
54122
|
-
var DEVELOPER_PORTAL_URL = `${
|
|
54120
|
+
var DEVELOPER_PORTAL_URL = `${SERVE_ORIGIN_PUBLIC_APPS}/docs`;
|
|
54123
54121
|
var MAX_APP_SOURCE_BYTES_HELP = "4 MiB (4,194,304 bytes)";
|
|
54124
54122
|
var USAGE = `himi \u2014 the Himalaya authoring CLI (remote dev loop)
|
|
54125
54123
|
|
|
@@ -54596,7 +54594,7 @@ Learn (works offline \u2014 no serve stack needed):
|
|
|
54596
54594
|
Content dir defaults to the current directory (its himalaya.content.json / dir name is the app id).
|
|
54597
54595
|
Transport: same targets/tokens as himi-serve \u2014 --target <local|prod|bo>, --serve-url/--control-url/
|
|
54598
54596
|
--telemetry-url, --control-token/--telemetry-token, or HIMI_* env.
|
|
54599
|
-
|
|
54597
|
+
Guides: ${DEVELOPER_PORTAL_URL}`;
|
|
54600
54598
|
var ONLY_USAGE = "himi push --only <selector>[,<selector>\u2026] (--draft|--live) [--from-release <id>] [--accept-regressions] [--accept-partial]";
|
|
54601
54599
|
var PATCH_HELP = `himi patch \u2014 change ONE item of a published app, from a raw file, on a machine
|
|
54602
54600
|
that does not have the content package.
|
|
@@ -55458,7 +55456,7 @@ ${cat}`);
|
|
|
55458
55456
|
io.log("\n\u2014\u2014\u2014\n");
|
|
55459
55457
|
io.log(readGuide("headless-loop") ?? "");
|
|
55460
55458
|
io.log(`
|
|
55461
|
-
|
|
55459
|
+
Guides in your browser: ${idx.portalUrl}`);
|
|
55462
55460
|
io.log("\nTopics \u2014 read one with `himi docs <id>`:");
|
|
55463
55461
|
listByCategory();
|
|
55464
55462
|
return 0;
|
|
@@ -55568,7 +55566,7 @@ Developer portal: ${idx.portalUrl}`);
|
|
|
55568
55566
|
io.log("Himalaya authoring topics you can learn \u2014 read one with `himi docs <id>`:");
|
|
55569
55567
|
listByCategory();
|
|
55570
55568
|
io.log(`
|
|
55571
|
-
Seed your coding agent with \`himi agent-init\`.
|
|
55569
|
+
Seed your coding agent with \`himi agent-init\`. Guides: ${idx.portalUrl}`);
|
|
55572
55570
|
return 0;
|
|
55573
55571
|
}
|
|
55574
55572
|
case "agent-init": {
|
|
@@ -28,9 +28,9 @@ to guess the customer's data model.
|
|
|
28
28
|
|
|
29
29
|
## Two ways to begin
|
|
30
30
|
|
|
31
|
-
**A.
|
|
32
|
-
|
|
33
|
-
|
|
31
|
+
**A. Read the guides in your browser** — https://apps.wix.com/_api/himi-serve/docs
|
|
32
|
+
Every guide, the component reference and the importable SDKs, as readable pages. Public: no Wix
|
|
33
|
+
account, no corporate network. Append `/<topic>.md` for the raw markdown an agent should fetch.
|
|
34
34
|
|
|
35
35
|
**B. From your terminal / coding agent:**
|
|
36
36
|
|
package/guides/index.json
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"componentCount": 82,
|
|
5
5
|
"schemaSource": "core/ts/src/schema.ts"
|
|
6
6
|
},
|
|
7
|
-
"portalUrl": "https://
|
|
7
|
+
"portalUrl": "https://apps.wix.com/_api/himi-serve/docs",
|
|
8
8
|
"bootstrapPrompt": "I want to create a mobile app for my business. Follow this skill:\nhttps://apps.wix.com/_api/himi-serve/skill.md",
|
|
9
9
|
"categories": [
|
|
10
10
|
"Overview",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.913.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": "
|
|
67
|
+
"falconPackageHash": "74209736fe693e504b68490cef2bea93e7407a47b5ea88b4ba22e8ab"
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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.
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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.
|
|
73
|
-
|
|
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
|
-
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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.
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
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.
|
|
73
|
-
|
|
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
|
+
}
|