@agent-native/core 0.92.9 → 0.92.11

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 (345) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +29 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +181 -15
  5. package/corpus/core/src/agent/run-manager.ts +31 -4
  6. package/corpus/core/src/agent/run-store.ts +80 -0
  7. package/corpus/core/src/cli/design-connect.ts +433 -151
  8. package/corpus/core/src/cli/skills.ts +14 -0
  9. package/corpus/core/src/client/AssistantChat.tsx +25 -0
  10. package/corpus/core/src/client/MultiTabAssistantChat.tsx +40 -6
  11. package/corpus/core/src/client/agent-chat.ts +165 -1
  12. package/corpus/core/src/client/chat/index.ts +9 -1
  13. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  14. package/corpus/core/src/client/index.ts +5 -0
  15. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  16. package/corpus/core/src/collab/awareness-store.ts +23 -12
  17. package/corpus/core/src/collab/client.ts +16 -5
  18. package/corpus/core/src/collab/index.ts +4 -0
  19. package/corpus/core/src/collab/recent-edits.ts +65 -2
  20. package/corpus/core/src/deploy/build.ts +133 -5
  21. package/corpus/core/src/index.browser.ts +1 -0
  22. package/corpus/core/src/server/agent-chat-plugin.ts +98 -11
  23. package/corpus/core/src/server/builder-design-systems.ts +30 -4
  24. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +1 -1
  25. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +12 -6
  26. package/corpus/templates/clips/AGENTS.md +4 -0
  27. package/corpus/templates/clips/actions/create-recording.ts +2 -0
  28. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +5 -3
  29. package/corpus/templates/clips/actions/request-transcript.ts +102 -33
  30. package/corpus/templates/clips/app/components/library/library-grid.tsx +1 -0
  31. package/corpus/templates/clips/app/components/player/share-dialog.tsx +59 -19
  32. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +48 -4
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  34. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  44. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  45. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  46. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  47. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  48. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  57. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  58. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  59. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  60. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  61. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  62. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  63. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  64. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  65. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  66. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  67. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  68. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  69. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  70. package/corpus/templates/content/AGENTS.md +7 -6
  71. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  72. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  73. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  74. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  75. package/corpus/templates/content/actions/pull-document.ts +5 -7
  76. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  77. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  78. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  79. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  80. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  81. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  82. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  83. package/corpus/templates/content/app/i18n-data.ts +18 -0
  84. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  85. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  86. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  87. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  88. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  89. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  90. package/corpus/templates/design/AGENTS.md +45 -10
  91. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  92. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  93. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  94. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  95. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  96. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  97. package/corpus/templates/design/actions/generate-design.ts +79 -12
  98. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  99. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  100. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  101. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  102. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  103. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  104. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  105. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  106. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  107. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  108. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  109. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  110. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  111. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  112. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  113. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  114. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  115. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  116. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  117. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  118. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  119. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  120. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  121. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  122. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  123. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  124. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  125. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  126. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  127. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  128. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  129. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  130. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  131. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  132. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  133. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  134. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  135. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  136. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  137. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  138. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  139. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  140. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  141. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  142. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  143. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  144. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  145. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  146. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  147. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  148. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  149. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  150. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  151. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  152. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  153. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  154. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  155. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  156. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  157. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  158. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  159. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  160. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  161. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  162. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  163. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  164. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  165. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  166. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  167. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  168. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  169. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  170. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  171. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  172. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  173. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  174. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  179. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  180. package/corpus/templates/design/app/components/design/types.ts +33 -0
  181. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  182. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  183. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  184. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  185. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  186. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  187. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  188. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  189. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  190. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  191. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  192. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  193. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  194. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  195. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  196. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  197. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  198. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  199. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  200. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  201. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  202. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  203. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  240. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  241. package/corpus/templates/design/e2e/helpers.ts +27 -6
  242. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  243. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  244. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  245. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  246. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  247. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  248. package/corpus/templates/design/server/plugins/db.ts +41 -0
  249. package/corpus/templates/design/server/source-workspace.ts +64 -0
  250. package/corpus/templates/design/shared/board-file.ts +13 -2
  251. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  252. package/corpus/templates/design/shared/code-layer.ts +122 -8
  253. package/corpus/templates/design/shared/component-library.ts +161 -0
  254. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  255. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  256. package/corpus/templates/design/shared/figma-url.ts +149 -0
  257. package/corpus/templates/design/shared/pen-path.ts +16 -7
  258. package/corpus/templates/design/shared/source-mode.ts +1 -0
  259. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  260. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  261. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  262. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  263. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  264. package/dist/agent/production-agent.d.ts +79 -6
  265. package/dist/agent/production-agent.d.ts.map +1 -1
  266. package/dist/agent/production-agent.js +161 -14
  267. package/dist/agent/production-agent.js.map +1 -1
  268. package/dist/agent/run-manager.d.ts.map +1 -1
  269. package/dist/agent/run-manager.js +29 -5
  270. package/dist/agent/run-manager.js.map +1 -1
  271. package/dist/agent/run-store.d.ts +46 -0
  272. package/dist/agent/run-store.d.ts.map +1 -1
  273. package/dist/agent/run-store.js +61 -0
  274. package/dist/agent/run-store.js.map +1 -1
  275. package/dist/cli/design-connect.d.ts +6 -0
  276. package/dist/cli/design-connect.d.ts.map +1 -1
  277. package/dist/cli/design-connect.js +336 -139
  278. package/dist/cli/design-connect.js.map +1 -1
  279. package/dist/cli/skills.d.ts.map +1 -1
  280. package/dist/cli/skills.js +14 -0
  281. package/dist/cli/skills.js.map +1 -1
  282. package/dist/client/AssistantChat.d.ts +2 -0
  283. package/dist/client/AssistantChat.d.ts.map +1 -1
  284. package/dist/client/AssistantChat.js +19 -3
  285. package/dist/client/AssistantChat.js.map +1 -1
  286. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  287. package/dist/client/MultiTabAssistantChat.js +38 -18
  288. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  289. package/dist/client/agent-chat.d.ts +60 -0
  290. package/dist/client/agent-chat.d.ts.map +1 -1
  291. package/dist/client/agent-chat.js +122 -1
  292. package/dist/client/agent-chat.js.map +1 -1
  293. package/dist/client/chat/index.d.ts +1 -1
  294. package/dist/client/chat/index.d.ts.map +1 -1
  295. package/dist/client/chat/index.js +1 -1
  296. package/dist/client/chat/index.js.map +1 -1
  297. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  298. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  299. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  300. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  301. package/dist/client/index.d.ts +1 -1
  302. package/dist/client/index.d.ts.map +1 -1
  303. package/dist/client/index.js +1 -1
  304. package/dist/client/index.js.map +1 -1
  305. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  306. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  307. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  308. package/dist/collab/awareness-store.d.ts +5 -0
  309. package/dist/collab/awareness-store.d.ts.map +1 -1
  310. package/dist/collab/awareness-store.js +19 -12
  311. package/dist/collab/awareness-store.js.map +1 -1
  312. package/dist/collab/awareness.d.ts +2 -2
  313. package/dist/collab/awareness.d.ts.map +1 -1
  314. package/dist/collab/client.d.ts.map +1 -1
  315. package/dist/collab/client.js +13 -5
  316. package/dist/collab/client.js.map +1 -1
  317. package/dist/collab/index.d.ts +1 -0
  318. package/dist/collab/index.d.ts.map +1 -1
  319. package/dist/collab/index.js +1 -0
  320. package/dist/collab/index.js.map +1 -1
  321. package/dist/collab/recent-edits.d.ts +3 -1
  322. package/dist/collab/recent-edits.d.ts.map +1 -1
  323. package/dist/collab/recent-edits.js +57 -2
  324. package/dist/collab/recent-edits.js.map +1 -1
  325. package/dist/collab/routes.d.ts +1 -1
  326. package/dist/deploy/build.d.ts +24 -0
  327. package/dist/deploy/build.d.ts.map +1 -1
  328. package/dist/deploy/build.js +102 -5
  329. package/dist/deploy/build.js.map +1 -1
  330. package/dist/index.browser.d.ts +1 -1
  331. package/dist/index.browser.d.ts.map +1 -1
  332. package/dist/index.browser.js +1 -1
  333. package/dist/index.browser.js.map +1 -1
  334. package/dist/notifications/routes.d.ts +1 -1
  335. package/dist/observability/routes.d.ts +5 -5
  336. package/dist/progress/routes.d.ts +1 -1
  337. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  338. package/dist/server/agent-chat-plugin.js +87 -10
  339. package/dist/server/agent-chat-plugin.js.map +1 -1
  340. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  341. package/dist/server/builder-design-systems.d.ts +15 -0
  342. package/dist/server/builder-design-systems.d.ts.map +1 -1
  343. package/dist/server/builder-design-systems.js +9 -4
  344. package/dist/server/builder-design-systems.js.map +1 -1
  345. package/package.json +1 -1
@@ -13,6 +13,9 @@
13
13
  * __EDITOR_CHROME_SCALE_Y__ — number string, e.g. "1.5"
14
14
  * __DESIGN_CANVAS_SCREEN_ID__ — string literal for the owning screen/file id
15
15
  * __DESIGN_CANVAS_BOARD_SURFACE__ — boolean literal for top-level board iframe
16
+ * __DESIGN_CANVAS_CONTENT_OFFSET_X__ — embedded board render-window x offset
17
+ * __DESIGN_CANVAS_CONTENT_OFFSET_Y__ — embedded board render-window y offset
18
+ * __RUNTIME_LAYER_SNAPSHOT_ENABLED__ — true only for URL-backed localhost apps
16
19
  *
17
20
  * Rules:
18
21
  * • No import/require of any module (DOM globals only).
@@ -28,6 +31,9 @@ declare var __EDITOR_CHROME_SCALE_X__: string;
28
31
  declare var __EDITOR_CHROME_SCALE_Y__: string;
29
32
  declare var __DESIGN_CANVAS_SCREEN_ID__: string;
30
33
  declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
34
+ declare var __DESIGN_CANVAS_CONTENT_OFFSET_X__: number;
35
+ declare var __DESIGN_CANVAS_CONTENT_OFFSET_Y__: number;
36
+ declare var __RUNTIME_LAYER_SNAPSHOT_ENABLED__: boolean;
31
37
 
32
38
  (function () {
33
39
  // Idempotency guard: replace-document-content / srcdoc rebuilds can end up
@@ -51,6 +57,11 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
51
57
  var textEditingEnabled = !readOnly && textEditingEnabledFlag;
52
58
  var designCanvasScreenId = __DESIGN_CANVAS_SCREEN_ID__ || "";
53
59
  var designCanvasBoardSurface = !!__DESIGN_CANVAS_BOARD_SURFACE__;
60
+ var designCanvasContentOffsetX =
61
+ Number(__DESIGN_CANVAS_CONTENT_OFFSET_X__) || 0;
62
+ var designCanvasContentOffsetY =
63
+ Number(__DESIGN_CANVAS_CONTENT_OFFSET_Y__) || 0;
64
+ var runtimeLayerSnapshotEnabled = !!__RUNTIME_LAYER_SNAPSHOT_ENABLED__;
54
65
  var scaleToolEnabled = false;
55
66
  // Interaction-state forced preview (phase 2 — see shared/interaction-states.ts's
56
67
  // "Forced-preview mechanism" doc comment). Tracks which single node id
@@ -211,6 +222,359 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
211
222
  );
212
223
  }
213
224
 
