@agent-native/core 0.83.0 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (424) hide show
  1. package/README.md +2 -2
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +28 -0
  4. package/corpus/core/README.md +2 -2
  5. package/corpus/core/docs/content/cloneable-saas.mdx +2 -2
  6. package/corpus/core/docs/content/faq.mdx +1 -1
  7. package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
  8. package/corpus/core/docs/content/locales/ar-SA/faq.mdx +1 -1
  9. package/corpus/core/docs/content/locales/ar-SA/template-chat.mdx +1 -1
  10. package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
  11. package/corpus/core/docs/content/locales/de-DE/faq.mdx +1 -1
  12. package/corpus/core/docs/content/locales/de-DE/template-chat.mdx +1 -1
  13. package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
  14. package/corpus/core/docs/content/locales/es-ES/faq.mdx +1 -1
  15. package/corpus/core/docs/content/locales/es-ES/template-chat.mdx +1 -1
  16. package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  17. package/corpus/core/docs/content/locales/fr-FR/faq.mdx +1 -1
  18. package/corpus/core/docs/content/locales/fr-FR/template-chat.mdx +1 -1
  19. package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
  20. package/corpus/core/docs/content/locales/hi-IN/faq.mdx +1 -1
  21. package/corpus/core/docs/content/locales/hi-IN/template-chat.mdx +1 -1
  22. package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  23. package/corpus/core/docs/content/locales/ja-JP/faq.mdx +1 -1
  24. package/corpus/core/docs/content/locales/ja-JP/template-chat.mdx +1 -1
  25. package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
  26. package/corpus/core/docs/content/locales/ko-KR/faq.mdx +1 -1
  27. package/corpus/core/docs/content/locales/ko-KR/template-chat.mdx +1 -1
  28. package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
  29. package/corpus/core/docs/content/locales/pt-BR/faq.mdx +1 -1
  30. package/corpus/core/docs/content/locales/pt-BR/template-chat.mdx +1 -1
  31. package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
  32. package/corpus/core/docs/content/locales/zh-CN/faq.mdx +1 -1
  33. package/corpus/core/docs/content/locales/zh-CN/template-chat.mdx +1 -1
  34. package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
  35. package/corpus/core/docs/content/locales/zh-TW/faq.mdx +1 -1
  36. package/corpus/core/docs/content/locales/zh-TW/template-chat.mdx +1 -1
  37. package/corpus/core/docs/content/template-chat.mdx +1 -1
  38. package/corpus/core/package.json +1 -1
  39. package/corpus/core/src/agent/engine/builtin.ts +1 -1
  40. package/corpus/core/src/agent/engine/output-tokens.ts +1 -1
  41. package/corpus/core/src/agent/harness/acp-adapter.ts +28 -4
  42. package/corpus/core/src/agent/harness/ai-sdk-adapter.ts +3 -0
  43. package/corpus/core/src/agent/harness/translate.ts +3 -0
  44. package/corpus/core/src/agent/harness/types.ts +1 -0
  45. package/corpus/core/src/agent/model-config.ts +34 -13
  46. package/corpus/core/src/agent/production-agent.ts +87 -10
  47. package/corpus/core/src/agent/thread-data-builder.ts +29 -4
  48. package/corpus/core/src/agent/tool-call-journal.ts +109 -48
  49. package/corpus/core/src/agent/types.ts +6 -1
  50. package/corpus/core/src/cli/design-connect.ts +106 -0
  51. package/corpus/core/src/cli/gateway-helpers.ts +17 -0
  52. package/corpus/core/src/cli/recap.ts +0 -2
  53. package/corpus/core/src/cli/skills.ts +54 -16
  54. package/corpus/core/src/cli/workspace-dev.ts +29 -5
  55. package/corpus/core/src/client/AssistantChat.tsx +89 -42
  56. package/corpus/core/src/client/DefaultSpinner.tsx +8 -1
  57. package/corpus/core/src/client/chat/message-components.tsx +49 -1
  58. package/corpus/core/src/client/chat/repo-helpers.ts +71 -0
  59. package/corpus/core/src/client/chat/run-recovery.tsx +15 -5
  60. package/corpus/core/src/client/components/ApiKeySettings.tsx +8 -8
  61. package/corpus/core/src/client/components/CodeRequiredDialog.tsx +26 -15
  62. package/corpus/core/src/client/components/MissingKeyCard.tsx +13 -6
  63. package/corpus/core/src/client/composer/TiptapComposer.tsx +4 -3
  64. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +0 -1
  65. package/corpus/core/src/client/guided-questions.tsx +21 -27
  66. package/corpus/core/src/client/integrations/IntegrationCard.tsx +1 -1
  67. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +1 -1
  68. package/corpus/core/src/client/onboarding/OnboardingBanner.tsx +2 -2
  69. package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +23 -19
  70. package/corpus/core/src/client/resources/ResourceEditor.tsx +18 -10
  71. package/corpus/core/src/client/resources/ResourcesPanel.tsx +5 -1
  72. package/corpus/core/src/client/settings/SettingsPanel.tsx +8 -3
  73. package/corpus/core/src/client/settings/useBuilderStatus.ts +8 -41
  74. package/corpus/core/src/client/useProductionAgent.ts +6 -2
  75. package/corpus/core/src/deploy/build.ts +1 -1
  76. package/corpus/core/src/observability/traces.ts +3 -3
  77. package/corpus/core/src/onboarding/default-steps.ts +1 -1
  78. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  79. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  80. package/corpus/core/src/server/agent-chat-plugin.ts +177 -81
  81. package/corpus/core/src/server/core-routes-plugin.ts +7 -1
  82. package/corpus/core/src/server/open-route.ts +59 -40
  83. package/corpus/core/src/shared/reasoning-effort.ts +2 -0
  84. package/corpus/core/src/sharing/access.ts +22 -2
  85. package/corpus/core/src/sharing/registry.ts +18 -0
  86. package/corpus/core/src/styles/agent-native.css +104 -20
  87. package/corpus/core/src/styles/rich-markdown-editor.css +11 -10
  88. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +10 -10
  89. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +10 -10
  90. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +6 -5
  91. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +9 -9
  92. package/corpus/templates/analytics/app/global.css +4 -0
  93. package/corpus/templates/analytics/app/lib/chart-theme.ts +13 -0
  94. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +22 -24
  95. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-charts-now-use-theme-aware-tooltip-and-grid-colors.md +6 -0
  96. package/corpus/templates/analytics/changelog/2026-06-30-fixed-ask-composer-spacing-so-the-chat-field-has-a-comfortab.md +6 -0
  97. package/corpus/templates/calendar/AGENTS.md +4 -0
  98. package/corpus/templates/calendar/actions/connect-google-calendar.ts +80 -0
  99. package/corpus/templates/calendar/changelog/2026-06-30-calendar-now-gives-the-agent-a-safe-google-connection-link-i.md +6 -0
  100. package/corpus/templates/calendar/server/plugins/agent-chat.ts +4 -0
  101. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -5
  102. package/corpus/templates/content/app/global.css +6 -5
  103. package/corpus/templates/content/changelog/2026-06-30-editor-floating-toolbars-now-use-theme-aware-colors-in-light.md +6 -0
  104. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +6 -1
  105. package/corpus/templates/design/actions/add-localhost-screens.ts +63 -8
  106. package/corpus/templates/design/actions/apply-motion-edit.ts +12 -46
  107. package/corpus/templates/design/actions/list-design-native-assets.ts +2 -1
  108. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +49 -1
  109. package/corpus/templates/design/actions/navigate.ts +3 -12
  110. package/corpus/templates/design/actions/open-visual-edit.ts +341 -0
  111. package/corpus/templates/design/actions/view-screen.ts +1 -1
  112. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +1 -1
  113. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +345 -14
  114. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +71 -91
  115. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +25 -27
  116. package/corpus/templates/design/app/components/design/DeviceFrame.tsx +1 -1
  117. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +2 -2
  118. package/corpus/templates/design/app/components/design/EditPanel.tsx +1111 -570
  119. package/corpus/templates/design/app/components/design/LayersPanel.tsx +162 -32
  120. package/corpus/templates/design/app/components/design/MotionDock.tsx +268 -240
  121. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1038 -150
  122. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +1 -1
  123. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +81 -102
  124. package/corpus/templates/design/app/components/design/StatesPanel.tsx +6 -6
  125. package/corpus/templates/design/app/components/design/TokensPanel.tsx +11 -11
  126. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -7
  127. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1129 -155
  128. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +130 -37
  129. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +24 -11
  130. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +184 -162
  131. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +137 -79
  132. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +13 -13
  133. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +8 -8
  134. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
  135. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +4 -4
  136. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +3 -3
  137. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -7
  138. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +14 -11
  139. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +4 -4
  140. package/corpus/templates/design/app/components/design/inspector/index.ts +2 -0
  141. package/corpus/templates/design/app/components/design/types.ts +19 -0
  142. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +61 -78
  143. package/corpus/templates/design/app/components/layout/Header.tsx +0 -1
  144. package/corpus/templates/design/app/components/layout/Layout.tsx +3 -2
  145. package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
  146. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +5 -4
  147. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  148. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +2 -2
  149. package/corpus/templates/design/app/global.css +56 -0
  150. package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -5
  151. package/corpus/templates/design/app/hooks/use-navigation-state.ts +6 -7
  152. package/corpus/templates/design/app/hooks/use-question-flow.ts +3 -2
  153. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +6 -4
  154. package/corpus/templates/design/app/i18n/zh-TW.ts +5 -14
  155. package/corpus/templates/design/app/i18n-data.ts +63 -188
  156. package/corpus/templates/design/app/lib/agent-chat.ts +13 -0
  157. package/corpus/templates/design/app/pages/DesignEditor.tsx +1428 -384
  158. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -5
  159. package/corpus/templates/design/app/pages/DesignSystems.tsx +2 -2
  160. package/corpus/templates/design/app/pages/Index.tsx +49 -26
  161. package/corpus/templates/design/app/pages/NotFound.tsx +25 -8
  162. package/corpus/templates/design/app/pages/VisualEdit.tsx +17 -5
  163. package/corpus/templates/design/app/routes/examples.tsx +1 -7
  164. package/corpus/templates/design/app/routes/templates.tsx +7 -4
  165. package/corpus/templates/design/changelog/2026-06-30-board-shapes-and-auto-layout-controls-are-clearer.md +6 -0
  166. package/corpus/templates/design/changelog/2026-06-30-component-selections-now-use-purple-component-chrome-and-sho.md +6 -0
  167. package/corpus/templates/design/changelog/2026-06-30-constraints-now-stay-tucked-into-a-compact-inspector-preview.md +6 -0
  168. package/corpus/templates/design/changelog/2026-06-30-creating-a-component-now-opens-in-a-compact-inspector-popove.md +6 -0
  169. package/corpus/templates/design/changelog/2026-06-30-design-cards-no-longer-show-legacy-project-type-badges.md +6 -0
  170. package/corpus/templates/design/changelog/2026-06-30-design-chat-now-keeps-new-turns-in-the-visible-design-agent-.md +6 -0
  171. package/corpus/templates/design/changelog/2026-06-30-design-editor-panels-now-keep-their-compact-text-sizing-cons.md +6 -0
  172. package/corpus/templates/design/changelog/2026-06-30-design-no-longer-shows-the-connected-local-code-warning-bann.md +6 -0
  173. package/corpus/templates/design/changelog/2026-06-30-design-now-opens-to-the-files-panel-unless-a-generation-is-a.md +6 -0
  174. package/corpus/templates/design/changelog/2026-06-30-design-opens-with-a-cleaner-empty-canvas-and-loading-preview.md +6 -0
  175. package/corpus/templates/design/changelog/2026-06-30-design-tools-use-simpler-less-decorative-icons.md +6 -0
  176. package/corpus/templates/design/changelog/2026-06-30-dragging-absolute-elements-no-longer-shows-coordinate-labels.md +6 -0
  177. package/corpus/templates/design/changelog/2026-06-30-fixed-all-screens-opening-too-zoomed-in-by-default.md +6 -0
  178. package/corpus/templates/design/changelog/2026-06-30-fixed-the-public-design-assets-sidebar-so-native-components-.md +6 -0
  179. package/corpus/templates/design/changelog/2026-06-30-fixed-the-screen-overview-board-so-empty-board-space-no-long.md +6 -0
  180. package/corpus/templates/design/changelog/2026-06-30-full-view-screens-can-now-scroll-while-remaining-editable.md +6 -0
  181. package/corpus/templates/design/changelog/2026-06-30-inspector-element-headers-show-the-selected-html-tag-and-the.md +6 -0
  182. package/corpus/templates/design/changelog/2026-06-30-inspector-inputs-stay-compact-across-desktop-breakpoints.md +6 -0
  183. package/corpus/templates/design/changelog/2026-06-30-layers-can-be-multi-selected-from-the-canvas-and-edited-toge.md +6 -0
  184. package/corpus/templates/design/changelog/2026-06-30-local-visual-edit-links-can-edit-localhost-frames-without-si.md +6 -0
  185. package/corpus/templates/design/changelog/2026-06-30-localhost-visual-edit-frames-can-now-be-selected-and-edited.md +6 -0
  186. package/corpus/templates/design/changelog/2026-06-30-marquee-selection-now-works-inside-screens-multi-selected-la.md +6 -0
  187. package/corpus/templates/design/changelog/2026-06-30-motion-is-now-available-from-the-persistent-left-rail-and-th.md +6 -0
  188. package/corpus/templates/design/changelog/2026-06-30-motion-timeline-collapse-and-reopen-animations-feel-smoother.md +6 -0
  189. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +6 -0
  190. package/corpus/templates/design/changelog/2026-06-30-new-board-shapes-now-start-with-neutral-gray-styling.md +6 -0
  191. package/corpus/templates/design/changelog/2026-06-30-new-designs-open-into-a-steadier-loading-state-before-the-ed.md +6 -0
  192. package/corpus/templates/design/changelog/2026-06-30-new-designs-show-a-loading-state-during-handoff-and-keep-edi.md +6 -0
  193. package/corpus/templates/design/changelog/2026-06-30-public-visual-edit-design-links-now-open-directly-instead-of.md +6 -0
  194. package/corpus/templates/design/changelog/2026-06-30-removed-the-confusing-states-section-from-the-design-editor-.md +6 -0
  195. package/corpus/templates/design/changelog/2026-06-30-removed-the-templates-gallery-from-design-navigation.md +6 -0
  196. package/corpus/templates/design/changelog/2026-06-30-share-stays-visible-as-soon-as-editable-designs-load.md +6 -0
  197. package/corpus/templates/design/changelog/2026-06-30-signed-out-save-and-share-controls-use-simpler-labels-and-op.md +6 -0
  198. package/corpus/templates/design/changelog/2026-06-30-text-added-on-the-canvas-now-focuses-cleanly-with-the-right-.md +6 -0
  199. package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-is-simpler-and-its-bottom.md +6 -0
  200. package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-now-follows-the-current-t.md +6 -0
  201. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-child-layout-controls-o.md +6 -0
  202. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-the-ask-ai-section-abov.md +6 -0
  203. package/corpus/templates/design/changelog/2026-06-30-the-design-review-panel-no-longer-shows-a-separate-inline-au.md +6 -0
  204. package/corpus/templates/design/changelog/2026-06-30-the-empty-designs-page-now-focuses-on-a-single-primary-new-d.md +6 -0
  205. package/corpus/templates/design/changelog/2026-06-30-the-layers-panel-header-stays-as-layers-while-multiple-items.md +6 -0
  206. package/corpus/templates/design/changelog/2026-06-30-the-missing-design-screen-now-uses-a-neutral-back-button.md +6 -0
  207. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-now-closes-without-squeezing-the-canvas-.md +6 -0
  208. package/corpus/templates/design/changelog/2026-06-30-the-new-design-connect-ai-setup-now-lines-up-with-the-compos.md +6 -0
  209. package/corpus/templates/design/changelog/2026-06-30-the-screen-overview-board-now-blends-into-the-canvas-and-new.md +6 -0
  210. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-drops-now-show-screen-guides.md +6 -0
  211. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-keep-a-transparent-backdrop-and-.md +6 -0
  212. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-select-reliably-from-all-screens.md +6 -0
  213. package/corpus/templates/design/changelog/2026-06-30-visual-edit-can-open-and-refresh-localhost-screens-in-one-au.md +6 -0
  214. package/corpus/templates/design/e2e/helpers.ts +15 -4
  215. package/corpus/templates/design/server/db/index.ts +52 -0
  216. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -0
  217. package/corpus/templates/design/server/plugins/auth.ts +1 -0
  218. package/corpus/templates/design/server/plugins/core-routes.ts +4 -0
  219. package/corpus/templates/design/shared/board-file.ts +380 -5
  220. package/corpus/templates/design/shared/board-objects.ts +2 -2
  221. package/corpus/templates/design/shared/code-layer.ts +50 -6
  222. package/corpus/templates/design/shared/motion-compiler.ts +27 -0
  223. package/corpus/templates/macros/app/components/DailyProgress.tsx +41 -37
  224. package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
  225. package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
  226. package/corpus/templates/macros/app/components/VoiceDictation.tsx +1 -1
  227. package/corpus/templates/macros/app/components/WeeklyCaloriesChart.tsx +1 -1
  228. package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
  229. package/corpus/templates/macros/app/components/WeightTracker.tsx +1 -1
  230. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -1
  231. package/corpus/templates/macros/app/routes/_index.tsx +5 -5
  232. package/corpus/templates/macros/app/routes/analytics.tsx +1 -1
  233. package/corpus/templates/macros/changelog/2026-06-30-dashboard-loading-and-chart-surfaces-now-use-theme-aware-col.md +6 -0
  234. package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +14 -11
  235. package/corpus/templates/mail/app/global.css +3 -2
  236. package/corpus/templates/mail/changelog/2026-06-30-compose-floating-toolbars-now-use-theme-aware-colors-in-ligh.md +6 -0
  237. package/corpus/templates/plan/app/pages/PlansPage.tsx +2 -2
  238. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +3 -3
  239. package/corpus/templates/slides/app/global.css +10 -0
  240. package/corpus/templates/slides/app/pages/Index.tsx +55 -51
  241. package/corpus/templates/slides/changelog/2026-06-30-deck-thumbnails-collapse-cleanly-to-one-column-when-the-chat.md +6 -0
  242. package/dist/agent/engine/anthropic-engine.d.ts +2 -2
  243. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  244. package/dist/agent/engine/builder-engine.d.ts +2 -2
  245. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  246. package/dist/agent/engine/builtin.js +1 -1
  247. package/dist/agent/engine/builtin.js.map +1 -1
  248. package/dist/agent/engine/output-tokens.js +1 -1
  249. package/dist/agent/engine/output-tokens.js.map +1 -1
  250. package/dist/agent/harness/acp-adapter.d.ts +4 -1
  251. package/dist/agent/harness/acp-adapter.d.ts.map +1 -1
  252. package/dist/agent/harness/acp-adapter.js +19 -2
  253. package/dist/agent/harness/acp-adapter.js.map +1 -1
  254. package/dist/agent/harness/ai-sdk-adapter.d.ts.map +1 -1
  255. package/dist/agent/harness/ai-sdk-adapter.js +3 -0
  256. package/dist/agent/harness/ai-sdk-adapter.js.map +1 -1
  257. package/dist/agent/harness/translate.d.ts.map +1 -1
  258. package/dist/agent/harness/translate.js +3 -0
  259. package/dist/agent/harness/translate.js.map +1 -1
  260. package/dist/agent/harness/types.d.ts +1 -0
  261. package/dist/agent/harness/types.d.ts.map +1 -1
  262. package/dist/agent/harness/types.js.map +1 -1
  263. package/dist/agent/model-config.d.ts +21 -20
  264. package/dist/agent/model-config.d.ts.map +1 -1
  265. package/dist/agent/model-config.js +31 -13
  266. package/dist/agent/model-config.js.map +1 -1
  267. package/dist/agent/production-agent.d.ts.map +1 -1
  268. package/dist/agent/production-agent.js +85 -10
  269. package/dist/agent/production-agent.js.map +1 -1
  270. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  271. package/dist/agent/thread-data-builder.js +27 -6
  272. package/dist/agent/thread-data-builder.js.map +1 -1
  273. package/dist/agent/tool-call-journal.d.ts +11 -9
  274. package/dist/agent/tool-call-journal.d.ts.map +1 -1
  275. package/dist/agent/tool-call-journal.js +92 -40
  276. package/dist/agent/tool-call-journal.js.map +1 -1
  277. package/dist/agent/types.d.ts +5 -1
  278. package/dist/agent/types.d.ts.map +1 -1
  279. package/dist/agent/types.js.map +1 -1
  280. package/dist/cli/design-connect.d.ts.map +1 -1
  281. package/dist/cli/design-connect.js +84 -0
  282. package/dist/cli/design-connect.js.map +1 -1
  283. package/dist/cli/gateway-helpers.d.ts +4 -0
  284. package/dist/cli/gateway-helpers.d.ts.map +1 -1
  285. package/dist/cli/gateway-helpers.js +9 -0
  286. package/dist/cli/gateway-helpers.js.map +1 -1
  287. package/dist/cli/recap.d.ts.map +1 -1
  288. package/dist/cli/recap.js +0 -2
  289. package/dist/cli/recap.js.map +1 -1
  290. package/dist/cli/skills.d.ts.map +1 -1
  291. package/dist/cli/skills.js +54 -16
  292. package/dist/cli/skills.js.map +1 -1
  293. package/dist/cli/workspace-dev.d.ts.map +1 -1
  294. package/dist/cli/workspace-dev.js +33 -6
  295. package/dist/cli/workspace-dev.js.map +1 -1
  296. package/dist/client/AssistantChat.d.ts.map +1 -1
  297. package/dist/client/AssistantChat.js +72 -26
  298. package/dist/client/AssistantChat.js.map +1 -1
  299. package/dist/client/DefaultSpinner.d.ts.map +1 -1
  300. package/dist/client/DefaultSpinner.js +8 -1
  301. package/dist/client/DefaultSpinner.js.map +1 -1
  302. package/dist/client/chat/message-components.d.ts +7 -1
  303. package/dist/client/chat/message-components.d.ts.map +1 -1
  304. package/dist/client/chat/message-components.js +37 -1
  305. package/dist/client/chat/message-components.js.map +1 -1
  306. package/dist/client/chat/repo-helpers.d.ts +1 -0
  307. package/dist/client/chat/repo-helpers.d.ts.map +1 -1
  308. package/dist/client/chat/repo-helpers.js +59 -0
  309. package/dist/client/chat/repo-helpers.js.map +1 -1
  310. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  311. package/dist/client/chat/run-recovery.js +4 -4
  312. package/dist/client/chat/run-recovery.js.map +1 -1
  313. package/dist/client/components/ApiKeySettings.js +8 -8
  314. package/dist/client/components/ApiKeySettings.js.map +1 -1
  315. package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
  316. package/dist/client/components/CodeRequiredDialog.js +24 -15
  317. package/dist/client/components/CodeRequiredDialog.js.map +1 -1
  318. package/dist/client/components/MissingKeyCard.d.ts.map +1 -1
  319. package/dist/client/components/MissingKeyCard.js +11 -6
  320. package/dist/client/components/MissingKeyCard.js.map +1 -1
  321. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  322. package/dist/client/composer/TiptapComposer.js +4 -3
  323. package/dist/client/composer/TiptapComposer.js.map +1 -1
  324. package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
  325. package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
  326. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  327. package/dist/client/guided-questions.d.ts.map +1 -1
  328. package/dist/client/guided-questions.js +6 -6
  329. package/dist/client/guided-questions.js.map +1 -1
  330. package/dist/client/integrations/IntegrationCard.js +1 -1
  331. package/dist/client/integrations/IntegrationCard.js.map +1 -1
  332. package/dist/client/integrations/IntegrationsPanel.js +1 -1
  333. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  334. package/dist/client/onboarding/OnboardingBanner.js +2 -2
  335. package/dist/client/onboarding/OnboardingBanner.js.map +1 -1
  336. package/dist/client/onboarding/OnboardingPanel.js +23 -19
  337. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  338. package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
  339. package/dist/client/resources/ResourceEditor.js +13 -10
  340. package/dist/client/resources/ResourceEditor.js.map +1 -1
  341. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  342. package/dist/client/resources/ResourcesPanel.js +2 -1
  343. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  344. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  345. package/dist/client/settings/SettingsPanel.js +8 -3
  346. package/dist/client/settings/SettingsPanel.js.map +1 -1
  347. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  348. package/dist/client/settings/useBuilderStatus.js +6 -38
  349. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  350. package/dist/client/useProductionAgent.d.ts +2 -1
  351. package/dist/client/useProductionAgent.d.ts.map +1 -1
  352. package/dist/client/useProductionAgent.js.map +1 -1
  353. package/dist/deploy/build.js +1 -1
  354. package/dist/deploy/build.js.map +1 -1
  355. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  356. package/dist/notifications/routes.d.ts +1 -1
  357. package/dist/observability/routes.d.ts +5 -5
  358. package/dist/observability/traces.d.ts.map +1 -1
  359. package/dist/observability/traces.js.map +1 -1
  360. package/dist/onboarding/default-steps.js +1 -1
  361. package/dist/onboarding/default-steps.js.map +1 -1
  362. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  363. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  364. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  365. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  366. package/dist/server/agent-chat-plugin.d.ts +3 -0
  367. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  368. package/dist/server/agent-chat-plugin.js +134 -72
  369. package/dist/server/agent-chat-plugin.js.map +1 -1
  370. package/dist/server/core-routes-plugin.d.ts +3 -0
  371. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  372. package/dist/server/core-routes-plugin.js +4 -1
  373. package/dist/server/core-routes-plugin.js.map +1 -1
  374. package/dist/server/open-route.d.ts +9 -0
  375. package/dist/server/open-route.d.ts.map +1 -1
  376. package/dist/server/open-route.js +48 -38
  377. package/dist/server/open-route.js.map +1 -1
  378. package/dist/shared/reasoning-effort.js +3 -0
  379. package/dist/shared/reasoning-effort.js.map +1 -1
  380. package/dist/sharing/access.d.ts.map +1 -1
  381. package/dist/sharing/access.js +18 -2
  382. package/dist/sharing/access.js.map +1 -1
  383. package/dist/sharing/registry.d.ts +10 -0
  384. package/dist/sharing/registry.d.ts.map +1 -1
  385. package/dist/sharing/registry.js.map +1 -1
  386. package/dist/styles/agent-native.css +104 -20
  387. package/dist/styles/rich-markdown-editor.css +11 -10
  388. package/docs/content/cloneable-saas.mdx +2 -2
  389. package/docs/content/faq.mdx +1 -1
  390. package/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
  391. package/docs/content/locales/ar-SA/faq.mdx +1 -1
  392. package/docs/content/locales/ar-SA/template-chat.mdx +1 -1
  393. package/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
  394. package/docs/content/locales/de-DE/faq.mdx +1 -1
  395. package/docs/content/locales/de-DE/template-chat.mdx +1 -1
  396. package/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
  397. package/docs/content/locales/es-ES/faq.mdx +1 -1
  398. package/docs/content/locales/es-ES/template-chat.mdx +1 -1
  399. package/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  400. package/docs/content/locales/fr-FR/faq.mdx +1 -1
  401. package/docs/content/locales/fr-FR/template-chat.mdx +1 -1
  402. package/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
  403. package/docs/content/locales/hi-IN/faq.mdx +1 -1
  404. package/docs/content/locales/hi-IN/template-chat.mdx +1 -1
  405. package/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  406. package/docs/content/locales/ja-JP/faq.mdx +1 -1
  407. package/docs/content/locales/ja-JP/template-chat.mdx +1 -1
  408. package/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
  409. package/docs/content/locales/ko-KR/faq.mdx +1 -1
  410. package/docs/content/locales/ko-KR/template-chat.mdx +1 -1
  411. package/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
  412. package/docs/content/locales/pt-BR/faq.mdx +1 -1
  413. package/docs/content/locales/pt-BR/template-chat.mdx +1 -1
  414. package/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
  415. package/docs/content/locales/zh-CN/faq.mdx +1 -1
  416. package/docs/content/locales/zh-CN/template-chat.mdx +1 -1
  417. package/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
  418. package/docs/content/locales/zh-TW/faq.mdx +1 -1
  419. package/docs/content/locales/zh-TW/template-chat.mdx +1 -1
  420. package/docs/content/template-chat.mdx +1 -1
  421. package/package.json +1 -1
  422. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +0 -157
  423. package/corpus/templates/design/app/lib/design-templates.ts +0 -1305
  424. package/corpus/templates/design/app/pages/Templates.tsx +0 -279
