@byline/host-tanstack-start 4.19.0 → 5.0.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 (142) hide show
  1. package/dist/admin-shell/collections/edit.d.ts +1 -1
  2. package/dist/admin-shell/collections/edit.js +151 -31
  3. package/dist/admin-shell/collections/history.js +4 -2
  4. package/dist/admin-shell/collections/list.js +30 -6
  5. package/dist/admin-shell/collections/restore-version-modal.d.ts +3 -1
  6. package/dist/admin-shell/collections/restore-version-modal.js +9 -4
  7. package/dist/admin-shell/collections/tree-list.js +16 -6
  8. package/dist/admin-shell/collections/version-history.d.ts +2 -1
  9. package/dist/admin-shell/collections/version-history.js +11 -2
  10. package/dist/admin-shell/document-mutation-notice.d.ts +6 -0
  11. package/dist/admin-shell/document-mutation-notice.js +53 -0
  12. package/dist/admin-shell/document-mutation-state.d.ts +22 -0
  13. package/dist/admin-shell/document-mutation-state.js +71 -0
  14. package/dist/admin-shell/document-mutation-state.test.d.ts +1 -0
  15. package/dist/admin-shell/scheduled-publications/list.js +15 -5
  16. package/dist/admin-shell/singletons/history.js +4 -2
  17. package/dist/admin-shell/singletons/view.d.ts +1 -1
  18. package/dist/admin-shell/singletons/view.js +98 -19
  19. package/dist/integrations/byline-field-services.js +2 -9
  20. package/dist/routes/create-collection-edit-route.js +1 -1
  21. package/dist/routes/create-collection-list-route.d.ts +0 -7
  22. package/dist/routes/create-collection-list-route.js +7 -9
  23. package/dist/routes/create-singleton-route.d.ts +2 -0
  24. package/dist/routes/create-singleton-route.js +4 -3
  25. package/dist/server-fns/collections/copy-to-locale.d.ts +2 -7
  26. package/dist/server-fns/collections/copy-to-locale.js +4 -2
  27. package/dist/server-fns/collections/create.d.ts +1 -7
  28. package/dist/server-fns/collections/create.js +5 -3
  29. package/dist/server-fns/collections/delete-locale.d.ts +2 -7
  30. package/dist/server-fns/collections/delete-locale.js +4 -2
  31. package/dist/server-fns/collections/delete-outcome.d.ts +7 -3
  32. package/dist/server-fns/collections/delete-outcome.js +4 -0
  33. package/dist/server-fns/collections/delete.d.ts +2 -7
  34. package/dist/server-fns/collections/delete.js +5 -3
  35. package/dist/server-fns/collections/duplicate.d.ts +2 -7
  36. package/dist/server-fns/collections/duplicate.js +5 -3
  37. package/dist/server-fns/collections/get.d.ts +1 -7
  38. package/dist/server-fns/collections/get.js +5 -4
  39. package/dist/server-fns/collections/list.d.ts +0 -7
  40. package/dist/server-fns/collections/list.js +11 -3
  41. package/dist/server-fns/collections/reorder.d.ts +7 -8
  42. package/dist/server-fns/collections/reorder.js +20 -76
  43. package/dist/server-fns/collections/restore-version.d.ts +3 -7
  44. package/dist/server-fns/collections/restore-version.js +5 -2
  45. package/dist/server-fns/collections/save-outcome.d.ts +1 -0
  46. package/dist/server-fns/collections/save-outcome.js +1 -0
  47. package/dist/server-fns/collections/scheduled-publication.d.ts +49 -9
  48. package/dist/server-fns/collections/scheduled-publication.js +31 -21
  49. package/dist/server-fns/collections/status.d.ts +8 -7
  50. package/dist/server-fns/collections/status.js +13 -5
  51. package/dist/server-fns/collections/tree-mutation.d.ts +7 -6
  52. package/dist/server-fns/collections/tree-mutation.js +17 -5
  53. package/dist/server-fns/collections/tree.d.ts +14 -8
  54. package/dist/server-fns/collections/tree.js +21 -17
  55. package/dist/server-fns/collections/update.d.ts +12 -11
  56. package/dist/server-fns/collections/update.js +34 -20
  57. package/dist/server-fns/collections/upload.d.ts +1 -0
  58. package/dist/server-fns/collections/upload.js +3 -2
  59. package/dist/server-fns/document-mutation-errors.d.ts +5 -0
  60. package/dist/server-fns/document-mutation-errors.js +50 -0
  61. package/dist/server-fns/editable-revision.d.ts +9 -0
  62. package/dist/server-fns/editable-revision.js +11 -0
  63. package/dist/server-fns/singleton-document-read.d.ts +1 -1
  64. package/dist/server-fns/singleton-document-read.js +7 -3
  65. package/dist/server-fns/singletons/cancel-scheduled-publish.d.ts +6 -8
  66. package/dist/server-fns/singletons/cancel-scheduled-publish.js +4 -1
  67. package/dist/server-fns/singletons/change-status.d.ts +2 -7
  68. package/dist/server-fns/singletons/change-status.js +4 -1
  69. package/dist/server-fns/singletons/confirm-scheduled-publish.d.ts +5 -8
  70. package/dist/server-fns/singletons/confirm-scheduled-publish.js +4 -2
  71. package/dist/server-fns/singletons/copy-to-locale.d.ts +2 -7
  72. package/dist/server-fns/singletons/copy-to-locale.js +4 -2
  73. package/dist/server-fns/singletons/get.d.ts +4 -4
  74. package/dist/server-fns/singletons/restore-version.d.ts +2 -7
  75. package/dist/server-fns/singletons/restore-version.js +6 -3
  76. package/dist/server-fns/singletons/schedule-publish.d.ts +5 -8
  77. package/dist/server-fns/singletons/schedule-publish.js +4 -2
  78. package/dist/server-fns/singletons/unpublish.d.ts +2 -7
  79. package/dist/server-fns/singletons/unpublish.js +4 -1
  80. package/dist/server-fns/singletons/update.d.ts +4 -11
  81. package/dist/server-fns/singletons/update.js +5 -4
  82. package/dist/server-fns/structural-receipt.d.ts +4 -0
  83. package/dist/server-fns/structural-receipt.js +31 -0
  84. package/package.json +11 -11
  85. package/src/admin-shell/collections/edit.tsx +173 -54
  86. package/src/admin-shell/collections/history.tsx +11 -2
  87. package/src/admin-shell/collections/list.tsx +22 -6
  88. package/src/admin-shell/collections/restore-version-modal.tsx +12 -3
  89. package/src/admin-shell/collections/save-navigation.test.tsx +156 -8
  90. package/src/admin-shell/collections/tree-list.tsx +16 -6
  91. package/src/admin-shell/collections/version-history.tsx +13 -3
  92. package/src/admin-shell/document-mutation-notice.tsx +45 -0
  93. package/src/admin-shell/document-mutation-state.test.tsx +146 -0
  94. package/src/admin-shell/document-mutation-state.ts +95 -0
  95. package/src/admin-shell/scheduled-publications/list.tsx +16 -6
  96. package/src/admin-shell/singletons/history.test.tsx +3 -1
  97. package/src/admin-shell/singletons/history.tsx +10 -2
  98. package/src/admin-shell/singletons/view.test.tsx +59 -18
  99. package/src/admin-shell/singletons/view.tsx +110 -29
  100. package/src/integrations/byline-field-services.ts +2 -3
  101. package/src/routes/create-collection-edit-route.tsx +1 -0
  102. package/src/routes/create-collection-list-route.tsx +13 -2
  103. package/src/routes/create-singleton-route.test.node.ts +10 -2
  104. package/src/routes/create-singleton-route.tsx +6 -4
  105. package/src/server-fns/collections/copy-to-locale.ts +41 -36
  106. package/src/server-fns/collections/create.ts +54 -50
  107. package/src/server-fns/collections/delete-locale.ts +41 -32
  108. package/src/server-fns/collections/delete-outcome.test.node.ts +16 -0
  109. package/src/server-fns/collections/delete-outcome.ts +27 -16
  110. package/src/server-fns/collections/delete.ts +44 -38
  111. package/src/server-fns/collections/duplicate.ts +38 -32
  112. package/src/server-fns/collections/get.ts +8 -4
  113. package/src/server-fns/collections/list.ts +8 -3
  114. package/src/server-fns/collections/reorder.ts +32 -124
  115. package/src/server-fns/collections/restore-version.ts +47 -39
  116. package/src/server-fns/collections/revision-input.test.node.ts +108 -0
  117. package/src/server-fns/collections/save-outcome.test.node.ts +3 -0
  118. package/src/server-fns/collections/save-outcome.ts +2 -0
  119. package/src/server-fns/collections/scheduled-publication.ts +138 -111
  120. package/src/server-fns/collections/status.ts +86 -64
  121. package/src/server-fns/collections/tree-mutation.test.node.ts +87 -29
  122. package/src/server-fns/collections/tree-mutation.ts +28 -11
  123. package/src/server-fns/collections/tree.ts +150 -122
  124. package/src/server-fns/collections/update.ts +110 -86
  125. package/src/server-fns/collections/upload-singleton.test.node.ts +3 -7
  126. package/src/server-fns/collections/upload.ts +81 -77
  127. package/src/server-fns/document-mutation-errors.ts +67 -0
  128. package/src/server-fns/document-mutation-transport.test.node.ts +211 -0
  129. package/src/server-fns/editable-revision.test.node.ts +23 -0
  130. package/src/server-fns/editable-revision.ts +18 -0
  131. package/src/server-fns/singleton-document-read.ts +7 -3
  132. package/src/server-fns/singletons/cancel-scheduled-publish.ts +10 -3
  133. package/src/server-fns/singletons/change-status.ts +10 -3
  134. package/src/server-fns/singletons/confirm-scheduled-publish.ts +12 -6
  135. package/src/server-fns/singletons/copy-to-locale.ts +20 -15
  136. package/src/server-fns/singletons/get.ts +3 -3
  137. package/src/server-fns/singletons/restore-version.ts +17 -12
  138. package/src/server-fns/singletons/schedule-publish.ts +18 -7
  139. package/src/server-fns/singletons/singletons.test.node.ts +63 -25
  140. package/src/server-fns/singletons/unpublish.ts +10 -3
  141. package/src/server-fns/singletons/update.ts +21 -16
  142. package/src/server-fns/structural-receipt.ts +41 -0
