@parhelia/core 0.1.12927 → 0.1.12931

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 (217) hide show
  1. package/dist/agents-view/AgentCard.js +4 -1
  2. package/dist/agents-view/AgentCard.js.map +1 -1
  3. package/dist/agents-view/AgentProfileIcon.js +8 -8
  4. package/dist/agents-view/AgentProfileIcon.js.map +1 -1
  5. package/dist/agents-view/AgentsView.js +1 -1
  6. package/dist/client-components/index.d.ts +1 -1
  7. package/dist/client-components/index.js +1 -1
  8. package/dist/client-components/index.js.map +1 -1
  9. package/dist/components/WorkspaceHomeView.d.ts +13 -0
  10. package/dist/components/WorkspaceHomeView.js +13 -0
  11. package/dist/components/WorkspaceHomeView.js.map +1 -0
  12. package/dist/components/ui/styled-dialog-title.d.ts +3 -1
  13. package/dist/components/ui/styled-dialog-title.js +2 -2
  14. package/dist/components/ui/styled-dialog-title.js.map +1 -1
  15. package/dist/config/config.js +7 -19
  16. package/dist/config/config.js.map +1 -1
  17. package/dist/config/notificationRoutes.js +1 -1
  18. package/dist/config/notificationRoutes.js.map +1 -1
  19. package/dist/editor/ConfirmationDialog.js +1 -1
  20. package/dist/editor/ConfirmationDialog.js.map +1 -1
  21. package/dist/editor/FieldListField.js +8 -0
  22. package/dist/editor/FieldListField.js.map +1 -1
  23. package/dist/editor/ImageEditButton.js +14 -2
  24. package/dist/editor/ImageEditButton.js.map +1 -1
  25. package/dist/editor/ImageEditor.js +1 -1
  26. package/dist/editor/ImageEditor.js.map +1 -1
  27. package/dist/editor/MainLayout.js +2 -1
  28. package/dist/editor/MainLayout.js.map +1 -1
  29. package/dist/editor/PerfProfiler.d.ts +11 -0
  30. package/dist/editor/PerfProfiler.js +29 -0
  31. package/dist/editor/PerfProfiler.js.map +1 -0
  32. package/dist/editor/PictureEditor.js +1 -1
  33. package/dist/editor/PictureEditor.js.map +1 -1
  34. package/dist/editor/ai/Agents.js +15 -79
  35. package/dist/editor/ai/Agents.js.map +1 -1
  36. package/dist/editor/ai/GuidanceOverlay.js +4 -9
  37. package/dist/editor/ai/GuidanceOverlay.js.map +1 -1
  38. package/dist/editor/ai/InlineAiDialog.js +2 -2
  39. package/dist/editor/ai/InlineAiDialog.js.map +1 -1
  40. package/dist/editor/ai/dialogs/capturePageScreenshot.js +1 -1
  41. package/dist/editor/ai/dialogs/capturePageScreenshot.js.map +1 -1
  42. package/dist/editor/ai/terminal/components/AgentMemoryPanel.d.ts +12 -0
  43. package/dist/editor/ai/terminal/components/AgentMemoryPanel.js +131 -0
  44. package/dist/editor/ai/terminal/components/AgentMemoryPanel.js.map +1 -0
  45. package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js +10 -2
  46. package/dist/editor/ai/terminal/components/AgentPromptTrayPopovers.js.map +1 -1
  47. package/dist/editor/ai/terminal/components/AgentSettingsContent.d.ts +2 -1
  48. package/dist/editor/ai/terminal/components/AgentSettingsContent.js.map +1 -1
  49. package/dist/editor/ai/terminal/components/AgentSettingsSections.d.ts +2 -1
  50. package/dist/editor/ai/terminal/components/AgentSettingsSections.js +9 -2
  51. package/dist/editor/ai/terminal/components/AgentSettingsSections.js.map +1 -1
  52. package/dist/editor/ai/terminal/components/AgentTerminalContextPanels.js +11 -0
  53. package/dist/editor/ai/terminal/components/AgentTerminalContextPanels.js.map +1 -1
  54. package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js +27 -20
  55. package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js.map +1 -1
  56. package/dist/editor/ai/terminal/components/ContextInfoBar.js +4 -3
  57. package/dist/editor/ai/terminal/components/ContextInfoBar.js.map +1 -1
  58. package/dist/editor/ai/terminal/useAgentSettingsData.d.ts +8 -1
  59. package/dist/editor/ai/terminal/useAgentSettingsData.js +44 -5
  60. package/dist/editor/ai/terminal/useAgentSettingsData.js.map +1 -1
  61. package/dist/editor/ai/terminal/useAgentSettingsPanel.d.ts +1 -1
  62. package/dist/editor/bridge/BridgeClient.js +9 -1
  63. package/dist/editor/bridge/BridgeClient.js.map +1 -1
  64. package/dist/editor/bridge/protocol.d.ts +19 -0
  65. package/dist/editor/bridge/protocol.js +1 -1
  66. package/dist/editor/bridge/protocol.js.map +1 -1
  67. package/dist/editor/client/EditorShell.js +163 -67
  68. package/dist/editor/client/EditorShell.js.map +1 -1
  69. package/dist/editor/client/editContext.d.ts +35 -2
  70. package/dist/editor/client/editContext.js +8 -0
  71. package/dist/editor/client/editContext.js.map +1 -1
  72. package/dist/editor/client/fieldModificationStore.js +64 -0
  73. package/dist/editor/client/fieldModificationStore.js.map +1 -1
  74. package/dist/editor/client/itemsRepository.js +95 -3
  75. package/dist/editor/client/itemsRepository.js.map +1 -1
  76. package/dist/editor/client/operations.d.ts +1 -0
  77. package/dist/editor/client/operations.js +166 -13
  78. package/dist/editor/client/operations.js.map +1 -1
  79. package/dist/editor/client/ui/EditorChrome.js +6 -5
  80. package/dist/editor/client/ui/EditorChrome.js.map +1 -1
  81. package/dist/editor/commands/componentCommands.js +2 -1
  82. package/dist/editor/commands/componentCommands.js.map +1 -1
  83. package/dist/editor/commands/keyboardCommands.js +1 -1
  84. package/dist/editor/commands/keyboardCommands.js.map +1 -1
  85. package/dist/editor/componentTreeHelper.js +15 -5
  86. package/dist/editor/componentTreeHelper.js.map +1 -1
  87. package/dist/editor/context-menu/InsertMenu.js +1 -1
  88. package/dist/editor/context-menu/InsertMenu.js.map +1 -1
  89. package/dist/editor/field-types/SingleLineText.js +79 -9
  90. package/dist/editor/field-types/SingleLineText.js.map +1 -1
  91. package/dist/editor/fieldValueDiagnostics.d.ts +79 -0
  92. package/dist/editor/fieldValueDiagnostics.js +133 -0
  93. package/dist/editor/fieldValueDiagnostics.js.map +1 -0
  94. package/dist/editor/menubar/PageSelector.js +1 -1
  95. package/dist/editor/menubar/PageSelector.js.map +1 -1
  96. package/dist/editor/menubar/toolbar-sections/EditControls.js +1 -2
  97. package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
  98. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js +6 -0
  99. package/dist/editor/menubar/toolbar-sections/ManualBrowser.js.map +1 -1
  100. package/dist/editor/menubar/toolbar-sections/UtilityControls.js +5 -5
  101. package/dist/editor/menubar/toolbar-sections/UtilityControls.js.map +1 -1
  102. package/dist/editor/page-editor-chrome/DragInsertionOverlay.d.ts +8 -1
  103. package/dist/editor/page-editor-chrome/DragInsertionOverlay.js +203 -51
  104. package/dist/editor/page-editor-chrome/DragInsertionOverlay.js.map +1 -1
  105. package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.d.ts +10 -0
  106. package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.js +236 -0
  107. package/dist/editor/page-editor-chrome/EmptyImageFieldIndicators.js.map +1 -0
  108. package/dist/editor/page-editor-chrome/FrameMenu.d.ts +2 -0
  109. package/dist/editor/page-editor-chrome/FrameMenu.js +8 -6
  110. package/dist/editor/page-editor-chrome/FrameMenu.js.map +1 -1
  111. package/dist/editor/page-editor-chrome/FrameMenus.d.ts +3 -1
  112. package/dist/editor/page-editor-chrome/FrameMenus.js +4 -4
  113. package/dist/editor/page-editor-chrome/FrameMenus.js.map +1 -1
  114. package/dist/editor/page-editor-chrome/PageEditorChrome.js +18 -4
  115. package/dist/editor/page-editor-chrome/PageEditorChrome.js.map +1 -1
  116. package/dist/editor/page-editor-chrome/PictureEditorOverlay.d.ts +3 -0
  117. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js +5 -8
  118. package/dist/editor/page-editor-chrome/PictureEditorOverlay.js.map +1 -1
  119. package/dist/editor/page-editor-chrome/PlaceholderDropZone.d.ts +8 -1
  120. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +20 -5
  121. package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
  122. package/dist/editor/page-editor-chrome/PlaceholderDropZones.d.ts +3 -1
  123. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js +2 -2
  124. package/dist/editor/page-editor-chrome/PlaceholderDropZones.js.map +1 -1
  125. package/dist/editor/page-editor-chrome/overlay/placeholderZones.d.ts +10 -0
  126. package/dist/editor/page-editor-chrome/overlay/placeholderZones.js +61 -1
  127. package/dist/editor/page-editor-chrome/overlay/placeholderZones.js.map +1 -1
  128. package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js +121 -48
  129. package/dist/editor/page-editor-chrome/useBridgeInlineEditing.js.map +1 -1
  130. package/dist/editor/page-viewer/EditorForm.js +94 -7
  131. package/dist/editor/page-viewer/EditorForm.js.map +1 -1
  132. package/dist/editor/page-viewer/MiniMap.js +74 -9
  133. package/dist/editor/page-viewer/MiniMap.js.map +1 -1
  134. package/dist/editor/page-viewer/PageViewer.js +9 -7
  135. package/dist/editor/page-viewer/PageViewer.js.map +1 -1
  136. package/dist/editor/page-viewer/PageViewerFrame.js +390 -54
  137. package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
  138. package/dist/editor/page-viewer/bridgeFieldPatch.js +49 -2
  139. package/dist/editor/page-viewer/bridgeFieldPatch.js.map +1 -1
  140. package/dist/editor/page-viewer/minimapVisibility.d.ts +33 -0
  141. package/dist/editor/page-viewer/minimapVisibility.js +72 -0
  142. package/dist/editor/page-viewer/minimapVisibility.js.map +1 -0
  143. package/dist/editor/page-viewer/pageViewContext.d.ts +1 -0
  144. package/dist/editor/page-viewer/pageViewContext.js +7 -0
  145. package/dist/editor/page-viewer/pageViewContext.js.map +1 -1
  146. package/dist/editor/page-viewer/renderUrl.js +29 -7
  147. package/dist/editor/page-viewer/renderUrl.js.map +1 -1
  148. package/dist/editor/perfTrace.d.ts +46 -0
  149. package/dist/editor/perfTrace.js +213 -0
  150. package/dist/editor/perfTrace.js.map +1 -0
  151. package/dist/editor/reviews/CommentPopover.js +2 -3
  152. package/dist/editor/reviews/CommentPopover.js.map +1 -1
  153. package/dist/editor/reviews/CreateReviewConfirmStep.js +5 -4
  154. package/dist/editor/reviews/CreateReviewConfirmStep.js.map +1 -1
  155. package/dist/editor/reviews/CreateReviewDialog.js +120 -202
  156. package/dist/editor/reviews/CreateReviewDialog.js.map +1 -1
  157. package/dist/editor/reviews/CreateReviewItemsStep.d.ts +18 -0
  158. package/dist/editor/reviews/CreateReviewItemsStep.js +6 -0
  159. package/dist/editor/reviews/CreateReviewItemsStep.js.map +1 -0
  160. package/dist/editor/reviews/CreateReviewLanguagesStep.d.ts +9 -0
  161. package/dist/editor/reviews/CreateReviewLanguagesStep.js +36 -0
  162. package/dist/editor/reviews/CreateReviewLanguagesStep.js.map +1 -0
  163. package/dist/editor/reviews/CreateReviewReviewersStep.d.ts +17 -0
  164. package/dist/editor/reviews/CreateReviewReviewersStep.js +33 -0
  165. package/dist/editor/reviews/CreateReviewReviewersStep.js.map +1 -0
  166. package/dist/editor/reviews/CreateReviewSettingsStep.d.ts +21 -0
  167. package/dist/editor/reviews/CreateReviewSettingsStep.js +33 -0
  168. package/dist/editor/reviews/CreateReviewSettingsStep.js.map +1 -0
  169. package/dist/editor/reviews/MultiReviewManager.js +1 -4
  170. package/dist/editor/reviews/MultiReviewManager.js.map +1 -1
  171. package/dist/editor/reviews/commentAi.js +1 -1
  172. package/dist/editor/reviews/commentAi.js.map +1 -1
  173. package/dist/editor/reviews/openCommentsFeedback.d.ts +2 -6
  174. package/dist/editor/reviews/openCommentsFeedback.js +3 -11
  175. package/dist/editor/reviews/openCommentsFeedback.js.map +1 -1
  176. package/dist/editor/services/agentService.d.ts +38 -0
  177. package/dist/editor/services/agentService.js +42 -0
  178. package/dist/editor/services/agentService.js.map +1 -1
  179. package/dist/editor/services/aiService.d.ts +4 -1
  180. package/dist/editor/services/aiService.js.map +1 -1
  181. package/dist/editor/services/editService.d.ts +1 -1
  182. package/dist/editor/services/editService.js +2 -2
  183. package/dist/editor/services/editService.js.map +1 -1
  184. package/dist/editor/services/reviewsService.d.ts +6 -0
  185. package/dist/editor/services/reviewsService.js +15 -0
  186. package/dist/editor/services/reviewsService.js.map +1 -1
  187. package/dist/editor/settings/panels/AgentProfileConfigPanel.js +2 -1
  188. package/dist/editor/settings/panels/AgentProfileConfigPanel.js.map +1 -1
  189. package/dist/editor/sidebar/ComponentTree.js +31 -10
  190. package/dist/editor/sidebar/ComponentTree.js.map +1 -1
  191. package/dist/editor/sidebar/ContentTreePanel.d.ts +2 -2
  192. package/dist/editor/sidebar/ContentTreePanel.js +6 -2
  193. package/dist/editor/sidebar/ContentTreePanel.js.map +1 -1
  194. package/dist/editor/sidebar/MainContentTree.js +3 -2
  195. package/dist/editor/sidebar/MainContentTree.js.map +1 -1
  196. package/dist/editor/tree-indicators/GutterColumns.js +24 -6
  197. package/dist/editor/tree-indicators/GutterColumns.js.map +1 -1
  198. package/dist/editor/ui/ItemCollectionEditor.js +4 -4
  199. package/dist/editor/ui/ItemCollectionEditor.js.map +1 -1
  200. package/dist/editor/ui/PerfectTree.js +5 -5
  201. package/dist/editor/ui/PerfectTree.js.map +1 -1
  202. package/dist/editor/ui/PublishItemDialog.js +1 -1
  203. package/dist/editor/ui/PublishItemDialog.js.map +1 -1
  204. package/dist/editor/ui/PublishRestrictionsDialog.js +179 -148
  205. package/dist/editor/ui/PublishRestrictionsDialog.js.map +1 -1
  206. package/dist/editor/utils/keyboardNavigation.js +9 -2
  207. package/dist/editor/utils/keyboardNavigation.js.map +1 -1
  208. package/dist/editor/views/SingleEditView.js +32 -2
  209. package/dist/editor/views/SingleEditView.js.map +1 -1
  210. package/dist/index.d.ts +3 -1
  211. package/dist/index.js +2 -1
  212. package/dist/index.js.map +1 -1
  213. package/dist/revision.d.ts +2 -2
  214. package/dist/revision.js +2 -2
  215. package/dist/task-board/components/TaskBoardHomeView.js +3 -6
  216. package/dist/task-board/components/TaskBoardHomeView.js.map +1 -1
  217. package/package.json +1 -1
