@agent-native/core 0.82.0 → 0.84.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (474) 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/faq.mdx +1 -1
  7. package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
  8. package/corpus/core/docs/content/locales/ar-SA/faq.mdx +1 -1
  9. package/corpus/core/docs/content/locales/ar-SA/template-chat.mdx +1 -1
  10. package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
  11. package/corpus/core/docs/content/locales/de-DE/faq.mdx +1 -1
  12. package/corpus/core/docs/content/locales/de-DE/template-chat.mdx +1 -1
  13. package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
  14. package/corpus/core/docs/content/locales/es-ES/faq.mdx +1 -1
  15. package/corpus/core/docs/content/locales/es-ES/template-chat.mdx +1 -1
  16. package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  17. package/corpus/core/docs/content/locales/fr-FR/faq.mdx +1 -1
  18. package/corpus/core/docs/content/locales/fr-FR/template-chat.mdx +1 -1
  19. package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
  20. package/corpus/core/docs/content/locales/hi-IN/faq.mdx +1 -1
  21. package/corpus/core/docs/content/locales/hi-IN/template-chat.mdx +1 -1
  22. package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  23. package/corpus/core/docs/content/locales/ja-JP/faq.mdx +1 -1
  24. package/corpus/core/docs/content/locales/ja-JP/template-chat.mdx +1 -1
  25. package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
  26. package/corpus/core/docs/content/locales/ko-KR/faq.mdx +1 -1
  27. package/corpus/core/docs/content/locales/ko-KR/template-chat.mdx +1 -1
  28. package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
  29. package/corpus/core/docs/content/locales/pt-BR/faq.mdx +1 -1
  30. package/corpus/core/docs/content/locales/pt-BR/template-chat.mdx +1 -1
  31. package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
  32. package/corpus/core/docs/content/locales/zh-CN/faq.mdx +1 -1
  33. package/corpus/core/docs/content/locales/zh-CN/template-chat.mdx +1 -1
  34. package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
  35. package/corpus/core/docs/content/locales/zh-TW/faq.mdx +1 -1
  36. package/corpus/core/docs/content/locales/zh-TW/template-chat.mdx +1 -1
  37. package/corpus/core/docs/content/template-chat.mdx +1 -1
  38. package/corpus/core/package.json +1 -1
  39. package/corpus/core/src/agent/engine/builtin.ts +1 -1
  40. package/corpus/core/src/agent/engine/output-tokens.ts +1 -1
  41. package/corpus/core/src/agent/harness/acp-adapter.ts +28 -4
  42. package/corpus/core/src/agent/harness/ai-sdk-adapter.ts +3 -0
  43. package/corpus/core/src/agent/harness/translate.ts +3 -0
  44. package/corpus/core/src/agent/harness/types.ts +1 -0
  45. package/corpus/core/src/agent/model-config.ts +34 -13
  46. package/corpus/core/src/agent/production-agent.ts +87 -10
  47. package/corpus/core/src/agent/thread-data-builder.ts +29 -4
  48. package/corpus/core/src/agent/tool-call-journal.ts +109 -48
  49. package/corpus/core/src/agent/types.ts +6 -1
  50. package/corpus/core/src/cli/design-connect.ts +106 -0
  51. package/corpus/core/src/cli/gateway-helpers.ts +17 -0
  52. package/corpus/core/src/cli/recap.ts +0 -2
  53. package/corpus/core/src/cli/skills.ts +54 -16
  54. package/corpus/core/src/cli/workspace-dev.ts +29 -5
  55. package/corpus/core/src/client/AssistantChat.tsx +89 -42
  56. package/corpus/core/src/client/CommandMenu.tsx +34 -12
  57. package/corpus/core/src/client/DefaultSpinner.tsx +8 -1
  58. package/corpus/core/src/client/chat/message-components.tsx +49 -1
  59. package/corpus/core/src/client/chat/repo-helpers.ts +71 -0
  60. package/corpus/core/src/client/chat/run-recovery.tsx +15 -5
  61. package/corpus/core/src/client/components/ApiKeySettings.tsx +8 -8
  62. package/corpus/core/src/client/components/CodeRequiredDialog.tsx +26 -15
  63. package/corpus/core/src/client/components/MissingKeyCard.tsx +13 -6
  64. package/corpus/core/src/client/composer/TiptapComposer.tsx +4 -3
  65. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +0 -1
  66. package/corpus/core/src/client/guided-questions.tsx +21 -27
  67. package/corpus/core/src/client/integrations/IntegrationCard.tsx +1 -1
  68. package/corpus/core/src/client/integrations/IntegrationsPanel.tsx +1 -1
  69. package/corpus/core/src/client/onboarding/OnboardingBanner.tsx +2 -2
  70. package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +23 -19
  71. package/corpus/core/src/client/resources/ResourceEditor.tsx +18 -10
  72. package/corpus/core/src/client/resources/ResourcesPanel.tsx +5 -1
  73. package/corpus/core/src/client/settings/SettingsPanel.tsx +8 -3
  74. package/corpus/core/src/client/settings/useBuilderStatus.ts +8 -41
  75. package/corpus/core/src/client/useProductionAgent.ts +6 -2
  76. package/corpus/core/src/deploy/build.ts +1 -1
  77. package/corpus/core/src/observability/traces.ts +3 -3
  78. package/corpus/core/src/onboarding/default-steps.ts +1 -1
  79. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  80. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  81. package/corpus/core/src/server/agent-chat-plugin.ts +177 -81
  82. package/corpus/core/src/server/core-routes-plugin.ts +7 -1
  83. package/corpus/core/src/server/open-route.ts +59 -40
  84. package/corpus/core/src/shared/reasoning-effort.ts +2 -0
  85. package/corpus/core/src/sharing/access.ts +22 -2
  86. package/corpus/core/src/sharing/registry.ts +18 -0
  87. package/corpus/core/src/styles/agent-native.css +104 -20
  88. package/corpus/core/src/styles/rich-markdown-editor.css +11 -10
  89. package/corpus/templates/analytics/actions/hubspot-deals.ts +64 -4
  90. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +10 -10
  91. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +10 -10
  92. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +6 -5
  93. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +9 -9
  94. package/corpus/templates/analytics/app/global.css +4 -0
  95. package/corpus/templates/analytics/app/lib/chart-theme.ts +13 -0
  96. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +22 -24
  97. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-charts-now-use-theme-aware-tooltip-and-grid-colors.md +6 -0
  98. package/corpus/templates/analytics/changelog/2026-06-30-fixed-ask-composer-spacing-so-the-chat-field-has-a-comfortab.md +6 -0
  99. package/corpus/templates/calendar/AGENTS.md +4 -0
  100. package/corpus/templates/calendar/actions/connect-google-calendar.ts +80 -0
  101. package/corpus/templates/calendar/changelog/2026-06-30-calendar-now-gives-the-agent-a-safe-google-connection-link-i.md +6 -0
  102. package/corpus/templates/calendar/server/plugins/agent-chat.ts +4 -0
  103. package/corpus/templates/content/.env.local.example +6 -0
  104. package/corpus/templates/content/AGENTS.md +10 -1
  105. package/corpus/templates/content/actions/_builder-cms-read-client.ts +14 -1
  106. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +43 -0
  107. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +79 -7
  108. package/corpus/templates/content/actions/_database-source-utils.ts +987 -22
  109. package/corpus/templates/content/actions/_database-utils.ts +41 -0
  110. package/corpus/templates/content/actions/_local-file-documents.ts +74 -1
  111. package/corpus/templates/content/actions/attach-content-database-source.ts +33 -8
  112. package/corpus/templates/content/actions/change-content-database-source-role.ts +14 -0
  113. package/corpus/templates/content/actions/disconnect-content-database-source.ts +3 -0
  114. package/corpus/templates/content/actions/execute-builder-source-execution.ts +71 -3
  115. package/corpus/templates/content/actions/list-content-databases.ts +41 -33
  116. package/corpus/templates/content/actions/process-builder-body-hydration.ts +47 -0
  117. package/corpus/templates/content/actions/update-document.ts +93 -0
  118. package/corpus/templates/content/app/blocks/SourceComponentBlock.tsx +277 -0
  119. package/corpus/templates/content/app/blocks/contentBlockRegistry.tsx +2 -0
  120. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -5
  121. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +45 -4
  122. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +5 -1
  123. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +92 -0
  124. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +5 -3
  125. package/corpus/templates/content/app/components/editor/extensions/registryBlocks.ts +41 -0
  126. package/corpus/templates/content/app/components/editor/registrySlashItems.ts +4 -0
  127. package/corpus/templates/content/app/global.css +16 -8
  128. package/corpus/templates/content/app/hooks/use-content-database.ts +24 -0
  129. package/corpus/templates/content/app/i18n/zh-TW.ts +24 -0
  130. package/corpus/templates/content/app/i18n-data.ts +212 -0
  131. package/corpus/templates/content/app/lib/content-command-search.ts +64 -0
  132. package/corpus/templates/content/app/root.tsx +236 -8
  133. package/corpus/templates/content/changelog/2026-06-29-builder-cms-sources-now-sync-article-bodies-through-content.md +6 -0
  134. package/corpus/templates/content/changelog/2026-06-30-builder-article-media-now-render-as-images-and-embeds-when-r.md +6 -0
  135. package/corpus/templates/content/changelog/2026-06-30-builder-source-components-now-render-as-preserved-preview-bl.md +6 -0
  136. package/corpus/templates/content/changelog/2026-06-30-cmd-k-search-now-opens-from-the-editor-and-finds-real-content.md +6 -0
  137. package/corpus/templates/content/changelog/2026-06-30-editor-floating-toolbars-now-use-theme-aware-colors-in-light.md +6 -0
  138. package/corpus/templates/content/package.json +3 -1
  139. package/corpus/templates/content/scripts/check-native-deps.mjs +57 -0
  140. package/corpus/templates/content/scripts/dev-database.mjs +83 -0
  141. package/corpus/templates/content/scripts/seed-demo-user.mjs +107 -0
  142. package/corpus/templates/content/server/db/schema.ts +27 -0
  143. package/corpus/templates/content/server/plugins/db.ts +33 -0
  144. package/corpus/templates/content/shared/api.ts +45 -0
  145. package/corpus/templates/content/shared/builder-mdx.ts +905 -5
  146. package/corpus/templates/content/shared/nfm-registry.ts +2 -0
  147. package/corpus/templates/content/shared/source-component-block.ts +141 -0
  148. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +6 -1
  149. package/corpus/templates/design/actions/add-localhost-screens.ts +63 -8
  150. package/corpus/templates/design/actions/apply-motion-edit.ts +12 -46
  151. package/corpus/templates/design/actions/list-design-native-assets.ts +2 -1
  152. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +49 -1
  153. package/corpus/templates/design/actions/navigate.ts +3 -12
  154. package/corpus/templates/design/actions/open-visual-edit.ts +341 -0
  155. package/corpus/templates/design/actions/view-screen.ts +1 -1
  156. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +1 -1
  157. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +350 -15
  158. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +71 -91
  159. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +25 -27
  160. package/corpus/templates/design/app/components/design/DeviceFrame.tsx +1 -1
  161. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +2 -2
  162. package/corpus/templates/design/app/components/design/EditPanel.tsx +1111 -570
  163. package/corpus/templates/design/app/components/design/LayersPanel.tsx +162 -32
  164. package/corpus/templates/design/app/components/design/MotionDock.tsx +268 -240
  165. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1193 -207
  166. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +1 -1
  167. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +81 -102
  168. package/corpus/templates/design/app/components/design/StatesPanel.tsx +6 -6
  169. package/corpus/templates/design/app/components/design/TokensPanel.tsx +11 -11
  170. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +9 -7
  171. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1225 -154
  172. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +130 -37
  173. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +24 -11
  174. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +184 -162
  175. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +137 -79
  176. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +13 -13
  177. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +8 -8
  178. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +2 -2
  179. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +4 -4
  180. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +3 -3
  181. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -7
  182. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +14 -11
  183. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +4 -4
  184. package/corpus/templates/design/app/components/design/inspector/index.ts +2 -0
  185. package/corpus/templates/design/app/components/design/types.ts +19 -0
  186. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +61 -78
  187. package/corpus/templates/design/app/components/layout/Header.tsx +0 -1
  188. package/corpus/templates/design/app/components/layout/Layout.tsx +3 -2
  189. package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
  190. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +5 -4
  191. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  192. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +2 -2
  193. package/corpus/templates/design/app/global.css +56 -0
  194. package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -5
  195. package/corpus/templates/design/app/hooks/use-navigation-state.ts +6 -7
  196. package/corpus/templates/design/app/hooks/use-question-flow.ts +3 -2
  197. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +6 -4
  198. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +6 -3
  199. package/corpus/templates/design/app/i18n/zh-TW.ts +5 -14
  200. package/corpus/templates/design/app/i18n-data.ts +63 -188
  201. package/corpus/templates/design/app/lib/agent-chat.ts +13 -0
  202. package/corpus/templates/design/app/pages/DesignEditor.tsx +1755 -401
  203. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +9 -5
  204. package/corpus/templates/design/app/pages/DesignSystems.tsx +2 -2
  205. package/corpus/templates/design/app/pages/Index.tsx +49 -26
  206. package/corpus/templates/design/app/pages/NotFound.tsx +25 -8
  207. package/corpus/templates/design/app/pages/VisualEdit.tsx +17 -5
  208. package/corpus/templates/design/app/routes/examples.tsx +1 -7
  209. package/corpus/templates/design/app/routes/templates.tsx +7 -4
  210. package/corpus/templates/design/changelog/2026-06-30-board-shapes-and-auto-layout-controls-are-clearer.md +6 -0
  211. package/corpus/templates/design/changelog/2026-06-30-canvas-editing-now-keeps-undo-redo-and-cross-screen-layer-mo.md +6 -0
  212. package/corpus/templates/design/changelog/2026-06-30-component-selections-now-use-purple-component-chrome-and-sho.md +6 -0
  213. package/corpus/templates/design/changelog/2026-06-30-constraints-now-stay-tucked-into-a-compact-inspector-preview.md +6 -0
  214. package/corpus/templates/design/changelog/2026-06-30-creating-a-component-now-opens-in-a-compact-inspector-popove.md +6 -0
  215. package/corpus/templates/design/changelog/2026-06-30-design-cards-no-longer-show-legacy-project-type-badges.md +6 -0
  216. package/corpus/templates/design/changelog/2026-06-30-design-chat-now-keeps-new-turns-in-the-visible-design-agent-.md +6 -0
  217. package/corpus/templates/design/changelog/2026-06-30-design-editor-panels-now-keep-their-compact-text-sizing-cons.md +6 -0
  218. package/corpus/templates/design/changelog/2026-06-30-design-no-longer-shows-the-connected-local-code-warning-bann.md +6 -0
  219. package/corpus/templates/design/changelog/2026-06-30-design-now-opens-to-the-files-panel-unless-a-generation-is-a.md +6 -0
  220. package/corpus/templates/design/changelog/2026-06-30-design-opens-with-a-cleaner-empty-canvas-and-loading-preview.md +6 -0
  221. package/corpus/templates/design/changelog/2026-06-30-design-tools-use-simpler-less-decorative-icons.md +6 -0
  222. package/corpus/templates/design/changelog/2026-06-30-dragging-absolute-elements-no-longer-shows-coordinate-labels.md +6 -0
  223. package/corpus/templates/design/changelog/2026-06-30-fixed-all-screens-opening-too-zoomed-in-by-default.md +6 -0
  224. package/corpus/templates/design/changelog/2026-06-30-fixed-the-public-design-assets-sidebar-so-native-components-.md +6 -0
  225. package/corpus/templates/design/changelog/2026-06-30-fixed-the-screen-overview-board-so-empty-board-space-no-long.md +6 -0
  226. package/corpus/templates/design/changelog/2026-06-30-full-view-screens-can-now-scroll-while-remaining-editable.md +6 -0
  227. package/corpus/templates/design/changelog/2026-06-30-inspector-element-headers-show-the-selected-html-tag-and-the.md +6 -0
  228. package/corpus/templates/design/changelog/2026-06-30-inspector-inputs-stay-compact-across-desktop-breakpoints.md +6 -0
  229. package/corpus/templates/design/changelog/2026-06-30-layers-can-be-multi-selected-from-the-canvas-and-edited-toge.md +6 -0
  230. package/corpus/templates/design/changelog/2026-06-30-local-visual-edit-links-can-edit-localhost-frames-without-si.md +6 -0
  231. package/corpus/templates/design/changelog/2026-06-30-localhost-visual-edit-frames-can-now-be-selected-and-edited.md +6 -0
  232. package/corpus/templates/design/changelog/2026-06-30-marquee-selection-now-works-inside-screens-multi-selected-la.md +6 -0
  233. package/corpus/templates/design/changelog/2026-06-30-motion-is-now-available-from-the-persistent-left-rail-and-th.md +6 -0
  234. package/corpus/templates/design/changelog/2026-06-30-motion-timeline-collapse-and-reopen-animations-feel-smoother.md +6 -0
  235. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +6 -0
  236. package/corpus/templates/design/changelog/2026-06-30-new-board-shapes-now-start-with-neutral-gray-styling.md +6 -0
  237. package/corpus/templates/design/changelog/2026-06-30-new-designs-open-into-a-steadier-loading-state-before-the-ed.md +6 -0
  238. package/corpus/templates/design/changelog/2026-06-30-new-designs-show-a-loading-state-during-handoff-and-keep-edi.md +6 -0
  239. package/corpus/templates/design/changelog/2026-06-30-public-visual-edit-design-links-now-open-directly-instead-of.md +6 -0
  240. package/corpus/templates/design/changelog/2026-06-30-removed-the-confusing-states-section-from-the-design-editor-.md +6 -0
  241. package/corpus/templates/design/changelog/2026-06-30-removed-the-templates-gallery-from-design-navigation.md +6 -0
  242. package/corpus/templates/design/changelog/2026-06-30-share-stays-visible-as-soon-as-editable-designs-load.md +6 -0
  243. package/corpus/templates/design/changelog/2026-06-30-signed-out-save-and-share-controls-use-simpler-labels-and-op.md +6 -0
  244. package/corpus/templates/design/changelog/2026-06-30-text-added-on-the-canvas-now-focuses-cleanly-with-the-right-.md +6 -0
  245. package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-is-simpler-and-its-bottom.md +6 -0
  246. package/corpus/templates/design/changelog/2026-06-30-the-design-editor-loading-skeleton-now-follows-the-current-t.md +6 -0
  247. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-child-layout-controls-o.md +6 -0
  248. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-no-longer-shows-the-ask-ai-section-abov.md +6 -0
  249. package/corpus/templates/design/changelog/2026-06-30-the-design-review-panel-no-longer-shows-a-separate-inline-au.md +6 -0
  250. package/corpus/templates/design/changelog/2026-06-30-the-empty-designs-page-now-focuses-on-a-single-primary-new-d.md +6 -0
  251. package/corpus/templates/design/changelog/2026-06-30-the-layers-panel-header-stays-as-layers-while-multiple-items.md +6 -0
  252. package/corpus/templates/design/changelog/2026-06-30-the-missing-design-screen-now-uses-a-neutral-back-button.md +6 -0
  253. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-now-closes-without-squeezing-the-canvas-.md +6 -0
  254. package/corpus/templates/design/changelog/2026-06-30-the-new-design-connect-ai-setup-now-lines-up-with-the-compos.md +6 -0
  255. package/corpus/templates/design/changelog/2026-06-30-the-screen-overview-board-now-blends-into-the-canvas-and-new.md +6 -0
  256. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-drops-now-show-screen-guides.md +6 -0
  257. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-keep-a-transparent-backdrop-and-.md +6 -0
  258. package/corpus/templates/design/changelog/2026-06-30-top-level-canvas-layers-now-select-reliably-from-all-screens.md +6 -0
  259. package/corpus/templates/design/changelog/2026-06-30-visual-edit-can-open-and-refresh-localhost-screens-in-one-au.md +6 -0
  260. package/corpus/templates/design/e2e/helpers.ts +15 -4
  261. package/corpus/templates/design/server/db/index.ts +52 -0
  262. package/corpus/templates/design/server/plugins/agent-chat.ts +5 -0
  263. package/corpus/templates/design/server/plugins/auth.ts +1 -0
  264. package/corpus/templates/design/server/plugins/core-routes.ts +4 -0
  265. package/corpus/templates/design/shared/board-file.ts +380 -5
  266. package/corpus/templates/design/shared/board-objects.ts +2 -2
  267. package/corpus/templates/design/shared/code-layer.ts +50 -6
  268. package/corpus/templates/design/shared/motion-compiler.ts +27 -0
  269. package/corpus/templates/macros/app/components/DailyProgress.tsx +41 -37
  270. package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
  271. package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
  272. package/corpus/templates/macros/app/components/VoiceDictation.tsx +1 -1
  273. package/corpus/templates/macros/app/components/WeeklyCaloriesChart.tsx +1 -1
  274. package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
  275. package/corpus/templates/macros/app/components/WeightTracker.tsx +1 -1
  276. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +1 -1
  277. package/corpus/templates/macros/app/routes/_index.tsx +5 -5
  278. package/corpus/templates/macros/app/routes/analytics.tsx +1 -1
  279. package/corpus/templates/macros/changelog/2026-06-30-dashboard-loading-and-chart-surfaces-now-use-theme-aware-col.md +6 -0
  280. package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +14 -11
  281. package/corpus/templates/mail/app/global.css +3 -2
  282. package/corpus/templates/mail/changelog/2026-06-30-compose-floating-toolbars-now-use-theme-aware-colors-in-ligh.md +6 -0
  283. package/corpus/templates/plan/app/pages/PlansPage.tsx +2 -2
  284. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +3 -3
  285. package/corpus/templates/slides/app/global.css +10 -0
  286. package/corpus/templates/slides/app/pages/Index.tsx +55 -51
  287. package/corpus/templates/slides/changelog/2026-06-30-deck-thumbnails-collapse-cleanly-to-one-column-when-the-chat.md +6 -0
  288. package/dist/agent/engine/anthropic-engine.d.ts +2 -2
  289. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  290. package/dist/agent/engine/builder-engine.d.ts +2 -2
  291. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  292. package/dist/agent/engine/builtin.js +1 -1
  293. package/dist/agent/engine/builtin.js.map +1 -1
  294. package/dist/agent/engine/output-tokens.js +1 -1
  295. package/dist/agent/engine/output-tokens.js.map +1 -1
  296. package/dist/agent/harness/acp-adapter.d.ts +4 -1
  297. package/dist/agent/harness/acp-adapter.d.ts.map +1 -1
  298. package/dist/agent/harness/acp-adapter.js +19 -2
  299. package/dist/agent/harness/acp-adapter.js.map +1 -1
  300. package/dist/agent/harness/ai-sdk-adapter.d.ts.map +1 -1
  301. package/dist/agent/harness/ai-sdk-adapter.js +3 -0
  302. package/dist/agent/harness/ai-sdk-adapter.js.map +1 -1
  303. package/dist/agent/harness/translate.d.ts.map +1 -1
  304. package/dist/agent/harness/translate.js +3 -0
  305. package/dist/agent/harness/translate.js.map +1 -1
  306. package/dist/agent/harness/types.d.ts +1 -0
  307. package/dist/agent/harness/types.d.ts.map +1 -1
  308. package/dist/agent/harness/types.js.map +1 -1
  309. package/dist/agent/model-config.d.ts +21 -20
  310. package/dist/agent/model-config.d.ts.map +1 -1
  311. package/dist/agent/model-config.js +31 -13
  312. package/dist/agent/model-config.js.map +1 -1
  313. package/dist/agent/production-agent.d.ts.map +1 -1
  314. package/dist/agent/production-agent.js +85 -10
  315. package/dist/agent/production-agent.js.map +1 -1
  316. package/dist/agent/thread-data-builder.d.ts.map +1 -1
  317. package/dist/agent/thread-data-builder.js +27 -6
  318. package/dist/agent/thread-data-builder.js.map +1 -1
  319. package/dist/agent/tool-call-journal.d.ts +11 -9
  320. package/dist/agent/tool-call-journal.d.ts.map +1 -1
  321. package/dist/agent/tool-call-journal.js +92 -40
  322. package/dist/agent/tool-call-journal.js.map +1 -1
  323. package/dist/agent/types.d.ts +5 -1
  324. package/dist/agent/types.d.ts.map +1 -1
  325. package/dist/agent/types.js.map +1 -1
  326. package/dist/cli/design-connect.d.ts.map +1 -1
  327. package/dist/cli/design-connect.js +84 -0
  328. package/dist/cli/design-connect.js.map +1 -1
  329. package/dist/cli/gateway-helpers.d.ts +4 -0
  330. package/dist/cli/gateway-helpers.d.ts.map +1 -1
  331. package/dist/cli/gateway-helpers.js +9 -0
  332. package/dist/cli/gateway-helpers.js.map +1 -1
  333. package/dist/cli/recap.d.ts.map +1 -1
  334. package/dist/cli/recap.js +0 -2
  335. package/dist/cli/recap.js.map +1 -1
  336. package/dist/cli/skills.d.ts.map +1 -1
  337. package/dist/cli/skills.js +54 -16
  338. package/dist/cli/skills.js.map +1 -1
  339. package/dist/cli/workspace-dev.d.ts.map +1 -1
  340. package/dist/cli/workspace-dev.js +33 -6
  341. package/dist/cli/workspace-dev.js.map +1 -1
  342. package/dist/client/AssistantChat.d.ts.map +1 -1
  343. package/dist/client/AssistantChat.js +72 -26
  344. package/dist/client/AssistantChat.js.map +1 -1
  345. package/dist/client/CommandMenu.d.ts +6 -2
  346. package/dist/client/CommandMenu.d.ts.map +1 -1
  347. package/dist/client/CommandMenu.js +25 -14
  348. package/dist/client/CommandMenu.js.map +1 -1
  349. package/dist/client/DefaultSpinner.d.ts.map +1 -1
  350. package/dist/client/DefaultSpinner.js +8 -1
  351. package/dist/client/DefaultSpinner.js.map +1 -1
  352. package/dist/client/chat/message-components.d.ts +7 -1
  353. package/dist/client/chat/message-components.d.ts.map +1 -1
  354. package/dist/client/chat/message-components.js +37 -1
  355. package/dist/client/chat/message-components.js.map +1 -1
  356. package/dist/client/chat/repo-helpers.d.ts +1 -0
  357. package/dist/client/chat/repo-helpers.d.ts.map +1 -1
  358. package/dist/client/chat/repo-helpers.js +59 -0
  359. package/dist/client/chat/repo-helpers.js.map +1 -1
  360. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  361. package/dist/client/chat/run-recovery.js +4 -4
  362. package/dist/client/chat/run-recovery.js.map +1 -1
  363. package/dist/client/components/ApiKeySettings.js +8 -8
  364. package/dist/client/components/ApiKeySettings.js.map +1 -1
  365. package/dist/client/components/CodeRequiredDialog.d.ts.map +1 -1
  366. package/dist/client/components/CodeRequiredDialog.js +24 -15
  367. package/dist/client/components/CodeRequiredDialog.js.map +1 -1
  368. package/dist/client/components/MissingKeyCard.d.ts.map +1 -1
  369. package/dist/client/components/MissingKeyCard.js +11 -6
  370. package/dist/client/components/MissingKeyCard.js.map +1 -1
  371. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  372. package/dist/client/composer/TiptapComposer.js +4 -3
  373. package/dist/client/composer/TiptapComposer.js.map +1 -1
  374. package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
  375. package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
  376. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  377. package/dist/client/guided-questions.d.ts.map +1 -1
  378. package/dist/client/guided-questions.js +6 -6
  379. package/dist/client/guided-questions.js.map +1 -1
  380. package/dist/client/integrations/IntegrationCard.js +1 -1
  381. package/dist/client/integrations/IntegrationCard.js.map +1 -1
  382. package/dist/client/integrations/IntegrationsPanel.js +1 -1
  383. package/dist/client/integrations/IntegrationsPanel.js.map +1 -1
  384. package/dist/client/onboarding/OnboardingBanner.js +2 -2
  385. package/dist/client/onboarding/OnboardingBanner.js.map +1 -1
  386. package/dist/client/onboarding/OnboardingPanel.js +23 -19
  387. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  388. package/dist/client/resources/ResourceEditor.d.ts.map +1 -1
  389. package/dist/client/resources/ResourceEditor.js +13 -10
  390. package/dist/client/resources/ResourceEditor.js.map +1 -1
  391. package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
  392. package/dist/client/resources/ResourcesPanel.js +2 -1
  393. package/dist/client/resources/ResourcesPanel.js.map +1 -1
  394. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  395. package/dist/client/settings/SettingsPanel.js +8 -3
  396. package/dist/client/settings/SettingsPanel.js.map +1 -1
  397. package/dist/client/settings/useBuilderStatus.d.ts.map +1 -1
  398. package/dist/client/settings/useBuilderStatus.js +6 -38
  399. package/dist/client/settings/useBuilderStatus.js.map +1 -1
  400. package/dist/client/useProductionAgent.d.ts +2 -1
  401. package/dist/client/useProductionAgent.d.ts.map +1 -1
  402. package/dist/client/useProductionAgent.js.map +1 -1
  403. package/dist/deploy/build.js +1 -1
  404. package/dist/deploy/build.js.map +1 -1
  405. package/dist/notifications/routes.d.ts +3 -3
  406. package/dist/observability/routes.d.ts +3 -3
  407. package/dist/observability/traces.d.ts.map +1 -1
  408. package/dist/observability/traces.js.map +1 -1
  409. package/dist/onboarding/default-steps.js +1 -1
  410. package/dist/onboarding/default-steps.js.map +1 -1
  411. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  412. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  413. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  414. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  415. package/dist/server/agent-chat-plugin.d.ts +3 -0
  416. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  417. package/dist/server/agent-chat-plugin.js +134 -72
  418. package/dist/server/agent-chat-plugin.js.map +1 -1
  419. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  420. package/dist/server/core-routes-plugin.d.ts +3 -0
  421. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  422. package/dist/server/core-routes-plugin.js +4 -1
  423. package/dist/server/core-routes-plugin.js.map +1 -1
  424. package/dist/server/open-route.d.ts +9 -0
  425. package/dist/server/open-route.d.ts.map +1 -1
  426. package/dist/server/open-route.js +48 -38
  427. package/dist/server/open-route.js.map +1 -1
  428. package/dist/shared/reasoning-effort.js +3 -0
  429. package/dist/shared/reasoning-effort.js.map +1 -1
  430. package/dist/sharing/access.d.ts.map +1 -1
  431. package/dist/sharing/access.js +18 -2
  432. package/dist/sharing/access.js.map +1 -1
  433. package/dist/sharing/registry.d.ts +10 -0
  434. package/dist/sharing/registry.d.ts.map +1 -1
  435. package/dist/sharing/registry.js.map +1 -1
  436. package/dist/styles/agent-native.css +104 -20
  437. package/dist/styles/rich-markdown-editor.css +11 -10
  438. package/docs/content/cloneable-saas.mdx +2 -2
  439. package/docs/content/faq.mdx +1 -1
  440. package/docs/content/locales/ar-SA/cloneable-saas.mdx +2 -2
  441. package/docs/content/locales/ar-SA/faq.mdx +1 -1
  442. package/docs/content/locales/ar-SA/template-chat.mdx +1 -1
  443. package/docs/content/locales/de-DE/cloneable-saas.mdx +2 -2
  444. package/docs/content/locales/de-DE/faq.mdx +1 -1
  445. package/docs/content/locales/de-DE/template-chat.mdx +1 -1
  446. package/docs/content/locales/es-ES/cloneable-saas.mdx +2 -2
  447. package/docs/content/locales/es-ES/faq.mdx +1 -1
  448. package/docs/content/locales/es-ES/template-chat.mdx +1 -1
  449. package/docs/content/locales/fr-FR/cloneable-saas.mdx +2 -2
  450. package/docs/content/locales/fr-FR/faq.mdx +1 -1
  451. package/docs/content/locales/fr-FR/template-chat.mdx +1 -1
  452. package/docs/content/locales/hi-IN/cloneable-saas.mdx +2 -2
  453. package/docs/content/locales/hi-IN/faq.mdx +1 -1
  454. package/docs/content/locales/hi-IN/template-chat.mdx +1 -1
  455. package/docs/content/locales/ja-JP/cloneable-saas.mdx +2 -2
  456. package/docs/content/locales/ja-JP/faq.mdx +1 -1
  457. package/docs/content/locales/ja-JP/template-chat.mdx +1 -1
  458. package/docs/content/locales/ko-KR/cloneable-saas.mdx +2 -2
  459. package/docs/content/locales/ko-KR/faq.mdx +1 -1
  460. package/docs/content/locales/ko-KR/template-chat.mdx +1 -1
  461. package/docs/content/locales/pt-BR/cloneable-saas.mdx +2 -2
  462. package/docs/content/locales/pt-BR/faq.mdx +1 -1
  463. package/docs/content/locales/pt-BR/template-chat.mdx +1 -1
  464. package/docs/content/locales/zh-CN/cloneable-saas.mdx +2 -2
  465. package/docs/content/locales/zh-CN/faq.mdx +1 -1
  466. package/docs/content/locales/zh-CN/template-chat.mdx +1 -1
  467. package/docs/content/locales/zh-TW/cloneable-saas.mdx +2 -2
  468. package/docs/content/locales/zh-TW/faq.mdx +1 -1
  469. package/docs/content/locales/zh-TW/template-chat.mdx +1 -1
  470. package/docs/content/template-chat.mdx +1 -1
  471. package/package.json +1 -1
  472. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +0 -157
  473. package/corpus/templates/design/app/lib/design-templates.ts +0 -1305
  474. 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,25 +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";