@@ -1,4 +1,6 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { DocumentMutationNotice } from "../document-mutation-notice.js";
3
+ import { useDocumentMutationState } from "../document-mutation-state.js";
2
4
  import { useEffect, useMemo, useRef, useState } from "react";
3
5
  import { useRouter } from "@tanstack/react-router";
4
6
  import { StatusBadge, renderFormatted } from "@byline/admin/react";
@@ -55,6 +57,7 @@ function SortableTreeRow({ id, dragging, disabled, dragHandleLabel, children })
55
57
  });
56
58
  }
57
59
  const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection, collectionLabels, onMove, listActions })=>{
60
+ const mutation = useDocumentMutationState(void 0);
58
61
  const { t } = useTranslation('byline-admin');
59
62
  const router = useRouter();
60
63
  const toastManager = useToastManager();
@@ -66,12 +69,13 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
66
69
  const [moveController] = useState(()=>createAdminTreeMoveController());
67
70
  const preserveOptimisticForRows = useRef(null);
68
71
  useEffect(()=>{
69
- if (!shouldSyncAdminTreeRows(rows, preserveOptimisticForRows.current, isMoving)) return;
72
+ if (mutation.blocked || !shouldSyncAdminTreeRows(rows, preserveOptimisticForRows.current, isMoving)) return;
70
73
  preserveOptimisticForRows.current = null;
71
74
  setLocalPlaced(rows.filter((r)=>!r.unplaced));
72
75
  }, [
73
76
  rows,
74
- isMoving
77
+ isMoving,
78
+ mutation.blocked
75
79
  ]);
76
80
  const [activeId, setActiveId] = useState(null);
77
81
  const [overId, setOverId] = useState(null);
@@ -98,12 +102,12 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
98
102
  setOffsetLeft(0);
99
103
  };