@@ -1,15 +1,16 @@
1
1
  "use client";
2
2
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
- import React, { useState, useEffect, useRef, useCallback, useMemo, startTransition, } from "react";
3
+ import React, { useState, useEffect, useLayoutEffect, useRef, useCallback, useMemo, startTransition, } from "react";
4
4
  import { toast } from "sonner";
5
- import { EditContextProvider, FieldsEditContextProvider, OperationsContextProvider, useEditContext, } from "./editContext";
5
+ import { isPerfTraceActive, perfTracePaintStep, perfTraceStep, } from "../perfTrace";
6
+ import { EditContextProvider, FieldsEditContextProvider, OperationsContextProvider, SelectionContextProvider, useEditContext, } from "./editContext";
6
7
  import { aggregateModifiedFieldsFromStores, areModifiedFieldSnapshotsEqual, FieldModificationStore, } from "./fieldModificationStore";
7
8
  import { SplashScreenAgentProvider } from "../../splash-screen/SplashScreenAgentContext";
8
9
  import { useRouter, useSearchParams, usePathname } from "./navigation";
9
10
  import { findComponent, getComponentById } from "../componentTreeHelper";
10
11
  import { getOperationsContext } from "./operations";
11
12
  import { handleErrorResult } from "./helpers";
12
- import { showExpandableErrorToast, } from "./errorToast";
13
+ import { showExpandableErrorToast } from "./errorToast";
13
14
  import { isManualShowMeFocusTransfer, isManualShowMeTarget, } from "./manualShowMeFocus";