225
+ /**
226
+ * React 19 development builds keep the exact jsxDEV call site on the host
227
+ * element's Fiber `_debugStack`, even though React does not emit that source
228
+ * location as a DOM attribute. Recover it without depending on React internals
229
+ * for mutations: this is read-only provenance used to identify the selected
230
+ * source file and line. Explicit data-source-* / data-loc attributes still
231
+ * win below, and production builds simply return undefined.
232
+ */
233
+ // Fiber debug stacks are immutable for the lifetime of a mounted host node.
234
+ // Keep the relatively expensive Object.keys + owner walk off the snapshot
235
+ // hot path after the first successful read. A WeakMap also means React can
236
+ // collect unmounted nodes normally. Do not cache misses: the bridge can run
237
+ // before a slow/Suspense hydration attaches Fiber to an existing DOM node.
238
+ var reactDebugProvenanceCache =
239
+ typeof WeakMap !== "undefined"
240
+ ? new WeakMap<Element, ReturnType<typeof reactDebugProvenance>>()
241
+ : null;
242
+
243
+ function reactDebugProvenance(el: Element):
244
+ | {
245
+ sourceFile: string;
246
+ line: number;
247
+ column?: number;
248
+ component?: string;
249
+ }
250
+ | undefined {
251
+ var cached = reactDebugProvenanceCache?.get(el);
252
+ if (cached !== undefined) return cached;
253
+ var fiberKey = Object.keys(el).find(function (key) {
254
+ return key.indexOf("__reactFiber$") === 0;
255
+ });
256
+ if (!fiberKey) return undefined;
257
+ var fiber = (el as unknown as Record<string, any>)[fiberKey];
258
+ for (var depth = 0; fiber && depth < 12; depth += 1) {
259
+ var stack = String(fiber._debugStack?.stack || "");
260
+ var lines = stack.split("\n");
261
+ for (var index = 0; index < lines.length; index += 1) {
262
+ var lineText = lines[index];
263
+ var match = lineText.match(
264
+ /(?:\(|\s)(https?:\/\/[^\s)]+?):(\d+):(\d+)\)?\s*$/,
265
+ );
266
+ if (!match) continue;
267
+ try {
268
+ var sourceUrl = new URL(match[1]);
269
+ var pathname = decodeURIComponent(sourceUrl.pathname);
270
+ if (pathname.indexOf("/node_modules/") >= 0) continue;
271
+ var sourceFile =
272
+ pathname.indexOf("/@fs/") === 0
273
+ ? pathname.slice("/@fs".length)
274
+ : pathname.replace(/^\/+/, "");
275
+ if (!sourceFile) continue;
276
+ var componentMatch = lineText.match(/\bat\s+([^\s(]+)\s*\(/);
277
+ var provenance = {
278
+ sourceFile: sourceFile,
279
+ line: parseInt(match[2], 10),
280
+ column: parseInt(match[3], 10),
281
+ component: componentMatch ? componentMatch[1] : undefined,
282
+ };
283
+ reactDebugProvenanceCache?.set(el, provenance);
284
+ return provenance;
285
+ } catch (_error) {
286
+ // Ignore malformed/non-URL debug frames and keep walking owners.
287
+ }
288
+ }
289
+ fiber = fiber.return;
290
+ }
291
+ return undefined;
292
+ }
293
+
294
+ var runtimeLayerSnapshotTimer: number | null = null;
295
+ var runtimeLayerSnapshotMaxTimer: number | null = null;
296
+ var lastRuntimeLayerSnapshotHtml = "";
297
+
298
+ function runtimeLayerHash(value: string): string {
299
+ var hash = 0x811c9dc5;
300
+ for (var index = 0; index < value.length; index += 1) {
301
+ hash ^= value.charCodeAt(index);
302
+ hash = Math.imul(hash, 0x01000193);
303
+ }
304
+ return (hash >>> 0).toString(36);
305
+ }
306
+
307
+ function runtimeLayerStructuralPath(el: Element): string {
308
+ var parts: string[] = [];
309
+ var node: Element | null = el;
310
+ while (node && node !== document.body) {
311
+ var tag = node.tagName.toLowerCase();
312
+ var parent = node.parentElement;
313
+ if (parent) {
314
+ var sameTag = Array.prototype.filter.call(
315
+ parent.children,
316
+ function (sibling) {
317
+ return sibling.tagName === node!.tagName;
318
+ },
319
+ );
320
+ tag += ":nth-of-type(" + (sameTag.indexOf(node) + 1) + ")";
321
+ }
322
+ parts.unshift(tag);
323
+ node = parent;
324
+ }
325
+ return parts.join(" > ") || "body";
326
+ }
327
+
328
+ function ensureRuntimeLayerNodeId(el: Element): string {
329
+ var existing = el.getAttribute("data-agent-native-node-id")?.trim();
330
+ if (existing) return existing;
331
+ var provenance = reactDebugProvenance(el);
332
+ var provenanceKey = provenance
333
+ ? [
334
+ provenance.sourceFile,
335
+ provenance.line,
336
+ provenance.column || 0,
337
+ provenance.component || "",
338
+ ].join(":")
339
+ : "dom";
340
+ var nodeId =
341
+ "runtime-" +
342
+ runtimeLayerHash(
343
+ designCanvasScreenId +
344
+ ":" +
345
+ provenanceKey +
346
+ ":" +
347
+ runtimeLayerStructuralPath(el),
348
+ );
349
+ // This attribute is editor-only and never persisted. Stamping the live DOM
350
+ // gives the runtime projection and the selectable element one exact shared
351
+ // identity. Qualifying the hash with the owning screen/file id prevents a
352
+ // shared React shell from producing the same layer id in two route iframes
353
+ // (which would make the editor's document-wide owner map route selection
354
+ // and hover to whichever screen registered last). The structural path
355
+ // keeps the id stable across reloads within that iframe while still
356
+ // disambiguating repeated JSX emitted from the same source location.
357
+ el.setAttribute("data-agent-native-node-id", nodeId);
358
+ return nodeId;
359
+ }
360
+
361
+ function isRuntimeLayerVisualNode(el: Element): boolean {
362
+ if (
363
+ /^(script|style|template|noscript|link|meta|title)$/i.test(el.tagName)
364
+ ) {
365
+ return false;
366
+ }
367
+ return !(
368
+ isOverlayElement(el) || el.closest("[data-agent-native-edit-overlay]")
369
+ );
370
+ }
371
+
372
+ function serializeRuntimeLayerSnapshot(): {
373
+ html: string;
374
+ nodeCount: number;
375
+ } | null {
376
+ if (!document.body) return null;
377
+ var sourceNodes = Array.prototype.slice.call(
378
+ document.body.querySelectorAll("*"),
379
+ ) as Element[];
380
+ var cloneBody = document.body.cloneNode(true) as HTMLElement;
381
+ var cloneNodes = Array.prototype.slice.call(
382
+ cloneBody.querySelectorAll("*"),
383
+ ) as Element[];
384
+ var nodeCount = 0;
385
+ for (
386
+ var index = 0;
387
+ index < sourceNodes.length && index < cloneNodes.length;
388
+ index += 1
389
+ ) {
390
+ var sourceNode = sourceNodes[index];
391
+ var cloneNode = cloneNodes[index];
392
+ if (!isRuntimeLayerVisualNode(sourceNode)) {
393
+ cloneNode.setAttribute("data-an-runtime-layer-remove", "true");
394
+ continue;
395
+ }
396
+ cloneNode.setAttribute(
397
+ "data-agent-native-node-id",
398
+ ensureRuntimeLayerNodeId(sourceNode),
399
+ );
400
+ var provenance = reactDebugProvenance(sourceNode);
401
+ if (provenance) {
402
+ cloneNode.setAttribute("data-source-file", provenance.sourceFile);
403
+ cloneNode.setAttribute("data-source-line", String(provenance.line));
404
+ if (provenance.column) {
405
+ cloneNode.setAttribute(
406
+ "data-source-column",
407
+ String(provenance.column),
408
+ );
409
+ }
410
+ if (provenance.component) {
411
+ cloneNode.setAttribute("data-component-name", provenance.component);
412
+ }
413
+ }
414
+ nodeCount += 1;
415
+ }
416
+ cloneBody
417
+ .querySelectorAll("[data-an-runtime-layer-remove]")
418
+ .forEach(function (node) {
419
+ node.remove();
420
+ });
421
+ cloneBody
422
+ .querySelectorAll("script,style,template,noscript,link,meta,title")
423
+ .forEach(function (node) {
424
+ node.remove();
425
+ });
426
+ cloneBody.setAttribute(
427
+ "data-agent-native-node-id",
428
+ ensureRuntimeLayerNodeId(document.body),
429
+ );
430
+ cloneBody.setAttribute("data-an-runtime-layer-snapshot", "true");
431
+ var html = "<!doctype html><html>" + cloneBody.outerHTML + "</html>"; // i18n-ignore serialized runtime-layer HTML payload, not visible UI copy
432
+ if (html.length > 2_000_000) return null;
433
+ return { html: html, nodeCount: nodeCount };
434
+ }
435
+
436
+ function postRuntimeLayerSnapshot(): void {
437
+ if (runtimeLayerSnapshotTimer !== null) {
438
+ window.clearTimeout(runtimeLayerSnapshotTimer);
439
+ }
440
+ if (runtimeLayerSnapshotMaxTimer !== null) {
441
+ window.clearTimeout(runtimeLayerSnapshotMaxTimer);
442
+ }
443
+ runtimeLayerSnapshotTimer = null;
444
+ runtimeLayerSnapshotMaxTimer = null;
445
+ var snapshot = serializeRuntimeLayerSnapshot();
446
+ if (!snapshot || snapshot.html === lastRuntimeLayerSnapshotHtml) return;
447
+ lastRuntimeLayerSnapshotHtml = snapshot.html;
448
+ (window.parent as Window).postMessage(
449
+ {
450
+ type: "agent-native:runtime-layer-snapshot",
451
+ payload: snapshot,
452
+ },
453
+ "*",
454
+ );
455
+ }
456
+
457
+ function scheduleRuntimeLayerSnapshot(): void {
458
+ // A leading-edge throttle still serializes a fast clock, streaming list,
459
+ // or hydration loop five times per second forever. Debounce on the trailing
460
+ // edge so a normal React commit becomes one snapshot, with a bounded max
461
+ // wait so a genuinely continuous stream still refreshes Layers eventually
462
+ // without monopolising the iframe main thread.
463
+ if (runtimeLayerSnapshotTimer !== null) {
464
+ window.clearTimeout(runtimeLayerSnapshotTimer);
465
+ }
466
+ runtimeLayerSnapshotTimer = window.setTimeout(
467
+ postRuntimeLayerSnapshot,
468
+ 300,
469
+ );
470
+ if (runtimeLayerSnapshotMaxTimer === null) {
471
+ runtimeLayerSnapshotMaxTimer = window.setTimeout(
472
+ postRuntimeLayerSnapshot,
473
+ 1500,
474
+ );
475
+ }
476
+ }
477
+
478
+ // Runtime Layers describes hierarchy, names, and layout containers. It does
479
+ // not need a new full-document snapshot for animation-frame churn such as
480
+ // transform/opacity/style streaming or state-only utility classes. Compare
481
+ // only the class/style subset that can change the projected Layers tree.
482
+ function runtimeLayerClassSignature(value: string | null): string {
483
+ return String(value || "")
484
+ .split(/\s+/)
485
+ .map(function (token) {
486
+ var parts = token.split(":");
487
+ var utility = String(parts[parts.length - 1] || "").replace(/^!/, "");
488
+ if (
489
+ /^(?:flex|inline-flex|grid|inline-grid|hidden|block|inline-block|flex-row|flex-col)$/.test(
490
+ utility,
491
+ ) ||
492
+ /^(?:items|justify)-/.test(utility)
493
+ ) {
494
+ return utility;
495
+ }
496
+ // The projection only asks whether a component-like class exists; a
497
+ // transition from `button-idle` to `button-active` does not change the
498
+ // layer type and should not trigger another full snapshot.
499
+ return /(?:component|card|button|control)/.test(utility)
500
+ ? "component-like"
501
+ : "";
502
+ })
503
+ .filter(Boolean)
504
+ .sort()
505
+ .join(" ");
506
+ }
507
+
508
+ function runtimeLayerStyleSignature(value: string | null): string {
509
+ var relevant: Record<string, string> = {};
510
+ String(value || "")
511
+ .split(";")
512
+ .forEach(function (declaration) {
513
+ var separator = declaration.indexOf(":");
514
+ if (separator < 0) return;
515
+ var property = declaration.slice(0, separator).trim().toLowerCase();
516
+ if (
517
+ !/^(?:display|flex-direction|align-items|justify-content)$/.test(
518
+ property,
519
+ )
520
+ ) {
521
+ return;
522
+ }
523
+ relevant[property] = declaration.slice(separator + 1).trim();
524
+ });
525
+ return Object.keys(relevant)
526
+ .sort()
527
+ .map(function (property) {
528
+ return property + ":" + relevant[property];
529
+ })
530
+ .join(";");
531
+ }
532
+
533
+ function runtimeLayerMutationIsMeaningful(mutation: MutationRecord): boolean {
534
+ var target = mutation.target as Element;
535
+ if (
536
+ target.nodeType === 1 &&
537
+ (isOverlayElement(target) ||
538
+ target.closest?.("[data-agent-native-edit-overlay]"))
539
+ ) {
540
+ return false;
541
+ }
542
+ if (mutation.type === "childList") {
543
+ var changedNodes = Array.prototype.slice
544
+ .call(mutation.addedNodes)
545
+ .concat(Array.prototype.slice.call(mutation.removedNodes));
546
+ return changedNodes.some(function (node: Node) {
547
+ var element =
548
+ node.nodeType === 1
549
+ ? (node as Element)
550
+ : node.parentElement || mutation.target;
551
+ return !(
552
+ element.nodeType === 1 &&
553
+ (isOverlayElement(element as Element) ||
554
+ (element as Element).closest?.("[data-agent-native-edit-overlay]"))
555
+ );
556
+ });
557
+ }
558
+ if (mutation.type === "characterData") {
559
+ return true;
560
+ }
561
+ if (mutation.type !== "attributes") return false;
562
+ var name = mutation.attributeName || "";
563
+ if (name === "class") {
564
+ return (
565
+ runtimeLayerClassSignature(mutation.oldValue) !==
566
+ runtimeLayerClassSignature(target.getAttribute("class"))
567
+ );
568
+ }
569
+ if (name === "style") {
570
+ return (
571
+ runtimeLayerStyleSignature(mutation.oldValue) !==
572
+ runtimeLayerStyleSignature(target.getAttribute("style"))
573
+ );
574
+ }
575
+ return true;
576
+ }
577
+
214
578
  function isDocumentRootElement(el: Element | null): boolean {
215
579
  return el === document.body || el === document.documentElement;
216
580
  }
@@ -806,10 +1170,8 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
806
1170
  "Parent layout context decides whether movement means gap, order, alignment, or wrapper structure.",
807
1171
  });
