@wix/himalaya-cli 0.1574.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.
Files changed (90) hide show
  1. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/dev-server/src/app-config.d.ts +16 -5
  2. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/core/ts/src/define-app.d.ts +6 -0
  3. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/index.d.ts +15 -0
  4. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/_types/stdlib/telemetry/wix-bi/src/oneapp.d.ts +90 -0
  5. package/dist/authoring-runtime/node_modules/@wix/himalaya/dist/wix-bi.mjs +165 -5
  6. package/dist/cli.mjs +3479 -2601
  7. package/dist/preview-assets.generated.bin +0 -0
  8. package/guides/designing-himi-screens.md +7 -1
  9. package/guides/hhp.md +2 -2
  10. package/guides/himi-json-output.md +12 -0
  11. package/guides/push-cli-onboarding.md +10 -0
  12. package/package.json +2 -2
  13. package/templates/beauty-salon/dev/index.ts +74 -127
  14. package/templates/beauty-salon/tier5-src/_shared/exclusive-writes.ts +66 -0
  15. package/templates/beauty-salon/tier5-src/himi-branded-lite-cart/index.ts +18 -5
  16. package/templates/beauty-salon-owner/dev/index.ts +5 -0
  17. package/templates/beauty-salon-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  18. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  19. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
  20. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
  21. package/templates/branded-lite/dev/index.ts +114 -136
  22. package/templates/branded-lite/l10n/en.json +570 -0
  23. package/templates/branded-lite/l10n/he.json +200 -0
  24. package/templates/branded-lite/tier5-src/_shared/business-chat-copy.ts +40 -0
  25. package/templates/branded-lite/tier5-src/_shared/business-chat-message-model.ts +1 -1
  26. package/templates/branded-lite/tier5-src/_shared/business-chat-model.ts +3 -2
  27. package/templates/branded-lite/tier5-src/_shared/business-chat-presenter.ts +26 -21
  28. package/templates/branded-lite/tier5-src/_shared/exclusive-writes.ts +66 -0
  29. package/templates/branded-lite/tier5-src/business-chat/index.ts +184 -63
  30. package/templates/branded-lite/tier5-src/himi-branded-lite-cart/index.ts +18 -5
  31. package/templates/community-club-owner/dev/index.ts +5 -0
  32. package/templates/community-club-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  33. package/templates/community-club-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
  34. package/templates/community-club-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  35. package/templates/community-club-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
  36. package/templates/events-meetups-owner/dev/index.ts +3 -0
  37. package/templates/events-meetups-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  38. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
  39. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  40. package/templates/gym-fitness/dev/index.ts +74 -127
  41. package/templates/gym-fitness/tier5-src/_shared/exclusive-writes.ts +66 -0
  42. package/templates/gym-fitness/tier5-src/himi-branded-lite-cart/index.ts +18 -5
  43. package/templates/gym-fitness-owner/dev/index.ts +6 -0
  44. package/templates/gym-fitness-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  45. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
  46. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  47. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
  48. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
  49. package/templates/nonprofit-cause-owner/dev/index.ts +4 -0
  50. package/templates/nonprofit-cause-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  51. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
  52. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  53. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
  54. package/templates/owner-lite/dev/index.ts +6 -0
  55. package/templates/owner-lite/tier5-src/_shared/exclusive-writes.ts +66 -0
  56. package/templates/owner-lite/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
  57. package/templates/owner-lite/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  58. package/templates/owner-lite/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
  59. package/templates/owner-lite/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
  60. package/templates/podcast-creator-owner/dev/index.ts +5 -0
  61. package/templates/podcast-creator-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  62. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
  63. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  64. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
  65. package/templates/portfolio-creative/dev/index.ts +74 -127
  66. package/templates/portfolio-creative/tier5-src/_shared/exclusive-writes.ts +66 -0
  67. package/templates/portfolio-creative/tier5-src/himi-branded-lite-cart/index.ts +18 -5
  68. package/templates/portfolio-creative-owner/dev/index.ts +5 -0
  69. package/templates/portfolio-creative-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  70. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  71. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
  72. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
  73. package/templates/restaurant-owner/dev/index.ts +6 -0
  74. package/templates/restaurant-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  75. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
  76. package/templates/restaurant-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  77. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
  78. package/templates/restaurant-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
  79. package/templates/retail-store/dev/index.ts +74 -127
  80. package/templates/retail-store/tier5-src/_shared/exclusive-writes.ts +66 -0
  81. package/templates/retail-store/tier5-src/himi-branded-lite-cart/index.ts +18 -5
  82. package/templates/retail-store-owner/dev/index.ts +4 -0
  83. package/templates/retail-store-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  84. package/templates/retail-store-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  85. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product-form/index.ts +5 -1
  86. package/templates/yoga-studio-owner/dev/index.ts +4 -0
  87. package/templates/yoga-studio-owner/tier5-src/_shared/exclusive-writes.ts +66 -0
  88. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event-form/index.ts +5 -1
  89. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post-form/index.ts +5 -1
  90. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-service-form/index.ts +5 -1
