@agent-native/core 0.79.27 → 0.80.0

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 (346) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +26 -0
  3. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +41 -0
  4. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +51 -0
  5. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +50 -0
  6. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +49 -0
  7. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +44 -0
  8. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +46 -0
  9. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +44 -0
  10. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +48 -0
  11. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +36 -0
  12. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +38 -0
  13. package/corpus/core/docs/content/template-design.mdx +45 -0
  14. package/corpus/core/package.json +2 -1
  15. package/corpus/core/src/agent/engine/index.ts +1 -0
  16. package/corpus/core/src/agent/engine/registry.ts +40 -0
  17. package/corpus/core/src/agent/production-agent.ts +6 -2
  18. package/corpus/core/src/cli/design-connect.ts +490 -0
  19. package/corpus/core/src/cli/index.ts +16 -0
  20. package/corpus/core/src/cli/skills.ts +126 -3
  21. package/corpus/core/src/client/AssistantChat.tsx +43 -7
  22. package/corpus/core/src/client/agent-chat.ts +27 -3
  23. package/corpus/core/src/client/blocks/library/MermaidBlock.tsx +1 -1
  24. package/corpus/core/src/client/blocks/library/diagram.tsx +2 -2
  25. package/corpus/core/src/client/blocks/library/wireframe.tsx +1 -1
  26. package/corpus/core/src/client/chat/message-components.tsx +1 -1
  27. package/corpus/core/src/client/chat-model-groups.ts +1 -2
  28. package/corpus/core/src/client/components/LiveCursorOverlay.tsx +33 -66
  29. package/corpus/core/src/client/composer/TiptapComposer.tsx +99 -24
  30. package/corpus/core/src/client/composer/useVoiceDictation.ts +429 -260
  31. package/corpus/core/src/client/frame-protocol.ts +17 -2
  32. package/corpus/core/src/client/rich-markdown-editor/RegistryBlockNode.tsx +2 -2
  33. package/corpus/core/src/client/sharing/ShareButton.tsx +20 -15
  34. package/corpus/core/src/client/use-agent-engine-configured.ts +80 -28
  35. package/corpus/core/src/server/transcribe-voice.ts +57 -22
  36. package/corpus/core/src/shared/mcp-embed-headers.ts +23 -1
  37. package/corpus/core/src/styles/agent-native.css +56 -7
  38. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  39. package/corpus/core/src/vite/client.ts +2 -0
  40. package/corpus/core/src/voice/index.ts +18 -0
  41. package/corpus/core/src/voice/voice-cleanup-prompt.ts +38 -0
  42. package/corpus/core/src/voice/voice-context.ts +217 -0
  43. package/corpus/core/src/voice/voice-replacements.ts +75 -0
  44. package/corpus/templates/analytics/AGENTS.md +4 -3
  45. package/corpus/templates/analytics/actions/list-session-recordings.ts +6 -1
  46. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +557 -400
  47. package/corpus/templates/analytics/app/global.css +0 -4
  48. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  49. package/corpus/templates/analytics/app/i18n-data.ts +20 -0
  50. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +33 -8
  51. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +7 -0
  52. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +7 -0
  53. package/corpus/templates/analytics/changelog/2026-06-28-ask-tab-now-matches-the-darker-app-background.md +6 -0
  54. package/corpus/templates/analytics/changelog/2026-06-28-dashboard-email-reports-now-use-cleaner-light-mode-snapshots.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  56. package/corpus/templates/analytics/changelog/2026-06-28-sessions-now-show-only-signed-in-recordings-with-playable-re.md +6 -0
  57. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +3 -3
  58. package/corpus/templates/analytics/server/lib/dashboard-report.ts +29 -7
  59. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +5 -2
  60. package/corpus/templates/analytics/server/lib/session-replay.ts +24 -4
  61. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -4
  62. package/corpus/templates/assets/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  63. package/corpus/templates/brain/app/components/layout/Layout.tsx +29 -2
  64. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +131 -42
  65. package/corpus/templates/brain/app/i18n/zh-TW.ts +2 -0
  66. package/corpus/templates/brain/app/i18n-data.ts +20 -0
  67. package/corpus/templates/brain/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  68. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +29 -1
  69. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +417 -299
  70. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  71. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  72. package/corpus/templates/calendar/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  73. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +6 -19
  74. package/corpus/templates/chat/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  75. package/corpus/templates/clips/AGENTS.md +4 -0
  76. package/corpus/templates/clips/actions/cleanup-transcript.ts +39 -9
  77. package/corpus/templates/clips/actions/finalize-meeting.ts +81 -12
  78. package/corpus/templates/clips/actions/lib/agents-md-context.ts +4 -1
  79. package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -3
  80. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +1 -1
  81. package/corpus/templates/clips/app/components/player/share-dialog.tsx +2 -2
  82. package/corpus/templates/clips/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
  83. package/corpus/templates/clips/desktop/src/lib/personal-vocabulary.ts +13 -8
  84. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +60 -1
  85. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +1 -1
  86. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -1
  87. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -5
  88. package/corpus/templates/content/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  89. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +137 -0
  90. package/corpus/templates/design/AGENTS.md +95 -5
  91. package/corpus/templates/design/DESIGN.md +82 -0
  92. package/corpus/templates/design/DEVELOPING.md +43 -0
  93. package/corpus/templates/design/actions/add-localhost-screens.ts +426 -0
  94. package/corpus/templates/design/actions/apply-shader.ts +277 -0
  95. package/corpus/templates/design/actions/apply-visual-edit.ts +303 -0
  96. package/corpus/templates/design/actions/connect-localhost.ts +183 -0
  97. package/corpus/templates/design/actions/edit-design.ts +195 -42
  98. package/corpus/templates/design/actions/generate-design.ts +71 -1
  99. package/corpus/templates/design/actions/generate-screens.ts +193 -0
  100. package/corpus/templates/design/actions/get-code-layer-projection.ts +191 -0
  101. package/corpus/templates/design/actions/get-shader.ts +119 -0
  102. package/corpus/templates/design/actions/list-localhost-connections.ts +95 -0
  103. package/corpus/templates/design/actions/navigate.ts +55 -2
  104. package/corpus/templates/design/actions/view-screen.ts +68 -1
  105. package/corpus/templates/design/agent-native.app-skill.json +10 -0
  106. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +761 -0
  107. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +2057 -103
  108. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +74 -38
  109. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +399 -0
  110. package/corpus/templates/design/app/components/design/EditPanel.tsx +3185 -439
  111. package/corpus/templates/design/app/components/design/LayersPanel.tsx +1124 -0
  112. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +3116 -147
  113. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +700 -13
  114. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +107 -63
  115. package/corpus/templates/design/app/components/design/index.ts +0 -5
  116. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +286 -0
  117. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +699 -0
  118. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +461 -0
  119. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +166 -0
  120. package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +1420 -0
  121. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +420 -0
  122. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +215 -0
  123. package/corpus/templates/design/app/components/design/inspector/ShaderControls.stories.tsx +71 -0
  124. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +518 -0
  125. package/corpus/templates/design/app/components/design/inspector/ShaderEffectRow.tsx +106 -0
  126. package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +361 -0
  127. package/corpus/templates/design/app/components/design/inspector/index.ts +60 -0
  128. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +201 -0
  129. package/corpus/templates/design/app/components/design/types.ts +23 -0
  130. package/corpus/templates/design/app/components/layout/Layout.tsx +24 -13
  131. package/corpus/templates/design/app/components/layout/Sidebar.tsx +4 -4
  132. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +474 -36
  133. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +22 -5
  134. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +24 -11
  135. package/corpus/templates/design/app/global.css +121 -3
  136. package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -0
  137. package/corpus/templates/design/app/hooks/use-navigation-state.ts +130 -8
  138. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +329 -0
  139. package/corpus/templates/design/app/i18n/zh-TW.ts +264 -0
  140. package/corpus/templates/design/app/i18n-data.ts +2790 -1
  141. package/corpus/templates/design/app/pages/DesignEditor.tsx +5108 -851
  142. package/corpus/templates/design/changelog/2026-06-28-annotate-mode-now-combines-drawing-and-comment-pins-in-one-c.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-28-design-editor-panel-borders-are-cleaner-so-the-canvas-edge-s.md +6 -0
  144. package/corpus/templates/design/changelog/2026-06-28-drawing-prompts-now-reliably-open-and-submit-to-the-design-a.md +6 -0
  145. package/corpus/templates/design/changelog/2026-06-28-editor-app-menus-now-use-an-outline-agent-native-mark-with-b.md +6 -0
  146. package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +6 -0
  147. package/corpus/templates/design/changelog/2026-06-28-layer-selection-now-updates-chat-context-without-opening-the.md +6 -0
  148. package/corpus/templates/design/changelog/2026-06-28-layer-selections-now-preserve-hidden-and-locked-canvas-state.md +6 -0
  149. package/corpus/templates/design/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  150. package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +6 -0
  151. package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-handles-zooming-drawing-opening-and-dele.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-selects-screens-on-click-and-opens-them-.md +6 -0
  154. package/corpus/templates/design/changelog/2026-06-28-screen-overview-zooms-into-the-active-screen-with-smoother-e.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-28-selected-element-chips-in-chat-are-shorter-and-easier-to-sca.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-28-the-design-editor-toolbar-is-more-compact-and-the-app-menu-m.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-at-the-top-of-the-design-sidebar-instead-of-.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-in-the-right-inspector-beside-the-design-tab.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-29-active-sidebar-rows-use-soft-highlights.md +6 -0
  160. package/corpus/templates/design/changelog/2026-06-29-agents-can-now-open-screen-overview-focus-screens-and-add.md +6 -0
  161. package/corpus/templates/design/changelog/2026-06-29-design-agents-now-edit-broad-copy-changes-more-reliably.md +6 -0
  162. package/corpus/templates/design/changelog/2026-06-29-design-editor-extensions-now-run-inline-beside-the-artboard.md +6 -0
  163. package/corpus/templates/design/changelog/2026-06-29-sidebars-use-darker-chrome-and-chat-warns-immediately.md +6 -0
  164. package/corpus/templates/design/changelog/2026-06-29-visual-edit-can-open-localhost-routes-as-url-backed-screens.md +6 -0
  165. package/corpus/templates/design/package.json +1 -0
  166. package/corpus/templates/design/server/db/schema.ts +26 -0
  167. package/corpus/templates/design/server/lib/design-snapshot.ts +1 -1
  168. package/corpus/templates/design/server/plugins/agent-chat.ts +3 -0
  169. package/corpus/templates/design/shared/api.ts +27 -0
  170. package/corpus/templates/design/shared/canvas-frames.ts +103 -0
  171. package/corpus/templates/design/shared/canvas-math.ts +1058 -0
  172. package/corpus/templates/design/shared/code-layer.ts +2212 -0
  173. package/corpus/templates/design/shared/color-utils.ts +180 -0
  174. package/corpus/templates/design/shared/generation-session.ts +61 -0
  175. package/corpus/templates/design/shared/shader-presets.ts +782 -0
  176. package/corpus/templates/design/shared/shader-safety.ts +137 -0
  177. package/corpus/templates/design/shared/source-mode.ts +237 -0
  178. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +149 -6
  179. package/corpus/templates/forms/app/i18n/ar-SA.ts +2 -0
  180. package/corpus/templates/forms/app/i18n/de-DE.ts +2 -0
  181. package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
  182. package/corpus/templates/forms/app/i18n/es-ES.ts +2 -0
  183. package/corpus/templates/forms/app/i18n/fr-FR.ts +2 -0
  184. package/corpus/templates/forms/app/i18n/hi-IN.ts +2 -0
  185. package/corpus/templates/forms/app/i18n/ja-JP.ts +2 -0
  186. package/corpus/templates/forms/app/i18n/ko-KR.ts +2 -0
  187. package/corpus/templates/forms/app/i18n/pt-BR.ts +2 -0
  188. package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
  189. package/corpus/templates/forms/app/i18n/zh-TW.ts +2 -0
  190. package/corpus/templates/forms/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  191. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +2 -2
  192. package/corpus/templates/macros/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
  193. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +408 -247
  194. package/corpus/templates/mail/app/i18n/ar-SA.ts +4 -0
  195. package/corpus/templates/mail/app/i18n/de-DE.ts +4 -0
  196. package/corpus/templates/mail/app/i18n/en-US.ts +4 -0
  197. package/corpus/templates/mail/app/i18n/es-ES.ts +4 -0
  198. package/corpus/templates/mail/app/i18n/fr-FR.ts +4 -0
  199. package/corpus/templates/mail/app/i18n/hi-IN.ts +4 -0
  200. package/corpus/templates/mail/app/i18n/ja-JP.ts +4 -0
  201. package/corpus/templates/mail/app/i18n/ko-KR.ts +4 -0
  202. package/corpus/templates/mail/app/i18n/pt-BR.ts +4 -0
  203. package/corpus/templates/mail/app/i18n/zh-CN.ts +4 -0
  204. package/corpus/templates/mail/app/i18n/zh-TW.ts +4 -0
  205. package/corpus/templates/mail/changelog/2026-06-28-pinned-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  206. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +6 -8
  207. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +1 -1
  208. package/corpus/templates/plan/app/global.css +61 -78
  209. package/corpus/templates/plan/app/pages/PlansPage.tsx +8 -0
  210. package/corpus/templates/plan/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  211. package/corpus/templates/plan/changelog/2026-06-28-plans-use-the-shared-neutral-light-and-dark-theme.md +6 -0
  212. package/corpus/templates/plan/changelog/2026-06-28-sending-open-feedback-from-a-plan-now-opens-the-inline-plan-.md +6 -0
  213. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +5 -5
  214. package/corpus/templates/slides/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  215. package/corpus/templates/videos/app/components/Sidebar.tsx +2 -2
  216. package/corpus/templates/videos/app/components/layout/Layout.tsx +29 -2
  217. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +113 -30
  218. package/corpus/templates/videos/app/i18n/ar-SA.ts +2 -0
  219. package/corpus/templates/videos/app/i18n/de-DE.ts +2 -0
  220. package/corpus/templates/videos/app/i18n/en-US.ts +2 -0
  221. package/corpus/templates/videos/app/i18n/es-ES.ts +2 -0
  222. package/corpus/templates/videos/app/i18n/fr-FR.ts +2 -0
  223. package/corpus/templates/videos/app/i18n/hi-IN.ts +2 -0
  224. package/corpus/templates/videos/app/i18n/ja-JP.ts +2 -0
  225. package/corpus/templates/videos/app/i18n/ko-KR.ts +2 -0
  226. package/corpus/templates/videos/app/i18n/pt-BR.ts +2 -0
  227. package/corpus/templates/videos/app/i18n/zh-CN.ts +2 -0
  228. package/corpus/templates/videos/app/i18n/zh-TW.ts +2 -0
  229. package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  230. package/dist/agent/engine/index.d.ts +1 -1
  231. package/dist/agent/engine/index.d.ts.map +1 -1
  232. package/dist/agent/engine/index.js +1 -1
  233. package/dist/agent/engine/index.js.map +1 -1
  234. package/dist/agent/engine/registry.d.ts +9 -0
  235. package/dist/agent/engine/registry.d.ts.map +1 -1
  236. package/dist/agent/engine/registry.js +37 -0
  237. package/dist/agent/engine/registry.js.map +1 -1
  238. package/dist/agent/production-agent.d.ts.map +1 -1
  239. package/dist/agent/production-agent.js +4 -3
  240. package/dist/agent/production-agent.js.map +1 -1
  241. package/dist/cli/design-connect.d.ts +52 -0
  242. package/dist/cli/design-connect.d.ts.map +1 -0
  243. package/dist/cli/design-connect.js +409 -0
  244. package/dist/cli/design-connect.js.map +1 -0
  245. package/dist/cli/index.js +15 -0
  246. package/dist/cli/index.js.map +1 -1
  247. package/dist/cli/skills.d.ts +3 -0
  248. package/dist/cli/skills.d.ts.map +1 -1
  249. package/dist/cli/skills.js +121 -3
  250. package/dist/cli/skills.js.map +1 -1
  251. package/dist/client/AssistantChat.d.ts.map +1 -1
  252. package/dist/client/AssistantChat.js +38 -8
  253. package/dist/client/AssistantChat.js.map +1 -1
  254. package/dist/client/agent-chat.d.ts +6 -0
  255. package/dist/client/agent-chat.d.ts.map +1 -1
  256. package/dist/client/agent-chat.js +17 -3
  257. package/dist/client/agent-chat.js.map +1 -1
  258. package/dist/client/blocks/library/MermaidBlock.js +1 -1
  259. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  260. package/dist/client/blocks/library/diagram.js +1 -1
  261. package/dist/client/blocks/library/diagram.js.map +1 -1
  262. package/dist/client/blocks/library/wireframe.js +1 -1
  263. package/dist/client/blocks/library/wireframe.js.map +1 -1
  264. package/dist/client/chat/message-components.js +1 -1
  265. package/dist/client/chat/message-components.js.map +1 -1
  266. package/dist/client/chat-model-groups.d.ts.map +1 -1
  267. package/dist/client/chat-model-groups.js +1 -2
  268. package/dist/client/chat-model-groups.js.map +1 -1
  269. package/dist/client/components/LiveCursorOverlay.d.ts +1 -2
  270. package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
  271. package/dist/client/components/LiveCursorOverlay.js +19 -40
  272. package/dist/client/components/LiveCursorOverlay.js.map +1 -1
  273. package/dist/client/composer/TiptapComposer.d.ts +3 -1
  274. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  275. package/dist/client/composer/TiptapComposer.js +86 -24
  276. package/dist/client/composer/TiptapComposer.js.map +1 -1
  277. package/dist/client/composer/useVoiceDictation.d.ts +9 -0
  278. package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
  279. package/dist/client/composer/useVoiceDictation.js +154 -32
  280. package/dist/client/composer/useVoiceDictation.js.map +1 -1
  281. package/dist/client/frame-protocol.d.ts +17 -2
  282. package/dist/client/frame-protocol.d.ts.map +1 -1
  283. package/dist/client/frame-protocol.js.map +1 -1
  284. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +2 -2
  285. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  286. package/dist/client/sharing/ShareButton.d.ts +2 -0
  287. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  288. package/dist/client/sharing/ShareButton.js +7 -6
  289. package/dist/client/sharing/ShareButton.js.map +1 -1
  290. package/dist/client/use-agent-engine-configured.d.ts +5 -0
  291. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  292. package/dist/client/use-agent-engine-configured.js +51 -25
  293. package/dist/client/use-agent-engine-configured.js.map +1 -1
  294. package/dist/collab/awareness.d.ts +2 -2
  295. package/dist/collab/awareness.d.ts.map +1 -1
  296. package/dist/collab/routes.d.ts +1 -1
  297. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  298. package/dist/notifications/routes.d.ts +3 -3
  299. package/dist/observability/routes.d.ts +8 -8
  300. package/dist/progress/routes.d.ts +1 -1
  301. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  302. package/dist/server/transcribe-voice.d.ts.map +1 -1
  303. package/dist/server/transcribe-voice.js +40 -24
  304. package/dist/server/transcribe-voice.js.map +1 -1
  305. package/dist/shared/mcp-embed-headers.d.ts +1 -0
  306. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  307. package/dist/shared/mcp-embed-headers.js +20 -1
  308. package/dist/shared/mcp-embed-headers.js.map +1 -1
  309. package/dist/styles/agent-native.css +56 -7
  310. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  311. package/dist/vite/client.d.ts.map +1 -1
  312. package/dist/vite/client.js +2 -0
  313. package/dist/vite/client.js.map +1 -1
  314. package/dist/voice/index.d.ts +5 -0
  315. package/dist/voice/index.d.ts.map +1 -0
  316. package/dist/voice/index.js +4 -0
  317. package/dist/voice/index.js.map +1 -0
  318. package/dist/voice/voice-cleanup-prompt.d.ts +6 -0
  319. package/dist/voice/voice-cleanup-prompt.d.ts.map +1 -0
  320. package/dist/voice/voice-cleanup-prompt.js +26 -0
  321. package/dist/voice/voice-cleanup-prompt.js.map +1 -0
  322. package/dist/voice/voice-context.d.ts +25 -0
  323. package/dist/voice/voice-context.d.ts.map +1 -0
  324. package/dist/voice/voice-context.js +156 -0
  325. package/dist/voice/voice-context.js.map +1 -0
  326. package/dist/voice/voice-replacements.d.ts +4 -0
  327. package/dist/voice/voice-replacements.d.ts.map +1 -0
  328. package/dist/voice/voice-replacements.js +52 -0
  329. package/dist/voice/voice-replacements.js.map +1 -0
  330. package/docs/content/locales/ar-SA/template-design.mdx +41 -0
  331. package/docs/content/locales/de-DE/template-design.mdx +51 -0
  332. package/docs/content/locales/es-ES/template-design.mdx +50 -0
  333. package/docs/content/locales/fr-FR/template-design.mdx +49 -0
  334. package/docs/content/locales/hi-IN/template-design.mdx +44 -0
  335. package/docs/content/locales/ja-JP/template-design.mdx +46 -0
  336. package/docs/content/locales/ko-KR/template-design.mdx +44 -0
  337. package/docs/content/locales/pt-BR/template-design.mdx +48 -0
  338. package/docs/content/locales/zh-CN/template-design.mdx +36 -0
  339. package/docs/content/locales/zh-TW/template-design.mdx +38 -0
  340. package/docs/content/template-design.mdx +45 -0
  341. package/package.json +2 -1
  342. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  343. package/corpus/templates/design/app/components/design/DesignToolbar.tsx +0 -339
  344. package/corpus/templates/design/app/components/design/PresentMode.tsx +0 -68
  345. package/corpus/templates/design/app/components/design/ViewportTabs.tsx +0 -149
  346. package/corpus/templates/design/app/components/design/ZoomControls.tsx +0 -157
