@agent-native/core 0.182.1 → 0.182.2-nightly-20260917225811
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/corpus/README.md +1 -1
- package/corpus/templates/clips/app/root.tsx +59 -10
- package/corpus/templates/clips/changelog/2026-09-17-desktop-popover-closes-when-clicking-away.md +6 -0
- package/corpus/templates/clips/changelog/2026-09-17-self-hosted-extension-sign-in.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +45 -25
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +43 -0
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +55 -6
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +107 -19
- package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +1 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
- package/corpus/templates/design/actions/apply-component-prop-edit.ts +174 -14
- package/corpus/templates/design/actions/apply-visual-edit.ts +3 -1
- package/corpus/templates/design/actions/create-component.ts +173 -12
- package/corpus/templates/design/actions/create-design-from-template.ts +50 -35
- package/corpus/templates/design/actions/create-design-version.ts +21 -0
- package/corpus/templates/design/actions/create-file.ts +35 -33
- package/corpus/templates/design/actions/delete-design.ts +29 -28
- package/corpus/templates/design/actions/delete-file.ts +154 -23
- package/corpus/templates/design/actions/generate-design.ts +28 -16
- package/corpus/templates/design/actions/get-component-details.ts +272 -33
- package/corpus/templates/design/actions/get-design-version.ts +9 -1
- package/corpus/templates/design/actions/go-to-main-component.ts +37 -35
- package/corpus/templates/design/actions/index-components.ts +241 -126
- package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +12 -9
- package/corpus/templates/design/actions/present-design-variants.ts +68 -72
- package/corpus/templates/design/actions/rename-component.ts +294 -0
- package/corpus/templates/design/actions/rename-screen.ts +10 -10
- package/corpus/templates/design/actions/update-file.ts +327 -330
- package/corpus/templates/design/actions/update-screen-source.ts +2 -43
- package/corpus/templates/design/app/components/design/EditPanel.tsx +17 -1
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +228 -24
- package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +17 -1
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +151 -32
- package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +4 -0
- package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +1 -1
- package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +8 -1
- package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +55 -33
- package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
- package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +4 -2
- package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +273 -60
- package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +9 -2
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
- package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +13 -0
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +14 -0
- package/corpus/templates/design/app/components/design/types.ts +9 -1
- package/corpus/templates/design/app/hooks/use-design-versions.ts +3 -2
- package/corpus/templates/design/app/i18n-breakpoints.ts +0 -110
- package/corpus/templates/design/app/i18n-data.ts +0 -10
- package/corpus/templates/design/app/pages/DesignEditor.tsx +669 -161
- package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +20 -0
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +2 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +21 -3
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +2 -0
- package/corpus/templates/design/app/pages/design-editor/commands/create-component.ts +44 -3
- package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +69 -47
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +157 -52
- package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +76 -1
- package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +152 -44
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +131 -2
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +5 -1
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
- package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +1 -0
- package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +58 -8
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
- package/corpus/templates/design/app/pages/design-editor/history.ts +3 -0
- package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +74 -22
- package/corpus/templates/design/app/pages/design-editor/source-publication.ts +14 -2
- package/corpus/templates/design/changelog/2026-09-16-design-components-retain-linked-identities-when-edited-and-d.md +6 -0
- package/corpus/templates/design/changelog/2026-09-16-fix-design-nested-layer-dragging-and-screen-duplication-pari.md +6 -0
- package/corpus/templates/design/changelog/2026-09-17-design-drag-and-drop-keeps-duplicates-cross-screen-moves-and.md +6 -0
- package/corpus/templates/design/changelog/2026-09-17-design-preserves-reusable-component-identity-and-props-acros.md +6 -0
- package/corpus/templates/design/changelog/2026-09-17-open-vector-paths-no-longer-show-fills-and-interact-dimensio.md +6 -0
- package/corpus/templates/design/changelog/2026-09-17-screen-deletion-is-immediate-and-undoable-with-history-that-.md +6 -0
- package/corpus/templates/design/e2e/fixtures/design-component-react/index.html +98 -0
- package/corpus/templates/design/e2e/fixtures/design-component-react/inline-component.html +65 -0
- package/corpus/templates/design/e2e/helpers.ts +6 -1
- package/corpus/templates/design/server/lib/design-data-mutation.ts +9 -5
- package/corpus/templates/design/server/lib/design-snapshot.ts +28 -16
- package/corpus/templates/design/server/lib/design-versions.ts +208 -130
- package/corpus/templates/design/server/lib/fusion-screens.ts +46 -20
- package/corpus/templates/design/server/lib/import-design-files.ts +5 -1
- package/corpus/templates/design/server/lib/localhost-connection.ts +54 -0
- package/corpus/templates/design/server/source-workspace.ts +412 -356
- package/corpus/templates/design/shared/component-library.ts +36 -6
- package/corpus/templates/design/shared/component-model.ts +12 -2
- package/corpus/templates/design/shared/local-jsx-visual-edit.ts +231 -5
- package/corpus/templates/design/shared/source-mode.ts +22 -0
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/AssistantChat.d.ts +4 -1
- package/dist/client/AssistantChat.js +26 -3
- package/dist/client/chat/agentkit-protocol.js +148 -34
- package/dist/client/chat/message-components.js +7 -5
- package/dist/client/chat/tool-call-display.js +4 -3
- package/dist/client/conversation/AgentConversation.js +4 -2
- package/dist/client/tool-display.d.ts +9 -0
- package/dist/client/tool-display.js +15 -0
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/localization/core-messages/ar-SA.js +2 -0
- package/dist/localization/core-messages/de-DE.js +2 -0
- package/dist/localization/core-messages/en-US.d.ts +2 -0
- package/dist/localization/core-messages/en-US.js +2 -0
- package/dist/localization/core-messages/es-ES.js +2 -0
- package/dist/localization/core-messages/fr-FR.js +2 -0
- package/dist/localization/core-messages/hi-IN.js +2 -0
- package/dist/localization/core-messages/ja-JP.js +2 -0
- package/dist/localization/core-messages/ko-KR.js +2 -0
- package/dist/localization/core-messages/pt-BR.js +2 -0
- package/dist/localization/core-messages/zh-CN.js +2 -0
- package/dist/localization/core-messages/zh-TW.js +2 -0
- package/dist/localization/core-messages.d.ts +2 -0
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/package.json +3 -3
- package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +0 -66
|
@@ -5,7 +5,10 @@ import { toast } from "sonner";
|
|
|
5
5
|
import * as Y from "yjs";
|
|
6
6
|
|
|
7
7
|
import { trace } from "@/components/design/design-trace";
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
ElementInfo,
|
|
10
|
+
RuntimeStructureInsertRequest,
|
|
11
|
+
} from "@/components/design/types";
|
|
9
12
|
import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
|
|
10
13
|
import {
|
|
11
14
|
extractLayerPosition,
|
|
@@ -13,6 +16,7 @@ import {
|
|
|
13
16
|
insertClonedHtmlLayer,
|
|
14
17
|
insertClonedHtmlLayers,
|
|
15
18
|
planLinkedComponentStructureClone,
|
|
19
|
+
prepareClonedHtmlLayersForLiveInsert,
|
|
16
20
|
type ComponentCloneBatchContext,
|
|
17
21
|
type LinkedComponentStructureClonePlan,
|
|
18
22
|
} from "@/pages/design-editor/clone-and-pen-edit";
|
|
@@ -28,6 +32,7 @@ import {
|
|
|
28
32
|
mapAcceptedSelectionNode,
|
|
29
33
|
projectAcceptedSource,
|
|
30
34
|
} from "@/pages/design-editor/commands/selection-publication";
|
|
35
|
+
import { isStandaloneHttpUrl } from "@/pages/design-editor/editor-state";
|
|
31
36
|
import type { GeometryHistorySelection } from "@/pages/design-editor/history";
|
|
32
37
|
import type { DesignFile } from "@/pages/design-editor/types";
|
|
33
38
|
|
|
@@ -147,10 +152,16 @@ export interface DuplicateSelectionArgs {
|
|
|
147
152
|
nodeIdMap: Map<string, string>,
|
|
148
153
|
targetFileId: string,
|
|
149
154
|
) => void;
|
|
155
|
+
runtimeStructureInsertRevisionRef?: RefObject<number>;
|
|
150
156
|
selectionBefore?: GeometryHistorySelection;
|
|
151
157
|
selectedCanvasSelector: string;
|
|
152
158
|
selectedElement: ElementInfo | null;
|
|
153
159
|
selectedLayerIdsState: string[];
|
|
160
|
+
setRuntimeStructureInsertRequest?: Dispatch<
|
|
161
|
+
SetStateAction<
|
|
162
|
+
(RuntimeStructureInsertRequest & { screenId: string }) | null
|
|
163
|
+
>
|
|
164
|
+
>;
|
|
154
165
|
setOverviewSelectedScreenIds: Dispatch<SetStateAction<string[]>>;
|
|
155
166
|
setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
|
|
156
167
|
setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
|
|
@@ -174,10 +185,12 @@ export function runDuplicateSelection({
|
|
|
174
185
|
lastDuplicateTransformRef,
|
|
175
186
|
overviewSelectedScreenIds,
|
|
176
187
|
remapMotionTracksForClone,
|
|
188
|
+
runtimeStructureInsertRevisionRef,
|
|
177
189
|
selectionBefore,
|
|
178
190
|
selectedCanvasSelector,
|
|
179
191
|
selectedElement,
|
|
180
192
|
selectedLayerIdsState,
|
|
193
|
+
setRuntimeStructureInsertRequest,
|
|
181
194
|
setOverviewSelectedScreenIds,
|
|
182
195
|
setSelectedElement,
|
|
183
196
|
setSelectedLayerIdsState,
|
|
@@ -263,6 +276,68 @@ export function runDuplicateSelection({
|
|
|
263
276
|
: null;
|
|
264
277
|
const nextDuplicateRootNodeIds: string[] = [];
|
|
265
278
|
|
|
279
|
+
// A localhost screen stores its route URL, while the selected layer only
|
|
280
|
+
// exists in the running iframe. Reusing the design-file clone path here
|
|
281
|
+
// would ask the URL string for an HTML projection and silently turn Cmd+D
|
|
282
|
+
// into a no-op. Prepare the runtime snapshots for the same one-shot bridge
|
|
283
|
+
// insert lifecycle used by paste so the clone gets a fresh identity and
|
|
284
|
+
// participates in pending-edit undo/redo.
|
|
285
|
+
if (
|
|
286
|
+
activeFile &&
|
|
287
|
+
isStandaloneHttpUrl(activeFile.content ?? "") &&
|
|
288
|
+
snapshots.every((snapshot) => snapshot.sourceFileId === activeFile.id)
|
|
289
|
+
) {
|
|
290
|
+
const sourcePositions = snapshots.map((snapshot) =>
|
|
291
|
+
extractLayerPosition(snapshot.html),
|
|
292
|
+
);
|
|
293
|
+
const prepared = prepareClonedHtmlLayersForLiveInsert(
|
|
294
|
+
activeFile.content ?? "",
|
|
295
|
+
snapshots.map((snapshot) => snapshot.html),
|
|
296
|
+
{
|
|
297
|
+
stripRootPosition: true,
|
|
298
|
+
positions: sourcePositions.map((position) =>
|
|
299
|
+
position
|
|
300
|
+
? {
|
|
301
|
+
x: position.x + (repeatTransform?.dx ?? 0),
|
|
302
|
+
y: position.y + (repeatTransform?.dy ?? 0),
|
|
303
|
+
space: "layout" as const,
|
|
304
|
+
}
|
|
305
|
+
: undefined,
|
|
306
|
+
),
|
|
307
|
+
},
|
|
308
|
+
);
|
|
309
|
+
if (
|
|
310
|
+
prepared &&
|
|
311
|
+
runtimeStructureInsertRevisionRef &&
|
|
312
|
+
setRuntimeStructureInsertRequest
|
|
313
|
+
) {
|
|
314
|
+
const anchorSelector =
|
|
315
|
+
selectedElement?.runtimeSelector ??
|
|
316
|
+
selectedCanvasSelector ??
|
|
317
|
+
selectedElement?.selector;
|
|
318
|
+
const anchorSourceId =
|
|
319
|
+
selectedElement?.runtimeSourceId ?? selectedElement?.sourceId;
|
|
320
|
+
const hasAnchor = Boolean(anchorSelector);
|
|
321
|
+
runtimeStructureInsertRevisionRef.current += 1;
|
|
322
|
+
setRuntimeStructureInsertRequest({
|
|
323
|
+
requestId: runtimeStructureInsertRevisionRef.current,
|
|
324
|
+
screenId: activeFile.id,
|
|
325
|
+
html: prepared.htmlFragments[0]!,
|
|
326
|
+
additionalHtml: prepared.htmlFragments.slice(1),
|
|
327
|
+
anchor: hasAnchor
|
|
328
|
+
? { selector: anchorSelector!, sourceId: anchorSourceId }
|
|
329
|
+
: { selector: "body" },
|
|
330
|
+
placement: hasAnchor ? "after" : "inside",
|
|
331
|
+
});
|
|
332
|
+
lastDuplicateTransformRef.current = {
|
|
333
|
+
rootNodeIds: [...prepared.rootNodeIds].sort(),
|
|
334
|
+
dx: repeatTransform?.dx ?? 0,
|
|
335
|
+
dy: repeatTransform?.dy ?? 0,
|
|
336
|
+
};
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
|
|
266
341
|
const sortedSnapshotsByFile = new Map(
|
|
267
342
|
files.map((file) => [
|
|
268
343
|
file.id,
|
|
@@ -29,6 +29,8 @@ import type { LiveScreenSnapshot } from "@/pages/design-editor/command-types";
|
|
|
29
29
|
import type { ApplyFileContentUpdateResult } from "@/pages/design-editor/commands/apply-file-content-update";
|
|
30
30
|
import type { ApplyLocalContentUpdateResult } from "@/pages/design-editor/commands/apply-local-content-update";
|
|
31
31
|
import { prepareContentHistoryReplay } from "@/pages/design-editor/commands/prepare-content-history-replay";
|
|
32
|
+
import type { DesignDataOperation } from "@/pages/design-editor/data-operations";
|
|
33
|
+
import { applyDesignDataOperations } from "@/pages/design-editor/data-operations";
|
|
32
34
|
import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
|
|
33
35
|
import {
|
|
34
36
|
getCanvasFrameGeometry,
|
|
@@ -153,8 +155,17 @@ export interface RedoArgs {
|
|
|
153
155
|
fileDeletionRedoStackRef: RefObject<FileDeletionHistoryEntry[]>;
|
|
154
156
|
fileDeletionUndoStackRef: RefObject<FileDeletionHistoryEntry[]>;
|
|
155
157
|
fileHistoryMutationPendingRef: RefObject<boolean>;
|
|
158
|
+
clearPendingHistory?: () => void;
|
|
156
159
|
files: DesignFile[];
|
|
157
|
-
|
|
160
|
+
filesRef?: RefObject<DesignFile[]>;
|
|
161
|
+
focusCreatedScreen: (
|
|
162
|
+
screenId: string,
|
|
163
|
+
geometry: FrameGeometry,
|
|
164
|
+
options?: {
|
|
165
|
+
preserveCamera?: boolean;
|
|
166
|
+
suppressLineupRecenter?: boolean;
|
|
167
|
+
},
|
|
168
|
+
) => void;
|
|
158
169
|
geometryRedoStackRef: RefObject<GeometryHistoryEntry[]>;
|
|
159
170
|
geometryUndoStackRef: RefObject<GeometryHistoryEntry[]>;
|
|
160
171
|
getFreshActiveContent: () => string;
|
|
@@ -299,6 +310,9 @@ export interface RedoArgs {
|
|
|
299
310
|
html: string,
|
|
300
311
|
options?: { recordHistory?: boolean },
|
|
301
312
|
) => boolean;
|
|
313
|
+
updateDesignAsync: ReturnType<
|
|
314
|
+
typeof useActionMutation<undefined, undefined, "update-design">
|
|
315
|
+
>["mutateAsync"];
|
|
302
316
|
viewModeRef: RefObject<"single" | "overview">;
|
|
303
317
|
writeFrameGeometrySnapshot: (
|
|
304
318
|
geometryById: CanvasFrameGeometryById,
|
|
@@ -335,7 +349,9 @@ export function runRedo({
|
|
|
335
349
|
fileDeletionRedoStackRef,
|
|
336
350
|
fileDeletionUndoStackRef,
|
|
337
351
|
fileHistoryMutationPendingRef,
|
|
352
|
+
clearPendingHistory,
|
|
338
353
|
files,
|
|
354
|
+
filesRef,
|
|
339
355
|
focusCreatedScreen,
|
|
340
356
|
geometryRedoStackRef,
|
|
341
357
|
geometryUndoStackRef,
|
|
@@ -391,6 +407,7 @@ export function runRedo({
|
|
|
391
407
|
t,
|
|
392
408
|
undoManagerRef,
|
|
393
409
|
updateLiveScreenSnapshotContent,
|
|
410
|
+
updateDesignAsync,
|
|
394
411
|
viewModeRef,
|
|
395
412
|
writeFrameGeometrySnapshot,
|
|
396
413
|
ydoc,
|
|
@@ -399,8 +416,9 @@ export function runRedo({
|
|
|
399
416
|
selection: GeometryHistorySelection | undefined,
|
|
400
417
|
replaySources: Record<string, string> = {},
|
|
401
418
|
) => {
|
|
419
|
+
const currentFiles = filesRef?.current ?? files;
|
|
402
420
|
const actualSources = Object.fromEntries(
|
|
403
|
-
|
|
421
|
+
currentFiles.map((file) => [
|
|
404
422
|
file.id,
|
|
405
423
|
replaySources[file.id] ?? getScreenContent(file.id),
|
|
406
424
|
]),
|
|
@@ -1144,9 +1162,8 @@ export function runRedo({
|
|
|
1144
1162
|
// U12: redo a screen create/duplicate by recreating the file with the
|
|
1145
1163
|
// same filename/content/fileType and restoring its recorded geometry.
|
|
1146
1164
|
// This is async (createFileMutation), unlike every other redo path here,
|
|
1147
|
-
// so
|
|
1148
|
-
//
|
|
1149
|
-
// failure instead of rolling the redo stacks back.
|
|
1165
|
+
// so the history stacks move optimistically while the mutation runs and
|
|
1166
|
+
// restore the redo entry if creation or its metadata commit fails.
|
|
1150
1167
|
const redoFileCreation = () => {
|
|
1151
1168
|
if (!canUseOverviewHistory) return false;
|
|
1152
1169
|
const entry = fileCreationRedoStackRef.current.pop();
|
|
@@ -1162,6 +1179,77 @@ export function runRedo({
|
|
|
1162
1179
|
];
|
|
1163
1180
|
fileHistoryMutationPendingRef.current = true;
|
|
1164
1181
|
syncUndoRedoState();
|
|
1182
|
+
const restoreFailedRedo = (error: unknown) => {
|
|
1183
|
+
if (
|
|
1184
|
+
fileCreationUndoStackRef.current[
|
|
1185
|
+
fileCreationUndoStackRef.current.length - 1
|
|
1186
|
+
] === entry
|
|
1187
|
+
) {
|
|
1188
|
+
fileCreationUndoStackRef.current =
|
|
1189
|
+
fileCreationUndoStackRef.current.slice(0, -1);
|
|
1190
|
+
}
|
|
1191
|
+
historyOrderRef.current = removeRecentUndoRedoOrderKinds(
|
|
1192
|
+
historyOrderRef.current,
|
|
1193
|
+
"file-created",
|
|
1194
|
+
1,
|
|
1195
|
+
);
|
|
1196
|
+
fileCreationRedoStackRef.current = [
|
|
1197
|
+
...fileCreationRedoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
1198
|
+
entry,
|
|
1199
|
+
];
|
|
1200
|
+
redoOrderRef.current = [
|
|
1201
|
+
...redoOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
1202
|
+
"file-created",
|
|
1203
|
+
];
|
|
1204
|
+
clearPendingHistory?.();
|
|
1205
|
+
fileHistoryMutationPendingRef.current = false;
|
|
1206
|
+
syncUndoRedoState();
|
|
1207
|
+
toast.error(
|
|
1208
|
+
error instanceof Error
|
|
1209
|
+
? error.message
|
|
1210
|
+
: t("designEditor.toasts.screenDuplicateError"),
|
|
1211
|
+
);
|
|
1212
|
+
};
|
|
1213
|
+
const reconcileFailedRedo = async (
|
|
1214
|
+
error: unknown,
|
|
1215
|
+
nextId?: string,
|
|
1216
|
+
result?: Record<string, unknown>,
|
|
1217
|
+
) => {
|
|
1218
|
+
if (nextId) {
|
|
1219
|
+
try {
|
|
1220
|
+
await performDeleteFiles(
|
|
1221
|
+
[
|
|
1222
|
+
{
|
|
1223
|
+
id: nextId,
|
|
1224
|
+
filename: entry.filename,
|
|
1225
|
+
fileType: entry.fileType,
|
|
1226
|
+
content: entry.content,
|
|
1227
|
+
createdAt:
|
|
1228
|
+
typeof result?.createdAt === "string" ? result.createdAt : "",
|
|
1229
|
+
updatedAt:
|
|
1230
|
+
typeof result?.updatedAt === "string" ? result.updatedAt : "",
|
|
1231
|
+
},
|
|
1232
|
+
],
|
|
1233
|
+
{
|
|
1234
|
+
preserveHistory: true,
|
|
1235
|
+
skipFileCreationRedoPrune: true,
|
|
1236
|
+
},
|
|
1237
|
+
);
|
|
1238
|
+
} catch (cleanupError) {
|
|
1239
|
+
trace("history", "redo-file-cleanup-failed", {
|
|
1240
|
+
id,
|
|
1241
|
+
error:
|
|
1242
|
+
cleanupError instanceof Error
|
|
1243
|
+
? cleanupError.message
|
|
1244
|
+
: String(cleanupError),
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
await queryClient.invalidateQueries({
|
|
1248
|
+
queryKey: ["action", "get-design"],
|
|
1249
|
+
});
|
|
1250
|
+
}
|
|
1251
|
+
restoreFailedRedo(error);
|
|
1252
|
+
};
|
|
1165
1253
|
createFileMutation.mutate(
|
|
1166
1254
|
{
|
|
1167
1255
|
designId: id,
|
|
@@ -1170,7 +1258,7 @@ export function runRedo({
|
|
|
1170
1258
|
fileType: entry.fileType,
|
|
1171
1259
|
} as any,
|
|
1172
1260
|
{
|
|
1173
|
-
onSuccess: (result: any) => {
|
|
1261
|
+
onSuccess: async (result: any) => {
|
|
1174
1262
|
const nextId = typeof result?.id === "string" ? result.id : null;
|
|
1175
1263
|
if (nextId) {
|
|
1176
1264
|
const geometry = {
|
|
@@ -1180,6 +1268,58 @@ export function runRedo({
|
|
|
1180
1268
|
}),
|
|
1181
1269
|
...entry.geometry,
|
|
1182
1270
|
};
|
|
1271
|
+
const dataOperations: DesignDataOperation[] = [
|
|
1272
|
+
{
|
|
1273
|
+
op: "set",
|
|
1274
|
+
path: ["canvasFrames", nextId],
|
|
1275
|
+
value: geometry,
|
|
1276
|
+
},
|
|
1277
|
+
...(entry.screenMetadata
|
|
1278
|
+
? [
|
|
1279
|
+
{
|
|
1280
|
+
op: "set" as const,
|
|
1281
|
+
path: ["screenMetadata", nextId] as [string, ...string[]],
|
|
1282
|
+
value: entry.screenMetadata,
|
|
1283
|
+
},
|
|
1284
|
+
]
|
|
1285
|
+
: []),
|
|
1286
|
+
...(entry.localhostScreen
|
|
1287
|
+
? [
|
|
1288
|
+
{
|
|
1289
|
+
op: "set" as const,
|
|
1290
|
+
path: ["localhostScreens", nextId] as [
|
|
1291
|
+
string,
|
|
1292
|
+
...string[],
|
|
1293
|
+
],
|
|
1294
|
+
value: entry.localhostScreen,
|
|
1295
|
+
},
|
|
1296
|
+
]
|
|
1297
|
+
: []),
|
|
1298
|
+
];
|
|
1299
|
+
if (dataOperations.length > 0) {
|
|
1300
|
+
const nextData = applyDesignDataOperations(
|
|
1301
|
+
designDataJsonRef.current,
|
|
1302
|
+
dataOperations,
|
|
1303
|
+
);
|
|
1304
|
+
designDataJsonRef.current = nextData;
|
|
1305
|
+
queryClient.setQueryData(
|
|
1306
|
+
["action", "get-design", { id }],
|
|
1307
|
+
(old: any) => {
|
|
1308
|
+
if (!old || typeof old !== "object") return old;
|
|
1309
|
+
return { ...old, data: JSON.stringify(nextData) };
|
|
1310
|
+
},
|
|
1311
|
+
);
|
|
1312
|
+
try {
|
|
1313
|
+
await updateDesignAsync({ id, dataOperations } as any);
|
|
1314
|
+
} catch (error) {
|
|
1315
|
+
trace("history", "redo-file-data-failed", {
|
|
1316
|
+
id,
|
|
1317
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1318
|
+
});
|
|
1319
|
+
await reconcileFailedRedo(error, nextId, result);
|
|
1320
|
+
return;
|
|
1321
|
+
}
|
|
1322
|
+
}
|
|
1183
1323
|
optimisticallyInsertCreatedFile({
|
|
1184
1324
|
fileId: nextId,
|
|
1185
1325
|
filename: entry.filename,
|
|
@@ -1187,52 +1327,19 @@ export function runRedo({
|
|
|
1187
1327
|
content: entry.content,
|
|
1188
1328
|
result,
|
|
1189
1329
|
});
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1330
|
+
focusCreatedScreen(nextId, geometry, {
|
|
1331
|
+
preserveCamera: entry.preserveCamera,
|
|
1332
|
+
suppressLineupRecenter: entry.preserveCamera,
|
|
1193
1333
|
});
|
|
1194
|
-
focusCreatedScreen(nextId, geometry);
|
|
1195
1334
|
}
|
|
1196
1335
|
fileHistoryMutationPendingRef.current = false;
|
|
1197
1336
|
syncUndoRedoState();
|
|
1198
|
-
|
|
1337
|
+
await queryClient.invalidateQueries({
|
|
1199
1338
|
queryKey: ["action", "get-design"],
|
|
1200
1339
|
});
|
|
1201
1340
|
},
|
|
1202
1341
|
onError: (error: unknown) => {
|
|
1203
|
-
|
|
1204
|
-
// entry back exactly where it came from so a failed redo remains
|
|
1205
|
-
// retryable and does not leave a phantom undo operation behind.
|
|
1206
|
-
if (
|
|
1207
|
-
fileCreationUndoStackRef.current[
|
|
1208
|
-
fileCreationUndoStackRef.current.length - 1
|
|
1209
|
-
] === entry
|
|
1210
|
-
) {
|
|
1211
|
-
fileCreationUndoStackRef.current =
|
|
1212
|
-
fileCreationUndoStackRef.current.slice(0, -1);
|
|
1213
|
-
}
|
|
1214
|
-
historyOrderRef.current = removeRecentUndoRedoOrderKinds(
|
|
1215
|
-
historyOrderRef.current,
|
|
1216
|
-
"file-created",
|
|
1217
|
-
1,
|
|
1218
|
-
);
|
|
1219
|
-
fileCreationRedoStackRef.current = [
|
|
1220
|
-
...fileCreationRedoStackRef.current.slice(
|
|
1221
|
-
-(MAX_DESIGN_UNDO_STACK - 1),
|
|
1222
|
-
),
|
|
1223
|
-
entry,
|
|
1224
|
-
];
|
|
1225
|
-
redoOrderRef.current = [
|
|
1226
|
-
...redoOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
1227
|
-
"file-created",
|
|
1228
|
-
];
|
|
1229
|
-
fileHistoryMutationPendingRef.current = false;
|
|
1230
|
-
syncUndoRedoState();
|
|
1231
|
-
toast.error(
|
|
1232
|
-
error instanceof Error
|
|
1233
|
-
? error.message
|
|
1234
|
-
: t("designEditor.toasts.screenDuplicateError"),
|
|
1235
|
-
);
|
|
1342
|
+
restoreFailedRedo(error);
|
|
1236
1343
|
},
|
|
1237
1344
|
},
|
|
1238
1345
|
);
|
|
@@ -1280,6 +1387,7 @@ export function runRedo({
|
|
|
1280
1387
|
...redoOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
|
|
1281
1388
|
"file-deleted",
|
|
1282
1389
|
];
|
|
1390
|
+
clearPendingHistory?.();
|
|
1283
1391
|
}
|
|
1284
1392
|
fileHistoryMutationPendingRef.current = false;
|
|
1285
1393
|
syncUndoRedoState();
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { useActionMutation } from "@agent-native/core/client/hooks";
|
|
2
|
+
import { sourceContentHash } from "@shared/source-workspace";
|
|
2
3
|
import type { QueryClient } from "@tanstack/react-query";
|
|
3
4
|
import type { Dispatch, RefObject, SetStateAction } from "react";
|
|
4
5
|
import { toast } from "sonner";
|
|
@@ -7,7 +8,11 @@ import type { DesignSaveOutboxEntry } from "@/lib/design-save-outbox";
|
|
|
7
8
|
import { updateFileResultPersistedContent } from "@/lib/design-save-outbox";
|
|
8
9
|
import type { PatchProofState } from "@/pages/design-editor/command-types";
|
|
9
10
|
import type { FileContentSaveRequest } from "@/pages/design-editor/editor-state";
|
|
10
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
advanceLatestUnloadSaveBase,
|
|
13
|
+
prepareFileContentSaveKeepalive,
|
|
14
|
+
shouldClearLatestUnloadSave,
|
|
15
|
+
} from "@/pages/design-editor/editor-state";
|
|
11
16
|
import {
|
|
12
17
|
classifyDesignSaveFailure,
|
|
13
18
|
designSaveErrorMessage,
|
|
@@ -21,6 +26,7 @@ export interface SaveFileContentArgs {
|
|
|
21
26
|
createFileSaveOutboxEntry: (
|
|
22
27
|
pending: FileContentSaveRequest,
|
|
23
28
|
) => DesignSaveOutboxEntry | null;
|
|
29
|
+
designId?: string;
|
|
24
30
|
fileSaveChainsRef: RefObject<Record<string, Promise<void>>>;
|
|
25
31
|
journalOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<boolean>;
|
|
26
32
|
latestFileSaveForUnloadRef: RefObject<Record<string, FileContentSaveRequest>>;
|
|
@@ -43,11 +49,86 @@ export interface SaveFileContentArgs {
|
|
|
43
49
|
warnChangesWillRetry: () => void;
|
|
44
50
|
}
|
|
45
51
|
|
|
52
|
+
type FileContentSaveKeepaliveAttempt =
|
|
53
|
+
| { accepted: true; completion: Promise<unknown> }
|
|
54
|
+
| { accepted: false; completion: null };
|
|
55
|
+
|
|
56
|
+
export interface SaveFileContentKeepaliveArgs {
|
|
57
|
+
acknowledgeOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<void>;
|
|
58
|
+
createFileSaveOutboxEntry: (
|
|
59
|
+
pending: FileContentSaveRequest,
|
|
60
|
+
) => DesignSaveOutboxEntry | null;
|
|
61
|
+
journalOutboxEntry: (entry: DesignSaveOutboxEntry) => Promise<boolean>;
|
|
62
|
+
latestFileSaveForUnloadRef: RefObject<Record<string, FileContentSaveRequest>>;
|
|
63
|
+
sendKeepalive: (
|
|
64
|
+
payload: Record<string, unknown>,
|
|
65
|
+
) => FileContentSaveKeepaliveAttempt;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export function runFileContentSaveKeepalive(
|
|
69
|
+
{
|
|
70
|
+
acknowledgeOutboxEntry,
|
|
71
|
+
createFileSaveOutboxEntry,
|
|
72
|
+
journalOutboxEntry,
|
|
73
|
+
latestFileSaveForUnloadRef,
|
|
74
|
+
sendKeepalive,
|
|
75
|
+
}: SaveFileContentKeepaliveArgs,
|
|
76
|
+
pending: FileContentSaveRequest,
|
|
77
|
+
) {
|
|
78
|
+
// Keep the folded oldest-base entry durable while the direct request uses
|
|
79
|
+
// this edit's own base. If the predecessor never lands, replay must start
|
|
80
|
+
// from the oldest known version rather than the successor's base.
|
|
81
|
+
const durableEntry = createFileSaveOutboxEntry(pending);
|
|
82
|
+
const keepaliveEntry = createFileSaveOutboxEntry(
|
|
83
|
+
prepareFileContentSaveKeepalive(pending),
|
|
84
|
+
);
|
|
85
|
+
if (!durableEntry || !keepaliveEntry) return;
|
|
86
|
+
const journalPromise = journalOutboxEntry(durableEntry);
|
|
87
|
+
const attempt = sendKeepalive(keepaliveEntry.payload);
|
|
88
|
+
if (!attempt.accepted) {
|
|
89
|
+
void journalPromise.catch(() => {});
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
void attempt.completion
|
|
93
|
+
.then(async (result: unknown) => {
|
|
94
|
+
await journalPromise;
|
|
95
|
+
const persistedContentMatches = updateFileResultPersistedContent(
|
|
96
|
+
result,
|
|
97
|
+
pending.content,
|
|
98
|
+
);
|
|
99
|
+
if (!persistedContentMatches) return;
|
|
100
|
+
const resultInfo = result as { versionHash?: string } | undefined;
|
|
101
|
+
const latest = latestFileSaveForUnloadRef.current[pending.id];
|
|
102
|
+
if (shouldClearLatestUnloadSave(latest, pending)) {
|
|
103
|
+
await acknowledgeOutboxEntry(durableEntry);
|
|
104
|
+
delete latestFileSaveForUnloadRef.current[pending.id];
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
if (
|
|
108
|
+
advanceLatestUnloadSaveBase(
|
|
109
|
+
latest,
|
|
110
|
+
pending,
|
|
111
|
+
resultInfo?.versionHash ?? sourceContentHash(pending.content),
|
|
112
|
+
)
|
|
113
|
+
) {
|
|
114
|
+
const advancedOutboxEntry = latest
|
|
115
|
+
? createFileSaveOutboxEntry(latest)
|
|
116
|
+
: null;
|
|
117
|
+
if (advancedOutboxEntry) {
|
|
118
|
+
await journalOutboxEntry(advancedOutboxEntry);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
await acknowledgeOutboxEntry(durableEntry);
|
|
122
|
+
})
|
|
123
|
+
.catch(() => {});
|
|
124
|
+
}
|
|
125
|
+
|
|
46
126
|
export function runSaveFileContent(
|
|
47
127
|
{
|
|
48
128
|
acknowledgeOutboxEntry,
|
|
49
129
|
canEditDesignRef,
|
|
50
130
|
createFileSaveOutboxEntry,
|
|
131
|
+
designId,
|
|
51
132
|
fileSaveChainsRef,
|
|
52
133
|
journalOutboxEntry,
|
|
53
134
|
latestFileSaveForUnloadRef,
|
|
@@ -120,8 +201,54 @@ export function runSaveFileContent(
|
|
|
120
201
|
pending.content,
|
|
121
202
|
t("common.genericError"),
|
|
122
203
|
);
|
|
204
|
+
const latest = latestFileSaveForUnloadRef.current[pending.id];
|
|
205
|
+
if (
|
|
206
|
+
persistedContentMatches &&
|
|
207
|
+
advanceLatestUnloadSaveBase(
|
|
208
|
+
latest,
|
|
209
|
+
pending,
|
|
210
|
+
resultInfo?.versionHash ?? sourceContentHash(pending.content),
|
|
211
|
+
)
|
|
212
|
+
) {
|
|
213
|
+
const advancedOutboxEntry = latest
|
|
214
|
+
? createFileSaveOutboxEntry(latest)
|
|
215
|
+
: null;
|
|
216
|
+
if (advancedOutboxEntry)
|
|
217
|
+
await journalOutboxEntry(advancedOutboxEntry);
|
|
218
|
+
}
|
|
219
|
+
if (
|
|
220
|
+
persistedContentMatches &&
|
|
221
|
+
pending.identityMigrationSourceContent !== undefined &&
|
|
222
|
+
latestFileSaveForUnloadRef.current[pending.id] === pending
|
|
223
|
+
) {
|
|
224
|
+
// Identity repair has landed. Retire its raw-base marker so the next
|
|
225
|
+
// user edit publishes against the canonical bytes it already sees.
|
|
226
|
+
markPendingLocalFileContent(pending.id, pending.content);
|
|
227
|
+
}
|
|
123
228
|
if (persistedContentMatches && outboxEntry) {
|
|
124
229
|
await acknowledgeOutboxEntry(outboxEntry);
|
|
230
|
+
}
|
|
231
|
+
if (persistedContentMatches && designId) {
|
|
232
|
+
queryClient.setQueryData(
|
|
233
|
+
["action", "get-design", { id: designId }],
|
|
234
|
+
(old: any) => {
|
|
235
|
+
if (
|
|
236
|
+
!old ||
|
|
237
|
+
typeof old !== "object" ||
|
|
238
|
+
!Array.isArray(old.files)
|
|
239
|
+
) {
|
|
240
|
+
return old;
|
|
241
|
+
}
|
|
242
|
+
return {
|
|
243
|
+
...old,
|
|
244
|
+
files: old.files.map((file: { id?: unknown }) =>
|
|
245
|
+
file.id === pending.id
|
|
246
|
+
? { ...file, content: pending.content }
|
|
247
|
+
: file,
|
|
248
|
+
),
|
|
249
|
+
};
|
|
250
|
+
},
|
|
251
|
+
);
|
|
125
252
|
} else if (!persistedContentMatches) {
|
|
126
253
|
// A stale/no-op save result is a source conflict, not a lost
|
|
127
254
|
// connection. Drop the rejected overlay before refetch — leaving
|
|
@@ -143,7 +270,6 @@ export function runSaveFileContent(
|
|
|
143
270
|
shouldClearLatestUnloadSave(
|
|
144
271
|
latestFileSaveForUnloadRef.current[pending.id],
|
|
145
272
|
pending,
|
|
146
|
-
!persistedContentMatches,
|
|
147
273
|
)
|
|
148
274
|
) {
|
|
149
275
|
delete latestFileSaveForUnloadRef.current[pending.id];
|
|
@@ -180,6 +306,9 @@ export function runSaveFileContent(
|
|
|
180
306
|
if (failureKind === "conflict") {
|
|
181
307
|
// Roll back our optimistic bytes before the refetch can race ahead.
|
|
182
308
|
rollbackPendingLocalFileContent(pending.id, pending.content);
|
|
309
|
+
if (latestFileSaveForUnloadRef.current[pending.id] === pending) {
|
|
310
|
+
delete latestFileSaveForUnloadRef.current[pending.id];
|
|
311
|
+
}
|
|
183
312
|
}
|
|
184
313
|
void queryClient.invalidateQueries({
|
|
185
314
|
queryKey: ["action", "get-design"],
|
package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts
CHANGED
|
@@ -33,6 +33,7 @@ export interface ScreenVisualDuplicateChangeArgs {
|
|
|
33
33
|
forcePreviewFullDocument?: boolean;
|
|
34
34
|
persist?: boolean;
|
|
35
35
|
recordHistory?: boolean;
|
|
36
|
+
historyBeforeContent?: string;
|
|
36
37
|
updatedAt?: string;
|
|
37
38
|
clipboardMutation?: ClipboardContentMutationPublication;
|
|
38
39
|
},
|
|
@@ -193,6 +194,9 @@ export function runScreenVisualDuplicateChange(
|
|
|
193
194
|
});
|
|
194
195
|
return false;
|
|
195
196
|
}
|
|
196
|
-
applyFileContentUpdate(screenId, nextContent, {
|
|
197
|
+
applyFileContentUpdate(screenId, nextContent, {
|
|
198
|
+
skipPreview: true,
|
|
199
|
+
historyBeforeContent: baseContent,
|
|
200
|
+
});
|
|
197
201
|
return true;
|
|
198
202
|
}
|
|
@@ -410,6 +410,13 @@ export interface UndoArgs {
|
|
|
410
410
|
createFileMutation: ReturnType<
|
|
411
411
|
typeof useActionMutation<undefined, undefined, "create-file">
|
|
412
412
|
>;
|
|
413
|
+
optimisticallyInsertCreatedFile?: (args: {
|
|
414
|
+
fileId: string;
|
|
415
|
+
filename: string;
|
|
416
|
+
fileType: DesignFile["fileType"];
|
|
417
|
+
content: string;
|
|
418
|
+
result?: Record<string, unknown> | null;
|
|
419
|
+
}) => void;
|
|
413
420
|
deleteFileMutation: ReturnType<
|
|
414
421
|
typeof useActionMutation<undefined, undefined, "delete-file">
|
|
415
422
|
>;
|
|
@@ -419,7 +426,9 @@ export interface UndoArgs {
|
|
|
419
426
|
fileDeletionRedoStackRef: RefObject<FileDeletionHistoryEntry[]>;
|
|
420
427
|
fileDeletionUndoStackRef: RefObject<FileDeletionHistoryEntry[]>;
|
|
421
428
|
fileHistoryMutationPendingRef: RefObject<boolean>;
|
|
429
|
+
clearPendingHistory?: () => void;
|
|
422
430
|
files: DesignFile[];
|
|
431
|
+
filesRef?: RefObject<DesignFile[]>;
|
|
423
432
|
geometryRedoStackRef: RefObject<GeometryHistoryEntry[]>;
|
|
424
433
|
geometryUndoStackRef: RefObject<GeometryHistoryEntry[]>;
|
|
425
434
|
getFreshActiveContent: () => string;
|
|
@@ -547,6 +556,7 @@ export function runUndo({
|
|
|
547
556
|
contentUndoSelectionStackRef,
|
|
548
557
|
contentUndoStackRef,
|
|
549
558
|
createFileMutation,
|
|
559
|
+
optimisticallyInsertCreatedFile,
|
|
550
560
|
deleteFileMutation,
|
|
551
561
|
designDataJsonRef,
|
|
552
562
|
fileCreationRedoStackRef,
|
|
@@ -554,7 +564,9 @@ export function runUndo({
|
|
|
554
564
|
fileDeletionRedoStackRef,
|
|
555
565
|
fileDeletionUndoStackRef,
|
|
556
566
|
fileHistoryMutationPendingRef,
|
|
567
|
+
clearPendingHistory,
|
|
557
568
|
files,
|
|
569
|
+
filesRef,
|
|
558
570
|
geometryRedoStackRef,
|
|
559
571
|
geometryUndoStackRef,
|
|
560
572
|
getFreshActiveContent,
|
|
@@ -609,8 +621,9 @@ export function runUndo({
|
|
|
609
621
|
selection: GeometryHistorySelection | undefined,
|
|
610
622
|
replaySources: Record<string, string> = {},
|
|
611
623
|
) => {
|
|
624
|
+
const currentFiles = filesRef?.current ?? files;
|
|
612
625
|
const actualSources = Object.fromEntries(
|
|
613
|
-
|
|
626
|
+
currentFiles.map((file) => [
|
|
614
627
|
file.id,
|
|
615
628
|
replaySources[file.id] ?? getScreenContent(file.id),
|
|
616
629
|
]),
|
|
@@ -1478,6 +1491,17 @@ export function runUndo({
|
|
|
1478
1491
|
...file,
|
|
1479
1492
|
content: preparedFiles[index]?.content ?? file.content,
|
|
1480
1493
|
}));
|
|
1494
|
+
entry.files.forEach((file, index) => {
|
|
1495
|
+
const recreatedFile = recreatedEntry.files[index];
|
|
1496
|
+
const prepared = preparedFiles[index];
|
|
1497
|
+
if (!recreatedFile || !prepared) return;
|
|
1498
|
+
optimisticallyInsertCreatedFile?.({
|
|
1499
|
+
fileId: recreatedFile.id,
|
|
1500
|
+
filename: file.filename,
|
|
1501
|
+
fileType: file.fileType,
|
|
1502
|
+
content: prepared.content,
|
|
1503
|
+
});
|
|
1504
|
+
});
|
|
1481
1505
|
const metadataRestore = restoreMetadataAndGeometry(
|
|
1482
1506
|
restoreEntry,
|
|
1483
1507
|
recreatedEntry,
|
|
@@ -1548,6 +1572,7 @@ export function runUndo({
|
|
|
1548
1572
|
setSelectedLayerIdsState(recreatedEntry.files.map((file) => file.id));
|
|
1549
1573
|
}
|
|
1550
1574
|
} catch (error) {
|
|
1575
|
+
clearPendingHistory?.();
|
|
1551
1576
|
const cleanupResults = await Promise.allSettled(
|
|
1552
1577
|
recreatedIds.map((fileId) =>
|
|
1553
1578
|
deleteFileMutation.mutateAsync({
|