100
104
  const handleDragStart = ({ active })=>{
101
- if (!moveController.isMoving()) setActiveId(String(active.id));
105
+ if (!mutation.blocked && !moveController.isMoving()) setActiveId(String(active.id));
102
106
  };
103
107
  const handleDragMove = ({ delta })=>setOffsetLeft(delta.x);
104
108
  const handleDragOver = ({ over })=>setOverId(over ? String(over.id) : null);
105
109
  const handleDragEnd = async ({ active, over })=>{
106
- if (moveController.isMoving()) return void resetDnd();
110
+ if (mutation.blocked || moveController.isMoving()) return void resetDnd();
107
111
  const proj = null != over ? getTreeProjection(localPlaced, String(active.id), String(over.id), offsetLeft, INDENT) : null;
108
112
  resetDnd();
109
113
  if (!onMove || null == proj) return;
@@ -125,9 +129,11 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
125
129
  setIsMoving(moveController.isMoving());
126
130
  return;
127
131
  }
132
+ if ('receipt' in outcome) mutation.reportStructure(outcome.receipt);
133
+ const handled = 'error' in outcome ? mutation.report(outcome.error) : null;
128
134
  const effects = getAdminTreeMoveUiEffects(outcome);
129
135
  if (effects.rollback) setLocalPlaced(current);
