@wix/himalaya-cli 0.1211.0 → 0.1213.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 (41) hide show
  1. package/dist/cli.mjs +20 -21
  2. package/dist/preview-assets.generated.bin +0 -0
  3. package/package.json +2 -2
  4. package/templates/beauty-salon-owner/dev/index.ts +218 -0
  5. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  6. package/templates/beauty-salon-owner/tier5-src/himi-owner-lite-product/index.ts +61 -16
  7. package/templates/community-club-owner/dev/index.ts +327 -0
  8. package/templates/community-club-owner/tier5-src/himi-owner-lite-event/index.ts +59 -14
  9. package/templates/community-club-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  10. package/templates/community-club-owner/tier5-src/himi-owner-lite-product/index.ts +61 -16
  11. package/templates/events-meetups-owner/dev/index.ts +218 -0
  12. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-event/index.ts +59 -14
  13. package/templates/events-meetups-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  14. package/templates/gym-fitness-owner/dev/index.ts +327 -0
  15. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-event/index.ts +59 -14
  16. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  17. package/templates/gym-fitness-owner/tier5-src/himi-owner-lite-product/index.ts +61 -16
  18. package/templates/nonprofit-cause-owner/dev/index.ts +218 -0
  19. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-event/index.ts +59 -14
  20. package/templates/nonprofit-cause-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  21. package/templates/owner-lite/dev/index.ts +327 -0
  22. package/templates/owner-lite/tier5-src/himi-owner-lite-event/index.ts +59 -14
  23. package/templates/owner-lite/tier5-src/himi-owner-lite-post/index.ts +63 -16
  24. package/templates/owner-lite/tier5-src/himi-owner-lite-product/index.ts +61 -16
  25. package/templates/podcast-creator-owner/dev/index.ts +327 -0
  26. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-event/index.ts +59 -14
  27. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  28. package/templates/podcast-creator-owner/tier5-src/himi-owner-lite-product/index.ts +61 -16
  29. package/templates/portfolio-creative-owner/dev/index.ts +218 -0
  30. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  31. package/templates/portfolio-creative-owner/tier5-src/himi-owner-lite-product/index.ts +61 -16
  32. package/templates/restaurant-owner/dev/index.ts +327 -0
  33. package/templates/restaurant-owner/tier5-src/himi-owner-lite-event/index.ts +59 -14
  34. package/templates/restaurant-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  35. package/templates/restaurant-owner/tier5-src/himi-owner-lite-product/index.ts +61 -16
  36. package/templates/retail-store-owner/dev/index.ts +218 -0
  37. package/templates/retail-store-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
  38. package/templates/retail-store-owner/tier5-src/himi-owner-lite-product/index.ts +61 -16
  39. package/templates/yoga-studio-owner/dev/index.ts +218 -0
  40. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-event/index.ts +59 -14
  41. package/templates/yoga-studio-owner/tier5-src/himi-owner-lite-post/index.ts +63 -16
@@ -80442,6 +80442,115 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
80442
80442
  }
80443
80443
  }
80444
80444
  },