808
1172
  }
809
- // --- provenance: read source-location attributes when present ---
810
- // These are emitted by connected apps via @vitejs/plugin-react jsxDEV or a
811
- // Babel source plugin. Cross-origin localhost iframes cannot be read (CSP /
812
- // same-origin policy), so this will be undefined in that case — expected.
1173
+ // --- provenance: read explicit source-location attributes when present,
1174
+ // then fall back to React's development-only jsxDEV Fiber stack. ---
813
1175
  var provenance:
814
1176
  | {
815
1177
  sourceFile?: string;
@@ -844,6 +1206,18 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
844
1206
  if (hasColumn) dataSourceColumn = lastPart;
845
1207
  }
846
1208
  }
1209
+ if (!dataSourceFile) {
1210
+ var reactProvenance = reactDebugProvenance(el);
1211
+ if (reactProvenance) {
1212
+ dataSourceFile = reactProvenance.sourceFile;
1213
+ dataSourceLine = String(reactProvenance.line);
1214
+ dataSourceColumn = reactProvenance.column
1215
+ ? String(reactProvenance.column)
1216
+ : null;
1217
+ dataComponentName =
1218
+ dataComponentName || reactProvenance.component || null;
1219
+ }
1220
+ }
847
1221
  if (
848
1222
  dataSourceFile ||
849
1223
  dataSourceLine ||
@@ -884,11 +1258,18 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
884
1258
  lineHeight: cs.lineHeight,
885
1259
  letterSpacing: cs.letterSpacing,
886
1260
  textAlign: cs.textAlign,
1261
+ // Clean longhand for decoration-toggle state (Cmd+U underline /
1262
+ // Cmd+Shift+X strikethrough). Deliberately the longhand, not the
1263
+ // `textDecoration` shorthand — see typography-helpers.ts's
1264
+ // PERSISTENCE GOTCHA comment: reads use this clean value, writes
1265
+ // still commit through the shorthand property name.
1266
+ textDecorationLine: cs.textDecorationLine,
887
1267
  display: cs.display,
888
1268
  overflow: cs.overflow,
889
1269
  flexDirection: cs.flexDirection,
890
1270
  justifyContent: cs.justifyContent,
891
1271
  alignItems: cs.alignItems,
1272
+ justifyItems: cs.justifyItems,
892
1273
  alignSelf: cs.alignSelf,
893
1274
  flexGrow: cs.flexGrow,
894
1275
  flexShrink: cs.flexShrink,
@@ -896,12 +1277,17 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
896
1277
  order: cs.order,
897
1278
  gridColumn: cs.gridColumn,
898
1279
  gridRow: cs.gridRow,
1280
+ gridTemplateColumns: cs.gridTemplateColumns,
1281
+ gridTemplateRows: cs.gridTemplateRows,
1282
+ gridAutoFlow: cs.gridAutoFlow,
899
1283
  position: cs.position,
900
1284
  top: cs.top,
901
1285
  right: cs.right,
902
1286
  bottom: cs.bottom,
903
1287
  left: cs.left,
904
1288
  gap: cs.gap,
1289
+ rowGap: cs.rowGap,
1290
+ columnGap: cs.columnGap,
905
1291
  width: cs.width,
906
1292
  height: cs.height,
907
1293
  opacity: cs.opacity,
@@ -961,6 +1347,9 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
961
1347
  width: rect.width,
962
1348
  height: rect.height,
963
1349
  },
1350
+ parentBoundingRect: el.parentElement
1351
+ ? rectInfoForElement(el.parentElement)
1352
+ : undefined,
964
1353
  textContent: el.textContent ? el.textContent.slice(0, 200) : undefined,
965
1354
  htmlContent:
966
1355
  el.innerHTML && el.innerHTML !== el.textContent
@@ -1475,6 +1864,27 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
1475
1864
 
1476
1865
  var selectedEl: Element | null = null;
1477
1866
  var hoveredEl: Element | null = null;
1867
+ // Last element an "element-hover" message was actually posted for. Lets
1868
+ // the shield's pointermove handler skip getLightElementInfo's two
1869
+ // getComputedStyle calls plus the postMessage on every one of the dozens
1870
+ // of raw pointermove ticks a slow hover over one unchanged element
1871
+ // produces — only the FIRST tick that lands on a given element needs to
1872
+ // tell the host anything new. The parent already de-dupes on its side
1873
+ // (see the "PF9" equality-bail comment in DesignEditor.tsx), so skipping
1874
+ // the redundant sends here is a pure perf win with no behavior change.
1875
+ var lastHoverInfoPostedEl: Element | null = null;
1876
+
1877
+ // Every path that clears (or invalidates) the current hover must re-arm the
1878
+ // post gate above through this helper, not just null out hoveredEl. If a
1879
+ // path nulls hoveredEl without resetting lastHoverInfoPostedEl, the pointer
1880
+ // returning to the SAME element the gate already posted for will silently
1881
+ // skip re-posting "element-hover" to the host, leaving its hover state
1882
+ // stuck until a different element is hovered.
1883
+ function clearHoverGate(): void {
1884
+ hoveredEl = null;
1885
+ lastHoverInfoPostedEl = null;
1886
+ }
1887
+
1478
1888
  var passiveSelectionEls: Element[] = [];
1479
1889
  var passiveSelectionOverlays: HTMLElement[] = [];
