@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,213 @@
1
+ import type { CanvasFrameGeometryById } from "@shared/canvas-frames";
2
+ import type { MoveNodeEditIntent } from "@shared/code-layer";
3
+ import { applyVisualEdit } from "@shared/code-layer";
4
+ import type { Dispatch, RefObject, SetStateAction } from "react";
5
+
6
+ import { trace } from "@/components/design/design-trace";
7
+ import { getInitialFrameGeometry } from "@/components/design/multi-screen/frame-geometry";
8
+ import type { FrameGeometry } from "@/components/design/multi-screen/types";
9
+ import type { ElementInfo } from "@/components/design/types";
10
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
11
+ import type { SelectedLayerTarget } from "@/pages/design-editor/code-layer-state";
12
+ import {
13
+ elementInfoFromCodeLayerNode,
14
+ resolveCodeLayerNodeFromElementInfo,
15
+ } from "@/pages/design-editor/code-layer-state";
16
+ import type { OverviewScreen } from "@/pages/design-editor/derive/overview-screens";
17
+ import {
18
+ cloneCanvasFrameGeometry,
19
+ getCanvasFrameGeometry,
20
+ } from "@/pages/design-editor/design-data-geometry-utils";
21
+ import type { DesignEditorPreferences } from "@/pages/design-editor/editor-preferences";
22
+ import {
23
+ resolveElementNudgeIntent,
24
+ resolveNudgeIntent,
25
+ } from "@/pages/design-editor/nudge-intent";
26
+ import { overviewSelectionTargetsElement } from "@/pages/design-editor/selection-state";
27
+ import type { DesignFile } from "@/pages/design-editor/types";
28
+
29
+ export interface NudgeSelectionArgs {
30
+ activeFile: DesignFile;
31
+ applyLocalContentUpdate: (
32
+ nextContent: string,
33
+ options?: {
34
+ refreshPreview?: boolean;
35
+ skipPreview?: boolean;
36
+ forcePreviewFullDocument?: boolean;
37
+ immediateSave?: boolean;
38
+ persist?: boolean;
39
+ recordHistory?: boolean;
40
+ historyBeforeContent?: string;
41
+ updatedAt?: string;
42
+ clipboardMutation?: ClipboardContentMutationPublication;
43
+ },
44
+ ) => void;
45
+ boardFileId: string | undefined;
46
+ boardFrameGeometry: FrameGeometry | undefined;
47
+ canEditDesign: boolean;
48
+ commitVisualStyles: (
49
+ selector: string,
50
+ styles: Record<string, string>,
51
+ options?: {
52
+ runtimeApplied?: boolean;
53
+ elementInfo?: ElementInfo;
54
+ originalStyles?: Record<string, string>;
55
+ },
56
+ ) => void;
57
+ designDataJsonRef: RefObject<Record<string, unknown>>;
58
+ editorPreferences: DesignEditorPreferences;
59
+ files: DesignFile[];
60
+ getFreshActiveContent: () => string;
61
+ handleGeometryCommit: (
62
+ before: CanvasFrameGeometryById,
63
+ after: CanvasFrameGeometryById,
64
+ options?: { source?: "pointer" | "keyboard" },
65
+ ) => void;
66
+ hideSelectionChromeForNudge: () => void;
67
+ overviewScreens: OverviewScreen[];
68
+ overviewSelectedScreenIds: string[];
69
+ selectedElement: ElementInfo | null;
70
+ selectedLayerIdsState: string[];
71
+ selectedLayerTargetsRef: RefObject<SelectedLayerTarget[]>;
72
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
73
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
74
+ viewModeRef: RefObject<"single" | "overview">;
75
+ }
76
+
77
+ export function runNudgeSelection(
78
+ {
79
+ activeFile,
80
+ applyLocalContentUpdate,
81
+ boardFileId,
82
+ boardFrameGeometry,
83
+ canEditDesign,
84
+ commitVisualStyles,
85
+ designDataJsonRef,
86
+ editorPreferences,
87
+ files,
88
+ getFreshActiveContent,
89
+ handleGeometryCommit,
90
+ hideSelectionChromeForNudge,
91
+ overviewScreens,
92
+ overviewSelectedScreenIds,
93
+ selectedElement,
94
+ selectedLayerIdsState,
95
+ selectedLayerTargetsRef,
96
+ setSelectedElement,
97
+ setSelectedLayerIdsState,
98
+ viewModeRef,
99
+ }: NudgeSelectionArgs,
100
+ direction: "up" | "right" | "down" | "left",
101
+ largeStep: boolean,
102
+ ) {
103
+ trace("structure", "nudge", { direction, largeStep });
104
+ if (!canEditDesign) return;
105
+ const nudgeAmounts = editorPreferences.nudge;
106
+ const freeTranslation = resolveNudgeIntent({
107
+ direction,
108
+ largeStep,
109
+ amounts: nudgeAmounts,
110
+ });
111
+ const dx = freeTranslation.kind === "translate" ? freeTranslation.dx : 0;
112
+ const dy = freeTranslation.kind === "translate" ? freeTranslation.dy : 0;
113
+
114
+ // Screen frames nudge through the same geometry-commit path mouse-drag
115
+ // uses, so a held arrow key gets handleGeometryCommit's ~800ms undo
116
+ // coalescing instead of one history entry per keypress.
117
+ if (
118
+ viewModeRef.current === "overview" &&
119
+ overviewSelectedScreenIds.length > 0 &&
120
+ !overviewSelectionTargetsElement({
121
+ selectedElement,
122
+ selectedLayerIds: selectedLayerIdsState,
123
+ fileIds: files.map((file) => file.id),
124
+ })
125
+ ) {
126
+ const before = getCanvasFrameGeometry(designDataJsonRef.current);
127
+ const after = cloneCanvasFrameGeometry(before);
128
+ let changed = false;
129
+ overviewSelectedScreenIds.forEach((screenId) => {
130
+ const screenIndex = overviewScreens.findIndex(
131
+ (screen) => screen.id === screenId,
132
+ );
133
+ const screen =
134
+ screenIndex >= 0 ? overviewScreens[screenIndex] : undefined;
135
+ const fallbackGeometry =
136
+ screenIndex >= 0
137
+ ? getInitialFrameGeometry(screenIndex, {
138
+ width: screen?.width ?? 1280,
139
+ height: screen?.height ?? 2560,
140
+ })
141
+ : boardFileId === screenId
142
+ ? boardFrameGeometry
143
+ : undefined;
144
+ if (!fallbackGeometry) return;
145
+ const current = { ...fallbackGeometry, ...before[screenId] };
146
+ after[screenId] = {
147
+ ...current,
148
+ x: current.x + dx,
149
+ y: current.y + dy,
150
+ };
151
+ changed = true;
152
+ });
153
+ if (changed) {
154
+ handleGeometryCommit(before, after, { source: "keyboard" });
155
+ }
156
+ return;
157
+ }
158
+
159
+ // Selecting in the layers tree fills selectedLayerTargets before the
160
+ // bridge round-trip fills selectedElement, so keying off the latter
161
+ // alone silently drops the first nudge after every tree selection.
162
+ const nudgeTarget = selectedElement?.selector
163
+ ? selectedElement
164
+ : selectedLayerTargetsRef.current[0]?.elementInfo;
165
+ if (!nudgeTarget?.selector) return;
166
+
167
+ const intent = resolveElementNudgeIntent({
168
+ content: activeFile ? getFreshActiveContent() : "",
169
+ selectedElement: nudgeTarget,
170
+ direction,
171
+ largeStep,
172
+ amounts: nudgeAmounts,
173
+ });
174
+ if (intent.kind === "none") return;
175
+ if (intent.kind === "reorder") {
176
+ const patch = applyVisualEdit(intent.content, {
177
+ kind: "moveNode",
178
+ target: { nodeId: intent.targetNodeId },
179
+ anchor: { nodeId: intent.anchorNodeId },
180
+ placement: intent.placement,
181
+ } satisfies MoveNodeEditIntent);
182
+ if (patch.result.status !== "applied") return;
183
+ applyLocalContentUpdate(patch.content, { skipPreview: true });
184
+ // A node with no stable `data-agent-native-node-id` has its id derived
185
+ // from path/offset, and the move changes both — so the pre-move id
186
+ // finds nothing and the selection has to be re-resolved by identity.
187
+ const movedNode =
188
+ patch.projection.nodes.find(
189
+ (node) =>
190
+ node.id === intent.targetNodeId ||
191
+ node.dataAttributes["data-agent-native-node-id"] ===
192
+ intent.targetNodeId,
193
+ ) ??
194
+ resolveCodeLayerNodeFromElementInfo(patch.projection, selectedElement);
195
+ if (movedNode) {
196
+ setSelectedElement(elementInfoFromCodeLayerNode(movedNode));
197
+ setSelectedLayerIdsState([movedNode.id]);
198
+ }
199
+ return;
200
+ }
201
+
202
+ hideSelectionChromeForNudge();
203
+ const left = parseFloat(nudgeTarget.computedStyles.left || "0") || 0;
204
+ const top = parseFloat(nudgeTarget.computedStyles.top || "0") || 0;
205
+ commitVisualStyles(nudgeTarget.selector, {
206
+ position:
207
+ nudgeTarget.computedStyles.position === "static"
208
+ ? "relative"
209
+ : nudgeTarget.computedStyles.position || "relative",
210
+ left: `${Math.round(left + intent.dx)}px`,
211
+ top: `${Math.round(top + intent.dy)}px`,
212
+ });
213
+ }
@@ -0,0 +1,318 @@
1
+ import {
2
+ computeReparentedChildPosition,
3
+ normalizePoisonedBoardNestedCoords,
4
+ } from "@shared/board-file";
5
+ import {
6
+ applyVisualEdit,
7
+ buildCodeLayerProjection,
8
+ moveNodeBetweenDocuments,
9
+ } from "@shared/code-layer";
10
+ import type { Dispatch, SetStateAction } from "react";
11
+ import { toast } from "sonner";
12
+
13
+ import type { ElementInfo } from "@/components/design/types";
14
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
15
+ import {
16
+ codeLayerPatchMessage,
17
+ elementInfoFromCodeLayerNode,
18
+ } from "@/pages/design-editor/code-layer-state";
19
+ import type { ContentHistoryEntry } from "@/pages/design-editor/history";
20
+ import {
21
+ getAbsolutePositioningForNodeInHtml,
22
+ removeAbsolutePositioningFromNodeInHtml,
23
+ setAbsolutePositioningForNodeInHtml,
24
+ warnIfPoisonedBoardCoordsNormalized,
25
+ } from "@/pages/design-editor/html-layer-positioning";
26
+
27
+ export interface OverviewPrimitiveReparentArgs {
28
+ applyFileContentUpdate: (
29
+ fileId: string,
30
+ nextContent: string,
31
+ options?: {
32
+ refreshPreview?: boolean;
33
+ skipPreview?: boolean;
34
+ forcePreviewFullDocument?: boolean;
35
+ persist?: boolean;
36
+ recordHistory?: boolean;
37
+ updatedAt?: string;
38
+ clipboardMutation?: ClipboardContentMutationPublication;
39
+ },
40
+ ) => void;
41
+ boardFileId: string | undefined;
42
+ canEditDesign: boolean;
43
+ getScreenContent: (screenId: string) => string;
44
+ recordContentHistoryEntry: (entry: ContentHistoryEntry) => void;
45
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
46
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
47
+ t: (key: string, options?: Record<string, unknown>) => string;
48
+ }
49
+
50
+ export function runOverviewPrimitiveReparent(
51
+ {
52
+ applyFileContentUpdate,
53
+ boardFileId,
54
+ canEditDesign,
55
+ getScreenContent,
56
+ recordContentHistoryEntry,
57
+ setSelectedElement,
58
+ setSelectedLayerIdsState,
59
+ t,
60
+ }: OverviewPrimitiveReparentArgs,
61
+ {
62
+ sourceNodeId,
63
+ sourceScreenId,
64
+ targetNodeId,
65
+ targetScreenId,
66
+ placement = "inside",
67
+ }: {
68
+ sourceNodeId: string;
69
+ sourceScreenId: string;
70
+ targetNodeId: string;
71
+ targetScreenId: string;
72
+ placement?: "before" | "after" | "inside";
73
+ },
74
+ ) {
75
+ if (!canEditDesign) return;
76
+
77
+ if (sourceScreenId === targetScreenId) {
78
+ // --- Same-screen reparent ---
79
+ const baseContent = getScreenContent(sourceScreenId);
80
+ if (!baseContent) return;
81
+
82
+ // 1. Move the node relative to the target anchor. For "inside" the
83
+ // anchor is the container itself (append); for "before"/"after" the
84
+ // anchor is a sibling child already inside an auto-layout container
85
+ // (flow-insert at that index) — applyMoveNodeEdit's
86
+ // prepareMovedFragmentForParent already strips absolute positioning
87
+ // from the moved fragment's root when the destination parent is a
88
+ // flow container, regardless of which placement resolved it there.
89
+ const movePatch = applyVisualEdit(baseContent, {
90
+ kind: "moveNode",
91
+ target: { nodeId: sourceNodeId },
92
+ anchor: { nodeId: targetNodeId },
93
+ placement,
94
+ });
95
+ if (movePatch.result.status !== "applied") {
96
+ toast.error(
97
+ codeLayerPatchMessage(
98
+ movePatch.result.message,
99
+ t("designEditor.toasts.layerMoveFailed"),
100
+ ),
101
+ { duration: 4000 },
102
+ );
103
+ return;
104
+ }
105
+
106
+ const movedNodeAttrId =
107
+ movePatch.projection.nodes.find(
108
+ (n) =>
109
+ n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
110
+ n.id === sourceNodeId,
111
+ )?.dataAttributes["data-agent-native-node-id"] ?? sourceNodeId;
112
+
113
+ if (placement !== "inside") {
114
+ // Auto-layout flow-insert: belt-and-suspenders alongside
115
+ // prepareMovedFragmentForParent above — make sure the moved node
116
+ // itself carries no leftover position/left/top so it renders as a
117
+ // pure flow child at its new index instead of an absolute layer
118
+ // sitting on top of its new siblings.
119
+ const flowContent = removeAbsolutePositioningFromNodeInHtml(
120
+ movePatch.content,
121
+ movedNodeAttrId,
122
+ );
123
+ applyFileContentUpdate(sourceScreenId, flowContent, {
124
+ skipPreview: true,
125
+ });
126
+ const nextProjection = buildCodeLayerProjection(flowContent);
127
+ const movedNodeAfter = nextProjection.nodes.find(
128
+ (n) =>
129
+ n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
130
+ n.id === sourceNodeId,
131
+ );
132
+ if (movedNodeAfter) {
133
+ setSelectedLayerIdsState([movedNodeAfter.id]);
134
+ setSelectedElement(elementInfoFromCodeLayerNode(movedNodeAfter));
135
+ }
136
+ return;
137
+ }
138
+
139
+ const sourcePosition = getAbsolutePositioningForNodeInHtml(
140
+ baseContent,
141
+ sourceNodeId,
142
+ );
143
+ const targetPosition = getAbsolutePositioningForNodeInHtml(
144
+ baseContent,
145
+ targetNodeId,
146
+ );
147
+ // Rebase the moved node's left/top to be PARENT-relative.
148
+ // computeReparentedChildPosition strips the board-surface offset
149
+ // (65536-multiples) from either side first, so a source that was
150
+ // persisted in board-iframe viewport coordinates (the historic
151
+ // container-drop poison — see BOARD_SURFACE_CONTENT_OFFSET_PX in
152
+ // shared/board-file.ts) still comes out as a sane parent-relative
153
+ // position instead of an off-world near-65536 value.
154
+ const rebasedContent =
155
+ sourcePosition && targetPosition
156
+ ? setAbsolutePositioningForNodeInHtml(
157
+ movePatch.content,
158
+ movedNodeAttrId,
159
+ computeReparentedChildPosition(sourcePosition, targetPosition),
160
+ )
161
+ : movePatch.content;
162
+ // Board safety net: if any nested coordinate still carries the
163
+ // surface-offset fingerprint (e.g. the position pair above could not
164
+ // be resolved and the rebase was skipped), normalize the final
165
+ // content so a nested board child can never persist off-world.
166
+ const nextContent = (() => {
167
+ if (!boardFileId || sourceScreenId !== boardFileId) {
168
+ return rebasedContent;
169
+ }
170
+ const normalized = normalizePoisonedBoardNestedCoords(rebasedContent);
171
+ warnIfPoisonedBoardCoordsNormalized(sourceScreenId, normalized);
172
+ return normalized.html;
173
+ })();
174
+
175
+ applyFileContentUpdate(sourceScreenId, nextContent, {
176
+ skipPreview: true,
177
+ });
178
+
179
+ // Re-select the moved node.
180
+ const nextProjection = buildCodeLayerProjection(nextContent);
181
+ const movedNodeAfter = nextProjection.nodes.find(
182
+ (n) =>
183
+ n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
184
+ n.id === sourceNodeId,
185
+ );
186
+ if (movedNodeAfter) {
187
+ setSelectedLayerIdsState([movedNodeAfter.id]);
188
+ setSelectedElement(elementInfoFromCodeLayerNode(movedNodeAfter));
189
+ }
190
+ return;
191
+ }
192
+
193
+ // --- Cross-screen reparent ---
194
+ const sourceContent = getScreenContent(sourceScreenId);
195
+ const destContent = getScreenContent(targetScreenId);
196
+ if (!sourceContent || !destContent) return;
197
+
198
+ // Resolve data-agent-native-node-id attributes for moveNodeBetweenDocuments.
199
+ const sourceProjection = buildCodeLayerProjection(sourceContent);
200
+ const destProjection = buildCodeLayerProjection(destContent);
201
+ const sourceNode = sourceProjection.nodes.find(
202
+ (n) =>
203
+ n.dataAttributes["data-agent-native-node-id"] === sourceNodeId ||
204
+ n.id === sourceNodeId,
205
+ );
206
+ const anchorNode = destProjection.nodes.find(
207
+ (n) =>
208
+ n.dataAttributes["data-agent-native-node-id"] === targetNodeId ||
209
+ n.id === targetNodeId,
210
+ );
211
+ const nodeAttrId =
212
+ sourceNode?.dataAttributes["data-agent-native-node-id"] ?? sourceNodeId;
213
+ const anchorAttrId =
214
+ anchorNode?.dataAttributes["data-agent-native-node-id"] ?? targetNodeId;
215
+
216
+ const result = moveNodeBetweenDocuments(sourceContent, destContent, {
217
+ nodeId: nodeAttrId,
218
+ anchorNodeId: anchorAttrId,
219
+ placement,
220
+ });
221
+ if (result.status !== "applied") {
222
+ toast.error(
223
+ codeLayerPatchMessage(
224
+ result.message,
225
+ t("designEditor.toasts.layerMoveFailed"),
226
+ ),
227
+ { duration: 4000 },
228
+ );
229
+ return;
230
+ }
231
+ // Finding 8: the requested anchor placement landed inside a
232
+ // <template> interior and was redirected to a real DOM slot right
233
+ // after the enclosing template instead — let the user know the drop
234
+ // wasn't silently discarded, just relocated nearby.
235
+ if (result.anchorRedirected) {
236
+ toast(t("designEditor.toasts.layerMoveRedirected"), {
237
+ duration: 4000,
238
+ });
239
+ }
240
+
241
+ const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
242
+ // "inside" rebases to a parent-relative absolute position (matches the
243
+ // same-screen branch above and historic behavior for dropping onto a
244
+ // plain absolute container). "before"/"after" is an auto-layout
245
+ // flow-insert — moveNodeBetweenDocuments's prepareMovedFragmentForParent
246
+ // already stripped absolute positioning from the moved fragment's
247
+ // root, so just belt-and-suspenders clear it again by node id rather
248
+ // than reintroducing position:absolute via the rebase below.
249
+ const nextDestContent = (() => {
250
+ const rebasedDestContent = (() => {
251
+ if (placement !== "inside") {
252
+ return removeAbsolutePositioningFromNodeInHtml(
253
+ result.destHtml,
254
+ destNodeAttrId,
255
+ );
256
+ }
257
+ const sourcePosition = getAbsolutePositioningForNodeInHtml(
258
+ sourceContent,
259
+ nodeAttrId,
260
+ );
261
+ const targetPosition = getAbsolutePositioningForNodeInHtml(
262
+ destContent,
263
+ anchorAttrId,
264
+ );
265
+ // Same parent-relative rebase + board-poison stripping as the
266
+ // same-screen branch above (see computeReparentedChildPosition).
267
+ return sourcePosition && targetPosition
268
+ ? setAbsolutePositioningForNodeInHtml(
269
+ result.destHtml,
270
+ destNodeAttrId,
271
+ computeReparentedChildPosition(sourcePosition, targetPosition),
272
+ )
273
+ : result.destHtml;
274
+ })();
275
+ if (!boardFileId || targetScreenId !== boardFileId) {
276
+ return rebasedDestContent;
277
+ }
278
+ const normalized = normalizePoisonedBoardNestedCoords(rebasedDestContent);
279
+ warnIfPoisonedBoardCoordsNormalized(targetScreenId, normalized);
280
+ return normalized.html;
281
+ })();
282
+
283
+ recordContentHistoryEntry({
284
+ changes: [
285
+ {
286
+ fileId: sourceScreenId,
287
+ before: sourceContent,
288
+ after: result.sourceHtml,
289
+ },
290
+ {
291
+ fileId: targetScreenId,
292
+ before: destContent,
293
+ after: nextDestContent,
294
+ },
295
+ ],
296
+ });
297
+
298
+ applyFileContentUpdate(sourceScreenId, result.sourceHtml, {
299
+ recordHistory: false,
300
+ refreshPreview: false,
301
+ forcePreviewFullDocument: true,
302
+ });
303
+ applyFileContentUpdate(targetScreenId, nextDestContent, {
304
+ recordHistory: false,
305
+ refreshPreview: false,
306
+ forcePreviewFullDocument: true,
307
+ });
308
+
309
+ // Re-select the moved node in the destination.
310
+ const finalProjection = buildCodeLayerProjection(nextDestContent);
311
+ const movedNodeFinal = finalProjection.nodes.find(
312
+ (n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
313
+ );
314
+ if (movedNodeFinal) {
315
+ setSelectedLayerIdsState([movedNodeFinal.id]);
316
+ setSelectedElement(elementInfoFromCodeLayerNode(movedNodeFinal));
317
+ }
318
+ }
@@ -0,0 +1,114 @@
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 { Dispatch, RefObject, SetStateAction } from "react";
5
+ import { toast } from "sonner";
6
+
7
+ import type { ElementInfo } from "@/components/design/types";
8
+ import type { DesignClipboardScreenEntry } from "@/lib/design-import";
9
+ import {
10
+ nextDuplicatedFilename,
11
+ normalizedDesignFileType,
12
+ reassignDuplicatedNodeIds,
13
+ } from "@/pages/design-editor/canvas-primitive-insert";
14
+ import type { DesignFile, DesignTool } from "@/pages/design-editor/types";
15
+
16
+ export interface PasteCopiedScreensArgs {
17
+ canEditDesign: boolean;
18
+ canvasFrameGeometryById: CanvasFrameGeometryById;
19
+ createFileMutation: ReturnType<
20
+ typeof useActionMutation<undefined, undefined, "create-file">
21
+ >;
22
+ files: DesignFile[];
23
+ id: string | undefined;
24
+ pasteCascadeRef: RefObject<number>;
25
+ queryClient: QueryClient;
26
+ queueFrameGeometrySave: (geometryById: CanvasFrameGeometryById) => void;
27
+ setActiveFileId: Dispatch<SetStateAction<string | null>>;
28
+ setActiveTool: Dispatch<SetStateAction<DesignTool>>;
29
+ setOverviewSelectedScreenIds: Dispatch<SetStateAction<string[]>>;
30
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
31
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
32
+ setViewMode: Dispatch<SetStateAction<"single" | "overview">>;
33
+ t: (key: string, options?: Record<string, unknown>) => string;
34
+ viewModeRef: RefObject<"single" | "overview">;
35
+ }
36
+
37
+ export function runPasteCopiedScreens(
38
+ {
39
+ canEditDesign,
40
+ canvasFrameGeometryById,
41
+ createFileMutation,
42
+ files,
43
+ id,
44
+ pasteCascadeRef,
45
+ queryClient,
46
+ queueFrameGeometrySave,
47
+ setActiveFileId,
48
+ setActiveTool,
49
+ setOverviewSelectedScreenIds,
50
+ setSelectedElement,
51
+ setSelectedLayerIdsState,
52
+ setViewMode,
53
+ t,
54
+ viewModeRef,
55
+ }: PasteCopiedScreensArgs,
56
+ screens: DesignClipboardScreenEntry[],
57
+ position?: { x: number; y: number },
58
+ ) {
59
+ if (!id || !canEditDesign || screens.length === 0) return;
60
+ const pasteOffset = 32 + pasteCascadeRef.current * 16;
61
+ pasteCascadeRef.current += 1;
62
+ screens.forEach((screen, index) => {
63
+ const filename = nextDuplicatedFilename(files, screen.filename);
64
+ const content = reassignDuplicatedNodeIds(screen.content);
65
+ const sourceGeometry = screen.canvasFrame;
66
+ createFileMutation.mutate(
67
+ {
68
+ designId: id,
69
+ filename,
70
+ content,
71
+ fileType: normalizedDesignFileType(screen.fileType ?? "html"),
72
+ } as any,
73
+ {
74
+ onSuccess: (result: any) => {
75
+ const nextId = typeof result?.id === "string" ? result.id : null;
76
+ queryClient.invalidateQueries({
77
+ queryKey: ["action", "get-design"],
78
+ });
79
+ if (!nextId) return;
80
+ const nextX = position
81
+ ? position.x + index * 16
82
+ : (sourceGeometry?.x ?? 0) + pasteOffset;
83
+ const nextY = position
84
+ ? position.y + index * 16
85
+ : (sourceGeometry?.y ?? 0) + pasteOffset;
86
+ queueFrameGeometrySave({
87
+ ...canvasFrameGeometryById,
88
+ [nextId]: {
89
+ ...sourceGeometry,
90
+ x: nextX,
91
+ y: nextY,
92
+ },
93
+ });
94
+ if (index === screens.length - 1) {
95
+ setActiveFileId(nextId);
96
+ setActiveTool("move");
97
+ setSelectedElement(null);
98
+ setSelectedLayerIdsState([nextId]);
99
+ viewModeRef.current = "overview";
100
+ setViewMode("overview");
101
+ setOverviewSelectedScreenIds([nextId]);
102
+ }
103
+ },
104
+ onError: (error: unknown) => {
105
+ toast.error(
106
+ error instanceof Error
107
+ ? error.message
108
+ : t("designEditor.toasts.screenDuplicateError"),
109
+ );
110
+ },
111
+ },
112
+ );
113
+ });
114
+ }