@@ -52,11 +52,14 @@ import {
52
52
  DesignCanvas,
53
53
  LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT,
54
54
  appendHitTestResponder,
55
+ type IframeContextMenuPayload,
56
+ type IframeHotkeyPayload,
55
57
  } from "./DesignCanvas";
56
58
  import {
57
59
  DEVICE_FRAME_VIEWPORTS,
58
60
  type DeviceFrameType,
59
61
  type ElementInfo,
62
+ type ElementSelectionIntent,
60
63
  } from "./types";
61
64
 
62
65
  // Re-export so consumers of MultiScreenCanvas can use the same script without
@@ -78,6 +81,7 @@ interface ScreenFile {
78
81
  height?: number;
79
82
  url?: string;
80
83
  previewUrl?: string;
84
+ bridgeUrl?: string;
81
85
  /**
82
86
  * When set, renders multiple side-by-side breakpoint frames (mobile-first,
83
87
  * §6.4). Each entry is a pixel width; the active breakpoint determines the
@@ -90,7 +94,7 @@ interface ScreenFile {
90
94
 
91
95
  type ScreenSourceType = "localhost" | "fusion" | "inline";
92
96
  type ScreenPreviewState = "live" | "snapshot" | "preview";
93
- type MultiScreenCanvasTool =
97
+ export type MultiScreenCanvasTool =
94
98
  | "move"
95
99
  | "frame"
96
100
  | "rect"
@@ -142,6 +146,7 @@ interface ScreenMetadata {
142
146
  height?: number;
143
147
  url?: string;
144
148
  previewUrl?: string;
149
+ bridgeUrl?: string;
145
150
  }
146
151
 
147
152
  interface DuplicateRequest {
@@ -213,6 +218,11 @@ interface MultiScreenCanvasProps {
213
218
  widthPx: number | undefined,
214
219
  ) => void;
215
220
  onSelectionChange?: (selectedIds: string[]) => void;
221
+ onLayerMarqueeSelectionChange?: (
222
+ selection: CanvasLayerMarqueeSelection[],
223
+ intent: ElementSelectionIntent,
224
+ ) => void;
225
+ selectedLayerSelectorGroupsByScreen?: Record<string, string[][]>;
216
226
  /**
217
227
  * Called when the user drags an element out of the active screen's iframe
218
228
  * and drops it onto a different screen. The bridge in the source iframe
@@ -231,6 +241,12 @@ interface MultiScreenCanvasProps {
231
241
  targetAnchorNodeId?: string;
232
242
  /** DOM insertion placement relative to the anchor node. */
233
243
  targetAnchorPlacement?: "before" | "after" | "inside";
244
+ /** Final drop point in logical overview canvas coordinates. */
245
+ targetCanvasPoint?: Point;
246
+ /** Final drop point in the destination iframe/content coordinate space. */
247
+ targetLocalPoint?: Point;
248
+ /** Pointer offset from the dragged element's top-left in source iframe px. */
249
+ sourcePointerOffset?: Point;
234
250
  }) => void;
