@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
@@ -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 post",
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
@@ -72672,6 +72672,7 @@ const SCREENS = runtimeSetting({
72672
72672
  "title": "Create post",
72673
72673
  "style": "primary",
72674
72674
  "fullWidth": true,
72675
+ "disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
72675
72676
  "action": {
72676
72677
  "actionId": "tier5.himiOwnerLitePostForm.submit"
72677
72678
  }
@@ -73146,6 +73147,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
73146
73147
  "title": "Save changes",
73147
73148
  "style": "primary",
73148
73149
  "fullWidth": true,
73150
+ "disabledPath": "$service:himiOwnerLiteEventForm.writeBusy",
73149
73151
  "action": {
73150
73152
  "actionId": "tier5.himiOwnerLiteEventForm.submit"
73151
73153
  }
@@ -73470,6 +73472,7 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
73470
73472
  "title": "Save changes",
73471
73473
  "style": "primary",
73472
73474
  "fullWidth": true,
73475
+ "disabledPath": "$service:himiOwnerLitePostForm.writeBusy",
73473
73476
  "action": {
73474
73477
  "actionId": "tier5.himiOwnerLitePostForm.submit"
73475
73478
  }
@@ -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
@@ -15759,39 +15759,28 @@ const SCREENS = runtimeSetting({
15759
15759
  "id": "himi_cart.qty_minus",
15760
15760
  "body": {
15761
15761
  "package": "himalaya",
15762
- "type": "ZStack",
15762
+ "type": "Button",
15763
15763
  "props": {
15764
- "alignment": "center",
15765
- "children": [
15766
- {
15767
- "id": "",
15768
- "body": {
15769
- "package": "himalaya",
15770
- "type": "Icon",
15771
- "props": {
15772
- "name": "minus",
15773
- "size": 24,
15774
- "tint": "textPrimary"
15775
- }
15776
- },
15777
- "modifiers": {
15778
- "accessibilityRole": "decorative"
15779
- }
15764
+ "title": "Decrease quantity",
15765
+ "action": {
15766
+ "actionId": "tier5.himiStoreCart.decrement",
15767
+ "params": {
15768
+ "id": "$item.id"
15780
15769
  }
15781
- ]
15770
+ },
15771
+ "style": "tertiary",
15772
+ "iconOnly": true,
15773
+ "leadingIcon": "minus",
15774
+ "cornerRadius": 22,
15775
+ "minHeight": 44,
15776
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15782
15777
  }
15783
15778
  },
15784
15779
  "modifiers": {
15785
15780
  "width": 44,
15786
15781
  "height": 44,
15787
- "onTap": {
15788
- "actionId": "tier5.himiStoreCart.decrement",
15789
- "params": {
15790
- "id": "$item.id"
15791
- }
15792
- },
15793
- "accessibilityRole": "button",
15794
15782
  "accessibilityLabel": "Decrease quantity",
15783
+ "accessibilityRole": "button",
15795
15784
  "haptic": "light"
15796
15785
  }
15797
15786
  },
@@ -15814,39 +15803,28 @@ const SCREENS = runtimeSetting({
15814
15803
  "id": "himi_cart.qty_plus",
15815
15804
  "body": {
15816
15805
  "package": "himalaya",
15817
- "type": "ZStack",
15806
+ "type": "Button",
15818
15807
  "props": {
15819
- "alignment": "center",
15820
- "children": [
15821
- {
15822
- "id": "",
15823
- "body": {
15824
- "package": "himalaya",
15825
- "type": "Icon",
15826
- "props": {
15827
- "name": "plus",
15828
- "size": 24,
15829
- "tint": "textPrimary"
15830
- }
15831
- },
15832
- "modifiers": {
15833
- "accessibilityRole": "decorative"
15834
- }
15808
+ "title": "Increase quantity",
15809
+ "action": {
15810
+ "actionId": "tier5.himiStoreCart.increment",
15811
+ "params": {
15812
+ "id": "$item.id"
15835
15813
  }
15836
- ]
15814
+ },
15815
+ "style": "tertiary",
15816
+ "iconOnly": true,
15817
+ "leadingIcon": "plus",
15818
+ "cornerRadius": 22,
15819
+ "minHeight": 44,
15820
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15837
15821
  }
15838
15822
  },
15839
15823
  "modifiers": {
15840
15824
  "width": 44,
15841
15825
  "height": 44,
15842
- "onTap": {
15843
- "actionId": "tier5.himiStoreCart.increment",
15844
- "params": {
15845
- "id": "$item.id"
15846
- }
15847
- },
15848
- "accessibilityRole": "button",
15849
15826
  "accessibilityLabel": "Increase quantity",
15827
+ "accessibilityRole": "button",
15850
15828
  "haptic": "light"
15851
15829
  }
15852
15830
  },
@@ -15862,39 +15840,28 @@ const SCREENS = runtimeSetting({
15862
15840
  "id": "himi_cart.save_filled",
15863
15841
  "body": {
15864
15842
  "package": "himalaya",
15865
- "type": "ZStack",
15843
+ "type": "Button",
15866
15844
  "props": {
15867
- "alignment": "center",
15868
- "children": [
15869
- {
15870
- "id": "",
15871
- "body": {
15872
- "package": "himalaya",
15873
- "type": "Icon",
15874
- "props": {
15875
- "name": "heart_fill",
15876
- "size": 24,
15877
- "tint": "textPrimary"
15878
- }
15879
- },
15880
- "modifiers": {
15881
- "accessibilityRole": "decorative"
15882
- }
15845
+ "title": "Remove from wishlist",
15846
+ "action": {
15847
+ "actionId": "tier5.himiStoreCart.save",
15848
+ "params": {
15849
+ "id": "$item.id"
15883
15850
  }
15884
- ]
15851
+ },
15852
+ "style": "tertiary",
15853
+ "iconOnly": true,
15854
+ "leadingIcon": "heart_fill",
15855
+ "cornerRadius": 22,
15856
+ "minHeight": 44,
15857
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15885
15858
  }
15886
15859
  },
15887
15860
  "modifiers": {
15888
15861
  "width": 44,
15889
15862
  "height": 44,
15890
- "onTap": {
15891
- "actionId": "tier5.himiStoreCart.save",
15892
- "params": {
15893
- "id": "$item.id"
15894
- }
15895
- },
15896
- "accessibilityRole": "button",
15897
15863
  "accessibilityLabel": "Remove from wishlist",
15864
+ "accessibilityRole": "button",
15898
15865
  "haptic": "light",
15899
15866
  "hiddenPath": "$item.notSaved"
15900
15867
  }
@@ -15903,39 +15870,28 @@ const SCREENS = runtimeSetting({
15903
15870
  "id": "himi_cart.save",
15904
15871
  "body": {
15905
15872
  "package": "himalaya",
15906
- "type": "ZStack",
15873
+ "type": "Button",
15907
15874
  "props": {
15908
- "alignment": "center",
15909
- "children": [
15910
- {
15911
- "id": "",
15912
- "body": {
15913
- "package": "himalaya",
15914
- "type": "Icon",
15915
- "props": {
15916
- "name": "heart",
15917
- "size": 24,
15918
- "tint": "textPrimary"
15919
- }
15920
- },
15921
- "modifiers": {
15922
- "accessibilityRole": "decorative"
15923
- }
15875
+ "title": "Add to wishlist",
15876
+ "action": {
15877
+ "actionId": "tier5.himiStoreCart.save",
15878
+ "params": {
15879
+ "id": "$item.id"
15924
15880
  }
15925
- ]
15881
+ },
15882
+ "style": "tertiary",
15883
+ "iconOnly": true,
15884
+ "leadingIcon": "heart",
15885
+ "cornerRadius": 22,
15886
+ "minHeight": 44,
15887
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15926
15888
  }
15927
15889
  },
15928
15890
  "modifiers": {
15929
15891
  "width": 44,
15930
15892
  "height": 44,
15931
- "onTap": {
15932
- "actionId": "tier5.himiStoreCart.save",
15933
- "params": {
15934
- "id": "$item.id"
15935
- }
15936
- },
15937
- "accessibilityRole": "button",
15938
15893
  "accessibilityLabel": "Add to wishlist",
15894
+ "accessibilityRole": "button",
15939
15895
  "haptic": "light",
15940
15896
  "hiddenPath": "$item.saved"
15941
15897
  }
@@ -15944,39 +15900,28 @@ const SCREENS = runtimeSetting({
15944
15900
  "id": "himi_cart.remove",
15945
15901
  "body": {
15946
15902
  "package": "himalaya",
15947
- "type": "ZStack",
15903
+ "type": "Button",
15948
15904
  "props": {
15949
- "alignment": "center",
15950
- "children": [
15951
- {
15952
- "id": "",
15953
- "body": {
15954
- "package": "himalaya",
15955
- "type": "Icon",
15956
- "props": {
15957
- "name": "trash",
15958
- "size": 24,
15959
- "tint": "textPrimary"
15960
- }
15961
- },
15962
- "modifiers": {
15963
- "accessibilityRole": "decorative"
15964
- }
15905
+ "title": "Remove from cart",
15906
+ "action": {
15907
+ "actionId": "tier5.himiStoreCart.remove",
15908
+ "params": {
15909
+ "id": "$item.id"
15965
15910
  }
15966
- ]
15911
+ },
15912
+ "style": "tertiary",
15913
+ "iconOnly": true,
15914
+ "leadingIcon": "trash",
15915
+ "cornerRadius": 22,
15916
+ "minHeight": 44,
15917
+ "disabledPath": "$service:himiStoreCart.writeBusy"
15967
15918
  }
15968
15919
  },
15969
15920
  "modifiers": {
15970
15921
  "width": 44,
15971
15922
  "height": 44,
15972
- "onTap": {
15973
- "actionId": "tier5.himiStoreCart.remove",
15974
- "params": {
15975
- "id": "$item.id"
15976
- }
15977
- },
15978
- "accessibilityRole": "button",
15979
15923
  "accessibilityLabel": "Remove from cart",
15924
+ "accessibilityRole": "button",
15980
15925
  "haptic": "light"
15981
15926
  }
15982
15927
  }
@@ -16225,7 +16170,7 @@ const SCREENS = runtimeSetting({
16225
16170
  "action": {
16226
16171
  "actionId": "tier5.himiStoreCart.applyPromo"
16227
16172
  },
16228
- "disabledPath": "$service:himiStoreCart.promoBusy"
16173
+ "disabledPath": "$service:himiStoreCart.writeBusy"
16229
16174
  }
16230
16175
  },
16231
16176
  "modifiers": {
@@ -16282,7 +16227,8 @@ const SCREENS = runtimeSetting({
16282
16227
  "iconOnly": true,
16283
16228
  "leadingIcon": "close",
16284
16229
  "cornerRadius": 22,
16285
- "minHeight": 44
16230
+ "minHeight": 44,
16231
+ "disabledPath": "$service:himiStoreCart.writeBusy"
16286
16232
  }
16287
16233
  },
16288
16234
  "modifiers": {
@@ -16416,6 +16362,7 @@ const SCREENS = runtimeSetting({
16416
16362
  "title": "Checkout",
16417
16363
  "style": "primary",
16418
16364
  "fullWidth": true,
16365
+ "disabledPath": "$service:himiStoreCart.writeBusy",
16419
16366
  "action": {
16420
16367
  "actionId": "tier5.himiStoreCart.checkout"
16421
16368
  }