1480
1890
  var activeMarqueeSelection: {
@@ -1648,6 +2058,8 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
1648
2058
  var spacingHatchNodesByKey: Record<string, Element> = {};
1649
2059
  var spacingOverlayRenderKey = "";
1650
2060
  var activeDragCancel: (() => boolean) | null = null;
2061
+ var bridgeSpaceKeyPressed = false;
2062
+ var bridgeSpaceKeyConsumedByDrag = false;
1651
2063
  var activeCrossScreenStyleSnapshot: unknown | undefined = undefined;
1652
2064
  var spacingDrag: {
1653
2065
  key: string;
@@ -1668,7 +2080,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
1668
2080
 
1669
2081
  function clearRuntimeSelection(): void {
1670
2082
  selectedEl = null;
1671
- hoveredEl = null;
2083
+ clearHoverGate();
1672
2084
  setPassiveSelectionElements([]);
1673
2085
  clearSpacingHoverTimer();
1674
2086
  selectedSpacingHovered = false;
@@ -2015,7 +2427,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
2015
2427
  selectedEl = document.querySelector(matchedSelector);
2016
2428
  } catch (_err) {}
2017
2429
  }
2018
- hoveredEl = null;
2430
+ clearHoverGate();
2019
2431
  if (selectedEl && !isLayerInteractionBlocked(selectedEl)) {
2020
2432
  positionOverlay(selectionOverlay, selectedEl);
2021
2433
  postElementSelect(selectedEl);
@@ -2049,7 +2461,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
2049
2461
  applyHiddenSelectors();
2050
2462
 
2051
2463
  selectedEl = null;
2052
- hoveredEl = null;
2464
+ clearHoverGate();
2053
2465
  for (var i = 0; i < activeCandidates.length && !selectedEl; i += 1) {
2054
2466
  try {
2055
2467
  var match = document.querySelector(activeCandidates[i]);
@@ -3372,6 +3784,86 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
3372
3784
  }
3373
3785
  }
3374
3786
 
3787
+ // Transition/animation overlay tracking: the ResizeObserver above only
3788
+ // fires on border-box SIZE changes, so a purely transform- or left/top-
3789
+ // driven CSS transition/animation on the selected or hovered element (very
3790
+ // common for hover states, toggles, carousels in AI-generated prototypes)
3791
+ // never triggers it. Without this, the one-shot MutationObserver callback
3792
+ // that fires when the triggering class/style attribute changes reads
3793
+ // getBoundingClientRect() at essentially the START of the transition, and
3794
+ // the overlay then freezes there while the real element visually slides/
3795
+ // fades to its final position — the selection outline and its handles
3796
+ // visibly detach from the animating element for the transition's whole
3797
+ // duration. Fixed by running a bounded rAF refresh loop for the duration of
3798
+ // any transition/animation that starts on a tracked element, so the
3799
+ // overlay follows every intermediate frame instead of only the first and
3800
+ // (via the next unrelated mutation/resize/scroll) last.
3801
+ var overlayAnimationTrackingActive = false;
3802
+ var overlayAnimationTrackingUntil = 0;
3803
+ var overlayAnimationTrackingStartedAt = 0;
3804
+ // Covers the vast majority of real UI transitions/animations (hover/toggle
3805
+ // durations are almost always well under a second); the max below is a
3806
+ // safety net for a transition whose end event never fires (e.g. cancelled
3807
+ // by a later style write with no transitionend), so a slow/held animation
3808
+ // can't pin this loop on forever.
3809
+ var OVERLAY_ANIMATION_TRACKING_WINDOW_MS = 1000;
3810
+ var OVERLAY_ANIMATION_TRACKING_MAX_MS = 4000;
3811
+
3812
+ function isOverlayAnimationTrackingTarget(
3813
+ target: EventTarget | null,
3814
+ ): boolean {
3815
+ if (!target) return false;
3816
+ return target === selectedEl || target === hoveredEl;
3817
+ }
3818
+
3819
+ function tickOverlayAnimationTracking(): void {
3820
+ if (!overlayAnimationTrackingActive) return;
3821
+ refreshOverlays();
3822
+ var now = Date.now();
3823
+ if (
3824
+ now >= overlayAnimationTrackingUntil ||
3825
+ now - overlayAnimationTrackingStartedAt >=
3826
+ OVERLAY_ANIMATION_TRACKING_MAX_MS
3827
+ ) {
3828
+ overlayAnimationTrackingActive = false;
3829
+ return;
3830
+ }
3831
+ window.requestAnimationFrame(tickOverlayAnimationTracking);
3832
+ }
3833
+
3834
+ // Re-armed (window extended) by every qualifying transitionrun/
3835
+ // animationstart, so a sequence of staggered transitions on the same
3836
+ // element keeps the loop running for their combined duration rather than
3837
+ // stopping partway through.
3838
+ function startOverlayAnimationTracking(): void {
3839
+ var now = Date.now();
3840
+ overlayAnimationTrackingUntil = now + OVERLAY_ANIMATION_TRACKING_WINDOW_MS;
3841
+ if (overlayAnimationTrackingActive) return;
3842
+ overlayAnimationTrackingActive = true;
3843
+ overlayAnimationTrackingStartedAt = now;
3844
+ window.requestAnimationFrame(tickOverlayAnimationTracking);
3845
+ }
3846
+
3847
+ function onOverlayAnimationTrackingEvent(e: Event): void {
3848
+ if (isOverlayAnimationTrackingTarget(e.target as EventTarget | null)) {
3849
+ startOverlayAnimationTracking();
3850
+ }
3851
+ }
3852
+ // Capture-phase, delegated on document (not per-element add/remove-
3853
+ // listener bookkeeping tied to selection changes): transitionrun/
3854
+ // animationstart bubble, so one pair of listeners registered once at
3855
+ // bridge init covers whichever element is currently selected/hovered.
3856
+ document.addEventListener(
3857
+ "transitionrun",
3858
+ onOverlayAnimationTrackingEvent,
3859
+ true,
3860
+ );
3861
+ document.addEventListener(
3862
+ "animationstart",
3863
+ onOverlayAnimationTrackingEvent,
3864
+ true,
3865
+ );
3866
+
3375
3867
  function hideMeasurements(): void {
3376
3868
  measurementOverlay.style.display = "none";
3377
3869
  measurementOverlay.innerHTML = "";
@@ -3717,7 +4209,15 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
3717
4209
  // not intercept Tab when the user is tabbing through browser UI with nothing
3718
4210
  // selected (preserves native keyboard accessibility).
3719
4211
  if (key === "Tab") return !!selectedEl;
3720
- if (key === "Delete" || key === "Backspace") return !primary;
4212
+ if (key === "Delete" || key === "Backspace") {
4213
+ // Cmd/Ctrl+Backspace is Figma's "ungroup" chord (see onUngroup in
4214
+ // useDesignHotkeys.ts) — carve out that one primary-modifier exception
4215
+ // so it isn't swallowed by the blanket "no modifier" rule below. Any
4216
+ // other primary+Delete/Backspace combo has no host binding, so it stays
4217
+ // local (matches prior behavior).
4218
+ if (primary) return key === "Backspace" && !e.altKey && !e.shiftKey;
4219
+ return true;
4220
+ }
3721
4221
  if (/^Arrow/.test(key || "")) return !e.altKey;
3722
4222
  if (primary) {
3723
4223
  return (
@@ -3736,11 +4236,44 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
3736
4236
  "0",
3737
4237
  "]",
3738
4238
  "[",
4239
+ // Cmd/Ctrl+U — toggle underline (useDesignHotkeys.ts onToggleUnderline).
4240
+ "u",
4241
+ // Cmd/Ctrl+F — find (onFind). Bridge's "primary" doesn't distinguish
4242
+ // Cmd from Ctrl the way isPlatformPrimaryModifier does host-side, but
4243
+ // forwarding is harmless when the host has no match for the combo.
4244
+ "f",
4245
+ // Cmd/Ctrl+R rename / Cmd/Ctrl+Shift+R paste-to-replace (onRename /
4246
+ // onPasteToReplace) — both live under bare primary+r.
4247
+ "r",
4248
+ // Cmd/Ctrl+\ — toggle UI (onToggleUi).
4249
+ "\\",
3739
4250
  ].indexOf(normalized) !== -1 ||
3740
4251
  e.code === "Digit1" ||
3741
4252
  e.code === "Digit2" ||
3742
4253
  key === "1" ||
3743
- key === "2"
4254
+ key === "2" ||
4255
+ // Cmd/Ctrl+Shift+H / +L — toggle hidden / toggle locked
4256
+ // (onToggleHidden / onToggleLocked). Gated on shiftKey so bare
4257
+ // Cmd+H / Cmd+L — common OS "Hide app" / browser "focus address bar"
4258
+ // shortcuts the host has no bare-primary binding for — are left
4259
+ // alone (see useDesignHotkeys.ts: both require event.shiftKey).
4260
+ (e.shiftKey && (normalized === "h" || normalized === "l")) ||
4261
+ // Cmd/Ctrl+Alt+B detach instance / Cmd/Ctrl+Alt+K create component
4262
+ // (onDetachInstance / onCreateComponent). Gated on altKey so bare
4263
+ // Cmd+B / Cmd+K are left alone — the host has no bare-primary
4264
+ // binding for either.
4265
+ (e.altKey && (normalized === "b" || normalized === "k")) ||
4266
+ // Ctrl+Alt+H / Ctrl+Alt+T — distribute horizontal / tidy up
4267
+ // (onDistributeSelection / onTidyUp). useDesignHotkeys.ts keeps these
4268
+ // on LITERAL Control on every platform (never remapped to Cmd), so
4269
+ // this mirrors that exact gate instead of the generic "primary" flag
4270
+ // — a blanket "t" entry above would otherwise swallow the common
4271
+ // Cmd+T "new tab" browser shortcut for a combo the host never binds.
4272
+ (e.ctrlKey &&
4273
+ e.altKey &&
4274
+ !e.metaKey &&
4275
+ !e.shiftKey &&
4276
+ (normalized === "h" || normalized === "t"))
3744
4277
  );
3745
4278
  }
3746
4279
  if (
@@ -4164,8 +4697,64 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
4164
4697
  function openContextMenuAtEvent(e) {
4165
4698
  stopNativeInteraction(e);
4166
4699
  blurActiveTextEditor();
4167
- var target = elementFromEditorPoint(e.clientX, e.clientY);
4168
- if (!target && lastEditorPointWasBlocked) return;
4700
+ var shieldPointerEvents = shieldOverlay.style.pointerEvents;
4701
+ var selectionPointerEvents = selectionOverlay.style.pointerEvents;
4702
+ var highlightPointerEvents = highlightOverlay.style.pointerEvents;
4703
+ shieldOverlay.style.pointerEvents = "none";
4704
+ selectionOverlay.style.pointerEvents = "none";
4705
+ highlightOverlay.style.pointerEvents = "none";
4706
+ var pointTargets = document.elementsFromPoint
4707
+ ? document.elementsFromPoint(e.clientX, e.clientY)
4708
+ : [document.elementFromPoint(e.clientX, e.clientY)];
4709
+ shieldOverlay.style.pointerEvents = shieldPointerEvents;
4710
+ selectionOverlay.style.pointerEvents = selectionPointerEvents;
4711
+ highlightOverlay.style.pointerEvents = highlightPointerEvents;
4712
+
4713
+ var candidateElements: Element[] = [];
4714
+ var layerCandidates: Array<{
4715
+ key: string;
4716
+ label: string;
4717
+ info: unknown;
4718
+ }> = [];
4719
+ pointTargets.forEach(function (pointTarget) {
4720
+ if (!pointTarget || pointTarget.nodeType !== 1) return;
4721
+ if (isOverlayElement(pointTarget)) return;
4722
+ var candidate = selectionTargetForHit(pointTarget);
4723
+ if (
4724
+ !candidate ||
4725
+ isDocumentRootElement(candidate) ||
4726
+ isOverlayElement(candidate) ||
4727
+ isLayerInteractionBlocked(candidate) ||
4728
+ isTemplateCloneElement(candidate) ||
4729
+ candidateElements.indexOf(candidate) !== -1
4730
+ ) {
4731
+ return;
4732
+ }
4733
+ candidateElements.push(candidate);
4734
+ var candidateInfo = getElementInfo(candidate);
4735
+ var explicitLabel =
4736
+ (candidate.getAttribute &&
4737
+ candidate.getAttribute("data-agent-native-layer-name")) ||
4738
+ "";
4739
+ var textLabel = (candidate.textContent || "").trim().replace(/\s+/g, " ");
4740
+ var label =
4741
+ explicitLabel ||
4742
+ candidateInfo.componentName ||
4743
+ candidate.id ||
4744
+ (textLabel && textLabel.length <= 48 ? textLabel : "") ||
4745
+ candidate.tagName.toLowerCase();
4746
+ var identity =
4747
+ candidateInfo.sourceId ||
4748
+ candidateInfo.selector ||
4749
+ String(layerCandidates.length);
4750
+ layerCandidates.push({
4751
+ key: String(identity) + ":" + String(layerCandidates.length),
4752
+ label: String(label).slice(0, 80),
4753
+ info: candidateInfo,
4754
+ });
4755
+ });
4756
+
4757
+ var target = candidateElements[0] || null;
4169
4758
  var info = null;
4170
4759
  if (target) {
4171
4760
  selectedSpacingHovered = false;
@@ -4174,7 +4763,6 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
4174
4763
  if (selectedEl && !isLayerInteractionBlocked(selectedEl)) {
4175
4764
  info = getElementInfo(selectedEl);
4176
4765
  positionOverlay(selectionOverlay, selectedEl);
4177
- postElementSelect(selectedEl, e);
4178
4766
  } else {
4179
4767
  selectedEl = null;
4180
4768
  hideSelectionOverlay();
@@ -4183,9 +4771,11 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
4183
4771
  (window.parent as Window).postMessage(
4184
4772
  {
4185
4773
  type: "element-contextmenu",
4774
+ screenId: designCanvasScreenId,
4186
4775
  clientX: e.clientX,
4187
4776
  clientY: e.clientY,
4188
4777
  payload: info,
4778
+ layerCandidates: layerCandidates,
4189
4779
  },
4190
4780
  "*",
4191
4781
  );
@@ -4223,6 +4813,61 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
4223
4813
  return null;
4224
4814
  }
4225
4815
 
4816
+ // Host-driven Layers-panel moves must never inherit findRuntimeTarget's
4817
+ // selected-element shortcut or first-match querySelector behavior. Runtime
4818
+ // React trees routinely repeat classes and component markup, so a selector
4819
+ // is only safe when it identifies exactly one live element. Prefer the
4820
+ // runtime projection's stable source id and fall back to the selector only
4821
+ // when that id has no match at all.
4822
+ function findUniqueRuntimeStructureTarget(selector, sourceId) {
4823
+ var matches = new Set<Element>();
4824
+ if (typeof sourceId === "string" && sourceId) {
4825
+ var attributes = [
4826
+ "data-agent-native-node-id",
4827
+ "data-code-layer-id",
4828
+ "data-layer-id",
4829
+ "data-builder-id",
4830
+ "data-loc",
4831
+ "id",
4832
+ ];
4833
+ for (var i = 0; i < attributes.length; i += 1) {
4834
+ try {
4835
+ var sourceMatches = document.querySelectorAll(
4836
+ "[" + attributes[i] + '=\"' + escapeAttribute(sourceId) + '\"]',
4837
+ );
4838
+ for (var j = 0; j < sourceMatches.length; j += 1) {
4839
+ matches.add(sourceMatches[j]);
4840
+ }
4841
+ } catch (_err) {}
4842
+ }
4843
+ if (matches.size > 1) return null;
4844
+ if (matches.size === 1) {
4845
+ var sourceMatch = Array.from(matches)[0];
4846
+ return sourceMatch &&
4847
+ sourceMatch !== document.body &&
4848
+ sourceMatch !== document.documentElement &&
4849
+ !isOverlayElement(sourceMatch) &&
4850
+ !isLayerInteractionBlocked(sourceMatch)
4851
+ ? sourceMatch
4852
+ : null;
4853
+ }
4854
+ }
4855
+ if (typeof selector !== "string" || !selector) return null;
4856
+ try {
4857
+ var selectorMatches = document.querySelectorAll(selector);
4858
+ if (selectorMatches.length !== 1) return null;
4859
+ var selectorMatch = selectorMatches[0];
4860
+ return selectorMatch !== document.body &&
4861
+ selectorMatch !== document.documentElement &&
4862
+ !isOverlayElement(selectorMatch) &&
4863
+ !isLayerInteractionBlocked(selectorMatch)
4864
+ ? selectorMatch
4865
+ : null;
4866
+ } catch (_err) {
4867
+ return null;
4868
+ }
4869
+ }
4870
+
4226
4871
  function removeRuntimeTarget(selector, selectorCandidates) {
4227
4872
  var target = findRuntimeTarget(selector, selectorCandidates);
4228
4873
  if (
@@ -4243,7 +4888,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
4243
4888
  selectedEl = null;
4244
4889
  hideSelectionOverlay();
4245
4890
  }
4246
- hoveredEl = null;
4891
+ clearHoverGate();
4247
4892
  highlightOverlay.style.display = "none";
4248
4893
  hideMeasurements();
4249
4894
  refreshOverlays();
@@ -5368,7 +6013,12 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
5368
6013
  el.getAttribute("data-agent-native-primitive") ||
5369
6014
  ""
5370
6015
  ).toLowerCase();
5371
- if (primitive !== "rectangle" && primitive !== "rect") return false;
6016
+ if (
6017
+ primitive !== "rectangle" &&
6018
+ primitive !== "rect" &&
6019
+ primitive !== "frame"
6020
+ )
6021
+ return false;
5372
6022
  var cs = window.getComputedStyle(el);
5373
6023
  return cs.position === "absolute" || cs.position === "fixed";
5374
6024
  }
@@ -6020,6 +6670,125 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6020
6670
  };
6021
6671
  }
6022
6672
 
6673
+ /** Resolve a flow child's full Figma-style move target. The legacy reorder
6674
+ * resolver always fell back to the current parent/root, so a child could be
6675
+ * reordered but never dragged out of auto layout onto the freeform screen.
6676
+ * Keep ordinary flow insertion intact, but turn a root/background release
6677
+ * into an absolute-container move. Space preserves the current parent;
6678
+ * Control drops into an auto-layout parent as Ignore auto layout. */
6679
+ function flowMoveTargetForPoint(
6680
+ el,
6681
+ clientX,
6682
+ clientY,
6683
+ excludeEls,
6684
+ keepCurrentParent,
6685
+ ignoreTargetAutoLayout,
6686
+ ) {
6687
+ if (!el || !el.parentElement) return null;
6688
+ var currentParent = el.parentElement;
6689
+ var dragged: Element[] = [el].concat(excludeEls || []);
6690
+ var parentRect = currentParent.getBoundingClientRect();
6691
+ var pointerOutsideCurrentParent =
6692
+ clientX < parentRect.left ||
6693
+ clientX > parentRect.right ||
6694
+ clientY < parentRect.top ||
6695
+ clientY > parentRect.bottom;
6696
+
6697
+ if (keepCurrentParent && pointerOutsideCurrentParent) {
6698
+ var retainedSlot = nearestChildInsertionTarget(
6699
+ currentParent,
6700
+ clientX,
6701
+ clientY,
6702
+ dragged,
6703
+ );
6704
+ return (
6705
+ retainedSlot || {
6706
+ anchor: currentParent,
6707
+ placement: "inside",
6708
+ axis: parentFlowAxis(currentParent),
6709
+ dropMode: "flow-insert",
6710
+ }
6711
+ );
6712
+ }
6713
+
6714
+ var target = reorderTargetForPoint(el, clientX, clientY, excludeEls);
6715
+ var container = dropContainerForTarget(target);
6716
+
6717
+ // Figma Ignore auto layout: Control-drag into an auto-layout frame keeps
6718
+ // the new parent but excludes the object from its flow.
6719
+ if (
6720
+ ignoreTargetAutoLayout &&
6721
+ container &&
6722
+ container !== document.body &&
6723
+ isAutoLayoutElement(container)
6724
+ ) {
6725
+ return {
6726
+ anchor: container,
6727
+ placement: "inside",
6728
+ axis: parentFlowAxis(container),
6729
+ dropMode: "absolute-container",
6730
+ };
6731
+ }
6732
+
6733
+ // A target whose resolved container is body is the freeform screen/root,
6734
+ // not an auto-layout list. Reparent inside body and preserve the release
6735
+ // point with absolute positioning instead of inserting before/after a
6736
+ // top-level frame as another flow child.
6737
+ if (
6738
+ currentParent !== document.body &&
6739
+ (container === document.body ||
6740
+ container === document.documentElement ||
6741
+ target?.anchor === document.body)
6742
+ ) {
6743
+ return {
6744
+ anchor: document.body,
6745
+ placement: "inside",
6746
+ axis: "y",
6747
+ dropMode: "absolute-container",
6748
+ };
6749
+ }
6750
+
6751
+ // Flow child dropped into a plain container: match the absolute-drag path
6752
+ // by converting that container to auto layout before the structural move.
6753
+ if (
6754
+ target &&
6755
+ target.dropMode === "flow-insert" &&
6756
+ container &&
6757
+ container !== document.body &&
6758
+ isContainerDropTarget(container) &&
6759
+ !isAutoLayoutElement(container)
6760
+ ) {
6761
+ target.needsAutoLayoutConversion = true;
6762
+ target.conversionTarget = container;
6763
+ }
6764
+ return target;
6765
+ }
6766
+
6767
+ /** Apply Figma's Control-drag "Ignore auto layout" modifier to an
6768
+ * absolute/freeform drag target. The flow-origin path above already made
6769
+ * this conversion, but the ordinary absolute drag path used to ignore the
6770
+ * modifier and strip position/left/top on drop. Resolve to the auto-layout
6771
+ * container itself so the object keeps absolute positioning inside its new
6772
+ * parent, regardless of whether the pointer is over a child insertion slot
6773
+ * or the container background. */
6774
+ function ignoreAutoLayoutForDropTarget(target) {
6775
+ var container = dropContainerForTarget(target);
6776
+ if (
6777
+ !target ||
6778
+ !container ||
6779
+ container === document.body ||
6780
+ !isAutoLayoutElement(container)
6781
+ ) {
6782
+ return target;
6783
+ }
6784
+ return {
6785
+ anchor: container,
6786
+ placement: "inside",
6787
+ axis: parentFlowAxis(container),
6788
+ dropMode: "absolute-container",
6789
+ };
6790
+ }
6791
+
6023
6792
  // Accepts a single element or an array (group drags temporarily disable
6024
6793
  // pointer-events on EVERY dragged member so the hit test sees what's
6025
6794
  // underneath the whole group, not a sibling member riding along under the
@@ -6374,6 +7143,24 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6374
7143
  for (var i = 0; i < ABS_POSITION_INLINE_PROPS.length; i += 1) {
6375
7144
  htmlEl.style.removeProperty(ABS_POSITION_INLINE_PROPS[i]);
6376
7145
  }
7146
+ // Utility classes and authored stylesheets can still resolve the member
7147
+ // to absolute/fixed after the inline properties are removed. The host
7148
+ // strips those source declarations in the same structural history step,
7149
+ // but waiting for its in-place document round-trip leaves one rendered
7150
+ // frame where the optimistically reparented node is still absolute and
7151
+ // appears to jump or overlap. Override only that residual computed state
7152
+ // until the source replacement arrives. Inline-authored absolute nodes
7153
+ // naturally compute as static after the removal and keep the historical
7154
+ // empty-inline-style behavior.
7155
+ var afterRemoval = window.getComputedStyle(htmlEl).position;
7156
+ if (afterRemoval === "absolute" || afterRemoval === "fixed") {
7157
+ // An authored stylesheet may carry !important, so the optimistic
7158
+ // override must match that priority. Tell the host to persist the same
7159
+ // narrow override; otherwise its source round-trip would re-apply the
7160
+ // stylesheet and pop the newly-flowed child back out of auto layout.
7161
+ htmlEl.style.setProperty("position", "static", "important");
7162
+ target.forceFlowPositionOverride = true;
7163
+ }
6377
7164
  }
6378
7165
 
6379
7166
  // Absolute-container nest rebase: an "absolute-container" drop keeps the
@@ -6395,6 +7182,11 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6395
7182
  // (transform-inflated) bounding box.
6396
7183
  function rebaseAbsoluteMemberForContainerDrop(el, target): void {
6397
7184
  if (!el || !target || target.dropMode !== "absolute-container") return;
7185
+ // Flow children are prepared directly in the destination containing-block
7186
+ // coordinate space immediately before their DOM move. Rebasing those
7187
+ // coordinates as if they came from an old absolute containing block would
7188
+ // apply the parent-origin delta twice.
7189
+ if (target.absoluteCoordinatesPrepared) return;
6398
7190
  var container = dropContainerForTarget(target);
6399
7191
  if (!container || container === document.body || container === el) return;
6400
7192
  if (el.contains && el.contains(container)) return;
@@ -6405,12 +7197,20 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6405
7197
  // coordinates (border box + border widths − its own scroll offsets).
6406
7198
  var containerRect = container.getBoundingClientRect();
6407
7199
  var containerCS = window.getComputedStyle(container);
7200
+ var boardOffsetX = designCanvasBoardSurface
7201
+ ? designCanvasContentOffsetX
7202
+ : 0;
7203
+ var boardOffsetY = designCanvasBoardSurface
7204
+ ? designCanvasContentOffsetY
7205
+ : 0;
6408
7206
  var newOriginX =
6409
- containerRect.left +
7207
+ containerRect.left -
7208
+ boardOffsetX +
6410
7209
  readPx(containerCS.borderLeftWidth) -
6411
7210
  container.scrollLeft;
6412
7211
  var newOriginY =
6413
- containerRect.top +
7212
+ containerRect.top -
7213
+ boardOffsetY +
6414
7214
  readPx(containerCS.borderTopWidth) -
6415
7215
  container.scrollTop;
6416
7216
  // Current containing block origin: the member's offsetParent when it is
@@ -6432,10 +7232,30 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6432
7232
  if (offsetParentIsRealContainingBlock) {
6433
7233
  var opRect = offsetParent.getBoundingClientRect();
6434
7234
  var opCS = window.getComputedStyle(offsetParent);
7235
+ // The finite board offset is applied to `body > [data-node-id]`, not
7236
+ // to body itself. A top-level member whose containing block is the
7237
+ // positioned body therefore still has a true origin of 0; subtracting
7238
+ // the render offset here poisoned a frame nest by exactly one chunk
7239
+ // (for example -4096px). Nested containing blocks do inherit the
7240
+ // translated top-level visual space, so only those need normalization.
7241
+ var oldContainingBlockOffsetX =
7242
+ designCanvasBoardSurface && offsetParent !== document.body
7243
+ ? boardOffsetX
7244
+ : 0;
7245
+ var oldContainingBlockOffsetY =
7246
+ designCanvasBoardSurface && offsetParent !== document.body
7247
+ ? boardOffsetY
7248
+ : 0;
6435
7249
  oldOriginX =
6436
- opRect.left + readPx(opCS.borderLeftWidth) - offsetParent.scrollLeft;
7250
+ opRect.left -
7251
+ oldContainingBlockOffsetX +
7252
+ readPx(opCS.borderLeftWidth) -
7253
+ offsetParent.scrollLeft;
6437
7254
  oldOriginY =
6438
- opRect.top + readPx(opCS.borderTopWidth) - offsetParent.scrollTop;
7255
+ opRect.top -
7256
+ oldContainingBlockOffsetY +
7257
+ readPx(opCS.borderTopWidth) -
7258
+ offsetParent.scrollTop;
6439
7259
  }
6440
7260
  }
6441
7261
  var currentLeft = readPx(htmlEl.style.left || cs.left);
@@ -6444,6 +7264,59 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6444
7264
  htmlEl.style.top = currentTop + (oldOriginY - newOriginY) + "px";
6445
7265
  }
