@wix/himalaya-cli 0.1241.0 → 0.1243.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/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/tokens.d.ts +94 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +40 -6
- package/dist/cli.mjs +363 -227
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/tokens.md +28 -0
- package/package.json +2 -2
- package/templates/beauty-salon-owner/dev/index.ts +2 -2
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/community-club-owner/dev/index.ts +2 -2
- package/templates/community-club-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/community-club-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/community-club-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/events-meetups-owner/dev/index.ts +2 -2
- package/templates/events-meetups-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/gym-fitness-owner/dev/index.ts +2 -2
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/nonprofit-cause-owner/dev/index.ts +2 -2
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/owner-lite/dev/index.ts +2 -2
- package/templates/owner-lite/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/owner-lite/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/owner-lite/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/podcast-creator-owner/dev/index.ts +2 -2
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/portfolio-creative-owner/dev/index.ts +2 -2
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/restaurant-owner/dev/index.ts +2 -2
- package/templates/restaurant-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/retail-store-owner/dev/index.ts +2 -2
- package/templates/retail-store-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
- package/templates/yoga-studio-owner/dev/index.ts +2 -2
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
|
Binary file
|
package/guides/tokens.md
CHANGED
|
@@ -21,6 +21,7 @@ one registry, mirrored byte-for-byte by the iOS + Android decoders.
|
|
|
21
21
|
| `kit` | { material?, variant?, density?, motion? } | optional — the active design kit |
|
|
22
22
|
| `contentMaxWidth` | { base, medium?, expanded? } | optional |
|
|
23
23
|
| `elevations` | name → { level?, tint?, shadow?, border? } | optional — a Card wears `card` |
|
|
24
|
+
| `controls` | { button?: { sm?, md?, lg?, variants? } } | optional — every Button's size and paint |
|
|
24
25
|
|
|
25
26
|
## ⚠️ Typography requires `size`, `weight`, AND `line`
|
|
26
27
|
|
|
@@ -96,3 +97,30 @@ by copying its `tokens` into tokens.json; the active kit's app-wide axes
|
|
|
96
97
|
is a hairline inside the edge (default 0.5). Declare none and Cards stay flat. To look native on
|
|
97
98
|
each platform, declare `"card": {}` and let the idiom overlays fill it: `material` a `level`,
|
|
98
99
|
`ios` a `border` + `shadow`.
|
|
100
|
+
|
|
101
|
+
## Buttons (`controls.button`)
|
|
102
|
+
|
|
103
|
+
Restyle every `Button` of one size or one style from the design system — no per-screen props:
|
|
104
|
+
|
|
105
|
+
```json
|
|
106
|
+
"controls": {
|
|
107
|
+
"button": {
|
|
108
|
+
"sm": { "height": 36, "paddingH": 13, "paddingV": 6, "text": "bodyEmphasis", "icon": 14 },
|
|
109
|
+
"variants": { "secondary": { "radius": 4, "borderWidth": 1 }, "primary": { "fill": "brand" } }
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
- **Per size** (`sm` / `md` / `lg`): `height` (the VISIBLE minimum height), `paddingH`,
|
|
115
|
+
`paddingV`, `text` (a `typography` token), `icon` (glyph size), `radius`.
|
|
116
|
+
- **Per variant** (`variants.<style>`, e.g. `primary`, `secondary`, `tertiary`, `destructive`):
|
|
117
|
+
`fill`, `label`, `border` (colour tokens or `#hex`), `borderWidth` (0 = no outline), `radius`
|
|
118
|
+
(wins over the size's).
|
|
119
|
+
- Every field is optional. An absent field keeps the built-in Button, so a tokens.json without
|
|
120
|
+
`controls` renders exactly as before. `Button.cornerRadius` / `Button.minHeight` on a node
|
|
121
|
+
still win over the tokens.
|
|
122
|
+
- The **44pt tap target stays**: a `height` under 44 gets a transparent hit area above and below
|
|
123
|
+
(web: a hit band, React Native: `hitSlop`, iOS/macOS: a padded content shape, Android: Material
|
|
124
|
+
3's 48dp touch target), so a 36px `sm` button still passes accessibility.
|
|
125
|
+
- `himi validate` errors when `text` names no `typography` token, or a colour names no
|
|
126
|
+
`colors` token.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1243.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": [
|
|
@@ -67,5 +67,5 @@
|
|
|
67
67
|
"artifactId": "himalaya-cli"
|
|
68
68
|
}
|
|
69
69
|
},
|
|
70
|
-
"falconPackageHash": "
|
|
70
|
+
"falconPackageHash": "7334685c40352434e2281db8d3506edef751ff4142ca450fc5c38c3d"
|
|
71
71
|
}
|
|
@@ -61885,7 +61885,7 @@ const SCREENS = runtimeSetting({
|
|
|
61885
61885
|
},
|
|
61886
61886
|
"pressFeedback": "scale",
|
|
61887
61887
|
"haptic": "light",
|
|
61888
|
-
"accessibilityLabel": "
|
|
61888
|
+
"accessibilityLabel": "$item.spokenLabel"
|
|
61889
61889
|
}
|
|
61890
61890
|
}
|
|
61891
61891
|
]
|
|
@@ -62445,7 +62445,7 @@ const SCREENS = runtimeSetting({
|
|
|
62445
62445
|
},
|
|
62446
62446
|
"pressFeedback": "scale",
|
|
62447
62447
|
"haptic": "light",
|
|
62448
|
-
"accessibilityLabel": "
|
|
62448
|
+
"accessibilityLabel": "$item.spokenLabel"
|
|
62449
62449
|
}
|
|
62450
62450
|
}
|
|
62451
62451
|
]
|
|
@@ -27,6 +27,8 @@ type Host = typeof globalThis & { himalaya?: { storage?: StorageLike; input?: Re
|
|
|
27
27
|
export interface SelectedSite {
|
|
28
28
|
id: string;
|
|
29
29
|
name: string;
|
|
30
|
+
/** True when the picker wrote this from a current, repaired Wix site row. */
|
|
31
|
+
nameFromWix?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const KEY = "himi.ownerLite.selectedSite.v1";
|
|
@@ -60,13 +62,15 @@ export function readSelectedSite(): SelectedSite | null {
|
|
|
60
62
|
if (account && account !== me) return null;
|
|
61
63
|
// An entry the picker wrote before the host knew the owner is CLAIMED by the first
|
|
62
64
|
// worker that does know it, so from then on no other owner on the device reads it.
|
|
63
|
-
if (!account && me) host()?.storage?.setItem(KEY, JSON.stringify({
|
|
65
|
+
if (!account && me) host()?.storage?.setItem(KEY, JSON.stringify({
|
|
66
|
+
id, name, ...(parsed.nameFromWix === true ? { nameFromWix: true } : {}), account: me,
|
|
67
|
+
}));
|
|
64
68
|
// A screen opened FOR a specific business (a deep link or push carrying
|
|
65
69
|
// `business`) names that business's data; a name recorded for another one would
|
|
66
70
|
// sit over the wrong numbers, so it falls back instead.
|
|
67
71
|
const opened = inputString("business");
|
|
68
72
|
if (opened && opened !== id) return null;
|
|
69
|
-
return { id, name };
|
|
73
|
+
return { id, name, ...(parsed.nameFromWix === true ? { nameFromWix: true } : {}) };
|
|
70
74
|
} catch {
|
|
71
75
|
return null; // a corrupt entry reads as "nothing picked", never as a crash
|
|
72
76
|
}
|
|
@@ -80,7 +84,9 @@ export function rememberSelectedSite(site: SelectedSite): void {
|
|
|
80
84
|
if (!id || !name) return;
|
|
81
85
|
const account = inputString("__himiPreferenceAccount");
|
|
82
86
|
try {
|
|
83
|
-
host()?.storage?.setItem(KEY, JSON.stringify(account
|
|
87
|
+
host()?.storage?.setItem(KEY, JSON.stringify(account
|
|
88
|
+
? { id, name, nameFromWix: true, account }
|
|
89
|
+
: { id, name, nameFromWix: true }));
|
|
84
90
|
} catch {
|
|
85
91
|
// Quota / storage disabled: the chrome falls back to the build-time name.
|
|
86
92
|
}
|
|
@@ -44,6 +44,8 @@ const PER_SITE_LIMIT = 50;
|
|
|
44
44
|
interface OverviewRow {
|
|
45
45
|
id: string;
|
|
46
46
|
name: string;
|
|
47
|
+
/** A complete, useful spoken name for the row's tap target. */
|
|
48
|
+
spokenLabel: string;
|
|
47
49
|
initial: string;
|
|
48
50
|
/** "3", "50+", or "—" when that business could not be read. */
|
|
49
51
|
attention: string;
|
|
@@ -79,21 +81,24 @@ function countLabel(n: number | null): string {
|
|
|
79
81
|
|
|
80
82
|
function rowFor(id: string, name: string, count: number | null): OverviewRow {
|
|
81
83
|
const needs = count !== null && count > 0;
|
|
84
|
+
const attention = countLabel(count);
|
|
85
|
+
const attentionLabel = count === null
|
|
86
|
+
? copyParam("ownerOverview.unavailable", "Couldn’t read this business")
|
|
87
|
+
: count === 0
|
|
88
|
+
? copyParam("ownerOverview.allClear", "All clear")
|
|
89
|
+
: count === 1
|
|
90
|
+
? copyParam("ownerOverview.oneNeedsAttention", "order needs attention")
|
|
91
|
+
: copyParam("ownerOverview.manyNeedAttention", "orders need attention");
|
|
82
92
|
return {
|
|
83
93
|
id,
|
|
84
94
|
name,
|
|
95
|
+
spokenLabel: [name, attention, attentionLabel].filter(Boolean).join(", "),
|
|
85
96
|
initial: initialOf(name),
|
|
86
|
-
attention
|
|
97
|
+
attention,
|
|
87
98
|
// Reads as ONE phrase with the count rendered beside it — "3" + "orders need
|
|
88
99
|
// attention". So the label never repeats the number, or the row would say "1 · 1 order
|
|
89
100
|
// needs attention".
|
|
90
|
-
attentionLabel
|
|
91
|
-
? copyParam("ownerOverview.unavailable", "Couldn’t read this business")
|
|
92
|
-
: count === 0
|
|
93
|
-
? copyParam("ownerOverview.allClear", "All clear")
|
|
94
|
-
: count === 1
|
|
95
|
-
? copyParam("ownerOverview.oneNeedsAttention", "order needs attention")
|
|
96
|
-
: copyParam("ownerOverview.manyNeedAttention", "orders need attention"),
|
|
101
|
+
attentionLabel,
|
|
97
102
|
needsAttention: needs,
|
|
98
103
|
calm: !needs,
|
|
99
104
|
};
|
|
@@ -179,7 +184,7 @@ async function loadRollup(): Promise<OverviewState> {
|
|
|
179
184
|
.filter((r: WixSite) => r?.id)
|
|
180
185
|
.map((r: WixSite) => ({
|
|
181
186
|
id: r.id || "",
|
|
182
|
-
name: r.displayName || (r as { name?: string }).name ||
|
|
187
|
+
name: r.displayName || (r as { name?: string }).name || copyParam("ownerOverview.unnamedBusiness", "Business"),
|
|
183
188
|
}));
|
|
184
189
|
if (businesses.length === 0) return shape({ status: "ready", rows: [] });
|
|
185
190
|
|
|
@@ -13,6 +13,7 @@ import { DEMO_SITES, DEMO_NOTE } from "../_shared/owner-preview-demo.js";
|
|
|
13
13
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
14
14
|
import { getAll, upsert, type DbEntity } from "../_shared/owner-local-db.js";
|
|
15
15
|
import { pickSite, pickedSite } from "../_shared/owner-site-pick.js";
|
|
16
|
+
import { readSelectedSite } from "../_shared/owner-selected-site.js";
|
|
16
17
|
|
|
17
18
|
// Offline-first (data-offline): the owner's last-synced businesses are the
|
|
18
19
|
// READ-THROUGH cache for the `sites` entity — paint them instantly (works
|
|
@@ -33,6 +34,8 @@ interface SiteRow {
|
|
|
33
34
|
id: string; // metaSiteId
|
|
34
35
|
name: string;
|
|
35
36
|
meta: string;
|
|
37
|
+
/** A complete, useful spoken name for the row's tap target. */
|
|
38
|
+
spokenLabel: string;
|
|
36
39
|
initial: string;
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -55,12 +58,13 @@ interface SitesState {
|
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
function normalize(raw: WixSite): SiteRow {
|
|
58
|
-
const name = raw.displayName || (raw as { name?: string }).name ||
|
|
61
|
+
const name = raw.displayName || (raw as { name?: string }).name || copyParam("ownerSites.unnamedBusiness", "Business");
|
|
59
62
|
const studio = (raw as { namespace?: string }).namespace === "WIX_STUDIO";
|
|
60
63
|
return {
|
|
61
64
|
id: raw.id || "",
|
|
62
65
|
name,
|
|
63
66
|
meta: studio ? "Wix Studio site" : "Wix site",
|
|
67
|
+
spokenLabel: [name, studio ? "Wix Studio site" : "Wix site"].join(", "),
|
|
64
68
|
initial: (name.trim()[0] || "S").toUpperCase(),
|
|
65
69
|
};
|
|
66
70
|
}
|
|
@@ -70,6 +74,30 @@ function applySearch(rows: SiteRow[], query: string): SiteRow[] {
|
|
|
70
74
|
return q ? rows.filter((r) => r.name.toLowerCase().includes(q)) : rows;
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
/** Upgrade rows cached by older bundles, which used the private site id as a fallback name. */
|
|
78
|
+
function repairCachedRow(row: SiteRow, selected: ReturnType<typeof readSelectedSite>): SiteRow {
|
|
79
|
+
// Rows written by this bundle already carry a spoken name, so preserve valid
|
|
80
|
+
// Wix display names even when a business name happens to equal its site ID.
|
|
81
|
+
if (typeof row.spokenLabel === "string" && row.spokenLabel) return row;
|
|
82
|
+
// A legacy cache has no provenance bit. For the currently selected business,
|
|
83
|
+
// the picker kept the Wix display name separately; use it to distinguish a
|
|
84
|
+
// legitimate same-as-ID name from the old private-ID fallback.
|
|
85
|
+
const selectedName = selected?.id === row.id
|
|
86
|
+
&& (selected.name !== row.id || selected.nameFromWix === true)
|
|
87
|
+
? selected.name : "";
|
|
88
|
+
const name = selectedName || (row.name && row.name !== row.id
|
|
89
|
+
? row.name
|
|
90
|
+
: copyParam("ownerSites.unnamedBusiness", "Business"));
|
|
91
|
+
const meta = typeof row.meta === "string" ? row.meta : "";
|
|
92
|
+
return {
|
|
93
|
+
...row,
|
|
94
|
+
name,
|
|
95
|
+
meta,
|
|
96
|
+
spokenLabel: [name, ...meta.split(" · ")].filter(Boolean).join(", "),
|
|
97
|
+
initial: (name.trim()[0] || "S").toUpperCase(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
73
101
|
function emptyFor(query = ""): string {
|
|
74
102
|
return query.trim()
|
|
75
103
|
? copyParam("ownerSites.emptySearch", "No businesses match that search.")
|
|
@@ -106,6 +134,7 @@ function demoList(s: SitesState): SitesState {
|
|
|
106
134
|
id: d.id,
|
|
107
135
|
name: d.name,
|
|
108
136
|
meta: [d.domain, d.plan].filter(Boolean).join(" · "),
|
|
137
|
+
spokenLabel: [d.name, d.domain, d.plan].filter(Boolean).join(", "),
|
|
109
138
|
initial: (d.name[0] || "S").toUpperCase(),
|
|
110
139
|
}));
|
|
111
140
|
return shape({
|
|
@@ -122,8 +151,10 @@ async function loadList(s: SitesState): Promise<SitesState> {
|
|
|
122
151
|
if (isOwnerPreviewDemo()) return demoList(s);
|
|
123
152
|
// 1) Instant paint from the on-device cache (offline-safe; empty on first-ever run).
|
|
124
153
|
const cached = await readCachedRows();
|
|
125
|
-
const
|
|
126
|
-
|
|
154
|
+
const selected = readSelectedSite();
|
|
155
|
+
const repairedCache = cached.map((row) => repairCachedRow(row, selected));
|
|
156
|
+
const base = repairedCache.length
|
|
157
|
+
? shape({ ...s, status: "ready", allSites: repairedCache, sites: applySearch(repairedCache, s.searchQuery), error: "", demoNote: "" })
|
|
127
158
|
: s;
|
|
128
159
|
// 2) Revalidate from Wix and refresh the cache.
|
|
129
160
|
try {
|
|
@@ -133,7 +164,7 @@ async function loadList(s: SitesState): Promise<SitesState> {
|
|
|
133
164
|
return shape({ ...s, status: "ready", allSites: rows, sites: applySearch(rows, s.searchQuery), resultSummary: `${rows.length} ${rows.length === 1 ? "business" : "businesses"}`, error: "", demoNote: "" });
|
|
134
165
|
} catch (e) {
|
|
135
166
|
// Offline / fetch failure: keep the last-synced cache BEFORE the demo/error fallbacks.
|
|
136
|
-
if (
|
|
167
|
+
if (repairedCache.length) return base;
|
|
137
168
|
if (isBridgeUnavailable(e)) return demoList(s);
|
|
138
169
|
return shape({ ...s, status: "error", allSites: [], sites: [], error: ownerLoadError(e) });
|
|
139
170
|
}
|
|
@@ -61885,7 +61885,7 @@ const SCREENS = runtimeSetting({
|
|
|
61885
61885
|
},
|
|
61886
61886
|
"pressFeedback": "scale",
|
|
61887
61887
|
"haptic": "light",
|
|
61888
|
-
"accessibilityLabel": "
|
|
61888
|
+
"accessibilityLabel": "$item.spokenLabel"
|
|
61889
61889
|
}
|
|
61890
61890
|
}
|
|
61891
61891
|
]
|
|
@@ -62445,7 +62445,7 @@ const SCREENS = runtimeSetting({
|
|
|
62445
62445
|
},
|
|
62446
62446
|
"pressFeedback": "scale",
|
|
62447
62447
|
"haptic": "light",
|
|
62448
|
-
"accessibilityLabel": "
|
|
62448
|
+
"accessibilityLabel": "$item.spokenLabel"
|
|
62449
62449
|
}
|
|
62450
62450
|
}
|
|
62451
62451
|
]
|
|
@@ -27,6 +27,8 @@ type Host = typeof globalThis & { himalaya?: { storage?: StorageLike; input?: Re
|
|
|
27
27
|
export interface SelectedSite {
|
|
28
28
|
id: string;
|
|
29
29
|
name: string;
|
|
30
|
+
/** True when the picker wrote this from a current, repaired Wix site row. */
|
|
31
|
+
nameFromWix?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const KEY = "himi.ownerLite.selectedSite.v1";
|
|
@@ -60,13 +62,15 @@ export function readSelectedSite(): SelectedSite | null {
|
|
|
60
62
|
if (account && account !== me) return null;
|
|
61
63
|
// An entry the picker wrote before the host knew the owner is CLAIMED by the first
|
|
62
64
|
// worker that does know it, so from then on no other owner on the device reads it.
|
|
63
|
-
if (!account && me) host()?.storage?.setItem(KEY, JSON.stringify({
|
|
65
|
+
if (!account && me) host()?.storage?.setItem(KEY, JSON.stringify({
|
|
66
|
+
id, name, ...(parsed.nameFromWix === true ? { nameFromWix: true } : {}), account: me,
|
|
67
|
+
}));
|
|
64
68
|
// A screen opened FOR a specific business (a deep link or push carrying
|
|
65
69
|
// `business`) names that business's data; a name recorded for another one would
|
|
66
70
|
// sit over the wrong numbers, so it falls back instead.
|
|
67
71
|
const opened = inputString("business");
|
|
68
72
|
if (opened && opened !== id) return null;
|
|
69
|
-
return { id, name };
|
|
73
|
+
return { id, name, ...(parsed.nameFromWix === true ? { nameFromWix: true } : {}) };
|
|
70
74
|
} catch {
|
|
71
75
|
return null; // a corrupt entry reads as "nothing picked", never as a crash
|
|
72
76
|
}
|
|
@@ -80,7 +84,9 @@ export function rememberSelectedSite(site: SelectedSite): void {
|
|
|
80
84
|
if (!id || !name) return;
|
|
81
85
|
const account = inputString("__himiPreferenceAccount");
|
|
82
86
|
try {
|
|
83
|
-
host()?.storage?.setItem(KEY, JSON.stringify(account
|
|
87
|
+
host()?.storage?.setItem(KEY, JSON.stringify(account
|
|
88
|
+
? { id, name, nameFromWix: true, account }
|
|
89
|
+
: { id, name, nameFromWix: true }));
|
|
84
90
|
} catch {
|
|
85
91
|
// Quota / storage disabled: the chrome falls back to the build-time name.
|
|
86
92
|
}
|
|
@@ -44,6 +44,8 @@ const PER_SITE_LIMIT = 50;
|
|
|
44
44
|
interface OverviewRow {
|
|
45
45
|
id: string;
|
|
46
46
|
name: string;
|
|
47
|
+
/** A complete, useful spoken name for the row's tap target. */
|
|
48
|
+
spokenLabel: string;
|
|
47
49
|
initial: string;
|
|
48
50
|
/** "3", "50+", or "—" when that business could not be read. */
|
|
49
51
|
attention: string;
|
|
@@ -79,21 +81,24 @@ function countLabel(n: number | null): string {
|
|
|
79
81
|
|
|
80
82
|
function rowFor(id: string, name: string, count: number | null): OverviewRow {
|
|
81
83
|
const needs = count !== null && count > 0;
|
|
84
|
+
const attention = countLabel(count);
|
|
85
|
+
const attentionLabel = count === null
|
|
86
|
+
? copyParam("ownerOverview.unavailable", "Couldn’t read this business")
|
|
87
|
+
: count === 0
|
|
88
|
+
? copyParam("ownerOverview.allClear", "All clear")
|
|
89
|
+
: count === 1
|
|
90
|
+
? copyParam("ownerOverview.oneNeedsAttention", "order needs attention")
|
|
91
|
+
: copyParam("ownerOverview.manyNeedAttention", "orders need attention");
|
|
82
92
|
return {
|
|
83
93
|
id,
|
|
84
94
|
name,
|
|
95
|
+
spokenLabel: [name, attention, attentionLabel].filter(Boolean).join(", "),
|
|
85
96
|
initial: initialOf(name),
|
|
86
|
-
attention
|
|
97
|
+
attention,
|
|
87
98
|
// Reads as ONE phrase with the count rendered beside it — "3" + "orders need
|
|
88
99
|
// attention". So the label never repeats the number, or the row would say "1 · 1 order
|
|
89
100
|
// needs attention".
|
|
90
|
-
attentionLabel
|
|
91
|
-
? copyParam("ownerOverview.unavailable", "Couldn’t read this business")
|
|
92
|
-
: count === 0
|
|
93
|
-
? copyParam("ownerOverview.allClear", "All clear")
|
|
94
|
-
: count === 1
|
|
95
|
-
? copyParam("ownerOverview.oneNeedsAttention", "order needs attention")
|
|
96
|
-
: copyParam("ownerOverview.manyNeedAttention", "orders need attention"),
|
|
101
|
+
attentionLabel,
|
|
97
102
|
needsAttention: needs,
|
|
98
103
|
calm: !needs,
|
|
99
104
|
};
|
|
@@ -179,7 +184,7 @@ async function loadRollup(): Promise<OverviewState> {
|
|
|
179
184
|
.filter((r: WixSite) => r?.id)
|
|
180
185
|
.map((r: WixSite) => ({
|
|
181
186
|
id: r.id || "",
|
|
182
|
-
name: r.displayName || (r as { name?: string }).name ||
|
|
187
|
+
name: r.displayName || (r as { name?: string }).name || copyParam("ownerOverview.unnamedBusiness", "Business"),
|
|
183
188
|
}));
|
|
184
189
|
if (businesses.length === 0) return shape({ status: "ready", rows: [] });
|
|
185
190
|
|
|
@@ -13,6 +13,7 @@ import { DEMO_SITES, DEMO_NOTE } from "../_shared/owner-preview-demo.js";
|
|
|
13
13
|
import { stashCopy, copyParam } from "../_shared/himi-copy.js";
|
|
14
14
|
import { getAll, upsert, type DbEntity } from "../_shared/owner-local-db.js";
|
|
15
15
|
import { pickSite, pickedSite } from "../_shared/owner-site-pick.js";
|
|
16
|
+
import { readSelectedSite } from "../_shared/owner-selected-site.js";
|
|
16
17
|
|
|
17
18
|
// Offline-first (data-offline): the owner's last-synced businesses are the
|
|
18
19
|
// READ-THROUGH cache for the `sites` entity — paint them instantly (works
|
|
@@ -33,6 +34,8 @@ interface SiteRow {
|
|
|
33
34
|
id: string; // metaSiteId
|
|
34
35
|
name: string;
|
|
35
36
|
meta: string;
|
|
37
|
+
/** A complete, useful spoken name for the row's tap target. */
|
|
38
|
+
spokenLabel: string;
|
|
36
39
|
initial: string;
|
|
37
40
|
}
|
|
38
41
|
|
|
@@ -55,12 +58,13 @@ interface SitesState {
|
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
function normalize(raw: WixSite): SiteRow {
|
|
58
|
-
const name = raw.displayName || (raw as { name?: string }).name ||
|
|
61
|
+
const name = raw.displayName || (raw as { name?: string }).name || copyParam("ownerSites.unnamedBusiness", "Business");
|
|
59
62
|
const studio = (raw as { namespace?: string }).namespace === "WIX_STUDIO";
|
|
60
63
|
return {
|
|
61
64
|
id: raw.id || "",
|
|
62
65
|
name,
|
|
63
66
|
meta: studio ? "Wix Studio site" : "Wix site",
|
|
67
|
+
spokenLabel: [name, studio ? "Wix Studio site" : "Wix site"].join(", "),
|
|
64
68
|
initial: (name.trim()[0] || "S").toUpperCase(),
|
|
65
69
|
};
|
|
66
70
|
}
|
|
@@ -70,6 +74,30 @@ function applySearch(rows: SiteRow[], query: string): SiteRow[] {
|
|
|
70
74
|
return q ? rows.filter((r) => r.name.toLowerCase().includes(q)) : rows;
|
|
71
75
|
}
|
|
72
76
|
|
|
77
|
+
/** Upgrade rows cached by older bundles, which used the private site id as a fallback name. */
|
|
78
|
+
function repairCachedRow(row: SiteRow, selected: ReturnType<typeof readSelectedSite>): SiteRow {
|
|
79
|
+
// Rows written by this bundle already carry a spoken name, so preserve valid
|
|
80
|
+
// Wix display names even when a business name happens to equal its site ID.
|
|
81
|
+
if (typeof row.spokenLabel === "string" && row.spokenLabel) return row;
|
|
82
|
+
// A legacy cache has no provenance bit. For the currently selected business,
|
|
83
|
+
// the picker kept the Wix display name separately; use it to distinguish a
|
|
84
|
+
// legitimate same-as-ID name from the old private-ID fallback.
|
|
85
|
+
const selectedName = selected?.id === row.id
|
|
86
|
+
&& (selected.name !== row.id || selected.nameFromWix === true)
|
|
87
|
+
? selected.name : "";
|
|
88
|
+
const name = selectedName || (row.name && row.name !== row.id
|
|
89
|
+
? row.name
|
|
90
|
+
: copyParam("ownerSites.unnamedBusiness", "Business"));
|
|
91
|
+
const meta = typeof row.meta === "string" ? row.meta : "";
|
|
92
|
+
return {
|
|
93
|
+
...row,
|
|
94
|
+
name,
|
|
95
|
+
meta,
|
|
96
|
+
spokenLabel: [name, ...meta.split(" · ")].filter(Boolean).join(", "),
|
|
97
|
+
initial: (name.trim()[0] || "S").toUpperCase(),
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
73
101
|
function emptyFor(query = ""): string {
|
|
74
102
|
return query.trim()
|
|
75
103
|
? copyParam("ownerSites.emptySearch", "No businesses match that search.")
|
|
@@ -106,6 +134,7 @@ function demoList(s: SitesState): SitesState {
|
|
|
106
134
|
id: d.id,
|
|
107
135
|
name: d.name,
|
|
108
136
|
meta: [d.domain, d.plan].filter(Boolean).join(" · "),
|
|
137
|
+
spokenLabel: [d.name, d.domain, d.plan].filter(Boolean).join(", "),
|
|
109
138
|
initial: (d.name[0] || "S").toUpperCase(),
|
|
110
139
|
}));
|
|
111
140
|
return shape({
|
|
@@ -122,8 +151,10 @@ async function loadList(s: SitesState): Promise<SitesState> {
|
|
|
122
151
|
if (isOwnerPreviewDemo()) return demoList(s);
|
|
123
152
|
// 1) Instant paint from the on-device cache (offline-safe; empty on first-ever run).
|
|
124
153
|
const cached = await readCachedRows();
|
|
125
|
-
const
|
|
126
|
-
|
|
154
|
+
const selected = readSelectedSite();
|
|
155
|
+
const repairedCache = cached.map((row) => repairCachedRow(row, selected));
|
|
156
|
+
const base = repairedCache.length
|
|
157
|
+
? shape({ ...s, status: "ready", allSites: repairedCache, sites: applySearch(repairedCache, s.searchQuery), error: "", demoNote: "" })
|
|
127
158
|
: s;
|
|
128
159
|
// 2) Revalidate from Wix and refresh the cache.
|
|
129
160
|
try {
|
|
@@ -133,7 +164,7 @@ async function loadList(s: SitesState): Promise<SitesState> {
|
|
|
133
164
|
return shape({ ...s, status: "ready", allSites: rows, sites: applySearch(rows, s.searchQuery), resultSummary: `${rows.length} ${rows.length === 1 ? "business" : "businesses"}`, error: "", demoNote: "" });
|
|
134
165
|
} catch (e) {
|
|
135
166
|
// Offline / fetch failure: keep the last-synced cache BEFORE the demo/error fallbacks.
|
|
136
|
-
if (
|
|
167
|
+
if (repairedCache.length) return base;
|
|
137
168
|
if (isBridgeUnavailable(e)) return demoList(s);
|
|
138
169
|
return shape({ ...s, status: "error", allSites: [], sites: [], error: ownerLoadError(e) });
|
|
139
170
|
}
|
|
@@ -61517,7 +61517,7 @@ const SCREENS = runtimeSetting({
|
|
|
61517
61517
|
},
|
|
61518
61518
|
"pressFeedback": "scale",
|
|
61519
61519
|
"haptic": "light",
|
|
61520
|
-
"accessibilityLabel": "
|
|
61520
|
+
"accessibilityLabel": "$item.spokenLabel"
|
|
61521
61521
|
}
|
|
61522
61522
|
}
|
|
61523
61523
|
]
|
|
@@ -62077,7 +62077,7 @@ const SCREENS = runtimeSetting({
|
|
|
62077
62077
|
},
|
|
62078
62078
|
"pressFeedback": "scale",
|
|
62079
62079
|
"haptic": "light",
|
|
62080
|
-
"accessibilityLabel": "
|
|
62080
|
+
"accessibilityLabel": "$item.spokenLabel"
|
|
62081
62081
|
}
|
|
62082
62082
|
}
|
|
62083
62083
|
]
|
|
@@ -27,6 +27,8 @@ type Host = typeof globalThis & { himalaya?: { storage?: StorageLike; input?: Re
|
|
|
27
27
|
export interface SelectedSite {
|
|
28
28
|
id: string;
|
|
29
29
|
name: string;
|
|
30
|
+
/** True when the picker wrote this from a current, repaired Wix site row. */
|
|
31
|
+
nameFromWix?: boolean;
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
const KEY = "himi.ownerLite.selectedSite.v1";
|
|
@@ -60,13 +62,15 @@ export function readSelectedSite(): SelectedSite | null {
|
|
|
60
62
|
if (account && account !== me) return null;
|
|
61
63
|
// An entry the picker wrote before the host knew the owner is CLAIMED by the first
|
|
62
64
|
// worker that does know it, so from then on no other owner on the device reads it.
|
|
63
|
-
if (!account && me) host()?.storage?.setItem(KEY, JSON.stringify({
|
|
65
|
+
if (!account && me) host()?.storage?.setItem(KEY, JSON.stringify({
|
|
66
|
+
id, name, ...(parsed.nameFromWix === true ? { nameFromWix: true } : {}), account: me,
|
|
67
|
+
}));
|
|
64
68
|
// A screen opened FOR a specific business (a deep link or push carrying
|
|
65
69
|
// `business`) names that business's data; a name recorded for another one would
|
|
66
70
|
// sit over the wrong numbers, so it falls back instead.
|
|
67
71
|
const opened = inputString("business");
|
|
68
72
|
if (opened && opened !== id) return null;
|
|
69
|
-
return { id, name };
|
|
73
|
+
return { id, name, ...(parsed.nameFromWix === true ? { nameFromWix: true } : {}) };
|
|
70
74
|
} catch {
|
|
71
75
|
return null; // a corrupt entry reads as "nothing picked", never as a crash
|
|
72
76
|
}
|
|
@@ -80,7 +84,9 @@ export function rememberSelectedSite(site: SelectedSite): void {
|
|
|
80
84
|
if (!id || !name) return;
|
|
81
85
|
const account = inputString("__himiPreferenceAccount");
|
|
82
86
|
try {
|
|
83
|
-
host()?.storage?.setItem(KEY, JSON.stringify(account
|
|
87
|
+
host()?.storage?.setItem(KEY, JSON.stringify(account
|
|
88
|
+
? { id, name, nameFromWix: true, account }
|
|
89
|
+
: { id, name, nameFromWix: true }));
|
|
84
90
|
} catch {
|
|
85
91
|
// Quota / storage disabled: the chrome falls back to the build-time name.
|
|
86
92
|
}
|
|
@@ -44,6 +44,8 @@ const PER_SITE_LIMIT = 50;
|
|
|
44
44
|
interface OverviewRow {
|
|
45
45
|
id: string;
|
|
46
46
|
name: string;
|
|
47
|
+
/** A complete, useful spoken name for the row's tap target. */
|
|
48
|
+
spokenLabel: string;
|
|
47
49
|
initial: string;
|
|
48
50
|
/** "3", "50+", or "—" when that business could not be read. */
|
|
49
51
|
attention: string;
|
|
@@ -79,21 +81,24 @@ function countLabel(n: number | null): string {
|
|
|
79
81
|
|
|
80
82
|
function rowFor(id: string, name: string, count: number | null): OverviewRow {
|
|
81
83
|
const needs = count !== null && count > 0;
|
|
84
|
+
const attention = countLabel(count);
|
|
85
|
+
const attentionLabel = count === null
|
|
86
|
+
? copyParam("ownerOverview.unavailable", "Couldn’t read this business")
|
|
87
|
+
: count === 0
|
|
88
|
+
? copyParam("ownerOverview.allClear", "All clear")
|
|
89
|
+
: count === 1
|
|
90
|
+
? copyParam("ownerOverview.oneNeedsAttention", "order needs attention")
|
|
91
|
+
: copyParam("ownerOverview.manyNeedAttention", "orders need attention");
|
|
82
92
|
return {
|
|
83
93
|
id,
|
|
84
94
|
name,
|
|
95
|
+
spokenLabel: [name, attention, attentionLabel].filter(Boolean).join(", "),
|
|
85
96
|
initial: initialOf(name),
|
|
86
|
-
attention
|
|
97
|
+
attention,
|
|
87
98
|
// Reads as ONE phrase with the count rendered beside it — "3" + "orders need
|
|
88
99
|
// attention". So the label never repeats the number, or the row would say "1 · 1 order
|
|
89
100
|
// needs attention".
|
|
90
|
-
attentionLabel
|
|
91
|
-
? copyParam("ownerOverview.unavailable", "Couldn’t read this business")
|
|
92
|
-
: count === 0
|
|
93
|
-
? copyParam("ownerOverview.allClear", "All clear")
|
|
94
|
-
: count === 1
|
|
95
|
-
? copyParam("ownerOverview.oneNeedsAttention", "order needs attention")
|
|
96
|
-
: copyParam("ownerOverview.manyNeedAttention", "orders need attention"),
|
|
101
|
+
attentionLabel,
|
|
97
102
|
needsAttention: needs,
|
|
98
103
|
calm: !needs,
|
|
99
104
|
};
|
|
@@ -179,7 +184,7 @@ async function loadRollup(): Promise<OverviewState> {
|
|
|
179
184
|
.filter((r: WixSite) => r?.id)
|
|
180
185
|
.map((r: WixSite) => ({
|
|
181
186
|
id: r.id || "",
|
|
182
|
-
name: r.displayName || (r as { name?: string }).name ||
|
|
187
|
+
name: r.displayName || (r as { name?: string }).name || copyParam("ownerOverview.unnamedBusiness", "Business"),
|
|
183
188
|
}));
|
|
184
189
|
if (businesses.length === 0) return shape({ status: "ready", rows: [] });
|
|
185
190
|
|