14
15
  import { executeFieldAction as executeFieldServerAction, connectSocket, getEditHistory, getRunningOperations, reconnectSession, releaseFieldLocks, validateItems, } from "../services/editService";
15
16
  import { emitAgentDocumentsChanged } from "../services/agentService";
@@ -37,6 +38,7 @@ import { post } from "../services/serviceHelper";
37
38
  import { CONTENT_TREE_SIDEBAR_ID } from "../sidebar/contentTreeConstants";
38
39
  import { PageViewerFrame } from "../page-viewer/PageViewerFrame";
39
40
  import { useItemsRepository } from "./itemsRepository";
41
+ import { logFieldValueDiagnostic, summarizeFieldDescriptor, } from "../fieldValueDiagnostics";
40
42
  import { Spinner } from "../ui/Spinner";
41
43
  import { createSettleRegistry } from "../ui/animationSettle";
42
44
  import { cleanId } from "../utils/id-helper";
@@ -47,7 +49,7 @@ import { Tour } from "../../tour/Tour";
47
49
  import { GuidanceOverlay } from "../ai/GuidanceOverlay";
48
50
  import { AgentDialogHandler } from "../ai/dialogs";
49
51
  import { setActiveAgentConversation } from "../ai/useActiveAgentConversation";
50
- import { usePageViewContext, } from "../page-viewer/pageViewContext";
52
+ import { CANVAS_REFRESH_COMPLETED_EVENT, usePageViewContext, } from "../page-viewer/pageViewContext";
51
53
  import { QuickItemSwitcher } from "../QuickItemSwitcher";
