@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,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
+ }