6446
7266
 
7267
+ /** Convert flow members to absolute positioning at their drag release point
7268
+ * before moving them into a freeform root/absolute container. The DOM move
7269
+ * happens synchronously in the same event turn, so no intermediate frame is
7270
+ * painted; postVisualStructureChange then measures the final geometry for
7271
+ * the source persistence/undo entry. */
7272
+ function prepareFlowMembersForAbsoluteDrop(
7273
+ members: Element[],
7274
+ target,
7275
+ startRects: DOMRect[],
7276
+ gestureStartRect: DOMRect,
7277
+ pointerOffset: { x: number; y: number },
7278
+ clientX: number,
7279
+ clientY: number,
7280
+ ): void {
7281
+ if (!target || target.dropMode !== "absolute-container") return;
7282
+ var container = dropContainerForTarget(target);
7283
+ if (!container) return;
7284
+ var containerRect = container.getBoundingClientRect();
7285
+ var containerCS = window.getComputedStyle(container);
7286
+ var containerIsBodyContainingBlock = true;
7287
+ if (container === document.body) {
7288
+ containerIsBodyContainingBlock =
7289
+ containerCS.position !== "static" ||
7290
+ containerCS.transform !== "none" ||
7291
+ (containerCS.getPropertyValue("translate") || "none") !== "none";
7292
+ }
7293
+ var originX = containerIsBodyContainingBlock
7294
+ ? containerRect.left +
7295
+ readPx(containerCS.borderLeftWidth) -
7296
+ container.scrollLeft
7297
+ : -(window.scrollX || 0);
7298
+ var originY = containerIsBodyContainingBlock
7299
+ ? containerRect.top +
7300
+ readPx(containerCS.borderTopWidth) -
7301
+ container.scrollTop
7302
+ : -(window.scrollY || 0);
7303
+ var desiredGestureLeft = clientX - pointerOffset.x;
7304
+ var desiredGestureTop = clientY - pointerOffset.y;
7305
+ var deltaX = desiredGestureLeft - gestureStartRect.left;
7306
+ var deltaY = desiredGestureTop - gestureStartRect.top;
7307
+
7308
+ members.forEach(function (member, index) {
7309
+ var startRect = startRects[index] || member.getBoundingClientRect();
7310
+ var htmlEl = member as HTMLElement;
7311
+ htmlEl.style.position = "absolute";
7312
+ htmlEl.style.left = Math.round(startRect.left + deltaX - originX) + "px";
7313
+ htmlEl.style.top = Math.round(startRect.top + deltaY - originY) + "px";
7314
+ htmlEl.style.removeProperty("right");
7315
+ htmlEl.style.removeProperty("bottom");
7316
+ });
7317
+ target.absoluteCoordinatesPrepared = true;
7318
+ }
7319
+
6447
7320
  function applyRuntimeReorder(el, target) {
6448
7321
  if (!el || !target || !target.anchor || !target.anchor.parentElement)
6449
7322
  return;
@@ -6487,9 +7360,11 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6487
7360
  anchorSourceId: getSourceId(target.anchor),
6488
7361
  placement: target.placement,
6489
7362
  dropMode: target.dropMode || "flow-insert",
7363
+ forceFlowPositionOverride: Boolean(target.forceFlowPositionOverride),
6490
7364
  sourceRect: rectInfoForElement(el),
6491
7365
  anchorRect: rectInfoForElement(target.anchor),
6492
7366
  payload: getElementInfo(el),
7367
+ anchorPayload: getElementInfo(target.anchor),
6493
7368
  },