@@ -1,6 +1,5 @@
1
- import { agentChat } from "@agent-native/core";
2
- import { usePinchZoom, useT } from "@agent-native/core/client";
3
- import { useRef, useEffect, useCallback, useMemo } from "react";
1
+ import { sendToAgentChat, usePinchZoom, useT } from "@agent-native/core/client";
2
+ import { useRef, useEffect, useCallback, useMemo, useState } from "react";
4
3
 
5
4
  // NOTE: This wires up the NEW shared visual-editor DrawOverlay + comment-pin
6
5
  // components from `@/components/visual-editor`. The legacy iframe-only
@@ -9,6 +8,7 @@ import { useRef, useEffect, useCallback, useMemo } from "react";
9
8
  import {
10
9
  DrawOverlay as SharedDrawOverlay,
11
10
  CanvasCommentPins,
11
+ type CanvasPin,
12
12
  } from "@/components/visual-editor";
13
13
 
14
14
  import { isTrustedCanvasBridgeMessage } from "./bridge-security";
@@ -67,6 +67,49 @@ const ZOOM_BRIDGE_SCRIPT = `
67
67
  </script>
68
68
  `;
69
69
 
70
+ /**
71
+ * Embedded overview bridge. A screen preview is a real iframe, so normal wheel
72
+ * events never bubble to the overview canvas underneath. In embedded mode we
73
+ * forward a bounded wheel payload to the parent so the existing canvas wheel
74
+ * handler can pan/zoom exactly as if the pointer were over empty canvas.
75
+ */
76
+ const EMBEDDED_WHEEL_BRIDGE_SCRIPT = `
77
+ <script data-agent-native-embedded-wheel-bridge>
78
+ (function() {
79
+ var enabled = __EMBEDDED_WHEEL_FORWARDING_ENABLED__;
80
+ if (!enabled) return;
81
+ function clamp(value, limit) {
82
+ var number = Number(value) || 0;
83
+ if (number > limit) return limit;
84
+ if (number < -limit) return -limit;
85
+ return number;
86
+ }
87
+ function onWheel(e) {
88
+ e.preventDefault();
89
+ e.stopPropagation();
90
+ if (e.stopImmediatePropagation) e.stopImmediatePropagation();
91
+ try {
92
+ window.parent.postMessage({
93
+ type: 'embedded-canvas-wheel',
94
+ deltaX: clamp(e.deltaX, 240),
95
+ deltaY: clamp(e.deltaY, 240),
96
+ deltaZ: clamp(e.deltaZ, 240),
97
+ deltaMode: e.deltaMode,
98
+ clientX: e.clientX,
99
+ clientY: e.clientY,
100
+ ctrlKey: !!e.ctrlKey,
101
+ metaKey: !!e.metaKey,
102
+ shiftKey: !!e.shiftKey,
103
+ altKey: !!e.altKey,
104
+ }, '*');
105
+ } catch (err) {}
106
+ }
107
+ var target = document.documentElement || document.body || document;
108
+ target.addEventListener('wheel', onWheel, { passive: false, capture: true });
109
+ })();
110
+ </script>
111
+ `;
112
+
70
113
  /**
71
114
  * Navigation bridge. ALWAYS injected. A prototype lives in a `srcdoc` iframe,
72
115
  * so a plain `<a href="/pricing">` resolves the relative URL against the PARENT
@@ -158,13 +201,55 @@ const NAV_BRIDGE_SCRIPT = `
158
201
  </script>
159
202
  `;
160
203
 
204
+ const EDITOR_BRIDGE_VAR_NAMES = [
205
+ "--design-editor-accent-color",
206
+ "--design-editor-accent-hover-color",
207
+ "--design-editor-selection-color",
208
+ "--design-editor-accent-strong-color",
209
+ "--design-editor-accent-contrast-color",
210
+ "--design-editor-measure-color",
211
+ "--background",
212
+ "--foreground",
213
+ "--border",
214
+ ];
215
+
216
+ function readEditorBridgeThemeVars(): Record<string, string> {
217
+ if (typeof window === "undefined") return {};
218
+ const styles = window.getComputedStyle(document.documentElement);
219
+ return Object.fromEntries(
220
+ EDITOR_BRIDGE_VAR_NAMES.map((name) => [
221
+ name,
222
+ styles.getPropertyValue(name).trim(),
223
+ ]).filter(([, value]) => value.length > 0),
224
+ );
225
+ }
226
+
227
+ function createEditorBridgeThemeScript(vars: Record<string, string>) {
228
+ const serializedVars = JSON.stringify(vars).replace(/</g, "\\u003c");
229
+ return `
230
+ <script data-agent-native-editor-theme>
231
+ (function() {
232
+ var vars = ${serializedVars};
233
+ var root = document.documentElement;
234
+ Object.keys(vars).forEach(function(name) {
235
+ root.style.setProperty(name, vars[name]);
236
+ });
237
+ })();
238
+ </script>
239
+ `;
240
+ }
241
+
161
242
  /**
162
- * Edit-mode bridge: element click/hover overlays + selector-targeted
163
- * style-change messages. Only injected when the user is in Edit mode.
243
+ * Editor chrome bridge: blocks native iframe app interaction outside Interact
244
+ * mode and replaces it with element hover/selection overlays. Double-click text
245
+ * editing is enabled only while the editor is specifically in Edit mode.
164
246
  */