52
54
  import { useEditorSlotContext, } from "../views/editorSlotContext";
53
55
  import { getComments, getAvailableCommentTags, } from "../services/reviewsService";
@@ -176,6 +178,10 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
176
178
  const parheliaSettingsRefreshInFlightRef = useRef(null);
177
179
  const minimapEnabledInSettings = effectiveParheliaSettings?.showMinimap !== false;
178
180
  const [selection, setSelection] = useState([]);
181
+ // Current selection for event-time reads (select() early-bail, getSelection)
182
+ // without making callers depend on the reactive value.
183
+ const selectionRef = useRef(selection);
184
+ selectionRef.current = selection;
179
185
  const [contentTreeSelection, setContentTreeSelection] = useState([]);
180
186
  const [selectedForInsertion, setSelectedForInsertion] = useState("");
181
187
  const [refreshCompletedFlag, setRefreshCompletedFlag] = useState(false);
@@ -230,6 +236,51 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
230
236
  const [inserting, setInserting] = useState();
231
237
  const [removingComponentIds, setRemovingComponentIds] = useState([]);
232
238
  const insertingSequenceRef = useRef(0);
239
+ // Armed by selectWithoutAutoScroll for programmatic selections (drop
240
+ // target's parent, post-operation focus): the canvas selection-scroll
241
+ // effect consumes it and skips exactly that change. Deterministic — unlike
242
+ // a time window, it cannot expire while a slow operation or a frozen host
243
+ // delays the effect flush.
244
+ const suppressNextSelectionAutoScrollRef = useRef(false);
245
+ const selectWithoutAutoScroll = useCallback((ids) => {
246
+ const current = selectionRef.current;
247
+ if (ids.length === current.length &&
248
+ ids.every((id, index) => id === current[index])) {
249
+ // No state change: nothing renders, so don't leave the suppression
250
+ // armed for an unrelated later selection.
251
+ return;
252
+ }
253
+ suppressNextSelectionAutoScrollRef.current = true;
254
+ setSelection(ids);
255
+ }, []);
256
+ const consumeNextSelectionAutoScrollSuppression = useCallback(() => {
257
+ const suppressed = suppressNextSelectionAutoScrollRef.current;
258
+ suppressNextSelectionAutoScrollRef.current = false;
259
+ return suppressed;
260
+ }, []);
261
+ const insertingHoldCleanupRef = useRef(null);
262
+ // The drop operation's HTTP resolve only means the server accepted it — the
263
+ // canvas re-render (fetch + DOM patch) lands seconds later. Keep the
264
+ // inserting indicator up until the first canvas refresh after the operation
265
+ // completes, so it visually hands off to the component actually appearing,
266
+ // with a timeout backstop for hosts that never report a refresh.
267
+ const holdInsertingUntilCanvasRefresh = useCallback((sequence) => {
268
+ insertingHoldCleanupRef.current?.();
269
+ const finish = () => {
270
+ insertingHoldCleanupRef.current = null;
271
+ window.removeEventListener(CANVAS_REFRESH_COMPLETED_EVENT, finish);
272
+ window.clearTimeout(timeoutId);
273
+ if (insertingSequenceRef.current === sequence) {
274
+ setInserting(undefined);
275
+ }
276
+ };
277
+ const timeoutId = window.setTimeout(finish, 15000);
278
+ window.addEventListener(CANVAS_REFRESH_COMPLETED_EVENT, finish);
279
+ insertingHoldCleanupRef.current = finish;
280
+ }, []);
281
+ useEffect(() => () => {
282
+ insertingHoldCleanupRef.current?.();
283
+ }, []);
233
284
  const [showFullscreenHint, setShowFullscreenHint] = useState(false);
