@agent-native/core 0.163.3 → 0.164.1

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 (202) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  3. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  4. package/corpus/templates/analytics/netlify.toml +2 -0
  5. package/corpus/templates/assets/netlify.toml +2 -0
  6. package/corpus/templates/brain/netlify.toml +2 -0
  7. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  8. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  9. package/corpus/templates/calendar/netlify.toml +2 -0
  10. package/corpus/templates/chat/netlify.toml +2 -0
  11. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  12. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  13. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  14. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  15. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  16. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  17. package/corpus/templates/clips/app/routes/_index.tsx +10 -2
  18. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  19. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  20. package/corpus/templates/clips/netlify.toml +2 -0
  21. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  22. package/corpus/templates/content/netlify.toml +2 -0
  23. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  24. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  25. package/corpus/templates/design/AGENTS.md +3 -1
  26. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  27. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  28. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  29. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  30. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  31. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  32. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  33. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  34. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  35. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  36. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  37. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  38. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  39. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  40. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  41. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  42. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  43. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  44. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  45. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  46. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  47. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  48. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  49. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  50. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  51. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  53. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  55. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  56. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  126. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  127. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  128. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  129. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  130. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  131. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  132. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  133. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  134. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  135. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  136. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  137. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  138. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  139. package/corpus/templates/design/netlify.toml +2 -0
  140. package/corpus/templates/design/shared/board-file.ts +2 -1
  141. package/corpus/templates/dispatch/netlify.toml +2 -0
  142. package/corpus/templates/forms/netlify.toml +2 -0
  143. package/corpus/templates/mail/netlify.toml +2 -0
  144. package/corpus/templates/plan/netlify.toml +2 -0
  145. package/corpus/templates/slides/netlify.toml +2 -0
  146. package/dist/agent/engine/builder-engine.d.ts +6 -4
  147. package/dist/agent/engine/builder-engine.js +91 -26
  148. package/dist/agent/engine/registry.js +16 -0
  149. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  150. package/dist/cli/code-agent-agent-tools.js +188 -0
  151. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  152. package/dist/cli/code-agent-collaboration.js +104 -0
  153. package/dist/cli/code-agent-executor.js +6 -0
  154. package/dist/cli/code-agent-schedules.d.ts +54 -0
  155. package/dist/cli/code-agent-schedules.js +198 -0
  156. package/dist/client/AgentPanel.js +1 -1
  157. package/dist/client/ConnectBuilderCard.js +5 -2
  158. package/dist/client/EnvironmentBadge.js +6 -5
  159. package/dist/client/chat/run-recovery.js +3 -2
  160. package/dist/client/chat/tool-call-display.js +2 -3
  161. package/dist/client/components/CodeRequiredDialog.js +4 -2
  162. package/dist/client/i18n.js +5 -4
  163. package/dist/client/settings/SettingsPanel.js +1 -1
  164. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  165. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  166. package/dist/client/settings/useBuilderStatus.js +22 -33
  167. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  168. package/dist/code-agents/index.d.ts +2 -0
  169. package/dist/code-agents/index.js +2 -0
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/localization/shared.d.ts +2 -0
  172. package/dist/localization/shared.js +5 -0
  173. package/dist/mcp-client/oauth-client.d.ts +1 -0
  174. package/dist/mcp-client/oauth-client.js +50 -1
  175. package/dist/observability/routes.d.ts +1 -1
  176. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  177. package/dist/resources/handlers.d.ts +1 -1
  178. package/dist/server/auth-marketing-locales.d.ts +7 -0
  179. package/dist/server/auth-marketing-locales.js +1322 -0
  180. package/dist/server/auth-marketing.d.ts +1 -0
  181. package/dist/server/auth-marketing.js +35 -6
  182. package/dist/server/auth.js +4 -17
  183. package/dist/server/builder-browser.d.ts +22 -0
  184. package/dist/server/builder-browser.js +77 -1
  185. package/dist/server/builder-oauth.d.ts +50 -0
  186. package/dist/server/builder-oauth.js +263 -0
  187. package/dist/server/core-routes-plugin.d.ts +11 -0
  188. package/dist/server/core-routes-plugin.js +291 -415
  189. package/dist/server/credential-provider.d.ts +1 -1
  190. package/dist/server/credential-provider.js +1 -1
  191. package/dist/server/csrf.js +2 -2
  192. package/dist/server/onboarding-html.js +52 -120
  193. package/dist/server/realtime-token.d.ts +1 -1
  194. package/dist/server/transcribe-voice.d.ts +1 -1
  195. package/dist/settings/store.d.ts +9 -0
  196. package/dist/settings/store.js +19 -0
  197. package/dist/templates/chat/netlify.toml +2 -0
  198. package/dist/vite/client.js +35 -37
  199. package/docs/content/deployment.mdx +2 -0
  200. package/docs/content/multi-app-workspace.mdx +1 -1
  201. package/package.json +2 -2
  202. package/src/templates/chat/netlify.toml +2 -0