6494
7369
  "*",
6495
7370
  );
@@ -6756,7 +7631,11 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6756
7631
  // pointer went down on when this drag preserves a 2+ selection instead of
6757
7632
  // collapsing to one element (see beginPotentialShieldDrag's group branch).
6758
7633
  // Defaults to selectedEl — the selection-overlay drag path.
6759
- function startMove(e, gestureElParam?: Element) {
7634
+ function startMove(
7635
+ e,
7636
+ gestureElParam?: Element,
7637
+ pointerStartParam?: { clientX: number; clientY: number },
7638
+ ) {
6760
7639
  var gestureEl = gestureElParam || selectedEl;
6761
7640
  if (!gestureEl) return;
6762
7641
  if (isLayerInteractionBlocked(gestureEl)) return;
@@ -6870,11 +7749,18 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6870
7749
  // Snapshot the element being reordered so a concurrent select-element or
6871
7750
  // clear-selection postMessage cannot mutate the wrong element mid-drag.
6872
7751
  var reorderEl = gestureEl;
6873
- var currentTarget = reorderTargetForPoint(
7752
+ var reorderGroupStartRects = groupEls.map(function (member) {
7753
+ return member.getBoundingClientRect();
7754
+ });
7755
+ var reorderGestureStartRect = reorderEl.getBoundingClientRect();
7756
+ var keepCurrentFlowParent = bridgeSpaceKeyPressed;
7757
+ var currentTarget = flowMoveTargetForPoint(
6874
7758
  reorderEl,
6875
7759
  e.clientX,
6876
7760
  e.clientY,
6877
7761
  groupOthers,
7762
+ keepCurrentFlowParent,
7763
+ Boolean(e.ctrlKey),
6878
7764
  );
6879
7765
  showInsertionGuideFor(currentTarget);
6880
7766
  // Cross-screen drag state: true when the pointer is outside this iframe's
@@ -6885,9 +7771,10 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6885
7771
  var reorderSourceId = getSourceId(reorderEl);
6886
7772
  var reorderStyleSnapshot = collectPortableStyleSnapshot(reorderEl);
6887
7773
  var reorderRect = reorderEl.getBoundingClientRect();
7774
+ var reorderPointerStart = pointerStartParam || e;
6888
7775
  var reorderPointerOffset = {
6889
- x: e.clientX - reorderRect.left,
6890
- y: e.clientY - reorderRect.top,
7776
+ x: reorderPointerStart.clientX - reorderRect.left,
7777
+ y: reorderPointerStart.clientY - reorderRect.top,
6891
7778
  };
6892
7779
  function onReorderMove(ev) {
6893
7780
  var vw = window.innerWidth;
@@ -6924,7 +7811,14 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6924
7811
  showTransformBadge("Move layer", cx, cy);
6925
7812
  } else {
6926
7813
  // Cursor is inside this iframe — use existing in-iframe behavior.
6927
- currentTarget = reorderTargetForPoint(reorderEl, cx, cy, groupOthers);
7814
+ currentTarget = flowMoveTargetForPoint(
7815
+ reorderEl,
7816
+ cx,
7817
+ cy,
7818
+ groupOthers,
7819
+ keepCurrentFlowParent,
7820
+ Boolean(ev.ctrlKey),
7821
+ );
6928
7822
  showInsertionGuideFor(currentTarget);
6929
7823
  showTransformBadge(currentTarget ? "Move layer" : "Move", cx, cy);
6930
7824
  }
@@ -6933,6 +7827,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6933
7827
  document.removeEventListener(events.move, onReorderMove, true);
6934
7828
  document.removeEventListener(events.up, onReorderUp, true);
6935
7829
  document.removeEventListener("keydown", onReorderKeyDown, true);
7830
+ document.removeEventListener("keyup", onReorderKeyUp, true);
6936
7831
  clearActiveDragCancel(onReorderEscape);
6937
7832
  }
6938
7833
  function onReorderEscape() {
@@ -6959,11 +7854,21 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
6959
7854
  return true;
6960
7855
  }
6961
7856
  function onReorderKeyDown(ev) {
7857
+ if (ev.code === "Space" || ev.key === " ") {
7858
+ keepCurrentFlowParent = true;
7859
+ ev.preventDefault();
7860
+ return;
7861
+ }
6962
7862
  if (ev.key === "Escape") {
6963
7863
  stopNativeInteraction(ev);
6964
7864
  onReorderEscape();
6965
7865
  }
6966
7866
  }
7867
+ function onReorderKeyUp(ev) {
7868
+ if (ev.code !== "Space" && ev.key !== " ") return;
7869
+ keepCurrentFlowParent = false;
7870
+ ev.preventDefault();
7871
+ }
6967
7872
  function onReorderUp(ev) {
6968
7873
  cleanupReorderDrag();
6969
7874
  hideTransformBadge();
@@ -7003,6 +7908,14 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7003
7908
  // already clears cross-screen state on re-entry so checking the
7004
7909
  // momentary excursion flag here would wrongly drop the element nowhere.
7005
7910
  if (outsideOnDrop) return;
7911
+ currentTarget = flowMoveTargetForPoint(
7912
+ reorderEl,
7913
+ cx,
7914
+ cy,
7915
+ groupOthers,
7916
+ keepCurrentFlowParent,
7917
+ Boolean(ev?.ctrlKey),
7918
+ );
7006
7919
  if (!currentTarget) {
7007
7920
  // No valid drop target — clean up the clone if one was inserted so
7008
7921
  // no ghost element is left in the DOM.
@@ -7019,6 +7932,24 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7019
7932
  }
7020
7933
  return;
7021
7934
  }
7935
+ if (
7936
+ currentTarget.needsAutoLayoutConversion &&
7937
+ currentTarget.conversionTarget
7938
+ ) {
7939
+ applyAutoLayoutConversionForDrop(
7940
+ currentTarget.conversionTarget,
7941
+ groupEls,
7942
+ );
7943
+ }
7944
+ prepareFlowMembersForAbsoluteDrop(
7945
+ groupEls,
7946
+ currentTarget,
7947
+ reorderGroupStartRects,
7948
+ reorderGestureStartRect,
7949
+ reorderPointerOffset,
7950
+ cx,
7951
+ cy,
7952
+ );
7022
7953
  if (duplicatedForDrag) {
7023
7954
  applyRuntimeReorder(reorderEl, currentTarget);
7024
7955
  postVisualDuplicateChange(
@@ -7058,6 +7989,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7058
7989
  document.addEventListener(events.move, onReorderMove, true);
7059
7990
  document.addEventListener(events.up, onReorderUp, true);
7060
7991
  document.addEventListener("keydown", onReorderKeyDown, true);
7992
+ document.addEventListener("keyup", onReorderKeyUp, true);
7061
7993
  setActiveDragCancel(onReorderEscape);
7062
7994
  return;
7063
7995
  }
@@ -7120,6 +8052,34 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7120
8052
  if (!duplicatedForDrag && !isGroupDrag) {
7121
8053
  postCrossScreenDrag("start", dragEl, e);
7122
8054
  }
8055
+ // rAF-coalesce the "move" phase postMessage: a raw mousemove/pointermove
8056
+ // stream can fire well above 60/s on a high-poll-rate mouse or trackpad,
8057
+ // and every tick of this handler already recomputes a getBoundingClientRect
8058
+ // for postCrossScreenDrag — batching to one postMessage per animation
8059
+ // frame matches the parent's own equivalent coalescing (see
8060
+ // MultiScreenCanvas.tsx's rAF-batched cross-screen hit-test) without
8061
+ // changing what gets sent, only how often. cleanupMoveDrag cancels any
8062
+ // still-pending tick so a stale "move" can never post after "end"/"cancel".
8063
+ var crossScreenDragMoveScheduled = false;
8064
+ var crossScreenDragMovePendingEv: {
8065
+ clientX: number;
8066
+ clientY: number;
8067
+ } | null = null;
8068
+ function flushCrossScreenDragMove() {
8069
+ crossScreenDragMoveScheduled = false;
8070
+ var pendingEv = crossScreenDragMovePendingEv;
8071
+ crossScreenDragMovePendingEv = null;
8072
+ if (pendingEv) postCrossScreenDrag("move", dragEl, pendingEv);
8073
+ }
8074
+ function scheduleCrossScreenDragMove(ev): void {
8075
+ crossScreenDragMovePendingEv = {
8076
+ clientX: ev.clientX,
8077
+ clientY: ev.clientY,
8078
+ };
8079
+ if (crossScreenDragMoveScheduled) return;
8080
+ crossScreenDragMoveScheduled = true;
8081
+ window.requestAnimationFrame(flushCrossScreenDragMove);
8082
+ }
7123
8083
  function onMove(ev) {
7124
8084
  if (
7125
8085
  !moved &&
@@ -7174,7 +8134,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7174
8134
  state.el.style.top = Math.round(state.originTop + appliedDy) + "px";
7175
8135
  });
7176
8136
  if (!duplicatedForDrag && !isGroupDrag) {
7177
- postCrossScreenDrag("move", dragEl, ev);
8137
+ scheduleCrossScreenDragMove(ev);
7178
8138
  }
7179
8139
  if (
7180
8140
  !duplicatedForDrag &&
@@ -7184,14 +8144,20 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7184
8144
  hideInsertionGuide();
7185
8145
  setMembersOpacity(null);
7186
8146
  } else {
7187
- currentAutoLayoutTarget = !duplicatedForDrag
7188
- ? autoLayoutInsertionTargetForPoint(
7189
- dragEl,
7190
- ev.clientX,
7191
- ev.clientY,
7192
- groupOthers,
7193
- )
7194
- : null;
8147
+ currentAutoLayoutTarget =
8148
+ !duplicatedForDrag && !bridgeSpaceKeyPressed
8149
+ ? autoLayoutInsertionTargetForPoint(
8150
+ dragEl,
8151
+ ev.clientX,
8152
+ ev.clientY,
8153
+ groupOthers,
8154
+ )
8155
+ : null;
8156
+ if (currentAutoLayoutTarget && ev.ctrlKey) {
8157
+ currentAutoLayoutTarget = ignoreAutoLayoutForDropTarget(
8158
+ currentAutoLayoutTarget,
8159
+ );
8160
+ }
7195
8161
  if (currentAutoLayoutTarget) {
7196
8162
  showInsertionGuideFor(currentAutoLayoutTarget);
7197
8163
  setMembersOpacity("0.4");
@@ -7235,6 +8201,10 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7235
8201
  document.removeEventListener(events.up, onUp, true);
7236
8202
  document.removeEventListener("keydown", onMoveKeyDown, true);
7237
8203
  clearActiveDragCancel(cancelMoveDrag);
8204
+ // Drop any rAF-scheduled "move" tick so it can never fire and post
8205
+ // after this gesture's "end"/"cancel" phase has already gone out.
8206
+ crossScreenDragMoveScheduled = false;
8207
+ crossScreenDragMovePendingEv = null;
7238
8208
  }
7239
8209
  function cancelMoveDrag() {
7240
8210
  cleanupMoveDrag();
@@ -7286,16 +8256,32 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7286
8256
  restoreSourceDragPosition();
7287
8257
  return;
7288
8258
  }
7289
- if (ev && !duplicatedForDrag && !outsideOnDrop) {
8259
+ if (
8260
+ ev &&
8261
+ !duplicatedForDrag &&
8262
+ !outsideOnDrop &&
8263
+ !bridgeSpaceKeyPressed
8264
+ ) {
7290
8265
  var finalAutoLayoutTarget = autoLayoutInsertionTargetForPoint(
7291
8266
  dragEl,
7292
8267
  ev.clientX,
7293
8268
  ev.clientY,
7294
8269
  groupOthers,
7295
8270
  );
8271
+ if (finalAutoLayoutTarget && ev.ctrlKey) {
8272
+ finalAutoLayoutTarget = ignoreAutoLayoutForDropTarget(
8273
+ finalAutoLayoutTarget,
8274
+ );
8275
+ }
7296
8276
  if (finalAutoLayoutTarget) {
7297
8277
  currentAutoLayoutTarget = finalAutoLayoutTarget;
7298
8278
  }
8279
+ } else if (bridgeSpaceKeyPressed) {
8280
+ // Space is Figma's retain-parent modifier. Absolute/freeform drags
8281
+ // already move in their current containing-block coordinates, so
8282
+ // suppressing the nest target keeps the existing parent while still
8283
+ // committing the new left/top in one style change.
8284
+ currentAutoLayoutTarget = null;
7299
8285
  }
7300
8286
  if (duplicatedForDrag && !moved) {
7301
8287
  // Alt-click with no real drag — remove the premature clone and restore the original selection.
@@ -7414,11 +8400,24 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7414
8400
  var originalInlineFontSize = resizeEl.style.fontSize;
7415
8401
  ensurePositionable(resizeEl);
7416
8402
  var cs = window.getComputedStyle(resizeEl);
7417
- // Bug fix: use CSS width/height (not getBoundingClientRect) for the resize
7418
- // origin dimensions so that rotated elements don't use the inflated
7419
- // axis-aligned bounding box as the starting size.
7420
- var originW = readPx(resizeEl.style.width || cs.width);
7421
- var originH = readPx(resizeEl.style.height || cs.height);
8403
+ // Bug fix: use COMPUTED width/height (never the raw inline style string)
8404
+ // for the resize origin dimensions. Two distinct hazards, one fix:
8405
+ // 1. Rotated elements getBoundingClientRect() returns the inflated
8406
+ // axis-aligned bounding box of the rotated box, not its own
8407
+ // width/height, so it can't seed the origin either.
8408
+ // 2. Non-px inline values — an inline style of "100%" / "50vw" / "2rem"
8409
+ // / "auto" / "calc(...)" is NOT a pixel measurement. Reading
8410
+ // `resizeEl.style.width` directly and running it through
8411
+ // parseFloat (readPx) previously parsed "100%" as the number 100
8412
+ // and treated it as 100PX, so a +50px drag produced 150px instead
8413
+ // of correctly growing from the ~358px the element actually
8414
+ // rendered at. getComputedStyle always resolves to the element's
8415
+ // used-value size in px regardless of the authored unit (and is
8416
+ // unaffected by a rotate transform, unlike getBoundingClientRect),
8417
+ // so it's the one source that's simultaneously rotation-safe and
8418
+ // unit-agnostic.
8419
+ var originW = readPx(cs.width);
8420
+ var originH = readPx(cs.height);
7422
8421
  // K-scale (Figma "Scale" tool) parity: capture the element's own border
7423
8422
  // width and font size once at drag-start so a uniform per-tick scale
7424
8423
  // factor (derived from width/height growth, see nextRect below) can
@@ -7448,6 +8447,18 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7448
8447
  // Capture the element rotation once at drag-start so per-move projection is
7449
8448
  // cheap and consistent even if the transform changes during the drag.
7450
8449
  var resizeTheta = (currentRotation(resizeEl) * Math.PI) / 180;
8450
+ // Figma commit-semantics parity: a resize must only ever write the
8451
+ // axis/axes the user actually dragged. A pure vertical edge-drag (handle
8452
+ // "n"/"s", no Shift, scale tool off) must leave width completely alone —
8453
+ // e.g. a `width: 100%` element stays percentage-based after a
8454
+ // height-only resize instead of being silently pinned to a px value the
8455
+ // user never touched. These accumulate for the life of the gesture (once
8456
+ // an axis is touched — including transiently, e.g. Shift held mid-drag
8457
+ // then released — it stays "touched" for this gesture's commit) and
8458
+ // gate both the live style writes in onMove and the committed style keys
8459
+ // in onUp.
8460
+ var widthTouched = false;
8461
+ var heightTouched = false;
7451
8462
  function nextRect(ev) {
7452
8463
  var screenDx = ev.clientX - startX;
7453
8464
  var screenDy = ev.clientY - startY;
@@ -7525,15 +8536,42 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7525
8536
  if (handle.indexOf("n") !== -1 || handle.indexOf("s") !== -1)
7526
8537
  top = origin.top - (height - origin.height) / 2;
7527
8538
  }
7528
- return { left: left, top: top, width: width, height: height };
8539
+ // Which axis/axes this handle actually drives: the handle's own
8540
+ // letters directly touch their axis; an active aspect-ratio lock
8541
+ // (Shift or the K-scale tool) additionally derives the OTHER axis from
8542
+ // the touched one for a single-axis edge handle (e.g. dragging "n"
8543
+ // with Shift held also changes width to hold the ratio). Two-letter
8544
+ // corner handles already touch both axes regardless of the lock.
8545
+ var handlesWidth =
8546
+ handle.indexOf("w") !== -1 || handle.indexOf("e") !== -1;
8547
+ var handlesHeight =
8548
+ handle.indexOf("n") !== -1 || handle.indexOf("s") !== -1;
8549
+ var aspectLocked = !!(ev.shiftKey || scaleToolEnabled);
8550
+ var touchesWidth = handlesWidth || (aspectLocked && handlesHeight);
8551
+ var touchesHeight = handlesHeight || (aspectLocked && handlesWidth);
8552
+ return {
8553
+ left: left,
8554
+ top: top,
8555
+ width: width,
8556
+ height: height,
8557
+ touchesWidth: touchesWidth,
8558
+ touchesHeight: touchesHeight,
8559
+ };
7529
8560
  }
7530
8561
  function onMove(ev) {
7531
8562
  if (!resizeEl) return;
7532
8563
  var rect = nextRect(ev);
8564
+ if (rect.touchesWidth) widthTouched = true;
8565
+ if (rect.touchesHeight) heightTouched = true;
7533
8566
  resizeEl.style.left = Math.round(rect.left) + "px";
7534
8567
  resizeEl.style.top = Math.round(rect.top) + "px";
7535
- resizeEl.style.width = Math.round(rect.width) + "px";
7536
- resizeEl.style.height = Math.round(rect.height) + "px";
8568
+ // Only write width/height for an axis this gesture actually touched —
8569
+ // writing the untouched axis every tick (even to its own unchanged
8570
+ // origin value) would silently convert e.g. `width: 100%` to a px
8571
+ // value on a pure vertical drag, which is exactly the "shrank instead
8572
+ // of preserved" class of bug this fixes.
8573
+ if (widthTouched) resizeEl.style.width = Math.round(rect.width) + "px";
8574
+ if (heightTouched) resizeEl.style.height = Math.round(rect.height) + "px";
7537
8575
  if (scaleToolEnabled) {
7538
8576
  // Uniform scale factor: scaleToolEnabled already forces the
7539
8577
  // aspect-ratio lock above (nextRect), so width/origin.width and
@@ -7597,9 +8635,14 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7597
8635
  position: resizeEl.style.position,
7598
8636
  left: resizeEl.style.left,
7599
8637
  top: resizeEl.style.top,
7600
- width: resizeEl.style.width,
7601
- height: resizeEl.style.height,
7602
8638
  };
8639
+ // Only commit width/height for an axis this gesture actually touched
8640
+ // (see widthTouched/heightTouched above) — a pure vertical or
8641
+ // horizontal edge-drag must not also commit a px value for the axis
8642
+ // the user never dragged, which would silently convert e.g.
8643
+ // `width: 100%` to a fixed px width.
8644
+ if (widthTouched) styles.width = resizeEl.style.width;
8645
+ if (heightTouched) styles.height = resizeEl.style.height;
7603
8646
  // Only include borderWidth/fontSize when the K-scale tool actually
7604
8647
  // changed them (originBorderWidth/originFontSize > 0 AND
7605
8648
  // scaleToolEnabled) — a normal resize must never introduce these keys,
@@ -7804,12 +8847,15 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7804
8847
  collectMoveGroupMembers(groupGestureMember).length > 1
7805
8848
  ) {
7806
8849
  suppressNextShieldClickBriefly();
7807
- startMove(ev, groupGestureMember);
8850
+ startMove(ev, groupGestureMember, {
8851
+ clientX: startX,
8852
+ clientY: startY,
8853
+ });
7808
8854
  return;
7809
8855
  }
7810
8856
  selectTarget(dragTarget, ev);
7811
8857
  suppressNextShieldClickBriefly();
7812
- startMove(ev);
8858
+ startMove(ev, undefined, { clientX: startX, clientY: startY });
7813
8859
  }
7814
8860
  function onUp(ev) {
7815
8861
  clearPendingShieldDrag();
@@ -7941,6 +8987,19 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
7941
8987
  document.addEventListener(
7942
8988
  "keydown",
7943
8989
  function (e) {
8990
+ if (
8991
+ e.key === " " &&
8992
+ e.code === "Space" &&
8993
+ !activeTextEditEl &&
8994
+ !isEditorTypingTarget(e.target)
8995
+ ) {
8996
+ bridgeSpaceKeyPressed = true;
8997
+ if (activeDragCancel) {
8998
+ bridgeSpaceKeyConsumedByDrag = true;
8999
+ stopNativeInteraction(e);
9000
+ return;
9001
+ }
9002
+ }
7944
9003
  // T25: pending-window keydown routing — a begin-text-edit is still
7945
9004
  // waiting for its node. Keystrokes that land in THIS document during
7946
9005
  // the wait belong to the upcoming text session: buffer printable
@@ -8086,6 +9145,12 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
8086
9145
  "keyup",
8087
9146
  function (e) {
8088
9147
  if (e.key !== " " || e.code !== "Space") return;
9148
+ bridgeSpaceKeyPressed = false;
9149
+ if (bridgeSpaceKeyConsumedByDrag) {
9150
+ bridgeSpaceKeyConsumedByDrag = false;
9151
+ stopNativeInteraction(e);
9152
+ return;
9153
+ }
8089
9154
  if (activeTextEditEl || isEditorTypingTarget(e.target)) return;
8090
9155
  stopNativeInteraction(e);
8091
9156
  (window.parent as Window).postMessage(
@@ -8317,6 +9382,45 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
8317
9382
  }
8318
9383
  return;
8319
9384
  }
9385
+ // Template-clone text-edit rejection (mirrors the reorder-rejection fix
9386
+ // above): `target` here can be a raw Alpine `<template x-for>`/`x-if`
9387
+ // runtime clone — findTextEditTarget's climb only requires every
9388
+ // descendant tag to be inline-editable, it never checks whether the
9389
+ // ancestor chain crosses a clone boundary, so a repeated list/card item
9390
+ // whose own content is plain text passes straight through. Entering
9391
+ // contenteditable on that clone lets the user type and see the change
9392
+ // live (it's a real DOM node), but the clone has no per-instance
9393
+ // counterpart in the static source HTML (only the single `<template>`
9394
+ // stays there), so getSelector's live-DOM nth-of-type path — unlike
9395
+ // hit-test.bridge.ts's source-equivalent selector builder — cannot
9396
+ // resolve it on commit. Previously the edit silently failed on the host
9397
+ // (error toast, or a no-op) and vanished for good on the next Alpine
9398
+ // re-render, with no indication anything was wrong. Reject up front
9399
+ // instead — same UX contract as the reorder-rejection badge — and fall
9400
+ // back to selecting the nearest source-backed ancestor (typically the
9401
+ // repeated item's container) so the user isn't left with a stale or
9402
+ // empty selection.
9403
+ if (!programmaticFlag && isTemplateCloneElement(target)) {
9404
+ showRejectedDragBadge(
9405
+ "Can't edit repeated items directly",
9406
+ e.clientX,
9407
+ e.clientY,
9408
+ );
9409
+ // No ongoing gesture here (unlike the reorder-rejection badge, which
9410
+ // hides on the drag's own mouseup/Escape) to hide it on, so time it
9411
+ // out on its own instead of leaving it on screen indefinitely.
9412
+ window.setTimeout(hideTransformBadge, 1400);
9413
+ var rejectedTextEditFallback = selectionTargetForHit(target);
9414
+ if (
9415
+ rejectedTextEditFallback &&
9416
+ !isLayerInteractionBlocked(rejectedTextEditFallback)
9417
+ ) {
9418
+ selectedEl = rejectedTextEditFallback;
9419
+ positionOverlay(selectionOverlay, selectedEl);
9420
+ postElementSelect(selectedEl, e);
9421
+ }
9422
+ return;
9423
+ }
8320
9424
  // Anchor the selection identity to the nearest source-backed element. Text
8321
9425
  // editing still operates on the actual target text node, but a later
8322
9426
  // style edit posts from selectedEl, so it must point at a patchable
@@ -8700,6 +9804,12 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
8700
9804
  scheduleSpacingHoverClear(e);
8701
9805
  }
8702
9806
  hideMeasurements();
9807
+ // Re-arm the hover-info post gate below: leaving all content (e.g.
9808
+ // pointer over empty canvas or off the iframe entirely) means the
9809
+ // NEXT element this pointer lands on — even if it's the same one
9810
+ // hovered before — is a genuinely new hover the host hasn't heard
9811
+ // about since.
9812
+ lastHoverInfoPostedEl = null;
8703
9813
  return;
8704
9814
  }
8705
9815
  if (hoveredEl && hoveredEl.closest("[data-agent-native-text-editing]"))
@@ -8743,11 +9853,14 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
8743
9853
  } else {
8744
9854
  hideMeasurements();
8745
9855
  }
8746
- var info = getLightElementInfo(hoveredEl);
8747
- (window.parent as Window).postMessage(
8748
- { type: "element-hover", payload: info },
8749
- "*",
8750
- );
9856
+ if (hoveredEl !== lastHoverInfoPostedEl) {
9857
+ lastHoverInfoPostedEl = hoveredEl;
9858
+ var info = getLightElementInfo(hoveredEl);
9859
+ (window.parent as Window).postMessage(
9860
+ { type: "element-hover", payload: info },
9861
+ "*",
9862
+ );
9863
+ }
8751
9864
  },
8752
9865
  true,
8753
9866
  );
@@ -8781,7 +9894,11 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
8781
9894
  updateSpacingOverlay(selectedEl);
8782
9895
  return;
8783
9896
  }
8784
- hoveredEl = null;
9897
+ // Leaving the iframe entirely (e.g. to a panel or outside the window)
9898
+ // must re-arm the post gate, not just clear hoveredEl — otherwise the
9899
+ // pointer returning to this SAME element never re-posts "element-hover"
9900
+ // and the host's hover highlight stays stuck at "nothing".
9901
+ clearHoverGate();
8785
9902
  if (!spacingDrag) {
8786
9903
  scheduleSpacingHoverClear(e);
8787
9904
  }
@@ -8858,6 +9975,17 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
8858
9975
  }