234
285
  // Fullscreen is a shell-level UI mode. Keep it in EditorShell state so the shell can rerender
235
286
  // immediately when toggled, while still syncing the active slot's PageViewContext.fullscreen.
@@ -709,43 +760,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
709
760
  const resolvedMode = typeof nextMode === "function" ? nextMode(modeRef.current) : nextMode;
710
761
  void changeMode(resolvedMode);
711
762
  }, [changeMode]);
712
- const closeFeedbackSuggestionsMode = useCallback(() => {
713
- setShowComments(false);
714
- setShowSuggestedEdits(false);
715
- setShowSuggestedEditsDiff(false);
716
- if (modeRef.current === "suggestions") {
717
- void changeMode("edit", { reason: "feedback-sidebar-close" });
718
- }
719
- }, [changeMode]);
720
- const feedbackSidebarOpenRef = useRef(openSidebars.includes("feedback"));
721
- const feedbackSidebarSuggestionsLoadedRef = useRef(false);
722
- useEffect(() => {
723
- const feedbackSidebarOpen = openSidebars.includes("feedback");
724
- const wasFeedbackSidebarOpen = feedbackSidebarOpenRef.current;
725
- feedbackSidebarOpenRef.current = feedbackSidebarOpen;
726
- if (!feedbackSidebarOpen) {
727
- feedbackSidebarSuggestionsLoadedRef.current = false;
728
- if (wasFeedbackSidebarOpen) {
729
- closeFeedbackSuggestionsMode();
730
- }
731
- return;
732
- }
733
- if (!showComments) {
734
- setShowComments(true);
735
- }
736
- if (!showSuggestedEdits) {
737
- setShowSuggestedEdits(true);
738
- }
739
- if (!feedbackSidebarSuggestionsLoadedRef.current) {
740
- feedbackSidebarSuggestionsLoadedRef.current = true;
741
- void loadSuggestedEditsRef.current();
742
- }
743
- }, [
744
- closeFeedbackSuggestionsMode,
745
- openSidebars,
746
- showComments,
747
- showSuggestedEdits,
748
- ]);
749
763
  const activateCommentsModeForWriting = useCallback(async (reason) => {
750
764
  setShowComments(true);
751
765
  setShowSuggestedEdits(false);
@@ -873,6 +887,15 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
873
887
  setSelectedHelpSectionId(helpParam === "contents" || helpParam === "true" ? null : helpParam);
874
888
  }
875
889
  }, [searchParams, isInitialLoad]);
