@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,127 @@
1
+ import type { CodeLayerNode, CodeLayerTreeNode } from "@shared/code-layer";
2
+ import { applyVisualEdit, buildCodeLayerProjection } from "@shared/code-layer";
3
+ import type { Dispatch, RefObject, SetStateAction } from "react";
4
+ import { toast } from "sonner";
5
+
6
+ import { trace } from "@/components/design/design-trace";
7
+ import type { ElementInfo } from "@/components/design/types";
8
+ import type { ClipboardContentMutationPublication } from "@/lib/clipboard-content-lineage";
9
+ import {
10
+ codeLayerPatchMessage,
11
+ elementInfoFromCodeLayerNode,
12
+ } from "@/pages/design-editor/code-layer-state";
13
+ import { buildActiveFileNodeIdSet } from "@/pages/design-editor/selection-state";
14
+ import type { DesignFile } from "@/pages/design-editor/types";
15
+
16
+ export interface GroupSelectionArgs {
17
+ activeFile: DesignFile;
18
+ applyLocalContentUpdate: (
19
+ nextContent: string,
20
+ options?: {
21
+ refreshPreview?: boolean;
22
+ skipPreview?: boolean;
23
+ forcePreviewFullDocument?: boolean;
24
+ immediateSave?: boolean;
25
+ persist?: boolean;
26
+ recordHistory?: boolean;
27
+ historyBeforeContent?: string;
28
+ updatedAt?: string;
29
+ clipboardMutation?: ClipboardContentMutationPublication;
30
+ },
31
+ ) => void;
32
+ canEditDesign: boolean;
33
+ codeLayerOwnerByNodeIdRef: RefObject<
34
+ Map<
35
+ string,
36
+ {
37
+ fileId: string;
38
+ node: CodeLayerNode;
39
+ tree: CodeLayerTreeNode[];
40
+ runtimeOnly: boolean;
41
+ }
42
+ >
43
+ >;
44
+ files: DesignFile[];
45
+ getFreshActiveContent: () => string;
46
+ selectedLayerIdsState: string[];
47
+ sendRuntimeLayerSemanticHandoff: (
48
+ operation: "group" | "ungroup" | "auto-layout",
49
+ layerIds: readonly string[],
50
+ options?: {
51
+ desiredChange?: string;
52
+ description?: string;
53
+ commandContext?: string;
54
+ },
55
+ ) => boolean;
56
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
57
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
58
+ t: (key: string, options?: Record<string, unknown>) => string;
59
+ }
60
+
61
+ export function runGroupSelection({
62
+ activeFile,
63
+ applyLocalContentUpdate,
64
+ canEditDesign,
65
+ codeLayerOwnerByNodeIdRef,
66
+ files,
67
+ getFreshActiveContent,
68
+ selectedLayerIdsState,
69
+ sendRuntimeLayerSemanticHandoff,
70
+ setSelectedElement,
71
+ setSelectedLayerIdsState,
72
+ t,
73
+ }: GroupSelectionArgs) {
74
+ trace("structure", "group", { layers: selectedLayerIdsState.length });
75
+ if (!canEditDesign || !activeFile) return;
76
+ const selectedRuntimeLayerIds = selectedLayerIdsState.filter(
77
+ (layerId) => codeLayerOwnerByNodeIdRef.current.get(layerId)?.runtimeOnly,
78
+ );
79
+ if (selectedRuntimeLayerIds.length > 0) {
80
+ sendRuntimeLayerSemanticHandoff("group", selectedRuntimeLayerIds);
81
+ return;
82
+ }
83
+ const baseContent = getFreshActiveContent();
84
+ // Collect the DOM-node layer ids that belong to the active screen.
85
+ // Build a set of ids present in the active content so stale ids from
86
+ // other files (which can persist in selectedLayerIdsState after a
87
+ // cross-screen layers-panel selection) are excluded before wrapNodes
88
+ // runs against activeContent. Without this filter, cross-file ids
89
+ // cause wrapNodes to return "conflict" even for a valid same-file
90
+ // selection.
91
+ const fileIds = new Set(files.map((f) => f.id));
92
+ const activeNodeIdSet = buildActiveFileNodeIdSet(
93
+ buildCodeLayerProjection(baseContent),
94
+ );
95
+ const nodeIds = selectedLayerIdsState.filter(
96
+ (id) => !id.startsWith("__") && !fileIds.has(id) && activeNodeIdSet.has(id),
97
+ );
98
+ if (nodeIds.length < 2) return;
99
+ const patch = applyVisualEdit(baseContent, {
100
+ kind: "wrapNodes",
101
+ targetIds: nodeIds,
102
+ autoLayout: false,
103
+ });
104
+ if (patch.result.status !== "applied") {
105
+ toast.error(
106
+ codeLayerPatchMessage(
107
+ patch.result.message,
108
+ t("designEditor.toasts.layerMoveFailed"),
109
+ ),
110
+ { duration: 4000 },
111
+ );
112
+ return;
113
+ }
114
+ applyLocalContentUpdate(patch.content, { skipPreview: true });
115
+ // Select the new wrapper node if the substrate reported its id.
116
+ const wrapperId = patch.result.wrapperNodeId;
117
+ if (wrapperId) {
118
+ // Find the projection node whose data-agent-native-node-id matches.
119
+ const wrapperNode = patch.projection.nodes.find(
120
+ (n) => n.dataAttributes["data-agent-native-node-id"] === wrapperId,
121
+ );
122
+ if (wrapperNode) {
123
+ setSelectedLayerIdsState([wrapperNode.id]);
124
+ setSelectedElement(elementInfoFromCodeLayerNode(wrapperNode));
125
+ }
126
+ }
127
+ }
@@ -0,0 +1,121 @@
1
+ import type { Dispatch, RefObject, SetStateAction } from "react";
2
+ import { flushSync } from "react-dom";
3
+
4
+ import type { CanvasContextMenuHandle } from "@/components/design/CanvasContextMenu";
5
+ import type { IframeContextMenuPayload } from "@/components/design/design-canvas/iframe-events";
6
+ import { findCanvasIframeForScreen } from "@/components/design/multi-screen/iframe-targeting";
7
+ import type {
8
+ CanvasLayerHitCandidate,
9
+ ElementInfo,
10
+ ElementSelectionIntent,
11
+ } from "@/components/design/types";
12
+ import {
13
+ computeIframeLocalCanvasPoint,
14
+ readOverviewZoomPercentFromTransform,
15
+ } from "@/pages/design-editor/overview-camera";
16
+ import type { DesignFile } from "@/pages/design-editor/types";
17
+
18
+ export interface IframeContextMenuArgs {
19
+ activeFile: DesignFile;
20
+ activeFileId: string | null;
21
+ boardFileId: string | undefined;
22
+ canvasContainerRef: RefObject<HTMLDivElement | null>;
23
+ canvasContextMenuRef: RefObject<CanvasContextMenuHandle | null>;
24
+ focusDesignInspectorForSelection: () => void;
25
+ handleScreenElementSelect: (
26
+ screenId: string,
27
+ info: ElementInfo,
28
+ intent?: ElementSelectionIntent,
29
+ options?: { persistPendingNodeId?: boolean; breakpointWidthPx?: number },
30
+ ) => void;
31
+ overviewCanvasZoom: number;
32
+ setCanvasLayerHitCandidates: Dispatch<
33
+ SetStateAction<CanvasLayerHitCandidate[]>
34
+ >;
35
+ viewMode: "single" | "overview";
36
+ zoom: number;
37
+ }
38
+
39
+ export function runIframeContextMenu(
40
+ {
41
+ activeFile,
42
+ activeFileId,
43
+ boardFileId,
44
+ canvasContainerRef,
45
+ canvasContextMenuRef,
46
+ focusDesignInspectorForSelection,
47
+ handleScreenElementSelect,
48
+ overviewCanvasZoom,
49
+ setCanvasLayerHitCandidates,
50
+ viewMode,
51
+ zoom,
52
+ }: IframeContextMenuArgs,
53
+ payload: IframeContextMenuPayload,
54
+ ) {
55
+ const container = canvasContainerRef.current;
56
+ const menu = canvasContextMenuRef.current;
57
+ if (!container || !menu) return;
58
+ const contextScreenId =
59
+ payload.screenId ?? activeFile?.id ?? activeFileId ?? null;
60
+ flushSync(() => {
61
+ setCanvasLayerHitCandidates(payload.layerCandidates ?? []);
62
+ });
63
+ if (payload.info && contextScreenId) {
64
+ flushSync(() => {
65
+ handleScreenElementSelect(contextScreenId, payload.info!, undefined, {
66
+ persistPendingNodeId: false,
67
+ });
68
+ });
69
+ focusDesignInspectorForSelection();
70
+ }
71
+ const clientX =
72
+ typeof payload.viewportClientX === "number"
73
+ ? payload.viewportClientX
74
+ : payload.clientX;
75
+ const clientY =
76
+ typeof payload.viewportClientY === "number"
77
+ ? payload.viewportClientY
78
+ : payload.clientY;
79
+ // PASTE-HERE-IN-CONTENT: this imperative openAt() call bypasses
80
+ // CanvasContextMenu's own onContextMenuCapture handler entirely — the
81
+ // ONLY place that normally calls getCanvasPoint to attach canvasX/
82
+ // canvasY to the menu's point. Without computing it here too, a
83
+ // right-click that lands ON rendered screen content (an element, or
84
+ // empty in-screen space — as opposed to the shared canvas background,
85
+ // which still goes through onContextMenuCapture) never got a
86
+ // canvasX/canvasY at all, so "Paste here" from it silently degraded to
87
+ // the position-less cascade/offset paste instead of landing under the
88
+ // cursor. Overview screens each carry their own screenId through the
89
+ // context-menu bridge, including non-active screens, so target that
90
+ // exact iframe rather than the currently-active screen. Otherwise
91
+ // Paste here would translate the pointer through the wrong frame just
92
+ // before Select layer activates the right-clicked screen.
93
+ const iframeForPoint =
94
+ viewMode === "single"
95
+ ? container.querySelector<HTMLElement>("[data-design-preview-iframe]")
96
+ : findCanvasIframeForScreen(
97
+ container,
98
+ contextScreenId ?? "",
99
+ boardFileId ?? undefined,
100
+ );
101
+ const liveOverviewZoom =
102
+ viewMode === "overview"
103
+ ? readOverviewZoomPercentFromTransform(
104
+ container.querySelector<HTMLElement>(
105
+ "[data-multi-screen-canvas-world]",
106
+ )?.style.transform,
107
+ overviewCanvasZoom,
108
+ )
109
+ : overviewCanvasZoom;
110
+ const canvasPoint = computeIframeLocalCanvasPoint({
111
+ clientX,
112
+ clientY,
113
+ iframeRect: iframeForPoint?.getBoundingClientRect() ?? null,
114
+ zoomPercent: viewMode === "single" ? zoom : liveOverviewZoom,
115
+ });
116
+ menu.openAt({
117
+ clientX,
118
+ clientY,
119
+ ...(canvasPoint ? { canvasX: canvasPoint.x, canvasY: canvasPoint.y } : {}),
120
+ });
121
+ }
@@ -0,0 +1,119 @@
1
+ import { callAction } from "@agent-native/core/client/hooks";
2
+ import type { QueryClient } from "@tanstack/react-query";
3
+ import type { Dispatch, RefObject, SetStateAction } from "react";
4
+ import type { NavigateFunction } from "react-router";
5
+ import { toast } from "sonner";
6
+
7
+ import type { ImportResult } from "@/lib/design-import";
8
+ import { importResultSummary } from "@/lib/design-import";
9
+ import { resolveFigmaPasteImportCall } from "@/lib/figma-clipboard";
10
+
11
+ export interface ImportFigmaClipboardIntoDesignArgs {
12
+ canEditDesign: boolean;
13
+ figmaPasteImportingRef: RefObject<boolean>;
14
+ id: string | undefined;
15
+ navigate: NavigateFunction;
16
+ queryClient: QueryClient;
17
+ setFigmaHydrationFileIds: Dispatch<SetStateAction<string[]>>;
18
+ setFigmaHydrationImageCount: Dispatch<SetStateAction<number>>;
19
+ setFigmaHydrationOpen: Dispatch<SetStateAction<boolean>>;
20
+ t: (key: string, options?: Record<string, unknown>) => string;
21
+ }
22
+
23
+ export async function runImportFigmaClipboardIntoDesign(
24
+ {
25
+ canEditDesign,
26
+ figmaPasteImportingRef,
27
+ id,
28
+ navigate,
29
+ queryClient,
30
+ setFigmaHydrationFileIds,
31
+ setFigmaHydrationImageCount,
32
+ setFigmaHydrationOpen,
33
+ t,
34
+ }: ImportFigmaClipboardIntoDesignArgs,
35
+ content: string,
36
+ ) {
37
+ if (!id) return;
38
+ if (!canEditDesign) {
39
+ toast.error("Import requires editor access" /* i18n-ignore */);
40
+ return;
41
+ }
42
+ if (figmaPasteImportingRef.current) {
43
+ toast.info(t("designEditor.import.figUploadProcessing"));
44
+ return;
45
+ }
46
+ figmaPasteImportingRef.current = true;
47
+ const loadingToastId = toast.loading(
48
+ t("designEditor.import.figUploadProcessing"),
49
+ );
50
+ try {
51
+ const figmaPasteCall = resolveFigmaPasteImportCall(content);
52
+ const result = (await callAction(figmaPasteCall.action, {
53
+ designId: id,
54
+ ...figmaPasteCall.payload,
55
+ })) as ImportResult;
56
+ if (result?.error) throw new Error(result.error);
57
+ if (!result?.files?.length) {
58
+ toast.error(t("designEditor.import.errors.figmaPasteFailed"), {
59
+ description:
60
+ result?.guidance ?? t("designEditor.import.figmaPasteMatchGuidance"),
61
+ });
62
+ return;
63
+ }
64
+ await Promise.all([
65
+ queryClient.invalidateQueries({ queryKey: ["action", "get-design"] }),
66
+ queryClient.invalidateQueries({ queryKey: ["action"] }),
67
+ ]);
68
+ const figmaStrategyLabel =
69
+ result?.strategy === "restNodes"
70
+ ? t("designEditor.import.figmaPasteRestLabel")
71
+ : result?.strategy === "htmlFallback"
72
+ ? t("designEditor.import.figmaPasteHtmlLabel")
73
+ : result?.strategy === "localKiwi"
74
+ ? t("designEditor.import.figmaPasteLocalKiwiLabel")
75
+ : undefined;
76
+ toast.success(
77
+ importResultSummary(result, t("designEditor.import.figmaSuccess")),
78
+ figmaStrategyLabel ? { description: figmaStrategyLabel } : undefined,
79
+ );
80
+ if (
81
+ result?.strategy === "localKiwi" &&
82
+ (result?.unresolvedImages ?? 0) > 0 &&
83
+ result?.files?.length
84
+ ) {
85
+ const count = result.unresolvedImages!;
86
+ const fileIds = result.files.map((f) => f.id);
87
+ setFigmaHydrationFileIds(fileIds);
88
+ setFigmaHydrationImageCount(count);
89
+ setFigmaHydrationOpen(true);
90
+ toast.info(
91
+ t("designEditor.import.figmaPasteImagesNeedToken", {
92
+ count,
93
+ plural: count === 1 ? "" : "s",
94
+ }),
95
+ );
96
+ } else if (result?.figmaApiKeyMissing) {
97
+ toast.info(t("designEditor.import.figmaPasteApiKeyHint"));
98
+ } else if (
99
+ result?.strategy === "htmlFallback" &&
100
+ (result?.matchStatus === "ambiguous" || result?.matchStatus === "none")
101
+ ) {
102
+ toast.info(t("designEditor.import.figmaPasteMatchGuidance"));
103
+ }
104
+ if (result?.warnings?.length) {
105
+ toast.warning(t("designEditor.import.warningsToast"), {
106
+ description: result.warnings[0],
107
+ });
108
+ }
109
+ navigate(`/design/${result?.designId ?? id}?view=overview`);
110
+ } catch (error) {
111
+ toast.error(t("designEditor.import.errors.figmaPasteFailed"), {
112
+ description:
113
+ error instanceof Error ? error.message : t("common.genericError"),
114
+ });
115
+ } finally {
116
+ figmaPasteImportingRef.current = false;
117
+ toast.dismiss(loadingToastId);
118
+ }
119
+ }
@@ -0,0 +1,163 @@
1
+ import type { CodeLayerNode, CodeLayerProjection } from "@shared/code-layer";
2
+ import type { Dispatch, RefObject, SetStateAction } from "react";
3
+
4
+ import type { CanvasLayerMarqueeSelection } from "@/components/design/multi-screen/types";
5
+ import type {
6
+ ElementInfo,
7
+ ElementSelectionIntent,
8
+ } from "@/components/design/types";
9
+ import {
10
+ canonicalizeElementInfoFromProjection,
11
+ resolveCodeLayerNodeFromElementInfo,
12
+ } from "@/pages/design-editor/code-layer-state";
13
+ import {
14
+ dedupeStringIds,
15
+ isScreenRootElementInfo,
16
+ shouldClearBridgeSelectionOnEmptyMarquee,
17
+ } from "@/pages/design-editor/selection-state";
18
+ import type { DesignTool, EditorMode } from "@/pages/design-editor/types";
19
+
20
+ export interface LayerMarqueeSelectionChangeArgs {
21
+ clearPendingOverviewLayerSelectionTimer: () => void;
22
+ focusDesignInspectorForSelection: () => void;
23
+ getCodeLayerProjectionForScreen: (
24
+ screenId: string,
25
+ ) => CodeLayerProjection | null;
26
+ hasActiveSelectionRef: RefObject<boolean>;
27
+ lastMarqueeSelectionSignatureRef: RefObject<string | null>;
28
+ pendingOverviewLayerSelectionRef: RefObject<string | null>;
29
+ pendingOverviewScreenSelectionRef: RefObject<string | null>;
30
+ setActiveFileId: Dispatch<SetStateAction<string | null>>;
31
+ setActiveTool: Dispatch<SetStateAction<DesignTool>>;
32
+ setCreatedOverviewLayerSelection: Dispatch<
33
+ SetStateAction<{ screenId: string; layerId: string } | null>
34
+ >;
35
+ setMode: Dispatch<SetStateAction<EditorMode>>;
36
+ setOverviewClearSelectionRequest: Dispatch<SetStateAction<number>>;
37
+ setOverviewSelectedScreenIds: Dispatch<SetStateAction<string[]>>;
38
+ setSelectedElement: Dispatch<SetStateAction<ElementInfo | null>>;
39
+ setSelectedLayerIdsState: Dispatch<SetStateAction<string[]>>;
40
+ viewModeRef: RefObject<"single" | "overview">;
41
+ }
42
+
43
+ export function runLayerMarqueeSelectionChange(
44
+ {
45
+ clearPendingOverviewLayerSelectionTimer,
46
+ focusDesignInspectorForSelection,
47
+ getCodeLayerProjectionForScreen,
48
+ hasActiveSelectionRef,
49
+ lastMarqueeSelectionSignatureRef,
50
+ pendingOverviewLayerSelectionRef,
51
+ pendingOverviewScreenSelectionRef,
52
+ setActiveFileId,
53
+ setActiveTool,
54
+ setCreatedOverviewLayerSelection,
55
+ setMode,
56
+ setOverviewClearSelectionRequest,
57
+ setOverviewSelectedScreenIds,
58
+ setSelectedElement,
59
+ setSelectedLayerIdsState,
60
+ viewModeRef,
61
+ }: LayerMarqueeSelectionChangeArgs,
62
+ selection: CanvasLayerMarqueeSelection[],
63
+ intent: ElementSelectionIntent,
64
+ ) {
65
+ // PF10: MultiScreenCanvas reports the marquee hit-set on every
66
+ // mousemove tick during a drag, not just on settle (see
67
+ // reportLayerSelection in MultiScreenCanvas.tsx). Bail before any
68
+ // projection/canonicalization work when the reported set is identical
69
+ // to the last tick's — the common case while the marquee rect isn't
70
+ // currently crossing an element boundary.
71
+ //
72
+ // The dedup is ONLY applied to non-empty hit-sets. An empty hit-set (a
73
+ // plain empty-space click, or dragging over blank canvas) is cheap to
74
+ // process and must never be deduped away: the last non-marquee selection
75
+ // (iframe click, layers panel, agent, undo/redo, keyboard) does not
76
+ // update this signature, so an empty "#0" tick could otherwise match a
77
+ // stale "#0" and skip the deselect entirely.
78
+ const signature =
79
+ selection
80
+ .map(
81
+ (item) =>
82
+ `${item.screenId}:${item.info.sourceId ?? item.info.selector ?? ""}`,
83
+ )
84
+ .join("|") + `#${intent.additive ? "1" : "0"}`;
85
+ if (selection.length > 0) {
86
+ if (lastMarqueeSelectionSignatureRef.current === signature) return;
87
+ }
88
+ lastMarqueeSelectionSignatureRef.current = signature;
89
+
90
+ pendingOverviewScreenSelectionRef.current = null;
91
+ pendingOverviewLayerSelectionRef.current = null;
92
+ clearPendingOverviewLayerSelectionTimer();
93
+ setCreatedOverviewLayerSelection(null);
94
+
95
+ const resolved = selection
96
+ .map((item) => {
97
+ const projection = getCodeLayerProjectionForScreen(item.screenId);
98
+ if (!projection) return null;
99
+ const canonical = canonicalizeElementInfoFromProjection(
100
+ projection,
101
+ item.info,
102
+ );
103
+ const node = resolveCodeLayerNodeFromElementInfo(projection, canonical);
104
+ if (!node || isScreenRootElementInfo(canonical)) return null;
105
+ return {
106
+ screenId: item.screenId,
107
+ node,
108
+ elementInfo: canonical,
109
+ };
110
+ })
111
+ .filter(
112
+ (
113
+ item,
114
+ ): item is {
115
+ screenId: string;
116
+ node: CodeLayerNode;
117
+ elementInfo: ElementInfo;
118
+ } => Boolean(item),
119
+ );
120
+
121
+ const hitLayerIds = dedupeStringIds(resolved.map((item) => item.node.id));
122
+ setSelectedLayerIdsState((current) =>
123
+ intent.additive
124
+ ? dedupeStringIds([
125
+ ...current.filter((layerId) => !layerId.startsWith("__")),
126
+ ...hitLayerIds,
127
+ ])
128
+ : hitLayerIds,
129
+ );
130
+ if (viewModeRef.current === "overview") {
131
+ setOverviewSelectedScreenIds([]);
132
+ }
133
+
134
+ const primary = resolved[resolved.length - 1];
135
+ if (primary) {
136
+ setActiveFileId(primary.screenId);
137
+ setSelectedElement(primary.elementInfo);
138
+ focusDesignInspectorForSelection();
139
+ } else if (
140
+ hasActiveSelectionRef.current &&
141
+ shouldClearBridgeSelectionOnEmptyMarquee({
142
+ resolvedCount: resolved.length,
143
+ additive: intent.additive,
144
+ })
145
+ ) {
146
+ // B5-1: an empty-space click (zero-hit marquee) must deselect an
147
+ // in-screen/bridge element selection too, not just the host
148
+ // selectedElement state. Without bumping this counter, the iframe's
149
+ // own selection-overlay highlight (set via the bridge for an
150
+ // in-screen element) never gets the clear signal and keeps
151
+ // rendering, mirroring the same clear used by Escape (~20428).
152
+ //
153
+ // Gated on hasActiveSelectionRef so that empty ticks during a
154
+ // blank-canvas marquee drag (which now always reach here, since empty
155
+ // sets are no longer deduped) don't bump the clear counter on every
156
+ // mousemove when there is nothing selected to clear.
157
+ setSelectedElement(null);
158
+ setOverviewClearSelectionRequest((request) => request + 1);
159
+ }
160
+
161
+ setActiveTool("move");
162
+ setMode("edit");
163
+ }