@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
|
@@ -111,8 +111,10 @@ import {
|
|
|
111
111
|
import { linkedComponentRootForNode } from "@shared/component-links";
|
|
112
112
|
import {
|
|
113
113
|
componentNodeIdMatches,
|
|
114
|
+
extractProps,
|
|
114
115
|
isComponentInstance,
|
|
115
116
|
isComponentInstanceForInstanceActions,
|
|
117
|
+
propNameToDataAttribute,
|
|
116
118
|
} from "@shared/component-model";
|
|
117
119
|
import { getOverviewScreenFileIds } from "@shared/design-files";
|
|
118
120
|
import { DESIGN_REVIEW_PANEL } from "@shared/design-flags";
|
|
@@ -126,6 +128,7 @@ import { assertDesignHtmlEditIntegrity } from "@shared/html-integrity";
|
|
|
126
128
|
import type { InteractionState } from "@shared/interaction-states";
|
|
127
129
|
import { DESIGN_TWEAKS } from "@shared/labs";
|
|
128
130
|
import type { LayoutGrid } from "@shared/layout-grid";
|
|
131
|
+
import { readLiteralJsxPropsAtAnchor } from "@shared/local-jsx-visual-edit";
|
|
129
132
|
import { countLockedLayersAcrossFiles } from "@shared/locked-layers";
|
|
130
133
|
import type { MotionAnimationClip, MotionEase } from "@shared/motion-timeline";
|
|
131
134
|
import {
|
|
@@ -162,6 +165,7 @@ import {
|
|
|
162
165
|
import {
|
|
163
166
|
isRunningAppSourceType,
|
|
164
167
|
normalizeDesignSourceType,
|
|
168
|
+
sourcePositionPrecision,
|
|
165
169
|
} from "@shared/source-mode";
|
|
166
170
|
import { sourceContentHash } from "@shared/source-workspace";
|
|
167
171
|
import {
|
|
@@ -272,7 +276,6 @@ import {
|
|
|
272
276
|
import { HistoryPanel } from "@/components/design/editor/HistoryPanel";
|
|
273
277
|
import type { DesignMigrationResult } from "@/components/design/editor/MakeRealDialog";
|
|
274
278
|
import { MakeRealDialog } from "@/components/design/editor/MakeRealDialog";
|
|
275
|
-
import { PendingScreenDeletionDialog } from "@/components/design/editor/PendingScreenDeletionDialog";
|
|
276
279
|
import { PendingVisualStyleWarningDialog } from "@/components/design/editor/PendingVisualStyleWarningDialog";
|
|
277
280
|
import { ReadOnlyEditorPanel } from "@/components/design/editor/ReadOnlyEditorPanel";
|
|
278
281
|
import { SaveTemplateDialog } from "@/components/design/editor/SaveTemplateDialog";
|
|
@@ -490,7 +493,6 @@ import {
|
|
|
490
493
|
discardDesignSaveOutboxEntry,
|
|
491
494
|
drainDesignSaveOutbox,
|
|
492
495
|
journalDesignSaveOutboxEntry,
|
|
493
|
-
updateFileResultPersistedContent,
|
|
494
496
|
type DesignSaveOutboxEntry,
|
|
495
497
|
} from "@/lib/design-save-outbox";
|
|
496
498
|
import { isDesignSystemUsableForGeneration } from "@/lib/design-system-data";
|
|
@@ -688,7 +690,10 @@ import { runRecordPendingLiveTextEdit } from "./design-editor/commands/record-pe
|
|
|
688
690
|
import { runRecordPendingVisualStyleEdit } from "./design-editor/commands/record-pending-visual-style-edit";
|
|
689
691
|
import { runRedo } from "./design-editor/commands/redo";
|
|
690
692
|
import { runRenderPngBlob } from "./design-editor/commands/render-png-blob";
|
|
691
|
-
import {
|
|
693
|
+
import {
|
|
694
|
+
runFileContentSaveKeepalive,
|
|
695
|
+
runSaveFileContent,
|
|
696
|
+
} from "./design-editor/commands/save-file-content";
|
|
692
697
|
import { runScreenElementSelect } from "./design-editor/commands/screen-element-select";
|
|
693
698
|
import { runScreenTextContentChange } from "./design-editor/commands/screen-text-content-change";
|
|
694
699
|
import { runScreenVisualDuplicateChange } from "./design-editor/commands/screen-visual-duplicate-change";
|
|
@@ -780,6 +785,7 @@ import {
|
|
|
780
785
|
designSelectionStateKeys,
|
|
781
786
|
isSupersededSelectionEcho,
|
|
782
787
|
reloadRunningAppPreviewFrames,
|
|
788
|
+
runtimeMultiplicityForElementProvenance,
|
|
783
789
|
withMeasuredGeometry,
|
|
784
790
|
} from "./design-editor/editor-helpers";
|
|
785
791
|
import {
|
|
@@ -809,6 +815,7 @@ import {
|
|
|
809
815
|
resolveOptimisticTextDecorationLine,
|
|
810
816
|
resolveServerFiles,
|
|
811
817
|
shouldRetirePendingLocalFileContent,
|
|
818
|
+
shouldClearLatestUnloadSaveForOutboxEntry,
|
|
812
819
|
shouldSendKeepalive,
|
|
813
820
|
type OptimisticTextDecorationLineEntry,
|
|
814
821
|
type PreviewContentReplaceResult,
|
|
@@ -931,6 +938,7 @@ import {
|
|
|
931
938
|
formatVisualEditClipboardPrompt,
|
|
932
939
|
getPendingVisualEditCount,
|
|
933
940
|
pendingVisualStyleGestureIdForPhase,
|
|
941
|
+
projectRelativeSourcePath,
|
|
934
942
|
type PendingLiveLayerStateEdit,
|
|
935
943
|
type PendingLiveNonStyleEdit,
|
|
936
944
|
type PendingLiveNonStyleUndoEntry,
|
|
@@ -1053,6 +1061,21 @@ function pageHasWebMcpHost(): boolean {
|
|
|
1053
1061
|
return hasNativeWebMcpHost();
|
|
1054
1062
|
}
|
|
1055
1063
|
|
|
1064
|
+
type RequestLocalhostWrite = (opts: {
|
|
1065
|
+
files: string[];
|
|
1066
|
+
onGranted: LocalhostWriteConsentPayload["onGranted"];
|
|
1067
|
+
onCancel?: () => void;
|
|
1068
|
+
}) => void;
|
|
1069
|
+
|
|
1070
|
+
function isLocalhostWriteConsentError(error: unknown): boolean {
|
|
1071
|
+
return (
|
|
1072
|
+
error instanceof Error &&
|
|
1073
|
+
(error.name === "LocalWriteConsentRequiredError" ||
|
|
1074
|
+
error.name === "WriteConsentRequiredError" ||
|
|
1075
|
+
/write-consent grant|grant expired/i.test(error.message))
|
|
1076
|
+
);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1056
1079
|
function readRenderedLayerInfo(
|
|
1057
1080
|
owner: {
|
|
1058
1081
|
fileId: string;
|
|
@@ -1135,6 +1158,7 @@ function DesignEditor() {
|
|
|
1135
1158
|
const isSignedIn = Boolean(session?.email);
|
|
1136
1159
|
const sessionResolved = !sessionLoading;
|
|
1137
1160
|
const designSaveActorScope = session?.userId ?? "anonymous";
|
|
1161
|
+
const requestLocalhostWriteRef = useRef<RequestLocalhostWrite | null>(null);
|
|
1138
1162
|
const navigate = useNavigate();
|
|
1139
1163
|
const location = useLocation();
|
|
1140
1164
|
// Long overview sessions (pan/zoom/select/undo across many screens) build
|
|
@@ -1326,6 +1350,14 @@ function DesignEditor() {
|
|
|
1326
1350
|
paddingScreenPx?: number;
|
|
1327
1351
|
} | null>(null);
|
|
1328
1352
|
const cameraCommandNonceRef = useRef(0);
|
|
1353
|
+
const [suppressLineupRecenter, setSuppressLineupRecenter] = useState<{
|
|
1354
|
+
fromCount: number;
|
|
1355
|
+
addedCount: number;
|
|
1356
|
+
nonce: number;
|
|
1357
|
+
} | null>(null);
|
|
1358
|
+
const suppressLineupRecenterNonceRef = useRef(0);
|
|
1359
|
+
const [optimisticFrameGeometryById, setOptimisticFrameGeometryById] =
|
|
1360
|
+
useState<Record<string, FrameGeometry>>({});
|
|
1329
1361
|
const measuredScreenHeightByIdRef = useRef<Record<string, number>>({});
|
|
1330
1362
|
const handleOverviewPrimaryContentHeightChange = useCallback(
|
|
1331
1363
|
(screenId: string, heightPx: number) => {
|
|
@@ -2505,6 +2537,11 @@ function DesignEditor() {
|
|
|
2505
2537
|
// Disable every history command while one of those mutations is in flight
|
|
2506
2538
|
// so a rapid second Cmd+Z cannot race a create against the pending delete.
|
|
2507
2539
|
const fileHistoryMutationPendingRef = useRef(false);
|
|
2540
|
+
const pendingHistoryDirectionsRef = useRef<Array<"undo" | "redo">>([]);
|
|
2541
|
+
const pendingHistoryDrainScheduledRef = useRef(false);
|
|
2542
|
+
const replayPendingHistoryRef = useRef<
|
|
2543
|
+
((direction: "undo" | "redo") => void) | null
|
|
2544
|
+
>(null);
|
|
2508
2545
|
const historyOrderRef = useRef<(UndoRedoOrderKind | "selection")[]>([]);
|
|
2509
2546
|
const redoOrderRef = useRef<(UndoRedoOrderKind | "selection")[]>([]);
|
|
2510
2547
|
// Figma parity (ground-truth Round 4): a plain selection change (no
|
|
@@ -2531,6 +2568,10 @@ function DesignEditor() {
|
|
|
2531
2568
|
redoOrderRef.current = [];
|
|
2532
2569
|
undoManagerRef.current?.clear(false, true);
|
|
2533
2570
|
}, []);
|
|
2571
|
+
const clearPendingHistoryDirections = useCallback(() => {
|
|
2572
|
+
pendingHistoryDirectionsRef.current = [];
|
|
2573
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2574
|
+
}, []);
|
|
2534
2575
|
// Figma-parity undo/redo selection restore: snapshots "what's selected
|
|
2535
2576
|
// right now" from the ref mirrors above (always current — see their doc
|
|
2536
2577
|
// comment) into a plain GeometryHistorySelection. Reads refs only, so this
|
|
@@ -2578,19 +2619,36 @@ function DesignEditor() {
|
|
|
2578
2619
|
selection.selectedLayerIds.length === 1
|
|
2579
2620
|
? selection.selectedLayerIds[0]
|
|
2580
2621
|
: undefined;
|
|
2581
|
-
|
|
2622
|
+
const restoredScreenSelectionId =
|
|
2623
|
+
selection.overviewSelectedScreenIds.length === 1 &&
|
|
2624
|
+
selection.selectedLayerIds.length === 1 &&
|
|
2625
|
+
selection.overviewSelectedScreenIds[0] === restoredLayerId
|
|
2626
|
+
? restoredLayerId
|
|
2627
|
+
: null;
|
|
2628
|
+
pendingOverviewScreenSelectionRef.current = restoredScreenSelectionId;
|
|
2582
2629
|
pendingOverviewLayerSelectionRef.current =
|
|
2583
2630
|
restoredLayerId &&
|
|
2584
|
-
codeLayerOwnerByNodeIdRef.current.has(restoredLayerId)
|
|
2631
|
+
(codeLayerOwnerByNodeIdRef.current.has(restoredLayerId) ||
|
|
2632
|
+
restoredLayerId === restoredScreenSelectionId)
|
|
2585
2633
|
? restoredLayerId
|
|
2586
2634
|
: null;
|
|
2635
|
+
if (pendingOverviewLayerSelectionRef.current) {
|
|
2636
|
+
schedulePendingOverviewLayerSelectionClear(
|
|
2637
|
+
pendingOverviewLayerSelectionRef.current,
|
|
2638
|
+
);
|
|
2639
|
+
} else {
|
|
2640
|
+
clearPendingOverviewLayerSelectionTimer();
|
|
2641
|
+
}
|
|
2587
2642
|
setOverviewSelectedScreenIds(selection.overviewSelectedScreenIds);
|
|
2588
2643
|
setSelectedLayerIdsState(selection.selectedLayerIds);
|
|
2589
2644
|
if (selection.activeFileId) {
|
|
2590
2645
|
setActiveFileId(selection.activeFileId);
|
|
2591
2646
|
}
|
|
2592
2647
|
},
|
|
2593
|
-
[
|
|
2648
|
+
[
|
|
2649
|
+
clearPendingOverviewLayerSelectionTimer,
|
|
2650
|
+
schedulePendingOverviewLayerSelectionClear,
|
|
2651
|
+
],
|
|
2594
2652
|
);
|
|
2595
2653
|
const syncUndoRedoState = useCallback(() => {
|
|
2596
2654
|
if (fileHistoryMutationPendingRef.current) {
|
|
@@ -2598,6 +2656,33 @@ function DesignEditor() {
|
|
|
2598
2656
|
setCanRedo(false);
|
|
2599
2657
|
return;
|
|
2600
2658
|
}
|
|
2659
|
+
if (
|
|
2660
|
+
pendingHistoryDirectionsRef.current.length > 0 &&
|
|
2661
|
+
!pendingHistoryDrainScheduledRef.current
|
|
2662
|
+
) {
|
|
2663
|
+
pendingHistoryDrainScheduledRef.current = true;
|
|
2664
|
+
queueMicrotask(function drainPendingHistory() {
|
|
2665
|
+
if (fileHistoryMutationPendingRef.current) {
|
|
2666
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2667
|
+
return;
|
|
2668
|
+
}
|
|
2669
|
+
const direction = pendingHistoryDirectionsRef.current.shift();
|
|
2670
|
+
if (!direction) {
|
|
2671
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2672
|
+
return;
|
|
2673
|
+
}
|
|
2674
|
+
replayPendingHistoryRef.current?.(direction);
|
|
2675
|
+
if (fileHistoryMutationPendingRef.current) {
|
|
2676
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2677
|
+
return;
|
|
2678
|
+
}
|
|
2679
|
+
if (pendingHistoryDirectionsRef.current.length > 0) {
|
|
2680
|
+
queueMicrotask(drainPendingHistory);
|
|
2681
|
+
} else {
|
|
2682
|
+
pendingHistoryDrainScheduledRef.current = false;
|
|
2683
|
+
}
|
|
2684
|
+
});
|
|
2685
|
+
}
|
|
2601
2686
|
const undoManager = undoManagerRef.current;
|
|
2602
2687
|
const canUseOverviewHistory = viewModeRef.current === "overview";
|
|
2603
2688
|
const activeHistoryFileId = activeFileIdForUndoRef.current;
|
|
@@ -3066,6 +3151,7 @@ function DesignEditor() {
|
|
|
3066
3151
|
fileDeletionUndoStackRef.current = [];
|
|
3067
3152
|
fileDeletionRedoStackRef.current = [];
|
|
3068
3153
|
fileHistoryMutationPendingRef.current = false;
|
|
3154
|
+
clearPendingHistoryDirections();
|
|
3069
3155
|
selectionUndoStackRef.current = [];
|
|
3070
3156
|
selectionRedoStackRef.current = [];
|
|
3071
3157
|
clipboardPasteUndoStackRef.current = [];
|
|
@@ -3073,7 +3159,7 @@ function DesignEditor() {
|
|
|
3073
3159
|
latestClipboardMutationContentRef.current.clear();
|
|
3074
3160
|
historyOrderRef.current = [];
|
|
3075
3161
|
redoOrderRef.current = [];
|
|
3076
|
-
}, []);
|
|
3162
|
+
}, [clearPendingHistoryDirections]);
|
|
3077
3163
|
// U12: record a screen create/duplicate as an undoable entry. Always pushed
|
|
3078
3164
|
// to the undo stack (screen creation is only meaningful in overview mode's
|
|
3079
3165
|
// shared chronological history) and clears the redo stack like any other
|
|
@@ -3585,6 +3671,7 @@ function DesignEditor() {
|
|
|
3585
3671
|
: isDesignData(designResult)
|
|
3586
3672
|
? designResult
|
|
3587
3673
|
: null;
|
|
3674
|
+
const overviewDataReady = shellMode || designResult !== undefined;
|
|
3588
3675
|
const activeBreakpointStateVersion = useChangeVersion(
|
|
3589
3676
|
id ? `app-state:design-active-breakpoint:${id}` : "",
|
|
3590
3677
|
);
|
|
@@ -3679,6 +3766,7 @@ function DesignEditor() {
|
|
|
3679
3766
|
}, [reviewAgentQueueThreadIds, reviewSendingThreadId]);
|
|
3680
3767
|
const canEditDesignRef = useRef(canEditDesign);
|
|
3681
3768
|
const rawServerFilesByIdRef = useRef(new Map<string, DesignFile>());
|
|
3769
|
+
const historyFilesRef = useRef<DesignFile[]>([]);
|
|
3682
3770
|
const pendingLocalFileContentsRef = useRef<
|
|
3683
3771
|
Map<string, PendingLocalFileContent>
|
|
3684
3772
|
>(new Map());
|
|
@@ -3791,6 +3879,10 @@ function DesignEditor() {
|
|
|
3791
3879
|
skipActionQueryInvalidation: true,
|
|
3792
3880
|
});
|
|
3793
3881
|
const createFileAsync = createFileMutation.mutateAsync;
|
|
3882
|
+
const createDesignVersionMutation = useActionMutation(
|
|
3883
|
+
"create-design-version",
|
|
3884
|
+
);
|
|
3885
|
+
const createDesignVersionAsync = createDesignVersionMutation.mutateAsync;
|
|
3794
3886
|
const deleteFileMutation = useActionMutation("delete-file");
|
|
3795
3887
|
const updateDesignMutation = useActionMutation("update-design");
|
|
3796
3888
|
const updateDesignAsync = updateDesignMutation.mutateAsync;
|
|
@@ -3986,6 +4078,26 @@ function DesignEditor() {
|
|
|
3986
4078
|
toast.error(t("designEditor.toasts.saveConflict"), {
|
|
3987
4079
|
id: "design-save-conflict:outbox",
|
|
3988
4080
|
});
|
|
4081
|
+
for (const { entry } of result.rebased) {
|
|
4082
|
+
const content = entry.payload.content;
|
|
4083
|
+
if (typeof content === "string") {
|
|
4084
|
+
rollbackPendingLocalFileContent(entry.resourceId, content);
|
|
4085
|
+
}
|
|
4086
|
+
}
|
|
4087
|
+
}
|
|
4088
|
+
for (const entry of [
|
|
4089
|
+
...result.saved,
|
|
4090
|
+
...result.rebased.map(({ entry }) => entry),
|
|
4091
|
+
...result.dropped.map(({ entry }) => entry),
|
|
4092
|
+
]) {
|
|
4093
|
+
if (
|
|
4094
|
+
shouldClearLatestUnloadSaveForOutboxEntry(
|
|
4095
|
+
latestFileSaveForUnloadRef.current[entry.resourceId],
|
|
4096
|
+
entry,
|
|
4097
|
+
)
|
|
4098
|
+
) {
|
|
4099
|
+
delete latestFileSaveForUnloadRef.current[entry.resourceId];
|
|
4100
|
+
}
|
|
3989
4101
|
}
|
|
3990
4102
|
if (result.saved.length > 0 || result.rebased.length > 0) {
|
|
3991
4103
|
void queryClient.invalidateQueries({
|
|
@@ -4006,6 +4118,7 @@ function DesignEditor() {
|
|
|
4006
4118
|
}, [
|
|
4007
4119
|
designSaveActorScope,
|
|
4008
4120
|
id,
|
|
4121
|
+
rollbackPendingLocalFileContent,
|
|
4009
4122
|
queryClient,
|
|
4010
4123
|
t,
|
|
4011
4124
|
warnChangesWillRetry,
|
|
@@ -4064,7 +4177,8 @@ function DesignEditor() {
|
|
|
4064
4177
|
syncCollab: pending.syncCollab,
|
|
4065
4178
|
operationSource: pending.operationSource,
|
|
4066
4179
|
operationRevision: pending.operationRevision,
|
|
4067
|
-
expectedVersionHash:
|
|
4180
|
+
expectedVersionHash:
|
|
4181
|
+
pending.unloadExpectedVersionHash ?? pending.expectedVersionHash,
|
|
4068
4182
|
...(pending.identityMigrationSourceContent !== undefined
|
|
4069
4183
|
? { identityOnly: true }
|
|
4070
4184
|
: {}),
|
|
@@ -4099,6 +4213,7 @@ function DesignEditor() {
|
|
|
4099
4213
|
acknowledgeOutboxEntry,
|
|
4100
4214
|
canEditDesignRef,
|
|
4101
4215
|
createFileSaveOutboxEntry,
|
|
4216
|
+
designId: id,
|
|
4102
4217
|
fileSaveChainsRef,
|
|
4103
4218
|
journalOutboxEntry,
|
|
4104
4219
|
latestFileSaveForUnloadRef,
|
|
@@ -4137,21 +4252,39 @@ function DesignEditor() {
|
|
|
4137
4252
|
},
|
|
4138
4253
|
) => {
|
|
4139
4254
|
if (!canEditDesignRef.current) return;
|
|
4255
|
+
const queuedIdentityMigration = pendingFileSavesRef.current[fileId];
|
|
4256
|
+
const latestIdentityMigration =
|
|
4257
|
+
latestFileSaveForUnloadRef.current[fileId];
|
|
4258
|
+
const identityMigrationIsInFlight =
|
|
4259
|
+
options.identityMigrationSourceContent === undefined &&
|
|
4260
|
+
queuedIdentityMigration === undefined &&
|
|
4261
|
+
latestIdentityMigration?.identityMigrationSourceContent !== undefined;
|
|
4262
|
+
const expectedVersionHash = identityMigrationIsInFlight
|
|
4263
|
+
? sourceContentHash(latestIdentityMigration.content)
|
|
4264
|
+
: options.expectedVersionHash;
|
|
4140
4265
|
// Allocate the revision when the edit ENTERS the queue, not when its
|
|
4141
4266
|
// debounce fires. A pagehide keepalive and the ordinary chained save
|
|
4142
4267
|
// therefore carry the same idempotency key, while any newer queued edit
|
|
4143
4268
|
// is guaranteed to have a higher revision even if requests arrive at
|
|
4144
4269
|
// the server out of order.
|
|
4145
|
-
const
|
|
4270
|
+
const nextPending = coalescePendingFileContentSave(
|
|
4146
4271
|
createFileContentSaveRequest(
|
|
4147
4272
|
fileId,
|
|
4148
4273
|
content,
|
|
4149
4274
|
options.syncCollab ?? true,
|
|
4150
|
-
|
|
4275
|
+
expectedVersionHash,
|
|
4151
4276
|
options.identityMigrationSourceContent,
|
|
4152
4277
|
),
|
|
4153
4278
|
pendingFileSavesRef.current[fileId],
|
|
4154
4279
|
);
|
|
4280
|
+
const pending = {
|
|
4281
|
+
...nextPending,
|
|
4282
|
+
unloadExpectedVersionHash:
|
|
4283
|
+
pendingFileSavesRef.current[fileId]?.unloadExpectedVersionHash ??
|
|
4284
|
+
latestFileSaveForUnloadRef.current[fileId]
|
|
4285
|
+
?.unloadExpectedVersionHash ??
|
|
4286
|
+
nextPending.expectedVersionHash,
|
|
4287
|
+
};
|
|
4155
4288
|
markPendingLocalFileContent(
|
|
4156
4289
|
fileId,
|
|
4157
4290
|
content,
|
|
@@ -4253,25 +4386,17 @@ function DesignEditor() {
|
|
|
4253
4386
|
// Keep pagehide mirrors behind the same source-version guard as normal saves.
|
|
4254
4387
|
const collabLive = pending.syncCollab === false;
|
|
4255
4388
|
if (!shouldSendKeepalive(true, collabLive)) return;
|
|
4256
|
-
|
|
4257
|
-
|
|
4258
|
-
|
|
4259
|
-
|
|
4260
|
-
|
|
4261
|
-
|
|
4389
|
+
runFileContentSaveKeepalive(
|
|
4390
|
+
{
|
|
4391
|
+
acknowledgeOutboxEntry,
|
|
4392
|
+
createFileSaveOutboxEntry,
|
|
4393
|
+
journalOutboxEntry,
|
|
4394
|
+
latestFileSaveForUnloadRef,
|
|
4395
|
+
sendKeepalive: (payload) =>
|
|
4396
|
+
tryCallActionKeepalive("update-file", payload as any),
|
|
4397
|
+
},
|
|
4398
|
+
pending,
|
|
4262
4399
|
);
|
|
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
4400
|
},
|
|
4276
4401
|
[acknowledgeOutboxEntry, createFileSaveOutboxEntry, journalOutboxEntry],
|
|
4277
4402
|
);
|
|
@@ -4618,6 +4743,7 @@ function DesignEditor() {
|
|
|
4618
4743
|
}),
|
|
4619
4744
|
[pendingLocalFileContentsSnapshot, serverFiles],
|
|
4620
4745
|
);
|
|
4746
|
+
historyFilesRef.current = files;
|
|
4621
4747
|
|
|
4622
4748
|
const codeLayerSourceForScreen = useCallback(
|
|
4623
4749
|
(
|
|
@@ -4843,6 +4969,46 @@ function DesignEditor() {
|
|
|
4843
4969
|
() => getCanvasFrameGeometry(designDataJson),
|
|
4844
4970
|
[designDataJson],
|
|
4845
4971
|
);
|
|
4972
|
+
const displayedCanvasFrameGeometryById = useMemo(() => {
|
|
4973
|
+
if (Object.keys(optimisticFrameGeometryById).length === 0) {
|
|
4974
|
+
return canvasFrameGeometryById;
|
|
4975
|
+
}
|
|
4976
|
+
const next = { ...canvasFrameGeometryById };
|
|
4977
|
+
for (const [screenId, geometry] of Object.entries(
|
|
4978
|
+
optimisticFrameGeometryById,
|
|
4979
|
+
)) {
|
|
4980
|
+
const persisted = canvasFrameGeometryById[screenId];
|
|
4981
|
+
const persistedGeometryIsComplete =
|
|
4982
|
+
persisted &&
|
|
4983
|
+
typeof persisted.x === "number" &&
|
|
4984
|
+
typeof persisted.y === "number" &&
|
|
4985
|
+
typeof persisted.width === "number" &&
|
|
4986
|
+
typeof persisted.height === "number";
|
|
4987
|
+
if (!persistedGeometryIsComplete) next[screenId] = geometry;
|
|
4988
|
+
}
|
|
4989
|
+
return next;
|
|
4990
|
+
}, [canvasFrameGeometryById, optimisticFrameGeometryById]);
|
|
4991
|
+
useEffect(() => {
|
|
4992
|
+
setOptimisticFrameGeometryById((current) => {
|
|
4993
|
+
let changed = false;
|
|
4994
|
+
const next: Record<string, FrameGeometry> = {};
|
|
4995
|
+
for (const [screenId, geometry] of Object.entries(current)) {
|
|
4996
|
+
const persisted = canvasFrameGeometryById[screenId];
|
|
4997
|
+
const persistedGeometryIsComplete =
|
|
4998
|
+
persisted &&
|
|
4999
|
+
typeof persisted.x === "number" &&
|
|
5000
|
+
typeof persisted.y === "number" &&
|
|
5001
|
+
typeof persisted.width === "number" &&
|
|
5002
|
+
typeof persisted.height === "number";
|
|
5003
|
+
if (persistedGeometryIsComplete) {
|
|
5004
|
+
changed = true;
|
|
5005
|
+
} else {
|
|
5006
|
+
next[screenId] = geometry;
|
|
5007
|
+
}
|
|
5008
|
+
}
|
|
5009
|
+
return changed ? next : current;
|
|
5010
|
+
});
|
|
5011
|
+
}, [canvasFrameGeometryById]);
|
|
4846
5012
|
// Freshest live geometry for the geometry-undo freshness guard. Read from a
|
|
4847
5013
|
// ref (not a render-time closure) so undo/redo compare against the geometry a
|
|
4848
5014
|
// concurrent peer/agent may have just written, not the value captured when
|
|
@@ -6020,7 +6186,7 @@ function DesignEditor() {
|
|
|
6020
6186
|
? overviewZoomBasisScreen?.width
|
|
6021
6187
|
: DEVICE_FRAME_VIEWPORTS[deviceFrame].width;
|
|
6022
6188
|
const activeOverviewFrameWidth = overviewZoomBasisScreenId
|
|
6023
|
-
?
|
|
6189
|
+
? displayedCanvasFrameGeometryById[overviewZoomBasisScreenId]?.width
|
|
6024
6190
|
: undefined;
|
|
6025
6191
|
const overviewZoomScale = getOverviewZoomScale({
|
|
6026
6192
|
frameWidth: activeOverviewFrameWidth,
|
|
@@ -6066,6 +6232,12 @@ function DesignEditor() {
|
|
|
6066
6232
|
getOverviewDisplayZoom(overviewCanvasZoom, overviewZoomScale),
|
|
6067
6233
|
);
|
|
6068
6234
|
const zoom = viewMode === "overview" ? overviewZoom : screenZoom;
|
|
6235
|
+
const initialOverviewZoomValue = initialSearchParams.get("zoom");
|
|
6236
|
+
const hasExplicitOverviewZoomCommand =
|
|
6237
|
+
viewMode === "overview" &&
|
|
6238
|
+
initialSearchParams.get("view") === "overview" &&
|
|
6239
|
+
initialOverviewZoomValue !== null &&
|
|
6240
|
+
Number.isFinite(Number(initialOverviewZoomValue));
|
|
6069
6241
|
const setZoomForView = useCallback(
|
|
6070
6242
|
(targetView: "single" | "overview", update: SetStateAction<number>) => {
|
|
6071
6243
|
if (targetView === "overview") {
|
|
@@ -6142,12 +6314,15 @@ function DesignEditor() {
|
|
|
6142
6314
|
setInteractDeviceName,
|
|
6143
6315
|
setInteractDeviceSize,
|
|
6144
6316
|
setMode,
|
|
6317
|
+
setOverviewSelectedScreenIds,
|
|
6145
6318
|
setPinMode,
|
|
6146
6319
|
setScreenZoom,
|
|
6147
6320
|
setSelectedElement,
|
|
6148
6321
|
setSelectedLayerIdsState,
|
|
6149
6322
|
setViewMode,
|
|
6150
6323
|
setZoomForView,
|
|
6324
|
+
pendingOverviewScreenSelectionRef,
|
|
6325
|
+
overviewDataReady,
|
|
6151
6326
|
viewModeRef,
|
|
6152
6327
|
requestCameraFit: (camera) => {
|
|
6153
6328
|
cameraCommandNonceRef.current += 1;
|
|
@@ -6165,6 +6340,7 @@ function DesignEditor() {
|
|
|
6165
6340
|
files,
|
|
6166
6341
|
id,
|
|
6167
6342
|
overviewScreens,
|
|
6343
|
+
overviewDataReady,
|
|
6168
6344
|
setZoomForView,
|
|
6169
6345
|
],
|
|
6170
6346
|
);
|
|
@@ -6233,6 +6409,29 @@ function DesignEditor() {
|
|
|
6233
6409
|
}) => {
|
|
6234
6410
|
if (!id) return;
|
|
6235
6411
|
const now = new Date().toISOString();
|
|
6412
|
+
const optimisticFile: DesignFile = {
|
|
6413
|
+
id: args.fileId,
|
|
6414
|
+
filename: args.filename,
|
|
6415
|
+
fileType: args.fileType,
|
|
6416
|
+
content: annotateScreenHtmlForPersist(args.content, args.fileType),
|
|
6417
|
+
createdAt:
|
|
6418
|
+
typeof args.result?.createdAt === "string"
|
|
6419
|
+
? args.result.createdAt
|
|
6420
|
+
: now,
|
|
6421
|
+
updatedAt:
|
|
6422
|
+
typeof args.result?.updatedAt === "string"
|
|
6423
|
+
? args.result.updatedAt
|
|
6424
|
+
: now,
|
|
6425
|
+
};
|
|
6426
|
+
// Queued history replay drains in a microtask, before React can publish
|
|
6427
|
+
// the query-cache update through the rendered files projection.
|
|
6428
|
+
historyFilesRef.current = historyFilesRef.current.some(
|
|
6429
|
+
(file) => file.id === args.fileId,
|
|
6430
|
+
)
|
|
6431
|
+
? historyFilesRef.current.map((file) =>
|
|
6432
|
+
file.id === args.fileId ? optimisticFile : file,
|
|
6433
|
+
)
|
|
6434
|
+
: [...historyFilesRef.current, optimisticFile];
|
|
6236
6435
|
void queryClient.cancelQueries({
|
|
6237
6436
|
queryKey: ["action", "get-design", { id }],
|
|
6238
6437
|
exact: true,
|
|
@@ -6241,20 +6440,6 @@ function DesignEditor() {
|
|
|
6241
6440
|
if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
|
|
6242
6441
|
return old;
|
|
6243
6442
|
}
|
|
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
6443
|
return {
|
|
6259
6444
|
...old,
|
|
6260
6445
|
files: old.files.some((file: DesignFile) => file.id === args.fileId)
|
|
@@ -6269,8 +6454,19 @@ function DesignEditor() {
|
|
|
6269
6454
|
);
|
|
6270
6455
|
|
|
6271
6456
|
const focusCreatedScreen = useCallback(
|
|
6272
|
-
(
|
|
6457
|
+
(
|
|
6458
|
+
screenId: string,
|
|
6459
|
+
geometry: FrameGeometry,
|
|
6460
|
+
options?: {
|
|
6461
|
+
preserveCamera?: boolean;
|
|
6462
|
+
suppressLineupRecenter?: boolean;
|
|
6463
|
+
},
|
|
6464
|
+
) => {
|
|
6273
6465
|
const plan = getCreatedScreenNavigationPlan({ screenId, geometry });
|
|
6466
|
+
setOptimisticFrameGeometryById((current) => ({
|
|
6467
|
+
...current,
|
|
6468
|
+
[screenId]: geometry,
|
|
6469
|
+
}));
|
|
6274
6470
|
pendingOverviewScreenSelectionRef.current = screenId;
|
|
6275
6471
|
pendingOverviewLayerSelectionRef.current = null;
|
|
6276
6472
|
clearPendingOverviewLayerSelectionTimer();
|
|
@@ -6283,13 +6479,26 @@ function DesignEditor() {
|
|
|
6283
6479
|
setMode("edit");
|
|
6284
6480
|
viewModeRef.current = plan.viewMode;
|
|
6285
6481
|
setViewMode(plan.viewMode);
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6482
|
+
if (options?.suppressLineupRecenter) {
|
|
6483
|
+
const nonce = ++suppressLineupRecenterNonceRef.current;
|
|
6484
|
+
setSuppressLineupRecenter((current) => {
|
|
6485
|
+
const sameBatch = current?.fromCount === overviewScreens.length;
|
|
6486
|
+
return {
|
|
6487
|
+
fromCount: sameBatch ? current.fromCount : overviewScreens.length,
|
|
6488
|
+
addedCount: sameBatch ? current.addedCount + 1 : 1,
|
|
6489
|
+
nonce,
|
|
6490
|
+
};
|
|
6491
|
+
});
|
|
6492
|
+
}
|
|
6493
|
+
if (!options?.preserveCamera) {
|
|
6494
|
+
cameraCommandNonceRef.current += 1;
|
|
6495
|
+
setCameraCommand({
|
|
6496
|
+
...plan.camera,
|
|
6497
|
+
nonce: cameraCommandNonceRef.current,
|
|
6498
|
+
});
|
|
6499
|
+
}
|
|
6291
6500
|
},
|
|
6292
|
-
[clearPendingOverviewLayerSelectionTimer],
|
|
6501
|
+
[clearPendingOverviewLayerSelectionTimer, overviewScreens.length],
|
|
6293
6502
|
);
|
|
6294
6503
|
|
|
6295
6504
|
const handleDuplicateScreen = useCallback(
|
|
@@ -6297,6 +6506,7 @@ function DesignEditor() {
|
|
|
6297
6506
|
screenId: string,
|
|
6298
6507
|
request?: {
|
|
6299
6508
|
canvasPosition?: { x: number; y: number };
|
|
6509
|
+
preserveCamera?: boolean;
|
|
6300
6510
|
},
|
|
6301
6511
|
) =>
|
|
6302
6512
|
runDuplicateScreen(
|
|
@@ -7543,21 +7753,21 @@ function DesignEditor() {
|
|
|
7543
7753
|
freshActiveContent: getFreshActiveFileContent({
|
|
7544
7754
|
activeContent,
|
|
7545
7755
|
pendingContent: activeFile?.id
|
|
7546
|
-
?
|
|
7756
|
+
? (latestFileSaveForUnloadRef.current[activeFile.id]?.content ??
|
|
7757
|
+
pendingLocalFileContentsRef.current.get(activeFile.id)?.content)
|
|
7547
7758
|
: null,
|
|
7548
7759
|
latestContent: latestActiveContentRef.current,
|
|
7549
7760
|
lastLocalContent: lastLocalContentRef.current,
|
|
7550
7761
|
}),
|
|
7551
7762
|
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.
|
|
7763
|
+
// Same-tick freshness (see the param's doc on getFreshScreenContent):
|
|
7764
|
+
// applyFileContentUpdate writes these refs synchronously, while the
|
|
7765
|
+
// files-derived map above only refreshes on the next render. The
|
|
7766
|
+
// save outbox is also authoritative while reconciliation lags.
|
|
7559
7767
|
pendingContent:
|
|
7560
|
-
|
|
7768
|
+
latestFileSaveForUnloadRef.current[screenId]?.content ??
|
|
7769
|
+
pendingLocalFileContentsRef.current.get(screenId)?.content ??
|
|
7770
|
+
null,
|
|
7561
7771
|
}),
|
|
7562
7772
|
{
|
|
7563
7773
|
fileId: screenId,
|
|
@@ -8378,11 +8588,15 @@ function DesignEditor() {
|
|
|
8378
8588
|
// The selected node id, when it already is a recognised component instance —
|
|
8379
8589
|
// unlocks the contextual Component section at the top of the Design tab.
|
|
8380
8590
|
const selectedComponentNodeId = useMemo(() => {
|
|
8381
|
-
if (
|
|
8382
|
-
|
|
8383
|
-
|
|
8591
|
+
if (selectedCodeLayerNode && isComponentInstance(selectedCodeLayerNode)) {
|
|
8592
|
+
return bridgeSourceIdForCodeLayerNode(selectedCodeLayerNode);
|
|
8593
|
+
}
|
|
8594
|
+
return activeCanvasSourceType === "localhost"
|
|
8595
|
+
? (selectedElement?.runtimeComponent?.instanceId ??
|
|
8596
|
+
selectedElement?.sourceId ??
|
|
8597
|
+
undefined)
|
|
8384
8598
|
: undefined;
|
|
8385
|
-
}, [selectedCodeLayerNode]);
|
|
8599
|
+
}, [activeCanvasSourceType, selectedCodeLayerNode, selectedElement]);
|
|
8386
8600
|
// Keep canonical mains available to the Component section's read/source
|
|
8387
8601
|
// controls while withholding instance-only actions from those selections.
|
|
8388
8602
|
const selectedInstanceActionNodeId = useMemo(() => {
|
|
@@ -8400,8 +8614,11 @@ function DesignEditor() {
|
|
|
8400
8614
|
const acceptedActiveContent = pendingActiveFileEntry
|
|
8401
8615
|
? pendingActiveFileEntry.baseContent
|
|
8402
8616
|
: acceptedActiveFile?.content;
|
|
8403
|
-
const hasSelectedComponent =
|
|
8404
|
-
|
|
8617
|
+
const hasSelectedComponent = Boolean(
|
|
8618
|
+
selectedComponentNodeId &&
|
|
8619
|
+
(activeCanvasSourceType === "inline" ||
|
|
8620
|
+
Boolean(selectedElement?.runtimeComponent)),
|
|
8621
|
+
);
|
|
8405
8622
|
const acceptedComponentProjection = useMemo(() => {
|
|
8406
8623
|
if (
|
|
8407
8624
|
!hasSelectedComponent ||
|
|
@@ -8458,6 +8675,13 @@ function DesignEditor() {
|
|
|
8458
8675
|
);
|
|
8459
8676
|
const selectedElementAlreadyComponent = useMemo(() => {
|
|
8460
8677
|
if (!selectedElement) return false;
|
|
8678
|
+
if (
|
|
8679
|
+
selectedElement.runtimeComponent &&
|
|
8680
|
+
!(selectedCodeLayerNode && isComponentInstance(selectedCodeLayerNode))
|
|
8681
|
+
) {
|
|
8682
|
+
return false;
|
|
8683
|
+
}
|
|
8684
|
+
if (selectedElement.componentAnnotation?.trim()) return true;
|
|
8461
8685
|
if (selectedElement.componentName?.trim()) return true;
|
|
8462
8686
|
return codeLayerNodeLooksLikeComponent(selectedCodeLayerNode);
|
|
8463
8687
|
}, [selectedCodeLayerNode, selectedElement]);
|
|
@@ -8507,6 +8731,110 @@ function DesignEditor() {
|
|
|
8507
8731
|
return "Component";
|
|
8508
8732
|
}, [selectedCodeLayerNode?.layerName, selectedElement?.tagName]);
|
|
8509
8733
|
|
|
8734
|
+
const selectedComponentLocalSourceAnchor = useMemo(() => {
|
|
8735
|
+
if (
|
|
8736
|
+
activeCanvasSourceType !== "localhost" ||
|
|
8737
|
+
selectedElement?.runtimeComponent?.writeCapability !==
|
|
8738
|
+
"authored-jsx-literal"
|
|
8739
|
+
) {
|
|
8740
|
+
return undefined;
|
|
8741
|
+
}
|
|
8742
|
+
const connectionId =
|
|
8743
|
+
(activeOverviewScreen as { connectionId?: string } | undefined)
|
|
8744
|
+
?.connectionId ?? "";
|
|
8745
|
+
const runtimeComponent = selectedElement.runtimeComponent;
|
|
8746
|
+
const sourcePath = projectRelativeSourcePath({
|
|
8747
|
+
sourceFile: runtimeComponent.sourceFile,
|
|
8748
|
+
rootPath: connectionId
|
|
8749
|
+
? localhostConnectionRootPathByIdRef.current.get(connectionId)
|
|
8750
|
+
: undefined,
|
|
8751
|
+
});
|
|
8752
|
+
if (
|
|
8753
|
+
!connectionId ||
|
|
8754
|
+
!sourcePath ||
|
|
8755
|
+
!runtimeComponent.line ||
|
|
8756
|
+
!runtimeComponent.column
|
|
8757
|
+
)
|
|
8758
|
+
return undefined;
|
|
8759
|
+
const runtimeMultiplicity = runtimeMultiplicityForElementProvenance(
|
|
8760
|
+
runtimeLayerSnapshotsById,
|
|
8761
|
+
selectedElement,
|
|
8762
|
+
);
|
|
8763
|
+
return {
|
|
8764
|
+
connectionId,
|
|
8765
|
+
path: sourcePath,
|
|
8766
|
+
line: runtimeComponent.line,
|
|
8767
|
+
column: runtimeComponent.column,
|
|
8768
|
+
positionPrecision: sourcePositionPrecision(runtimeComponent.method),
|
|
8769
|
+
runtimeMultiplicity,
|
|
8770
|
+
scope:
|
|
8771
|
+
runtimeMultiplicity === 1
|
|
8772
|
+
? ("single-instance" as const)
|
|
8773
|
+
: ("repeated-render" as const),
|
|
8774
|
+
};
|
|
8775
|
+
}, [
|
|
8776
|
+
activeCanvasSourceType,
|
|
8777
|
+
activeOverviewScreen,
|
|
8778
|
+
runtimeLayerSnapshotsById,
|
|
8779
|
+
selectedElement,
|
|
8780
|
+
]);
|
|
8781
|
+
|
|
8782
|
+
const { data: selectedComponentSource } = useActionQuery<{
|
|
8783
|
+
versionHash?: string;
|
|
8784
|
+
content?: string;
|
|
8785
|
+
}>(
|
|
8786
|
+
"read-local-file",
|
|
8787
|
+
{
|
|
8788
|
+
designId: id ?? "",
|
|
8789
|
+
connectionId: selectedComponentLocalSourceAnchor?.connectionId ?? "",
|
|
8790
|
+
path: selectedComponentLocalSourceAnchor?.path ?? "",
|
|
8791
|
+
},
|
|
8792
|
+
{
|
|
8793
|
+
enabled: Boolean(id && selectedComponentLocalSourceAnchor),
|
|
8794
|
+
refetchOnMount: "always",
|
|
8795
|
+
},
|
|
8796
|
+
);
|
|
8797
|
+
|
|
8798
|
+
const selectedComponentLiteralProps = useMemo(() => {
|
|
8799
|
+
if (
|
|
8800
|
+
!selectedComponentLocalSourceAnchor ||
|
|
8801
|
+
typeof selectedComponentSource?.content !== "string"
|
|
8802
|
+
) {
|
|
8803
|
+
return undefined;
|
|
8804
|
+
}
|
|
8805
|
+
const literalProps = readLiteralJsxPropsAtAnchor({
|
|
8806
|
+
content: selectedComponentSource.content,
|
|
8807
|
+
anchor: selectedComponentLocalSourceAnchor,
|
|
8808
|
+
});
|
|
8809
|
+
return literalProps?.filter(
|
|
8810
|
+
({ name }) =>
|
|
8811
|
+
!name.startsWith("data-agent-native-") &&
|
|
8812
|
+
name !== "style" &&
|
|
8813
|
+
name !== "className",
|
|
8814
|
+
);
|
|
8815
|
+
}, [selectedComponentLocalSourceAnchor, selectedComponentSource?.content]);
|
|
8816
|
+
|
|
8817
|
+
const selectedComponentLocalSource = useMemo(() => {
|
|
8818
|
+
if (!selectedComponentLocalSourceAnchor) return undefined;
|
|
8819
|
+
const expectedVersionHash = selectedComponentSource?.versionHash;
|
|
8820
|
+
if (!expectedVersionHash) return undefined;
|
|
8821
|
+
const propStamps = (selectedComponentLiteralProps ?? []).map(
|
|
8822
|
+
({ name, value }) => ({
|
|
8823
|
+
name: propNameToDataAttribute(name),
|
|
8824
|
+
value,
|
|
8825
|
+
}),
|
|
8826
|
+
);
|
|
8827
|
+
return {
|
|
8828
|
+
...selectedComponentLocalSourceAnchor,
|
|
8829
|
+
expectedVersionHash,
|
|
8830
|
+
...(propStamps.length > 0 ? { propStamps } : {}),
|
|
8831
|
+
};
|
|
8832
|
+
}, [
|
|
8833
|
+
selectedComponentLocalSourceAnchor,
|
|
8834
|
+
selectedComponentSource?.versionHash,
|
|
8835
|
+
selectedComponentLiteralProps,
|
|
8836
|
+
]);
|
|
8837
|
+
|
|
8510
8838
|
// Outer HTML of the selection — backs the inline/Alpine "Inspect code" view.
|
|
8511
8839
|
const selectedElementOuterHtml = useMemo(() => {
|
|
8512
8840
|
if (!selectedElement?.selector) return null;
|
|
@@ -8707,45 +9035,95 @@ function DesignEditor() {
|
|
|
8707
9035
|
selectedElementInsideComponent
|
|
8708
9036
|
)
|
|
8709
9037
|
return;
|
|
9038
|
+
if (
|
|
9039
|
+
activeCanvasSourceType === "localhost" &&
|
|
9040
|
+
selectedElement.runtimeComponent?.writeCapability ===
|
|
9041
|
+
"authored-jsx-literal" &&
|
|
9042
|
+
!selectedComponentLocalSource
|
|
9043
|
+
) {
|
|
9044
|
+
toast.error(t("designEditor.toasts.componentCreateFailed"));
|
|
9045
|
+
return;
|
|
9046
|
+
}
|
|
8710
9047
|
const current = linkedComponentMutationQueueRef.current;
|
|
8711
9048
|
if (current?.designId !== id) return;
|
|
8712
9049
|
const nodeId = selectedElementLayerId ?? undefined;
|
|
8713
9050
|
const selector = selectedCanvasSelector ?? selectedElement.selector;
|
|
8714
|
-
|
|
8715
|
-
|
|
8716
|
-
|
|
8717
|
-
|
|
8718
|
-
|
|
8719
|
-
|
|
8720
|
-
|
|
8721
|
-
|
|
8722
|
-
|
|
8723
|
-
|
|
8724
|
-
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8728
|
-
|
|
8729
|
-
|
|
8730
|
-
|
|
9051
|
+
const run = (retriedAfterConsent = false) =>
|
|
9052
|
+
runCreateComponent(
|
|
9053
|
+
{
|
|
9054
|
+
canEditDesign,
|
|
9055
|
+
designId: id,
|
|
9056
|
+
fileId: activeFileId,
|
|
9057
|
+
selectionBefore: captureCurrentSelection(),
|
|
9058
|
+
createComponent: (request) =>
|
|
9059
|
+
callAction<CreateComponentActionResult>(
|
|
9060
|
+
"create-component",
|
|
9061
|
+
request,
|
|
9062
|
+
),
|
|
9063
|
+
mutationTransaction: {
|
|
9064
|
+
enqueue: (request) =>
|
|
9065
|
+
current.queue.enqueueSourceMutation({
|
|
9066
|
+
...request,
|
|
9067
|
+
validate: createComponentActionChange,
|
|
9068
|
+
}),
|
|
9069
|
+
},
|
|
8731
9070
|
},
|
|
8732
|
-
|
|
8733
|
-
|
|
8734
|
-
|
|
8735
|
-
|
|
8736
|
-
|
|
8737
|
-
|
|
9071
|
+
{
|
|
9072
|
+
nodeId,
|
|
9073
|
+
selector,
|
|
9074
|
+
name,
|
|
9075
|
+
...(selectedComponentLocalSource
|
|
9076
|
+
? { source: { local: selectedComponentLocalSource } }
|
|
9077
|
+
: {}),
|
|
9078
|
+
},
|
|
9079
|
+
)
|
|
9080
|
+
.then((outcome) => {
|
|
9081
|
+
if (!outcome) return;
|
|
9082
|
+
const source = outcome.result.source;
|
|
9083
|
+
if (source) {
|
|
9084
|
+
queryClient.setQueryData(
|
|
9085
|
+
[
|
|
9086
|
+
"action",
|
|
9087
|
+
"read-local-file",
|
|
9088
|
+
{
|
|
9089
|
+
designId: id,
|
|
9090
|
+
connectionId: source.connectionId,
|
|
9091
|
+
path: source.path,
|
|
9092
|
+
},
|
|
9093
|
+
],
|
|
9094
|
+
(previous: { versionHash?: string } | undefined) => ({
|
|
9095
|
+
...previous,
|
|
9096
|
+
versionHash: source.versionHash,
|
|
9097
|
+
}),
|
|
9098
|
+
);
|
|
9099
|
+
}
|
|
9100
|
+
if (!outcome.historyRecorded) {
|
|
9101
|
+
toast.error(t("designEditor.toasts.componentCreateFailed"));
|
|
9102
|
+
return;
|
|
9103
|
+
}
|
|
9104
|
+
if (outcome.hostSync === "accepted") {
|
|
9105
|
+
toast.success(t("designEditor.toasts.componentCreated"));
|
|
9106
|
+
}
|
|
9107
|
+
})
|
|
9108
|
+
.catch((error: unknown) => {
|
|
9109
|
+
if (
|
|
9110
|
+
!retriedAfterConsent &&
|
|
9111
|
+
selectedComponentLocalSource &&
|
|
9112
|
+
isLocalhostWriteConsentError(error)
|
|
9113
|
+
) {
|
|
9114
|
+
requestLocalhostWriteRef.current?.({
|
|
9115
|
+
files: [selectedComponentLocalSource.path],
|
|
9116
|
+
onGranted: () => run(true),
|
|
9117
|
+
});
|
|
9118
|
+
return;
|
|
9119
|
+
}
|
|
8738
9120
|
toast.error(t("designEditor.toasts.componentCreateFailed"));
|
|
8739
|
-
|
|
8740
|
-
|
|
8741
|
-
if (outcome.hostSync === "accepted") {
|
|
8742
|
-
toast.success(t("designEditor.toasts.componentCreated"));
|
|
8743
|
-
}
|
|
8744
|
-
})
|
|
8745
|
-
.catch(() => {});
|
|
9121
|
+
});
|
|
9122
|
+
void run();
|
|
8746
9123
|
},
|
|
8747
9124
|
[
|
|
8748
9125
|
canEditDesign,
|
|
9126
|
+
activeCanvasSourceType,
|
|
8749
9127
|
captureCurrentSelection,
|
|
8750
9128
|
id,
|
|
8751
9129
|
selectedElement,
|
|
@@ -8753,6 +9131,8 @@ function DesignEditor() {
|
|
|
8753
9131
|
selectedCanvasSelector,
|
|
8754
9132
|
activeFileId,
|
|
8755
9133
|
selectedElementInsideComponent,
|
|
9134
|
+
selectedComponentLocalSource,
|
|
9135
|
+
queryClient,
|
|
8756
9136
|
t,
|
|
8757
9137
|
],
|
|
8758
9138
|
);
|
|
@@ -9131,6 +9511,7 @@ function DesignEditor() {
|
|
|
9131
9511
|
refreshPreview?: boolean;
|
|
9132
9512
|
skipPreview?: boolean;
|
|
9133
9513
|
forcePreviewFullDocument?: boolean;
|
|
9514
|
+
immediateSave?: boolean;
|
|
9134
9515
|
persist?: boolean;
|
|
9135
9516
|
recordHistory?: boolean;
|
|
9136
9517
|
historyBeforeContent?: string;
|
|
@@ -13380,9 +13761,11 @@ function DesignEditor() {
|
|
|
13380
13761
|
lastDuplicateTransformRef,
|
|
13381
13762
|
overviewSelectedScreenIds,
|
|
13382
13763
|
remapMotionTracksForClone,
|
|
13764
|
+
runtimeStructureInsertRevisionRef,
|
|
13383
13765
|
selectedCanvasSelector,
|
|
13384
13766
|
selectedElement,
|
|
13385
13767
|
selectedLayerIdsState,
|
|
13768
|
+
setRuntimeStructureInsertRequest,
|
|
13386
13769
|
setOverviewSelectedScreenIds,
|
|
13387
13770
|
setSelectedElement,
|
|
13388
13771
|
setSelectedLayerIdsState,
|
|
@@ -13404,6 +13787,7 @@ function DesignEditor() {
|
|
|
13404
13787
|
handleDuplicateScreen,
|
|
13405
13788
|
overviewSelectedScreenIds,
|
|
13406
13789
|
remapMotionTracksForClone,
|
|
13790
|
+
runtimeStructureInsertRevisionRef,
|
|
13407
13791
|
selectedCanvasSelector,
|
|
13408
13792
|
selectedElement,
|
|
13409
13793
|
t,
|
|
@@ -14668,14 +15052,11 @@ function DesignEditor() {
|
|
|
14668
15052
|
handleDeleteSelection();
|
|
14669
15053
|
}, [handleCopySelection, handleDeleteSelection]);
|
|
14670
15054
|
|
|
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);
|
|
15055
|
+
const captureDeleteHistoryCheckpoint = useCallback(async () => {
|
|
15056
|
+
if (!id) throw new Error(t("common.genericError"));
|
|
15057
|
+
const version = await createDesignVersionAsync({ designId: id });
|
|
15058
|
+
return version.id;
|
|
15059
|
+
}, [createDesignVersionAsync, id, t]);
|
|
14679
15060
|
|
|
14680
15061
|
const performDeleteFiles = useCallback(
|
|
14681
15062
|
(
|
|
@@ -14717,6 +15098,8 @@ function DesignEditor() {
|
|
|
14717
15098
|
fileCreationUndoStackRef,
|
|
14718
15099
|
fileDeletionUndoStackRef,
|
|
14719
15100
|
fileHistoryMutationPendingRef,
|
|
15101
|
+
captureHistoryCheckpoint: captureDeleteHistoryCheckpoint,
|
|
15102
|
+
clearPendingHistory: clearPendingHistoryDirections,
|
|
14720
15103
|
files,
|
|
14721
15104
|
geometryRedoStackRef,
|
|
14722
15105
|
geometryUndoStackRef,
|
|
@@ -14746,7 +15129,9 @@ function DesignEditor() {
|
|
|
14746
15129
|
[
|
|
14747
15130
|
activeFile,
|
|
14748
15131
|
canvasFrameGeometryById,
|
|
15132
|
+
captureDeleteHistoryCheckpoint,
|
|
14749
15133
|
clearRedoStacks,
|
|
15134
|
+
clearPendingHistoryDirections,
|
|
14750
15135
|
deleteFileMutation,
|
|
14751
15136
|
queryClient,
|
|
14752
15137
|
syncUndoRedoState,
|
|
@@ -14754,6 +15139,38 @@ function DesignEditor() {
|
|
|
14754
15139
|
writeFrameGeometrySnapshot,
|
|
14755
15140
|
],
|
|
14756
15141
|
);
|
|
15142
|
+
const handleDeleteInlineFile = useCallback(
|
|
15143
|
+
async (fileId: string) => {
|
|
15144
|
+
if (!canEditDesign) return;
|
|
15145
|
+
if (fileHistoryMutationPendingRef.current) {
|
|
15146
|
+
throw new Error(t("common.genericError"));
|
|
15147
|
+
}
|
|
15148
|
+
const queryKey = ["action", "get-design", { id }] as const;
|
|
15149
|
+
let file =
|
|
15150
|
+
files.find((candidate) => candidate.id === fileId) ??
|
|
15151
|
+
rawServerFilesByIdRef.current.get(fileId) ??
|
|
15152
|
+
historyFilesRef.current.find((candidate) => candidate.id === fileId);
|
|
15153
|
+
if (!file) {
|
|
15154
|
+
await queryClient.refetchQueries({ queryKey, exact: true });
|
|
15155
|
+
const refreshed = queryClient.getQueryData<{
|
|
15156
|
+
files?: DesignFile[];
|
|
15157
|
+
}>(queryKey);
|
|
15158
|
+
file = refreshed?.files?.find((candidate) => candidate.id === fileId);
|
|
15159
|
+
}
|
|
15160
|
+
if (!file) throw new Error(t("common.genericError"));
|
|
15161
|
+
|
|
15162
|
+
const targetFile = file;
|
|
15163
|
+
let deleted = false;
|
|
15164
|
+
await performDeleteFiles([targetFile], {
|
|
15165
|
+
recordDeletionHistory: true,
|
|
15166
|
+
onMutationSettled: (deletedFiles) => {
|
|
15167
|
+
deleted = deletedFiles.some((candidate) => candidate.id === fileId);
|
|
15168
|
+
},
|
|
15169
|
+
});
|
|
15170
|
+
if (!deleted) throw new Error(t("common.genericError"));
|
|
15171
|
+
},
|
|
15172
|
+
[canEditDesign, files, id, performDeleteFiles, queryClient, t],
|
|
15173
|
+
);
|
|
14757
15174
|
|
|
14758
15175
|
// MultiScreenCanvas consumes arrow keys in the capture phase while a frame
|
|
14759
15176
|
// is selected, and an in-screen element selection keeps its screen there.
|
|
@@ -14770,19 +15187,20 @@ function DesignEditor() {
|
|
|
14770
15187
|
return true;
|
|
14771
15188
|
}, [files, selectedElement, selectedLayerIdsState]);
|
|
14772
15189
|
|
|
14773
|
-
//
|
|
14774
|
-
//
|
|
15190
|
+
// Delete selected screens immediately and record the rows as one grouped
|
|
15191
|
+
// history entry so Cmd+Z can recreate every selected screen.
|
|
14775
15192
|
// Always returns false to MultiScreenCanvas so it never performs its own
|
|
14776
|
-
// synchronous local frame-geometry delete ahead of the
|
|
15193
|
+
// synchronous local frame-geometry delete ahead of the file mutation.
|
|
14777
15194
|
const handleDeleteOverviewSelection = useCallback(
|
|
14778
15195
|
(selectedIds: string[]) => {
|
|
14779
15196
|
if (!canEditDesign) return false;
|
|
15197
|
+
if (fileHistoryMutationPendingRef.current) return false;
|
|
14780
15198
|
// BUG-DELETE-OVERVIEW-COLLISION: MultiScreenCanvas consumes Delete in
|
|
14781
15199
|
// the capture phase whenever a frame is selected, and an in-screen
|
|
14782
15200
|
// element selection keeps its screen there — so deleting one node in a
|
|
14783
15201
|
// screen offered to delete the whole screen and the editor's own
|
|
14784
15202
|
// onDelete hotkey never ran. Route to the element delete instead; the
|
|
14785
|
-
//
|
|
15203
|
+
// Screen deletion is only for a real frame selection.
|
|
14786
15204
|
if (
|
|
14787
15205
|
overviewSelectionTargetsElement({
|
|
14788
15206
|
selectedElement,
|
|
@@ -14793,59 +15211,38 @@ function DesignEditor() {
|
|
|
14793
15211
|
handleDeleteSelection();
|
|
14794
15212
|
return false;
|
|
14795
15213
|
}
|
|
14796
|
-
if (!selectedIds.length ||
|
|
15214
|
+
if (!selectedIds.length || overviewScreens.length <= 1) return false;
|
|
14797
15215
|
|
|
14798
15216
|
const selectedIdSet = new Set(selectedIds);
|
|
14799
|
-
const
|
|
15217
|
+
const overviewScreenIds = new Set(
|
|
15218
|
+
overviewScreens.map((screen) => screen.id),
|
|
15219
|
+
);
|
|
15220
|
+
const selectedFiles = files.filter(
|
|
15221
|
+
(file) => selectedIdSet.has(file.id) && overviewScreenIds.has(file.id),
|
|
15222
|
+
);
|
|
14800
15223
|
if (!selectedFiles.length) return false;
|
|
14801
15224
|
|
|
14802
15225
|
const maxDeleteCount =
|
|
14803
|
-
selectedFiles.length >=
|
|
14804
|
-
? Math.max(0,
|
|
15226
|
+
selectedFiles.length >= overviewScreens.length
|
|
15227
|
+
? Math.max(0, overviewScreens.length - 1)
|
|
14805
15228
|
: selectedFiles.length;
|
|
14806
15229
|
const filesToDelete = selectedFiles.slice(0, maxDeleteCount);
|
|
14807
15230
|
if (!filesToDelete.length) return false;
|
|
14808
15231
|
|
|
14809
|
-
|
|
15232
|
+
performDeleteFiles(filesToDelete, { recordDeletionHistory: true });
|
|
14810
15233
|
return false;
|
|
14811
15234
|
},
|
|
14812
15235
|
[
|
|
14813
15236
|
canEditDesign,
|
|
14814
15237
|
files,
|
|
15238
|
+
performDeleteFiles,
|
|
14815
15239
|
handleDeleteSelection,
|
|
15240
|
+
overviewScreens,
|
|
14816
15241
|
selectedElement,
|
|
14817
15242
|
selectedLayerIdsState,
|
|
14818
15243
|
],
|
|
14819
15244
|
);
|
|
14820
15245
|
|
|
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
15246
|
// ── Props/animation clipboard, transforms, nudge ───────────────────────────
|
|
14850
15247
|
const handleCopyProps = useCallback(() => {
|
|
14851
15248
|
if (!selectedElement) return;
|
|
@@ -15257,7 +15654,9 @@ function DesignEditor() {
|
|
|
15257
15654
|
fileDeletionRedoStackRef,
|
|
15258
15655
|
fileDeletionUndoStackRef,
|
|
15259
15656
|
fileHistoryMutationPendingRef,
|
|
15657
|
+
clearPendingHistory: clearPendingHistoryDirections,
|
|
15260
15658
|
files,
|
|
15659
|
+
filesRef: historyFilesRef,
|
|
15261
15660
|
geometryRedoStackRef,
|
|
15262
15661
|
geometryUndoStackRef,
|
|
15263
15662
|
getFreshActiveContent,
|
|
@@ -15272,6 +15671,7 @@ function DesignEditor() {
|
|
|
15272
15671
|
localContentRedoStackRef,
|
|
15273
15672
|
localContentUndoStackRef,
|
|
15274
15673
|
markPendingLocalFileContent,
|
|
15674
|
+
optimisticallyInsertCreatedFile,
|
|
15275
15675
|
pendingLiveNonStyleEditsRef,
|
|
15276
15676
|
pendingLiveNonStyleRedoStackRef,
|
|
15277
15677
|
pendingLiveNonStyleUndoStackRef,
|
|
@@ -15316,6 +15716,7 @@ function DesignEditor() {
|
|
|
15316
15716
|
applyGeometryHistoryContentChangesRef,
|
|
15317
15717
|
applyLocalContentUpdate,
|
|
15318
15718
|
canEditDesign,
|
|
15719
|
+
clearPendingHistoryDirections,
|
|
15319
15720
|
createFileMutation,
|
|
15320
15721
|
deleteFileMutation,
|
|
15321
15722
|
files,
|
|
@@ -15325,6 +15726,7 @@ function DesignEditor() {
|
|
|
15325
15726
|
isSynced,
|
|
15326
15727
|
liveScreenSnapshotsById,
|
|
15327
15728
|
markPendingLocalFileContent,
|
|
15729
|
+
optimisticallyInsertCreatedFile,
|
|
15328
15730
|
performDeleteFiles,
|
|
15329
15731
|
publishAuthoritativeClipboardMutation,
|
|
15330
15732
|
queryClient,
|
|
@@ -15369,7 +15771,9 @@ function DesignEditor() {
|
|
|
15369
15771
|
fileDeletionRedoStackRef,
|
|
15370
15772
|
fileDeletionUndoStackRef,
|
|
15371
15773
|
fileHistoryMutationPendingRef,
|
|
15774
|
+
clearPendingHistory: clearPendingHistoryDirections,
|
|
15372
15775
|
files,
|
|
15776
|
+
filesRef: historyFilesRef,
|
|
15373
15777
|
focusCreatedScreen,
|
|
15374
15778
|
geometryRedoStackRef,
|
|
15375
15779
|
geometryUndoStackRef,
|
|
@@ -15424,6 +15828,7 @@ function DesignEditor() {
|
|
|
15424
15828
|
syncUndoRedoState,
|
|
15425
15829
|
t,
|
|
15426
15830
|
undoManagerRef,
|
|
15831
|
+
updateDesignAsync,
|
|
15427
15832
|
updateLiveScreenSnapshotContent,
|
|
15428
15833
|
viewModeRef,
|
|
15429
15834
|
writeFrameGeometrySnapshot,
|
|
@@ -15437,6 +15842,7 @@ function DesignEditor() {
|
|
|
15437
15842
|
applyGeometryHistoryContentChangesRef,
|
|
15438
15843
|
applyLocalContentUpdate,
|
|
15439
15844
|
canEditDesign,
|
|
15845
|
+
clearPendingHistoryDirections,
|
|
15440
15846
|
createFileMutation,
|
|
15441
15847
|
deleteRuntimeElement,
|
|
15442
15848
|
files,
|
|
@@ -15460,9 +15866,9 @@ function DesignEditor() {
|
|
|
15460
15866
|
syncLiveScreenSnapshotPreview,
|
|
15461
15867
|
syncUndoRedoState,
|
|
15462
15868
|
t,
|
|
15869
|
+
updateDesignAsync,
|
|
15463
15870
|
updateLiveScreenSnapshotContent,
|
|
15464
15871
|
writeFrameGeometrySnapshot,
|
|
15465
|
-
t,
|
|
15466
15872
|
],
|
|
15467
15873
|
);
|
|
15468
15874
|
|
|
@@ -15472,6 +15878,10 @@ function DesignEditor() {
|
|
|
15472
15878
|
});
|
|
15473
15879
|
historyDispatchRef.current = { undo: runCurrentUndo, redo: runCurrentRedo };
|
|
15474
15880
|
const dispatchHistory = useCallback((direction: "undo" | "redo") => {
|
|
15881
|
+
if (fileHistoryMutationPendingRef.current) {
|
|
15882
|
+
pendingHistoryDirectionsRef.current.push(direction);
|
|
15883
|
+
return;
|
|
15884
|
+
}
|
|
15475
15885
|
const pendingCountBefore =
|
|
15476
15886
|
pendingVisualStyleEditsRef.current.length +
|
|
15477
15887
|
pendingLiveNonStyleEditsRef.current.length;
|
|
@@ -15490,6 +15900,7 @@ function DesignEditor() {
|
|
|
15490
15900
|
: run();
|
|
15491
15901
|
if (pending) void pending.catch((error) => toast.error(String(error)));
|
|
15492
15902
|
}, []);
|
|
15903
|
+
replayPendingHistoryRef.current = dispatchHistory;
|
|
15493
15904
|
const handleUndo = useCallback(
|
|
15494
15905
|
() => dispatchHistory("undo"),
|
|
15495
15906
|
[dispatchHistory],
|
|
@@ -16733,7 +17144,7 @@ function DesignEditor() {
|
|
|
16733
17144
|
// never reaches MultiScreenCanvas' capture-phase Delete.
|
|
16734
17145
|
onDelete: canEditDesign
|
|
16735
17146
|
? () => {
|
|
16736
|
-
handleDeleteOverviewSelection(
|
|
17147
|
+
handleDeleteOverviewSelection(overviewSelectedScreenIds);
|
|
16737
17148
|
}
|
|
16738
17149
|
: undefined,
|
|
16739
17150
|
// Screen and element rows share the Layers panel's inline rename editor.
|
|
@@ -19002,10 +19413,20 @@ function DesignEditor() {
|
|
|
19002
19413
|
|
|
19003
19414
|
useEffect(() => {
|
|
19004
19415
|
if (!id || files.length === 0) return;
|
|
19416
|
+
const initialRouteScreen = initialRouteScreenTarget
|
|
19417
|
+
? findDesignFileByScreenTarget(files, initialRouteScreenTarget)
|
|
19418
|
+
: undefined;
|
|
19419
|
+
if (initialRouteScreenTarget && !initialRouteScreen && !activeFileId) {
|
|
19420
|
+
return;
|
|
19421
|
+
}
|
|
19422
|
+
// Do not let the URL mirror replace a direct screen/zoom route with the
|
|
19423
|
+
// pre-command default while the initial navigation command is still
|
|
19424
|
+
// waiting for the target file. The command owns the first synchronized
|
|
19425
|
+
// selection and zoom; once activeFileId reaches that target, normal URL
|
|
19426
|
+
// mirroring resumes.
|
|
19005
19427
|
if (
|
|
19006
|
-
|
|
19007
|
-
|
|
19008
|
-
!activeFileId
|
|
19428
|
+
initialRouteScreen &&
|
|
19429
|
+
(activeFileId ?? activeFile?.id) !== initialRouteScreen.id
|
|
19009
19430
|
) {
|
|
19010
19431
|
return;
|
|
19011
19432
|
}
|
|
@@ -19018,7 +19439,7 @@ function DesignEditor() {
|
|
|
19018
19439
|
const nextSearch = getDesignEditorStateUrlSearch({
|
|
19019
19440
|
currentSearch: location.search,
|
|
19020
19441
|
viewMode,
|
|
19021
|
-
screenId: activeFile?.id
|
|
19442
|
+
screenId: activeFileId ?? activeFile?.id,
|
|
19022
19443
|
leftPanel: activeLeftPanel,
|
|
19023
19444
|
codeFileId: activeLeftPanel === "code" ? activeCodeFile?.fileId : null,
|
|
19024
19445
|
codeFilename: activeLeftPanel === "code" ? activeCodeFile?.path : null,
|
|
@@ -19420,6 +19841,7 @@ function DesignEditor() {
|
|
|
19420
19841
|
screenStylePreviewRef.current.delete(screenId);
|
|
19421
19842
|
}
|
|
19422
19843
|
applyFileContentUpdate(screenId, next, {
|
|
19844
|
+
immediateSave: !previewOnly,
|
|
19423
19845
|
persist: !previewOnly,
|
|
19424
19846
|
recordHistory: !previewOnly,
|
|
19425
19847
|
historyBeforeContent: previewOnly ? undefined : historyBeforeContent,
|
|
@@ -20716,6 +21138,71 @@ function DesignEditor() {
|
|
|
20716
21138
|
(connection) => connection.id === activeLocalhostConnectionId,
|
|
20717
21139
|
)?.rootPath ?? undefined;
|
|
20718
21140
|
|
|
21141
|
+
const componentRuntime = useMemo(() => {
|
|
21142
|
+
if (
|
|
21143
|
+
activeCanvasSourceType !== "localhost" ||
|
|
21144
|
+
!selectedComponentNodeId ||
|
|
21145
|
+
!selectedCodeLayerNode
|
|
21146
|
+
) {
|
|
21147
|
+
return undefined;
|
|
21148
|
+
}
|
|
21149
|
+
const runtimeIdentity = selectedElement?.runtimeComponent;
|
|
21150
|
+
const name =
|
|
21151
|
+
selectedCodeLayerNode.dataAttributes[
|
|
21152
|
+
"data-agent-native-component"
|
|
21153
|
+
]?.trim() ??
|
|
21154
|
+
runtimeIdentity?.name?.trim() ??
|
|
21155
|
+
selectedElement?.provenance?.component?.trim();
|
|
21156
|
+
if (!name) return undefined;
|
|
21157
|
+
|
|
21158
|
+
const runtimeSource = selectedElement?.runtimeComponent;
|
|
21159
|
+
const sourceFile = runtimeSource?.sourceFile?.trim();
|
|
21160
|
+
const canWriteAuthoredJsx =
|
|
21161
|
+
runtimeIdentity?.writeCapability === "authored-jsx-literal";
|
|
21162
|
+
const local = canWriteAuthoredJsx
|
|
21163
|
+
? selectedComponentLocalSource
|
|
21164
|
+
: undefined;
|
|
21165
|
+
|
|
21166
|
+
return {
|
|
21167
|
+
name,
|
|
21168
|
+
nodeId: selectedComponentNodeId,
|
|
21169
|
+
selector:
|
|
21170
|
+
selectedElement?.runtimeSelector ??
|
|
21171
|
+
selectedElement?.selector ??
|
|
21172
|
+
selectedCodeLayerNode.selector,
|
|
21173
|
+
props: runtimeIdentity?.props?.length
|
|
21174
|
+
? runtimeIdentity.props
|
|
21175
|
+
: extractProps(selectedCodeLayerNode),
|
|
21176
|
+
...(selectedComponentLiteralProps
|
|
21177
|
+
? { literalProps: selectedComponentLiteralProps }
|
|
21178
|
+
: {}),
|
|
21179
|
+
...(runtimeIdentity?.componentId
|
|
21180
|
+
? { componentId: runtimeIdentity.componentId }
|
|
21181
|
+
: {}),
|
|
21182
|
+
sourceLocation: sourceFile
|
|
21183
|
+
? {
|
|
21184
|
+
filePath:
|
|
21185
|
+
selectedComponentLocalSource?.path ??
|
|
21186
|
+
projectRelativeSourcePath({
|
|
21187
|
+
sourceFile,
|
|
21188
|
+
rootPath: activeLocalhostConnectionRootPath,
|
|
21189
|
+
}) ??
|
|
21190
|
+
sourceFile,
|
|
21191
|
+
...(runtimeSource?.name ? { exportName: runtimeSource.name } : {}),
|
|
21192
|
+
}
|
|
21193
|
+
: undefined,
|
|
21194
|
+
local,
|
|
21195
|
+
};
|
|
21196
|
+
}, [
|
|
21197
|
+
activeCanvasSourceType,
|
|
21198
|
+
activeLocalhostConnectionRootPath,
|
|
21199
|
+
selectedCodeLayerNode,
|
|
21200
|
+
selectedComponentNodeId,
|
|
21201
|
+
selectedElement,
|
|
21202
|
+
selectedComponentLiteralProps,
|
|
21203
|
+
selectedComponentLocalSource,
|
|
21204
|
+
]);
|
|
21205
|
+
|
|
20719
21206
|
/**
|
|
20720
21207
|
* Request consent to write a local file for the active localhost screen.
|
|
20721
21208
|
* If no valid grant exists, opens the consent dialog; once granted the
|
|
@@ -20768,9 +21255,7 @@ function DesignEditor() {
|
|
|
20768
21255
|
id,
|
|
20769
21256
|
],
|
|
20770
21257
|
);
|
|
20771
|
-
|
|
20772
|
-
// connected inspector components; not all render paths call it directly.
|
|
20773
|
-
void requestLocalhostWrite;
|
|
21258
|
+
requestLocalhostWriteRef.current = requestLocalhostWrite;
|
|
20774
21259
|
|
|
20775
21260
|
// Localhost workspace roots for the code workbench: one per distinct
|
|
20776
21261
|
// connection referenced by this design's localhost-backed screens.
|
|
@@ -22999,8 +23484,24 @@ function DesignEditor() {
|
|
|
22999
23484
|
<DropdownMenuShortcut>{shortcut("$mod+d")}</DropdownMenuShortcut>
|
|
23000
23485
|
</DropdownMenuItem>
|
|
23001
23486
|
<DropdownMenuItem
|
|
23002
|
-
onClick={
|
|
23003
|
-
|
|
23487
|
+
onClick={() => {
|
|
23488
|
+
if (viewMode === "overview") {
|
|
23489
|
+
handleDeleteOverviewSelection(overviewSelectedScreenIds);
|
|
23490
|
+
} else {
|
|
23491
|
+
handleDeleteSelection();
|
|
23492
|
+
}
|
|
23493
|
+
}}
|
|
23494
|
+
disabled={
|
|
23495
|
+
viewMode === "overview"
|
|
23496
|
+
? !overviewSelectionTargetsElement({
|
|
23497
|
+
selectedElement,
|
|
23498
|
+
selectedLayerIds: selectedLayerIdsState,
|
|
23499
|
+
fileIds: files.map((file) => file.id),
|
|
23500
|
+
}) &&
|
|
23501
|
+
(overviewScreens.length <= 1 ||
|
|
23502
|
+
overviewSelectedScreenIds.length === 0)
|
|
23503
|
+
: !selectedElement && !activeFile
|
|
23504
|
+
}
|
|
23004
23505
|
>
|
|
23005
23506
|
{"Delete" /* i18n-ignore design menu command */}
|
|
23006
23507
|
<DropdownMenuShortcut>⌫</DropdownMenuShortcut>
|
|
@@ -23837,6 +24338,8 @@ function DesignEditor() {
|
|
|
23837
24338
|
fileId: activeFile?.id,
|
|
23838
24339
|
boardFileId,
|
|
23839
24340
|
componentNodeId: selectedComponentNodeId,
|
|
24341
|
+
componentRuntime,
|
|
24342
|
+
requestLocalhostWrite,
|
|
23840
24343
|
componentInstanceHasLocalOverrides: selectedComponentHasLocalOverrides,
|
|
23841
24344
|
onResetComponentInstanceOverrides:
|
|
23842
24345
|
id && activeFile?.id && selectedComponentHasLocalOverrides
|
|
@@ -24192,6 +24695,9 @@ function DesignEditor() {
|
|
|
24192
24695
|
selectedNodeId={selectedElementLayerId}
|
|
24193
24696
|
selectedSelector={selectedCanvasSelector}
|
|
24194
24697
|
canEdit={canEditDesign}
|
|
24698
|
+
onDeleteInlineFile={
|
|
24699
|
+
canEditDesign ? handleDeleteInlineFile : undefined
|
|
24700
|
+
}
|
|
24195
24701
|
onActiveFileChange={setActiveCodeFile}
|
|
24196
24702
|
localhostConnections={workbenchLocalhostConnections}
|
|
24197
24703
|
onRequestLocalWriteConsent={
|
|
@@ -24229,7 +24735,7 @@ function DesignEditor() {
|
|
|
24229
24735
|
row rather than a second floating control. Not needed for the
|
|
24230
24736
|
floating (minimal-UI) bar: minimal UI hides this rail entirely. */}
|
|
24231
24737
|
{responsiveInteractActive && !minimalUi ? (
|
|
24232
|
-
<div className="pointer-events-none absolute right-0 top-0 z-[80] flex h-12 items-center pr-3">
|
|
24738
|
+
<div className="pointer-events-none absolute right-0 top-0 z-[80] flex h-12 items-center bg-[var(--design-editor-panel-bg)] pl-1 pr-3">
|
|
24233
24739
|
<ResponsiveInteractExitButton
|
|
24234
24740
|
onClose={handleExitResponsiveInteract}
|
|
24235
24741
|
className="pointer-events-auto"
|
|
@@ -24365,7 +24871,7 @@ function DesignEditor() {
|
|
|
24365
24871
|
canDelete={Boolean(
|
|
24366
24872
|
canEditDesign &&
|
|
24367
24873
|
(selectedElement ||
|
|
24368
|
-
(selectedScreenIds.length > 0 &&
|
|
24874
|
+
(selectedScreenIds.length > 0 && overviewScreens.length > 1)),
|
|
24369
24875
|
)}
|
|
24370
24876
|
canReorder={canEditDesign && Boolean(selectedElement)}
|
|
24371
24877
|
// Rename is only offered for a single selectable layer target;
|
|
@@ -24745,6 +25251,14 @@ function DesignEditor() {
|
|
|
24745
25251
|
zoom={overviewCanvasZoom}
|
|
24746
25252
|
onZoomChange={setExplicitOverviewCanvasZoom}
|
|
24747
25253
|
cameraCommand={cameraCommand}
|
|
25254
|
+
suppressLineupRecenter={suppressLineupRecenter}
|
|
25255
|
+
preserveCameraOnScreenCountChange={
|
|
25256
|
+
explicitOverviewCanvasZoom !== null
|
|
25257
|
+
}
|
|
25258
|
+
deferLineupZoomChange={
|
|
25259
|
+
hasExplicitOverviewZoomCommand &&
|
|
25260
|
+
explicitOverviewCanvasZoom === null
|
|
25261
|
+
}
|
|
24748
25262
|
chromeInsetLeft={chromeInsetLeft}
|
|
24749
25263
|
chromeInsetRight={chromeInsetRight}
|
|
24750
25264
|
activeId={activeFileId}
|
|
@@ -24791,7 +25305,8 @@ function DesignEditor() {
|
|
|
24791
25305
|
onScreenSelectionChange={
|
|
24792
25306
|
handleOverviewScreenSelectionChange
|
|
24793
25307
|
}
|
|
24794
|
-
geometryById={
|
|
25308
|
+
geometryById={displayedCanvasFrameGeometryById}
|
|
25309
|
+
geometryOverridesById={optimisticFrameGeometryById}
|
|
24795
25310
|
onGeometryChange={queueFrameGeometrySave}
|
|
24796
25311
|
onGeometryCommit={handleGeometryCommit}
|
|
24797
25312
|
onBreakpointContentHeightChange={
|
|
@@ -25464,13 +25979,6 @@ function DesignEditor() {
|
|
|
25464
25979
|
}}
|
|
25465
25980
|
/>
|
|
25466
25981
|
|
|
25467
|
-
<PendingScreenDeletionDialog
|
|
25468
|
-
pendingScreenDeletion={pendingScreenDeletion}
|
|
25469
|
-
onCancel={handleCancelScreenDeletion}
|
|
25470
|
-
onConfirm={handleConfirmScreenDeletion}
|
|
25471
|
-
confirming={screenDeletionConfirming}
|
|
25472
|
-
/>
|
|
25473
|
-
|
|
25474
25982
|
{/* ── Render: motion dock ── */}
|
|
25475
25983
|
{/* Motion dock (§6.3) — bottom timeline mounted while opening, open, or
|
|
25476
25984
|
closing. Canvas remains visible above.
|