@agent-native/core 0.182.1 → 0.182.2-nightly-20260917210345
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 +1 -0
- package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
- package/corpus/templates/design/actions/apply-visual-edit.ts +1 -0
- 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 +187 -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/bridge/editor-chrome.bridge.ts +1 -0
- 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/element-classification.ts +5 -1
- package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
- package/corpus/templates/design/app/components/design/types.ts +1 -0
- 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 +250 -107
- 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/delete-files.ts +69 -47
- package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +8 -1
- package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +107 -2
- package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
- 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-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/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 +8 -2
- package/dist/automation/index.d.ts +1 -1
- package/dist/client/chat/agentkit-protocol.js +148 -34
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/package.json +4 -4
- package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +0 -66
|
@@ -272,7 +272,6 @@ import {
|
|
|
272
272
|
import { HistoryPanel } from "@/components/design/editor/HistoryPanel";
|
|
273
273
|
import type { DesignMigrationResult } from "@/components/design/editor/MakeRealDialog";
|
|
274
274
|
import { MakeRealDialog } from "@/components/design/editor/MakeRealDialog";
|
|
275
|
-
import { PendingScreenDeletionDialog } from "@/components/design/editor/PendingScreenDeletionDialog";
|
|
276
275
|
import { PendingVisualStyleWarningDialog } from "@/components/design/editor/PendingVisualStyleWarningDialog";
|
|
277
276
|
import { ReadOnlyEditorPanel } from "@/components/design/editor/ReadOnlyEditorPanel";
|
|
278
277
|
import { SaveTemplateDialog } from "@/components/design/editor/SaveTemplateDialog";
|
|
@@ -490,7 +489,6 @@ import {
|
|
|
490
489
|
discardDesignSaveOutboxEntry,
|
|
491
490
|
drainDesignSaveOutbox,
|
|
492
491
|
journalDesignSaveOutboxEntry,
|
|
493
|
-
updateFileResultPersistedContent,
|
|
494
492
|
type DesignSaveOutboxEntry,
|
|
495
493
|
} from "@/lib/design-save-outbox";
|
|
496
494
|
import { isDesignSystemUsableForGeneration } from "@/lib/design-system-data";
|
|
@@ -688,7 +686,10 @@ import { runRecordPendingLiveTextEdit } from "./design-editor/commands/record-pe
|
|
|
688
686
|
import { runRecordPendingVisualStyleEdit } from "./design-editor/commands/record-pending-visual-style-edit";
|
|
689
687
|
import { runRedo } from "./design-editor/commands/redo";
|
|
690
688
|
import { runRenderPngBlob } from "./design-editor/commands/render-png-blob";
|
|
691
|
-
import {
|
|
689
|
+
import {
|
|
690
|
+
runFileContentSaveKeepalive,
|
|
691
|
+
runSaveFileContent,
|
|
692
|
+
} from "./design-editor/commands/save-file-content";
|
|
692
693
|
import { runScreenElementSelect } from "./design-editor/commands/screen-element-select";
|
|
693
694
|
import { runScreenTextContentChange } from "./design-editor/commands/screen-text-content-change";
|
|
694
695
|
import { runScreenVisualDuplicateChange } from "./design-editor/commands/screen-visual-duplicate-change";
|
|
@@ -809,6 +810,7 @@ import {
|
|
|
809
810
|
resolveOptimisticTextDecorationLine,
|
|
810
811
|
resolveServerFiles,
|
|
811
812
|
shouldRetirePendingLocalFileContent,
|
|
813
|
+
shouldClearLatestUnloadSaveForOutboxEntry,
|
|
812
814
|
shouldSendKeepalive,
|
|
813
815
|
type OptimisticTextDecorationLineEntry,
|
|
814
816
|
type PreviewContentReplaceResult,
|
|
@@ -2505,6 +2507,11 @@ function DesignEditor() {
|
|
|
2505
2507
|
// Disable every history command while one of those mutations is in flight
|
|
2506
2508
|
// so a rapid second Cmd+Z cannot race a create against the pending delete.
|
|
2507
2509
|
const fileHistoryMutationPendingRef = useRef(false);
|
|
2510
|
+
const pendingHistoryDirectionsRef = useRef<Array<"undo" | "redo">>([]);
|
|
2511
|
+
const pendingHistoryDrainScheduledRef = useRef(false);
|
|
2512
|
+
const replayPendingHistoryRef = useRef<
|
|
2513
|
+
((direction: "undo" | "redo") => void) | null
|
|
2514
|
+
>(null);
|
|
2508
2515
|
const historyOrderRef = useRef<(UndoRedoOrderKind | "selection")[]>([]);
|
|
2509
2516
|
const redoOrderRef = useRef<(UndoRedoOrderKind | "selection")[]>([]);
|
|
2510
2517
|
// Figma parity (ground-truth Round 4): a plain selection change (no
|
|
@@ -2531,6 +2538,10 @@ function DesignEditor() {
|
|
|
2531
2538
|
redoOrderRef.current = [];
|
|
2532
2539
|
undoManagerRef.current?.clear(false, true);
|
|
2533
2540
|
}, []);
|
|
2541
|
+
const clearPendingHistoryDirections = useCallback(() => {
|
|
2542
|
+
pendingHistoryDirectionsRef.current = [];
|
|
2543
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2544
|
+
}, []);
|
|
2534
2545
|
// Figma-parity undo/redo selection restore: snapshots "what's selected
|
|
2535
2546
|
// right now" from the ref mirrors above (always current — see their doc
|
|
2536
2547
|
// comment) into a plain GeometryHistorySelection. Reads refs only, so this
|
|
@@ -2578,19 +2589,36 @@ function DesignEditor() {
|
|
|
2578
2589
|
selection.selectedLayerIds.length === 1
|
|
2579
2590
|
? selection.selectedLayerIds[0]
|
|
2580
2591
|
: undefined;
|
|
2581
|
-
|
|
2592
|
+
const restoredScreenSelectionId =
|
|
2593
|
+
selection.overviewSelectedScreenIds.length === 1 &&
|
|
2594
|
+
selection.selectedLayerIds.length === 1 &&
|
|
2595
|
+
selection.overviewSelectedScreenIds[0] === restoredLayerId
|
|
2596
|
+
? restoredLayerId
|
|
2597
|
+
: null;
|
|
2598
|
+
pendingOverviewScreenSelectionRef.current = restoredScreenSelectionId;
|
|
2582
2599
|
pendingOverviewLayerSelectionRef.current =
|
|
2583
2600
|
restoredLayerId &&
|
|
2584
|
-
codeLayerOwnerByNodeIdRef.current.has(restoredLayerId)
|
|
2601
|
+
(codeLayerOwnerByNodeIdRef.current.has(restoredLayerId) ||
|
|
2602
|
+
restoredLayerId === restoredScreenSelectionId)
|
|
2585
2603
|
? restoredLayerId
|
|
2586
2604
|
: null;
|
|
2605
|
+
if (pendingOverviewLayerSelectionRef.current) {
|
|
2606
|
+
schedulePendingOverviewLayerSelectionClear(
|
|
2607
|
+
pendingOverviewLayerSelectionRef.current,
|
|
2608
|
+
);
|
|
2609
|
+
} else {
|
|
2610
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
2611
|
+
}
|
|
2587
2612
|
setOverviewSelectedScreenIds(selection.overviewSelectedScreenIds);
|
|
2588
2613
|
setSelectedLayerIdsState(selection.selectedLayerIds);
|
|
2589
2614
|
if (selection.activeFileId) {
|
|
2590
2615
|
setActiveFileId(selection.activeFileId);
|
|
2591
2616
|
}
|
|
2592
2617
|
},
|
|
2593
|
-
[
|
|
2618
|
+
[
|
|
2619
|
+
clearPendingOverviewLayerSelectionTimer,
|
|
2620
|
+
schedulePendingOverviewLayerSelectionClear,
|
|
2621
|
+
],
|
|
2594
2622
|
);
|
|
2595
2623
|
const syncUndoRedoState = useCallback(() => {
|
|
2596
2624
|
if (fileHistoryMutationPendingRef.current) {
|
|
@@ -2598,6 +2626,33 @@ function DesignEditor() {
|
|
|
2598
2626
|
setCanRedo(false);
|
|
2599
2627
|
return;
|
|
2600
2628
|
}
|
|
2629
|
+
if (
|
|
2630
|
+
pendingHistoryDirectionsRef.current.length > 0 &&
|
|
2631
|
+
!pendingHistoryDrainScheduledRef.current
|
|
2632
|
+
) {
|
|
2633
|
+
pendingHistoryDrainScheduledRef.current = true;
|
|
2634
|
+
queueMicrotask(function drainPendingHistory() {
|
|
2635
|
+
if (fileHistoryMutationPendingRef.current) {
|
|
2636
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2637
|
+
return;
|
|
2638
|
+
}
|
|
2639
|
+
const direction = pendingHistoryDirectionsRef.current.shift();
|
|
2640
|
+
if (!direction) {
|
|
2641
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2642
|
+
return;
|
|
2643
|
+
}
|
|
2644
|
+
replayPendingHistoryRef.current?.(direction);
|
|
2645
|
+
if (fileHistoryMutationPendingRef.current) {
|
|
2646
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2647
|
+
return;
|
|
2648
|
+
}
|
|
2649
|
+
if (pendingHistoryDirectionsRef.current.length > 0) {
|
|
2650
|
+
queueMicrotask(drainPendingHistory);
|
|
2651
|
+
} else {
|
|
2652
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2653
|
+
}
|
|
2654
|
+
});
|
|
2655
|
+
}
|
|
2601
2656
|
const undoManager = undoManagerRef.current;
|
|
2602
2657
|
const canUseOverviewHistory = viewModeRef.current === "overview";
|
|
2603
2658
|
const activeHistoryFileId = activeFileIdForUndoRef.current;
|
|
@@ -3066,6 +3121,7 @@ function DesignEditor() {
|
|
|
3066
3121
|
fileDeletionUndoStackRef.current = [];
|
|
3067
3122
|
fileDeletionRedoStackRef.current = [];
|
|
3068
3123
|
fileHistoryMutationPendingRef.current = false;
|
|
3124
|
+
clearPendingHistoryDirections();
|
|
3069
3125
|
selectionUndoStackRef.current = [];
|
|
3070
3126
|
selectionRedoStackRef.current = [];
|
|
3071
3127
|
clipboardPasteUndoStackRef.current = [];
|
|
@@ -3073,7 +3129,7 @@ function DesignEditor() {
|
|
|
3073
3129
|
latestClipboardMutationContentRef.current.clear();
|
|
3074
3130
|
historyOrderRef.current = [];
|
|
3075
3131
|
redoOrderRef.current = [];
|
|
3076
|
-
}, []);
|
|
3132
|
+
}, [clearPendingHistoryDirections]);
|
|
3077
3133
|
// U12: record a screen create/duplicate as an undoable entry. Always pushed
|
|
3078
3134
|
// to the undo stack (screen creation is only meaningful in overview mode's
|
|
3079
3135
|
// shared chronological history) and clears the redo stack like any other
|
|
@@ -3679,6 +3735,7 @@ function DesignEditor() {
|
|
|
3679
3735
|
}, [reviewAgentQueueThreadIds, reviewSendingThreadId]);
|
|
3680
3736
|
const canEditDesignRef = useRef(canEditDesign);
|
|
3681
3737
|
const rawServerFilesByIdRef = useRef(new Map<string, DesignFile>());
|
|
3738
|
+
const historyFilesRef = useRef<DesignFile[]>([]);
|
|
3682
3739
|
const pendingLocalFileContentsRef = useRef<
|
|
3683
3740
|
Map<string, PendingLocalFileContent>
|
|
3684
3741
|
>(new Map());
|
|
@@ -3791,6 +3848,10 @@ function DesignEditor() {
|
|
|
3791
3848
|
skipActionQueryInvalidation: true,
|
|
3792
3849
|
});
|
|
3793
3850
|
const createFileAsync = createFileMutation.mutateAsync;
|
|
3851
|
+
const createDesignVersionMutation = useActionMutation(
|
|
3852
|
+
"create-design-version",
|
|
3853
|
+
);
|
|
3854
|
+
const createDesignVersionAsync = createDesignVersionMutation.mutateAsync;
|
|
3794
3855
|
const deleteFileMutation = useActionMutation("delete-file");
|
|
3795
3856
|
const updateDesignMutation = useActionMutation("update-design");
|
|
3796
3857
|
const updateDesignAsync = updateDesignMutation.mutateAsync;
|
|
@@ -3986,6 +4047,26 @@ function DesignEditor() {
|
|
|
3986
4047
|
toast.error(t("designEditor.toasts.saveConflict"), {
|
|
3987
4048
|
id: "design-save-conflict:outbox",
|
|
3988
4049
|
});
|
|
4050
|
+
for (const { entry } of result.rebased) {
|
|
4051
|
+
const content = entry.payload.content;
|
|
4052
|
+
if (typeof content === "string") {
|
|
4053
|
+
rollbackPendingLocalFileContent(entry.resourceId, content);
|
|
4054
|
+
}
|
|
4055
|
+
}
|
|
4056
|
+
}
|
|
4057
|
+
for (const entry of [
|
|
4058
|
+
...result.saved,
|
|
4059
|
+
...result.rebased.map(({ entry }) => entry),
|
|
4060
|
+
...result.dropped.map(({ entry }) => entry),
|
|
4061
|
+
]) {
|
|
4062
|
+
if (
|
|
4063
|
+
shouldClearLatestUnloadSaveForOutboxEntry(
|
|
4064
|
+
latestFileSaveForUnloadRef.current[entry.resourceId],
|
|
4065
|
+
entry,
|
|
4066
|
+
)
|
|
4067
|
+
) {
|
|
4068
|
+
delete latestFileSaveForUnloadRef.current[entry.resourceId];
|
|
4069
|
+
}
|
|
3989
4070
|
}
|
|
3990
4071
|
if (result.saved.length > 0 || result.rebased.length > 0) {
|
|
3991
4072
|
void queryClient.invalidateQueries({
|
|
@@ -4006,6 +4087,7 @@ function DesignEditor() {
|
|
|
4006
4087
|
}, [
|
|
4007
4088
|
designSaveActorScope,
|
|
4008
4089
|
id,
|
|
4090
|
+
rollbackPendingLocalFileContent,
|
|
4009
4091
|
queryClient,
|
|
4010
4092
|
t,
|
|
4011
4093
|
warnChangesWillRetry,
|
|
@@ -4064,7 +4146,8 @@ function DesignEditor() {
|
|
|
4064
4146
|
syncCollab: pending.syncCollab,
|
|
4065
4147
|
operationSource: pending.operationSource,
|
|
4066
4148
|
operationRevision: pending.operationRevision,
|
|
4067
|
-
expectedVersionHash:
|
|
4149
|
+
expectedVersionHash:
|
|
4150
|
+
pending.unloadExpectedVersionHash ?? pending.expectedVersionHash,
|
|
4068
4151
|
...(pending.identityMigrationSourceContent !== undefined
|
|
4069
4152
|
? { identityOnly: true }
|
|
4070
4153
|
: {}),
|
|
@@ -4137,21 +4220,39 @@ function DesignEditor() {
|
|
|
4137
4220
|
},
|
|
4138
4221
|
) => {
|
|
4139
4222
|
if (!canEditDesignRef.current) return;
|
|
4223
|
+
const queuedIdentityMigration = pendingFileSavesRef.current[fileId];
|
|
4224
|
+
const latestIdentityMigration =
|
|
4225
|
+
latestFileSaveForUnloadRef.current[fileId];
|
|
4226
|
+
const identityMigrationIsInFlight =
|
|
4227
|
+
options.identityMigrationSourceContent === undefined &&
|
|
4228
|
+
queuedIdentityMigration === undefined &&
|
|
4229
|
+
latestIdentityMigration?.identityMigrationSourceContent !== undefined;
|
|
4230
|
+
const expectedVersionHash = identityMigrationIsInFlight
|
|
4231
|
+
? sourceContentHash(latestIdentityMigration.content)
|
|
4232
|
+
: options.expectedVersionHash;
|
|
4140
4233
|
// Allocate the revision when the edit ENTERS the queue, not when its
|
|
4141
4234
|
// debounce fires. A pagehide keepalive and the ordinary chained save
|
|
4142
4235
|
// therefore carry the same idempotency key, while any newer queued edit
|
|
4143
4236
|
// is guaranteed to have a higher revision even if requests arrive at
|
|
4144
4237
|
// the server out of order.
|
|
4145
|
-
const
|
|
4238
|
+
const nextPending = coalescePendingFileContentSave(
|
|
4146
4239
|
createFileContentSaveRequest(
|
|
4147
4240
|
fileId,
|
|
4148
4241
|
content,
|
|
4149
4242
|
options.syncCollab ?? true,
|
|
4150
|
-
|
|
4243
|
+
expectedVersionHash,
|
|
4151
4244
|
options.identityMigrationSourceContent,
|
|
4152
4245
|
),
|
|
4153
4246
|
pendingFileSavesRef.current[fileId],
|
|
4154
4247
|
);
|
|
4248
|
+
const pending = {
|
|
4249
|
+
...nextPending,
|
|
4250
|
+
unloadExpectedVersionHash:
|
|
4251
|
+
pendingFileSavesRef.current[fileId]?.unloadExpectedVersionHash ??
|
|
4252
|
+
latestFileSaveForUnloadRef.current[fileId]
|
|
4253
|
+
?.unloadExpectedVersionHash ??
|
|
4254
|
+
nextPending.expectedVersionHash,
|
|
4255
|
+
};
|
|
4155
4256
|
markPendingLocalFileContent(
|
|
4156
4257
|
fileId,
|
|
4157
4258
|
content,
|
|
@@ -4253,25 +4354,17 @@ function DesignEditor() {
|
|
|
4253
4354
|
// Keep pagehide mirrors behind the same source-version guard as normal saves.
|
|
4254
4355
|
const collabLive = pending.syncCollab === false;
|
|
4255
4356
|
if (!shouldSendKeepalive(true, collabLive)) return;
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4357
|
+
runFileContentSaveKeepalive(
|
|
4358
|
+
{
|
|
4359
|
+
acknowledgeOutboxEntry,
|
|
4360
|
+
createFileSaveOutboxEntry,
|
|
4361
|
+
journalOutboxEntry,
|
|
4362
|
+
latestFileSaveForUnloadRef,
|
|
4363
|
+
sendKeepalive: (payload) =>
|
|
4364
|
+
tryCallActionKeepalive("update-file", payload as any),
|
|
4365
|
+
},
|
|
4366
|
+
pending,
|
|
4262
4367
|
);
|
|
4263
|
-
if (!attempt.accepted) return;
|
|
4264
|
-
void attempt.completion
|
|
4265
|
-
.then((result: unknown) => {
|
|
4266
|
-
if (!updateFileResultPersistedContent(result, pending.content)) {
|
|
4267
|
-
return;
|
|
4268
|
-
}
|
|
4269
|
-
return acknowledgeOutboxEntry(entry);
|
|
4270
|
-
})
|
|
4271
|
-
// Pagehide/navigation can intentionally abort this request. The
|
|
4272
|
-
// journaled operation remains available for replay, and there is no
|
|
4273
|
-
// useful visible surface for a toast while the page is leaving.
|
|
4274
|
-
.catch(() => {});
|
|
4275
4368
|
},
|
|
4276
4369
|
[acknowledgeOutboxEntry, createFileSaveOutboxEntry, journalOutboxEntry],
|
|
4277
4370
|
);
|
|
@@ -4618,6 +4711,7 @@ function DesignEditor() {
|
|
|
4618
4711
|
}),
|
|
4619
4712
|
[pendingLocalFileContentsSnapshot, serverFiles],
|
|
4620
4713
|
);
|
|
4714
|
+
historyFilesRef.current = files;
|
|
4621
4715
|
|
|
4622
4716
|
const codeLayerSourceForScreen = useCallback(
|
|
4623
4717
|
(
|
|
@@ -6233,6 +6327,29 @@ function DesignEditor() {
|
|
|
6233
6327
|
}) => {
|
|
6234
6328
|
if (!id) return;
|
|
6235
6329
|
const now = new Date().toISOString();
|
|
6330
|
+
const optimisticFile: DesignFile = {
|
|
6331
|
+
id: args.fileId,
|
|
6332
|
+
filename: args.filename,
|
|
6333
|
+
fileType: args.fileType,
|
|
6334
|
+
content: annotateScreenHtmlForPersist(args.content, args.fileType),
|
|
6335
|
+
createdAt:
|
|
6336
|
+
typeof args.result?.createdAt === "string"
|
|
6337
|
+
? args.result.createdAt
|
|
6338
|
+
: now,
|
|
6339
|
+
updatedAt:
|
|
6340
|
+
typeof args.result?.updatedAt === "string"
|
|
6341
|
+
? args.result.updatedAt
|
|
6342
|
+
: now,
|
|
6343
|
+
};
|
|
6344
|
+
// Queued history replay drains in a microtask, before React can publish
|
|
6345
|
+
// the query-cache update through the rendered files projection.
|
|
6346
|
+
historyFilesRef.current = historyFilesRef.current.some(
|
|
6347
|
+
(file) => file.id === args.fileId,
|
|
6348
|
+
)
|
|
6349
|
+
? historyFilesRef.current.map((file) =>
|
|
6350
|
+
file.id === args.fileId ? optimisticFile : file,
|
|
6351
|
+
)
|
|
6352
|
+
: [...historyFilesRef.current, optimisticFile];
|
|
6236
6353
|
void queryClient.cancelQueries({
|
|
6237
6354
|
queryKey: ["action", "get-design", { id }],
|
|
6238
6355
|
exact: true,
|
|
@@ -6241,20 +6358,6 @@ function DesignEditor() {
|
|
|
6241
6358
|
if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
|
|
6242
6359
|
return old;
|
|
6243
6360
|
}
|
|
6244
|
-
const optimisticFile: DesignFile = {
|
|
6245
|
-
id: args.fileId,
|
|
6246
|
-
filename: args.filename,
|
|
6247
|
-
fileType: args.fileType,
|
|
6248
|
-
content: annotateScreenHtmlForPersist(args.content, args.fileType),
|
|
6249
|
-
createdAt:
|
|
6250
|
-
typeof args.result?.createdAt === "string"
|
|
6251
|
-
? args.result.createdAt
|
|
6252
|
-
: now,
|
|
6253
|
-
updatedAt:
|
|
6254
|
-
typeof args.result?.updatedAt === "string"
|
|
6255
|
-
? args.result.updatedAt
|
|
6256
|
-
: now,
|
|
6257
|
-
};
|
|
6258
6361
|
return {
|
|
6259
6362
|
...old,
|
|
6260
6363
|
files: old.files.some((file: DesignFile) => file.id === args.fileId)
|
|
@@ -7543,21 +7646,21 @@ function DesignEditor() {
|
|
|
7543
7646
|
freshActiveContent: getFreshActiveFileContent({
|
|
7544
7647
|
activeContent,
|
|
7545
7648
|
pendingContent: activeFile?.id
|
|
7546
|
-
?
|
|
7649
|
+
? (latestFileSaveForUnloadRef.current[activeFile.id]?.content ??
|
|
7650
|
+
pendingLocalFileContentsRef.current.get(activeFile.id)?.content)
|
|
7547
7651
|
: null,
|
|
7548
7652
|
latestContent: latestActiveContentRef.current,
|
|
7549
7653
|
lastLocalContent: lastLocalContentRef.current,
|
|
7550
7654
|
}),
|
|
7551
7655
|
fileContentById,
|
|
7552
|
-
// Same-tick freshness
|
|
7553
|
-
//
|
|
7554
|
-
//
|
|
7555
|
-
//
|
|
7556
|
-
// it, the second message of a bridge drop sequence (auto-layout
|
|
7557
|
-
// conversion style → structure move) rebased off stale content and
|
|
7558
|
-
// clobbered the first message's edit.
|
|
7656
|
+
// Same-tick freshness (see the param's doc on getFreshScreenContent):
|
|
7657
|
+
// applyFileContentUpdate writes these refs synchronously, while the
|
|
7658
|
+
// files-derived map above only refreshes on the next render. The
|
|
7659
|
+
// save outbox is also authoritative while reconciliation lags.
|
|
7559
7660
|
pendingContent:
|
|
7560
|
-
|
|
7661
|
+
latestFileSaveForUnloadRef.current[screenId]?.content ??
|
|
7662
|
+
pendingLocalFileContentsRef.current.get(screenId)?.content ??
|
|
7663
|
+
null,
|
|
7561
7664
|
}),
|
|
7562
7665
|
{
|
|
7563
7666
|
fileId: screenId,
|
|
@@ -9131,6 +9234,7 @@ function DesignEditor() {
|
|
|
9131
9234
|
refreshPreview?: boolean;
|
|
9132
9235
|
skipPreview?: boolean;
|
|
9133
9236
|
forcePreviewFullDocument?: boolean;
|
|
9237
|
+
immediateSave?: boolean;
|
|
9134
9238
|
persist?: boolean;
|
|
9135
9239
|
recordHistory?: boolean;
|
|
9136
9240
|
historyBeforeContent?: string;
|
|
@@ -14668,14 +14772,11 @@ function DesignEditor() {
|
|
|
14668
14772
|
handleDeleteSelection();
|
|
14669
14773
|
}, [handleCopySelection, handleDeleteSelection]);
|
|
14670
14774
|
|
|
14671
|
-
const
|
|
14672
|
-
|
|
14673
|
-
|
|
14674
|
-
|
|
14675
|
-
|
|
14676
|
-
// Covers the gap before React re-renders after confirm — a second activation
|
|
14677
|
-
// must not start another delete while the first mutation is still settling.
|
|
14678
|
-
const screenDeletionConfirmingRef = useRef(false);
|
|
14775
|
+
const captureDeleteHistoryCheckpoint = useCallback(async () => {
|
|
14776
|
+
if (!id) throw new Error(t("common.genericError"));
|
|
14777
|
+
const version = await createDesignVersionAsync({ designId: id });
|
|
14778
|
+
return version.id;
|
|
14779
|
+
}, [createDesignVersionAsync, id, t]);
|
|
14679
14780
|
|
|
14680
14781
|
const performDeleteFiles = useCallback(
|
|
14681
14782
|
(
|
|
@@ -14717,6 +14818,8 @@ function DesignEditor() {
|
|
|
14717
14818
|
fileCreationUndoStackRef,
|
|
14718
14819
|
fileDeletionUndoStackRef,
|
|
14719
14820
|
fileHistoryMutationPendingRef,
|
|
14821
|
+
captureHistoryCheckpoint: captureDeleteHistoryCheckpoint,
|
|
14822
|
+
clearPendingHistory: clearPendingHistoryDirections,
|
|
14720
14823
|
files,
|
|
14721
14824
|
geometryRedoStackRef,
|
|
14722
14825
|
geometryUndoStackRef,
|
|
@@ -14746,7 +14849,9 @@ function DesignEditor() {
|
|
|
14746
14849
|
[
|
|
14747
14850
|
activeFile,
|
|
14748
14851
|
canvasFrameGeometryById,
|
|
14852
|
+
captureDeleteHistoryCheckpoint,
|
|
14749
14853
|
clearRedoStacks,
|
|
14854
|
+
clearPendingHistoryDirections,
|
|
14750
14855
|
deleteFileMutation,
|
|
14751
14856
|
queryClient,
|
|
14752
14857
|
syncUndoRedoState,
|
|
@@ -14754,6 +14859,38 @@ function DesignEditor() {
|
|
|
14754
14859
|
writeFrameGeometrySnapshot,
|
|
14755
14860
|
],
|
|
14756
14861
|
);
|
|
14862
|
+
const handleDeleteInlineFile = useCallback(
|
|
14863
|
+
async (fileId: string) => {
|
|
14864
|
+
if (!canEditDesign) return;
|
|
14865
|
+
if (fileHistoryMutationPendingRef.current) {
|
|
14866
|
+
throw new Error(t("common.genericError"));
|
|
14867
|
+
}
|
|
14868
|
+
const queryKey = ["action", "get-design", { id }] as const;
|
|
14869
|
+
let file =
|
|
14870
|
+
files.find((candidate) => candidate.id === fileId) ??
|
|
14871
|
+
rawServerFilesByIdRef.current.get(fileId) ??
|
|
14872
|
+
historyFilesRef.current.find((candidate) => candidate.id === fileId);
|
|
14873
|
+
if (!file) {
|
|
14874
|
+
await queryClient.refetchQueries({ queryKey, exact: true });
|
|
14875
|
+
const refreshed = queryClient.getQueryData<{
|
|
14876
|
+
files?: DesignFile[];
|
|
14877
|
+
}>(queryKey);
|
|
14878
|
+
file = refreshed?.files?.find((candidate) => candidate.id === fileId);
|
|
14879
|
+
}
|
|
14880
|
+
if (!file) throw new Error(t("common.genericError"));
|
|
14881
|
+
|
|
14882
|
+
const targetFile = file;
|
|
14883
|
+
let deleted = false;
|
|
14884
|
+
await performDeleteFiles([targetFile], {
|
|
14885
|
+
recordDeletionHistory: true,
|
|
14886
|
+
onMutationSettled: (deletedFiles) => {
|
|
14887
|
+
deleted = deletedFiles.some((candidate) => candidate.id === fileId);
|
|
14888
|
+
},
|
|
14889
|
+
});
|
|
14890
|
+
if (!deleted) throw new Error(t("common.genericError"));
|
|
14891
|
+
},
|
|
14892
|
+
[canEditDesign, files, id, performDeleteFiles, queryClient, t],
|
|
14893
|
+
);
|
|
14757
14894
|
|
|
14758
14895
|
// MultiScreenCanvas consumes arrow keys in the capture phase while a frame
|
|
14759
14896
|
// is selected, and an in-screen element selection keeps its screen there.
|
|
@@ -14770,19 +14907,20 @@ function DesignEditor() {
|
|
|
14770
14907
|
return true;
|
|
14771
14908
|
}, [files, selectedElement, selectedLayerIdsState]);
|
|
14772
14909
|
|
|
14773
|
-
//
|
|
14774
|
-
//
|
|
14910
|
+
// Delete selected screens immediately and record the rows as one grouped
|
|
14911
|
+
// history entry so Cmd+Z can recreate every selected screen.
|
|
14775
14912
|
// Always returns false to MultiScreenCanvas so it never performs its own
|
|
14776
|
-
// synchronous local frame-geometry delete ahead of the
|
|
14913
|
+
// synchronous local frame-geometry delete ahead of the file mutation.
|
|
14777
14914
|
const handleDeleteOverviewSelection = useCallback(
|
|
14778
14915
|
(selectedIds: string[]) => {
|
|
14779
14916
|
if (!canEditDesign) return false;
|
|
14917
|
+
if (fileHistoryMutationPendingRef.current) return false;
|
|
14780
14918
|
// BUG-DELETE-OVERVIEW-COLLISION: MultiScreenCanvas consumes Delete in
|
|
14781
14919
|
// the capture phase whenever a frame is selected, and an in-screen
|
|
14782
14920
|
// element selection keeps its screen there — so deleting one node in a
|
|
14783
14921
|
// screen offered to delete the whole screen and the editor's own
|
|
14784
14922
|
// onDelete hotkey never ran. Route to the element delete instead; the
|
|
14785
|
-
//
|
|
14923
|
+
// Screen deletion is only for a real frame selection.
|
|
14786
14924
|
if (
|
|
14787
14925
|
overviewSelectionTargetsElement({
|
|
14788
14926
|
selectedElement,
|
|
@@ -14793,59 +14931,38 @@ function DesignEditor() {
|
|
|
14793
14931
|
handleDeleteSelection();
|
|
14794
14932
|
return false;
|
|
14795
14933
|
}
|
|
14796
|
-
if (!selectedIds.length ||
|
|
14934
|
+
if (!selectedIds.length || overviewScreens.length <= 1) return false;
|
|
14797
14935
|
|
|
14798
14936
|
const selectedIdSet = new Set(selectedIds);
|
|
14799
|
-
const
|
|
14937
|
+
const overviewScreenIds = new Set(
|
|
14938
|
+
overviewScreens.map((screen) => screen.id),
|
|
14939
|
+
);
|
|
14940
|
+
const selectedFiles = files.filter(
|
|
14941
|
+
(file) => selectedIdSet.has(file.id) && overviewScreenIds.has(file.id),
|
|
14942
|
+
);
|
|
14800
14943
|
if (!selectedFiles.length) return false;
|
|
14801
14944
|
|
|
14802
14945
|
const maxDeleteCount =
|
|
14803
|
-
selectedFiles.length >=
|
|
14804
|
-
? Math.max(0,
|
|
14946
|
+
selectedFiles.length >= overviewScreens.length
|
|
14947
|
+
? Math.max(0, overviewScreens.length - 1)
|
|
14805
14948
|
: selectedFiles.length;
|
|
14806
14949
|
const filesToDelete = selectedFiles.slice(0, maxDeleteCount);
|
|
14807
14950
|
if (!filesToDelete.length) return false;
|
|
14808
14951
|
|
|
14809
|
-
|
|
14952
|
+
performDeleteFiles(filesToDelete, { recordDeletionHistory: true });
|
|
14810
14953
|
return false;
|
|
14811
14954
|
},
|
|
14812
14955
|
[
|
|
14813
14956
|
canEditDesign,
|
|
14814
14957
|
files,
|
|
14958
|
+
performDeleteFiles,
|
|
14815
14959
|
handleDeleteSelection,
|
|
14960
|
+
overviewScreens,
|
|
14816
14961
|
selectedElement,
|
|
14817
14962
|
selectedLayerIdsState,
|
|
14818
14963
|
],
|
|
14819
14964
|
);
|
|
14820
14965
|
|
|
14821
|
-
const handleCancelScreenDeletion = useCallback(() => {
|
|
14822
|
-
if (screenDeletionConfirmingRef.current || screenDeletionConfirming) return;
|
|
14823
|
-
setPendingScreenDeletion(null);
|
|
14824
|
-
}, [screenDeletionConfirming]);
|
|
14825
|
-
|
|
14826
|
-
const handleConfirmScreenDeletion = useCallback(() => {
|
|
14827
|
-
const pending = pendingScreenDeletion;
|
|
14828
|
-
if (
|
|
14829
|
-
!pending ||
|
|
14830
|
-
screenDeletionConfirmingRef.current ||
|
|
14831
|
-
screenDeletionConfirming
|
|
14832
|
-
) {
|
|
14833
|
-
return;
|
|
14834
|
-
}
|
|
14835
|
-
// Keep the dialog locked until delete settles so a rapid second confirm
|
|
14836
|
-
// cannot start a concurrent deletion / duplicate history entry.
|
|
14837
|
-
screenDeletionConfirmingRef.current = true;
|
|
14838
|
-
setScreenDeletionConfirming(true);
|
|
14839
|
-
setPendingScreenDeletion(null);
|
|
14840
|
-
performDeleteFiles(pending.files, {
|
|
14841
|
-
recordDeletionHistory: true,
|
|
14842
|
-
onMutationSettled: () => {
|
|
14843
|
-
screenDeletionConfirmingRef.current = false;
|
|
14844
|
-
setScreenDeletionConfirming(false);
|
|
14845
|
-
},
|
|
14846
|
-
});
|
|
14847
|
-
}, [pendingScreenDeletion, performDeleteFiles, screenDeletionConfirming]);
|
|
14848
|
-
|
|
14849
14966
|
// ── Props/animation clipboard, transforms, nudge ───────────────────────────
|
|
14850
14967
|
const handleCopyProps = useCallback(() => {
|
|
14851
14968
|
if (!selectedElement) return;
|
|
@@ -15257,7 +15374,9 @@ function DesignEditor() {
|
|
|
15257
15374
|
fileDeletionRedoStackRef,
|
|
15258
15375
|
fileDeletionUndoStackRef,
|
|
15259
15376
|
fileHistoryMutationPendingRef,
|
|
15377
|
+
clearPendingHistory: clearPendingHistoryDirections,
|
|
15260
15378
|
files,
|
|
15379
|
+
filesRef: historyFilesRef,
|
|
15261
15380
|
geometryRedoStackRef,
|
|
15262
15381
|
geometryUndoStackRef,
|
|
15263
15382
|
getFreshActiveContent,
|
|
@@ -15272,6 +15391,7 @@ function DesignEditor() {
|
|
|
15272
15391
|
localContentRedoStackRef,
|
|
15273
15392
|
localContentUndoStackRef,
|
|
15274
15393
|
markPendingLocalFileContent,
|
|
15394
|
+
optimisticallyInsertCreatedFile,
|
|
15275
15395
|
pendingLiveNonStyleEditsRef,
|
|
15276
15396
|
pendingLiveNonStyleRedoStackRef,
|
|
15277
15397
|
pendingLiveNonStyleUndoStackRef,
|
|
@@ -15316,6 +15436,7 @@ function DesignEditor() {
|
|
|
15316
15436
|
applyGeometryHistoryContentChangesRef,
|
|
15317
15437
|
applyLocalContentUpdate,
|
|
15318
15438
|
canEditDesign,
|
|
15439
|
+
clearPendingHistoryDirections,
|
|
15319
15440
|
createFileMutation,
|
|
15320
15441
|
deleteFileMutation,
|
|
15321
15442
|
files,
|
|
@@ -15325,6 +15446,7 @@ function DesignEditor() {
|
|
|
15325
15446
|
isSynced,
|
|
15326
15447
|
liveScreenSnapshotsById,
|
|
15327
15448
|
markPendingLocalFileContent,
|
|
15449
|
+
optimisticallyInsertCreatedFile,
|
|
15328
15450
|
performDeleteFiles,
|
|
15329
15451
|
publishAuthoritativeClipboardMutation,
|
|
15330
15452
|
queryClient,
|
|
@@ -15369,7 +15491,9 @@ function DesignEditor() {
|
|
|
15369
15491
|
fileDeletionRedoStackRef,
|
|
15370
15492
|
fileDeletionUndoStackRef,
|
|
15371
15493
|
fileHistoryMutationPendingRef,
|
|
15494
|
+
clearPendingHistory: clearPendingHistoryDirections,
|
|
15372
15495
|
files,
|
|
15496
|
+
filesRef: historyFilesRef,
|
|
15373
15497
|
focusCreatedScreen,
|
|
15374
15498
|
geometryRedoStackRef,
|
|
15375
15499
|
geometryUndoStackRef,
|
|
@@ -15437,6 +15561,7 @@ function DesignEditor() {
|
|
|
15437
15561
|
applyGeometryHistoryContentChangesRef,
|
|
15438
15562
|
applyLocalContentUpdate,
|
|
15439
15563
|
canEditDesign,
|
|
15564
|
+
clearPendingHistoryDirections,
|
|
15440
15565
|
createFileMutation,
|
|
15441
15566
|
deleteRuntimeElement,
|
|
15442
15567
|
files,
|
|
@@ -15472,6 +15597,10 @@ function DesignEditor() {
|
|
|
15472
15597
|
});
|
|
15473
15598
|
historyDispatchRef.current = { undo: runCurrentUndo, redo: runCurrentRedo };
|
|
15474
15599
|
const dispatchHistory = useCallback((direction: "undo" | "redo") => {
|
|
15600
|
+
if (fileHistoryMutationPendingRef.current) {
|
|
15601
|
+
pendingHistoryDirectionsRef.current.push(direction);
|
|
15602
|
+
return;
|
|
15603
|
+
}
|
|
15475
15604
|
const pendingCountBefore =
|
|
15476
15605
|
pendingVisualStyleEditsRef.current.length +
|
|
15477
15606
|
pendingLiveNonStyleEditsRef.current.length;
|
|
@@ -15490,6 +15619,7 @@ function DesignEditor() {
|
|
|
15490
15619
|
: run();
|
|
15491
15620
|
if (pending) void pending.catch((error) => toast.error(String(error)));
|
|
15492
15621
|
}, []);
|
|
15622
|
+
replayPendingHistoryRef.current = dispatchHistory;
|
|
15493
15623
|
const handleUndo = useCallback(
|
|
15494
15624
|
() => dispatchHistory("undo"),
|
|
15495
15625
|
[dispatchHistory],
|
|
@@ -16733,7 +16863,7 @@ function DesignEditor() {
|
|
|
16733
16863
|
// never reaches MultiScreenCanvas' capture-phase Delete.
|
|
16734
16864
|
onDelete: canEditDesign
|
|
16735
16865
|
? () => {
|
|
16736
|
-
handleDeleteOverviewSelection(
|
|
16866
|
+
handleDeleteOverviewSelection(overviewSelectedScreenIds);
|
|
16737
16867
|
}
|
|
16738
16868
|
: undefined,
|
|
16739
16869
|
// Screen and element rows share the Layers panel's inline rename editor.
|
|
@@ -19420,6 +19550,7 @@ function DesignEditor() {
|
|
|
19420
19550
|
screenStylePreviewRef.current.delete(screenId);
|
|
19421
19551
|
}
|
|
19422
19552
|
applyFileContentUpdate(screenId, next, {
|
|
19553
|
+
immediateSave: !previewOnly,
|
|
19423
19554
|
persist: !previewOnly,
|
|
19424
19555
|
recordHistory: !previewOnly,
|
|
19425
19556
|
historyBeforeContent: previewOnly ? undefined : historyBeforeContent,
|
|
@@ -22999,8 +23130,24 @@ function DesignEditor() {
|
|
|
22999
23130
|
<DropdownMenuShortcut>{shortcut("$mod+d")}</DropdownMenuShortcut>
|
|
23000
23131
|
</DropdownMenuItem>
|
|
23001
23132
|
<DropdownMenuItem
|
|
23002
|
-
onClick={
|
|
23003
|
-
|
|
23133
|
+
onClick={() => {
|
|
23134
|
+
if (viewMode === "overview") {
|
|
23135
|
+
handleDeleteOverviewSelection(overviewSelectedScreenIds);
|
|
23136
|
+
} else {
|
|
23137
|
+
handleDeleteSelection();
|
|
23138
|
+
}
|
|
23139
|
+
}}
|
|
23140
|
+
disabled={
|
|
23141
|
+
viewMode === "overview"
|
|
23142
|
+
? !overviewSelectionTargetsElement({
|
|
23143
|
+
selectedElement,
|
|
23144
|
+
selectedLayerIds: selectedLayerIdsState,
|
|
23145
|
+
fileIds: files.map((file) => file.id),
|
|
23146
|
+
}) &&
|
|
23147
|
+
(overviewScreens.length <= 1 ||
|
|
23148
|
+
overviewSelectedScreenIds.length === 0)
|
|
23149
|
+
: !selectedElement && !activeFile
|
|
23150
|
+
}
|
|
23004
23151
|
>
|
|
23005
23152
|
{"Delete" /* i18n-ignore design menu command */}
|
|
23006
23153
|
<DropdownMenuShortcut>⌫</DropdownMenuShortcut>
|
|
@@ -24192,6 +24339,9 @@ function DesignEditor() {
|
|
|
24192
24339
|
selectedNodeId={selectedElementLayerId}
|
|
24193
24340
|
selectedSelector={selectedCanvasSelector}
|
|
24194
24341
|
canEdit={canEditDesign}
|
|
24342
|
+
onDeleteInlineFile={
|
|
24343
|
+
canEditDesign ? handleDeleteInlineFile : undefined
|
|
24344
|
+
}
|
|
24195
24345
|
onActiveFileChange={setActiveCodeFile}
|
|
24196
24346
|
localhostConnections={workbenchLocalhostConnections}
|
|
24197
24347
|
onRequestLocalWriteConsent={
|
|
@@ -24365,7 +24515,7 @@ function DesignEditor() {
|
|
|
24365
24515
|
canDelete={Boolean(
|
|
24366
24516
|
canEditDesign &&
|
|
24367
24517
|
(selectedElement ||
|
|
24368
|
-
(selectedScreenIds.length > 0 &&
|
|
24518
|
+
(selectedScreenIds.length > 0 && overviewScreens.length > 1)),
|
|
24369
24519
|
)}
|
|
24370
24520
|
canReorder={canEditDesign && Boolean(selectedElement)}
|
|
24371
24521
|
// Rename is only offered for a single selectable layer target;
|
|
@@ -25464,13 +25614,6 @@ function DesignEditor() {
|
|
|
25464
25614
|
}}
|
|
25465
25615
|
/>
|
|
25466
25616
|
|
|
25467
|
-
<PendingScreenDeletionDialog
|
|
25468
|
-
pendingScreenDeletion={pendingScreenDeletion}
|
|
25469
|
-
onCancel={handleCancelScreenDeletion}
|
|
25470
|
-
onConfirm={handleConfirmScreenDeletion}
|
|
25471
|
-
confirming={screenDeletionConfirming}
|
|
25472
|
-
/>
|
|
25473
|
-
|
|
25474
25617
|
{/* ── Render: motion dock ── */}
|
|
25475
25618
|
{/* Motion dock (§6.3) — bottom timeline mounted while opening, open, or
|
|
25476
25619
|
closing. Canvas remains visible above.
|