@parhelia/core 0.1.12886 → 0.1.12889
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.
- package/dist/agents-view/AgentCard.d.ts +2 -1
- package/dist/agents-view/AgentCard.js +16 -35
- package/dist/agents-view/AgentCard.js.map +1 -1
- package/dist/agents-view/AgentGalleryCard.d.ts +12 -0
- package/dist/agents-view/AgentGalleryCard.js +13 -0
- package/dist/agents-view/AgentGalleryCard.js.map +1 -0
- package/dist/agents-view/AgentGalleryView.d.ts +18 -0
- package/dist/agents-view/AgentGalleryView.js +88 -0
- package/dist/agents-view/AgentGalleryView.js.map +1 -0
- package/dist/agents-view/AgentsView.js +39 -11
- package/dist/agents-view/AgentsView.js.map +1 -1
- package/dist/agents-view/AgentsWorkspaceView.js +40 -21
- package/dist/agents-view/AgentsWorkspaceView.js.map +1 -1
- package/dist/agents-view/DateAgentsGroup.d.ts +2 -1
- package/dist/agents-view/DateAgentsGroup.js +2 -2
- package/dist/agents-view/DateAgentsGroup.js.map +1 -1
- package/dist/agents-view/ProfileAgentsGroup.d.ts +2 -1
- package/dist/agents-view/ProfileAgentsGroup.js +5 -5
- package/dist/agents-view/ProfileAgentsGroup.js.map +1 -1
- package/dist/agents-view/RenameAgentDialog.d.ts +12 -0
- package/dist/agents-view/RenameAgentDialog.js +40 -0
- package/dist/agents-view/RenameAgentDialog.js.map +1 -0
- package/dist/components/FilterInput.d.ts +2 -0
- package/dist/components/FilterInput.js +4 -2
- package/dist/components/FilterInput.js.map +1 -1
- package/dist/components/ui/alert-dialog.d.ts +1 -1
- package/dist/components/ui/alert-dialog.js +5 -5
- package/dist/components/ui/alert-dialog.js.map +1 -1
- package/dist/components/ui/alert.d.ts +3 -2
- package/dist/components/ui/alert.js +9 -4
- package/dist/components/ui/alert.js.map +1 -1
- package/dist/components/ui/badge.d.ts +1 -1
- package/dist/components/ui/button.d.ts +9 -4
- package/dist/components/ui/button.js +16 -9
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/dialog.d.ts +1 -1
- package/dist/components/ui/dialog.js +4 -3
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/styled-dialog-title.js +1 -1
- package/dist/components/ui/styled-dialog-title.js.map +1 -1
- package/dist/editor/FieldListField.js +6 -2
- package/dist/editor/FieldListField.js.map +1 -1
- package/dist/editor/GlobalMenuBar.js +2 -1
- package/dist/editor/GlobalMenuBar.js.map +1 -1
- package/dist/editor/LinkEditorDialog.js +3 -3
- package/dist/editor/LinkEditorDialog.js.map +1 -1
- package/dist/editor/ai/AgentInlineDialogContent.js +1 -1
- package/dist/editor/ai/AgentInlineDialogContent.js.map +1 -1
- package/dist/editor/ai/Agents.js +2 -2
- package/dist/editor/ai/Agents.js.map +1 -1
- package/dist/editor/ai/InlineAiDialog.js +1 -1
- package/dist/editor/ai/InlineAiDialog.js.map +1 -1
- package/dist/editor/ai/dialogs/QuestionnaireInline.js +52 -82
- package/dist/editor/ai/dialogs/QuestionnaireInline.js.map +1 -1
- package/dist/editor/ai/terminal/agentQuestionnaireRecap.d.ts +82 -0
- package/dist/editor/ai/terminal/agentQuestionnaireRecap.js +277 -0
- package/dist/editor/ai/terminal/agentQuestionnaireRecap.js.map +1 -0
- package/dist/editor/ai/terminal/agentQuestionnaireTranscriptRecovery.d.ts +3 -0
- package/dist/editor/ai/terminal/agentQuestionnaireTranscriptRecovery.js +3 -3
- package/dist/editor/ai/terminal/agentQuestionnaireTranscriptRecovery.js.map +1 -1
- package/dist/editor/ai/terminal/agentSessionLiveTotals.d.ts +5 -0
- package/dist/editor/ai/terminal/agentSessionLiveTotals.js +14 -0
- package/dist/editor/ai/terminal/agentSessionLiveTotals.js.map +1 -1
- package/dist/editor/ai/terminal/agentSessionSnapshot.js +2 -0
- package/dist/editor/ai/terminal/agentSessionSnapshot.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentGreeting.js +5 -2
- package/dist/editor/ai/terminal/components/AgentGreeting.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentProjectProgress.d.ts +12 -0
- package/dist/editor/ai/terminal/components/AgentProjectProgress.js +207 -0
- package/dist/editor/ai/terminal/components/AgentProjectProgress.js.map +1 -0
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.d.ts +4 -3
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.js +4 -19
- package/dist/editor/ai/terminal/components/AgentTerminalFullLayout.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalFullUpperContent.d.ts +5 -1
- package/dist/editor/ai/terminal/components/AgentTerminalFullUpperContent.js +33 -2
- package/dist/editor/ai/terminal/components/AgentTerminalFullUpperContent.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.d.ts +2 -0
- package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js +20 -1
- package/dist/editor/ai/terminal/components/AgentTerminalStatusBar.js.map +1 -1
- package/dist/editor/ai/terminal/components/AgentTerminalView.d.ts +3 -2
- package/dist/editor/ai/terminal/components/AgentTerminalView.js +11 -2
- package/dist/editor/ai/terminal/components/AgentTerminalView.js.map +1 -1
- package/dist/editor/ai/terminal/components/AiResponseMessage.js +59 -7
- package/dist/editor/ai/terminal/components/AiResponseMessage.js.map +1 -1
- package/dist/editor/ai/terminal/components/QuestionnaireRecap.d.ts +9 -0
- package/dist/editor/ai/terminal/components/QuestionnaireRecap.js +56 -0
- package/dist/editor/ai/terminal/components/QuestionnaireRecap.js.map +1 -0
- package/dist/editor/ai/terminal/contextBreakdown.d.ts +17 -0
- package/dist/editor/ai/terminal/contextBreakdown.js +58 -0
- package/dist/editor/ai/terminal/contextBreakdown.js.map +1 -0
- package/dist/editor/ai/terminal/types.d.ts +2 -0
- package/dist/editor/ai/terminal/useAgentAutoStartOnCreate.d.ts +29 -0
- package/dist/editor/ai/terminal/useAgentAutoStartOnCreate.js +68 -0
- package/dist/editor/ai/terminal/useAgentAutoStartOnCreate.js.map +1 -0
- package/dist/editor/ai/terminal/useAgentInlineDialogEvents.js +8 -3
- package/dist/editor/ai/terminal/useAgentInlineDialogEvents.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentInlineDialogState.d.ts +10 -3
- package/dist/editor/ai/terminal/useAgentInlineDialogState.js +33 -4
- package/dist/editor/ai/terminal/useAgentInlineDialogState.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentRunResyncSocketHandler.js +16 -2
- package/dist/editor/ai/terminal/useAgentRunResyncSocketHandler.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentRunStatusSocketHandler.js +3 -1
- package/dist/editor/ai/terminal/useAgentRunStatusSocketHandler.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentSession.js +12 -1
- package/dist/editor/ai/terminal/useAgentSession.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentSubmitLifecycle.d.ts +3 -0
- package/dist/editor/ai/terminal/useAgentSubmitLifecycle.js +6 -1
- package/dist/editor/ai/terminal/useAgentSubmitLifecycle.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentTerminalController.js +48 -4
- package/dist/editor/ai/terminal/useAgentTerminalController.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentTerminalViewProps.d.ts +1 -0
- package/dist/editor/ai/terminal/useAgentTerminalViewProps.js +2 -1
- package/dist/editor/ai/terminal/useAgentTerminalViewProps.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentToolCallHandler.js +13 -1
- package/dist/editor/ai/terminal/useAgentToolCallHandler.js.map +1 -1
- package/dist/editor/ai/terminal/useAgentToolResultHandler.js +31 -7
- package/dist/editor/ai/terminal/useAgentToolResultHandler.js.map +1 -1
- package/dist/editor/bridge/BridgeClient.d.ts +1 -8
- package/dist/editor/bridge/BridgeClient.js +2 -4
- package/dist/editor/bridge/BridgeClient.js.map +1 -1
- package/dist/editor/bridge/protocol.d.ts +517 -0
- package/dist/editor/bridge/protocol.js +188 -0
- package/dist/editor/bridge/protocol.js.map +1 -0
- package/dist/editor/client/AboutDialog.js +15 -4
- package/dist/editor/client/AboutDialog.js.map +1 -1
- package/dist/editor/client/EditorShell.js +36 -6
- package/dist/editor/client/EditorShell.js.map +1 -1
- package/dist/editor/client/editContext.d.ts +6 -0
- package/dist/editor/client/editContext.js.map +1 -1
- package/dist/editor/client/hooks/useSocketMessageHandler.d.ts +3 -0
- package/dist/editor/client/hooks/useSocketMessageHandler.js +57 -25
- package/dist/editor/client/hooks/useSocketMessageHandler.js.map +1 -1
- package/dist/editor/client/hooks/useWorkbox.d.ts +1 -0
- package/dist/editor/client/hooks/useWorkbox.js +14 -1
- package/dist/editor/client/hooks/useWorkbox.js.map +1 -1
- package/dist/editor/client/operationToasts.d.ts +2 -0
- package/dist/editor/client/operationToasts.js +35 -0
- package/dist/editor/client/operationToasts.js.map +1 -0
- package/dist/editor/client/operations.d.ts +4 -0
- package/dist/editor/client/operations.js +134 -74
- package/dist/editor/client/operations.js.map +1 -1
- package/dist/editor/client/waitForEditOperationTerminal.d.ts +1 -0
- package/dist/editor/client/waitForEditOperationTerminal.js +1 -1
- package/dist/editor/client/waitForEditOperationTerminal.js.map +1 -1
- package/dist/editor/commands/itemCommands.js +5 -16
- package/dist/editor/commands/itemCommands.js.map +1 -1
- package/dist/editor/field-types/CheckboxEditor.js +3 -1
- package/dist/editor/field-types/CheckboxEditor.js.map +1 -1
- package/dist/editor/field-types/MultiLineText.js +24 -7
- package/dist/editor/field-types/MultiLineText.js.map +1 -1
- package/dist/editor/field-types/SingleLineText.js +19 -2
- package/dist/editor/field-types/SingleLineText.js.map +1 -1
- package/dist/editor/field-types/useTextCompletion.d.ts +20 -0
- package/dist/editor/field-types/useTextCompletion.js +166 -0
- package/dist/editor/field-types/useTextCompletion.js.map +1 -0
- package/dist/editor/menubar/OperationsIndicator.d.ts +7 -0
- package/dist/editor/menubar/OperationsIndicator.js +87 -0
- package/dist/editor/menubar/OperationsIndicator.js.map +1 -0
- package/dist/editor/menubar/ToolbarFactory.js +4 -1
- package/dist/editor/menubar/ToolbarFactory.js.map +1 -1
- package/dist/editor/menubar/WorkflowButton.js +7 -4
- package/dist/editor/menubar/WorkflowButton.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/EditControls.js +7 -3
- package/dist/editor/menubar/toolbar-sections/EditControls.js.map +1 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js +4 -1
- package/dist/editor/menubar/toolbar-sections/ViewportControls.js.map +1 -1
- package/dist/editor/notifications/NotificationCenter.js +28 -27
- package/dist/editor/notifications/NotificationCenter.js.map +1 -1
- package/dist/editor/notifications/NotificationItem.js +99 -15
- package/dist/editor/notifications/NotificationItem.js.map +1 -1
- package/dist/editor/page-editor-chrome/BridgeInlineFormatOverlay.d.ts +1 -1
- package/dist/editor/page-editor-chrome/PageEditorChrome.d.ts +1 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js +7 -1
- package/dist/editor/page-editor-chrome/PlaceholderDropZone.js.map +1 -1
- package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.js +3 -10
- package/dist/editor/page-editor-chrome/overlay/IframeOverlayProvider.js.map +1 -1
- package/dist/editor/page-editor-chrome/overlay/geometry.d.ts +2 -2
- package/dist/editor/page-editor-chrome/useBridgeInlineEditing.d.ts +1 -1
- package/dist/editor/page-viewer/MiniMap.d.ts +21 -0
- package/dist/editor/page-viewer/MiniMap.js +113 -11
- package/dist/editor/page-viewer/MiniMap.js.map +1 -1
- package/dist/editor/page-viewer/PageViewerFrame.js +88 -22
- package/dist/editor/page-viewer/PageViewerFrame.js.map +1 -1
- package/dist/editor/page-viewer/bridgeFieldPatch.d.ts +1 -2
- package/dist/editor/page-viewer/bridgeFieldPatch.js.map +1 -1
- package/dist/editor/page-viewer/pageViewContext.d.ts +1 -1
- package/dist/editor/reviews/DecisionsMatrix.js +5 -1
- package/dist/editor/reviews/DecisionsMatrix.js.map +1 -1
- package/dist/editor/reviews/SuggestedEdit.js +12 -2
- package/dist/editor/reviews/SuggestedEdit.js.map +1 -1
- package/dist/editor/reviews/latestFeedbackData.js +7 -1
- package/dist/editor/reviews/latestFeedbackData.js.map +1 -1
- package/dist/editor/reviews/suggestedEditState.d.ts +2 -0
- package/dist/editor/reviews/suggestedEditState.js +20 -0
- package/dist/editor/reviews/suggestedEditState.js.map +1 -1
- package/dist/editor/reviews/suggestionContentChange.d.ts +1 -1
- package/dist/editor/reviews/suggestionContentChange.js +23 -3
- package/dist/editor/reviews/suggestionContentChange.js.map +1 -1
- package/dist/editor/services/agentService.d.ts +6 -0
- package/dist/editor/services/agentService.js +14 -0
- package/dist/editor/services/agentService.js.map +1 -1
- package/dist/editor/services/aiService.d.ts +5 -0
- package/dist/editor/services/aiService.js.map +1 -1
- package/dist/editor/settings/About.js +1 -1
- package/dist/editor/settings/About.js.map +1 -1
- package/dist/editor/settings/panels/AgentProfileConfigPanel.js +1 -0
- package/dist/editor/settings/panels/AgentProfileConfigPanel.js.map +1 -1
- package/dist/editor/settings/panels/AgentToolsPanel.js +61 -33
- package/dist/editor/settings/panels/AgentToolsPanel.js.map +1 -1
- package/dist/editor/settings/panels/ClusterInstancesPanel.js +2 -1
- package/dist/editor/settings/panels/ClusterInstancesPanel.js.map +1 -1
- package/dist/editor/settings/panels/StatusPanel.js +10 -7
- package/dist/editor/settings/panels/StatusPanel.js.map +1 -1
- package/dist/editor/sidebar/Completions.js +1 -1
- package/dist/editor/sidebar/Completions.js.map +1 -1
- package/dist/editor/sidebar/ComponentTree.js +1 -1
- package/dist/editor/sidebar/ComponentTree.js.map +1 -1
- package/dist/editor/sidebar/OperationItem.js +1 -1
- package/dist/editor/sidebar/OperationItem.js.map +1 -1
- package/dist/editor/sidebar/Workbox.js +18 -4
- package/dist/editor/sidebar/Workbox.js.map +1 -1
- package/dist/editor/ui/DialogButtons.d.ts +3 -1
- package/dist/editor/ui/DialogButtons.js +3 -2
- package/dist/editor/ui/DialogButtons.js.map +1 -1
- package/dist/editor/ui/ItemCollectionEditor.js +2 -2
- package/dist/editor/ui/ItemCollectionEditor.js.map +1 -1
- package/dist/editor/ui/PublishItemDialog.js +4 -6
- package/dist/editor/ui/PublishItemDialog.js.map +1 -1
- package/dist/editor/version-diff/versionDiffTargets.d.ts +1 -1
- package/dist/editor/workflowFields.d.ts +1 -0
- package/dist/editor/workflowFields.js +16 -0
- package/dist/editor/workflowFields.js.map +1 -0
- package/dist/index.d.ts +4 -2
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/revision.d.ts +2 -2
- package/dist/revision.js +2 -2
- package/dist/splash-screen/NewPage.js +26 -7
- package/dist/splash-screen/NewPage.js.map +1 -1
- package/dist/task-board/TaskBoardWorkspace.js +57 -4
- package/dist/task-board/TaskBoardWorkspace.js.map +1 -1
- package/dist/task-board/components/TaskBoardHomeView.d.ts +27 -0
- package/dist/task-board/components/TaskBoardHomeView.js +158 -0
- package/dist/task-board/components/TaskBoardHomeView.js.map +1 -0
- package/package.json +1 -2
- package/styles.css +4 -3
|
@@ -9,24 +9,21 @@ import { createOrUpdateSuggestedEdit, deleteSuggestedEdit, } from "../services/s
|
|
|
9
9
|
import { hasSuggestionContentChange, shouldRemoveNoOpSuggestion, } from "../reviews/suggestionContentChange";
|
|
10
10
|
import { GUID_REGEX_EXACT, getItemDescriptor } from "../utils";
|
|
11
11
|
import { cleanId } from "../utils/id-helper";
|
|
12
|
-
import { copyItemsNeedsTerminalCompletion, waitForEditOperationTerminal, } from "./waitForEditOperationTerminal";
|
|
12
|
+
import { copyItemsNeedsTerminalCompletion, normalizeOpId, waitForEditOperationTerminal, } from "./waitForEditOperationTerminal";
|
|
13
|
+
import { isWorkflowMetadataField } from "../workflowFields";
|
|
13
14
|
import { ExternalChangesWarningContent, TreeOperationConfirmContent, UndoDifferentItemContent, } from "./OperationDialogContent";
|
|
14
|
-
import { upsertSuggestedEdit } from "../reviews/suggestedEditState";
|
|
15
|
+
import { isSuggestedEditRemoved, markSuggestedEditRemoved, upsertSuggestedEdit, } from "../reviews/suggestedEditState";
|
|
15
16
|
/** When true, the delete dialog shows "permanently delete (bypass recycle bin)". */
|
|
16
17
|
const ENABLE_HARD_DELETE_IN_DELETE_DIALOG = false;
|
|
17
18
|
// Track pending suggested edit save requests to prevent race conditions
|
|
18
19
|
const pendingSuggestedEditSaves = new Map();
|
|
19
20
|
// Track pending suggested edits to prevent creating duplicates before server response
|
|
20
21
|
const pendingSuggestedEdits = new Map();
|
|
21
|
-
// Suggestions that were reverted to a no-op while a debounced save was still in
|
|
22
|
-
// flight. A pending save must not (re-)create or re-add them on the server or in
|
|
23
|
-
// local state. Ids are uuids and never reused, so retaining them is safe.
|
|
24
|
-
const removedSuggestedEditIds = new Set();
|
|
25
22
|
const SUGGESTED_EDIT_SAVE_DEBOUNCE_MS = 500;
|
|
26
23
|
function createDelayedSuggestedEditSave(suggestedEdit) {
|
|
27
24
|
return new Promise((resolve, reject) => {
|
|
28
25
|
globalThis.setTimeout(() => {
|
|
29
|
-
if (
|
|
26
|
+
if (isSuggestedEditRemoved(suggestedEdit.id)) {
|
|
30
27
|
// The suggestion was reverted to a no-op while this save was pending.
|
|
31
28
|
// It never reached the server, so there is nothing to persist or delete.
|
|
32
29
|
resolve({ type: "ignored" });
|
|
@@ -124,6 +121,12 @@ export function getOperationsContext(state, ui) {
|
|
|
124
121
|
if (returnedOp.executionStatus !== "executing") {
|
|
125
122
|
setExecutingEditOperations((ops) => ops ? ops.filter((x) => x.id !== op.id) : []);
|
|
126
123
|
}
|
|
124
|
+
else {
|
|
125
|
+
// Swap in the server's "executing" version so consumers (e.g. the
|
|
126
|
+
// operations indicator) can tell long-running ops apart from ops that
|
|
127
|
+
// are merely awaiting their HTTP response
|
|
128
|
+
setExecutingEditOperations((ops) => (ops ?? []).map((x) => (x.id === op.id ? returnedOp : x)));
|
|
129
|
+
}
|
|
127
130
|
return returnedOp;
|
|
128
131
|
}, []);
|
|
129
132
|
const duplicateComponents = useCallback(({ componentIds }) => {
|
|
@@ -288,6 +291,9 @@ export function getOperationsContext(state, ui) {
|
|
|
288
291
|
else
|
|
289
292
|
stateRef.current.requestRefresh("immediate");
|
|
290
293
|
}, []);
|
|
294
|
+
const refreshWorkboxAfterWorkflowChange = useDebouncedCallback(() => {
|
|
295
|
+
stateRef.current.refreshWorkbox?.();
|
|
296
|
+
}, 300, { trailing: true });
|
|
291
297
|
const executeWorkflowCommandAndRefresh = useCallback(async (item, commandId) => {
|
|
292
298
|
const result = await executeWorkflowCommand(item, commandId, "");
|
|
293
299
|
if (result.type === "success") {
|
|
@@ -298,9 +304,12 @@ export function getOperationsContext(state, ui) {
|
|
|
298
304
|
};
|
|
299
305
|
ui.showErrorToast(errorMessage);
|
|
300
306
|
}
|
|
307
|
+
if (result.data.succeeded) {
|
|
308
|
+
refreshWorkboxAfterWorkflowChange();
|
|
309
|
+
}
|
|
301
310
|
stateRef.current.requestRefresh("immediate");
|
|
302
311
|
}
|
|
303
|
-
}, [ui]);
|
|
312
|
+
}, [refreshWorkboxAfterWorkflowChange, ui]);
|
|
304
313
|
const renameItem = useCallback(async (item, newName) => {
|
|
305
314
|
await executeOp({
|
|
306
315
|
type: "rename-item",
|
|
@@ -382,6 +391,7 @@ export function getOperationsContext(state, ui) {
|
|
|
382
391
|
user: stateRef.current.user,
|
|
383
392
|
itemsRepository: itemsRepository,
|
|
384
393
|
comments: stateRef.current.comments,
|
|
394
|
+
commentsLoaded: stateRef.current.commentsLoaded,
|
|
385
395
|
suggestedEdits: stateRef.current.suggestedEdits,
|
|
386
396
|
setSuggestedEdits: stateRef.current.setSuggestedEdits,
|
|
387
397
|
});
|
|
@@ -412,10 +422,14 @@ export function getOperationsContext(state, ui) {
|
|
|
412
422
|
// Update the suggested edits state with the result from the server
|
|
413
423
|
if (result.type === "success" && result.data) {
|
|
414
424
|
const updatedEdit = result.data;
|
|
415
|
-
if (
|
|
425
|
+
if (isSuggestedEditRemoved(updatedEdit.id)) {
|
|
416
426
|
// Reverted to a no-op while this save was in flight; make sure
|
|
417
427
|
// it stays gone on the server and do not re-add it locally.
|
|
418
|
-
void deleteSuggestedEdit(updatedEdit).
|
|
428
|
+
void deleteSuggestedEdit(updatedEdit).then((deleteResult) => {
|
|
429
|
+
if (deleteResult.type !== "success") {
|
|
430
|
+
console.warn("Failed to delete reverted suggestion after save:", deleteResult.summary, deleteResult.details);
|
|
431
|
+
}
|
|
432
|
+
}, () => undefined);
|
|
419
433
|
return result;
|
|
420
434
|
}
|
|
421
435
|
stateRef.current.setSuggestedEdits((current) => upsertSuggestedEdit(current, updatedEdit, {
|
|
@@ -425,11 +439,15 @@ export function getOperationsContext(state, ui) {
|
|
|
425
439
|
}
|
|
426
440
|
else if (result.type === "error" ||
|
|
427
441
|
result.type === "unauthorized") {
|
|
428
|
-
// Show error toast for failed suggested edit save
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
442
|
+
// Show error toast for failed suggested edit save — unless the
|
|
443
|
+
// suggestion was reverted while the request was in flight, in
|
|
444
|
+
// which case there is nothing for the user to see or retry.
|
|
445
|
+
if (!isSuggestedEditRemoved(saveSnapshot.id)) {
|
|
446
|
+
ui.showErrorToast({
|
|
447
|
+
summary: result.summary || "Failed to save suggestion",
|
|
448
|
+
details: result.details,
|
|
449
|
+
});
|
|
450
|
+
}
|
|
433
451
|
}
|
|
434
452
|
return result;
|
|
435
453
|
})
|
|
@@ -515,6 +533,10 @@ export function getOperationsContext(state, ui) {
|
|
|
515
533
|
// Don't refresh here - the websocket handler will trigger a refresh when
|
|
516
534
|
// it receives the item-changed notification from the server. This avoids
|
|
517
535
|
// duplicate refresh calls for the same edit.
|
|
536
|
+
if (result.executionStatus !== "failed" &&
|
|
537
|
+
isWorkflowMetadataField(op.fieldId, op.fieldName)) {
|
|
538
|
+
refreshWorkboxAfterWorkflowChange();
|
|
539
|
+
}
|
|
518
540
|
itemsRepository.onFieldSaved(field, val, saveSequence || 0, fieldModificationStore);
|
|
519
541
|
}
|
|
520
542
|
catch (error) {
|
|
@@ -523,7 +545,7 @@ export function getOperationsContext(state, ui) {
|
|
|
523
545
|
console.error("[editFieldImmediate] Error saving field:", error);
|
|
524
546
|
itemsRepository.clearDirtyState(field, fieldModificationStore);
|
|
525
547
|
}
|
|
526
|
-
}, [itemsRepository, executeOp]);
|
|
548
|
+
}, [itemsRepository, executeOp, refreshWorkboxAfterWorkflowChange]);
|
|
527
549
|
const executeEditFieldDebounced = useDebouncedCallback(async ({ field, value, rawValue, refresh, saveSequence, fieldModificationStore, }) => {
|
|
528
550
|
// Fix: Use the saveSequence passed from editField instead of calling
|
|
529
551
|
// updateFieldValue again. This prevents the race condition where the
|
|
@@ -1367,19 +1389,14 @@ export function getOperationsContext(state, ui) {
|
|
|
1367
1389
|
const terminal = await waitForEditOperationTerminal(cloneResult.id, 120_000);
|
|
1368
1390
|
cloneResult = { ...cloneResult, ...terminal };
|
|
1369
1391
|
}
|
|
1370
|
-
catch
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
details: e?.message || "Operation timed out",
|
|
1374
|
-
});
|
|
1392
|
+
catch {
|
|
1393
|
+
// No terminal message within the timeout; the central lifecycle
|
|
1394
|
+
// toast keeps tracking the operation
|
|
1375
1395
|
return [];
|
|
1376
1396
|
}
|
|
1377
1397
|
}
|
|
1378
1398
|
if (cloneResult?.errorMessage) {
|
|
1379
|
-
|
|
1380
|
-
summary: "Clone failed",
|
|
1381
|
-
details: cloneResult.errorMessage,
|
|
1382
|
-
});
|
|
1399
|
+
// operation:failed already produced the central error toast
|
|
1383
1400
|
return [];
|
|
1384
1401
|
}
|
|
1385
1402
|
if (cloneResult?.createdItemIds) {
|
|
@@ -1390,7 +1407,7 @@ export function getOperationsContext(state, ui) {
|
|
|
1390
1407
|
}));
|
|
1391
1408
|
}
|
|
1392
1409
|
return [];
|
|
1393
|
-
}, [executeOp
|
|
1410
|
+
}, [executeOp]);
|
|
1394
1411
|
const duplicateItem = useCallback(async (item, target, name) => {
|
|
1395
1412
|
// Use generic edit endpoint for long-running operation support
|
|
1396
1413
|
// Duplicate is a copy to the same parent, positioned after the original
|
|
@@ -1411,19 +1428,14 @@ export function getOperationsContext(state, ui) {
|
|
|
1411
1428
|
const terminal = await waitForEditOperationTerminal(copyResult.id, 120_000);
|
|
1412
1429
|
copyResult = { ...copyResult, ...terminal };
|
|
1413
1430
|
}
|
|
1414
|
-
catch
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
details: e?.message || "Operation timed out",
|
|
1418
|
-
});
|
|
1431
|
+
catch {
|
|
1432
|
+
// No terminal message within the timeout; the central lifecycle
|
|
1433
|
+
// toast keeps tracking the operation
|
|
1419
1434
|
return;
|
|
1420
1435
|
}
|
|
1421
1436
|
}
|
|
1422
1437
|
if (copyResult.errorMessage) {
|
|
1423
|
-
|
|
1424
|
-
summary: "Duplicate failed",
|
|
1425
|
-
details: copyResult.errorMessage,
|
|
1426
|
-
});
|
|
1438
|
+
// operation:failed already produced the central error toast
|
|
1427
1439
|
return;
|
|
1428
1440
|
}
|
|
1429
1441
|
itemsRepository.refreshItems([target]);
|
|
@@ -1436,7 +1448,7 @@ export function getOperationsContext(state, ui) {
|
|
|
1436
1448
|
};
|
|
1437
1449
|
}
|
|
1438
1450
|
return undefined;
|
|
1439
|
-
}, [executeOp, itemsRepository
|
|
1451
|
+
}, [executeOp, itemsRepository]);
|
|
1440
1452
|
const publishItem = useCallback(async (operation) => {
|
|
1441
1453
|
const result = (await executeOp(operation, {
|
|
1442
1454
|
refresh: "immediate",
|
|
@@ -1509,11 +1521,32 @@ export function getOperationsContext(state, ui) {
|
|
|
1509
1521
|
// This prevents fields from getting stuck in "saving" state when navigating away
|
|
1510
1522
|
const cleanup = useCallback(() => {
|
|
1511
1523
|
executeEditFieldDebounced.flush();
|
|
1512
|
-
|
|
1524
|
+
refreshWorkboxAfterWorkflowChange.cancel();
|
|
1525
|
+
}, [executeEditFieldDebounced, refreshWorkboxAfterWorkflowChange]);
|
|
1513
1526
|
// Function to mark an operation as no longer executing
|
|
1514
1527
|
// Called when WebSocket receives operation:completed or operation:failed
|
|
1515
1528
|
const markOperationComplete = useCallback((operationId) => {
|
|
1516
|
-
|
|
1529
|
+
const key = normalizeOpId(operationId);
|
|
1530
|
+
setExecutingEditOperations((ops) => ops ? ops.filter((x) => normalizeOpId(x.id) !== key) : []);
|
|
1531
|
+
}, []);
|
|
1532
|
+
// Upsert a long-running operation reported by the WebSocket. Covers ops
|
|
1533
|
+
// started in another tab of the same user, which never pass through
|
|
1534
|
+
// executeOp in this tab.
|
|
1535
|
+
const addExecutingOperation = useCallback((operation) => {
|
|
1536
|
+
const key = normalizeOpId(operation.id);
|
|
1537
|
+
setExecutingEditOperations((ops) => {
|
|
1538
|
+
const list = ops ?? [];
|
|
1539
|
+
const existing = list.find((x) => normalizeOpId(x.id) === key);
|
|
1540
|
+
if (existing) {
|
|
1541
|
+
return list.map((x) => normalizeOpId(x.id) === key ? { ...x, ...operation } : x);
|
|
1542
|
+
}
|
|
1543
|
+
return [...list, operation];
|
|
1544
|
+
});
|
|
1545
|
+
}, []);
|
|
1546
|
+
// Live progress for an executing operation (from operation:progress messages)
|
|
1547
|
+
const updateExecutingOperationProgress = useCallback((operationId, progress, statusMessage) => {
|
|
1548
|
+
const key = normalizeOpId(operationId);
|
|
1549
|
+
setExecutingEditOperations((ops) => (ops ?? []).map((x) => normalizeOpId(x.id) === key ? { ...x, progress, statusMessage } : x));
|
|
1517
1550
|
}, []);
|
|
1518
1551
|
return useMemo(() => ({
|
|
1519
1552
|
ops,
|
|
@@ -1521,40 +1554,46 @@ export function getOperationsContext(state, ui) {
|
|
|
1521
1554
|
editOperationExecuted,
|
|
1522
1555
|
executingEditOperations,
|
|
1523
1556
|
markOperationComplete,
|
|
1557
|
+
addExecutingOperation,
|
|
1558
|
+
updateExecutingOperationProgress,
|
|
1524
1559
|
},
|
|
1525
1560
|
cleanup,
|
|
1526
1561
|
}), [
|
|
1527
1562
|
executingEditOperations,
|
|
1528
1563
|
editOperationExecuted,
|
|
1529
1564
|
markOperationComplete,
|
|
1565
|
+
addExecutingOperation,
|
|
1566
|
+
updateExecutingOperationProgress,
|
|
1530
1567
|
ops,
|
|
1531
1568
|
cleanup,
|
|
1532
1569
|
]);
|
|
1533
1570
|
}
|
|
1534
|
-
function removeSuggestedEditFromState(edit, state,
|
|
1535
|
-
//
|
|
1571
|
+
function removeSuggestedEditFromState(edit, state, keys) {
|
|
1572
|
+
// Tombstone first so any in-flight debounced save neither re-creates the
|
|
1536
1573
|
// suggestion on the server nor re-adds it to local state via its save-ack.
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1574
|
+
// The tombstone is permanent for the session: a save scheduled while this
|
|
1575
|
+
// removal is settling (e.g. an edit queued behind the in-flight save) would
|
|
1576
|
+
// otherwise fire after an early release and resurrect the suggestion.
|
|
1577
|
+
markSuggestedEditRemoved(edit.id);
|
|
1578
|
+
// The saves map is keyed by the field's own item descriptor (which can have
|
|
1579
|
+
// a different version/language than the page the suggestion is stored
|
|
1580
|
+
// under), so the caller passes the exact keys it used.
|
|
1581
|
+
const pendingSave = pendingSuggestedEditSaves.get(keys.saveKey);
|
|
1582
|
+
pendingSuggestedEdits.delete(keys.pendingFieldKey);
|
|
1541
1583
|
cleanupPendingSuggestedEditOperations(edit, state.user);
|
|
1542
|
-
|
|
1543
|
-
state.suggestedEdits = nextSuggestedEdits;
|
|
1544
|
-
state.setSuggestedEdits(nextSuggestedEdits);
|
|
1584
|
+
state.setSuggestedEdits((current) => current.filter((candidate) => candidate.id !== edit.id));
|
|
1545
1585
|
// Delete on the server only after any in-flight save settles, otherwise the
|
|
1546
1586
|
// delete can race ahead of a pending create and the suggestion comes back.
|
|
1547
|
-
// Once that latest save has resolved, all earlier debounced timers for this
|
|
1548
|
-
// field have already fired and checked the removed set, so the id can be
|
|
1549
|
-
// dropped to keep the set from growing unbounded over a long session.
|
|
1550
1587
|
void Promise.resolve(pendingSave)
|
|
1551
1588
|
.catch(() => undefined)
|
|
1552
1589
|
.then(() => deleteSuggestedEdit(edit))
|
|
1590
|
+
.then((result) => {
|
|
1591
|
+
if (result.type !== "success") {
|
|
1592
|
+
console.warn("Failed to delete no-op suggestion:", result.summary, result.details);
|
|
1593
|
+
}
|
|
1594
|
+
})
|
|
1553
1595
|
.catch((error) => {
|
|
1554
1596
|
console.warn("Failed to delete no-op suggestion:", error);
|
|
1555
|
-
})
|
|
1556
|
-
.finally(() => {
|
|
1557
|
-
removedSuggestedEditIds.delete(edit.id);
|
|
1558
1597
|
});
|
|
1559
1598
|
}
|
|
1560
1599
|
function getSuggestionCommentCount(edit, state) {
|
|
@@ -1578,14 +1617,37 @@ async function getOrMergeSuggestedEditOp(field, rawValue, value, state) {
|
|
|
1578
1617
|
if (!pageDescriptor) {
|
|
1579
1618
|
return;
|
|
1580
1619
|
}
|
|
1581
|
-
//
|
|
1582
|
-
|
|
1583
|
-
//
|
|
1584
|
-
|
|
1620
|
+
// Keys for the per-field bookkeeping maps. The saves map must use the same
|
|
1621
|
+
// key format as editField (the field's own item descriptor — NOT the
|
|
1622
|
+
// suggestion's mainItem fields, which hold the page's language/version and
|
|
1623
|
+
// diverge for datasource items).
|
|
1624
|
+
const saveKey = `${field.item.id}:${field.item.language}:${field.item.version}:${field.fieldId}`;
|
|
1625
|
+
const fieldKey = `${saveKey}:${state.user?.name || "unknown"}`;
|
|
1626
|
+
const shouldRemoveAsNoOp = (edit, nextValue) =>
|
|
1627
|
+
// Comments load asynchronously; deleting on the strength of a not-yet-
|
|
1628
|
+
// loaded comments array would destroy another reviewer's thread (the
|
|
1629
|
+
// server cascade-deletes a suggestion's comments along with it).
|
|
1630
|
+
state.commentsLoaded === true &&
|
|
1631
|
+
shouldRemoveNoOpSuggestion(edit.oldValue, nextValue, () => getSuggestionCommentCount(edit, state)) &&
|
|
1632
|
+
// Only auto-remove when the typed value is also a no-op against the
|
|
1633
|
+
// field's live value: if the field changed underneath the suggestion, the
|
|
1634
|
+
// typed content still differs from the page and must stay recorded.
|
|
1635
|
+
!hasSuggestionContentChange(fieldItem.rawValue, nextValue);
|
|
1636
|
+
// First check if there's a pending suggested edit for this field. A draft
|
|
1637
|
+
// whose suggestion was already reverted must not be revived — a new edit
|
|
1638
|
+
// mints a fresh suggestion instead.
|
|
1639
|
+
let pendingEdit = pendingSuggestedEdits.get(fieldKey);
|
|
1640
|
+
if (pendingEdit && isSuggestedEditRemoved(pendingEdit.id)) {
|
|
1641
|
+
pendingSuggestedEdits.delete(fieldKey);
|
|
1642
|
+
pendingEdit = undefined;
|
|
1643
|
+
}
|
|
1585
1644
|
if (pendingEdit) {
|
|
1586
1645
|
const nextValue = newVal || "";
|
|
1587
|
-
if (
|
|
1588
|
-
removeSuggestedEditFromState(pendingEdit, state,
|
|
1646
|
+
if (shouldRemoveAsNoOp(pendingEdit, nextValue)) {
|
|
1647
|
+
removeSuggestedEditFromState(pendingEdit, state, {
|
|
1648
|
+
pendingFieldKey: fieldKey,
|
|
1649
|
+
saveKey,
|
|
1650
|
+
});
|
|
1589
1651
|
return;
|
|
1590
1652
|
}
|
|
1591
1653
|
// Update the pending edit instead of creating a new one.
|
|
@@ -1596,9 +1658,7 @@ async function getOrMergeSuggestedEditOp(field, rawValue, value, state) {
|
|
|
1596
1658
|
updatedBy: state.user?.name || "unknown",
|
|
1597
1659
|
};
|
|
1598
1660
|
pendingSuggestedEdits.set(fieldKey, updatedEdit);
|
|
1599
|
-
|
|
1600
|
-
state.suggestedEdits = nextSuggestedEdits;
|
|
1601
|
-
state.setSuggestedEdits(nextSuggestedEdits);
|
|
1661
|
+
state.setSuggestedEdits((current) => upsertSuggestedEdit(current, updatedEdit, { source: "local" }));
|
|
1602
1662
|
return updatedEdit;
|
|
1603
1663
|
}
|
|
1604
1664
|
const matchingSuggestions = state.suggestedEdits.filter((edit) => edit.mainItemId === pageDescriptor.id &&
|
|
@@ -1606,17 +1666,24 @@ async function getOrMergeSuggestedEditOp(field, rawValue, value, state) {
|
|
|
1606
1666
|
edit.itemId === item.id &&
|
|
1607
1667
|
edit.fieldId === field.fieldId);
|
|
1608
1668
|
// Attempt to find an existing suggested edit by the same user for this field.
|
|
1669
|
+
// Tombstoned ids are skipped: the state snapshot can still contain a
|
|
1670
|
+
// suggestion that was just reverted, and reusing its id would tie the new
|
|
1671
|
+
// edit to a save pipeline that permanently ignores it.
|
|
1609
1672
|
const existing = matchingSuggestions.find((edit) => edit.mainItemId === pageDescriptor.id &&
|
|
1610
1673
|
edit.mainItemLanguage === pageDescriptor.language &&
|
|
1611
1674
|
edit.mainItemVersion === pageDescriptor.version &&
|
|
1612
1675
|
edit.itemId === item.id &&
|
|
1613
1676
|
edit.fieldId === field.fieldId &&
|
|
1614
1677
|
edit.author === (state.user?.name || "unknown") &&
|
|
1615
|
-
edit.status === "pending"
|
|
1678
|
+
edit.status === "pending" && // or any status that indicates it's open for further editing.
|
|
1679
|
+
!isSuggestedEditRemoved(edit.id));
|
|
1616
1680
|
if (existing) {
|
|
1617
1681
|
const nextValue = newVal || "";
|
|
1618
|
-
if (
|
|
1619
|
-
removeSuggestedEditFromState(existing, state,
|
|
1682
|
+
if (shouldRemoveAsNoOp(existing, nextValue)) {
|
|
1683
|
+
removeSuggestedEditFromState(existing, state, {
|
|
1684
|
+
pendingFieldKey: fieldKey,
|
|
1685
|
+
saveKey,
|
|
1686
|
+
});
|
|
1620
1687
|
return;
|
|
1621
1688
|
}
|
|
1622
1689
|
// Update the existing suggestion.
|
|
@@ -1626,10 +1693,7 @@ async function getOrMergeSuggestedEditOp(field, rawValue, value, state) {
|
|
|
1626
1693
|
updated: new Date().toISOString(),
|
|
1627
1694
|
updatedBy: state.user?.name || "unknown",
|
|
1628
1695
|
};
|
|
1629
|
-
|
|
1630
|
-
state.suggestedEdits = nextSuggestedEdits;
|
|
1631
|
-
// Optionally, you might update the comments or other fields.
|
|
1632
|
-
state.setSuggestedEdits(nextSuggestedEdits);
|
|
1696
|
+
state.setSuggestedEdits((current) => upsertSuggestedEdit(current, updatedEdit, { source: "local" }));
|
|
1633
1697
|
return updatedEdit;
|
|
1634
1698
|
}
|
|
1635
1699
|
else {
|
|
@@ -1657,11 +1721,7 @@ async function getOrMergeSuggestedEditOp(field, rawValue, value, state) {
|
|
|
1657
1721
|
};
|
|
1658
1722
|
// Add to pending edits to prevent race conditions
|
|
1659
1723
|
pendingSuggestedEdits.set(fieldKey, newEdit);
|
|
1660
|
-
|
|
1661
|
-
source: "local",
|
|
1662
|
-
});
|
|
1663
|
-
state.suggestedEdits = nextSuggestedEdits;
|
|
1664
|
-
state.setSuggestedEdits(nextSuggestedEdits);
|
|
1724
|
+
state.setSuggestedEdits((current) => upsertSuggestedEdit(current, newEdit, { source: "local" }));
|
|
1665
1725
|
return newEdit;
|
|
1666
1726
|
}
|
|
1667
1727
|
}
|