235
251
  // ── Board file (new model) ───────────────────────────────────────────────
236
252
  /**
@@ -264,16 +280,51 @@ interface MultiScreenCanvasProps {
264
280
  * Pass `canEditDesign` from DesignEditor. Defaults to false.
265
281
  */
266
282
  boardEditMode?: boolean;
283
+ /**
284
+ * When true the board is the active surface (activeFileId === boardFileId),
285
+ * so the board <DesignCanvas> owns the global window runtime bridge
286
+ * (`registerRuntimeBridge={boardIsActive}`). This mirrors how the active
287
+ * screen owns the bridge in single/overview mode: at most one surface
288
+ * registers the global `window.__designCanvas*` helpers at a time
289
+ * (active screen XOR active board, since `activeFileId` is exclusive), so
290
+ * in-place ops — delete removal, begin-text-edit — reach the board exactly
291
+ * like a screen. DesignEditor passes `activeFileId === boardFileId`.
292
+ * Defaults to false.
293
+ */
294
+ boardIsActive?: boolean;
267
295
  /**
268
296
  * Called when the user selects an element on the board surface.
269
297
  * DesignEditor should set boardFileId as the active file and push the
270
298
  * selection to the inspector.
271
299
  */
272
- onBoardElementSelect?: (info: ElementInfo) => void;
300
+ onBoardElementSelect?: (
301
+ info: ElementInfo,
302
+ intent?: ElementSelectionIntent,
303
+ ) => void;
304
+ onBoardElementMarqueeSelect?: (
305
+ infos: ElementInfo[],
306
+ intent?: ElementSelectionIntent,
307
+ ) => void;
273
308
  /**
274
309
  * Called when the user hovers an element on the board surface.
275
310
  */
276
311
  onBoardElementHover?: (info: ElementInfo | null) => void;
