@agent-native/core 0.163.4 → 0.164.3

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 (228) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/components/agents/FeatureFlagsFleetPanel.tsx +78 -19
  3. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  4. package/corpus/templates/analytics/app/lib/use-auto-focus-select.ts +4 -4
  5. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -0
  6. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +6 -0
  7. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  8. package/corpus/templates/analytics/app/routes/_index.tsx +3 -2
  9. package/corpus/templates/analytics/app/routes/adhoc.$id.tsx +7 -2
  10. package/corpus/templates/analytics/app/routes/dashboard.tsx +3 -2
  11. package/corpus/templates/analytics/app/routes/overview.tsx +3 -2
  12. package/corpus/templates/analytics/app/routes/traffic.tsx +3 -2
  13. package/corpus/templates/analytics/changelog/2026-08-18-verify-cross-app-feature-flag-changes.md +6 -0
  14. package/corpus/templates/analytics/netlify.toml +2 -0
  15. package/corpus/templates/analytics/server/lib/workspace-feature-flags.ts +196 -48
  16. package/corpus/templates/analytics/server/plugins/feature-flags.ts +5 -0
  17. package/corpus/templates/analytics/shared/feature-flags.ts +10 -0
  18. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +5 -1
  19. package/corpus/templates/assets/app/routes/brand-kits._index.tsx +4 -1
  20. package/corpus/templates/assets/app/routes/libraries.tsx +4 -1
  21. package/corpus/templates/assets/app/routes/picker.tsx +4 -1
  22. package/corpus/templates/assets/netlify.toml +2 -0
  23. package/corpus/templates/brain/netlify.toml +2 -0
  24. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  25. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  26. package/corpus/templates/calendar/netlify.toml +2 -0
  27. package/corpus/templates/chat/netlify.toml +2 -0
  28. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  29. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  30. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  31. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  32. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  33. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  34. package/corpus/templates/clips/app/routes/_app.wispr.tsx +2 -1
  35. package/corpus/templates/clips/app/routes/_index.tsx +7 -3
  36. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  37. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  38. package/corpus/templates/clips/netlify.toml +2 -0
  39. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  40. package/corpus/templates/content/netlify.toml +2 -0
  41. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  42. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  43. package/corpus/templates/design/AGENTS.md +3 -1
  44. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  45. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  46. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  47. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  48. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  49. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  50. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  51. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  52. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  53. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  54. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  55. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  56. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  126. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  127. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  128. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  129. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  130. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  131. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  132. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  133. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  134. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  135. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  136. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  137. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  138. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  139. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  140. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  141. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  142. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  143. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  144. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  145. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  146. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  147. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  148. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  149. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  150. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  151. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  152. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  153. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  154. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  155. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  156. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  157. package/corpus/templates/design/app/routes/examples.tsx +2 -1
  158. package/corpus/templates/design/netlify.toml +2 -0
  159. package/corpus/templates/design/shared/board-file.ts +2 -1
  160. package/corpus/templates/dispatch/netlify.toml +2 -0
  161. package/corpus/templates/forms/app/routes/_index.tsx +3 -2
  162. package/corpus/templates/forms/netlify.toml +2 -0
  163. package/corpus/templates/mail/app/routes/_index.tsx +3 -2
  164. package/corpus/templates/mail/netlify.toml +2 -0
  165. package/corpus/templates/plan/netlify.toml +2 -0
  166. package/corpus/templates/slides/netlify.toml +2 -0
  167. package/corpus/templates/tasks/app/routes/_index.tsx +2 -1
  168. package/dist/agent/engine/builder-engine.d.ts +6 -4
  169. package/dist/agent/engine/builder-engine.js +91 -26
  170. package/dist/agent/engine/registry.js +16 -0
  171. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  172. package/dist/cli/code-agent-agent-tools.js +188 -0
  173. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  174. package/dist/cli/code-agent-collaboration.js +104 -0
  175. package/dist/cli/code-agent-executor.js +6 -0
  176. package/dist/cli/code-agent-schedules.d.ts +54 -0
  177. package/dist/cli/code-agent-schedules.js +198 -0
  178. package/dist/client/AgentPanel.js +1 -1
  179. package/dist/client/ConnectBuilderCard.js +5 -2
  180. package/dist/client/EnvironmentBadge.js +6 -5
  181. package/dist/client/chat/run-recovery.js +3 -2
  182. package/dist/client/chat/tool-call-display.js +2 -3
  183. package/dist/client/components/CodeRequiredDialog.js +4 -2
  184. package/dist/client/i18n.js +5 -4
  185. package/dist/client/settings/SettingsPanel.js +1 -1
  186. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  187. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  188. package/dist/client/settings/useBuilderStatus.js +22 -33
  189. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  190. package/dist/code-agents/index.d.ts +2 -0
  191. package/dist/code-agents/index.js +2 -0
  192. package/dist/collab/awareness.d.ts +2 -2
  193. package/dist/collab/routes.d.ts +1 -1
  194. package/dist/deploy/build.js +10 -4
  195. package/dist/localization/shared.d.ts +2 -0
  196. package/dist/localization/shared.js +5 -0
  197. package/dist/mcp-client/oauth-client.d.ts +1 -0
  198. package/dist/mcp-client/oauth-client.js +50 -1
  199. package/dist/notifications/routes.d.ts +3 -3
  200. package/dist/observability/routes.d.ts +1 -1
  201. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  202. package/dist/server/auth-marketing-locales.d.ts +7 -0
  203. package/dist/server/auth-marketing-locales.js +1322 -0
  204. package/dist/server/auth-marketing.d.ts +1 -0
  205. package/dist/server/auth-marketing.js +35 -6
  206. package/dist/server/auth.js +4 -17
  207. package/dist/server/builder-browser.d.ts +22 -0
  208. package/dist/server/builder-browser.js +77 -1
  209. package/dist/server/builder-oauth.d.ts +50 -0
  210. package/dist/server/builder-oauth.js +263 -0
  211. package/dist/server/core-routes-plugin.d.ts +11 -0
  212. package/dist/server/core-routes-plugin.js +291 -415
  213. package/dist/server/credential-provider.d.ts +1 -1
  214. package/dist/server/credential-provider.js +1 -1
  215. package/dist/server/csrf.js +2 -2
  216. package/dist/server/onboarding-html.js +52 -120
  217. package/dist/server/ssr-handler.js +13 -4
  218. package/dist/settings/store.d.ts +9 -0
  219. package/dist/settings/store.js +19 -0
  220. package/dist/shared/cache-control.d.ts +22 -0
  221. package/dist/shared/cache-control.js +25 -0
  222. package/dist/shared/index.d.ts +1 -0
  223. package/dist/shared/index.js +1 -0
  224. package/dist/templates/chat/netlify.toml +2 -0
  225. package/docs/content/deployment.mdx +2 -0
  226. package/docs/content/multi-app-workspace.mdx +1 -1
  227. package/package.json +2 -2
  228. package/src/templates/chat/netlify.toml +2 -0
