@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.
Files changed (119) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/clips/app/root.tsx +59 -10
  3. package/corpus/templates/clips/changelog/2026-09-17-desktop-popover-closes-when-clicking-away.md +6 -0
  4. package/corpus/templates/clips/changelog/2026-09-17-self-hosted-extension-sign-in.md +6 -0
  5. package/corpus/templates/clips/chrome-extension/src/background.ts +45 -25
  6. package/corpus/templates/clips/chrome-extension/src/content-script.ts +43 -0
  7. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +55 -6
  8. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +107 -19
  9. package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +1 -0
  10. package/corpus/templates/design/actions/add-localhost-screens.ts +54 -22
  11. package/corpus/templates/design/actions/apply-component-prop-edit.ts +174 -14
  12. package/corpus/templates/design/actions/apply-visual-edit.ts +3 -1
  13. package/corpus/templates/design/actions/create-component.ts +173 -12
  14. package/corpus/templates/design/actions/create-design-from-template.ts +50 -35
  15. package/corpus/templates/design/actions/create-design-version.ts +21 -0
  16. package/corpus/templates/design/actions/create-file.ts +35 -33
  17. package/corpus/templates/design/actions/delete-design.ts +29 -28
  18. package/corpus/templates/design/actions/delete-file.ts +154 -23
  19. package/corpus/templates/design/actions/generate-design.ts +28 -16
  20. package/corpus/templates/design/actions/get-component-details.ts +272 -33
  21. package/corpus/templates/design/actions/get-design-version.ts +9 -1
  22. package/corpus/templates/design/actions/go-to-main-component.ts +37 -35
  23. package/corpus/templates/design/actions/index-components.ts +241 -126
  24. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +12 -9
  25. package/corpus/templates/design/actions/present-design-variants.ts +68 -72
  26. package/corpus/templates/design/actions/rename-component.ts +294 -0
  27. package/corpus/templates/design/actions/rename-screen.ts +10 -10
  28. package/corpus/templates/design/actions/update-file.ts +327 -330
  29. package/corpus/templates/design/actions/update-screen-source.ts +2 -43
  30. package/corpus/templates/design/app/components/design/EditPanel.tsx +17 -1
  31. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +228 -24
  32. package/corpus/templates/design/app/components/design/ResponsiveInteractBar.tsx +17 -1
  33. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +151 -32
  34. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +4 -0
  35. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +1 -1
  36. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +8 -1
  37. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +55 -33
  38. package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +2 -0
  39. package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +4 -2
  40. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +273 -60
  41. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +9 -2
  42. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +57 -9
  43. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +13 -0
  44. package/corpus/templates/design/app/components/design/multi-screen/types.ts +14 -0
  45. package/corpus/templates/design/app/components/design/types.ts +9 -1
  46. package/corpus/templates/design/app/hooks/use-design-versions.ts +3 -2
  47. package/corpus/templates/design/app/i18n-breakpoints.ts +0 -110
  48. package/corpus/templates/design/app/i18n-data.ts +0 -10
  49. package/corpus/templates/design/app/pages/DesignEditor.tsx +669 -161
  50. package/corpus/templates/design/app/pages/design-editor/clone-and-pen-edit.ts +20 -0
  51. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +2 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +21 -3
  53. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +2 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/create-component.ts +44 -3
  55. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +69 -47
  56. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +157 -52
  57. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +76 -1
  58. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +152 -44
  59. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +131 -2
  60. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +5 -1
  61. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +26 -1
  62. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +1 -0
  63. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +58 -8
  64. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +63 -2
  65. package/corpus/templates/design/app/pages/design-editor/history.ts +3 -0
  66. package/corpus/templates/design/app/pages/design-editor/nudge-intent.ts +74 -22
  67. package/corpus/templates/design/app/pages/design-editor/source-publication.ts +14 -2
  68. package/corpus/templates/design/changelog/2026-09-16-design-components-retain-linked-identities-when-edited-and-d.md +6 -0
  69. package/corpus/templates/design/changelog/2026-09-16-fix-design-nested-layer-dragging-and-screen-duplication-pari.md +6 -0
  70. package/corpus/templates/design/changelog/2026-09-17-design-drag-and-drop-keeps-duplicates-cross-screen-moves-and.md +6 -0
  71. package/corpus/templates/design/changelog/2026-09-17-design-preserves-reusable-component-identity-and-props-acros.md +6 -0
  72. package/corpus/templates/design/changelog/2026-09-17-open-vector-paths-no-longer-show-fills-and-interact-dimensio.md +6 -0
  73. package/corpus/templates/design/changelog/2026-09-17-screen-deletion-is-immediate-and-undoable-with-history-that-.md +6 -0
  74. package/corpus/templates/design/e2e/fixtures/design-component-react/index.html +98 -0
  75. package/corpus/templates/design/e2e/fixtures/design-component-react/inline-component.html +65 -0
  76. package/corpus/templates/design/e2e/helpers.ts +6 -1
  77. package/corpus/templates/design/server/lib/design-data-mutation.ts +9 -5
  78. package/corpus/templates/design/server/lib/design-snapshot.ts +28 -16
  79. package/corpus/templates/design/server/lib/design-versions.ts +208 -130
  80. package/corpus/templates/design/server/lib/fusion-screens.ts +46 -20
  81. package/corpus/templates/design/server/lib/import-design-files.ts +5 -1
  82. package/corpus/templates/design/server/lib/localhost-connection.ts +54 -0
  83. package/corpus/templates/design/server/source-workspace.ts +412 -356
  84. package/corpus/templates/design/shared/component-library.ts +36 -6
  85. package/corpus/templates/design/shared/component-model.ts +12 -2
  86. package/corpus/templates/design/shared/local-jsx-visual-edit.ts +231 -5
  87. package/corpus/templates/design/shared/source-mode.ts +22 -0
  88. package/dist/automation/index.d.ts +1 -1
  89. package/dist/client/AssistantChat.d.ts +4 -1
  90. package/dist/client/AssistantChat.js +26 -3
  91. package/dist/client/chat/agentkit-protocol.js +148 -34
  92. package/dist/client/chat/message-components.js +7 -5
  93. package/dist/client/chat/tool-call-display.js +4 -3
  94. package/dist/client/conversation/AgentConversation.js +4 -2
  95. package/dist/client/tool-display.d.ts +9 -0
  96. package/dist/client/tool-display.js +15 -0
  97. package/dist/collab/awareness.d.ts +2 -2
  98. package/dist/collab/routes.d.ts +1 -1
  99. package/dist/localization/core-messages/ar-SA.js +2 -0
  100. package/dist/localization/core-messages/de-DE.js +2 -0
  101. package/dist/localization/core-messages/en-US.d.ts +2 -0
  102. package/dist/localization/core-messages/en-US.js +2 -0
  103. package/dist/localization/core-messages/es-ES.js +2 -0
  104. package/dist/localization/core-messages/fr-FR.js +2 -0
  105. package/dist/localization/core-messages/hi-IN.js +2 -0
  106. package/dist/localization/core-messages/ja-JP.js +2 -0
  107. package/dist/localization/core-messages/ko-KR.js +2 -0
  108. package/dist/localization/core-messages/pt-BR.js +2 -0
  109. package/dist/localization/core-messages/zh-CN.js +2 -0
  110. package/dist/localization/core-messages/zh-TW.js +2 -0
  111. package/dist/localization/core-messages.d.ts +2 -0
  112. package/dist/notifications/routes.d.ts +3 -3
  113. package/dist/observability/routes.d.ts +3 -3
  114. package/dist/resources/handlers.d.ts +1 -1
  115. package/dist/secrets/routes.d.ts +3 -3
  116. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  117. package/dist/server/core-routes-plugin.d.ts +1 -1
  118. package/package.json +3 -3
  119. 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 { runSaveFileContent } from "./design-editor/commands/save-file-content";
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
- pendingOverviewScreenSelectionRef.current = null;
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: pending.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 pending = coalescePendingFileContentSave(
4270
+ const nextPending = coalescePendingFileContentSave(
4146
4271
  createFileContentSaveRequest(
4147
4272
  fileId,
4148
4273
  content,
4149
4274
  options.syncCollab ?? true,
4150
- options.expectedVersionHash,
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
- const entry = createFileSaveOutboxEntry(pending);
4257
- if (!entry) return;
4258
- void journalOutboxEntry(entry);
4259
- const attempt = tryCallActionKeepalive(
4260
- "update-file",
4261
- entry.payload as any,
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
- ? canvasFrameGeometryById[overviewZoomBasisScreenId]?.width
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
- (screenId: string, geometry: FrameGeometry) => {
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
- cameraCommandNonceRef.current += 1;
6287
- setCameraCommand({
6288
- ...plan.camera,
6289
- nonce: cameraCommandNonceRef.current,
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
- ? pendingLocalFileContentsRef.current.get(activeFile.id)?.content
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 for NON-ACTIVE screens (see the param's doc on
7553
- // getFreshScreenContent): applyFileContentUpdate writes this ref
7554
- // synchronously via markPendingLocalFileContent, while the
7555
- // files-derived map above only refreshes on the next render. Without
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
- pendingLocalFileContentsRef.current.get(screenId)?.content ?? null,
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 (!selectedCodeLayerNode) return undefined;
8382
- return isComponentInstance(selectedCodeLayerNode)
8383
- ? bridgeSourceIdForCodeLayerNode(selectedCodeLayerNode)
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
- activeCanvasSourceType === "inline" && Boolean(selectedComponentNodeId);
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
- void runCreateComponent(
8715
- {
8716
- canEditDesign,
8717
- designId: id,
8718
- fileId: activeFileId,
8719
- selectionBefore: captureCurrentSelection(),
8720
- createComponent: (request) =>
8721
- callAction<CreateComponentActionResult>(
8722
- "create-component",
8723
- request,
8724
- ),
8725
- mutationTransaction: {
8726
- enqueue: (request) =>
8727
- current.queue.enqueueSourceMutation({
8728
- ...request,
8729
- validate: createComponentActionChange,
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
- { nodeId, selector, name },
8734
- )
8735
- .then((outcome) => {
8736
- if (!outcome) return;
8737
- if (!outcome.historyRecorded) {
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
- return;
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 [pendingScreenDeletion, setPendingScreenDeletion] = useState<{
14672
- files: DesignFile[];
14673
- } | null>(null);
14674
- const [screenDeletionConfirming, setScreenDeletionConfirming] =
14675
- useState(false);
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
- // Gate screen deletion behind confirmation, then record the deleted rows as
14774
- // one grouped history entry so Cmd+Z can recreate every selected screen.
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 confirmation.
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
- // screen-delete confirmation is only for a real frame selection.
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 || files.length <= 1) return false;
15214
+ if (!selectedIds.length || overviewScreens.length <= 1) return false;
14797
15215
 
14798
15216
  const selectedIdSet = new Set(selectedIds);
14799
- const selectedFiles = files.filter((file) => selectedIdSet.has(file.id));
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 >= files.length
14804
- ? Math.max(0, files.length - 1)
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
- setPendingScreenDeletion({ files: filesToDelete });
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(selectedLayerIdsState);
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
- initialRouteScreenTarget &&
19007
- !findDesignFileByScreenTarget(files, initialRouteScreenTarget) &&
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 ?? activeFileId,
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
- // requestLocalhostWrite is consumed via the component instance or by
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={handleDeleteSelection}
23003
- disabled={!selectedElement && (!activeFile || files.length <= 1)}
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 && files.length > 1)),
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={canvasFrameGeometryById}
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.