8859
9976
  return;
8860
9977
  }
9978
+ if (e.data.type === "set-content-offset") {
9979
+ var nextContentOffsetX = Number(e.data.x);
9980
+ var nextContentOffsetY = Number(e.data.y);
9981
+ designCanvasContentOffsetX = Number.isFinite(nextContentOffsetX)
9982
+ ? nextContentOffsetX
9983
+ : 0;
9984
+ designCanvasContentOffsetY = Number.isFinite(nextContentOffsetY)
9985
+ ? nextContentOffsetY
9986
+ : 0;
9987
+ return;
9988
+ }
8861
9989
  // begin-text-edit: enter text-editing mode for the element identified by
8862
9990
  // nodeId immediately (no double-click needed). Used after programmatic
8863
9991
  // text-element creation so the user can type right away and the autosize
@@ -9165,13 +10293,28 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
9165
10293
  // whenever the cursor RESTED on a handle (the user only ever saw the
9166
10294
  // badge flash, i.e. "the value box never shows"). A same-element
9167
10295
  // replay must be a no-op for hover state.
9168
- if (target !== selectedEl) {
10296
+ var selectionChangedByHost = target !== selectedEl;
10297
+ if (selectionChangedByHost) {
9169
10298
  selectedSpacingHovered = false;
9170
10299
  hoveredSpacingHandleKey = "";
9171
10300
  }
9172
10301
  selectedEl = target;
9173
10302
  positionOverlay(selectionOverlay, target);
9174
10303
  if (hoveredEl === selectedEl) highlightOverlay.style.display = "none";
10304
+ // A host-driven selection (e.g. picking a layer in the Layers panel)
10305
+ // only ever moved the overlay above — it never sent the rich
10306
+ // getElementInfo() payload (computedStyles, portableStyleSnapshot,
10307
+ // gradients, etc.) back up, unlike pointer-driven selection which
10308
+ // calls postElementSelect() immediately. The properties panel's
10309
+ // "element-select" listener was therefore left with whatever payload
10310
+ // (or lack of one) it already had, which surfaced as an empty Fill
10311
+ // section and zeroed-out layout fields right after a Layers-panel
10312
+ // click even though the same element selected by clicking the canvas
10313
+ // rendered correctly. Post the full payload on genuine selection
10314
+ // changes only — the `selectionChangedByHost` guard above already
10315
+ // keeps this a no-op on the ~1-2s poll-tick replay so it can't turn
10316
+ // into a message-spam loop.
10317
+ if (selectionChangedByHost) postElementSelect(target);
9175
10318
  return;
9176
10319
  }