130
- if (null != effects.structuralFailure) {
136
+ if (!handled && null != effects.structuralFailure) {
131
137
  const conflict = 'conflict' === effects.structuralFailure;
132
138
  toastManager.add({
133
139
  title: t(conflict ? 'collections.list.treeConflictToast' : 'collections.list.reorderFailedToast'),
@@ -202,6 +208,10 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
202
208
  return /*#__PURE__*/ jsx(Section, {
203
209
  children: /*#__PURE__*/ jsxs(Container, {
204
210
  children: [
211
+ /*#__PURE__*/ jsx(DocumentMutationNotice, {
212
+ issue: mutation.issue,
213
+ scheduleNotice: mutation.scheduleNotice
214
+ }),
205
215
  /*#__PURE__*/ jsxs("div", {
206
216
  className: clsx('byline-coll-list-head', tree_list_module.head),
207
217
  children: [
@@ -271,7 +281,7 @@ const TreeListView = ({ rows, columns, workflowStatuses, useAsTitle, collection,
271
281
  return dndEnabled ? /*#__PURE__*/ jsx(SortableTreeRow, {
272
282
  id: row.id,
273
283
  dragging: row.id === activeId,
274
- disabled: isMoving,
284
+ disabled: mutation.blocked || isMoving,
275
285
  dragHandleLabel: t('collections.list.dragHandleAriaLabel'),
276
286
  children: renderCells(row, depth)
277
287
  }, row.id) : /*#__PURE__*/ jsx(Table.Row, {
@@ -40,6 +40,7 @@ interface VersionHistoryRowContext {
40
40
  actionLabel: string;
41
41
  restoreStatusLabel: string;
42
42
  openCompare: () => void;
43
+ mutationsBlocked: boolean;
43
44
  openRestore: () => void;
44
45
  }
45
46
  /**
@@ -66,7 +67,7 @@ export declare function VersionHistoryCore({ data, resourceDefinition, resourceP
66
67
  rowPresentation: VersionHistoryRowPresentation;
67
68
  loadHistoricalVersion: HistoricalVersionLoader;
68
69
  onPageSizeChange: (pageSize: number) => void;
69
- restoreVersion: (versionId: string) => Promise<unknown>;
70
+ restoreVersion: (versionId: string, expectedRevision: number) => Promise<unknown>;
70
71
  onRestoreComplete: () => void | Promise<void>;
71
72
  }): import("react").JSX.Element;
72
73
  export {};
@@ -1,4 +1,6 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { DocumentMutationNotice } from "../document-mutation-notice.js";
3
+ import { useDocumentMutationState } from "../document-mutation-state.js";
2
4
  import { Fragment as external_react_Fragment, Suspense, lazy, useState } from "react";
3
5
  import { getDefaultStatus, getWorkflowStatuses } from "@byline/core";
4
6
  import { useTranslation } from "@byline/i18n/react";
@@ -33,6 +35,7 @@ function padRows(value) {
33
35
  }, `empty-row-${index}`));
34
36
  }
35
37
  function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId, currentDocument, locale, rowPresentation, loadHistoricalVersion, onPageSizeChange, restoreVersion, onRestoreComplete }) {
38
+ const mutation = useDocumentMutationState(currentDocument);
36
39
  const { t } = useTranslation('byline-admin');
37
40
  const [selectedVersion, setSelectedVersion] = useState(null);
38
41
  const [restoreTarget, setRestoreTarget] = useState(null);
@@ -41,6 +44,9 @@ function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId
41
44
  const restoreStatusLabel = getWorkflowStatuses(resourceDefinition).find((status)=>status.name === restoreStatus)?.label ?? restoreStatus;
42
45
  return /*#__PURE__*/ jsxs(Fragment, {
43
46
  children: [
47
+ /*#__PURE__*/ jsx(DocumentMutationNotice, {
48
+ issue: mutation.issue
49
+ }),
44
50
  /*#__PURE__*/ jsx("div", {
45
51
  className: clsx('byline-coll-history-options', history_module.options),
46
52
  children: /*#__PURE__*/ jsx(RouterPager, {
@@ -85,7 +91,7 @@ function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId
85
91
  });
86
92
  };
87
93
  const openRestore = ()=>{
88
- if (null == versionId || versionId === currentVersionId) return;
94
+ if (mutation.blocked || null == versionId || versionId === currentVersionId) return;
89
95
  setRestoreTarget({
90
96
  versionId,
91
97
  label: versionLabel,
@@ -119,6 +125,7 @@ function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId
119
125
  actionLabel,
120
126
  restoreStatusLabel,
121
127
  openCompare,
128
+ mutationsBlocked: mutation.blocked,
122
129
  openRestore
123
130
  })
124
131
  ]
@@ -243,11 +250,13 @@ function VersionHistoryCore({ data, resourceDefinition, resourcePath, documentId
243
250
  ]
244
251
  }),
245
252
  restoreTarget ? /*#__PURE__*/ jsx(RestoreVersionModal, {
253
+ disabled: mutation.blocked,
254
+ onMutationError: mutation.report,
246
255
  versionLabel: restoreTarget.label,
247
256
  versionNumber: restoreTarget.versionNumber,
248
257
  restoreStatusLabel: restoreStatusLabel,
249
258
  onClose: ()=>setRestoreTarget(null),
250
- restoreVersion: ()=>restoreVersion(restoreTarget.versionId),
259
+ restoreVersion: ()=>restoreVersion(restoreTarget.versionId, mutation.expectedRevision()),
251
260
  onRestoreComplete: onRestoreComplete
252
261
  }) : null
253
262
  ]
@@ -0,0 +1,6 @@
1
+ import type { DocumentMutationIssue } from './document-mutation-state.js';
2
+ /** Persistent recovery for list, history and schedule views without local form drafts. */
3
+ export declare function DocumentMutationNotice({ issue, scheduleNotice, }: {
4
+ issue: DocumentMutationIssue | null;
5
+ scheduleNotice?: boolean;
6
+ }): import("react").JSX.Element | null;
@@ -0,0 +1,53 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { useEffect, useRef } from "react";
3
+ import { useTranslation } from "@byline/i18n/react";
4
+ import { Alert, Button } from "@byline/ui/react";
5
+ import { getAdminRoutePath } from "../routes/admin-path.js";
6
+ function DocumentMutationNotice({ issue, scheduleNotice = false }) {
7
+ const { t } = useTranslation('byline-admin');
8
+ const warning = useRef(null);
9
+ useEffect(()=>{
10
+ if (issue) warning.current?.focus();
11
+ }, [
12
+ issue
13
+ ]);
14
+ if (!issue && !scheduleNotice) return null;
15
+ return /*#__PURE__*/ jsxs("div", {
16
+ ref: warning,
17
+ tabIndex: -1,
18
+ role: "alert",
19
+ "aria-live": "assertive",
20
+ children: [
21
+ issue && /*#__PURE__*/ jsxs(Alert, {
22
+ close: false,
23
+ intent: "warning",
24
+ title: t(`documentConcurrency.${issue}Title`),
25
+ children: [
26
+ /*#__PURE__*/ jsx("p", {
27
+ children: t(`documentConcurrency.${issue}`)
28
+ }),
29
+ 'committed' !== issue && /*#__PURE__*/ jsx(Button, {
30
+ type: "button",
31
+ onClick: ()=>window.location.reload(),
32
+ children: t('documentConcurrency.reloadAction')
33
+ })
34
+ ]
35
+ }),
36
+ scheduleNotice && /*#__PURE__*/ jsxs(Alert, {
37
+ close: false,
38
+ intent: "warning",
39
+ title: t('documentConcurrency.schedulesTitle'),
40
+ children: [
41
+ /*#__PURE__*/ jsx("p", {
42
+ children: t('documentConcurrency.schedules')
43
+ }),
44
+ /*#__PURE__*/ jsx("a", {
45
+ href: getAdminRoutePath('scheduled-publications'),
46
+ children: t('documentConcurrency.reviewSchedules')
47
+ })
48
+ ]
49
+ })
50
+ ]
51
+ });
52
+ }
53
+ export { DocumentMutationNotice };
@@ -0,0 +1,22 @@
1
+ export type DocumentMutationIssue = 'stale' | 'reload' | 'lock' | 'unavailable' | 'committed';
2
+ /** A rejected observation stays frozen until an explicit editor reload. */
3
+ export declare function useDocumentMutationState<T extends {
4
+ id?: unknown;
5
+ versionId?: unknown;
6
+ revision?: unknown;
7
+ }>(document: T | null | undefined): {
8
+ issue: DocumentMutationIssue | null;
9
+ blocked: boolean;
10
+ scheduleNotice: boolean;
11
+ document: T | null | undefined;
12
+ revision: number | undefined;
13
+ expectedRevision: () => number;
14
+ assertWritable: () => void;
15
+ adopt: (receipt: {
16
+ documentId: string;
17
+ revision: number;
18
+ scheduledPublicationsNeedReconfirmation?: boolean;
19
+ }) => void;
20
+ report: (error: unknown) => 'blocked' | 'committed' | null;
21
+ reportStructure: (result: unknown) => void;
22
+ };
@@ -0,0 +1,71 @@
1
+ import { useRef, useState } from "react";
2
+ import { ErrorCodes, getDocumentRevisionValidationDetails, getDocumentStaleDetails, getLockConflictDetails, parseDocumentRevision } from "@byline/core";
3
+ import { getDocumentHookCommittedDetails } from "@byline/core/services";
4
+ import { committedTreeReceipt, structuralReceipt } from "../server-fns/structural-receipt.js";
5
+ function documentMutationIssue(error) {
6
+ if (getDocumentStaleDetails(error)) return 'stale';
7
+ if (getDocumentRevisionValidationDetails(error)) return 'reload';
8
+ if (getLockConflictDetails(error)) return 'lock';
9
+ if (getDocumentHookCommittedDetails(error) || committedTreeReceipt(error)) return 'committed';
10
+ if ('object' == typeof error && null !== error && Reflect.get(error, 'code') === ErrorCodes.NOT_FOUND) return 'unavailable';
11
+ return null;
12
+ }
13
+ function useDocumentMutationState(document) {
14
+ const [issue, setIssue] = useState(null);
15
+ const [scheduleNotice, setScheduleNotice] = useState(false);
16
+ const [, setReceiptRevision] = useState();
17
+ const blockedError = useRef(void 0);
18
+ const observation = useRef({
19
+ identity: document,
20
+ revision: document?.revision
21
+ });
22
+ if (observation.current.identity !== document && void 0 === blockedError.current && document?.revision === observation.current.revision) observation.current = {
23
+ identity: document,
24
+ revision: document?.revision
25
+ };
26
+ const assertWritable = ()=>{
27
+ if (void 0 !== blockedError.current) throw blockedError.current;
28
+ };
29
+ const expectedRevision = ()=>{
30
+ assertWritable();
31
+ return parseDocumentRevision(observation.current.revision);
32
+ };
33
+ const adopt = (receipt)=>{
34
+ if (void 0 !== blockedError.current) return;
35
+ if (document?.id != null && receipt.documentId !== document.id) return;
36
+ observation.current.revision = parseDocumentRevision(receipt.revision);
37
+ setReceiptRevision(receipt.revision);
38
+ if (receipt.scheduledPublicationsNeedReconfirmation) setScheduleNotice(true);
39
+ };
40
+ const reportStructure = (result)=>{
41
+ const receipt = structuralReceipt(result);
42
+ if (receipt) adopt(receipt);
43
+ };
44
+ const report = (error)=>{
45
+ const kind = documentMutationIssue(error);
46
+ if (null === kind) return null;
47
+ if ('committed' === kind) {
48
+ if (void 0 !== blockedError.current) return 'blocked';
49
+ const details = getDocumentHookCommittedDetails(error) ?? committedTreeReceipt(error);
50
+ if (details) adopt(details);
51
+ setIssue(kind);
52
+ return 'committed';
53
+ }
54
+ blockedError.current = error;
55
+ setIssue(kind);
56
+ return 'blocked';
57
+ };
58
+ return {
59
+ issue,
60
+ blocked: null !== issue && 'committed' !== issue,
61
+ scheduleNotice,
62
+ document: observation.current.identity,
63
+ revision: 'number' == typeof observation.current.revision ? observation.current.revision : void 0,
64
+ expectedRevision,
65
+ assertWritable,
66
+ adopt,
67
+ report,
68
+ reportStructure
69
+ };
70
+ }
71
+ export { useDocumentMutationState };
@@ -0,0 +1 @@
1
+ export {};
@@ -1,5 +1,7 @@
1
1
  "use client";
2
2
  import { jsx, jsxs } from "react/jsx-runtime";
3
+ import { DocumentMutationNotice } from "../document-mutation-notice.js";
4
+ import { useDocumentMutationState } from "../document-mutation-state.js";
3
5
  import { useMemo, useState } from "react";
4
6
  import { useRouterState } from "@tanstack/react-router";
5
7
  import { deriveScheduledPublicationState } from "@byline/admin/react";
@@ -35,6 +37,7 @@ function ScheduledPublicationsView({ data }) {
35
37
  select: (state)=>state.location
36
38
  });
37
39
  const search = location.search;
40
+ const mutation = useDocumentMutationState(void 0);
38
41
  const [cancelling, setCancelling] = useState(null);
39
42
  const applySearch = (patch)=>{
40
43
  navigate({
@@ -46,13 +49,16 @@ function ScheduledPublicationsView({ data }) {
46
49
  }
47
50
  });
48
51
  };
49
- const cancel = async (collection, documentId)=>{
52
+ const cancel = async (collection, documentId, expectedRevision)=>{
53
+ if (mutation.blocked || cancelling) return;
50
54
  setCancelling(documentId);
51
55
  try {
56
+ mutation.assertWritable();
52
57
  const result = await cancelCollectionDocumentScheduledPublish({
53
58
  data: {
54
59
  collection,
55
- id: documentId
60
+ id: documentId,
61
+ expectedRevision
56
62
  }
57
63
  });
58
64
  const cancelled = 'cancelled' === result.status;
@@ -71,9 +77,10 @@ function ScheduledPublicationsView({ data }) {
71
77
  search: location.search
72
78
  });
73
79
  } catch (error) {
80
+ if (mutation.report(error)) return;
74
81
  toastManager.add({
75
82
  title: t('scheduledPublication.toast.failedTitle'),
76
- description: error.message,
83
+ description: t('documentConcurrency.failed'),
77
84
  data: {
78
85
  intent: 'danger',
79
86
  iconType: 'danger',
@@ -89,6 +96,9 @@ function ScheduledPublicationsView({ data }) {
89
96
  return /*#__PURE__*/ jsx(Section, {
90
97
  children: /*#__PURE__*/ jsxs(Container, {
91
98
  children: [
99
+ /*#__PURE__*/ jsx(DocumentMutationNotice, {
100
+ issue: mutation.issue
101
+ }),
92
102
  /*#__PURE__*/ jsxs("div", {
93
103
  className: clsx('byline-scheduled-list-head', list_module.head),
94
104
  children: [
@@ -264,9 +274,9 @@ function ScheduledPublicationsView({ data }) {
264
274
  size: "xs",
265
275
  type: "button",
266
276
  intent: "danger",
267
- disabled: cancelling === schedule.documentId,
277
+ disabled: mutation.blocked || null !== cancelling,
268
278
  "aria-label": t('scheduledPublication.actions.cancel'),
269
- onClick: ()=>cancel(schedule.collectionPath, schedule.documentId),
279
+ onClick: ()=>cancel(schedule.collectionPath, schedule.documentId, schedule.revision),
270
280
  children: t('common.actions.cancel')
271
281
  })
272
282
  })
@@ -53,7 +53,7 @@ function SingletonHistoryView({ singletonDefinition, adminConfig, data, currentD
53
53
  ]
54
54
  }),
55
55
  auditColSpan: 4,
56
- renderCells: ({ document, versionId, currentVersionId, actorLabel, restoreStatusLabel, openRestore })=>/*#__PURE__*/ jsxs(Fragment, {
56
+ renderCells: ({ document, versionId, currentVersionId, actorLabel, restoreStatusLabel, mutationsBlocked, openRestore })=>/*#__PURE__*/ jsxs(Fragment, {
57
57
  children: [
58
58
  /*#__PURE__*/ jsx(Table.Cell, {
59
59
  className: "byline-singleton-history-status-cell",
@@ -77,6 +77,7 @@ function SingletonHistoryView({ singletonDefinition, adminConfig, data, currentD
77
77
  variant: "outlined",
78
78
  size: "xs",
79
79
  intent: "noeffect",
80
+ disabled: mutationsBlocked,
80
81
  onClick: openRestore,
81
82
  className: clsx('byline-coll-history-restore-button', history_module.restoreButton),
82
83
  title: t('collections.history.restoreButtonTitle', {
@@ -143,8 +144,9 @@ function SingletonHistoryView({ singletonDefinition, adminConfig, data, currentD
143
144
  return historical;
144
145
  },
145
146
  onPageSizeChange: handlePageSizeChange,
146
- restoreVersion: (versionId)=>restoreSingletonVersion({
147
+ restoreVersion: (versionId, expectedRevision)=>restoreSingletonVersion({
147
148
  data: {
149
+ expectedRevision,
148
150
  singleton,
149
151
  versionId
150
152
  }
@@ -5,7 +5,7 @@ type SingletonDocument = Record<string, any> & {
5
5
  status: string;
6
6
  fields: Record<string, any>;
7
7
  };
8
- export declare function SingletonView({ singletonDefinition, adminConfig, document, initialData, locale, contentLocales, defaultContentLocale, }: {
8
+ export declare function SingletonView({ singletonDefinition, adminConfig, document: loadedDocument, initialData, locale, contentLocales, defaultContentLocale, }: {
9
9
  singletonDefinition: SingletonDefinition;
10
10
  adminConfig?: SingletonAdminConfig;
11
11
  document: SingletonDocument | null;