@agent-native/core 0.163.3 → 0.164.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +29 -0
  3. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +1 -2
  4. package/corpus/templates/analytics/netlify.toml +2 -0
  5. package/corpus/templates/assets/netlify.toml +2 -0
  6. package/corpus/templates/brain/netlify.toml +2 -0
  7. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +2 -1
  8. package/corpus/templates/calendar/changelog/2026-08-19-calendar-page-loads-stay-fast-after-idle-periods.md +6 -0
  9. package/corpus/templates/calendar/netlify.toml +2 -0
  10. package/corpus/templates/chat/netlify.toml +2 -0
  11. package/corpus/templates/clips/app/components/player/player-controls.tsx +3 -3
  12. package/corpus/templates/clips/app/components/player/reaction-emojis.ts +12 -0
  13. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +90 -9
  14. package/corpus/templates/clips/app/components/player/scrubber.tsx +43 -10
  15. package/corpus/templates/clips/app/components/player/video-player.tsx +105 -15
  16. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +1 -2
  17. package/corpus/templates/clips/app/routes/_index.tsx +10 -2
  18. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +13 -8
  19. package/corpus/templates/clips/app/routes/share.$shareId.tsx +15 -8
  20. package/corpus/templates/clips/netlify.toml +2 -0
  21. package/corpus/templates/clips/server/routes/[...page].head.ts +5 -0
  22. package/corpus/templates/content/netlify.toml +2 -0
  23. package/corpus/templates/design/.agents/skills/design-editor-architecture/SKILL.md +103 -0
  24. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -1
  25. package/corpus/templates/design/AGENTS.md +3 -1
  26. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +3 -2
  27. package/corpus/templates/design/app/components/design/editor/AgentNativeMenuMark.tsx +25 -0
  28. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +411 -0
  29. package/corpus/templates/design/app/components/design/editor/DesignCollaborators.tsx +153 -0
  30. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +231 -0
  31. package/corpus/templates/design/app/components/design/editor/MakeRealDialog.tsx +223 -0
  32. package/corpus/templates/design/app/components/design/editor/PendingScreenDeletionDialog.tsx +58 -0
  33. package/corpus/templates/design/app/components/design/editor/PendingVisualStyleWarningDialog.tsx +56 -0
  34. package/corpus/templates/design/app/components/design/editor/ReadOnlyEditorPanel.tsx +23 -0
  35. package/corpus/templates/design/app/components/design/editor/SaveTemplateDialog.tsx +161 -0
  36. package/corpus/templates/design/app/components/design/editor/toolbar-controls.tsx +185 -0
  37. package/corpus/templates/design/app/pages/DesignEditor.tsx +20913 -0
  38. package/corpus/templates/design/app/pages/design-editor/command-types.ts +120 -0
  39. package/corpus/templates/design/app/pages/design-editor/commands/README.md +193 -0
  40. package/corpus/templates/design/app/pages/design-editor/commands/add-auto-layout.ts +416 -0
  41. package/corpus/templates/design/app/pages/design-editor/commands/add-screen.ts +112 -0
  42. package/corpus/templates/design/app/pages/design-editor/commands/align-selection.ts +183 -0
  43. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +243 -0
  44. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +236 -0
  45. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +304 -0
  46. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +376 -0
  47. package/corpus/templates/design/app/pages/design-editor/commands/apply-to-source.ts +132 -0
  48. package/corpus/templates/design/app/pages/design-editor/commands/can-move-layer.ts +117 -0
  49. package/corpus/templates/design/app/pages/design-editor/commands/change-selected-z-index.ts +212 -0
  50. package/corpus/templates/design/app/pages/design-editor/commands/commit-relative-style-delta-to-selected-layers.ts +176 -0
  51. package/corpus/templates/design/app/pages/design-editor/commands/commit-styles-to-selected-layers.ts +175 -0
  52. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +710 -0
  53. package/corpus/templates/design/app/pages/design-editor/commands/confirm-make-real.ts +99 -0
  54. package/corpus/templates/design/app/pages/design-editor/commands/copy-as-figma-svg.ts +95 -0
  55. package/corpus/templates/design/app/pages/design-editor/commands/copy-selection.ts +156 -0
  56. package/corpus/templates/design/app/pages/design-editor/commands/create-primitive.ts +291 -0
  57. package/corpus/templates/design/app/pages/design-editor/commands/create-screen-frame.ts +131 -0
  58. package/corpus/templates/design/app/pages/design-editor/commands/cross-screen-element-drop.ts +577 -0
  59. package/corpus/templates/design/app/pages/design-editor/commands/delete-files.ts +364 -0
  60. package/corpus/templates/design/app/pages/design-editor/commands/delete-selection.ts +521 -0
  61. package/corpus/templates/design/app/pages/design-editor/commands/detach-instance-menu-action.ts +88 -0
  62. package/corpus/templates/design/app/pages/design-editor/commands/distribute-selection.ts +114 -0
  63. package/corpus/templates/design/app/pages/design-editor/commands/download-all-screens-pdf.ts +116 -0
  64. package/corpus/templates/design/app/pages/design-editor/commands/download-pdf.ts +103 -0
  65. package/corpus/templates/design/app/pages/design-editor/commands/download-svg.ts +159 -0
  66. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-screen.ts +201 -0
  67. package/corpus/templates/design/app/pages/design-editor/commands/duplicate-selection.ts +279 -0
  68. package/corpus/templates/design/app/pages/design-editor/commands/editor-paste.ts +81 -0
  69. package/corpus/templates/design/app/pages/design-editor/commands/enter-hotkey.ts +155 -0
  70. package/corpus/templates/design/app/pages/design-editor/commands/enter-single-screen.ts +168 -0
  71. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +212 -0
  72. package/corpus/templates/design/app/pages/design-editor/commands/frame-selection.ts +119 -0
  73. package/corpus/templates/design/app/pages/design-editor/commands/geometry-commit.ts +169 -0
  74. package/corpus/templates/design/app/pages/design-editor/commands/get-selected-layer-snapshots.ts +177 -0
  75. package/corpus/templates/design/app/pages/design-editor/commands/group-selection.ts +127 -0
  76. package/corpus/templates/design/app/pages/design-editor/commands/iframe-context-menu.ts +121 -0
  77. package/corpus/templates/design/app/pages/design-editor/commands/import-figma-clipboard-into-design.ts +119 -0
  78. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +163 -0
  79. package/corpus/templates/design/app/pages/design-editor/commands/layer-move-to-screen.ts +331 -0
  80. package/corpus/templates/design/app/pages/design-editor/commands/layer-move.ts +517 -0
  81. package/corpus/templates/design/app/pages/design-editor/commands/layer-rename.ts +247 -0
  82. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +161 -0
  83. package/corpus/templates/design/app/pages/design-editor/commands/mode-change.ts +122 -0
  84. package/corpus/templates/design/app/pages/design-editor/commands/nudge-selection.ts +213 -0
  85. package/corpus/templates/design/app/pages/design-editor/commands/overview-primitive-reparent.ts +318 -0
  86. package/corpus/templates/design/app/pages/design-editor/commands/paste-copied-screens.ts +114 -0
  87. package/corpus/templates/design/app/pages/design-editor/commands/paste-over-selection.ts +70 -0
  88. package/corpus/templates/design/app/pages/design-editor/commands/paste-selection.ts +436 -0
  89. package/corpus/templates/design/app/pages/design-editor/commands/paste-to-replace.ts +135 -0
  90. package/corpus/templates/design/app/pages/design-editor/commands/pasted-image-files.ts +209 -0
  91. package/corpus/templates/design/app/pages/design-editor/commands/persist-frame-geometry-save.ts +113 -0
  92. package/corpus/templates/design/app/pages/design-editor/commands/persist-tweak-save.ts +160 -0
  93. package/corpus/templates/design/app/pages/design-editor/commands/primitive-created.ts +168 -0
  94. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-layer-state-edit.ts +149 -0
  95. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-structure-edit.ts +200 -0
  96. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-live-text-edit.ts +152 -0
  97. package/corpus/templates/design/app/pages/design-editor/commands/record-pending-visual-style-edit.ts +247 -0
  98. package/corpus/templates/design/app/pages/design-editor/commands/redo.ts +1086 -0
  99. package/corpus/templates/design/app/pages/design-editor/commands/render-png-blob.ts +200 -0
  100. package/corpus/templates/design/app/pages/design-editor/commands/save-file-content.ts +207 -0
  101. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +262 -0
  102. package/corpus/templates/design/app/pages/design-editor/commands/screen-text-content-change.ts +216 -0
  103. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-duplicate-change.ts +108 -0
  104. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-structure-change.ts +280 -0
  105. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +151 -0
  106. package/corpus/templates/design/app/pages/design-editor/commands/send-overview-annotations.ts +88 -0
  107. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +122 -0
  108. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +143 -0
  109. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +115 -0
  110. package/corpus/templates/design/app/pages/design-editor/commands/start-retry-generation.ts +152 -0
  111. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +99 -0
  112. package/corpus/templates/design/app/pages/design-editor/commands/style-change.ts +152 -0
  113. package/corpus/templates/design/app/pages/design-editor/commands/styles-change.ts +155 -0
  114. package/corpus/templates/design/app/pages/design-editor/commands/suggest-auto-layout.ts +125 -0
  115. package/corpus/templates/design/app/pages/design-editor/commands/text-content-change.ts +201 -0
  116. package/corpus/templates/design/app/pages/design-editor/commands/tidy-up.ts +123 -0
  117. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-hidden.ts +167 -0
  118. package/corpus/templates/design/app/pages/design-editor/commands/toggle-layer-locked.ts +175 -0
  119. package/corpus/templates/design/app/pages/design-editor/commands/toggle-motion-keyframe.ts +99 -0
  120. package/corpus/templates/design/app/pages/design-editor/commands/tweak-prompt-submit.ts +84 -0
  121. package/corpus/templates/design/app/pages/design-editor/commands/undo.ts +906 -0
  122. package/corpus/templates/design/app/pages/design-editor/commands/ungroup-selection.ts +170 -0
  123. package/corpus/templates/design/app/pages/design-editor/commands/visual-duplicate-change.ts +119 -0
  124. package/corpus/templates/design/app/pages/design-editor/commands/visual-structure-change.ts +274 -0
  125. package/corpus/templates/design/app/pages/design-editor/commands/write-frame-geometry-snapshot.ts +99 -0
  126. package/corpus/templates/design/app/pages/design-editor/derive/design-breakpoints.ts +56 -0
  127. package/corpus/templates/design/app/pages/design-editor/derive/overview-screens.ts +146 -0
  128. package/corpus/templates/design/app/pages/design-editor/domains/use-tweaks.ts +326 -0
  129. package/corpus/templates/design/app/pages/design-editor/editor-constants.ts +54 -0
  130. package/corpus/templates/design/app/pages/design-editor/editor-helpers.ts +183 -0
  131. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +270 -0
  132. package/corpus/templates/design/app/pages/design-editor/effects/mirror-selection-to-agent-chat.ts +134 -0
  133. package/corpus/templates/design/app/pages/design-editor/effects/motion-autosave.ts +335 -0
  134. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +183 -0
  135. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +252 -0
  136. package/corpus/templates/design/app/pages/design-editor/effects/resume-pending-generation.ts +178 -0
  137. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +157 -0
  138. package/corpus/templates/design/app/pages/design-editor/preview-html.ts +19 -0
  139. package/corpus/templates/design/netlify.toml +2 -0
  140. package/corpus/templates/design/shared/board-file.ts +2 -1
  141. package/corpus/templates/dispatch/netlify.toml +2 -0
  142. package/corpus/templates/forms/netlify.toml +2 -0
  143. package/corpus/templates/mail/netlify.toml +2 -0
  144. package/corpus/templates/plan/netlify.toml +2 -0
  145. package/corpus/templates/slides/netlify.toml +2 -0
  146. package/dist/agent/engine/builder-engine.d.ts +6 -4
  147. package/dist/agent/engine/builder-engine.js +91 -26
  148. package/dist/agent/engine/registry.js +16 -0
  149. package/dist/cli/code-agent-agent-tools.d.ts +2 -0
  150. package/dist/cli/code-agent-agent-tools.js +188 -0
  151. package/dist/cli/code-agent-collaboration.d.ts +34 -0
  152. package/dist/cli/code-agent-collaboration.js +104 -0
  153. package/dist/cli/code-agent-executor.js +6 -0
  154. package/dist/cli/code-agent-schedules.d.ts +54 -0
  155. package/dist/cli/code-agent-schedules.js +198 -0
  156. package/dist/client/AgentPanel.js +1 -1
  157. package/dist/client/ConnectBuilderCard.js +5 -2
  158. package/dist/client/EnvironmentBadge.js +6 -5
  159. package/dist/client/chat/run-recovery.js +3 -2
  160. package/dist/client/chat/tool-call-display.js +2 -3
  161. package/dist/client/components/CodeRequiredDialog.js +4 -2
  162. package/dist/client/i18n.js +5 -4
  163. package/dist/client/settings/SettingsPanel.js +1 -1
  164. package/dist/client/settings/VoiceTranscriptionSection.js +1 -1
  165. package/dist/client/settings/useBuilderStatus.d.ts +6 -1
  166. package/dist/client/settings/useBuilderStatus.js +22 -33
  167. package/dist/client/transcription/BuilderTranscriptionCta.js +1 -1
  168. package/dist/code-agents/index.d.ts +2 -0
  169. package/dist/code-agents/index.js +2 -0
  170. package/dist/collab/routes.d.ts +1 -1
  171. package/dist/localization/shared.d.ts +2 -0
  172. package/dist/localization/shared.js +5 -0
  173. package/dist/mcp-client/oauth-client.d.ts +1 -0
  174. package/dist/mcp-client/oauth-client.js +50 -1
  175. package/dist/observability/routes.d.ts +1 -1
  176. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  177. package/dist/resources/handlers.d.ts +1 -1
  178. package/dist/server/auth-marketing-locales.d.ts +7 -0
  179. package/dist/server/auth-marketing-locales.js +1322 -0
  180. package/dist/server/auth-marketing.d.ts +1 -0
  181. package/dist/server/auth-marketing.js +35 -6
  182. package/dist/server/auth.js +4 -17
  183. package/dist/server/builder-browser.d.ts +22 -0
  184. package/dist/server/builder-browser.js +77 -1
  185. package/dist/server/builder-oauth.d.ts +50 -0
  186. package/dist/server/builder-oauth.js +263 -0
  187. package/dist/server/core-routes-plugin.d.ts +11 -0
  188. package/dist/server/core-routes-plugin.js +291 -415
  189. package/dist/server/credential-provider.d.ts +1 -1
  190. package/dist/server/credential-provider.js +1 -1
  191. package/dist/server/csrf.js +2 -2
  192. package/dist/server/onboarding-html.js +52 -120
  193. package/dist/server/realtime-token.d.ts +1 -1
  194. package/dist/server/transcribe-voice.d.ts +1 -1
  195. package/dist/settings/store.d.ts +9 -0
  196. package/dist/settings/store.js +19 -0
  197. package/dist/templates/chat/netlify.toml +2 -0
  198. package/dist/vite/client.js +35 -37
  199. package/docs/content/deployment.mdx +2 -0
  200. package/docs/content/multi-app-workspace.mdx +1 -1
  201. package/package.json +2 -2
  202. package/src/templates/chat/netlify.toml +2 -0
@@ -0,0 +1,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
+ }