890
+ // Perf probe: the gap between shell:setSelection-state-update and
891
+ // shell:selection-render-committed is the React render of the entire shell
892
+ // tree (every editContext consumer); the paint step adds compositor time.
893
+ useLayoutEffect(() => {
894
+ if (!isPerfTraceActive())
895
+ return;
896
+ perfTraceStep("shell:selection-render-committed", { ids: selection });
897
+ perfTracePaintStep("shell:selection-painted");
898
+ }, [selection]);
876
899
  useEffect(() => {
877
900
  if (focusedField &&
878
901
  selection.length > 0 &&
@@ -3054,6 +3077,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3054
3077
  configuration,
3055
3078
  selection,
3056
3079
  setSelection,
3080
+ selectWithoutAutoScroll,
3057
3081
  loadItem,
3058
3082
  addToEditHistory,
3059
3083
  setLockedField,
@@ -3515,9 +3539,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3515
3539
  if (sidebarId === "agents-panel") {
3516
3540
  setUserPreferences({ showAgentsPanel: false });
3517
3541
  }
3518
- if (sidebarId === "feedback") {
3519
- closeFeedbackSuggestionsMode();
3520
- }
3521
3542
  startTransition(() => {
3522
3543
  updateUrl({ sidebar: sidebarUrlValue(newSidebars) });
3523
3544
  });
@@ -3553,7 +3574,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
3553
3574
  ensureSidebarPinned,
3554
3575
  setUserPreferences,
3555
3576
  isMobile,
3556
- closeFeedbackSuggestionsMode,
3557
3577
  ]);
3558
3578
  // Ensure a sidebar is open (without toggling it closed if already open)
3559
3579
  const openSidebar = useCallback((sidebarId, options) => {
@@ -4030,6 +4050,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4030
4050
  }, [operationsContext.cleanup]);
4031
4051
  // Shared focused field setter with lock handling (used in both contexts)
4032
4052
  const setFocusedFieldWithLock = useCallback(async (field, requestLock) => {
4053
+ logFieldValueDiagnostic("focused-field", "setFocusedField requested", {
4054
+ field: summarizeFieldDescriptor(field),
4055
+ requestLock,
4056
+ ignoreBlur,
4057
+ currentFocusedField: summarizeFieldDescriptor(focusedFieldRef.current),
4058
+ lockedField: summarizeFieldDescriptor(lockedField),
4059
+ });
4033
4060
  if (field) {
4034
4061
  setIgnoreBlur(true);
4035
4062
  setTimeout(() => {
@@ -4038,12 +4065,25 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4038
4065
  if (requestLock) {
4039
4066
  const lockResult = (await operations.ensureLock(field)) || false;
4040
4067
  if (!lockResult) {
4068
+ logFieldValueDiagnostic("focused-field", "lock failed", {
4069
+ field: summarizeFieldDescriptor(field),
4070
+ requestLock,
4071
+ });
4041
4072
  return false;
4042
4073
  }
4043
4074
  setFocusedField({ ...field });
4075
+ logFieldValueDiagnostic("focused-field", "focused field set", {
4076
+ field: summarizeFieldDescriptor(field),
4077
+ requestLock,
4078
+ lockResult,
4079
+ });
4044
4080
  return true;
4045
4081
  }
4046
4082
  setFocusedField({ ...field });
4083
+ logFieldValueDiagnostic("focused-field", "focused field set", {
4084
+ field: summarizeFieldDescriptor(field),
4085
+ requestLock,
4086
+ });
4047
4087
  }
4048
4088
  else {
4049
4089
  // Keep the field selected when the blur was caused by clicking a User
@@ -4060,13 +4100,25 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4060
4100
  now: Date.now(),
4061
4101
  });
4062
4102
  if (!ignoreBlur && !focusMovedToShowMe) {
4103
+ logFieldValueDiagnostic("focused-field", "clearing focused field", {
4104
+ previousFocusedField: summarizeFieldDescriptor(focusedFieldRef.current),
4105
+ lockedField: summarizeFieldDescriptor(lockedField),
4106
+ sessionId,
4107
+ });
4063
4108
  setFocusedField(undefined);
4064
4109
  setLockedField(undefined); // Clear client-side lock state
4065
4110
  releaseFieldLocks(sessionId);
4066
4111
  }
4112
+ else {
4113
+ logFieldValueDiagnostic("focused-field", "clear skipped", {
4114
+ reason: ignoreBlur ? "ignoreBlur" : "manual show-me focus transfer",
4115
+ previousFocusedField: summarizeFieldDescriptor(focusedFieldRef.current),
4116
+ focusMovedToShowMe,
4117
+ });
4118
+ }
4067
4119
  }
4068
4120
  return true;
4069
- }, [operations, ignoreBlur, sessionId]);
4121
+ }, [operations, ignoreBlur, sessionId, lockedField]);
4070
4122
  const quickSwitcherEntries = useMemo(() => {
4071
4123
  const entries = [];
4072
4124
  const seen = new Set();
@@ -4552,10 +4604,21 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4552
4604
  loadItemVersions,
4553
4605
  sessionId: sessionId,
4554
4606
  readonly: isReadOnly,
4555
- selection,
4607
+ getSelection: () => selectionRef.current,
4556
4608
  select: (ids) => {
4609
+ // Re-selecting the same components is a no-op: without this bail a
4610
+ // fresh array identity re-renders every selection consumer.
4611
+ const current = selectionRef.current;
4612
+ if (ids.length === current.length &&
4613
+ ids.every((id, index) => id === current[index])) {
4614
+ perfTraceStep("shell:setSelection-bailed (unchanged)", { ids });
4615
+ return;
4616
+ }
4617
+ perfTraceStep("shell:setSelection-state-update", { ids });
4557
4618
  setSelection(ids);
4558
4619
  },
4620
+ selectWithoutAutoScroll,
4621
+ consumeNextSelectionAutoScrollSuppression,
4559
4622
  contentTreeSelection,
4560
4623
  setContentTreeSelection,
4561
4624
  selectedForInsertion,
@@ -4597,6 +4660,10 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4597
4660
  return await getComponentCommands(components, editContext, providerContext);
4598
4661
  },
4599
4662
  dragStart: (dragObject) => {
4663
+ // A drag supersedes any unresolved scroll request (e.g. a comment
4664
+ // target still waiting for geometry). Left pending, it would replay
4665
+ // against the post-drop refresh and yank the canvas to a stale spot.
4666
+ setScrollIntoView(undefined);
4600
4667
  setDragObject(dragObject);
4601
4668
  },
4602
4669
  dragEnd: () => {
@@ -4604,18 +4671,21 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4604
4671
  },
4605
4672
  droppedInPlaceholder: async (placeholderKey, index, spotPositionElement, spotPositionAnchor, insertOption, spotPosition, color) => {
4606
4673
  const droppedDragObject = dragObject;
4607
- if ((!droppedDragObject && !insertOption) || !page)
4608
- return;
4674
+ if ((!droppedDragObject && !insertOption) || !page) {
4675
+ return { committed: false };
4676
+ }
4609
4677
  setDragObject(undefined);
4610
4678
  const placeholderKeyComponents = placeholderKey.split("_");
4611
4679
  const parentId = placeholderKeyComponents.length === 1
4612
4680
  ? undefined
4613
4681
  : placeholderKeyComponents[1];
4614
- if (parentId)
4615
- setSelection([parentId]);
4682
+ // Selection is left alone at drop. The server response's focus id
4683
+ // selects the moved/added component (executeEditOperation); selecting
4684
+ // the drop target's parent here made the single-selection frame
4685
+ // FLIP-animate component -> parent -> component around every drop.
4616
4686
  let op;
4617
4687
  if (!currentItemDescriptorRef.current)
4618
- return;
4688
+ return { committed: false };
4619
4689
  let insertionSequence;
4620
4690
  if ((spotPosition || spotPositionElement) && spotPositionAnchor) {
4621
4691
  insertionSequence = ++insertingSequenceRef.current;
@@ -4687,20 +4757,34 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
4687
4757
  insertingSequenceRef.current === insertionSequence) {
4688
4758
  setInserting(undefined);
4689
4759
  }
4690
- return;
4760
+ return { committed: false };
4691
4761
  }
4762
+ let operationFailed = false;
4692
4763
  try {
4693
- await operationPromise;
4764
+ // executeOp resolves (instead of throwing) with a failed status when
4765
+ // the server rejects the operation — no refresh follows then.
4766
+ const result = (await operationPromise);
4767
+ operationFailed = result?.executionStatus === "failed";
4694
4768
  }
4695
4769
  catch (err) {
4770
+ operationFailed = true;
4696
4771
  console.error("Failed to drop component in placeholder", err);
4697
4772
  }
4698
4773
  finally {
4699
4774
  if (insertionSequence !== undefined &&
4700
4775
  insertingSequenceRef.current === insertionSequence) {
4701
- setInserting(undefined);
4776
+ if (operationFailed) {
4777
+ setInserting(undefined);
4778
+ }
4779
+ else {
4780
+ // The server accepted the drop; the canvas has not repainted
4781
+ // yet. Hold the indicator until the refresh lands so it stops
4782
+ // when the component actually appears, not seconds earlier.
4783
+ holdInsertingUntilCanvasRefresh(insertionSequence);
4784
+ }
4702
4785
  }
4703
4786
  }
4787
+ return { committed: !operationFailed };
4704
4788
  },