9177
10320
  if (e.data.type === "hover-element") {
@@ -9194,7 +10337,7 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
9194
10337
  hoverCandidates.push(String(e.data.selector));
9195
10338
  }
9196
10339
  if (hoverCandidates.length === 0) {
9197
- hoveredEl = null;
10340
+ clearHoverGate();
9198
10341
  highlightOverlay.style.display = "none";
9199
10342
  hideMeasurements();
9200
10343
  return;
@@ -9232,12 +10375,72 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
9232
10375
  hideSelectionOverlay();
9233
10376
  }
9234
10377
  if (hoveredEl && isLayerInteractionBlocked(hoveredEl)) {
9235
- hoveredEl = null;
10378
+ clearHoverGate();
9236
10379
  highlightOverlay.style.display = "none";
9237
10380
  }
9238
10381
  applyHiddenSelectors();
9239
10382
  return;
9240
10383
  }
10384
+ if (e.data.type === "runtime-structure-move") {
10385
+ if (readOnly) return;
10386
+ var runtimePlacement = String(e.data.placement || "");
10387
+ if (
10388
+ runtimePlacement !== "before" &&
10389
+ runtimePlacement !== "after" &&
10390
+ runtimePlacement !== "inside"
10391
+ ) {
10392
+ return;
10393
+ }
10394
+ var runtimeSubject = findUniqueRuntimeStructureTarget(
10395
+ String(e.data.subjectSelector || ""),
10396
+ typeof e.data.subjectSourceId === "string"
10397
+ ? e.data.subjectSourceId
10398
+ : "",
10399
+ );
10400
+ var runtimeAnchor = findUniqueRuntimeStructureTarget(
10401
+ String(e.data.anchorSelector || ""),
10402
+ typeof e.data.anchorSourceId === "string" ? e.data.anchorSourceId : "",
10403
+ );
10404
+ if (
10405
+ !runtimeSubject ||
10406
+ !runtimeAnchor ||
10407
+ runtimeSubject === runtimeAnchor ||
10408
+ runtimeSubject.contains(runtimeAnchor)
10409
+ ) {
10410
+ return;
10411
+ }
10412
+ if (
10413
+ (runtimePlacement === "inside" &&
10414
+ !isContainerDropTarget(runtimeAnchor)) ||
10415
+ (runtimePlacement !== "inside" && !runtimeAnchor.parentElement)
10416
+ ) {
10417
+ return;
10418
+ }
10419
+ var runtimeTarget = {
10420
+ anchor: runtimeAnchor,
10421
+ placement: runtimePlacement,
10422
+ axis: parentFlowAxis(
10423
+ runtimePlacement === "inside"
10424
+ ? runtimeAnchor
10425
+ : runtimeAnchor.parentElement!,
10426
+ ),
10427
+ dropMode:
10428
+ runtimePlacement === "inside" &&
10429
+ isAbsolutePrimitiveContainer(runtimeAnchor)
10430
+ ? "absolute-container"
10431
+ : "flow-insert",
10432
+ };
10433
+ var runtimeOrigin = {
10434
+ prevParent: runtimeSubject.parentElement!,
10435
+ prevNextSibling: runtimeSubject.nextSibling,
10436
+ prevInlinePositionStyles: snapshotInlinePositionStyles(runtimeSubject),
10437
+ };
10438
+ applyRuntimeReorder(runtimeSubject, runtimeTarget);
10439
+ selectedEl = runtimeSubject;
10440
+ positionOverlay(selectionOverlay, selectedEl);
10441
+ postVisualStructureChange(runtimeSubject, runtimeTarget, runtimeOrigin);
10442
+ return;
10443
+ }
9241
10444
  if (e.data.type === "visual-structure-ack") {
9242
10445
  var move = pendingStructureMoves[e.data.requestId];
9243
10446
  if (!move) return;
@@ -9434,6 +10637,42 @@ declare var __DESIGN_CANVAS_BOARD_SURFACE__: boolean;
9434
10637
  window.addEventListener("resize", scheduleRefreshOverlays);
9435
10638
  applyEditorChromeScale();
9436
10639
 
10640
+ if (
10641
+ runtimeLayerSnapshotEnabled &&
10642
+ typeof MutationObserver !== "undefined" &&
10643
+ document.body
10644
+ ) {
10645
+ var runtimeLayerObserver = new MutationObserver(function (mutations) {
10646
+ var hasMeaningfulMutation = mutations.some(
10647
+ runtimeLayerMutationIsMeaningful,
10648
+ );
10649
+ if (hasMeaningfulMutation) scheduleRuntimeLayerSnapshot();
10650
+ });
10651
+ runtimeLayerObserver.observe(document.body, {
10652
+ attributes: true,
10653
+ attributeOldValue: true,
10654
+ childList: true,
10655
+ characterData: true,
10656
+ subtree: true,
10657
+ attributeFilter: [
10658
+ "aria-label",
10659
+ "class",
10660
+ "data-agent-native-component",
10661
+ "data-agent-native-layer-name",
10662
+ "data-an-primitive",
10663
+ "data-component-name",
10664
+ "data-source-column",
10665
+ "data-source-file",
10666
+ "data-source-line",
10667
+ "hidden",
10668
+ "id",
10669
+ "style",
10670
+ "title",
10671
+ ],
10672
+ });
10673
+ }
10674
+ if (runtimeLayerSnapshotEnabled) scheduleRuntimeLayerSnapshot();
10675
+
9437
10676
  // One-time ready signal: tells the host that every message listener above is
9438
10677
  // now attached, so one-shot commands (begin-text-edit, set-editor-chrome-scale,
9439
10678
  // style-change, delete-element, replace-document-content) sent immediately