@wix/himalaya-cli 0.1028.0 → 0.1030.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 +146 -36
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/component-reference.json +0 -7
- package/guides/component-reference.md +0 -2
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +590 -217
- package/templates/beauty-salon/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/beauty-salon/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/beauty-salon/tier5-src/himi-store-product/index.ts +36 -5
- package/templates/beauty-salon/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/beauty-salon-owner/dev/index.ts +22 -27
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/beauty-salon-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/branded-lite/dev/index.ts +731 -232
- package/templates/branded-lite/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-notifsettings/index.ts +11 -6
- package/templates/branded-lite/tier5-src/himi-branded-lite-reviews/index.ts +11 -6
- package/templates/branded-lite/tier5-src/himi-store-product/index.ts +36 -5
- package/templates/branded-lite/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/community-club/dev/index.ts +451 -204
- package/templates/community-club/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/community-club/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/community-club/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/community-club-owner/dev/index.ts +22 -27
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/community-club-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/events-meetups/dev/index.ts +451 -204
- package/templates/events-meetups/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/events-meetups/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/events-meetups/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/events-meetups-owner/dev/index.ts +22 -27
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/events-meetups-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/gym-fitness/dev/index.ts +590 -217
- package/templates/gym-fitness/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/gym-fitness/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/gym-fitness/tier5-src/himi-store-product/index.ts +36 -5
- package/templates/gym-fitness/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/gym-fitness-owner/dev/index.ts +22 -27
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/gym-fitness-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/nonprofit-cause/dev/index.ts +451 -204
- package/templates/nonprofit-cause/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/nonprofit-cause/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/nonprofit-cause-owner/dev/index.ts +22 -27
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/nonprofit-cause-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/owner-lite/dev/index.ts +22 -27
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/owner-lite/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/podcast-creator/dev/index.ts +451 -204
- package/templates/podcast-creator/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/podcast-creator/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/podcast-creator/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/podcast-creator-owner/dev/index.ts +22 -27
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/podcast-creator-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/portfolio-creative/dev/index.ts +575 -214
- package/templates/portfolio-creative/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/portfolio-creative/tier5-src/himi-store-product/index.ts +36 -5
- package/templates/portfolio-creative/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/portfolio-creative-owner/dev/index.ts +22 -27
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/portfolio-creative-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/restaurant/dev/index.ts +451 -204
- package/templates/restaurant/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/restaurant/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/restaurant/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/restaurant-owner/dev/index.ts +22 -27
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/restaurant-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/retail-store/dev/index.ts +575 -214
- package/templates/retail-store/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/retail-store/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/retail-store/tier5-src/himi-store-product/index.ts +36 -5
- package/templates/retail-store/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/retail-store-owner/dev/index.ts +22 -27
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/retail-store-owner/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/yoga-studio/dev/index.ts +466 -205
- package/templates/yoga-studio/tier5-src/_shared/collection-contract.ts +4 -1
- package/templates/yoga-studio/tier5-src/himi-branded-lite-appearance/index.ts +12 -1
- package/templates/yoga-studio/tier5-src/wix-spaces-app-language/index.ts +4 -2
- package/templates/yoga-studio-owner/dev/index.ts +22 -27
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +23 -3
- package/templates/yoga-studio-owner/tier5-src/_shared/collection-contract.ts +4 -1
|
@@ -14,6 +14,9 @@ export interface CollectionConfig {
|
|
|
14
14
|
searchPlaceholder?: string;
|
|
15
15
|
sortLabels?: { default: string; az: string; za: string };
|
|
16
16
|
metadata?: string[];
|
|
17
|
+
/** Numeric 0–5 row field rendered as a Rating (accessible "N out of 5") instead of
|
|
18
|
+
* folding a "★★★★☆" glyph string into the metadata line, so screen readers get a value. */
|
|
19
|
+
rating?: string;
|
|
17
20
|
sectioned?: boolean;
|
|
18
21
|
view: 'list' | 'grid';
|
|
19
22
|
backendQuery?: boolean;
|
|
@@ -58,7 +61,7 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
58
61
|
himiBrandedCoupons: { rows: 'coupons', title: 'name', subtitle: 'subtitle', view: 'list', primaryVerb: { label: 'Get code' } },
|
|
59
62
|
himiBrandedNotifications: { rows: 'items', title: 'name', subtitle: 'subtitle', image: 'photo', metadata: ['unreadDot'], view: 'list' },
|
|
60
63
|
himiBrandedLoyalty: { rows: 'rewards', title: 'name', subtitle: 'description', view: 'grid', primaryVerb: { label: 'Redeem' } },
|
|
61
|
-
himiBrandedReviews: { rows: 'reviews', title: 'title', subtitle: 'body', metadata: ['author', '
|
|
64
|
+
himiBrandedReviews: { rows: 'reviews', title: 'title', subtitle: 'body', metadata: ['author'], rating: 'rating', view: 'list' },
|
|
62
65
|
himiBrandedPortfolio: { rows: 'projects', title: 'title', subtitle: 'subtitle', image: 'image', view: 'grid', gridImageHeight: 180 },
|
|
63
66
|
himiBrandedInvoices: { rows: 'invoices', title: 'title', subtitle: 'subtitle', view: 'list' },
|
|
64
67
|
himiBrandedBadges: { rows: 'badges', title: 'name', subtitle: 'description', view: 'grid' },
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { defineService } from "@himalaya/state";
|
|
2
2
|
import {
|
|
3
|
+
ACCENT_IDS,
|
|
3
4
|
ACCENT_KEY,
|
|
4
5
|
ACCENT_META,
|
|
5
6
|
DEFAULT_ACCENT,
|
|
@@ -29,6 +30,10 @@ interface State {
|
|
|
29
30
|
accentHex_sage: string;
|
|
30
31
|
accentHex_plum: string;
|
|
31
32
|
accentHex_ocean: string;
|
|
33
|
+
// Per-accent selection flags for the radio group (`selected_<id>`) and the
|
|
34
|
+
// check mark on the swatch (`notSelected_<id>`, hiddenWhen hides on TRUE). Flat
|
|
35
|
+
// fields for the same reason as the hex ones: the swatches are static nodes.
|
|
36
|
+
[flag: `selected_${string}` | `notSelected_${string}`]: boolean | string;
|
|
32
37
|
}
|
|
33
38
|
|
|
34
39
|
interface StorageLike {
|
|
@@ -50,7 +55,13 @@ function paramValue(arg: unknown, key: string): string {
|
|
|
50
55
|
}
|
|
51
56
|
|
|
52
57
|
function build(pendingAccent: string, brandAccent: string): State {
|
|
58
|
+
const flags: Record<string, boolean> = {};
|
|
59
|
+
for (const id of ACCENT_IDS) {
|
|
60
|
+
flags[`selected_${id}`] = id === pendingAccent;
|
|
61
|
+
flags[`notSelected_${id}`] = id !== pendingAccent;
|
|
62
|
+
}
|
|
53
63
|
return {
|
|
64
|
+
...flags,
|
|
54
65
|
pendingAccent,
|
|
55
66
|
brandAccent,
|
|
56
67
|
accentHex_brand: brandAccent || ACCENT_META.amber.hex,
|
|
@@ -85,7 +96,7 @@ const svc = defineService<State, {
|
|
|
85
96
|
selectAccent: (state, arg) => {
|
|
86
97
|
const id = paramValue(arg, "id");
|
|
87
98
|
if (!isAccentId(id)) return state;
|
|
88
|
-
return
|
|
99
|
+
return build(id, state.brandAccent);
|
|
89
100
|
},
|
|
90
101
|
// Apply: persist the accent; the home worker re-reads it on launch.
|
|
91
102
|
apply: (state) => {
|
|
@@ -55,6 +55,12 @@ interface S {
|
|
|
55
55
|
shareUrl: string;
|
|
56
56
|
cartHint: string;
|
|
57
57
|
cartCount: number;
|
|
58
|
+
/** An add-to-cart write is in flight (pending line under the CTA). */
|
|
59
|
+
adding: boolean;
|
|
60
|
+
notAdding: boolean;
|
|
61
|
+
/** The last add succeeded (confirmation + View cart under the CTA). */
|
|
62
|
+
added: boolean;
|
|
63
|
+
notAdded: boolean;
|
|
58
64
|
emptyText: string;
|
|
59
65
|
isLoading: boolean;
|
|
60
66
|
notLoading: boolean;
|
|
@@ -145,6 +151,8 @@ function derive(s: S): S {
|
|
|
145
151
|
return {
|
|
146
152
|
...s,
|
|
147
153
|
saveLabel: s.saved ? copyParam("product.saved", "Saved") : copyParam("product.save", "Save product"),
|
|
154
|
+
notAdding: !s.adding,
|
|
155
|
+
notAdded: !s.added,
|
|
148
156
|
isLoading: s.status === "loading",
|
|
149
157
|
notLoading: s.status !== "loading",
|
|
150
158
|
isErrored: s.status === "error",
|
|
@@ -163,6 +171,7 @@ const svc = defineService<S, {
|
|
|
163
171
|
load: (s: S, arg?: unknown) => Promise<S>;
|
|
164
172
|
setQuantity: (s: S, arg?: unknown) => S;
|
|
165
173
|
addToCart: (s: S) => Promise<S>;
|
|
174
|
+
beginAdd: (s: S, arg?: unknown) => S;
|
|
166
175
|
share: (s: S) => Promise<S>;
|
|
167
176
|
toggleSave: (s: S) => S;
|
|
168
177
|
}>("himiStoreProduct", {
|
|
@@ -179,6 +188,10 @@ const svc = defineService<S, {
|
|
|
179
188
|
shareUrl: "",
|
|
180
189
|
cartHint: "",
|
|
181
190
|
cartCount: 0,
|
|
191
|
+
adding: false,
|
|
192
|
+
notAdding: true,
|
|
193
|
+
added: false,
|
|
194
|
+
notAdded: true,
|
|
182
195
|
emptyText: "",
|
|
183
196
|
isLoading: true,
|
|
184
197
|
notLoading: false,
|
|
@@ -201,7 +214,7 @@ const svc = defineService<S, {
|
|
|
201
214
|
// Storage problems must not prevent browsing or buying a product.
|
|
202
215
|
let saved = false;
|
|
203
216
|
try { saved = isProductSaved(id); } catch { /* save action reports a retryable error */ }
|
|
204
|
-
s = { ...s, saved, saveHint: "" };
|
|
217
|
+
s = { ...s, saved, saveHint: "", adding: false, added: false };
|
|
205
218
|
// A fresh navigation into the product resets any quantity left over
|
|
206
219
|
// from a previous visit to the same worker instance.
|
|
207
220
|
if (!id) return derive({ ...s, status: "ready", quantity: 1, emptyText: copyParam("product.notFoundText", "Product not found.") });
|
|
@@ -254,25 +267,42 @@ const svc = defineService<S, {
|
|
|
254
267
|
const quantity = parseQuantity(argValue(arg, "value"));
|
|
255
268
|
return quantity ? derive({ ...s, quantity }) : s;
|
|
256
269
|
},
|
|
270
|
+
// The pending half of addToCart, as its own reducer so it PUBLISHES before the
|
|
271
|
+
// network write starts: the pending line shows and the top-bar badge ticks up in the same
|
|
272
|
+
// interaction, instead of the tap looking dead until the round-trip (and the badge re-read)
|
|
273
|
+
// settle. Called from addToCart, not from the descriptor.
|
|
274
|
+
beginAdd: (s, arg) => {
|
|
275
|
+
const quantity = parseQuantity(argValue(arg, "quantity")) ?? 1;
|
|
276
|
+
return derive({ ...s, adding: true, added: false, cartHint: "", cartCount: s.cartCount + quantity });
|
|
277
|
+
},
|
|
257
278
|
addToCart: async (s) => {
|
|
258
279
|
const id = inputId();
|
|
259
280
|
if (!id) return derive({ ...s, cartHint: copyParam("product.cartHintNoProduct", "No product to add.") });
|
|
281
|
+
// A second tap while the first write is in flight must not add the line twice.
|
|
282
|
+
if (s.adding) return s;
|
|
260
283
|
const quantity = s.quantity > 0 ? s.quantity : 1;
|
|
284
|
+
const before = s.cartCount;
|
|
285
|
+
await svc.beginAdd({ quantity: String(quantity) });
|
|
286
|
+
s = svc.getState() as S;
|
|
261
287
|
// Optimistic local cart line — the cart repaints from cache immediately (offline or not).
|
|
262
288
|
const line: DbEntity = { id: `line-${id}`, productId: id, name: s.name || "product", title: s.name || "product", subtitle: `Qty ${quantity}`, quantity: String(quantity) };
|
|
263
289
|
await cartPut(line);
|
|
264
290
|
try {
|
|
265
291
|
await addToCurrentCart(id, quantity);
|
|
266
|
-
return derive({ ...s, cartHint: copyTemplate("product.cartHintAdded", "Added {name} to cart.", { name: s.name || "product" }), cartCount: await cartItemCount() });
|
|
267
292
|
} catch (e) {
|
|
268
293
|
if (isBridgeUnavailable(e)) {
|
|
269
294
|
// Offline / no bridge: the optimistic cache line already shows; queue the
|
|
270
295
|
// add to replay (WriteQueue, idempotent) when connectivity returns.
|
|
271
296
|
await queueWrite("tier5.himiStoreProduct.addToCart", { id });
|
|
272
|
-
return derive({ ...s, cartHint: copyTemplate("product.cartHintQueued", "Added {name} to cart. Will sync when you reconnect.", { name: s.name || "product" }) });
|
|
297
|
+
return derive({ ...s, adding: false, added: false, cartHint: copyTemplate("product.cartHintQueued", "Added {name} to cart. Will sync when you reconnect.", { name: s.name || "product" }) });
|
|
273
298
|
}
|
|
274
|
-
|
|
299
|
+
// Roll the optimistic badge back — nothing was added.
|
|
300
|
+
return derive({ ...s, adding: false, added: false, cartCount: before, cartHint: copyParam("product.cartHintFailed", "Could not add to cart. Try again.") });
|
|
275
301
|
}
|
|
302
|
+
// The write LANDED. The badge re-read is outside the write's try on purpose: a failed
|
|
303
|
+
// count must never be mistaken for a failed add. Server count wins; else keep optimistic.
|
|
304
|
+
const count = await cartItemCount().catch(() => 0);
|
|
305
|
+
return derive({ ...s, adding: false, added: true, cartHint: copyTemplate("product.cartHintAdded", "Added {name} to cart.", { name: s.name || "product" }), cartCount: count || s.cartCount });
|
|
276
306
|
},
|
|
277
307
|
toggleSave: (s) => {
|
|
278
308
|
if (s.status !== "ready" || !s.name || !inputId()) return s;
|
|
@@ -291,7 +321,8 @@ const svc = defineService<S, {
|
|
|
291
321
|
await shareSheet({ subject: s.name, text: s.name, ...(s.shareUrl ? { url: s.shareUrl } : {}) });
|
|
292
322
|
return s;
|
|
293
323
|
} catch {
|
|
294
|
-
|
|
324
|
+
// `added: false` — this hint must not render inside the add-to-cart confirmation.
|
|
325
|
+
return derive({ ...s, added: false, cartHint: copyParam("product.shareUnavailable", "Sharing is not available on this device.") });
|
|
295
326
|
}
|
|
296
327
|
},
|
|
297
328
|
},
|
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
import { defineService } from "@himalaya/state";
|
|
6
6
|
import { dispatch } from "@himalaya/dispatch";
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
/** `selected` drives the row's radio; `subtitle` stays for older descriptors. */
|
|
9
|
+
interface Row { id: string; name: string; subtitle: string; selected: boolean }
|
|
9
10
|
interface S { status: "ready"; options: Row[] }
|
|
10
11
|
|
|
11
12
|
/** Locales with catalogs (`tools/l10n/catalog.<tag>.json`) + LocaleStore support. */
|
|
12
|
-
const LOCALE_OPTIONS: Omit<Row, "subtitle">[] = [
|
|
13
|
+
const LOCALE_OPTIONS: Omit<Row, "subtitle" | "selected">[] = [
|
|
13
14
|
{ id: "en", name: "English" },
|
|
14
15
|
{ id: "es", name: "Español" },
|
|
15
16
|
{ id: "he", name: "עברית" },
|
|
@@ -19,6 +20,7 @@ function withSelected(selected: string): Row[] {
|
|
|
19
20
|
return LOCALE_OPTIONS.map((o) => ({
|
|
20
21
|
...o,
|
|
21
22
|
subtitle: o.id === selected ? "Selected" : "",
|
|
23
|
+
selected: o.id === selected,
|
|
22
24
|
}));
|
|
23
25
|
}
|
|
24
26
|
|
|
@@ -55262,7 +55262,7 @@ const SCREENS = runtimeSetting({
|
|
|
55262
55262
|
"spacing": 3,
|
|
55263
55263
|
"children": [
|
|
55264
55264
|
{
|
|
55265
|
-
"id": "himi_owner_lite_aria.msg_$item.id.
|
|
55265
|
+
"id": "himi_owner_lite_aria.msg_$item.id.content",
|
|
55266
55266
|
"body": {
|
|
55267
55267
|
"package": "himalaya",
|
|
55268
55268
|
"type": "HStack",
|
|
@@ -55281,43 +55281,34 @@ const SCREENS = runtimeSetting({
|
|
|
55281
55281
|
}
|
|
55282
55282
|
},
|
|
55283
55283
|
{
|
|
55284
|
-
"id": "",
|
|
55284
|
+
"id": "himi_owner_lite_aria.msg_$item.id.bubble",
|
|
55285
55285
|
"body": {
|
|
55286
55286
|
"package": "himalaya",
|
|
55287
55287
|
"type": "Text",
|
|
55288
55288
|
"props": {
|
|
55289
|
-
"text": "$item.
|
|
55290
|
-
"style": "
|
|
55291
|
-
"
|
|
55292
|
-
"maxLines": 1
|
|
55289
|
+
"text": "$item.content",
|
|
55290
|
+
"style": "body",
|
|
55291
|
+
"maxLines": 50
|
|
55293
55292
|
}
|
|
55294
|
-
}
|
|
55295
|
-
}
|
|
55296
|
-
]
|
|
55297
|
-
}
|
|
55298
|
-
}
|
|
55299
|
-
},
|
|
55300
|
-
{
|
|
55301
|
-
"id": "himi_owner_lite_aria.msg_$item.id.content",
|
|
55302
|
-
"body": {
|
|
55303
|
-
"package": "himalaya",
|
|
55304
|
-
"type": "HStack",
|
|
55305
|
-
"props": {
|
|
55306
|
-
"spacing": 0,
|
|
55307
|
-
"children": [
|
|
55308
|
-
{
|
|
55309
|
-
"id": "",
|
|
55310
|
-
"body": {
|
|
55311
|
-
"package": "himalaya",
|
|
55312
|
-
"type": "Spacer",
|
|
55313
|
-
"props": {}
|
|
55314
55293
|
},
|
|
55315
55294
|
"modifiers": {
|
|
55295
|
+
"accessibilityLabel": "$item.spoken",
|
|
55296
|
+
"padding": {
|
|
55297
|
+
"leading": 16,
|
|
55298
|
+
"trailing": 16,
|
|
55299
|
+
"top": 8,
|
|
55300
|
+
"bottom": 8
|
|
55301
|
+
},
|
|
55302
|
+
"background": {
|
|
55303
|
+
"token": "surfaceSecondary"
|
|
55304
|
+
},
|
|
55305
|
+
"cornerRadius": 20,
|
|
55306
|
+
"maxWidth": 300,
|
|
55316
55307
|
"hiddenPath": "$item.notUser"
|
|
55317
55308
|
}
|
|
55318
55309
|
},
|
|
55319
55310
|
{
|
|
55320
|
-
"id": "",
|
|
55311
|
+
"id": "himi_owner_lite_aria.msg_$item.id.text",
|
|
55321
55312
|
"body": {
|
|
55322
55313
|
"package": "himalaya",
|
|
55323
55314
|
"type": "Text",
|
|
@@ -55326,6 +55317,10 @@ const SCREENS = runtimeSetting({
|
|
|
55326
55317
|
"style": "body",
|
|
55327
55318
|
"maxLines": 50
|
|
55328
55319
|
}
|
|
55320
|
+
},
|
|
55321
|
+
"modifiers": {
|
|
55322
|
+
"accessibilityLabel": "$item.spoken",
|
|
55323
|
+
"hiddenPath": "$item.notAria"
|
|
55329
55324
|
}
|
|
55330
55325
|
}
|
|
55331
55326
|
]
|
|
@@ -83,6 +83,11 @@ export interface AriaBubble {
|
|
|
83
83
|
* that right-aligns the owner renders two templates and collapses one. */
|
|
84
84
|
notUser: boolean;
|
|
85
85
|
notAria: boolean;
|
|
86
|
+
/** What a screen reader says for the row: the author, then the words. A
|
|
87
|
+
* screen that draws the speaker only as a bubble and a side of the screen
|
|
88
|
+
* (`messageStyle: 'userBubble'`) has no visible author line to read, and
|
|
89
|
+
* without this the owner's question and Aria's reply sound the same. */
|
|
90
|
+
spoken: string;
|
|
86
91
|
link1Label: string; link1Url: string; link1Hidden: boolean;
|
|
87
92
|
link2Label: string; link2Url: string; link2Hidden: boolean;
|
|
88
93
|
link3Label: string; link3Url: string; link3Hidden: boolean;
|
|
@@ -561,9 +566,23 @@ export function disclosedLabel(link: { label: string; url: string }): string {
|
|
|
561
566
|
* machinery dies. (Measured: 20 tests, every one of them about polling.) */
|
|
562
567
|
export { stripMarkdown };
|
|
563
568
|
|
|
569
|
+
/** The screen-reader line for a bubble — see `AriaBubble.spoken`. */
|
|
570
|
+
export function spokenOf(who: string, content: string): string {
|
|
571
|
+
return who ? `${who}: ${content}` : content;
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
/** A persisted transcript from before `spoken` existed, made whole. A `$item.`
|
|
575
|
+
* path missing on a row does not substitute, so a row without the field would
|
|
576
|
+
* announce the literal binding until the next rebuild replaced it. */
|
|
577
|
+
export function withSpoken(bubbles: AriaBubble[]): AriaBubble[] {
|
|
578
|
+
return bubbles.every((b) => typeof b.spoken === "string")
|
|
579
|
+
? bubbles
|
|
580
|
+
: bubbles.map((b) => (typeof b.spoken === "string" ? b : { ...b, spoken: spokenOf(b.who, b.content) }));
|
|
581
|
+
}
|
|
582
|
+
|
|
564
583
|
/** A greeting or optimistic bubble: no links, all slots defaulted. */
|
|
565
584
|
export function plainBubble(id: string, role: string, who: string, content: string): AriaBubble {
|
|
566
|
-
return { id, role, who, content, notUser: role !== "USER", notAria: role === "USER", ...linkFieldsOf("", role) };
|
|
585
|
+
return { id, role, who, content, notUser: role !== "USER", notAria: role === "USER", spoken: spokenOf(who, content), ...linkFieldsOf("", role) };
|
|
567
586
|
}
|
|
568
587
|
|
|
569
588
|
/** Server transcript → bubbles. USER text is literal; ASSISTANT replies are
|
|
@@ -644,6 +663,7 @@ function bubbleOf(id: string, role: string, who: string, raw: string, writing: b
|
|
|
644
663
|
content,
|
|
645
664
|
notUser: role !== "USER",
|
|
646
665
|
notAria: role === "USER",
|
|
666
|
+
spoken: spokenOf(who, content),
|
|
647
667
|
...linkFieldsOf(prose, role),
|
|
648
668
|
};
|
|
649
669
|
}
|
|
@@ -2004,7 +2024,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2004
2024
|
// a rehydrated turn there was no ladder at all, leaving "Aria is
|
|
2005
2025
|
// typing…" on screen with nothing ever fetching the answer.
|
|
2006
2026
|
if (s._pending || s.status === "sending") {
|
|
2007
|
-
const held: AriaChatState = { ...s, status: "sending", prompts: [] };
|
|
2027
|
+
const held: AriaChatState = { ...s, status: "sending", prompts: [], messages: withSpoken(s.messages) };
|
|
2008
2028
|
return cadence(held, o.poll ? o.poll.fastMs : null, null);
|
|
2009
2029
|
}
|
|
2010
2030
|
// …but the self-kick pass runs BEFORE the pack is stashed, so the
|
|
@@ -2013,7 +2033,7 @@ export function createAriaChat(o: AriaChatOptions): AriaChatCore {
|
|
|
2013
2033
|
// still the only thing on screen — `prompts` is already recomputed
|
|
2014
2034
|
// here, and a chip pack that localizes beside a greeting that does not
|
|
2015
2035
|
// is the visible half of the same bug.
|
|
2016
|
-
const messages = isGreetingOnly(s.messages) ? greetingOnly() : s.messages;
|
|
2036
|
+
const messages = isGreetingOnly(s.messages) ? greetingOnly() : withSpoken(s.messages);
|
|
2017
2037
|
return { ...s, status: "ready", messages, prompts: promptsFor(messages, promptList()) };
|
|
2018
2038
|
}
|
|
2019
2039
|
try {
|
|
@@ -14,6 +14,9 @@ export interface CollectionConfig {
|
|
|
14
14
|
searchPlaceholder?: string;
|
|
15
15
|
sortLabels?: { default: string; az: string; za: string };
|
|
16
16
|
metadata?: string[];
|
|
17
|
+
/** Numeric 0–5 row field rendered as a Rating (accessible "N out of 5") instead of
|
|
18
|
+
* folding a "★★★★☆" glyph string into the metadata line, so screen readers get a value. */
|
|
19
|
+
rating?: string;
|
|
17
20
|
sectioned?: boolean;
|
|
18
21
|
view: 'list' | 'grid';
|
|
19
22
|
backendQuery?: boolean;
|
|
@@ -58,7 +61,7 @@ export const COLLECTIONS: Record<string, CollectionConfig> = {
|
|
|
58
61
|
himiBrandedCoupons: { rows: 'coupons', title: 'name', subtitle: 'subtitle', view: 'list', primaryVerb: { label: 'Get code' } },
|
|
59
62
|
himiBrandedNotifications: { rows: 'items', title: 'name', subtitle: 'subtitle', image: 'photo', metadata: ['unreadDot'], view: 'list' },
|
|
60
63
|
himiBrandedLoyalty: { rows: 'rewards', title: 'name', subtitle: 'description', view: 'grid', primaryVerb: { label: 'Redeem' } },
|
|
61
|
-
himiBrandedReviews: { rows: 'reviews', title: 'title', subtitle: 'body', metadata: ['author', '
|
|
64
|
+
himiBrandedReviews: { rows: 'reviews', title: 'title', subtitle: 'body', metadata: ['author'], rating: 'rating', view: 'list' },
|
|
62
65
|
himiBrandedPortfolio: { rows: 'projects', title: 'title', subtitle: 'subtitle', image: 'image', view: 'grid', gridImageHeight: 180 },
|
|
63
66
|
himiBrandedInvoices: { rows: 'invoices', title: 'title', subtitle: 'subtitle', view: 'list' },
|
|
64
67
|
himiBrandedBadges: { rows: 'badges', title: 'name', subtitle: 'description', view: 'grid' },
|