@wix/himalaya-cli 0.1241.0 → 0.1242.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/dist/cli.mjs +0 -1
  2. package/package.json +2 -2
  3. package/templates/beauty-salon-owner/dev/index.ts +2 -2
  4. package/templates/beauty-salon-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  5. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  6. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  7. package/templates/community-club-owner/dev/index.ts +2 -2
  8. package/templates/community-club-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  9. package/templates/community-club-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  10. package/templates/community-club-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  11. package/templates/events-meetups-owner/dev/index.ts +2 -2
  12. package/templates/events-meetups-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  13. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  14. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  15. package/templates/gym-fitness-owner/dev/index.ts +2 -2
  16. package/templates/gym-fitness-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  17. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  18. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  19. package/templates/nonprofit-cause-owner/dev/index.ts +2 -2
  20. package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  21. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  22. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  23. package/templates/owner-lite/dev/index.ts +2 -2
  24. package/templates/owner-lite/tier5-src/_shared/owner-selected-site.ts +9 -3
  25. package/templates/owner-lite/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  26. package/templates/owner-lite/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  27. package/templates/podcast-creator-owner/dev/index.ts +2 -2
  28. package/templates/podcast-creator-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  29. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  30. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  31. package/templates/portfolio-creative-owner/dev/index.ts +2 -2
  32. package/templates/portfolio-creative-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  33. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  34. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  35. package/templates/restaurant-owner/dev/index.ts +2 -2
  36. package/templates/restaurant-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  37. package/templates/restaurant-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  38. package/templates/restaurant-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  39. package/templates/retail-store-owner/dev/index.ts +2 -2
  40. package/templates/retail-store-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  41. package/templates/retail-store-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  42. package/templates/retail-store-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
  43. package/templates/yoga-studio-owner/dev/index.ts +2 -2
  44. package/templates/yoga-studio-owner/tier5-src/_shared/owner-selected-site.ts +9 -3
  45. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-overview/index.ts +14 -9
  46. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-sites/index.ts +35 -4
@@ -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 || raw.id || "Site";
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 base = cached.length
126
- ? shape({ ...s, status: "ready", allSites: cached, sites: applySearch(cached, s.searchQuery), error: "", demoNote: "" })
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 (cached.length) return base;
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
  }
@@ -61588,7 +61588,7 @@ const SCREENS = runtimeSetting({
61588
61588
  },
61589
61589
  "pressFeedback": "scale",
61590
61590
  "haptic": "light",
61591
- "accessibilityLabel": "himi_owner_lite.site_row"
61591
+ "accessibilityLabel": "$item.spokenLabel"
61592
61592
  }
61593
61593
  }
61594
61594
  ]
@@ -62148,7 +62148,7 @@ const SCREENS = runtimeSetting({
62148
62148
  },
62149
62149
  "pressFeedback": "scale",
62150
62150
  "haptic": "light",
62151
- "accessibilityLabel": "himi_owner_lite.overview_row"
62151
+ "accessibilityLabel": "$item.spokenLabel"
62152
62152
  }
62153
62153
  }
62154
62154
  ]
@@ -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({ id, name, account: me }));
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 ? { id, name, account } : { id, name }));
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: countLabel(count),
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: count === null
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 || r.id || "Site",
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 || raw.id || "Site";
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 base = cached.length
126
- ? shape({ ...s, status: "ready", allSites: cached, sites: applySearch(cached, s.searchQuery), error: "", demoNote: "" })
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 (cached.length) return base;
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
  }