@@ -0,0 +1,131 @@
1
+ import { useActionMutation } from "@agent-native/core/client/hooks";
2
+ import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
3
+ import type { QueryClient } from "@tanstack/react-query";
4
+ import type { RefObject } from "react";
5
+ import { toast } from "sonner";
6
+
7
+ import { trace } from "@/components/design/design-trace";
8
+ import type { FrameGeometry } from "@/components/design/multi-screen/types";
9
+ import { prettyScreenName } from "@/lib/screen-names";
10
+ import {
11
+ blankScreenHtml,
12
+ nextBlankScreenFilename,
13
+ } from "@/pages/design-editor/canvas-primitive-insert";
14
+ import type { FileCreationHistoryEntry } from "@/pages/design-editor/history";
15
+ import type { DesignFile } from "@/pages/design-editor/types";
16
+
17
+ export interface CreateScreenFrameArgs {
18
+ canEditDesign: boolean;
19
+ canvasFrameGeometryById: CanvasFrameGeometryById;
20
+ createFileMutation: ReturnType<
21
+ typeof useActionMutation<undefined, undefined, "create-file">
22
+ >;
23
+ files: DesignFile[];
24
+ focusCreatedScreen: (screenId: string, geometry: FrameGeometry) => void;
25
+ id: string | undefined;
26
+ locallyPinnedHeightIdsRef: RefObject<Set<string>>;
27
+ optimisticallyInsertCreatedFile: (args: {
28
+ fileId: string;
29
+ filename: string;
30
+ fileType: DesignFile["fileType"];
31
+ content: string;
32
+ result?: Record<string, unknown> | null;
33
+ }) => void;
34
+ queryClient: QueryClient;
35
+ recordFileCreationHistoryEntry: (entry: FileCreationHistoryEntry) => void;
36
+ t: (key: string, options?: Record<string, unknown>) => string;
37
+ writeFrameGeometrySnapshot: (
38
+ geometryById: CanvasFrameGeometryById,
39
+ options?: { syncViewportFrameIds?: string[]; pinHeightFrameIds?: string[] },
40
+ ) => void;
41
+ }
42
+
43
+ export function runCreateScreenFrame(
44
+ {
45
+ canEditDesign,
46
+ canvasFrameGeometryById,
47
+ createFileMutation,
48
+ files,
49
+ focusCreatedScreen,
50
+ id,
51
+ locallyPinnedHeightIdsRef,
52
+ optimisticallyInsertCreatedFile,
53
+ queryClient,
54
+ recordFileCreationHistoryEntry,
55
+ t,
56
+ writeFrameGeometrySnapshot,
57
+ }: CreateScreenFrameArgs,
58
+ geometry: { x: number; y: number; width: number; height: number },
59
+ ) {
60
+ if (!id || !canEditDesign) return;
61
+ const filename = nextBlankScreenFilename(files);
62
+ const content = blankScreenHtml(prettyScreenName(filename));
63
+ const nextGeometry = {
64
+ x: Math.round(geometry.x),
65
+ y: Math.round(geometry.y),
66
+ width: Math.max(64, Math.round(geometry.width)),
67
+ height: Math.max(64, Math.round(geometry.height)),
68
+ };
69
+ createFileMutation.mutate(
70
+ {
71
+ designId: id,
72
+ filename,
73
+ content,
74
+ fileType: "html",
75
+ } as any,
76
+ {
77
+ onSuccess: (result: any) => {
78
+ const nextId = typeof result?.id === "string" ? result.id : null;
79
+ if (nextId) {
80
+ optimisticallyInsertCreatedFile({
81
+ fileId: nextId,
82
+ filename,
83
+ fileType: "html",
84
+ content,
85
+ result,
86
+ });
87
+ locallyPinnedHeightIdsRef.current.add(nextId);
88
+ trace("screen", "pin-drawn-height", {
89
+ fileId: nextId,
90
+ height: nextGeometry.height,
91
+ why: "a drawn size is deliberate; without this the device floor and content-fit pass override it",
92
+ });
93
+ // The drawn height is a deliberate size, so pin it: otherwise
94
+ // the device floor and content-fit pass immediately override it.
95
+ writeFrameGeometrySnapshot(
96
+ {
97
+ ...canvasFrameGeometryById,
98
+ [nextId]: nextGeometry,
99
+ },
100
+ {
101
+ syncViewportFrameIds: [nextId],
102
+ pinHeightFrameIds: [nextId],
103
+ },
104
+ );
105
+ focusCreatedScreen(nextId, nextGeometry);
106
+ recordFileCreationHistoryEntry({
107
+ filename,
108
+ content,
109
+ fileType: "html",
110
+ geometry: nextGeometry,
111
+ });
112
+ }
113
+ // Refetch only when there is no created id to insert optimistically:
114
+ // a whole-design refetch re-downloads every screen's HTML, which is
115
+ // what made adding a frame feel slow.
116
+ if (!nextId) {
117
+ queryClient.invalidateQueries({
118
+ queryKey: ["action", "get-design"],
119
+ });
120
+ }
121
+ },
122
+ onError: (error) => {
123
+ toast.error(
124
+ error instanceof Error
125
+ ? error.message
126
+ : t("designEditor.toasts.screenDuplicateError"),
127
+ );
128
+ },
129
+ },
130
+ );
131
+ }
@@ -0,0 +1,577 @@
1
+ import type { CodeLayerNode, CodeLayerTreeNode } from "@shared/code-layer";
2
+ import {
3
+ applyVisualEdit,
4
+ buildCodeLayerProjection,
5
+ moveNodeBetweenDocuments,
6
+ } from "@shared/code-layer";
7
+ import type { Dispatch, RefObject, SetStateAction } from "react";
8
+ import { toast } from "sonner";
9
+
10
+ import { trace } from "@/components/design/design-trace";
11
+ import { dndHostLog } from "@/components/design/dnd-debug";
12
+ import { validateCrossScreenSourceHtmlSnapshot } from "@/components/design/multi-screen/cross-screen-drop";
13
+ import { getPrimaryIframeId } from "@/components/design/multi-screen/iframe-targeting";
14
+ import type {
15
+ ElementInfo,
16
+ PortableStyleSnapshot,
17
+ RuntimeStructureInsertRequest,
18
+ } from "@/components/design/types";
19
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
20
+ import {
21
+ bridgeSourceIdForCodeLayerNode,
22
+ codeLayerPatchMessage,
23
+ elementInfoFromCodeLayerNode,
24
+ resolveCodeLayerNodeFromBridge,
25
+ } from "@/pages/design-editor/code-layer-state";
26
+ import { adaptAutoTextColorForCrossScreenNode } from "@/pages/design-editor/cross-screen-text-color";
27
+ import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
28
+ import { DESIGN_EDITOR_DEBUG_LOGS } from "@/pages/design-editor/editor-constants";
29
+ import type { ContentHistoryEntry } from "@/pages/design-editor/history";
30
+ import {
31
+ removeAbsolutePositioningFromNodeInHtml,
32
+ setAbsolutePositioningForNodeInHtml,
33
+ } from "@/pages/design-editor/html-layer-positioning";
34
+ import { resolveOverviewScreenSourceType } from "@/pages/design-editor/pending-edits";
35
+ import { applyPortableStyleSnapshotToHtml } from "@/pages/design-editor/portable-style";
36
+ import { resolveRuntimeStructureMoveExecutionMode } from "@/pages/design-editor/react-semantic-handoff";
37
+
38
+ export interface CrossScreenElementDropArgs {
39
+ applyFileContentUpdate: (
40
+ fileId: string,
41
+ nextContent: string,
42
+ options?: {
43
+ refreshPreview?: boolean;
44
+ skipPreview?: boolean;
45
+ forcePreviewFullDocument?: boolean;
46
+ persist?: boolean;
47
+ recordHistory?: boolean;
48
+ updatedAt?: string;
49
+ clipboardMutation?: ClipboardContentMutationPublication;
50
+ },
51
+ ) => void;
52
+ boardFileId: string | undefined;
53
+ canEditDesign: boolean;
54
+ clearPendingOverviewLayerSelectionTimer: () => void;
55
+ codeLayerOwnerByNodeIdRef: RefObject<
56
+ Map<
57
+ string,
58
+ {
59
+ fileId: string;
60
+ node: CodeLayerNode;
61
+ tree: CodeLayerTreeNode[];
62
+ runtimeOnly: boolean;
63
+ }
64
+ >
65
+ >;
66
+ designSourceType: "inline" | "localhost" | "fusion";
67
+ getScreenContent: (screenId: string) => string;
68
+ id: string | undefined;
69
+ overviewScreens: OverviewScreen[];
70
+ pendingOverviewLayerSelectionRef: RefObject<string | null>;
71
+ pendingOverviewScreenSelectionRef: RefObject<string | null>;
72
+ recordContentHistoryEntry: (entry: ContentHistoryEntry) => void;
73
+ runtimeStructureInsertRevisionRef: RefObject<number>;
74
+ sendRuntimeLayerMoveSemanticHandoff: (
75
+ subjectLayerId: string,
76
+ targetLayerId: string,
77
+ placement: "before" | "after" | "inside",
78
+ ) => boolean;
79
+ setActiveFileId: Dispatch<SetStateAction<string | null>>;
80
+ setCreatedOverviewLayerSelection: Dispatch<
81
+ SetStateAction<{ screenId: string; layerId: string } | null>
82
+ >;
83
+ setOverviewSelectedScreenIds: Dispatch<SetStateAction<string[]>>;
84
+ setRuntimeStructureInsertRequest: Dispatch<
85
+ SetStateAction<
86
+ (RuntimeStructureInsertRequest & { screenId: string }) | null
87
+ >
88
+ >;
89
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
90
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
91
+ t: (key: string, options?: Record<string, unknown>) => string;
92
+ viewModeRef: RefObject<"single" | "overview">;
93
+ }
94
+
95
+ export function runCrossScreenElementDrop(
96
+ {
97
+ applyFileContentUpdate,
98
+ boardFileId,
99
+ canEditDesign,
100
+ clearPendingOverviewLayerSelectionTimer,
101
+ codeLayerOwnerByNodeIdRef,
102
+ designSourceType,
103
+ getScreenContent,
104
+ id,
105
+ overviewScreens,
106
+ pendingOverviewLayerSelectionRef,
107
+ pendingOverviewScreenSelectionRef,
108
+ recordContentHistoryEntry,
109
+ runtimeStructureInsertRevisionRef,
110
+ sendRuntimeLayerMoveSemanticHandoff,
111
+ setActiveFileId,
112
+ setCreatedOverviewLayerSelection,
113
+ setOverviewSelectedScreenIds,
114
+ setRuntimeStructureInsertRequest,
115
+ setSelectedElement,
116
+ setSelectedLayerIdsState,
117
+ t,
118
+ viewModeRef,
119
+ }: CrossScreenElementDropArgs,
120
+ {
121
+ sourceSelector,
122
+ sourceNodeId,
123
+ sourceScreenId,
124
+ targetScreenId,
125
+ targetAnchorNodeId,
126
+ targetAnchorPendingNodeId,
127
+ targetAnchorSelector,
128
+ targetAnchorPlacement,
129
+ targetDropMode,
130
+ targetAnchorRect,
131
+ targetLocalPoint,
132
+ sourcePointerOffset,
133
+ sourceHtmlSnapshot,
134
+ styleSnapshot,
135
+ }: {
136
+ sourceSelector: string;
137
+ sourceNodeId?: string;
138
+ sourceScreenId: string;
139
+ targetScreenId: string;
140
+ targetAnchorNodeId?: string;
141
+ targetAnchorPendingNodeId?: string;
142
+ targetAnchorSelector?: string;
143
+ targetAnchorPlacement?: "before" | "after" | "inside";
144
+ targetDropMode?: "flow-insert" | "absolute-container";
145
+ targetAnchorRect?: {
146
+ left: number;
147
+ top: number;
148
+ width: number;
149
+ height: number;
150
+ };
151
+ targetCanvasPoint?: { x: number; y: number };
152
+ targetLocalPoint?: { x: number; y: number };
153
+ sourcePointerOffset?: { x: number; y: number };
154
+ sourceHtmlSnapshot?: string;
155
+ styleSnapshot?: PortableStyleSnapshot;
156
+ },
157
+ ) {
158
+ dndHostLog("persist:cross-screen", {
159
+ sourceScreenId,
160
+ targetScreenId,
161
+ targetAnchorPlacement,
162
+ targetDropMode,
163
+ });
164
+ trace("drop", "cross-screen-persist", {
165
+ from: sourceScreenId,
166
+ to: targetScreenId,
167
+ mode: targetDropMode,
168
+ placement: targetAnchorPlacement,
169
+ anchor: targetAnchorNodeId ?? targetAnchorSelector ?? null,
170
+ node: sourceNodeId ?? sourceSelector,
171
+ blocked: !canEditDesign
172
+ ? "read-only design"
173
+ : sourceScreenId === targetScreenId
174
+ ? "same screen — nothing to move"
175
+ : null,
176
+ });
177
+ if (!canEditDesign) return;
178
+ if (sourceScreenId === targetScreenId) return;
179
+
180
+ const findLayerOwner = (
181
+ screenId: string,
182
+ nodeId: string | undefined,
183
+ selector: string | undefined,
184
+ ) =>
185
+ Array.from(codeLayerOwnerByNodeIdRef.current.entries()).find(
186
+ ([candidateId, owner]) =>
187
+ owner.fileId === screenId &&
188
+ ((nodeId &&
189
+ (candidateId === nodeId ||
190
+ bridgeSourceIdForCodeLayerNode(owner.node) === nodeId)) ||
191
+ (selector && owner.node.selector === selector)),
192
+ );
193
+ const sourceOwnerEntry = findLayerOwner(
194
+ sourceScreenId,
195
+ sourceNodeId,
196
+ sourceSelector,
197
+ );
198
+ const targetOwnerEntry = findLayerOwner(
199
+ targetScreenId,
200
+ targetAnchorNodeId,
201
+ targetAnchorSelector,
202
+ );
203
+ // A live localhost destination has no editable stored document — its
204
+ // stored "content" is the bridge URL — so the source-edit path below
205
+ // would write a whole HTML document over that URL and never reach the
206
+ // running app. Key off the destination SCREEN's source type: a live
207
+ // anchor normally has no stored layer owner at all, so both runtimeOnly
208
+ // flags read false and the drop looks like an ordinary source move.
209
+ const crossScreenExecutionMode = resolveRuntimeStructureMoveExecutionMode({
210
+ subjectRuntimeOnly: Boolean(sourceOwnerEntry?.[1].runtimeOnly),
211
+ targetRuntimeOnly: Boolean(targetOwnerEntry?.[1].runtimeOnly),
212
+ sourceScreenId,
213
+ targetScreenId,
214
+ // Only a board primitive may be reinterpreted as an insert; a real
215
+ // screen's element dropped into a live app is a move, and inserting it
216
+ // would leave a duplicate behind in its own screen.
217
+ sourceScreenIsBoard: Boolean(boardFileId) && sourceScreenId === boardFileId,
218
+ targetScreenIsLive: (() => {
219
+ // overviewScreens deliberately excludes the board file, and
220
+ // resolveOverviewScreenSourceType answers with the DESIGN-level
221
+ // fallback for an unknown screen. Trusting that fallback would
222
+ // route a live→board drop into the board's own preview DOM, where
223
+ // nothing is persisted and the node disappears on next render.
224
+ // An unresolved screen is not a live screen.
225
+ const targetScreen = overviewScreens.find(
226
+ (screen) => screen.id === targetScreenId,
227
+ );
228
+ return (
229
+ Boolean(targetScreen) &&
230
+ resolveOverviewScreenSourceType(targetScreen, designSourceType) ===
231
+ "localhost"
232
+ );
233
+ })(),
234
+ });
235
+ if (crossScreenExecutionMode === "screen-bridge-insert") {
236
+ const boardContent = getScreenContent(sourceScreenId);
237
+ if (!boardContent) return;
238
+ const boardProjection = buildCodeLayerProjection(boardContent);
239
+ const subjectNode = sourceNodeId
240
+ ? (boardProjection.nodes.find(
241
+ (node) =>
242
+ node.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
243
+ node.id === sourceNodeId,
244
+ ) ??
245
+ resolveCodeLayerNodeFromBridge(
246
+ boardProjection,
247
+ sourceSelector,
248
+ sourceNodeId,
249
+ ))
250
+ : resolveCodeLayerNodeFromBridge(boardProjection, sourceSelector);
251
+ const subjectNodeId =
252
+ subjectNode?.dataAttributes["data-agent-native-node-id"];
253
+ const validatedSourceHtmlSnapshot =
254
+ subjectNodeId && sourceHtmlSnapshot
255
+ ? validateCrossScreenSourceHtmlSnapshot(
256
+ sourceHtmlSnapshot,
257
+ subjectNodeId,
258
+ )
259
+ : undefined;
260
+ if (sourceHtmlSnapshot && !validatedSourceHtmlSnapshot) {
261
+ toast.error(t("designEditor.toasts.layerMoveFailed"), {
262
+ duration: 4000,
263
+ });
264
+ return;
265
+ }
266
+ // Reuse the stored-document transforms instead of slicing the source
267
+ // span: they already own absolute/flow semantics. The portable style
268
+ // snapshot is always inlined (no sourceContent argument) — a live app
269
+ // never shares the board's stylesheet head, so the node would land
270
+ // unstyled otherwise.
271
+ const insertedHtml = subjectNodeId
272
+ ? (() => {
273
+ const styled = applyPortableStyleSnapshotToHtml(
274
+ validatedSourceHtmlSnapshot ?? boardContent,
275
+ subjectNodeId,
276
+ styleSnapshot,
277
+ );
278
+ const positioned =
279
+ targetDropMode === "absolute-container" &&
280
+ targetLocalPoint &&
281
+ targetAnchorRect
282
+ ? setAbsolutePositioningForNodeInHtml(
283
+ styled,
284
+ subjectNodeId,
285
+ {
286
+ x: targetLocalPoint.x - targetAnchorRect.left,
287
+ y: targetLocalPoint.y - targetAnchorRect.top,
288
+ },
289
+ sourcePointerOffset,
290
+ )
291
+ : removeAbsolutePositioningFromNodeInHtml(styled, subjectNodeId);
292
+ return new DOMParser()
293
+ .parseFromString(positioned, "text/html")
294
+ .querySelector(
295
+ `[data-agent-native-node-id="${CSS.escape(subjectNodeId)}"]`,
296
+ )?.outerHTML;
297
+ })()
298
+ : undefined;
299
+ if (!insertedHtml) {
300
+ toast.error(t("designEditor.toasts.layerMoveFailed"), {
301
+ duration: 4000,
302
+ });
303
+ return;
304
+ }
305
+ runtimeStructureInsertRevisionRef.current += 1;
306
+ setRuntimeStructureInsertRequest({
307
+ requestId: runtimeStructureInsertRevisionRef.current,
308
+ screenId: targetScreenId,
309
+ html: insertedHtml,
310
+ anchor: {
311
+ selector: targetAnchorSelector ?? "",
312
+ sourceId: targetAnchorNodeId,
313
+ pendingNodeId: targetAnchorPendingNodeId,
314
+ },
315
+ placement: targetAnchorPlacement ?? "inside",
316
+ });
317
+ // The board keeps its copy until the pending live edit is applied.
318
+ // Removing it here would commit the board file immediately while the
319
+ // destination is still only a pending live edit, and undo pops whichever
320
+ // stack is newer — one Cmd+Z would revert half the gesture, and a drop
321
+ // that is never applied would lose the primitive entirely.
322
+ return;
323
+ }
324
+ if (crossScreenExecutionMode === "semantic-handoff") {
325
+ if (!sourceOwnerEntry || !targetOwnerEntry) {
326
+ // A runtime/source cross-screen drop without an exact target (for
327
+ // example, dropping on the bare screen root) cannot satisfy the
328
+ // semantic handoff's two-anchor contract. Do not fall through to a
329
+ // selector guess or mutate stored wrapper HTML that does not own the
330
+ // runtime React node.
331
+ toast.error(t("designEditor.toasts.reactSourceAnchorsLoading"));
332
+ return;
333
+ }
334
+ sendRuntimeLayerMoveSemanticHandoff(
335
+ sourceOwnerEntry[0],
336
+ targetOwnerEntry[0],
337
+ targetAnchorPlacement ?? "inside",
338
+ );
339
+ return;
340
+ }
341
+
342
+ const sourceContent = getScreenContent(sourceScreenId);
343
+ const rawDestContent = getScreenContent(targetScreenId);
344
+ if (!sourceContent || !rawDestContent) return;
345
+
346
+ // Id-on-demand handshake (two-step, mirroring the element-select
347
+ // persist-on-select path above): AI-generated/duplicated screens often
348
+ // carry ZERO data-agent-native-node-id attributes, so the hit-test
349
+ // bridge can't return an anchor id — it mints a pendingNodeId (stamped
350
+ // on the LIVE dest DOM as data-an-pending-node-id) plus a
351
+ // source-equivalent structural anchorSelector. Persist that pending id
352
+ // as the anchor's real node id in the STORED dest document first, then
353
+ // resolve the drop against it — otherwise every flow-insert into an
354
+ // id-less screen silently degrades to absolute placement even though
355
+ // the hit-test found a valid before/after/inside slot. applyVisualEdit
356
+ // resolves the selector STRICTLY (unique match or conflict), so a
357
+ // selector that can't be honestly mapped to one source element (e.g.
358
+ // Alpine template instances) leaves the absolute fallback untouched
359
+ // rather than ever stamping the wrong node.
360
+ let destContent = rawDestContent;
361
+ let effectiveAnchorNodeId = targetAnchorNodeId;
362
+ if (
363
+ !targetAnchorNodeId &&
364
+ targetAnchorPendingNodeId &&
365
+ targetAnchorSelector
366
+ ) {
367
+ const stamped = applyVisualEdit(
368
+ rawDestContent,
369
+ {
370
+ kind: "attribute",
371
+ target: { selector: targetAnchorSelector },
372
+ name: "data-agent-native-node-id",
373
+ value: targetAnchorPendingNodeId,
374
+ },
375
+ {
376
+ source: {
377
+ kind: "design-file",
378
+ designId: id,
379
+ fileId: targetScreenId,
380
+ },
381
+ },
382
+ );
383
+ if (
384
+ stamped.result.status === "applied" &&
385
+ stamped.content !== rawDestContent
386
+ ) {
387
+ destContent = stamped.content;
388
+ effectiveAnchorNodeId = targetAnchorPendingNodeId;
389
+ } else {
390
+ // Silent degradation: the hit-test found a valid before/after/
391
+ // inside slot, but the pending anchor id couldn't be honestly
392
+ // persisted (e.g. targetAnchorSelector resolved to an Alpine
393
+ // template instance or no longer matches uniquely) — this drop
394
+ // falls through to absolute placement below with no anchor at
395
+ // all. Surface it instead of failing quietly; fallback behavior
396
+ // itself is intentionally unchanged. Dev-only: this is a known,
397
+ // handled degraded path (not a correctness bug), so keep
398
+ // production consoles quiet — see DESIGN_EDITOR_DEBUG_LOGS.
399
+ if (DESIGN_EDITOR_DEBUG_LOGS) {
400
+ console.warn(
401
+ "[design] cross-screen drop: could not stamp pending anchor node id — falling back to absolute placement",
402
+ {
403
+ targetScreenId,
404
+ targetAnchorSelector,
405
+ targetAnchorPendingNodeId,
406
+ status: stamped.result.status,
407
+ },
408
+ );
409
+ }
410
+ }
411
+ }
412
+
413
+ // Resolve the data-agent-native-node-id that moveNodeBetweenDocuments
414
+ // uses as a stable key. Prefer the bridge-supplied sourceNodeId when it
415
+ // looks like a node-attr id; otherwise look up via selector projection.
416
+ const sourceProjection = buildCodeLayerProjection(sourceContent);
417
+ const resolvedSourceNode = sourceNodeId
418
+ ? (sourceProjection.nodes.find(
419
+ (n) =>
420
+ n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
421
+ n.id === sourceNodeId,
422
+ ) ??
423
+ resolveCodeLayerNodeFromBridge(
424
+ sourceProjection,
425
+ sourceSelector,
426
+ sourceNodeId,
427
+ ))
428
+ : resolveCodeLayerNodeFromBridge(sourceProjection, sourceSelector);
429
+ const nodeAttrId =
430
+ resolvedSourceNode?.dataAttributes["data-agent-native-node-id"] ??
431
+ sourceNodeId ??
432
+ sourceSelector;
433
+ const destProjection = buildCodeLayerProjection(destContent);
434
+ const resolvedTargetAnchor = effectiveAnchorNodeId
435
+ ? resolveCodeLayerNodeFromBridge(
436
+ destProjection,
437
+ undefined,
438
+ effectiveAnchorNodeId,
439
+ )
440
+ : null;
441
+ const targetAnchorAttrId =
442
+ resolvedTargetAnchor?.dataAttributes["data-agent-native-node-id"];
443
+
444
+ // Use hit-test anchor when the canvas supplied one; fall back to
445
+ // top-level body append ("inside" with no anchor = existing behaviour).
446
+ const result = moveNodeBetweenDocuments(sourceContent, destContent, {
447
+ nodeId: nodeAttrId,
448
+ ...(targetAnchorAttrId
449
+ ? {
450
+ anchorNodeId: targetAnchorAttrId,
451
+ placement: targetAnchorPlacement ?? "inside",
452
+ }
453
+ : { placement: "inside" }),
454
+ });
455
+ if (result.status !== "applied") {
456
+ toast.error(
457
+ codeLayerPatchMessage(
458
+ result.message,
459
+ t("designEditor.toasts.layerMoveFailed"),
460
+ ),
461
+ { duration: 4000 },
462
+ );
463
+ return;
464
+ }
465
+ // Finding 8: see the same-screen move's identical handling above —
466
+ // the anchor placement was redirected out of a <template> interior to
467
+ // right after the enclosing template's close instead of failing or
468
+ // teleporting to doc end.
469
+ if (result.anchorRedirected) {
470
+ toast(t("designEditor.toasts.layerMoveRedirected"), {
471
+ duration: 4000,
472
+ });
473
+ }
474
+
475
+ // Hit-test anchors are emitted only for auto-layout insertion targets. If
476
+ // there is no anchor, preserve absolute mode and rebase left/top to the
477
+ // release point so screen↔board moves behave like Figma absolute layers.
478
+ const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
479
+ const styleSnapshotDest = applyPortableStyleSnapshotToHtml(
480
+ result.destHtml,
481
+ destNodeAttrId,
482
+ styleSnapshot,
483
+ sourceContent,
484
+ );
485
+ // Finding 8: board/screen text carrying the auto-applied white default
486
+ // (see BOARD_TEXT_AUTO_COLOR_MARKER / defaultCanvasTextColor) must not
487
+ // keep that forced white when it lands cross-screen in a light
488
+ // destination — otherwise it renders invisible white-on-white. The
489
+ // in-screen drag path already adapts via the bridge's
490
+ // adaptAutoTextColorForNest; this is the cross-screen mirror, applied
491
+ // host-side now that the node has actually been re-parented into
492
+ // destContent.
493
+ const liveDestIframe = document.querySelector<HTMLIFrameElement>(
494
+ `[data-screen-iframe-id="${CSS.escape(getPrimaryIframeId(targetScreenId))}"]`,
495
+ );
496
+ const stylePreservedDest = adaptAutoTextColorForCrossScreenNode(
497
+ styleSnapshotDest,
498
+ destNodeAttrId,
499
+ liveDestIframe?.contentDocument ?? null,
500
+ );
501
+ const nextDestContent = targetAnchorAttrId
502
+ ? targetDropMode === "absolute-container"
503
+ ? targetLocalPoint && targetAnchorRect
504
+ ? setAbsolutePositioningForNodeInHtml(
505
+ stylePreservedDest,
506
+ destNodeAttrId,
507
+ {
508
+ x: targetLocalPoint.x - targetAnchorRect.left,
509
+ y: targetLocalPoint.y - targetAnchorRect.top,
510
+ },
511
+ sourcePointerOffset,
512
+ )
513
+ : stylePreservedDest
514
+ : removeAbsolutePositioningFromNodeInHtml(
515
+ stylePreservedDest,
516
+ destNodeAttrId,
517
+ )
518
+ : targetLocalPoint
519
+ ? setAbsolutePositioningForNodeInHtml(
520
+ stylePreservedDest,
521
+ destNodeAttrId,
522
+ targetLocalPoint,
523
+ sourcePointerOffset,
524
+ )
525
+ : stylePreservedDest;
526
+
527
+ recordContentHistoryEntry({
528
+ changes: [
529
+ {
530
+ fileId: sourceScreenId,
531
+ before: sourceContent,
532
+ after: result.sourceHtml,
533
+ },
534
+ {
535
+ fileId: targetScreenId,
536
+ before: destContent,
537
+ after: nextDestContent,
538
+ },
539
+ ],
540
+ });
541
+
542
+ applyFileContentUpdate(sourceScreenId, result.sourceHtml, {
543
+ recordHistory: false,
544
+ refreshPreview: false,
545
+ forcePreviewFullDocument: true,
546
+ });
547
+ applyFileContentUpdate(targetScreenId, nextDestContent, {
548
+ recordHistory: false,
549
+ refreshPreview: false,
550
+ forcePreviewFullDocument: true,
551
+ });
552
+
553
+ // Switch active screen to the target and select the moved node; viewMode
554
+ // stays "overview" (no setViewMode call).
555
+ pendingOverviewScreenSelectionRef.current =
556
+ targetScreenId === boardFileId ? null : targetScreenId;
557
+ pendingOverviewLayerSelectionRef.current = destNodeAttrId;
558
+ clearPendingOverviewLayerSelectionTimer();
559
+ setActiveFileId(targetScreenId);
560
+ const finalProjection = buildCodeLayerProjection(nextDestContent);
561
+ const movedNodeFinal = finalProjection.nodes.find(
562
+ (n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
563
+ );
564
+ if (movedNodeFinal) {
565
+ setCreatedOverviewLayerSelection({
566
+ screenId: targetScreenId,
567
+ layerId: movedNodeFinal.id,
568
+ });
569
+ setSelectedLayerIdsState([movedNodeFinal.id]);
570
+ setSelectedElement(elementInfoFromCodeLayerNode(movedNodeFinal));
571
+ if (viewModeRef.current === "overview") {
572
+ setOverviewSelectedScreenIds(
573
+ targetScreenId === boardFileId ? [] : [targetScreenId],
574
+ );
575
+ }
576
+ }
577
+ }