@wix/himalaya-cli 0.1573.0 → 0.1575.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/dev-server/src/app-config.d.ts +16 -5
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +6 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/message-format.d.ts +59 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/index.d.ts +15 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/oneapp.d.ts +90 -0
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/core.mjs +3 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/l10n.mjs +8 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/sdui.mjs +3 -3
- package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bi.mjs +165 -5
- package/dist/cli.mjs +5261 -2937
- package/dist/preview-assets.generated.bin +0 -0
- package/guides/designing-himi-screens.md +7 -1
- package/guides/hhp.md +2 -2
- package/guides/himi-json-output.md +53 -2
- package/guides/himi-mcp.md +1 -1
- package/guides/himi-test.md +3 -2
- package/guides/iap-products.md +1 -1
- package/guides/importing-oneapp-translations.md +115 -0
- package/guides/index.json +6 -0
- package/guides/one-app-section-mappings.md +16 -4
- package/guides/one-app-to-himi-app.md +167 -10
- package/guides/push-cli-onboarding.md +10 -0
- package/guides/qa-builds.md +21 -1
- package/guides/templates.md +3 -2
- package/guides/testing-himi-apps.md +1 -1
- package/package.json +3 -3
- package/templates/beauty-salon/dev/index.ts +96 -133
- package/templates/beauty-salon/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/beauty-salon-owner/dev/index.ts +27 -6
- package/templates/beauty-salon-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/branded-lite/dev/index.ts +136 -142
- package/templates/branded-lite/l10n/en.json +570 -0
- package/templates/branded-lite/l10n/he.json +200 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-copy.ts +40 -0
- package/templates/branded-lite/tier5-src/_shared/business-chat-message-model.ts +1 -1
- package/templates/branded-lite/tier5-src/_shared/business-chat-model.ts +3 -2
- package/templates/branded-lite/tier5-src/_shared/business-chat-presenter.ts +26 -21
- package/templates/branded-lite/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/branded-lite/tier5-src/business-chat/index.ts +184 -63
- package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/community-club/dev/index.ts +22 -6
- package/templates/community-club-owner/dev/index.ts +27 -6
- package/templates/community-club-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/events-meetups/dev/index.ts +22 -6
- package/templates/events-meetups-owner/dev/index.ts +25 -6
- package/templates/events-meetups-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/gym-fitness/dev/index.ts +96 -133
- package/templates/gym-fitness/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/gym-fitness-owner/dev/index.ts +28 -6
- package/templates/gym-fitness-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/himi-chat/dev/index.ts +22 -6
- package/templates/nonprofit-cause/dev/index.ts +22 -6
- package/templates/nonprofit-cause-owner/dev/index.ts +26 -6
- package/templates/nonprofit-cause-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/owner-lite/dev/index.ts +28 -6
- package/templates/owner-lite/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/podcast-creator/dev/index.ts +22 -6
- package/templates/podcast-creator-owner/dev/index.ts +27 -6
- package/templates/podcast-creator-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/portfolio-creative/dev/index.ts +96 -133
- package/templates/portfolio-creative/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/portfolio-creative-owner/dev/index.ts +27 -6
- package/templates/portfolio-creative-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/restaurant/dev/index.ts +22 -6
- package/templates/restaurant-owner/dev/index.ts +28 -6
- package/templates/restaurant-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
- package/templates/retail-store/dev/index.ts +96 -133
- package/templates/retail-store/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +18 -5
- package/templates/retail-store-owner/dev/index.ts +26 -6
- package/templates/retail-store-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
- package/templates/surface-showcase/dev/index.ts +22 -6
- package/templates/yoga-studio/dev/index.ts +22 -6
- package/templates/yoga-studio-owner/dev/index.ts +26 -6
- package/templates/yoga-studio-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
- package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
package/guides/qa-builds.md
CHANGED
|
@@ -1193,7 +1193,8 @@ himi dist --app <app> --platform android --aab --wait --target bo
|
|
|
1193
1193
|
activates a draft release: `GET /v1/admin/site/store-build-check?app=&site=[&shell=]` answers
|
|
1194
1194
|
`200 { ok: true, android: { packageName } }` or the same refusals, through `CheckIfBuildIsAllowed`,
|
|
1195
1195
|
so it locks nothing. It names no release, so it cannot give
|
|
1196
|
-
`409 release_predates_store_builds`; a draft the editor just built is on a current commit. It
|
|
1196
|
+
`409 release_predates_store_builds`; a draft the editor just built is on a current commit. It is
|
|
1197
|
+
not sent the App name and icon either, so it cannot give their refusals (below). It never
|
|
1197
1198
|
falls back to the locking call.
|
|
1198
1199
|
- **A Containers app's TestFlight build is an App Store build.** For an app bound to a Wix site,
|
|
1199
1200
|
`--platform ios --destination testflight --version <x.y.z>` with no `--signing` builds the app for
|
|
@@ -1226,6 +1227,25 @@ himi dist --app <app> --platform android --aab --wait --target bo
|
|
|
1226
1227
|
502 (the prepared files failed Himalaya's checks, the check's code in `reason`: a fix on Wix's
|
|
1227
1228
|
side, not a retry), `store_builds_unavailable` 503. A named `--signing byo` keeps the
|
|
1228
1229
|
customer-signed path above, from the site's legacy BuildSecrets.
|
|
1230
|
+
- **A store build ships under the owner's App name and icon.** Both store builds above (a Containers
|
|
1231
|
+
app's `--aab` and its TestFlight build) need `appName` and `appIconUrl` on `POST /v1/admin/dist`
|
|
1232
|
+
(`himi dist --app-name <name> --app-icon-url <url>`): the App name and App icon the owner set in
|
|
1233
|
+
the Containers editor, which the Containers server reads off the project and sends. They win over
|
|
1234
|
+
the release's app-config `name` and icon, which the generation agent picked. Any other build
|
|
1235
|
+
takes them too when they are sent, and keeps the release's when they aren't. The name is trimmed,
|
|
1236
|
+
1 to 200 characters, with no control characters and no `$` (Xcode would expand `$(NAME)` from the
|
|
1237
|
+
pool's environment into the binary), and can't start with `@` or `?` (Android would read it as a
|
|
1238
|
+
resource reference). The icon must be a Wix Media file, exactly
|
|
1239
|
+
`https://static.wixstatic.com/media/<file>`. Its 1024x1024 PNG rendition
|
|
1240
|
+
(`<url>/v1/fill/w_1024,h_1024,al_c,q_100/icon.png`) is fetched and checked last before the store
|
|
1241
|
+
id is locked, and again when the pool reads its contract (`GET /v1/dist/contract`), twice at most.
|
|
1242
|
+
If it can't be read then, the contract answers `503 app_icon_unavailable` and the build fails,
|
|
1243
|
+
rather than baking the release's or the shell's icon. iOS gets the icon re-encoded with no alpha
|
|
1244
|
+
channel, which App Store Connect refuses. The job records `appInfo`, and
|
|
1245
|
+
`GET /v1/owner/rebuild-reasons` reports `app_name` and `app_icon` as `unknown` for such a build.
|
|
1246
|
+
Refusals, each with `code` equal to `error`: `store_build_app_info_missing`, `app_info_incomplete`
|
|
1247
|
+
(one without the other), `invalid_app_name`, `invalid_app_icon_url` and `app_icon_invalid` (not a
|
|
1248
|
+
1024x1024 PNG a platform can use) 400, `app_icon_unavailable` 503 (Wix Media couldn't be read).
|
|
1229
1249
|
|
|
1230
1250
|
**Why the Wix production key, and only for the bundle.** There are two Android keys: the committed
|
|
1231
1251
|
QA keystore and the Wix production key (WixOne). WixOne is the Play **upload** key every Branded
|
package/guides/templates.md
CHANGED
|
@@ -106,8 +106,9 @@ details beside the app:
|
|
|
106
106
|
| `.himi/himi-test.stub.json` | a typed-journey skeleton per form (every field, then its submit) with `<FILL>` slots |
|
|
107
107
|
|
|
108
108
|
`bootsOffline` is the consent preflight: it is `false` when any screen's `onAppear` dispatches into
|
|
109
|
-
a worker that can reach Wix or the network,
|
|
110
|
-
|
|
109
|
+
a worker that can reach Wix or the network, or when a screen boots such a worker that calls an
|
|
110
|
+
action while its module evaluates (`svc.load();` at the top level). `bootRequests` names each one,
|
|
111
|
+
with `trigger: "onAppear"` or `"module-load"`. It is static and errs on the safe side ("may request"). The commands behind it:
|
|
111
112
|
|
|
112
113
|
```sh
|
|
113
114
|
himi template recommend --text "a salon where clients book and buy products" # rank the catalog (or --verticals / --book)
|
|
@@ -267,7 +267,7 @@ render, local browser, zero network. `--no-shots` opts out (sweeps should).
|
|
|
267
267
|
|
|
268
268
|
Absence is said, never silent: without the browser the report carries
|
|
269
269
|
`shots.unavailable` and the ledger names the one-time remedy —
|
|
270
|
-
`
|
|
270
|
+
the install command `himi render --png` prints. Web fidelity, honestly named: these are the web
|
|
271
271
|
renderer's pixels; native truth still comes from a device (`himi push` a preview).
|
|
272
272
|
|
|
273
273
|
Hygiene: a **full** run owns `.himi/shots/` and clears it first, so a deleted screen
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1575.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": [
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
},
|
|
36
36
|
"dependencies": {
|
|
37
37
|
"@modelcontextprotocol/server": "^2.0.0",
|
|
38
|
-
"@rive-app/canvas": "
|
|
38
|
+
"@rive-app/canvas": "2.38.3",
|
|
39
39
|
"ajv": "^8.17.1",
|
|
40
40
|
"ajv-formats": "^3.0.1",
|
|
41
41
|
"cheerio": "^1.0.0",
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "ddd3e8a75c29cf1fa48b60d543d4ba99be00f7a8a9707e3b6c88e776"
|
|
75
75
|
}
|
|
@@ -15589,39 +15589,28 @@ const SCREENS = runtimeSetting({
|
|
|
15589
15589
|
"id": "himi_cart.qty_minus",
|
|
15590
15590
|
"body": {
|
|
15591
15591
|
"package": "himalaya",
|
|
15592
|
-
"type": "
|
|
15592
|
+
"type": "Button",
|
|
15593
15593
|
"props": {
|
|
15594
|
-
"
|
|
15595
|
-
"
|
|
15596
|
-
|
|
15597
|
-
|
|
15598
|
-
"
|
|
15599
|
-
"package": "himalaya",
|
|
15600
|
-
"type": "Icon",
|
|
15601
|
-
"props": {
|
|
15602
|
-
"name": "minus",
|
|
15603
|
-
"size": 24,
|
|
15604
|
-
"tint": "textPrimary"
|
|
15605
|
-
}
|
|
15606
|
-
},
|
|
15607
|
-
"modifiers": {
|
|
15608
|
-
"accessibilityRole": "decorative"
|
|
15609
|
-
}
|
|
15594
|
+
"title": "Decrease quantity",
|
|
15595
|
+
"action": {
|
|
15596
|
+
"actionId": "tier5.himiStoreCart.decrement",
|
|
15597
|
+
"params": {
|
|
15598
|
+
"id": "$item.id"
|
|
15610
15599
|
}
|
|
15611
|
-
|
|
15600
|
+
},
|
|
15601
|
+
"style": "tertiary",
|
|
15602
|
+
"iconOnly": true,
|
|
15603
|
+
"leadingIcon": "minus",
|
|
15604
|
+
"cornerRadius": 22,
|
|
15605
|
+
"minHeight": 44,
|
|
15606
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
15612
15607
|
}
|
|
15613
15608
|
},
|
|
15614
15609
|
"modifiers": {
|
|
15615
15610
|
"width": 44,
|
|
15616
15611
|
"height": 44,
|
|
15617
|
-
"onTap": {
|
|
15618
|
-
"actionId": "tier5.himiStoreCart.decrement",
|
|
15619
|
-
"params": {
|
|
15620
|
-
"id": "$item.id"
|
|
15621
|
-
}
|
|
15622
|
-
},
|
|
15623
|
-
"accessibilityRole": "button",
|
|
15624
15612
|
"accessibilityLabel": "Decrease quantity",
|
|
15613
|
+
"accessibilityRole": "button",
|
|
15625
15614
|
"haptic": "light"
|
|
15626
15615
|
}
|
|
15627
15616
|
},
|
|
@@ -15644,39 +15633,28 @@ const SCREENS = runtimeSetting({
|
|
|
15644
15633
|
"id": "himi_cart.qty_plus",
|
|
15645
15634
|
"body": {
|
|
15646
15635
|
"package": "himalaya",
|
|
15647
|
-
"type": "
|
|
15636
|
+
"type": "Button",
|
|
15648
15637
|
"props": {
|
|
15649
|
-
"
|
|
15650
|
-
"
|
|
15651
|
-
|
|
15652
|
-
|
|
15653
|
-
"
|
|
15654
|
-
"package": "himalaya",
|
|
15655
|
-
"type": "Icon",
|
|
15656
|
-
"props": {
|
|
15657
|
-
"name": "plus",
|
|
15658
|
-
"size": 24,
|
|
15659
|
-
"tint": "textPrimary"
|
|
15660
|
-
}
|
|
15661
|
-
},
|
|
15662
|
-
"modifiers": {
|
|
15663
|
-
"accessibilityRole": "decorative"
|
|
15664
|
-
}
|
|
15638
|
+
"title": "Increase quantity",
|
|
15639
|
+
"action": {
|
|
15640
|
+
"actionId": "tier5.himiStoreCart.increment",
|
|
15641
|
+
"params": {
|
|
15642
|
+
"id": "$item.id"
|
|
15665
15643
|
}
|
|
15666
|
-
|
|
15644
|
+
},
|
|
15645
|
+
"style": "tertiary",
|
|
15646
|
+
"iconOnly": true,
|
|
15647
|
+
"leadingIcon": "plus",
|
|
15648
|
+
"cornerRadius": 22,
|
|
15649
|
+
"minHeight": 44,
|
|
15650
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
15667
15651
|
}
|
|
15668
15652
|
},
|
|
15669
15653
|
"modifiers": {
|
|
15670
15654
|
"width": 44,
|
|
15671
15655
|
"height": 44,
|
|
15672
|
-
"onTap": {
|
|
15673
|
-
"actionId": "tier5.himiStoreCart.increment",
|
|
15674
|
-
"params": {
|
|
15675
|
-
"id": "$item.id"
|
|
15676
|
-
}
|
|
15677
|
-
},
|
|
15678
|
-
"accessibilityRole": "button",
|
|
15679
15656
|
"accessibilityLabel": "Increase quantity",
|
|
15657
|
+
"accessibilityRole": "button",
|
|
15680
15658
|
"haptic": "light"
|
|
15681
15659
|
}
|
|
15682
15660
|
},
|
|
@@ -15692,39 +15670,28 @@ const SCREENS = runtimeSetting({
|
|
|
15692
15670
|
"id": "himi_cart.save_filled",
|
|
15693
15671
|
"body": {
|
|
15694
15672
|
"package": "himalaya",
|
|
15695
|
-
"type": "
|
|
15673
|
+
"type": "Button",
|
|
15696
15674
|
"props": {
|
|
15697
|
-
"
|
|
15698
|
-
"
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
"
|
|
15702
|
-
"package": "himalaya",
|
|
15703
|
-
"type": "Icon",
|
|
15704
|
-
"props": {
|
|
15705
|
-
"name": "heart_fill",
|
|
15706
|
-
"size": 24,
|
|
15707
|
-
"tint": "textPrimary"
|
|
15708
|
-
}
|
|
15709
|
-
},
|
|
15710
|
-
"modifiers": {
|
|
15711
|
-
"accessibilityRole": "decorative"
|
|
15712
|
-
}
|
|
15675
|
+
"title": "Remove from wishlist",
|
|
15676
|
+
"action": {
|
|
15677
|
+
"actionId": "tier5.himiStoreCart.save",
|
|
15678
|
+
"params": {
|
|
15679
|
+
"id": "$item.id"
|
|
15713
15680
|
}
|
|
15714
|
-
|
|
15681
|
+
},
|
|
15682
|
+
"style": "tertiary",
|
|
15683
|
+
"iconOnly": true,
|
|
15684
|
+
"leadingIcon": "heart_fill",
|
|
15685
|
+
"cornerRadius": 22,
|
|
15686
|
+
"minHeight": 44,
|
|
15687
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
15715
15688
|
}
|
|
15716
15689
|
},
|
|
15717
15690
|
"modifiers": {
|
|
15718
15691
|
"width": 44,
|
|
15719
15692
|
"height": 44,
|
|
15720
|
-
"onTap": {
|
|
15721
|
-
"actionId": "tier5.himiStoreCart.save",
|
|
15722
|
-
"params": {
|
|
15723
|
-
"id": "$item.id"
|
|
15724
|
-
}
|
|
15725
|
-
},
|
|
15726
|
-
"accessibilityRole": "button",
|
|
15727
15693
|
"accessibilityLabel": "Remove from wishlist",
|
|
15694
|
+
"accessibilityRole": "button",
|
|
15728
15695
|
"haptic": "light",
|
|
15729
15696
|
"hiddenPath": "$item.notSaved"
|
|
15730
15697
|
}
|
|
@@ -15733,39 +15700,28 @@ const SCREENS = runtimeSetting({
|
|
|
15733
15700
|
"id": "himi_cart.save",
|
|
15734
15701
|
"body": {
|
|
15735
15702
|
"package": "himalaya",
|
|
15736
|
-
"type": "
|
|
15703
|
+
"type": "Button",
|
|
15737
15704
|
"props": {
|
|
15738
|
-
"
|
|
15739
|
-
"
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
"
|
|
15743
|
-
"package": "himalaya",
|
|
15744
|
-
"type": "Icon",
|
|
15745
|
-
"props": {
|
|
15746
|
-
"name": "heart",
|
|
15747
|
-
"size": 24,
|
|
15748
|
-
"tint": "textPrimary"
|
|
15749
|
-
}
|
|
15750
|
-
},
|
|
15751
|
-
"modifiers": {
|
|
15752
|
-
"accessibilityRole": "decorative"
|
|
15753
|
-
}
|
|
15705
|
+
"title": "Add to wishlist",
|
|
15706
|
+
"action": {
|
|
15707
|
+
"actionId": "tier5.himiStoreCart.save",
|
|
15708
|
+
"params": {
|
|
15709
|
+
"id": "$item.id"
|
|
15754
15710
|
}
|
|
15755
|
-
|
|
15711
|
+
},
|
|
15712
|
+
"style": "tertiary",
|
|
15713
|
+
"iconOnly": true,
|
|
15714
|
+
"leadingIcon": "heart",
|
|
15715
|
+
"cornerRadius": 22,
|
|
15716
|
+
"minHeight": 44,
|
|
15717
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
15756
15718
|
}
|
|
15757
15719
|
},
|
|
15758
15720
|
"modifiers": {
|
|
15759
15721
|
"width": 44,
|
|
15760
15722
|
"height": 44,
|
|
15761
|
-
"onTap": {
|
|
15762
|
-
"actionId": "tier5.himiStoreCart.save",
|
|
15763
|
-
"params": {
|
|
15764
|
-
"id": "$item.id"
|
|
15765
|
-
}
|
|
15766
|
-
},
|
|
15767
|
-
"accessibilityRole": "button",
|
|
15768
15723
|
"accessibilityLabel": "Add to wishlist",
|
|
15724
|
+
"accessibilityRole": "button",
|
|
15769
15725
|
"haptic": "light",
|
|
15770
15726
|
"hiddenPath": "$item.saved"
|
|
15771
15727
|
}
|
|
@@ -15774,39 +15730,28 @@ const SCREENS = runtimeSetting({
|
|
|
15774
15730
|
"id": "himi_cart.remove",
|
|
15775
15731
|
"body": {
|
|
15776
15732
|
"package": "himalaya",
|
|
15777
|
-
"type": "
|
|
15733
|
+
"type": "Button",
|
|
15778
15734
|
"props": {
|
|
15779
|
-
"
|
|
15780
|
-
"
|
|
15781
|
-
|
|
15782
|
-
|
|
15783
|
-
"
|
|
15784
|
-
"package": "himalaya",
|
|
15785
|
-
"type": "Icon",
|
|
15786
|
-
"props": {
|
|
15787
|
-
"name": "trash",
|
|
15788
|
-
"size": 24,
|
|
15789
|
-
"tint": "textPrimary"
|
|
15790
|
-
}
|
|
15791
|
-
},
|
|
15792
|
-
"modifiers": {
|
|
15793
|
-
"accessibilityRole": "decorative"
|
|
15794
|
-
}
|
|
15735
|
+
"title": "Remove from cart",
|
|
15736
|
+
"action": {
|
|
15737
|
+
"actionId": "tier5.himiStoreCart.remove",
|
|
15738
|
+
"params": {
|
|
15739
|
+
"id": "$item.id"
|
|
15795
15740
|
}
|
|
15796
|
-
|
|
15741
|
+
},
|
|
15742
|
+
"style": "tertiary",
|
|
15743
|
+
"iconOnly": true,
|
|
15744
|
+
"leadingIcon": "trash",
|
|
15745
|
+
"cornerRadius": 22,
|
|
15746
|
+
"minHeight": 44,
|
|
15747
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
15797
15748
|
}
|
|
15798
15749
|
},
|
|
15799
15750
|
"modifiers": {
|
|
15800
15751
|
"width": 44,
|
|
15801
15752
|
"height": 44,
|
|
15802
|
-
"onTap": {
|
|
15803
|
-
"actionId": "tier5.himiStoreCart.remove",
|
|
15804
|
-
"params": {
|
|
15805
|
-
"id": "$item.id"
|
|
15806
|
-
}
|
|
15807
|
-
},
|
|
15808
|
-
"accessibilityRole": "button",
|
|
15809
15753
|
"accessibilityLabel": "Remove from cart",
|
|
15754
|
+
"accessibilityRole": "button",
|
|
15810
15755
|
"haptic": "light"
|
|
15811
15756
|
}
|
|
15812
15757
|
}
|
|
@@ -16055,7 +16000,7 @@ const SCREENS = runtimeSetting({
|
|
|
16055
16000
|
"action": {
|
|
16056
16001
|
"actionId": "tier5.himiStoreCart.applyPromo"
|
|
16057
16002
|
},
|
|
16058
|
-
"disabledPath": "$service:himiStoreCart.
|
|
16003
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
16059
16004
|
}
|
|
16060
16005
|
},
|
|
16061
16006
|
"modifiers": {
|
|
@@ -16112,7 +16057,8 @@ const SCREENS = runtimeSetting({
|
|
|
16112
16057
|
"iconOnly": true,
|
|
16113
16058
|
"leadingIcon": "close",
|
|
16114
16059
|
"cornerRadius": 22,
|
|
16115
|
-
"minHeight": 44
|
|
16060
|
+
"minHeight": 44,
|
|
16061
|
+
"disabledPath": "$service:himiStoreCart.writeBusy"
|
|
16116
16062
|
}
|
|
16117
16063
|
},
|
|
16118
16064
|
"modifiers": {
|
|
@@ -16246,6 +16192,7 @@ const SCREENS = runtimeSetting({
|
|
|
16246
16192
|
"title": "Checkout",
|
|
16247
16193
|
"style": "primary",
|
|
16248
16194
|
"fullWidth": true,
|
|
16195
|
+
"disabledPath": "$service:himiStoreCart.writeBusy",
|
|
16249
16196
|
"action": {
|
|
16250
16197
|
"actionId": "tier5.himiStoreCart.checkout"
|
|
16251
16198
|
}
|
|
@@ -41003,15 +40950,31 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
41003
40950
|
|
|
41004
40951
|
// Live seed-id enumeration: a published release serves only EXACT screens, so
|
|
41005
40952
|
// `himi push` bakes each templated detail route's CURRENT catalog ids as exact
|
|
41006
|
-
// screens via `seedIds`. Anonymous visitor rung against this app's own clientId
|
|
41007
|
-
//
|
|
40953
|
+
// screens via `seedIds`. Anonymous visitor rung against this app's own clientId — or,
|
|
40954
|
+
// for an app bound through the One App visitor identity (`visitorSource: "one-app"`,
|
|
40955
|
+
// no client), that site's One App visitor instance. Best-effort (offline build → no
|
|
40956
|
+
// baked details, preview still live-resolves).
|
|
40957
|
+
async function liveSeedToken(WIX: string): Promise<string | undefined> {
|
|
40958
|
+
const auth = (config as { auth?: { config?: { clientId?: string; visitorSource?: string; metaSiteId?: string } } }).auth?.config;
|
|
40959
|
+
// A One App binding boots as the site's One App visitor, so the seed reads as that visitor too —
|
|
40960
|
+
// even beside a member-login clientId, and never repointed by a stray local WIX_CLIENT_ID.
|
|
40961
|
+
const oneApp = auth?.visitorSource === "one-app";
|
|
40962
|
+
const clientId = oneApp ? "" : process.env.WIX_CLIENT_ID ?? auth?.clientId ?? "";
|
|
40963
|
+
if (clientId && !clientId.startsWith("YOUR_")) {
|
|
40964
|
+
const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
|
|
40965
|
+
return ((await mint.json()) as { access_token?: string }).access_token;
|
|
40966
|
+
}
|
|
40967
|
+
const msid = oneApp ? auth?.metaSiteId ?? "" : "";
|
|
40968
|
+
if (!/^[0-9a-fA-F-]{10,64}$/.test(msid)) return undefined;
|
|
40969
|
+
const mint = await fetch(`https://apps.wix.com/_api/one-app-session-web/v3/businesses/${msid}/signed-instance?signed_instance_type=Visitor`, { headers: { Accept: "application/json" }, redirect: "error" });
|
|
40970
|
+
const visitor = (await mint.json()) as { signedInstance?: string; signedInstanceType?: string };
|
|
40971
|
+
return visitor.signedInstanceType === "Visitor" ? visitor.signedInstance : undefined;
|
|
40972
|
+
}
|
|
40973
|
+
|
|
41008
40974
|
async function liveSeedIds(spec: LiveSeed): Promise<string[]> {
|
|
41009
40975
|
try {
|
|
41010
40976
|
const WIX = "https://www.wixapis.com";
|
|
41011
|
-
const
|
|
41012
|
-
if (!clientId || clientId.startsWith("YOUR_")) return [];
|
|
41013
|
-
const mint = await fetch(WIX + "/oauth2/token", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ clientId, grantType: "anonymous" }) });
|
|
41014
|
-
const token = ((await mint.json()) as { access_token?: string }).access_token;
|
|
40977
|
+
const token = await liveSeedToken(WIX);
|
|
41015
40978
|
if (!token) return [];
|
|
41016
40979
|
const res = await fetch(WIX + spec.path, { method: "POST", headers: { Authorization: token, "Content-Type": "application/json" }, body: JSON.stringify(spec.body) });
|
|
41017
40980
|
const json = (await res.json()) as Record<string, unknown>;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { defineService as register, type ServiceDefinition } from '@himalaya/state';
|
|
2
|
+
|
|
3
|
+
/** Reject repeated taps until the accepted write's result is committed. A promise
|
|
4
|
+
* queue alone releases before the host applies the reducer result, so the next
|
|
5
|
+
* write can still read an old revision/id. The private synchronous reducer below
|
|
6
|
+
* publishes busy and commits completion through the ordinary service handle;
|
|
7
|
+
* awaiting that handle guarantees the host has stored the result before unlock.
|
|
8
|
+
* Skipped actions return undefined, so they cannot roll back the accepted write.
|
|
9
|
+
*/
|
|
10
|
+
export function withExclusiveWrites<S extends object = Record<string, unknown>>(
|
|
11
|
+
registerService: typeof register, names: readonly string[],
|
|
12
|
+
options: { prepare?: (action: string, args: readonly unknown[]) => void; coalesce?: readonly string[]; settle?: (before: S, next: S, live: S) => S } = {},
|
|
13
|
+
): typeof register {
|
|
14
|
+
return ((name: string, definition: ServiceDefinition<Record<string, unknown>>) => {
|
|
15
|
+
let busy = false;
|
|
16
|
+
let activeAction = "";
|
|
17
|
+
let completion = Promise.resolve();
|
|
18
|
+
let complete = () => {};
|
|
19
|
+
let handle: { getState(): Record<string, unknown>; exclusiveWriteState(next: Record<string, unknown>): Promise<unknown> };
|
|
20
|
+
const wrapped: ServiceDefinition<Record<string, unknown>> = {
|
|
21
|
+
...definition,
|
|
22
|
+
state: { ...definition.state, writeBusy: false },
|
|
23
|
+
notHydrated: [...(definition.notHydrated ?? []), 'writeBusy'],
|
|
24
|
+
actions: {
|
|
25
|
+
...Object.fromEntries(Object.entries(definition.actions ?? {}).map(([action, reduce]) => [action,
|
|
26
|
+
names.includes(action) ? async (state: Record<string, unknown>, ...args: never[]) => {
|
|
27
|
+
// Intake request metadata even when its state replacement is skipped.
|
|
28
|
+
options.prepare?.(action, args);
|
|
29
|
+
if (busy) {
|
|
30
|
+
// Repeated initial loads must await the accepted load, so callers
|
|
31
|
+
// observe its committed rows rather than the initial skeleton.
|
|
32
|
+
if (activeAction === action && options.coalesce?.includes(action)) await completion;
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
busy = true;
|
|
36
|
+
activeAction = action;
|
|
37
|
+
completion = new Promise<void>(resolve => { complete = resolve; });
|
|
38
|
+
try {
|
|
39
|
+
await handle.exclusiveWriteState({ ...handle.getState(), writeBusy: true });
|
|
40
|
+
const next = await reduce(state, ...args);
|
|
41
|
+
const live = handle.getState();
|
|
42
|
+
// Non-write actions (for example editing a draft field) can still run
|
|
43
|
+
// during I/O. Let the owning service preserve those newer edits.
|
|
44
|
+
const settled = options.settle ? options.settle(state as S, (next ?? live) as S, live as S) : next ?? live;
|
|
45
|
+
await handle.exclusiveWriteState({ ...settled, writeBusy: false });
|
|
46
|
+
} catch (error) {
|
|
47
|
+
await handle.exclusiveWriteState({ ...handle.getState(), writeBusy: false });
|
|
48
|
+
throw error;
|
|
49
|
+
} finally {
|
|
50
|
+
busy = false;
|
|
51
|
+
complete();
|
|
52
|
+
}
|
|
53
|
+
} : reduce,
|
|
54
|
+
])),
|
|
55
|
+
exclusiveWriteState: (_state, next: Record<string, unknown>) => next,
|
|
56
|
+
},
|
|
57
|
+
};
|
|
58
|
+
const registered = registerService(name, wrapped);
|
|
59
|
+
// Registration preserves this private action, but the dynamic action map
|
|
60
|
+
// cannot express its payload to the generic service handle.
|
|
61
|
+
handle = registered as unknown as typeof handle;
|
|
62
|
+
// Older hosts ignore notHydrated. A fresh worker has no pending write.
|
|
63
|
+
handle.getState().writeBusy = false;
|
|
64
|
+
return registered;
|
|
65
|
+
}) as typeof register;
|
|
66
|
+
}
|
|
@@ -115,6 +115,7 @@ interface State {
|
|
|
115
115
|
checkoutHint: string;
|
|
116
116
|
itemCount: number;
|
|
117
117
|
promoCode: string;
|
|
118
|
+
promoEditRevision: number;
|
|
118
119
|
appliedCode: string;
|
|
119
120
|
appliedLabel: string;
|
|
120
121
|
couponId: string;
|
|
@@ -380,11 +381,24 @@ function couponDetails(result: unknown): { id: string; code: string } {
|
|
|
380
381
|
return { id: coupon?.coupon?.id ?? coupon?.id ?? "", code: coupon?.coupon?.code ?? coupon?.code ?? "" };
|
|
381
382
|
}
|
|
382
383
|
|
|
384
|
+
import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
|
|
385
|
+
|
|
383
386
|
let cartPricing: IapCommerce;
|
|
384
387
|
const defineService = withIapPresentation<State>({
|
|
385
|
-
register: withOnlineWrites(registerState, ["remove", "increment", "decrement", "checkout", "beginPromo", "applyPromo", "removePromo"], { derive, messageFields: action => action.includes("Promo") ? ["promoError"] : ["checkoutHint"] }),
|
|
388
|
+
register: withExclusiveWrites<State>(withOnlineWrites(registerState, ["remove", "increment", "decrement", "checkout", "beginPromo", "applyPromo", "removePromo"], { derive, messageFields: action => action.includes("Promo") ? ["promoError"] : ["checkoutHint"] }), ["load", "remove", "increment", "decrement", "checkout", "applyPromo", "removePromo", "save"], {
|
|
389
|
+
prepare: (action, args) => { if (action === "load") stashCopy(args[0]); },
|
|
390
|
+
coalesce: ["load"],
|
|
391
|
+
// Full cart reloads share the transaction, since they also replace rows and
|
|
392
|
+
// reset promo fields. Only setPromo increments the edit revision: beginPromo's internal error reset
|
|
393
|
+
// must not masquerade as a user edit when a rejected code is retried.
|
|
394
|
+
settle: (before, next, live) => {
|
|
395
|
+
if ((before.promoEditRevision ?? 0) === (live.promoEditRevision ?? 0)) return next;
|
|
396
|
+
return derive({ ...next, promoEditRevision: live.promoEditRevision,
|
|
397
|
+
...(next.items.length ? { promoCode: live.promoCode, promoError: live.promoError } : {}) });
|
|
398
|
+
},
|
|
399
|
+
}),
|
|
386
400
|
onCreate: commerce => { cartPricing = commerce; },
|
|
387
|
-
prefix: "cart", transientKeys: ["items", "subtotal", "promoBusy"], contentActions: ["load", "remove", "increment", "decrement"],
|
|
401
|
+
prefix: "cart", transientKeys: ["items", "subtotal", "promoBusy", "promoEditRevision"], contentActions: ["load", "remove", "increment", "decrement"],
|
|
388
402
|
items: state => state.items.flatMap(row => row.catalogReference?.catalogItemId ? [{ reference: { kind: "storesProduct", id: row.catalogReference.catalogItemId }, commerceDisplayPrice: row.commerceTotal ?? row.total }] : []),
|
|
389
403
|
present: (state, price, allowed) => ({ ...state,
|
|
390
404
|
items: state.items.map(row => ({ ...row, total: row.catalogReference?.catalogItemId ? price(row.catalogReference.catalogItemId, row.commerceTotal ?? row.total) : row.total })),
|
|
@@ -404,10 +418,9 @@ const svc = defineService<State, {
|
|
|
404
418
|
decrement: (state: State, arg?: unknown) => Promise<State>;
|
|
405
419
|
checkout: (state: State) => Promise<State>;
|
|
406
420
|
}>("himiStoreCart", {
|
|
407
|
-
state: derive({ status: "loading", errorKind: "", items: [], subtotal: "", subtotalLabel: "", emptyText: "", checkoutHint: "", showPaidToast: false, noPaidToast: true, itemCount: 0, promoCode: "", appliedCode: "", appliedLabel: "", couponId: "", promoError: "", promoBusy: false, noPromoError: true, hasCoupon: false, discount: "", noDiscount: true, noCheckoutHint: true, noCoupon: true, isLoading: true, notLoading: false, isErrored: false, notErrored: true, noOfflineError: true, noServerError: true, hideContent: true, noEmpty: true, noItems: true }),
|
|
421
|
+
state: derive({ status: "loading", errorKind: "", items: [], subtotal: "", subtotalLabel: "", emptyText: "", checkoutHint: "", showPaidToast: false, noPaidToast: true, itemCount: 0, promoCode: "", promoEditRevision: 0, appliedCode: "", appliedLabel: "", couponId: "", promoError: "", promoBusy: false, noPromoError: true, hasCoupon: false, discount: "", noDiscount: true, noCheckoutHint: true, noCoupon: true, isLoading: true, notLoading: false, isErrored: false, notErrored: true, noOfflineError: true, noServerError: true, hideContent: true, noEmpty: true, noItems: true }),
|
|
408
422
|
actions: {
|
|
409
423
|
load: async (state, arg) => {
|
|
410
|
-
stashCopy(arg);
|
|
411
424
|
// 1) Instant paint from the on-device cache (offline-safe; empty on first-ever run).
|
|
412
425
|
const cached = await readCachedItems();
|
|
413
426
|
const base = cached.length
|
|
@@ -423,7 +436,7 @@ const svc = defineService<State, {
|
|
|
423
436
|
: derive({ ...state, status: "error", errorKind: isOfflineFailure(error) ? "offline" : "server", items: [], subtotal: "", emptyText: copyParam("cart.loadErrorText", "Cart could not load."), checkoutHint: "" });
|
|
424
437
|
}
|
|
425
438
|
},
|
|
426
|
-
setPromo: (state, arg) => derive({ ...state, promoCode: (typeof arg === "string" ? arg : String((arg as { value?: unknown } | undefined)?.value ?? "")).slice(0, 100), promoError: "" }),
|
|
439
|
+
setPromo: (state, arg) => derive({ ...state, promoEditRevision: (state.promoEditRevision ?? 0) + 1, promoCode: (typeof arg === "string" ? arg : String((arg as { value?: unknown } | undefined)?.value ?? "")).slice(0, 100), promoError: "" }),
|
|
427
440
|
beginPromo: state => derive({ ...state, promoBusy: true, promoError: "" }),
|
|
428
441
|
applyPromo: async (state): Promise<State> => {
|
|
429
442
|
const code = state.promoCode.trim();
|