@agent-native/core 0.92.10 → 0.92.12

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 (354) 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 +214 -17
  5. package/corpus/core/src/agent/run-manager.ts +72 -14
  6. package/corpus/core/src/agent/run-store.ts +152 -27
  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-adapter.ts +56 -12
  12. package/corpus/core/src/client/agent-chat.ts +165 -1
  13. package/corpus/core/src/client/chat/index.ts +9 -1
  14. package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +38 -2
  15. package/corpus/core/src/client/index.ts +5 -0
  16. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +18 -7
  17. package/corpus/core/src/collab/awareness-store.ts +23 -12
  18. package/corpus/core/src/collab/client.ts +16 -5
  19. package/corpus/core/src/collab/index.ts +4 -0
  20. package/corpus/core/src/collab/recent-edits.ts +65 -2
  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 +75 -9
  33. package/corpus/templates/clips/app/components/player/video-player.tsx +17 -12
  34. package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +1 -0
  35. package/corpus/templates/clips/app/i18n/ar-SA.ts +1 -0
  36. package/corpus/templates/clips/app/i18n/de-DE.ts +1 -0
  37. package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
  38. package/corpus/templates/clips/app/i18n/es-ES.ts +1 -0
  39. package/corpus/templates/clips/app/i18n/fr-FR.ts +1 -0
  40. package/corpus/templates/clips/app/i18n/hi-IN.ts +1 -0
  41. package/corpus/templates/clips/app/i18n/ja-JP.ts +1 -0
  42. package/corpus/templates/clips/app/i18n/ko-KR.ts +1 -0
  43. package/corpus/templates/clips/app/i18n/pt-BR.ts +1 -0
  44. package/corpus/templates/clips/app/i18n/zh-CN.ts +1 -0
  45. package/corpus/templates/clips/app/i18n/zh-TW.ts +1 -0
  46. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +70 -37
  47. package/corpus/templates/clips/app/routes/record.tsx +17 -4
  48. package/corpus/templates/clips/app/routes/share.$shareId.tsx +2 -0
  49. package/corpus/templates/clips/changelog/2026-07-09-desktop-popover-sits-closer-to-the-menu-bar.md +6 -0
  50. package/corpus/templates/clips/changelog/2026-07-09-desktop-recording-optimization-progress-stays-visible-until-the-clip-opens.md +6 -0
  51. package/corpus/templates/clips/changelog/2026-07-09-fixed-large-video-file-uploads-failing-before-their-resumabl.md +6 -0
  52. package/corpus/templates/clips/changelog/2026-07-09-fixed-new-video-uploads-defaulting-to-private.md +6 -0
  53. package/corpus/templates/clips/changelog/2026-07-09-long-recordings-now-get-enough-time-to-download-their-media-.md +6 -0
  54. package/corpus/templates/clips/changelog/2026-07-09-meeting-notes-no-longer-lowers-your-microphone-volume-for-ot.md +6 -0
  55. package/corpus/templates/clips/changelog/2026-07-09-on-mobile-tapping-a-playing-clip-reveals-playback-controls-w.md +6 -0
  56. package/corpus/templates/clips/changelog/2026-07-09-regenerate-any-clip-transcript-from-its-saved-recording-or-r.md +6 -0
  57. package/corpus/templates/clips/changelog/2026-07-09-slack-previews-now-show-the-clip-length-alongside-its-descri.md +6 -0
  58. package/corpus/templates/clips/changelog/2026-07-10-fixed-copying-agent-responses-and-transcript-text-from-recor.md +6 -0
  59. package/corpus/templates/clips/desktop/src/app.tsx +12 -2
  60. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +8 -1
  61. package/corpus/templates/clips/desktop/src/lib/recorder.ts +8 -5
  62. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +5 -5
  63. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +3 -0
  64. package/corpus/templates/clips/desktop/src/styles.css +6 -3
  65. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +29 -11
  66. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1 -1
  67. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +103 -21
  68. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +117 -39
  69. package/corpus/templates/clips/server/lib/slack-unfurls.ts +20 -1
  70. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +10 -4
  71. package/corpus/templates/clips/shared/recording-core.ts +12 -0
  72. package/corpus/templates/content/AGENTS.md +7 -6
  73. package/corpus/templates/content/actions/_builder-cms-read-client.ts +82 -7
  74. package/corpus/templates/content/actions/_database-source-utils.ts +149 -30
  75. package/corpus/templates/content/actions/_document-flush.ts +132 -17
  76. package/corpus/templates/content/actions/_notion-action-utils.ts +15 -0
  77. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +4 -7
  78. package/corpus/templates/content/actions/attach-content-database-source.ts +41 -17
  79. package/corpus/templates/content/actions/change-content-database-source-role.ts +46 -8
  80. package/corpus/templates/content/actions/create-and-link-notion-page.ts +2 -0
  81. package/corpus/templates/content/actions/link-notion-page.ts +2 -0
  82. package/corpus/templates/content/actions/pull-document.ts +5 -7
  83. package/corpus/templates/content/actions/pull-notion-page.ts +2 -0
  84. package/corpus/templates/content/actions/push-notion-page.ts +10 -0
  85. package/corpus/templates/content/actions/resolve-notion-sync-conflict.ts +2 -0
  86. package/corpus/templates/content/actions/suggest-source-join-key.ts +8 -1
  87. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +88 -23
  88. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +32 -2
  89. package/corpus/templates/content/app/hooks/use-notion.ts +6 -6
  90. package/corpus/templates/content/app/i18n/zh-TW.ts +1 -0
  91. package/corpus/templates/content/app/i18n-data.ts +18 -0
  92. package/corpus/templates/content/changelog/2026-07-09-builder-source-refreshes-now-keep-mapped-fields-such-as-topi.md +6 -0
  93. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-choices-and-version-restores-preserve-docume.md +6 -0
  94. package/corpus/templates/content/server/lib/notion-sync.ts +80 -64
  95. package/corpus/templates/content/shared/api.ts +1 -0
  96. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +29 -2
  97. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +86 -0
  98. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +57 -0
  99. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +11 -0
  100. package/corpus/templates/design/AGENTS.md +45 -10
  101. package/corpus/templates/design/actions/add-localhost-screens.ts +66 -13
  102. package/corpus/templates/design/actions/apply-component-prop-edit.ts +45 -36
  103. package/corpus/templates/design/actions/apply-motion-edit.ts +25 -31
  104. package/corpus/templates/design/actions/apply-shader-fill.ts +29 -42
  105. package/corpus/templates/design/actions/detach-component-instance.ts +337 -0
  106. package/corpus/templates/design/actions/export-design-as-figma-svg.ts +219 -0
  107. package/corpus/templates/design/actions/generate-design.ts +79 -12
  108. package/corpus/templates/design/actions/generate-screens.ts +57 -12
  109. package/corpus/templates/design/actions/get-figma-styles.ts +115 -0
  110. package/corpus/templates/design/actions/go-to-main-component.ts +254 -0
  111. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +4 -3
  112. package/corpus/templates/design/actions/import-design-source.ts +4 -31
  113. package/corpus/templates/design/actions/import-figma-clipboard.ts +134 -0
  114. package/corpus/templates/design/actions/import-figma-frame.ts +95 -0
  115. package/corpus/templates/design/actions/index-design-system-with-builder.ts +16 -1
  116. package/corpus/templates/design/actions/list-design-components.ts +104 -0
  117. package/corpus/templates/design/actions/list-figma-library-assets.ts +1 -24
  118. package/corpus/templates/design/actions/open-component-source.ts +2 -4
  119. package/corpus/templates/design/actions/present-design-variants.ts +412 -16
  120. package/corpus/templates/design/actions/preview-source-edit.ts +14 -1
  121. package/corpus/templates/design/actions/run-design-audit.ts +25 -3
  122. package/corpus/templates/design/actions/show-design-questions.ts +10 -0
  123. package/corpus/templates/design/actions/swap-component-instance.ts +544 -0
  124. package/corpus/templates/design/actions/take-design-screenshot.ts +45 -60
  125. package/corpus/templates/design/actions/write-local-file.ts +25 -1
  126. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +152 -0
  127. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1077 -58
  128. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +5 -5
  129. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +73 -21
  130. package/corpus/templates/design/app/components/design/EditPanel.tsx +16 -11
  131. package/corpus/templates/design/app/components/design/KeyboardShortcutsPanel.tsx +364 -0
  132. package/corpus/templates/design/app/components/design/LayersPanel.tsx +180 -53
  133. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +0 -2
  134. package/corpus/templates/design/app/components/design/MotionDock.tsx +144 -21
  135. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1152 -167
  136. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +27 -5
  137. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +23 -2
  138. package/corpus/templates/design/app/components/design/TokensPanel.tsx +63 -49
  139. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +1293 -54
  140. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +7 -0
  141. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +6 -1
  142. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +23 -6
  143. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +7 -0
  144. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +5 -0
  145. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +14 -2
  146. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +37 -2
  147. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open-guard.ts +25 -0
  148. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +19 -1
  149. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +43 -5
  150. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +27 -2
  151. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +10 -1
  152. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +19 -2
  153. package/corpus/templates/design/app/components/design/design-canvas/annotation-snapshot.ts +265 -0
  154. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +16 -5
  155. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +16 -2
  156. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +7 -2
  157. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +3 -1
  158. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +78 -8
  159. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +451 -73
  160. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +12 -4
  161. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +398 -272
  162. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +41 -7
  163. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +16 -3
  164. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +24 -4
  165. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +222 -4
  166. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +171 -78
  167. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +45 -5
  168. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +299 -28
  169. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +120 -150
  170. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +53 -1
  171. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +284 -117
  172. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +81 -0
  173. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +19 -14
  174. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +7 -0
  175. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +155 -0
  176. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +268 -46
  177. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +350 -31
  178. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +13 -4
  179. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +233 -23
  180. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +72 -2
  181. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +51 -11
  182. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +84 -11
  183. package/corpus/templates/design/app/components/design/inspector/index.ts +3 -0
  184. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +552 -0
  185. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +157 -0
  186. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +53 -0
  187. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +184 -0
  188. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +403 -26
  189. package/corpus/templates/design/app/components/design/multi-screen/types.ts +20 -1
  190. package/corpus/templates/design/app/components/design/types.ts +33 -0
  191. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +59 -7
  192. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +119 -17
  193. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +275 -63
  194. package/corpus/templates/design/app/hooks/use-question-flow.ts +14 -3
  195. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +67 -10
  196. package/corpus/templates/design/app/i18n/zh-TW.ts +91 -1
  197. package/corpus/templates/design/app/i18n-data.ts +1217 -10
  198. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +577 -0
  199. package/corpus/templates/design/app/lib/agent-chat.ts +23 -0
  200. package/corpus/templates/design/app/lib/design-import.ts +24 -0
  201. package/corpus/templates/design/app/lib/figma-clipboard.ts +157 -0
  202. package/corpus/templates/design/app/lib/figma-svg-copy.ts +221 -0
  203. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +143 -30
  204. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +15 -0
  205. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +33 -0
  206. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +38 -0
  207. package/corpus/templates/design/app/pages/design-editor/overview-annotation-context.ts +157 -0
  208. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +62 -6
  209. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +245 -0
  210. package/corpus/templates/design/app/pages/design-editor/react-semantic-handoff.ts +586 -0
  211. package/corpus/templates/design/app/pages/design-editor/screen-auto-layout.ts +169 -0
  212. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +201 -1
  213. package/corpus/templates/design/changelog/2026-07-09-added-underline-strikethrough-and-text-case-uppercase-lowerc.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-09-annotate-now-draws-across-the-all-screens-canvas-without-swi.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-09-auto-layout-children-can-now-be-dragged-into-freeform-screen.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-09-board-shapes-now-stay-visible-and-nest-correctly-across-the-.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-09-component-source-navigation-now-opens-reliably-from-the-insp.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-09-cross-screen-local-react-layer-moves-now-hand-off-exact-sour.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-09-cross-screen-moves-undo-and-redo-now-keep-both-screens-stabl.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-09-detach-a-component-instance-into-plain-editable-eleme.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-09-dragging-layers-into-auto-layout-now-respects-ignore-auto-la.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-09-drawing-annotations-sent-to-the-agent-now-include-a-rendered.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-09-editor-sidebars-now-use-figma-matched-default-widths-for-a-r.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-09-empty-frames-and-rectangles-now-expose-full-auto-layout-cont.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-09-export-any-design-screen-as-a-real-vector-svg-for-figma-with.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-09-grid-auto-layout-now-uses-true-responsive-rows-and-columns-w.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-09-hidden-and-locked-screens-now-behave-consistently-between-la.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-09-image-fills-now-preserve-valid-urls-and-layered-fit-settings.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-09-import-figma-frames-as-pixel-accurate-design-screens-via-a-s.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-09-inspect-code-now-hides-internal-styling-metadata-and-wraps-l.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-09-inspector-blur-and-shadow-effects-now-preserve-other-effects.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-09-jump-to-a-components-other-instances-from-the-inspecto.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-09-keyboard-shortcuts-are-now-discoverable-in-a-figma-style-bot.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-09-layer-selection-now-keeps-cmd-or-ctrl-toggles-and-shift-rang.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-09-layers-dropped-into-auto-layout-now-participate-correctly-in.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-09-local-live-edit-screens-now-enter-and-leave-full-view-withou.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-09-local-preview-urls-now-live-in-the-inspector-instead-of-taki.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-can-now-be-hidden-and-locked-from-layers-.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-09-local-react-layers-now-keep-exact-source-provenance-and-supp.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-09-local-react-visual-editing-now-loads-reliably-and-shows-live.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-09-paste-from-figma-cmd-v-now-imports-exact-editable-nodes-when.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-09-pressing-escape-after-selecting-a-screen-now-reveals-page-pr.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-09-right-click-overlapping-or-nested-objects-to-choose-the-exac.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-09-scale-edge-stretch-and-center-constraints-now-preserve-layer.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-09-screen-rows-can-now-be-reordered-in-layers-with-matching-can.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-09-screen-selection-stays-responsive-in-designs-with-hundreds-o.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-09-shift-a-now-enables-auto-layout-on-a-selected-screen-with-fl.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-09-swap-a-component-instance-for-another-from-the-libra.md +6 -0
  249. package/corpus/templates/design/changelog/2026-07-10-drawing-annotations-now-stay-available-to-retry-when-the.md +6 -0
  250. package/corpus/templates/design/e2e/global-setup.ts +3 -6
  251. package/corpus/templates/design/e2e/helpers.ts +27 -6
  252. package/corpus/templates/design/server/lib/design-to-figma-svg.ts +1412 -0
  253. package/corpus/templates/design/server/lib/figma-clipboard-match.ts +196 -0
  254. package/corpus/templates/design/server/lib/figma-node-import.ts +311 -0
  255. package/corpus/templates/design/server/lib/figma-node-to-html.ts +1334 -0
  256. package/corpus/templates/design/server/lib/figma-paste-fallback.ts +64 -0
  257. package/corpus/templates/design/server/lib/playwright-runtime.ts +78 -0
  258. package/corpus/templates/design/server/plugins/db.ts +41 -0
  259. package/corpus/templates/design/server/source-workspace.ts +64 -0
  260. package/corpus/templates/design/shared/board-file.ts +13 -2
  261. package/corpus/templates/design/shared/canvas-math.ts +231 -33
  262. package/corpus/templates/design/shared/code-layer.ts +122 -8
  263. package/corpus/templates/design/shared/component-library.ts +161 -0
  264. package/corpus/templates/design/shared/db-conflict.ts +27 -0
  265. package/corpus/templates/design/shared/design-source-capabilities.ts +2 -1
  266. package/corpus/templates/design/shared/figma-url.ts +149 -0
  267. package/corpus/templates/design/shared/pen-path.ts +16 -7
  268. package/corpus/templates/design/shared/source-mode.ts +1 -0
  269. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +7 -19
  270. package/corpus/templates/plan/changelog/2026-07-09-canvas-panning-now-keeps-zoom-fixed-even-at-the-edges.md +6 -0
  271. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +16 -1
  272. package/corpus/templates/slides/app/context/DeckContext.tsx +311 -70
  273. package/corpus/templates/slides/changelog/2026-07-09-fixed-the-slide-rail-going-permanently-stale-during-a-long-a.md +6 -0
  274. package/dist/agent/production-agent.d.ts +82 -7
  275. package/dist/agent/production-agent.d.ts.map +1 -1
  276. package/dist/agent/production-agent.js +180 -17
  277. package/dist/agent/production-agent.js.map +1 -1
  278. package/dist/agent/run-manager.d.ts.map +1 -1
  279. package/dist/agent/run-manager.js +63 -14
  280. package/dist/agent/run-manager.js.map +1 -1
  281. package/dist/agent/run-store.d.ts +46 -0
  282. package/dist/agent/run-store.d.ts.map +1 -1
  283. package/dist/agent/run-store.js +129 -22
  284. package/dist/agent/run-store.js.map +1 -1
  285. package/dist/cli/design-connect.d.ts +6 -0
  286. package/dist/cli/design-connect.d.ts.map +1 -1
  287. package/dist/cli/design-connect.js +336 -139
  288. package/dist/cli/design-connect.js.map +1 -1
  289. package/dist/cli/skills.d.ts.map +1 -1
  290. package/dist/cli/skills.js +14 -0
  291. package/dist/cli/skills.js.map +1 -1
  292. package/dist/client/AssistantChat.d.ts +2 -0
  293. package/dist/client/AssistantChat.d.ts.map +1 -1
  294. package/dist/client/AssistantChat.js +19 -3
  295. package/dist/client/AssistantChat.js.map +1 -1
  296. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  297. package/dist/client/MultiTabAssistantChat.js +38 -18
  298. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  299. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  300. package/dist/client/agent-chat-adapter.js +48 -12
  301. package/dist/client/agent-chat-adapter.js.map +1 -1
  302. package/dist/client/agent-chat.d.ts +60 -0
  303. package/dist/client/agent-chat.d.ts.map +1 -1
  304. package/dist/client/agent-chat.js +122 -1
  305. package/dist/client/agent-chat.js.map +1 -1
  306. package/dist/client/chat/index.d.ts +1 -1
  307. package/dist/client/chat/index.d.ts.map +1 -1
  308. package/dist/client/chat/index.js +1 -1
  309. package/dist/client/chat/index.js.map +1 -1
  310. package/dist/client/extensions/EmbeddedExtension.d.ts +12 -0
  311. package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
  312. package/dist/client/extensions/EmbeddedExtension.js +36 -2
  313. package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
  314. package/dist/client/index.d.ts +1 -1
  315. package/dist/client/index.d.ts.map +1 -1
  316. package/dist/client/index.js +1 -1
  317. package/dist/client/index.js.map +1 -1
  318. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  319. package/dist/client/rich-markdown-editor/useCollabReconcile.js +18 -6
  320. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  321. package/dist/collab/awareness-store.d.ts +5 -0
  322. package/dist/collab/awareness-store.d.ts.map +1 -1
  323. package/dist/collab/awareness-store.js +19 -12
  324. package/dist/collab/awareness-store.js.map +1 -1
  325. package/dist/collab/awareness.d.ts +2 -2
  326. package/dist/collab/awareness.d.ts.map +1 -1
  327. package/dist/collab/client.d.ts.map +1 -1
  328. package/dist/collab/client.js +13 -5
  329. package/dist/collab/client.js.map +1 -1
  330. package/dist/collab/index.d.ts +1 -0
  331. package/dist/collab/index.d.ts.map +1 -1
  332. package/dist/collab/index.js +1 -0
  333. package/dist/collab/index.js.map +1 -1
  334. package/dist/collab/recent-edits.d.ts +3 -1
  335. package/dist/collab/recent-edits.d.ts.map +1 -1
  336. package/dist/collab/recent-edits.js +57 -2
  337. package/dist/collab/recent-edits.js.map +1 -1
  338. package/dist/collab/struct-routes.d.ts +1 -1
  339. package/dist/index.browser.d.ts +1 -1
  340. package/dist/index.browser.d.ts.map +1 -1
  341. package/dist/index.browser.js +1 -1
  342. package/dist/index.browser.js.map +1 -1
  343. package/dist/observability/routes.d.ts +5 -5
  344. package/dist/progress/routes.d.ts +1 -1
  345. package/dist/resources/handlers.d.ts +3 -3
  346. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  347. package/dist/server/agent-chat-plugin.js +87 -10
  348. package/dist/server/agent-chat-plugin.js.map +1 -1
  349. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  350. package/dist/server/builder-design-systems.d.ts +15 -0
  351. package/dist/server/builder-design-systems.d.ts.map +1 -1
  352. package/dist/server/builder-design-systems.js +9 -4
  353. package/dist/server/builder-design-systems.js.map +1 -1
  354. package/package.json +1 -1
