@alpaca-editor/core 1.0.4085 → 1.0.4088

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 (146) hide show
  1. package/dist/components/ui/card.d.ts +1 -1
  2. package/dist/components/ui/paste-button.d.ts +14 -0
  3. package/dist/components/ui/paste-button.js +114 -0
  4. package/dist/components/ui/paste-button.js.map +1 -0
  5. package/dist/config/config.js +60 -3
  6. package/dist/config/config.js.map +1 -1
  7. package/dist/config/types.d.ts +25 -0
  8. package/dist/editor/ContentTree.js +43 -21
  9. package/dist/editor/ContentTree.js.map +1 -1
  10. package/dist/editor/FieldListField.js +62 -2
  11. package/dist/editor/FieldListField.js.map +1 -1
  12. package/dist/editor/ai/AgentTerminal.d.ts +3 -1
  13. package/dist/editor/ai/AgentTerminal.js +96 -74
  14. package/dist/editor/ai/AgentTerminal.js.map +1 -1
  15. package/dist/editor/ai/Agents.js +46 -2
  16. package/dist/editor/ai/Agents.js.map +1 -1
  17. package/dist/editor/ai/AiResponseMessage.js +171 -75
  18. package/dist/editor/ai/AiResponseMessage.js.map +1 -1
  19. package/dist/editor/ai/AiTerminal.js +27 -14
  20. package/dist/editor/ai/AiTerminal.js.map +1 -1
  21. package/dist/editor/client/EditorShell.js +110 -17
  22. package/dist/editor/client/EditorShell.js.map +1 -1
  23. package/dist/editor/client/editContext.d.ts +4 -0
  24. package/dist/editor/client/editContext.js.map +1 -1
  25. package/dist/editor/client/hooks/useSocketMessageHandler.d.ts +1 -0
  26. package/dist/editor/client/hooks/useSocketMessageHandler.js +54 -20
  27. package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
  28. package/dist/editor/client/hooks/useWorkbox.d.ts +1 -1
  29. package/dist/editor/client/hooks/useWorkbox.js +4 -4
  30. package/dist/editor/client/hooks/useWorkbox.js.map +1 -1
  31. package/dist/editor/client/itemsRepository.d.ts +13 -1
  32. package/dist/editor/client/itemsRepository.js +34 -21
  33. package/dist/editor/client/itemsRepository.js.map +1 -1
  34. package/dist/editor/client/pageModelBuilder.js +1 -1
  35. package/dist/editor/client/pageModelBuilder.js.map +1 -1
  36. package/dist/editor/control-center/Setup.d.ts +1 -0
  37. package/dist/editor/control-center/Setup.js +18 -0
  38. package/dist/editor/control-center/Setup.js.map +1 -0
  39. package/dist/editor/control-center/setup-steps/AiSetupStep.d.ts +2 -0
  40. package/dist/editor/control-center/setup-steps/AiSetupStep.js +287 -0
  41. package/dist/editor/control-center/setup-steps/AiSetupStep.js.map +1 -0
  42. package/dist/editor/control-center/setup-steps/DbSetupStep.d.ts +2 -0
  43. package/dist/editor/control-center/setup-steps/DbSetupStep.js +46 -0
  44. package/dist/editor/control-center/setup-steps/DbSetupStep.js.map +1 -0
  45. package/dist/editor/control-center/setup-steps/IndexSetupStep.d.ts +2 -0
  46. package/dist/editor/control-center/setup-steps/IndexSetupStep.js +34 -0
  47. package/dist/editor/control-center/setup-steps/IndexSetupStep.js.map +1 -0
  48. package/dist/editor/control-center/setup-steps/SettingsSetupStep.d.ts +2 -0
  49. package/dist/editor/control-center/setup-steps/SettingsSetupStep.js +104 -0
  50. package/dist/editor/control-center/setup-steps/SettingsSetupStep.js.map +1 -0
  51. package/dist/editor/field-types/InternalLinkFieldEditor.js +3 -1
  52. package/dist/editor/field-types/InternalLinkFieldEditor.js.map +1 -1
  53. package/dist/editor/field-types/RichTextEditorComponent.js +1 -1
  54. package/dist/editor/field-types/RichTextEditorComponent.js.map +1 -1
  55. package/dist/editor/field-types/richtext/components/ReactSlate.js +2 -2
  56. package/dist/editor/field-types/richtext/components/ReactSlate.js.map +1 -1
  57. package/dist/editor/field-types/richtext/utils/profileServiceCache.d.ts +1 -1
  58. package/dist/editor/field-types/richtext/utils/profileServiceCache.js +16 -14
  59. package/dist/editor/field-types/richtext/utils/profileServiceCache.js.map +1 -1
  60. package/dist/editor/menubar/toolbar-sections/CompareControls.js +1 -1
  61. package/dist/editor/menubar/toolbar-sections/CompareControls.js.map +1 -1
  62. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -1
  63. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  64. package/dist/editor/menubar/toolbar-sections/ViewportControls.js +1 -1
  65. package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
  66. package/dist/editor/page-editor-chrome/InlineEditor.js +25 -6
  67. package/dist/editor/page-editor-chrome/InlineEditor.js.map +1 -1
  68. package/dist/editor/page-viewer/EditorForm.js +9 -2
  69. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  70. package/dist/editor/page-viewer/PageViewerFrame.js +5 -0
  71. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  72. package/dist/editor/pageModel.d.ts +1 -0
  73. package/dist/editor/reviews/Comment.js +1 -1
  74. package/dist/editor/reviews/Comment.js.map +1 -1
  75. package/dist/editor/reviews/CommentDisplayPopover.js +3 -24
  76. package/dist/editor/reviews/CommentDisplayPopover.js.map +1 -1
  77. package/dist/editor/reviews/CommentPopover.js +3 -23
  78. package/dist/editor/reviews/CommentPopover.js.map +1 -1
  79. package/dist/editor/reviews/CommentView.js +2 -1
  80. package/dist/editor/reviews/CommentView.js.map +1 -1
  81. package/dist/editor/reviews/Comments.js +88 -37
  82. package/dist/editor/reviews/Comments.js.map +1 -1
  83. package/dist/editor/reviews/commentAi.js +3 -0
  84. package/dist/editor/reviews/commentAi.js.map +1 -1
  85. package/dist/editor/sidebar/Debug.js +1 -5
  86. package/dist/editor/sidebar/Debug.js.map +1 -1
  87. package/dist/editor/sidebar/ViewSelector.js +72 -6
  88. package/dist/editor/sidebar/ViewSelector.js.map +1 -1
  89. package/dist/editor/ui/Icons.d.ts +5 -0
  90. package/dist/editor/ui/Icons.js +14 -0
  91. package/dist/editor/ui/Icons.js.map +1 -1
  92. package/dist/editor/utils.d.ts +5 -0
  93. package/dist/editor/utils.js +29 -0
  94. package/dist/editor/utils.js.map +1 -1
  95. package/dist/revision.d.ts +2 -2
  96. package/dist/revision.js +2 -2
  97. package/dist/splash-screen/SplashScreen.js +2 -2
  98. package/dist/splash-screen/SplashScreen.js.map +1 -1
  99. package/dist/styles.css +3 -0
  100. package/dist/types.d.ts +2 -0
  101. package/package.json +1 -1
  102. package/src/components/ui/card.tsx +1 -1
  103. package/src/components/ui/paste-button.tsx +163 -0
  104. package/src/config/config.tsx +68 -2
  105. package/src/config/types.ts +26 -0
  106. package/src/editor/ContentTree.tsx +48 -23
  107. package/src/editor/FieldListField.tsx +75 -2
  108. package/src/editor/ai/AgentTerminal.tsx +118 -71
  109. package/src/editor/ai/Agents.tsx +52 -1
  110. package/src/editor/ai/AiResponseMessage.tsx +234 -78
  111. package/src/editor/ai/AiTerminal.tsx +30 -14
  112. package/src/editor/client/EditorShell.tsx +128 -25
  113. package/src/editor/client/editContext.ts +1 -0
  114. package/src/editor/client/hooks/useSocketMessageHandler.ts +70 -21
  115. package/src/editor/client/hooks/useWorkbox.ts +4 -4
  116. package/src/editor/client/itemsRepository.ts +56 -25
  117. package/src/editor/client/pageModelBuilder.ts +1 -1
  118. package/src/editor/control-center/Setup.tsx +26 -0
  119. package/src/editor/control-center/setup-steps/AiSetupStep.tsx +462 -0
  120. package/src/editor/control-center/setup-steps/DbSetupStep.tsx +84 -0
  121. package/src/editor/control-center/setup-steps/IndexSetupStep.tsx +56 -0
  122. package/src/editor/control-center/setup-steps/SettingsSetupStep.tsx +176 -0
  123. package/src/editor/field-types/InternalLinkFieldEditor.tsx +3 -1
  124. package/src/editor/field-types/RichTextEditorComponent.tsx +0 -1
  125. package/src/editor/field-types/richtext/components/ReactSlate.tsx +14 -6
  126. package/src/editor/field-types/richtext/utils/profileServiceCache.ts +42 -32
  127. package/src/editor/menubar/toolbar-sections/CompareControls.tsx +1 -0
  128. package/src/editor/menubar/toolbar-sections/EditControls.tsx +1 -0
  129. package/src/editor/menubar/toolbar-sections/ViewportControls.tsx +1 -0
  130. package/src/editor/page-editor-chrome/InlineEditor.tsx +29 -6
  131. package/src/editor/page-viewer/EditorForm.tsx +13 -2
  132. package/src/editor/page-viewer/PageViewerFrame.tsx +4 -0
  133. package/src/editor/pageModel.ts +1 -0
  134. package/src/editor/reviews/Comment.tsx +1 -1
  135. package/src/editor/reviews/CommentDisplayPopover.tsx +2 -22
  136. package/src/editor/reviews/CommentPopover.tsx +3 -24
  137. package/src/editor/reviews/CommentView.tsx +3 -2
  138. package/src/editor/reviews/Comments.tsx +162 -35
  139. package/src/editor/reviews/commentAi.ts +5 -0
  140. package/src/editor/sidebar/Debug.tsx +1 -5
  141. package/src/editor/sidebar/ViewSelector.tsx +144 -28
  142. package/src/editor/ui/Icons.tsx +55 -0
  143. package/src/editor/utils.ts +33 -0
  144. package/src/revision.ts +2 -2
  145. package/src/splash-screen/SplashScreen.tsx +5 -6
  146. package/src/types.ts +3 -0