4705
4789
  page,
4706
4790
  triggerFieldAction: async (fieldDescriptor, actionButton, parameters, event) => {
@@ -5200,7 +5284,6 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
5200
5284
  loadItemVersions,
5201
5285
  sessionId,
5202
5286
  isReadOnly,
5203
- selection,
5204
5287
  contentTreeSelection,
5205
5288
  selectedForInsertion,
5206
5289
  dragObject,
@@ -5348,7 +5431,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
5348
5431
  // This ensures editContextRef.current always has the latest value when executeCommand is called
5349
5432
  // Using useEffect would create a timing window where the ref is stale
5350
5433
  editContextRef.current = editContext;
5351
- const fieldsEditContext = {
5434
+ // Selection is deliberately NOT part of editContext (see SelectionContext):
5435
+ // a canvas click would otherwise rebuild the context and re-render every
5436
+ // consumer. Only SelectionContext subscribers re-render on selection change.
5437
+ const selectionContextValue = useMemo(() => ({ selection }), [selection]);
5438
+ // Memoized: a fresh identity here re-renders every FieldsEditContext
5439
+ // consumer on every shell render (same churn class as SelectionContext).
5440
+ const fieldsEditContext = useMemo(() => ({
5352
5441
  modifiedFields,
5353
5442
  recentEdits,
5354
5443
  clearRecentEdits: () => {
@@ -5361,7 +5450,14 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
5361
5450
  setLockedField(undefined); // Clear client-side lock state
5362
5451
  await releaseFieldLocks(sessionId);
5363
5452
  },
5364
- };
5453
+ }), [
5454
+ modifiedFields,
5455
+ recentEdits,
5456
+ addRecentEdit,
5457
+ focusedField,
5458
+ setFocusedFieldWithLock,
5459
+ sessionId,
5460
+ ]);
5365
5461
  // Debug: Track editContext changes
5366
5462
  // const prevDependencies = useRef<any[]>([]);
5367
5463
  // useEffect(() => {
@@ -5598,7 +5694,7 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
5598
5694
  ]);
5599
5695
  if (!currentWorkspace)
5600
5696
  return null;