165
- const EDIT_BRIDGE_SCRIPT = `
166
- <script data-agent-native-edit-bridge>
247
+ const EDITOR_CHROME_BRIDGE_SCRIPT = `
248
+ <script data-agent-native-editor-chrome-bridge>
167
249
  (function() {
250
+ var textEditingEnabled = __TEXT_EDITING_ENABLED__;
251
+ var scaleToolEnabled = false;
252
+
168
253
  function escapeIdent(value) {
169
254
  if (window.CSS && typeof window.CSS.escape === 'function') {
170
255
  return window.CSS.escape(value);
@@ -172,65 +257,199 @@ const EDIT_BRIDGE_SCRIPT = `
172
257
  return String(value).replace(/[^a-zA-Z0-9_-]/g, '\\\\$&');
173
258
  }
174
259
 
175
- function getSelector(el) {
260
+ function escapeAttribute(value) {
261
+ return String(value).replace(/\\\\/g, '\\\\\\\\').replace(/"/g, '\\\\"');
262
+ }
263
+
264
+ function attributeSelector(el, name) {
265
+ var value = el && el.getAttribute && el.getAttribute(name);
266
+ return value ? '[' + name + '="' + escapeAttribute(value) + '"]' : '';
267
+ }
268
+
269
+ function classSelectorSuffix(el, maxCount) {
270
+ if (!el || !el.classList) return '';
271
+ return Array.prototype.slice.call(el.classList, 0, maxCount)
272
+ .map(function(token) { return '.' + escapeIdent(token); })
273
+ .join('');
274
+ }
275
+
276
+ function selectorPart(el) {
277
+ if (!el || !el.tagName) return '';
278
+ var stableSelector =
279
+ attributeSelector(el, 'data-agent-native-node-id') ||
280
+ attributeSelector(el, 'data-code-layer-id') ||
281
+ attributeSelector(el, 'data-layer-id') ||
282
+ attributeSelector(el, 'data-builder-id') ||
283
+ attributeSelector(el, 'data-loc');
284
+ if (stableSelector) return el.tagName.toLowerCase() + stableSelector;
176
285
  if (el.id) return '#' + escapeIdent(el.id);
177
- var builderId = el.closest('[data-builder-id]') &&
178
- el.closest('[data-builder-id]').getAttribute('data-builder-id');
179
- if (builderId) return '[data-builder-id="' + builderId.replace(/"/g, '\\\\"') + '"]';
286
+ var part = el.tagName.toLowerCase() + (stableSelector || classSelectorSuffix(el, 2));
287
+ var parent = el.parentElement;
288
+ if (parent) {
289
+ var sameTag = Array.prototype.filter.call(
290
+ parent.children,
291
+ function(child) { return child.tagName === el.tagName; }
292
+ );
293
+ if (sameTag.length > 1) {
294
+ part += ':nth-of-type(' + (sameTag.indexOf(el) + 1) + ')';
295
+ }
296
+ }
297
+ return part;
298
+ }
180
299
 
300
+ function selectorPath(el, stopEl) {
181
301
  var parts = [];
182
302
  var node = el;
183
- while (node && node.nodeType === 1 && node !== document.documentElement) {
184
- var part = node.tagName.toLowerCase();
185
- if (node.id) {
186
- part += '#' + escapeIdent(node.id);
187
- parts.unshift(part);
188
- break;
303
+ while (node && node.nodeType === 1) {
304
+ if (node !== stopEl) parts.unshift(selectorPart(node));
305
+ if (node === stopEl) break;
306
+ node = node.parentElement;
307
+ }
308
+ return parts.slice(-5).join(' > ');
309
+ }
310
+
311
+ function getSourceId(el) {
312
+ if (!el || !el.getAttribute) return '';
313
+ return (
314
+ el.getAttribute('data-agent-native-node-id') ||
315
+ el.getAttribute('data-code-layer-id') ||
316
+ el.getAttribute('data-layer-id') ||
317
+ el.getAttribute('data-builder-id') ||
318
+ el.getAttribute('data-loc') ||
319
+ el.id ||
320
+ ''
321
+ );
322
+ }
323
+
324
+ function freshRuntimeNodeId(prefix) {
325
+ var random = '';
326
+ try {
327
+ if (window.crypto && window.crypto.getRandomValues) {
328
+ var bytes = new Uint32Array(2);
329
+ window.crypto.getRandomValues(bytes);
330
+ random = Array.prototype.map.call(bytes, function(part) {
331
+ return part.toString(36);
332
+ }).join('');
189
333
  }
190
- var parent = node.parentElement;
191
- if (parent) {
192
- var sameTag = Array.prototype.filter.call(
193
- parent.children,
194
- function(child) { return child.tagName === node.tagName; }
195
- );
196
- if (sameTag.length > 1) {
197
- part += ':nth-of-type(' + (sameTag.indexOf(node) + 1) + ')';
198
- }
334
+ } catch (_err) {}
335
+ if (!random) random = Date.now().toString(36) + Math.random().toString(36).slice(2, 8);
336
+ return 'an-' + String(prefix || 'copy') + '-' + random;
337
+ }
338
+
339
+ function resetRuntimeStableIds(root) {
340
+ if (!root || !root.querySelectorAll) return;
341
+ var nodes = [root].concat(Array.prototype.slice.call(root.querySelectorAll('[data-agent-native-node-id]')));
342
+ nodes.forEach(function(node, index) {
343
+ if (node && node.setAttribute) {
344
+ node.setAttribute('data-agent-native-node-id', freshRuntimeNodeId(index === 0 ? 'copy' : 'copy-child'));
199
345
  }
200
- parts.unshift(part);
201
- node = parent;
202
- if (node === document.body) {
203
- parts.unshift('body');
204
- break;
346
+ });
347
+ }
348
+
349
+ function getSelector(el) {
350
+ var stableOwnSelector =
351
+ attributeSelector(el, 'data-agent-native-node-id') ||
352
+ attributeSelector(el, 'data-code-layer-id') ||
353
+ attributeSelector(el, 'data-layer-id') ||
354
+ attributeSelector(el, 'data-builder-id') ||
355
+ attributeSelector(el, 'data-loc');
356
+ if (stableOwnSelector) return stableOwnSelector;
357
+
358
+ if (el.id) return '#' + escapeIdent(el.id);
359
+ var stableAncestor = el.closest('[data-agent-native-node-id],[data-code-layer-id],[data-layer-id],[data-builder-id],[data-loc]');
360
+ if (stableAncestor && stableAncestor !== el) {
361
+ var stableAncestorSelector = selectorPart(stableAncestor);
362
+ if (stableAncestorSelector) {
363
+ var descendantPath = selectorPath(el, stableAncestor);
364
+ var descendantParts = descendantPath ? descendantPath.split(' > ') : [];
365
+ if (descendantParts.length) {
366
+ return stableAncestorSelector + ' > ' + descendantParts.join(' > ');
367
+ }
368
+ return stableAncestorSelector;
205
369
  }
206
370
  }
207
- return parts.join(' > ');
371
+
372
+ return selectorPath(el);
208
373
  }
209
374
 
210
375
  function getElementInfo(el) {
211
376
  var cs = window.getComputedStyle(el);
212
377
  var rect = el.getBoundingClientRect();
213
- var parentDisplay = el.parentElement
214
- ? window.getComputedStyle(el.parentElement).display
378
+ var parentStyles = el.parentElement
379
+ ? window.getComputedStyle(el.parentElement)
380
+ : null;
381
+ var parentDisplay = parentStyles ? parentStyles.display : undefined;
382
+ var sourceId = getSourceId(el) || getSelector(el);
383
+ var parentLayout = parentStyles
384
+ ? {
385
+ display: parentStyles.display,
386
+ flexDirection: parentStyles.flexDirection,
387
+ alignItems: parentStyles.alignItems,
388
+ justifyContent: parentStyles.justifyContent,
389
+ gap: parentStyles.gap,
390
+ gridTemplateColumns: parentStyles.gridTemplateColumns,
391
+ gridTemplateRows: parentStyles.gridTemplateRows,
392
+ position: parentStyles.position,
393
+ }
215
394
  : undefined;
395
+ var capabilities = [
396
+ {
397
+ kind: 'deterministic-style-edit',
398
+ label: 'deterministic-style-edit',
399
+ confidence: 0.92,
400
+ reason: 'Inline style can be patched and replayed through HMR/collab.',
401
+ },
402
+ ];
403
+ if (el.classList && el.classList.length > 0) {
404
+ capabilities.push({
405
+ kind: 'deterministic-class-edit',
406
+ label: 'deterministic-class-edit',
407
+ confidence: 0.78,
408
+ reason: 'Class tokens are visible on the selected element.',
409
+ });
410
+ }
411
+ if (parentDisplay === 'flex' || parentDisplay === 'inline-flex' || parentDisplay === 'grid' || parentDisplay === 'inline-grid') {
412
+ capabilities.push({
413
+ kind: 'agent-structural-edit',
414
+ label: 'agent-structural-edit',
415
+ confidence: 0.54,
416
+ reason: 'Parent layout context decides whether movement means gap, order, alignment, or wrapper structure.',
417
+ });
418
+ }
216
419
  return {
217
420
  tagName: el.tagName.toLowerCase(),
218
421
  id: el.id || undefined,
422
+ sourceId: sourceId,
219
423
  selector: getSelector(el),
220
424
  classes: Array.from(el.classList),
221
425
  computedStyles: {
222
426
  color: cs.color,
223
427
  backgroundColor: cs.backgroundColor,
428
+ backgroundImage: cs.backgroundImage,
429
+ backgroundBlendMode: cs.backgroundBlendMode,
224
430
  fontSize: cs.fontSize,
225
431
  fontFamily: cs.fontFamily,
226
432
  fontWeight: cs.fontWeight,
227
433
  lineHeight: cs.lineHeight,
228
434
  letterSpacing: cs.letterSpacing,
229
435
  textAlign: cs.textAlign,
230
- display: cs.display,
231
- flexDirection: cs.flexDirection,
436
+ display: cs.display,
437
+ overflow: cs.overflow,
438
+ flexDirection: cs.flexDirection,
232
439
  justifyContent: cs.justifyContent,
233
440
  alignItems: cs.alignItems,
441
+ alignSelf: cs.alignSelf,
442
+ flexGrow: cs.flexGrow,
443
+ flexShrink: cs.flexShrink,
444
+ flexBasis: cs.flexBasis,
445
+ order: cs.order,
446
+ gridColumn: cs.gridColumn,
447
+ gridRow: cs.gridRow,
448
+ position: cs.position,
449
+ top: cs.top,
450
+ right: cs.right,
451
+ bottom: cs.bottom,
452
+ left: cs.left,
234
453
  gap: cs.gap,
235
454
  width: cs.width,
236
455
  height: cs.height,
@@ -244,29 +463,302 @@ const EDIT_BRIDGE_SCRIPT = `
244
463
  marginBottom: cs.marginBottom,
245
464
  marginLeft: cs.marginLeft,
246
465
  borderWidth: cs.borderWidth,
466
+ borderStyle: cs.borderStyle,
247
467
  borderColor: cs.borderColor,
248
468
  borderRadius: cs.borderRadius,
469
+ borderTopLeftRadius: cs.borderTopLeftRadius,
470
+ borderTopRightRadius: cs.borderTopRightRadius,
471
+ borderBottomRightRadius: cs.borderBottomRightRadius,
472
+ borderBottomLeftRadius: cs.borderBottomLeftRadius,
473
+ outlineWidth: cs.outlineWidth,
474
+ outlineStyle: cs.outlineStyle,
475
+ outlineColor: cs.outlineColor,
476
+ outlineOffset: cs.outlineOffset,
477
+ boxShadow: cs.boxShadow,
478
+ textShadow: cs.textShadow,
479
+ filter: cs.filter,
480
+ mixBlendMode: cs.mixBlendMode,
481
+ zIndex: cs.zIndex,
249
482
  },
250
483
  boundingRect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
251
484
  textContent: el.textContent ? el.textContent.slice(0, 200) : undefined,
485
+ htmlContent: el.innerHTML && el.innerHTML !== el.textContent ? el.innerHTML.slice(0, 4000) : undefined,
252
486
  isFlexContainer: cs.display === 'flex' || cs.display === 'inline-flex',
253
487
  isFlexChild: parentDisplay === 'flex' || parentDisplay === 'inline-flex',
254
488
  parentDisplay: parentDisplay,
489
+ parentLayout: parentLayout,
490
+ editCapabilities: capabilities,
491
+ confidence: capabilities.reduce(function(best, item) {
492
+ return Math.max(best, item.confidence || 0);
493
+ }, 0),
255
494
  };
256
495
  }
257
496
 
497
+ var shieldOverlay = document.createElement('div');
498
+ shieldOverlay.setAttribute('data-agent-native-edit-overlay', 'shield');
499
+ shieldOverlay.style.cssText = 'position:fixed;inset:0;z-index:99990;background:transparent;pointer-events:auto;touch-action:none;cursor:default;';
500
+ document.body.appendChild(shieldOverlay);
501
+
258
502
  var highlightOverlay = document.createElement('div');
259
503
  highlightOverlay.setAttribute('data-agent-native-edit-overlay', 'highlight');
260
- highlightOverlay.style.cssText = 'position:fixed;pointer-events:none;z-index:99999;border:2px solid #609FF8;background:rgba(96,159,248,0.08);display:none;';
504
+ highlightOverlay.style.cssText = 'position:fixed;pointer-events:none;z-index:99999;border:1.5px solid var(--design-editor-accent-color);background:transparent;display:none;box-sizing:border-box;';
261
505
  document.body.appendChild(highlightOverlay);
262
506
 
263
507
  var selectionOverlay = document.createElement('div');
264
508
  selectionOverlay.setAttribute('data-agent-native-edit-overlay', 'selection');
265
- selectionOverlay.style.cssText = 'position:fixed;pointer-events:none;z-index:99998;border:2px solid #609FF8;background:rgba(96,159,248,0.12);display:none;';
509
+ selectionOverlay.style.cssText = 'position:fixed;pointer-events:auto;z-index:99998;border:1.5px solid var(--design-editor-accent-color);background:transparent;display:none;box-sizing:border-box;cursor:move;';
510
+ ['n','e','s','w'].forEach(function(pos) {
511
+ var edge = document.createElement('span');
512
+ edge.setAttribute('data-agent-native-edge-handle', pos);
513
+ var cursor = pos === 'n' || pos === 's' ? 'ns-resize' : 'ew-resize';
514
+ edge.style.cssText = 'position:absolute;pointer-events:auto;cursor:' + cursor + ';background:transparent;';
515
+ if (pos === 'n') {
516
+ edge.style.left = '0';
517
+ edge.style.right = '0';
518
+ edge.style.top = '-5px';
519
+ edge.style.height = '10px';
520
+ }
521
+ if (pos === 's') {
522
+ edge.style.left = '0';
523
+ edge.style.right = '0';
524
+ edge.style.bottom = '-5px';
525
+ edge.style.height = '10px';
526
+ }
527
+ if (pos === 'e') {
528
+ edge.style.top = '0';
529
+ edge.style.bottom = '0';
530
+ edge.style.right = '-5px';
531
+ edge.style.width = '10px';
532
+ }
533
+ if (pos === 'w') {
534
+ edge.style.top = '0';
535
+ edge.style.bottom = '0';
536
+ edge.style.left = '-5px';
537
+ edge.style.width = '10px';
538
+ }
539
+ selectionOverlay.appendChild(edge);
540
+ });
541
+ ['nw','ne','se','sw'].forEach(function(pos) {
542
+ var handle = document.createElement('span');
543
+ handle.setAttribute('data-agent-native-edit-handle', pos);
544
+ var cursor = pos === 'n' || pos === 's' ? 'ns-resize' : pos === 'e' || pos === 'w' ? 'ew-resize' : pos === 'nw' || pos === 'se' ? 'nwse-resize' : 'nesw-resize';
545
+ handle.style.cssText = 'position:absolute;width:7px;height:7px;border:1px solid var(--design-editor-accent-color);background:var(--design-editor-accent-contrast-color);box-sizing:border-box;border-radius:1px;pointer-events:auto;cursor:' + cursor + ';';
546
+ if (pos.indexOf('n') !== -1) handle.style.top = '-4px';
547
+ if (pos.indexOf('s') !== -1) handle.style.bottom = '-4px';
548
+ if (pos.indexOf('w') !== -1) handle.style.left = '-4px';
549
+ if (pos.indexOf('e') !== -1) handle.style.right = '-4px';
550
+ if (pos === 'n' || pos === 's') {
551
+ handle.style.left = '50%';
552
+ handle.style.transform = 'translateX(-50%)';
553
+ }
554
+ if (pos === 'e' || pos === 'w') {
555
+ handle.style.top = '50%';
556
+ handle.style.transform = 'translateY(-50%)';
557
+ }
558
+ selectionOverlay.appendChild(handle);
559
+ });
560
+ ['nw','ne','se','sw'].forEach(function(pos) {
561
+ var rotate = document.createElement('span');
562
+ rotate.setAttribute('data-agent-native-rotate-handle', pos);
563
+ rotate.style.cssText = 'position:absolute;width:18px;height:18px;border-radius:999px;pointer-events:auto;cursor:grab;';
564
+ if (pos.indexOf('n') !== -1) rotate.style.top = '-26px';
565
+ if (pos.indexOf('s') !== -1) rotate.style.bottom = '-26px';
566
+ if (pos.indexOf('w') !== -1) rotate.style.left = '-26px';
567
+ if (pos.indexOf('e') !== -1) rotate.style.right = '-26px';
568
+ selectionOverlay.appendChild(rotate);
569
+ });
570
+ var paddingOverlay = document.createElement('div');
571
+ paddingOverlay.setAttribute('data-agent-native-padding-overlay', '');
572
+ paddingOverlay.style.cssText = 'position:absolute;border:1px dashed var(--design-editor-accent-strong-color);border-radius:2px;pointer-events:none;';
573
+ selectionOverlay.appendChild(paddingOverlay);
266
574
  document.body.appendChild(selectionOverlay);
267
575
 
268
- var selectedEl = null;
269
- var hoveredEl = null;
576
+ var transformBadge = document.createElement('div');
577
+ transformBadge.setAttribute('data-agent-native-transform-badge', '');
578
+ transformBadge.style.cssText = 'position:fixed;z-index:100000;display:none;pointer-events:none;border:1px solid hsl(var(--border));border-radius:4px;background:hsl(var(--background) / 0.96);color:hsl(var(--foreground));font:11px/1.4 ui-monospace,SFMono-Regular,Menlo,monospace;padding:3px 5px;box-shadow:0 8px 20px color-mix(in srgb, hsl(var(--foreground)) 16%, transparent);';
579
+ document.body.appendChild(transformBadge);
580
+
581
+ var insertionGuide = document.createElement('div');
582
+ insertionGuide.setAttribute('data-agent-native-insertion-guide', '');
583
+ insertionGuide.style.cssText = 'position:fixed;z-index:100000;display:none;pointer-events:none;background:var(--design-editor-accent-color);border-radius:999px;box-shadow:0 0 0 1px var(--design-editor-accent-color);';
584
+ document.body.appendChild(insertionGuide);
585
+
586
+ var measurementOverlay = document.createElement('div');
587
+ measurementOverlay.setAttribute('data-agent-native-measurement-overlay', '');
588
+ measurementOverlay.style.cssText = 'position:fixed;inset:0;z-index:100001;display:none;pointer-events:none;color:var(--design-editor-measure-color);font:11px/1.2 ui-monospace,SFMono-Regular,Menlo,monospace;';
589
+ document.body.appendChild(measurementOverlay);
590
+
591
+ var selectedEl = null;
592
+ var hoveredEl = null;
593
+ var activeTextEditEl = null;
594
+ var textEditPointerState = null;
595
+ var pendingStructureMove = null;
596
+ var lockedSelectors = [];
597
+ var hiddenSelectors = [];
598
+
599
+ function matchesSelectorList(el, selectors) {
600
+ if (!el || !selectors || selectors.length === 0) return false;
601
+ for (var i = 0; i < selectors.length; i += 1) {
602
+ try {
603
+ if (el.matches(selectors[i]) || el.closest(selectors[i])) return true;
604
+ } catch (_err) {}
605
+ }
606
+ return false;
607
+ }
608
+
609
+ function isLayerInteractionBlocked(el) {
610
+ return matchesSelectorList(el, lockedSelectors) || matchesSelectorList(el, hiddenSelectors);
611
+ }
612
+
613
+ function applyHiddenSelectors() {
614
+ document.querySelectorAll('[data-agent-native-runtime-hidden]').forEach(function(el) {
615
+ var previous = el.getAttribute('data-agent-native-previous-display');
616
+ if (previous === null) {
617
+ el.style.removeProperty('display');
618
+ } else {
619
+ el.style.display = previous;
620
+ }
621
+ el.removeAttribute('data-agent-native-runtime-hidden');
622
+ el.removeAttribute('data-agent-native-previous-display');
623
+ });
624
+ hiddenSelectors.forEach(function(selector) {
625
+ try {
626
+ document.querySelectorAll(selector).forEach(function(el) {
627
+ if (!el.hasAttribute('data-agent-native-runtime-hidden')) {
628
+ el.setAttribute('data-agent-native-previous-display', el.style.display || '');
629
+ }
630
+ el.setAttribute('data-agent-native-runtime-hidden', 'true');
631
+ el.style.display = 'none';
632
+ });
633
+ } catch (_err) {}
634
+ });
635
+ }
636
+
637
+ function replaceRuntimeDocument(html, preferredSelector, selectorCandidates) {
638
+ if (typeof html !== 'string') return;
639
+ if (activeTextEditEl) {
640
+ applyHiddenSelectors();
641
+ refreshOverlays();
642
+ return;
643
+ }
644
+ var parser = new DOMParser();
645
+ var nextDoc = parser.parseFromString(html, 'text/html');
646
+ if (!nextDoc || !nextDoc.body) return;
647
+
648
+ var persistentNodes = Array.prototype.slice.call(
649
+ document.querySelectorAll('[data-agent-native-edit-overlay]'),
650
+ );
651
+ var activeSelector = preferredSelector || (selectedEl ? getSelector(selectedEl) : '');
652
+ var activeCandidates = [];
653
+ if (Array.isArray(selectorCandidates)) {
654
+ selectorCandidates.forEach(function(selector) {
655
+ if (typeof selector === 'string' && selector && activeCandidates.indexOf(selector) === -1) {
656
+ activeCandidates.push(selector);
657
+ }
658
+ });
659
+ }
660
+ if (activeSelector && activeCandidates.indexOf(activeSelector) === -1) {
661
+ activeCandidates.push(activeSelector);
662
+ }
663
+
664
+ var nextHeadHtml = nextDoc.head ? nextDoc.head.innerHTML : '';
665
+ if (nextHeadHtml === document.head.innerHTML && activeCandidates.length > 0) {
666
+ var currentMatch = null;
667
+ var nextMatch = null;
668
+ var matchedSelector = '';
669
+ for (var matchIndex = 0; matchIndex < activeCandidates.length; matchIndex += 1) {
670
+ try {
671
+ currentMatch = document.querySelector(activeCandidates[matchIndex]);
672
+ nextMatch = nextDoc.querySelector(activeCandidates[matchIndex]);
673
+ matchedSelector = activeCandidates[matchIndex];
674
+ } catch (_err) {
675
+ currentMatch = null;
676
+ nextMatch = null;
677
+ }
678
+ if (currentMatch) break;
679
+ }
680
+ if (
681
+ currentMatch &&
682
+ currentMatch !== document.body &&
683
+ currentMatch !== document.documentElement &&
684
+ !isOverlayElement(currentMatch)
685
+ ) {
686
+ if (nextMatch) {
687
+ currentMatch.replaceWith(document.importNode(nextMatch, true));
688
+ } else if (currentMatch !== document.body && currentMatch !== document.documentElement) {
689
+ currentMatch.parentElement && currentMatch.parentElement.removeChild(currentMatch);
690
+ }
691
+ applyHiddenSelectors();
692
+ selectedEl = null;
693
+ if (nextMatch) {
694
+ try {
695
+ selectedEl = document.querySelector(matchedSelector);
696
+ } catch (_err) {}
697
+ }
698
+ hoveredEl = null;
699
+ if (selectedEl && !isLayerInteractionBlocked(selectedEl)) {
700
+ positionOverlay(selectionOverlay, selectedEl);
701
+ window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
702
+ } else {
703
+ selectionOverlay.style.display = 'none';
704
+ }
705
+ highlightOverlay.style.display = 'none';
706
+ hideMeasurements();
707
+ refreshOverlays();
708
+ return;
709
+ }
710
+ }
711
+ if (document.head.innerHTML !== nextHeadHtml) {
712
+ document.head.innerHTML = nextHeadHtml;
713
+ }
714
+ Array.prototype.slice.call(document.body.attributes).forEach(function(attribute) {
715
+ document.body.removeAttribute(attribute.name);
716
+ });
717
+ Array.prototype.slice.call(nextDoc.body.attributes).forEach(function(attribute) {
718
+ document.body.setAttribute(attribute.name, attribute.value);
719
+ });
720
+ document.body.innerHTML = nextDoc.body.innerHTML;
721
+ persistentNodes.forEach(function(node) {
722
+ document.body.appendChild(node);
723
+ });
724
+ applyHiddenSelectors();
725
+
726
+ selectedEl = null;
727
+ hoveredEl = null;
728
+ for (var i = 0; i < activeCandidates.length && !selectedEl; i += 1) {
729
+ try {
730
+ var match = document.querySelector(activeCandidates[i]);
731
+ if (match && !isLayerInteractionBlocked(match)) selectedEl = match;
732
+ } catch (_err) {}
733
+ }
734
+ if (selectedEl) {
735
+ positionOverlay(selectionOverlay, selectedEl);
736
+ window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
737
+ } else {
738
+ selectionOverlay.style.display = 'none';
739
+ }
740
+ highlightOverlay.style.display = 'none';
741
+ hideMeasurements();
742
+ refreshOverlays();
743
+ }
744
+
745
+ function updatePaddingOverlay(el) {
746
+ if (!el) {
747
+ paddingOverlay.style.display = 'none';
748
+ return;
749
+ }
750
+ var cs = window.getComputedStyle(el);
751
+ var top = readPx(cs.paddingTop) + readPx(cs.borderTopWidth);
752
+ var right = readPx(cs.paddingRight) + readPx(cs.borderRightWidth);
753
+ var bottom = readPx(cs.paddingBottom) + readPx(cs.borderBottomWidth);
754
+ var left = readPx(cs.paddingLeft) + readPx(cs.borderLeftWidth);
755
+ var hasPadding = top > 0 || right > 0 || bottom > 0 || left > 0;
756
+ paddingOverlay.style.display = hasPadding ? 'block' : 'none';
757
+ paddingOverlay.style.top = top + 'px';
758
+ paddingOverlay.style.right = right + 'px';
759
+ paddingOverlay.style.bottom = bottom + 'px';
760
+ paddingOverlay.style.left = left + 'px';
761
+ }
270
762
 
271
763
  function positionOverlay(overlay, el) {
272
764
  if (!el || !document.documentElement.contains(el)) {
@@ -279,6 +771,7 @@ const EDIT_BRIDGE_SCRIPT = `
279
771
  overlay.style.left = rect.left + 'px';
280
772
  overlay.style.width = rect.width + 'px';
281
773
  overlay.style.height = rect.height + 'px';
774
+ if (overlay === selectionOverlay) updatePaddingOverlay(el);
282
775
  }
283
776
 
284
777
  function refreshOverlays() {
@@ -286,38 +779,1031 @@ const EDIT_BRIDGE_SCRIPT = `
286
779
  if (selectedEl) positionOverlay(selectionOverlay, selectedEl);
287
780
  }
288
781
 
289
- document.addEventListener('click', function(e) {
290
- if (e.target && e.target.closest('[data-agent-native-edit-overlay]')) return;
782
+ function hideMeasurements() {
783
+ measurementOverlay.style.display = 'none';
784
+ measurementOverlay.innerHTML = '';
785
+ }
786
+
787
+ function addMeasurementLine(x1, y1, x2, y2, label) {
788
+ var horizontal = Math.abs(x2 - x1) >= Math.abs(y2 - y1);
789
+ var line = document.createElement('div');
790
+ var labelEl = document.createElement('div');
791
+ if (horizontal) {
792
+ var left = Math.min(x1, x2);
793
+ var width = Math.max(1, Math.abs(x2 - x1));
794
+ line.style.cssText = 'position:fixed;left:' + left + 'px;top:' + y1 + 'px;width:' + width + 'px;border-top:1px dashed var(--design-editor-measure-color);';
795
+ labelEl.style.cssText = 'position:fixed;left:' + (left + width / 2) + 'px;top:' + (y1 - 9) + 'px;transform:translateX(-50%);border-radius:3px;background:var(--design-editor-measure-color);color:white;padding:1px 4px;';
796
+ } else {
797
+ var top = Math.min(y1, y2);
798
+ var height = Math.max(1, Math.abs(y2 - y1));
799
+ line.style.cssText = 'position:fixed;left:' + x1 + 'px;top:' + top + 'px;height:' + height + 'px;border-left:1px dashed var(--design-editor-measure-color);';
800
+ labelEl.style.cssText = 'position:fixed;left:' + (x1 + 5) + 'px;top:' + (top + height / 2) + 'px;transform:translateY(-50%);border-radius:3px;background:var(--design-editor-measure-color);color:white;padding:1px 4px;';
801
+ }
802
+ labelEl.textContent = label;
803
+ measurementOverlay.appendChild(line);
804
+ measurementOverlay.appendChild(labelEl);
805
+ }
806
+
807
+ function showMeasurements(a, b) {
808
+ if (!a || !b || a === b) {
809
+ hideMeasurements();
810
+ return;
811
+ }
812
+ var selectedRect = a.getBoundingClientRect();
813
+ var hoverRect = b.getBoundingClientRect();
814
+ measurementOverlay.innerHTML = '';
815
+ measurementOverlay.style.display = 'block';
816
+
817
+ if (hoverRect.right <= selectedRect.left) {
818
+ var yLeft = Math.max(hoverRect.top, Math.min(hoverRect.bottom, selectedRect.top + selectedRect.height / 2));
819
+ addMeasurementLine(hoverRect.right, yLeft, selectedRect.left, yLeft, Math.round(selectedRect.left - hoverRect.right) + 'px');
820
+ return;
821
+ }
822
+ if (selectedRect.right <= hoverRect.left) {
823
+ var yRight = Math.max(selectedRect.top, Math.min(selectedRect.bottom, hoverRect.top + hoverRect.height / 2));
824
+ addMeasurementLine(selectedRect.right, yRight, hoverRect.left, yRight, Math.round(hoverRect.left - selectedRect.right) + 'px');
825
+ return;
826
+ }
827
+ if (hoverRect.bottom <= selectedRect.top) {
828
+ var xTop = Math.max(hoverRect.left, Math.min(hoverRect.right, selectedRect.left + selectedRect.width / 2));
829
+ addMeasurementLine(xTop, hoverRect.bottom, xTop, selectedRect.top, Math.round(selectedRect.top - hoverRect.bottom) + 'px');
830
+ return;
831
+ }
832
+ if (selectedRect.bottom <= hoverRect.top) {
833
+ var xBottom = Math.max(selectedRect.left, Math.min(selectedRect.right, hoverRect.left + hoverRect.width / 2));
834
+ addMeasurementLine(xBottom, selectedRect.bottom, xBottom, hoverRect.top, Math.round(hoverRect.top - selectedRect.bottom) + 'px');
835
+ return;
836
+ }
837
+ addMeasurementLine(
838
+ selectedRect.left + selectedRect.width / 2,
839
+ selectedRect.top + selectedRect.height / 2,
840
+ hoverRect.left + hoverRect.width / 2,
841
+ hoverRect.top + hoverRect.height / 2,
842
+ Math.round(Math.hypot(
843
+ hoverRect.left + hoverRect.width / 2 - (selectedRect.left + selectedRect.width / 2),
844
+ hoverRect.top + hoverRect.height / 2 - (selectedRect.top + selectedRect.height / 2)
845
+ )) + 'px'
846
+ );
847
+ }
848
+
849
+ function elementFromEditorPoint(clientX, clientY) {
850
+ var shieldPointerEvents = shieldOverlay.style.pointerEvents;
851
+ var selectionPointerEvents = selectionOverlay.style.pointerEvents;
852
+ var highlightPointerEvents = highlightOverlay.style.pointerEvents;
853
+ shieldOverlay.style.pointerEvents = 'none';
854
+ selectionOverlay.style.pointerEvents = 'none';
855
+ highlightOverlay.style.pointerEvents = 'none';
856
+ var target = document.elementFromPoint(clientX, clientY);
857
+ shieldOverlay.style.pointerEvents = shieldPointerEvents;
858
+ selectionOverlay.style.pointerEvents = selectionPointerEvents;
859
+ highlightOverlay.style.pointerEvents = highlightPointerEvents;
860
+ if (!target || target.nodeType !== 1) return null;
861
+ if (isLayerInteractionBlocked(target)) return null;
862
+ return target;
863
+ }
864
+
865
+ function stopNativeInteraction(e) {
291
866
  e.preventDefault();
292
867
  e.stopPropagation();
293
- selectedEl = e.target;
868
+ if (e.stopImmediatePropagation) e.stopImmediatePropagation();
869
+ }
870
+
871
+ function isEditorTypingTarget(target) {
872
+ if (!target || !target.closest) return false;
873
+ return !!target.closest('input, textarea, select, [contenteditable], [role="textbox"], [data-agent-native-text-editing]');
874
+ }
875
+
876
+ function shouldForwardDesignHotkey(e) {
877
+ if (activeTextEditEl || isEditorTypingTarget(e.target) || e.isComposing) return false;
878
+ var key = e.key;
879
+ var normalized = key && key.length === 1 ? key.toLowerCase() : key;
880
+ var primary = e.metaKey || e.ctrlKey;
881
+ if (key === 'Escape' || key === 'Enter' || key === 'Tab') return true;
882
+ if (key === 'Delete' || key === 'Backspace') return !primary;
883
+ if (/^Arrow/.test(key || '')) return !e.altKey;
884
+ if (primary) {
885
+ return ['z','y','a','x','c','v','d','g','=','+','-','0',']','['].indexOf(normalized) !== -1 ||
886
+ e.code === 'Digit1' ||
887
+ e.code === 'Digit2' ||
888
+ key === '1' ||
889
+ key === '2';
890
+ }
891
+ if (e.shiftKey && (e.code === 'Digit1' || e.code === 'Digit2' || key === '1' || key === '2')) return true;
892
+ return !e.altKey && !e.shiftKey && ['v','f','r','t','p','h','c','k'].indexOf(normalized) !== -1;
893
+ }
894
+
895
+ function blurActiveTextEditor() {
896
+ var active = document.activeElement;
897
+ if (
898
+ active &&
899
+ active.closest &&
900
+ active.closest('[data-agent-native-text-editing]') &&
901
+ typeof active.blur === 'function'
902
+ ) {
903
+ active.blur();
904
+ }
905
+ }
906
+
907
+ function setTextEditingPointerPassthrough(enabled) {
908
+ if (enabled) {
909
+ if (!textEditPointerState) {
910
+ textEditPointerState = {
911
+ shield: shieldOverlay.style.pointerEvents,
912
+ selection: selectionOverlay.style.pointerEvents,
913
+ highlight: highlightOverlay.style.pointerEvents,
914
+ };
915
+ }
916
+ shieldOverlay.style.pointerEvents = 'none';
917
+ selectionOverlay.style.pointerEvents = 'none';
918
+ highlightOverlay.style.pointerEvents = 'none';
919
+ return;
920
+ }
921
+ if (!textEditPointerState) return;
922
+ shieldOverlay.style.pointerEvents = textEditPointerState.shield;
923
+ selectionOverlay.style.pointerEvents = textEditPointerState.selection;
924
+ highlightOverlay.style.pointerEvents = textEditPointerState.highlight;
925
+ textEditPointerState = null;
926
+ }
927
+
928
+ function hasTextContent(el) {
929
+ return !!(el && el.textContent && el.textContent.trim().length > 0);
930
+ }
931
+
932
+ function isInlineEditableDescendant(el) {
933
+ if (!el || !el.tagName) return false;
934
+ return [
935
+ 'a',
936
+ 'abbr',
937
+ 'b',
938
+ 'br',
939
+ 'cite',
940
+ 'code',
941
+ 'em',
942
+ 'i',
943
+ 'mark',
944
+ 'small',
945
+ 'span',
946
+ 'strong',
947
+ 'sub',
948
+ 'sup',
949
+ 'time',
950
+ 'u',
951
+ 'wbr'
952
+ ].indexOf(el.tagName.toLowerCase()) !== -1;
953
+ }
954
+
955
+ function hasOnlyInlineEditableChildren(el) {
956
+ if (!el || !hasTextContent(el)) return false;
957
+ var descendants = el.querySelectorAll ? el.querySelectorAll('*') : [];
958
+ for (var i = 0; i < descendants.length; i += 1) {
959
+ if (!isInlineEditableDescendant(descendants[i])) return false;
960
+ }
961
+ return true;
962
+ }
963
+
964
+ function findTextEditTarget(hit) {
965
+ if (!hit || hit.nodeType !== 1 || hit === document.body || hit === document.documentElement) return null;
966
+ var selectedContainsHit = selectedEl && selectedEl.contains && selectedEl.contains(hit);
967
+ if (selectedContainsHit && hasOnlyInlineEditableChildren(selectedEl)) return selectedEl;
968
+
969
+ var candidate = null;
970
+ var node = hit;
971
+ while (node && node.nodeType === 1 && node !== document.body && node !== document.documentElement) {
972
+ if (hasOnlyInlineEditableChildren(node)) {
973
+ candidate = node;
974
+ }
975
+ if (selectedEl && node === selectedEl) break;
976
+ node = node.parentElement;
977
+ }
978
+ return candidate || hit;
979
+ }
980
+
981
+ function selectElementAtEvent(e) {
982
+ stopNativeInteraction(e);
983
+ blurActiveTextEditor();
984
+ var target = elementFromEditorPoint(e.clientX, e.clientY);
985
+ if (!target) return;
986
+ selectedEl = target;
294
987
  var info = getElementInfo(selectedEl);
295
988
  positionOverlay(selectionOverlay, selectedEl);
296
989
  window.parent.postMessage({ type: 'element-select', payload: info }, '*');
990
+ }
991
+
992
+ function openContextMenuAtEvent(e) {
993
+ stopNativeInteraction(e);
994
+ blurActiveTextEditor();
995
+ var target = elementFromEditorPoint(e.clientX, e.clientY);
996
+ var info = null;
997
+ if (target) {
998
+ selectedEl = target;
999
+ info = getElementInfo(target);
1000
+ positionOverlay(selectionOverlay, target);
1001
+ window.parent.postMessage({ type: 'element-select', payload: info }, '*');
1002
+ }
1003
+ window.parent.postMessage({
1004
+ type: 'element-contextmenu',
1005
+ clientX: e.clientX,
1006
+ clientY: e.clientY,
1007
+ payload: info
1008
+ }, '*');
1009
+ }
1010
+
1011
+ function findRuntimeTarget(selector, selectorCandidates) {
1012
+ var candidates = [];
1013
+ if (Array.isArray(selectorCandidates)) {
1014
+ selectorCandidates.forEach(function(candidate) {
1015
+ if (typeof candidate === 'string' && candidate && candidates.indexOf(candidate) === -1) {
1016
+ candidates.push(candidate);
1017
+ }
1018
+ });
1019
+ }
1020
+ if (selector && candidates.indexOf(selector) === -1) candidates.push(selector);
1021
+ if (selectedEl && document.documentElement.contains(selectedEl)) return selectedEl;
1022
+ for (var i = 0; i < candidates.length; i += 1) {
1023
+ try {
1024
+ var match = document.querySelector(candidates[i]);
1025
+ if (match && !isLayerInteractionBlocked(match)) return match;
1026
+ } catch (_err) {}
1027
+ }
1028
+ return null;
1029
+ }
1030
+
1031
+ function removeRuntimeTarget(selector, selectorCandidates) {
1032
+ var target = findRuntimeTarget(selector, selectorCandidates);
1033
+ if (!target || target === document.body || target === document.documentElement) return false;
1034
+ if (target.parentElement) target.parentElement.removeChild(target);
1035
+ if (selectedEl === target || !document.documentElement.contains(selectedEl)) {
1036
+ selectedEl = null;
1037
+ selectionOverlay.style.display = 'none';
1038
+ }
1039
+ hoveredEl = null;
1040
+ highlightOverlay.style.display = 'none';
1041
+ hideMeasurements();
1042
+ refreshOverlays();
1043
+ return true;
1044
+ }
1045
+
1046
+ function readPx(value) {
1047
+ var num = parseFloat(value);
1048
+ return Number.isFinite(num) ? num : 0;
1049
+ }
1050
+
1051
+ function currentRotation(el) {
1052
+ var transform = el.style.transform || window.getComputedStyle(el).transform || '';
1053
+ var match = transform.match(/rotate\\((-?\\d+(?:\\.\\d+)?)deg\\)/);
1054
+ if (match) return parseFloat(match[1]) || 0;
1055
+ if (transform && transform !== 'none' && window.DOMMatrixReadOnly) {
1056
+ try {
1057
+ var matrix = new DOMMatrixReadOnly(transform);
1058
+ return Math.round(Math.atan2(matrix.b, matrix.a) * 180 / Math.PI);
1059
+ } catch (err) {}
1060
+ }
1061
+ return 0;
1062
+ }
1063
+
1064
+ function mergeRotation(el, degrees) {
1065
+ var inline = el.style.transform || '';
1066
+ var next = inline.match(/rotate\\((-?\\d+(?:\\.\\d+)?)deg\\)/)
1067
+ ? inline.replace(/rotate\\((-?\\d+(?:\\.\\d+)?)deg\\)/, 'rotate(' + degrees + 'deg)')
1068
+ : (inline && inline !== 'none' ? inline + ' ' : '') + 'rotate(' + degrees + 'deg)';
1069
+ return next.trim();
1070
+ }
1071
+
1072
+ function ensurePositionable(el) {
1073
+ var cs = window.getComputedStyle(el);
1074
+ if (cs.position === 'static') {
1075
+ el.style.position = 'relative';
1076
+ if (!el.style.left) el.style.left = '0px';
1077
+ if (!el.style.top) el.style.top = '0px';
1078
+ }
1079
+ }
1080
+
1081
+ function postVisualStyleChange(styles) {
1082
+ if (!selectedEl) return;
1083
+ window.parent.postMessage({
1084
+ type: 'visual-style-change',
1085
+ selector: getSelector(selectedEl),
1086
+ styles: styles,
1087
+ payload: getElementInfo(selectedEl),
1088
+ }, '*');
1089
+ }
1090
+
1091
+ function postTextContentChange(el, value, html) {
1092
+ window.parent.postMessage({
1093
+ type: 'text-content-change',
1094
+ selector: getSelector(el),
1095
+ value: value,
1096
+ html: html,
1097
+ payload: getElementInfo(el),
1098
+ }, '*');
1099
+ }
1100
+
1101
+ function postTextEditingState(el, active) {
1102
+ var selection = window.getSelection && window.getSelection();
1103
+ window.parent.postMessage({
1104
+ type: 'text-editing-state',
1105
+ active: !!active,
1106
+ selector: el ? getSelector(el) : '',
1107
+ hasRange: !!(active && selection && selection.rangeCount > 0 && !selection.isCollapsed && selectionBelongsToElement(selection, el)),
1108
+ }, '*');
1109
+ }
1110
+
1111
+ function insertPlainTextAtSelection(text) {
1112
+ if (!text) return;
1113
+ if (document.queryCommandSupported && document.queryCommandSupported('insertText')) {
1114
+ document.execCommand('insertText', false, text);
1115
+ return;
1116
+ }
1117
+ var selection = window.getSelection && window.getSelection();
1118
+ if (!selection || selection.rangeCount === 0) return;
1119
+ var range = selection.getRangeAt(0);
1120
+ range.deleteContents();
1121
+ var textNode = document.createTextNode(text);
1122
+ range.insertNode(textNode);
1123
+ range.setStartAfter(textNode);
1124
+ range.setEndAfter(textNode);
1125
+ selection.removeAllRanges();
1126
+ selection.addRange(range);
1127
+ }
1128
+
1129
+ function selectionBelongsToElement(selection, el) {
1130
+ if (!selection || !el || selection.rangeCount === 0) return false;
1131
+ var range = selection.getRangeAt(0);
1132
+ var ancestor = range.commonAncestorContainer;
1133
+ var ancestorEl = ancestor && ancestor.nodeType === 1 ? ancestor : ancestor && ancestor.parentElement;
1134
+ return !!(ancestorEl && (ancestorEl === el || el.contains(ancestorEl)));
1135
+ }
1136
+
1137
+ function applyTextRangeStyle(property, value) {
1138
+ if (!activeTextEditEl || !property) return false;
1139
+ var selection = window.getSelection && window.getSelection();
1140
+ if (!selection || selection.rangeCount === 0 || selection.isCollapsed) return false;
1141
+ if (!selectionBelongsToElement(selection, activeTextEditEl)) return false;
1142
+ var range = selection.getRangeAt(0);
1143
+ var span = document.createElement('span');
1144
+ span.style[property] = value;
1145
+ if (!span.getAttribute('style')) return false;
1146
+ try {
1147
+ range.surroundContents(span);
1148
+ } catch (err) {
1149
+ var contents = range.extractContents();
1150
+ span.appendChild(contents);
1151
+ range.insertNode(span);
1152
+ }
1153
+ selection.removeAllRanges();
1154
+ var nextRange = document.createRange();
1155
+ nextRange.selectNodeContents(span);
1156
+ selection.addRange(nextRange);
1157
+ return true;
1158
+ }
1159
+
1160
+ function showTransformBadge(text, clientX, clientY) {
1161
+ transformBadge.textContent = text;
1162
+ transformBadge.style.display = 'block';
1163
+ transformBadge.style.left = clientX + 12 + 'px';
1164
+ transformBadge.style.top = clientY + 12 + 'px';
1165
+ }
1166
+
1167
+ function hideTransformBadge() {
1168
+ transformBadge.style.display = 'none';
1169
+ }
1170
+
1171
+ function hideInsertionGuide() {
1172
+ insertionGuide.style.display = 'none';
1173
+ }
1174
+
1175
+ function isOverlayElement(el) {
1176
+ return Boolean(el && el.getAttribute && el.getAttribute('data-agent-native-edit-overlay'));
1177
+ }
1178
+
1179
+ function draggableElementChildren(parent) {
1180
+ return Array.prototype.slice.call(parent.children).filter(function(child) {
1181
+ return child.nodeType === 1 && !isOverlayElement(child) && !isLayerInteractionBlocked(child);
1182
+ });
1183
+ }
1184
+
1185
+ function isFlowReorderCandidate(el) {
1186
+ if (!el || !el.parentElement) return false;
1187
+ if (el === document.body || el === document.documentElement) return false;
1188
+ var cs = window.getComputedStyle(el);
1189
+ if (cs.position === 'absolute' || cs.position === 'fixed') return false;
1190
+ var parent = el.parentElement;
1191
+ if (parent === document.body && draggableElementChildren(parent).length <= 2) return false;
1192
+ return draggableElementChildren(parent).filter(function(child) { return child !== el; }).length > 0;
1193
+ }
1194
+
1195
+ function parentFlowAxis(parent) {
1196
+ var cs = window.getComputedStyle(parent);
1197
+ if (cs.display === 'flex' || cs.display === 'inline-flex') {
1198
+ return cs.flexDirection && cs.flexDirection.indexOf('row') === 0 ? 'x' : 'y';
1199
+ }
1200
+ if (cs.display === 'grid' || cs.display === 'inline-grid') {
1201
+ var cols = (cs.gridTemplateColumns || '').split(' ').filter(Boolean).length;
1202
+ return cols > 1 ? 'x' : 'y';
1203
+ }
1204
+ return 'y';
1205
+ }
1206
+
1207
+ function reorderTargetForPoint(el, clientX, clientY) {
1208
+ if (!el || !el.parentElement) return null;
1209
+ var hit = elementFromEditorPoint(clientX, clientY);
1210
+ if (
1211
+ hit &&
1212
+ hit !== document.body &&
1213
+ hit !== document.documentElement &&
1214
+ hit !== el &&
1215
+ !el.contains(hit) &&
1216
+ !isOverlayElement(hit)
1217
+ ) {
1218
+ var hitChildren = draggableElementChildren(hit).filter(function(child) {
1219
+ return child !== el;
1220
+ });
1221
+ if (hitChildren.length === 0) {
1222
+ return { anchor: hit, placement: 'inside', axis: parentFlowAxis(hit) };
1223
+ }
1224
+ var hitParent = hit.parentElement;
1225
+ if (hitParent) {
1226
+ var hitAxis = parentFlowAxis(hitParent);
1227
+ var hitRect = hit.getBoundingClientRect();
1228
+ var hitCenter = hitAxis === 'x'
1229
+ ? hitRect.left + hitRect.width / 2
1230
+ : hitRect.top + hitRect.height / 2;
1231
+ var hitPointer = hitAxis === 'x' ? clientX : clientY;
1232
+ return {
1233
+ anchor: hit,
1234
+ placement: hitPointer < hitCenter ? 'before' : 'after',
1235
+ axis: hitAxis,
1236
+ };
1237
+ }
1238
+ }
1239
+ var parent = el.parentElement;
1240
+ var axis = parentFlowAxis(parent);
1241
+ var siblings = draggableElementChildren(parent).filter(function(child) {
1242
+ return child !== el;
1243
+ });
1244
+ if (!siblings.length) return null;
1245
+ var beforeTarget = null;
1246
+ for (var i = 0; i < siblings.length; i += 1) {
1247
+ var rect = siblings[i].getBoundingClientRect();
1248
+ var center = axis === 'x' ? rect.left + rect.width / 2 : rect.top + rect.height / 2;
1249
+ var pointer = axis === 'x' ? clientX : clientY;
1250
+ if (pointer < center) {
1251
+ beforeTarget = siblings[i];
1252
+ break;
1253
+ }
1254
+ }
1255
+ var anchor = beforeTarget || siblings[siblings.length - 1];
1256
+ var placement = beforeTarget ? 'before' : 'after';
1257
+ return { anchor: anchor, placement: placement, axis: axis };
1258
+ }
1259
+
1260
+ function showInsertionGuideFor(target) {
1261
+ if (!target || !target.anchor) {
1262
+ hideInsertionGuide();
1263
+ return;
1264
+ }
1265
+ var rect = target.anchor.getBoundingClientRect();
1266
+ insertionGuide.style.display = 'block';
1267
+ insertionGuide.style.background = 'var(--design-editor-accent-color)';
1268
+ insertionGuide.style.border = '0';
1269
+ insertionGuide.style.boxShadow = '0 0 0 1px var(--design-editor-accent-color)';
1270
+ if (target.placement === 'inside') {
1271
+ insertionGuide.style.left = rect.left + 'px';
1272
+ insertionGuide.style.top = rect.top + 'px';
1273
+ insertionGuide.style.width = rect.width + 'px';
1274
+ insertionGuide.style.height = rect.height + 'px';
1275
+ insertionGuide.style.background = 'color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)';
1276
+ insertionGuide.style.border = '2px solid var(--design-editor-accent-color)';
1277
+ insertionGuide.style.boxShadow = 'none';
1278
+ return;
1279
+ }
1280
+ if (target.axis === 'x') {
1281
+ var x = target.placement === 'before' ? rect.left : rect.right;
1282
+ insertionGuide.style.left = x + 'px';
1283
+ insertionGuide.style.top = rect.top + 'px';
1284
+ insertionGuide.style.width = '2px';
1285
+ insertionGuide.style.height = rect.height + 'px';
1286
+ } else {
1287
+ var y = target.placement === 'before' ? rect.top : rect.bottom;
1288
+ insertionGuide.style.left = rect.left + 'px';
1289
+ insertionGuide.style.top = y + 'px';
1290
+ insertionGuide.style.width = rect.width + 'px';
1291
+ insertionGuide.style.height = '2px';
1292
+ }
1293
+ }
1294
+
1295
+ function applyRuntimeReorder(el, target) {
1296
+ if (!el || !target || !target.anchor || !target.anchor.parentElement) return;
1297
+ if (target.placement === 'inside') {
1298
+ target.anchor.appendChild(el);
1299
+ return;
1300
+ }
1301
+ var parent = target.anchor.parentElement;
1302
+ if (target.placement === 'before') {
1303
+ parent.insertBefore(el, target.anchor);
1304
+ } else {
1305
+ parent.insertBefore(el, target.anchor.nextSibling);
1306
+ }
1307
+ }
1308
+
1309
+ function postVisualStructureChange(el, target) {
1310
+ if (!el || !target || !target.anchor) return;
1311
+ var requestId = 'move-' + Date.now() + '-' + Math.random().toString(16).slice(2);
1312
+ pendingStructureMove = { requestId: requestId, el: el, target: target };
1313
+ window.parent.postMessage({
1314
+ type: 'visual-structure-change',
1315
+ requestId: requestId,
1316
+ selector: getSelector(el),
1317
+ sourceId: getSourceId(el),
1318
+ anchorSelector: getSelector(target.anchor),
1319
+ anchorSourceId: getSourceId(target.anchor),
1320
+ placement: target.placement,
1321
+ payload: getElementInfo(el),
1322
+ }, '*');
1323
+ }
1324
+
1325
+ function postVisualDuplicateChange(originalEl, cloneEl, target) {
1326
+ if (!originalEl || !cloneEl) return;
1327
+ window.parent.postMessage({
1328
+ type: 'visual-duplicate-change',
1329
+ selector: getSelector(originalEl),
1330
+ sourceId: getSourceId(originalEl),
1331
+ anchorSelector: target && target.anchor ? getSelector(target.anchor) : '',
1332
+ anchorSourceId: target && target.anchor ? getSourceId(target.anchor) : '',
1333
+ placement: target && target.placement ? target.placement : 'after',
1334
+ cloneHtml: cloneEl.outerHTML,
1335
+ payload: getElementInfo(cloneEl),
1336
+ }, '*');
1337
+ }
1338
+
1339
+ function startMove(e) {
1340
+ if (!selectedEl) return;
1341
+ if (isLayerInteractionBlocked(selectedEl)) return;
1342
+ e.preventDefault();
1343
+ e.stopPropagation();
1344
+ var originalSelectedEl = selectedEl;
1345
+ var duplicatedForDrag = false;
1346
+ if (e.altKey && selectedEl !== document.body && selectedEl !== document.documentElement) {
1347
+ var clone = selectedEl.cloneNode(true);
1348
+ resetRuntimeStableIds(clone);
1349
+ selectedEl.parentElement.insertBefore(clone, selectedEl.nextSibling);
1350
+ selectedEl = clone;
1351
+ duplicatedForDrag = true;
1352
+ positionOverlay(selectionOverlay, selectedEl);
1353
+ window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
1354
+ }
1355
+ if (isFlowReorderCandidate(selectedEl)) {
1356
+ var currentTarget = reorderTargetForPoint(selectedEl, e.clientX, e.clientY);
1357
+ showInsertionGuideFor(currentTarget);
1358
+ function onReorderMove(ev) {
1359
+ currentTarget = reorderTargetForPoint(selectedEl, ev.clientX, ev.clientY);
1360
+ showInsertionGuideFor(currentTarget);
1361
+ showTransformBadge(currentTarget ? 'Move layer' : 'Move', ev.clientX, ev.clientY);
1362
+ }
1363
+ function onReorderUp() {
1364
+ document.removeEventListener('mousemove', onReorderMove, true);
1365
+ document.removeEventListener('mouseup', onReorderUp, true);
1366
+ hideTransformBadge();
1367
+ hideInsertionGuide();
1368
+ if (!currentTarget) return;
1369
+ if (duplicatedForDrag) {
1370
+ applyRuntimeReorder(selectedEl, currentTarget);
1371
+ postVisualDuplicateChange(originalSelectedEl, selectedEl, currentTarget);
1372
+ }
1373
+ else postVisualStructureChange(selectedEl, currentTarget);
1374
+ }
1375
+ document.addEventListener('mousemove', onReorderMove, true);
1376
+ document.addEventListener('mouseup', onReorderUp, true);
1377
+ return;
1378
+ }
1379
+ ensurePositionable(selectedEl);
1380
+ var cs = window.getComputedStyle(selectedEl);
1381
+ var originLeft = readPx(selectedEl.style.left || cs.left);
1382
+ var originTop = readPx(selectedEl.style.top || cs.top);
1383
+ var startX = e.clientX;
1384
+ var startY = e.clientY;
1385
+ function onMove(ev) {
1386
+ var nextLeft = originLeft + ev.clientX - startX;
1387
+ var nextTop = originTop + ev.clientY - startY;
1388
+ selectedEl.style.left = Math.round(nextLeft) + 'px';
1389
+ selectedEl.style.top = Math.round(nextTop) + 'px';
1390
+ showTransformBadge('X ' + Math.round(nextLeft) + ' Y ' + Math.round(nextTop), ev.clientX, ev.clientY);
1391
+ refreshOverlays();
1392
+ }
1393
+ function onUp() {
1394
+ document.removeEventListener('mousemove', onMove, true);
1395
+ document.removeEventListener('mouseup', onUp, true);
1396
+ hideTransformBadge();
1397
+ if (duplicatedForDrag) {
1398
+ postVisualDuplicateChange(originalSelectedEl, selectedEl);
1399
+ } else {
1400
+ postVisualStyleChange({
1401
+ position: selectedEl.style.position,
1402
+ left: selectedEl.style.left,
1403
+ top: selectedEl.style.top,
1404
+ });
1405
+ }
1406
+ }
1407
+ document.addEventListener('mousemove', onMove, true);
1408
+ document.addEventListener('mouseup', onUp, true);
1409
+ }
1410
+
1411
+ function startResize(handle, e) {
1412
+ if (!selectedEl) return;
1413
+ if (isLayerInteractionBlocked(selectedEl)) return;
1414
+ e.preventDefault();
1415
+ e.stopPropagation();
1416
+ ensurePositionable(selectedEl);
1417
+ var cs = window.getComputedStyle(selectedEl);
1418
+ var origin = {
1419
+ left: readPx(selectedEl.style.left || cs.left),
1420
+ top: readPx(selectedEl.style.top || cs.top),
1421
+ width: selectedEl.getBoundingClientRect().width,
1422
+ height: selectedEl.getBoundingClientRect().height,
1423
+ ratio: selectedEl.getBoundingClientRect().width / Math.max(1, selectedEl.getBoundingClientRect().height),
1424
+ };
1425
+ var startX = e.clientX;
1426
+ var startY = e.clientY;
1427
+ function nextRect(ev) {
1428
+ var dx = ev.clientX - startX;
1429
+ var dy = ev.clientY - startY;
1430
+ var left = origin.left;
1431
+ var top = origin.top;
1432
+ var width = origin.width;
1433
+ var height = origin.height;
1434
+ if (handle.indexOf('w') !== -1) {
1435
+ left = origin.left + dx;
1436
+ width = origin.width - dx;
1437
+ }
1438
+ if (handle.indexOf('e') !== -1) width = origin.width + dx;
1439
+ if (handle.indexOf('n') !== -1) {
1440
+ top = origin.top + dy;
1441
+ height = origin.height - dy;
1442
+ }
1443
+ if (handle.indexOf('s') !== -1) height = origin.height + dy;
1444
+ width = Math.max(8, width);
1445
+ height = Math.max(8, height);
1446
+ if ((ev.shiftKey || scaleToolEnabled) && handle.length === 2) {
1447
+ if (Math.abs(dx) > Math.abs(dy)) height = width / origin.ratio;
1448
+ else width = height * origin.ratio;
1449
+ }
1450
+ if (ev.altKey) {
1451
+ if (handle.indexOf('w') !== -1 || handle.indexOf('e') !== -1) left = origin.left - (width - origin.width) / 2;
1452
+ if (handle.indexOf('n') !== -1 || handle.indexOf('s') !== -1) top = origin.top - (height - origin.height) / 2;
1453
+ }
1454
+ return { left: left, top: top, width: width, height: height };
1455
+ }
1456
+ function onMove(ev) {
1457
+ var rect = nextRect(ev);
1458
+ selectedEl.style.left = Math.round(rect.left) + 'px';
1459
+ selectedEl.style.top = Math.round(rect.top) + 'px';
1460
+ selectedEl.style.width = Math.round(rect.width) + 'px';
1461
+ selectedEl.style.height = Math.round(rect.height) + 'px';
1462
+ showTransformBadge(Math.round(rect.width) + ' x ' + Math.round(rect.height), ev.clientX, ev.clientY);
1463
+ refreshOverlays();
1464
+ }
1465
+ function onUp() {
1466
+ document.removeEventListener('mousemove', onMove, true);
1467
+ document.removeEventListener('mouseup', onUp, true);
1468
+ hideTransformBadge();
1469
+ postVisualStyleChange({
1470
+ position: selectedEl.style.position,
1471
+ left: selectedEl.style.left,
1472
+ top: selectedEl.style.top,
1473
+ width: selectedEl.style.width,
1474
+ height: selectedEl.style.height,
1475
+ });
1476
+ }
1477
+ document.addEventListener('mousemove', onMove, true);
1478
+ document.addEventListener('mouseup', onUp, true);
1479
+ }
1480
+
1481
+ function startRotate(e) {
1482
+ if (!selectedEl) return;
1483
+ if (isLayerInteractionBlocked(selectedEl)) return;
1484
+ e.preventDefault();
1485
+ e.stopPropagation();
1486
+ var rect = selectedEl.getBoundingClientRect();
1487
+ var center = { x: rect.left + rect.width / 2, y: rect.top + rect.height / 2 };
1488
+ var originAngle = Math.atan2(e.clientY - center.y, e.clientX - center.x) * 180 / Math.PI;
1489
+ var originRotation = currentRotation(selectedEl);
1490
+ function onMove(ev) {
1491
+ var pointerAngle = Math.atan2(ev.clientY - center.y, ev.clientX - center.x) * 180 / Math.PI;
1492
+ var next = originRotation + pointerAngle - originAngle;
1493
+ if (ev.shiftKey) next = Math.round(next / 15) * 15;
1494
+ next = Math.round(next);
1495
+ selectedEl.style.transform = mergeRotation(selectedEl, next);
1496
+ showTransformBadge(next + 'deg', ev.clientX, ev.clientY);
1497
+ refreshOverlays();
1498
+ }
1499
+ function onUp() {
1500
+ document.removeEventListener('mousemove', onMove, true);
1501
+ document.removeEventListener('mouseup', onUp, true);
1502
+ hideTransformBadge();
1503
+ postVisualStyleChange({
1504
+ transform: selectedEl.style.transform,
1505
+ });
1506
+ }
1507
+ document.addEventListener('mousemove', onMove, true);
1508
+ document.addEventListener('mouseup', onUp, true);
1509
+ }
1510
+
1511
+ selectionOverlay.addEventListener('mousedown', function(e) {
1512
+ var resizeHandle = e.target && e.target.getAttribute && e.target.getAttribute('data-agent-native-edit-handle');
1513
+ if (!resizeHandle && e.target && e.target.getAttribute) {
1514
+ resizeHandle = e.target.getAttribute('data-agent-native-edge-handle');
1515
+ }
1516
+ if (resizeHandle) {
1517
+ startResize(resizeHandle, e);
1518
+ return;
1519
+ }
1520
+ var rotateHandle = e.target && e.target.getAttribute && e.target.getAttribute('data-agent-native-rotate-handle');
1521
+ if (rotateHandle) {
1522
+ startRotate(e);
1523
+ return;
1524
+ }
1525
+ startMove(e);
1526
+ }, true);
1527
+
1528
+ ['pointerdown','pointerup','mousedown','mouseup','auxclick'].forEach(function(type) {
1529
+ shieldOverlay.addEventListener(type, stopNativeInteraction, true);
1530
+ });
1531
+
1532
+ shieldOverlay.addEventListener('click', selectElementAtEvent, true);
1533
+ shieldOverlay.addEventListener('contextmenu', openContextMenuAtEvent, true);
1534
+ selectionOverlay.addEventListener('contextmenu', openContextMenuAtEvent, true);
1535
+ document.addEventListener('contextmenu', function(e) {
1536
+ if (isOverlayElement(e.target)) return;
1537
+ openContextMenuAtEvent(e);
297
1538
  }, true);
298
1539
 
299
- document.addEventListener('mouseover', function(e) {
300
- if (e.target && e.target.closest('[data-agent-native-edit-overlay]')) return;
301
- hoveredEl = e.target;
1540
+ document.addEventListener('keydown', function(e) {
1541
+ if (!shouldForwardDesignHotkey(e)) return;
1542
+ stopNativeInteraction(e);
1543
+ window.parent.postMessage({
1544
+ type: 'design-hotkey',
1545
+ key: e.key,
1546
+ code: e.code,
1547
+ metaKey: !!e.metaKey,
1548
+ ctrlKey: !!e.ctrlKey,
1549
+ shiftKey: !!e.shiftKey,
1550
+ altKey: !!e.altKey,
1551
+ repeat: !!e.repeat
1552
+ }, '*');
1553
+ }, true);
1554
+
1555
+ function placeTextCaretFromPoint(target, clientX, clientY) {
1556
+ try {
1557
+ var range = null;
1558
+ if (document.caretRangeFromPoint) {
1559
+ range = document.caretRangeFromPoint(clientX, clientY);
1560
+ } else if (document.caretPositionFromPoint) {
1561
+ var position = document.caretPositionFromPoint(clientX, clientY);
1562
+ if (position) {
1563
+ range = document.createRange();
1564
+ range.setStart(position.offsetNode, position.offset);
1565
+ }
1566
+ }
1567
+ if (!range) {
1568
+ range = document.createRange();
1569
+ range.selectNodeContents(target);
1570
+ range.collapse(false);
1571
+ }
1572
+ var selection = window.getSelection();
1573
+ selection.removeAllRanges();
1574
+ selection.addRange(range);
1575
+ } catch (err) {
1576
+ try {
1577
+ var fallbackRange = document.createRange();
1578
+ fallbackRange.selectNodeContents(target);
1579
+ fallbackRange.collapse(false);
1580
+ var fallbackSelection = window.getSelection();
1581
+ fallbackSelection.removeAllRanges();
1582
+ fallbackSelection.addRange(fallbackRange);
1583
+ } catch (_err) {}
1584
+ }
1585
+ }
1586
+
1587
+ function beginTextEditingFromEvent(e) {
1588
+ if (activeTextEditEl && e.target && activeTextEditEl.contains(e.target)) return;
1589
+ if (!textEditingEnabled) {
1590
+ stopNativeInteraction(e);
1591
+ return;
1592
+ }
1593
+ stopNativeInteraction(e);
1594
+ var target = findTextEditTarget(elementFromEditorPoint(e.clientX, e.clientY));
1595
+ if (!target || target.nodeType !== 1) return;
1596
+ selectedEl = target;
1597
+ var originalText = target.textContent || '';
1598
+ var originalHtml = target.innerHTML || '';
1599
+ var committed = false;
1600
+ activeTextEditEl = target;
1601
+ target.setAttribute('contenteditable', 'true');
1602
+ target.setAttribute('data-agent-native-text-editing', 'true');
1603
+ target.style.cursor = 'text';
1604
+ target.style.outline = '1.5px solid var(--design-editor-accent-color)';
1605
+ target.style.outlineOffset = '2px';
1606
+ setTextEditingPointerPassthrough(true);
1607
+ positionOverlay(selectionOverlay, target);
1608
+ window.parent.postMessage({ type: 'element-select', payload: getElementInfo(target) }, '*');
1609
+ postTextEditingState(target, true);
1610
+
1611
+ function finish(commit) {
1612
+ if (committed) return;
1613
+ committed = true;
1614
+ target.removeEventListener('blur', onBlur, true);
1615
+ target.removeEventListener('keydown', onKeyDown, true);
1616
+ target.removeEventListener('paste', onPaste, true);
1617
+ target.removeEventListener('keyup', onSelectionChange, true);
1618
+ target.removeEventListener('mouseup', onSelectionChange, true);
1619
+ document.removeEventListener('selectionchange', onSelectionChange);
1620
+ target.removeAttribute('contenteditable');
1621
+ target.removeAttribute('data-agent-native-text-editing');
1622
+ target.style.cursor = '';
1623
+ target.style.outline = '';
1624
+ target.style.outlineOffset = '';
1625
+ setTextEditingPointerPassthrough(false);
1626
+ if (activeTextEditEl === target) activeTextEditEl = null;
1627
+ postTextEditingState(target, false);
1628
+ if (!commit) {
1629
+ target.innerHTML = originalHtml;
1630
+ refreshOverlays();
1631
+ return;
1632
+ }
1633
+ var next = target.textContent || '';
1634
+ var nextHtml = target.innerHTML || '';
1635
+ refreshOverlays();
1636
+ if (next !== originalText || nextHtml !== originalHtml) {
1637
+ postTextContentChange(target, next, nextHtml);
1638
+ }
1639
+ }
1640
+
1641
+ function onBlur() {
1642
+ finish(true);
1643
+ }
1644
+
1645
+ function onKeyDown(ev) {
1646
+ if (ev.key === 'Escape') {
1647
+ ev.preventDefault();
1648
+ finish(true);
1649
+ target.blur();
1650
+ return;
1651
+ }
1652
+ if (ev.key === 'Enter' && !ev.shiftKey) {
1653
+ ev.preventDefault();
1654
+ finish(true);
1655
+ target.blur();
1656
+ }
1657
+ }
1658
+
1659
+ function onPaste(ev) {
1660
+ ev.preventDefault();
1661
+ insertPlainTextAtSelection((ev.clipboardData && ev.clipboardData.getData('text/plain')) || '');
1662
+ }
1663
+
1664
+ function onSelectionChange() {
1665
+ postTextEditingState(target, true);
1666
+ }
1667
+
1668
+ target.addEventListener('blur', onBlur, true);
1669
+ target.addEventListener('keydown', onKeyDown, true);
1670
+ target.addEventListener('paste', onPaste, true);
1671
+ target.addEventListener('keyup', onSelectionChange, true);
1672
+ target.addEventListener('mouseup', onSelectionChange, true);
1673
+ document.addEventListener('selectionchange', onSelectionChange);
1674
+ target.focus();
1675
+ placeTextCaretFromPoint(target, e.clientX, e.clientY);
1676
+ }
1677
+
1678
+ shieldOverlay.addEventListener('dblclick', beginTextEditingFromEvent, true);
1679
+ selectionOverlay.addEventListener('dblclick', beginTextEditingFromEvent, true);
1680
+ document.addEventListener('dblclick', function(e) {
1681
+ if (isOverlayElement(e.target)) return;
1682
+ beginTextEditingFromEvent(e);
1683
+ }, true);
1684
+
1685
+ shieldOverlay.addEventListener('pointermove', function(e) {
1686
+ stopNativeInteraction(e);
1687
+ hoveredEl = elementFromEditorPoint(e.clientX, e.clientY);
1688
+ if (!hoveredEl) {
1689
+ highlightOverlay.style.display = 'none';
1690
+ hideMeasurements();
1691
+ return;
1692
+ }
1693
+ if (hoveredEl && hoveredEl.closest('[data-agent-native-text-editing]')) return;
302
1694
  positionOverlay(highlightOverlay, hoveredEl);
1695
+ if (e.altKey && selectedEl && hoveredEl && selectedEl !== hoveredEl) {
1696
+ showMeasurements(selectedEl, hoveredEl);
1697
+ } else {
1698
+ hideMeasurements();
1699
+ }
303
1700
  var info = getElementInfo(hoveredEl);
304
1701
  window.parent.postMessage({ type: 'element-hover', payload: info }, '*');
305
1702
  }, true);
306
1703
 
307
- document.addEventListener('mouseout', function() {
1704
+ shieldOverlay.addEventListener('pointerleave', function(e) {
1705
+ stopNativeInteraction(e);
308
1706
  hoveredEl = null;
309
1707
  highlightOverlay.style.display = 'none';
1708
+ hideMeasurements();
1709
+ }, true);
1710
+
1711
+ window.addEventListener('keyup', function(e) {
1712
+ if (e.key === 'Alt') hideMeasurements();
310
1713
  }, true);
311
1714
 
312
1715
  window.addEventListener('message', function(e) {
313
1716
  if (e.source !== window.parent) return;
314
- if (!e.data || e.data.type !== 'style-change') return;
315
- var sel = e.data.selector;
316
- var prop = e.data.property;
317
- var val = e.data.value;
318
- var el = sel ? document.querySelector(sel) : null;
319
- if (el) el.style[prop] = val;
320
- });
1717
+ if (!e.data) return;
1718
+ if (e.data.type === 'scale-tool-mode') {
1719
+ scaleToolEnabled = !!e.data.enabled;
1720
+ return;
1721
+ }
1722
+ if (e.data.type === 'clear-selection') {
1723
+ selectedEl = null;
1724
+ hoveredEl = null;
1725
+ selectionOverlay.style.display = 'none';
1726
+ highlightOverlay.style.display = 'none';
1727
+ hideMeasurements();
1728
+ return;
1729
+ }
1730
+ if (e.data.type === 'select-element') {
1731
+ var candidates = [];
1732
+ if (Array.isArray(e.data.selectorCandidates)) {
1733
+ e.data.selectorCandidates.forEach(function(selector) {
1734
+ if (typeof selector === 'string' && selector && candidates.indexOf(selector) === -1) {
1735
+ candidates.push(selector);
1736
+ }
1737
+ });
1738
+ }
1739
+ if (e.data.selector && candidates.indexOf(String(e.data.selector)) === -1) {
1740
+ candidates.push(String(e.data.selector));
1741
+ }
1742
+ var target = null;
1743
+ for (var i = 0; i < candidates.length && !target; i += 1) {
1744
+ try {
1745
+ var matches = document.querySelectorAll(candidates[i]);
1746
+ for (var j = 0; j < matches.length; j += 1) {
1747
+ if (!isLayerInteractionBlocked(matches[j])) {
1748
+ target = matches[j];
1749
+ break;
1750
+ }
1751
+ }
1752
+ } catch (_err) {}
1753
+ }
1754
+ if (!target) return;
1755
+ selectedEl = target;
1756
+ var selectedInfo = getElementInfo(target);
1757
+ positionOverlay(selectionOverlay, target);
1758
+ window.parent.postMessage({ type: 'element-select', payload: selectedInfo }, '*');
1759
+ return;
1760
+ }
1761
+ if (e.data.type === 'layer-states') {
1762
+ lockedSelectors = Array.isArray(e.data.lockedSelectors) ? e.data.lockedSelectors.filter(function(item) { return typeof item === 'string'; }) : [];
1763
+ hiddenSelectors = Array.isArray(e.data.hiddenSelectors) ? e.data.hiddenSelectors.filter(function(item) { return typeof item === 'string'; }) : [];
1764
+ if (selectedEl && isLayerInteractionBlocked(selectedEl)) {
1765
+ selectedEl = null;
1766
+ selectionOverlay.style.display = 'none';
1767
+ }
1768
+ if (hoveredEl && isLayerInteractionBlocked(hoveredEl)) {
1769
+ hoveredEl = null;
1770
+ highlightOverlay.style.display = 'none';
1771
+ }
1772
+ applyHiddenSelectors();
1773
+ return;
1774
+ }
1775
+ if (e.data.type === 'visual-structure-ack') {
1776
+ if (!pendingStructureMove || e.data.requestId !== pendingStructureMove.requestId) return;
1777
+ var move = pendingStructureMove;
1778
+ pendingStructureMove = null;
1779
+ if (e.data.applied) {
1780
+ applyRuntimeReorder(move.el, move.target);
1781
+ selectedEl = move.el;
1782
+ positionOverlay(selectionOverlay, selectedEl);
1783
+ window.parent.postMessage({ type: 'element-select', payload: getElementInfo(selectedEl) }, '*');
1784
+ }
1785
+ return;
1786
+ }
1787
+ if (e.data.type === 'replace-document-content') {
1788
+ replaceRuntimeDocument(e.data.content, e.data.selectedSelector, e.data.selectorCandidates);
1789
+ return;
1790
+ }
1791
+ if (e.data.type === 'delete-element') {
1792
+ removeRuntimeTarget(e.data.selector, e.data.selectorCandidates);
1793
+ return;
1794
+ }
1795
+ if (e.data.type !== 'style-change') return;
1796
+ var sel = e.data.selector;
1797
+ var prop = e.data.property;
1798
+ var val = e.data.value;
1799
+ var el = sel ? document.querySelector(sel) : null;
1800
+ if (activeTextEditEl && el === activeTextEditEl && applyTextRangeStyle(prop, val)) {
1801
+ postTextContentChange(activeTextEditEl, activeTextEditEl.textContent || '', activeTextEditEl.innerHTML || '');
1802
+ refreshOverlays();
1803
+ return;
1804
+ }
1805
+ if (el) el.style[prop] = val;
1806
+ });
321
1807
 
322
1808
  window.addEventListener('scroll', refreshOverlays, true);
323
1809
  window.addEventListener('resize', refreshOverlays);
@@ -327,12 +1813,61 @@ const EDIT_BRIDGE_SCRIPT = `
327
1813
 
328
1814
  interface DesignCanvasProps {
329
1815
  content: string;
1816
+ contentKey?: string;
330
1817
  zoom: number;
331
1818
  onZoomChange?: (zoom: number) => void;
332
1819
  deviceFrame: DeviceFrameType;
1820
+ embeddedFrame?: {
1821
+ viewportWidth: number;
1822
+ viewportHeight: number;
1823
+ displayWidth: number;
1824
+ displayHeight: number;
1825
+ };
333
1826
  editMode: boolean;
1827
+ interactMode: boolean;
1828
+ scaleMode?: boolean;
334
1829
  onElementSelect: (info: ElementInfo) => void;
335
1830
  onElementHover: (info: ElementInfo) => void;
1831
+ onVisualStyleChange?: (
1832
+ selector: string,
1833
+ styles: Record<string, string>,
1834
+ info?: ElementInfo,
1835
+ ) => void;
1836
+ onTextContentChange?: (
1837
+ selector: string,
1838
+ value: string,
1839
+ info?: ElementInfo,
1840
+ details?: { html?: string },
1841
+ ) => void;
1842
+ onTextEditingStateChange?: (state: {
1843
+ active: boolean;
1844
+ selector?: string;
1845
+ hasRange?: boolean;
1846
+ }) => void;
1847
+ onIframeHotkey?: (event: IframeHotkeyPayload) => void;
1848
+ onIframeContextMenu?: (event: IframeContextMenuPayload) => void;
1849
+ onVisualStructureChange?: (
1850
+ selector: string,
1851
+ anchorSelector: string,
1852
+ placement: "before" | "after" | "inside",
1853
+ info?: ElementInfo,
1854
+ details?: {
1855
+ sourceId?: string;
1856
+ anchorSourceId?: string;
1857
+ requestId?: string;
1858
+ },
1859
+ ) => boolean | void;
1860
+ onVisualDuplicateChange?: (
1861
+ selector: string,
1862
+ cloneHtml: string,
1863
+ info?: ElementInfo,
1864
+ details?: {
1865
+ sourceId?: string;
1866
+ anchorSelector?: string;
1867
+ anchorSourceId?: string;
1868
+ placement?: "before" | "after" | "inside";
1869
+ },
1870
+ ) => boolean | void;
336
1871
  tweakValues: Record<string, string>;
337
1872
  /** Whether draw-to-prompt mode is active (overlays the iframe). */
338
1873
  drawMode?: boolean;
@@ -340,6 +1875,13 @@ interface DesignCanvasProps {
340
1875
  onExitDrawMode?: () => void;
341
1876
  /** Whether comment-pin drop mode is active. */
342
1877
  pinMode?: boolean;
1878
+ selectedSelector?: string | null;
1879
+ selectedSelectorCandidates?: string[];
1880
+ hoveredSelector?: string | null;
1881
+ hoveredSelectorCandidates?: string[];
1882
+ lockedSelectors?: string[];
1883
+ hiddenSelectors?: string[];
1884
+ clearSelectionRequest?: number;
343
1885
  /** Called when the user exits pin mode. */
344
1886
  onExitPinMode?: () => void;
345
1887
  /** Stable id of the open design (used for pin scoping + agent prompt). */
@@ -359,18 +1901,66 @@ interface DesignCanvasProps {
359
1901
  onPrototypeNavigate?: (screen: string, href: string) => void;
360
1902
  }
361
1903
 
1904
+ function getExternalPreviewUrl(content: string): string | null {
1905
+ const trimmed = content.trim();
1906
+ if (!/^https?:\/\//i.test(trimmed)) return null;
1907
+ try {
1908
+ const url = new URL(trimmed);
1909
+ url.hash = "";
1910
+ return url.toString();
1911
+ } catch {
1912
+ return null;
1913
+ }
1914
+ }
1915
+
1916
+ export interface IframeHotkeyPayload {
1917
+ key: string;
1918
+ code: string;
1919
+ metaKey: boolean;
1920
+ ctrlKey: boolean;
1921
+ shiftKey: boolean;
1922
+ altKey: boolean;
1923
+ repeat: boolean;
1924
+ }
1925
+
1926
+ export interface IframeContextMenuPayload {
1927
+ clientX: number;
1928
+ clientY: number;
1929
+ viewportClientX?: number;
1930
+ viewportClientY?: number;
1931
+ info?: ElementInfo | null;
1932
+ }
1933
+
362
1934
  export function DesignCanvas({
363
1935
  content,
1936
+ contentKey,
364
1937
  zoom,
365
1938
  onZoomChange,
366
1939
  deviceFrame,
1940
+ embeddedFrame,
367
1941
  editMode,
1942
+ interactMode,
1943
+ scaleMode = false,
1944
+ clearSelectionRequest,
368
1945
  onElementSelect,
369
1946
  onElementHover,
1947
+ onVisualStyleChange,
1948
+ onTextContentChange,
1949
+ onTextEditingStateChange,
1950
+ onIframeHotkey,
1951
+ onIframeContextMenu,
1952
+ onVisualStructureChange,
1953
+ onVisualDuplicateChange,
370
1954
  tweakValues,
371
1955
  drawMode,
372
1956
  onExitDrawMode,
373
1957
  pinMode,
1958
+ selectedSelector,
1959
+ selectedSelectorCandidates = [],
1960
+ hoveredSelector,
1961
+ hoveredSelectorCandidates = [],
1962
+ lockedSelectors = [],
1963
+ hiddenSelectors = [],
374
1964
  onExitPinMode,
375
1965
  designId,
376
1966
  designTitle,
@@ -382,8 +1972,37 @@ export function DesignCanvas({
382
1972
  const iframeRef = useRef<HTMLIFrameElement>(null);
383
1973
  const scrollContainerRef = useRef<HTMLDivElement>(null);
384
1974
  const zoomRef = useRef(zoom);
1975
+ const previousContentKeyRef = useRef(contentKey);
1976
+ const [renderedContent, setRenderedContent] = useState(content);
1977
+ const [annotationPins, setAnnotationPins] = useState<CanvasPin[]>([]);
1978
+ const [pinSubmitSignal, setPinSubmitSignal] = useState(0);
1979
+ const isEmbeddedFrame = Boolean(embeddedFrame);
1980
+ const externalPreviewUrl = useMemo(
1981
+ () => getExternalPreviewUrl(renderedContent),
1982
+ [renderedContent],
1983
+ );
385
1984
  zoomRef.current = zoom;
386
1985
 
1986
+ const queuedAnnotationPins = useMemo(
1987
+ () =>
1988
+ annotationPins.filter(
1989
+ (pin) => pin.queued && !pin.submitted && (pin.draft || "").trim(),
1990
+ ),
1991
+ [annotationPins],
1992
+ );
1993
+
1994
+ useEffect(() => {
1995
+ if (previousContentKeyRef.current !== contentKey) {
1996
+ previousContentKeyRef.current = contentKey;
1997
+ setRenderedContent(content);
1998
+ }
1999
+ // Same-screen visual edits are already applied optimistically inside the
2000
+ // iframe before the source write is queued. Rebuilding srcdoc for that echo
2001
+ // reloads the iframe, flashes unstyled content, and drops selection. Only a
2002
+ // content-key change (screen switch / explicit remount) should replace the
2003
+ // iframe document here; the bridge replays inspector state after that load.
2004
+ }, [content, contentKey]);
2005
+
387
2006
  usePinchZoom({
388
2007
  containerRef: scrollContainerRef,
389
2008
  zoom,
@@ -395,22 +2014,41 @@ export function DesignCanvas({
395
2014
  });
396
2015
 
397
2016
  // Build the srcdoc. The tweak bridge ALWAYS goes in so the panel works
398
- // outside Edit mode. The edit bridge (click/hover overlays) is gated.
2017
+ // outside Edit mode. The editor chrome bridge is omitted only for Interact.
399
2018
  const srcdoc = useMemo(() => {
2019
+ if (externalPreviewUrl) return undefined;
2020
+ const editorChromeBridge = interactMode
2021
+ ? ""
2022
+ : createEditorBridgeThemeScript(readEditorBridgeThemeVars()) +
2023
+ EDITOR_CHROME_BRIDGE_SCRIPT.replace(
2024
+ "__TEXT_EDITING_ENABLED__",
2025
+ editMode ? "true" : "false",
2026
+ );
2027
+ const embeddedWheelBridge = EMBEDDED_WHEEL_BRIDGE_SCRIPT.replace(
2028
+ "__EMBEDDED_WHEEL_FORWARDING_ENABLED__",
2029
+ isEmbeddedFrame ? "true" : "false",
2030
+ );
400
2031
  const bridgeToInject =
401
2032
  TWEAK_BRIDGE_SCRIPT +
402
2033
  ZOOM_BRIDGE_SCRIPT +
403
2034
  NAV_BRIDGE_SCRIPT +
404
- (editMode ? EDIT_BRIDGE_SCRIPT : "");
405
- if (content.includes("</body>")) {
406
- return content.replace("</body>", bridgeToInject + "</body>"); // i18n-ignore generated iframe HTML injection
2035
+ embeddedWheelBridge +
2036
+ editorChromeBridge;
2037
+ if (renderedContent.includes("</body>")) {
2038
+ return renderedContent.replace("</body>", bridgeToInject + "</body>"); // i18n-ignore generated iframe HTML injection
407
2039
  }
408
- if (content.includes("</html>")) {
409
- return content.replace("</html>", bridgeToInject + "</html>"); // i18n-ignore generated iframe HTML injection
2040
+ if (renderedContent.includes("</html>")) {
2041
+ return renderedContent.replace("</html>", bridgeToInject + "</html>"); // i18n-ignore generated iframe HTML injection
410
2042
  }
411
2043
  // No body/html tags — wrap it
412
- return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body>${content}${bridgeToInject}</body></html>`;
413
- }, [content, editMode]);
2044
+ return `<!DOCTYPE html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"></head><body>${renderedContent}${bridgeToInject}</body></html>`;
2045
+ }, [
2046
+ editMode,
2047
+ externalPreviewUrl,
2048
+ interactMode,
2049
+ isEmbeddedFrame,
2050
+ renderedContent,
2051
+ ]);
414
2052
 
415
2053
  // Listen for messages from the iframe
416
2054
  useEffect(() => {
@@ -432,6 +2070,143 @@ export function DesignCanvas({
432
2070
  if (e.data.type === "element-hover") {
433
2071
  onElementHover(e.data.payload);
434
2072
  }
2073
+ if (e.data.type === "visual-style-change") {
2074
+ const selector = String(e.data.selector || "");
2075
+ const styles =
2076
+ e.data.styles && typeof e.data.styles === "object"
2077
+ ? (e.data.styles as Record<string, string>)
2078
+ : {};
2079
+ if (selector && Object.keys(styles).length > 0) {
2080
+ onVisualStyleChange?.(selector, styles, e.data.payload);
2081
+ }
2082
+ return;
2083
+ }
2084
+ if (e.data.type === "text-content-change") {
2085
+ const selector = String(e.data.selector || "");
2086
+ const value = String(e.data.value ?? "");
2087
+ const html =
2088
+ typeof e.data.html === "string" ? String(e.data.html) : undefined;
2089
+ if (selector) {
2090
+ onTextContentChange?.(selector, value, e.data.payload, { html });
2091
+ }
2092
+ return;
2093
+ }
2094
+ if (e.data.type === "visual-structure-change") {
2095
+ const selector = String(e.data.selector || "");
2096
+ const anchorSelector = String(e.data.anchorSelector || "");
2097
+ const placement = String(e.data.placement || "after");
2098
+ const requestId =
2099
+ typeof e.data.requestId === "string" ? e.data.requestId : undefined;
2100
+ if (
2101
+ selector &&
2102
+ anchorSelector &&
2103
+ (placement === "before" ||
2104
+ placement === "after" ||
2105
+ placement === "inside")
2106
+ ) {
2107
+ const applied = onVisualStructureChange?.(
2108
+ selector,
2109
+ anchorSelector,
2110
+ placement,
2111
+ e.data.payload,
2112
+ {
2113
+ requestId,
2114
+ sourceId:
2115
+ typeof e.data.sourceId === "string"
2116
+ ? e.data.sourceId
2117
+ : undefined,
2118
+ anchorSourceId:
2119
+ typeof e.data.anchorSourceId === "string"
2120
+ ? e.data.anchorSourceId
2121
+ : undefined,
2122
+ },
2123
+ );
2124
+ if (requestId) {
2125
+ iframeRef.current?.contentWindow?.postMessage(
2126
+ {
2127
+ type: "visual-structure-ack",
2128
+ requestId,
2129
+ applied: applied !== false,
2130
+ },
2131
+ "*",
2132
+ );
2133
+ }
2134
+ }
2135
+ return;
2136
+ }
2137
+ if (e.data.type === "visual-duplicate-change") {
2138
+ const selector = String(e.data.selector || "");
2139
+ const cloneHtml =
2140
+ typeof e.data.cloneHtml === "string" ? String(e.data.cloneHtml) : "";
2141
+ const placement = String(e.data.placement || "after");
2142
+ if (
2143
+ selector &&
2144
+ cloneHtml &&
2145
+ (placement === "before" ||
2146
+ placement === "after" ||
2147
+ placement === "inside")
2148
+ ) {
2149
+ onVisualDuplicateChange?.(selector, cloneHtml, e.data.payload, {
2150
+ sourceId:
2151
+ typeof e.data.sourceId === "string" ? e.data.sourceId : undefined,
2152
+ anchorSelector:
2153
+ typeof e.data.anchorSelector === "string"
2154
+ ? e.data.anchorSelector
2155
+ : undefined,
2156
+ anchorSourceId:
2157
+ typeof e.data.anchorSourceId === "string"
2158
+ ? e.data.anchorSourceId
2159
+ : undefined,
2160
+ placement,
2161
+ });
2162
+ }
2163
+ return;
2164
+ }
2165
+ if (e.data.type === "text-editing-state") {
2166
+ onTextEditingStateChange?.({
2167
+ active: Boolean(e.data.active),
2168
+ selector:
2169
+ typeof e.data.selector === "string" ? e.data.selector : undefined,
2170
+ hasRange: Boolean(e.data.hasRange),
2171
+ });
2172
+ return;
2173
+ }
2174
+ if (e.data.type === "design-hotkey") {
2175
+ onIframeHotkey?.({
2176
+ key: String(e.data.key || ""),
2177
+ code: String(e.data.code || ""),
2178
+ metaKey: Boolean(e.data.metaKey),
2179
+ ctrlKey: Boolean(e.data.ctrlKey),
2180
+ shiftKey: Boolean(e.data.shiftKey),
2181
+ altKey: Boolean(e.data.altKey),
2182
+ repeat: Boolean(e.data.repeat),
2183
+ });
2184
+ return;
2185
+ }
2186
+ if (e.data.type === "element-contextmenu") {
2187
+ const clientX = Number(e.data.clientX);
2188
+ const clientY = Number(e.data.clientY);
2189
+ if (Number.isFinite(clientX) && Number.isFinite(clientY)) {
2190
+ const iframe = iframeRef.current;
2191
+ const iframeRect = iframe?.getBoundingClientRect();
2192
+ const scaleX =
2193
+ iframe && iframeRect && iframe.clientWidth > 0
2194
+ ? iframeRect.width / iframe.clientWidth
2195
+ : 1;
2196
+ const scaleY =
2197
+ iframe && iframeRect && iframe.clientHeight > 0
2198
+ ? iframeRect.height / iframe.clientHeight
2199
+ : 1;
2200
+ onIframeContextMenu?.({
2201
+ clientX,
2202
+ clientY,
2203
+ viewportClientX: (iframeRect?.left ?? 0) + clientX * scaleX,
2204
+ viewportClientY: (iframeRect?.top ?? 0) + clientY * scaleY,
2205
+ info: e.data.payload ?? null,
2206
+ });
2207
+ }
2208
+ return;
2209
+ }
435
2210
  if (e.data.type === "prototype-navigate") {
436
2211
  // External links are opened inside the iframe (sandbox allow-popups);
437
2212
  // only internal screen switches reach the parent.
@@ -441,6 +2216,34 @@ export function DesignCanvas({
441
2216
  );
442
2217
  return;
443
2218
  }
2219
+ if (e.data.type === "embedded-canvas-wheel") {
2220
+ if (!isEmbeddedFrame) return;
2221
+ const iframe = iframeRef.current;
2222
+ if (!iframe) return;
2223
+ const rect = iframe.getBoundingClientRect();
2224
+ const scaleX =
2225
+ iframe.clientWidth > 0 ? rect.width / iframe.clientWidth : 1;
2226
+ const scaleY =
2227
+ iframe.clientHeight > 0 ? rect.height / iframe.clientHeight : 1;
2228
+ const clientX = rect.left + Number(e.data.clientX || 0) * scaleX;
2229
+ const clientY = rect.top + Number(e.data.clientY || 0) * scaleY;
2230
+ const forwarded = new WheelEvent("wheel", {
2231
+ bubbles: true,
2232
+ cancelable: true,
2233
+ deltaX: Math.max(-240, Math.min(240, Number(e.data.deltaX) || 0)),
2234
+ deltaY: Math.max(-240, Math.min(240, Number(e.data.deltaY) || 0)),
2235
+ deltaZ: Math.max(-240, Math.min(240, Number(e.data.deltaZ) || 0)),
2236
+ deltaMode: Number(e.data.deltaMode) || WheelEvent.DOM_DELTA_PIXEL,
2237
+ clientX,
2238
+ clientY,
2239
+ ctrlKey: Boolean(e.data.ctrlKey),
2240
+ metaKey: Boolean(e.data.metaKey),
2241
+ shiftKey: Boolean(e.data.shiftKey),
2242
+ altKey: Boolean(e.data.altKey),
2243
+ });
2244
+ iframe.dispatchEvent(forwarded);
2245
+ return;
2246
+ }
444
2247
  if (e.data.type === "pinch-zoom-wheel") {
445
2248
  if (!onZoomChange) return;
446
2249
  const iframe = iframeRef.current;
@@ -486,28 +2289,83 @@ export function DesignCanvas({
486
2289
  }, [
487
2290
  onElementSelect,
488
2291
  onElementHover,
2292
+ onVisualStyleChange,
2293
+ onTextContentChange,
2294
+ onTextEditingStateChange,
2295
+ onIframeHotkey,
2296
+ onIframeContextMenu,
2297
+ onVisualStructureChange,
2298
+ onVisualDuplicateChange,
489
2299
  onZoomChange,
490
2300
  deviceFrame,
491
2301
  onPrototypeNavigate,
2302
+ isEmbeddedFrame,
2303
+ ]);
2304
+
2305
+ const replayIframeEditorState = useCallback(() => {
2306
+ const iframe = iframeRef.current;
2307
+ if (!iframe) return;
2308
+ iframe.contentWindow?.postMessage(
2309
+ { type: "tweak-values", values: tweakValues },
2310
+ "*",
2311
+ );
2312
+ iframe.contentWindow?.postMessage(
2313
+ { type: "layer-states", lockedSelectors, hiddenSelectors },
2314
+ "*",
2315
+ );
2316
+ iframe.contentWindow?.postMessage(
2317
+ { type: "scale-tool-mode", enabled: scaleMode },
2318
+ "*",
2319
+ );
2320
+ iframe.contentWindow?.postMessage(
2321
+ selectedSelector
2322
+ ? {
2323
+ type: "select-element",
2324
+ selector: selectedSelector,
2325
+ selectorCandidates: selectedSelectorCandidates,
2326
+ }
2327
+ : { type: "clear-selection" },
2328
+ "*",
2329
+ );
2330
+ iframe.contentWindow?.postMessage(
2331
+ hoveredSelector
2332
+ ? {
2333
+ type: "hover-element",
2334
+ selector: hoveredSelector,
2335
+ selectorCandidates: hoveredSelectorCandidates,
2336
+ }
2337
+ : { type: "hover-element", selector: "", selectorCandidates: [] },
2338
+ "*",
2339
+ );
2340
+ }, [
2341
+ hoveredSelector,
2342
+ hoveredSelectorCandidates,
2343
+ hiddenSelectors,
2344
+ lockedSelectors,
2345
+ scaleMode,
2346
+ selectedSelector,
2347
+ selectedSelectorCandidates,
2348
+ tweakValues,
492
2349
  ]);
493
2350
 
494
- // Send tweak values to the iframe whenever they change OR the iframe
495
- // (re)loads. The reload case matters: changing `content` or toggling Edit
496
- // mode rebuilds srcdoc and remounts the iframe; without replaying values
497
- // here, the freshly mounted document loses the user's tweak state.
2351
+ // Replay the editor state whenever it changes OR the iframe (re)loads. The
2352
+ // load case matters for screen switches and mode changes; without replaying
2353
+ // selection/layer state here, the freshly mounted document looks deselected.
498
2354
  useEffect(() => {
499
2355
  const iframe = iframeRef.current;
500
2356
  if (!iframe) return;
501
- const send = () => {
502
- iframe.contentWindow?.postMessage(
503
- { type: "tweak-values", values: tweakValues },
504
- "*",
505
- );
506
- };
507
- send();
508
- iframe.addEventListener("load", send);
509
- return () => iframe.removeEventListener("load", send);
510
- }, [tweakValues]);
2357
+ replayIframeEditorState();
2358
+ iframe.addEventListener("load", replayIframeEditorState);
2359
+ return () => iframe.removeEventListener("load", replayIframeEditorState);
2360
+ }, [replayIframeEditorState]);
2361
+
2362
+ useEffect(() => {
2363
+ if (clearSelectionRequest === undefined) return;
2364
+ iframeRef.current?.contentWindow?.postMessage(
2365
+ { type: "clear-selection" },
2366
+ "*",
2367
+ );
2368
+ }, [clearSelectionRequest]);
511
2369
 
512
2370
  const sendStyleChange = useCallback(
513
2371
  (selector: string, property: string, value: string) => {
@@ -521,13 +2379,52 @@ export function DesignCanvas({
521
2379
  [],
522
2380
  );
523
2381
 
524
- // Expose sendStyleChange for external use
2382
+ const replacePreviewContent = useCallback(
2383
+ (nextContent: string, selector?: string | null, candidates?: string[]) => {
2384
+ const iframe = iframeRef.current;
2385
+ if (!iframe?.contentWindow) return false;
2386
+ iframe.contentWindow.postMessage(
2387
+ {
2388
+ type: "replace-document-content",
2389
+ content: nextContent,
2390
+ selectedSelector: selector ?? "",
2391
+ selectorCandidates: candidates ?? [],
2392
+ },
2393
+ "*",
2394
+ );
2395
+ return true;
2396
+ },
2397
+ [],
2398
+ );
2399
+
2400
+ const deleteRuntimeElement = useCallback(
2401
+ (selector?: string | null, candidates?: string[]) => {
2402
+ const iframe = iframeRef.current;
2403
+ if (!iframe?.contentWindow) return false;
2404
+ iframe.contentWindow.postMessage(
2405
+ {
2406
+ type: "delete-element",
2407
+ selector: selector ?? "",
2408
+ selectorCandidates: candidates ?? [],
2409
+ },
2410
+ "*",
2411
+ );
2412
+ return true;
2413
+ },
2414
+ [],
2415
+ );
2416
+
2417
+ // Expose iframe runtime mutations for the editor orchestrator.
525
2418
  useEffect(() => {
526
2419
  (window as any).__designCanvasSendStyle = sendStyleChange;
2420
+ (window as any).__designCanvasReplaceContent = replacePreviewContent;
2421
+ (window as any).__designCanvasDeleteElement = deleteRuntimeElement;
527
2422
  return () => {
528
2423
  delete (window as any).__designCanvasSendStyle;
2424
+ delete (window as any).__designCanvasReplaceContent;
2425
+ delete (window as any).__designCanvasDeleteElement;
529
2426
  };
530
- }, [sendStyleChange]);
2427
+ }, [deleteRuntimeElement, replacePreviewContent, sendStyleChange]);
531
2428
 
532
2429
  // Device dimensions match real-world devices. iframes are replaced elements
533
2430
  // with an intrinsic 300×150 size, so `aspect-ratio` + `height: auto` doesn't
@@ -550,28 +2447,38 @@ export function DesignCanvas({
550
2447
  // iframe. The pin component anchors to `.design-canvas-iframe-wrapper`
551
2448
  // via canvasSelector.
552
2449
  //
553
- // The wrapper carries a faint outline + soft shadow so the frame edge is
554
- // visible even when the design's background matches the canvas dot-grid
555
- // (e.g. both dark). Without this, a dark design dissolves into the canvas.
2450
+ // The wrapper carries a faint outline + soft shadow so the frame edge stays
2451
+ // visible when a design background matches the editor canvas.
556
2452
  const iframeElement = (
557
2453
  <div
558
2454
  className="design-canvas-iframe-wrapper relative inline-block ring-1 ring-border/60 shadow-[0_0_0_1px_rgba(0,0,0,0.04),0_8px_24px_-12px_rgba(0,0,0,0.45)]"
559
2455
  style={{
560
- width: iframeWidth,
561
- height: deviceFrame === "none" ? "100%" : (iframeHeight ?? undefined),
2456
+ width: embeddedFrame ? embeddedFrame.viewportWidth : iframeWidth,
2457
+ height: embeddedFrame
2458
+ ? embeddedFrame.viewportHeight
2459
+ : deviceFrame === "none"
2460
+ ? "100%"
2461
+ : (iframeHeight ?? undefined),
562
2462
  }}
563
2463
  >
564
2464
  <iframe
565
2465
  ref={iframeRef}
566
- srcDoc={srcdoc}
567
- sandbox="allow-scripts allow-popups allow-popups-to-escape-sandbox"
2466
+ src={externalPreviewUrl ?? undefined}
2467
+ srcDoc={externalPreviewUrl ? undefined : srcdoc}
2468
+ sandbox={
2469
+ externalPreviewUrl
2470
+ ? "allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin"
2471
+ : "allow-scripts allow-popups allow-popups-to-escape-sandbox"
2472
+ }
568
2473
  data-design-preview-iframe
569
- className="border-0 bg-white block w-full h-full"
2474
+ className="block h-full w-full border-0 bg-transparent"
570
2475
  title={t("designEditor.designPreview")}
571
2476
  />
572
2477
  {/* Draw-to-prompt overlay — sits over the iframe, NOT inside it. */}
573
2478
  <SharedDrawOverlay
574
2479
  visible={!!drawMode}
2480
+ canvasInteractive={!pinMode}
2481
+ queuedAnnotationCount={queuedAnnotationPins.length}
575
2482
  onClose={() => onExitDrawMode?.()}
576
2483
  onSend={(annotations, instruction, canvasSize) => {
577
2484
  const summary = annotations
@@ -581,24 +2488,77 @@ export function DesignCanvas({
581
2488
  : `[label "${a.text}" at ${a.position.x.toFixed(0)},${a.position.y.toFixed(0)}]`,
582
2489
  )
583
2490
  .join("\n");
2491
+ const pinSummary = queuedAnnotationPins
2492
+ .flatMap((pin, index) => {
2493
+ const lines = [
2494
+ `[${index + 1}] Comment pin on ${commentContextLabel || designTitle || commentContextId || designId || "design"}`,
2495
+ `Position: ${pin.xPct.toFixed(1)}% from left, ${pin.yPct.toFixed(1)}% from top`,
2496
+ ];
2497
+ if (pin.targetAnchorId)
2498
+ lines.push(`Anchor id: ${pin.targetAnchorId}`);
2499
+ if (pin.targetSelector)
2500
+ lines.push(`Element: ${pin.targetSelector}`);
2501
+ if (pin.targetText)
2502
+ lines.push(`Nearby text: "${pin.targetText}"`);
2503
+ lines.push("");
2504
+ lines.push((pin.draft || "").trim());
2505
+ return [...lines, ""];
2506
+ })
2507
+ .join("\n");
584
2508
  const lines = [
585
- `[Drawing on design ${designId || ""}${designTitle ? ` (${designTitle})` : ""}]`,
2509
+ `[Annotations on design ${designId || ""}${designTitle ? ` (${designTitle})` : ""}]`,
586
2510
  `Canvas size: ${canvasSize.width.toFixed(0)}x${canvasSize.height.toFixed(0)}`,
587
- summary,
2511
+ ...(summary ? ["", "[Drawing]", summary] : []),
2512
+ ...(pinSummary ? ["", "[Comment pins]", pinSummary] : []),
588
2513
  "",
589
2514
  instruction || "Apply these annotations to the design.",
590
2515
  ];
591
2516
  try {
592
- agentChat.submit(lines.join("\n"));
2517
+ sendToAgentChat({
2518
+ message: lines.join("\n"),
2519
+ submit: true,
2520
+ openSidebar: true,
2521
+ });
593
2522
  } catch (err) {
594
2523
  console.error("[DesignCanvas] failed to submit drawing:", err);
595
2524
  }
2525
+ if (queuedAnnotationPins.length > 0) {
2526
+ setPinSubmitSignal((signal) => signal + 1);
2527
+ }
596
2528
  onExitDrawMode?.();
597
2529
  }}
598
2530
  />
599
2531
  </div>
600
2532
  );
601
2533
 
2534
+ if (embeddedFrame) {
2535
+ const scaleX =
2536
+ embeddedFrame.displayWidth / Math.max(1, embeddedFrame.viewportWidth);
2537
+ const scaleY =
2538
+ embeddedFrame.displayHeight / Math.max(1, embeddedFrame.viewportHeight);
2539
+ return (
2540
+ <div
2541
+ ref={scrollContainerRef}
2542
+ className="relative h-full w-full overflow-hidden"
2543
+ style={{
2544
+ width: embeddedFrame.displayWidth,
2545
+ height: embeddedFrame.displayHeight,
2546
+ }}
2547
+ >
2548
+ <div
2549
+ style={{
2550
+ width: embeddedFrame.viewportWidth,
2551
+ height: embeddedFrame.viewportHeight,
2552
+ transform: `scale(${scaleX}, ${scaleY})`,
2553
+ transformOrigin: "top left",
2554
+ }}
2555
+ >
2556
+ {iframeElement}
2557
+ </div>
2558
+ </div>
2559
+ );
2560
+ }
2561
+
602
2562
  const wrappedContent =
603
2563
  deviceFrame === "none" ? (
604
2564
  iframeElement
@@ -611,32 +2571,22 @@ export function DesignCanvas({
611
2571
  ref={scrollContainerRef}
612
2572
  className="relative flex-1 h-full overflow-auto"
613
2573
  >
614
- {/* Dot grid background */}
615
- <div
616
- className="absolute inset-0"
617
- style={{
618
- backgroundImage:
619
- "radial-gradient(circle, hsl(var(--border)) 1px, transparent 1px)",
620
- backgroundSize: "24px 24px",
621
- }}
622
- />
623
-
624
2574
  {/* Canvas area. "none" mode fills the canvas (responsive preview);
625
- framed modes are centered inside the dot-grid with zoom applied. */}
2575
+ framed modes are centered inside the canvas with zoom applied. */}
626
2576
  {deviceFrame === "none" ? (
627
- <div className="relative h-full w-full p-8">
2577
+ <div className="relative flex h-full w-full items-center justify-center">
628
2578
  <div
629
2579
  className="h-full w-full"
630
2580
  style={{
631
2581
  transform: `scale(${zoom / 100})`,
632
- transformOrigin: "top left",
2582
+ transformOrigin: "center center",
633
2583
  }}
634
2584
  >
635
2585
  {wrappedContent}
636
2586
  </div>
637
2587
  </div>
638
2588
  ) : (
639
- <div className="relative flex items-center justify-center min-h-full p-8">
2589
+ <div className="relative flex items-center justify-center min-h-full">
640
2590
  <div
641
2591
  style={{
642
2592
  transform: `scale(${zoom / 100})`,
@@ -653,6 +2603,10 @@ export function DesignCanvas({
653
2603
  the zoom-transformed container to keep coordinates stable. */}
654
2604
  <CanvasCommentPins
655
2605
  active={!!pinMode}
2606
+ submitMode={drawMode ? "queue" : "direct"}
2607
+ onPinsChange={setAnnotationPins}
2608
+ submitQueuedSignal={pinSubmitSignal}
2609
+ clickPlaneUnderToolbar={!!drawMode}
656
2610
  onClose={() => onExitPinMode?.()}
657
2611
  canvasSelector=".design-canvas-iframe-wrapper"
658
2612
  contextId={commentContextId || designId || "design"}