80445
+ {
80446
+ "id": "product_delete_confirmation",
80447
+ "body": {
80448
+ "package": "himalaya",
80449
+ "type": "VStack",
80450
+ "props": {
80451
+ "spacing": 10,
80452
+ "alignment": "leading",
80453
+ "children": [
80454
+ {
80455
+ "id": "product_delete_confirmation.title",
80456
+ "body": {
80457
+ "package": "himalaya",
80458
+ "type": "Text",
80459
+ "props": {
80460
+ "text": "Delete this pro shop item?",
80461
+ "style": "bodyEmphasis"
80462
+ }
80463
+ }
80464
+ },
80465
+ {
80466
+ "id": "product_delete_confirmation.record",
80467
+ "body": {
80468
+ "package": "himalaya",
80469
+ "type": "Text",
80470
+ "props": {
80471
+ "text": "$service:himiOwnerLiteProduct.title",
80472
+ "style": "body",
80473
+ "color": "textSecondary",
80474
+ "maxLines": 2
80475
+ }
80476
+ }
80477
+ },
80478
+ {
80479
+ "id": "",
80480
+ "body": {
80481
+ "package": "himalaya",
80482
+ "type": "Text",
80483
+ "props": {
80484
+ "text": "This cannot be undone.",
80485
+ "style": "footnote",
80486
+ "color": "textSecondary"
80487
+ }
80488
+ }
80489
+ },
80490
+ {
80491
+ "id": "",
80492
+ "body": {
80493
+ "package": "himalaya",
80494
+ "type": "HStack",
80495
+ "props": {
80496
+ "spacing": 8,
80497
+ "children": [
80498
+ {
80499
+ "id": "product_delete_cancel",
80500
+ "body": {
80501
+ "package": "himalaya",
80502
+ "type": "Button",
80503
+ "props": {
80504
+ "title": "Cancel",
80505
+ "style": "secondary",
80506
+ "action": {
80507
+ "actionId": "tier5.himiOwnerLiteProduct.cancelDelete"
80508
+ }
80509
+ }
80510
+ }
80511
+ },
80512
+ {
80513
+ "id": "product_delete_confirm",
80514
+ "body": {
80515
+ "package": "himalaya",
80516
+ "type": "Button",
80517
+ "props": {
80518
+ "title": "Delete",
80519
+ "style": "destructive",
80520
+ "action": {
80521
+ "actionId": "tier5.himiOwnerLiteProduct.confirmDelete"
80522
+ }
80523
+ }
80524
+ }
80525
+ }
80526
+ ]
80527
+ }
80528
+ }
80529
+ }
80530
+ ]
80531
+ }
80532
+ },
80533
+ "modifiers": {
80534
+ "background": {
80535
+ "token": "surfaceElevated"
80536
+ },
80537
+ "border": {
80538
+ "width": 1,
80539
+ "color": {
80540
+ "token": "danger"
80541
+ }
80542
+ },
80543
+ "cornerRadius": 12,
80544
+ "padding": {
80545
+ "leading": 14,
80546
+ "trailing": 14,
80547
+ "top": 14,
80548
+ "bottom": 14
80549
+ },
80550
+ "fillMaxWidth": true,
80551
+ "hiddenPath": "$service:himiOwnerLiteProduct.noDeleteConfirmation"
80552
+ }
80553
+ },
80445
80554
  {
80446
80555
  "id": "",
80447
80556
  "body": {
@@ -82033,6 +82142,115 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
82033
82142
  }
82034
82143
  }
82035
82144
  },
82145
+ {
82146
+ "id": "event_delete_confirmation",
82147
+ "body": {
82148
+ "package": "himalaya",
82149
+ "type": "VStack",
82150
+ "props": {
82151
+ "spacing": 10,
82152
+ "alignment": "leading",
82153
+ "children": [
82154
+ {
82155
+ "id": "event_delete_confirmation.title",
82156
+ "body": {
82157
+ "package": "himalaya",
82158
+ "type": "Text",
82159
+ "props": {
82160
+ "text": "Delete this challenge?",
82161
+ "style": "bodyEmphasis"
82162
+ }
82163
+ }
82164
+ },
82165
+ {
82166
+ "id": "event_delete_confirmation.record",
82167
+ "body": {
82168
+ "package": "himalaya",
82169
+ "type": "Text",
82170
+ "props": {
82171
+ "text": "$service:himiOwnerLiteEvent.title",
82172
+ "style": "body",
82173
+ "color": "textSecondary",
82174
+ "maxLines": 2
82175
+ }
82176
+ }
82177
+ },
82178
+ {
82179
+ "id": "",
82180
+ "body": {
82181
+ "package": "himalaya",
82182
+ "type": "Text",
82183
+ "props": {
82184
+ "text": "This cannot be undone.",
82185
+ "style": "footnote",
82186
+ "color": "textSecondary"
82187
+ }
82188
+ }
82189
+ },
82190
+ {
82191
+ "id": "",
82192
+ "body": {
82193
+ "package": "himalaya",
82194
+ "type": "HStack",
82195
+ "props": {
82196
+ "spacing": 8,
82197
+ "children": [
82198
+ {
82199
+ "id": "event_delete_cancel",
82200
+ "body": {
82201
+ "package": "himalaya",
82202
+ "type": "Button",
82203
+ "props": {
82204
+ "title": "Cancel",
82205
+ "style": "secondary",
82206
+ "action": {
82207
+ "actionId": "tier5.himiOwnerLiteEvent.cancelDelete"
82208
+ }
82209
+ }
82210
+ }
82211
+ },
82212
+ {
82213
+ "id": "event_delete_confirm",
82214
+ "body": {
82215
+ "package": "himalaya",
82216
+ "type": "Button",
82217
+ "props": {
82218
+ "title": "Delete",
82219
+ "style": "destructive",
82220
+ "action": {
82221
+ "actionId": "tier5.himiOwnerLiteEvent.confirmDelete"
82222
+ }
82223
+ }
82224
+ }
82225
+ }
82226
+ ]
82227
+ }
82228
+ }
82229
+ }
82230
+ ]
82231
+ }
82232
+ },
82233
+ "modifiers": {
82234
+ "background": {
82235
+ "token": "surfaceElevated"
82236
+ },
82237
+ "border": {
82238
+ "width": 1,
82239
+ "color": {
82240
+ "token": "danger"
82241
+ }
82242
+ },
82243
+ "cornerRadius": 12,
82244
+ "padding": {
82245
+ "leading": 14,
82246
+ "trailing": 14,
82247
+ "top": 14,
82248
+ "bottom": 14
82249
+ },
82250
+ "fillMaxWidth": true,
82251
+ "hiddenPath": "$service:himiOwnerLiteEvent.noDeleteConfirmation"
82252
+ }
82253
+ },
82036
82254
  {
82037
82255
  "id": "",
82038
82256
  "body": {
@@ -82894,6 +83112,115 @@ const TEMPLATED: { pattern: string; display: string; liveSeed?: LiveSeed; static
82894
83112
  }
82895
83113
  }
82896
83114
  },