@@ -0,0 +1,170 @@
1
+ import type { CodeLayerNode, CodeLayerTreeNode } from "@shared/code-layer";
2
+ import { applyVisualEdit, buildCodeLayerProjection } from "@shared/code-layer";
3
+ import type { Dispatch, RefObject, SetStateAction } from "react";
4
+ import { toast } from "sonner";
5
+
6
+ import { trace } from "@/components/design/design-trace";
7
+ import type { ElementInfo } from "@/components/design/types";
8
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
9
+ import {
10
+ codeLayerPatchMessage,
11
+ elementInfoFromCodeLayerNode,
12
+ } from "@/pages/design-editor/code-layer-state";
13
+ import { buildActiveFileNodeIdSet } from "@/pages/design-editor/selection-state";
14
+ import type { DesignFile } from "@/pages/design-editor/types";
15
+
16
+ export interface UngroupSelectionArgs {
17
+ activeFile: DesignFile;
18
+ applyLocalContentUpdate: (
19
+ nextContent: string,
20
+ options?: {
21
+ refreshPreview?: boolean;
22
+ skipPreview?: boolean;
23
+ forcePreviewFullDocument?: boolean;
24
+ immediateSave?: boolean;
25
+ persist?: boolean;
26
+ recordHistory?: boolean;
27
+ historyBeforeContent?: string;
28
+ updatedAt?: string;
29
+ clipboardMutation?: ClipboardContentMutationPublication;
30
+ },
31
+ ) => void;
32
+ canEditDesign: boolean;
33
+ codeLayerOwnerByNodeIdRef: RefObject<
34
+ Map<
35
+ string,
36
+ {
37
+ fileId: string;
38
+ node: CodeLayerNode;
39
+ tree: CodeLayerTreeNode[];
40
+ runtimeOnly: boolean;
41
+ }
42
+ >
43
+ >;
44
+ files: DesignFile[];
45
+ getFreshActiveContent: () => string;
46
+ selectedLayerIdsState: string[];
47
+ sendRuntimeLayerSemanticHandoff: (
48
+ operation: "group" | "ungroup" | "auto-layout",
49
+ layerIds: readonly string[],
50
+ options?: {
51
+ desiredChange?: string;
52
+ description?: string;
53
+ commandContext?: string;
54
+ },
55
+ ) => boolean;
56
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
57
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
58
+ t: (key: string, options?: Record<string, unknown>) => string;
59
+ }
60
+
61
+ export function runUngroupSelection({
62
+ activeFile,
63
+ applyLocalContentUpdate,
64
+ canEditDesign,
65
+ codeLayerOwnerByNodeIdRef,
66
+ files,
67
+ getFreshActiveContent,
68
+ selectedLayerIdsState,
69
+ sendRuntimeLayerSemanticHandoff,
70
+ setSelectedElement,
71
+ setSelectedLayerIdsState,
72
+ t,
73
+ }: UngroupSelectionArgs) {
74
+ trace("structure", "ungroup", { layers: selectedLayerIdsState.length });
75
+ if (!canEditDesign || !activeFile) return;
76
+ const selectedRuntimeLayerIds = selectedLayerIdsState.filter(
77
+ (layerId) => codeLayerOwnerByNodeIdRef.current.get(layerId)?.runtimeOnly,
78
+ );
79
+ if (selectedRuntimeLayerIds.length > 0) {
80
+ sendRuntimeLayerSemanticHandoff("ungroup", selectedRuntimeLayerIds);
81
+ return;
82
+ }
83
+ const initialContent = getFreshActiveContent();
84
+ // Filter to active-file nodes only (mirrors handleGroupSelection fix).
85
+ // A stale id from another file must not be passed to unwrap or it will
86
+ // fail with "conflict" even though the actual selection is valid.
87
+ const fileIds = new Set(files.map((f) => f.id));
88
+ const activeNodeIdSet = buildActiveFileNodeIdSet(
89
+ buildCodeLayerProjection(initialContent),
90
+ );
91
+ const targetIds = selectedLayerIdsState.filter(
92
+ (id) => !id.startsWith("__") && !fileIds.has(id) && activeNodeIdSet.has(id),
93
+ );
94
+ if (targetIds.length === 0) return;
95
+
96
+ let content = initialContent;
97
+ let anySucceeded = false;
98
+ let lastFailureMessage: string | null = null;
99
+ const releasedChildAttrIds = new Set<string>();
100
+ for (const targetId of targetIds) {
101
+ // Resolve the container's current child data-attribute ids from a
102
+ // fresh projection of the running content so ids stay accurate across
103
+ // multiple sequential unwraps in this same loop.
104
+ const runningProjection = buildCodeLayerProjection(content);
105
+ const containerNode = runningProjection.nodes.find(
106
+ (n) =>
107
+ n.dataAttributes["data-agent-native-node-id"] === targetId ||
108
+ n.id === targetId,
109
+ );
110
+ const childNodeIds = containerNode?.children ?? [];
111
+ const childAttrIds = childNodeIds
112
+ .map(
113
+ (childId) =>
114
+ runningProjection.nodes.find((n) => n.id === childId)?.dataAttributes[
115
+ "data-agent-native-node-id"
116
+ ],
117
+ )
118
+ .filter((attrId): attrId is string => Boolean(attrId));
119
+
120
+ const patch = applyVisualEdit(content, {
121
+ kind: "unwrap",
122
+ targetId,
123
+ });
124
+ if (patch.result.status !== "applied") {
125
+ lastFailureMessage = patch.result.message ?? lastFailureMessage;
126
+ continue;
127
+ }
128
+ content = patch.content;
129
+ anySucceeded = true;
130
+ childAttrIds.forEach((attrId) => releasedChildAttrIds.add(attrId));
131
+ }
132
+
133
+ if (!anySucceeded) {
134
+ toast.error(
135
+ codeLayerPatchMessage(
136
+ lastFailureMessage,
137
+ t("designEditor.toasts.layerMoveFailed"),
138
+ ),
139
+ { duration: 4000 },
140
+ );
141
+ return;
142
+ }
143
+ if (lastFailureMessage) {
144
+ toast.error(
145
+ codeLayerPatchMessage(
146
+ lastFailureMessage,
147
+ t("designEditor.toasts.layerMoveFailed"),
148
+ ),
149
+ { duration: 4000 },
150
+ );
151
+ }
152
+
153
+ applyLocalContentUpdate(content, { skipPreview: true });
154
+
155
+ const finalProjection = buildCodeLayerProjection(content);
156
+ const releasedNodes = finalProjection.nodes.filter((n) => {
157
+ const attrId = n.dataAttributes["data-agent-native-node-id"];
158
+ return attrId ? releasedChildAttrIds.has(attrId) : false;
159
+ });
160
+ if (releasedNodes.length > 0) {
161
+ setSelectedLayerIdsState(releasedNodes.map((n) => n.id));
162
+ const lastReleasedNode = releasedNodes[releasedNodes.length - 1];
163
+ if (lastReleasedNode) {
164
+ setSelectedElement(elementInfoFromCodeLayerNode(lastReleasedNode));
165
+ }
166
+ } else {
167
+ setSelectedElement(null);
168
+ setSelectedLayerIdsState([]);
169
+ }
170
+ }
@@ -0,0 +1,119 @@
1
+ import { buildCodeLayerProjection } from "@shared/code-layer";
2
+ import type { Dispatch, SetStateAction } from "react";
3
+ import { toast } from "sonner";
4
+
5
+ import type { ElementInfo } from "@/components/design/types";
6
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
7
+ import { insertClonedHtmlLayer } from "@/pages/design-editor/clone-and-pen-edit";
8
+ import {
9
+ bridgeSourceIdForCodeLayerNode,
10
+ codeLayerSelectorAliases,
11
+ elementInfoFromCodeLayerNode,
12
+ preferredCodeLayerSelector,
13
+ resolveCodeLayerNodeFromBridge,
14
+ resolveCodeLayerNodeFromElementInfo,
15
+ } from "@/pages/design-editor/code-layer-state";
16
+ import type { DesignFile } from "@/pages/design-editor/types";
17
+
18
+ export interface VisualDuplicateChangeArgs {
19
+ activeFile: DesignFile;
20
+ applyLocalContentUpdate: (
21
+ nextContent: string,
22
+ options?: {
23
+ refreshPreview?: boolean;
24
+ skipPreview?: boolean;
25
+ forcePreviewFullDocument?: boolean;
26
+ immediateSave?: boolean;
27
+ persist?: boolean;
28
+ recordHistory?: boolean;
29
+ historyBeforeContent?: string;
30
+ updatedAt?: string;
31
+ clipboardMutation?: ClipboardContentMutationPublication;
32
+ },
33
+ ) => void;
34
+ canEditDesign: boolean;
35
+ getFreshActiveContent: () => string;
36
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
37
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
38
+ t: (key: string, options?: Record<string, unknown>) => string;
39
+ }
40
+
41
+ export function runVisualDuplicateChange(
42
+ {
43
+ activeFile,
44
+ applyLocalContentUpdate,
45
+ canEditDesign,
46
+ getFreshActiveContent,
47
+ setSelectedElement,
48
+ setSelectedLayerIdsState,
49
+ t,
50
+ }: VisualDuplicateChangeArgs,
51
+ selector: string,
52
+ cloneHtml: string,
53
+ elementInfo?: ElementInfo,
54
+ details?: {
55
+ sourceId?: string;
56
+ anchorSelector?: string;
57
+ anchorSourceId?: string;
58
+ placement?: "before" | "after" | "inside";
59
+ },
60
+ ) {
61
+ if (!canEditDesign) return false;
62
+ if (!activeFile) return false;
63
+ const baseContent = getFreshActiveContent();
64
+ const projection = buildCodeLayerProjection(baseContent);
65
+ const targetInfo = elementInfo
66
+ ? {
67
+ ...elementInfo,
68
+ selector,
69
+ sourceId: details?.sourceId ?? elementInfo.sourceId,
70
+ }
71
+ : null;
72
+ const targetNode = targetInfo
73
+ ? resolveCodeLayerNodeFromElementInfo(projection, targetInfo)
74
+ : resolveCodeLayerNodeFromBridge(projection, selector, details?.sourceId);
75
+ const anchorNode = resolveCodeLayerNodeFromBridge(
76
+ projection,
77
+ details?.anchorSelector,
78
+ details?.anchorSourceId,
79
+ );
80
+ const nextContent = insertClonedHtmlLayer(baseContent, cloneHtml, {
81
+ targetSelectors: targetNode
82
+ ? codeLayerSelectorAliases(targetNode)
83
+ : [selector],
84
+ anchorSelectors: anchorNode
85
+ ? codeLayerSelectorAliases(anchorNode)
86
+ : details?.anchorSelector
87
+ ? [details.anchorSelector]
88
+ : undefined,
89
+ placement: details?.placement ?? "after",
90
+ });
91
+ if (!nextContent) {
92
+ toast.error(t("designEditor.toasts.layerMoveFailed"), {
93
+ duration: 4000,
94
+ });
95
+ return false;
96
+ }
97
+ // Structural insert: a selector-scoped push matches the live clone but
98
+ // not the re-keyed copy in the new source, and the bridge deletes what
99
+ // it cannot match.
100
+ applyLocalContentUpdate(nextContent, {
101
+ refreshPreview: false,
102
+ forcePreviewFullDocument: true,
103
+ });
104
+ const nextProjection = buildCodeLayerProjection(nextContent);
105
+ const nextNode = elementInfo
106
+ ? resolveCodeLayerNodeFromElementInfo(nextProjection, elementInfo)
107
+ : null;
108
+ if (nextNode) {
109
+ setSelectedLayerIdsState([nextNode.id]);
110
+ setSelectedElement({
111
+ ...(elementInfo ?? elementInfoFromCodeLayerNode(nextNode)),
112
+ sourceId: bridgeSourceIdForCodeLayerNode(nextNode),
113
+ selector: preferredCodeLayerSelector(nextNode),
114
+ });
115
+ } else if (elementInfo) {
116
+ setSelectedElement(elementInfo);
117
+ }
118
+ return true;
119
+ }
@@ -0,0 +1,274 @@
1
+ import { stripBoardSurfaceOffsetFromCoord } from "@shared/board-file";
2
+ import { applyVisualEdit, buildCodeLayerProjection } from "@shared/code-layer";
3
+ import type { Dispatch, SetStateAction } from "react";
4
+ import { toast } from "sonner";
5
+
6
+ import { dndHostLog } from "@/components/design/dnd-debug";
7
+ import type { ElementInfo } from "@/components/design/types";
8
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
9
+ import {
10
+ bridgeSourceIdForCodeLayerNode,
11
+ codeLayerPatchMessage,
12
+ preferredCodeLayerSelector,
13
+ resolveCodeLayerNodeFromBridge,
14
+ resolveCodeLayerNodeFromElementInfo,
15
+ } from "@/pages/design-editor/code-layer-state";
16
+ import {
17
+ isAbsoluteCodeLayerNode,
18
+ removeAbsolutePositioningFromNodeInHtml,
19
+ setAbsolutePositioningForNodeInHtml,
20
+ setFlowPositioningOverrideForNodeInHtml,
21
+ } from "@/pages/design-editor/html-layer-positioning";
22
+ import type { DesignFile } from "@/pages/design-editor/types";
23
+
24
+ export interface VisualStructureChangeArgs {
25
+ activeCanvasSourceType: "inline" | "localhost" | "fusion";
26
+ activeFile: DesignFile;
27
+ applyLocalContentUpdate: (
28
+ nextContent: string,
29
+ options?: {
30
+ refreshPreview?: boolean;
31
+ skipPreview?: boolean;
32
+ forcePreviewFullDocument?: boolean;
33
+ immediateSave?: boolean;
34
+ persist?: boolean;
35
+ recordHistory?: boolean;
36
+ historyBeforeContent?: string;
37
+ updatedAt?: string;
38
+ clipboardMutation?: ClipboardContentMutationPublication;
39
+ },
40
+ ) => void;
41
+ canEditDesign: boolean;
42
+ getFreshActiveContent: () => string;
43
+ recordPendingLiveStructureEdit: (
44
+ screenId: string,
45
+ selector: string,
46
+ anchorSelector: string,
47
+ placement: "before" | "after" | "inside",
48
+ elementInfo?: ElementInfo,
49
+ details?: {
50
+ sourceId?: string;
51
+ anchorSourceId?: string;
52
+ anchorElementInfo?: ElementInfo;
53
+ requestId?: string;
54
+ dropMode?: "flow-insert" | "absolute-container";
55
+ forceFlowPositionOverride?: boolean;
56
+ sourceRect?: { x: number; y: number; width: number; height: number };
57
+ anchorRect?: { x: number; y: number; width: number; height: number };
58
+ insertedHtml?: string;
59
+ replaced?: true;
60
+ replacementSelector?: string;
61
+ replacementSourceId?: string;
62
+ removed?: true;
63
+ },
64
+ ) => void;
65
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
66
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
67
+ t: (key: string, options?: Record<string, unknown>) => string;
68
+ }
69
+
70
+ export function runVisualStructureChange(
71
+ {
72
+ activeCanvasSourceType,
73
+ activeFile,
74
+ applyLocalContentUpdate,
75
+ canEditDesign,
76
+ getFreshActiveContent,
77
+ recordPendingLiveStructureEdit,
78
+ setSelectedElement,
79
+ setSelectedLayerIdsState,
80
+ t,
81
+ }: VisualStructureChangeArgs,
82
+ selector: string,
83
+ anchorSelector: string,
84
+ placement: "before" | "after" | "inside",
85
+ elementInfo?: ElementInfo,
86
+ details?: {
87
+ sourceId?: string;
88
+ anchorSourceId?: string;
89
+ anchorElementInfo?: ElementInfo;
90
+ requestId?: string;
91
+ dropMode?: "flow-insert" | "absolute-container";
92
+ forceFlowPositionOverride?: boolean;
93
+ sourceRect?: { x: number; y: number; width: number; height: number };
94
+ anchorRect?: { x: number; y: number; width: number; height: number };
95
+ /** Markup this change introduced; the subject does not exist in the
96
+ * screen's source yet, so it must be added rather than relocated. */
97
+ insertedHtml?: string;
98
+ replaced?: true;
99
+ replacementSelector?: string;
100
+ replacementSourceId?: string;
101
+ },
102
+ ) {
103
+ dndHostLog("persist:begin", {
104
+ selector,
105
+ anchorSelector,
106
+ placement,
107
+ dropMode: details?.dropMode,
108
+ source: activeCanvasSourceType,
109
+ });
110
+ if (!canEditDesign) return false;
111
+ if (!activeFile) return false;
112
+ if (activeCanvasSourceType === "localhost") {
113
+ recordPendingLiveStructureEdit(
114
+ activeFile.id,
115
+ selector,
116
+ anchorSelector,
117
+ placement,
118
+ elementInfo,
119
+ details,
120
+ );
121
+ return "pending";
122
+ }
123
+ const baseContent = getFreshActiveContent();
124
+ const projection = buildCodeLayerProjection(baseContent);
125
+ const resolveBridgeNode = (targetSelector: string, sourceId?: string) =>
126
+ resolveCodeLayerNodeFromBridge(projection, targetSelector, sourceId);
127
+ const targetInfo = elementInfo
128
+ ? {
129
+ ...elementInfo,
130
+ selector,
131
+ sourceId: details?.sourceId ?? elementInfo.sourceId,
132
+ }
133
+ : null;
134
+ const targetNode = targetInfo
135
+ ? resolveCodeLayerNodeFromElementInfo(projection, targetInfo)
136
+ : resolveBridgeNode(selector, details?.sourceId);
137
+ const anchorNode = resolveBridgeNode(anchorSelector, details?.anchorSourceId);
138
+ const patch = applyVisualEdit(baseContent, {
139
+ kind: "moveNode",
140
+ // Keep the bridge's stable source id on the fallback: resolving by
141
+ // selector alone fails for stamped nodes, and the resolver tries
142
+ // nodeId first before falling back to the selector anyway.
143
+ target: targetNode
144
+ ? { nodeId: targetNode.id }
145
+ : details?.sourceId
146
+ ? { nodeId: details.sourceId, selector }
147
+ : { selector },
148
+ anchor: anchorNode
149
+ ? { nodeId: anchorNode.id }
150
+ : details?.anchorSourceId
151
+ ? { nodeId: details.anchorSourceId, selector: anchorSelector }
152
+ : { selector: anchorSelector },
153
+ placement,
154
+ });
155
+ dndHostLog("persist:rewrite", {
156
+ status: patch.result.status,
157
+ message: patch.result.message,
158
+ });
159
+ if (patch.result.status !== "applied") {
160
+ toast.error(
161
+ codeLayerPatchMessage(
162
+ patch.result.message,
163
+ t("designEditor.toasts.layerMoveFailed"),
164
+ ),
165
+ { duration: 4000 },
166
+ );
167
+ return false;
168
+ }
169
+ const movedNodeAttrId =
170
+ targetNode?.dataAttributes["data-agent-native-node-id"] ??
171
+ details?.sourceId ??
172
+ elementInfo?.sourceId ??
173
+ (patch.result.after?.nodeId
174
+ ? patch.projection.nodes.find(
175
+ (node) => node.id === patch.result.after?.nodeId,
176
+ )?.dataAttributes["data-agent-native-node-id"]
177
+ : undefined);
178
+ // Absolute-container drops persist sourceRect − anchorRect (both
179
+ // measured in-iframe by the bridge AFTER its optimistic DOM move). On
180
+ // the BOARD surface, top-level elements carry the content-offset
181
+ // translate (+65536 — see embeddedContentOffsetStyle in
182
+ // DesignCanvas.tsx) while nested ones do not, and the bridge's
183
+ // rect-space delta math doesn't model that translate — the measured
184
+ // offset for a board nest comes out exactly one surface offset
185
+ // (65536px) away from the true parent-relative value and, persisted
186
+ // verbatim, parks the nested child off-world. Strip that fingerprint
187
+ // before persisting (a no-op for screens and for sane offsets), and
188
+ // when it fired, ALSO refresh the preview: the bridge's optimistic
189
+ // in-iframe placement was off by the same 65536, so the iframe must be
190
+ // re-rendered from the corrected content instead of being trusted.
191
+ const rawAbsoluteContainerOffset =
192
+ details?.dropMode === "absolute-container" &&
193
+ details.sourceRect &&
194
+ details.anchorRect
195
+ ? {
196
+ x: details.sourceRect.x - details.anchorRect.x,
197
+ y: details.sourceRect.y - details.anchorRect.y,
198
+ }
199
+ : null;
200
+ const absoluteContainerOffset = rawAbsoluteContainerOffset
201
+ ? {
202
+ x: stripBoardSurfaceOffsetFromCoord(rawAbsoluteContainerOffset.x),
203
+ y: stripBoardSurfaceOffsetFromCoord(rawAbsoluteContainerOffset.y),
204
+ }
205
+ : null;
206
+ const absoluteOffsetWasPoisoned = Boolean(
207
+ rawAbsoluteContainerOffset &&
208
+ absoluteContainerOffset &&
209
+ (rawAbsoluteContainerOffset.x !== absoluteContainerOffset.x ||
210
+ rawAbsoluteContainerOffset.y !== absoluteContainerOffset.y),
211
+ );
212
+ const nextContent =
213
+ movedNodeAttrId && details?.dropMode === "absolute-container"
214
+ ? absoluteContainerOffset
215
+ ? setAbsolutePositioningForNodeInHtml(
216
+ patch.content,
217
+ movedNodeAttrId,
218
+ absoluteContainerOffset,
219
+ )
220
+ : patch.content
221
+ : movedNodeAttrId &&
222
+ details?.dropMode === "flow-insert" &&
223
+ details.forceFlowPositionOverride
224
+ ? setFlowPositioningOverrideForNodeInHtml(
225
+ patch.content,
226
+ movedNodeAttrId,
227
+ )
228
+ : isAbsoluteCodeLayerNode(targetNode) && movedNodeAttrId
229
+ ? removeAbsolutePositioningFromNodeInHtml(
230
+ patch.content,
231
+ movedNodeAttrId,
232
+ )
233
+ : patch.content;
234
+ const nextProjection = buildCodeLayerProjection(nextContent);
235
+ const movedNode =
236
+ (movedNodeAttrId
237
+ ? nextProjection.nodes.find(
238
+ (node) =>
239
+ node.dataAttributes["data-agent-native-node-id"] ===
240
+ movedNodeAttrId,
241
+ )
242
+ : null) ??
243
+ (patch.result.after?.nodeId
244
+ ? nextProjection.nodes.find(
245
+ (node) => node.id === patch.result.after?.nodeId,
246
+ )
247
+ : null) ??
248
+ resolveCodeLayerNodeFromBridge(
249
+ nextProjection,
250
+ selector,
251
+ details?.sourceId ??
252
+ elementInfo?.sourceId ??
253
+ (targetNode ? bridgeSourceIdForCodeLayerNode(targetNode) : undefined),
254
+ );
255
+ applyLocalContentUpdate(
256
+ nextContent,
257
+ absoluteOffsetWasPoisoned
258
+ ? { forcePreviewFullDocument: true }
259
+ : { skipPreview: true },
260
+ );
261
+ if (movedNode) setSelectedLayerIdsState([movedNode.id]);
262
+ if (elementInfo) {
263
+ setSelectedElement({
264
+ ...elementInfo,
265
+ sourceId: movedNode
266
+ ? bridgeSourceIdForCodeLayerNode(movedNode)
267
+ : elementInfo.sourceId,
268
+ selector: movedNode
269
+ ? preferredCodeLayerSelector(movedNode)
270
+ : elementInfo.selector,
271
+ });
272
+ }
273
+ return true;
274
+ }
@@ -0,0 +1,99 @@
1
+ import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
2
+ import type { QueryClient } from "@tanstack/react-query";
3
+ import type { RefObject } from "react";
4
+
5
+ import type { DesignDataOperation } from "@/pages/design-editor/data-operations";
6
+ import {
7
+ applyDesignDataOperations,
8
+ buildFrameGeometryDataOperations,
9
+ compactDesignDataOperations,
10
+ } from "@/pages/design-editor/data-operations";
11
+ import {
12
+ cloneCanvasFrameGeometry,
13
+ getCanvasFrameGeometry,
14
+ } from "@/pages/design-editor/design-data-geometry-utils";
15
+ import { sanitizeCanvasFrameGeometryForPersist } from "@/pages/design-editor/geometry-persistence";
16
+
17
+ export interface WriteFrameGeometrySnapshotArgs {
18
+ boardFileId: string | undefined;
19
+ canEditDesignRef: RefObject<boolean>;
20
+ designDataJsonRef: RefObject<Record<string, unknown>>;
21
+ enqueueFrameGeometryDataSave: (
22
+ dataOperations: DesignDataOperation[],
23
+ ) => boolean;
24
+ frameGeometrySaveTimerRef: RefObject<number | null>;
25
+ id: string | undefined;
26
+ pendingFrameGeometrySaveRef: RefObject<{
27
+ geometryById: CanvasFrameGeometryById;
28
+ previousGeometry: CanvasFrameGeometryById;
29
+ } | null>;
30
+ queryClient: QueryClient;
31
+ }
32
+
33
+ export function runWriteFrameGeometrySnapshot(
34
+ {
35
+ boardFileId,
36
+ canEditDesignRef,
37
+ designDataJsonRef,
38
+ enqueueFrameGeometryDataSave,
39
+ frameGeometrySaveTimerRef,
40
+ id,
41
+ pendingFrameGeometrySaveRef,
42
+ queryClient,
43
+ }: WriteFrameGeometrySnapshotArgs,
44
+ geometryById: CanvasFrameGeometryById,
45
+ options?: {
46
+ syncViewportFrameIds?: string[];
47
+ pinHeightFrameIds?: string[];
48
+ },
49
+ ) {
50
+ if (!id || !canEditDesignRef.current) return;
51
+ const queuedSave = pendingFrameGeometrySaveRef.current;
52
+ if (frameGeometrySaveTimerRef.current !== null) {
53
+ window.clearTimeout(frameGeometrySaveTimerRef.current);
54
+ frameGeometrySaveTimerRef.current = null;
55
+ }
56
+ pendingFrameGeometrySaveRef.current = null;
57
+ // Geometry-persist guard — same contract as queueFrameGeometrySave:
58
+ // never let absurd frame geometry (a corrupted zoom basis' product)
59
+ // reach canvasFrames or the screenMetadata viewport sync below.
60
+ const { geometryById: safeGeometryById } =
61
+ sanitizeCanvasFrameGeometryForPersist(
62
+ geometryById,
63
+ getCanvasFrameGeometry(designDataJsonRef.current),
64
+ boardFileId ? [boardFileId] : [],
65
+ );
66
+ const snapshot = cloneCanvasFrameGeometry(safeGeometryById);
67
+ const queuedOperations = queuedSave
68
+ ? buildFrameGeometryDataOperations({
69
+ previousGeometry: queuedSave.previousGeometry,
70
+ nextGeometry: sanitizeCanvasFrameGeometryForPersist(
71
+ queuedSave.geometryById,
72
+ queuedSave.previousGeometry,
73
+ boardFileId ? [boardFileId] : [],
74
+ ).geometryById,
75
+ designData: designDataJsonRef.current,
76
+ })
77
+ : [];
78
+ const dataOperations = compactDesignDataOperations([
79
+ ...queuedOperations,
80
+ ...buildFrameGeometryDataOperations({
81
+ previousGeometry: getCanvasFrameGeometry(designDataJsonRef.current),
82
+ nextGeometry: snapshot,
83
+ designData: designDataJsonRef.current,
84
+ syncViewportFrameIds: options?.syncViewportFrameIds,
85
+ pinHeightFrameIds: options?.pinHeightFrameIds,
86
+ }),
87
+ ]);
88
+ if (dataOperations.length === 0) return;
89
+ const nextData = applyDesignDataOperations(
90
+ designDataJsonRef.current,
91
+ dataOperations,
92
+ );
93
+ designDataJsonRef.current = nextData;
94
+ queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
95
+ if (!old || typeof old !== "object") return old;
96
+ return { ...old, data: JSON.stringify(nextData) };
97
+ });
98
+ enqueueFrameGeometryDataSave(dataOperations);
99
+ }