@agent-native/core 0.79.27 → 0.80.1

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