83115
+ {
83116
+ "id": "post_delete_confirmation",
83117
+ "body": {
83118
+ "package": "himalaya",
83119
+ "type": "VStack",
83120
+ "props": {
83121
+ "spacing": 10,
83122
+ "alignment": "leading",
83123
+ "children": [
83124
+ {
83125
+ "id": "post_delete_confirmation.title",
83126
+ "body": {
83127
+ "package": "himalaya",
83128
+ "type": "Text",
83129
+ "props": {
83130
+ "text": "Delete this post?",
83131
+ "style": "bodyEmphasis"
83132
+ }
83133
+ }
83134
+ },
83135
+ {
83136
+ "id": "post_delete_confirmation.record",
83137
+ "body": {
83138
+ "package": "himalaya",
83139
+ "type": "Text",
83140
+ "props": {
83141
+ "text": "$service:himiOwnerLitePost.title",
83142
+ "style": "body",
83143
+ "color": "textSecondary",
83144
+ "maxLines": 2
83145
+ }
83146
+ }
83147
+ },
83148
+ {
83149
+ "id": "",
83150
+ "body": {
83151
+ "package": "himalaya",
83152
+ "type": "Text",
83153
+ "props": {
83154
+ "text": "This cannot be undone.",
83155
+ "style": "footnote",
83156
+ "color": "textSecondary"
83157
+ }
83158
+ }
83159
+ },
83160
+ {
83161
+ "id": "",
83162
+ "body": {
83163
+ "package": "himalaya",
83164
+ "type": "HStack",
83165
+ "props": {
83166
+ "spacing": 8,
83167
+ "children": [
83168
+ {
83169
+ "id": "post_delete_cancel",
83170
+ "body": {
83171
+ "package": "himalaya",
83172
+ "type": "Button",
83173
+ "props": {
83174
+ "title": "Cancel",
83175
+ "style": "secondary",
83176
+ "action": {
83177
+ "actionId": "tier5.himiOwnerLitePost.cancelDelete"
83178
+ }
83179
+ }
83180
+ }
83181
+ },
83182
+ {
83183
+ "id": "post_delete_confirm",
83184
+ "body": {
83185
+ "package": "himalaya",
83186
+ "type": "Button",
83187
+ "props": {
83188
+ "title": "Delete",
83189
+ "style": "destructive",
83190
+ "action": {
83191
+ "actionId": "tier5.himiOwnerLitePost.confirmDelete"
83192
+ }
83193
+ }
83194
+ }
83195
+ }
83196
+ ]
83197
+ }
83198
+ }
83199
+ }
83200
+ ]
83201
+ }
83202
+ },
83203
+ "modifiers": {
83204
+ "background": {
83205
+ "token": "surfaceElevated"
83206
+ },
83207
+ "border": {
83208
+ "width": 1,
83209
+ "color": {
83210
+ "token": "danger"
83211
+ }
83212
+ },
83213
+ "cornerRadius": 12,
83214
+ "padding": {
83215
+ "leading": 14,
83216
+ "trailing": 14,
83217
+ "top": 14,
83218
+ "bottom": 14
83219
+ },
83220
+ "fillMaxWidth": true,
83221
+ "hiddenPath": "$service:himiOwnerLitePost.noDeleteConfirmation"
83222
+ }
83223
+ },
82897
83224
  {
82898
83225
  "id": "",
82899
83226
  "body": {
@@ -62,6 +62,9 @@ interface EventState {
62
62
  /** Non-empty when showing owner-preview-demo sample data (web preview, no owner bridge). */
63
63
  demoNote: string;
64
64
  notDemo: boolean;
65
+ /** #3158 — Delete ARMS a confirmation; only confirmDelete reaches the API. */
66
+ deletePending: boolean;
67
+ noDeleteConfirmation: boolean;
65
68
  }
66
69
 
67
70
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -139,6 +142,9 @@ function shape(s: Partial<EventState>): EventState {
139
142
  noCover: !image,
140
143
  demoNote: s.demoNote ?? "",
141
144
  notDemo: !s.demoNote,
145
+ // Only a loaded entity can be pending deletion; every other action disarms it (`disarm`).
146
+ deletePending: !!s.deletePending && hasEntity,
147
+ noDeleteConfirmation: !(s.deletePending && hasEntity),
142
148
  };
143
149
  }
144
150
 
@@ -224,15 +230,32 @@ async function refresh(prev: EventState, id: string): Promise<EventState> {
224
230
  });