5601
- const editorUi = fullscreen ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "fixed inset-0 flex", children: _jsx(PageViewerFrame, { compareView: compareMode, pageViewContext: activePageViewContext }) }), showFullscreenHint && !configuration.forceFullscreen && !isMobile && (_jsx("div", { className: "fixed inset-0 z-10000", onMouseMoveCapture: () => {
5697
+ const editorUi = fullscreen ? (_jsxs(_Fragment, { children: [_jsx("div", { className: "bg-neutral-grey-5 fixed inset-0 flex", "data-testid": "fullscreen-root", children: _jsx(PageViewerFrame, { compareView: compareMode, pageViewContext: activePageViewContext }) }), showFullscreenHint && !configuration.forceFullscreen && !isMobile && (_jsx("div", { className: "fixed inset-0 z-10000", onMouseMoveCapture: () => {
5602
5698
  setTimeout(() => {
5603
5699
  setShowFullscreenHint(false);
5604
5700
  }, 600);
@@ -5615,13 +5711,13 @@ export function EditorShell({ configuration, className, item: loadItemDescriptor
5615
5711
  window.history.replaceState(getCurrentHistoryState(), "", newUrl);
5616
5712
  }, configuration: configuration, restoredFromUrl: tourRestoredRef.current })), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(GuidanceOverlay, {}) }), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(AgentDialogHandler, {}) })] }));
5617
5713
  const showRefreshSpinner = editContext.isRefreshing && !activePageViewContext.iframeSupportsRefresh;
5618
- return (_jsx(LicenseProvider, { initialLicenseStatus: initialLicenseStatus, initialStatusLoaded: initialLicenseStatusLoaded, children: _jsx("div", { className: `editor h-full w-full`, children: _jsx(OperationsContextProvider, { value: operationsContext.context, children: _jsx(FieldsEditContextProvider, { value: fieldsEditContext, children: _jsx(EditContextProvider, { value: editContext, children: _jsxs(SplashScreenAgentProvider, { children: [_jsx(DevModeIndicator, {}), startupChecks.state === "loading" && _jsx(EditorLoadingOverlay, {}), showRefreshSpinner && (_jsx("div", { className: "text-neutral-grey-50 pointer-events-none fixed right-0 bottom-0 flex h-24 w-24 items-center justify-center opacity-50 select-none", children: _jsx(Spinner, {}) })), (currentWorkspace.id === "agents" ||
5619
- currentWorkspace.id === "taskboard" ||
5620
- currentWorkspace.id === "translation-management") &&
5621
- showAgentsWorkspaceEditor && (_jsx(AgentsSlotContextBridgeHost, { slots: editorSlots })), startupChecks.state !== "loading" && (children || editorUi), startupChecks.state !== "loading" && dialog, _jsx(Toaster, { position: "top-center" }), " ", _jsx(ConfirmationDialog, { ref: confirmationDialogRef }), _jsx(ConcurrentUserLimitDialog, { open: concurrentUserLimitError !== null, onOpenChange: (open) => {
5622
- if (!open) {
5623
- setConcurrentUserLimitError(null);
5624
- }
5625
- }, sessionId: sessionId, currentUsers: concurrentUserLimitError?.currentUsers ?? 0, maxUsers: concurrentUserLimitError?.maxUsers ?? 0, message: concurrentUserLimitError?.message ?? "", onRetry: handleRetryConnection, isAdministrator: userInfo.user.isAdministrator === true }), _jsx(QuickItemSwitcher, { visible: quickSwitcherVisible, entries: quickSwitcherEntries.slice(0, 5), selectedIndex: quickSwitcherSelectedIndex, onSelect: handleQuickSwitcherSelect, onClose: () => setQuickSwitcherVisible(false) }), _jsx(EditContextMenu, { ref: contextMenuRef, onOpenChange: handleContextMenuOpenChange }), _jsx(EditorFormHintPopover, {}), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(InlineAiTrigger, {}) }), media.mediaSelectorVisible && (_jsx(MediaSelector, { language: editContext.currentItemDescriptor.language, visible: media.mediaSelectorVisible, onHide: media.handleHide, onMediaSelected: media.onMediaSelect, selectedIdPath: media.selectedMediaIdPath, mode: media.mediaSelectorMode, initialSearchTerm: media.initialSearchTerm })), _jsx(FieldEditorPopup, { ref: fieldEditorPopupRef }), _jsx(LicenseOverlay, {}), _jsx(SessionExpiredOverlay, {})] }) }) }) }) }) }));
5714
+ return (_jsx(LicenseProvider, { initialLicenseStatus: initialLicenseStatus, initialStatusLoaded: initialLicenseStatusLoaded, children: _jsx("div", { className: `editor h-full w-full`, children: _jsx(OperationsContextProvider, { value: operationsContext.context, children: _jsx(FieldsEditContextProvider, { value: fieldsEditContext, children: _jsx(EditContextProvider, { value: editContext, children: _jsx(SelectionContextProvider, { value: selectionContextValue, children: _jsxs(SplashScreenAgentProvider, { children: [_jsx(DevModeIndicator, {}), startupChecks.state === "loading" && (_jsx(EditorLoadingOverlay, {})), showRefreshSpinner && (_jsx("div", { className: "text-neutral-grey-50 pointer-events-none fixed right-0 bottom-0 flex h-24 w-24 items-center justify-center opacity-50 select-none", children: _jsx(Spinner, {}) })), (currentWorkspace.id === "agents" ||
5715
+ currentWorkspace.id === "taskboard" ||
5716
+ currentWorkspace.id === "translation-management") &&
5717
+ showAgentsWorkspaceEditor && (_jsx(AgentsSlotContextBridgeHost, { slots: editorSlots })), startupChecks.state !== "loading" && (children || editorUi), startupChecks.state !== "loading" && dialog, _jsx(Toaster, { position: "top-center" }), " ", _jsx(ConfirmationDialog, { ref: confirmationDialogRef }), _jsx(ConcurrentUserLimitDialog, { open: concurrentUserLimitError !== null, onOpenChange: (open) => {
5718
+ if (!open) {
5719
+ setConcurrentUserLimitError(null);
5720
+ }
5721
+ }, sessionId: sessionId, currentUsers: concurrentUserLimitError?.currentUsers ?? 0, maxUsers: concurrentUserLimitError?.maxUsers ?? 0, message: concurrentUserLimitError?.message ?? "", onRetry: handleRetryConnection, isAdministrator: userInfo.user.isAdministrator === true }), _jsx(QuickItemSwitcher, { visible: quickSwitcherVisible, entries: quickSwitcherEntries.slice(0, 5), selectedIndex: quickSwitcherSelectedIndex, onSelect: handleQuickSwitcherSelect, onClose: () => setQuickSwitcherVisible(false) }), _jsx(EditContextMenu, { ref: contextMenuRef, onOpenChange: handleContextMenuOpenChange }), _jsx(EditorFormHintPopover, {}), _jsx(FeatureGate, { feature: LicenseFeatures.AI, children: _jsx(InlineAiTrigger, {}) }), media.mediaSelectorVisible && (_jsx(MediaSelector, { language: editContext.currentItemDescriptor.language, visible: media.mediaSelectorVisible, onHide: media.handleHide, onMediaSelected: media.onMediaSelect, selectedIdPath: media.selectedMediaIdPath, mode: media.mediaSelectorMode, initialSearchTerm: media.initialSearchTerm })), _jsx(FieldEditorPopup, { ref: fieldEditorPopupRef }), _jsx(LicenseOverlay, {}), _jsx(SessionExpiredOverlay, {})] }) }) }) }) }) }) }));
5626
5722
  }
5627
5723
  //# sourceMappingURL=EditorShell.js.map