@@ -6,7 +6,9 @@
6
6
  // form can't safely collect — so this form only edits an existing event's text
7
7
  // (title + description). Bound by buildForm ($service:himiOwnerLiteEventForm.*).
8
8
 
9
- import { defineService } from "@himalaya/state";
9
+ import { defineService as registerService } from "@himalaya/state";
10
+ import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
11
+ const defineService = withExclusiveWrites(registerService, ["submit"]);
10
12
  import { getEvent, updateEvent, type WixEvent } from "@himalaya/wix-events";
11
13
  import { normalizeEvent } from "../_shared/himi-events.js";
12
14
  import { postBodyText, plainPostBody } from "../_shared/owner-post-content.js";
@@ -66,6 +68,7 @@ interface EventFormState {
66
68
  when: string;
67
69
  location: string;
68
70
  status: "idle" | "loading" | "saving" | "saved" | "error";
71
+ writeBusy: boolean;
69
72
  error: string;
70
73
  savedMessage: string;
71
74
  /** WS-C — canonical detail target (`himi_owner_lite_event`); `savedId` rides
@@ -140,6 +143,7 @@ function shape(s: Partial<EventFormState>): EventFormState {
140
143
  when: s.when ?? "—",
141
144
  location: s.location ?? "—",
142
145
  status: s.status ?? "idle",
146
+ writeBusy: s.writeBusy ?? false,
143
147
  error: s.error ?? "",
144
148
  savedMessage: s.savedMessage ?? "",
145
149
  // WS-C — canonical detail target + the saved entity's id as a separate nav
@@ -8,7 +8,9 @@ import { editorSubmitValue, queuedDocument, editorChanged, editorRunKeys, resetH
8
8
  import { ricosUploadService } from "@himalaya/wix-rich-content-editor";
9
9
  import { wixCall } from "@himalaya/wix-site-media";
10
10
  import { wixMediaUrl, WIX_DETAIL_IMAGE } from "../_shared/himi-store.js";
11
- import { defineService } from "@himalaya/state";
11
+ import { defineService as registerService } from "@himalaya/state";
12
+ import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
13
+ const defineService = withExclusiveWrites(registerService, ["submit"]);
12
14
  import { getDraftPost, createDraftPost, updateDraftPost, type WixDraftPostWrite, type WixRichContent } from "@himalaya/wix-blog";
13
15
  import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback, ownerWriteError } from "../_shared/owner-load-error.js";
14
16
  import { DEMO_POSTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
@@ -53,6 +55,7 @@ interface PostFormState {
53
55
  noCover: boolean;
54
56
  isFeatured: string;
55
57
  status: "idle" | "loading" | "saving" | "saved" | "error";
58
+ writeBusy: boolean;
56
59
  error: string;
57
60
  savedMessage: string;
58
61
  /** WS-C — canonical detail target (`himi_owner_lite_post`); `savedId` rides
@@ -128,6 +131,7 @@ function shape(s: Partial<PostFormState>): PostFormState {
128
131
  noCover: !s.coverUrl,
129
132
  isFeatured: s.isFeatured ?? "false",
130
133
  status: s.status ?? "idle",
134
+ writeBusy: s.writeBusy ?? false,
131
135
  error: s.error ?? "",
132
136
  savedMessage: s.savedMessage ?? "",
133
137
  // WS-C — canonical detail target + the saved entity's id as a separate nav
@@ -6,7 +6,9 @@
6
6
  // the WRITE shape needs `priceData: { price: <number>, currency }`. Bound by the
7
7
  // generic buildForm descriptor ($service:himiOwnerLiteProductForm.*).
8
8
 
9
- import { defineService } from "@himalaya/state";
9
+ import { defineService as registerService } from "@himalaya/state";
10
+ import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
11
+ const defineService = withExclusiveWrites(registerService, ["submit"]);
10
12
  import {
11
13
  getProduct,
12
14
  createProduct,
@@ -76,6 +78,7 @@ interface ProductFormState {
76
78
  descriptionNote: string;
77
79
  visible: string;
78
80
  status: "idle" | "loading" | "saving" | "saved" | "error";
81
+ writeBusy: boolean;
79
82
  error: string;
80
83
  savedMessage: string;
81
84
  /** WS-C — canonical detail target (`himi_owner_lite_product`); `savedId` rides
@@ -161,6 +164,7 @@ function shape(s: Partial<ProductFormState>): ProductFormState {
161
164
  descriptionNote: dMode === "locked" ? copyParam("ownerProductForm.richDescriptionNote", "This description has formatting or media. Edit it in your site dashboard to keep that content intact; name, price and SKU can be changed here.") : "",
162
165
  visible: s.visible ?? "true",
163
166
  status: s.status ?? "idle",
167
+ writeBusy: s.writeBusy ?? false,
164
168
  error: s.error ?? "",
165
169
  savedMessage: s.savedMessage ?? "",
166
170
  // WS-C — canonical detail target + the saved entity's id as a separate nav
@@ -5,7 +5,9 @@
5
5
  // updateService. Bound by buildForm ($service:himiOwnerLiteServiceForm.*).
6
6
 
7
7
  import { landsOnLive } from "../_shared/owner-editor-value.js";
8
- import { defineService } from "@himalaya/state";
8
+ import { defineService as registerService } from "@himalaya/state";
9
+ import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
10
+ const defineService = withExclusiveWrites(registerService, ["submit"]);
9
11
  import { getService, updateService, type WixService } from "@himalaya/wix-bookings";
10
12
  import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback, ownerWriteError } from "../_shared/owner-load-error.js";
11
13
  import { DEMO_SERVICES, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
@@ -31,6 +33,7 @@ interface ServiceFormState {
31
33
  description: string;
32
34
  category: string;
33
35
  status: "idle" | "loading" | "saving" | "saved" | "error";
36
+ writeBusy: boolean;
34
37
  error: string;
35
38
  savedMessage: string;
36
39
  /** WS-C — canonical detail target (`himi_owner_lite_service`); `savedId` rides
@@ -88,6 +91,7 @@ function shape(s: Partial<ServiceFormState>): ServiceFormState {
88
91
  description: s.description ?? "",
89
92
  category: s.category ?? "",
90
93
  status: s.status ?? "idle",
94
+ writeBusy: s.writeBusy ?? false,
91
95
  error: s.error ?? "",
92
96
  savedMessage: s.savedMessage ?? "",
93
97
  // WS-C — canonical detail target + the saved entity's id as a separate nav
@@ -78486,6 +78486,7 @@ const SCREENS = runtimeSetting({
78486
78486
  "title": "Create product",
78487
78487
  "style": "primary",
78488
78488
  "fullWidth": true,
78489
+ "disabledPath": "$service:himiOwnerLiteProductForm.writeBusy",
78489
78490
  "action": {
78490
78491
  "actionId": "tier5.himiOwnerLiteProductForm.submit"
78491
78492
  }
@@ -78805,6 +78806,7 @@ const SCREENS = runtimeSetting({
78805
78806
  "title": "Create episode",
78806
78807
  "style": "primary",
78807
78808
  "fullWidth": true,
78809
+ "disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
78808
78810
  "action": {
78809
78811
  "actionId": "tier5.himiOwnerLitePostForm.submit"
78810
78812
  }
@@ -79321,6 +79323,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
79321
79323
  "title": "Save changes",
79322
79324
  "style": "primary",
79323
79325
  "fullWidth": true,
79326
+ "disabledPath": "$service:himiOwnerLiteProductForm.writeBusy",
79324
79327
  "action": {
79325
79328
  "actionId": "tier5.himiOwnerLiteProductForm.submit"
79326
79329
  }
@@ -79683,6 +79686,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
79683
79686
  "title": "Save changes",
79684
79687
  "style": "primary",
79685
79688
  "fullWidth": true,
79689
+ "disabledPath": "$service:himiOwnerLiteEventForm.writeBusy",
79686
79690
  "action": {
79687
79691
  "actionId": "tier5.himiOwnerLiteEventForm.submit"
79688
79692
  }
@@ -80006,6 +80010,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
80006
80010
  "title": "Save changes",
80007
80011
  "style": "primary",
80008
80012
  "fullWidth": true,
80013
+ "disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
80009
80014
  "action": {
80010
80015
  "actionId": "tier5.himiOwnerLitePostForm.submit"
80011
80016
  }
@@ -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
+ }
@@ -6,7 +6,9 @@
6
6
  // form can't safely collect — so this form only edits an existing event's text
7
7
  // (title + description). Bound by buildForm ($service:himiOwnerLiteEventForm.*).
8
8
 
9
- import { defineService } from "@himalaya/state";
9
+ import { defineService as registerService } from "@himalaya/state";
10
+ import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
11
+ const defineService = withExclusiveWrites(registerService, ["submit"]);
10
12
  import { getEvent, updateEvent, type WixEvent } from "@himalaya/wix-events";
11
13
  import { normalizeEvent } from "../_shared/himi-events.js";
12
14
  import { postBodyText, plainPostBody } from "../_shared/owner-post-content.js";
@@ -66,6 +68,7 @@ interface EventFormState {
66
68
  when: string;
67
69
  location: string;
68
70
  status: "idle" | "loading" | "saving" | "saved" | "error";
71
+ writeBusy: boolean;
69
72
  error: string;
70
73
  savedMessage: string;
71
74
  /** WS-C — canonical detail target (`himi_owner_lite_event`); `savedId` rides
@@ -140,6 +143,7 @@ function shape(s: Partial<EventFormState>): EventFormState {
140
143
  when: s.when ?? "—",
141
144
  location: s.location ?? "—",
142
145
  status: s.status ?? "idle",
146
+ writeBusy: s.writeBusy ?? false,
143
147
  error: s.error ?? "",
144
148
  savedMessage: s.savedMessage ?? "",
145
149
  // WS-C — canonical detail target + the saved entity's id as a separate nav
@@ -8,7 +8,9 @@ import { editorSubmitValue, queuedDocument, editorChanged, editorRunKeys, resetH
8
8
  import { ricosUploadService } from "@himalaya/wix-rich-content-editor";
9
9
  import { wixCall } from "@himalaya/wix-site-media";
10
10
  import { wixMediaUrl, WIX_DETAIL_IMAGE } from "../_shared/himi-store.js";
11
- import { defineService } from "@himalaya/state";
11
+ import { defineService as registerService } from "@himalaya/state";
12
+ import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
13
+ const defineService = withExclusiveWrites(registerService, ["submit"]);
12
14
  import { getDraftPost, createDraftPost, updateDraftPost, type WixDraftPostWrite, type WixRichContent } from "@himalaya/wix-blog";
13
15
  import { ownerLoadError, isOwnerPreviewDemo, ownerDemoFallback, ownerWriteError } from "../_shared/owner-load-error.js";
14
16
  import { DEMO_POSTS, DEMO_NOTE, DEMO_SAVE_NOTE, demoRefusalPatch } from "../_shared/owner-preview-demo.js";
@@ -53,6 +55,7 @@ interface PostFormState {
53
55
  noCover: boolean;
54
56
  isFeatured: string;
55
57
  status: "idle" | "loading" | "saving" | "saved" | "error";
58
+ writeBusy: boolean;
56
59
  error: string;
57
60
  savedMessage: string;
58
61
  /** WS-C — canonical detail target (`himi_owner_lite_post`); `savedId` rides
@@ -128,6 +131,7 @@ function shape(s: Partial<PostFormState>): PostFormState {
128
131
  noCover: !s.coverUrl,
129
132
  isFeatured: s.isFeatured ?? "false",
130
133
  status: s.status ?? "idle",
134
+ writeBusy: s.writeBusy ?? false,
131
135
  error: s.error ?? "",
132
136
  savedMessage: s.savedMessage ?? "",
133
137
  // WS-C — canonical detail target + the saved entity's id as a separate nav
@@ -6,7 +6,9 @@
6
6
  // the WRITE shape needs `priceData: { price: <number>, currency }`. Bound by the
7
7
  // generic buildForm descriptor ($service:himiOwnerLiteProductForm.*).
8
8
 
9
- import { defineService } from "@himalaya/state";
9
+ import { defineService as registerService } from "@himalaya/state";
10
+ import { withExclusiveWrites } from "../_shared/exclusive-writes.js";
11
+ const defineService = withExclusiveWrites(registerService, ["submit"]);
10
12
  import {
11
13
  getProduct,
12
14
  createProduct,
@@ -76,6 +78,7 @@ interface ProductFormState {
76
78
  descriptionNote: string;
77
79
  visible: string;
78
80
  status: "idle" | "loading" | "saving" | "saved" | "error";
81
+ writeBusy: boolean;
79
82
  error: string;
80
83
  savedMessage: string;
81
84
  /** WS-C — canonical detail target (`himi_owner_lite_product`); `savedId` rides
@@ -161,6 +164,7 @@ function shape(s: Partial<ProductFormState>): ProductFormState {
161
164
  descriptionNote: dMode === "locked" ? copyParam("ownerProductForm.richDescriptionNote", "This description has formatting or media. Edit it in your site dashboard to keep that content intact; name, price and SKU can be changed here.") : "",
162
165
  visible: s.visible ?? "true",
163
166
  status: s.status ?? "idle",
167
+ writeBusy: s.writeBusy ?? false,
164
168
  error: s.error ?? "",
165
169
  savedMessage: s.savedMessage ?? "",
166
170
  // WS-C — canonical detail target + the saved entity's id as a separate nav
@@ -15063,39 +15063,28 @@ const SCREENS = runtimeSetting({
15063
15063
  "id": "himi_cart.qty_minus",
15064
15064
  "body": {
15065
15065
  "package": "himalaya",
15066
- "type": "ZStack",
15066
+ "type": "Button",
15067
15067
  "props": {
15068
- "alignment": "center",
15069
- "children": [
15070
- {
15071
- "id": "",
15072
- "body": {
15073
- "package": "himalaya",
15074
- "type": "Icon",
15075
- "props": {
15076
- "name": "minus",
15077
- "size": 24,
15078
- "tint": "textPrimary"
15079
- }
15080
- },
15081
- "modifiers": {
15082
- "accessibilityRole": "decorative"
15083
- }
15068
+ "title": "Decrease quantity",
15069
+ "action": {
15070
+ "actionId": "tier5.himiStoreCart.decrement",
15071
+ "params": {
15072
+ "id": "$item.id"
15084
15073
  }
15085
- ]
15074
+ },
15075
+ "style": "tertiary",
15076
+ "iconOnly": true,
15077
+ "leadingIcon": "minus",
15078
+ "cornerRadius": 22,
15079
+ "minHeight": 44,
15080
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15086
15081
  }
15087
15082
  },
15088
15083
  "modifiers": {
15089
15084
  "width": 44,
15090
15085
  "height": 44,
15091
- "onTap": {
15092
- "actionId": "tier5.himiStoreCart.decrement",
15093
- "params": {
15094
- "id": "$item.id"
15095
- }
15096
- },
15097
- "accessibilityRole": "button",
15098
15086
  "accessibilityLabel": "Decrease quantity",
15087
+ "accessibilityRole": "button",
15099
15088
  "haptic": "light"
15100
15089
  }
15101
15090
  },
@@ -15118,39 +15107,28 @@ const SCREENS = runtimeSetting({
15118
15107
  "id": "himi_cart.qty_plus",
15119
15108
  "body": {
15120
15109
  "package": "himalaya",
15121
- "type": "ZStack",
15110
+ "type": "Button",
15122
15111
  "props": {
15123
- "alignment": "center",
15124
- "children": [
15125
- {
15126
- "id": "",
15127
- "body": {
15128
- "package": "himalaya",
15129
- "type": "Icon",
15130
- "props": {
15131
- "name": "plus",
15132
- "size": 24,
15133
- "tint": "textPrimary"
15134
- }
15135
- },
15136
- "modifiers": {
15137
- "accessibilityRole": "decorative"
15138
- }
15112
+ "title": "Increase quantity",
15113
+ "action": {
15114
+ "actionId": "tier5.himiStoreCart.increment",
15115
+ "params": {
15116
+ "id": "$item.id"
15139
15117
  }
15140
- ]
15118
+ },
15119
+ "style": "tertiary",
15120
+ "iconOnly": true,
15121
+ "leadingIcon": "plus",
15122
+ "cornerRadius": 22,
15123
+ "minHeight": 44,
15124
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15141
15125
  }
15142
15126
  },
15143
15127
  "modifiers": {
15144
15128
  "width": 44,
15145
15129
  "height": 44,
15146
- "onTap": {
15147
- "actionId": "tier5.himiStoreCart.increment",
15148
- "params": {
15149
- "id": "$item.id"
15150
- }
15151
- },
15152
- "accessibilityRole": "button",
15153
15130
  "accessibilityLabel": "Increase quantity",
15131
+ "accessibilityRole": "button",
15154
15132
  "haptic": "light"
15155
15133
  }
15156
15134
  },
@@ -15166,39 +15144,28 @@ const SCREENS = runtimeSetting({
15166
15144
  "id": "himi_cart.save_filled",
15167
15145
  "body": {
15168
15146
  "package": "himalaya",
15169
- "type": "ZStack",
15147
+ "type": "Button",
15170
15148
  "props": {
15171
- "alignment": "center",
15172
- "children": [
15173
- {
15174
- "id": "",
15175
- "body": {
15176
- "package": "himalaya",
15177
- "type": "Icon",
15178
- "props": {
15179
- "name": "heart_fill",
15180
- "size": 24,
15181
- "tint": "textPrimary"
15182
- }
15183
- },
15184
- "modifiers": {
15185
- "accessibilityRole": "decorative"
15186
- }
15149
+ "title": "Remove from wishlist",
15150
+ "action": {
15151
+ "actionId": "tier5.himiStoreCart.save",
15152
+ "params": {
15153
+ "id": "$item.id"
15187
15154
  }
15188
- ]
15155
+ },
15156
+ "style": "tertiary",
15157
+ "iconOnly": true,
15158
+ "leadingIcon": "heart_fill",
15159
+ "cornerRadius": 22,
15160
+ "minHeight": 44,
15161
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15189
15162
  }
15190
15163
  },
15191
15164
  "modifiers": {
15192
15165
  "width": 44,
15193
15166
  "height": 44,
15194
- "onTap": {
15195
- "actionId": "tier5.himiStoreCart.save",
15196
- "params": {
15197
- "id": "$item.id"
15198
- }
15199
- },
15200
- "accessibilityRole": "button",
15201
15167
  "accessibilityLabel": "Remove from wishlist",
15168
+ "accessibilityRole": "button",
15202
15169
  "haptic": "light",
15203
15170
  "hiddenPath": "$item.notSaved"
15204
15171
  }
@@ -15207,39 +15174,28 @@ const SCREENS = runtimeSetting({
15207
15174
  "id": "himi_cart.save",
15208
15175
  "body": {
15209
15176
  "package": "himalaya",
15210
- "type": "ZStack",
15177
+ "type": "Button",
15211
15178
  "props": {
15212
- "alignment": "center",
15213
- "children": [
15214
- {
15215
- "id": "",
15216
- "body": {
15217
- "package": "himalaya",
15218
- "type": "Icon",
15219
- "props": {
15220
- "name": "heart",
15221
- "size": 24,
15222
- "tint": "textPrimary"
15223
- }
15224
- },
15225
- "modifiers": {
15226
- "accessibilityRole": "decorative"
15227
- }
15179
+ "title": "Add to wishlist",
15180
+ "action": {
15181
+ "actionId": "tier5.himiStoreCart.save",
15182
+ "params": {
15183
+ "id": "$item.id"
15228
15184
  }
15229
- ]
15185
+ },
15186
+ "style": "tertiary",
15187
+ "iconOnly": true,
15188
+ "leadingIcon": "heart",
15189
+ "cornerRadius": 22,
15190
+ "minHeight": 44,
15191
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15230
15192
  }
15231
15193
  },
15232
15194
  "modifiers": {
15233
15195
  "width": 44,
15234
15196
  "height": 44,
15235
- "onTap": {
15236
- "actionId": "tier5.himiStoreCart.save",
15237
- "params": {
15238
- "id": "$item.id"
15239
- }
15240
- },
15241
- "accessibilityRole": "button",
15242
15197
  "accessibilityLabel": "Add to wishlist",
15198
+ "accessibilityRole": "button",
15243
15199
  "haptic": "light",
15244
15200
  "hiddenPath": "$item.saved"
15245
15201
  }
@@ -15248,39 +15204,28 @@ const SCREENS = runtimeSetting({
15248
15204
  "id": "himi_cart.remove",
15249
15205
  "body": {
15250
15206
  "package": "himalaya",
15251
- "type": "ZStack",
15207
+ "type": "Button",
15252
15208
  "props": {
15253
- "alignment": "center",
15254
- "children": [
15255
- {
15256
- "id": "",
15257
- "body": {
15258
- "package": "himalaya",
15259
- "type": "Icon",
15260
- "props": {
15261
- "name": "trash",
15262
- "size": 24,
15263
- "tint": "textPrimary"
15264
- }
15265
- },
15266
- "modifiers": {
15267
- "accessibilityRole": "decorative"
15268
- }
15209
+ "title": "Remove from cart",
15210
+ "action": {
15211
+ "actionId": "tier5.himiStoreCart.remove",
15212
+ "params": {
15213
+ "id": "$item.id"
15269
15214
  }
15270
- ]
15215
+ },
15216
+ "style": "tertiary",
15217
+ "iconOnly": true,
15218
+ "leadingIcon": "trash",
15219
+ "cornerRadius": 22,
15220
+ "minHeight": 44,
15221
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15271
15222
  }
15272
15223
  },
15273
15224
  "modifiers": {
15274
15225
  "width": 44,
15275
15226
  "height": 44,
15276
- "onTap": {
15277
- "actionId": "tier5.himiStoreCart.remove",
15278
- "params": {
15279
- "id": "$item.id"
15280
- }
15281
- },
15282
- "accessibilityRole": "button",
15283
15227
  "accessibilityLabel": "Remove from cart",
15228
+ "accessibilityRole": "button",
15284
15229
  "haptic": "light"
15285
15230
  }
15286
15231
  }
@@ -15529,7 +15474,7 @@ const SCREENS = runtimeSetting({
15529
15474
  "action": {
15530
15475
  "actionId": "tier5.himiStoreCart.applyPromo"
15531
15476
  },
15532
- "disabledPath": "$service:himiStoreCart.promoBusy"
15477
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15533
15478
  }
15534
15479
  },
15535
15480
  "modifiers": {
@@ -15586,7 +15531,8 @@ const SCREENS = runtimeSetting({
15586
15531
  "iconOnly": true,
15587
15532
  "leadingIcon": "close",
15588
15533
  "cornerRadius": 22,
15589
- "minHeight": 44
15534
+ "minHeight": 44,
15535
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15590
15536
  }
15591
15537
  },
15592
15538
  "modifiers": {
@@ -15720,6 +15666,7 @@ const SCREENS = runtimeSetting({
15720
15666
  "title": "Checkout",
15721
15667
  "style": "primary",
15722
15668
  "fullWidth": true,
15669
+ "disabledPath": "$service:himiStoreCart.writeBusy",
15723
15670
  "action": {
15724
15671
  "actionId": "tier5.himiStoreCart.checkout"
15725
15672
  }