225
231
  }
226
232
 
233
+ /** #3158 — a confirmed delete in flight. Synchronous and module-level on purpose: two
234
+ * confirmDelete dispatches (a double tap) both start from a state that says "armed", and only a
235
+ * flag set BEFORE the first await stops the second from issuing its own DELETE — whose late 404
236
+ * would otherwise land after, and overwrite, the first one's "deleted". */
237
+ let deleteInFlight = false;
238
+
239
+ /** #3158 — the confirmation describes THIS visit. Never take it from a persisted snapshot, or a
240
+ * reopened record comes back with "Delete this …?" already armed. */
241
+ const DELETE_RUN_KEYS = ["deletePending", "noDeleteConfirmation"] as const;
242
+
243
+ /** #3158 — clear an armed delete confirmation. */
244
+ const disarm = <T extends { deletePending: boolean }>(s: T): T => ({ ...s, deletePending: false });
245
+
227
246
  const svc = defineService<EventState, {
228
247
  load: (s: EventState, arg?: unknown) => Promise<EventState>;
229
248
  retry: (s: EventState) => Promise<EventState>;
230
249
  delete: (s: EventState) => Promise<EventState>;
250
+ cancelDelete: (s: EventState) => Promise<EventState>;
251
+ confirmDelete: (s: EventState) => Promise<EventState | undefined>;
231
252
  openLink: (s: EventState, arg?: unknown) => Promise<EventState>;
232
253
  }>("himiOwnerLiteEvent", {
233
254
  state: shape({ status: "loading" }),
255
+ notHydrated: [...DELETE_RUN_KEYS],
234
256
  actions: {
235
- load: async (s, arg) => {
257
+ load: async (s0, arg) => {
258
+ const s = disarm(s0);
236
259
  stashCopy(arg); // absorb the descriptor's onAppear copy-pack param
237
260
  const id = idFrom(arg, s.id) || inputId();
238
261
  if (!id) return shape({ ...s, status: "error", error: copyParam("ownerEvent.errorNoId", "no event id") });
@@ -257,7 +280,8 @@ const svc = defineService<EventState, {
257
280
  return shape({ ...s, id, status: "error", error: ownerLoadError(e) });
258
281
  }
259
282
  },
260
- retry: async (s) => {
283
+ retry: async (s0) => {
284
+ const s = disarm(s0);
261
285
  if (!s.id) return shape({ ...s, status: "error", error: copyParam("ownerEvent.errorNoId", "no event id") });
262
286
  if (isOwnerPreviewDemo()) {
263
287
  const demo = demoDetail(s, s.id);
@@ -279,20 +303,35 @@ const svc = defineService<EventState, {
279
303
  return shape({ ...s, status: "error", error: ownerLoadError(e) });
280
304
  }
281
305
  },
282
- delete: async (s) => {
283
- if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
284
- if (!s.id) return shape({ ...s, actionMessage: copyParam("ownerEvent.noIdMessage", "No event id.") });
285
- // Optimistic local delete — the list drops the row immediately.
286
- await cacheDrop(s.id);
306
+ // #3158 — destructive, so two steps. `delete` (the Manage card's Delete button)
307
+ // only ARMS the inline confirmation, which names the record and says it cannot
308
+ // be undone; `cancelDelete` disarms it; only `confirmDelete` deletes. Anything
309
+ // that dispatches `delete` directly therefore still asks first.
310
+ delete: async (s) => shape({ ...s, deletePending: true, actionMessage: "" }),
311
+ cancelDelete: async (s) => shape({ ...s, deletePending: false }),
312
+ confirmDelete: async (s0) => {
313
+ // Nothing armed (a stray dispatch, a rehydrated snapshot) or a delete already running
314
+ // (a second tap): ignore it — `undefined` leaves the state exactly as it is.
315
+ if (!s0.deletePending || deleteInFlight) return undefined;
316
+ deleteInFlight = true;
287
317
  try {
288
- await deleteEvent(s.id);
289
- return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerEvent.deletedEmpty", "Event deleted."), actionMessage: "" });
290
- } catch (e) {
291
- if (shouldQueueOwnerWrite(e)) {
292
- await queueWrite("tier5.himiOwnerLiteEvent.delete", { id: s.id });
293
- return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerEvent.deletedEmpty", "Event deleted."), actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
318
+ const s = disarm(s0);
319
+ if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
320
+ if (!s.id) return shape({ ...s, actionMessage: copyParam("ownerEvent.noIdMessage", "No event id.") });
321
+ // Optimistic local delete — the list drops the row immediately.
322
+ await cacheDrop(s.id);
323
+ try {
324
+ await deleteEvent(s.id);
325
+ return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerEvent.deletedEmpty", "Event deleted."), actionMessage: "" });
326
+ } catch (e) {
327
+ if (shouldQueueOwnerWrite(e)) {
328
+ await queueWrite("tier5.himiOwnerLiteEvent.delete", { id: s.id });
329
+ return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerEvent.deletedEmpty", "Event deleted."), actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
330
+ }
331
+ return shape({ ...s, actionMessage: ownerLoadError(e) });
294
332
  }
295
- return shape({ ...s, actionMessage: ownerLoadError(e) });
333
+ } finally {
334
+ deleteInFlight = false;
296
335
  }
297
336
  },
298
337
  // [rico] #3499 — a link tapped in the RichContent description.
@@ -303,4 +342,10 @@ const svc = defineService<EventState, {
303
342
  },
304
343
  });
305
344
 
345
+ // The same reset on a runtime from before `notHydrated` (an older binary on a new bundle).
346
+ if ((globalThis as { himalaya?: { hydrationOptOut?: unknown } }).himalaya?.hydrationOptOut !== true) {
347
+ const booted = svc.getState?.() as unknown as Record<string, unknown> | undefined;
348
+ if (booted && typeof booted === "object") Object.assign(booted, { deletePending: false, noDeleteConfirmation: true });
349
+ }
350
+
306
351
  export { svc };
@@ -80,6 +80,9 @@ interface PostState {
80
80
  /** Non-empty when showing owner-preview-demo sample data (web preview, no owner bridge). */
81
81
  demoNote: string;
82
82
  notDemo: boolean;
83
+ /** #3158 — Delete ARMS a confirmation; only confirmDelete reaches the API. */
84
+ deletePending: boolean;
85
+ noDeleteConfirmation: boolean;
83
86
  }
84
87
 
85
88
  /* eslint-disable @typescript-eslint/no-explicit-any */
@@ -159,6 +162,9 @@ function shape(s: Partial<PostState>): PostState {
159
162
  hideUnpublish: !published || status !== "ready",
160
163
  demoNote: s.demoNote ?? "",
161
164
  notDemo: !s.demoNote,
165
+ // Only a loaded entity can be pending deletion; every other action disarms it (`disarm`).
166
+ deletePending: !!s.deletePending && hasEntity,
167
+ noDeleteConfirmation: !(s.deletePending && hasEntity),
162
168
  };
163
169
  }
164
170
 
@@ -276,18 +282,35 @@ async function refresh(prev: PostState, id: string, actionMessage = ""): Promise
276
282
  });
277
283
  }
278
284
 
285
+ /** #3158 — a confirmed delete in flight. Synchronous and module-level on purpose: two
286
+ * confirmDelete dispatches (a double tap) both start from a state that says "armed", and only a
287
+ * flag set BEFORE the first await stops the second from issuing its own DELETE — whose late 404
288
+ * would otherwise land after, and overwrite, the first one's "deleted". */
289
+ let deleteInFlight = false;
290
+
291
+ /** #3158 — the confirmation describes THIS visit. Never take it from a persisted snapshot, or a
292
+ * reopened record comes back with "Delete this …?" already armed. */
293
+ const DELETE_RUN_KEYS = ["deletePending", "noDeleteConfirmation"] as const;
294
+
295
+ /** #3158 — clear an armed delete confirmation. */
296
+ const disarm = <T extends { deletePending: boolean }>(s: T): T => ({ ...s, deletePending: false });
297
+
279
298
  const svc = defineService<PostState, {
280
299
  load: (s: PostState, arg?: unknown) => Promise<PostState>;
281
300
  retry: (s: PostState) => Promise<PostState>;
282
301
  publish: (s: PostState) => Promise<PostState>;
283
302
  unpublish: (s: PostState) => Promise<PostState>;
284
303
  delete: (s: PostState) => Promise<PostState>;
304
+ cancelDelete: (s: PostState) => Promise<PostState>;
305
+ confirmDelete: (s: PostState) => Promise<PostState | undefined>;
285
306
  /** [rico] #3370 — RichContent's on_link: `{ url }` of the tapped link. */
286
307
  openLink: (s: PostState, arg?: unknown) => Promise<PostState>;
287
308
  }>("himiOwnerLitePost", {
288
309
  state: shape({ status: "loading" }),
310
+ notHydrated: [...DELETE_RUN_KEYS],
289
311
  actions: {
290
- load: async (s, arg) => {
312
+ load: async (s0, arg) => {
313
+ const s = disarm(s0);
291
314
  stashCopy(arg); // absorb the descriptor's onAppear copy-pack param
292
315
  const id = idFrom(arg, s.id) || inputId();
293
316
  if (!id) return shape({ ...s, status: "error", error: copyParam("ownerPost.errorNoId", "no post id") });
@@ -312,7 +335,8 @@ const svc = defineService<PostState, {
312
335
  return shape({ ...s, id, status: "error", error: ownerLoadError(e) });
313
336
  }
314
337
  },
315
- retry: async (s) => {
338
+ retry: async (s0) => {
339
+ const s = disarm(s0);
316
340
  if (!s.id) return shape({ ...s, status: "error", error: copyParam("ownerPost.errorNoId", "no post id") });
317
341
  if (isOwnerPreviewDemo()) {
318
342
  const demo = demoDetail(s, s.id);
@@ -334,7 +358,8 @@ const svc = defineService<PostState, {
334
358
  return shape({ ...s, status: "error", error: ownerLoadError(e) });
335
359
  }
336
360
  },
337
- publish: async (s) => {
361
+ publish: async (s0) => {
362
+ const s = disarm(s0);
338
363
  if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
339
364
  if (!s.id) return shape({ ...s, actionMessage: copyParam("ownerPost.noIdMessage", "No post id.") });
340
365
  if (s.postStatusRaw === "PUBLISHED") return shape({ ...s, actionMessage: copyParam("ownerPost.alreadyPublished", "Post is already published.") });
@@ -352,7 +377,8 @@ const svc = defineService<PostState, {
352
377
  return shape({ ...s, actionMessage: ownerLoadError(e) });
353
378
  }
354
379
  },
355
- unpublish: async (s) => {
380
+ unpublish: async (s0) => {
381
+ const s = disarm(s0);
356
382
  if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
357
383
  if (!s.id) return shape({ ...s, actionMessage: copyParam("ownerPost.noIdMessage", "No post id.") });
358
384
  if (s.postStatusRaw !== "PUBLISHED" && s.postStatusRaw !== "SCHEDULED") {
@@ -372,20 +398,35 @@ const svc = defineService<PostState, {
372
398
  return shape({ ...s, actionMessage: ownerLoadError(e) });
373
399
  }
374
400
  },
375
- delete: async (s) => {
376
- if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
377
- if (!s.id) return shape({ ...s, actionMessage: copyParam("ownerPost.noIdMessage", "No post id.") });
378
- // Optimistic local delete — the list drops the row immediately.
379
- await cacheDrop(s.id);
401
+ // #3158 — destructive, so two steps. `delete` (the Manage card's Delete button)
402
+ // only ARMS the inline confirmation, which names the record and says it cannot
403
+ // be undone; `cancelDelete` disarms it; only `confirmDelete` deletes. Anything
404
+ // that dispatches `delete` directly therefore still asks first.
405
+ delete: async (s) => shape({ ...s, deletePending: true, actionMessage: "" }),
406
+ cancelDelete: async (s) => shape({ ...s, deletePending: false }),
407
+ confirmDelete: async (s0) => {
408
+ // Nothing armed (a stray dispatch, a rehydrated snapshot) or a delete already running
409
+ // (a second tap): ignore it — `undefined` leaves the state exactly as it is.
410
+ if (!s0.deletePending || deleteInFlight) return undefined;
411
+ deleteInFlight = true;
380
412
  try {
381
- await deleteDraftPost(s.id);
382
- return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerPost.deletedEmpty", "Post deleted."), actionMessage: "" });
383
- } catch (e) {
384
- if (shouldQueueOwnerWrite(e)) {
385
- await queueWrite("tier5.himiOwnerLitePost.delete", { id: s.id });
386
- return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerPost.deletedEmpty", "Post deleted."), actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
413
+ const s = disarm(s0);
414
+ if (isOwnerPreviewDemo() || s.demoNote) return shape({ ...s, actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
415
+ if (!s.id) return shape({ ...s, actionMessage: copyParam("ownerPost.noIdMessage", "No post id.") });
416
+ // Optimistic local delete — the list drops the row immediately.
417
+ await cacheDrop(s.id);
418
+ try {
419
+ await deleteDraftPost(s.id);
420
+ return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerPost.deletedEmpty", "Post deleted."), actionMessage: "" });
421
+ } catch (e) {
422
+ if (shouldQueueOwnerWrite(e)) {
423
+ await queueWrite("tier5.himiOwnerLitePost.delete", { id: s.id });
424
+ return shape({ ...s, status: "deleted", title: "", emptyText: copyParam("ownerPost.deletedEmpty", "Post deleted."), actionMessage: copyParam("ownerCommon.demoSaveNote", DEMO_SAVE_NOTE) });
425
+ }
426
+ return shape({ ...s, actionMessage: ownerLoadError(e) });
387
427
  }
388
- return shape({ ...s, actionMessage: ownerLoadError(e) });
428
+ } finally {
429
+ deleteInFlight = false;
389
430
  }
390
431
  },
391
432
  // Only http(s), mailto and tel links reach open_url; a refused link, or one nothing opened, says so, and
@@ -397,4 +438,10 @@ const svc = defineService<PostState, {
397
438
  },
398
439
  });
399
440
 
441
+ // The same reset on a runtime from before `notHydrated` (an older binary on a new bundle).
442
+ if ((globalThis as { himalaya?: { hydrationOptOut?: unknown } }).himalaya?.hydrationOptOut !== true) {
443
+ const booted = svc.getState?.() as unknown as Record<string, unknown> | undefined;
444
+ if (booted && typeof booted === "object") Object.assign(booted, { deletePending: false, noDeleteConfirmation: true });
445
+ }
446
+
400
447
  export { svc };