499
591
 
500
- function isContentUndoRedoOrderKind(
501
- kind: UndoRedoOrderKind | undefined,
502
- ): kind is "content" | "file-content" {
503
- return kind === "content" || kind === "file-content";
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"];
504
599
  }
505
600
 
506
601
  function resolveZoomUpdate(update: SetStateAction<number>, current: number) {
507
602
  return typeof update === "function" ? update(current) : update;
508
603
  }
509
604
 
510
- export function shouldLockInspectorForInitialGeneration(args: {
605
+ export function shouldLimitEditorChromeUntilContentReady(args: {
511
606
  fileCount: number;
607
+ hasActiveCanvasContent: boolean;
512
608
  generating: boolean;
513
609
  pendingGenerationActive: boolean;
514
610
  }) {
515
- const { fileCount, generating, pendingGenerationActive } = args;
516
- 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
+ );
517
621
  }
518
622
 
519
623
  export function shouldEscapeToOverview(args: {
@@ -755,6 +859,13 @@ interface DesignFile {
755
859
  updatedAt: string;
756
860
  }
757
861
 
862
+ interface LiveScreenSnapshot {
863
+ url: string;
864
+ html: string;
865
+ status?: number;
866
+ contentType?: string;
867
+ }
868
+
758
869
  interface DesignData {
759
870
  id: string;
760
871
  title: string;
@@ -882,6 +993,32 @@ interface GeometryHistoryEntry {
882
993
  after: CanvasFrameGeometryById;
883
994
  }
884
995
 
996
+ function geometryHistoryEntryTouchesFrameIds(
997
+ entry: GeometryHistoryEntry,
998
+ frameIds: Set<string>,
999
+ ) {
1000
+ for (const frameId of frameIds) {
1001
+ if (entry.before[frameId] || entry.after[frameId]) return true;
1002
+ }
1003
+ return false;
1004
+ }
1005
+
1006
+ function removeRecentUndoRedoOrderKinds<T extends string>(
1007
+ order: T[],
1008
+ kind: T,
1009
+ count: number,
1010
+ ): T[] {
1011
+ if (count <= 0) return order;
1012
+ const next = [...order];
1013
+ let remaining = count;
1014
+ for (let index = next.length - 1; index >= 0 && remaining > 0; index -= 1) {
1015
+ if (next[index] !== kind) continue;
1016
+ next.splice(index, 1);
1017
+ remaining -= 1;
1018
+ }
1019
+ return next;
1020
+ }
1021
+
885
1022
  export interface ContentHistoryChange {
886
1023
  fileId: string;
887
1024
  before: string;
@@ -911,6 +1048,17 @@ export function getAvailableContentHistoryChanges(
911
1048
  );
912
1049
  }
913
1050
 
1051
+ function findLastContentHistoryChangeIndex(
1052
+ stack: ContentHistoryChange[],
1053
+ fileId?: string | null,
1054
+ ) {
1055
+ if (!fileId) return -1;
1056
+ for (let index = stack.length - 1; index >= 0; index -= 1) {
1057
+ if (stack[index]?.fileId === fileId) return index;
1058
+ }
1059
+ return -1;
1060
+ }
1061
+
914
1062
  type PatchProofStatus =
915
1063
  | "runtime"
916
1064
  | "queued"
@@ -1055,6 +1203,7 @@ function designEditorCommandFromSearchParams(
1055
1203
  searchParams.get("screen") ??
1056
1204
  searchParams.get("fileId") ??
1057
1205
  searchParams.get("filename");
1206
+ const selection = searchParams.get("selection");
1058
1207
  const rawZoom = searchParams.get("zoom");
1059
1208
  const zoom = rawZoom !== null ? Number(rawZoom) : NaN;
1060
1209
  const tool = normalizeDesignTool(searchParams.get("tool"));
@@ -1066,6 +1215,7 @@ function designEditorCommandFromSearchParams(
1066
1215
  inspector !== "extensions" &&
1067
1216
  !leftPanel &&
1068
1217
  !screen &&
1218
+ !selection &&
1069
1219
  !tool
1070
1220
  ) {
1071
1221
  return null;
@@ -1086,6 +1236,7 @@ function designEditorCommandFromSearchParams(
1086
1236
  }
1087
1237
  if (leftPanel) command.leftPanel = leftPanel;
1088
1238
  if (screen) command.screen = screen;
1239
+ if (selection) command.selection = selection;
1089
1240
  if (Number.isFinite(zoom)) {
1090
1241
  command.zoom = zoom;
1091
1242
  } else if (editorView === "single") {
@@ -1276,6 +1427,35 @@ function removeAbsolutePositioningFromNodeInHtml(
1276
1427
  }
1277
1428
  }
1278
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
+
1279
1459
  function escapeHtmlText(value: string): string {
1280
1460
  return value
1281
1461
  .replace(/&/g, "&amp;")
@@ -1457,14 +1637,19 @@ function primitiveLayerName(primitive: CanvasPrimitiveInsert): string {
1457
1637
  function appendCanvasPrimitiveToHtml(
1458
1638
  content: string,
1459
1639
  primitive: CanvasPrimitiveInsert,
1640
+ options?: { preserveNegativePosition?: boolean },
1460
1641
  ): string | null {
1461
1642
  if (typeof window === "undefined") return null;
1462
1643
  try {
1463
1644
  const doc = new DOMParser().parseFromString(content, "text/html");
1464
1645
  if (!doc.body) return null;
1465
1646
  const geometry = primitive.geometry;
1466
- const left = Math.max(0, Math.round(geometry.x));
1467
- const top = Math.max(0, Math.round(geometry.y));
1647
+ const left = options?.preserveNegativePosition
1648
+ ? Math.round(geometry.x)
1649
+ : Math.max(0, Math.round(geometry.x));
1650
+ const top = options?.preserveNegativePosition
1651
+ ? Math.round(geometry.y)
1652
+ : Math.max(0, Math.round(geometry.y));
1468
1653
  const width = Math.max(1, Math.round(geometry.width));
1469
1654
  const height = Math.max(1, Math.round(geometry.height));
1470
1655
  const nodeId = primitive.nodeId ?? uniqueLayerId(primitive.kind);
@@ -1538,6 +1723,9 @@ function appendCanvasPrimitiveToHtml(
1538
1723
  }
1539
1724
  svg.setAttribute("data-agent-native-node-id", nodeId);
1540
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.
1541
1729
  svg.setAttribute("data-an-primitive", primitive.kind);
1542
1730
  svg.setAttribute(
1543
1731
  "viewBox",
@@ -1583,6 +1771,10 @@ function appendCanvasPrimitiveToHtml(
1583
1771
  polygon.setAttribute("stroke-linejoin", "round");
1584
1772
  svg.setAttribute("data-agent-native-node-id", nodeId);
1585
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);
1586
1778
  svg.setAttribute("viewBox", `0 0 ${width} ${height}`);
1587
1779
  svg.setAttribute(
1588
1780
  "style",
@@ -1637,7 +1829,7 @@ function appendCanvasPrimitiveToHtml(
1637
1829
  element.style.borderRadius = canonical.borderRadius;
1638
1830
  element.style.overflow = "hidden";
1639
1831
  } else if (primitive.kind === "text") {
1640
- element.textContent = primitive.text ?? "Text";
1832
+ element.textContent = primitive.text ?? "";
1641
1833
  element.style.display = primitive.autoSize ? "inline-block" : "flex";
1642
1834
  if (!primitive.autoSize) {
1643
1835
  element.style.alignItems = "center";
@@ -2061,12 +2253,38 @@ function layerTypeForCodeLayer(
2061
2253
  ): LayersPanelNode["type"] {
2062
2254
  if (node.type === "group") return "group";
2063
2255
  if (node.type === "component") return "component";
2256
+ if (node.type === "ellipse") return "ellipse";
2064
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";
2065
2263
  if (node.type === "text") return "text";
2066
2264
  if (node.type === "image") return "image";
2067
2265
  return "element";
2068
2266
  }
2069
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
+
2070
2288
  function preferredCodeLayerSelector(node: CodeLayerNode): string {
2071
2289
  return (
2072
2290
  node.selectors.find((selector) =>
@@ -2236,6 +2454,7 @@ function codeLayerTreeToPanelNodes(
2236
2454
  id: node.id,
2237
2455
  name: resolvedLayerName(node),
2238
2456
  type: layerTypeForCodeLayer(node),
2457
+ tagName: node.tag,
2239
2458
  layout: node.layout,
2240
2459
  detail: node.detail,
2241
2460
  badge: node.badge,
@@ -2255,6 +2474,14 @@ interface EffectiveCodeLayerState {
2255
2474
  hiddenIds: Set<string>;
2256
2475
  }
2257
2476
 
2477
+ interface SelectedLayerTarget {
2478
+ layerId: string;
2479
+ fileId: string;
2480
+ node: CodeLayerNode;
2481
+ tree: CodeLayerTreeNode[];
2482
+ elementInfo: ElementInfo;
2483
+ }
2484
+
2258
2485
  function collectEffectiveCodeLayerState(
2259
2486
  nodes: CodeLayerTreeNode[],
2260
2487
  lockedIds: Set<string>,
@@ -2316,6 +2543,7 @@ function elementInfoFromCodeLayerNode(node: CodeLayerNode): ElementInfo {
2316
2543
  ),
2317
2544
  boundingRect: { x: 0, y: 0, width: 0, height: 0 },
2318
2545
  textContent: node.textSnippet ?? undefined,
2546
+ childElementCount: node.children.length,
2319
2547
  isFlexChild: node.layout.parentDisplay?.includes("flex") ? true : false,
2320
2548
  isFlexContainer: node.layout.isFlexContainer,
2321
2549
  parentDisplay: node.layout.parentDisplay,
@@ -2444,6 +2672,7 @@ function canonicalElementInfoForCodeLayerNode(
2444
2672
  selector: preferredCodeLayerSelector(node),
2445
2673
  classes: node.classes,
2446
2674
  confidence: node.confidence,
2675
+ childElementCount: node.children.length,
2447
2676
  editCapabilities: info.editCapabilities?.some((capability) =>
2448
2677
  capability.kind.startsWith("deterministic"),
2449
2678
  )
@@ -2510,6 +2739,7 @@ export function refreshElementInfoFromContent(
2510
2739
  sourceInfo.classes,
2511
2740
  ),
2512
2741
  textContent: sourceInfo.textContent,
2742
+ childElementCount: sourceInfo.childElementCount,
2513
2743
  isFlexChild: sourceInfo.isFlexChild,
2514
2744
  isFlexContainer: sourceInfo.isFlexContainer,
2515
2745
  parentDisplay: sourceInfo.parentDisplay,
@@ -2530,6 +2760,7 @@ export function refreshElementInfoFromContent(
2530
2760
  classes,
2531
2761
  ),
2532
2762
  textContent: element.textContent?.slice(0, 200) ?? info.textContent,
2763
+ childElementCount: element.children.length,
2533
2764
  };
2534
2765
  } catch {
2535
2766
  return null;
@@ -2687,6 +2918,13 @@ function AgentNativeMenuMark({ className }: { className?: string }) {
2687
2918
 
2688
2919
  type DesignLeftPanel = "file" | "agent" | "assets" | "tools" | "tokens";
2689
2920
 
2921
+ const INITIAL_GENERATION_DISABLED_LEFT_PANELS = new Set<DesignLeftPanel>([
2922
+ "file",
2923
+ "assets",
2924
+ "tools",
2925
+ "tokens",
2926
+ ]);
2927
+
2690
2928
  function normalizeDesignLeftPanel(value: unknown): DesignLeftPanel | undefined {
2691
2929
  if (value === "extensions") return "tools";
2692
2930
  return value === "file" ||
@@ -2700,11 +2938,19 @@ function normalizeDesignLeftPanel(value: unknown): DesignLeftPanel | undefined {
2700
2938
 
2701
2939
  function DesignWorkspaceRail({
2702
2940
  activePanel,
2941
+ disabledPanels,
2942
+ motionOpen,
2943
+ motionDisabled,
2703
2944
  projectMenu,
2945
+ onMotionToggle,
2704
2946
  onPanelChange,
2705
2947
  }: {
2706
2948
  activePanel: DesignLeftPanel;
2949
+ disabledPanels?: ReadonlySet<DesignLeftPanel>;
2950
+ motionOpen?: boolean;
2951
+ motionDisabled?: boolean;
2707
2952
  projectMenu: ReactNode;
2953
+ onMotionToggle?: () => void;
2708
2954
  onPanelChange: (panel: DesignLeftPanel) => void;
2709
2955
  }) {
2710
2956
  const t = useT();
@@ -2752,16 +2998,27 @@ function DesignWorkspaceRail({
2752
2998
  <div className="flex min-h-0 flex-1 flex-col items-center gap-4">
2753
2999
  {items.map((item) => {
2754
3000
  const active = item.panel === activePanel;
3001
+ const disabled = disabledPanels?.has(item.panel) ?? false;
2755
3002
  return (
2756
3003
  <Tooltip key={item.panel}>
2757
3004
  <TooltipTrigger asChild>
2758
3005
  <button
2759
3006
  type="button"
2760
3007
  aria-label={item.label}
3008
+ aria-disabled={disabled || undefined}
2761
3009
  aria-current={active ? "page" : undefined}
2762
- 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
+ }}
2763
3018
  className={cn(
2764
- "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",
2765
3022
  active && "text-foreground",
2766
3023
  )}
2767
3024
  >
@@ -2771,6 +3028,8 @@ function DesignWorkspaceRail({
2771
3028
  active
2772
3029
  ? "bg-[var(--design-editor-selection-color)] text-[var(--design-editor-accent-color)]"
2773
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",
2774
3033
  )}
2775
3034
  >
2776
3035
  {item.icon}
@@ -2785,6 +3044,53 @@ function DesignWorkspaceRail({
2785
3044
  );
2786
3045
  })}
2787
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}
2788
3094
  </nav>
2789
3095
  );
2790
3096
  }
@@ -3598,6 +3904,25 @@ export default function DesignEditor() {
3598
3904
  const { id } = useParams<{ id: string }>();
3599
3905
  const navigate = useNavigate();
3600
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;
3601
3926
  const searchParams = useMemo(
3602
3927
  () => new URLSearchParams(location.search),
3603
3928
  [location.search],
@@ -3632,7 +3957,9 @@ export default function DesignEditor() {
3632
3957
  const [titleEditing, setTitleEditing] = useState(false);
3633
3958
  const [titleDraft, setTitleDraft] = useState("");
3634
3959
  const [screenZoom, setScreenZoom] = useState(FOCUSED_SCREEN_ZOOM);
3635
- const [overviewCanvasZoom, setOverviewCanvasZoom] = useState(100);
3960
+ const [explicitOverviewCanvasZoom, setExplicitOverviewCanvasZoom] = useState<
3961
+ number | null
3962
+ >(null);
3636
3963
  const [deviceFrame, setDeviceFrame] = useState<DeviceFrameType>("none");
3637
3964
  const [viewMode, setViewMode] = useState<"single" | "overview">("overview");
3638
3965
  const viewModeRef = useRef<"single" | "overview">("overview");
@@ -3660,6 +3987,8 @@ export default function DesignEditor() {
3660
3987
  useState<InspectorTab>("design");
3661
3988
  const [activeLeftPanel, setActiveLeftPanel] =
3662
3989
  useState<DesignLeftPanel>("file");
3990
+ const initialSearchCommandAppliedForIdRef = useRef<string | null>(null);
3991
+ const initialUrlSelectionHydratedForIdRef = useRef<string | null>(null);
3663
3992
  const [leftSidebarWidth, setLeftSidebarWidth] = useState(256);
3664
3993
  const [rightSidebarWidth, setRightSidebarWidth] = useState(256);
3665
3994
  const [layersSearchQuery, setLayersSearchQuery] = useState("");
@@ -3667,6 +3996,11 @@ export default function DesignEditor() {
3667
3996
  const [selectedLayerIdsState, setSelectedLayerIdsState] = useState<string[]>(
3668
3997
  [],
3669
3998
  );
3999
+ const selectedLayerTargetsRef = useRef<SelectedLayerTarget[]>([]);
4000
+ const effectiveCodeLayerStateRef = useRef<EffectiveCodeLayerState>({
4001
+ lockedIds: new Set(),
4002
+ hiddenIds: new Set(),
4003
+ });
3670
4004
  const [overviewSelectedScreenIds, setOverviewSelectedScreenIds] = useState<
3671
4005
  string[]
3672
4006
  >([]);
@@ -3817,10 +4151,22 @@ export default function DesignEditor() {
3817
4151
  motionDockUnmountTimerRef.current = window.setTimeout(() => {
3818
4152
  setMotionDockMounted(false);
3819
4153
  motionDockUnmountTimerRef.current = null;
3820
- }, MOTION_DOCK_TRANSITION_MS);
4154
+ }, MOTION_DOCK_EXIT_FALLBACK_MS);
3821
4155
  },
3822
4156
  [clearMotionDockUnmountTimer],
3823
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]);
3824
4170
  useEffect(
3825
4171
  () => () => clearMotionDockUnmountTimer(),
3826
4172
  [clearMotionDockUnmountTimer],
@@ -3973,22 +4319,50 @@ export default function DesignEditor() {
3973
4319
  const undoManagerRef = useRef<Y.UndoManager | null>(null);
3974
4320
  const contentUndoStackRef = useRef<ContentHistoryEntry[]>([]);
3975
4321
  const contentRedoStackRef = useRef<ContentHistoryEntry[]>([]);
4322
+ const localContentUndoStackRef = useRef<ContentHistoryChange[]>([]);
4323
+ const localContentRedoStackRef = useRef<ContentHistoryChange[]>([]);
4324
+ const activeFileIdForUndoRef = useRef<string | null>(null);
3976
4325
  const suppressContentHistoryRef = useRef(false);
3977
4326
  const geometryUndoStackRef = useRef<GeometryHistoryEntry[]>([]);
3978
4327
  const geometryRedoStackRef = useRef<GeometryHistoryEntry[]>([]);
3979
4328
  const historyOrderRef = useRef<UndoRedoOrderKind[]>([]);
3980
4329
  const redoOrderRef = useRef<UndoRedoOrderKind[]>([]);
4330
+ const clearRedoStacks = useCallback(() => {
4331
+ contentRedoStackRef.current = [];
4332
+ localContentRedoStackRef.current = [];
4333
+ geometryRedoStackRef.current = [];
4334
+ redoOrderRef.current = [];
4335
+ undoManagerRef.current?.clear(false, true);
4336
+ }, []);
3981
4337
  const syncUndoRedoState = useCallback(() => {
3982
4338
  const undoManager = undoManagerRef.current;
4339
+ const canUseOverviewHistory = viewModeRef.current === "overview";
4340
+ const activeHistoryFileId = activeFileIdForUndoRef.current;
4341
+ const hasLocalUndo =
4342
+ !canUseOverviewHistory &&
4343
+ findLastContentHistoryChangeIndex(
4344
+ localContentUndoStackRef.current,
4345
+ activeHistoryFileId,
4346
+ ) !== -1;
4347
+ const hasLocalRedo =
4348
+ !canUseOverviewHistory &&
4349
+ findLastContentHistoryChangeIndex(
4350
+ localContentRedoStackRef.current,
4351
+ activeHistoryFileId,
4352
+ ) !== -1;
3983
4353
  setCanUndo(
3984
4354
  Boolean(undoManager?.canUndo()) ||
3985
- contentUndoStackRef.current.length > 0 ||
3986
- geometryUndoStackRef.current.length > 0,
4355
+ hasLocalUndo ||
4356
+ (canUseOverviewHistory &&
4357
+ (contentUndoStackRef.current.length > 0 ||
4358
+ geometryUndoStackRef.current.length > 0)),
3987
4359
  );
3988
4360
  setCanRedo(
3989
4361
  Boolean(undoManager?.canRedo()) ||
3990
- contentRedoStackRef.current.length > 0 ||
3991
- geometryRedoStackRef.current.length > 0,
4362
+ hasLocalRedo ||
4363
+ (canUseOverviewHistory &&
4364
+ (contentRedoStackRef.current.length > 0 ||
4365
+ geometryRedoStackRef.current.length > 0)),
3992
4366
  );
3993
4367
  }, []);
3994
4368
  const recordContentHistoryEntry = useCallback(
@@ -3997,23 +4371,59 @@ export default function DesignEditor() {
3997
4371
  (change) => change.before !== change.after,
3998
4372
  );
3999
4373
  if (changes.length === 0) return;
4374
+ const activeHistoryFileId = activeFileIdForUndoRef.current;
4375
+ if (
4376
+ activeHistoryFileId &&
4377
+ changes.some((change) => change.fileId === activeHistoryFileId)
4378
+ ) {
4379
+ undoManagerRef.current?.clear(true, false);
4380
+ localContentUndoStackRef.current =
4381
+ localContentUndoStackRef.current.filter(
4382
+ (change) => change.fileId !== activeHistoryFileId,
4383
+ );
4384
+ localContentRedoStackRef.current =
4385
+ localContentRedoStackRef.current.filter(
4386
+ (change) => change.fileId !== activeHistoryFileId,
4387
+ );
4388
+ historyOrderRef.current = removeUndoRedoOrderKind(
4389
+ historyOrderRef.current,
4390
+ "content",
4391
+ );
4392
+ redoOrderRef.current = removeUndoRedoOrderKind(
4393
+ redoOrderRef.current,
4394
+ "content",
4395
+ );
4396
+ }
4000
4397
  contentUndoStackRef.current = [
4001
4398
  ...contentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
4002
4399
  changes.length === 1 ? changes[0] : { changes },
4003
4400
  ];
4004
- contentRedoStackRef.current = [];
4401
+ clearRedoStacks();
4005
4402
  historyOrderRef.current = [
4006
4403
  ...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
4007
4404
  "file-content",
4008
4405
  ];
4009
- redoOrderRef.current = [];
4010
4406
  syncUndoRedoState();
4011
4407
  },
4012
- [syncUndoRedoState],
4408
+ [clearRedoStacks, syncUndoRedoState],
4409
+ );
4410
+ const recordLocalContentHistoryEntry = useCallback(
4411
+ (change: ContentHistoryChange) => {
4412
+ if (change.before === change.after) return;
4413
+ localContentUndoStackRef.current = [
4414
+ ...localContentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
4415
+ change,
4416
+ ];
4417
+ clearRedoStacks();
4418
+ syncUndoRedoState();
4419
+ },
4420
+ [clearRedoStacks, syncUndoRedoState],
4013
4421
  );
4014
4422
  const clearLocalUndoRedoStacks = useCallback(() => {
4015
4423
  contentUndoStackRef.current = [];
4016
4424
  contentRedoStackRef.current = [];
4425
+ localContentUndoStackRef.current = [];
4426
+ localContentRedoStackRef.current = [];
4017
4427
  geometryUndoStackRef.current = [];
4018
4428
  geometryRedoStackRef.current = [];
4019
4429
  historyOrderRef.current = [];
@@ -4023,9 +4433,6 @@ export default function DesignEditor() {
4023
4433
  const designSelectionOwnerIdRef = useRef(`${TAB_ID}:${generateTabId()}`);
4024
4434
  const frameGeometrySaveTimerRef = useRef<number | null>(null);
4025
4435
  const [tweakSaveActive, setTweakSaveActive] = useState(false);
4026
- // Dismissible localhost-source banner (reset per session).
4027
- const [localSourceBannerDismissed, setLocalSourceBannerDismissed] =
4028
- useState(false);
4029
4436
  // Localhost write-consent dialog state. When the agent wants to write a local
4030
4437
  // file and no valid grant exists for the active connection, we show the dialog
4031
4438
  // with a pending payload; the user clicks "Allow writes" to mint a grant.
@@ -4218,7 +4625,9 @@ export default function DesignEditor() {
4218
4625
  onComplete: handleGenerationComplete,
4219
4626
  onStale: markGenerationStale,
4220
4627
  shouldAdoptRunningTab: () =>
4221
- Boolean(id) && !generationOutputReadyRef.current,
4628
+ Boolean(id) &&
4629
+ !generationOutputReadyRef.current &&
4630
+ hasFreshPendingGeneration(id),
4222
4631
  onAdoptRunningTab: (tabId) => {
4223
4632
  generationRunConfirmedRef.current = true;
4224
4633
  setGenerationChatTabId(tabId);
@@ -4271,8 +4680,9 @@ export default function DesignEditor() {
4271
4680
  pendingQuestions && pendingQuestions.length > 0,
4272
4681
  );
4273
4682
 
4274
- const { session } = useSession();
4683
+ const { session, isLoading: sessionLoading } = useSession();
4275
4684
  const isSignedIn = Boolean(session?.email);
4685
+ const sessionResolved = !sessionLoading;
4276
4686
 
4277
4687
  useEffect(() => {
4278
4688
  return () => clearGenerationCompleteTimer();
@@ -4310,12 +4720,11 @@ export default function DesignEditor() {
4310
4720
  : undefined,
4311
4721
  [session?.email, session?.name],
4312
4722
  );
4723
+ const signInToSaveHref = buildSignInHrefForDesignIntent("save");
4724
+ const signInToShareHref = buildSignInHrefForDesignIntent("share");
4313
4725
  const handleSignInToSave = useCallback(() => {
4314
4726
  window.location.href = buildSignInHrefForDesignIntent("save");
4315
4727
  }, []);
4316
- const handleSignInToShare = useCallback(() => {
4317
- window.location.href = buildSignInHrefForDesignIntent("share");
4318
- }, []);
4319
4728
 
4320
4729
  // Data fetching
4321
4730
  useEffect(() => {
@@ -4362,6 +4771,7 @@ export default function DesignEditor() {
4362
4771
  const canShareDesign =
4363
4772
  designAccessRole === "owner" || designAccessRole === "admin";
4364
4773
  const canEditDesign = canShareDesign || designAccessRole === "editor";
4774
+ const canRenderAuthenticatedShare = isSignedIn || canEditDesign;
4365
4775
  const canEditDesignRef = useRef(canEditDesign);
4366
4776
  const pendingLocalFileContentsRef = useRef<
4367
4777
  Map<
@@ -4909,6 +5319,24 @@ export default function DesignEditor() {
4909
5319
  return pending ? { ...file, content: pending.content } : file;
4910
5320
  });
4911
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]);
4912
5340
  const designDataJson = useMemo(
4913
5341
  () => parseDesignDataJson(design?.data),
4914
5342
  [design?.data],
@@ -5024,6 +5452,7 @@ export default function DesignEditor() {
5024
5452
  height: numberValue("height"),
5025
5453
  url: stringValue("url"),
5026
5454
  previewUrl: stringValue("previewUrl"),
5455
+ bridgeUrl: stringValue("bridgeUrl"),
5027
5456
  // Breakpoint preview widths (§6.4). When non-empty, MultiScreenCanvas
5028
5457
  // renders one iframe per width to the right of the primary frame.
5029
5458
  breakpointWidths: bpWidths,
@@ -5046,28 +5475,19 @@ export default function DesignEditor() {
5046
5475
  return typeof boardFile?.content === "string" ? boardFile.content : "";
5047
5476
  }, [boardFileId, files]);
5048
5477
 
5049
- // Logical canvas-space bounding box of the board iframe. The board covers the
5050
- // full canvas surface, so we compute the union of all screen frame geometries.
5051
- // 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.
5052
5481
  const boardFrameGeometry = useMemo((): FrameGeometry | undefined => {
5053
5482
  if (!boardFileId) return undefined;
5054
- const entries = Object.values(canvasFrameGeometryById);
5055
- if (entries.length === 0) return { x: 0, y: 0, width: 1920, height: 1080 };
5056
- let maxRight = 0;
5057
- let maxBottom = 0;
5058
- for (const geo of entries) {
5059
- const right = (geo.x ?? 0) + (geo.width ?? 0);
5060
- const bottom = (geo.y ?? 0) + (geo.height ?? 0);
5061
- if (right > maxRight) maxRight = right;
5062
- if (bottom > maxBottom) maxBottom = bottom;
5063
- }
5483
+ const origin = -BOARD_SURFACE_SIZE / 2;
5064
5484
  return {
5065
- x: 0,
5066
- y: 0,
5067
- width: Math.min(Math.max(maxRight, 1920), 16384),
5068
- height: Math.min(Math.max(maxBottom, 1080), 16384),
5485
+ x: origin,
5486
+ y: origin,
5487
+ width: BOARD_SURFACE_SIZE,
5488
+ height: BOARD_SURFACE_SIZE,
5069
5489
  };
5070
- }, [boardFileId, canvasFrameGeometryById]);
5490
+ }, [boardFileId]);
5071
5491
 
5072
5492
  const queueFrameGeometrySave = useCallback(
5073
5493
  (geometryById: CanvasFrameGeometryById) => {
@@ -5160,14 +5580,11 @@ export default function DesignEditor() {
5160
5580
  after: afterSnapshot,
5161
5581
  },
5162
5582
  ];
5163
- geometryRedoStackRef.current = [];
5164
- contentRedoStackRef.current = [];
5165
- undoManagerRef.current?.clear(false, true);
5583
+ clearRedoStacks();
5166
5584
  historyOrderRef.current = [
5167
5585
  ...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
5168
5586
  "geometry",
5169
5587
  ];
5170
- redoOrderRef.current = [];
5171
5588
  const resizedFrameIds = viewportChangedFrameIds(
5172
5589
  beforeSnapshot,
5173
5590
  afterSnapshot,
@@ -5180,7 +5597,7 @@ export default function DesignEditor() {
5180
5597
  );
5181
5598
  syncUndoRedoState();
5182
5599
  },
5183
- [syncUndoRedoState, writeFrameGeometrySnapshot],
5600
+ [clearRedoStacks, syncUndoRedoState, writeFrameGeometrySnapshot],
5184
5601
  );
5185
5602
 
5186
5603
  // §6.6 — "Make this a real app" handler.
@@ -5356,6 +5773,7 @@ export default function DesignEditor() {
5356
5773
  }, [files, activeFileId]);
5357
5774
 
5358
5775
  const activeFile = files.find((f) => f.id === activeFileId) ?? files[0];
5776
+ activeFileIdForUndoRef.current = activeFile?.id ?? null;
5359
5777
  const motionTimelineQueryParams =
5360
5778
  id && activeFile?.id
5361
5779
  ? { designId: id, sourceRef: activeFile.id }
@@ -5384,12 +5802,6 @@ export default function DesignEditor() {
5384
5802
  setReviewAuditLoading(false);
5385
5803
  }, [activeFile?.id, reviewFileId]);
5386
5804
 
5387
- const initialGenerationReadOnly = shouldLockInspectorForInitialGeneration({
5388
- fileCount: files.length,
5389
- generating,
5390
- pendingGenerationActive,
5391
- });
5392
-
5393
5805
  const selectedScreenIds = useMemo(
5394
5806
  () =>
5395
5807
  getSelectedScreenIdsForEditorState({
@@ -5408,6 +5820,10 @@ export default function DesignEditor() {
5408
5820
  : undefined,
5409
5821
  [activeOverviewScreenId, overviewScreens],
5410
5822
  );
5823
+ const activeScreenBridgeUrl = activeOverviewScreen?.bridgeUrl;
5824
+ const activeScreenExternalSnapshotHtml = activeFile?.id
5825
+ ? liveScreenSnapshotsById[activeFile.id]?.html
5826
+ : undefined;
5411
5827
  const activeOverviewSourceWidth =
5412
5828
  deviceFrame === "none"
5413
5829
  ? activeOverviewScreen?.width
@@ -5425,6 +5841,9 @@ export default function DesignEditor() {
5425
5841
  overviewZoomScaleRef.current = overviewZoomScale;
5426
5842
  }, [overviewZoomScale]);
5427
5843
 
5844
+ const overviewCanvasZoom =
5845
+ explicitOverviewCanvasZoom ??
5846
+ getDefaultOverviewCanvasZoom(overviewZoomScale);
5428
5847
  const overviewZoom = getOverviewDisplayZoom(
5429
5848
  overviewCanvasZoom,
5430
5849
  overviewZoomScale,
@@ -5433,10 +5852,12 @@ export default function DesignEditor() {
5433
5852
  const setZoomForView = useCallback(
5434
5853
  (targetView: "single" | "overview", update: SetStateAction<number>) => {
5435
5854
  if (targetView === "overview") {
5436
- setOverviewCanvasZoom((currentCanvasZoom) => {
5855
+ setExplicitOverviewCanvasZoom((currentCanvasZoom) => {
5437
5856
  const scale = overviewZoomScaleRef.current;
5857
+ const resolvedCanvasZoom =
5858
+ currentCanvasZoom ?? getDefaultOverviewCanvasZoom(scale);
5438
5859
  const currentDisplayZoom = getOverviewDisplayZoom(
5439
- currentCanvasZoom,
5860
+ resolvedCanvasZoom,
5440
5861
  scale,
5441
5862
  );
5442
5863
  const nextDisplayZoom = resolveZoomUpdate(update, currentDisplayZoom);
@@ -5463,6 +5884,7 @@ export default function DesignEditor() {
5463
5884
  const applyDesignEditorCommand = useCallback(
5464
5885
  (command: DesignEditorCommand | Record<string, unknown>) => {
5465
5886
  if (!id || command.designId !== id) return true;
5887
+ const commandRecord = command as Record<string, unknown>;
5466
5888
  const editorView =
5467
5889
  command.editorView === "overview" || command.editorView === "single"
5468
5890
  ? command.editorView
@@ -5479,6 +5901,14 @@ export default function DesignEditor() {
5479
5901
  : typeof command.screen === "string"
5480
5902
  ? command.screen
5481
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;
5482
5912
  const targetFile = findDesignFileByScreenTarget(files, target);
5483
5913
  // A navigate command can name a screen the agent just created that the
5484
5914
  // get-design query hasn't refetched yet. Treat any unresolved named target
@@ -5526,6 +5956,9 @@ export default function DesignEditor() {
5526
5956
  if (targetFile) {
5527
5957
  setActiveFileId(targetFile.id);
5528
5958
  }
5959
+ if (selectionId) {
5960
+ setSelectedLayerIdsState([selectionId]);
5961
+ }
5529
5962
 
5530
5963
  const commandZoom =
5531
5964
  typeof command.zoom === "number" && Number.isFinite(command.zoom)
@@ -5537,12 +5970,12 @@ export default function DesignEditor() {
5537
5970
 
5538
5971
  if (editorView === "overview") {
5539
5972
  viewModeRef.current = "overview";
5540
- setSelectedElement(null);
5973
+ if (!selectionId) setSelectedElement(null);
5541
5974
  applyCommandTool("move");
5542
5975
  setViewMode("overview");
5543
5976
  } else if (editorView === "single") {
5544
5977
  viewModeRef.current = "single";
5545
- setSelectedElement(null);
5978
+ if (!selectionId) setSelectedElement(null);
5546
5979
  applyCommandTool("move");
5547
5980
  if (commandZoom === null) {
5548
5981
  setScreenZoom(FOCUSED_SCREEN_ZOOM);
@@ -5559,10 +5992,20 @@ export default function DesignEditor() {
5559
5992
 
5560
5993
  useEffect(() => {
5561
5994
  if (!id) return;
5562
- const command = designEditorCommandFromSearchParams(id, searchParams);
5563
- if (!command) return;
5564
- applyDesignEditorCommand(command);
5565
- }, [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]);
5566
6009
 
5567
6010
  useEffect(() => {
5568
6011
  if (!id) return;
@@ -5862,6 +6305,7 @@ export default function DesignEditor() {
5862
6305
  const [collabContentFileId, setCollabContentFileId] = useState<string | null>(
5863
6306
  null,
5864
6307
  );
6308
+ const previousDesignIdForHistoryRef = useRef<string | null>(null);
5865
6309
  const prevActiveFileIdRef = useRef<string | null>(null);
5866
6310
  // `updatedAt` of the DB content this preview currently reflects. A poll that
5867
6311
  // returns an older-or-equal value is a stale snapshot and is ignored; a newer
@@ -5907,7 +6351,16 @@ export default function DesignEditor() {
5907
6351
  };
5908
6352
  }, [awareness, ydoc]);
5909
6353
 
5910
- // Reset per-file reconcile state when switching files
6354
+ useEffect(() => {
6355
+ if (previousDesignIdForHistoryRef.current === id) return;
6356
+ previousDesignIdForHistoryRef.current = id ?? null;
6357
+ clearLocalUndoRedoStacks();
6358
+ syncUndoRedoState();
6359
+ }, [clearLocalUndoRedoStacks, id, syncUndoRedoState]);
6360
+
6361
+ // Reset per-file reconcile state when switching files.
6362
+ // Keep undo/redo content + geometry stacks intact: overview mode needs one
6363
+ // chronological history across all screens, board edits, and frame geometry.
5911
6364
  useEffect(() => {
5912
6365
  if (viewMode === "overview") {
5913
6366
  prevActiveFileIdRef.current = activeFileId;
@@ -5916,9 +6369,7 @@ export default function DesignEditor() {
5916
6369
  lastAppliedFileUpdatedAtRef.current = null;
5917
6370
  lastLocalContentRef.current = null;
5918
6371
  latestActiveContentRef.current = null;
5919
- clearLocalUndoRedoStacks();
5920
6372
  clearStaleAgentCollabRecovery();
5921
- syncUndoRedoState();
5922
6373
  return;
5923
6374
  }
5924
6375
  if (activeFileId !== prevActiveFileIdRef.current) {
@@ -5928,17 +6379,9 @@ export default function DesignEditor() {
5928
6379
  lastAppliedFileUpdatedAtRef.current = null;
5929
6380
  lastLocalContentRef.current = null;
5930
6381
  latestActiveContentRef.current = null;
5931
- clearLocalUndoRedoStacks();
5932
6382
  clearStaleAgentCollabRecovery();
5933
- syncUndoRedoState();
5934
6383
  }
5935
- }, [
5936
- activeFileId,
5937
- clearLocalUndoRedoStacks,
5938
- clearStaleAgentCollabRecovery,
5939
- syncUndoRedoState,
5940
- viewMode,
5941
- ]);
6384
+ }, [activeFileId, clearStaleAgentCollabRecovery, viewMode]);
5942
6385
 
5943
6386
  useEffect(() => {
5944
6387
  return clearStaleAgentCollabRecovery;
@@ -6102,12 +6545,19 @@ export default function DesignEditor() {
6102
6545
  });
6103
6546
 
6104
6547
  const syncState = () => syncUndoRedoState();
6105
- const handleStackItemAdded = () => {
6548
+ const handleStackItemAdded = (event: {
6549
+ origin?: unknown;
6550
+ type?: "undo" | "redo";
6551
+ }) => {
6552
+ if (event.origin !== LOCAL_EDIT_ORIGIN || event.type !== "undo") {
6553
+ syncUndoRedoState();
6554
+ return;
6555
+ }
6106
6556
  historyOrderRef.current = [
6107
6557
  ...historyOrderRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
6108
6558
  "content",
6109
6559
  ];
6110
- redoOrderRef.current = [];
6560
+ clearRedoStacks();
6111
6561
  syncUndoRedoState();
6112
6562
  };
6113
6563
  um.on("stack-item-added", handleStackItemAdded);
@@ -6135,7 +6585,7 @@ export default function DesignEditor() {
6135
6585
  );
6136
6586
  syncUndoRedoState();
6137
6587
  };
6138
- }, [ydoc, isSynced, syncUndoRedoState]);
6588
+ }, [clearRedoStacks, ydoc, isSynced, syncUndoRedoState]);
6139
6589
 
6140
6590
  // Reconcile authoritative external DB content (agent edit / peer-via-SQL) into
6141
6591
  // the live preview. This is the robustness fallback the Yjs observe path can't
@@ -6485,9 +6935,20 @@ export default function DesignEditor() {
6485
6935
  : (activeFile?.content ?? ""));
6486
6936
  const activeContent =
6487
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
+ });
6488
6945
  useLayoutEffect(() => {
6489
6946
  latestActiveContentRef.current = activeContent;
6490
6947
  }, [activeContent]);
6948
+ useEffect(() => {
6949
+ if (!initialGenerationChromeLimited || activeLeftPanel === "agent") return;
6950
+ setActiveLeftPanel("agent");
6951
+ }, [activeLeftPanel, initialGenerationChromeLimited]);
6491
6952
  const fileContentById = useMemo(() => {
6492
6953
  const map = new Map<string, string>();
6493
6954
  for (const file of files) {
@@ -6500,22 +6961,58 @@ export default function DesignEditor() {
6500
6961
  getFreshScreenContent({
6501
6962
  screenId,
6502
6963
  activeFileId: activeFile?.id,
6503
- freshActiveContent: getFreshActiveFileContent({
6504
- activeContent,
6505
- latestContent: latestActiveContentRef.current,
6506
- lastLocalContent: lastLocalContentRef.current,
6507
- }),
6964
+ freshActiveContentFileId: activeFile?.id,
6965
+ freshActiveContent: activeContent,
6508
6966
  fileContentById,
6509
6967
  }),
6510
6968
  [activeContent, activeFile?.id, fileContentById],
6511
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;
6512
7009
  const pageStyles = useMemo(
6513
7010
  () => getBodyInlineStyles(activeContent),
6514
7011
  [activeContent],
6515
7012
  );
6516
7013
  const activeCodeLayerProjection = useMemo(
6517
- () => buildCodeLayerProjection(activeContent),
6518
- [activeContent],
7014
+ () => buildCodeLayerProjection(activeProjectionContent),
7015
+ [activeProjectionContent],
6519
7016
  );
6520
7017
  const activeMotionTimeline = motionTimelineResult?.timelines?.[0] ?? null;
6521
7018
  const activeMotionHydrationFingerprint = activeFile?.id
@@ -6621,9 +7118,13 @@ export default function DesignEditor() {
6621
7118
  const designSourceType = useMemo(
6622
7119
  () =>
6623
7120
  normalizeDesignSourceType(designDataJson.sourceType as unknown) ??
7121
+ normalizeDesignSourceType(designDataJson.sourceMode as unknown) ??
6624
7122
  "inline",
6625
- [designDataJson.sourceType],
7123
+ [designDataJson.sourceMode, designDataJson.sourceType],
6626
7124
  );
7125
+ const activeCanvasSourceType =
7126
+ normalizeDesignSourceType(activeOverviewScreen?.sourceType) ??
7127
+ designSourceType;
6627
7128
  const sourceCapabilities = useMemo(() => {
6628
7129
  const caps = resolveSourceCapabilities(designSourceType);
6629
7130
  return DESIGN_CAPABILITY_NAMES.filter((name) => hasCapability(caps, name));
@@ -6666,6 +7167,11 @@ export default function DesignEditor() {
6666
7167
  ? bridgeSourceIdForCodeLayerNode(selectedCodeLayerNode)
6667
7168
  : undefined;
6668
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]);
6669
7175
 
6670
7176
  useEffect(() => {
6671
7177
  setShaderFillPreview(null);
@@ -6686,11 +7192,8 @@ export default function DesignEditor() {
6686
7192
  // Outer HTML of the selection — backs the inline/Alpine "Inspect code" view.
6687
7193
  const selectedElementOuterHtml = useMemo(() => {
6688
7194
  if (!selectedElement?.selector) return null;
6689
- return (
6690
- selectedElement.htmlContent ??
6691
- getElementOuterHtml(activeContent, selectedElement.selector)
6692
- );
6693
- }, [activeContent, selectedElement?.selector, selectedElement?.htmlContent]);
7195
+ return getElementOuterHtml(activeContent, selectedElement.selector);
7196
+ }, [activeContent, selectedElement?.selector]);
6694
7197
 
6695
7198
  // §6.3 — the motion-dock target: the selected element's literal
6696
7199
  // `data-agent-native-node-id` (the value the motion compiler + preview bridge
@@ -6757,7 +7260,13 @@ export default function DesignEditor() {
6757
7260
  // resolveNodeToFile bridge op, which is wired through open-component-source
6758
7261
  // when an external file path is available; until that round-trip is hooked
6759
7262
  // up here the popover shows the projected HTML for all source types.
6760
- 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
+ };
6761
7270
  }, [selectedElement, selectedElementOuterHtml]);
6762
7271
 
6763
7272
  const handleCreateComponent = useCallback(
@@ -6783,7 +7292,7 @@ export default function DesignEditor() {
6783
7292
  // Follow-up: ask the Design agent to extract props and replace repeated
6784
7293
  // instances with this component. The deterministic annotate above is the
6785
7294
  // core; this is an enhancement that runs in the agent chat.
6786
- sendToAgentChat({
7295
+ sendToDesignAgentChat({
6787
7296
  message: `Extract props for the "${name}" component and replace repeated instances on this design with it.`,
6788
7297
  context: [
6789
7298
  `Design id: "${id}".`,
@@ -6838,18 +7347,22 @@ export default function DesignEditor() {
6838
7347
  (screenId: string) => {
6839
7348
  if (screenId === activeFile?.id) return activeCodeLayerProjection;
6840
7349
  if (!fileContentById.has(screenId)) return null;
6841
- return buildCodeLayerProjection(getScreenContent(screenId));
7350
+ return buildCodeLayerProjection(getProjectionContentForScreen(screenId));
6842
7351
  },
6843
7352
  [
6844
7353
  activeCodeLayerProjection,
6845
7354
  activeFile?.id,
6846
7355
  fileContentById,
6847
- getScreenContent,
7356
+ getProjectionContentForScreen,
6848
7357
  ],
6849
7358
  );
6850
7359
 
6851
7360
  const replacePreviewContent = useCallback(
6852
- (nextContent: string, selector?: string | null) => {
7361
+ (
7362
+ nextContent: string,
7363
+ selector?: string | null,
7364
+ options: { forceFullDocument?: boolean } = {},
7365
+ ) => {
6853
7366
  const replaceContent = (window as any).__designCanvasReplaceContent;
6854
7367
  if (typeof replaceContent !== "function") return false;
6855
7368
  return Boolean(
@@ -6857,7 +7370,9 @@ export default function DesignEditor() {
6857
7370
  nextContent,
6858
7371
  selector ?? selectedCanvasSelector,
6859
7372
  selectedCanvasSelectorCandidates,
6860
- selectedElement?.sourceId ?? selectedElement?.id,
7373
+ {
7374
+ forceFullDocument: options.forceFullDocument === true,
7375
+ },
6861
7376
  ),
6862
7377
  );
6863
7378
  },
@@ -6884,9 +7399,11 @@ export default function DesignEditor() {
6884
7399
  options: {
6885
7400
  refreshPreview?: boolean;
6886
7401
  skipPreview?: boolean;
7402
+ forcePreviewFullDocument?: boolean;
6887
7403
  immediateSave?: boolean;
6888
7404
  persist?: boolean;
6889
7405
  recordHistory?: boolean;
7406
+ historyBeforeContent?: string;
6890
7407
  updatedAt?: string;
6891
7408
  } = {},
6892
7409
  ) => {
@@ -6894,12 +7411,18 @@ export default function DesignEditor() {
6894
7411
  const shouldRecordHistory =
6895
7412
  options.recordHistory !== false && !options.updatedAt;
6896
7413
  const previousContent =
6897
- collabContentFileIdRef.current === activeFile.id &&
6898
- typeof collabContentRef.current === "string"
6899
- ? collabContentRef.current
6900
- : (activeFile.content ?? "");
7414
+ typeof options.historyBeforeContent === "string"
7415
+ ? options.historyBeforeContent
7416
+ : collabContentFileIdRef.current === activeFile.id &&
7417
+ typeof collabContentRef.current === "string"
7418
+ ? collabContentRef.current
7419
+ : (activeFile.content ?? "");
6901
7420
  const yjsHistoryAvailable = Boolean(
6902
- shouldRecordHistory && ydoc && isSynced && undoManagerRef.current,
7421
+ shouldRecordHistory &&
7422
+ viewModeRef.current !== "overview" &&
7423
+ ydoc &&
7424
+ isSynced &&
7425
+ undoManagerRef.current,
6903
7426
  );
6904
7427
  if (
6905
7428
  !suppressContentHistoryRef.current &&
@@ -6907,11 +7430,16 @@ export default function DesignEditor() {
6907
7430
  !yjsHistoryAvailable &&
6908
7431
  previousContent !== nextContent
6909
7432
  ) {
6910
- recordContentHistoryEntry({
7433
+ const change = {
6911
7434
  fileId: activeFile.id,
6912
7435
  before: previousContent,
6913
7436
  after: nextContent,
6914
- });
7437
+ };
7438
+ if (viewModeRef.current === "overview") {
7439
+ recordContentHistoryEntry(change);
7440
+ } else {
7441
+ recordLocalContentHistoryEntry(change);
7442
+ }
6915
7443
  }
6916
7444
  if (options.updatedAt) {
6917
7445
  clearPendingLocalFileContent(activeFile.id);
@@ -6960,8 +7488,18 @@ export default function DesignEditor() {
6960
7488
  ? true
6961
7489
  : forceRefresh
6962
7490
  ? false
6963
- : replacePreviewContent(nextContent);
6964
- 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
+ ) {
6965
7503
  setContentRenderRevision((revision) => revision + 1);
6966
7504
  }
6967
7505
  if (ydoc && isSynced) {
@@ -6972,7 +7510,7 @@ export default function DesignEditor() {
6972
7510
  ytext.delete(0, ytext.length);
6973
7511
  ytext.insert(0, nextContent);
6974
7512
  },
6975
- shouldRecordHistory ? LOCAL_EDIT_ORIGIN : TAB_ID,
7513
+ yjsHistoryAvailable ? LOCAL_EDIT_ORIGIN : TAB_ID,
6976
7514
  );
6977
7515
  }
6978
7516
  }
@@ -6996,6 +7534,7 @@ export default function DesignEditor() {
6996
7534
  queueFileContentSave,
6997
7535
  replacePreviewContent,
6998
7536
  recordContentHistoryEntry,
7537
+ recordLocalContentHistoryEntry,
6999
7538
  syncUndoRedoState,
7000
7539
  ydoc,
7001
7540
  ],
@@ -7008,6 +7547,7 @@ export default function DesignEditor() {
7008
7547
  options: {
7009
7548
  refreshPreview?: boolean;
7010
7549
  skipPreview?: boolean;
7550
+ forcePreviewFullDocument?: boolean;
7011
7551
  persist?: boolean;
7012
7552
  recordHistory?: boolean;
7013
7553
  updatedAt?: string;
@@ -7107,27 +7647,47 @@ export default function DesignEditor() {
7107
7647
  motionAutosaveTimerRef.current = window.setTimeout(() => {
7108
7648
  motionAutosaveTimerRef.current = null;
7109
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
+ );
7110
7674
  applyMotionEdit(
7111
7675
  {
7112
7676
  designId: id,
7113
7677
  fileId: activeFile.id,
7114
7678
  timelineId: motionTimelineId ?? undefined,
7115
7679
  sourceRef: activeFile.id,
7116
- tracks: motionTracks.map(({ label: _label, ...track }) => track),
7680
+ tracks: tracksForSave,
7117
7681
  durationMs: motionDurationMs,
7118
- currentContent: getFreshActiveFileContent({
7119
- activeContent,
7120
- latestContent: latestActiveContentRef.current,
7121
- lastLocalContent: lastLocalContentRef.current,
7122
- }),
7123
- includeContent: true,
7682
+ currentContent,
7683
+ includeContent: false,
7124
7684
  },
7125
7685
  {
7126
7686
  onSuccess: (result) => {
7127
7687
  const response = result as {
7128
7688
  fileId?: unknown;
7129
7689
  timelineId?: unknown;
7130
- patchedContent?: unknown;
7690
+ updatedAt?: unknown;
7131
7691
  };
7132
7692
  if (typeof response.timelineId === "string") {
7133
7693
  setMotionTimelineId(response.timelineId);
@@ -7139,10 +7699,14 @@ export default function DesignEditor() {
7139
7699
  }
7140
7700
  if (
7141
7701
  typeof response.fileId === "string" &&
7142
- typeof response.patchedContent === "string"
7702
+ response.fileId === activeFile.id
7143
7703
  ) {
7144
- applyFileContentUpdate(response.fileId, response.patchedContent, {
7145
- 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
+ : {}),
7146
7710
  });
7147
7711
  }
7148
7712
  void queryClient.invalidateQueries({
@@ -7273,7 +7837,9 @@ export default function DesignEditor() {
7273
7837
  : storedContent;
7274
7838
  })()
7275
7839
  : storedContent);
7276
- const nextContent = appendCanvasPrimitiveToHtml(baseContent, primitive);
7840
+ const nextContent = appendCanvasPrimitiveToHtml(baseContent, primitive, {
7841
+ preserveNegativePosition: targetFile.id === boardFileId,
7842
+ });
7277
7843
  if (!nextContent) {
7278
7844
  toast.error(t("designEditor.toasts.primitiveInsertFailed"));
7279
7845
  return false;
@@ -7287,7 +7853,11 @@ export default function DesignEditor() {
7287
7853
  : null;
7288
7854
 
7289
7855
  if (targetFile.id === activeFile?.id) {
7290
- applyLocalContentUpdate(nextContent, { immediateSave: true });
7856
+ applyLocalContentUpdate(nextContent, {
7857
+ forcePreviewFullDocument: true,
7858
+ historyBeforeContent: baseContent,
7859
+ immediateSave: true,
7860
+ });
7291
7861
  } else {
7292
7862
  recordContentHistoryEntry({
7293
7863
  fileId: targetFile.id,
@@ -7335,6 +7905,7 @@ export default function DesignEditor() {
7335
7905
  activeContent,
7336
7906
  activeFile?.id,
7337
7907
  applyLocalContentUpdate,
7908
+ boardFileId,
7338
7909
  canEditDesign,
7339
7910
  files,
7340
7911
  id,
@@ -7348,7 +7919,7 @@ export default function DesignEditor() {
7348
7919
  );
7349
7920
 
7350
7921
  const handlePrimitiveCreated = useCallback(
7351
- (screenId: string, nodeId: string) => {
7922
+ (screenId: string, nodeId: string, options?: { selectFrame?: boolean }) => {
7352
7923
  // B2/B4 fix: stay in overview mode after drawing a primitive. The user
7353
7924
  // drew a shape on the board — they should remain on the board with the
7354
7925
  // new primitive selected, matching Figma behaviour. We activate the
@@ -7363,7 +7934,9 @@ export default function DesignEditor() {
7363
7934
  setSelectedElement(null);
7364
7935
  setHoveredElement(null);
7365
7936
  setSelectedLayerIdsState([nodeId]);
7366
- setOverviewSelectedScreenIds([screenId]);
7937
+ setOverviewSelectedScreenIds(
7938
+ options?.selectFrame === false ? [] : [screenId],
7939
+ );
7367
7940
  setActiveTool("move");
7368
7941
  setMode("edit");
7369
7942
  });
@@ -7380,7 +7953,7 @@ export default function DesignEditor() {
7380
7953
  scheduleBeginTextEditForScreen(screenId, textNodeId);
7381
7954
  }
7382
7955
  },
7383
- [clearPendingOverviewLayerSelectionTimer],
7956
+ [clearPendingOverviewLayerSelectionTimer, scheduleBeginTextEditForScreen],
7384
7957
  );
7385
7958
 
7386
7959
  /**
@@ -7394,22 +7967,14 @@ export default function DesignEditor() {
7394
7967
  if (!boardFileId || !canEditDesign) return false;
7395
7968
  const result = handleCreatePrimitive(boardFileId, primitive);
7396
7969
  if (!result) return false;
7397
-
7398
- // For TEXT primitives drawn on the board surface, immediately enter
7399
- // text-editing mode via begin-text-edit. The target is the board file,
7400
- // so use the same iframe-targeted retry path as screen primitives.
7401
- if (primitive.kind === "text") {
7402
- const textNodeId =
7403
- pendingTextEditNodeIdRef.current ?? primitive.nodeId ?? null;
7404
- pendingTextEditNodeIdRef.current = null;
7405
- if (textNodeId) {
7406
- scheduleBeginTextEditForScreen(boardFileId, textNodeId);
7407
- }
7970
+ const nodeId = typeof result === "string" ? result : primitive.nodeId;
7971
+ if (nodeId) {
7972
+ handlePrimitiveCreated(boardFileId, nodeId, { selectFrame: false });
7408
7973
  }
7409
7974
 
7410
7975
  return result;
7411
7976
  },
7412
- [boardFileId, canEditDesign, handleCreatePrimitive],
7977
+ [boardFileId, canEditDesign, handleCreatePrimitive, handlePrimitiveCreated],
7413
7978
  );
7414
7979
 
7415
7980
  const handleOverviewScreenSelectionChange = useCallback(
@@ -7447,6 +8012,7 @@ export default function DesignEditor() {
7447
8012
 
7448
8013
  const handleMoveTool = useCallback(() => {
7449
8014
  if (!canEditDesign) return;
8015
+ blurActiveDesignEditableTarget();
7450
8016
  setActiveTool("move");
7451
8017
  setMode("edit");
7452
8018
  setDrawMode(false);
@@ -7455,6 +8021,7 @@ export default function DesignEditor() {
7455
8021
 
7456
8022
  const handleFrameTool = useCallback(() => {
7457
8023
  if (!canEditDesign) return;
8024
+ blurActiveDesignEditableTarget();
7458
8025
  flushSync(() => {
7459
8026
  setActiveTool("frame");
7460
8027
  setMode("edit");
@@ -7468,6 +8035,7 @@ export default function DesignEditor() {
7468
8035
 
7469
8036
  const handleTextTool = useCallback(() => {
7470
8037
  if (!canEditDesign) return;
8038
+ blurActiveDesignEditableTarget();
7471
8039
  flushSync(() => {
7472
8040
  setActiveTool("text");
7473
8041
  viewModeRef.current = "overview";
@@ -7482,6 +8050,7 @@ export default function DesignEditor() {
7482
8050
  const handleShapeTool = useCallback(
7483
8051
  (tool: ShapeTool) => {
7484
8052
  if (!canEditDesign) return;
8053
+ blurActiveDesignEditableTarget();
7485
8054
  flushSync(() => {
7486
8055
  setActiveTool(tool);
7487
8056
  viewModeRef.current = "overview";
@@ -7501,6 +8070,7 @@ export default function DesignEditor() {
7501
8070
 
7502
8071
  const handlePenTool = useCallback(() => {
7503
8072
  if (!canEditDesign) return;
8073
+ blurActiveDesignEditableTarget();
7504
8074
  flushSync(() => {
7505
8075
  setActiveTool("pen");
7506
8076
  viewModeRef.current = "overview";
@@ -7514,6 +8084,7 @@ export default function DesignEditor() {
7514
8084
 
7515
8085
  const handleHandTool = useCallback(() => {
7516
8086
  if (!canEditDesign) return;
8087
+ blurActiveDesignEditableTarget();
7517
8088
  setActiveTool("hand");
7518
8089
  setMode("edit");
7519
8090
  setDrawMode(false);
@@ -7524,6 +8095,7 @@ export default function DesignEditor() {
7524
8095
 
7525
8096
  const handleScaleTool = useCallback(() => {
7526
8097
  if (!activeFile || !canEditDesign) return;
8098
+ blurActiveDesignEditableTarget();
7527
8099
  setActiveTool("scale");
7528
8100
  setMode("edit");
7529
8101
  setDrawMode(false);
@@ -7630,7 +8202,7 @@ export default function DesignEditor() {
7630
8202
  "For tiny source changes, prefer `edit-design`, but make sure the tweak definitions are saved so the Tweaks panel updates.",
7631
8203
  ].join("\n");
7632
8204
 
7633
- sendToAgentChat({
8205
+ sendToDesignAgentChat({
7634
8206
  message: `Add tweak controls to "${design.title}": ${trimmed}`,
7635
8207
  context,
7636
8208
  submit: true,
@@ -7894,7 +8466,7 @@ export default function DesignEditor() {
7894
8466
  );
7895
8467
 
7896
8468
  const handleScreenElementSelect = useCallback(
7897
- (screenId: string, info: ElementInfo) => {
8469
+ (screenId: string, info: ElementInfo, intent?: ElementSelectionIntent) => {
7898
8470
  const pendingLayerId = pendingOverviewLayerSelectionRef.current;
7899
8471
  const pendingScreenId = pendingOverviewScreenSelectionRef.current;
7900
8472
  const createdSelection = createdOverviewLayerSelection;
@@ -7927,11 +8499,33 @@ export default function DesignEditor() {
7927
8499
  ) {
7928
8500
  return;
7929
8501
  }
8502
+ const additiveSelection = Boolean(
8503
+ node &&
8504
+ (intent?.additive ||
8505
+ intent?.range ||
8506
+ intent?.shiftKey ||
8507
+ intent?.metaKey ||
8508
+ intent?.ctrlKey),
8509
+ );
7930
8510
  setActiveFileId(screenId);
7931
8511
  setSelectedElement(canonical);
7932
8512
  setHoveredElement(null);
7933
8513
  setHoveredElementScreenId(null);
7934
- 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
+ }
7935
8529
  if (viewModeRef.current === "overview") {
7936
8530
  setOverviewSelectedScreenIds([]);
7937
8531
  }
@@ -7984,10 +8578,10 @@ export default function DesignEditor() {
7984
8578
  );
7985
8579
 
7986
8580
  const handleElementSelect = useCallback(
7987
- (info: ElementInfo) => {
8581
+ (info: ElementInfo, intent?: ElementSelectionIntent) => {
7988
8582
  const screenId = activeFile?.id ?? activeFileId;
7989
8583
  if (screenId) {
7990
- handleScreenElementSelect(screenId, info);
8584
+ handleScreenElementSelect(screenId, info, intent);
7991
8585
  return;
7992
8586
  }
7993
8587
  setSelectedElement(
@@ -8172,7 +8766,11 @@ export default function DesignEditor() {
8172
8766
  // advance lastLocalContentRef.current to resolvedNextContent below, the
8173
8767
  // next synchronous call reads the previous call's result and the patches
8174
8768
  // compose. Falls back to activeContent when the ref is unset (file switch).
8769
+ const activeLiveSnapshot = activeFile
8770
+ ? liveScreenSnapshotsById[activeFile.id]
8771
+ : undefined;
8175
8772
  const baseContent =
8773
+ activeLiveSnapshot?.html ??
8176
8774
  latestActiveContentRef.current ??
8177
8775
  lastLocalContentRef.current ??
8178
8776
  activeContent;
@@ -8369,37 +8967,78 @@ export default function DesignEditor() {
8369
8967
  );
8370
8968
  })
8371
8969
  : null;
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
+ }
9002
+ }
8372
9003
 
8373
- setCollabContent(resolvedNextContent);
8374
- setCollabContentFileId(activeFile.id);
8375
- setPatchProof((prev) =>
8376
- prev?.id === proofId ? { ...prev, status: "queued" } : prev,
8377
- );
8378
- // Mark as our own write so the get-design reconcile + Yjs observe don't
8379
- // treat the echo as an external edit and fight the live value.
8380
- lastLocalContentRef.current = resolvedNextContent;
8381
- latestActiveContentRef.current = resolvedNextContent;
8382
- // Write the edit into the shared Y.Doc so other open clients see it live
8383
- // through Yjs (not only via the slower update-file → applyText round-trip).
8384
- // Use LOCAL_EDIT_ORIGIN so the UndoManager captures this transaction.
8385
- if (ydoc && isSynced) {
8386
- const ytext = ydoc.getText("content");
8387
- if (ytext.toString() !== resolvedNextContent) {
8388
- ydoc.transact(() => {
8389
- ytext.delete(0, ytext.length);
8390
- ytext.insert(0, resolvedNextContent);
8391
- }, LOCAL_EDIT_ORIGIN);
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);
8392
9040
  }
8393
9041
  }
8394
- queueFileContentSave(activeFile.id, resolvedNextContent, {
8395
- syncCollab: !(ydoc && isSynced),
8396
- });
8397
- if (
8398
- !runtimeStyleApplied &&
8399
- !replacePreviewContent(resolvedNextContent, selector)
8400
- ) {
8401
- setContentRenderRevision((revision) => revision + 1);
8402
- }
8403
9042
  if (resolvedNode) setSelectedLayerIdsState([resolvedNode.id]);
8404
9043
  setSelectedElement((prev) => {
8405
9044
  if (options.elementInfo) return options.elementInfo;
@@ -8426,15 +9065,128 @@ export default function DesignEditor() {
8426
9065
  activeFile,
8427
9066
  activeBreakpointWidthState,
8428
9067
  canEditDesign,
9068
+ liveScreenSnapshotsById,
8429
9069
  queueFileContentSave,
9070
+ recordContentHistoryEntry,
9071
+ recordLocalContentHistoryEntry,
8430
9072
  replacePreviewContent,
8431
9073
  selectedElement,
8432
9074
  t,
9075
+ updateLiveScreenSnapshotContent,
8433
9076
  ydoc,
8434
9077
  isSynced,
8435
9078
  ],
8436
9079
  );
8437
9080
 
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
+
8438
9190
  const handleStyleChange = useCallback(
8439
9191
  (property: string, value: string) => {
8440
9192
  const selector = selectedElement?.selector ?? "body";
@@ -8452,9 +9204,11 @@ export default function DesignEditor() {
8452
9204
  return;
8453
9205
  }
8454
9206
  }
9207
+ if (commitStylesToSelectedLayers({ [property]: value })) return;
8455
9208
  commitVisualStyles(selector, { [property]: value });
8456
9209
  },
8457
9210
  [
9211
+ commitStylesToSelectedLayers,
8458
9212
  commitVisualStyles,
8459
9213
  selectedElement?.selector,
8460
9214
  selectedElement?.sourceId,
@@ -8472,9 +9226,14 @@ export default function DesignEditor() {
8472
9226
  Boolean(value),
8473
9227
  );
8474
9228
  if (entries.length === 0) return;
9229
+ if (commitStylesToSelectedLayers(Object.fromEntries(entries))) return;
8475
9230
  commitVisualStyles(selector, Object.fromEntries(entries));
8476
9231
  },
8477
- [commitVisualStyles, selectedElement?.selector],
9232
+ [
9233
+ commitStylesToSelectedLayers,
9234
+ commitVisualStyles,
9235
+ selectedElement?.selector,
9236
+ ],
8478
9237
  );
8479
9238
 
8480
9239
  const getFreshActiveContent = useCallback(
@@ -8551,14 +9310,38 @@ export default function DesignEditor() {
8551
9310
  );
8552
9311
  return false;
8553
9312
  }
8554
- const movedNode =
9313
+ const movedNodeAttrId =
9314
+ targetNode?.dataAttributes["data-agent-native-node-id"] ??
9315
+ details?.sourceId ??
9316
+ elementInfo?.sourceId ??
8555
9317
  (patch.result.after?.nodeId
8556
9318
  ? patch.projection.nodes.find(
8557
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,
8558
9341
  )
8559
9342
  : null) ??
8560
9343
  resolveCodeLayerNodeFromBridge(
8561
- patch.projection,
9344
+ nextProjection,
8562
9345
  selector,
8563
9346
  details?.sourceId ??
8564
9347
  elementInfo?.sourceId ??
@@ -8566,7 +9349,7 @@ export default function DesignEditor() {
8566
9349
  ? bridgeSourceIdForCodeLayerNode(targetNode)
8567
9350
  : undefined),
8568
9351
  );
8569
- applyLocalContentUpdate(patch.content, { skipPreview: true });
9352
+ applyLocalContentUpdate(nextContent, { skipPreview: true });
8570
9353
  if (movedNode) setSelectedLayerIdsState([movedNode.id]);
8571
9354
  if (elementInfo) {
8572
9355
  setSelectedElement({
@@ -8677,7 +9460,8 @@ export default function DesignEditor() {
8677
9460
  ) => {
8678
9461
  if (!canEditDesign) return;
8679
9462
  if (!activeFile) return;
8680
- const baseContent = getFreshActiveContent();
9463
+ const activeLiveSnapshot = liveScreenSnapshotsById[activeFile.id];
9464
+ const baseContent = activeLiveSnapshot?.html ?? getFreshActiveContent();
8681
9465
  const projection = buildCodeLayerProjection(baseContent);
8682
9466
  const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
8683
9467
  const targetNode = targetInfo
@@ -8710,7 +9494,11 @@ export default function DesignEditor() {
8710
9494
  );
8711
9495
  return;
8712
9496
  }
8713
- applyLocalContentUpdate(nextContent, { skipPreview: true });
9497
+ if (activeLiveSnapshot) {
9498
+ updateLiveScreenSnapshotContent(activeFile.id, nextContent);
9499
+ } else {
9500
+ applyLocalContentUpdate(nextContent, { skipPreview: true });
9501
+ }
8714
9502
  setActiveTool("text");
8715
9503
  setMode("edit");
8716
9504
  if (removedContent) {
@@ -8753,7 +9541,9 @@ export default function DesignEditor() {
8753
9541
  applyLocalContentUpdate,
8754
9542
  canEditDesign,
8755
9543
  getFreshActiveContent,
9544
+ liveScreenSnapshotsById,
8756
9545
  t,
9546
+ updateLiveScreenSnapshotContent,
8757
9547
  ],
8758
9548
  );
8759
9549
 
@@ -8773,7 +9563,8 @@ export default function DesignEditor() {
8773
9563
  ([, value]) => value !== undefined,
8774
9564
  );
8775
9565
  if (entries.length === 0) return;
8776
- const baseContent = getScreenContent(screenId);
9566
+ const liveSnapshot = liveScreenSnapshotsById[screenId];
9567
+ const baseContent = liveSnapshot?.html ?? getScreenContent(screenId);
8777
9568
  const projection = buildCodeLayerProjection(baseContent);
8778
9569
  const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
8779
9570
  const targetNode = targetInfo
@@ -8817,6 +9608,10 @@ export default function DesignEditor() {
8817
9608
  );
8818
9609
  return;
8819
9610
  }
9611
+ if (liveSnapshot) {
9612
+ updateLiveScreenSnapshotContent(screenId, nextContent);
9613
+ return;
9614
+ }
8820
9615
  applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
8821
9616
  },
8822
9617
  [
@@ -8825,7 +9620,9 @@ export default function DesignEditor() {
8825
9620
  canEditDesign,
8826
9621
  getScreenContent,
8827
9622
  handleVisualStyleChange,
9623
+ liveScreenSnapshotsById,
8828
9624
  t,
9625
+ updateLiveScreenSnapshotContent,
8829
9626
  ],
8830
9627
  );
8831
9628
 
@@ -9013,7 +9810,8 @@ export default function DesignEditor() {
9013
9810
  return;
9014
9811
  }
9015
9812
  if (!canEditDesign) return;
9016
- const baseContent = getScreenContent(screenId);
9813
+ const liveSnapshot = liveScreenSnapshotsById[screenId];
9814
+ const baseContent = liveSnapshot?.html ?? getScreenContent(screenId);
9017
9815
  const projection = buildCodeLayerProjection(baseContent);
9018
9816
  const targetInfo = elementInfo ? { ...elementInfo, selector } : null;
9019
9817
  const targetNode = targetInfo
@@ -9046,7 +9844,11 @@ export default function DesignEditor() {
9046
9844
  );
9047
9845
  return;
9048
9846
  }
9049
- applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
9847
+ if (liveSnapshot) {
9848
+ updateLiveScreenSnapshotContent(screenId, nextContent);
9849
+ } else {
9850
+ applyFileContentUpdate(screenId, nextContent, { skipPreview: true });
9851
+ }
9050
9852
  setActiveFileId(screenId);
9051
9853
  setActiveTool("text");
9052
9854
  setMode("edit");
@@ -9091,7 +9893,9 @@ export default function DesignEditor() {
9091
9893
  canEditDesign,
9092
9894
  getScreenContent,
9093
9895
  handleTextContentChange,
9896
+ liveScreenSnapshotsById,
9094
9897
  t,
9898
+ updateLiveScreenSnapshotContent,
9095
9899
  ],
9096
9900
  );
9097
9901
 
@@ -9272,7 +10076,9 @@ export default function DesignEditor() {
9272
10076
  });
9273
10077
  if (result) {
9274
10078
  pasteCascadeRef.current += 1;
9275
- applyLocalContentUpdate(result.content);
10079
+ applyLocalContentUpdate(result.content, {
10080
+ forcePreviewFullDocument: true,
10081
+ });
9276
10082
  selectInsertedLayers(
9277
10083
  activeFile.id,
9278
10084
  result.content,
@@ -9324,7 +10130,9 @@ export default function DesignEditor() {
9324
10130
  });
9325
10131
  if (!result) return;
9326
10132
  if (!position) pasteCascadeRef.current += 1;
9327
- applyLocalContentUpdate(result.content);
10133
+ applyLocalContentUpdate(result.content, {
10134
+ forcePreviewFullDocument: true,
10135
+ });
9328
10136
  selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
9329
10137
  },
9330
10138
  [
@@ -9356,7 +10164,9 @@ export default function DesignEditor() {
9356
10164
  },
9357
10165
  );
9358
10166
  if (!result) return;
9359
- applyLocalContentUpdate(result.content);
10167
+ applyLocalContentUpdate(result.content, {
10168
+ forcePreviewFullDocument: true,
10169
+ });
9360
10170
  selectInsertedLayers(activeFile.id, result.content, result.rootNodeIds);
9361
10171
  } else {
9362
10172
  handlePasteSelection();
@@ -9407,7 +10217,10 @@ export default function DesignEditor() {
9407
10217
  insertedRootNodeIds.unshift(...result.rootNodeIds);
9408
10218
  }
9409
10219
  if (insertedRootNodeIds.length === 0) continue;
9410
- applyFileContentUpdate(file.id, content, { refreshPreview: false });
10220
+ applyFileContentUpdate(file.id, content, {
10221
+ forcePreviewFullDocument: true,
10222
+ refreshPreview: false,
10223
+ });
9411
10224
  selectedScreenIds.push(file.id);
9412
10225
  const finalProjection = buildCodeLayerProjection(content);
9413
10226
  insertedRootNodeIds.forEach((rootNodeId) => {
@@ -9472,7 +10285,9 @@ export default function DesignEditor() {
9472
10285
  placement: "after",
9473
10286
  });
9474
10287
  if (nextContent) {
9475
- applyLocalContentUpdate(nextContent);
10288
+ applyLocalContentUpdate(nextContent, {
10289
+ forcePreviewFullDocument: true,
10290
+ });
9476
10291
  } else {
9477
10292
  toast.error(t("designEditor.toasts.duplicateElementFailed"));
9478
10293
  }
@@ -9683,57 +10498,6 @@ export default function DesignEditor() {
9683
10498
  t,
9684
10499
  ]);
9685
10500
 
9686
- /**
9687
- * Convert the selected container to full auto-layout. Applies the
9688
- * { kind: "autoLayout", enabled: true } substrate intent which sets
9689
- * display:flex on the target AND strips position:absolute/left/top/right/bottom
9690
- * from its direct children so they become flow children.
9691
- */
9692
- const handleAutoLayoutConvert = useCallback(
9693
- (
9694
- targetNodeId: string,
9695
- opts?: { direction?: "row" | "column"; gap?: string },
9696
- ) => {
9697
- if (!canEditDesign || !activeFile) return;
9698
- const baseContent = getFreshActiveContent();
9699
- const patch = applyVisualEdit(baseContent, {
9700
- kind: "autoLayout",
9701
- targetId: targetNodeId,
9702
- enabled: true,
9703
- direction: opts?.direction ?? "row",
9704
- gap: opts?.gap ?? "8px",
9705
- });
9706
- if (patch.result.status !== "applied") {
9707
- toast.error(
9708
- codeLayerPatchMessage(
9709
- patch.result.message,
9710
- t("designEditor.toasts.layerMoveFailed"),
9711
- ),
9712
- { duration: 4000 },
9713
- );
9714
- return;
9715
- }
9716
- applyLocalContentUpdate(patch.content, { skipPreview: true });
9717
- // Re-select the container so the inspector refreshes its layout state.
9718
- const containerNode = patch.projection.nodes.find(
9719
- (n) =>
9720
- n.dataAttributes["data-agent-native-node-id"] === targetNodeId ||
9721
- n.id === targetNodeId,
9722
- );
9723
- if (containerNode) {
9724
- setSelectedLayerIdsState([containerNode.id]);
9725
- setSelectedElement(elementInfoFromCodeLayerNode(containerNode));
9726
- }
9727
- },
9728
- [
9729
- activeFile,
9730
- applyLocalContentUpdate,
9731
- canEditDesign,
9732
- getFreshActiveContent,
9733
- t,
9734
- ],
9735
- );
9736
-
9737
10501
  /**
9738
10502
  * Handle a primitive being drag-dropped onto another primitive in the
9739
10503
  * MultiScreenCanvas overview (CONTRACT: onPrimitiveReparent prop).
@@ -9931,6 +10695,8 @@ export default function DesignEditor() {
9931
10695
  targetScreenId,
9932
10696
  targetAnchorNodeId,
9933
10697
  targetAnchorPlacement,
10698
+ targetLocalPoint,
10699
+ sourcePointerOffset,
9934
10700
  }: {
9935
10701
  sourceSelector: string;
9936
10702
  sourceNodeId?: string;
@@ -9938,6 +10704,9 @@ export default function DesignEditor() {
9938
10704
  targetScreenId: string;
9939
10705
  targetAnchorNodeId?: string;
9940
10706
  targetAnchorPlacement?: "before" | "after" | "inside";
10707
+ targetCanvasPoint?: { x: number; y: number };
10708
+ targetLocalPoint?: { x: number; y: number };
10709
+ sourcePointerOffset?: { x: number; y: number };
9941
10710
  }) => {
9942
10711
  if (!canEditDesign) return;
9943
10712
  if (sourceScreenId === targetScreenId) return;
@@ -9966,14 +10735,24 @@ export default function DesignEditor() {
9966
10735
  resolvedSourceNode?.dataAttributes["data-agent-native-node-id"] ??
9967
10736
  sourceNodeId ??
9968
10737
  sourceSelector;
10738
+ const destProjection = buildCodeLayerProjection(destContent);
10739
+ const resolvedTargetAnchor = targetAnchorNodeId
10740
+ ? resolveCodeLayerNodeFromBridge(
10741
+ destProjection,
10742
+ undefined,
10743
+ targetAnchorNodeId,
10744
+ )
10745
+ : null;
10746
+ const targetAnchorAttrId =
10747
+ resolvedTargetAnchor?.dataAttributes["data-agent-native-node-id"];
9969
10748
 
9970
10749
  // Use hit-test anchor when the canvas supplied one; fall back to
9971
10750
  // top-level body append ("inside" with no anchor = existing behaviour).
9972
10751
  const result = moveNodeBetweenDocuments(sourceContent, destContent, {
9973
10752
  nodeId: nodeAttrId,
9974
- ...(targetAnchorNodeId
10753
+ ...(targetAnchorAttrId
9975
10754
  ? {
9976
- anchorNodeId: targetAnchorNodeId,
10755
+ anchorNodeId: targetAnchorAttrId,
9977
10756
  placement: targetAnchorPlacement ?? "inside",
9978
10757
  }
9979
10758
  : { placement: "inside" }),
@@ -9989,16 +10768,23 @@ export default function DesignEditor() {
9989
10768
  return;
9990
10769
  }
9991
10770
 
9992
- // Strip absolute positioning from the moved node in the destination so
9993
- // it flows naturally as a body child (mirrors handleOverviewPrimitiveReparent).
9994
- // Use result.movedNodeId (the final id in destHtml, which may differ from
9995
- // nodeAttrId when a collision triggered an id re-stamp) so the strip and
9996
- // 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.
9997
10774
  const destNodeAttrId = result.movedNodeId ?? nodeAttrId;
9998
- const strippedDest = removeAbsolutePositioningFromNodeInHtml(
9999
- result.destHtml,
10000
- destNodeAttrId,
10001
- );
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;
10002
10788
 
10003
10789
  recordContentHistoryEntry({
10004
10790
  changes: [
@@ -10010,7 +10796,7 @@ export default function DesignEditor() {
10010
10796
  {
10011
10797
  fileId: targetScreenId,
10012
10798
  before: destContent,
10013
- after: strippedDest,
10799
+ after: nextDestContent,
10014
10800
  },
10015
10801
  ],
10016
10802
  });
@@ -10019,7 +10805,7 @@ export default function DesignEditor() {
10019
10805
  recordHistory: false,
10020
10806
  refreshPreview: true,
10021
10807
  });
10022
- applyFileContentUpdate(targetScreenId, strippedDest, {
10808
+ applyFileContentUpdate(targetScreenId, nextDestContent, {
10023
10809
  recordHistory: false,
10024
10810
  refreshPreview: true,
10025
10811
  });
@@ -10027,7 +10813,7 @@ export default function DesignEditor() {
10027
10813
  // Switch active screen to the target and select the moved node; viewMode
10028
10814
  // stays "overview" (no setViewMode call).
10029
10815
  setActiveFileId(targetScreenId);
10030
- const finalProjection = buildCodeLayerProjection(strippedDest);
10816
+ const finalProjection = buildCodeLayerProjection(nextDestContent);
10031
10817
  const movedNodeFinal = finalProjection.nodes.find(
10032
10818
  (n) => n.dataAttributes["data-agent-native-node-id"] === destNodeAttrId,
10033
10819
  );
@@ -10077,6 +10863,38 @@ export default function DesignEditor() {
10077
10863
  delete nextGeometry[file.id];
10078
10864
  });
10079
10865
 
10866
+ const nextGeometryUndoStack: GeometryHistoryEntry[] = [];
10867
+ let removedGeometryUndoEntries = 0;
10868
+ geometryUndoStackRef.current.forEach((entry) => {
10869
+ if (geometryHistoryEntryTouchesFrameIds(entry, deleteIds)) {
10870
+ removedGeometryUndoEntries += 1;
10871
+ return;
10872
+ }
10873
+ nextGeometryUndoStack.push(entry);
10874
+ });
10875
+ geometryUndoStackRef.current = nextGeometryUndoStack;
10876
+ historyOrderRef.current = removeRecentUndoRedoOrderKinds(
10877
+ historyOrderRef.current,
10878
+ "geometry",
10879
+ removedGeometryUndoEntries,
10880
+ );
10881
+
10882
+ const nextGeometryRedoStack: GeometryHistoryEntry[] = [];
10883
+ let removedGeometryRedoEntries = 0;
10884
+ geometryRedoStackRef.current.forEach((entry) => {
10885
+ if (geometryHistoryEntryTouchesFrameIds(entry, deleteIds)) {
10886
+ removedGeometryRedoEntries += 1;
10887
+ return;
10888
+ }
10889
+ nextGeometryRedoStack.push(entry);
10890
+ });
10891
+ geometryRedoStackRef.current = nextGeometryRedoStack;
10892
+ redoOrderRef.current = removeRecentUndoRedoOrderKinds(
10893
+ redoOrderRef.current,
10894
+ "geometry",
10895
+ removedGeometryRedoEntries,
10896
+ );
10897
+
10080
10898
  writeFrameGeometrySnapshot(nextGeometry);
10081
10899
  queryClient.setQueryData(["action", "get-design", { id }], (old: any) => {
10082
10900
  if (!old || typeof old !== "object" || !Array.isArray(old.files)) {
@@ -10109,7 +10927,12 @@ export default function DesignEditor() {
10109
10927
  });
10110
10928
  });
10111
10929
 
10112
- return true;
10930
+ // File-backed screen deletion is not a geometry-only edit. The screen rows
10931
+ // are hard-deleted, so suppress MultiScreenCanvas' local frame-history
10932
+ // entry; otherwise undo would restore geometry for files that no longer
10933
+ // exist.
10934
+ syncUndoRedoState();
10935
+ return false;
10113
10936
  },
10114
10937
  [
10115
10938
  activeFile,
@@ -10119,6 +10942,7 @@ export default function DesignEditor() {
10119
10942
  files,
10120
10943
  id,
10121
10944
  queryClient,
10945
+ syncUndoRedoState,
10122
10946
  t,
10123
10947
  writeFrameGeometrySnapshot,
10124
10948
  ],
@@ -10213,8 +11037,10 @@ export default function DesignEditor() {
10213
11037
  const handleUndo = useCallback(() => {
10214
11038
  if (!canEditDesign) return;
10215
11039
  const um = undoManagerRef.current;
10216
- const undoContent = () => {
10217
- if (um?.canUndo()) {
11040
+ const canUseOverviewHistory = viewModeRef.current === "overview";
11041
+ let prunedUndoHistory = false;
11042
+ const undoContent = (scope: "any" | "local" | "global" = "any") => {
11043
+ if (scope !== "global" && um?.canUndo()) {
10218
11044
  um.undo();
10219
11045
  if (ydoc && activeFile) {
10220
11046
  const next = ydoc.getText("content").toString();
@@ -10227,9 +11053,8 @@ export default function DesignEditor() {
10227
11053
  queueFileContentSave(activeFile.id, next, {
10228
11054
  syncCollab: !(ydoc && isSynced),
10229
11055
  });
10230
- if (!replacePreviewContent(next)) {
10231
- setContentRenderRevision((revision) => revision + 1);
10232
- }
11056
+ replacePreviewContent(next, null, { forceFullDocument: true });
11057
+ setContentRenderRevision((revision) => revision + 1);
10233
11058
  // Clear stale selection if the undo removed the selected element.
10234
11059
  setSelectedElement((prev) => {
10235
11060
  if (!prev) return prev;
@@ -10247,6 +11072,44 @@ export default function DesignEditor() {
10247
11072
  return true;
10248
11073
  }
10249
11074
 
11075
+ if (!canUseOverviewHistory && scope !== "global" && activeFile?.id) {
11076
+ const localIndex = findLastContentHistoryChangeIndex(
11077
+ localContentUndoStackRef.current,
11078
+ activeFile.id,
11079
+ );
11080
+ if (localIndex !== -1) {
11081
+ const [entry] = localContentUndoStackRef.current.splice(
11082
+ localIndex,
11083
+ 1,
11084
+ );
11085
+ if (entry) {
11086
+ localContentRedoStackRef.current = [
11087
+ ...localContentRedoStackRef.current.slice(
11088
+ -(MAX_DESIGN_UNDO_STACK - 1),
11089
+ ),
11090
+ entry,
11091
+ ];
11092
+ applyLocalContentUpdate(entry.before, {
11093
+ refreshPreview: false,
11094
+ forcePreviewFullDocument: true,
11095
+ immediateSave: true,
11096
+ recordHistory: false,
11097
+ });
11098
+ setSelectedElement((prev) => {
11099
+ if (!prev) return prev;
11100
+ return refreshElementInfoFromContent(entry.before, prev);
11101
+ });
11102
+ setHoveredElement((prev) => {
11103
+ if (!prev) return prev;
11104
+ return refreshElementInfoFromContent(entry.before, prev);
11105
+ });
11106
+ return true;
11107
+ }
11108
+ }
11109
+ }
11110
+
11111
+ if (scope === "local") return false;
11112
+ if (!canUseOverviewHistory) return false;
10250
11113
  const entry =
10251
11114
  contentUndoStackRef.current[contentUndoStackRef.current.length - 1];
10252
11115
  if (!entry) return false;
@@ -10255,7 +11118,11 @@ export default function DesignEditor() {
10255
11118
  files.map((file) => file.id),
10256
11119
  activeFile?.id,
10257
11120
  );
10258
- if (changes.length === 0) return false;
11121
+ if (changes.length === 0) {
11122
+ contentUndoStackRef.current.pop();
11123
+ prunedUndoHistory = true;
11124
+ return false;
11125
+ }
10259
11126
  contentUndoStackRef.current.pop();
10260
11127
  contentRedoStackRef.current = [
10261
11128
  ...contentRedoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
@@ -10271,6 +11138,7 @@ export default function DesignEditor() {
10271
11138
  if (change.fileId === activeFile?.id) {
10272
11139
  applyLocalContentUpdate(change.before, {
10273
11140
  refreshPreview: false,
11141
+ forcePreviewFullDocument: true,
10274
11142
  immediateSave: true,
10275
11143
  recordHistory: false,
10276
11144
  });
@@ -10300,6 +11168,7 @@ export default function DesignEditor() {
10300
11168
  return true;
10301
11169
  };
10302
11170
  const undoGeometry = () => {
11171
+ if (!canUseOverviewHistory) return false;
10303
11172
  const entry = geometryUndoStackRef.current.pop();
10304
11173
  if (!entry) return false;
10305
11174
  geometryRedoStackRef.current = [
@@ -10319,14 +11188,25 @@ export default function DesignEditor() {
10319
11188
  return true;
10320
11189
  };
10321
11190
 
10322
- const preferred = historyOrderRef.current.pop();
10323
- const didUndo =
11191
+ const undoByOrder = (preferred?: UndoRedoOrderKind) =>
10324
11192
  preferred === "geometry"
10325
11193
  ? undoGeometry() || undoContent()
10326
- : isContentUndoRedoOrderKind(preferred)
10327
- ? undoContent() || undoGeometry()
10328
- : undoContent() || undoGeometry();
10329
- if (didUndo) {
11194
+ : preferred === "file-content"
11195
+ ? undoContent("global") || undoGeometry()
11196
+ : preferred === "content"
11197
+ ? undoContent("local") || undoContent("global") || undoGeometry()
11198
+ : undoContent() || undoGeometry();
11199
+ let didUndo = false;
11200
+ if (canUseOverviewHistory) {
11201
+ while (!didUndo) {
11202
+ const preferred = historyOrderRef.current.pop();
11203
+ didUndo = undoByOrder(preferred);
11204
+ if (didUndo || preferred === undefined) break;
11205
+ }
11206
+ } else {
11207
+ didUndo = undoContent("local");
11208
+ }
11209
+ if (didUndo || prunedUndoHistory) {
10330
11210
  syncUndoRedoState();
10331
11211
  }
10332
11212
  }, [
@@ -10347,8 +11227,10 @@ export default function DesignEditor() {
10347
11227
  const handleRedo = useCallback(() => {
10348
11228
  if (!canEditDesign) return;
10349
11229
  const um = undoManagerRef.current;
10350
- const redoContent = () => {
10351
- if (um?.canRedo()) {
11230
+ const canUseOverviewHistory = viewModeRef.current === "overview";
11231
+ let prunedRedoHistory = false;
11232
+ const redoContent = (scope: "any" | "local" | "global" = "any") => {
11233
+ if (scope !== "global" && um?.canRedo()) {
10352
11234
  um.redo();
10353
11235
  if (ydoc && activeFile) {
10354
11236
  const next = ydoc.getText("content").toString();
@@ -10361,9 +11243,8 @@ export default function DesignEditor() {
10361
11243
  queueFileContentSave(activeFile.id, next, {
10362
11244
  syncCollab: !(ydoc && isSynced),
10363
11245
  });
10364
- if (!replacePreviewContent(next)) {
10365
- setContentRenderRevision((revision) => revision + 1);
10366
- }
11246
+ replacePreviewContent(next, null, { forceFullDocument: true });
11247
+ setContentRenderRevision((revision) => revision + 1);
10367
11248
  // Clear stale selection if the redo removed the selected element.
10368
11249
  setSelectedElement((prev) => {
10369
11250
  if (!prev) return prev;
@@ -10381,6 +11262,44 @@ export default function DesignEditor() {
10381
11262
  return true;
10382
11263
  }
10383
11264
 
11265
+ if (!canUseOverviewHistory && scope !== "global" && activeFile?.id) {
11266
+ const localIndex = findLastContentHistoryChangeIndex(
11267
+ localContentRedoStackRef.current,
11268
+ activeFile.id,
11269
+ );
11270
+ if (localIndex !== -1) {
11271
+ const [entry] = localContentRedoStackRef.current.splice(
11272
+ localIndex,
11273
+ 1,
11274
+ );
11275
+ if (entry) {
11276
+ localContentUndoStackRef.current = [
11277
+ ...localContentUndoStackRef.current.slice(
11278
+ -(MAX_DESIGN_UNDO_STACK - 1),
11279
+ ),
11280
+ entry,
11281
+ ];
11282
+ applyLocalContentUpdate(entry.after, {
11283
+ refreshPreview: false,
11284
+ forcePreviewFullDocument: true,
11285
+ immediateSave: true,
11286
+ recordHistory: false,
11287
+ });
11288
+ setSelectedElement((prev) => {
11289
+ if (!prev) return prev;
11290
+ return refreshElementInfoFromContent(entry.after, prev);
11291
+ });
11292
+ setHoveredElement((prev) => {
11293
+ if (!prev) return prev;
11294
+ return refreshElementInfoFromContent(entry.after, prev);
11295
+ });
11296
+ return true;
11297
+ }
11298
+ }
11299
+ }
11300
+
11301
+ if (scope === "local") return false;
11302
+ if (!canUseOverviewHistory) return false;
10384
11303
  const entry =
10385
11304
  contentRedoStackRef.current[contentRedoStackRef.current.length - 1];
10386
11305
  if (!entry) return false;
@@ -10389,7 +11308,11 @@ export default function DesignEditor() {
10389
11308
  files.map((file) => file.id),
10390
11309
  activeFile?.id,
10391
11310
  );
10392
- if (changes.length === 0) return false;
11311
+ if (changes.length === 0) {
11312
+ contentRedoStackRef.current.pop();
11313
+ prunedRedoHistory = true;
11314
+ return false;
11315
+ }
10393
11316
  contentRedoStackRef.current.pop();
10394
11317
  contentUndoStackRef.current = [
10395
11318
  ...contentUndoStackRef.current.slice(-(MAX_DESIGN_UNDO_STACK - 1)),
@@ -10405,6 +11328,7 @@ export default function DesignEditor() {
10405
11328
  if (change.fileId === activeFile?.id) {
10406
11329
  applyLocalContentUpdate(change.after, {
10407
11330
  refreshPreview: false,
11331
+ forcePreviewFullDocument: true,
10408
11332
  immediateSave: true,
10409
11333
  recordHistory: false,
10410
11334
  });
@@ -10434,6 +11358,7 @@ export default function DesignEditor() {
10434
11358
  return true;
10435
11359
  };
10436
11360
  const redoGeometry = () => {
11361
+ if (!canUseOverviewHistory) return false;
10437
11362
  const entry = geometryRedoStackRef.current.pop();
10438
11363
  if (!entry) return false;
10439
11364
  geometryUndoStackRef.current = [
@@ -10453,14 +11378,25 @@ export default function DesignEditor() {
10453
11378
  return true;
10454
11379
  };
10455
11380
 
10456
- const preferred = redoOrderRef.current.pop();
10457
- const didRedo =
11381
+ const redoByOrder = (preferred?: UndoRedoOrderKind) =>
10458
11382
  preferred === "geometry"
10459
11383
  ? redoGeometry() || redoContent()
10460
- : isContentUndoRedoOrderKind(preferred)
10461
- ? redoContent() || redoGeometry()
10462
- : redoContent() || redoGeometry();
10463
- if (didRedo) {
11384
+ : preferred === "file-content"
11385
+ ? redoContent("global") || redoGeometry()
11386
+ : preferred === "content"
11387
+ ? redoContent("local") || redoContent("global") || redoGeometry()
11388
+ : redoContent() || redoGeometry();
11389
+ let didRedo = false;
11390
+ if (canUseOverviewHistory) {
11391
+ while (!didRedo) {
11392
+ const preferred = redoOrderRef.current.pop();
11393
+ didRedo = redoByOrder(preferred);
11394
+ if (didRedo || preferred === undefined) break;
11395
+ }
11396
+ } else {
11397
+ didRedo = redoContent("local");
11398
+ }
11399
+ if (didRedo || prunedRedoHistory) {
10464
11400
  syncUndoRedoState();
10465
11401
  }
10466
11402
  }, [
@@ -10496,7 +11432,7 @@ export default function DesignEditor() {
10496
11432
  viewModeRef.current = "overview";
10497
11433
  setViewMode("overview");
10498
11434
  setActiveTool("move");
10499
- setOverviewCanvasZoom(100);
11435
+ setExplicitOverviewCanvasZoom(100);
10500
11436
  }, []);
10501
11437
 
10502
11438
  const runEditorViewTransition = useCallback((update: () => void) => {
@@ -11539,7 +12475,7 @@ ${serializedHtml}
11539
12475
  : codingHandoffPreviewFallback);
11540
12476
  const shareExportTab = (
11541
12477
  <div className="space-y-3">
11542
- <div className="text-[11px] font-semibold uppercase text-muted-foreground">
12478
+ <div className="!text-[11px] font-semibold uppercase text-muted-foreground">
11543
12479
  {"Format" /* i18n-ignore share export section label */}
11544
12480
  </div>
11545
12481
  <div className="grid grid-cols-1 gap-1.5 sm:grid-cols-2">
@@ -11564,11 +12500,11 @@ ${serializedHtml}
11564
12500
  <span className="min-w-0 flex-1">
11565
12501
  <span className="block truncate text-[12px] font-semibold text-foreground">
11566
12502
  {option.title}{" "}
11567
- <span className="text-[11px] font-medium text-muted-foreground">
12503
+ <span className="!text-[11px] font-medium text-muted-foreground">
11568
12504
  {option.extension}
11569
12505
  </span>
11570
12506
  </span>
11571
- <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">
11572
12508
  {option.description}
11573
12509
  </span>
11574
12510
  </span>
@@ -11592,7 +12528,7 @@ ${serializedHtml}
11592
12528
  <div className="text-[12px] font-medium text-foreground">
11593
12529
  {selectedShareExportOption.title}
11594
12530
  </div>
11595
- <div className="text-[11px] text-muted-foreground">
12531
+ <div className="!text-[11px] text-muted-foreground">
11596
12532
  {selectedShareExportOption.description}
11597
12533
  </div>
11598
12534
  </div>
@@ -11664,7 +12600,7 @@ ${serializedHtml}
11664
12600
  <div className="text-[12px] font-medium text-foreground">
11665
12601
  {"Download zip instead" /* i18n-ignore share send option */}
11666
12602
  </div>
11667
- <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">
11668
12604
  {
11669
12605
  "For agents without the Design connector, drop the bundle into your agent's chat manually." /* i18n-ignore share send option description */
11670
12606
  }
@@ -11694,7 +12630,7 @@ ${serializedHtml}
11694
12630
  const designSharePopoverClassName =
11695
12631
  "z-[100010] !w-[min(620px,calc(100vw-32px))] !p-3 " +
11696
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 " +
11697
- "[&_[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 " +
11698
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";
11699
12635
  const designShareTabs = {
11700
12636
  shareLabel: "Share link" /* i18n-ignore share tab label */,
@@ -11725,7 +12661,7 @@ ${serializedHtml}
11725
12661
  },
11726
12662
  });
11727
12663
  if (!stamped.changed || stamped.content === activeContent) return;
11728
- applyLocalContentUpdate(stamped.content);
12664
+ applyLocalContentUpdate(stamped.content, { recordHistory: false });
11729
12665
  }, [activeContent, activeFile, applyLocalContentUpdate, id, viewMode]);
11730
12666
  const activeCodeLayerTree = useMemo(
11731
12667
  () => buildCodeLayerTree(activeCodeLayerProjection),
@@ -11740,7 +12676,9 @@ ${serializedHtml}
11740
12676
  () =>
11741
12677
  files.map((file) => {
11742
12678
  const content =
11743
- file.id === activeFile?.id ? activeContent : (file.content ?? "");
12679
+ file.id === activeFile?.id
12680
+ ? activeProjectionContent
12681
+ : getProjectionContentForScreen(file.id);
11744
12682
  const projection =
11745
12683
  file.id === activeFile?.id
11746
12684
  ? activeCodeLayerProjection
@@ -11759,9 +12697,10 @@ ${serializedHtml}
11759
12697
  [
11760
12698
  activeCodeLayerProjection,
11761
12699
  activeCodeLayerTree,
11762
- activeContent,
12700
+ activeProjectionContent,
11763
12701
  activeFile?.id,
11764
12702
  files,
12703
+ getProjectionContentForScreen,
11765
12704
  ],
11766
12705
  );
11767
12706
  const codeLayerModelByFileId = useMemo(
@@ -11805,6 +12744,7 @@ ${serializedHtml}
11805
12744
  });
11806
12745
  return state;
11807
12746
  }, [codeLayerModelsByFile, hiddenLayerIds, lockedLayerIds]);
12747
+ effectiveCodeLayerStateRef.current = effectiveCodeLayerState;
11808
12748
  useEffect(() => {
11809
12749
  shouldPreserveBlockedOverviewLayerSelectionRef.current = (
11810
12750
  screenId: string,
@@ -12048,12 +12988,191 @@ ${serializedHtml}
12048
12988
  selectedLayerIdsState,
12049
12989
  viewMode,
12050
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
+ );
12051
13000
  const selectedLayerIdsRef = useRef<string[]>(selectedLayerIds);
12052
13001
 
12053
13002
  useLayoutEffect(() => {
12054
13003
  selectedLayerIdsRef.current = selectedLayerIds;
12055
13004
  }, [selectedLayerIds]);
12056
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
+
12057
13176
  const layerPanelSelectedIds = useMemo(
12058
13177
  () =>
12059
13178
  viewMode === "overview" && createdOverviewLayerSelection
@@ -12848,7 +13967,7 @@ ${serializedHtml}
12848
13967
  const handleLayerSelectionChange = useCallback(
12849
13968
  (
12850
13969
  ids: string[],
12851
- intent: {
13970
+ _intent: {
12852
13971
  additive: boolean;
12853
13972
  currentSelectedIds?: string[];
12854
13973
  id: string;
@@ -12860,40 +13979,30 @@ ${serializedHtml}
12860
13979
  pendingOverviewLayerSelectionRef.current = null;
12861
13980
  clearPendingOverviewLayerSelectionTimer();
12862
13981
  setCreatedOverviewLayerSelection(null);
12863
- if (intent.additive && !intent.range) {
12864
- const currentLayerIds = (
12865
- intent.currentSelectedIds && intent.currentSelectedIds.length > 0
12866
- ? intent.currentSelectedIds
12867
- : selectedLayerIdsRef.current
12868
- ).filter((layerId) => !layerId.startsWith("__"));
12869
- const additiveLayerIds = currentLayerIds.includes(intent.id)
12870
- ? currentLayerIds.filter((layerId) => layerId !== intent.id)
12871
- : [...currentLayerIds, intent.id];
12872
- setSelectedLayerIdsState(additiveLayerIds);
12873
- if (viewModeRef.current === "overview") {
12874
- const fileIds = files.map((file) => file.id);
12875
- const selectedScreenIds = getOverviewScreenIdsFromLayerSelection({
12876
- fileIds,
12877
- layerIds: additiveLayerIds,
12878
- });
12879
- setOverviewSelectedScreenIds(selectedScreenIds);
12880
- }
12881
- setSelectedElement(null);
12882
- focusDesignInspectorForSelection();
12883
- setActiveTool("move");
12884
- setMode("edit");
12885
- return;
12886
- }
12887
13982
  setSelectedLayerIdsState(nextLayerIds);
12888
- 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];
12889
13995
  if (!selectedId) {
12890
13996
  setSelectedElement(null);
12891
13997
  return;
12892
13998
  }
12893
13999
  const codeLayerOwner = codeLayerOwnerByNodeId.get(selectedId);
12894
14000
  if (codeLayerOwner) {
14001
+ const ownerIsScreenFile = screenFileIds.includes(codeLayerOwner.fileId);
12895
14002
  if (viewModeRef.current === "overview") {
12896
- pendingOverviewScreenSelectionRef.current = codeLayerOwner.fileId;
14003
+ pendingOverviewScreenSelectionRef.current = ownerIsScreenFile
14004
+ ? codeLayerOwner.fileId
14005
+ : null;
12897
14006
  pendingOverviewLayerSelectionRef.current = selectedId;
12898
14007
  }
12899
14008
  if (codeLayerOwner.fileId !== activeFile?.id) {
@@ -12903,6 +14012,7 @@ ${serializedHtml}
12903
14012
  currentViewMode: viewModeRef.current,
12904
14013
  ownerFileId: codeLayerOwner.fileId,
12905
14014
  overviewSelectedScreenIds,
14015
+ screenFileIds,
12906
14016
  });
12907
14017
  viewModeRef.current = nextSelectionState.viewMode;
12908
14018
  setViewMode(nextSelectionState.viewMode);
@@ -12933,11 +14043,19 @@ ${serializedHtml}
12933
14043
  const fileId = selectedId.startsWith("code:")
12934
14044
  ? selectedId.slice("code:".length)
12935
14045
  : selectedId;
12936
- if (files.some((file) => file.id === fileId)) {
14046
+ if (
14047
+ files.some((file) => file.id === fileId && !isBoardFile(file.filename))
14048
+ ) {
12937
14049
  setOverviewSelectedScreenIds([fileId]);
12938
14050
  setActiveFileId(fileId);
12939
14051
  setSelectedElement(null);
12940
- setSelectedLayerIdsState([fileId]);
14052
+ setSelectedLayerIdsState(
14053
+ nextLayerIds.some((layerId) =>
14054
+ files.some((file) => file.id === layerId),
14055
+ )
14056
+ ? nextLayerIds
14057
+ : [fileId],
14058
+ );
12941
14059
  setActiveTool("move");
12942
14060
  setMode("edit");
12943
14061
  viewModeRef.current = "overview";
@@ -12955,6 +14073,113 @@ ${serializedHtml}
12955
14073
  ],
12956
14074
  );
12957
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
+
12958
14183
  const handleLayerRename = useCallback(
12959
14184
  (layerId: string, name: string) => {
12960
14185
  if (!canEditDesign) return;
@@ -13208,7 +14433,12 @@ ${serializedHtml}
13208
14433
  if (!id) return null;
13209
14434
 
13210
14435
  if (designLoading || (!design && pendingGenerationActive)) {
13211
- return <DesignEditorSkeleton embedded={embedded} />;
14436
+ return (
14437
+ <DesignEditorSkeleton
14438
+ embedded={embedded}
14439
+ pendingGeneration={pendingGenerationActive}
14440
+ />
14441
+ );
13212
14442
  }
13213
14443
 
13214
14444
  if (!design) {
@@ -13223,7 +14453,7 @@ ${serializedHtml}
13223
14453
  className="absolute inset-x-0 top-0 h-px bg-[var(--design-editor-panel-divider-color)]"
13224
14454
  />
13225
14455
  <div className="relative flex w-full max-w-sm flex-col items-center text-center">
13226
- <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">
13227
14457
  404
13228
14458
  </div>
13229
14459
  <h1 className="text-xl font-semibold text-foreground">
@@ -13232,7 +14462,7 @@ ${serializedHtml}
13232
14462
  <Button
13233
14463
  variant="default"
13234
14464
  onClick={() => navigate("/")}
13235
- 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"
13236
14466
  >
13237
14467
  <IconArrowLeft className="size-4 rtl:-scale-x-100" />
13238
14468
  {t("designEditor.backToDesigns")}
@@ -13537,30 +14767,39 @@ ${serializedHtml}
13537
14767
  <Tooltip>
13538
14768
  <TooltipTrigger asChild>
13539
14769
  <Button
14770
+ asChild
13540
14771
  variant="outline"
13541
14772
  size="sm"
13542
- onClick={handleSignInToSave}
13543
- 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"
13544
14774
  aria-label={t("designEditor.signUpToSave")}
13545
14775
  >
13546
- <IconDeviceFloppy className="size-4 shrink-0" />
13547
- <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>
13548
14783
  </Button>
13549
14784
  </TooltipTrigger>
13550
- <TooltipContent>
13551
- {t("designEditor.signUpToSaveDescription")}
13552
- </TooltipContent>
14785
+ <TooltipContent>{t("designEditor.signUpToSave")}</TooltipContent>
13553
14786
  </Tooltip>
13554
14787
  <Tooltip>
13555
14788
  <TooltipTrigger asChild>
13556
14789
  <Button
14790
+ asChild
13557
14791
  variant="default"
13558
14792
  size="sm"
13559
- onClick={handleSignInToShare}
13560
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")}
13561
14795
  >
13562
- <span>{t("designEditor.share")}</span>
13563
- <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>
13564
14803
  </Button>
13565
14804
  </TooltipTrigger>
13566
14805
  <TooltipContent>{t("designEditor.signUpToShare")}</TooltipContent>
@@ -13701,7 +14940,7 @@ ${serializedHtml}
13701
14940
  </PopoverContent>
13702
14941
  </Popover>
13703
14942
 
13704
- {isSignedIn ? (
14943
+ {canRenderAuthenticatedShare ? (
13705
14944
  <ShareButton
13706
14945
  resourceType="design"
13707
14946
  resourceId={id}
@@ -13715,40 +14954,15 @@ ${serializedHtml}
13715
14954
  popoverClassName={designSharePopoverClassName}
13716
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)]"
13717
14956
  />
13718
- ) : (
14957
+ ) : sessionResolved ? (
13719
14958
  signedOutPersistenceActions
13720
- )}
14959
+ ) : null}
13721
14960
  </div>
13722
14961
  </div>
13723
14962
  </div>
13724
14963
  );
13725
14964
 
13726
14965
  const leftContentWidth = Math.max(leftSidebarWidth, 320);
13727
- const motionDockLauncherHidden = motionDockMounted || motionDockOpen;
13728
- const motionDockLauncher =
13729
- !embedded && activeFile ? (
13730
- <div
13731
- aria-hidden={motionDockLauncherHidden ? true : undefined}
13732
- className={cn(
13733
- "overflow-hidden border-t border-[var(--design-editor-panel-divider-color)] transition-[max-height,opacity,border-color] duration-200 ease-out",
13734
- motionDockLauncherHidden
13735
- ? "max-h-0 border-transparent opacity-0"
13736
- : "max-h-9 opacity-100",
13737
- )}
13738
- >
13739
- <button
13740
- type="button"
13741
- aria-label={"Expand motion dock" /* i18n-ignore */}
13742
- 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"
13743
- disabled={motionDockLauncherHidden}
13744
- onClick={() => setMotionDockOpenAnimated(true)}
13745
- >
13746
- <IconChevronRight className="size-3.5 shrink-0" />
13747
- <span className="min-w-0 flex-1 truncate">Motion</span>
13748
- </button>
13749
- </div>
13750
- ) : null;
13751
-
13752
14966
  return (
13753
14967
  // h-full not flex-1: the parent <main> uses overflow-y-auto, not flex,
13754
14968
  // so flex-1 on the child doesn't resolve to the available height. h-full
@@ -14124,7 +15338,7 @@ ${serializedHtml}
14124
15338
  />
14125
15339
  )}
14126
15340
 
14127
- {!embedded && isSignedIn ? (
15341
+ {!embedded && canRenderAuthenticatedShare ? (
14128
15342
  <ShareButton
14129
15343
  resourceType="design"
14130
15344
  resourceId={id}
@@ -14139,7 +15353,7 @@ ${serializedHtml}
14139
15353
  popoverClassName={designSharePopoverClassName}
14140
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)]"
14141
15355
  />
14142
- ) : !embedded ? (
15356
+ ) : !embedded && sessionResolved ? (
14143
15357
  signedOutPersistenceActions
14144
15358
  ) : null}
14145
15359
  </div>
@@ -14152,7 +15366,15 @@ ${serializedHtml}
14152
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)]">
14153
15367
  <DesignWorkspaceRail
14154
15368
  activePanel={activeLeftPanel}
15369
+ disabledPanels={
15370
+ initialGenerationChromeLimited
15371
+ ? INITIAL_GENERATION_DISABLED_LEFT_PANELS
15372
+ : undefined
15373
+ }
15374
+ motionOpen={motionDockOpen}
15375
+ motionDisabled={!activeFile}
14155
15376
  projectMenu={projectMenu}
15377
+ onMotionToggle={() => setMotionDockOpenAnimated(!motionDockOpen)}
14156
15378
  onPanelChange={setActiveLeftPanel}
14157
15379
  />
14158
15380
  <div
@@ -14186,7 +15408,6 @@ ${serializedHtml}
14186
15408
  selectedIds={layerPanelSelectedIds}
14187
15409
  expandedIds={layerPanelExpandedIds}
14188
15410
  searchQuery={layersSearchQuery}
14189
- footer={motionDockLauncher}
14190
15411
  onScreenSelect={handleSidebarScreenSelect}
14191
15412
  onScreenOverview={handleSidebarScreenOverview}
14192
15413
  onAddScreen={handleAddScreen}
@@ -14216,6 +15437,7 @@ ${serializedHtml}
14216
15437
  <AgentChatSurface
14217
15438
  mode="panel"
14218
15439
  className="min-h-0 flex-1 border-0 bg-transparent shadow-none"
15440
+ storageKey={DESIGN_CHAT_STORAGE_KEY}
14219
15441
  emptyStateText={t("chat.emptyState")}
14220
15442
  suggestions={[
14221
15443
  t("chat.suggestionLandingPage"),
@@ -14398,18 +15620,6 @@ ${serializedHtml}
14398
15620
  >
14399
15621
  {activeFile ? (
14400
15622
  <div className="flex min-w-0 flex-1 flex-col overflow-hidden">
14401
- {/* Banner for screens connected to a local dev server: edits
14402
- route through the agent rather than being applied inline. */}
14403
- {activeScreenIsLocalSource &&
14404
- !localSourceBannerDismissed &&
14405
- id && (
14406
- <LocalSourceEditBanner
14407
- designId={id}
14408
- fileId={activeFile.id}
14409
- routeSourceFile={activeScreenRouteSourceFile}
14410
- onDismiss={() => setLocalSourceBannerDismissed(true)}
14411
- />
14412
- )}
14413
15623
  {/* "Apply to source" affordance: write the current editor
14414
15624
  content back to the local HTML/CSS file via the bridge.
14415
15625
  Only shown for localhost-backed screens where the route
@@ -14420,7 +15630,7 @@ ${serializedHtml}
14420
15630
  <Button
14421
15631
  size="sm"
14422
15632
  variant="outline"
14423
- className="h-6 gap-1.5 px-2 text-[11px]"
15633
+ className="h-6 gap-1.5 px-2 !text-[11px]"
14424
15634
  disabled={applyToSourcePending}
14425
15635
  onClick={handleApplyToSource}
14426
15636
  >
@@ -14459,7 +15669,7 @@ ${serializedHtml}
14459
15669
  <MultiScreenCanvas
14460
15670
  screens={overviewScreens}
14461
15671
  zoom={overviewCanvasZoom}
14462
- onZoomChange={setOverviewCanvasZoom}
15672
+ onZoomChange={setExplicitOverviewCanvasZoom}
14463
15673
  activeId={activeFileId}
14464
15674
  selectedScreenIds={overviewSelectedScreenIds}
14465
15675
  fullViewScreenIds={fullViewScreenIds}
@@ -14488,16 +15698,68 @@ ${serializedHtml}
14488
15698
  onPrimitiveReparent={handleOverviewPrimitiveReparent}
14489
15699
  onCrossScreenElementDrop={handleCrossScreenElementDrop}
14490
15700
  boardFileId={boardFileId}
15701
+ boardIsActive={activeFileId === boardFileId}
14491
15702
  boardFileContent={boardFileContent}
14492
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
+ }
14493
15741
  onBoardDrawPrimitive={
14494
15742
  canEditDesign ? handleBoardDrawPrimitive : undefined
14495
15743
  }
14496
15744
  boardEditMode={canEditDesign}
14497
15745
  onBoardElementSelect={
14498
15746
  boardFileId
14499
- ? (info) =>
14500
- 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
+ )
14501
15763
  : undefined
14502
15764
  }
14503
15765
  onBoardElementHover={
@@ -14506,6 +15768,20 @@ ${serializedHtml}
14506
15768
  handleScreenElementHover(boardFileId, info)
14507
15769
  : undefined
14508
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
+ }
14509
15785
  onBoardVisualStyleChange={
14510
15786
  boardFileId
14511
15787
  ? (selector, styles, info) =>
@@ -14563,6 +15839,12 @@ ${serializedHtml}
14563
15839
  onCreateScreenFrame={handleCreateScreenFrame}
14564
15840
  onDeleteSelection={handleDeleteOverviewSelection}
14565
15841
  onSelectionChange={handleOverviewScreenSelectionChange}
15842
+ onLayerMarqueeSelectionChange={
15843
+ handleLayerMarqueeSelectionChange
15844
+ }
15845
+ selectedLayerSelectorGroupsByScreen={
15846
+ selectedLayerSelectorGroupsByScreen
15847
+ }
14566
15848
  onPick={(id) => {
14567
15849
  pendingOverviewScreenSelectionRef.current = null;
14568
15850
  pendingOverviewLayerSelectionRef.current = null;
@@ -14631,6 +15913,13 @@ ${serializedHtml}
14631
15913
  renderScreenContent={(screen, metadata, geometry) => {
14632
15914
  const screenIsActive = screen.id === activeFile?.id;
14633
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;
14634
15923
  const screenContentKey = screenIsActive
14635
15924
  ? [screen.id, contentRenderRevision].join(":")
14636
15925
  : [
@@ -14647,7 +15936,15 @@ ${serializedHtml}
14647
15936
  screenId={screen.id}
14648
15937
  zoom={100}
14649
15938
  deviceFrame="none"
14650
- sourceType={designSourceType}
15939
+ sourceType={screenSourceType}
15940
+ bridgeUrl={screenBridgeUrl}
15941
+ externalSnapshotHtml={screenSnapshot}
15942
+ onExternalContentSnapshot={(snapshot) =>
15943
+ handleScreenExternalContentSnapshot(
15944
+ screen.id,
15945
+ snapshot,
15946
+ )
15947
+ }
14651
15948
  fusionUrl={designFusionUrl}
14652
15949
  onComponentSourceJump={handleComponentSourceJump}
14653
15950
  embeddedFrame={{
@@ -14673,7 +15970,7 @@ ${serializedHtml}
14673
15970
  editorChromeScaleY={overviewCanvasZoom / 100}
14674
15971
  editMode={mode === "edit"}
14675
15972
  interactMode={false}
14676
- readOnly={!screenIsActive || !canEditDesign}
15973
+ readOnly={!canEditDesign}
14677
15974
  scaleMode={screenIsActive && activeTool === "scale"}
14678
15975
  clearSelectionRequest={
14679
15976
  overviewClearSelectionRequest
@@ -14687,6 +15984,10 @@ ${serializedHtml}
14687
15984
  ? selectedCanvasSelectorCandidates
14688
15985
  : []
14689
15986
  }
15987
+ selectedSelectorGroups={
15988
+ selectedLayerSelectorGroupsByScreen[screen.id] ??
15989
+ []
15990
+ }
14690
15991
  hoveredSelector={
14691
15992
  hoveredElementScreenId === screen.id
14692
15993
  ? hoveredCanvasSelector
@@ -14705,8 +16006,15 @@ ${serializedHtml}
14705
16006
  screen.id,
14706
16007
  hiddenLayerIds,
14707
16008
  )}
14708
- onElementSelect={(info) =>
14709
- 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
+ )
14710
16018
  }
14711
16019
  onElementHover={(info) =>
14712
16020
  handleScreenElementHover(screen.id, info)
@@ -14791,7 +16099,16 @@ ${serializedHtml}
14791
16099
  zoom={zoom}
14792
16100
  onZoomChange={setZoom}
14793
16101
  deviceFrame={deviceFrame}
14794
- 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
+ }}
14795
16112
  fusionUrl={designFusionUrl}
14796
16113
  previewWidthPx={activeBreakpointWidthState}
14797
16114
  shaderFillPreview={shaderFillPreview}
@@ -14806,6 +16123,13 @@ ${serializedHtml}
14806
16123
  selectedSelectorCandidates={
14807
16124
  selectedCanvasSelectorCandidates
14808
16125
  }
16126
+ selectedSelectorGroups={
16127
+ activeFile
16128
+ ? (selectedLayerSelectorGroupsByScreen[
16129
+ activeFile.id
16130
+ ] ?? [])
16131
+ : []
16132
+ }
14809
16133
  hoveredSelector={hoveredCanvasSelector}
14810
16134
  hoveredSelectorCandidates={
14811
16135
  hoveredCanvasSelectorCandidates
@@ -14813,6 +16137,7 @@ ${serializedHtml}
14813
16137
  lockedSelectors={lockedLayerSelectors}
14814
16138
  hiddenSelectors={hiddenLayerSelectors}
14815
16139
  onElementSelect={handleElementSelect}
16140
+ onElementMarqueeSelect={handleElementMarqueeSelect}
14816
16141
  onElementHover={handleElementHover}
14817
16142
  onClearSelection={() => {
14818
16143
  setSelectedElement(null);
@@ -14881,18 +16206,43 @@ ${serializedHtml}
14881
16206
  </div>
14882
16207
  </div>
14883
16208
  ) : (
14884
- <div className="flex flex-1 items-center justify-center">
14885
- <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">
14886
16211
  {generating || pendingGenerationActive ? (
14887
16212
  <>
14888
- <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>
14889
16216
  <p className="text-sm text-muted-foreground">
14890
16217
  {t("designEditor.generating")}
14891
16218
  </p>
14892
16219
  </>
14893
16220
  ) : (
14894
16221
  <>
14895
- <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">
14896
16246
  {generationIssue ?? t("designEditor.noFiles")}
14897
16247
  </p>
14898
16248
  {retryablePrompt ? (
@@ -14904,7 +16254,7 @@ ${serializedHtml}
14904
16254
  {retryablePrompt ? (
14905
16255
  <Button
14906
16256
  size="sm"
14907
- className="cursor-pointer"
16257
+ className="h-8 cursor-pointer rounded-md"
14908
16258
  onClick={handleRetryGeneration}
14909
16259
  >
14910
16260
  <IconRefresh className="h-3.5 w-3.5" />
@@ -14915,7 +16265,7 @@ ${serializedHtml}
14915
16265
  ref={generateBtnRef}
14916
16266
  variant={retryablePrompt ? "ghost" : "outline"}
14917
16267
  size="sm"
14918
- className="cursor-pointer"
16268
+ className="h-8 cursor-pointer rounded-md"
14919
16269
  onClick={() => {
14920
16270
  setRetryablePrompt(null);
14921
16271
  handlePromptOpenChange(true);
@@ -14936,7 +16286,7 @@ ${serializedHtml}
14936
16286
  )}
14937
16287
 
14938
16288
  {/* Right rail */}
14939
- {!embedded ? (
16289
+ {!embedded && !initialGenerationChromeLimited ? (
14940
16290
  <div
14941
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)]"
14942
16292
  style={{ width: rightSidebarWidth }}
@@ -14953,6 +16303,7 @@ ${serializedHtml}
14953
16303
  <div className="min-h-0 flex-1">
14954
16304
  <EditPanel
14955
16305
  selectedElement={selectedElement}
16306
+ selectedElements={selectedInspectorElements}
14956
16307
  pageStyles={pageStyles}
14957
16308
  zoom={zoom}
14958
16309
  headerTrailing={zoomControl}
@@ -14961,7 +16312,6 @@ ${serializedHtml}
14961
16312
  onActiveTabChange={setActiveInspectorTab}
14962
16313
  tweaks={tweaks}
14963
16314
  tweakValues={tweakSelections}
14964
- readOnly={initialGenerationReadOnly}
14965
16315
  activeContent={activeContent}
14966
16316
  activeFileUpdatedAt={activeFile?.updatedAt ?? null}
14967
16317
  onComponentPropApplied={handleComponentPropApplied}
@@ -14976,16 +16326,19 @@ ${serializedHtml}
14976
16326
  onRequestTweaks={handleRequestTweaks}
14977
16327
  onStyleChange={handleStyleChange}
14978
16328
  onStylesChange={handleStylesChange}
14979
- onAutoLayoutConvert={handleAutoLayoutConvert}
14980
16329
  onExport={handleInspectorExport}
14981
16330
  exporting={pngExporting || svgExporting}
14982
16331
  designId={id}
14983
16332
  fileId={activeFile?.id}
14984
- filename={activeFile?.filename}
14985
16333
  componentNodeId={selectedComponentNodeId}
14986
16334
  sourceCapabilities={sourceCapabilities}
16335
+ selectedElementAlreadyComponent={
16336
+ selectedElementAlreadyComponent
16337
+ }
14987
16338
  onCreateComponent={
14988
- id && selectedElement ? handleCreateComponent : undefined
16339
+ id && selectedElement && !selectedElementAlreadyComponent
16340
+ ? handleCreateComponent
16341
+ : undefined
14989
16342
  }
14990
16343
  defaultComponentName={defaultComponentName}
14991
16344
  inspectCode={inspectCodeData}
@@ -15056,6 +16409,7 @@ ${serializedHtml}
15056
16409
  durationMs={motionDurationMs}
15057
16410
  open={motionDockOpen}
15058
16411
  onOpenChange={setMotionDockOpenAnimated}
16412
+ onExitComplete={handleMotionDockExitComplete}
15059
16413
  onTracksChange={handleMotionTracksChange}
15060
16414
  onDurationChange={handleMotionDurationChange}
15061
16415
  canvasIframeRef={canvasIframeRef}