@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,70 @@
1
+ import type { ElementInfo } from "@/components/design/types";
2
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
3
+ import { insertClonedHtmlLayers } from "@/pages/design-editor/clone-and-pen-edit";
4
+ import type { CanvasLayerClipboardEntry } from "@/pages/design-editor/command-types";
5
+ import type { DesignFile } from "@/pages/design-editor/types";
6
+
7
+ export interface PasteOverSelectionArgs {
8
+ activeFile: DesignFile | undefined;
9
+ applyLocalContentUpdate: (
10
+ nextContent: string,
11
+ options?: {
12
+ refreshPreview?: boolean;
13
+ skipPreview?: boolean;
14
+ forcePreviewFullDocument?: boolean;
15
+ immediateSave?: boolean;
16
+ persist?: boolean;
17
+ recordHistory?: boolean;
18
+ historyBeforeContent?: string;
19
+ updatedAt?: string;
20
+ clipboardMutation?: ClipboardContentMutationPublication;
21
+ },
22
+ ) => void;
23
+ getCanvasClipboardEntries: () => CanvasLayerClipboardEntry[];
24
+ getFreshActiveContent: () => string;
25
+ handlePasteSelection: () => Promise<void>;
26
+ selectedElement: ElementInfo | null;
27
+ selectInsertedLayers: (
28
+ screenId: string,
29
+ content: string,
30
+ rootNodeIds: string[],
31
+ ) => void;
32
+ }
33
+
34
+ export function runPasteOverSelection({
35
+ activeFile,
36
+ applyLocalContentUpdate,
37
+ getCanvasClipboardEntries,
38
+ getFreshActiveContent,
39
+ handlePasteSelection,
40
+ selectedElement,
41
+ selectInsertedLayers,
42
+ }: PasteOverSelectionArgs) {
43
+ const entries = getCanvasClipboardEntries();
44
+ if (!activeFile || entries.length === 0) return;
45
+ const baseContent = getFreshActiveContent();
46
+ if (selectedElement?.boundingRect) {
47
+ const { x, y } = selectedElement.boundingRect;
48
+ const result = insertClonedHtmlLayers(
49
+ baseContent,
50
+ entries.map((entry) => entry.html),
51
+ {
52
+ positions: entries.map((_, index) => ({
53
+ x: x + index * 16,
54
+ y: y + index * 16,
55
+ })),
56
+ styleSnapshots: entries.map((entry) => entry.portableStyleSnapshot),
57
+ managedStyleSnapshots: entries.map(
58
+ (entry) => entry.managedStyleSnapshot,
59
+ ),
60
+ },
61
+ );
62
+ if (!result) return;
63
+ applyLocalContentUpdate(result.content, {
64
+ forcePreviewFullDocument: true,
65
+ });
66
+ selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
67
+ } else {
68
+ void handlePasteSelection();
69
+ }
70
+ }
@@ -0,0 +1,436 @@
1
+ import type { Dispatch, RefObject, SetStateAction } from "react";
2
+ import { toast } from "sonner";
3
+ import * as Y from "yjs";
4
+
5
+ import type {
6
+ ElementInfo,
7
+ RuntimeStructureInsertRequest,
8
+ } from "@/components/design/types";
9
+ import type {
10
+ ClipboardContentLineage,
11
+ ClipboardContentMutationOrigin,
12
+ ClipboardContentMutationPublication,
13
+ } from "@/lib/clipboard-content-lineage";
14
+ import type { DesignClipboardScreenEntry } from "@/lib/design-import";
15
+ import {
16
+ extractLayerPosition,
17
+ insertClonedHtmlLayers,
18
+ prepareClonedHtmlLayersForLiveInsert,
19
+ } from "@/pages/design-editor/clone-and-pen-edit";
20
+ import type { CanvasLayerClipboardEntry } from "@/pages/design-editor/command-types";
21
+ import { isStandaloneHttpUrl } from "@/pages/design-editor/editor-state";
22
+ import type { ContentHistoryChange } from "@/pages/design-editor/history";
23
+ import { MAX_DESIGN_UNDO_STACK } from "@/pages/design-editor/history";
24
+ import { resolvePastePlacementForSelection } from "@/pages/design-editor/paste-placement";
25
+ import type { DesignFile } from "@/pages/design-editor/types";
26
+
27
+ export interface PasteSelectionArgs {
28
+ activeFile: DesignFile;
29
+ applyFileContentUpdate: (
30
+ fileId: string,
31
+ nextContent: string,
32
+ options?: {
33
+ refreshPreview?: boolean;
34
+ skipPreview?: boolean;
35
+ forcePreviewFullDocument?: boolean;
36
+ persist?: boolean;
37
+ recordHistory?: boolean;
38
+ updatedAt?: string;
39
+ clipboardMutation?: ClipboardContentMutationPublication;
40
+ },
41
+ ) => void;
42
+ applyLocalContentUpdate: (
43
+ nextContent: string,
44
+ options?: {
45
+ refreshPreview?: boolean;
46
+ skipPreview?: boolean;
47
+ forcePreviewFullDocument?: boolean;
48
+ immediateSave?: boolean;
49
+ persist?: boolean;
50
+ recordHistory?: boolean;
51
+ historyBeforeContent?: string;
52
+ updatedAt?: string;
53
+ clipboardMutation?: ClipboardContentMutationPublication;
54
+ },
55
+ ) => void;
56
+ boardFileId: string | undefined;
57
+ canEditDesign: boolean;
58
+ canvasContainerRef: RefObject<HTMLDivElement | null>;
59
+ clearRedoStacks: () => void;
60
+ clipboardPasteRedoStackRef: RefObject<ContentHistoryChange[]>;
61
+ clipboardPasteUndoStackRef: RefObject<ContentHistoryChange[]>;
62
+ files: DesignFile[];
63
+ getCanvasClipboardEntries: () => CanvasLayerClipboardEntry[];
64
+ getCanvasScreenClipboardEntries: () => DesignClipboardScreenEntry[];
65
+ getFreshActiveContent: () => string;
66
+ getScreenContent: (screenId: string) => string;
67
+ latestClipboardMutationContentRef: RefObject<
68
+ Map<string, ClipboardContentLineage>
69
+ >;
70
+ pasteCascadeRef: RefObject<number>;
71
+ pasteCopiedScreens: (
72
+ screens: DesignClipboardScreenEntry[],
73
+ position?: { x: number; y: number },
74
+ ) => void;
75
+ pendingLocalFileContentsRef: RefObject<
76
+ Map<
77
+ string,
78
+ { content: string; startedAt: number; baseUpdatedAt?: string | null }
79
+ >
80
+ >;
81
+ publishAuthoritativeClipboardMutation: (args: {
82
+ fileId: string;
83
+ baseContent: string;
84
+ nextContent: string;
85
+ origin: ClipboardContentMutationOrigin;
86
+ }) => ClipboardContentMutationPublication | null;
87
+ refreshClipboardFromSystemClipboard: () => Promise<void>;
88
+ remapMotionTracksForClone: (
89
+ nodeIdMap: Map<string, string>,
90
+ targetFileId: string,
91
+ ) => void;
92
+ runtimeStructureInsertRevisionRef: RefObject<number>;
93
+ selectInsertedLayers: (
94
+ screenId: string,
95
+ content: string,
96
+ rootNodeIds: string[],
97
+ ) => void;
98
+ selectedCanvasSelector: string;
99
+ selectedElement: ElementInfo | null;
100
+ setRuntimeStructureInsertRequest: Dispatch<
101
+ SetStateAction<
102
+ (RuntimeStructureInsertRequest & { screenId: string }) | null
103
+ >
104
+ >;
105
+ syncUndoRedoState: () => void;
106
+ t: (key: string, options?: Record<string, unknown>) => string;
107
+ undoManagerRef: RefObject<Y.UndoManager | null>;
108
+ viewModeRef: RefObject<"single" | "overview">;
109
+ zoom: number;
110
+ }
111
+
112
+ export async function runPasteSelection(
113
+ {
114
+ activeFile,
115
+ applyFileContentUpdate,
116
+ applyLocalContentUpdate,
117
+ boardFileId,
118
+ canEditDesign,
119
+ canvasContainerRef,
120
+ clearRedoStacks,
121
+ clipboardPasteRedoStackRef,
122
+ clipboardPasteUndoStackRef,
123
+ files,
124
+ getCanvasClipboardEntries,
125
+ getCanvasScreenClipboardEntries,
126
+ getFreshActiveContent,
127
+ getScreenContent,
128
+ latestClipboardMutationContentRef,
129
+ pasteCascadeRef,
130
+ pasteCopiedScreens,
131
+ pendingLocalFileContentsRef,
132
+ publishAuthoritativeClipboardMutation,
133
+ refreshClipboardFromSystemClipboard,
134
+ remapMotionTracksForClone,
135
+ runtimeStructureInsertRevisionRef,
136
+ selectInsertedLayers,
137
+ selectedCanvasSelector,
138
+ selectedElement,
139
+ setRuntimeStructureInsertRequest,
140
+ syncUndoRedoState,
141
+ t,
142
+ undoManagerRef,
143
+ viewModeRef,
144
+ zoom,
145
+ }: PasteSelectionArgs,
146
+ position?: { x: number; y: number },
147
+ ) {
148
+ // U19: paste is a discrete one-shot action, never a continuous gesture
149
+ // like a slider drag. Without stopCapturing(), a paste that happens to
150
+ // land within 800ms of the previous Yjs-tracked edit (captureTimeout)
151
+ // would merge with it into one undo step — Cmd+Z would then undo both
152
+ // the unrelated prior edit AND the paste together.
153
+ undoManagerRef.current?.stopCapturing();
154
+ await refreshClipboardFromSystemClipboard();
155
+ const entries = getCanvasClipboardEntries();
156
+ const targetFileId =
157
+ viewModeRef.current === "overview" && position && boardFileId
158
+ ? boardFileId
159
+ : activeFile?.id;
160
+ if (entries.length === 0) {
161
+ // No layer-level clipboard content — fall back to whole-screen paste
162
+ // (U6) when the clipboard instead carries copied screen snapshots.
163
+ const screens = getCanvasScreenClipboardEntries();
164
+ if (screens.length > 0 && canEditDesign) {
165
+ pasteCopiedScreens(screens, position);
166
+ }
167
+ return;
168
+ }
169
+ if (!targetFileId || !canEditDesign) return;
170
+ // The pending-local map is the synchronous write-through source for
171
+ // same-task/repeated operations. React query/collab mirrors can lag one
172
+ // render behind a just-completed paste even after its save is already
173
+ // observable from another request; rebasing a second paste on that stale
174
+ // mirror makes its history `before` skip the first clone, so one undo
175
+ // removes both. Prefer the pending snapshot exactly like primitive and
176
+ // cross-screen structure writes do elsewhere in this editor.
177
+ const baseContent =
178
+ latestClipboardMutationContentRef.current.get(targetFileId)?.content ??
179
+ pendingLocalFileContentsRef.current.get(targetFileId)?.content ??
180
+ (targetFileId === activeFile?.id
181
+ ? getFreshActiveContent()
182
+ : (getScreenContent(targetFileId) ?? ""));
183
+ if (!baseContent && targetFileId !== boardFileId) return;
184
+ const layerHtmls = entries.map((entry) => entry.html);
185
+ const styleSnapshots = entries.map((entry) => entry.portableStyleSnapshot);
186
+ const managedStyleSnapshots = entries.map(
187
+ (entry) => entry.managedStyleSnapshot,
188
+ );
189
+ const targetFile = files.find((file) => file.id === targetFileId);
190
+ const targetStoredContent = targetFile?.content ?? baseContent;
191
+ if (isStandaloneHttpUrl(targetStoredContent)) {
192
+ const selectedAnchor =
193
+ !position &&
194
+ targetFileId === activeFile?.id &&
195
+ selectedElement?.selector &&
196
+ !["body", "html"].includes(selectedElement.tagName?.toLowerCase() ?? "")
197
+ ? {
198
+ selector:
199
+ selectedElement.runtimeSelector ??
200
+ selectedCanvasSelector ??
201
+ selectedElement.selector,
202
+ sourceId:
203
+ selectedElement.runtimeSourceId ??
204
+ selectedElement.sourceId ??
205
+ undefined,
206
+ }
207
+ : null;
208
+ const sourcePositions = entries.map((entry) =>
209
+ extractLayerPosition(entry.html),
210
+ );
211
+ const positionedSources = sourcePositions.filter(
212
+ (source): source is { x: number; y: number } => Boolean(source),
213
+ );
214
+ const minSourceX = positionedSources.length
215
+ ? Math.min(...positionedSources.map((source) => source.x))
216
+ : 0;
217
+ const minSourceY = positionedSources.length
218
+ ? Math.min(...positionedSources.map((source) => source.y))
219
+ : 0;
220
+ const iframe =
221
+ canvasContainerRef.current?.querySelector<HTMLElement>(
222
+ "[data-design-preview-iframe]",
223
+ ) ?? null;
224
+ const iframeRect = iframe?.getBoundingClientRect();
225
+ const factor = zoom / 100;
226
+ const viewportCenter = iframeRect
227
+ ? {
228
+ x: Math.max(0, iframeRect.width / 2 / factor),
229
+ y: Math.max(0, iframeRect.height / 2 / factor),
230
+ }
231
+ : { x: 120, y: 120 };
232
+ const cascadeOffset = pasteCascadeRef.current * 16;
233
+ const pastingIntoSourceScreen = entries.every(
234
+ (entry) => entry.sourceFileId === targetFileId,
235
+ );
236
+ const positions = selectedAnchor
237
+ ? undefined
238
+ : entries.map((_, index) => {
239
+ const source = sourcePositions[index];
240
+ if (position) {
241
+ return source && positionedSources.length
242
+ ? {
243
+ x: position.x + source.x - minSourceX,
244
+ y: position.y + source.y - minSourceY,
245
+ }
246
+ : {
247
+ x: position.x + index * 16,
248
+ y: position.y + index * 16,
249
+ };
250
+ }
251
+ return source && pastingIntoSourceScreen
252
+ ? {
253
+ x: source.x + 10 + cascadeOffset,
254
+ y: source.y + 10 + cascadeOffset,
255
+ }
256
+ : {
257
+ x: viewportCenter.x + cascadeOffset + index * 16,
258
+ y: viewportCenter.y + cascadeOffset + index * 16,
259
+ };
260
+ });
261
+ const prepared = prepareClonedHtmlLayersForLiveInsert(
262
+ targetStoredContent,
263
+ layerHtmls,
264
+ {
265
+ stripRootPosition: Boolean(selectedAnchor),
266
+ positions,
267
+ styleSnapshots,
268
+ },
269
+ );
270
+ const firstHtml = prepared?.htmlFragments[0];
271
+ if (!prepared || !firstHtml) {
272
+ toast.error(t("designEditor.toasts.layerMoveFailed"), {
273
+ duration: 4000,
274
+ });
275
+ return;
276
+ }
277
+ pasteCascadeRef.current += 1;
278
+ runtimeStructureInsertRevisionRef.current += 1;
279
+ setRuntimeStructureInsertRequest({
280
+ requestId: runtimeStructureInsertRevisionRef.current,
281
+ screenId: targetFileId,
282
+ html: firstHtml,
283
+ additionalHtml: prepared.htmlFragments.slice(1),
284
+ anchor: selectedAnchor ?? { selector: "body" },
285
+ placement: selectedAnchor ? "after" : "inside",
286
+ });
287
+ return;
288
+ }
289
+ const applyPasteContentUpdate = (nextContent: string) => {
290
+ const clipboardMutation = publishAuthoritativeClipboardMutation({
291
+ fileId: targetFileId,
292
+ baseContent,
293
+ nextContent,
294
+ origin: "clipboard-paste",
295
+ });
296
+ if (!clipboardMutation) return false;
297
+ if (nextContent !== baseContent) {
298
+ // Capture the exact immutable pre-paste document here, before the
299
+ // optimistic cache/collab mirrors can advance independently. The
300
+ // dedicated stack owns paste history in both single and overview
301
+ // mode: generic Yjs/local history can be destroyed by a view switch
302
+ // and cannot publish the authoritative clipboard generation on
303
+ // undo. DOM insertion + every remapped managed rule stay in this
304
+ // single before/after snapshot.
305
+ clipboardPasteUndoStackRef.current = [
306
+ ...clipboardPasteUndoStackRef.current.slice(
307
+ -(MAX_DESIGN_UNDO_STACK - 1),
308
+ ),
309
+ {
310
+ fileId: targetFileId,
311
+ before: baseContent,
312
+ after: nextContent,
313
+ },
314
+ ];
315
+ clipboardPasteRedoStackRef.current = [];
316
+ clearRedoStacks();
317
+ syncUndoRedoState();
318
+ }
319
+ if (targetFileId === activeFile?.id) {
320
+ applyLocalContentUpdate(nextContent, {
321
+ forcePreviewFullDocument: true,
322
+ clipboardMutation,
323
+ recordHistory: false,
324
+ });
325
+ return true;
326
+ }
327
+ applyFileContentUpdate(targetFileId, nextContent, {
328
+ forcePreviewFullDocument: true,
329
+ clipboardMutation,
330
+ recordHistory: false,
331
+ });
332
+ return true;
333
+ };
334
+
335
+ // Inside a frame, after an object — but always into normal flow: a
336
+ // container is not a free canvas, so carrying the source's left/top
337
+ // across drops the clone on top of the target's content.
338
+ if (!position && targetFileId !== boardFileId && selectedElement?.selector) {
339
+ const selector = selectedCanvasSelector ?? selectedElement.selector;
340
+ const decision = resolvePastePlacementForSelection({
341
+ content: baseContent,
342
+ selectedElement,
343
+ });
344
+ const result = insertClonedHtmlLayers(baseContent, layerHtmls, {
345
+ targetSelectors: [selector],
346
+ placement: decision?.placement ?? "after",
347
+ stripRootPosition: true,
348
+ styleSnapshots,
349
+ managedStyleSnapshots,
350
+ });
351
+ if (result) {
352
+ pasteCascadeRef.current += 1;
353
+ if (!applyPasteContentUpdate(result.content)) return;
354
+ remapMotionTracksForClone(result.nodeIdMap, targetFileId);
355
+ selectInsertedLayers(targetFileId, result.content, result.rootNodeIds);
356
+ return;
357
+ }
358
+ // Fall through to position-based clone if insert failed.
359
+ }
360
+
361
+ // Explicit positions (e.g. "Paste here" at the cursor) are honored as-is.
362
+ // Keyboard pastes land near the source layer and cascade so repeats don't
363
+ // stack exactly.
364
+ const sourcePositions = entries.map((entry) =>
365
+ extractLayerPosition(entry.html),
366
+ );
367
+ const positionedSources = sourcePositions.filter(
368
+ (source): source is { x: number; y: number } => Boolean(source),
369
+ );
370
+ const minSourceX = positionedSources.length
371
+ ? Math.min(...positionedSources.map((source) => source.x))
372
+ : 0;
373
+ const minSourceY = positionedSources.length
374
+ ? Math.min(...positionedSources.map((source) => source.y))
375
+ : 0;
376
+ const cascadeOffset = pasteCascadeRef.current * 16;
377
+ // U16: reusing the raw source coordinates only makes sense when the
378
+ // source screen is the one being pasted into — otherwise that source
379
+ // screen may not even be visible in the current viewport (a different
380
+ // active screen, or the source screen scrolled off in overview), and
381
+ // the paste would land somewhere the user can't see. Fall back to the
382
+ // current viewport's center in that case (same computation as the
383
+ // U8 image-paste center).
384
+ const pastingIntoSourceScreen = entries.every(
385
+ (entry) => entry.sourceFileId === targetFileId,
386
+ );
387
+ const viewportCenter = (() => {
388
+ if (viewModeRef.current === "single") {
389
+ const iframe = canvasContainerRef.current?.querySelector<HTMLElement>(
390
+ "[data-design-preview-iframe]",
391
+ );
392
+ if (iframe) {
393
+ const iframeRect = iframe.getBoundingClientRect();
394
+ const factor = zoom / 100;
395
+ return {
396
+ x: Math.max(0, iframeRect.width / 2 / factor),
397
+ y: Math.max(0, iframeRect.height / 2 / factor),
398
+ };
399
+ }
400
+ }
401
+ const rect = canvasContainerRef.current?.getBoundingClientRect();
402
+ return rect
403
+ ? { x: Math.max(0, rect.width / 2), y: Math.max(0, rect.height / 2) }
404
+ : { x: 120, y: 120 };
405
+ })();
406
+ const positions = entries.map((_, index) => {
407
+ const source = sourcePositions[index];
408
+ if (position) {
409
+ return source && positionedSources.length
410
+ ? {
411
+ x: position.x + source.x - minSourceX,
412
+ y: position.y + source.y - minSourceY,
413
+ }
414
+ : { x: position.x + index * 16, y: position.y + index * 16 };
415
+ }
416
+ return source && pastingIntoSourceScreen
417
+ ? {
418
+ x: source.x + 10 + cascadeOffset,
419
+ y: source.y + 10 + cascadeOffset,
420
+ }
421
+ : {
422
+ x: viewportCenter.x + cascadeOffset + index * 16,
423
+ y: viewportCenter.y + cascadeOffset + index * 16,
424
+ };
425
+ });
426
+ const result = insertClonedHtmlLayers(baseContent, layerHtmls, {
427
+ positions,
428
+ styleSnapshots,
429
+ managedStyleSnapshots,
430
+ });
431
+ if (!result) return;
432
+ if (!position) pasteCascadeRef.current += 1;
433
+ if (!applyPasteContentUpdate(result.content)) return;
434
+ remapMotionTracksForClone(result.nodeIdMap, targetFileId);
435
+ selectInsertedLayers(targetFileId, result.content, result.rootNodeIds);
436
+ }
@@ -0,0 +1,135 @@
1
+ import {
2
+ buildCodeLayerProjection,
3
+ removeCodeLayerNodeFromHtml,
4
+ } from "@shared/code-layer";
5
+ import type { Dispatch, RefObject, SetStateAction } from "react";
6
+ import { toast } from "sonner";
7
+
8
+ import type {
9
+ ElementInfo,
10
+ RuntimeStructureInsertRequest,
11
+ } from "@/components/design/types";
12
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
13
+ import {
14
+ insertClonedHtmlLayers,
15
+ prepareClonedHtmlLayersForLiveInsert,
16
+ } from "@/pages/design-editor/clone-and-pen-edit";
17
+ import type { CanvasLayerClipboardEntry } from "@/pages/design-editor/command-types";
18
+ import { isStandaloneHttpUrl } from "@/pages/design-editor/editor-state";
19
+ import type { DesignFile } from "@/pages/design-editor/types";
20
+
21
+ export interface PasteToReplaceArgs {
22
+ activeFile: DesignFile;
23
+ applyLocalContentUpdate: (
24
+ nextContent: string,
25
+ options?: {
26
+ refreshPreview?: boolean;
27
+ skipPreview?: boolean;
28
+ forcePreviewFullDocument?: boolean;
29
+ immediateSave?: boolean;
30
+ persist?: boolean;
31
+ recordHistory?: boolean;
32
+ historyBeforeContent?: string;
33
+ updatedAt?: string;
34
+ clipboardMutation?: ClipboardContentMutationPublication;
35
+ },
36
+ ) => void;
37
+ canEditDesign: boolean;
38
+ getCanvasClipboardEntries: () => CanvasLayerClipboardEntry[];
39
+ getFreshActiveContent: () => string;
40
+ runtimeStructureInsertRevisionRef: RefObject<number>;
41
+ selectInsertedLayers: (
42
+ screenId: string,
43
+ content: string,
44
+ rootNodeIds: string[],
45
+ ) => void;
46
+ selectedCanvasSelector: string;
47
+ selectedElement: ElementInfo | null;
48
+ setRuntimeStructureInsertRequest: Dispatch<
49
+ SetStateAction<
50
+ (RuntimeStructureInsertRequest & { screenId: string }) | null
51
+ >
52
+ >;
53
+ t: (key: string, options?: Record<string, unknown>) => string;
54
+ }
55
+
56
+ export function runPasteToReplace({
57
+ activeFile,
58
+ applyLocalContentUpdate,
59
+ canEditDesign,
60
+ getCanvasClipboardEntries,
61
+ getFreshActiveContent,
62
+ runtimeStructureInsertRevisionRef,
63
+ selectInsertedLayers,
64
+ selectedCanvasSelector,
65
+ selectedElement,
66
+ setRuntimeStructureInsertRequest,
67
+ t,
68
+ }: PasteToReplaceArgs) {
69
+ if (!canEditDesign || !activeFile) return;
70
+ const entries = getCanvasClipboardEntries();
71
+ if (entries.length !== 1) return;
72
+ const targetSelector = selectedElement?.selector;
73
+ const targetPosition = selectedElement?.boundingRect;
74
+ if (!targetSelector || !targetPosition) return;
75
+ const baseContent = getFreshActiveContent();
76
+ const targetStoredContent = activeFile.content ?? baseContent;
77
+ if (isStandaloneHttpUrl(targetStoredContent)) {
78
+ const prepared = prepareClonedHtmlLayersForLiveInsert(
79
+ targetStoredContent,
80
+ [entries[0]!.html],
81
+ {
82
+ positions: [{ x: targetPosition.x, y: targetPosition.y }],
83
+ styleSnapshots: [entries[0]!.portableStyleSnapshot],
84
+ },
85
+ );
86
+ const html = prepared?.htmlFragments[0];
87
+ if (!prepared || !html) {
88
+ toast.error(t("designEditor.toasts.layerMoveFailed"), {
89
+ duration: 4000,
90
+ });
91
+ return;
92
+ }
93
+ runtimeStructureInsertRevisionRef.current += 1;
94
+ setRuntimeStructureInsertRequest({
95
+ requestId: runtimeStructureInsertRevisionRef.current,
96
+ screenId: activeFile.id,
97
+ html,
98
+ replaceAnchor: true,
99
+ anchor: {
100
+ selector:
101
+ selectedElement.runtimeSelector ??
102
+ selectedCanvasSelector ??
103
+ targetSelector,
104
+ sourceId:
105
+ selectedElement.runtimeSourceId ??
106
+ selectedElement.sourceId ??
107
+ undefined,
108
+ },
109
+ placement: "before",
110
+ });
111
+ return;
112
+ }
113
+ const projection = buildCodeLayerProjection(baseContent);
114
+ const targetNode = projection.nodes.find((node) =>
115
+ node.selectors.includes(targetSelector),
116
+ );
117
+ if (!targetNode) return;
118
+ const contentWithoutTarget = removeCodeLayerNodeFromHtml(
119
+ baseContent,
120
+ targetNode,
121
+ );
122
+ if (!contentWithoutTarget) return;
123
+ const result = insertClonedHtmlLayers(
124
+ contentWithoutTarget,
125
+ [entries[0]!.html],
126
+ {
127
+ positions: [{ x: targetPosition.x, y: targetPosition.y }],
128
+ styleSnapshots: [entries[0]!.portableStyleSnapshot],
129
+ managedStyleSnapshots: [entries[0]!.managedStyleSnapshot],
130
+ },
131
+ );
132
+ if (!result) return;
133
+ applyLocalContentUpdate(result.content, { forcePreviewFullDocument: true });
134
+ selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
135
+ }