@@ -43,7 +43,11 @@ import {
43
43
  AppearanceScrubField,
44
44
  CornerRadiusControl,
45
45
  } from "./appearance-properties";
46
- import { FieldTrailer, ScrubStyleInput } from "./field-primitives";
46
+ import {
47
+ commitStylePatch,
48
+ FieldTrailer,
49
+ ScrubStyleInput,
50
+ } from "./field-primitives";
47
51
  import {
48
52
  InspectorIconButton,
49
53
  InspectorSegment,
@@ -57,6 +61,7 @@ import type {
57
61
  BreakpointOverrideFieldContext,
58
62
  MotionKeyframeFieldContext,
59
63
  StyleChangeHandler,
64
+ StylesChangeHandler,
60
65
  } from "./style-change-types";
61
66
  import {
62
67
  mergeRotationValue,
@@ -65,16 +70,254 @@ import {
65
70
  parseScaleValue,
66
71
  } from "./transform-helpers";
67
72
 
73
+ /**
74
+ * `authoredStyleValue()` returns the *inline* value when one is set, but
75
+ * falls back to the *computed* style otherwise — and `getComputedStyle()`
76
+ * reports "auto" for left/right/top/bottom on any element that has never had
77
+ * one of those offsets explicitly authored (the ordinary case for a plain,
78
+ * not-yet-repositioned element). A bare "auto" is not a real authored pin,
79
+ * and the cross-selection Mixed sentinel isn't either. Without this guard,
80
+ * `authoredLeft && authoredRight` truthiness checks below treated "auto" as
81
+ * "yes, pinned", so a completely unconstrained element read as "left-right"/
82
+ * "top-bottom" (pinned to both edges) in the Constraints preview, the X/Y
83
+ * fields showed a parsed "0" instead of the element's real on-canvas
84
+ * position, and picking "Left"/"Top" from an unconstrained element could
85
+ * write the literal string `"auto"` as the new `left`/`top` value instead of
86
+ * anchoring it at its current position. Exported for tests.
87
+ */
88
+ export function definiteAuthoredOffset(
89
+ raw: string | undefined,
90
+ ): string | undefined {
91
+ if (!raw || raw === "auto" || isMixedValue(raw)) return undefined;
92
+ return raw;
93
+ }
94
+
95
+ function percentageLength(raw: string | undefined): boolean {
96
+ return !!raw && /^-?(?:\d+\.?\d*|\.\d+)%$/.test(raw.trim());
97
+ }
98
+
99
+ function geometryPercent(value: number, total: number): string {
100
+ if (!Number.isFinite(value) || !Number.isFinite(total) || total <= 0) {
101
+ return "0%";
102
+ }
103
+ return `${Number(((value / total) * 100).toFixed(6))}%`;
104
+ }
105
+
106
+ function geometryPx(value: number): string {
107
+ return `${Number(value.toFixed(3))}px`;
108
+ }
109
+
110
+ /** Once the bridge supplies an inline-style snapshot, absence is meaningful:
111
+ * an absolutely-positioned left-only element still has a computed `right`,
112
+ * but that resolved value is not an authored right pin. Older payloads omit
113
+ * `inlineStyles` entirely, so only those fall back to computed styles. */
114
+ function authoredConstraintValue(
115
+ element: ElementInfo,
116
+ property: string,
117
+ ): string | undefined {
118
+ if (element.inlineStyles !== undefined) {
119
+ const value = element.inlineStyles[property];
120
+ return value === "auto" ? "" : value;
121
+ }
122
+ return element.computedStyles[property];
123
+ }
124
+
125
+ /**
126
+ * Derives the Constraints preview/widget state from an element's authored
127
+ * left/right/top/bottom/width/height/transform. Pulled out as a standalone
128
+ * pure function (mirrors `autoLayoutStylesForFlow` in layout-properties.tsx)
129
+ * so the "auto"/Mixed-safe derivation fixed by `definiteAuthoredOffset` above
130
+ * is directly unit-testable without rendering the whole panel. Exported for
131
+ * tests.
132
+ */
133
+ export function deriveConstraintsValue(element: ElementInfo): ConstraintsValue {
134
+ const authoredLeft = authoredConstraintValue(element, "left");
135
+ const authoredRight = authoredConstraintValue(element, "right");
136
+ const authoredTop = authoredConstraintValue(element, "top");
137
+ const authoredBottom = authoredConstraintValue(element, "bottom");
138
+ const authoredWidth = authoredConstraintValue(element, "width");
139
+ const authoredHeight = authoredConstraintValue(element, "height");
140
+ const authoredTransform = authoredConstraintValue(element, "transform");
141
+ const definiteLeft = definiteAuthoredOffset(authoredLeft);
142
+ const definiteRight = definiteAuthoredOffset(authoredRight);
143
+ const definiteTop = definiteAuthoredOffset(authoredTop);
144
+ const definiteBottom = definiteAuthoredOffset(authoredBottom);
145
+ const horizontalMixed = [
146
+ authoredLeft,
147
+ authoredRight,
148
+ authoredWidth,
149
+ authoredTransform,
150
+ ].some(isMixedValue);
151
+ const verticalMixed = [
152
+ authoredTop,
153
+ authoredBottom,
154
+ authoredHeight,
155
+ authoredTransform,
156
+ ].some(isMixedValue);
157
+ return {
158
+ horizontal: horizontalMixed
159
+ ? "mixed"
160
+ : // Check scale before left+right: "scale" writes width:100% and clears
161
+ // left/right to auto, but legacy data may have 0px values that are truthy.
162
+ authoredWidth === "100%" ||
163
+ (percentageLength(authoredWidth) && percentageLength(definiteLeft))
164
+ ? "scale"
165
+ : definiteLeft && definiteRight
166
+ ? "left-right"
167
+ : definiteRight
168
+ ? "right"
169
+ : authoredTransform?.includes("translateX(-50%)")
170
+ ? "center"
171
+ : "left",
172
+ vertical: verticalMixed
173
+ ? "mixed"
174
+ : authoredHeight === "100%" ||
175
+ (percentageLength(authoredHeight) && percentageLength(definiteTop))
176
+ ? "scale"
177
+ : definiteTop && definiteBottom
178
+ ? "top-bottom"
179
+ : definiteBottom
180
+ ? "bottom"
181
+ : authoredTransform?.includes("translateY(-50%)")
182
+ ? "center"
183
+ : "top",
184
+ };
185
+ }
186
+
187
+ /** Build the complete constraints edit before writing anything. The previous
188
+ * implementation emitted position, four offsets, size, and transform as
189
+ * separate writes, producing visible intermediate layouts and one undo entry
190
+ * per property. Keeping this pure also makes the exact atomic patch directly
191
+ * regression-testable. A mixed axis is intentionally left untouched until the
192
+ * user chooses a concrete value for that axis. */
193
+ export function constraintsStylePatch(
194
+ element: ElementInfo,
195
+ value: ConstraintsValue,
196
+ ): Record<string, string> {
197
+ const authoredTransform = authoredConstraintValue(element, "transform");
198
+ const currentValue = deriveConstraintsValue(element);
199
+ const patch: Record<string, string> = {};
200
+ let transform = isMixedValue(authoredTransform)
201
+ ? undefined
202
+ : authoredTransform;
203
+ let transformChanged = false;
204
+ const parentBounds =
205
+ element.parentBoundingRect ?? element.parentAutoLayout?.boundingRect;
206
+ const childBounds = element.boundingRect;
207
+ const relativeLeft = parentBounds
208
+ ? childBounds.x - parentBounds.x
209
+ : childBounds.x;
210
+ const relativeTop = parentBounds
211
+ ? childBounds.y - parentBounds.y
212
+ : childBounds.y;
213
+ const rightGap = parentBounds
214
+ ? parentBounds.width - relativeLeft - childBounds.width
215
+ : 0;
216
+ const bottomGap = parentBounds
217
+ ? parentBounds.height - relativeTop - childBounds.height
218
+ : 0;
219
+
220
+ if (
221
+ value.horizontal !== "mixed" &&
222
+ value.horizontal !== currentValue.horizontal
223
+ ) {
224
+ patch.position = "absolute";
225
+ transform = mergeTranslateFunction(
226
+ transform,
227
+ "X",
228
+ value.horizontal === "center" ? "-50%" : null,
229
+ );
230
+ transformChanged = true;
231
+ if (value.horizontal === "left") {
232
+ patch.left = geometryPx(relativeLeft);
233
+ patch.right = "auto";
234
+ patch.width = geometryPx(childBounds.width);
235
+ } else if (value.horizontal === "right") {
236
+ patch.right = geometryPx(rightGap);
237
+ patch.left = "auto";
238
+ patch.width = geometryPx(childBounds.width);
239
+ } else if (value.horizontal === "left-right") {
240
+ patch.left = geometryPx(relativeLeft);
241
+ patch.right = geometryPx(rightGap);
242
+ patch.width = "auto";
243
+ } else if (value.horizontal === "center") {
244
+ const centerOffset = parentBounds
245
+ ? relativeLeft + childBounds.width / 2 - parentBounds.width / 2
246
+ : 0;
247
+ patch.left =
248
+ Math.abs(centerOffset) < 0.0005
249
+ ? "50%"
250
+ : `calc(50% + ${geometryPx(centerOffset)})`;
251
+ patch.right = "auto";
252
+ patch.width = geometryPx(childBounds.width);
253
+ } else {
254
+ patch.left = parentBounds
255
+ ? geometryPercent(relativeLeft, parentBounds.width)
256
+ : "0%";
257
+ patch.right = "auto";
258
+ patch.width = parentBounds
259
+ ? geometryPercent(childBounds.width, parentBounds.width)
260
+ : "100%";
261
+ }
262
+ }
263
+
264
+ if (value.vertical !== "mixed" && value.vertical !== currentValue.vertical) {
265
+ patch.position = "absolute";
266
+ transform = mergeTranslateFunction(
267
+ transform,
268
+ "Y",
269
+ value.vertical === "center" ? "-50%" : null,
270
+ );
271
+ transformChanged = true;
272
+ if (value.vertical === "top") {
273
+ patch.top = geometryPx(relativeTop);
274
+ patch.bottom = "auto";
275
+ patch.height = geometryPx(childBounds.height);
276
+ } else if (value.vertical === "bottom") {
277
+ patch.bottom = geometryPx(bottomGap);
278
+ patch.top = "auto";
279
+ patch.height = geometryPx(childBounds.height);
280
+ } else if (value.vertical === "top-bottom") {
281
+ patch.top = geometryPx(relativeTop);
282
+ patch.bottom = geometryPx(bottomGap);
283
+ patch.height = "auto";
284
+ } else if (value.vertical === "center") {
285
+ const centerOffset = parentBounds
286
+ ? relativeTop + childBounds.height / 2 - parentBounds.height / 2
287
+ : 0;
288
+ patch.top =
289
+ Math.abs(centerOffset) < 0.0005
290
+ ? "50%"
291
+ : `calc(50% + ${geometryPx(centerOffset)})`;
292
+ patch.bottom = "auto";
293
+ patch.height = geometryPx(childBounds.height);
294
+ } else {
295
+ patch.top = parentBounds
296
+ ? geometryPercent(relativeTop, parentBounds.height)
297
+ : "0%";
298
+ patch.bottom = "auto";
299
+ patch.height = parentBounds
300
+ ? geometryPercent(childBounds.height, parentBounds.height)
301
+ : "100%";
302
+ }
303
+ }
304
+
305
+ if (transformChanged) patch.transform = transform || "none";
306
+ return patch;
307
+ }
308
+
68
309
  /** Position, size, and spacing properties */
69
310
  export function PositionLayoutProperties({
70
311
  element,
71
312
  onStyleChange,
313
+ onStylesChange,
72
314
  onAlignSelection,
73
315
  motionKeyframeContext,
74
316
  breakpointOverrideContext,
75
317
  }: {
76
318
  element: ElementInfo;
77
319
  onStyleChange: StyleChangeHandler;
320
+ onStylesChange?: StylesChangeHandler;
78
321
  /**
79
322
  * Moves the selection itself (Figma's real "Alignment" row semantics):
80
323
  * aligns to the combined selection bounding box for a 2+ multi-selection,
@@ -113,40 +356,16 @@ export function PositionLayoutProperties({
113
356
  value === "top" ? "top" : value === "bottom" ? "bottom" : "center-v",
114
357
  );
115
358
  };
116
- // Authored (not computed) offsets: when inlineStyles is present, "auto"/absent
117
- // is treated as truly unset instead of the computed px fallback, so a plain
118
- // top-left-anchored element reads as "left" rather than always "left-right".
359
+ // Authored (not computed) left/top: used directly (not through
360
+ // `definiteAuthoredOffset`) below because the X/Y fields need to tell
361
+ // "Mixed" apart from "unset", and handleConstraintsChange's own fallback
362
+ // already normalizes through `definiteAuthoredOffset` at its call sites.
363
+ // Right/bottom and the scale/rotation checks needed for the Constraints
364
+ // preview live in `deriveConstraintsValue` (above) instead.
119
365
  const authoredLeft = authoredStyleValue(element, "left");
120
- const authoredRight = authoredStyleValue(element, "right");
121
366
  const authoredTop = authoredStyleValue(element, "top");
122
- const authoredBottom = authoredStyleValue(element, "bottom");
123
- const authoredWidth = authoredStyleValue(element, "width");
124
- const authoredHeight = authoredStyleValue(element, "height");
125
367
  const authoredTransform = authoredStyleValue(element, "transform");
126
- const constraintsValue: ConstraintsValue = {
127
- horizontal:
128
- // Check scale before left+right: "scale" writes width:100% and clears
129
- // left/right to auto, but legacy data may have 0px values that are truthy.
130
- authoredWidth === "100%"
131
- ? "scale"
132
- : authoredLeft && authoredRight
133
- ? "left-right"
134
- : authoredRight
135
- ? "right"
136
- : authoredTransform?.includes("translateX(-50%)")
137
- ? "center"
138
- : "left",
139
- vertical:
140
- authoredHeight === "100%"
141
- ? "scale"
142
- : authoredTop && authoredBottom
143
- ? "top-bottom"
144
- : authoredBottom
145
- ? "bottom"
146
- : authoredTransform?.includes("translateY(-50%)")
147
- ? "center"
148
- : "top",
149
- };
368
+ const constraintsValue = deriveConstraintsValue(element);
150
369
  const [constraintsExpanded, setConstraintsExpanded] = useState(false);
151
370
  // 3D rotation/perspective progressive-disclosure expander — mirrors
152
371
  // CornerRadiusControl's showIndependentCorners pattern. Default-expanded
@@ -164,87 +383,13 @@ export function PositionLayoutProperties({
164
383
 
165
384
  const handleConstraintsChange = useCallback(
166
385
  (value: ConstraintsValue) => {
167
- onStyleChange("position", "absolute");
168
-
169
- // Compute the desired translateX/Y for each axis independently, then
170
- // compose both into a single transform write so the two axes don't
171
- // overwrite each other when both change simultaneously.
172
- const txValue = value.horizontal === "center" ? "-50%" : null;
173
- const tyValue = value.vertical === "center" ? "-50%" : null;
174
- // Start from the current transform, apply X, then apply Y on top.
175
- const transformAfterX = mergeTranslateFunction(
176
- authoredTransform,
177
- "X",
178
- txValue,
179
- );
180
- const transformAfterXY = mergeTranslateFunction(
181
- transformAfterX,
182
- "Y",
183
- tyValue,
386
+ commitStylePatch(
387
+ constraintsStylePatch(element, value),
388
+ onStyleChange,
389
+ onStylesChange,
184
390
  );
185
-
186
- if (value.horizontal === "left") {
187
- onStyleChange(
188
- "left",
189
- authoredLeft || `${Math.round(element.boundingRect.x)}px`,
190
- );
191
- onStyleChange("right", "auto");
192
- } else if (value.horizontal === "right") {
193
- onStyleChange("right", "0px");
194
- onStyleChange("left", "auto");
195
- } else if (value.horizontal === "left-right") {
196
- onStyleChange(
197
- "left",
198
- authoredLeft || `${Math.round(element.boundingRect.x)}px`,
199
- );
200
- onStyleChange("right", "0px");
201
- } else if (value.horizontal === "center") {
202
- onStyleChange("left", "50%");
203
- onStyleChange("right", "auto");
204
- } else {
205
- // scale: use auto (not 0px) so the left && right truthiness check
206
- // in the reader does not misidentify this as "left-right".
207
- onStyleChange("left", "auto");
208
- onStyleChange("right", "auto");
209
- onStyleChange("width", "100%");
210
- }
211
-
212
- if (value.vertical === "top") {
213
- onStyleChange(
214
- "top",
215
- authoredTop || `${Math.round(element.boundingRect.y)}px`,
216
- );
217
- onStyleChange("bottom", "auto");
218
- } else if (value.vertical === "bottom") {
219
- onStyleChange("bottom", "0px");
220
- onStyleChange("top", "auto");
221
- } else if (value.vertical === "top-bottom") {
222
- onStyleChange(
223
- "top",
224
- authoredTop || `${Math.round(element.boundingRect.y)}px`,
225
- );
226
- onStyleChange("bottom", "0px");
227
- } else if (value.vertical === "center") {
228
- onStyleChange("top", "50%");
229
- onStyleChange("bottom", "auto");
230
- } else {
231
- // scale
232
- onStyleChange("top", "auto");
233
- onStyleChange("bottom", "auto");
234
- onStyleChange("height", "100%");
235
- }
236
-
237
- // Write the composed transform once, after both axes are resolved.
238
- onStyleChange("transform", transformAfterXY);
239
391
  },
240
- [
241
- element.boundingRect.x,
242
- element.boundingRect.y,
243
- onStyleChange,
244
- authoredLeft,
245
- authoredTop,
246
- authoredTransform,
247
- ],
392
+ [element, onStyleChange, onStylesChange],
248
393
  );
249
394
 
250
395
  return (
@@ -328,7 +473,9 @@ export function PositionLayoutProperties({
328
473
  ariaLabel="X-position"
329
474
  tooltipLabel="X-position"
330
475
  value={
331
- isMixedValue(authoredLeft) ? MIXED_VALUE : authoredLeft || ""
476
+ isMixedValue(authoredLeft)
477
+ ? MIXED_VALUE
478
+ : (definiteAuthoredOffset(authoredLeft) ?? "")
332
479
  }
333
480
  placeholder={element.boundingRect.x}
334
481
  inputClassName="h-6"
@@ -338,8 +485,17 @@ export function PositionLayoutProperties({
338
485
  // mirror handleConstraintsChange, which always sets
339
486
  // position:absolute (the convention canvas drag/resize and
340
487
  // primitive creation both use) before writing left/top.
341
- if (!constrainedPosition) onStyleChange("position", "absolute");
342
- onStyleChange("left", `${roundToOneDecimal(v)}px`, meta);
488
+ commitStylePatch(
489
+ {
490
+ ...(!constrainedPosition
491
+ ? { position: "absolute" }
492
+ : undefined),
493
+ left: `${roundToOneDecimal(v)}px`,
494
+ },
495
+ onStyleChange,
496
+ onStylesChange,
497
+ meta,
498
+ );
343
499
  }}
344
500
  />
345
501
  <FieldTrailer
@@ -358,13 +514,24 @@ export function PositionLayoutProperties({
358
514
  ariaLabel="Y-position"
359
515
  tooltipLabel="Y-position"
360
516
  value={
361
- isMixedValue(authoredTop) ? MIXED_VALUE : authoredTop || ""
517
+ isMixedValue(authoredTop)
518
+ ? MIXED_VALUE
519
+ : (definiteAuthoredOffset(authoredTop) ?? "")
362
520
  }
363
521
  placeholder={element.boundingRect.y}
364
522
  inputClassName="h-6"
365
523
  onChange={(v, meta) => {
366
- if (!constrainedPosition) onStyleChange("position", "absolute");
367
- onStyleChange("top", `${roundToOneDecimal(v)}px`, meta);
524
+ commitStylePatch(
525
+ {
526
+ ...(!constrainedPosition
527
+ ? { position: "absolute" }
528
+ : undefined),
529
+ top: `${roundToOneDecimal(v)}px`,
530
+ },
531
+ onStyleChange,
532
+ onStylesChange,
533
+ meta,
534
+ );
368
535
  }}
369
536
  />
370
537
  <FieldTrailer
@@ -12,6 +12,29 @@ export function sameOrMixed(values: string[]): string {
12
12
  return values.every((value) => value === first) ? first : MIXED_VALUE;
13
13
  }
14
14
 
15
+ /**
16
+ * Structural equality via JSON — good enough for the small, JSON-serializable
17
+ * parent-layout snapshots (`parentDisplay`/`parentAutoLayout`/`parentLayout`)
18
+ * this is used for below; not intended as a general deep-equal.
19
+ */
20
+ function sameStructure<T>(a: T | undefined, b: T | undefined): boolean {
21
+ return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
22
+ }
23
+
24
+ /**
25
+ * Returns `value` when every element in `values` is structurally identical
26
+ * to it, else `undefined`. Used for object/optional-string fields where a
27
+ * `MIXED_VALUE` string sentinel doesn't apply (they aren't rendered as text)
28
+ * but silently leaking one arbitrary element's value across a selection with
29
+ * genuinely different values would still be wrong — see `parentDisplay` /
30
+ * `parentAutoLayout` / `parentLayout` below.
31
+ */
32
+ function sameValueOrUndefined<T>(values: T[], candidate: T): T | undefined {
33
+ return values.every((value) => sameStructure(value, candidate))
34
+ ? candidate
35
+ : undefined;
36
+ }
37
+
15
38
  export function mixedElementFromSelection(
16
39
  elements: ElementInfo[],
17
40
  ): ElementInfo | null {
@@ -60,11 +83,35 @@ export function mixedElementFromSelection(
60
83
  (element) => element.boundingRect.y + element.boundingRect.height,
61
84
  ),
62
85
  );
86
+ // Only report a shared component name when every selected element is the
87
+ // *same* named component instance (mirrors Figma: a multi-instance
88
+ // same-component selection still reads as a component). Anything else —
89
+ // including a component mixed with a plain element — collapses to
90
+ // undefined rather than a "Mixed" sentinel string: elementIsComponentSelection()
91
+ // in element-classification.ts only checks `.length > 0`, so leaking
92
+ // MIXED_VALUE ("Mixed", a non-empty string) through here would make a
93
+ // component+non-component multi-selection misreport as "is a component"
94
+ // (wrong purple icon/tint in SelectionHeader) instead of falling back to
95
+ // undefined the way an actual mismatch should.
96
+ const firstComponentName = elements[0]?.componentName;
97
+ const componentName =
98
+ firstComponentName &&
99
+ elements.every((element) => element.componentName === firstComponentName)
100
+ ? firstComponentName
101
+ : undefined;
102
+
63
103
  return {
64
104
  ...base,
65
105
  tagName: sameOrMixed(elements.map((element) => element.tagName)),
66
106
  id: undefined,
67
107
  sourceId: undefined,
108
+ // A merged/synthetic multi-selection has no single stable pending id
109
+ // either — clear it like id/sourceId above instead of leaking whichever
110
+ // element happened to be last, which isTextElement()'s nodeId-prefix
111
+ // fallback (for older payloads without primitiveKind) would otherwise
112
+ // read as if it belonged to the whole selection.
113
+ pendingNodeId: undefined,
114
+ componentName,
68
115
  selector: base.selector,
69
116
  classes: [],
70
117
  computedStyles,
@@ -87,5 +134,39 @@ export function mixedElementFromSelection(
87
134
  childElementCount: undefined,
88
135
  isFlexChild: elements.every((element) => element.isFlexChild),
89
136
  isFlexContainer: elements.every((element) => element.isFlexContainer),
137
+ // Same treatment as isFlexContainer above: isContainerElement() checks
138
+ // isFlexContainer || isGridContainer, so leaving isGridContainer to leak
139
+ // from `base` alone (via the ...base spread) let a mixed grid+non-grid
140
+ // selection misclassify as a uniform container based on selection order.
141
+ isGridContainer: elements.every((element) => element.isGridContainer),
142
+ // isParentFlex/isParentGrid/parentFlexDirection (element-classification.ts)
143
+ // read these three fields to decide whether LayoutContextProperties shows
144
+ // FlexChildControls/GridChildControls at all, and with which direction.
145
+ // Left unhandled, `...base` above leaked whichever element was selected
146
+ // LAST: multi-selecting elements from two different parents (e.g. one
147
+ // flex child + one plain block child, or two flex children with
148
+ // different flexDirection) rendered/hid those controls based on an
149
+ // arbitrary member of the selection, and any align-self/flex-grow edit
150
+ // made through them would apply to every selected element regardless of
151
+ // whether its own parent actually supports that property. Falling back
152
+ // to undefined when the selection's parents disagree matches the
153
+ // isFlexContainer/isGridContainer treatment above: hide the
154
+ // parent-relative controls rather than guess from one element.
155
+ parentDisplay: sameValueOrUndefined(
156
+ elements.map((element) => element.parentDisplay),
157
+ base.parentDisplay,
158
+ ),
159
+ parentAutoLayout: sameValueOrUndefined(
160
+ elements.map((element) => element.parentAutoLayout),
161
+ base.parentAutoLayout,
162
+ ),
163
+ parentBoundingRect: sameValueOrUndefined(
164
+ elements.map((element) => element.parentBoundingRect),
165
+ base.parentBoundingRect,
166
+ ),
167
+ parentLayout: sameValueOrUndefined(
168
+ elements.map((element) => element.parentLayout),
169
+ base.parentLayout,
170
+ ),
90
171
  };
91
172
  }
@@ -33,10 +33,12 @@ import {
33
33
  outlineOffsetForPosition,
34
34
  readStrokeOutlinePosition,
35
35
  readTextStrokeStyle,
36
+ resolveRestoredStrokeStyle,
36
37
  resolveTextStrokeColor,
37
38
  roundToOneDecimal,
38
39
  strokeHiddenByColor,
39
40
  strokeIsVisible,
41
+ strokeShowPatch,
40
42
  textStrokeAddPatch,
41
43
  textStrokeIsVisible,
42
44
  } from "./position-helpers";
@@ -180,22 +182,21 @@ function StrokeLayerControl({
180
182
  // "none"/"solid". Writing "none" would lose a dashed/dotted
181
183
  // style permanently, since there is no round-trippable "unset"
182
184
  // for that keyword once it's overwritten.
183
- const parsed = parseCssColor(color);
184
185
  if (visible) {
186
+ const parsed = parseCssColor(color);
185
187
  onStyleChange(
186
188
  `${prefix}Color`,
187
189
  parsed ? rgbaToCss(withColorOpacity(parsed, 0)) : "transparent",
188
190
  );
189
191
  return;
190
192
  }
191
- const restoredColor = parsed
192
- ? rgbaToCss(withColorOpacity(parsed, 100))
193
- : "#000000";
194
- onStyleChange(`${prefix}Color`, restoredColor);
195
- if (styleValue === "none") onStyleChange(`${prefix}Style`, "solid");
196
- onStyleChange(
197
- `${prefix}Width`,
198
- width === "0px" ? "1px" : width || "1px",
193
+ // Restore color/width/style as ONE commit (single undo step)
194
+ // rather than three sequential onStyleChange calls — see
195
+ // strokeShowPatch's doc comment.
196
+ commitStylePatch(
197
+ strokeShowPatch(prefix, color, width, styleValue),
198
+ onStyleChange,
199
+ onStylesChange,
199
200
  );
200
201
  }}
201
202
  >
@@ -384,7 +385,12 @@ export function StrokeProperties({
384
385
  commitStylePatch(
385
386
  {
386
387
  borderWidth: "1px",
387
- borderStyle: "solid",
388
+ // Preserve a real style (dashed/dotted/etc) that survived
389
+ // on a hidden-via-alpha border — only the outline branch
390
+ // below used to do this; the border branch hardcoded
391
+ // "solid" unconditionally, silently discarding it. See
392
+ // resolveRestoredStrokeStyle's doc comment.
393
+ borderStyle: resolveRestoredStrokeStyle(styles.borderStyle),
388
394
  borderColor,
389
395
  },
390
396
  onStyleChange,
@@ -396,10 +402,9 @@ export function StrokeProperties({
396
402
  const outlineWidth = `${
397
403
  Math.max(1, cssLengthNumber(styles.outlineWidth, 1)) + 1
398
404
  }px`;
399
- const outlineStyle =
400
- styles.outlineStyle === "none"
401
- ? "solid"
402
- : styles.outlineStyle || "solid";
405
+ const outlineStyle = resolveRestoredStrokeStyle(
406
+ styles.outlineStyle,
407
+ );
403
408
  const outlineColor = cssColorOrFallback(
404
409
  styles.outlineColor || styles.borderColor,
405
410
  "#000000",
@@ -74,6 +74,13 @@ export function resolveLineHeight(
74
74
  if (Number.isFinite(lhPx) && Number.isFinite(fsPx) && fsPx > 0) {
75
75
  return Math.round((lhPx / fsPx) * 100) / 100;
76
76
  }
77
+ // A px-computed line-height that we can't divide by a valid font-size
78
+ // (missing/invalid fontSize) must NOT fall through to the unitless-ratio
79
+ // parse below — `parseFloat("19.2px")` silently reads as `19.2`, which
80
+ // would render as a wildly wrong multiplier (e.g. "19.2") instead of a
81
+ // sane ~1.2 ratio. Fall back to the same default the empty/"normal" case
82
+ // above uses.
83
+ return 1.2;
77
84
  }
78
85
  const numeric = parseFloat(lh);
79
86
  return Number.isFinite(numeric) && numeric > 0 ? numeric : 1.2;