312
+ onBoardElementClear?: () => void;
313
+ onBoardElementDblClickText?: (info: ElementInfo) => void;
314
+ onBoardIframeHotkey?: (event: IframeHotkeyPayload) => void;
315
+ onBoardIframeContextMenu?: (event: IframeContextMenuPayload) => void;
316
+ onBoardTextEditingStateChange?: (state: {
317
+ active: boolean;
318
+ selector?: string;
319
+ hasRange?: boolean;
320
+ }) => void;
321
+ boardClearSelectionRequest?: number;
322
+ boardSelectedSelector?: string | null;
323
+ boardSelectedSelectorCandidates?: string[];
324
+ boardHoveredSelector?: string | null;
325
+ boardHoveredSelectorCandidates?: string[];
326
+ boardLockedSelectors?: string[];
327
+ boardHiddenSelectors?: string[];
277
328
  /**
278
329
  * Called when a drag / reorder / reparent / drop-into-container or delete
279
330
  * occurs on a board element. Target file is boardFileId.
@@ -391,6 +442,245 @@ export function isDirectScreenHoverTarget(
391
442
  return !!element && !element.closest("[data-screen-content]");
392
443
  }
393
444
 
445
+ export function shouldBoardSurfaceCapturePointerEvents(args: {
446
+ tool: MultiScreenCanvasTool | string;
447
+ gestureActive?: boolean;
448
+ }) {
449
+ if (args.gestureActive) return false;
450
+ const tool = normalizeCanvasTool(args.tool as MultiScreenCanvasTool);
451
+ return (
452
+ !getDraftCreationTool(tool) &&
453
+ tool !== "hand" &&
454
+ tool !== "comment" &&
455
+ tool !== "draw"
456
+ );
457
+ }
458
+
459
+ export function getBoardSurfaceLayerStyle(args: {
460
+ geometry: FrameGeometry;
461
+ interactive: boolean;
462
+ }): CSSProperties {
463
+ return {
464
+ position: "absolute",
465
+ left: SURFACE_PADDING + args.geometry.x,
466
+ top: SURFACE_PADDING + args.geometry.y,
467
+ width: args.geometry.width,
468
+ height: args.geometry.height,
469
+ overflow: "hidden",
470
+ pointerEvents: args.interactive ? "auto" : "none",
471
+ background: "transparent",
472
+ zIndex: 0,
473
+ };
474
+ }
475
+
476
+ export function hasBoardSurfaceContent(html: string | undefined) {
477
+ if (!html) return false;
478
+ const bodyMatch = html.match(/<body\b[^>]*>([\s\S]*?)<\/body>/i);
479
+ const content = bodyMatch?.[1] ?? html;
480
+ return content.replace(/<!--[\s\S]*?-->/g, "").trim().length > 0;
481
+ }
482
+
483
+ const BOARD_SURFACE_RENDER_STYLE = `<style data-agent-native-board-surface-render>html,body{background:transparent!important;background-color:transparent!important;background-image:none!important;}body{margin:0!important;position:relative;overflow:visible;}body>:not([data-agent-native-node-id]):not(style):not(script),body>[data-agent-native-node-id]:not([data-an-primitive]):has([data-agent-native-node-id]),body>[data-agent-native-node-id="body"],body>[data-agent-native-node-id="Body"],body>[data-agent-native-layer-name="body"],body>[data-agent-native-layer-name="Body"],body>[data-agent-native-layer-name="<body>"]{background:transparent!important;background-color:transparent!important;background-image:none!important;box-shadow:none!important;}[data-agent-native-board-backdrop-candidate="true"]{display:none!important;pointer-events:none!important;}</style>`;
484
+ const BOARD_SURFACE_BACKGROUND = "hsl(0 0% 10%)";
485
+
486
+ const BOARD_SURFACE_BACKDROP_MIN_EDGE_PX = 2400;
487
+ const BOARD_SURFACE_BACKDROP_MIN_AREA_PX = 8_000_000;
488
+ const HTML_VOID_TAGS = new Set([
489
+ "area",
490
+ "base",
491
+ "br",
492
+ "col",
493
+ "embed",
494
+ "hr",
495
+ "img",
496
+ "input",
497
+ "link",
498
+ "meta",
499
+ "param",
500
+ "source",
501
+ "track",
502
+ "wbr",
503
+ ]);
504
+ const HTML_TAG_RE = /<!--[\s\S]*?-->|<\/?([a-zA-Z][\w:-]*)([^<>]*?)\/?>/g;
505
+
506
+ function getHtmlAttributeValue(tag: string, name: string) {
507
+ const match = tag.match(
508
+ new RegExp(
509
+ `\\s${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)'|([^\\s"'=<>]+))`,
510
+ "i",
511
+ ),
512
+ );
513
+ return match?.[1] ?? match?.[2] ?? match?.[3] ?? "";
514
+ }
515
+
516
+ function getCssDeclarationValue(style: string, name: string) {
517
+ const match = style.match(
518
+ new RegExp(`(?:^|;)\\s*${name}\\s*:\\s*([^;]+)`, "i"),
519
+ );
520
+ return match?.[1]?.trim() ?? "";
521
+ }
522
+
523
+ function getCssPixelValue(style: string, name: string) {
524
+ const value = getCssDeclarationValue(style, name);
525
+ const match = value.match(/^(-?\d+(?:\.\d+)?)px$/i);
526
+ if (!match?.[1]) return null;
527
+ const parsed = Number(match[1]);
528
+ return Number.isFinite(parsed) ? parsed : null;
529
+ }
530
+
531
+ function parseCssColor(value: string) {
532
+ const trimmed = value.trim().toLowerCase();
533
+ if (!trimmed || trimmed === "transparent") return null;
534
+ const rgb = trimmed.match(
535
+ /^rgba?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)(?:\s*,\s*(\d+(?:\.\d+)?))?\s*\)$/,
536
+ );
537
+ if (rgb?.[1] && rgb[2] && rgb[3]) {
538
+ const alpha = rgb[4] === undefined ? 1 : Number(rgb[4]);
539
+ if (alpha <= 0) return null;
540
+ return [Number(rgb[1]), Number(rgb[2]), Number(rgb[3])] as const;
541
+ }
542
+ const hex = trimmed.match(/^#([0-9a-f]{3}|[0-9a-f]{6})$/i);
543
+ if (!hex?.[1]) return null;
544
+ const raw = hex[1];
545
+ const full =
546
+ raw.length === 3
547
+ ? raw
548
+ .split("")
549
+ .map((part) => part + part)
550
+ .join("")
551
+ : raw;
552
+ return [
553
+ Number.parseInt(full.slice(0, 2), 16),
554
+ Number.parseInt(full.slice(2, 4), 16),
555
+ Number.parseInt(full.slice(4, 6), 16),
556
+ ] as const;
557
+ }
558
+
559
+ function isNeutralBackdropColor(value: string) {
560
+ const color = parseCssColor(value);
561
+ if (!color) return false;
562
+ const max = Math.max(...color);
563
+ const min = Math.min(...color);
564
+ return min >= 180 && max - min <= 24;
565
+ }
566
+
567
+ function isAccidentalBoardBackdropTag(tag: string) {
568
+ if (
569
+ getHtmlAttributeValue(tag, "data-agent-native-board-backdrop-candidate")
570
+ ) {
571
+ return false;
572
+ }
573
+ const primitive = getHtmlAttributeValue(
574
+ tag,
575
+ "data-an-primitive",
576
+ ).toLowerCase();
577
+ if (primitive !== "rectangle" && primitive !== "rect") return false;
578
+ const style = getHtmlAttributeValue(tag, "style");
579
+ if (!style) return false;
580
+ const width = getCssPixelValue(style, "width");
581
+ const height = getCssPixelValue(style, "height");
582
+ if (width === null || height === null) return false;
583
+ if (
584
+ width < BOARD_SURFACE_BACKDROP_MIN_EDGE_PX ||
585
+ height < BOARD_SURFACE_BACKDROP_MIN_EDGE_PX ||
586
+ width * height < BOARD_SURFACE_BACKDROP_MIN_AREA_PX
587
+ ) {
588
+ return false;
589
+ }
590
+ const background =
591
+ getCssDeclarationValue(style, "background-color") ||
592
+ getCssDeclarationValue(style, "background");
593
+ return isNeutralBackdropColor(background);
594
+ }
595
+
596
+ function markAccidentalBoardBackdropCandidates(html: string) {
597
+ return html.replace(HTML_TAG_RE, (tag: string, tagName?: string) => {
598
+ if (!tagName || tag.startsWith("</")) return tag;
599
+ if (!isAccidentalBoardBackdropTag(tag)) return tag;
600
+ return tag.replace(
601
+ /\/?>$/,
602
+ (ending) => ` data-agent-native-board-backdrop-candidate="true"${ending}`,
603
+ );
604
+ });
605
+ }
606
+
607
+ function findLastHtmlStackTagIndex(
608
+ stack: Array<{ tagName: string; nodeId: string }>,
609
+ tagName: string,
610
+ ) {
611
+ for (let i = stack.length - 1; i >= 0; i--) {
612
+ if (stack[i]?.tagName === tagName) return i;
613
+ }
614
+ return -1;
615
+ }
616
+
617
+ function getCurrentLayerParentNodeId(
618
+ stack: Array<{ tagName: string; nodeId: string }>,
619
+ ) {
620
+ for (let i = stack.length - 1; i >= 0; i--) {
621
+ const nodeId = stack[i]?.nodeId;
622
+ if (nodeId) return nodeId;
623
+ }
624
+ return "body";
625
+ }
626
+
627
+ export function getBoardSurfaceRenderContent(html: string) {
628
+ if (!html) return html;
629
+ const renderHtml = markAccidentalBoardBackdropCandidates(html);
630
+ if (renderHtml.includes("data-agent-native-board-surface-render")) {
631
+ return renderHtml;
632
+ }
633
+ if (/<\/head>/i.test(html)) {
634
+ return renderHtml.replace(
635
+ /<\/head>/i,
636
+ `${BOARD_SURFACE_RENDER_STYLE}</head>`,
637
+ );
638
+ }
639
+ if (/<body\b/i.test(html)) {
640
+ return renderHtml.replace(/<body\b/i, `${BOARD_SURFACE_RENDER_STYLE}<body`);
641
+ }
642
+ return `${BOARD_SURFACE_RENDER_STYLE}${renderHtml}`;
643
+ }
644
+
645
+ export function getBoardContentLayerSignature(html: string) {
646
+ const layers: string[] = [];
647
+ const stack: Array<{ tagName: string; nodeId: string }> = [];
648
+ const childCountsByParent = new Map<string, number>();
649
+
650
+ for (const match of html.matchAll(HTML_TAG_RE)) {
651
+ const token = match[0];
652
+ const tagName = match[1]?.toLowerCase();
653
+ if (!tagName) continue;
654
+
655
+ if (token.startsWith("</")) {
656
+ const index = findLastHtmlStackTagIndex(stack, tagName);
657
+ if (index >= 0) stack.splice(index);
658
+ continue;
659
+ }
660
+
661
+ const nodeId = getHtmlAttributeValue(token, "data-agent-native-node-id");
662
+ if (nodeId) {
663
+ const parentNodeId = getCurrentLayerParentNodeId(stack);
664
+ const childIndex = childCountsByParent.get(parentNodeId) ?? 0;
665
+ childCountsByParent.set(parentNodeId, childIndex + 1);
666
+ layers.push(`${nodeId}<${parentNodeId}#${childIndex}`);
667
+ }
668
+
669
+ const selfClosing = token.endsWith("/>") || HTML_VOID_TAGS.has(tagName);
670
+ if (!selfClosing) stack.push({ tagName, nodeId });
671
+ }
672
+
673
+ return `${layers.length}:${hashString(layers.join("\n"))}`;
674
+ }
675
+
676
+ export function getBoardContentKey(args: {
677
+ boardFileId: string;
678
+ boardFileContent: string;
679
+ boardIsActive: boolean;
680
+ }) {
681
+ return `${args.boardFileId}:layers:${getBoardContentLayerSignature(args.boardFileContent)}`;
682
+ }
683
+
394
684
  function getChromeHandleTransition(chromeSettling: boolean) {
395
685
  return chromeSettling
396
686
  ? CHROME_HANDLE_SETTLE_TRANSITION
@@ -454,6 +744,152 @@ export interface Point {
454
744
  y: number;
455
745
  }
456
746
 
747
+ export type CrossScreenDropPlacement = "before" | "after" | "inside";
748
+ export type CrossScreenDropAxis = "x" | "y";
749
+
750
+ export interface CrossScreenHitTestAnchorRect {
751
+ left: number;
752
+ top: number;
753
+ width: number;
754
+ height: number;
755
+ }
756
+
757
+ export interface CrossScreenHitTestResult {
758
+ anchorNodeId?: string;
759
+ placement?: CrossScreenDropPlacement;
760
+ axis?: CrossScreenDropAxis;
761
+ anchorRect?: CrossScreenHitTestAnchorRect;
762
+ }
763
+
764
+ interface CrossScreenDragElementRect {
765
+ left: number;
766
+ top: number;
767
+ width: number;
768
+ height: number;
769
+ }
770
+
771
+ function isFinitePoint(value: unknown): value is Point {
772
+ if (!value || typeof value !== "object") return false;
773
+ const point = value as Record<string, unknown>;
774
+ return Number.isFinite(point.x) && Number.isFinite(point.y);
775
+ }
776
+
777
+ interface CanvasLayerMarqueeCandidate {
778
+ screenId: string;
779
+ info: ElementInfo;
780
+ geometry: FrameGeometry;
781
+ frameGeometry: FrameGeometry;
782
+ }
783
+
784
+ export interface CanvasLayerMarqueeSelection {
785
+ screenId: string;
786
+ info: ElementInfo;
787
+ }
788
+
789
+ export interface CrossScreenDropGuide {
790
+ placement: CrossScreenDropPlacement;
791
+ axis: CrossScreenDropAxis;
792
+ boardRect: FrameGeometry;
793
+ }
794
+
795
+ function isCrossScreenDropPlacement(
796
+ value: unknown,
797
+ ): value is CrossScreenDropPlacement {
798
+ return value === "before" || value === "after" || value === "inside";
799
+ }
800
+
801
+ function isCrossScreenDropAxis(value: unknown): value is CrossScreenDropAxis {
802
+ return value === "x" || value === "y";
803
+ }
804
+
805
+ function isCrossScreenHitTestAnchorRect(
806
+ value: unknown,
807
+ ): value is CrossScreenHitTestAnchorRect {
808
+ if (!value || typeof value !== "object") return false;
809
+ const rect = value as Record<string, unknown>;
810
+ return (
811
+ Number.isFinite(rect.left) &&
812
+ Number.isFinite(rect.top) &&
813
+ Number.isFinite(rect.width) &&
814
+ Number.isFinite(rect.height)
815
+ );
816
+ }
817
+
818
+ export function getCrossScreenDropGuideForHitTest(args: {
819
+ hit: CrossScreenHitTestResult;
820
+ targetGeometry: FrameGeometry;
821
+ targetMetadata: { width: number; height: number };
822
+ }): CrossScreenDropGuide | null {
823
+ const rect = args.hit.anchorRect;
824
+ if (!rect) return null;
825
+ const placement = args.hit.placement ?? "inside";
826
+ const axis = args.hit.axis ?? "y";
827
+ const scaleX =
828
+ args.targetGeometry.width / Math.max(1, args.targetMetadata.width);
829
+ const scaleY =
830
+ args.targetGeometry.height / Math.max(1, args.targetMetadata.height);
831
+ return {
832
+ placement,
833
+ axis,
834
+ boardRect: {
835
+ x: args.targetGeometry.x + rect.left * scaleX,
836
+ y: args.targetGeometry.y + rect.top * scaleY,
837
+ width: Math.max(1, rect.width * scaleX),
838
+ height: Math.max(1, rect.height * scaleY),
839
+ },
840
+ };
841
+ }
842
+
843
+ export function getCrossScreenDropGuideStyle(args: {
844
+ guide: CrossScreenDropGuide;
845
+ pan: Point;
846
+ scale: number;
847
+ }): CSSProperties {
848
+ const { boardRect, placement, axis } = args.guide;
849
+ const left = args.pan.x + (SURFACE_PADDING + boardRect.x) * args.scale;
850
+ const top = args.pan.y + (SURFACE_PADDING + boardRect.y) * args.scale;
851
+ const width = Math.max(1, boardRect.width * args.scale);
852
+ const height = Math.max(1, boardRect.height * args.scale);
853
+
854
+ if (placement === "inside") {
855
+ return {
856
+ left,
857
+ top,
858
+ width,
859
+ height,
860
+ border: "2px solid var(--design-editor-accent-color)",
861
+ background:
862
+ "color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)",
863
+ borderRadius: 2,
864
+ boxShadow: "none",
865
+ };
866
+ }
867
+
868
+ if (axis === "x") {
869
+ const x = placement === "before" ? left : left + width;
870
+ return {
871
+ left: x - 1,
872
+ top,
873
+ width: 2,
874
+ height: Math.max(8, height),
875
+ background: "var(--design-editor-accent-color)",
876
+ borderRadius: 999,
877
+ boxShadow: "0 0 0 1px var(--design-editor-accent-color)",
878
+ };
879
+ }
880
+
881
+ const y = placement === "before" ? top : top + height;
882
+ return {
883
+ left,
884
+ top: y - 1,
885
+ width: Math.max(8, width),
886
+ height: 2,
887
+ background: "var(--design-editor-accent-color)",
888
+ borderRadius: 999,
889
+ boxShadow: "0 0 0 1px var(--design-editor-accent-color)",
890
+ };
891
+ }
892
+
457
893
  export type DraftPrimitiveKind =
458
894
  | "frame"
459
895
  | "rectangle"
@@ -498,7 +934,6 @@ interface DraftPrimitiveInput {
498
934
  points?: Point[];
499
935
  moved: boolean;
500
936
  toolProps?: CanvasToolProps;
501
- fallbackText: string;
502
937
  }
503
938
 
504
939
  interface MarqueeRect {
@@ -664,14 +1099,30 @@ export function MultiScreenCanvas({
664
1099
  onAddBreakpoint,
665
1100
  onActiveBreakpointChange,
666
1101
  onSelectionChange,
1102
+ onLayerMarqueeSelectionChange,
1103
+ selectedLayerSelectorGroupsByScreen = {},
667
1104
  onCrossScreenElementDrop,
668
1105
  boardFileId,
669
1106
  boardFileContent,
670
1107
  boardFrameGeometry,
671
1108
  onBoardDrawPrimitive,
672
1109
  boardEditMode = false,
1110
+ boardIsActive = false,
673
1111
  onBoardElementSelect,
1112
+ onBoardElementMarqueeSelect,
674
1113
  onBoardElementHover,
1114
+ onBoardElementClear,
1115
+ onBoardElementDblClickText,
1116
+ onBoardIframeHotkey,
1117
+ onBoardIframeContextMenu,
1118
+ onBoardTextEditingStateChange,
1119
+ boardClearSelectionRequest,
1120
+ boardSelectedSelector,
1121
+ boardSelectedSelectorCandidates,
1122
+ boardHoveredSelector,
1123
+ boardHoveredSelectorCandidates,
1124
+ boardLockedSelectors,
1125
+ boardHiddenSelectors,
675
1126
  onBoardVisualStructureChange,
676
1127
  onBoardVisualStyleChange,
677
1128
  onBoardVisualDuplicateChange,
@@ -742,12 +1193,19 @@ export function MultiScreenCanvas({
742
1193
  useState<CrossScreenDragGhost | null>(null);
743
1194
  const [crossScreenTarget, setCrossScreenTarget] =
744
1195
  useState<CrossScreenDragTarget | null>(null);
1196
+ const [crossScreenDropGuide, setCrossScreenDropGuide] =
1197
+ useState<CrossScreenDropGuide | null>(null);
1198
+ const [crossScreenSourceIsBoard, setCrossScreenSourceIsBoard] =
1199
+ useState(false);
745
1200
  /** Ref kept in sync with state so the message handler can read without closures. */
