@wix/himalaya-cli 0.1036.0 → 0.1038.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/stdlib/wix-sdk/genie-chat/src/index.d.ts +119 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/wix-sdk/portfolio/src/index.d.ts +46 -9
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-genie-chat.mjs +51 -1
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-portfolio.mjs +14 -2
- package/dist/cli.mjs +1323 -492
- package/dist/preview-assets.generated.bin +0 -0
- package/fixtures/wix-canned.json +51 -2
- package/guides/headless-loop.md +7 -0
- package/guides/himi-test.md +66 -1
- package/guides/sdk-reference.json +90 -12
- package/guides/trim-a-template.md +5 -0
- package/guides/wix-business-context.md +13 -1
- package/guides/wix-site-to-himi-app.md +10 -1
- package/package.json +2 -2
- package/templates/beauty-salon/tier5-src/himi-booking-bookings/index.ts +14 -8
- package/templates/beauty-salon/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/beauty-salon-owner/dev/index.ts +2148 -1262
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/beauty-salon-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/beauty-salon-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/branded-lite/tier5-src/himi-booking-bookings/index.ts +14 -8
- package/templates/branded-lite/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio/index.ts +2 -2
- package/templates/branded-lite/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -3
- package/templates/community-club/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/community-club-owner/dev/index.ts +2151 -1265
- package/templates/community-club-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/community-club-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/community-club-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/community-club-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/events-meetups/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/events-meetups-owner/dev/index.ts +2151 -1265
- package/templates/events-meetups-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/events-meetups-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/events-meetups-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/gym-fitness/tier5-src/himi-booking-bookings/index.ts +14 -8
- package/templates/gym-fitness/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/gym-fitness-owner/dev/index.ts +2148 -1262
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/gym-fitness-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/gym-fitness-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/nonprofit-cause/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/nonprofit-cause-owner/dev/index.ts +2148 -1262
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/nonprofit-cause-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/nonprofit-cause-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/owner-lite/dev/index.ts +2145 -1259
- package/templates/owner-lite/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/owner-lite/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/owner-lite/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/owner-lite/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/podcast-creator/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/podcast-creator-owner/dev/index.ts +2151 -1265
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/podcast-creator-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/podcast-creator-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio/index.ts +2 -2
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-portfolio-project/index.ts +3 -3
- package/templates/portfolio-creative-owner/dev/index.ts +2148 -1262
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/portfolio-creative-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/portfolio-creative-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/restaurant/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/restaurant-owner/dev/index.ts +2148 -1262
- package/templates/restaurant-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/restaurant-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/restaurant-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/retail-store/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/retail-store-owner/dev/index.ts +2151 -1265
- package/templates/retail-store-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/retail-store-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/retail-store-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
- package/templates/yoga-studio/tier5-src/himi-booking-bookings/index.ts +14 -8
- package/templates/yoga-studio/tier5-src/himi-branded-lite-home/index.ts +12 -1
- package/templates/yoga-studio-owner/dev/index.ts +2148 -1262
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-chat.ts +338 -26
- package/templates/yoga-studio-owner/tier5-src/_shared/aria-mentions.ts +202 -0
- package/templates/yoga-studio-owner/tier5-src/_shared/owner-module-data.ts +2 -2
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-appointments/index.ts +4 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-aria/index.ts +29 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-ask/index.ts +6 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-manage/index.ts +64 -6
|
Binary file
|
package/fixtures/wix-canned.json
CHANGED
|
@@ -1057,20 +1057,69 @@
|
|
|
1057
1057
|
"title": "Roastery Rebuild",
|
|
1058
1058
|
"description": "A 12-week fit-out, start to finish.",
|
|
1059
1059
|
"slug": "roastery-rebuild",
|
|
1060
|
-
"coverImage":
|
|
1060
|
+
"coverImage": {
|
|
1061
|
+
"type": "WIX_MEDIA",
|
|
1062
|
+
"imageInfo": {
|
|
1063
|
+
"id": "example-rebuild.jpg",
|
|
1064
|
+
"url": "https://static.wixstatic.com/media/example-rebuild.jpg",
|
|
1065
|
+
"width": 2752,
|
|
1066
|
+
"height": 1536
|
|
1067
|
+
},
|
|
1068
|
+
"secure": false
|
|
1069
|
+
}
|
|
1061
1070
|
},
|
|
1062
1071
|
{
|
|
1063
1072
|
"id": "proj-0002",
|
|
1064
1073
|
"title": "Harvest Field Notes",
|
|
1065
1074
|
"description": "Photography from the 2026 trip.",
|
|
1066
1075
|
"slug": "harvest-field-notes",
|
|
1067
|
-
"coverImage":
|
|
1076
|
+
"coverImage": {
|
|
1077
|
+
"type": "WIX_MEDIA",
|
|
1078
|
+
"imageInfo": {
|
|
1079
|
+
"id": "example-fieldnotes.jpg",
|
|
1080
|
+
"url": "https://static.wixstatic.com/media/example-fieldnotes.jpg",
|
|
1081
|
+
"width": 1024,
|
|
1082
|
+
"height": 1024
|
|
1083
|
+
},
|
|
1084
|
+
"secure": false
|
|
1085
|
+
}
|
|
1068
1086
|
}
|
|
1069
1087
|
],
|
|
1070
1088
|
"pagingMetadata": {
|
|
1071
1089
|
"count": 2
|
|
1072
1090
|
}
|
|
1073
1091
|
},
|
|
1092
|
+
"POST /portfolio/v1/items/query": {
|
|
1093
|
+
"items": [
|
|
1094
|
+
{
|
|
1095
|
+
"id": "item-0001",
|
|
1096
|
+
"projectId": "proj-0001",
|
|
1097
|
+
"sortOrder": 1,
|
|
1098
|
+
"type": "IMAGE",
|
|
1099
|
+
"title": "The finished bar",
|
|
1100
|
+
"image": {
|
|
1101
|
+
"type": "WIX_MEDIA",
|
|
1102
|
+
"imageInfo": {
|
|
1103
|
+
"id": "example-rebuild-bar.jpg",
|
|
1104
|
+
"url": "https://static.wixstatic.com/media/example-rebuild-bar.jpg",
|
|
1105
|
+
"width": 1600,
|
|
1106
|
+
"height": 1067
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
},
|
|
1110
|
+
{
|
|
1111
|
+
"id": "item-0002",
|
|
1112
|
+
"projectId": "proj-0001",
|
|
1113
|
+
"sortOrder": 2,
|
|
1114
|
+
"type": "TEXT",
|
|
1115
|
+
"title": "Twelve weeks",
|
|
1116
|
+
"description": "Stripped to the brick, rebuilt around the roaster."
|
|
1117
|
+
}
|
|
1118
|
+
],
|
|
1119
|
+
"metadata": {
|
|
1120
|
+
"count": 2
|
|
1121
|
+
}
|
|
1122
|
+
},
|
|
1074
1123
|
"POST /badges/v4/badges/query": {
|
|
1075
1124
|
"badges": [
|
|
1076
1125
|
{
|
package/guides/headless-loop.md
CHANGED
|
@@ -220,6 +220,13 @@ and every worker `_nav` directive is harvested, and each baked target is booted
|
|
|
220
220
|
params the source sends — the input a real router push delivers. Detail screens stop being
|
|
221
221
|
tested input-less; findings from edge boots arrive as errors tagged `edge:<source>`.
|
|
222
222
|
|
|
223
|
+
It also lists what the app **writes to Wix** — `writes[]` in `--json`, a `Writes to Wix`
|
|
224
|
+
section in the report: per screen and action, every call that is not a proven read and every
|
|
225
|
+
native write verb (member signup, payment), none of them sent anywhere. With a `## Data access`
|
|
226
|
+
table in `SPEC.md` (`Screen` + `Wix writes` columns), each write the table does not approve is a
|
|
227
|
+
`write-not-approved` warning. Reconcile that list with the owner-approved matrix before
|
|
228
|
+
publishing; see `himi docs himi-test` → "Writes to Wix".
|
|
229
|
+
|
|
223
230
|
The run ends with a **gap ledger**: every place the crawl is blind or unproven, each paired
|
|
224
231
|
with the himi command or source edit that closes it — empty lists that gate row taps ("declare
|
|
225
232
|
cms seed rows / add a net-mocks rule"), actions whose effect never reaches observable state,
|
package/guides/himi-test.md
CHANGED
|
@@ -91,7 +91,9 @@ in order, ending in a rendered result — add a short screen-local journey to `h
|
|
|
91
91
|
```
|
|
92
92
|
|
|
93
93
|
Every step must name a worker action already declared by that screen; `expectText` is
|
|
94
|
-
mandatory and is checked against the final resolved render tree.
|
|
94
|
+
mandatory and is checked against the final resolved render tree. A detail screen that loads
|
|
95
|
+
nothing without an id takes an optional `"input": { "id": "svc-0001" }` — the screen input a nav
|
|
96
|
+
edge would hand it — so the journey boots the way a real tap into it would. Keep cross-screen
|
|
95
97
|
navigation in the ordinary edge pass, which boots the destination with real source
|
|
96
98
|
parameters. Pair dynamic journeys with app-specific `dev/net-mocks.json` fixtures.
|
|
97
99
|
|
|
@@ -229,6 +231,69 @@ went unproven, it names the gap and pairs it with a himi command or a source edi
|
|
|
229
231
|
derived from what actually ran, never homework. Assembly:
|
|
230
232
|
`tools/himi-cli/src/coverage.ts`.
|
|
231
233
|
|
|
234
|
+
## Writes to Wix
|
|
235
|
+
|
|
236
|
+
Every run lists what the app **writes** to the Wix site, per screen and action — the inventory an
|
|
237
|
+
owner-approved plan is checked against. `writes[]` in `--json`, and a `Writes to Wix` section in the
|
|
238
|
+
pretty report:
|
|
239
|
+
|
|
240
|
+
```jsonc
|
|
241
|
+
"writes": [
|
|
242
|
+
{ "screen": "himi_blog_post", "action": "tier5.himiBlogPost.like", "via": "wix", "method": "POST", "path": "/blog/v3/posts/post-0001/like" },
|
|
243
|
+
{ "screen": "himi_time", "action": "tier5.himiBookingTime.register", "via": "native", "capability": "himi.wix.registerDirect", "kind": "member-signup", "what": "creates a site member" },
|
|
244
|
+
{ "screen": "himi_time", "action": "tier5.himiBookingTime.bookSelected", "via": "wix", "method": "POST", "path": "/ecom/v1/checkouts" }
|
|
245
|
+
],
|
|
246
|
+
"unreachedWrites": [
|
|
247
|
+
{ "screen": "himi_time", "action": "unreached", "via": "native", "capability": "capability.wix_cashier.proceedToPayment", "kind": "payment" }
|
|
248
|
+
]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
- **What counts as a write.** Any Wix call that is not a *proven read* — a read is `GET`, or a `POST`
|
|
252
|
+
whose last path segment is `/query`, `/search`, `/list*`, `/get*`, `/count`, `/aggregate`,
|
|
253
|
+
`/facets` or `/connections` (the same classifier `--record` uses to decide what it may send to the
|
|
254
|
+
live site). Plus the native verbs that change the site without the REST transport: member signup
|
|
255
|
+
(`himi.wix.registerDirect` — or the same verb under another `<ns>.wix` namespace), password
|
|
256
|
+
reset (`himi.wix.resetPassword`) and payment
|
|
257
|
+
(`capability.wix_cashier.proceedToPayment`). Sign-in and sign-out open a session and are not writes.
|
|
258
|
+
- **Nothing is sent.** The crawl's transport is hermetic and the host answers native verbs itself; a
|
|
259
|
+
write is recorded as *attempted*, which is exactly what the worker would send on a device.
|
|
260
|
+
- **Every axis feeds it** — boot, each tap, deep chains, journeys, nav-edge boots and the
|
|
261
|
+
capability-success pass.
|
|
262
|
+
- **Reach.** A write behind a form or a selection (type an email and a password, then register; pick
|
|
263
|
+
a slot, then book) is only reached by `--deep` or a typed journey — a single tap from a fresh boot
|
|
264
|
+
is refused by the worker's own validation. Native write verbs the screen's bundle names but the
|
|
265
|
+
crawl never reached are listed under `unreachedWrites`, so a signup or a payment is still in front
|
|
266
|
+
of you. REST writes get no such static pass: their paths are built at run time.
|
|
267
|
+
|
|
268
|
+
### Checking the writes against the approved plan
|
|
269
|
+
|
|
270
|
+
Put the owner-approved data-access matrix in `SPEC.md` (or `MOBILE-UX.md`) as a table under a
|
|
271
|
+
`## Data access` heading with a `Screen` column and a `Wix writes` column — other columns are yours:
|
|
272
|
+
|
|
273
|
+
```markdown
|
|
274
|
+
## Data access
|
|
275
|
+
| Screen | Role | Reads | Wix writes | Prohibited |
|
|
276
|
+
| --- | --- | --- | --- | --- |
|
|
277
|
+
| himi_blog_post | visitor | posts, comments | none | like, comment |
|
|
278
|
+
| himi_time | visitor | services, slots | `bookSelected`, `POST /bookings/v2/bookings` | signup, payment |
|
|
279
|
+
| * | visitor | published content | none | |
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
A `Wix writes` cell is `none` (or empty, `-`, `read-only`), or a comma list naming each approved
|
|
283
|
+
write by its **action** (`bookSelected` or `tier5.himiBookingTime.bookSelected`), its **endpoint**
|
|
284
|
+
(`POST /bookings/v2/bookings` — exact, with `*` for one path segment such as an id:
|
|
285
|
+
`POST /blog/v3/posts/*/like`), its **capability** (`registerDirect`) or its
|
|
286
|
+
**kind** (`payment`, `signup`). Prose such as "create a booking" approves nothing: it cannot say
|
|
287
|
+
whether the checkout and the payment after it were approved. A `*` row covers every screen without
|
|
288
|
+
its own row. Several rows for one screen (one per role) are combined.
|
|
289
|
+
|
|
290
|
+
Each write the table does not approve — including an `unreachedWrites` entry — is a
|
|
291
|
+
`write-not-approved` **warning** (warning so a naming slip never fails a run; silence a deliberate
|
|
292
|
+
one in `himi-test.json` like any other finding). With no filled table the writes are listed and
|
|
293
|
+
the report says nothing checked them. **Before publishing**, reconcile the list with the approved
|
|
294
|
+
plan: remove the action, or trim the screen (`himi template trim`), for every write the owner did
|
|
295
|
+
not approve.
|
|
296
|
+
|
|
232
297
|
## How agents should use it
|
|
233
298
|
|
|
234
299
|
**build → test → close the top gap → repeat.** No setup, no session, no simulator, no
|
|
@@ -16760,7 +16760,7 @@
|
|
|
16760
16760
|
"name": "ConversationMessage",
|
|
16761
16761
|
"kind": "interface",
|
|
16762
16762
|
"jsdoc": "",
|
|
16763
|
-
"decl": "export interface ConversationMessage {\n id?: string;\n content?: string;\n role?: MessageRole;\n createdDate?: string;\n status?: MessageStatus;\n /** Files the owner attached to this turn, as the service stored them. */\n attachments?: Attachment[];\n /** Client tools this assistant turn asks the CALLER to run — see `FunctionCall`. */\n functionCalls?: FunctionCall[];\n}"
|
|
16763
|
+
"decl": "export interface ConversationMessage {\n id?: string;\n content?: string;\n role?: MessageRole;\n createdDate?: string;\n status?: MessageStatus;\n /** Files the owner attached to this turn, as the service stored them. */\n attachments?: Attachment[];\n /** Client tools this assistant turn asks the CALLER to run — see `FunctionCall`. */\n functionCalls?: FunctionCall[];\n /** What the owner attached to this turn — a mentioned product, contact,\n * page — stored by the service BESIDE the text rather than inside it. The\n * transcript echoes them back under this key (the `wix.genie.v1` message\n * entity's `references`), which is the only way a reloaded conversation\n * can still say what a turn was about. */\n references?: MessageReference[];\n}"
|
|
16764
16764
|
},
|
|
16765
16765
|
{
|
|
16766
16766
|
"name": "Attachment",
|
|
@@ -16792,6 +16792,18 @@
|
|
|
16792
16792
|
"jsdoc": "/** The answer to one `FunctionCall`. Note the wire spells the flag `success`. */",
|
|
16793
16793
|
"decl": "export interface FunctionCallResult {\n id?: string;\n name?: string;\n /** Text for the model — the error text when `success` is false. */\n content?: string;\n success?: boolean;\n /** A JSON-ENCODED object carrying the tool's structured answer. */\n widgetResponse?: string;\n}"
|
|
16794
16794
|
},
|
|
16795
|
+
{
|
|
16796
|
+
"name": "MessageReference",
|
|
16797
|
+
"kind": "interface",
|
|
16798
|
+
"jsdoc": "/** One reference riding on a user turn (`messageReferences` on the persist\n * call, `references` on the stored message).\n *\n * Field meanings are the chat-service's own, from the\n * `@wix/ambassador-genie-chat-service-v1-no-entity` `MessageReference` type the\n * native Aria app sends: `data` is the context handed to the model, `source`\n * a low-cardinality feature tag, and `type` how a client renders it\n * (\"inline\" | \"global\" | \"hidden\"). Build a mention's with `mentionReference`\n * rather than by hand, so the one shape the service is known to accept is\n * spelled in one place. */",
|
|
16799
|
+
"decl": "export interface MessageReference {\n id?: string;\n /** Shown to the owner (a chip label). */\n label?: string;\n /** The context the assistant receives — for a mention, a JSON string\n * `{\"id\":…,\"type\":…}` naming the item and its category. */\n data?: string;\n /** Feature tag, e.g. \"mention\". */\n source?: string;\n /** Render scope, e.g. \"global\" — a chip above the text, not a token in it. */\n type?: string;\n}"
|
|
16800
|
+
},
|
|
16801
|
+
{
|
|
16802
|
+
"name": "mentionReference",
|
|
16803
|
+
"kind": "function",
|
|
16804
|
+
"jsdoc": "/** A mention of one item, in the exact shape the native Aria app sends: a\n * `global` reference (a chip, not a token in the text) whose `data` names the\n * item and the category it came from.\n *\n * `category` is the category's ENGLISH label (\"Products\", \"Contacts\") — it is\n * what the model reads, so it is not localized. Newlines in the label are\n * folded to spaces; `JSON.stringify` does the escaping the native client does\n * by hand. */",
|
|
16805
|
+
"decl": "export function mentionReference(item: { id: string; title: string }, category: string): MessageReference;"
|
|
16806
|
+
},
|
|
16795
16807
|
{
|
|
16796
16808
|
"name": "AssistantConfig",
|
|
16797
16809
|
"kind": "interface",
|
|
@@ -16850,7 +16862,7 @@
|
|
|
16850
16862
|
"name": "SendMessageOptions",
|
|
16851
16863
|
"kind": "interface",
|
|
16852
16864
|
"jsdoc": "/** Optional wire flags on `sendMessage`. `communicationMode` /\n * `enableRealtimeEvents` were in the original captured payload; only POLLING is\n * verified end-to-end. The rest were added from the 2026-09-19 re-capture. */",
|
|
16853
|
-
"decl": "export interface SendMessageOptions {\n communicationMode?: CommunicationMode;\n enableRealtimeEvents?: boolean;\n metadata?: UserMessageMetadata;\n /** The dashboard sends `true`. Optional: verified to answer either way. */\n enforceAccountScopedConversations?: boolean;\n /** Tag the turn with the same origin the conversation was created under. */\n origin?: string;\n /** Files already UPLOADED (see `createFileUpload`) to store with the turn.\n * Rides step 1 only — the trigger names the stored turn, which carries them. */\n attachments?: Attachment[];\n}"
|
|
16865
|
+
"decl": "export interface SendMessageOptions {\n communicationMode?: CommunicationMode;\n enableRealtimeEvents?: boolean;\n metadata?: UserMessageMetadata;\n /** The dashboard sends `true`. Optional: verified to answer either way. */\n enforceAccountScopedConversations?: boolean;\n /** Tag the turn with the same origin the conversation was created under. */\n origin?: string;\n /** References stored WITH the turn (step 1 only — the trigger names a turn\n * that already carries them). Omitted, the persist call sends `[]`, which is\n * what it always sent. */\n messageReferences?: MessageReference[];\n /** Files already UPLOADED (see `createFileUpload`) to store with the turn.\n * Rides step 1 only — the trigger names the stored turn, which carries them. */\n attachments?: Attachment[];\n}"
|
|
16854
16866
|
},
|
|
16855
16867
|
{
|
|
16856
16868
|
"name": "ConversationUserMessageResponse",
|
|
@@ -16930,6 +16942,66 @@
|
|
|
16930
16942
|
"jsdoc": "/** Fetch the messages of a conversation (poll after sendMessage). */",
|
|
16931
16943
|
"decl": "export function getConversationMessages(conversationId: string): Promise<GetConversationMessagesResponse>;"
|
|
16932
16944
|
},
|
|
16945
|
+
{
|
|
16946
|
+
"name": "ItemsPaginationMode",
|
|
16947
|
+
"kind": "type",
|
|
16948
|
+
"jsdoc": "/** How a provider pages its items. Any other value, or none, means unpaged:\n * `providersOf` reports it as \"NONE\" and such a provider is asked for one\n * page with no paging block. */",
|
|
16949
|
+
"decl": "export type ItemsPaginationMode = \"CURSOR\" | \"OFFSET\" | \"UNKNOWN_PAGINATION_MODE\";"
|
|
16950
|
+
},
|
|
16951
|
+
{
|
|
16952
|
+
"name": "ItemsSelectionProvider",
|
|
16953
|
+
"kind": "interface",
|
|
16954
|
+
"jsdoc": "/** An installed item provider. Only the fields a picker needs are typed. */",
|
|
16955
|
+
"decl": "export interface ItemsSelectionProvider {\n /** The handle `queryItems` is addressed by. */\n key?: string;\n appId?: string;\n contentData?: { providerName?: string; iconKey?: string; title?: string };\n paginationMode?: ItemsPaginationMode | string;\n}"
|
|
16956
|
+
},
|
|
16957
|
+
{
|
|
16958
|
+
"name": "ListInstalledProvidersResponse",
|
|
16959
|
+
"kind": "interface",
|
|
16960
|
+
"jsdoc": "",
|
|
16961
|
+
"decl": "export interface ListInstalledProvidersResponse { providers?: ItemsSelectionProvider[] }"
|
|
16962
|
+
},
|
|
16963
|
+
{
|
|
16964
|
+
"name": "ItemsSelectionItem",
|
|
16965
|
+
"kind": "interface",
|
|
16966
|
+
"jsdoc": "/** One item a provider returned. */",
|
|
16967
|
+
"decl": "export interface ItemsSelectionItem {\n id?: string;\n name?: string;\n description?: string;\n image?: { url?: string };\n /** Additional information, e.g. a price. */\n note?: string;\n}"
|
|
16968
|
+
},
|
|
16969
|
+
{
|
|
16970
|
+
"name": "QueryItemsResponse",
|
|
16971
|
+
"kind": "interface",
|
|
16972
|
+
"jsdoc": "",
|
|
16973
|
+
"decl": "export interface QueryItemsResponse {\n items?: ItemsSelectionItem[];\n pagingMetadata?: { hasNext?: boolean; cursors?: { next?: string } };\n}"
|
|
16974
|
+
},
|
|
16975
|
+
{
|
|
16976
|
+
"name": "ItemsQuery",
|
|
16977
|
+
"kind": "interface",
|
|
16978
|
+
"jsdoc": "/** The query `queryItems` sends. Set `cursorPaging` OR `paging` to match the\n * provider's `paginationMode`, or neither for an unpaged provider. */",
|
|
16979
|
+
"decl": "export interface ItemsQuery {\n search?: string;\n paging?: { limit: number; offset?: number };\n cursorPaging?: { limit: number; cursor?: string };\n}"
|
|
16980
|
+
},
|
|
16981
|
+
{
|
|
16982
|
+
"name": "listInstalledProviders",
|
|
16983
|
+
"kind": "function",
|
|
16984
|
+
"jsdoc": "/** The item providers installed on the CURRENT site — the categories a mention\n * picker offers. The site is the one whose signed instance authorizes the\n * call, so a caller that can switch business must re-list after a switch. */",
|
|
16985
|
+
"decl": "export function listInstalledProviders(): Promise<ListInstalledProvidersResponse>;"
|
|
16986
|
+
},
|
|
16987
|
+
{
|
|
16988
|
+
"name": "queryItems",
|
|
16989
|
+
"kind": "function",
|
|
16990
|
+
"jsdoc": "/** Query one provider's items. `providerKey` is a provider's `key`, never its\n * display name. */",
|
|
16991
|
+
"decl": "export function queryItems(providerKey: string, query: ItemsQuery = {}): Promise<QueryItemsResponse>;"
|
|
16992
|
+
},
|
|
16993
|
+
{
|
|
16994
|
+
"name": "providersOf",
|
|
16995
|
+
"kind": "function",
|
|
16996
|
+
"jsdoc": "/** The provider list from a `listInstalledProviders` response, with every\n * entry that has no key or no name dropped — neither can be offered or\n * queried. */",
|
|
16997
|
+
"decl": "export function providersOf(res: ListInstalledProvidersResponse | null | undefined): Array<{ key: string; name: string; paging: \"CURSOR\" | \"OFFSET\" | \"NONE\" }>;"
|
|
16998
|
+
},
|
|
16999
|
+
{
|
|
17000
|
+
"name": "itemsOf",
|
|
17001
|
+
"kind": "function",
|
|
17002
|
+
"jsdoc": "/** The items from a `queryItems` response, with every entry that has no id or\n * no name dropped — an item with neither cannot be referenced or shown. */",
|
|
17003
|
+
"decl": "export function itemsOf(res: QueryItemsResponse | null | undefined): Array<{ id: string; title: string; subtitle: string; imageUrl: string }>;"
|
|
17004
|
+
},
|
|
16933
17005
|
{
|
|
16934
17006
|
"name": "messagesOf",
|
|
16935
17007
|
"kind": "function",
|
|
@@ -19564,11 +19636,23 @@
|
|
|
19564
19636
|
"source": "stdlib/wix-sdk/portfolio/src/index.ts",
|
|
19565
19637
|
"description": "Portfolio — projects and collections",
|
|
19566
19638
|
"api": [
|
|
19639
|
+
{
|
|
19640
|
+
"name": "WixMediaImage",
|
|
19641
|
+
"kind": "interface",
|
|
19642
|
+
"jsdoc": "/** A Wix Portfolio media image — a project / collection cover or a project item's image. */",
|
|
19643
|
+
"decl": "export interface WixMediaImage {\n type?: string;\n imageInfo?: { id?: string; url?: string; width?: number; height?: number };\n}"
|
|
19644
|
+
},
|
|
19645
|
+
{
|
|
19646
|
+
"name": "portfolioCoverRef",
|
|
19647
|
+
"kind": "function",
|
|
19648
|
+
"jsdoc": "/**\n * The Wix media reference of a Portfolio cover or item image — its `imageInfo.url`, else\n * its `imageInfo.id` — for `wixMediaUrl` to resolve and size. `\"\"` when there is none.\n * A bare string is still accepted, so a cover stored as a url string keeps rendering.\n *\n * @example\n * const image = wixMediaUrl(portfolioCoverRef(project.coverImage));\n */",
|
|
19649
|
+
"decl": "export function portfolioCoverRef(cover: WixMediaImage | string | null | undefined): string;"
|
|
19650
|
+
},
|
|
19567
19651
|
{
|
|
19568
19652
|
"name": "WixPortfolioProject",
|
|
19569
19653
|
"kind": "interface",
|
|
19570
19654
|
"jsdoc": "",
|
|
19571
|
-
"decl": "export interface WixPortfolioProject {\n id: string;\n title?: string;\n description?: string;\n
|
|
19655
|
+
"decl": "export interface WixPortfolioProject {\n id: string;\n title?: string;\n description?: string;\n /**\n * Project cover. The live wire shape is a media OBJECT\n * (`{type:\"WIX_MEDIA\", imageInfo:{id,url,width,height}}`), not a url string —\n * read it with {@link portfolioCoverRef}; `wixMediaUrl` returns `\"\"` for the object.\n * A url string is still accepted, for projects constructed by hand.\n */\n coverImage?: WixMediaImage | string;\n slug?: string;\n hidden?: boolean;\n createdDate?: string;\n updatedDate?: string;\n}"
|
|
19572
19656
|
},
|
|
19573
19657
|
{
|
|
19574
19658
|
"name": "WixListProjectsResponse",
|
|
@@ -19583,12 +19667,6 @@
|
|
|
19583
19667
|
"decl": "export function listProjects(limit = 100, cursor?: string): Promise<WixListProjectsResponse>;",
|
|
19584
19668
|
"http": "GET /portfolio/v1/projects?{…}"
|
|
19585
19669
|
},
|
|
19586
|
-
{
|
|
19587
|
-
"name": "WixMediaImage",
|
|
19588
|
-
"kind": "interface",
|
|
19589
|
-
"jsdoc": "",
|
|
19590
|
-
"decl": "export interface WixMediaImage { imageInfo?: { id?: string; url?: string } }"
|
|
19591
|
-
},
|
|
19592
19670
|
{
|
|
19593
19671
|
"name": "WixPortfolioCollection",
|
|
19594
19672
|
"kind": "interface",
|
|
@@ -19618,14 +19696,14 @@
|
|
|
19618
19696
|
"name": "WixListProjectItemsResponse",
|
|
19619
19697
|
"kind": "interface",
|
|
19620
19698
|
"jsdoc": "",
|
|
19621
|
-
"decl": "export interface WixListProjectItemsResponse {\n items?: WixPortfolioProjectItem[];\n pagingMetadata?: { count?: number; cursors?: { next?: string }; hasNext?: boolean };\n}"
|
|
19699
|
+
"decl": "export interface WixListProjectItemsResponse {\n items?: WixPortfolioProjectItem[];\n /**\n * Paging, under the name this function always documented. A Wix query reports it as\n * `metadata`; {@link listProjectItems} copies that here when `pagingMetadata` is absent.\n */\n pagingMetadata?: { count?: number; cursors?: { next?: string }; hasNext?: boolean };\n /** The query endpoint's own name for the paging block. */\n metadata?: { count?: number; cursors?: { next?: string }; hasNext?: boolean };\n}"
|
|
19622
19700
|
},
|
|
19623
19701
|
{
|
|
19624
19702
|
"name": "listProjectItems",
|
|
19625
19703
|
"kind": "function",
|
|
19626
|
-
"jsdoc": "
|
|
19704
|
+
"jsdoc": "/**\n * POST /portfolio/v1/items/query — list a project's items.\n *\n * A first page filters on `projectId` with `paging.limit`. Passing a `cursor` (a previous\n * page's `pagingMetadata.cursors.next`) switches to `cursorPaging` and drops the filter,\n * because a Wix query cursor already carries it. Items come back in the order Wix returns\n * them — sort on `sortOrder` for the author's order.\n */",
|
|
19627
19705
|
"decl": "export function listProjectItems(projectId: string, limit = 100, cursor?: string): Promise<WixListProjectItemsResponse>;",
|
|
19628
|
-
"http": "
|
|
19706
|
+
"http": "POST /portfolio/v1/items/query"
|
|
19629
19707
|
}
|
|
19630
19708
|
]
|
|
19631
19709
|
},
|
|
@@ -70,6 +70,11 @@ footer's target; both are listed under `implicit`. It prints one JSON report and
|
|
|
70
70
|
sit in `.himi/trim-backup/`, which is deleted once the trim validates (and kept under
|
|
71
71
|
`--force`). A failure while *writing* the trim (a worker directory it cannot move, say) is
|
|
72
72
|
always put back, `--force` or not — `--force` keeps a finished trim, never a half-done one.
|
|
73
|
+
A validate failure is only blamed on the trim if the trim caused it: the untrimmed app is
|
|
74
|
+
validated too, and when every error was already there (a font `site analyze --apply` wrote
|
|
75
|
+
that the catalog lacks, say) the trim is kept, exits `0`, and lists those errors under
|
|
76
|
+
`preExistingErrors` ("trimmed; N errors were already present before trim: …"). Any error the
|
|
77
|
+
trim introduced still puts everything back, and is named under `introducedErrors`.
|
|
73
78
|
|
|
74
79
|
**Links between screens.** A kept screen that opens a removed one (a `himi.open` whose
|
|
75
80
|
`screenId` is a literal) would be a dead tap, so by default the trim is **refused** — and the
|
|
@@ -122,9 +122,21 @@ never change. If nothing is reachable, keep the default kit.
|
|
|
122
122
|
RFC 0048 refused a **content dump** on the runtime facade. The dump lives here, at **build time**:
|
|
123
123
|
|
|
124
124
|
```bash
|
|
125
|
-
himi site analyze --
|
|
125
|
+
himi site analyze --url https://… --out .himi/business-book # visitor-only pass
|
|
126
|
+
himi site analyze --url https://… --client-id <id> [--site <msid>] --out .himi/business-book [--apply]
|
|
126
127
|
```
|
|
127
128
|
|
|
129
|
+
`--url` alone is the **visitor-only pass**: the published pages, their look (screenshots,
|
|
130
|
+
`--wst-*`), the Copy chapter and the Placeholders chapter (template phone/email, Wix editor text, a
|
|
131
|
+
second business name, repeated images). It needs no OAuth client, so no anonymous token is minted
|
|
132
|
+
and every vertical probe reports `visitor skipped` — the Catalog has no counts. `--client-id` (the
|
|
133
|
+
site's Headless client) adds those counts, each Bookings service's price and payment mode
|
|
134
|
+
(`pay online only`, `pay in person only`, `free`, …), and the `wix` binding `--apply` writes;
|
|
135
|
+
`--apply` without it exits 2. `--site` adds the owner-only chapters (CMS, media, roles); an
|
|
136
|
+
ambient `WIX_SITE_ID` does so only beside an explicit `--client-id` flag (an exported
|
|
137
|
+
`WIX_CLIENT_ID` does not count), so a URL-only pass is never silently upgraded to an owner read
|
|
138
|
+
of whichever site that variable names.
|
|
139
|
+
|
|
128
140
|
Same collector, three doors:
|
|
129
141
|
|
|
130
142
|
| Door | Artifact |
|
|
@@ -59,14 +59,23 @@ banner. Keep **only the first ASCII line** (`head -1`) or HTTP headers throw
|
|
|
59
59
|
### 1. Analyze — do not re-fetch Wix
|
|
60
60
|
|
|
61
61
|
```sh
|
|
62
|
+
# Visitor-only pass — the URL is all it needs (published pages, look, copy, template leftovers):
|
|
63
|
+
himi site analyze --url https://customer.wixsite.com/site --out .himi/business-book
|
|
64
|
+
|
|
65
|
+
# Full pass — --client-id adds catalog counts, service payment modes and the --apply binding;
|
|
66
|
+
# --site adds the owner chapters (CMS, media, roles):
|
|
62
67
|
himi site analyze \
|
|
63
|
-
--client-id <headless-oauth-client-id> \
|
|
64
68
|
--url https://customer.wixsite.com/site \
|
|
69
|
+
--client-id <headless-oauth-client-id> \
|
|
65
70
|
--site <msid> \
|
|
66
71
|
--out .himi/business-book
|
|
67
72
|
# screenshots + --wst-*: npx playwright install chromium
|
|
68
73
|
```
|
|
69
74
|
|
|
75
|
+
Check `book.md` → **Placeholders** before reusing any copy: a template phone (`123-456-7890`),
|
|
76
|
+
Wix editor text, a second business name or one photo repeated across a gallery is the
|
|
77
|
+
template's, not the customer's.
|
|
78
|
+
|
|
70
79
|
Read `book.json`, `book.md`, `evidence/pages/`, `media/`, `velo/` (via
|
|
71
80
|
`book.velo.files[].localPath`). Write `.himi/business-book/look.notes.md` for mood,
|
|
72
81
|
overlay tie-break, language. **Never rewrite `book.json`.** Never log
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1038.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": [
|
|
@@ -65,5 +65,5 @@
|
|
|
65
65
|
"artifactId": "himalaya-cli"
|
|
66
66
|
}
|
|
67
67
|
},
|
|
68
|
-
"falconPackageHash": "
|
|
68
|
+
"falconPackageHash": "9cdd57a20df8f0a031d3306a62fee19f8378bdacaa6701a74ba4bb59"
|
|
69
69
|
}
|
|
@@ -11,9 +11,14 @@ const input = (globalThis as { himalaya?: { input?: Record<string, unknown> } })
|
|
|
11
11
|
const validDate = (value: unknown): value is string => typeof value === "string" && /^\d{4}-\d{2}-\d{2}$/.test(value) && dayKey(new Date(`${value}T12:00:00`)) === value;
|
|
12
12
|
const views = localPreferences("booking.schedule", value => ["agenda", "day", "month"].includes(String(value)) ? String(value) : "agenda");
|
|
13
13
|
let mode = validDate(input?.date) ? "day" : views.read();
|
|
14
|
-
|
|
14
|
+
// "" until the schedule is first shown, NOT today's date: the release build hashes this worker's
|
|
15
|
+
// initial state, so a date read off the build machine's clock made every build of unchanged
|
|
16
|
+
// source a different release (#3268, the `nondeterministic-initial-state` lint). `derive()` fills
|
|
17
|
+
// in today the first time the schedule is visible, which is never at build time (no member).
|
|
18
|
+
let selectedDate = validDate(input?.date) ? input.date : "";
|
|
19
|
+
const today = () => dayKey(new Date());
|
|
15
20
|
let pageMember: MemberAccess | undefined;
|
|
16
|
-
let queryNow =
|
|
21
|
+
let queryNow = ""; // set by the first-page fetch, never at boot (see selectedDate)
|
|
17
22
|
let cancelTarget: { row: MemberBookingRow; member: MemberAccess } | undefined;
|
|
18
23
|
let cancelling = false;
|
|
19
24
|
interface S {
|
|
@@ -26,11 +31,12 @@ interface S {
|
|
|
26
31
|
nextCursor?: string; complete?: boolean; loadingMore?: boolean; pageError?: boolean; total?: number;
|
|
27
32
|
}
|
|
28
33
|
function derive(s: S): S {
|
|
29
|
-
const
|
|
34
|
+
const hideSchedule = !s.isMember || s.isLoading;
|
|
35
|
+
if (!selectedDate && !hideSchedule) selectedDate = today();
|
|
36
|
+
const dayRows = selectedDate ? s.rows.filter(row => row.day === selectedDate) : [];
|
|
30
37
|
const counts = new Map<string, number>();
|
|
31
38
|
for (const row of s.rows) if (row.day) counts.set(row.day, (counts.get(row.day) ?? 0) + 1);
|
|
32
|
-
|
|
33
|
-
return { ...s, scheduleMode: mode, selectedDate, dayRows, dayHeading: dayLabel(selectedDate), hideSchedule,
|
|
39
|
+
return { ...s, scheduleMode: mode, selectedDate, dayRows, dayHeading: selectedDate ? dayLabel(selectedDate) : "", hideSchedule,
|
|
34
40
|
hideAgenda: hideSchedule || mode !== "agenda", hideCalendar: hideSchedule || mode === "agenda", hideMonth: mode !== "month",
|
|
35
41
|
decorations: [...counts].map(([date, n]) => ({ date, dotCount: Math.min(n, 3), dotColorToken: "accentPrimary" })),
|
|
36
42
|
dayEmptyText: dayRows.length ? "" : s.complete ? copyParam("bookings.emptyDay", "No bookings on this day.") : copyParam("bookings.emptyLoadedDay", "No bookings on this day in loaded results. Load more to check later pages."),
|
|
@@ -48,7 +54,7 @@ const svc = defineBackendCollection<S, MemberBookingRow>("himiBookingBookings",
|
|
|
48
54
|
async fetch(criteria, cursor) {
|
|
49
55
|
const member = await memberAccess();
|
|
50
56
|
if (cursor && (!pageMember || pageMember.email !== member.email || pageMember.scope !== member.scope)) throw memberAccessChanged();
|
|
51
|
-
if (!cursor) queryNow = new Date().toISOString();
|
|
57
|
+
if (!cursor || !queryNow) queryNow = new Date().toISOString();
|
|
52
58
|
return fetchMemberBookings(criteria, cursor, queryNow, member);
|
|
53
59
|
},
|
|
54
60
|
},
|
|
@@ -65,8 +71,8 @@ const svc = defineBackendCollection<S, MemberBookingRow>("himiBookingBookings",
|
|
|
65
71
|
actions: {
|
|
66
72
|
scheduleView(s, arg) { const value = (arg as { selectedId?: string })?.selectedId; if (["agenda", "day", "month"].includes(value || "")) { mode = value!; views.write(mode); } return derive(s); },
|
|
67
73
|
selectDate(s, arg) { const date = (arg as { date?: unknown })?.date; if (validDate(date)) selectedDate = date; return derive(s); },
|
|
68
|
-
previousDay(s) { const date = new Date(`${selectedDate}T12:00:00`); date.setDate(date.getDate() - 1); selectedDate = dayKey(date); return derive(s); },
|
|
69
|
-
nextDay(s) { const date = new Date(`${selectedDate}T12:00:00`); date.setDate(date.getDate() + 1); selectedDate = dayKey(date); return derive(s); },
|
|
74
|
+
previousDay(s) { const date = new Date(`${selectedDate || today()}T12:00:00`); date.setDate(date.getDate() - 1); selectedDate = dayKey(date); return derive(s); },
|
|
75
|
+
nextDay(s) { const date = new Date(`${selectedDate || today()}T12:00:00`); date.setDate(date.getDate() + 1); selectedDate = dayKey(date); return derive(s); },
|
|
70
76
|
async rebook(s, arg) {
|
|
71
77
|
const member = pageMember, row = s.rows.find(row => row.id === (arg as { id?: string })?.id);
|
|
72
78
|
if (!member || !row?.serviceId) return s;
|
|
@@ -388,6 +388,10 @@ function resolveEffectiveLayout(arg: unknown, productCount: number): LayoutId {
|
|
|
388
388
|
});
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
/** The time-of-day greeting. Read at REFRESH time only, never at boot: the release build hashes
|
|
392
|
+
* the initial state, so a greeting read off the build machine's clock made every build of an
|
|
393
|
+
* unchanged template a different release (#3268, the `nondeterministic-initial-state` lint).
|
|
394
|
+
* The initial state carries `neutralWelcome()` instead, and the first refresh swaps this in. */
|
|
391
395
|
function welcome(): string {
|
|
392
396
|
const hour = new Date().getHours();
|
|
393
397
|
if (hour < 12) return copyParam("home.greetingMorning", "Good morning");
|
|
@@ -395,6 +399,11 @@ function welcome(): string {
|
|
|
395
399
|
return copyParam("home.greetingEvening", "Good evening");
|
|
396
400
|
}
|
|
397
401
|
|
|
402
|
+
/** The boot-time greeting: clock-free, so the baked initial state is the same on every build. */
|
|
403
|
+
function neutralWelcome(): string {
|
|
404
|
+
return copyParam("home.greetingNeutral", "Welcome");
|
|
405
|
+
}
|
|
406
|
+
|
|
398
407
|
function feature(p: StoreProduct): ProductFeature {
|
|
399
408
|
const name = cleanTitle(p.name) || copyParam("home.productFallbackName", "Featured item");
|
|
400
409
|
const image = productImage(p);
|
|
@@ -871,7 +880,9 @@ function baseState(): State {
|
|
|
871
880
|
hideHeroInitial: true,
|
|
872
881
|
hideHeroFallback: true,
|
|
873
882
|
hideFeaturedSkeleton: false,
|
|
874
|
-
|
|
883
|
+
// Not welcome(): that reads the clock, and this state is baked into the release. A neutral
|
|
884
|
+
// greeting keeps the hero pill painted (same size, no reflow) until the first refresh.
|
|
885
|
+
welcome: neutralWelcome(),
|
|
875
886
|
refreshedAt: "",
|
|
876
887
|
effectiveLayout: resolveEffectiveLayout(undefined, 0),
|
|
877
888
|
accentColor: resolveAccent(undefined),
|