@agent-native/core 0.83.0 → 0.84.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (508) hide show
  1. package/README.md +2 -2
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +34 -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/evals.mdx +34 -12
  7. package/corpus/core/docs/content/faq.mdx +1 -1
  8. package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
  9. package/corpus/core/docs/content/locales/ar-SA/evals.mdx +7 -0
  10. package/corpus/core/docs/content/locales/ar-SA/faq.mdx +1 -1
  11. package/corpus/core/docs/content/locales/ar-SA/template-chat.mdx +1 -1
  12. package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
  13. package/corpus/core/docs/content/locales/de-DE/evals.mdx +8 -0
  14. package/corpus/core/docs/content/locales/de-DE/faq.mdx +1 -1
  15. package/corpus/core/docs/content/locales/de-DE/template-chat.mdx +1 -1
  16. package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
  17. package/corpus/core/docs/content/locales/es-ES/evals.mdx +8 -0
  18. package/corpus/core/docs/content/locales/es-ES/faq.mdx +1 -1
  19. package/corpus/core/docs/content/locales/es-ES/template-chat.mdx +1 -1
  20. package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  21. package/corpus/core/docs/content/locales/fr-FR/evals.mdx +8 -0
  22. package/corpus/core/docs/content/locales/fr-FR/faq.mdx +1 -1
  23. package/corpus/core/docs/content/locales/fr-FR/template-chat.mdx +1 -1
  24. package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
  25. package/corpus/core/docs/content/locales/hi-IN/evals.mdx +8 -0
  26. package/corpus/core/docs/content/locales/hi-IN/faq.mdx +1 -1
  27. package/corpus/core/docs/content/locales/hi-IN/template-chat.mdx +1 -1
  28. package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  29. package/corpus/core/docs/content/locales/ja-JP/evals.mdx +7 -0
  30. package/corpus/core/docs/content/locales/ja-JP/faq.mdx +1 -1
  31. package/corpus/core/docs/content/locales/ja-JP/template-chat.mdx +1 -1
  32. package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
  33. package/corpus/core/docs/content/locales/ko-KR/evals.mdx +7 -0
  34. package/corpus/core/docs/content/locales/ko-KR/faq.mdx +1 -1
  35. package/corpus/core/docs/content/locales/ko-KR/template-chat.mdx +1 -1
  36. package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
  37. package/corpus/core/docs/content/locales/pt-BR/evals.mdx +8 -0
  38. package/corpus/core/docs/content/locales/pt-BR/faq.mdx +1 -1
  39. package/corpus/core/docs/content/locales/pt-BR/template-chat.mdx +1 -1
  40. package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
  41. package/corpus/core/docs/content/locales/zh-CN/evals.mdx +7 -0
  42. package/corpus/core/docs/content/locales/zh-CN/faq.mdx +1 -1
  43. package/corpus/core/docs/content/locales/zh-CN/template-chat.mdx +1 -1
  44. package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
  45. package/corpus/core/docs/content/locales/zh-TW/evals.mdx +7 -0
  46. package/corpus/core/docs/content/locales/zh-TW/faq.mdx +1 -1
  47. package/corpus/core/docs/content/locales/zh-TW/template-chat.mdx +1 -1
  48. package/corpus/core/docs/content/template-chat.mdx +1 -1
  49. package/corpus/core/package.json +1 -1
  50. package/corpus/core/src/agent/engine/builtin.ts +1 -1
  51. package/corpus/core/src/agent/engine/output-tokens.ts +1 -1
  52. package/corpus/core/src/agent/harness/acp-adapter.ts +28 -4
  53. package/corpus/core/src/agent/harness/ai-sdk-adapter.ts +3 -0
  54. package/corpus/core/src/agent/harness/translate.ts +3 -0
  55. package/corpus/core/src/agent/harness/types.ts +1 -0
  56. package/corpus/core/src/agent/model-config.ts +34 -13
  57. package/corpus/core/src/agent/production-agent.ts +87 -10
  58. package/corpus/core/src/agent/thread-data-builder.ts +29 -4
  59. package/corpus/core/src/agent/tool-call-journal.ts +109 -48
  60. package/corpus/core/src/agent/types.ts +6 -1
  61. package/corpus/core/src/cli/design-connect.ts +106 -0
  62. package/corpus/core/src/cli/gateway-helpers.ts +17 -0
  63. package/corpus/core/src/cli/recap.ts +0 -2
  64. package/corpus/core/src/cli/skills.ts +54 -16
  65. package/corpus/core/src/cli/workspace-dev.ts +29 -5
  66. package/corpus/core/src/client/AssistantChat.tsx +89 -42
  67. package/corpus/core/src/client/DefaultSpinner.tsx +8 -1
  68. package/corpus/core/src/client/chat/message-components.tsx +49 -1
  69. package/corpus/core/src/client/chat/repo-helpers.ts +71 -0
  70. package/corpus/core/src/client/chat/run-recovery.tsx +15 -5
  71. package/corpus/core/src/client/components/ApiKeySettings.tsx +8 -8
  72. package/corpus/core/src/client/components/CodeRequiredDialog.tsx +26 -15
  73. package/corpus/core/src/client/components/MissingKeyCard.tsx +13 -6
  74. package/corpus/core/src/client/composer/TiptapComposer.tsx +4 -3
  75. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +0 -1
  76. package/corpus/core/src/client/guided-questions.tsx +21 -27
  77. package/corpus/core/src/client/integrations/IntegrationCard.tsx +1 -1
  78. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +1 -1
  79. package/corpus/core/src/client/onboarding/OnboardingBanner.tsx +2 -2
  80. package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +23 -19
  81. package/corpus/core/src/client/resources/ResourceEditor.tsx +18 -10
  82. package/corpus/core/src/client/resources/ResourcesPanel.tsx +5 -1
  83. package/corpus/core/src/client/settings/SettingsPanel.tsx +8 -3
  84. package/corpus/core/src/client/settings/useBuilderStatus.ts +8 -41
  85. package/corpus/core/src/client/useProductionAgent.ts +6 -2
  86. package/corpus/core/src/deploy/build.ts +1 -1
  87. package/corpus/core/src/eval/define-eval.ts +4 -1
  88. package/corpus/core/src/eval/report.ts +23 -8
  89. package/corpus/core/src/eval/runner.ts +41 -6
  90. package/corpus/core/src/eval/types.ts +11 -0
  91. package/corpus/core/src/observability/traces.ts +3 -3
  92. package/corpus/core/src/onboarding/default-steps.ts +1 -1
  93. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  94. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  95. package/corpus/core/src/server/agent-chat-plugin.ts +177 -81
  96. package/corpus/core/src/server/core-routes-plugin.ts +7 -1
  97. package/corpus/core/src/server/open-route.ts +59 -40
  98. package/corpus/core/src/shared/reasoning-effort.ts +2 -0
  99. package/corpus/core/src/sharing/access.ts +22 -2
  100. package/corpus/core/src/sharing/registry.ts +18 -0
  101. package/corpus/core/src/styles/agent-native.css +104 -20
  102. package/corpus/core/src/styles/rich-markdown-editor.css +11 -10
  103. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +10 -10
  104. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +10 -10
  105. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +6 -5
  106. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +9 -9
  107. package/corpus/templates/analytics/app/global.css +4 -0
  108. package/corpus/templates/analytics/app/lib/chart-theme.ts +13 -0
  109. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +22 -24
  110. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-charts-now-use-theme-aware-tooltip-and-grid-colors.md +6 -0
  111. package/corpus/templates/analytics/changelog/2026-06-30-fixed-ask-composer-spacing-so-the-chat-field-has-a-comfortab.md +6 -0
  112. package/corpus/templates/calendar/AGENTS.md +4 -0
  113. package/corpus/templates/calendar/actions/connect-google-calendar.ts +80 -0
  114. package/corpus/templates/calendar/changelog/2026-06-30-calendar-now-gives-the-agent-a-safe-google-connection-link-i.md +6 -0
  115. package/corpus/templates/calendar/server/plugins/agent-chat.ts +4 -0
  116. package/corpus/templates/content/AGENTS.md +9 -2
  117. package/corpus/templates/content/actions/_database-row-batch.ts +230 -0
  118. package/corpus/templates/content/actions/_database-utils.ts +17 -9
  119. package/corpus/templates/content/actions/_notion-action-utils.ts +26 -0
  120. package/corpus/templates/content/actions/connect-notion-status.ts +13 -4
  121. package/corpus/templates/content/actions/create-and-link-notion-page.ts +23 -0
  122. package/corpus/templates/content/actions/delete-database-items.ts +55 -0
  123. package/corpus/templates/content/actions/delete-document.ts +4 -4
  124. package/corpus/templates/content/actions/disconnect-notion.ts +16 -0
  125. package/corpus/templates/content/actions/duplicate-database-item.ts +1 -1
  126. package/corpus/templates/content/actions/duplicate-database-items.ts +191 -0
  127. package/corpus/templates/content/actions/link-notion-page.ts +10 -7
  128. package/corpus/templates/content/actions/list-notion-links.ts +3 -4
  129. package/corpus/templates/content/actions/pull-notion-page.ts +7 -9
  130. package/corpus/templates/content/actions/push-notion-page.ts +7 -9
  131. package/corpus/templates/content/actions/refresh-notion-sync-status.ts +24 -0
  132. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +23 -0
  133. package/corpus/templates/content/actions/search-notion-pages.ts +67 -0
  134. package/corpus/templates/content/actions/unlink-notion-page.ts +23 -0
  135. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -5
  136. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +27 -34
  137. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +16 -13
  138. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +10 -10
  139. package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
  140. package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +18 -9
  141. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +27 -34
  142. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +10 -4
  143. package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +32 -17
  144. package/corpus/templates/content/app/global.css +6 -5
  145. package/corpus/templates/content/app/hooks/use-content-database.ts +35 -0
  146. package/corpus/templates/content/app/hooks/use-notion.ts +108 -99
  147. package/corpus/templates/content/changelog/2026-06-30-editor-floating-toolbars-now-use-theme-aware-colors-in-light.md +6 -0
  148. package/corpus/templates/content/package.json +3 -0
  149. package/corpus/templates/content/parity/README.md +69 -0
  150. package/corpus/templates/content/parity/eval-scenarios.ts +118 -0
  151. package/corpus/templates/content/parity/exceptions.allowlist.ts +17 -0
  152. package/corpus/templates/content/parity/matrix.md +28 -0
  153. package/corpus/templates/content/parity/matrix.ts +606 -0
  154. package/corpus/templates/content/parity/matrix.types.ts +42 -0
  155. package/corpus/templates/content/parity/parity-evals.eval.ts +4 -0
  156. package/corpus/templates/content/parity/render-matrix.ts +90 -0
  157. package/corpus/templates/content/parity/scenario-to-eval.ts +57 -0
  158. package/corpus/templates/content/shared/api.ts +11 -0
  159. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +6 -1
  160. package/corpus/templates/design/actions/add-localhost-screens.ts +63 -8
  161. package/corpus/templates/design/actions/apply-motion-edit.ts +12 -46
  162. package/corpus/templates/design/actions/list-design-native-assets.ts +2 -1
  163. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +49 -1
  164. package/corpus/templates/design/actions/navigate.ts +3 -12
  165. package/corpus/templates/design/actions/open-visual-edit.ts +341 -0
  166. package/corpus/templates/design/actions/view-screen.ts +1 -1
  167. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +1 -1
  168. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +345 -14
  169. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +71 -91
  170. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +25 -27
  171. package/corpus/templates/design/app/components/design/DeviceFrame.tsx +1 -1
  172. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +2 -2
  173. package/corpus/templates/design/app/components/design/EditPanel.tsx +1111 -570
  174. package/corpus/templates/design/app/components/design/LayersPanel.tsx +162 -32
  175. package/corpus/templates/design/app/components/design/MotionDock.tsx +268 -240
  176. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1038 -150
  177. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +1 -1
  178. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +81 -102
  179. package/corpus/templates/design/app/components/design/StatesPanel.tsx +6 -6
  180. package/corpus/templates/design/app/components/design/TokensPanel.tsx +11 -11
  181. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -7
  182. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1129 -155
  183. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +130 -37
  184. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +24 -11
  185. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +184 -162
  186. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +137 -79
  187. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +13 -13
  188. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +8 -8
  189. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
  190. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +4 -4
  191. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +3 -3
  192. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -7
  193. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +14 -11
  194. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +4 -4
  195. package/corpus/templates/design/app/components/design/inspector/index.ts +2 -0
  196. package/corpus/templates/design/app/components/design/types.ts +19 -0
  197. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +61 -78
  198. package/corpus/templates/design/app/components/layout/Header.tsx +0 -1
  199. package/corpus/templates/design/app/components/layout/Layout.tsx +3 -2
  200. package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
  201. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +5 -4
  202. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  203. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +2 -2
  204. package/corpus/templates/design/app/global.css +56 -0
  205. package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -5
  206. package/corpus/templates/design/app/hooks/use-navigation-state.ts +6 -7
  207. package/corpus/templates/design/app/hooks/use-question-flow.ts +3 -2
  208. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +6 -4
  209. package/corpus/templates/design/app/i18n/zh-TW.ts +5 -14
  210. package/corpus/templates/design/app/i18n-data.ts +63 -188
  211. package/corpus/templates/design/app/lib/agent-chat.ts +13 -0
  212. package/corpus/templates/design/app/pages/DesignEditor.tsx +1428 -384
  213. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -5
  214. package/corpus/templates/design/app/pages/DesignSystems.tsx +2 -2
  215. package/corpus/templates/design/app/pages/Index.tsx +49 -26
  216. package/corpus/templates/design/app/pages/NotFound.tsx +25 -8
  217. package/corpus/templates/design/app/pages/VisualEdit.tsx +17 -5
  218. package/corpus/templates/design/app/routes/examples.tsx +1 -7
  219. package/corpus/templates/design/app/routes/templates.tsx +7 -4
  220. package/corpus/templates/design/changelog/2026-06-30-board-shapes-and-auto-layout-controls-are-clearer.md +6 -0
  221. package/corpus/templates/design/changelog/2026-06-30-component-selections-now-use-purple-component-chrome-and-sho.md +6 -0
  222. package/corpus/templates/design/changelog/2026-06-30-constraints-now-stay-tucked-into-a-compact-inspector-preview.md +6 -0
  223. package/corpus/templates/design/changelog/2026-06-30-creating-a-component-now-opens-in-a-compact-inspector-popove.md +6 -0
  224. package/corpus/templates/design/changelog/2026-06-30-design-cards-no-longer-show-legacy-project-type-badges.md +6 -0
  225. package/corpus/templates/design/changelog/2026-06-30-design-chat-now-keeps-new-turns-in-the-visible-design-agent-.md +6 -0
  226. package/corpus/templates/design/changelog/2026-06-30-design-editor-panels-now-keep-their-compact-text-sizing-cons.md +6 -0
  227. package/corpus/templates/design/changelog/2026-06-30-design-no-longer-shows-the-connected-local-code-warning-bann.md +6 -0
  228. package/corpus/templates/design/changelog/2026-06-30-design-now-opens-to-the-files-panel-unless-a-generation-is-a.md +6 -0
  229. package/corpus/templates/design/changelog/2026-06-30-design-opens-with-a-cleaner-empty-canvas-and-loading-preview.md +6 -0
  230. package/corpus/templates/design/changelog/2026-06-30-design-tools-use-simpler-less-decorative-icons.md +6 -0
  231. package/corpus/templates/design/changelog/2026-06-30-dragging-absolute-elements-no-longer-shows-coordinate-labels.md +6 -0
  232. package/corpus/templates/design/changelog/2026-06-30-fixed-all-screens-opening-too-zoomed-in-by-default.md +6 -0
  233. package/corpus/templates/design/changelog/2026-06-30-fixed-the-public-design-assets-sidebar-so-native-components-.md +6 -0
  234. package/corpus/templates/design/changelog/2026-06-30-fixed-the-screen-overview-board-so-empty-board-space-no-long.md +6 -0
  235. package/corpus/templates/design/changelog/2026-06-30-full-view-screens-can-now-scroll-while-remaining-editable.md +6 -0
  236. package/corpus/templates/design/changelog/2026-06-30-inspector-element-headers-show-the-selected-html-tag-and-the.md +6 -0
  237. package/corpus/templates/design/changelog/2026-06-30-inspector-inputs-stay-compact-across-desktop-breakpoints.md +6 -0
  238. package/corpus/templates/design/changelog/2026-06-30-layers-can-be-multi-selected-from-the-canvas-and-edited-toge.md +6 -0
  239. package/corpus/templates/design/changelog/2026-06-30-local-visual-edit-links-can-edit-localhost-frames-without-si.md +6 -0
  240. package/corpus/templates/design/changelog/2026-06-30-localhost-visual-edit-frames-can-now-be-selected-and-edited.md +6 -0
  241. package/corpus/templates/design/changelog/2026-06-30-marquee-selection-now-works-inside-screens-multi-selected-la.md +6 -0
  242. package/corpus/templates/design/changelog/2026-06-30-motion-is-now-available-from-the-persistent-left-rail-and-th.md +6 -0
  243. package/corpus/templates/design/changelog/2026-06-30-motion-timeline-collapse-and-reopen-animations-feel-smoother.md +6 -0
  244. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +6 -0
  245. package/corpus/templates/design/changelog/2026-06-30-new-board-shapes-now-start-with-neutral-gray-styling.md +6 -0
  246. package/corpus/templates/design/changelog/2026-06-30-new-designs-open-into-a-steadier-loading-state-before-the-ed.md +6 -0
  247. package/corpus/templates/design/changelog/2026-06-30-new-designs-show-a-loading-state-during-handoff-and-keep-edi.md +6 -0
  248. package/corpus/templates/design/changelog/2026-06-30-public-visual-edit-design-links-now-open-directly-instead-of.md +6 -0
  249. package/corpus/templates/design/changelog/2026-06-30-removed-the-confusing-states-section-from-the-design-editor-.md +6 -0
  250. package/corpus/templates/design/changelog/2026-06-30-removed-the-templates-gallery-from-design-navigation.md +6 -0
  251. package/corpus/templates/design/changelog/2026-06-30-share-stays-visible-as-soon-as-editable-designs-load.md +6 -0
  252. package/corpus/templates/design/changelog/2026-06-30-signed-out-save-and-share-controls-use-simpler-labels-and-op.md +6 -0
  253. package/corpus/templates/design/changelog/2026-06-30-text-added-on-the-canvas-now-focuses-cleanly-with-the-right-.md +6 -0
  254. package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-is-simpler-and-its-bottom.md +6 -0
  255. package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-now-follows-the-current-t.md +6 -0
  256. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-child-layout-controls-o.md +6 -0
  257. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-the-ask-ai-section-abov.md +6 -0
  258. package/corpus/templates/design/changelog/2026-06-30-the-design-review-panel-no-longer-shows-a-separate-inline-au.md +6 -0
  259. package/corpus/templates/design/changelog/2026-06-30-the-empty-designs-page-now-focuses-on-a-single-primary-new-d.md +6 -0
  260. package/corpus/templates/design/changelog/2026-06-30-the-layers-panel-header-stays-as-layers-while-multiple-items.md +6 -0
  261. package/corpus/templates/design/changelog/2026-06-30-the-missing-design-screen-now-uses-a-neutral-back-button.md +6 -0
  262. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-now-closes-without-squeezing-the-canvas-.md +6 -0
  263. package/corpus/templates/design/changelog/2026-06-30-the-new-design-connect-ai-setup-now-lines-up-with-the-compos.md +6 -0
  264. package/corpus/templates/design/changelog/2026-06-30-the-screen-overview-board-now-blends-into-the-canvas-and-new.md +6 -0
  265. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-drops-now-show-screen-guides.md +6 -0
  266. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-keep-a-transparent-backdrop-and-.md +6 -0
  267. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-select-reliably-from-all-screens.md +6 -0
  268. package/corpus/templates/design/changelog/2026-06-30-visual-edit-can-open-and-refresh-localhost-screens-in-one-au.md +6 -0
  269. package/corpus/templates/design/e2e/helpers.ts +15 -4
  270. package/corpus/templates/design/server/db/index.ts +52 -0
  271. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -0
  272. package/corpus/templates/design/server/plugins/auth.ts +1 -0
  273. package/corpus/templates/design/server/plugins/core-routes.ts +4 -0
  274. package/corpus/templates/design/shared/board-file.ts +380 -5
  275. package/corpus/templates/design/shared/board-objects.ts +2 -2
  276. package/corpus/templates/design/shared/code-layer.ts +50 -6
  277. package/corpus/templates/design/shared/motion-compiler.ts +27 -0
  278. package/corpus/templates/macros/app/components/DailyProgress.tsx +41 -37
  279. package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
  280. package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
  281. package/corpus/templates/macros/app/components/VoiceDictation.tsx +1 -1
  282. package/corpus/templates/macros/app/components/WeeklyCaloriesChart.tsx +1 -1
  283. package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
  284. package/corpus/templates/macros/app/components/WeightTracker.tsx +1 -1
  285. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -1
  286. package/corpus/templates/macros/app/routes/_index.tsx +5 -5
  287. package/corpus/templates/macros/app/routes/analytics.tsx +1 -1
  288. package/corpus/templates/macros/changelog/2026-06-30-dashboard-loading-and-chart-surfaces-now-use-theme-aware-col.md +6 -0
  289. package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +14 -11
  290. package/corpus/templates/mail/app/global.css +3 -2
  291. package/corpus/templates/mail/changelog/2026-06-30-compose-floating-toolbars-now-use-theme-aware-colors-in-ligh.md +6 -0
  292. package/corpus/templates/plan/app/pages/PlansPage.tsx +2 -2
  293. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +3 -3
  294. package/corpus/templates/slides/app/global.css +10 -0
  295. package/corpus/templates/slides/app/pages/Index.tsx +55 -51
  296. package/corpus/templates/slides/changelog/2026-06-30-deck-thumbnails-collapse-cleanly-to-one-column-when-the-chat.md +6 -0
  297. package/dist/agent/engine/anthropic-engine.d.ts +2 -2
  298. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  299. package/dist/agent/engine/builder-engine.d.ts +2 -2
  300. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  301. package/dist/agent/engine/builtin.js +1 -1
  302. package/dist/agent/engine/builtin.js.map +1 -1
  303. package/dist/agent/engine/output-tokens.js +1 -1
  304. package/dist/agent/engine/output-tokens.js.map +1 -1
  305. package/dist/agent/harness/acp-adapter.d.ts +4 -1
  306. package/dist/agent/harness/acp-adapter.d.ts.map +1 -1
  307. package/dist/agent/harness/acp-adapter.js +19 -2
  308. package/dist/agent/harness/acp-adapter.js.map +1 -1
  309. package/dist/agent/harness/ai-sdk-adapter.d.ts.map +1 -1
  310. package/dist/agent/harness/ai-sdk-adapter.js +3 -0
  311. package/dist/agent/harness/ai-sdk-adapter.js.map +1 -1
  312. package/dist/agent/harness/translate.d.ts.map +1 -1
  313. package/dist/agent/harness/translate.js +3 -0
  314. package/dist/agent/harness/translate.js.map +1 -1
  315. package/dist/agent/harness/types.d.ts +1 -0
  316. package/dist/agent/harness/types.d.ts.map +1 -1
  317. package/dist/agent/harness/types.js.map +1 -1
  318. package/dist/agent/model-config.d.ts +21 -20
  319. package/dist/agent/model-config.d.ts.map +1 -1
  320. package/dist/agent/model-config.js +31 -13
  321. package/dist/agent/model-config.js.map +1 -1
  322. package/dist/agent/production-agent.d.ts.map +1 -1
  323. package/dist/agent/production-agent.js +85 -10
  324. package/dist/agent/production-agent.js.map +1 -1
  325. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  326. package/dist/agent/thread-data-builder.js +27 -6
  327. package/dist/agent/thread-data-builder.js.map +1 -1
  328. package/dist/agent/tool-call-journal.d.ts +11 -9
  329. package/dist/agent/tool-call-journal.d.ts.map +1 -1
  330. package/dist/agent/tool-call-journal.js +92 -40
  331. package/dist/agent/tool-call-journal.js.map +1 -1
  332. package/dist/agent/types.d.ts +5 -1
  333. package/dist/agent/types.d.ts.map +1 -1
  334. package/dist/agent/types.js.map +1 -1
  335. package/dist/cli/design-connect.d.ts.map +1 -1
  336. package/dist/cli/design-connect.js +84 -0
  337. package/dist/cli/design-connect.js.map +1 -1
  338. package/dist/cli/gateway-helpers.d.ts +4 -0
  339. package/dist/cli/gateway-helpers.d.ts.map +1 -1
  340. package/dist/cli/gateway-helpers.js +9 -0
  341. package/dist/cli/gateway-helpers.js.map +1 -1
  342. package/dist/cli/recap.d.ts.map +1 -1
  343. package/dist/cli/recap.js +0 -2
  344. package/dist/cli/recap.js.map +1 -1
  345. package/dist/cli/skills.d.ts.map +1 -1
  346. package/dist/cli/skills.js +54 -16
  347. package/dist/cli/skills.js.map +1 -1
  348. package/dist/cli/workspace-dev.d.ts.map +1 -1
  349. package/dist/cli/workspace-dev.js +33 -6
  350. package/dist/cli/workspace-dev.js.map +1 -1
  351. package/dist/client/AssistantChat.d.ts.map +1 -1
  352. package/dist/client/AssistantChat.js +72 -26
  353. package/dist/client/AssistantChat.js.map +1 -1
  354. package/dist/client/DefaultSpinner.d.ts.map +1 -1
  355. package/dist/client/DefaultSpinner.js +8 -1
  356. package/dist/client/DefaultSpinner.js.map +1 -1
  357. package/dist/client/chat/message-components.d.ts +7 -1
  358. package/dist/client/chat/message-components.d.ts.map +1 -1
  359. package/dist/client/chat/message-components.js +37 -1
  360. package/dist/client/chat/message-components.js.map +1 -1
  361. package/dist/client/chat/repo-helpers.d.ts +1 -0
  362. package/dist/client/chat/repo-helpers.d.ts.map +1 -1
  363. package/dist/client/chat/repo-helpers.js +59 -0
  364. package/dist/client/chat/repo-helpers.js.map +1 -1
  365. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  366. package/dist/client/chat/run-recovery.js +4 -4
  367. package/dist/client/chat/run-recovery.js.map +1 -1
  368. package/dist/client/components/ApiKeySettings.js +8 -8
  369. package/dist/client/components/ApiKeySettings.js.map +1 -1
  370. package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
  371. package/dist/client/components/CodeRequiredDialog.js +24 -15
  372. package/dist/client/components/CodeRequiredDialog.js.map +1 -1
  373. package/dist/client/components/MissingKeyCard.d.ts.map +1 -1
  374. package/dist/client/components/MissingKeyCard.js +11 -6
  375. package/dist/client/components/MissingKeyCard.js.map +1 -1
  376. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  377. package/dist/client/composer/TiptapComposer.js +4 -3
  378. package/dist/client/composer/TiptapComposer.js.map +1 -1
  379. package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
  380. package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
  381. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  382. package/dist/client/guided-questions.d.ts.map +1 -1
  383. package/dist/client/guided-questions.js +6 -6
  384. package/dist/client/guided-questions.js.map +1 -1
  385. package/dist/client/integrations/IntegrationCard.js +1 -1
  386. package/dist/client/integrations/IntegrationCard.js.map +1 -1
  387. package/dist/client/integrations/IntegrationsPanel.js +1 -1
  388. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  389. package/dist/client/onboarding/OnboardingBanner.js +2 -2
  390. package/dist/client/onboarding/OnboardingBanner.js.map +1 -1
  391. package/dist/client/onboarding/OnboardingPanel.js +23 -19
  392. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  393. package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
  394. package/dist/client/resources/ResourceEditor.js +13 -10
  395. package/dist/client/resources/ResourceEditor.js.map +1 -1
  396. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  397. package/dist/client/resources/ResourcesPanel.js +2 -1
  398. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  399. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  400. package/dist/client/settings/SettingsPanel.js +8 -3
  401. package/dist/client/settings/SettingsPanel.js.map +1 -1
  402. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  403. package/dist/client/settings/useBuilderStatus.js +6 -38
  404. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  405. package/dist/client/useProductionAgent.d.ts +2 -1
  406. package/dist/client/useProductionAgent.d.ts.map +1 -1
  407. package/dist/client/useProductionAgent.js.map +1 -1
  408. package/dist/collab/awareness.d.ts +2 -2
  409. package/dist/collab/awareness.d.ts.map +1 -1
  410. package/dist/collab/routes.d.ts +1 -1
  411. package/dist/deploy/build.js +1 -1
  412. package/dist/deploy/build.js.map +1 -1
  413. package/dist/eval/define-eval.d.ts.map +1 -1
  414. package/dist/eval/define-eval.js +2 -1
  415. package/dist/eval/define-eval.js.map +1 -1
  416. package/dist/eval/report.d.ts.map +1 -1
  417. package/dist/eval/report.js +19 -4
  418. package/dist/eval/report.js.map +1 -1
  419. package/dist/eval/runner.d.ts.map +1 -1
  420. package/dist/eval/runner.js +38 -6
  421. package/dist/eval/runner.js.map +1 -1
  422. package/dist/eval/types.d.ts +11 -0
  423. package/dist/eval/types.d.ts.map +1 -1
  424. package/dist/eval/types.js.map +1 -1
  425. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  426. package/dist/notifications/routes.d.ts +3 -3
  427. package/dist/observability/routes.d.ts +5 -5
  428. package/dist/observability/traces.d.ts.map +1 -1
  429. package/dist/observability/traces.js.map +1 -1
  430. package/dist/onboarding/default-steps.js +1 -1
  431. package/dist/onboarding/default-steps.js.map +1 -1
  432. package/dist/progress/routes.d.ts +1 -1
  433. package/dist/resources/handlers.d.ts +1 -1
  434. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  435. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  436. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  437. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  438. package/dist/server/agent-chat-plugin.d.ts +3 -0
  439. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  440. package/dist/server/agent-chat-plugin.js +134 -72
  441. package/dist/server/agent-chat-plugin.js.map +1 -1
  442. package/dist/server/core-routes-plugin.d.ts +3 -0
  443. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  444. package/dist/server/core-routes-plugin.js +4 -1
  445. package/dist/server/core-routes-plugin.js.map +1 -1
  446. package/dist/server/open-route.d.ts +9 -0
  447. package/dist/server/open-route.d.ts.map +1 -1
  448. package/dist/server/open-route.js +48 -38
  449. package/dist/server/open-route.js.map +1 -1
  450. package/dist/shared/reasoning-effort.js +3 -0
  451. package/dist/shared/reasoning-effort.js.map +1 -1
  452. package/dist/sharing/access.d.ts.map +1 -1
  453. package/dist/sharing/access.js +18 -2
  454. package/dist/sharing/access.js.map +1 -1
  455. package/dist/sharing/registry.d.ts +10 -0
  456. package/dist/sharing/registry.d.ts.map +1 -1
  457. package/dist/sharing/registry.js.map +1 -1
  458. package/dist/styles/agent-native.css +104 -20
  459. package/dist/styles/rich-markdown-editor.css +11 -10
  460. package/docs/content/cloneable-saas.mdx +2 -2
  461. package/docs/content/evals.mdx +34 -12
  462. package/docs/content/faq.mdx +1 -1
  463. package/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
  464. package/docs/content/locales/ar-SA/evals.mdx +7 -0
  465. package/docs/content/locales/ar-SA/faq.mdx +1 -1
  466. package/docs/content/locales/ar-SA/template-chat.mdx +1 -1
  467. package/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
  468. package/docs/content/locales/de-DE/evals.mdx +8 -0
  469. package/docs/content/locales/de-DE/faq.mdx +1 -1
  470. package/docs/content/locales/de-DE/template-chat.mdx +1 -1
  471. package/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
  472. package/docs/content/locales/es-ES/evals.mdx +8 -0
  473. package/docs/content/locales/es-ES/faq.mdx +1 -1
  474. package/docs/content/locales/es-ES/template-chat.mdx +1 -1
  475. package/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  476. package/docs/content/locales/fr-FR/evals.mdx +8 -0
  477. package/docs/content/locales/fr-FR/faq.mdx +1 -1
  478. package/docs/content/locales/fr-FR/template-chat.mdx +1 -1
  479. package/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
  480. package/docs/content/locales/hi-IN/evals.mdx +8 -0
  481. package/docs/content/locales/hi-IN/faq.mdx +1 -1
  482. package/docs/content/locales/hi-IN/template-chat.mdx +1 -1
  483. package/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  484. package/docs/content/locales/ja-JP/evals.mdx +7 -0
  485. package/docs/content/locales/ja-JP/faq.mdx +1 -1
  486. package/docs/content/locales/ja-JP/template-chat.mdx +1 -1
  487. package/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
  488. package/docs/content/locales/ko-KR/evals.mdx +7 -0
  489. package/docs/content/locales/ko-KR/faq.mdx +1 -1
  490. package/docs/content/locales/ko-KR/template-chat.mdx +1 -1
  491. package/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
  492. package/docs/content/locales/pt-BR/evals.mdx +8 -0
  493. package/docs/content/locales/pt-BR/faq.mdx +1 -1
  494. package/docs/content/locales/pt-BR/template-chat.mdx +1 -1
  495. package/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
  496. package/docs/content/locales/zh-CN/evals.mdx +7 -0
  497. package/docs/content/locales/zh-CN/faq.mdx +1 -1
  498. package/docs/content/locales/zh-CN/template-chat.mdx +1 -1
  499. package/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
  500. package/docs/content/locales/zh-TW/evals.mdx +7 -0
  501. package/docs/content/locales/zh-TW/faq.mdx +1 -1
  502. package/docs/content/locales/zh-TW/template-chat.mdx +1 -1
  503. package/docs/content/template-chat.mdx +1 -1
  504. package/package.json +1 -1
  505. package/corpus/templates/content/server/routes/api/notion/disconnect.post.ts +0 -12
  506. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +0 -157
  507. package/corpus/templates/design/app/lib/design-templates.ts +0 -1305
  508. package/corpus/templates/design/app/pages/Templates.tsx +0 -279