@@ -122,7 +122,10 @@ import { Toaster } from "../../components/ui/sonner";
122
122
  import { Tour } from "../../tour/Tour";
123
123
  import { usePageViewContext } from "../page-viewer/pageViewContext";
124
124
 
125
- import { getComments } from "../services/reviewsService";
125
+ import {
126
+ getComments,
127
+ getAvailableCommentTags,
128
+ } from "../services/reviewsService";
126
129
  import { useReviews } from "../reviews/useReviews";
127
130
  import uuid from "react-uuid";
128
131
  import { flushSync } from "react-dom";
@@ -256,6 +259,10 @@ export function EditorShell({
256
259
  }
257
260
  });
258
261
 
262
+ // Block editing until the server acknowledges our client-info for the current item
263
+ const [isClientInfoAcknowledged, setIsClientInfoAcknowledged] =
264
+ useState(false);
265
+
259
266
  if (typeof window !== "undefined")
260
267
  sessionStorage?.setItem("sessionId", sessionId);
261
268
 
@@ -305,6 +312,9 @@ export function EditorShell({
305
312
  const [suggestedEdits, setSuggestedEdits] = useState<SuggestedEdit[]>([]);
306
313
  const [showSuggestedEdits, setShowSuggestedEdits] = useState(false);
307
314
  const [showSuggestedEditsDiff, setShowSuggestedEditsDiff] = useState(false);
315
+ const [availableCommentTags, setAvailableCommentTags] = useState<
316
+ { label: string; color: string }[]
317
+ >([]);
308
318
 
309
319
  const [showComments, setShowComments] = useState<boolean>(() => {
310
320
  const savedShowComments =
@@ -439,6 +449,34 @@ export function EditorShell({
439
449
  configuration,
440
450
  });
441
451
 
452
+ // Track which items are rendered/used by the current page for targeted refresh
453
+ const pageItemsSetRef = useRef<Set<string>>(new Set());
454
+ const makeItemKey = useCallback(
455
+ (d: ItemDescriptor) => `${d.id}-${d.language}-${d.version}`.toLowerCase(),
456
+ [],
457
+ );
458
+ useEffect(() => {
459
+ const newSet = new Set<string>();
460
+ const collect = (component?: Component) => {
461
+ if (!component) return;
462
+ if (component.datasourceItem)
463
+ newSet.add(makeItemKey(component.datasourceItem));
464
+ component.items?.forEach((i) => newSet.add(makeItemKey(i)));
465
+ component.placeholders?.forEach((ph) => ph.components?.forEach(collect));
466
+ };
467
+ if (pageViewContext?.page) {
468
+ // Include the page item itself
469
+ const pageDescriptor = pageViewContext.page.item.descriptor;
470
+ newSet.add(makeItemKey(pageDescriptor));
471
+ collect(pageViewContext.page.rootComponent);
472
+ }
473
+ pageItemsSetRef.current = newSet;
474
+ }, [pageViewContext.page?.editRevision]);
475
+ const isItemUsedInCurrentPage = useCallback(
476
+ (item: ItemDescriptor) => pageItemsSetRef.current.has(makeItemKey(item)),
477
+ [makeItemKey],
478
+ );
479
+
442
480
  const socketMessageListeners = useRef<Set<(data: any) => void>>(new Set());
443
481
 
444
482
  const addSocketMessageListener = useCallback(
@@ -450,7 +488,7 @@ export function EditorShell({
450
488
  );
451
489
 
452
490
  const reviews = useReviews({
453
- currentItemDescriptor: contentEditorItem?.descriptor,
491
+ currentItemDescriptor,
454
492
  addSocketMessageListener: addSocketMessageListener,
455
493
  });
456
494
 
@@ -488,7 +526,7 @@ export function EditorShell({
488
526
  }
489
527
  }, [currentView]);
490
528
 
491
- const sendClientInfo = async () => {
529
+ const sendClientInfoImmediate = useCallback(async () => {
492
530
  const clientInfoMessage = {
493
531
  type: "client-info",
494
532
  sessionId: sessionId,
@@ -502,15 +540,18 @@ export function EditorShell({
502
540
  : null,
503
541
  };
504
542
 
505
- // const socket = (globalThis as any).editorSocket;
506
-
507
- //if (socket.readyState !== WebSocket.OPEN) {
508
543
  const url = "/alpaca/editor/client";
544
+
509
545
  await post(url, clientInfoMessage);
510
- //} else {
511
- // socket.send(JSON.stringify(clientInfoMessage));
512
- //}
513
- };
546
+ }, [sessionId]);
547
+
548
+ const debouncedSendClientInfo = useDebouncedCallback(() => {
549
+ void sendClientInfoImmediate();
550
+ }, 100);
551
+
552
+ const sendClientInfo = useCallback(() => {
553
+ debouncedSendClientInfo();
554
+ }, [debouncedSendClientInfo]);
514
555
 
515
556
  const startTour = useCallback(() => {
516
557
  setIsTourActive(true);
@@ -797,9 +838,26 @@ export function EditorShell({
797
838
  }, [currentItemDescriptor, pageViewContext.page?.item.id]);
798
839
 
799
840
  useEffect(() => {
841
+ // When the item changes, reset acknowledgment and re-send client info
842
+ setIsClientInfoAcknowledged(false);
800
843
  sendClientInfo();
801
844
  }, [currentItemDescriptor]);
802
845
 
846
+ // Detect acknowledgment via presence of our session with matching item in active-sessions
847
+ useEffect(() => {
848
+ if (!currentItemDescriptor) return;
849
+ try {
850
+ const me = activeSessions.find((s) => s.sessionId === sessionId);
851
+ const item = me?.item;
852
+ const acknowledged =
853
+ !!item &&
854
+ item.id === currentItemDescriptor.id &&
855
+ item.language === currentItemDescriptor.language &&
856
+ item.version === currentItemDescriptor.version;
857
+ if (acknowledged) setIsClientInfoAcknowledged(true);
858
+ } catch {}
859
+ }, [activeSessions, currentItemDescriptor, sessionId]);
860
+
803
861
  const loadComments = useCallback(async () => {
804
862
  if (!currentItemDescriptor) return;
805
863
  const result = await getComments(
@@ -914,6 +972,31 @@ export function EditorShell({
914
972
  loadSuggestedEdits();
915
973
  }, [currentItemDescriptor]);
916
974
 
975
+ // Load available comment tags once per current item descriptor
976
+ useEffect(() => {
977
+ let cancelled = false;
978
+ const loadTags = async () => {
979
+ try {
980
+ if (!currentItemDescriptor) {
981
+ if (!cancelled) setAvailableCommentTags([]);
982
+ return;
983
+ }
984
+ const tags = await getAvailableCommentTags(currentItemDescriptor);
985
+ if (!cancelled) setAvailableCommentTags(tags || []);
986
+ } catch {
987
+ if (!cancelled) setAvailableCommentTags([]);
988
+ }
989
+ };
990
+ loadTags();
991
+ return () => {
992
+ cancelled = true;
993
+ };
994
+ }, [
995
+ currentItemDescriptor?.id,
996
+ currentItemDescriptor?.language,
997
+ currentItemDescriptor?.version,
998
+ ]);
999
+
917
1000
  // Load favorites on mount
918
1001
  useEffect(() => {
919
1002
  loadFavorites();
@@ -1278,7 +1361,7 @@ export function EditorShell({
1278
1361
 
1279
1362
  const { workboxItems } = useWorkbox({
1280
1363
  page,
1281
- contentEditorItem,
1364
+ currentItemDescriptor,
1282
1365
  validate,
1283
1366
  });
1284
1367
 
@@ -1301,6 +1384,7 @@ export function EditorShell({
1301
1384
  loadHistory,
1302
1385
  addRecentEdit,
1303
1386
  socketMessageListeners,
1387
+ isItemUsedInCurrentPage,
1304
1388
  });
1305
1389
 
1306
1390
  const { socketRef: socketInstanceRef } = useEditorWebSocket({
@@ -1444,7 +1528,9 @@ export function EditorShell({
1444
1528
  !contentEditorItem ||
1445
1529
  !contentEditorItem.canWriteItem ||
1446
1530
  !contentEditorItem.canWriteLanguage ||
1447
- !contentEditorItem.canWriteWorkflow;
1531
+ !contentEditorItem.canWriteWorkflow ||
1532
+ // Block editing until server acknowledges client-info for the current item
1533
+ (currentItemDescriptor ? !isClientInfoAcknowledged : false);
1448
1534
 
1449
1535
  const updateUrl = useCallback(
1450
1536
  (params: Record<string, string | undefined>) => {
@@ -1711,18 +1797,31 @@ export function EditorShell({
1711
1797
  }
1712
1798
 
1713
1799
  if (actionButton.action) {
1714
- setActiveFieldActions((prevFieldActions) => [
1715
- ...prevFieldActions.filter(
1716
- (x) =>
1717
- !(
1718
- x.field.fieldId == fieldDescriptor.fieldId &&
1719
- x.field.item.id == fieldItem.id &&
1720
- x.field.item.language == fieldItem.language &&
1721
- x.field.item.version == fieldItem.version
1722
- ),
1723
- ),
1724
- op,
1725
- ]);
1800
+ setActiveFieldActions((prevFieldActions) => {
1801
+ const isSameField = (x: FieldAction) =>
1802
+ x.field.fieldId == fieldDescriptor.fieldId &&
1803
+ x.field.item.id == fieldItem.id &&
1804
+ x.field.item.language == fieldItem.language &&
1805
+ x.field.item.version == fieldItem.version;
1806
+
1807
+ const existing = prevFieldActions.find(isSameField);
1808
+
1809
+ // Preserve any existing state/message (e.g., an early websocket error)
1810
+ // to avoid overwriting it with a stale "running" state
1811
+ const mergedOp: FieldAction = existing
1812
+ ? {
1813
+ ...op,
1814
+ state: existing.state,
1815
+ message: existing.message,
1816
+ label: existing.label ?? op.label,
1817
+ }
1818
+ : op;
1819
+
1820
+ return [
1821
+ ...prevFieldActions.filter((x) => !isSameField(x)),
1822
+ mergedOp,
1823
+ ];
1824
+ });
1726
1825
  await executeFieldServerAction(
1727
1826
  fieldDescriptor.item,
1728
1827
  fieldDescriptor.fieldId,
@@ -1741,7 +1840,10 @@ export function EditorShell({
1741
1840
 
1742
1841
  itemsRepository.refreshItems([fieldDescriptor.item]);
1743
1842
 
1744
- op.state = "success";
1843
+ // Only mark as success if no websocket error has been reported
1844
+ if (op.state === "running") {
1845
+ op.state = "success";
1846
+ }
1745
1847
  requestRefresh("immediate");
1746
1848
  }
1747
1849
  },
@@ -1853,6 +1955,7 @@ export function EditorShell({
1853
1955
  selectedComment,
1854
1956
  setSelectedComment,
1855
1957
  comments,
1958
+ availableCommentTags,
1856
1959
  loadComments,
1857
1960
  setComments,
1858
1961
  showComments,
@@ -136,6 +136,7 @@ export type EditContextType = {
136
136
  comments: Comment[];
137
137
  suggestedEdits: SuggestedEdit[];
138
138
  setSuggestedEdits: React.Dispatch<React.SetStateAction<SuggestedEdit[]>>;
139
+ availableCommentTags: { label: string; color: string }[];
139
140
  loadComments: () => void;
140
141
  setComments: React.Dispatch<React.SetStateAction<Comment[]>>;
141
142
  selectedComment: Comment | undefined;
@@ -48,6 +48,7 @@ export function useSocketMessageHandler(deps: {
48
48
  loadHistory: (item: ItemDescriptor) => void;
49
49
  addRecentEdit: (edit: any) => void;
50
50
  socketMessageListeners: React.MutableRefObject<Set<(data: any) => void>>;
51
+ isItemUsedInCurrentPage: (item: ItemDescriptor) => boolean;
51
52
  }) {
52
53
  const {
53
54
  sessionId,
@@ -67,6 +68,7 @@ export function useSocketMessageHandler(deps: {
67
68
  loadHistory,
68
69
  addRecentEdit,
69
70
  socketMessageListeners,
71
+ isItemUsedInCurrentPage,
70
72
  } = deps;
71
73
 
72
74
  const messageHandler = useCallback(
@@ -114,23 +116,37 @@ export function useSocketMessageHandler(deps: {
114
116
  } catch {}
115
117
  }
116
118
 
117
- if (message.type === "item-deleted") {
118
- itemsRepository.onItemsDeleted([
119
- { item: message.payload.item, parentId: message.payload.parentId },
120
- ]);
121
- if (message.payload.item.id === currentItemDescriptor?.id) {
122
- loadItem({
123
- id: message.payload.parentId,
124
- language: currentItemDescriptor?.language ?? "en",
125
- version: 0,
126
- });
127
- }
128
- }
129
-
130
119
  if (message.type === "item-changed") {
131
- await itemsRepository.refreshItems([message.payload.item]);
132
- if (message.payload.item.id === currentItemDescriptor?.id)
133
- await loadItemVersions();
120
+ if (message.payload.action === "delete") {
121
+ const parentId =
122
+ message.payload?.parentId ?? message.payload?.changes?.parentId;
123
+ itemsRepository.onItemsDeleted([
124
+ { item: message.payload.item, parentId },
125
+ ]);
126
+ if (
127
+ message.payload.item.id === currentItemDescriptor?.id &&
128
+ parentId
129
+ ) {
130
+ loadItem({
131
+ id: parentId,
132
+ language: currentItemDescriptor?.language ?? "en",
133
+ version: 0,
134
+ });
135
+ }
136
+ } else {
137
+ await itemsRepository.refreshItems(
138
+ [message.payload.item],
139
+ message.payload.changes,
140
+ );
141
+ const changes = message?.payload?.changes;
142
+ const isPropertiesChange = changes?.properties !== false; // default true
143
+ if (
144
+ isPropertiesChange &&
145
+ message.payload.item.id === currentItemDescriptor?.id
146
+ ) {
147
+ await loadItemVersions();
148
+ }
149
+ }
134
150
  }
135
151
 
136
152
  if (message.type === "item-version-added") {
@@ -242,10 +258,32 @@ export function useSocketMessageHandler(deps: {
242
258
  field.type !== "multi-line text" &&
243
259
  field.type !== "rich text")
244
260
  ) {
245
- itemsRepository.refreshItems([
246
- { ...editFieldOperation.mainItem, id: editFieldOperation.itemId },
247
- ]);
248
- requestRefresh("immediate");
261
+ itemsRepository.refreshItems(
262
+ [
263
+ {
264
+ ...editFieldOperation.mainItem,
265
+ id: editFieldOperation.itemId,
266
+ },
267
+ ],
268
+ { properties: true },
269
+ );
270
+ // Only refresh the page if it is affected
271
+ const pageDescriptor: ItemDescriptor | undefined =
272
+ currentItemRef.current?.descriptor;
273
+ const isSamePage =
274
+ !!pageDescriptor &&
275
+ !!op.mainItem &&
276
+ op.mainItem.id === pageDescriptor.id &&
277
+ op.mainItem.language === pageDescriptor.language &&
278
+ op.mainItem.version === pageDescriptor.version;
279
+ const targetItem: ItemDescriptor = {
280
+ id: editFieldOperation.itemId,
281
+ language: editFieldOperation.mainItem?.language,
282
+ version: editFieldOperation.mainItem?.version,
283
+ };
284
+ if (isSamePage || isItemUsedInCurrentPage(targetItem)) {
285
+ requestRefresh("immediate");
286
+ }
249
287
  } else {
250
288
  itemsRepository.updateFieldValue(
251
289
  {
@@ -263,7 +301,18 @@ export function useSocketMessageHandler(deps: {
263
301
  );
264
302
  }
265
303
  } else {
266
- requestRefresh("immediate");
304
+ // For non-field operations, refresh only if the current page matches the operation's main item
305
+ const pageDescriptor: ItemDescriptor | undefined =
306
+ currentItemRef.current?.descriptor;
307
+ const shouldRefresh =
308
+ !!pageDescriptor &&
309
+ !!op.mainItem &&
310
+ op.mainItem.id === pageDescriptor.id &&
311
+ op.mainItem.language === pageDescriptor.language &&
312
+ op.mainItem.version === pageDescriptor.version;
313
+ if (shouldRefresh) {
314
+ requestRefresh("immediate");
315
+ }
267
316
  }
268
317
  if (
269
318
  op.mainItem &&
@@ -7,10 +7,10 @@ import type { WorkboxItem } from "../../../types";
7
7
 
8
8
  export function useWorkbox(params: {
9
9
  page: any;
10
- contentEditorItem?: any;
10
+ currentItemDescriptor?: ItemDescriptor;
11
11
  validate: (items: ItemDescriptor[]) => void;
12
12
  }) {
13
- const { page, contentEditorItem, validate } = params;
13
+ const { page, currentItemDescriptor, validate } = params;
14
14
  const [workboxItems, setWorkboxItems] = useState<WorkboxItem[]>([]);
15
15
 
16
16
  useEffect(() => {
@@ -45,11 +45,11 @@ export function useWorkbox(params: {
45
45
 
46
46
  useEffect(() => {
47
47
  const items: ItemDescriptor[] = [];
48
- if (contentEditorItem) items.push(contentEditorItem.descriptor);
48
+ if (currentItemDescriptor) items.push(currentItemDescriptor);
49
49
  if (page) collectAllItems(page.rootComponent, items);
50
50
  loadWorkboxDebounced(items.filter(Boolean));
51
51
  return () => loadWorkboxDebounced.cancel();
52
- }, [page, contentEditorItem, loadWorkboxDebounced]);
52
+ }, [page, currentItemDescriptor, loadWorkboxDebounced]);
53
53
 
54
54
  function collectAllItems(component: Component, items: ItemDescriptor[]) {
55
55
  component.placeholders.forEach((x) => {
@@ -18,6 +18,14 @@ export type ItemChange = {
18
18
  item: ItemDescriptor;
19
19
  action: "add" | "delete" | "update";
20
20
  parentId?: string;
21
+ changes: {
22
+ fields?: string[];
23
+ children?: boolean;
24
+ properties?: boolean;
25
+ parentId?: string;
26
+ name?: string;
27
+ templateId?: string;
28
+ };
21
29
  };
22
30
  export type DeletedItem = { item: ItemDescriptor; parentId: string };
23
31
 
@@ -34,7 +42,10 @@ export type ItemsRepository = {
34
42
  rawValue?: string | null,
35
43
  ) => Promise<number>;
36
44
 
37
- refreshItems: (items: ItemDescriptor[]) => Promise<void>;
45
+ refreshItems: (
46
+ items: ItemDescriptor[],
47
+ changes?: { properties?: boolean; children?: boolean; fields?: string[] },
48
+ ) => Promise<void>;
38
49
  revision: number;
39
50
  onFieldSaved: (
40
51
  field: FieldDescriptor,
@@ -62,32 +73,14 @@ const isTemplateChange = async (
62
73
  getItem: (descriptor: ItemDescriptor) => Promise<FullItem | undefined>,
63
74
  ): Promise<boolean> => {
64
75
  try {
65
- const item = await getItem(change.item);
66
- if (!item) return false;
67
-
68
76
  // Check if the item is under the /sitecore/templates path
69
77
  if (
70
- item.path &&
71
- item.path.toLowerCase().startsWith("/sitecore/templates")
78
+ change.item?.path &&
79
+ change.item.path.toLowerCase().startsWith("/sitecore/templates")
72
80
  ) {
73
81
  return true;
74
82
  }
75
83
 
76
- // Check if the item's templateId indicates it's a template-related item
77
- const templateId = item.templateId?.toLowerCase();
78
- if (
79
- templateId === TEMPLATE_TEMPLATE_ID.toLowerCase() ||
80
- templateId === TEMPLATE_FIELD_TEMPLATE_ID.toLowerCase() ||
81
- templateId === TEMPLATE_SECTION_TEMPLATE_ID.toLowerCase()
82
- ) {
83
- return true;
84
- }
85
-
86
- // Check if the item is under the templates root using the configured templates root ID
87
- if (item.path && item.path.includes(templatesRootItemId)) {
88
- return true;
89
- }
90
-
91
84
  return false;
92
85
  } catch (error) {
93
86
  console.error("Error checking if item change is template-related:", error);
@@ -215,8 +208,10 @@ export function useItemsRepository(
215
208
  descriptor: x,
216
209
  }));
217
210
 
211
+ // Always refetch version 0 ("latest") to avoid stale caches after new versions are created
218
212
  const itemsMissing = keys.filter(
219
- (item) => !itemsMap.current.has(item.key),
213
+ (entry) =>
214
+ entry.descriptor.version === 0 || !itemsMap.current.has(entry.key),
220
215
  );
221
216
  const map = itemsMap;
222
217
 
@@ -306,6 +301,12 @@ export function useItemsRepository(
306
301
  const itemsToFetch: ItemDescriptor[] = [];
307
302
 
308
303
  for (const { key, descriptor } of keys) {
304
+ // Always refetch version 0 ("latest") stubs to avoid stale cache
305
+ if (descriptor.version === 0) {
306
+ itemsToFetch.push(descriptor);
307
+ continue;
308
+ }
309
+
309
310
  // If we have the full item, use it
310
311
  const fullItem = itemsMap.current.get(key);
311
312
  if (fullItem) {
@@ -442,7 +443,26 @@ export function useItemsRepository(
442
443
  );
443
444
 
444
445
  const refreshItems = useCallback(
445
- async (items: ItemDescriptor[]) => {
446
+ async (
447
+ items: ItemDescriptor[],
448
+ changes?: { properties?: boolean; children?: boolean; fields?: string[] },
449
+ ) => {
450
+ // If only children changed and no properties changed, avoid fetching items.
451
+ // Still notify listeners so UIs like ContentTree can reload children lists.
452
+ const onlyChildrenChanged =
453
+ changes?.children === true && changes?.fields === undefined;
454
+
455
+ if (onlyChildrenChanged) {
456
+ triggerItemsChangedEvent(
457
+ items.map((x) => ({
458
+ item: x,
459
+ action: "update",
460
+ changes: changes ?? {},
461
+ })),
462
+ );
463
+ return;
464
+ }
465
+
446
466
  // Filter to only include items that are already cached
447
467
  const itemsToFetch = items.filter((item) =>
448
468
  itemsMap.current.has(generateKey(item)),
@@ -530,7 +550,11 @@ export function useItemsRepository(
530
550
  setRevision((prev) => prev + 1);
531
551
  }
532
552
  triggerItemsChangedEvent(
533
- items.map((x) => ({ item: x, action: "update" })),
553
+ items.map((x) => ({
554
+ item: x,
555
+ action: "update",
556
+ changes: changes ?? {},
557
+ })),
534
558
  );
535
559
  },
536
560
  [setRevision, fetchItemsFromServer, setModifiedFields],
@@ -575,7 +599,13 @@ export function useItemsRepository(
575
599
  if (dependencies) {
576
600
  refreshItems(dependencies);
577
601
  }
578
- triggerItemsChangedEvent([{ item: field.item, action: "update" }]);
602
+ triggerItemsChangedEvent([
603
+ {
604
+ item: field.item,
605
+ action: "update",
606
+ changes: { fields: [field.fieldId] },
607
+ },
608
+ ]);
579
609
  },
580
610
  [setModifiedFields, refreshItems],
581
611
  );
@@ -593,6 +623,7 @@ export function useItemsRepository(
593
623
  item: x.item,
594
624
  action: "delete",
595
625
  parentId: x.parentId,
626
+ changes: {},
596
627
  })),
597
628
  );
598
629
  setRevision((prev) => prev + 1);
@@ -213,7 +213,7 @@ export function usePageModel(
213
213
 
214
214
  useEffect(() => {
215
215
  updatePageModel();
216
- }, [pageSkeleton, itemsRepository, insertOptions]);
216
+ }, [pageSkeleton, insertOptions]);
217
217
 
218
218
  return {
219
219
  setPageSkeleton,
@@ -0,0 +1,26 @@
1
+ import React from "react";
2
+ import { useEditContext } from "../client/editContext";
3
+ import { DbSetupStep } from "./setup-steps/DbSetupStep";
4
+ import { SettingsSetupStep } from "./setup-steps/SettingsSetupStep";
5
+ import { AiSetupStep } from "./setup-steps/AiSetupStep";
6
+ import { IndexSetupStep } from "./setup-steps/IndexSetupStep";
7
+
8
+ export function Setup() {
9
+ const editContext = useEditContext();
10
+ const configuredSteps = editContext?.configuration?.setup?.steps;
11
+ const steps = React.useMemo(() => {
12
+ if (configuredSteps && configuredSteps.length > 0) return configuredSteps;
13
+ return [DbSetupStep, SettingsSetupStep, AiSetupStep, IndexSetupStep];
14
+ }, [configuredSteps]);
15
+
16
+ return (
17
+ <div className="h-full overflow-auto p-4">
18
+ <div className="mb-4 text-lg font-semibold text-gray-800">Setup</div>
19
+ <div className="grid grid-cols-1 gap-4">
20
+ {steps.map((Step, idx) => (
21
+ <Step key={idx} />
22
+ ))}
23
+ </div>
24
+ </div>
25
+ );
26
+ }