@wix/himalaya-cli 0.1574.0 → 0.1576.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/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/wix-bi.mjs +165 -5
- package/dist/cli.mjs +3489 -2601
- 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 +20 -0
- package/guides/push-cli-onboarding.md +10 -0
- package/package.json +2 -2
- package/templates/beauty-salon/dev/index.ts +74 -127
- 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 +5 -0
- 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 +114 -136
- 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-owner/dev/index.ts +5 -0
- 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-owner/dev/index.ts +3 -0
- 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 +74 -127
- 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 +6 -0
- 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/nonprofit-cause-owner/dev/index.ts +4 -0
- 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 +6 -0
- 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-owner/dev/index.ts +5 -0
- 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 +74 -127
- 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 +5 -0
- 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-owner/dev/index.ts +6 -0
- 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 +74 -127
- 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 +4 -0
- 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/yoga-studio-owner/dev/index.ts +4 -0
- 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
|
Binary file
|
|
@@ -331,7 +331,13 @@ build/validate/render cycles because the failure looks like a content bug rather
|
|
|
331
331
|
of the scrolling `VStack`. Put the action row in `stickyFooter` with its own
|
|
332
332
|
`padding` + `background: { token: "surfacePrimary" }` so it reads as a bar rather than as floating
|
|
333
333
|
content, and keep the scroll root's `padding.bottom` generous enough that the last row is not
|
|
334
|
-
hidden behind it.
|
|
334
|
+
hidden behind it. Buttons in `stickyHeader` and `stickyFooter` use the same action-binding
|
|
335
|
+
contract as the body: non-worker actions resolve `$service:` values in `params` and `args`
|
|
336
|
+
against live state when tapped. Native iOS, macOS and Android also resolve `$app:` values
|
|
337
|
+
through the app service scope. Missing/null bindings are dropped; `params` accepts scalars
|
|
338
|
+
only, while `args` preserves typed values. `tier5.*` actions keep their
|
|
339
|
+
params/args as authored (read the state in the reducer); their declared `nav` resolves
|
|
340
|
+
through the existing navigation contract.
|
|
335
341
|
|
|
336
342
|
---
|
|
337
343
|
|
package/guides/hhp.md
CHANGED
|
@@ -121,8 +121,8 @@ The portable compiler stores its generated catalog as a deterministic gzip/base6
|
|
|
121
121
|
envelope with a SHA-256 check. `himi hhp profile --json` returns the complete decoded
|
|
122
122
|
metadata; the installed component guides and support decision source remain readable.
|
|
123
123
|
`gen-v2.ts` builds the envelope from the canonical schemas and SDK exports, and refuses while any
|
|
124
|
-
new field or export lacks a decision in `v2-support-decisions.json`. The catalog itself is
|
|
125
|
-
|
|
124
|
+
new field or export lacks a decision in `v2-support-decisions.json`. The catalog itself is gitignored
|
|
125
|
+
and generated before use, so a PR commits the decisions file and there is no catalog to commit.
|
|
126
126
|
|
|
127
127
|
## CSS
|
|
128
128
|
|
|
@@ -14,6 +14,14 @@ require those fields. Existing error-only push refusals now also carry `ok: fals
|
|
|
14
14
|
Output channels and process exit codes are unchanged. Read JSON failures on both
|
|
15
15
|
stdout and stderr; progress/warnings are not failure envelopes.
|
|
16
16
|
|
|
17
|
+
## `himi store preflight` / `himi store upload` / `himi testflight` config failures
|
|
18
|
+
|
|
19
|
+
| Code | Meaning |
|
|
20
|
+
| --- | --- |
|
|
21
|
+
| `store_config_unavailable` | Loading the local store configuration threw an error, such as a missing or unreadable file. Exit 2; no build or upload runs. The existing `error` remains on stderr, alongside `findings` from the app preflight, including developer identity diagnostics available without store configuration. If preflight itself fails, its diagnostic is included as an error finding. |
|
|
22
|
+
|
|
23
|
+
Other legacy store failures, including `store status`, remain outside this migration.
|
|
24
|
+
|
|
17
25
|
`code` is the machine contract. `error`/`errors`/`message` describe the failure for
|
|
18
26
|
people and `hint`, when present, suggests recovery. Neither their wording nor the
|
|
19
27
|
presence of one of those legacy shapes identifies the failure class. The complete
|
|
@@ -105,6 +113,18 @@ then carries a `ring_pinned` entry in `warnings` (also printed to stderr) and
|
|
|
105
113
|
that moves the ring to the new release. When the push passed `--control-url`, `promote` repeats it. It stays `ok: true`. When the rings cannot be read, push
|
|
106
114
|
prints neither.
|
|
107
115
|
|
|
116
|
+
## `himi notify` owner-login codes
|
|
117
|
+
|
|
118
|
+
`himi notify setup|status` and `himi notify send --provider ping` carry `code` on these refusals
|
|
119
|
+
only; their other refusals keep the legacy `error`-only envelope for now. Login hints name the
|
|
120
|
+
target the command read, e.g. `himi login --browser --target wix`.
|
|
121
|
+
|
|
122
|
+
| Code | Meaning |
|
|
123
|
+
|---|---|
|
|
124
|
+
| `control_token_expired` | The stored control token is past its `exp`. Decided locally before any network call; run `himi login`. |
|
|
125
|
+
| `owner_login_required` | No usable owner login: the control token is missing or has no subject, or the plane answered 401/403 to the owner-apps read (`GET /v1/owner/apps`). Run `himi login`. Other failures of that read stay `app_ownership_unknown`. |
|
|
126
|
+
| `flag_not_supported_with_ping` | A local-sender flag was passed with `--provider ping`; `flag` names it. `--badge` is one: Ping has no badge field, and for a Branded app Ping sets the iOS badge itself from the member's unseen Ping feed items. |
|
|
127
|
+
|
|
108
128
|
## `himi cms seed` codes
|
|
109
129
|
|
|
110
130
|
| Code | Meaning |
|
|
@@ -343,6 +343,16 @@ goes to the member's subscribed devices for this site's Branded-family app, whic
|
|
|
343
343
|
than one device. It does not go to their other Wix apps. Confirm receipt and the tap on the
|
|
344
344
|
device itself.
|
|
345
345
|
|
|
346
|
+
**There is no `--badge`.** Ping's send request has no badge field. For a Branded app, Ping sets
|
|
347
|
+
the iOS app-icon badge itself from the member's unseen items in their Ping feed, so the CLI refuses
|
|
348
|
+
`--badge` (`flag_not_supported_with_ping`) rather than send a number Ping would ignore.
|
|
349
|
+
|
|
350
|
+
**An expired login is named as one.** When the stored control token has expired, `notify send`
|
|
351
|
+
and `notify setup|status` refuse with `control_token_expired` before any network call; when the
|
|
352
|
+
plane rejects the token, they refuse with `owner_login_required`. Both mean log in again, not
|
|
353
|
+
an ownership problem: the hint names the target the command read (`himi login --browser --target
|
|
354
|
+
<target>`), because a bare `himi login` renews the default target instead.
|
|
355
|
+
|
|
346
356
|
Every send carries a request ID. If the outcome is unknown (`send_outcome_unknown`, or a lost
|
|
347
357
|
response), the CLI prints the ID and never retries on its own. Re-run the same command with
|
|
348
358
|
`--request-id <id>`: Ping runs one request at most once per site for 24 hours, so a retry
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wix/himalaya-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1576.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": [
|
|
@@ -71,5 +71,5 @@
|
|
|
71
71
|
"artifactId": "himalaya-cli"
|
|
72
72
|
}
|
|
73
73
|
},
|
|
74
|
-
"falconPackageHash": "
|
|
74
|
+
"falconPackageHash": "b3770b71ba8570708ab97e43a149a8ee2880f9fea27a618d80902862"
|
|
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
|
}
|
|
@@ -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();
|
|
@@ -79742,6 +79742,7 @@ const SCREENS = runtimeSetting({
|
|
|
79742
79742
|
"title": "Create product",
|
|
79743
79743
|
"style": "primary",
|
|
79744
79744
|
"fullWidth": true,
|
|
79745
|
+
"disabledPath": "$service:himiOwnerLiteProductForm.writeBusy",
|
|
79745
79746
|
"action": {
|
|
79746
79747
|
"actionId": "tier5.himiOwnerLiteProductForm.submit"
|
|
79747
79748
|
}
|
|
@@ -80061,6 +80062,7 @@ const SCREENS = runtimeSetting({
|
|
|
80061
80062
|
"title": "Create post",
|
|
80062
80063
|
"style": "primary",
|
|
80063
80064
|
"fullWidth": true,
|
|
80065
|
+
"disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
|
|
80064
80066
|
"action": {
|
|
80065
80067
|
"actionId": "tier5.himiOwnerLitePostForm.submit"
|
|
80066
80068
|
}
|
|
@@ -80576,6 +80578,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
80576
80578
|
"title": "Save changes",
|
|
80577
80579
|
"style": "primary",
|
|
80578
80580
|
"fullWidth": true,
|
|
80581
|
+
"disabledPath": "$service:himiOwnerLiteProductForm.writeBusy",
|
|
80579
80582
|
"action": {
|
|
80580
80583
|
"actionId": "tier5.himiOwnerLiteProductForm.submit"
|
|
80581
80584
|
}
|
|
@@ -80823,6 +80826,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
80823
80826
|
"title": "Save changes",
|
|
80824
80827
|
"style": "primary",
|
|
80825
80828
|
"fullWidth": true,
|
|
80829
|
+
"disabledPath": "$service:himiOwnerLiteServiceForm.writeBusy",
|
|
80826
80830
|
"action": {
|
|
80827
80831
|
"actionId": "tier5.himiOwnerLiteServiceForm.submit"
|
|
80828
80832
|
}
|
|
@@ -81147,6 +81151,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
|
|
|
81147
81151
|
"title": "Save changes",
|
|
81148
81152
|
"style": "primary",
|
|
81149
81153
|
"fullWidth": true,
|
|
81154
|
+
"disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
|
|
81150
81155
|
"action": {
|
|
81151
81156
|
"actionId": "tier5.himiOwnerLitePostForm.submit"
|
|
81152
81157
|
}
|