746
1201
  const crossScreenTargetRef = useRef<CrossScreenDragTarget | null>(null);
1202
+ const crossScreenHitTestSeqRef = useRef(0);
1203
+ const crossScreenPreviewTargetIdRef = useRef<string | null>(null);
747
1204
  /** The most-recent drag message payload — kept for use in the "end" handler. */
748
1205
  const crossScreenDragMsgRef = useRef<{
749
1206
  selector: string;
750
1207
  sourceId?: string;
1208
+ sourcePointerOffset?: Point;
751
1209
  } | null>(null);
752
1210
  const crossScreenParentDragCleanupRef = useRef<(() => void) | null>(null);
753
1211
  /** Board-space point from the last cross-screen-drag "move" message. */
@@ -771,6 +1229,21 @@ export function MultiScreenCanvas({
771
1229
  const [chromeSettling, setChromeSettling] = useState(false);
772
1230
  const previousPreviewDeviceFrameRef = useRef(previewDeviceFrame);
773
1231
 
1232
+ const claimKeyboardFocus = useCallback(() => {
1233
+ const surface = surfaceRef.current;
1234
+ if (!surface) return;
1235
+ const active = document.activeElement;
1236
+ if (
1237
+ active instanceof HTMLElement &&
1238
+ active !== surface &&
1239
+ !surface.contains(active) &&
1240
+ isEditableHotkeyTarget(active)
1241
+ ) {
1242
+ active.blur();
1243
+ }
1244
+ surface.focus({ preventScroll: true });
1245
+ }, []);
1246
+
774
1247
  const getResolvedMetadata = useCallback(
775
1248
  (screen: ScreenFile) =>
776
1249
  resolveScreenMetadata(
@@ -1145,6 +1618,32 @@ export function MultiScreenCanvas({
1145
1618
  useEffect(() => {
1146
1619
  if (!onCrossScreenElementDrop) return;
1147
1620
 
1621
+ const clearCrossScreenDropGuide = () => {
1622
+ crossScreenHitTestSeqRef.current += 1;
1623
+ setCrossScreenDropGuide(null);
1624
+ };
1625
+
1626
+ const postHitTestPreviewClear = (targetId: string | null | undefined) => {
1627
+ if (!targetId) return;
1628
+ const targetIframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
1629
+ `[data-screen-iframe-id="${CSS.escape(targetId)}"]`,
1630
+ );
1631
+ targetIframe?.contentWindow?.postMessage(
1632
+ { type: "agent-native:hit-test-preview-clear" },
1633
+ "*",
1634
+ );
1635
+ };
1636
+
1637
+ const clearCrossScreenPreviewGuide = (
1638
+ targetId?: string | null | undefined,
1639
+ ) => {
1640
+ const id = targetId ?? crossScreenPreviewTargetIdRef.current;
1641
+ postHitTestPreviewClear(id);
1642
+ if (!targetId || targetId === crossScreenPreviewTargetIdRef.current) {
1643
+ crossScreenPreviewTargetIdRef.current = null;
1644
+ }
1645
+ };
1646
+
1148
1647
  const stopParentCrossScreenDrag = () => {
1149
1648
  crossScreenParentDragCleanupRef.current?.();
1150
1649
  crossScreenParentDragCleanupRef.current = null;
@@ -1152,115 +1651,228 @@ export function MultiScreenCanvas({
1152
1651
 
1153
1652
  const clearCrossScreenDrag = () => {
1154
1653
  stopParentCrossScreenDrag();
1654
+ clearCrossScreenPreviewGuide();
1155
1655
  setCrossScreenGhost(null);
1156
1656
  setCrossScreenTarget(null);
1657
+ setCrossScreenSourceIsBoard(false);
1658
+ clearCrossScreenDropGuide();
1157
1659
  crossScreenTargetRef.current = null;
1158
1660
  crossScreenDragMsgRef.current = null;
1159
1661
  };
1160
1662
 
1663
+ const runHitTest = (
1664
+ candidate: CrossScreenDragTarget,
1665
+ boardPoint: Point,
1666
+ options: { preview?: boolean } = {},
1667
+ ): Promise<CrossScreenHitTestResult> => {
1668
+ const targetScreen = screensRef.current.find(
1669
+ (s) => s.id === candidate.id,
1670
+ );
1671
+ if (!targetScreen) return Promise.resolve({});
1672
+ const targetGeometry = candidate.geometry;
1673
+ const targetIframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
1674
+ `[data-screen-iframe-id="${CSS.escape(candidate.id)}"]`,
1675
+ );
1676
+ const targetContentWindow = targetIframe?.contentWindow;
1677
+ if (!targetContentWindow) return Promise.resolve({});
1678
+ const targetViewportWidth =
1679
+ targetIframe.clientWidth || getResolvedMetadata(targetScreen).width;
1680
+ const targetViewportHeight =
1681
+ targetIframe.clientHeight || getResolvedMetadata(targetScreen).height;
1682
+ const scaleX = targetViewportWidth / Math.max(1, targetGeometry.width);
1683
+ const scaleY = targetViewportHeight / Math.max(1, targetGeometry.height);
1684
+ const localX = (boardPoint.x - targetGeometry.x) * scaleX;
1685
+ const localY = (boardPoint.y - targetGeometry.y) * scaleY;
1686
+
1687
+ const correlationId = `hit-${Date.now()}-${Math.random()
1688
+ .toString(36)
1689
+ .slice(2, 6)}`;
1690
+
1691
+ return new Promise((resolve) => {
1692
+ const timer = window.setTimeout(() => {
1693
+ window.removeEventListener("message", hitListener);
1694
+ resolve({});
1695
+ }, 50);
1696
+
1697
+ const hitListener = (ev: MessageEvent) => {
1698
+ if (
1699
+ !ev.data ||
1700
+ ev.data.type !== "agent-native:hit-test-result" ||
1701
+ ev.data.correlationId !== correlationId
1702
+ ) {
1703
+ return;
1704
+ }
1705
+ window.clearTimeout(timer);
1706
+ window.removeEventListener("message", hitListener);
1707
+ resolve({
1708
+ anchorNodeId:
1709
+ typeof ev.data.anchorNodeId === "string"
1710
+ ? ev.data.anchorNodeId
1711
+ : undefined,
1712
+ placement: isCrossScreenDropPlacement(ev.data.placement)
1713
+ ? ev.data.placement
1714
+ : undefined,
1715
+ axis: isCrossScreenDropAxis(ev.data.axis)
1716
+ ? ev.data.axis
1717
+ : undefined,
1718
+ anchorRect: isCrossScreenHitTestAnchorRect(ev.data.anchorRect)
1719
+ ? ev.data.anchorRect
1720
+ : undefined,
1721
+ });
1722
+ };
1723
+ window.addEventListener("message", hitListener);
1724
+
1725
+ targetContentWindow.postMessage(
1726
+ {
1727
+ type: "agent-native:hit-test",
1728
+ correlationId,
1729
+ x: localX,
1730
+ y: localY,
1731
+ preview: options.preview === true,
1732
+ },
1733
+ "*",
1734
+ );
1735
+ if (options.preview) {
1736
+ crossScreenPreviewTargetIdRef.current = candidate.id;
1737
+ }
1738
+ });
1739
+ };
1740
+
1741
+ const getTargetLocalPoint = (
1742
+ candidate: CrossScreenDragTarget,
1743
+ boardPoint: Point,
1744
+ ): Point | null => {
1745
+ if (candidate.id === boardFileId) return boardPoint;
1746
+ const targetScreen = screensRef.current.find(
1747
+ (s) => s.id === candidate.id,
1748
+ );
1749
+ if (!targetScreen) return null;
1750
+ const targetIframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
1751
+ `[data-screen-iframe-id="${CSS.escape(candidate.id)}"]`,
1752
+ );
1753
+ const targetViewportWidth =
1754
+ targetIframe?.clientWidth || getResolvedMetadata(targetScreen).width;
1755
+ const targetViewportHeight =
1756
+ targetIframe?.clientHeight || getResolvedMetadata(targetScreen).height;
1757
+ const scaleX =
1758
+ targetViewportWidth / Math.max(1, candidate.geometry.width);
1759
+ const scaleY =
1760
+ targetViewportHeight / Math.max(1, candidate.geometry.height);
1761
+ return {
1762
+ x: (boardPoint.x - candidate.geometry.x) * scaleX,
1763
+ y: (boardPoint.y - candidate.geometry.y) * scaleY,
1764
+ };
1765
+ };
1766
+
1767
+ const requestCrossScreenDropGuide = (
1768
+ candidate: CrossScreenDragTarget,
1769
+ boardPoint: Point,
1770
+ sourceIsBoard: boolean,
1771
+ ) => {
1772
+ const requestSeq = ++crossScreenHitTestSeqRef.current;
1773
+ void runHitTest(candidate, boardPoint, { preview: sourceIsBoard }).then(
1774
+ (hit) => {
1775
+ if (crossScreenHitTestSeqRef.current !== requestSeq) return;
1776
+ if (crossScreenTargetRef.current?.id !== candidate.id) return;
1777
+ const targetScreen = screensRef.current.find(
1778
+ (s) => s.id === candidate.id,
1779
+ );
1780
+ const guide = targetScreen
1781
+ ? getCrossScreenDropGuideForHitTest({
1782
+ hit,
1783
+ targetGeometry: candidate.geometry,
1784
+ targetMetadata: getResolvedMetadata(targetScreen),
1785
+ })
1786
+ : null;
1787
+ setCrossScreenDropGuide(sourceIsBoard ? null : guide);
1788
+ },
1789
+ );
1790
+ };
1791
+
1161
1792
  const updateCrossScreenTargetFromBoardPoint = (
1162
1793
  boardPoint: Point,
1163
1794
  sourceScreenId: string,
1164
1795
  ) => {
1165
1796
  crossScreenLastBoardPointRef.current = boardPoint;
1166
- setCrossScreenGhost({ boardX: boardPoint.x, boardY: boardPoint.y });
1797
+ const sourceIsBoard = sourceScreenId === boardFileId;
1798
+ setCrossScreenSourceIsBoard(sourceIsBoard);
1799
+ setCrossScreenGhost(
1800
+ sourceIsBoard ? null : { boardX: boardPoint.x, boardY: boardPoint.y },
1801
+ );
1167
1802
  const target = getFrameEntryAtPoint(boardPoint);
1168
1803
  if (target && target.id !== sourceScreenId) {
1169
1804
  const nextTarget = { id: target.id, geometry: target.geometry };
1805
+ if (crossScreenTargetRef.current?.id !== nextTarget.id) {
1806
+ clearCrossScreenPreviewGuide();
1807
+ }
1170
1808
  crossScreenTargetRef.current = nextTarget;
1171
1809
  setCrossScreenTarget(nextTarget);
1810
+ requestCrossScreenDropGuide(nextTarget, boardPoint, sourceIsBoard);
1172
1811
  } else {
1812
+ clearCrossScreenPreviewGuide();
1173
1813
  crossScreenTargetRef.current = null;
1174
1814
  setCrossScreenTarget(null);
1815
+ clearCrossScreenDropGuide();
1175
1816
  }
1176
1817
  };
1177
1818
 
1178
1819
  const finalizeCrossScreenDrop = (
1179
1820
  sourceScreenId: string,
1180
1821
  candidate: CrossScreenDragTarget | null,
1181
- payload: { selector: string; sourceId?: string },
1822
+ payload: {
1823
+ selector: string;
1824
+ sourceId?: string;
1825
+ sourcePointerOffset?: Point;
1826
+ },
1182
1827
  lastBoardPoint: Point | null,
1183
1828
  ) => {
1184
1829
  clearCrossScreenDrag();
1185
1830
  crossScreenLastBoardPointRef.current = null;
1186
1831
  const hasIdentifier = !!(payload.selector || payload.sourceId);
1187
- if (!candidate || !hasIdentifier || !sourceScreenId) return;
1188
-
1189
- const runHitTest = (): Promise<{
1190
- anchorNodeId?: string;
1191
- placement?: "before" | "after" | "inside";
1192
- }> => {
1193
- if (!lastBoardPoint) return Promise.resolve({});
1194
- const targetScreen = screensRef.current.find(
1195
- (s) => s.id === candidate.id,
1196
- );
1197
- if (!targetScreen) return Promise.resolve({});
1198
- const targetGeometry = candidate.geometry;
1199
- const targetIframe =
1200
- surfaceRef.current?.querySelector<HTMLIFrameElement>(
1201
- `[data-screen-iframe-id="${CSS.escape(candidate.id)}"]`,
1202
- );
1203
- const targetContentWindow = targetIframe?.contentWindow;
1204
- if (!targetContentWindow) return Promise.resolve({});
1205
- const targetViewportWidth =
1206
- targetIframe.clientWidth || getResolvedMetadata(targetScreen).width;
1207
- const targetViewportHeight =
1208
- targetIframe.clientHeight || getResolvedMetadata(targetScreen).height;
1209
- const scaleX = targetViewportWidth / Math.max(1, targetGeometry.width);
1210
- const scaleY =
1211
- targetViewportHeight / Math.max(1, targetGeometry.height);
1212
- const localX = (lastBoardPoint.x - targetGeometry.x) * scaleX;
1213
- const localY = (lastBoardPoint.y - targetGeometry.y) * scaleY;
1214
-
1215
- const correlationId = `hit-${Date.now()}-${Math.random()
1216
- .toString(36)
1217
- .slice(2, 6)}`;
1218
-
1219
- return new Promise((resolve) => {
1220
- const timer = window.setTimeout(() => {
1221
- window.removeEventListener("message", hitListener);
1222
- resolve({});
1223
- }, 50);
1224
-
1225
- const hitListener = (ev: MessageEvent) => {
1226
- if (
1227
- !ev.data ||
1228
- ev.data.type !== "agent-native:hit-test-result" ||
1229
- ev.data.correlationId !== correlationId
1230
- ) {
1231
- return;
1232
- }
1233
- window.clearTimeout(timer);
1234
- window.removeEventListener("message", hitListener);
1235
- resolve({
1236
- anchorNodeId: ev.data.anchorNodeId ?? undefined,
1237
- placement: ev.data.placement ?? undefined,
1238
- });
1239
- };
1240
- window.addEventListener("message", hitListener);
1241
-
1242
- targetContentWindow.postMessage(
1243
- {
1244
- type: "agent-native:hit-test",
1245
- correlationId,
1246
- x: localX,
1247
- y: localY,
1248
- },
1249
- "*",
1250
- );
1251
- });
1252
- };
1253
-
1254
- void runHitTest().then(({ anchorNodeId, placement }) => {
1832
+ if (!hasIdentifier || !sourceScreenId) return;
1833
+ if (!lastBoardPoint) return;
1834
+ const targetCandidate =
1835
+ candidate ??
1836
+ (boardFileId && sourceScreenId !== boardFileId && boardFrameGeometry
1837
+ ? { id: boardFileId, geometry: boardFrameGeometry }
1838
+ : null);
1839
+ if (!targetCandidate) return;
1840
+
1841
+ if (targetCandidate.id === boardFileId) {
1255
1842
  onCrossScreenElementDropRef.current?.({
1256
1843
  sourceSelector: payload.selector,
1257
1844
  sourceNodeId: payload.sourceId,
1258
1845
  sourceScreenId,
1259
- targetScreenId: candidate.id,
1260
- targetAnchorNodeId: anchorNodeId,
1261
- targetAnchorPlacement: placement,
1846
+ targetScreenId: targetCandidate.id,
1847
+ targetCanvasPoint: lastBoardPoint,
1848
+ targetLocalPoint: lastBoardPoint,
1849
+ sourcePointerOffset: payload.sourcePointerOffset,
1262
1850
  });
1263
- });
1851
+ return;
1852
+ }
1853
+
1854
+ void runHitTest(targetCandidate, lastBoardPoint).then(
1855
+ ({ anchorNodeId, placement }) => {
1856
+ const targetAnchorPlacement = isCrossScreenDropPlacement(placement)
1857
+ ? placement
1858
+ : undefined;
1859
+ const targetLocalPoint = getTargetLocalPoint(
1860
+ targetCandidate,
1861
+ lastBoardPoint,
1862
+ );
1863
+ onCrossScreenElementDropRef.current?.({
1864
+ sourceSelector: payload.selector,
1865
+ sourceNodeId: payload.sourceId,
1866
+ sourceScreenId,
1867
+ targetScreenId: targetCandidate.id,
1868
+ targetAnchorNodeId: anchorNodeId,
1869
+ targetAnchorPlacement,
1870
+ targetCanvasPoint: lastBoardPoint,
1871
+ targetLocalPoint: targetLocalPoint ?? undefined,
1872
+ sourcePointerOffset: payload.sourcePointerOffset,
1873
+ });
1874
+ },
1875
+ );
1264
1876
  };
1265
1877
 
1266
1878
  const handleMessage = (event: MessageEvent) => {
@@ -1277,6 +1889,8 @@ export function MultiScreenCanvas({
1277
1889
  iframeY?: number;
1278
1890
  viewportW?: number;
1279
1891
  viewportH?: number;
1892
+ elementRect?: CrossScreenDragElementRect;
1893
+ pointerOffset?: Point;
1280
1894
  };
1281
1895
 
1282
1896
  if (msg.phase === "cancel") {
@@ -1301,9 +1915,13 @@ export function MultiScreenCanvas({
1301
1915
  }
1302
1916
 
1303
1917
  if (msg.phase === "start") {
1918
+ setCrossScreenSourceIsBoard(sourceScreenId === boardFileId);
1304
1919
  crossScreenDragMsgRef.current = {
1305
1920
  selector: msg.selector ?? "",
1306
1921
  sourceId: msg.sourceId,
1922
+ sourcePointerOffset: isFinitePoint(msg.pointerOffset)
1923
+ ? msg.pointerOffset
1924
+ : undefined,
1307
1925
  };
1308
1926
  stopParentCrossScreenDrag();
1309
1927
  const restorePreviewPointerEvents = mutePreviewIframePointerEvents(
@@ -1326,6 +1944,9 @@ export function MultiScreenCanvas({
1326
1944
  const payload = crossScreenDragMsgRef.current ?? {
1327
1945
  selector: msg.selector ?? "",
1328
1946
  sourceId: msg.sourceId,
1947
+ sourcePointerOffset: isFinitePoint(msg.pointerOffset)
1948
+ ? msg.pointerOffset
1949
+ : undefined,
1329
1950
  };
1330
1951
  const lastBoardPoint = crossScreenLastBoardPointRef.current;
1331
1952
  finalizeCrossScreenDrop(
@@ -1372,19 +1993,30 @@ export function MultiScreenCanvas({
1372
1993
  crossScreenDragMsgRef.current = {
1373
1994
  selector: selector ?? "",
1374
1995
  sourceId,
1996
+ sourcePointerOffset: isFinitePoint(msg.pointerOffset)
1997
+ ? msg.pointerOffset
1998
+ : (crossScreenDragMsgRef.current?.sourcePointerOffset ?? undefined),
1375
1999
  };
1376
2000
 
1377
- // Pointer is inside the source iframe — let the bridge handle it.
1378
- if (
2001
+ const pointerInsideSourceIframe =
1379
2002
  iframeX >= 0 &&
1380
2003
  iframeY >= 0 &&
1381
2004
  iframeX <= viewportW &&
1382
- iframeY <= viewportH
1383
- ) {
2005
+ iframeY <= viewportH;
2006
+ const sourceIsBoard = sourceScreenId === boardFileId;
2007
+ // Regular screen iframes are finite artboards, so an in-bounds pointer
2008
+ // means the source bridge should keep handling the drag. The board
2009
+ // iframe is different: it spans the whole overview canvas, including
2010
+ // every screen frame, so board-origin drags must still be checked
2011
+ // against screen drop targets while technically "inside" the source.
2012
+ if (pointerInsideSourceIframe && !sourceIsBoard) {
2013
+ clearCrossScreenPreviewGuide();
1384
2014
  setCrossScreenGhost(null);
1385
2015
  setCrossScreenTarget(null);
2016
+ setCrossScreenSourceIsBoard(false);
1386
2017
  crossScreenTargetRef.current = null;
1387
2018
  crossScreenLastBoardPointRef.current = null;
2019
+ clearCrossScreenDropGuide();
1388
2020
  return;
1389
2021
  }
1390
2022
 
@@ -1428,6 +2060,9 @@ export function MultiScreenCanvas({
1428
2060
  const payload = crossScreenDragMsgRef.current ?? {
1429
2061
  selector: msg.selector ?? "",
1430
2062
  sourceId: msg.sourceId,
2063
+ sourcePointerOffset: isFinitePoint(msg.pointerOffset)
2064
+ ? msg.pointerOffset
2065
+ : undefined,
1431
2066
  };
1432
2067
  const lastBoardPoint = crossScreenLastBoardPointRef.current;
1433
2068
  finalizeCrossScreenDrop(
@@ -1542,6 +2177,110 @@ export function MultiScreenCanvas({
1542
2177
  [],
1543
2178
  );
1544
2179
 
2180
+ const requestSelectableElementInfos = useCallback(
2181
+ (screenId: string): Promise<ElementInfo[]> => {
2182
+ const targetIframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
2183
+ `[data-screen-iframe-id="${CSS.escape(screenId)}"]`,
2184
+ );
2185
+ const targetContentWindow = targetIframe?.contentWindow;
2186
+ if (!targetContentWindow) return Promise.resolve([]);
2187
+ const correlationId = `rects-${Date.now()}-${Math.random()
2188
+ .toString(36)
2189
+ .slice(2, 6)}`;
2190
+ return new Promise((resolve) => {
2191
+ const timer = window.setTimeout(() => {
2192
+ window.removeEventListener("message", listener);
2193
+ resolve([]);
2194
+ }, 80);
2195
+ const listener = (event: MessageEvent) => {
2196
+ if (
2197
+ !event.data ||
2198
+ event.data.type !== "agent-native:selectable-rects-result" ||
2199
+ event.data.correlationId !== correlationId
2200
+ ) {
2201
+ return;
2202
+ }
2203
+ window.clearTimeout(timer);
2204
+ window.removeEventListener("message", listener);
2205
+ const payload: unknown[] = Array.isArray(event.data.payload)
2206
+ ? event.data.payload
2207
+ : [];
2208
+ resolve(
2209
+ payload.filter((item): item is ElementInfo => {
2210
+ if (!item || typeof item !== "object") return false;
2211
+ const candidate = item as Partial<ElementInfo>;
2212
+ return (
2213
+ typeof candidate.tagName === "string" &&
2214
+ !!candidate.boundingRect &&
2215
+ typeof candidate.boundingRect.width === "number" &&
2216
+ typeof candidate.boundingRect.height === "number"
2217
+ );
2218
+ }),
2219
+ );
2220
+ };
2221
+ window.addEventListener("message", listener);
2222
+ targetContentWindow.postMessage(
2223
+ { type: "agent-native:collect-selectable-rects", correlationId },
2224
+ "*",
2225
+ );
2226
+ });
2227
+ },
2228
+ [],
2229
+ );
2230
+
2231
+ const collectLayerMarqueeCandidates = useCallback(async () => {
2232
+ const frameCandidates = await Promise.all(
2233
+ getCurrentFrameEntries().map(async (entry) => {
2234
+ const screen = screensRef.current.find((item) => item.id === entry.id);
2235
+ if (!screen) return [] as CanvasLayerMarqueeCandidate[];
2236
+ const iframe = surfaceRef.current?.querySelector<HTMLIFrameElement>(
2237
+ `[data-screen-iframe-id="${CSS.escape(entry.id)}"]`,
2238
+ );
2239
+ const metadata = getResolvedMetadata(screen);
2240
+ const viewportWidth = iframe?.clientWidth || metadata.width;
2241
+ const viewportHeight = iframe?.clientHeight || metadata.height;
2242
+ const scaleX = entry.geometry.width / Math.max(1, viewportWidth);
2243
+ const scaleY = entry.geometry.height / Math.max(1, viewportHeight);
2244
+ const infos = await requestSelectableElementInfos(entry.id);
2245
+ return infos.map((info) => ({
2246
+ screenId: entry.id,
2247
+ info,
2248
+ geometry: {
2249
+ x: entry.geometry.x + info.boundingRect.x * scaleX,
2250
+ y: entry.geometry.y + info.boundingRect.y * scaleY,
2251
+ width: info.boundingRect.width * scaleX,
2252
+ height: info.boundingRect.height * scaleY,
2253
+ },
2254
+ frameGeometry: entry.geometry,
2255
+ }));
2256
+ }),
2257
+ );
2258
+ const boardCandidates =
2259
+ boardFileId && boardFrameGeometry
2260
+ ? await (async () => {
2261
+ const infos = await requestSelectableElementInfos(boardFileId);
2262
+ return infos.map((info) => ({
2263
+ screenId: boardFileId,
2264
+ info,
2265
+ geometry: {
2266
+ x: boardFrameGeometry.x + info.boundingRect.x,
2267
+ y: boardFrameGeometry.y + info.boundingRect.y,
2268
+ width: info.boundingRect.width,
2269
+ height: info.boundingRect.height,
2270
+ },
2271
+ frameGeometry: boardFrameGeometry,
2272
+ }));
2273
+ })()
2274
+ : [];
2275
+ return [...frameCandidates.flat(), ...boardCandidates];
2276
+ }, [
2277
+ boardFileId,
2278
+ boardFrameGeometry,
2279
+ getCurrentFrameEntries,
2280
+ getResolvedMetadata,
2281
+ requestSelectableElementInfos,
2282
+ ]);
2283
+
1545
2284
  const scheduleFeedbackClear = useCallback(() => {
1546
2285
  if (feedbackTimerRef.current !== null) {
1547
2286
  window.clearTimeout(feedbackTimerRef.current);
@@ -1735,6 +2474,30 @@ export function MultiScreenCanvas({
1735
2474
  e.preventDefault();
1736
2475
  e.stopPropagation();
1737
2476
  const originCanvas = getCanvasPoint(e.clientX, e.clientY);
2477
+ let latestRect = normalizeRectFromPoints(originCanvas, originCanvas);
2478
+ let layerCandidates: CanvasLayerMarqueeCandidate[] = [];
2479
+ const reportLayerSelection = (rect: MarqueeRect) => {
2480
+ const state = dragState.current;
2481
+ if (!state || state.type !== "marquee") return;
2482
+ const selection = layerCandidates
2483
+ .filter((candidate) =>
2484
+ rotatedRectIntersects(
2485
+ rect,
2486
+ getSelectableBounds(candidate.geometry),
2487
+ getFrameCenter(candidate.frameGeometry),
2488
+ candidate.frameGeometry.rotation ?? 0,
2489
+ ),
2490
+ )
2491
+ .map((candidate) => ({
2492
+ screenId: candidate.screenId,
2493
+ info: candidate.info,
2494
+ }));
2495
+ onLayerMarqueeSelectionChange?.(selection, {
2496
+ source: "marquee",
2497
+ additive: state.additive,
2498
+ shiftKey: state.additive,
2499
+ });
2500
+ };
1738
2501
  dragState.current = {
1739
2502
  type: "marquee",
1740
2503
  originClient: { x: e.clientX, y: e.clientY },
@@ -1748,14 +2511,25 @@ export function MultiScreenCanvas({
1748
2511
  if (!e.shiftKey) {
1749
2512
  updateSelectedIds(() => []);
1750
2513
  updateSelectedDraftIds(() => []);
2514
+ onLayerMarqueeSelectionChange?.([], {
2515
+ source: "marquee",
2516
+ additive: false,
2517
+ shiftKey: false,
2518
+ });
1751
2519
  }
1752
2520
  setIsDragging(true);
2521
+ void collectLayerMarqueeCandidates().then((candidates) => {
2522
+ if (dragState.current?.type !== "marquee") return;
2523
+ layerCandidates = candidates;
2524
+ reportLayerSelection(latestRect);
2525
+ });
1753
2526
 
1754
2527
  const handleMouseMove = (ev: MouseEvent) => {
1755
2528
  const state = dragState.current;
1756
2529
  if (!state || state.type !== "marquee") return;
1757
2530
  const nextPoint = getCanvasPoint(ev.clientX, ev.clientY);
1758
2531
  const rect = normalizeRectFromPoints(state.originCanvas, nextPoint);
2532
+ latestRect = rect;
1759
2533
  if (
1760
2534
  !state.hasMoved &&
1761
2535
  Math.hypot(
@@ -1798,6 +2572,7 @@ export function MultiScreenCanvas({
1798
2572
  ? dedupeIds([...state.baseSelectedDraftIds, ...hitDraftIds])
1799
2573
  : hitDraftIds,
1800
2574
  );
2575
+ reportLayerSelection(rect);
1801
2576
  };
1802
2577
 
1803
2578
  const handleMouseUp = () => {
@@ -1805,6 +2580,11 @@ export function MultiScreenCanvas({
1805
2580
  if (state?.type === "marquee" && !state.hasMoved && !state.additive) {
1806
2581
  updateSelectedIds(() => []);
1807
2582
  updateSelectedDraftIds(() => []);
2583
+ onLayerMarqueeSelectionChange?.([], {
2584
+ source: "marquee",
2585
+ additive: false,
2586
+ shiftKey: false,
2587
+ });
1808
2588
  }
1809
2589
  finishDrag();
1810
2590
  };
@@ -1812,11 +2592,13 @@ export function MultiScreenCanvas({
1812
2592
  installDragListeners(handleMouseMove, handleMouseUp);
1813
2593
  },
1814
2594
  [
2595
+ collectLayerMarqueeCandidates,
1815
2596
  finishDrag,
1816
2597
  getCanvasPoint,
1817
2598
  getCurrentDraftEntries,
1818
2599
  getCurrentFrameEntries,
1819
2600
  installDragListeners,
2601
+ onLayerMarqueeSelectionChange,
1820
2602
  updateSelectedDraftIds,
1821
2603
  updateSelectedIds,
1822
2604
  ],
@@ -2355,7 +3137,6 @@ export function MultiScreenCanvas({
2355
3137
  points,
2356
3138
  moved: releaseMoved,
2357
3139
  toolProps,
2358
- fallbackText: t("designEditor.tools.text"),
2359
3140
  });
2360
3141
  commitDraftPrimitive(nextDraft, state.originFrameId);
2361
3142
  if (activeTool === undefined) {
@@ -2383,7 +3164,6 @@ export function MultiScreenCanvas({
2383
3164
  installDragListeners,
2384
3165
  onActiveToolChange,
2385
3166
  onCreateScreenFrame,
2386
- t,
2387
3167
  toolProps,
2388
3168
  ],
2389
3169
  );
@@ -2464,14 +3244,6 @@ export function MultiScreenCanvas({
2464
3244
  }),
2465
3245
  );
2466
3246
  setAlignmentGuides(snap.guides);
2467
- const primary = state.originDrafts[state.primaryId].geometry;
2468
- showTransformFeedback(
2469
- `X ${Math.round(primary.x + dx + snap.dx)} Y ${Math.round(
2470
- primary.y + dy + snap.dy,
2471
- )}`,
2472
- ev.clientX,
2473
- ev.clientY,
2474
- );
2475
3247
 
2476
3248
  // Primitive drop-into-container detection: check if the dragged draft
2477
3249
  // is hovering over a committed container primitive on any screen.
@@ -2574,7 +3346,6 @@ export function MultiScreenCanvas({
2574
3346
  getCurrentCanvasEntries,
2575
3347
  installDragListeners,
2576
3348
  persistDraftPrimitive,
2577
- showTransformFeedback,
2578
3349
  updateDraftPrimitives,
2579
3350
  updatePrimitiveDropTarget,
2580
3351
  updateSelectedDraftIds,
@@ -2816,14 +3587,6 @@ export function MultiScreenCanvas({
2816
3587
  return next;
2817
3588
  });
2818
3589
  setAlignmentGuides(snap.guides);
2819
- const primary = state.originFrames[state.primaryId];
2820
- showTransformFeedback(
2821
- `X ${Math.round(primary.x + dx + snap.dx)} Y ${Math.round(
2822
- primary.y + dy + snap.dy,
2823
- )}`,
2824
- ev.clientX,
2825
- ev.clientY,
2826
- );
2827
3590
 
2828
3591
  // When all dragged ids are committed primitive nodeIds (not screen
2829
3592
  // frames), check for a container primitive drop target to highlight.
@@ -2889,7 +3652,6 @@ export function MultiScreenCanvas({
2889
3652
  getCurrentFrameEntries,
2890
3653
  installDragListeners,
2891
3654
  onPick,
2892
- showTransformFeedback,
2893
3655
  updateFrameGeometry,
2894
3656
  updatePrimitiveDropTarget,
2895
3657
  updateSelectedDraftIds,
@@ -3287,6 +4049,7 @@ export function MultiScreenCanvas({
3287
4049
 
3288
4050
  const handleMouseDown = useCallback(
3289
4051
  (e: React.MouseEvent) => {
4052
+ claimKeyboardFocus();
3290
4053
  // Clear any stale pick-suppression left over from a prior resize/rotate/move
3291
4054
  // gesture that never received its trailing frame click — otherwise it would
3292
4055
  // silently swallow this unrelated interaction.
@@ -3321,6 +4084,7 @@ export function MultiScreenCanvas({
3321
4084
  beginMarquee,
3322
4085
  beginPan,
3323
4086
  beginPenNodeCreation,
4087
+ claimKeyboardFocus,
3324
4088
  localActiveTool,
3325
4089
  ],
3326
4090
  );
@@ -3817,6 +4581,10 @@ export function MultiScreenCanvas({
3817
4581
  const penActive = effectiveTool === "pen";
3818
4582
  const creationToolActive = Boolean(getDraftCreationTool(effectiveTool));
3819
4583
  const canvasGestureActive = isDragging || isPanning;
4584
+ const boardSurfaceInteractive = shouldBoardSurfaceCapturePointerEvents({
4585
+ tool: effectiveTool,
4586
+ gestureActive: canvasGestureActive,
4587
+ });
3820
4588
  const displayedPenPath = penGesturePreview
3821
4589
  ? penGesturePreview
3822
4590
  : activePenPath && penPointer && activePenPath.nodes.length > 0
@@ -3884,10 +4652,14 @@ export function MultiScreenCanvas({
3884
4652
  selectedDraftEntries.length === 1 && !selectedDraftGroupBounds
3885
4653
  ? selectedDraftEntries[0]
3886
4654
  : null;
4655
+ const rootSelectedEntryCount =
4656
+ selectedFrameEntries.length + selectedDraftEntries.length;
4657
+ const showPassiveRootSelectionBoxes = rootSelectedEntryCount > 1;
3887
4658
  return (
3888
4659
  <div
3889
4660
  ref={surfaceRef}
3890
- className="relative h-full w-full select-none overflow-hidden"
4661
+ tabIndex={-1}
4662
+ className="relative h-full w-full select-none overflow-hidden outline-none"
3891
4663
  onMouseDownCapture={handleMouseDown}
3892
4664
  onMouseMove={handleMouseMove}
3893
4665
  style={{ cursor: surfaceCursor, touchAction: "none" }}
@@ -3924,6 +4696,7 @@ export function MultiScreenCanvas({
3924
4696
  >
3925
4697
  {boardFileId &&
3926
4698
  boardFileContent !== undefined &&
4699
+ hasBoardSurfaceContent(boardFileContent) &&
3927
4700
  (() => {
3928
4701
  const boardGeo = boardFrameGeometry ?? {
3929
4702
  x: 0,
@@ -3931,40 +4704,87 @@ export function MultiScreenCanvas({
3931
4704
  width: 8192,
3932
4705
  height: 8192,
3933
4706
  };
3934
- // Clamp board canvas to the DesignCanvas max safe dimension.
3935
- const boardW = Math.min(boardGeo.width, 16384);
3936
- const boardH = Math.min(boardGeo.height, 16384);
4707
+ const boardW = boardGeo.width;
4708
+ const boardH = boardGeo.height;
4709
+ const boardContentKey = getBoardContentKey({
4710
+ boardFileId,
4711
+ boardFileContent,
4712
+ boardIsActive,
4713
+ });
4714
+ const boardRenderContent =
4715
+ getBoardSurfaceRenderContent(boardFileContent);
3937
4716
  return (
3938
4717
  // Overflow-hidden wrapper so the board iframe never bleeds outside
3939
4718
  // its declared logical surface. z-index 0 keeps it below screen
3940
4719
  // iframes (which have their own stacking context above this).
3941
4720
  <div
3942
- style={{
3943
- position: "absolute",
3944
- left: SURFACE_PADDING,
3945
- top: SURFACE_PADDING,
3946
- width: boardW,
3947
- height: boardH,
3948
- overflow: "hidden",
3949
- pointerEvents: "auto",
3950
- // Board sits behind all screen iframes.
3951
- zIndex: 0,
3952
- }}
4721
+ className="[&_.design-canvas-iframe-wrapper]:shadow-none [&_.design-canvas-iframe-wrapper]:ring-0"
4722
+ style={getBoardSurfaceLayerStyle({
4723
+ geometry: boardGeo,
4724
+ interactive: boardSurfaceInteractive,
4725
+ })}
3953
4726
  >
3954
4727
  <DesignCanvas
3955
- content={boardFileContent}
3956
- contentKey={boardFileId}
4728
+ content={boardRenderContent}
4729
+ contentKey={boardContentKey}
4730
+ screenId={boardFileId}
3957
4731
  zoom={100}
3958
4732
  deviceFrame="none"
4733
+ boardSurface
4734
+ embeddedFrameBackground={BOARD_SURFACE_BACKGROUND}
4735
+ embeddedFrame={{
4736
+ viewportWidth: Math.max(1, Math.round(boardW)),
4737
+ viewportHeight: Math.max(1, Math.round(boardH)),
4738
+ displayWidth: Math.max(1, Math.round(boardW)),
4739
+ displayHeight: Math.max(1, Math.round(boardH)),
4740
+ fluid: true,
4741
+ contentOffsetX: -boardGeo.x,
4742
+ contentOffsetY: -boardGeo.y,
4743
+ }}
4744
+ editorChromeScaleX={canvasZoom / 100}
4745
+ editorChromeScaleY={canvasZoom / 100}
3959
4746
  editMode={boardEditMode}
3960
4747
  interactMode={false}
3961
- registerRuntimeBridge={false}
4748
+ scaleMode={boardIsActive && effectiveTool === "scale"}
4749
+ clearSelectionRequest={boardClearSelectionRequest}
4750
+ selectedSelector={
4751
+ boardIsActive ? (boardSelectedSelector ?? null) : null
4752
+ }
4753
+ selectedSelectorCandidates={
4754
+ boardIsActive ? (boardSelectedSelectorCandidates ?? []) : []
4755
+ }
4756
+ selectedSelectorGroups={
4757
+ selectedLayerSelectorGroupsByScreen[boardFileId] ?? []
4758
+ }
4759
+ hoveredSelector={boardHoveredSelector ?? null}
4760
+ hoveredSelectorCandidates={
4761
+ boardHoveredSelectorCandidates ?? []
4762
+ }
4763
+ lockedSelectors={boardLockedSelectors ?? []}
4764
+ hiddenSelectors={boardHiddenSelectors ?? []}
4765
+ // Board owns the global window runtime bridge only when it is
4766
+ // the active surface (activeFileId === boardFileId). This is
4767
+ // the XOR counterpart to the active screen's
4768
+ // registerRuntimeBridge={screenIsActive}: since activeFileId
4769
+ // is exclusive, the board and any screen can never both
4770
+ // register at once, so window.__designCanvas* in-place ops
4771
+ // (delete removal, begin-text-edit) target the board exactly
4772
+ // like a screen. editMode stays always-editable so a board
4773
+ // element can still be clicked to select it before the board
4774
+ // becomes active.
4775
+ registerRuntimeBridge={boardIsActive}
3962
4776
  onElementSelect={onBoardElementSelect ?? (() => {})}
4777
+ onElementMarqueeSelect={onBoardElementMarqueeSelect}
3963
4778
  onElementHover={onBoardElementHover ?? (() => {})}
4779
+ onClearSelection={onBoardElementClear}
4780
+ onIframeHotkey={onBoardIframeHotkey}
4781
+ onIframeContextMenu={onBoardIframeContextMenu}
3964
4782
  onVisualStructureChange={onBoardVisualStructureChange}
3965
4783
  onVisualStyleChange={onBoardVisualStyleChange}
3966
4784
  onVisualDuplicateChange={onBoardVisualDuplicateChange}
3967
4785
  onTextContentChange={onBoardTextContentChange}
4786
+ onTextEditingStateChange={onBoardTextEditingStateChange}
4787
+ onElementDblClickText={onBoardElementDblClickText}
3968
4788
  tweakValues={{}}
3969
4789
  />
3970
4790
  </div>
@@ -4044,6 +4864,28 @@ export function MultiScreenCanvas({
4044
4864
  />
4045
4865
  ) : null}
4046
4866
 
4867
+ {showPassiveRootSelectionBoxes
4868
+ ? selectedFrameEntries.map((entry) => (
4869
+ <PassiveSelectionBox
4870
+ key={`selected-frame-${entry.id}`}
4871
+ geometry={entry.geometry}
4872
+ chromeScale={chromeScale}
4873
+ chromeSettling={chromeSettling}
4874
+ />
4875
+ ))
4876
+ : null}
4877
+
4878
+ {showPassiveRootSelectionBoxes
4879
+ ? selectedDraftEntries.map((entry) => (
4880
+ <PassiveSelectionBox
4881
+ key={`selected-draft-${entry.id}`}
4882
+ geometry={entry.geometry}
4883
+ chromeScale={chromeScale}
4884
+ chromeSettling={chromeSettling}
4885
+ />
4886
+ ))
4887
+ : null}
4888
+
4047
4889
  {displayedPenPath ? (
4048
4890
  <PenPathOverlay path={displayedPenPath} closeHover={penCloseHover} />
4049
4891
  ) : null}
@@ -4181,7 +5023,7 @@ export function MultiScreenCanvas({
4181
5023
  }}
4182
5024
  >
4183
5025
  <div className="flex h-full w-full items-start justify-between rounded-lg bg-muted/20 p-2">
4184
- <span className="max-w-[190px] truncate text-[11px] font-medium text-foreground">
5026
+ <span className="max-w-[190px] truncate !text-[11px] font-medium text-foreground">
4185
5027
  {duplicatePreview.display}
4186
5028
  </span>
4187
5029
  <span className="flex items-center gap-1 rounded-md border border-border bg-background px-1.5 py-0.5 text-[10px] font-medium text-muted-foreground shadow-sm">
@@ -4199,37 +5041,29 @@ export function MultiScreenCanvas({
4199
5041
  {transformBadge ? (
4200
5042
  <div
4201
5043
  data-transform-badge
4202
- className="pointer-events-none fixed z-50 rounded border border-border bg-background/95 px-1.5 py-0.5 font-mono text-[11px] leading-5 text-foreground shadow-lg backdrop-blur"
5044
+ className="pointer-events-none fixed z-50 rounded border border-border bg-background/95 px-1.5 py-0.5 font-mono !text-[11px] leading-5 text-foreground shadow-lg backdrop-blur"
4203
5045
  style={{ left: transformBadge.x, top: transformBadge.y }}
4204
5046
  >
4205
5047
  {transformBadge.text}
4206
5048
  </div>
4207
5049
  ) : null}
4208
5050
 
4209
- {/* Cross-screen element drag: highlight the candidate target screen frame.
4210
- Uses the same style as the primitiveDropTarget overlay (2px accent border
4211
- + 14% accent fill) so it is visually consistent. */}
4212
- {crossScreenTarget ? (
5051
+ {crossScreenDropGuide ? (
4213
5052
  <span
4214
- data-cross-screen-drop-target
4215
- className="pointer-events-none absolute z-40 rounded-sm"
4216
- style={{
4217
- // Surface position = pan + (SURFACE_PADDING + canvasCoord) * scale
4218
- left:
4219
- pan.x + (SURFACE_PADDING + crossScreenTarget.geometry.x) * scale,
4220
- top:
4221
- pan.y + (SURFACE_PADDING + crossScreenTarget.geometry.y) * scale,
4222
- width: Math.max(1, crossScreenTarget.geometry.width * scale),
4223
- height: Math.max(1, crossScreenTarget.geometry.height * scale),
4224
- border: "2px solid var(--design-editor-accent-color)",
4225
- background:
4226
- "color-mix(in srgb, var(--design-editor-accent-color) 14%, transparent)",
4227
- }}
5053
+ data-cross-screen-drop-guide
5054
+ className="pointer-events-none absolute z-50 rounded-sm shadow-[0_0_0_1px_var(--design-editor-accent-contrast-color)]"
5055
+ style={getCrossScreenDropGuideStyle({
5056
+ guide: crossScreenDropGuide,
5057
+ pan,
5058
+ scale,
5059
+ })}
4228
5060
  />
4229
5061
  ) : null}
4230
5062
 
4231
5063
  {/* Cross-screen element drag: small ghost following the board-space cursor. */}
4232
- {crossScreenGhost ? (
5064
+ {crossScreenGhost &&
5065
+ !crossScreenSourceIsBoard &&
5066
+ !crossScreenDropGuide ? (
4233
5067
  <span
4234
5068
  data-cross-screen-drag-ghost
4235
5069
  className="pointer-events-none absolute z-50 rounded border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-accent-color)]/20 shadow"
@@ -4778,7 +5612,7 @@ const Screen = memo(function Screen({
4778
5612
  <span
4779
5613
  data-frame-title
4780
5614
  className={cn(
4781
- "min-w-0 truncate text-[11px] font-medium",
5615
+ "min-w-0 truncate !text-[11px] font-medium",
4782
5616
  emphasized
4783
5617
  ? "text-[var(--design-editor-accent-color)]"
4784
5618
  : activeOrEmphasized
@@ -5124,7 +5958,7 @@ function BreakpointPreviewRow({
5124
5958
  />
5125
5959
  <span
5126
5960
  className={cn(
5127
- "truncate text-[11px] font-medium",
5961
+ "truncate !text-[11px] font-medium",
5128
5962
  isActive
5129
5963
  ? "text-[var(--design-editor-accent-color)]"
5130
5964
  : "text-muted-foreground",
@@ -5232,16 +6066,63 @@ function GroupSelectionBox({
5232
6066
  chromeScale={chromeScale}
5233
6067
  chromeSettling={chromeSettling}
5234
6068
  showRotate={false}
6069
+ filled
5235
6070
  onStartResize={onStartResize}
5236
6071
  onStartRotate={() => {}}
5237
6072
  />
5238
6073
  );
5239
6074
  }
5240
6075
 
6076
+ function PassiveSelectionBox({
6077
+ geometry,
6078
+ chromeScale,
6079
+ chromeSettling,
6080
+ }: {
6081
+ geometry: FrameGeometry;
6082
+ chromeScale: number;
6083
+ chromeSettling: boolean;
6084
+ }) {
6085
+ return (
6086
+ <div
6087
+ data-passive-frame-selection-box
6088
+ className="pointer-events-none absolute border border-[var(--design-editor-accent-color)]"
6089
+ style={{
6090
+ left: SURFACE_PADDING + geometry.x,
6091
+ top: SURFACE_PADDING + geometry.y,
6092
+ width: geometry.width,
6093
+ height: geometry.height,
6094
+ borderRadius: 13 * chromeScale,
6095
+ borderWidth: 1.5 * chromeScale,
6096
+ transition: getSelectionBoxTransition(chromeSettling),
6097
+ transform: geometry.rotation
6098
+ ? `rotate(${geometry.rotation}deg)`
6099
+ : undefined,
6100
+ transformOrigin: `${geometry.width / 2}px ${geometry.height / 2}px`,
6101
+ zIndex: 999_999,
6102
+ }}
6103
+ >
6104
+ {CORNER_RESIZE_HANDLE_CONFIGS.map((config) => (
6105
+ <span
6106
+ key={config.handle}
6107
+ data-passive-resize-handle={config.handle}
6108
+ className="pointer-events-none absolute z-20 rounded-[2px] border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-accent-contrast-color)] shadow"
6109
+ style={cornerHandleStyle(
6110
+ config.handle,
6111
+ config.cursor,
6112
+ chromeScale,
6113
+ chromeSettling,
6114
+ )}
6115
+ />
6116
+ ))}
6117
+ </div>
6118
+ );
6119
+ }
6120
+
5241
6121
  function SelectionBox({
5242
6122
  geometry,
5243
6123
  chromeScale,
5244
6124
  chromeSettling,
6125
+ filled = false,
5245
6126
  showRotate = true,
5246
6127
  onStartResize,
5247
6128
  onStartRotate,
@@ -5249,6 +6130,7 @@ function SelectionBox({
5249
6130
  geometry: FrameGeometry;
5250
6131
  chromeScale: number;
5251
6132
  chromeSettling: boolean;
6133
+ filled?: boolean;
5252
6134
  showRotate?: boolean;
5253
6135
  onStartResize: (handle: ResizeHandle, e: React.MouseEvent) => void;
5254
6136
  onStartRotate: (e: React.MouseEvent) => void;
@@ -5263,6 +6145,9 @@ function SelectionBox({
5263
6145
  top: SURFACE_PADDING + geometry.y,
5264
6146
  width: geometry.width,
5265
6147
  height: geometry.height,
6148
+ background: filled
6149
+ ? "var(--design-editor-selection-color)"
6150
+ : "transparent",
5266
6151
  borderRadius: 13 * chromeScale,
5267
6152
  borderWidth: 1.5 * chromeScale,
5268
6153
  transition: getSelectionBoxTransition(chromeSettling),
@@ -5905,7 +6790,6 @@ function createDraftPrimitive({
5905
6790
  points,
5906
6791
  moved,
5907
6792
  toolProps,
5908
- fallbackText,
5909
6793
  }: DraftPrimitiveInput): DraftPrimitive {
5910
6794
  const id = createDraftId(tool);
5911
6795
  const geometry = moved
@@ -5937,7 +6821,7 @@ function createDraftPrimitive({
5937
6821
  id,
5938
6822
  kind: "text",
5939
6823
  geometry,
5940
- text: toolProps?.text ?? fallbackText,
6824
+ text: toolProps?.text ?? "",
5941
6825
  fill: toolProps?.fill,
5942
6826
  stroke: toolProps?.stroke,
5943
6827
  autoSize: !moved,
@@ -6463,17 +7347,21 @@ export function parsePrimitivesFromScreen(
6463
7347
  // Validity: must have non-zero size
6464
7348
  if (width <= 0 || height <= 0) return;
6465
7349
 
6466
- // Container check:
6467
- // - Must be a div (not svg/path/circle/polygon/etc.)
6468
- // - Must NOT be an ellipse (border-radius:50%)
6469
- // - NOT a text primitive (but text divs could be containers in principle;
6470
- // we exclude them by checking for display:inline-block autoSize pattern)
7350
+ // Only explicit auto-layout primitives should accept drops. Plain absolute
7351
+ // rectangles are visual layers, not flow containers; treating every div as a
7352
+ // container made top-level rectangles disappear into each other on drag.
6471
7353
  const isDiv = tag === "div";
6472
7354
  const isEllipse =
6473
7355
  style.borderRadius === "50%" ||
6474
7356
  style.borderRadius === "50% 50% 50% 50%";
6475
7357
  const isTextAutoSize = style.display === "inline-block";
6476
- const isContainer = isDiv && !isEllipse && !isTextAutoSize;
7358
+ const isAutoLayout =
7359
+ style.display === "flex" ||
7360
+ style.display === "inline-flex" ||
7361
+ style.display === "grid" ||
7362
+ style.display === "inline-grid";
7363
+ const isContainer =
7364
+ isDiv && !isEllipse && !isTextAutoSize && isAutoLayout;
6477
7365
 
6478
7366
  result.push({
6479
7367
  nodeId,