@@ -16,7 +16,6 @@ import {
16
16
  appBasePath,
17
17
  ensureEmbedAuthFetchInterceptor,
18
18
  isEmbedAuthActive,
19
- sendToAgentChat,
20
19
  getBrowserTabId,
21
20
  readClientAppState,
22
21
  setClientAppState,
@@ -58,6 +57,10 @@ import {
58
57
  hasCapability,
59
58
  } from "@shared/design-source-capabilities";
60
59
  import { shouldUseLiveFileContent } from "@shared/html-content";
60
+ import {
61
+ compile as compileMotionTimeline,
62
+ injectManagedMotionCss,
63
+ } from "@shared/motion-compiler";
61
64
  import type { MotionTrack } from "@shared/motion-timeline";
62
65
  import {
63
66
  resolveTweaksToCssVars,
@@ -90,7 +93,7 @@ import {
90
93
  IconPhoto,
91
94
  IconRefresh,
92
95
  IconChevronDown,
93
- IconChevronRight,
96
+ IconChevronUp,
94
97
  IconCheck,
95
98
  IconPointer,
96
99
  IconTypography,
@@ -165,7 +168,6 @@ import {
165
168
  LocalhostWriteConsentDialog,
166
169
  type LocalhostWriteConsentPayload,
167
170
  } from "@/components/design/LocalhostWriteConsentDialog";
168
- import { LocalSourceEditBanner } from "@/components/design/LocalSourceEditBanner";
169
171
  import {
170
172
  MotionDock,
171
173
  type MotionDockTrack,
@@ -173,13 +175,18 @@ import {
173
175
  import {
174
176
  MultiScreenCanvas,
175
177
  OVERVIEW_FRAME_WIDTH,
178
+ type CanvasLayerMarqueeSelection,
176
179
  type CanvasPrimitiveInsert,
177
180
  type FrameGeometry,
178
181
  } from "@/components/design/MultiScreenCanvas";
179
182
  import { QuestionFlow } from "@/components/design/QuestionFlow";
180
183
  import type { ReviewPanelProps } from "@/components/design/ReviewPanel";
181
184
  import { TokensPanel } from "@/components/design/TokensPanel";
182
- import type { ElementInfo, DeviceFrameType } from "@/components/design/types";
185
+ import type {
186
+ ElementInfo,
187
+ ElementSelectionIntent,
188
+ DeviceFrameType,
189
+ } from "@/components/design/types";
183
190
  import {
184
191
  DEVICE_FRAME_VIEWPORTS,
185
192
  ZOOM_PRESETS,
@@ -232,7 +239,14 @@ import {
232
239
  type DesignEditorCommand,
233
240
  } from "@/hooks/use-navigation-state";
234
241
  import { useQuestionFlow } from "@/hooks/use-question-flow";
235
- import { useDesignHotkeys } from "@/hooks/useDesignHotkeys";
242
+ import {
243
+ isDesignHotkeyEditableTarget,
244
+ useDesignHotkeys,
245
+ } from "@/hooks/useDesignHotkeys";
246
+ import {
247
+ DESIGN_CHAT_STORAGE_KEY,
248
+ sendToDesignAgentChat,
249
+ } from "@/lib/agent-chat";
236
250
  import {
237
251
  clearPendingGeneration,
238
252
  hasFreshPendingGeneration,
@@ -265,12 +279,16 @@ const STORED_RUN_LIVENESS_GRACE_MS = 20_000;
265
279
  const MAX_DESIGN_UNDO_STACK = 50;
266
280
  const OVERVIEW_ZOOM_THRESHOLD = 60;
267
281
  const MOTION_DOCK_TRANSITION_MS = 200;
282
+ const MOTION_DOCK_EXIT_SETTLE_MS = 80;
283
+ const MOTION_DOCK_EXIT_FALLBACK_MS = MOTION_DOCK_TRANSITION_MS * 2 + 600;
268
284
  const MOTION_AUTOSAVE_DELAY_MS = 500;
285
+ const BOARD_SURFACE_SIZE = 131_072;
269
286
  /** Extensions that the localhost bridge allows to be written back to source. */
270
287
  const LOCALHOST_WRITE_EXTENSIONS = new Set([".html", ".htm", ".css"]);
271
288
  const NO_LOCALHOST_WRITE_CONTENT_MESSAGE =
272
289
  "No content to write. Open the screen first." /* i18n-ignore */;
273
290
  const FOCUSED_SCREEN_ZOOM = 100;
291
+ export const DEFAULT_OVERVIEW_ZOOM = 60;
274
292
  const KEEPALIVE_FILE_SAVE_MAX_BYTES = 60_000;
275
293
  const UNSUPPORTED_HTML2CANVAS_COLOR_RE =
276
294
  /\b(?:color|color-mix|oklch|oklab|lab|lch)\(/i;
@@ -293,6 +311,14 @@ const HTML2CANVAS_UNSUPPORTED_VALUE_PROPERTIES = [
293
311
  "list-style-image",
294
312
  ] as const;
295
313
 
314
+ function blurActiveDesignEditableTarget() {
315
+ if (typeof document === "undefined") return;
316
+ const active = document.activeElement;
317
+ if (active instanceof HTMLElement && isDesignHotkeyEditableTarget(active)) {
318
+ active.blur();
319
+ }
320
+ }
321
+
296
322
  function getContentSignature(content: string): string {
297
323
  let hash = 2166136261;
298
324
  for (let index = 0; index < content.length; index += 1) {
@@ -302,6 +328,10 @@ function getContentSignature(content: string): string {
302
328
  return `${content.length}:${hash.toString(36)}`;
303
329
  }
304
330
 
331
+ function dedupeStringIds(ids: string[]): string[] {
332
+ return Array.from(new Set(ids.filter(Boolean)));
333
+ }
334
+
305
335
  export function isScreenRootElementInfo(info: ElementInfo | null | undefined) {
306
336
  const tagName = info?.tagName?.toUpperCase();
307
337
  return tagName === "BODY" || tagName === "HTML";
@@ -369,14 +399,26 @@ export function getSidebarCodeLayerSelectionState(args: {
369
399
  currentViewMode: "single" | "overview";
370
400
  ownerFileId?: string | null;
371
401
  overviewSelectedScreenIds: string[];
402
+ screenFileIds?: string[];
372
403
  }) {
373
- const { currentViewMode, ownerFileId, overviewSelectedScreenIds } = args;
404
+ const {
405
+ currentViewMode,
406
+ ownerFileId,
407
+ overviewSelectedScreenIds,
408
+ screenFileIds,
409
+ } = args;
410
+ const ownerScreenId =
411
+ ownerFileId && (!screenFileIds || screenFileIds.includes(ownerFileId))
412
+ ? ownerFileId
413
+ : null;
374
414
  return {
375
415
  viewMode: currentViewMode,
376
416
  overviewSelectedScreenIds:
377
- currentViewMode === "overview" && ownerFileId
378
- ? [ownerFileId]
379
- : overviewSelectedScreenIds,
417
+ currentViewMode === "overview" && ownerScreenId
418
+ ? [ownerScreenId]
419
+ : currentViewMode === "overview" && ownerFileId
420
+ ? []
421
+ : overviewSelectedScreenIds,
380
422
  };
381
423
  }
382
424
 
@@ -411,6 +453,10 @@ export function getOverviewCanvasZoom(
411
453
  return displayZoom / scale;
412
454
  }
413
455
 
456
+ export function getDefaultOverviewCanvasZoom(overviewZoomScale: number) {
457
+ return getOverviewCanvasZoom(DEFAULT_OVERVIEW_ZOOM, overviewZoomScale);
458
+ }
459
+
414
460
  export function getDesignEditorShareUrl(
415
461
  id: string,
416
462
  origin: string,
@@ -423,6 +469,41 @@ export function getDesignEditorShareUrl(
423
469
  return new URL(pathname, origin).toString();
424
470
  }
425
471
 
472
+ function formatDesignEditorUrlZoom(zoom: number): string {
473
+ const rounded = Math.round(zoom * 100) / 100;
474
+ return Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(2);
475
+ }
476
+
477
+ export function getDesignEditorStateUrlSearch(args: {
478
+ currentSearch: string;
479
+ viewMode: "single" | "overview";
480
+ screenId?: string | null;
481
+ selectionId?: string | null;
482
+ zoom?: number | null;
483
+ }) {
484
+ const params = new URLSearchParams(args.currentSearch);
485
+ params.set("view", args.viewMode);
486
+ if (args.screenId) {
487
+ params.set("screen", args.screenId);
488
+ } else {
489
+ params.delete("screen");
490
+ }
491
+ params.delete("fileId");
492
+ params.delete("filename");
493
+ if (args.selectionId) {
494
+ params.set("selection", args.selectionId);
495
+ } else {
496
+ params.delete("selection");
497
+ }
498
+ if (typeof args.zoom === "number" && Number.isFinite(args.zoom)) {
499
+ params.set("zoom", formatDesignEditorUrlZoom(args.zoom));
500
+ } else {
501
+ params.delete("zoom");
502
+ }
503
+ const query = params.toString();
504
+ return query ? `?${query}` : "";
505
+ }
506
+
426
507
  export function getLocalhostRouteSourceFile(args: {
427
508
  sourceFile?: string;
428
509
  source?: string;
@@ -473,14 +554,25 @@ export function getFreshActiveFileContent(args: {
473
554
  export function getFreshScreenContent(args: {
474
555
  screenId: string;
475
556
  activeFileId?: string | null;
557
+ freshActiveContentFileId?: string | null;
476
558
  freshActiveContent: string;
477
559
  fileContentById: ReadonlyMap<string, string>;
478
560
  }) {
479
- return args.screenId === args.activeFileId
561
+ const freshActiveContentFileId =
562
+ args.freshActiveContentFileId ?? args.activeFileId;
563
+ return args.screenId === args.activeFileId &&
564
+ args.screenId === freshActiveContentFileId
480
565
  ? args.freshActiveContent
481
566
  : (args.fileContentById.get(args.screenId) ?? "");
482
567
  }
483
568
 
569
+ export function shouldReplacePreviewAfterVisualStyleCommit(args: {
570
+ runtimeApplied?: boolean;
571
+ runtimeStyleApplied: boolean;
572
+ }) {
573
+ return !args.runtimeApplied && !args.runtimeStyleApplied;
574
+ }
575
+
484
576
  export function getLayerMoveIterationOrder<T>(
485
577
  orderedIds: readonly T[],
486
578
  placement: "before" | "after" | "inside",
@@ -495,19 +587,37 @@ export function removeUndoRedoOrderKind<T extends string>(
495
587
  return order.filter((entry) => entry !== kind);
496
588
  }
497
589
 
498
- type UndoRedoOrderKind = "content" | "file-content" | "geometry";
590
+ export type UndoRedoOrderKind = "content" | "file-content" | "geometry";
591
+
592
+ export function getUndoRedoPriorityOrder(
593
+ preferred: UndoRedoOrderKind | undefined,
594
+ ): UndoRedoOrderKind[] {
595
+ if (preferred === "geometry") return ["geometry", "content", "file-content"];
596
+ if (preferred === "file-content")
597
+ return ["file-content", "content", "geometry"];
598
+ return ["content", "file-content", "geometry"];
599
+ }
499
600
 
500
601
  function resolveZoomUpdate(update: SetStateAction<number>, current: number) {
501
602
  return typeof update === "function" ? update(current) : update;
502
603
  }
503
604
 
504
- export function shouldLockInspectorForInitialGeneration(args: {
605
+ export function shouldLimitEditorChromeUntilContentReady(args: {
505
606
  fileCount: number;
607
+ hasActiveCanvasContent: boolean;
506
608
  generating: boolean;
507
609
  pendingGenerationActive: boolean;
508
610
  }) {
509
- const { fileCount, generating, pendingGenerationActive } = args;
510
- return fileCount === 0 && (generating || pendingGenerationActive);
611
+ const {
612
+ fileCount,
613
+ generating,
614
+ hasActiveCanvasContent,
615
+ pendingGenerationActive,
616
+ } = args;
617
+ return (
618
+ (fileCount === 0 || !hasActiveCanvasContent) &&
619
+ (generating || pendingGenerationActive)
620
+ );
511
621
  }
512
622
 
513
623
  export function shouldEscapeToOverview(args: {
@@ -749,6 +859,13 @@ interface DesignFile {
749
859
  updatedAt: string;
750
860
  }
751
861
 
862
+ interface LiveScreenSnapshot {
863
+ url: string;
864
+ html: string;
865
+ status?: number;
866
+ contentType?: string;
867
+ }
868
+
752
869
  interface DesignData {
753
870
  id: string;
754
871
  title: string;
@@ -1086,6 +1203,7 @@ function designEditorCommandFromSearchParams(
1086
1203
  searchParams.get("screen") ??
1087
1204
  searchParams.get("fileId") ??
1088
1205
  searchParams.get("filename");
1206
+ const selection = searchParams.get("selection");
1089
1207
  const rawZoom = searchParams.get("zoom");
1090
1208
  const zoom = rawZoom !== null ? Number(rawZoom) : NaN;
1091
1209
  const tool = normalizeDesignTool(searchParams.get("tool"));
@@ -1097,6 +1215,7 @@ function designEditorCommandFromSearchParams(
1097
1215
  inspector !== "extensions" &&
1098
1216
  !leftPanel &&
1099
1217
  !screen &&
1218
+ !selection &&
1100
1219
  !tool
1101
1220
  ) {
1102
1221
  return null;
@@ -1117,6 +1236,7 @@ function designEditorCommandFromSearchParams(
1117
1236
  }
1118
1237
  if (leftPanel) command.leftPanel = leftPanel;
1119
1238
  if (screen) command.screen = screen;
1239
+ if (selection) command.selection = selection;
1120
1240
  if (Number.isFinite(zoom)) {
1121
1241
  command.zoom = zoom;
1122
1242
  } else if (editorView === "single") {
@@ -1307,6 +1427,35 @@ function removeAbsolutePositioningFromNodeInHtml(
1307
1427
  }
1308
1428
  }
1309
1429
 
1430
+ function setAbsolutePositioningForNodeInHtml(
1431
+ content: string,
1432
+ nodeAttrId: string,
1433
+ point: { x: number; y: number },
1434
+ pointerOffset?: { x: number; y: number },
1435
+ ): string {
1436
+ if (typeof window === "undefined") return content;
1437
+ try {
1438
+ const doc = new DOMParser().parseFromString(content, "text/html");
1439
+ const element = doc.querySelector(
1440
+ `[data-agent-native-node-id="${CSS.escape(nodeAttrId)}"]`,
1441
+ ) as HTMLElement | null;
1442
+ if (!element) return content;
1443
+ element.style.position = "absolute";
1444
+ element.style.left = `${Math.round(point.x - (pointerOffset?.x ?? 0))}px`;
1445
+ element.style.top = `${Math.round(point.y - (pointerOffset?.y ?? 0))}px`;
1446
+ element.style.removeProperty("right");
1447
+ element.style.removeProperty("bottom");
1448
+ return `<!DOCTYPE html>\n${doc.documentElement.outerHTML}`;
1449
+ } catch {
1450
+ return content;
1451
+ }
1452
+ }
1453
+
1454
+ function isAbsoluteCodeLayerNode(node: CodeLayerNode | null | undefined) {
1455
+ const position = String(node?.style.position ?? "").toLowerCase();
1456
+ return position === "absolute" || position === "fixed";
1457
+ }
1458
+
1310
1459
  function escapeHtmlText(value: string): string {
1311
1460
  return value
1312
1461
  .replace(/&/g, "&amp;")
@@ -1574,6 +1723,9 @@ function appendCanvasPrimitiveToHtml(
1574
1723
  }
1575
1724
  svg.setAttribute("data-agent-native-node-id", nodeId);
1576
1725
  svg.setAttribute("data-agent-native-layer-name", layerName);
1726
+ // Kind marker so the layers panel shows a true vector/line/arrow icon for
1727
+ // this SVG primitive instead of falling through to the rectangle glyph.
1728
+ // Read by treeTypeForNode in shared/code-layer.ts.
1577
1729
  svg.setAttribute("data-an-primitive", primitive.kind);
1578
1730
  svg.setAttribute(
1579
1731
  "viewBox",
@@ -1619,6 +1771,10 @@ function appendCanvasPrimitiveToHtml(
1619
1771
  polygon.setAttribute("stroke-linejoin", "round");
1620
1772
  svg.setAttribute("data-agent-native-node-id", nodeId);
1621
1773
  svg.setAttribute("data-agent-native-layer-name", layerName);
1774
+ // Kind marker so the layers panel shows a true polygon/star icon for this
1775
+ // SVG primitive instead of falling through to the rectangle glyph.
1776
+ // Read by treeTypeForNode in shared/code-layer.ts.
1777
+ svg.setAttribute("data-an-primitive", primitive.kind);
1622
1778
  svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
1623
1779
  svg.setAttribute(
1624
1780
  "style",
@@ -1673,7 +1829,7 @@ function appendCanvasPrimitiveToHtml(
1673
1829
  element.style.borderRadius = canonical.borderRadius;
1674
1830
  element.style.overflow = "hidden";
1675
1831
  } else if (primitive.kind === "text") {
1676
- element.textContent = primitive.text ?? "Text";
1832
+ element.textContent = primitive.text ?? "";
1677
1833
  element.style.display = primitive.autoSize ? "inline-block" : "flex";
1678
1834
  if (!primitive.autoSize) {
1679
1835
  element.style.alignItems = "center";
@@ -2097,12 +2253,38 @@ function layerTypeForCodeLayer(
2097
2253
  ): LayersPanelNode["type"] {
2098
2254
  if (node.type === "group") return "group";
2099
2255
  if (node.type === "component") return "component";
2256
+ if (node.type === "ellipse") return "ellipse";
2100
2257
  if (node.type === "shape") return "shape";
2258
+ if (node.type === "vector") return "vector";
2259
+ if (node.type === "line") return "line";
2260
+ if (node.type === "arrow") return "arrow";
2261
+ if (node.type === "polygon") return "polygon";
2262
+ if (node.type === "star") return "star";
2101
2263
  if (node.type === "text") return "text";
2102
2264
  if (node.type === "image") return "image";
2103
2265
  return "element";
2104
2266
  }
2105
2267
 
2268
+ function codeLayerNodeLooksLikeComponent(
2269
+ node: CodeLayerNode | null | undefined,
2270
+ ): boolean {
2271
+ if (!node) return false;
2272
+ if (isComponentInstance(node)) return true;
2273
+ const tag = node.tag.toLowerCase();
2274
+ if (
2275
+ tag === "button" ||
2276
+ tag === "input" ||
2277
+ tag === "select" ||
2278
+ tag === "textarea"
2279
+ ) {
2280
+ return true;
2281
+ }
2282
+ if (/component|card|button|control/i.test(node.layerName)) return true;
2283
+ return node.classes.some((item) =>
2284
+ /component|card|button|control/i.test(item),
2285
+ );
2286
+ }
2287
+
2106
2288
  function preferredCodeLayerSelector(node: CodeLayerNode): string {
2107
2289
  return (
2108
2290
  node.selectors.find((selector) =>
@@ -2272,6 +2454,7 @@ function codeLayerTreeToPanelNodes(
2272
2454
  id: node.id,
2273
2455
  name: resolvedLayerName(node),
2274
2456
  type: layerTypeForCodeLayer(node),
2457
+ tagName: node.tag,
2275
2458
  layout: node.layout,
2276
2459
  detail: node.detail,
2277
2460
  badge: node.badge,
@@ -2291,6 +2474,14 @@ interface EffectiveCodeLayerState {
2291
2474
  hiddenIds: Set<string>;
2292
2475
  }
2293
2476
 
2477
+ interface SelectedLayerTarget {
2478
+ layerId: string;
2479
+ fileId: string;
2480
+ node: CodeLayerNode;
2481
+ tree: CodeLayerTreeNode[];
2482
+ elementInfo: ElementInfo;
2483
+ }
2484
+
2294
2485
  function collectEffectiveCodeLayerState(
2295
2486
  nodes: CodeLayerTreeNode[],
2296
2487
  lockedIds: Set<string>,
@@ -2352,6 +2543,7 @@ function elementInfoFromCodeLayerNode(node: CodeLayerNode): ElementInfo {
2352
2543
  ),
2353
2544
  boundingRect: { x: 0, y: 0, width: 0, height: 0 },
2354
2545
  textContent: node.textSnippet ?? undefined,
2546
+ childElementCount: node.children.length,
2355
2547
  isFlexChild: node.layout.parentDisplay?.includes("flex") ? true : false,
2356
2548
  isFlexContainer: node.layout.isFlexContainer,
2357
2549
  parentDisplay: node.layout.parentDisplay,
@@ -2480,6 +2672,7 @@ function canonicalElementInfoForCodeLayerNode(
2480
2672
  selector: preferredCodeLayerSelector(node),
2481
2673
  classes: node.classes,
2482
2674
  confidence: node.confidence,
2675
+ childElementCount: node.children.length,
2483
2676
  editCapabilities: info.editCapabilities?.some((capability) =>
2484
2677
  capability.kind.startsWith("deterministic"),
2485
2678
  )
@@ -2546,6 +2739,7 @@ export function refreshElementInfoFromContent(
2546
2739
  sourceInfo.classes,
2547
2740
  ),
2548
2741
  textContent: sourceInfo.textContent,
2742
+ childElementCount: sourceInfo.childElementCount,
2549
2743
  isFlexChild: sourceInfo.isFlexChild,
2550
2744
  isFlexContainer: sourceInfo.isFlexContainer,
2551
2745
  parentDisplay: sourceInfo.parentDisplay,
@@ -2566,6 +2760,7 @@ export function refreshElementInfoFromContent(
2566
2760
  classes,
2567
2761
  ),
2568
2762
  textContent: element.textContent?.slice(0, 200) ?? info.textContent,
2763
+ childElementCount: element.children.length,
2569
2764
  };
2570
2765
  } catch {
2571
2766
  return null;
@@ -2723,6 +2918,13 @@ function AgentNativeMenuMark({ className }: { className?: string }) {
2723
2918
 
2724
2919
  type DesignLeftPanel = "file" | "agent" | "assets" | "tools" | "tokens";
2725
2920
 
2921
+ const INITIAL_GENERATION_DISABLED_LEFT_PANELS = new Set<DesignLeftPanel>([
2922
+ "file",
2923
+ "assets",
2924
+ "tools",
2925
+ "tokens",
2926
+ ]);
2927
+
2726
2928
  function normalizeDesignLeftPanel(value: unknown): DesignLeftPanel | undefined {
2727
2929
  if (value === "extensions") return "tools";
2728
2930
  return value === "file" ||
@@ -2736,11 +2938,19 @@ function normalizeDesignLeftPanel(value: unknown): DesignLeftPanel | undefined {
2736
2938
 
2737
2939
  function DesignWorkspaceRail({
2738
2940
  activePanel,
2941
+ disabledPanels,
2942
+ motionOpen,
2943
+ motionDisabled,
2739
2944
  projectMenu,
2945
+ onMotionToggle,
2740
2946
  onPanelChange,
2741
2947
  }: {
2742
2948
  activePanel: DesignLeftPanel;
2949
+ disabledPanels?: ReadonlySet<DesignLeftPanel>;
2950
+ motionOpen?: boolean;
2951
+ motionDisabled?: boolean;
2743
2952
  projectMenu: ReactNode;
2953
+ onMotionToggle?: () => void;
2744
2954
  onPanelChange: (panel: DesignLeftPanel) => void;
2745
2955
  }) {
2746
2956
  const t = useT();
@@ -2788,16 +2998,27 @@ function DesignWorkspaceRail({
2788
2998
  <div className="flex min-h-0 flex-1 flex-col items-center gap-4">
2789
2999
  {items.map((item) => {
2790
3000
  const active = item.panel === activePanel;
3001
+ const disabled = disabledPanels?.has(item.panel) ?? false;
2791
3002
  return (
2792
3003
  <Tooltip key={item.panel}>
2793
3004
  <TooltipTrigger asChild>
2794
3005
  <button
2795
3006
  type="button"
2796
3007
  aria-label={item.label}
3008
+ aria-disabled={disabled || undefined}
2797
3009
  aria-current={active ? "page" : undefined}
2798
- onClick={() => onPanelChange(item.panel)}
3010
+ tabIndex={disabled ? -1 : undefined}
3011
+ onClick={(event) => {
3012
+ if (disabled) {
3013
+ event.preventDefault();
3014
+ return;
3015
+ }
3016
+ onPanelChange(item.panel);
3017
+ }}
2799
3018
  className={cn(
2800
- "group flex w-12 cursor-pointer flex-col items-center justify-start gap-1.5 rounded-none text-[10px] font-medium leading-none text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
3019
+ "group flex w-12 cursor-pointer flex-col items-center justify-start gap-1 rounded-none text-[10px] font-[450] leading-none text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
3020
+ disabled &&
3021
+ "cursor-default opacity-35 hover:text-muted-foreground",
2801
3022
  active && "text-foreground",
2802
3023
  )}
2803
3024
  >
@@ -2807,6 +3028,8 @@ function DesignWorkspaceRail({
2807
3028
  active
2808
3029
  ? "bg-[var(--design-editor-selection-color)] text-[var(--design-editor-accent-color)]"
2809
3030
  : "text-muted-foreground group-hover:bg-[var(--design-editor-layer-hover-color)] group-hover:text-foreground",
3031
+ disabled &&
3032
+ "group-hover:bg-transparent group-hover:text-muted-foreground",
2810
3033
  )}
2811
3034
  >
2812
3035
  {item.icon}
@@ -2821,6 +3044,53 @@ function DesignWorkspaceRail({
2821
3044
  );
2822
3045
  })}
2823
3046
  </div>
3047
+ {onMotionToggle ? (
3048
+ <div className="mt-4 flex w-full flex-col items-center border-t border-border/70 pt-3">
3049
+ <Tooltip>
3050
+ <TooltipTrigger asChild>
3051
+ <button
3052
+ type="button"
3053
+ aria-label={"Motion" /* i18n-ignore */}
3054
+ aria-disabled={motionDisabled || undefined}
3055
+ aria-pressed={motionOpen || undefined}
3056
+ tabIndex={motionDisabled ? -1 : undefined}
3057
+ onClick={(event) => {
3058
+ if (motionDisabled) {
3059
+ event.preventDefault();
3060
+ return;
3061
+ }
3062
+ onMotionToggle();
3063
+ }}
3064
+ className={cn(
3065
+ "group flex w-12 cursor-pointer flex-col items-center justify-start gap-1 rounded-none !text-[10px] font-[450] leading-none text-muted-foreground outline-none transition-colors hover:text-foreground focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
3066
+ motionDisabled &&
3067
+ "cursor-default opacity-35 hover:text-muted-foreground",
3068
+ motionOpen && "text-foreground",
3069
+ )}
3070
+ >
3071
+ <span
3072
+ className={cn(
3073
+ "flex size-8 items-center justify-center rounded-lg transition-colors",
3074
+ motionOpen
3075
+ ? "bg-[var(--design-editor-selection-color)] text-[var(--design-editor-accent-color)]"
3076
+ : "text-muted-foreground group-hover:bg-[var(--design-editor-layer-hover-color)] group-hover:text-foreground",
3077
+ motionDisabled &&
3078
+ "group-hover:bg-transparent group-hover:text-muted-foreground",
3079
+ )}
3080
+ >
3081
+ <IconChevronUp className="size-[15px]" />
3082
+ </span>
3083
+ <span className="max-w-full truncate leading-none">
3084
+ {"Motion" /* i18n-ignore */}
3085
+ </span>
3086
+ </button>
3087
+ </TooltipTrigger>
3088
+ <TooltipContent side="right">
3089
+ {"Motion" /* i18n-ignore */}
3090
+ </TooltipContent>
3091
+ </Tooltip>
3092
+ </div>
3093
+ ) : null}
2824
3094
  </nav>
2825
3095
  );
2826
3096
  }
@@ -3634,6 +3904,25 @@ export default function DesignEditor() {
3634
3904
  const { id } = useParams<{ id: string }>();
3635
3905
  const navigate = useNavigate();
3636
3906
  const location = useLocation();
3907
+ const initialEditorUrlRef = useRef<{
3908
+ designId: string | undefined;
3909
+ searchParams: URLSearchParams;
3910
+ } | null>(null);
3911
+ if (
3912
+ !initialEditorUrlRef.current ||
3913
+ initialEditorUrlRef.current.designId !== id
3914
+ ) {
3915
+ initialEditorUrlRef.current = {
3916
+ designId: id,
3917
+ searchParams: new URLSearchParams(location.search),
3918
+ };
3919
+ }
3920
+ const initialSearchParams = initialEditorUrlRef.current.searchParams;
3921
+ const initialRouteScreenTarget =
3922
+ initialSearchParams.get("screen") ??
3923
+ initialSearchParams.get("fileId") ??
3924
+ initialSearchParams.get("filename");
3925
+ const initialRouteSelectionId = initialSearchParams.get("selection") || null;
3637
3926
  const searchParams = useMemo(
3638
3927
  () => new URLSearchParams(location.search),
3639
3928
  [location.search],
@@ -3668,7 +3957,9 @@ export default function DesignEditor() {
3668
3957
  const [titleEditing, setTitleEditing] = useState(false);
3669
3958
  const [titleDraft, setTitleDraft] = useState("");
3670
3959
  const [screenZoom, setScreenZoom] = useState(FOCUSED_SCREEN_ZOOM);
3671
- const [overviewCanvasZoom, setOverviewCanvasZoom] = useState(100);
3960
+ const [explicitOverviewCanvasZoom, setExplicitOverviewCanvasZoom] = useState<
3961
+ number | null
3962
+ >(null);
3672
3963
  const [deviceFrame, setDeviceFrame] = useState<DeviceFrameType>("none");
3673
3964
  const [viewMode, setViewMode] = useState<"single" | "overview">("overview");
3674
3965
  const viewModeRef = useRef<"single" | "overview">("overview");
@@ -3696,6 +3987,8 @@ export default function DesignEditor() {
3696
3987
  useState<InspectorTab>("design");
3697
3988
  const [activeLeftPanel, setActiveLeftPanel] =
3698
3989
  useState<DesignLeftPanel>("file");
3990
+ const initialSearchCommandAppliedForIdRef = useRef<string | null>(null);
3991
+ const initialUrlSelectionHydratedForIdRef = useRef<string | null>(null);
3699
3992
  const [leftSidebarWidth, setLeftSidebarWidth] = useState(256);
3700
3993
  const [rightSidebarWidth, setRightSidebarWidth] = useState(256);
3701
3994
  const [layersSearchQuery, setLayersSearchQuery] = useState("");
@@ -3703,6 +3996,11 @@ export default function DesignEditor() {
3703
3996
  const [selectedLayerIdsState, setSelectedLayerIdsState] = useState<string[]>(
3704
3997
  [],
3705
3998
  );
3999
+ const selectedLayerTargetsRef = useRef<SelectedLayerTarget[]>([]);
4000
+ const effectiveCodeLayerStateRef = useRef<EffectiveCodeLayerState>({
4001
+ lockedIds: new Set(),
4002
+ hiddenIds: new Set(),
4003
+ });
3706
4004
  const [overviewSelectedScreenIds, setOverviewSelectedScreenIds] = useState<
3707
4005
  string[]
3708
4006
  >([]);
@@ -3853,10 +4151,22 @@ export default function DesignEditor() {
3853
4151
  motionDockUnmountTimerRef.current = window.setTimeout(() => {
3854
4152
  setMotionDockMounted(false);
3855
4153
  motionDockUnmountTimerRef.current = null;
3856
- }, MOTION_DOCK_TRANSITION_MS);
4154
+ }, MOTION_DOCK_EXIT_FALLBACK_MS);
3857
4155
  },
3858
4156
  [clearMotionDockUnmountTimer],
3859
4157
  );
4158
+ const handleMotionDockExitComplete = useCallback(() => {
4159
+ if (motionDockOpen) return;
4160
+ clearMotionDockUnmountTimer();
4161
+ if (typeof window === "undefined") {
4162
+ setMotionDockMounted(false);
4163
+ return;
4164
+ }
4165
+ motionDockUnmountTimerRef.current = window.setTimeout(() => {
4166
+ setMotionDockMounted(false);
4167
+ motionDockUnmountTimerRef.current = null;
4168
+ }, MOTION_DOCK_EXIT_SETTLE_MS);
4169
+ }, [clearMotionDockUnmountTimer, motionDockOpen]);
3860
4170
  useEffect(
3861
4171
  () => () => clearMotionDockUnmountTimer(),
3862
4172
  [clearMotionDockUnmountTimer],
@@ -4123,9 +4433,6 @@ export default function DesignEditor() {
4123
4433
  const designSelectionOwnerIdRef = useRef(`${TAB_ID}:${generateTabId()}`);
4124
4434
  const frameGeometrySaveTimerRef = useRef<number | null>(null);
4125
4435
  const [tweakSaveActive, setTweakSaveActive] = useState(false);
4126
- // Dismissible localhost-source banner (reset per session).
4127
- const [localSourceBannerDismissed, setLocalSourceBannerDismissed] =
4128
- useState(false);
4129
4436
  // Localhost write-consent dialog state. When the agent wants to write a local
4130
4437
  // file and no valid grant exists for the active connection, we show the dialog
4131
4438
  // with a pending payload; the user clicks "Allow writes" to mint a grant.
@@ -4318,7 +4625,9 @@ export default function DesignEditor() {
4318
4625
  onComplete: handleGenerationComplete,
4319
4626
  onStale: markGenerationStale,
4320
4627
  shouldAdoptRunningTab: () =>
4321
- Boolean(id) && !generationOutputReadyRef.current,
4628
+ Boolean(id) &&
4629
+ !generationOutputReadyRef.current &&
4630
+ hasFreshPendingGeneration(id),
4322
4631
  onAdoptRunningTab: (tabId) => {
4323
4632
  generationRunConfirmedRef.current = true;
4324
4633
  setGenerationChatTabId(tabId);
@@ -4371,8 +4680,9 @@ export default function DesignEditor() {
4371
4680
  pendingQuestions && pendingQuestions.length > 0,
4372
4681
  );
4373
4682
 
4374
- const { session } = useSession();
4683
+ const { session, isLoading: sessionLoading } = useSession();
4375
4684
  const isSignedIn = Boolean(session?.email);
4685
+ const sessionResolved = !sessionLoading;
4376
4686
 
4377
4687
  useEffect(() => {
4378
4688
  return () => clearGenerationCompleteTimer();
@@ -4410,12 +4720,11 @@ export default function DesignEditor() {
4410
4720
  : undefined,
4411
4721
  [session?.email, session?.name],
4412
4722
  );
4723
+ const signInToSaveHref = buildSignInHrefForDesignIntent("save");
4724
+ const signInToShareHref = buildSignInHrefForDesignIntent("share");
4413
4725
  const handleSignInToSave = useCallback(() => {
4414
4726
  window.location.href = buildSignInHrefForDesignIntent("save");
4415
4727
  }, []);
4416
- const handleSignInToShare = useCallback(() => {
4417
- window.location.href = buildSignInHrefForDesignIntent("share");
4418
- }, []);
4419
4728
 
4420
4729
  // Data fetching
4421
4730
  useEffect(() => {
@@ -4462,6 +4771,7 @@ export default function DesignEditor() {
4462
4771
  const canShareDesign =
4463
4772
  designAccessRole === "owner" || designAccessRole === "admin";
4464
4773
  const canEditDesign = canShareDesign || designAccessRole === "editor";
4774
+ const canRenderAuthenticatedShare = isSignedIn || canEditDesign;
4465
4775
  const canEditDesignRef = useRef(canEditDesign);
4466
4776
  const pendingLocalFileContentsRef = useRef<
4467
4777
  Map<
@@ -5009,6 +5319,24 @@ export default function DesignEditor() {
5009
5319
  return pending ? { ...file, content: pending.content } : file;
5010
5320
  });
5011
5321
  }, [pendingLocalFileContentsSnapshot, serverFiles]);
5322
+ const [liveScreenSnapshotsById, setLiveScreenSnapshotsById] = useState<
5323
+ Record<string, LiveScreenSnapshot>
5324
+ >({});
5325
+ useEffect(() => {
5326
+ const liveFileIds = new Set(serverFiles.map((file) => file.id));
5327
+ setLiveScreenSnapshotsById((current) => {
5328
+ let changed = false;
5329
+ const next: Record<string, LiveScreenSnapshot> = {};
5330
+ Object.entries(current).forEach(([fileId, snapshot]) => {
5331
+ if (!liveFileIds.has(fileId)) {
5332
+ changed = true;
5333
+ return;
5334
+ }
5335
+ next[fileId] = snapshot;
5336
+ });
5337
+ return changed ? next : current;
5338
+ });
5339
+ }, [serverFiles]);
5012
5340
  const designDataJson = useMemo(
5013
5341
  () => parseDesignDataJson(design?.data),
5014
5342
  [design?.data],
@@ -5124,6 +5452,7 @@ export default function DesignEditor() {
5124
5452
  height: numberValue("height"),
5125
5453
  url: stringValue("url"),
5126
5454
  previewUrl: stringValue("previewUrl"),
5455
+ bridgeUrl: stringValue("bridgeUrl"),
5127
5456
  // Breakpoint preview widths (§6.4). When non-empty, MultiScreenCanvas
5128
5457
  // renders one iframe per width to the right of the primary frame.
5129
5458
  breakpointWidths: bpWidths,
@@ -5146,28 +5475,19 @@ export default function DesignEditor() {
5146
5475
  return typeof boardFile?.content === "string" ? boardFile.content : "";
5147
5476
  }, [boardFileId, files]);
5148
5477
 
5149
- // Logical canvas-space bounding box of the board iframe. The board covers the
5150
- // full canvas surface, so we compute the union of all screen frame geometries.
5151
- // x:0/y:0 since screen geometries use canvas-space coords that start at origin.
5478
+ // Logical canvas-space bounding box of the board iframe. The board is an
5479
+ // invisible editing layer behind screen frames, not a finite artboard, so keep
5480
+ // it at the canvas-safe maximum instead of clipping it to the screen union.
5152
5481
  const boardFrameGeometry = useMemo((): FrameGeometry | undefined => {
5153
5482
  if (!boardFileId) return undefined;
5154
- const entries = Object.values(canvasFrameGeometryById);
5155
- if (entries.length === 0) return { x: 0, y: 0, width: 1920, height: 1080 };
5156
- let maxRight = 0;
5157
- let maxBottom = 0;
5158
- for (const geo of entries) {
5159
- const right = (geo.x ?? 0) + (geo.width ?? 0);
5160
- const bottom = (geo.y ?? 0) + (geo.height ?? 0);
5161
- if (right > maxRight) maxRight = right;
5162
- if (bottom > maxBottom) maxBottom = bottom;
5163
- }
5483
+ const origin = -BOARD_SURFACE_SIZE / 2;
5164
5484
  return {
5165
- x: 0,
5166
- y: 0,
5167
- width: Math.min(Math.max(maxRight, 1920), 16384),
5168
- height: Math.min(Math.max(maxBottom, 1080), 16384),
5485
+ x: origin,
5486
+ y: origin,
5487
+ width: BOARD_SURFACE_SIZE,
5488
+ height: BOARD_SURFACE_SIZE,
5169
5489
  };
5170
- }, [boardFileId, canvasFrameGeometryById]);
5490
+ }, [boardFileId]);
5171
5491
 
5172
5492
  const queueFrameGeometrySave = useCallback(
5173
5493
  (geometryById: CanvasFrameGeometryById) => {
@@ -5482,12 +5802,6 @@ export default function DesignEditor() {
5482
5802
  setReviewAuditLoading(false);
5483
5803
  }, [activeFile?.id, reviewFileId]);
5484
5804
 
5485
- const initialGenerationReadOnly = shouldLockInspectorForInitialGeneration({
5486
- fileCount: files.length,
5487
- generating,
5488
- pendingGenerationActive,
5489
- });
5490
-
5491
5805
  const selectedScreenIds = useMemo(
5492
5806
  () =>
5493
5807
  getSelectedScreenIdsForEditorState({
@@ -5506,6 +5820,10 @@ export default function DesignEditor() {
5506
5820
  : undefined,
5507
5821
  [activeOverviewScreenId, overviewScreens],
5508
5822
  );
5823
+ const activeScreenBridgeUrl = activeOverviewScreen?.bridgeUrl;
5824
+ const activeScreenExternalSnapshotHtml = activeFile?.id
5825
+ ? liveScreenSnapshotsById[activeFile.id]?.html
5826
+ : undefined;
5509
5827
  const activeOverviewSourceWidth =
5510
5828
  deviceFrame === "none"
5511
5829
  ? activeOverviewScreen?.width
@@ -5523,6 +5841,9 @@ export default function DesignEditor() {
5523
5841
  overviewZoomScaleRef.current = overviewZoomScale;
5524
5842
  }, [overviewZoomScale]);
5525
5843
 
5844
+ const overviewCanvasZoom =
5845
+ explicitOverviewCanvasZoom ??
5846
+ getDefaultOverviewCanvasZoom(overviewZoomScale);
5526
5847
  const overviewZoom = getOverviewDisplayZoom(
5527
5848
  overviewCanvasZoom,
5528
5849
  overviewZoomScale,
@@ -5531,10 +5852,12 @@ export default function DesignEditor() {
5531
5852
  const setZoomForView = useCallback(
5532
5853
  (targetView: "single" | "overview", update: SetStateAction<number>) => {
5533
5854
  if (targetView === "overview") {
5534
- setOverviewCanvasZoom((currentCanvasZoom) => {
5855
+ setExplicitOverviewCanvasZoom((currentCanvasZoom) => {
5535
5856
  const scale = overviewZoomScaleRef.current;
5857
+ const resolvedCanvasZoom =
5858
+ currentCanvasZoom ?? getDefaultOverviewCanvasZoom(scale);
5536
5859
  const currentDisplayZoom = getOverviewDisplayZoom(
5537
- currentCanvasZoom,
5860
+ resolvedCanvasZoom,
5538
5861
  scale,
5539
5862
  );
5540
5863
  const nextDisplayZoom = resolveZoomUpdate(update, currentDisplayZoom);
@@ -5561,6 +5884,7 @@ export default function DesignEditor() {
5561
5884
  const applyDesignEditorCommand = useCallback(
5562
5885
  (command: DesignEditorCommand | Record<string, unknown>) => {
5563
5886
  if (!id || command.designId !== id) return true;
5887
+ const commandRecord = command as Record<string, unknown>;
5564
5888
  const editorView =
5565
5889
  command.editorView === "overview" || command.editorView === "single"
5566
5890
  ? command.editorView
@@ -5577,6 +5901,14 @@ export default function DesignEditor() {
5577
5901
  : typeof command.screen === "string"
5578
5902
  ? command.screen
5579
5903
  : null;
5904
+ const selectionId =
5905
+ typeof command.selection === "string"
5906
+ ? command.selection
5907
+ : typeof commandRecord.nodeId === "string"
5908
+ ? commandRecord.nodeId
5909
+ : typeof commandRecord.layerId === "string"
5910
+ ? commandRecord.layerId
5911
+ : null;
5580
5912
  const targetFile = findDesignFileByScreenTarget(files, target);
5581
5913
  // A navigate command can name a screen the agent just created that the
5582
5914
  // get-design query hasn't refetched yet. Treat any unresolved named target
@@ -5624,6 +5956,9 @@ export default function DesignEditor() {
5624
5956
  if (targetFile) {
5625
5957
  setActiveFileId(targetFile.id);
5626
5958
  }
5959
+ if (selectionId) {
5960
+ setSelectedLayerIdsState([selectionId]);
5961
+ }
5627
5962
 
5628
5963
  const commandZoom =
5629
5964
  typeof command.zoom === "number" && Number.isFinite(command.zoom)
@@ -5635,12 +5970,12 @@ export default function DesignEditor() {
5635
5970
 
5636
5971
  if (editorView === "overview") {
5637
5972
  viewModeRef.current = "overview";
5638
- setSelectedElement(null);
5973
+ if (!selectionId) setSelectedElement(null);
5639
5974
  applyCommandTool("move");
5640
5975
  setViewMode("overview");
5641
5976
  } else if (editorView === "single") {
5642
5977
  viewModeRef.current = "single";
5643
- setSelectedElement(null);
5978
+ if (!selectionId) setSelectedElement(null);
5644
5979
  applyCommandTool("move");
5645
5980
  if (commandZoom === null) {
5646
5981
  setScreenZoom(FOCUSED_SCREEN_ZOOM);
@@ -5657,10 +5992,20 @@ export default function DesignEditor() {
5657
5992
 
5658
5993
  useEffect(() => {
5659
5994
  if (!id) return;
5660
- const command = designEditorCommandFromSearchParams(id, searchParams);
5661
- if (!command) return;
5662
- applyDesignEditorCommand(command);
5663
- }, [applyDesignEditorCommand, id, searchParams]);
5995
+ if (initialSearchCommandAppliedForIdRef.current === id) return;
5996
+ const command = designEditorCommandFromSearchParams(
5997
+ id,
5998
+ initialSearchParams,
5999
+ );
6000
+ if (!command) {
6001
+ initialSearchCommandAppliedForIdRef.current = id;
6002
+ return;
6003
+ }
6004
+ const applied = applyDesignEditorCommand(command);
6005
+ if (applied) {
6006
+ initialSearchCommandAppliedForIdRef.current = id;
6007
+ }
6008
+ }, [applyDesignEditorCommand, id, initialSearchParams]);
5664
6009
 
5665
6010
  useEffect(() => {
5666
6011
  if (!id) return;
@@ -6590,9 +6935,20 @@ export default function DesignEditor() {
6590
6935
  : (activeFile?.content ?? ""));
6591
6936
  const activeContent =
6592
6937
  typeof activeContentSource === "string" ? activeContentSource : "";
6938
+ const initialGenerationChromeLimited =
6939
+ shouldLimitEditorChromeUntilContentReady({
6940
+ fileCount: files.length,
6941
+ generating,
6942
+ hasActiveCanvasContent: Boolean(activeFile && activeContent.trim()),
6943
+ pendingGenerationActive,
6944
+ });
6593
6945
  useLayoutEffect(() => {
6594
6946
  latestActiveContentRef.current = activeContent;
6595
6947
  }, [activeContent]);
6948
+ useEffect(() => {
6949
+ if (!initialGenerationChromeLimited || activeLeftPanel === "agent") return;
6950
+ setActiveLeftPanel("agent");
6951
+ }, [activeLeftPanel, initialGenerationChromeLimited]);
6596
6952
  const fileContentById = useMemo(() => {
6597
6953
  const map = new Map<string, string>();
6598
6954
  for (const file of files) {
@@ -6605,22 +6961,58 @@ export default function DesignEditor() {
6605
6961
  getFreshScreenContent({
6606
6962
  screenId,
6607
6963
  activeFileId: activeFile?.id,
6608
- freshActiveContent: getFreshActiveFileContent({
6609
- activeContent,
6610
- latestContent: latestActiveContentRef.current,
6611
- lastLocalContent: lastLocalContentRef.current,
6612
- }),
6964
+ freshActiveContentFileId: activeFile?.id,
6965
+ freshActiveContent: activeContent,
6613
6966
  fileContentById,
6614
6967
  }),
6615
6968
  [activeContent, activeFile?.id, fileContentById],
6616
6969
  );
6970
+ const getProjectionContentForScreen = useCallback(
6971
+ (screenId: string) =>
6972
+ liveScreenSnapshotsById[screenId]?.html ?? getScreenContent(screenId),
6973
+ [getScreenContent, liveScreenSnapshotsById],
6974
+ );
6975
+ const handleScreenExternalContentSnapshot = useCallback(
6976
+ (screenId: string, snapshot: LiveScreenSnapshot) => {
6977
+ setLiveScreenSnapshotsById((current) => {
6978
+ const existing = current[screenId];
6979
+ if (
6980
+ existing?.url === snapshot.url &&
6981
+ existing.html === snapshot.html &&
6982
+ existing.status === snapshot.status &&
6983
+ existing.contentType === snapshot.contentType
6984
+ ) {
6985
+ return current;
6986
+ }
6987
+ return { ...current, [screenId]: snapshot };
6988
+ });
6989
+ },
6990
+ [],
6991
+ );
6992
+ const updateLiveScreenSnapshotContent = useCallback(
6993
+ (screenId: string, html: string) => {
6994
+ const existing = liveScreenSnapshotsById[screenId];
6995
+ if (!existing) return false;
6996
+ if (existing.html === html) return true;
6997
+ setLiveScreenSnapshotsById((current) => ({
6998
+ ...current,
6999
+ [screenId]: { ...existing, html },
7000
+ }));
7001
+ return true;
7002
+ },
7003
+ [liveScreenSnapshotsById],
7004
+ );
7005
+ const activeProjectionContent =
7006
+ activeFile?.id !== undefined
7007
+ ? getProjectionContentForScreen(activeFile.id)
7008
+ : activeContent;
6617
7009
  const pageStyles = useMemo(
6618
7010
  () => getBodyInlineStyles(activeContent),
6619
7011
  [activeContent],
6620
7012
  );
6621
7013
  const activeCodeLayerProjection = useMemo(
6622
- () => buildCodeLayerProjection(activeContent),
6623
- [activeContent],
7014
+ () => buildCodeLayerProjection(activeProjectionContent),
7015
+ [activeProjectionContent],
6624
7016
  );
6625
7017
  const activeMotionTimeline = motionTimelineResult?.timelines?.[0] ?? null;
6626
7018
  const activeMotionHydrationFingerprint = activeFile?.id
@@ -6726,9 +7118,13 @@ export default function DesignEditor() {
6726
7118
  const designSourceType = useMemo(
6727
7119
  () =>
6728
7120
  normalizeDesignSourceType(designDataJson.sourceType as unknown) ??
7121
+ normalizeDesignSourceType(designDataJson.sourceMode as unknown) ??
6729
7122
  "inline",
6730
- [designDataJson.sourceType],
7123
+ [designDataJson.sourceMode, designDataJson.sourceType],
6731
7124
  );
7125
+ const activeCanvasSourceType =
7126
+ normalizeDesignSourceType(activeOverviewScreen?.sourceType) ??
7127
+ designSourceType;
6732
7128
  const sourceCapabilities = useMemo(() => {
6733
7129
  const caps = resolveSourceCapabilities(designSourceType);
6734
7130
  return DESIGN_CAPABILITY_NAMES.filter((name) => hasCapability(caps, name));
@@ -6771,6 +7167,11 @@ export default function DesignEditor() {
6771
7167
  ? bridgeSourceIdForCodeLayerNode(selectedCodeLayerNode)
6772
7168
  : undefined;
6773
7169
  }, [selectedCodeLayerNode]);
7170
+ const selectedElementAlreadyComponent = useMemo(() => {
7171
+ if (!selectedElement) return false;
7172
+ if (selectedElement.componentName?.trim()) return true;
7173
+ return codeLayerNodeLooksLikeComponent(selectedCodeLayerNode);
7174
+ }, [selectedCodeLayerNode, selectedElement]);
6774
7175
 
6775
7176
  useEffect(() => {
6776
7177
  setShaderFillPreview(null);
@@ -6791,11 +7192,8 @@ export default function DesignEditor() {
6791
7192
  // Outer HTML of the selection — backs the inline/Alpine "Inspect code" view.
6792
7193
  const selectedElementOuterHtml = useMemo(() => {
6793
7194
  if (!selectedElement?.selector) return null;
6794
- return (
6795
- selectedElement.htmlContent ??
6796
- getElementOuterHtml(activeContent, selectedElement.selector)
6797
- );
6798
- }, [activeContent, selectedElement?.selector, selectedElement?.htmlContent]);
7195
+ return getElementOuterHtml(activeContent, selectedElement.selector);
7196
+ }, [activeContent, selectedElement?.selector]);
6799
7197
 
6800
7198
  // §6.3 — the motion-dock target: the selected element's literal
6801
7199
  // `data-agent-native-node-id` (the value the motion compiler + preview bridge
@@ -6862,7 +7260,13 @@ export default function DesignEditor() {
6862
7260
  // resolveNodeToFile bridge op, which is wired through open-component-source
6863
7261
  // when an external file path is available; until that round-trip is hooked
6864
7262
  // up here the popover shows the projected HTML for all source types.
6865
- return { html: selectedElementOuterHtml, sourceLocation: null };
7263
+ return {
7264
+ html: selectedElementOuterHtml,
7265
+ tagName: selectedElement.tagName,
7266
+ id: selectedElement.id,
7267
+ classes: selectedElement.classes,
7268
+ sourceLocation: null,
7269
+ };
6866
7270
  }, [selectedElement, selectedElementOuterHtml]);
6867
7271
 
6868
7272
  const handleCreateComponent = useCallback(
@@ -6888,7 +7292,7 @@ export default function DesignEditor() {
6888
7292
  // Follow-up: ask the Design agent to extract props and replace repeated
6889
7293
  // instances with this component. The deterministic annotate above is the
6890
7294
  // core; this is an enhancement that runs in the agent chat.
6891
- sendToAgentChat({
7295
+ sendToDesignAgentChat({
6892
7296
  message: `Extract props for the "${name}" component and replace repeated instances on this design with it.`,
6893
7297
  context: [
6894
7298
  `Design id: "${id}".`,
@@ -6943,18 +7347,22 @@ export default function DesignEditor() {
6943
7347
  (screenId: string) => {
6944
7348
  if (screenId === activeFile?.id) return activeCodeLayerProjection;
6945
7349
  if (!fileContentById.has(screenId)) return null;
6946
- return buildCodeLayerProjection(getScreenContent(screenId));
7350
+ return buildCodeLayerProjection(getProjectionContentForScreen(screenId));
6947
7351
  },
6948
7352
  [
6949
7353
  activeCodeLayerProjection,
6950
7354
  activeFile?.id,
6951
7355
  fileContentById,
6952
- getScreenContent,
7356
+ getProjectionContentForScreen,
6953
7357
  ],
6954
7358
  );
6955
7359
 
6956
7360
  const replacePreviewContent = useCallback(
6957
- (nextContent: string, selector?: string | null) => {
7361
+ (
7362
+ nextContent: string,
7363
+ selector?: string | null,
7364
+ options: { forceFullDocument?: boolean } = {},
7365
+ ) => {
6958
7366
  const replaceContent = (window as any).__designCanvasReplaceContent;
6959
7367
  if (typeof replaceContent !== "function") return false;
6960
7368
  return Boolean(
@@ -6962,7 +7370,9 @@ export default function DesignEditor() {
6962
7370
  nextContent,
6963
7371
  selector ?? selectedCanvasSelector,
6964
7372
  selectedCanvasSelectorCandidates,
6965
- selectedElement?.sourceId ?? selectedElement?.id,
7373
+ {
7374
+ forceFullDocument: options.forceFullDocument === true,
7375
+ },
6966
7376
  ),
6967
7377
  );
6968
7378
  },
@@ -6989,6 +7399,7 @@ export default function DesignEditor() {
6989
7399
  options: {
6990
7400
  refreshPreview?: boolean;
6991
7401
  skipPreview?: boolean;
7402
+ forcePreviewFullDocument?: boolean;
6992
7403
  immediateSave?: boolean;
6993
7404
  persist?: boolean;
6994
7405
  recordHistory?: boolean;
@@ -7077,8 +7488,18 @@ export default function DesignEditor() {
7077
7488
  ? true
7078
7489
  : forceRefresh
7079
7490
  ? false
7080
- : replacePreviewContent(nextContent);
7081
- if (forceRefresh || !replacedPreview) {
7491
+ : replacePreviewContent(
7492
+ nextContent,
7493
+ null,
7494
+ options.forcePreviewFullDocument
7495
+ ? { forceFullDocument: true }
7496
+ : undefined,
7497
+ );
7498
+ if (
7499
+ forceRefresh ||
7500
+ options.forcePreviewFullDocument ||
7501
+ !replacedPreview
7502
+ ) {
7082
7503
  setContentRenderRevision((revision) => revision + 1);
7083
7504
  }
7084
7505
  if (ydoc && isSynced) {
@@ -7126,6 +7547,7 @@ export default function DesignEditor() {
7126
7547
  options: {
7127
7548
  refreshPreview?: boolean;
7128
7549
  skipPreview?: boolean;
7550
+ forcePreviewFullDocument?: boolean;
7129
7551
  persist?: boolean;
7130
7552
  recordHistory?: boolean;
7131
7553
  updatedAt?: string;
@@ -7225,27 +7647,47 @@ export default function DesignEditor() {
7225
7647
  motionAutosaveTimerRef.current = window.setTimeout(() => {
7226
7648
  motionAutosaveTimerRef.current = null;
7227
7649
  if (motionAutosaveRevisionRef.current !== revisionAtSchedule) return;
7650
+ const tracksForSave = motionTracks.map(
7651
+ ({ label: _label, ...track }) => track,
7652
+ );
7653
+ const currentContent = getFreshActiveFileContent({
7654
+ activeContent,
7655
+ latestContent: latestActiveContentRef.current,
7656
+ lastLocalContent: lastLocalContentRef.current,
7657
+ });
7658
+ const localMotionCss = compileMotionTimeline({
7659
+ id: motionTimelineId ?? "",
7660
+ designId: id,
7661
+ sourceRef: activeFile.id,
7662
+ filePath: null,
7663
+ tracks: tracksForSave,
7664
+ durationMs: motionDurationMs,
7665
+ defaultEase: "ease",
7666
+ compiledHash: null,
7667
+ createdAt: "",
7668
+ updatedAt: "",
7669
+ }).css;
7670
+ const localPatchedContent = injectManagedMotionCss(
7671
+ currentContent,
7672
+ localMotionCss,
7673
+ );
7228
7674
  applyMotionEdit(
7229
7675
  {
7230
7676
  designId: id,
7231
7677
  fileId: activeFile.id,
7232
7678
  timelineId: motionTimelineId ?? undefined,
7233
7679
  sourceRef: activeFile.id,
7234
- tracks: motionTracks.map(({ label: _label, ...track }) => track),
7680
+ tracks: tracksForSave,
7235
7681
  durationMs: motionDurationMs,
7236
- currentContent: getFreshActiveFileContent({
7237
- activeContent,
7238
- latestContent: latestActiveContentRef.current,
7239
- lastLocalContent: lastLocalContentRef.current,
7240
- }),
7241
- includeContent: true,
7682
+ currentContent,
7683
+ includeContent: false,
7242
7684
  },
7243
7685
  {
7244
7686
  onSuccess: (result) => {
7245
7687
  const response = result as {
7246
7688
  fileId?: unknown;
7247
7689
  timelineId?: unknown;
7248
- patchedContent?: unknown;
7690
+ updatedAt?: unknown;
7249
7691
  };
7250
7692
  if (typeof response.timelineId === "string") {
7251
7693
  setMotionTimelineId(response.timelineId);
@@ -7257,10 +7699,14 @@ export default function DesignEditor() {
7257
7699
  }
7258
7700
  if (
7259
7701
  typeof response.fileId === "string" &&
7260
- typeof response.patchedContent === "string"
7702
+ response.fileId === activeFile.id
7261
7703
  ) {
7262
- applyFileContentUpdate(response.fileId, response.patchedContent, {
7263
- refreshPreview: response.fileId === activeFile.id,
7704
+ applyFileContentUpdate(response.fileId, localPatchedContent, {
7705
+ refreshPreview: true,
7706
+ persist: false,
7707
+ ...(typeof response.updatedAt === "string"
7708
+ ? { updatedAt: response.updatedAt }
7709
+ : {}),
7264
7710
  });
7265
7711
  }
7266
7712
  void queryClient.invalidateQueries({
@@ -7408,6 +7854,7 @@ export default function DesignEditor() {
7408
7854
 
7409
7855
  if (targetFile.id === activeFile?.id) {
7410
7856
  applyLocalContentUpdate(nextContent, {
7857
+ forcePreviewFullDocument: true,
7411
7858
  historyBeforeContent: baseContent,
7412
7859
  immediateSave: true,
7413
7860
  });
@@ -7472,7 +7919,7 @@ export default function DesignEditor() {
7472
7919
  );
7473
7920
 
7474
7921
  const handlePrimitiveCreated = useCallback(
7475
- (screenId: string, nodeId: string) => {
7922
+ (screenId: string, nodeId: string, options?: { selectFrame?: boolean }) => {
7476
7923
  // B2/B4 fix: stay in overview mode after drawing a primitive. The user
7477
7924
  // drew a shape on the board — they should remain on the board with the
7478
7925
  // new primitive selected, matching Figma behaviour. We activate the
@@ -7487,7 +7934,9 @@ export default function DesignEditor() {
7487
7934
  setSelectedElement(null);
7488
7935
  setHoveredElement(null);
7489
7936
  setSelectedLayerIdsState([nodeId]);
7490
- setOverviewSelectedScreenIds([screenId]);
7937
+ setOverviewSelectedScreenIds(
7938
+ options?.selectFrame === false ? [] : [screenId],
7939
+ );
7491
7940
  setActiveTool("move");
7492
7941
  setMode("edit");
7493
7942
  });
@@ -7504,7 +7953,7 @@ export default function DesignEditor() {
7504
7953
  scheduleBeginTextEditForScreen(screenId, textNodeId);
7505
7954
  }
7506
7955
  },
7507
- [clearPendingOverviewLayerSelectionTimer],
7956
+ [clearPendingOverviewLayerSelectionTimer, scheduleBeginTextEditForScreen],
7508
7957
  );
7509
7958
 
7510
7959
  /**
@@ -7518,22 +7967,14 @@ export default function DesignEditor() {
7518
7967
  if (!boardFileId || !canEditDesign) return false;
7519
7968
  const result = handleCreatePrimitive(boardFileId, primitive);
7520
7969
  if (!result) return false;
7521
-
7522
- // For TEXT primitives drawn on the board surface, immediately enter
7523
- // text-editing mode via begin-text-edit. The target is the board file,
7524
- // so use the same iframe-targeted retry path as screen primitives.
7525
- if (primitive.kind === "text") {
7526
- const textNodeId =
7527
- pendingTextEditNodeIdRef.current ?? primitive.nodeId ?? null;
7528
- pendingTextEditNodeIdRef.current = null;
7529
- if (textNodeId) {
7530
- scheduleBeginTextEditForScreen(boardFileId, textNodeId);
7531
- }
7970
+ const nodeId = typeof result === "string" ? result : primitive.nodeId;
7971
+ if (nodeId) {
7972
+ handlePrimitiveCreated(boardFileId, nodeId, { selectFrame: false });
7532
7973
  }
7533
7974
 
7534
7975
  return result;
7535
7976
  },
7536
- [boardFileId, canEditDesign, handleCreatePrimitive],
7977
+ [boardFileId, canEditDesign, handleCreatePrimitive, handlePrimitiveCreated],
7537
7978
  );
7538
7979
 
7539
7980
  const handleOverviewScreenSelectionChange = useCallback(
@@ -7571,6 +8012,7 @@ export default function DesignEditor() {
7571
8012
 
7572
8013
  const handleMoveTool = useCallback(() => {
7573
8014
  if (!canEditDesign) return;
8015
+ blurActiveDesignEditableTarget();
7574
8016
  setActiveTool("move");
7575
8017
  setMode("edit");
7576
8018
  setDrawMode(false);
@@ -7579,6 +8021,7 @@ export default function DesignEditor() {
7579
8021
 
7580
8022
  const handleFrameTool = useCallback(() => {
7581
8023
  if (!canEditDesign) return;
8024
+ blurActiveDesignEditableTarget();
7582
8025
  flushSync(() => {
7583
8026
  setActiveTool("frame");
7584
8027
  setMode("edit");
@@ -7592,6 +8035,7 @@ export default function DesignEditor() {
7592
8035
 
7593
8036
  const handleTextTool = useCallback(() => {
7594
8037
  if (!canEditDesign) return;
8038
+ blurActiveDesignEditableTarget();
7595
8039
  flushSync(() => {
7596
8040
  setActiveTool("text");
7597
8041
  viewModeRef.current = "overview";
@@ -7606,6 +8050,7 @@ export default function DesignEditor() {
7606
8050
  const handleShapeTool = useCallback(
7607
8051
  (tool: ShapeTool) => {
7608
8052
  if (!canEditDesign) return;
8053
+ blurActiveDesignEditableTarget();
7609
8054
  flushSync(() => {
7610
8055
  setActiveTool(tool);
7611
8056
  viewModeRef.current = "overview";
@@ -7625,6 +8070,7 @@ export default function DesignEditor() {
7625
8070
 
7626
8071
  const handlePenTool = useCallback(() => {
7627
8072
  if (!canEditDesign) return;
8073
+ blurActiveDesignEditableTarget();
7628
8074
  flushSync(() => {
7629
8075
  setActiveTool("pen");
7630
8076
  viewModeRef.current = "overview";
@@ -7638,6 +8084,7 @@ export default function DesignEditor() {
7638
8084
 
7639
8085
  const handleHandTool = useCallback(() => {
7640
8086
  if (!canEditDesign) return;
8087
+ blurActiveDesignEditableTarget();
7641
8088
  setActiveTool("hand");
7642
8089
  setMode("edit");
7643
8090
  setDrawMode(false);
@@ -7648,6 +8095,7 @@ export default function DesignEditor() {
7648
8095
 
7649
8096
  const handleScaleTool = useCallback(() => {
7650
8097
  if (!activeFile || !canEditDesign) return;
8098
+ blurActiveDesignEditableTarget();
7651
8099
  setActiveTool("scale");
7652
8100
  setMode("edit");
7653
8101
  setDrawMode(false);
@@ -7754,7 +8202,7 @@ export default function DesignEditor() {
7754
8202
  "For tiny source changes, prefer `edit-design`, but make sure the tweak definitions are saved so the Tweaks panel updates.",
7755
8203
  ].join("\n");
7756
8204
 
7757
- sendToAgentChat({
8205
+ sendToDesignAgentChat({
7758
8206
  message: `Add tweak controls to "${design.title}": ${trimmed}`,
7759
8207
  context,
7760
8208
  submit: true,
@@ -8018,7 +8466,7 @@ export default function DesignEditor() {
8018
8466
  );
8019
8467
 
8020
8468
  const handleScreenElementSelect = useCallback(
8021
- (screenId: string, info: ElementInfo) => {
8469
+ (screenId: string, info: ElementInfo, intent?: ElementSelectionIntent) => {
8022
8470
  const pendingLayerId = pendingOverviewLayerSelectionRef.current;
8023
8471
  const pendingScreenId = pendingOverviewScreenSelectionRef.current;
8024
8472
  const createdSelection = createdOverviewLayerSelection;
@@ -8051,11 +8499,33 @@ export default function DesignEditor() {
8051
8499
  ) {
8052
8500
  return;
8053
8501
  }
8502
+ const additiveSelection = Boolean(
8503
+ node &&
8504
+ (intent?.additive ||
8505
+ intent?.range ||
8506
+ intent?.shiftKey ||
8507
+ intent?.metaKey ||
8508
+ intent?.ctrlKey),
8509
+ );
8054
8510
  setActiveFileId(screenId);
8055
8511
  setSelectedElement(canonical);
8056
8512
  setHoveredElement(null);
8057
8513
  setHoveredElementScreenId(null);
8058
- setSelectedLayerIdsState(node ? [node.id] : []);
8514
+ if (node && additiveSelection) {
8515
+ setSelectedLayerIdsState((current) => {
8516
+ const removeExisting =
8517
+ Boolean(intent?.metaKey || intent?.ctrlKey) &&
8518
+ !intent?.shiftKey &&
8519
+ current.includes(node.id);
8520
+ if (removeExisting) {
8521
+ const next = current.filter((layerId) => layerId !== node.id);
8522
+ return next.length > 0 ? next : [node.id];
8523
+ }
8524
+ return dedupeStringIds([...current, node.id]);
8525
+ });
8526
+ } else {
8527
+ setSelectedLayerIdsState(node ? [node.id] : []);
8528
+ }
8059
8529
  if (viewModeRef.current === "overview") {
8060
8530
  setOverviewSelectedScreenIds([]);
8061
8531
  }
@@ -8108,10 +8578,10 @@ export default function DesignEditor() {
8108
8578
  );
8109
8579
 
8110
8580
  const handleElementSelect = useCallback(
8111
- (info: ElementInfo) => {
8581
+ (info: ElementInfo, intent?: ElementSelectionIntent) => {
8112
8582
  const screenId = activeFile?.id ?? activeFileId;
8113
8583
  if (screenId) {
8114
- handleScreenElementSelect(screenId, info);
8584
+ handleScreenElementSelect(screenId, info, intent);
8115
8585
  return;
8116
8586
  }
8117
8587
  setSelectedElement(
@@ -8296,7 +8766,11 @@ export default function DesignEditor() {
8296
8766
  // advance lastLocalContentRef.current to resolvedNextContent below, the
8297
8767
  // next synchronous call reads the previous call's result and the patches
8298
8768
  // compose. Falls back to activeContent when the ref is unset (file switch).
8769
+ const activeLiveSnapshot = activeFile
8770
+ ? liveScreenSnapshotsById[activeFile.id]
8771
+ : undefined;
8299
8772
  const baseContent =
8773
+ activeLiveSnapshot?.html ??
8300
8774
  latestActiveContentRef.current ??
8301
8775
  lastLocalContentRef.current ??
8302
8776
  activeContent;
@@ -8493,62 +8967,77 @@ export default function DesignEditor() {
8493
8967
  );
8494
8968
  })
8495
8969
  : null;
8496
- const yjsHistoryAvailable = Boolean(
8497
- viewModeRef.current !== "overview" &&
8498
- ydoc &&
8499
- isSynced &&
8500
- undoManagerRef.current,
8501
- );
8502
- if (
8503
- !yjsHistoryAvailable &&
8504
- !suppressContentHistoryRef.current &&
8505
- baseContent !== resolvedNextContent
8506
- ) {
8507
- const change = {
8508
- fileId: activeFile.id,
8509
- before: baseContent,
8510
- after: resolvedNextContent,
8511
- };
8512
- if (viewModeRef.current === "overview") {
8513
- recordContentHistoryEntry(change);
8514
- } else {
8515
- recordLocalContentHistoryEntry(change);
8970
+ const liveSnapshotUpdated = activeLiveSnapshot
8971
+ ? updateLiveScreenSnapshotContent(activeFile.id, resolvedNextContent)
8972
+ : false;
8973
+ if (liveSnapshotUpdated) {
8974
+ setPatchProof((prev) =>
8975
+ prev?.id === proofId ? { ...prev, status: "queued" } : prev,
8976
+ );
8977
+ if (!runtimeStyleApplied) {
8978
+ setContentRenderRevision((revision) => revision + 1);
8979
+ }
8980
+ } else {
8981
+ const yjsHistoryAvailable = Boolean(
8982
+ viewModeRef.current !== "overview" &&
8983
+ ydoc &&
8984
+ isSynced &&
8985
+ undoManagerRef.current,
8986
+ );
8987
+ if (
8988
+ !yjsHistoryAvailable &&
8989
+ !suppressContentHistoryRef.current &&
8990
+ baseContent !== resolvedNextContent
8991
+ ) {
8992
+ const change = {
8993
+ fileId: activeFile.id,
8994
+ before: baseContent,
8995
+ after: resolvedNextContent,
8996
+ };
8997
+ if (viewModeRef.current === "overview") {
8998
+ recordContentHistoryEntry(change);
8999
+ } else {
9000
+ recordLocalContentHistoryEntry(change);
9001
+ }
8516
9002
  }
8517
- }
8518
9003
 
8519
- setCollabContent(resolvedNextContent);
8520
- setCollabContentFileId(activeFile.id);
8521
- setPatchProof((prev) =>
8522
- prev?.id === proofId ? { ...prev, status: "queued" } : prev,
8523
- );
8524
- // Mark as our own write so the get-design reconcile + Yjs observe don't
8525
- // treat the echo as an external edit and fight the live value.
8526
- lastLocalContentRef.current = resolvedNextContent;
8527
- latestActiveContentRef.current = resolvedNextContent;
8528
- // Write the edit into the shared Y.Doc so other open clients see it live
8529
- // through Yjs (not only via the slower update-file → applyText round-trip).
8530
- // Single-screen edits use the active-file UndoManager. Overview edits are
8531
- // tracked in the global file-content stack so all screens share one order.
8532
- if (ydoc && isSynced) {
8533
- const ytext = ydoc.getText("content");
8534
- if (ytext.toString() !== resolvedNextContent) {
8535
- ydoc.transact(
8536
- () => {
8537
- ytext.delete(0, ytext.length);
8538
- ytext.insert(0, resolvedNextContent);
8539
- },
8540
- yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
8541
- );
9004
+ setCollabContent(resolvedNextContent);
9005
+ setCollabContentFileId(activeFile.id);
9006
+ setPatchProof((prev) =>
9007
+ prev?.id === proofId ? { ...prev, status: "queued" } : prev,
9008
+ );
9009
+ // Mark as our own write so the get-design reconcile + Yjs observe don't
9010
+ // treat the echo as an external edit and fight the live value.
9011
+ lastLocalContentRef.current = resolvedNextContent;
9012
+ latestActiveContentRef.current = resolvedNextContent;
9013
+ // Write the edit into the shared Y.Doc so other open clients see it live
9014
+ // through Yjs (not only via the slower update-file → applyText round-trip).
9015
+ // Single-screen edits use the active-file UndoManager. Overview edits are
9016
+ // tracked in the global file-content stack so all screens share one order.
9017
+ if (ydoc && isSynced) {
9018
+ const ytext = ydoc.getText("content");
9019
+ if (ytext.toString() !== resolvedNextContent) {
9020
+ ydoc.transact(
9021
+ () => {
9022
+ ytext.delete(0, ytext.length);
9023
+ ytext.insert(0, resolvedNextContent);
9024
+ },
9025
+ yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
9026
+ );
9027
+ }
9028
+ }
9029
+ queueFileContentSave(activeFile.id, resolvedNextContent, {
9030
+ syncCollab: !(ydoc && isSynced),
9031
+ });
9032
+ if (
9033
+ shouldReplacePreviewAfterVisualStyleCommit({
9034
+ runtimeApplied: options.runtimeApplied,
9035
+ runtimeStyleApplied,
9036
+ }) &&
9037
+ !replacePreviewContent(resolvedNextContent, selector)
9038
+ ) {
9039
+ setContentRenderRevision((revision) => revision + 1);
8542
9040
  }
8543
- }
8544
- queueFileContentSave(activeFile.id, resolvedNextContent, {
8545
- syncCollab: !(ydoc && isSynced),
8546
- });
8547
- if (
8548
- !runtimeStyleApplied &&
8549
- !replacePreviewContent(resolvedNextContent, selector)
8550
- ) {
8551
- setContentRenderRevision((revision) => revision + 1);
8552
9041
  }
8553
9042
  if (resolvedNode) setSelectedLayerIdsState([resolvedNode.id]);
8554
9043
  setSelectedElement((prev) => {
@@ -8576,40 +9065,153 @@ export default function DesignEditor() {
8576
9065
  activeFile,
8577
9066
  activeBreakpointWidthState,
8578
9067
  canEditDesign,
9068
+ liveScreenSnapshotsById,
8579
9069
  queueFileContentSave,
8580
9070
  recordContentHistoryEntry,
8581
9071
  recordLocalContentHistoryEntry,
8582
9072
  replacePreviewContent,
8583
9073
  selectedElement,
8584
9074
  t,
9075
+ updateLiveScreenSnapshotContent,
8585
9076
  ydoc,
8586
9077
  isSynced,
8587
9078
  ],
8588
9079
  );
8589
9080
 
8590
- const handleStyleChange = useCallback(
8591
- (property: string, value: string) => {
8592
- const selector = selectedElement?.selector ?? "body";
8593
- if (
8594
- textEditingState.active &&
8595
- textEditingState.hasRange &&
8596
- textEditingState.selector === selector
8597
- ) {
8598
- const sendStyleChange = (window as any).__designCanvasSendStyle;
8599
- if (typeof sendStyleChange === "function") {
8600
- sendStyleChange(selector, property, value, {
8601
- selectorCandidates: selectedCanvasSelectorCandidates,
8602
- nodeId: selectedElement?.sourceId,
8603
- });
8604
- return;
8605
- }
8606
- }
8607
- commitVisualStyles(selector, { [property]: value });
8608
- },
8609
- [
8610
- commitVisualStyles,
8611
- selectedElement?.selector,
8612
- selectedElement?.sourceId,
9081
+ const commitStylesToSelectedLayers = useCallback(
9082
+ (styles: Record<string, string>) => {
9083
+ if (!canEditDesign) return false;
9084
+ const entries = Object.entries(styles).filter(
9085
+ ([, value]) => value !== undefined,
9086
+ );
9087
+ if (entries.length === 0) return false;
9088
+ const effectiveLayerState = effectiveCodeLayerStateRef.current;
9089
+ const targets = selectedLayerTargetsRef.current.filter(
9090
+ (target) =>
9091
+ !effectiveLayerState.lockedIds.has(target.fileId) &&
9092
+ !effectiveLayerState.hiddenIds.has(target.fileId) &&
9093
+ !effectiveLayerState.lockedIds.has(target.layerId) &&
9094
+ !effectiveLayerState.hiddenIds.has(target.layerId),
9095
+ );
9096
+ if (targets.length <= 1) return false;
9097
+
9098
+ const targetsByFile = new Map<string, SelectedLayerTarget[]>();
9099
+ targets.forEach((target) => {
9100
+ targetsByFile.set(target.fileId, [
9101
+ ...(targetsByFile.get(target.fileId) ?? []),
9102
+ target,
9103
+ ]);
9104
+ });
9105
+
9106
+ let appliedAny = false;
9107
+ targetsByFile.forEach((fileTargets, fileId) => {
9108
+ const baseContent =
9109
+ fileId === activeFile?.id
9110
+ ? getFreshActiveFileContent({
9111
+ activeContent,
9112
+ latestContent: latestActiveContentRef.current,
9113
+ lastLocalContent: lastLocalContentRef.current,
9114
+ })
9115
+ : getScreenContent(fileId);
9116
+ if (!baseContent) return;
9117
+ let nextContent = baseContent;
9118
+ let projection = buildCodeLayerProjection(nextContent);
9119
+ fileTargets.forEach((target) => {
9120
+ const sourceId = bridgeSourceIdForCodeLayerNode(target.node);
9121
+ const selector = preferredCodeLayerSelector(target.node);
9122
+ const node =
9123
+ projection.nodes.find((candidate) =>
9124
+ codeLayerNodeMatchesBridgeTarget(candidate, selector, sourceId),
9125
+ ) ??
9126
+ projection.nodes.find(
9127
+ (candidate) => candidate.id === target.node.id,
9128
+ );
9129
+ if (!node) return;
9130
+
9131
+ entries.forEach(([property, value]) => {
9132
+ const patch = applyVisualEdit(nextContent, {
9133
+ kind: "style",
9134
+ target: { nodeId: node.id },
9135
+ property,
9136
+ value,
9137
+ });
9138
+ if (patch.result.status !== "applied") return;
9139
+ nextContent = patch.content;
9140
+ projection = patch.projection;
9141
+ });
9142
+ });
9143
+ if (nextContent === baseContent) return;
9144
+ appliedAny = true;
9145
+ applyFileContentUpdate(fileId, nextContent, {
9146
+ refreshPreview: fileId === activeFile?.id,
9147
+ });
9148
+ });
9149
+
9150
+ if (appliedAny) {
9151
+ const stylePatch = Object.fromEntries(entries);
9152
+ const primaryTarget = targets[targets.length - 1];
9153
+ if (primaryTarget) {
9154
+ setSelectedElement((previous) => {
9155
+ const previousMatches =
9156
+ previous &&
9157
+ codeLayerNodeMatchesBridgeTarget(
9158
+ primaryTarget.node,
9159
+ previous.selector,
9160
+ previous.sourceId ?? previous.id,
9161
+ );
9162
+ const base = previousMatches
9163
+ ? canonicalElementInfoForCodeLayerNode(
9164
+ previous,
9165
+ primaryTarget.node,
9166
+ )
9167
+ : primaryTarget.elementInfo;
9168
+ return {
9169
+ ...base,
9170
+ computedStyles: {
9171
+ ...base.computedStyles,
9172
+ ...stylePatch,
9173
+ },
9174
+ };
9175
+ });
9176
+ }
9177
+ }
9178
+
9179
+ return true;
9180
+ },
9181
+ [
9182
+ activeContent,
9183
+ activeFile?.id,
9184
+ applyFileContentUpdate,
9185
+ canEditDesign,
9186
+ getScreenContent,
9187
+ ],
9188
+ );
9189
+
9190
+ const handleStyleChange = useCallback(
9191
+ (property: string, value: string) => {
9192
+ const selector = selectedElement?.selector ?? "body";
9193
+ if (
9194
+ textEditingState.active &&
9195
+ textEditingState.hasRange &&
9196
+ textEditingState.selector === selector
9197
+ ) {
9198
+ const sendStyleChange = (window as any).__designCanvasSendStyle;
9199
+ if (typeof sendStyleChange === "function") {
9200
+ sendStyleChange(selector, property, value, {
9201
+ selectorCandidates: selectedCanvasSelectorCandidates,
9202
+ nodeId: selectedElement?.sourceId,
9203
+ });
9204
+ return;
9205
+ }
9206
+ }
9207
+ if (commitStylesToSelectedLayers({ [property]: value })) return;
9208
+ commitVisualStyles(selector, { [property]: value });
9209
+ },
9210
+ [
9211
+ commitStylesToSelectedLayers,
9212
+ commitVisualStyles,
9213
+ selectedElement?.selector,
9214
+ selectedElement?.sourceId,
8613
9215
  selectedCanvasSelectorCandidates,
8614
9216
  textEditingState.active,
8615
9217
  textEditingState.hasRange,
@@ -8624,9 +9226,14 @@ export default function DesignEditor() {
8624
9226
  Boolean(value),
8625
9227
  );
8626
9228
  if (entries.length === 0) return;
9229
+ if (commitStylesToSelectedLayers(Object.fromEntries(entries))) return;
8627
9230
  commitVisualStyles(selector, Object.fromEntries(entries));
8628
9231
  },
8629
- [commitVisualStyles, selectedElement?.selector],
9232
+ [
9233
+ commitStylesToSelectedLayers,
9234
+ commitVisualStyles,
9235
+ selectedElement?.selector,
9236
+ ],
8630
9237
  );
8631
9238
 
8632
9239
  const getFreshActiveContent = useCallback(
@@ -8703,14 +9310,38 @@ export default function DesignEditor() {
8703
9310
  );
8704
9311
  return false;
8705
9312
  }
8706
- const movedNode =
9313
+ const movedNodeAttrId =
9314
+ targetNode?.dataAttributes["data-agent-native-node-id"] ??
9315
+ details?.sourceId ??
9316
+ elementInfo?.sourceId ??
8707
9317
  (patch.result.after?.nodeId
8708
9318
  ? patch.projection.nodes.find(
8709
9319
  (node) => node.id === patch.result.after?.nodeId,
9320
+ )?.dataAttributes["data-agent-native-node-id"]
9321
+ : undefined);
9322
+ const nextContent =
9323
+ isAbsoluteCodeLayerNode(targetNode) && movedNodeAttrId
9324
+ ? removeAbsolutePositioningFromNodeInHtml(
9325
+ patch.content,
9326
+ movedNodeAttrId,
9327
+ )
9328
+ : patch.content;
9329
+ const nextProjection = buildCodeLayerProjection(nextContent);
9330
+ const movedNode =
9331
+ (movedNodeAttrId
9332
+ ? nextProjection.nodes.find(
9333
+ (node) =>
9334
+ node.dataAttributes["data-agent-native-node-id"] ===
9335
+ movedNodeAttrId,
9336
+ )
9337
+ : null) ??
9338
+ (patch.result.after?.nodeId
9339
+ ? nextProjection.nodes.find(
9340
+ (node) => node.id === patch.result.after?.nodeId,
8710
9341
  )
8711
9342
  : null) ??
8712
9343
  resolveCodeLayerNodeFromBridge(
8713
- patch.projection,
9344
+ nextProjection,
8714
9345
  selector,
8715
9346
  details?.sourceId ??
8716
9347
  elementInfo?.sourceId ??
@@ -8718,7 +9349,7 @@ export default function DesignEditor() {
8718
9349
  ? bridgeSourceIdForCodeLayerNode(targetNode)
8719
9350
  : undefined),
8720
9351
  );
8721
- applyLocalContentUpdate(patch.content, { skipPreview: true });
9352
+ applyLocalContentUpdate(nextContent, { skipPreview: true });
8722
9353
  if (movedNode) setSelectedLayerIdsState([movedNode.id]);
8723
9354
  if (elementInfo) {
8724
9355
  setSelectedElement({
@@ -8829,7 +9460,8 @@ export default function DesignEditor() {
8829
9460
  ) => {
8830
9461
  if (!canEditDesign) return;
8831
9462
  if (!activeFile) return;
8832
- const baseContent = getFreshActiveContent();
9463
+ const activeLiveSnapshot = liveScreenSnapshotsById[activeFile.id];
9464
+ const baseContent = activeLiveSnapshot?.html ?? getFreshActiveContent();
8833
9465
  const projection = buildCodeLayerProjection(baseContent);
8834
9466
  const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
8835
9467
  const targetNode = targetInfo
@@ -8862,7 +9494,11 @@ export default function DesignEditor() {
8862
9494
  );
8863
9495
  return;
8864
9496
  }
8865
- applyLocalContentUpdate(nextContent, { skipPreview: true });
9497
+ if (activeLiveSnapshot) {
9498
+ updateLiveScreenSnapshotContent(activeFile.id, nextContent);
9499
+ } else {
9500
+ applyLocalContentUpdate(nextContent, { skipPreview: true });
9501
+ }
8866
9502
  setActiveTool("text");
8867
9503
  setMode("edit");
8868
9504
  if (removedContent) {
@@ -8905,7 +9541,9 @@ export default function DesignEditor() {
8905
9541
  applyLocalContentUpdate,
8906
9542
  canEditDesign,
8907
9543
  getFreshActiveContent,
9544
+ liveScreenSnapshotsById,
8908
9545
  t,
9546
+ updateLiveScreenSnapshotContent,
8909
9547
  ],
8910
9548
  );
8911
9549
 
@@ -8925,7 +9563,8 @@ export default function DesignEditor() {
8925
9563
  ([, value]) => value !== undefined,
8926
9564
  );
8927
9565
  if (entries.length === 0) return;
8928
- const baseContent = getScreenContent(screenId);
9566
+ const liveSnapshot = liveScreenSnapshotsById[screenId];
9567
+ const baseContent = liveSnapshot?.html ?? getScreenContent(screenId);
8929
9568
  const projection = buildCodeLayerProjection(baseContent);
8930
9569
  const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
8931
9570
  const targetNode = targetInfo
@@ -8969,6 +9608,10 @@ export default function DesignEditor() {
8969
9608
  );
8970
9609
  return;
8971
9610
  }
9611
+ if (liveSnapshot) {
9612
+ updateLiveScreenSnapshotContent(screenId, nextContent);
9613
+ return;
9614
+ }
8972
9615
  applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
8973
9616
  },
8974
9617
  [
@@ -8977,7 +9620,9 @@ export default function DesignEditor() {
8977
9620
  canEditDesign,
8978
9621
  getScreenContent,
8979
9622
  handleVisualStyleChange,
9623
+ liveScreenSnapshotsById,
8980
9624
  t,
9625
+ updateLiveScreenSnapshotContent,
8981
9626
  ],
8982
9627
  );
8983
9628
 
@@ -9165,7 +9810,8 @@ export default function DesignEditor() {
9165
9810
  return;
9166
9811
  }
9167
9812
  if (!canEditDesign) return;
9168
- const baseContent = getScreenContent(screenId);
9813
+ const liveSnapshot = liveScreenSnapshotsById[screenId];
9814
+ const baseContent = liveSnapshot?.html ?? getScreenContent(screenId);
9169
9815
  const projection = buildCodeLayerProjection(baseContent);
9170
9816
  const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
9171
9817
  const targetNode = targetInfo
@@ -9198,7 +9844,11 @@ export default function DesignEditor() {
9198
9844
  );
9199
9845
  return;
9200
9846
  }
9201
- applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
9847
+ if (liveSnapshot) {
9848
+ updateLiveScreenSnapshotContent(screenId, nextContent);
9849
+ } else {
9850
+ applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
9851
+ }
9202
9852
  setActiveFileId(screenId);
9203
9853
  setActiveTool("text");
9204
9854
  setMode("edit");
@@ -9243,7 +9893,9 @@ export default function DesignEditor() {
9243
9893
  canEditDesign,
9244
9894
  getScreenContent,
9245
9895
  handleTextContentChange,
9896
+ liveScreenSnapshotsById,
9246
9897
  t,
9898
+ updateLiveScreenSnapshotContent,
9247
9899
  ],
9248
9900
  );
9249
9901
 
@@ -9424,7 +10076,9 @@ export default function DesignEditor() {
9424
10076
  });
9425
10077
  if (result) {
9426
10078
  pasteCascadeRef.current += 1;
9427
- applyLocalContentUpdate(result.content);
10079
+ applyLocalContentUpdate(result.content, {
10080
+ forcePreviewFullDocument: true,
10081
+ });
9428
10082
  selectInsertedLayers(
9429
10083
  activeFile.id,
9430
10084
  result.content,
@@ -9476,7 +10130,9 @@ export default function DesignEditor() {
9476
10130
  });
9477
10131
  if (!result) return;
9478
10132
  if (!position) pasteCascadeRef.current += 1;
9479
- applyLocalContentUpdate(result.content);
10133
+ applyLocalContentUpdate(result.content, {
10134
+ forcePreviewFullDocument: true,
10135
+ });
9480
10136
  selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
9481
10137
  },
9482
10138
  [
@@ -9508,7 +10164,9 @@ export default function DesignEditor() {
9508
10164
  },
9509
10165
  );
9510
10166
  if (!result) return;
9511
- applyLocalContentUpdate(result.content);
10167
+ applyLocalContentUpdate(result.content, {
10168
+ forcePreviewFullDocument: true,
10169
+ });
9512
10170
  selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
9513
10171
  } else {
9514
10172
  handlePasteSelection();
@@ -9559,7 +10217,10 @@ export default function DesignEditor() {
9559
10217
  insertedRootNodeIds.unshift(...result.rootNodeIds);
9560
10218
  }
9561
10219
  if (insertedRootNodeIds.length === 0) continue;
9562
- applyFileContentUpdate(file.id, content, { refreshPreview: false });
10220
+ applyFileContentUpdate(file.id, content, {
10221
+ forcePreviewFullDocument: true,
10222
+ refreshPreview: false,
10223
+ });
9563
10224
  selectedScreenIds.push(file.id);
9564
10225
  const finalProjection = buildCodeLayerProjection(content);
9565
10226
  insertedRootNodeIds.forEach((rootNodeId) => {
@@ -9624,7 +10285,9 @@ export default function DesignEditor() {
9624
10285
  placement: "after",
9625
10286
  });
9626
10287
  if (nextContent) {
9627
- applyLocalContentUpdate(nextContent);
10288
+ applyLocalContentUpdate(nextContent, {
10289
+ forcePreviewFullDocument: true,
10290
+ });
9628
10291
  } else {
9629
10292
  toast.error(t("designEditor.toasts.duplicateElementFailed"));
9630
10293
  }
@@ -9835,57 +10498,6 @@ export default function DesignEditor() {
9835
10498
  t,
9836
10499
  ]);
9837
10500
 
9838
- /**
9839
- * Convert the selected container to full auto-layout. Applies the
9840
- * { kind: "autoLayout", enabled: true } substrate intent which sets
9841
- * display:flex on the target AND strips position:absolute/left/top/right/bottom
9842
- * from its direct children so they become flow children.
9843
- */
9844
- const handleAutoLayoutConvert = useCallback(
9845
- (
9846
- targetNodeId: string,
9847
- opts?: { direction?: "row" | "column"; gap?: string },
9848
- ) => {
9849
- if (!canEditDesign || !activeFile) return;
9850
- const baseContent = getFreshActiveContent();
9851
- const patch = applyVisualEdit(baseContent, {
9852
- kind: "autoLayout",
9853
- targetId: targetNodeId,
9854
- enabled: true,
9855
- direction: opts?.direction ?? "row",
9856
- gap: opts?.gap ?? "8px",
9857
- });
9858
- if (patch.result.status !== "applied") {
9859
- toast.error(
9860
- codeLayerPatchMessage(
9861
- patch.result.message,
9862
- t("designEditor.toasts.layerMoveFailed"),
9863
- ),
9864
- { duration: 4000 },
9865
- );
9866
- return;
9867
- }
9868
- applyLocalContentUpdate(patch.content, { skipPreview: true });
9869
- // Re-select the container so the inspector refreshes its layout state.
9870
- const containerNode = patch.projection.nodes.find(
9871
- (n) =>
9872
- n.dataAttributes["data-agent-native-node-id"] === targetNodeId ||
9873
- n.id === targetNodeId,
9874
- );
9875
- if (containerNode) {
9876
- setSelectedLayerIdsState([containerNode.id]);
9877
- setSelectedElement(elementInfoFromCodeLayerNode(containerNode));
9878
- }
9879
- },
9880
- [
9881
- activeFile,
9882
- applyLocalContentUpdate,
9883
- canEditDesign,
9884
- getFreshActiveContent,
9885
- t,
9886
- ],
9887
- );
9888
-
9889
10501
  /**
9890
10502
  * Handle a primitive being drag-dropped onto another primitive in the
9891
10503
  * MultiScreenCanvas overview (CONTRACT: onPrimitiveReparent prop).
@@ -10083,6 +10695,8 @@ export default function DesignEditor() {
10083
10695
  targetScreenId,
10084
10696
  targetAnchorNodeId,
10085
10697
  targetAnchorPlacement,
10698
+ targetLocalPoint,
10699
+ sourcePointerOffset,
10086
10700
  }: {
10087
10701
  sourceSelector: string;
10088
10702
  sourceNodeId?: string;
@@ -10090,6 +10704,9 @@ export default function DesignEditor() {
10090
10704
  targetScreenId: string;
10091
10705
  targetAnchorNodeId?: string;
10092
10706
  targetAnchorPlacement?: "before" | "after" | "inside";
10707
+ targetCanvasPoint?: { x: number; y: number };
10708
+ targetLocalPoint?: { x: number; y: number };
10709
+ sourcePointerOffset?: { x: number; y: number };
10093
10710
  }) => {
10094
10711
  if (!canEditDesign) return;
10095
10712
  if (sourceScreenId === targetScreenId) return;
@@ -10151,16 +10768,23 @@ export default function DesignEditor() {
10151
10768
  return;
10152
10769
  }
10153
10770
 
10154
- // Strip absolute positioning from the moved node in the destination so
10155
- // it flows naturally as a body child (mirrors handleOverviewPrimitiveReparent).
10156
- // Use result.movedNodeId (the final id in destHtml, which may differ from
10157
- // nodeAttrId when a collision triggered an id re-stamp) so the strip and
10158
- // re-selection always find the correct element.
10771
+ // Hit-test anchors are emitted only for auto-layout insertion targets. If
10772
+ // there is no anchor, preserve absolute mode and rebase left/top to the
10773
+ // release point so screen↔board moves behave like Figma absolute layers.
10159
10774
  const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
10160
- const strippedDest = removeAbsolutePositioningFromNodeInHtml(
10161
- result.destHtml,
10162
- destNodeAttrId,
10163
- );
10775
+ const nextDestContent = targetAnchorAttrId
10776
+ ? removeAbsolutePositioningFromNodeInHtml(
10777
+ result.destHtml,
10778
+ destNodeAttrId,
10779
+ )
10780
+ : targetLocalPoint
10781
+ ? setAbsolutePositioningForNodeInHtml(
10782
+ result.destHtml,
10783
+ destNodeAttrId,
10784
+ targetLocalPoint,
10785
+ sourcePointerOffset,
10786
+ )
10787
+ : result.destHtml;
10164
10788
 
10165
10789
  recordContentHistoryEntry({
10166
10790
  changes: [
@@ -10172,7 +10796,7 @@ export default function DesignEditor() {
10172
10796
  {
10173
10797
  fileId: targetScreenId,
10174
10798
  before: destContent,
10175
- after: strippedDest,
10799
+ after: nextDestContent,
10176
10800
  },
10177
10801
  ],
10178
10802
  });
@@ -10181,7 +10805,7 @@ export default function DesignEditor() {
10181
10805
  recordHistory: false,
10182
10806
  refreshPreview: true,
10183
10807
  });
10184
- applyFileContentUpdate(targetScreenId, strippedDest, {
10808
+ applyFileContentUpdate(targetScreenId, nextDestContent, {
10185
10809
  recordHistory: false,
10186
10810
  refreshPreview: true,
10187
10811
  });
@@ -10189,7 +10813,7 @@ export default function DesignEditor() {
10189
10813
  // Switch active screen to the target and select the moved node; viewMode
10190
10814
  // stays "overview" (no setViewMode call).
10191
10815
  setActiveFileId(targetScreenId);
10192
- const finalProjection = buildCodeLayerProjection(strippedDest);
10816
+ const finalProjection = buildCodeLayerProjection(nextDestContent);
10193
10817
  const movedNodeFinal = finalProjection.nodes.find(
10194
10818
  (n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
10195
10819
  );
@@ -10429,9 +11053,8 @@ export default function DesignEditor() {
10429
11053
  queueFileContentSave(activeFile.id, next, {
10430
11054
  syncCollab: !(ydoc && isSynced),
10431
11055
  });
10432
- if (!replacePreviewContent(next)) {
10433
- setContentRenderRevision((revision) => revision + 1);
10434
- }
11056
+ replacePreviewContent(next, null, { forceFullDocument: true });
11057
+ setContentRenderRevision((revision) => revision + 1);
10435
11058
  // Clear stale selection if the undo removed the selected element.
10436
11059
  setSelectedElement((prev) => {
10437
11060
  if (!prev) return prev;
@@ -10468,6 +11091,7 @@ export default function DesignEditor() {
10468
11091
  ];
10469
11092
  applyLocalContentUpdate(entry.before, {
10470
11093
  refreshPreview: false,
11094
+ forcePreviewFullDocument: true,
10471
11095
  immediateSave: true,
10472
11096
  recordHistory: false,
10473
11097
  });
@@ -10514,6 +11138,7 @@ export default function DesignEditor() {
10514
11138
  if (change.fileId === activeFile?.id) {
10515
11139
  applyLocalContentUpdate(change.before, {
10516
11140
  refreshPreview: false,
11141
+ forcePreviewFullDocument: true,
10517
11142
  immediateSave: true,
10518
11143
  recordHistory: false,
10519
11144
  });
@@ -10618,9 +11243,8 @@ export default function DesignEditor() {
10618
11243
  queueFileContentSave(activeFile.id, next, {
10619
11244
  syncCollab: !(ydoc && isSynced),
10620
11245
  });
10621
- if (!replacePreviewContent(next)) {
10622
- setContentRenderRevision((revision) => revision + 1);
10623
- }
11246
+ replacePreviewContent(next, null, { forceFullDocument: true });
11247
+ setContentRenderRevision((revision) => revision + 1);
10624
11248
  // Clear stale selection if the redo removed the selected element.
10625
11249
  setSelectedElement((prev) => {
10626
11250
  if (!prev) return prev;
@@ -10657,6 +11281,7 @@ export default function DesignEditor() {
10657
11281
  ];
10658
11282
  applyLocalContentUpdate(entry.after, {
10659
11283
  refreshPreview: false,
11284
+ forcePreviewFullDocument: true,
10660
11285
  immediateSave: true,
10661
11286
  recordHistory: false,
10662
11287
  });
@@ -10703,6 +11328,7 @@ export default function DesignEditor() {
10703
11328
  if (change.fileId === activeFile?.id) {
10704
11329
  applyLocalContentUpdate(change.after, {
10705
11330
  refreshPreview: false,
11331
+ forcePreviewFullDocument: true,
10706
11332
  immediateSave: true,
10707
11333
  recordHistory: false,
10708
11334
  });
@@ -10806,7 +11432,7 @@ export default function DesignEditor() {
10806
11432
  viewModeRef.current = "overview";
10807
11433
  setViewMode("overview");
10808
11434
  setActiveTool("move");
10809
- setOverviewCanvasZoom(100);
11435
+ setExplicitOverviewCanvasZoom(100);
10810
11436
  }, []);
10811
11437
 
10812
11438
  const runEditorViewTransition = useCallback((update: () => void) => {
@@ -11849,7 +12475,7 @@ ${serializedHtml}
11849
12475
  : codingHandoffPreviewFallback);
11850
12476
  const shareExportTab = (
11851
12477
  <div className="space-y-3">
11852
- <div className="text-[11px] font-semibold uppercase text-muted-foreground">
12478
+ <div className="!text-[11px] font-semibold uppercase text-muted-foreground">
11853
12479
  {"Format" /* i18n-ignore share export section label */}
11854
12480
  </div>
11855
12481
  <div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
@@ -11874,11 +12500,11 @@ ${serializedHtml}
11874
12500
  <span className="min-w-0 flex-1">
11875
12501
  <span className="block truncate text-[12px] font-semibold text-foreground">
11876
12502
  {option.title}{" "}
11877
- <span className="text-[11px] font-medium text-muted-foreground">
12503
+ <span className="!text-[11px] font-medium text-muted-foreground">
11878
12504
  {option.extension}
11879
12505
  </span>
11880
12506
  </span>
11881
- <span className="mt-0.5 block text-[11px] leading-4 text-muted-foreground">
12507
+ <span className="mt-0.5 block !text-[11px] leading-4 text-muted-foreground">
11882
12508
  {option.description}
11883
12509
  </span>
11884
12510
  </span>
@@ -11902,7 +12528,7 @@ ${serializedHtml}
11902
12528
  <div className="text-[12px] font-medium text-foreground">
11903
12529
  {selectedShareExportOption.title}
11904
12530
  </div>
11905
- <div className="text-[11px] text-muted-foreground">
12531
+ <div className="!text-[11px] text-muted-foreground">
11906
12532
  {selectedShareExportOption.description}
11907
12533
  </div>
11908
12534
  </div>
@@ -11974,7 +12600,7 @@ ${serializedHtml}
11974
12600
  <div className="text-[12px] font-medium text-foreground">
11975
12601
  {"Download zip instead" /* i18n-ignore share send option */}
11976
12602
  </div>
11977
- <div className="mt-0.5 text-[11px] leading-4 text-muted-foreground">
12603
+ <div className="mt-0.5 !text-[11px] leading-4 text-muted-foreground">
11978
12604
  {
11979
12605
  "For agents without the Design connector, drop the bundle into your agent's chat manually." /* i18n-ignore share send option description */
11980
12606
  }
@@ -12004,7 +12630,7 @@ ${serializedHtml}
12004
12630
  const designSharePopoverClassName =
12005
12631
  "z-[100010] !w-[min(620px,calc(100vw-32px))] !p-3 " +
12006
12632
  "[&_[role=tablist]]:!inline-flex [&_[role=tablist]]:!w-fit [&_[role=tablist]]:!self-start [&_[role=tablist]]:justify-start [&_[role=tablist]]:gap-0.5 [&_[role=tablist]]:rounded-none [&_[role=tablist]]:bg-transparent [&_[role=tablist]]:p-0 " +
12007
- "[&_[role=tab]]:!h-6 [&_[role=tab]]:!flex-none [&_[role=tab]]:rounded-md [&_[role=tab]]:px-2 [&_[role=tab]]:text-[11px] [&_[role=tab]]:font-semibold [&_[role=tab]]:shadow-none [&_[role=tab]]:ring-0 " +
12633
+ "[&_[role=tab]]:!h-6 [&_[role=tab]]:!flex-none [&_[role=tab]]:rounded-md [&_[role=tab]]:px-2 [&_[role=tab]]:!text-[11px] [&_[role=tab]]:font-semibold [&_[role=tab]]:shadow-none [&_[role=tab]]:ring-0 " +
12008
12634
  "[&_[role=tab]:hover]:bg-[var(--design-editor-panel-raised-bg)] [&_[role=tab]:hover]:text-foreground [&_[role=tab][aria-selected=true]]:bg-[var(--design-editor-panel-raised-bg)] [&_[role=tab][aria-selected=true]]:text-foreground [&_[role=tab][aria-selected=true]]:ring-0";
12009
12635
  const designShareTabs = {
12010
12636
  shareLabel: "Share link" /* i18n-ignore share tab label */,
@@ -12050,7 +12676,9 @@ ${serializedHtml}
12050
12676
  () =>
12051
12677
  files.map((file) => {
12052
12678
  const content =
12053
- file.id === activeFile?.id ? activeContent : (file.content ?? "");
12679
+ file.id === activeFile?.id
12680
+ ? activeProjectionContent
12681
+ : getProjectionContentForScreen(file.id);
12054
12682
  const projection =
12055
12683
  file.id === activeFile?.id
12056
12684
  ? activeCodeLayerProjection
@@ -12069,9 +12697,10 @@ ${serializedHtml}
12069
12697
  [
12070
12698
  activeCodeLayerProjection,
12071
12699
  activeCodeLayerTree,
12072
- activeContent,
12700
+ activeProjectionContent,
12073
12701
  activeFile?.id,
12074
12702
  files,
12703
+ getProjectionContentForScreen,
12075
12704
  ],
12076
12705
  );
12077
12706
  const codeLayerModelByFileId = useMemo(
@@ -12115,6 +12744,7 @@ ${serializedHtml}
12115
12744
  });
12116
12745
  return state;
12117
12746
  }, [codeLayerModelsByFile, hiddenLayerIds, lockedLayerIds]);
12747
+ effectiveCodeLayerStateRef.current = effectiveCodeLayerState;
12118
12748
  useEffect(() => {
12119
12749
  shouldPreserveBlockedOverviewLayerSelectionRef.current = (
12120
12750
  screenId: string,
@@ -12358,12 +12988,191 @@ ${serializedHtml}
12358
12988
  selectedLayerIdsState,
12359
12989
  viewMode,
12360
12990
  ]);
12991
+ const selectedUrlSelectionId = useMemo(
12992
+ () =>
12993
+ selectedElementLayerId ??
12994
+ [...selectedLayerIds]
12995
+ .reverse()
12996
+ .find((layerId) => codeLayerOwnerByNodeId.has(layerId)) ??
12997
+ null,
12998
+ [codeLayerOwnerByNodeId, selectedElementLayerId, selectedLayerIds],
12999
+ );
12361
13000
  const selectedLayerIdsRef = useRef<string[]>(selectedLayerIds);
12362
13001
 
12363
13002
  useLayoutEffect(() => {
12364
13003
  selectedLayerIdsRef.current = selectedLayerIds;
12365
13004
  }, [selectedLayerIds]);
12366
13005
 
13006
+ useEffect(() => {
13007
+ if (!id) return;
13008
+ if (initialUrlSelectionHydratedForIdRef.current === id) return;
13009
+ if (!initialRouteSelectionId) {
13010
+ initialUrlSelectionHydratedForIdRef.current = id;
13011
+ return;
13012
+ }
13013
+ if (
13014
+ selectedUrlSelectionId &&
13015
+ selectedUrlSelectionId !== initialRouteSelectionId
13016
+ ) {
13017
+ initialUrlSelectionHydratedForIdRef.current = id;
13018
+ return;
13019
+ }
13020
+ const owner = codeLayerOwnerByNodeId.get(initialRouteSelectionId);
13021
+ if (!owner) return;
13022
+ const selectionBlocked =
13023
+ effectiveCodeLayerState.lockedIds.has(owner.fileId) ||
13024
+ effectiveCodeLayerState.hiddenIds.has(owner.fileId) ||
13025
+ effectiveCodeLayerState.lockedIds.has(initialRouteSelectionId) ||
13026
+ effectiveCodeLayerState.hiddenIds.has(initialRouteSelectionId);
13027
+ if (
13028
+ activeFileId === owner.fileId &&
13029
+ selectedLayerIds.includes(initialRouteSelectionId) &&
13030
+ (selectionBlocked || selectedElementLayerId === initialRouteSelectionId)
13031
+ ) {
13032
+ initialUrlSelectionHydratedForIdRef.current = id;
13033
+ return;
13034
+ }
13035
+
13036
+ pendingOverviewScreenSelectionRef.current = null;
13037
+ pendingOverviewLayerSelectionRef.current = null;
13038
+ clearPendingOverviewLayerSelectionTimer();
13039
+ setCreatedOverviewLayerSelection(null);
13040
+ setActiveFileId(owner.fileId);
13041
+ setSelectedLayerIdsState([initialRouteSelectionId]);
13042
+ if (viewModeRef.current === "overview") {
13043
+ setOverviewSelectedScreenIds([]);
13044
+ }
13045
+ setSelectedElement(
13046
+ selectionBlocked ? null : elementInfoFromCodeLayerNode(owner.node),
13047
+ );
13048
+ setHoveredElement(null);
13049
+ setHoveredElementScreenId(null);
13050
+ setActiveTool("move");
13051
+ setMode("edit");
13052
+ if (!selectionBlocked) {
13053
+ focusDesignInspectorForSelection();
13054
+ }
13055
+ initialUrlSelectionHydratedForIdRef.current = id;
13056
+ }, [
13057
+ activeFileId,
13058
+ clearPendingOverviewLayerSelectionTimer,
13059
+ codeLayerOwnerByNodeId,
13060
+ effectiveCodeLayerState,
13061
+ focusDesignInspectorForSelection,
13062
+ id,
13063
+ initialRouteSelectionId,
13064
+ selectedElementLayerId,
13065
+ selectedLayerIds,
13066
+ selectedUrlSelectionId,
13067
+ ]);
13068
+
13069
+ useEffect(() => {
13070
+ if (!id || files.length === 0) return;
13071
+ if (
13072
+ initialRouteScreenTarget &&
13073
+ !findDesignFileByScreenTarget(files, initialRouteScreenTarget) &&
13074
+ !activeFileId
13075
+ ) {
13076
+ return;
13077
+ }
13078
+ const preserveInitialRouteSelection = Boolean(
13079
+ initialRouteSelectionId &&
13080
+ initialUrlSelectionHydratedForIdRef.current !== id &&
13081
+ initialRouteSelectionId !== selectedUrlSelectionId &&
13082
+ codeLayerOwnerByNodeId.size === 0,
13083
+ );
13084
+ const nextSearch = getDesignEditorStateUrlSearch({
13085
+ currentSearch: location.search,
13086
+ viewMode,
13087
+ screenId: activeFile?.id ?? activeFileId,
13088
+ selectionId:
13089
+ selectedUrlSelectionId ??
13090
+ (preserveInitialRouteSelection ? initialRouteSelectionId : null),
13091
+ zoom,
13092
+ });
13093
+ if (nextSearch === location.search) return;
13094
+ navigate(
13095
+ {
13096
+ pathname: location.pathname,
13097
+ search: nextSearch,
13098
+ hash: location.hash,
13099
+ },
13100
+ { replace: true, preventScrollReset: true },
13101
+ );
13102
+ }, [
13103
+ activeFile?.id,
13104
+ activeFileId,
13105
+ codeLayerOwnerByNodeId.size,
13106
+ files,
13107
+ id,
13108
+ location.hash,
13109
+ location.pathname,
13110
+ location.search,
13111
+ navigate,
13112
+ initialRouteScreenTarget,
13113
+ initialRouteSelectionId,
13114
+ selectedUrlSelectionId,
13115
+ viewMode,
13116
+ zoom,
13117
+ ]);
13118
+
13119
+ const selectedLayerTargets = useMemo<SelectedLayerTarget[]>(
13120
+ () =>
13121
+ selectedLayerIds
13122
+ .map((layerId) => {
13123
+ const owner = codeLayerOwnerByNodeId.get(layerId);
13124
+ if (!owner) return null;
13125
+ const selectedMatches =
13126
+ selectedElement &&
13127
+ codeLayerNodeMatchesBridgeTarget(
13128
+ owner.node,
13129
+ selectedElement.selector,
13130
+ selectedElement.sourceId ?? selectedElement.id,
13131
+ );
13132
+ return {
13133
+ layerId,
13134
+ fileId: owner.fileId,
13135
+ node: owner.node,
13136
+ tree: owner.tree,
13137
+ elementInfo: selectedMatches
13138
+ ? canonicalElementInfoForCodeLayerNode(
13139
+ selectedElement,
13140
+ owner.node,
13141
+ )
13142
+ : elementInfoFromCodeLayerNode(owner.node),
13143
+ };
13144
+ })
13145
+ .filter((target): target is SelectedLayerTarget => Boolean(target)),
13146
+ [codeLayerOwnerByNodeId, selectedElement, selectedLayerIds],
13147
+ );
13148
+
13149
+ useLayoutEffect(() => {
13150
+ selectedLayerTargetsRef.current = selectedLayerTargets;
13151
+ }, [selectedLayerTargets]);
13152
+
13153
+ const selectedLayerSelectorGroupsByScreen = useMemo(() => {
13154
+ const groupsByScreen: Record<string, string[][]> = {};
13155
+ selectedLayerTargets.forEach((target) => {
13156
+ const selectorGroup = codeLayerSelectorAliases(target.node);
13157
+ if (selectorGroup.length === 0) return;
13158
+ groupsByScreen[target.fileId] = [
13159
+ ...(groupsByScreen[target.fileId] ?? []),
13160
+ selectorGroup,
13161
+ ];
13162
+ });
13163
+ return groupsByScreen;
13164
+ }, [selectedLayerTargets]);
13165
+
13166
+ const selectedInspectorElements = useMemo(
13167
+ () =>
13168
+ selectedLayerTargets.length > 0
13169
+ ? selectedLayerTargets.map((target) => target.elementInfo)
13170
+ : selectedElement
13171
+ ? [selectedElement]
13172
+ : [],
13173
+ [selectedElement, selectedLayerTargets],
13174
+ );
13175
+
12367
13176
  const layerPanelSelectedIds = useMemo(
12368
13177
  () =>
12369
13178
  viewMode === "overview" && createdOverviewLayerSelection
@@ -13158,7 +13967,7 @@ ${serializedHtml}
13158
13967
  const handleLayerSelectionChange = useCallback(
13159
13968
  (
13160
13969
  ids: string[],
13161
- intent: {
13970
+ _intent: {
13162
13971
  additive: boolean;
13163
13972
  currentSelectedIds?: string[];
13164
13973
  id: string;
@@ -13170,40 +13979,30 @@ ${serializedHtml}
13170
13979
  pendingOverviewLayerSelectionRef.current = null;
13171
13980
  clearPendingOverviewLayerSelectionTimer();
13172
13981
  setCreatedOverviewLayerSelection(null);
13173
- if (intent.additive && !intent.range) {
13174
- const currentLayerIds = (
13175
- intent.currentSelectedIds && intent.currentSelectedIds.length > 0
13176
- ? intent.currentSelectedIds
13177
- : selectedLayerIdsRef.current
13178
- ).filter((layerId) => !layerId.startsWith("__"));
13179
- const additiveLayerIds = currentLayerIds.includes(intent.id)
13180
- ? currentLayerIds.filter((layerId) => layerId !== intent.id)
13181
- : [...currentLayerIds, intent.id];
13182
- setSelectedLayerIdsState(additiveLayerIds);
13183
- if (viewModeRef.current === "overview") {
13184
- const fileIds = files.map((file) => file.id);
13185
- const selectedScreenIds = getOverviewScreenIdsFromLayerSelection({
13186
- fileIds,
13187
- layerIds: additiveLayerIds,
13188
- });
13189
- setOverviewSelectedScreenIds(selectedScreenIds);
13190
- }
13191
- setSelectedElement(null);
13192
- focusDesignInspectorForSelection();
13193
- setActiveTool("move");
13194
- setMode("edit");
13195
- return;
13196
- }
13197
13982
  setSelectedLayerIdsState(nextLayerIds);
13198
- const selectedId = ids[ids.length - 1];
13983
+ const screenFileIds = files
13984
+ .filter((file) => !isBoardFile(file.filename))
13985
+ .map((file) => file.id);
13986
+ if (viewModeRef.current === "overview") {
13987
+ setOverviewSelectedScreenIds(
13988
+ getOverviewScreenIdsFromLayerSelection({
13989
+ fileIds: screenFileIds,
13990
+ layerIds: nextLayerIds,
13991
+ }),
13992
+ );
13993
+ }
13994
+ const selectedId = nextLayerIds[nextLayerIds.length - 1];
13199
13995
  if (!selectedId) {
13200
13996
  setSelectedElement(null);
13201
13997
  return;
13202
13998
  }
13203
13999
  const codeLayerOwner = codeLayerOwnerByNodeId.get(selectedId);
13204
14000
  if (codeLayerOwner) {
14001
+ const ownerIsScreenFile = screenFileIds.includes(codeLayerOwner.fileId);
13205
14002
  if (viewModeRef.current === "overview") {
13206
- pendingOverviewScreenSelectionRef.current = codeLayerOwner.fileId;
14003
+ pendingOverviewScreenSelectionRef.current = ownerIsScreenFile
14004
+ ? codeLayerOwner.fileId
14005
+ : null;
13207
14006
  pendingOverviewLayerSelectionRef.current = selectedId;
13208
14007
  }
13209
14008
  if (codeLayerOwner.fileId !== activeFile?.id) {
@@ -13213,6 +14012,7 @@ ${serializedHtml}
13213
14012
  currentViewMode: viewModeRef.current,
13214
14013
  ownerFileId: codeLayerOwner.fileId,
13215
14014
  overviewSelectedScreenIds,
14015
+ screenFileIds,
13216
14016
  });
13217
14017
  viewModeRef.current = nextSelectionState.viewMode;
13218
14018
  setViewMode(nextSelectionState.viewMode);
@@ -13243,11 +14043,19 @@ ${serializedHtml}
13243
14043
  const fileId = selectedId.startsWith("code:")
13244
14044
  ? selectedId.slice("code:".length)
13245
14045
  : selectedId;
13246
- if (files.some((file) => file.id === fileId)) {
14046
+ if (
14047
+ files.some((file) => file.id === fileId && !isBoardFile(file.filename))
14048
+ ) {
13247
14049
  setOverviewSelectedScreenIds([fileId]);
13248
14050
  setActiveFileId(fileId);
13249
14051
  setSelectedElement(null);
13250
- setSelectedLayerIdsState([fileId]);
14052
+ setSelectedLayerIdsState(
14053
+ nextLayerIds.some((layerId) =>
14054
+ files.some((file) => file.id === layerId),
14055
+ )
14056
+ ? nextLayerIds
14057
+ : [fileId],
14058
+ );
13251
14059
  setActiveTool("move");
13252
14060
  setMode("edit");
13253
14061
  viewModeRef.current = "overview";
@@ -13265,6 +14073,113 @@ ${serializedHtml}
13265
14073
  ],
13266
14074
  );
13267
14075
 
14076
+ const handleLayerMarqueeSelectionChange = useCallback(
14077
+ (
14078
+ selection: CanvasLayerMarqueeSelection[],
14079
+ intent: ElementSelectionIntent,
14080
+ ) => {
14081
+ pendingOverviewScreenSelectionRef.current = null;
14082
+ pendingOverviewLayerSelectionRef.current = null;
14083
+ clearPendingOverviewLayerSelectionTimer();
14084
+ setCreatedOverviewLayerSelection(null);
14085
+
14086
+ const resolved = selection
14087
+ .map((item) => {
14088
+ const projection = getCodeLayerProjectionForScreen(item.screenId);
14089
+ if (!projection) return null;
14090
+ const canonical = canonicalizeElementInfoFromProjection(
14091
+ projection,
14092
+ item.info,
14093
+ );
14094
+ const node = resolveCodeLayerNodeFromElementInfo(
14095
+ projection,
14096
+ canonical,
14097
+ );
14098
+ if (!node || isScreenRootElementInfo(canonical)) return null;
14099
+ return {
14100
+ screenId: item.screenId,
14101
+ node,
14102
+ elementInfo: canonical,
14103
+ };
14104
+ })
14105
+ .filter(
14106
+ (
14107
+ item,
14108
+ ): item is {
14109
+ screenId: string;
14110
+ node: CodeLayerNode;
14111
+ elementInfo: ElementInfo;
14112
+ } => Boolean(item),
14113
+ );
14114
+
14115
+ const hitLayerIds = dedupeStringIds(resolved.map((item) => item.node.id));
14116
+ setSelectedLayerIdsState((current) =>
14117
+ intent.additive
14118
+ ? dedupeStringIds([
14119
+ ...current.filter((layerId) => !layerId.startsWith("__")),
14120
+ ...hitLayerIds,
14121
+ ])
14122
+ : hitLayerIds,
14123
+ );
14124
+ if (viewModeRef.current === "overview") {
14125
+ setOverviewSelectedScreenIds([]);
14126
+ }
14127
+
14128
+ const primary = resolved[resolved.length - 1];
14129
+ if (primary) {
14130
+ setActiveFileId(primary.screenId);
14131
+ setSelectedElement(primary.elementInfo);
14132
+ focusDesignInspectorForSelection();
14133
+ } else if (!intent.additive) {
14134
+ setSelectedElement(null);
14135
+ }
14136
+
14137
+ setActiveTool("move");
14138
+ setMode("edit");
14139
+ },
14140
+ [
14141
+ clearPendingOverviewLayerSelectionTimer,
14142
+ focusDesignInspectorForSelection,
14143
+ getCodeLayerProjectionForScreen,
14144
+ ],
14145
+ );
14146
+
14147
+ const handleScreenElementMarqueeSelect = useCallback(
14148
+ (
14149
+ screenId: string,
14150
+ infos: ElementInfo[],
14151
+ intent?: ElementSelectionIntent,
14152
+ ) => {
14153
+ handleLayerMarqueeSelectionChange(
14154
+ infos.map((info) => ({ screenId, info })),
14155
+ {
14156
+ additive: Boolean(
14157
+ intent?.additive ||
14158
+ intent?.range ||
14159
+ intent?.shiftKey ||
14160
+ intent?.metaKey ||
14161
+ intent?.ctrlKey,
14162
+ ),
14163
+ range: Boolean(intent?.range || intent?.shiftKey),
14164
+ source: "marquee",
14165
+ shiftKey: Boolean(intent?.shiftKey),
14166
+ metaKey: Boolean(intent?.metaKey),
14167
+ ctrlKey: Boolean(intent?.ctrlKey),
14168
+ },
14169
+ );
14170
+ },
14171
+ [handleLayerMarqueeSelectionChange],
14172
+ );
14173
+
14174
+ const handleElementMarqueeSelect = useCallback(
14175
+ (infos: ElementInfo[], intent?: ElementSelectionIntent) => {
14176
+ const screenId = activeFile?.id ?? activeFileId;
14177
+ if (!screenId) return;
14178
+ handleScreenElementMarqueeSelect(screenId, infos, intent);
14179
+ },
14180
+ [activeFile?.id, activeFileId, handleScreenElementMarqueeSelect],
14181
+ );
14182
+
13268
14183
  const handleLayerRename = useCallback(
13269
14184
  (layerId: string, name: string) => {
13270
14185
  if (!canEditDesign) return;
@@ -13518,7 +14433,12 @@ ${serializedHtml}
13518
14433
  if (!id) return null;
13519
14434
 
13520
14435
  if (designLoading || (!design && pendingGenerationActive)) {
13521
- return <DesignEditorSkeleton embedded={embedded} />;
14436
+ return (
14437
+ <DesignEditorSkeleton
14438
+ embedded={embedded}
14439
+ pendingGeneration={pendingGenerationActive}
14440
+ />
14441
+ );
13522
14442
  }
13523
14443
 
13524
14444
  if (!design) {
@@ -13533,7 +14453,7 @@ ${serializedHtml}
13533
14453
  className="absolute inset-x-0 top-0 h-px bg-[var(--design-editor-panel-divider-color)]"
13534
14454
  />
13535
14455
  <div className="relative flex w-full max-w-sm flex-col items-center text-center">
13536
- <div className="mb-2 text-[11px] font-medium uppercase text-muted-foreground">
14456
+ <div className="mb-2 !text-[11px] font-medium uppercase text-muted-foreground">
13537
14457
  404
13538
14458
  </div>
13539
14459
  <h1 className="text-xl font-semibold text-foreground">
@@ -13542,7 +14462,7 @@ ${serializedHtml}
13542
14462
  <Button
13543
14463
  variant="default"
13544
14464
  onClick={() => navigate("/")}
13545
- className="mt-7 h-9 cursor-pointer gap-2 rounded-md border border-[var(--design-editor-accent-color)] bg-[var(--design-editor-accent-color)] px-3.5 text-[var(--design-editor-accent-contrast-color)] shadow-sm hover:border-[var(--design-editor-accent-hover-color)] hover:bg-[var(--design-editor-accent-hover-color)] hover:text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)]"
14465
+ className="mt-7 h-9 cursor-pointer gap-2 rounded-md border border-foreground bg-foreground px-3.5 text-background shadow-sm hover:border-foreground/90 hover:bg-foreground/90 hover:text-background focus-visible:ring-foreground"
13546
14466
  >
13547
14467
  <IconArrowLeft className="size-4 rtl:-scale-x-100" />
13548
14468
  {t("designEditor.backToDesigns")}
@@ -13847,30 +14767,39 @@ ${serializedHtml}
13847
14767
  <Tooltip>
13848
14768
  <TooltipTrigger asChild>
13849
14769
  <Button
14770
+ asChild
13850
14771
  variant="outline"
13851
14772
  size="sm"
13852
- onClick={handleSignInToSave}
13853
- className="h-8 max-w-[13rem] cursor-pointer gap-1.5 truncate rounded-md bg-[var(--design-editor-panel-raised-bg)] px-2 text-xs shadow-none"
14773
+ className="h-8 cursor-pointer gap-1.5 rounded-md bg-[var(--design-editor-panel-raised-bg)] px-3 text-sm shadow-none"
13854
14774
  aria-label={t("designEditor.signUpToSave")}
13855
14775
  >
13856
- <IconDeviceFloppy className="size-4 shrink-0" />
13857
- <span className="truncate">{t("designEditor.signUpToSave")}</span>
14776
+ <a
14777
+ href={signInToSaveHref}
14778
+ target="_blank"
14779
+ rel="noopener noreferrer"
14780
+ >
14781
+ <span>{t("home.save")}</span>
14782
+ </a>
13858
14783
  </Button>
13859
14784
  </TooltipTrigger>
13860
- <TooltipContent>
13861
- {t("designEditor.signUpToSaveDescription")}
13862
- </TooltipContent>
14785
+ <TooltipContent>{t("designEditor.signUpToSave")}</TooltipContent>
13863
14786
  </Tooltip>
13864
14787
  <Tooltip>
13865
14788
  <TooltipTrigger asChild>
13866
14789
  <Button
14790
+ asChild
13867
14791
  variant="default"
13868
14792
  size="sm"
13869
- onClick={handleSignInToShare}
13870
14793
  className="h-8 cursor-pointer gap-1.5 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 text-sm !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)]"
14794
+ aria-label={t("designEditor.signUpToShare")}
13871
14795
  >
13872
- <span>{t("designEditor.share")}</span>
13873
- <IconArrowUpRight className="size-4 shrink-0" />
14796
+ <a
14797
+ href={signInToShareHref}
14798
+ target="_blank"
14799
+ rel="noopener noreferrer"
14800
+ >
14801
+ <span>{t("designEditor.share")}</span>
14802
+ </a>
13874
14803
  </Button>
13875
14804
  </TooltipTrigger>
13876
14805
  <TooltipContent>{t("designEditor.signUpToShare")}</TooltipContent>
@@ -14011,7 +14940,7 @@ ${serializedHtml}
14011
14940
  </PopoverContent>
14012
14941
  </Popover>
14013
14942
 
14014
- {isSignedIn ? (
14943
+ {canRenderAuthenticatedShare ? (
14015
14944
  <ShareButton
14016
14945
  resourceType="design"
14017
14946
  resourceId={id}
@@ -14025,40 +14954,15 @@ ${serializedHtml}
14025
14954
  popoverClassName={designSharePopoverClassName}
14026
14955
  triggerClassName="h-8 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 text-sm !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)] [&_svg]:!text-[var(--design-editor-accent-contrast-color)]"
14027
14956
  />
14028
- ) : (
14957
+ ) : sessionResolved ? (
14029
14958
  signedOutPersistenceActions
14030
- )}
14959
+ ) : null}
14031
14960
  </div>
14032
14961
  </div>
14033
14962
  </div>
14034
14963
  );
14035
14964
 
14036
14965
  const leftContentWidth = Math.max(leftSidebarWidth, 320);
14037
- const motionDockLauncherHidden = motionDockMounted || motionDockOpen;
14038
- const motionDockLauncher =
14039
- !embedded && activeFile ? (
14040
- <div
14041
- aria-hidden={motionDockLauncherHidden ? true : undefined}
14042
- className={cn(
14043
- "overflow-hidden border-t border-[var(--design-editor-panel-divider-color)] transition-[max-height,opacity,border-color] duration-200 ease-out",
14044
- motionDockLauncherHidden
14045
- ? "max-h-0 border-transparent opacity-0"
14046
- : "max-h-9 opacity-100",
14047
- )}
14048
- >
14049
- <button
14050
- type="button"
14051
- aria-label={"Expand motion dock" /* i18n-ignore */}
14052
- className="flex h-8 w-full cursor-pointer items-center gap-2 bg-[var(--design-editor-panel-bg)] px-3 text-left text-[11px] font-medium uppercase tracking-wide text-muted-foreground outline-none transition-colors hover:bg-[var(--design-editor-panel-raised-bg)] hover:text-foreground focus-visible:ring-1 focus-visible:ring-inset focus-visible:ring-[var(--design-editor-accent-color)] disabled:cursor-default"
14053
- disabled={motionDockLauncherHidden}
14054
- onClick={() => setMotionDockOpenAnimated(true)}
14055
- >
14056
- <IconChevronRight className="size-3.5 shrink-0" />
14057
- <span className="min-w-0 flex-1 truncate">Motion</span>
14058
- </button>
14059
- </div>
14060
- ) : null;
14061
-
14062
14966
  return (
14063
14967
  // h-full not flex-1: the parent <main> uses overflow-y-auto, not flex,
14064
14968
  // so flex-1 on the child doesn't resolve to the available height. h-full
@@ -14434,7 +15338,7 @@ ${serializedHtml}
14434
15338
  />
14435
15339
  )}
14436
15340
 
14437
- {!embedded && isSignedIn ? (
15341
+ {!embedded && canRenderAuthenticatedShare ? (
14438
15342
  <ShareButton
14439
15343
  resourceType="design"
14440
15344
  resourceId={id}
@@ -14449,7 +15353,7 @@ ${serializedHtml}
14449
15353
  popoverClassName={designSharePopoverClassName}
14450
15354
  triggerClassName="h-8 rounded-md !border-[var(--design-editor-accent-color)] !bg-[var(--design-editor-accent-color)] px-3 !text-[var(--design-editor-accent-contrast-color)] shadow-none hover:!border-[var(--design-editor-accent-hover-color)] hover:!bg-[var(--design-editor-accent-hover-color)] hover:!text-[var(--design-editor-accent-contrast-color)] focus-visible:ring-[var(--design-editor-accent-color)] [&_svg]:!text-[var(--design-editor-accent-contrast-color)]"
14451
15355
  />
14452
- ) : !embedded ? (
15356
+ ) : !embedded && sessionResolved ? (
14453
15357
  signedOutPersistenceActions
14454
15358
  ) : null}
14455
15359
  </div>
@@ -14462,7 +15366,15 @@ ${serializedHtml}
14462
15366
  <div className="relative flex min-h-0 shrink-0 border-r border-[var(--design-editor-panel-divider-color)] bg-[var(--design-editor-panel-bg)]">
14463
15367
  <DesignWorkspaceRail
14464
15368
  activePanel={activeLeftPanel}
15369
+ disabledPanels={
15370
+ initialGenerationChromeLimited
15371
+ ? INITIAL_GENERATION_DISABLED_LEFT_PANELS
15372
+ : undefined
15373
+ }
15374
+ motionOpen={motionDockOpen}
15375
+ motionDisabled={!activeFile}
14465
15376
  projectMenu={projectMenu}
15377
+ onMotionToggle={() => setMotionDockOpenAnimated(!motionDockOpen)}
14466
15378
  onPanelChange={setActiveLeftPanel}
14467
15379
  />
14468
15380
  <div
@@ -14496,7 +15408,6 @@ ${serializedHtml}
14496
15408
  selectedIds={layerPanelSelectedIds}
14497
15409
  expandedIds={layerPanelExpandedIds}
14498
15410
  searchQuery={layersSearchQuery}
14499
- footer={motionDockLauncher}
14500
15411
  onScreenSelect={handleSidebarScreenSelect}
14501
15412
  onScreenOverview={handleSidebarScreenOverview}
14502
15413
  onAddScreen={handleAddScreen}
@@ -14526,6 +15437,7 @@ ${serializedHtml}
14526
15437
  <AgentChatSurface
14527
15438
  mode="panel"
14528
15439
  className="min-h-0 flex-1 border-0 bg-transparent shadow-none"
15440
+ storageKey={DESIGN_CHAT_STORAGE_KEY}
14529
15441
  emptyStateText={t("chat.emptyState")}
14530
15442
  suggestions={[
14531
15443
  t("chat.suggestionLandingPage"),
@@ -14708,18 +15620,6 @@ ${serializedHtml}
14708
15620
  >
14709
15621
  {activeFile ? (
14710
15622
  <div className="flex min-w-0 flex-1 flex-col overflow-hidden">
14711
- {/* Banner for screens connected to a local dev server: edits
14712
- route through the agent rather than being applied inline. */}
14713
- {activeScreenIsLocalSource &&
14714
- !localSourceBannerDismissed &&
14715
- id && (
14716
- <LocalSourceEditBanner
14717
- designId={id}
14718
- fileId={activeFile.id}
14719
- routeSourceFile={activeScreenRouteSourceFile}
14720
- onDismiss={() => setLocalSourceBannerDismissed(true)}
14721
- />
14722
- )}
14723
15623
  {/* "Apply to source" affordance: write the current editor
14724
15624
  content back to the local HTML/CSS file via the bridge.
14725
15625
  Only shown for localhost-backed screens where the route
@@ -14730,7 +15630,7 @@ ${serializedHtml}
14730
15630
  <Button
14731
15631
  size="sm"
14732
15632
  variant="outline"
14733
- className="h-6 gap-1.5 px-2 text-[11px]"
15633
+ className="h-6 gap-1.5 px-2 !text-[11px]"
14734
15634
  disabled={applyToSourcePending}
14735
15635
  onClick={handleApplyToSource}
14736
15636
  >
@@ -14769,7 +15669,7 @@ ${serializedHtml}
14769
15669
  <MultiScreenCanvas
14770
15670
  screens={overviewScreens}
14771
15671
  zoom={overviewCanvasZoom}
14772
- onZoomChange={setOverviewCanvasZoom}
15672
+ onZoomChange={setExplicitOverviewCanvasZoom}
14773
15673
  activeId={activeFileId}
14774
15674
  selectedScreenIds={overviewSelectedScreenIds}
14775
15675
  fullViewScreenIds={fullViewScreenIds}
@@ -14798,16 +15698,68 @@ ${serializedHtml}
14798
15698
  onPrimitiveReparent={handleOverviewPrimitiveReparent}
14799
15699
  onCrossScreenElementDrop={handleCrossScreenElementDrop}
14800
15700
  boardFileId={boardFileId}
15701
+ boardIsActive={activeFileId === boardFileId}
14801
15702
  boardFileContent={boardFileContent}
14802
15703
  boardFrameGeometry={boardFrameGeometry}
15704
+ boardClearSelectionRequest={overviewClearSelectionRequest}
15705
+ boardSelectedSelector={
15706
+ activeFileId === boardFileId
15707
+ ? selectedCanvasSelector
15708
+ : null
15709
+ }
15710
+ boardSelectedSelectorCandidates={
15711
+ activeFileId === boardFileId
15712
+ ? selectedCanvasSelectorCandidates
15713
+ : []
15714
+ }
15715
+ boardHoveredSelector={
15716
+ hoveredElementScreenId === boardFileId
15717
+ ? hoveredCanvasSelector
15718
+ : null
15719
+ }
15720
+ boardHoveredSelectorCandidates={
15721
+ hoveredElementScreenId === boardFileId
15722
+ ? hoveredCanvasSelectorCandidates
15723
+ : []
15724
+ }
15725
+ boardLockedSelectors={
15726
+ boardFileId
15727
+ ? getLayerSelectorsForFile(
15728
+ boardFileId,
15729
+ lockedLayerIds,
15730
+ )
15731
+ : []
15732
+ }
15733
+ boardHiddenSelectors={
15734
+ boardFileId
15735
+ ? getLayerSelectorsForFile(
15736
+ boardFileId,
15737
+ hiddenLayerIds,
15738
+ )
15739
+ : []
15740
+ }
14803
15741
  onBoardDrawPrimitive={
14804
15742
  canEditDesign ? handleBoardDrawPrimitive : undefined
14805
15743
  }
14806
15744
  boardEditMode={canEditDesign}
14807
15745
  onBoardElementSelect={
14808
15746
  boardFileId
14809
- ? (info) =>
14810
- handleScreenElementSelect(boardFileId, info)
15747
+ ? (info, intent) =>
15748
+ handleScreenElementSelect(
15749
+ boardFileId,
15750
+ info,
15751
+ intent,
15752
+ )
15753
+ : undefined
15754
+ }
15755
+ onBoardElementMarqueeSelect={
15756
+ boardFileId
15757
+ ? (infos, intent) =>
15758
+ handleScreenElementMarqueeSelect(
15759
+ boardFileId,
15760
+ infos,
15761
+ intent,
15762
+ )
14811
15763
  : undefined
14812
15764
  }
14813
15765
  onBoardElementHover={
@@ -14816,6 +15768,20 @@ ${serializedHtml}
14816
15768
  handleScreenElementHover(boardFileId, info)
14817
15769
  : undefined
14818
15770
  }
15771
+ onBoardElementClear={
15772
+ boardFileId
15773
+ ? () => handleScreenElementClear(boardFileId)
15774
+ : undefined
15775
+ }
15776
+ onBoardIframeHotkey={handleIframeHotkey}
15777
+ onBoardIframeContextMenu={handleIframeContextMenu}
15778
+ onBoardTextEditingStateChange={setTextEditingState}
15779
+ onBoardElementDblClickText={
15780
+ boardFileId
15781
+ ? (info) =>
15782
+ handleScreenElementDblClickText(boardFileId, info)
15783
+ : undefined
15784
+ }
14819
15785
  onBoardVisualStyleChange={
14820
15786
  boardFileId
14821
15787
  ? (selector, styles, info) =>
@@ -14873,6 +15839,12 @@ ${serializedHtml}
14873
15839
  onCreateScreenFrame={handleCreateScreenFrame}
14874
15840
  onDeleteSelection={handleDeleteOverviewSelection}
14875
15841
  onSelectionChange={handleOverviewScreenSelectionChange}
15842
+ onLayerMarqueeSelectionChange={
15843
+ handleLayerMarqueeSelectionChange
15844
+ }
15845
+ selectedLayerSelectorGroupsByScreen={
15846
+ selectedLayerSelectorGroupsByScreen
15847
+ }
14876
15848
  onPick={(id) => {
14877
15849
  pendingOverviewScreenSelectionRef.current = null;
14878
15850
  pendingOverviewLayerSelectionRef.current = null;
@@ -14941,6 +15913,13 @@ ${serializedHtml}
14941
15913
  renderScreenContent={(screen, metadata, geometry) => {
14942
15914
  const screenIsActive = screen.id === activeFile?.id;
14943
15915
  const screenContent = getScreenContent(screen.id);
15916
+ const screenSourceType =
15917
+ normalizeDesignSourceType(screen.sourceType) ??
15918
+ metadata.source ??
15919
+ designSourceType;
15920
+ const screenBridgeUrl = screen.bridgeUrl;
15921
+ const screenSnapshot =
15922
+ liveScreenSnapshotsById[screen.id]?.html;
14944
15923
  const screenContentKey = screenIsActive
14945
15924
  ? [screen.id, contentRenderRevision].join(":")
14946
15925
  : [
@@ -14957,7 +15936,15 @@ ${serializedHtml}
14957
15936
  screenId={screen.id}
14958
15937
  zoom={100}
14959
15938
  deviceFrame="none"
14960
- sourceType={designSourceType}
15939
+ sourceType={screenSourceType}
15940
+ bridgeUrl={screenBridgeUrl}
15941
+ externalSnapshotHtml={screenSnapshot}
15942
+ onExternalContentSnapshot={(snapshot) =>
15943
+ handleScreenExternalContentSnapshot(
15944
+ screen.id,
15945
+ snapshot,
15946
+ )
15947
+ }
14961
15948
  fusionUrl={designFusionUrl}
14962
15949
  onComponentSourceJump={handleComponentSourceJump}
14963
15950
  embeddedFrame={{
@@ -14983,7 +15970,7 @@ ${serializedHtml}
14983
15970
  editorChromeScaleY={overviewCanvasZoom / 100}
14984
15971
  editMode={mode === "edit"}
14985
15972
  interactMode={false}
14986
- readOnly={!screenIsActive || !canEditDesign}
15973
+ readOnly={!canEditDesign}
14987
15974
  scaleMode={screenIsActive && activeTool === "scale"}
14988
15975
  clearSelectionRequest={
14989
15976
  overviewClearSelectionRequest
@@ -14997,6 +15984,10 @@ ${serializedHtml}
14997
15984
  ? selectedCanvasSelectorCandidates
14998
15985
  : []
14999
15986
  }
15987
+ selectedSelectorGroups={
15988
+ selectedLayerSelectorGroupsByScreen[screen.id] ??
15989
+ []
15990
+ }
15000
15991
  hoveredSelector={
15001
15992
  hoveredElementScreenId === screen.id
15002
15993
  ? hoveredCanvasSelector
@@ -15015,8 +16006,15 @@ ${serializedHtml}
15015
16006
  screen.id,
15016
16007
  hiddenLayerIds,
15017
16008
  )}
15018
- onElementSelect={(info) =>
15019
- handleScreenElementSelect(screen.id, info)
16009
+ onElementSelect={(info, intent) =>
16010
+ handleScreenElementSelect(screen.id, info, intent)
16011
+ }
16012
+ onElementMarqueeSelect={(infos, intent) =>
16013
+ handleScreenElementMarqueeSelect(
16014
+ screen.id,
16015
+ infos,
16016
+ intent,
16017
+ )
15020
16018
  }
15021
16019
  onElementHover={(info) =>
15022
16020
  handleScreenElementHover(screen.id, info)
@@ -15101,7 +16099,16 @@ ${serializedHtml}
15101
16099
  zoom={zoom}
15102
16100
  onZoomChange={setZoom}
15103
16101
  deviceFrame={deviceFrame}
15104
- sourceType={designSourceType}
16102
+ sourceType={activeCanvasSourceType}
16103
+ bridgeUrl={activeScreenBridgeUrl}
16104
+ externalSnapshotHtml={activeScreenExternalSnapshotHtml}
16105
+ onExternalContentSnapshot={(snapshot) => {
16106
+ if (!activeFile?.id) return;
16107
+ handleScreenExternalContentSnapshot(
16108
+ activeFile.id,
16109
+ snapshot,
16110
+ );
16111
+ }}
15105
16112
  fusionUrl={designFusionUrl}
15106
16113
  previewWidthPx={activeBreakpointWidthState}
15107
16114
  shaderFillPreview={shaderFillPreview}
@@ -15116,6 +16123,13 @@ ${serializedHtml}
15116
16123
  selectedSelectorCandidates={
15117
16124
  selectedCanvasSelectorCandidates
15118
16125
  }
16126
+ selectedSelectorGroups={
16127
+ activeFile
16128
+ ? (selectedLayerSelectorGroupsByScreen[
16129
+ activeFile.id
16130
+ ] ?? [])
16131
+ : []
16132
+ }
15119
16133
  hoveredSelector={hoveredCanvasSelector}
15120
16134
  hoveredSelectorCandidates={
15121
16135
  hoveredCanvasSelectorCandidates
@@ -15123,6 +16137,7 @@ ${serializedHtml}
15123
16137
  lockedSelectors={lockedLayerSelectors}
15124
16138
  hiddenSelectors={hiddenLayerSelectors}
15125
16139
  onElementSelect={handleElementSelect}
16140
+ onElementMarqueeSelect={handleElementMarqueeSelect}
15126
16141
  onElementHover={handleElementHover}
15127
16142
  onClearSelection={() => {
15128
16143
  setSelectedElement(null);
@@ -15191,18 +16206,43 @@ ${serializedHtml}
15191
16206
  </div>
15192
16207
  </div>
15193
16208
  ) : (
15194
- <div className="flex flex-1 items-center justify-center">
15195
- <div className="text-center">
16209
+ <div className="flex min-h-0 flex-1 items-center justify-center px-8 py-10">
16210
+ <div className="flex w-full max-w-md flex-col items-center text-center">
15196
16211
  {generating || pendingGenerationActive ? (
15197
16212
  <>
15198
- <Spinner className="mx-auto mb-3 size-6 text-foreground/30" />
16213
+ <div className="mb-4 flex size-12 items-center justify-center rounded-xl border border-[var(--design-editor-panel-divider-color)] bg-[var(--design-editor-panel-bg)] shadow-[0_18px_50px_-34px_rgba(0,0,0,0.8)]">
16214
+ <Spinner className="size-5 text-foreground/40" />
16215
+ </div>
15199
16216
  <p className="text-sm text-muted-foreground">
15200
16217
  {t("designEditor.generating")}
15201
16218
  </p>
15202
16219
  </>
15203
16220
  ) : (
15204
16221
  <>
15205
- <p className="mb-3 text-sm text-muted-foreground">
16222
+ <div
16223
+ aria-hidden="true"
16224
+ className="mb-5 w-full max-w-sm rounded-xl bg-[#f7f8fb] p-3 dark:bg-[#f4f6f8]"
16225
+ >
16226
+ <div className="flex h-7 items-center justify-between px-1 pb-2">
16227
+ <div className="flex gap-1.5">
16228
+ <span className="size-2 rounded-full bg-slate-950/[0.12]" />
16229
+ <span className="size-2 rounded-full bg-slate-950/[0.1]" />
16230
+ <span className="size-2 rounded-full bg-slate-950/[0.08]" />
16231
+ </div>
16232
+ <span className="h-2 w-16 rounded bg-slate-950/[0.08]" />
16233
+ </div>
16234
+ <div className="space-y-3 pt-4">
16235
+ <span className="block h-5 w-2/3 rounded bg-slate-950/[0.085]" />
16236
+ <span className="block h-4 w-1/2 rounded bg-slate-950/[0.07]" />
16237
+ <div className="grid grid-cols-3 gap-2 pt-2">
16238
+ <span className="h-12 rounded-md bg-slate-950/[0.07]" />
16239
+ <span className="h-12 rounded-md bg-slate-950/[0.07]" />
16240
+ <span className="h-12 rounded-md bg-slate-950/[0.07]" />
16241
+ </div>
16242
+ <span className="block h-20 rounded-lg bg-slate-950/[0.07]" />
16243
+ </div>
16244
+ </div>
16245
+ <p className="mb-3 text-sm font-medium text-foreground/85">
15206
16246
  {generationIssue ?? t("designEditor.noFiles")}
15207
16247
  </p>
15208
16248
  {retryablePrompt ? (
@@ -15214,7 +16254,7 @@ ${serializedHtml}
15214
16254
  {retryablePrompt ? (
15215
16255
  <Button
15216
16256
  size="sm"
15217
- className="cursor-pointer"
16257
+ className="h-8 cursor-pointer rounded-md"
15218
16258
  onClick={handleRetryGeneration}
15219
16259
  >
15220
16260
  <IconRefresh className="h-3.5 w-3.5" />
@@ -15225,7 +16265,7 @@ ${serializedHtml}
15225
16265
  ref={generateBtnRef}
15226
16266
  variant={retryablePrompt ? "ghost" : "outline"}
15227
16267
  size="sm"
15228
- className="cursor-pointer"
16268
+ className="h-8 cursor-pointer rounded-md"
15229
16269
  onClick={() => {
15230
16270
  setRetryablePrompt(null);
15231
16271
  handlePromptOpenChange(true);
@@ -15246,7 +16286,7 @@ ${serializedHtml}
15246
16286
  )}
15247
16287
 
15248
16288
  {/* Right rail */}
15249
- {!embedded ? (
16289
+ {!embedded && !initialGenerationChromeLimited ? (
15250
16290
  <div
15251
16291
  className="relative flex h-full min-h-0 shrink-0 flex-col border-l border-[var(--design-editor-panel-divider-color)] bg-[var(--design-editor-panel-bg)]"
15252
16292
  style={{ width: rightSidebarWidth }}
@@ -15263,6 +16303,7 @@ ${serializedHtml}
15263
16303
  <div className="min-h-0 flex-1">
15264
16304
  <EditPanel
15265
16305
  selectedElement={selectedElement}
16306
+ selectedElements={selectedInspectorElements}
15266
16307
  pageStyles={pageStyles}
15267
16308
  zoom={zoom}
15268
16309
  headerTrailing={zoomControl}
@@ -15271,7 +16312,6 @@ ${serializedHtml}
15271
16312
  onActiveTabChange={setActiveInspectorTab}
15272
16313
  tweaks={tweaks}
15273
16314
  tweakValues={tweakSelections}
15274
- readOnly={initialGenerationReadOnly}
15275
16315
  activeContent={activeContent}
15276
16316
  activeFileUpdatedAt={activeFile?.updatedAt ?? null}
15277
16317
  onComponentPropApplied={handleComponentPropApplied}
@@ -15286,16 +16326,19 @@ ${serializedHtml}
15286
16326
  onRequestTweaks={handleRequestTweaks}
15287
16327
  onStyleChange={handleStyleChange}
15288
16328
  onStylesChange={handleStylesChange}
15289
- onAutoLayoutConvert={handleAutoLayoutConvert}
15290
16329
  onExport={handleInspectorExport}
15291
16330
  exporting={pngExporting || svgExporting}
15292
16331
  designId={id}
15293
16332
  fileId={activeFile?.id}
15294
- filename={activeFile?.filename}
15295
16333
  componentNodeId={selectedComponentNodeId}
15296
16334
  sourceCapabilities={sourceCapabilities}
16335
+ selectedElementAlreadyComponent={
16336
+ selectedElementAlreadyComponent
16337
+ }
15297
16338
  onCreateComponent={
15298
- id && selectedElement ? handleCreateComponent : undefined
16339
+ id && selectedElement && !selectedElementAlreadyComponent
16340
+ ? handleCreateComponent
16341
+ : undefined
15299
16342
  }
15300
16343
  defaultComponentName={defaultComponentName}
15301
16344
  inspectCode={inspectCodeData}
@@ -15366,6 +16409,7 @@ ${serializedHtml}
15366
16409
  durationMs={motionDurationMs}
15367
16410
  open={motionDockOpen}
15368
16411
  onOpenChange={setMotionDockOpenAnimated}
16412
+ onExitComplete={handleMotionDockExitComplete}
15369
16413
  onTracksChange={handleMotionTracksChange}
15370
16414
  onDurationChange={handleMotionDurationChange}
15371
16415
  canvasIframeRef={canvasIframeRef}