@wix/himalaya-cli 0.1240.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.
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/index.d.ts +2 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/pricing-contract.d.ts +81 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/sdk/iap/src/pricing.d.ts +3 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/iap.mjs +151 -16
- package/dist/cli.mjs +1 -0
- package/guides/iap-pricing.md +225 -0
- package/guides/index.json +6 -0
- package/guides/sdk-reference.json +72 -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
|
@@ -1705,6 +1705,78 @@
|
|
|
1705
1705
|
"jsdoc": "/** Creates a coordinator owned by the host/service lifecycle, not by a screen or render.\n * Closures, not classes: Tier-5 bundles must also evaluate on Hermes, which has no `class`. */",
|
|
1706
1706
|
"decl": "export function createPurchaseCoordinator(deps: PurchaseCoordinatorDependencies): PurchaseCoordinator;"
|
|
1707
1707
|
},
|
|
1708
|
+
{
|
|
1709
|
+
"name": "CatalogReference",
|
|
1710
|
+
"kind": "interface",
|
|
1711
|
+
"jsdoc": "",
|
|
1712
|
+
"decl": "export interface CatalogReference {\n kind: string;\n id: string;\n}"
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
"name": "PricingScope",
|
|
1716
|
+
"kind": "interface",
|
|
1717
|
+
"jsdoc": "/** Opaque context revision changes whenever a mapping-dependent audience changes. */",
|
|
1718
|
+
"decl": "export interface PricingScope {\n appId: string;\n bundleId: string;\n siteId: string;\n contextRevision?: string;\n}"
|
|
1719
|
+
},
|
|
1720
|
+
{
|
|
1721
|
+
"name": "PricingItem",
|
|
1722
|
+
"kind": "interface",
|
|
1723
|
+
"jsdoc": "",
|
|
1724
|
+
"decl": "export interface PricingItem {\n reference: CatalogReference;\n /** Owned/formatted by the commerce caller; used only for an explicit regular route. */\n commerceDisplayPrice?: string;\n}"
|
|
1725
|
+
},
|
|
1726
|
+
{
|
|
1727
|
+
"name": "PricingInput",
|
|
1728
|
+
"kind": "interface",
|
|
1729
|
+
"jsdoc": "",
|
|
1730
|
+
"decl": "export interface PricingInput {\n scope: PricingScope;\n /** Trusted host platform; only \"ios\" performs Apple lookup. */\n platform: string;\n items: PricingItem[];\n}"
|
|
1731
|
+
},
|
|
1732
|
+
{
|
|
1733
|
+
"name": "PricingMapping",
|
|
1734
|
+
"kind": "type",
|
|
1735
|
+
"jsdoc": "",
|
|
1736
|
+
"decl": "export type PricingMapping = { reference: CatalogReference } & (\n | { route: \"appleIap\"; productId: string; mappingRevision: string }\n | { route: \"regular\" }\n | { route: \"unavailable\" }\n);"
|
|
1737
|
+
},
|
|
1738
|
+
{
|
|
1739
|
+
"name": "PricingMappingProvider",
|
|
1740
|
+
"kind": "interface",
|
|
1741
|
+
"jsdoc": "/** Internal client port, not an agreed/deployed backend API. Never requires purchase context. */",
|
|
1742
|
+
"decl": "export interface PricingMappingProvider {\n resolve(input: { scope: PricingScope; references: CatalogReference[] }): Promise<PricingMapping[]>;\n}"
|
|
1743
|
+
},
|
|
1744
|
+
{
|
|
1745
|
+
"name": "PricingUnavailableReason",
|
|
1746
|
+
"kind": "type",
|
|
1747
|
+
"jsdoc": "",
|
|
1748
|
+
"decl": "export type PricingUnavailableReason = \"invalidInput\" | \"mappingUnavailable\" | \"mappingUnresolved\"\n | \"unsupportedPlatform\" | \"unsupportedProductType\" | \"priceUnavailable\" | \"commercePriceUnavailable\";"
|
|
1749
|
+
},
|
|
1750
|
+
{
|
|
1751
|
+
"name": "ItemPriceState",
|
|
1752
|
+
"kind": "type",
|
|
1753
|
+
"jsdoc": "",
|
|
1754
|
+
"decl": "export type ItemPriceState = { reference: CatalogReference } & (\n | { status: \"loading\" }\n | { status: \"ready\"; source: \"appleIap\"; displayPrice: string; productId: string; mappingRevision: string }\n | { status: \"ready\"; source: \"regular\"; displayPrice: string }\n | { status: \"unavailable\"; reason: PricingUnavailableReason; retryable: boolean }\n);"
|
|
1755
|
+
},
|
|
1756
|
+
{
|
|
1757
|
+
"name": "PricingState",
|
|
1758
|
+
"kind": "interface",
|
|
1759
|
+
"jsdoc": "/** Contains no credentials, transaction proof, entitlement, or purchase authorization. */",
|
|
1760
|
+
"decl": "export interface PricingState {\n revision: number;\n scope: PricingScope | null;\n items: ItemPriceState[];\n}"
|
|
1761
|
+
},
|
|
1762
|
+
{
|
|
1763
|
+
"name": "PricingDependencies",
|
|
1764
|
+
"kind": "interface",
|
|
1765
|
+
"jsdoc": "",
|
|
1766
|
+
"decl": "export interface PricingDependencies {\n mapping: PricingMappingProvider;\n getProducts(productIds: string[]): Promise<Product[]>;\n onState?(state: PricingState): void;\n}"
|
|
1767
|
+
},
|
|
1768
|
+
{
|
|
1769
|
+
"name": "PricingController",
|
|
1770
|
+
"kind": "interface",
|
|
1771
|
+
"jsdoc": "",
|
|
1772
|
+
"decl": "export interface PricingController {\n /** Fresh lookup; identical in-flight input is shared. Completed prices are never cached. */\n load(input: PricingInput): Promise<void>;\n /** Reloads the last input, joining a matching lookup already in progress. */\n refresh(): Promise<void>;\n getState(): PricingState;\n /** Clears state and suppresses subsequent calls/publications; cannot cancel native I/O. */\n dispose(): void;\n}"
|
|
1773
|
+
},
|
|
1774
|
+
{
|
|
1775
|
+
"name": "createPricingController",
|
|
1776
|
+
"kind": "function",
|
|
1777
|
+
"jsdoc": "/** Creates one worker's read-only pricing controller with injected mapping, store lookup, and state publication. */",
|
|
1778
|
+
"decl": "export function createPricingController(deps: PricingDependencies): PricingController;"
|
|
1779
|
+
},
|
|
1708
1780
|
{
|
|
1709
1781
|
"name": "ProductType",
|
|
1710
1782
|
"kind": "type",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1242.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": "b728c51ecb462034dd49f5a7e49e7f41069407835c052e156ff4d7ea"
|
|
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
|
|