@agent-native/core 0.90.1 → 0.90.2

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 (295) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  5. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
  6. package/corpus/core/src/client/use-action.ts +39 -2
  7. package/corpus/core/src/client/use-db-sync.ts +44 -18
  8. package/corpus/core/src/db/create-get-db.ts +32 -2
  9. package/corpus/core/src/server/framework-request-handler.ts +52 -4
  10. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
  11. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
  12. package/corpus/templates/assets/AGENTS.md +23 -4
  13. package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
  14. package/corpus/templates/assets/actions/_helpers.ts +8 -1
  15. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
  16. package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
  17. package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
  18. package/corpus/templates/assets/actions/generate-image.ts +342 -25
  19. package/corpus/templates/assets/actions/get-library.ts +4 -3
  20. package/corpus/templates/assets/actions/navigate.ts +2 -1
  21. package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
  22. package/corpus/templates/assets/actions/update-asset.ts +1 -0
  23. package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
  24. package/corpus/templates/assets/actions/view-screen.ts +8 -1
  25. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
  26. package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
  27. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
  28. package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
  29. package/corpus/templates/assets/app/i18n-data.ts +650 -0
  30. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
  31. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
  32. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
  33. package/corpus/templates/assets/app/routes/library.tsx +43 -1
  34. package/corpus/templates/assets/server/lib/generation.ts +366 -44
  35. package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
  36. package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
  37. package/corpus/templates/assets/shared/api.ts +47 -0
  38. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
  39. package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
  40. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
  41. package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
  42. package/corpus/templates/clips/app/routes/record.tsx +157 -68
  43. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -5
  44. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +20 -3
  45. package/corpus/templates/clips/shared/finalize-recovery.ts +23 -3
  46. package/corpus/templates/content/AGENTS.md +52 -52
  47. package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
  48. package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
  49. package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
  50. package/corpus/templates/content/actions/_database-utils.ts +44 -4
  51. package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
  52. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
  53. package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
  54. package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
  55. package/corpus/templates/content/actions/delete-content-database.ts +1 -0
  56. package/corpus/templates/content/actions/get-document.ts +11 -2
  57. package/corpus/templates/content/actions/list-content-databases.ts +57 -9
  58. package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
  59. package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
  60. package/corpus/templates/content/actions/restore-content-database.ts +1 -0
  61. package/corpus/templates/content/actions/update-document.ts +43 -0
  62. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
  63. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
  64. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
  65. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
  66. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
  67. package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
  68. package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
  69. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
  70. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
  71. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
  72. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
  73. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
  74. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
  75. package/corpus/templates/content/app/global.css +41 -0
  76. package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
  77. package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
  78. package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
  79. package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
  80. package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
  81. package/corpus/templates/content/app/i18n-data.ts +355 -0
  82. package/corpus/templates/content/app/root.tsx +22 -0
  83. package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
  84. package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
  85. package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
  86. package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
  87. package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
  88. package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
  89. package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
  90. package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
  91. package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
  92. package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
  93. package/corpus/templates/content/shared/api.ts +13 -0
  94. package/corpus/templates/content/shared/builder-mdx.ts +16 -3
  95. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
  96. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
  97. package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
  98. package/corpus/templates/design/AGENTS.md +9 -2
  99. package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
  100. package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
  101. package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
  102. package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
  103. package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
  104. package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
  105. package/corpus/templates/design/actions/create-component.ts +29 -60
  106. package/corpus/templates/design/actions/create-file.ts +9 -2
  107. package/corpus/templates/design/actions/duplicate-design.ts +31 -2
  108. package/corpus/templates/design/actions/edit-design.ts +36 -38
  109. package/corpus/templates/design/actions/generate-design-title.ts +87 -0
  110. package/corpus/templates/design/actions/generate-design.ts +53 -18
  111. package/corpus/templates/design/actions/generate-screens.ts +108 -4
  112. package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
  113. package/corpus/templates/design/actions/insert-asset.ts +34 -29
  114. package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
  115. package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
  116. package/corpus/templates/design/actions/present-design-variants.ts +7 -2
  117. package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
  118. package/corpus/templates/design/actions/update-file.ts +190 -75
  119. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
  120. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
  121. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
  122. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
  123. package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
  124. package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
  125. package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
  126. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
  127. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
  128. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
  129. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
  131. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
  132. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
  133. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
  134. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
  135. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
  136. package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
  137. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
  138. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
  139. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
  140. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
  141. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
  142. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
  143. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
  144. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
  145. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
  146. package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
  147. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
  148. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
  149. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
  150. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
  151. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
  152. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
  153. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
  154. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
  155. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
  156. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
  157. package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
  158. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
  159. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
  160. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
  161. package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
  162. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
  163. package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
  164. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
  165. package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
  166. package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
  167. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
  168. package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
  169. package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
  170. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
  171. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
  172. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
  173. package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
  174. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
  175. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
  176. package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
  177. package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
  178. package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
  179. package/corpus/templates/design/app/components/design/types.ts +15 -0
  180. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
  181. package/corpus/templates/design/app/global.css +8 -8
  182. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
  183. package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
  184. package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
  185. package/corpus/templates/design/app/i18n-data.ts +1664 -152
  186. package/corpus/templates/design/app/i18n-motion.ts +563 -0
  187. package/corpus/templates/design/app/lib/design-import.ts +73 -0
  188. package/corpus/templates/design/app/pages/Index.tsx +41 -22
  189. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
  190. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
  191. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
  192. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
  193. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
  194. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
  195. package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
  196. package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
  197. package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
  198. package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
  199. package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
  201. package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
  202. package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
  203. package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
  204. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
  205. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
  206. package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
  207. package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
  208. package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
  209. package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
  210. package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
  211. package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
  212. package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
  213. package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
  214. package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
  215. package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
  216. package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
  217. package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
  218. package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
  219. package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
  220. package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
  221. package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
  222. package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
  223. package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
  224. package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
  225. package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
  226. package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
  227. package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
  228. package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
  229. package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
  230. package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
  231. package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
  232. package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
  233. package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
  234. package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
  235. package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
  236. package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
  237. package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
  238. package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
  239. package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
  240. package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
  241. package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
  242. package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
  243. package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
  244. package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
  245. package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
  246. package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
  247. package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
  248. package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
  249. package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
  250. package/corpus/templates/design/server/source-workspace.ts +139 -59
  251. package/corpus/templates/design/shared/board-file.ts +11 -2
  252. package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
  253. package/corpus/templates/design/shared/canvas-math.ts +354 -35
  254. package/corpus/templates/design/shared/code-layer.ts +277 -8
  255. package/corpus/templates/design/shared/design-state.ts +11 -4
  256. package/corpus/templates/design/shared/interaction-states.ts +548 -0
  257. package/corpus/templates/design/shared/motion-compiler.ts +186 -23
  258. package/corpus/templates/design/shared/motion-easing.ts +294 -0
  259. package/corpus/templates/design/shared/motion-timeline.ts +400 -34
  260. package/corpus/templates/design/shared/prompt-title.ts +95 -0
  261. package/corpus/templates/design/shared/responsive-classes.ts +448 -10
  262. package/corpus/templates/design/shared/screen-annotation.ts +46 -0
  263. package/corpus/templates/design/shared/shader-fills.ts +1009 -0
  264. package/corpus/templates/design/shared/shader-presets.ts +852 -4
  265. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
  266. package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
  267. package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
  268. package/dist/client/AgentPanel.js +1 -1
  269. package/dist/client/AgentPanel.js.map +1 -1
  270. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  271. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
  272. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  273. package/dist/client/use-action.d.ts +3 -0
  274. package/dist/client/use-action.d.ts.map +1 -1
  275. package/dist/client/use-action.js +34 -3
  276. package/dist/client/use-action.js.map +1 -1
  277. package/dist/client/use-db-sync.d.ts +4 -0
  278. package/dist/client/use-db-sync.d.ts.map +1 -1
  279. package/dist/client/use-db-sync.js +37 -18
  280. package/dist/client/use-db-sync.js.map +1 -1
  281. package/dist/collab/routes.d.ts +1 -1
  282. package/dist/collab/struct-routes.d.ts +1 -1
  283. package/dist/db/create-get-db.d.ts +25 -0
  284. package/dist/db/create-get-db.d.ts.map +1 -1
  285. package/dist/db/create-get-db.js +25 -2
  286. package/dist/db/create-get-db.js.map +1 -1
  287. package/dist/observability/routes.d.ts +3 -3
  288. package/dist/secrets/routes.d.ts +9 -9
  289. package/dist/server/framework-request-handler.d.ts.map +1 -1
  290. package/dist/server/framework-request-handler.js +37 -1
  291. package/dist/server/framework-request-handler.js.map +1 -1
  292. package/package.json +1 -1
  293. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
  294. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
  295. package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
@@ -0,0 +1,628 @@
1
+ import { useT } from "@agent-native/core/client";
2
+ import {
3
+ composeTransform3D,
4
+ isTransform3DActive,
5
+ parseTransform3DParts,
6
+ type Transform3DParts,
7
+ } from "@shared/canvas-math";
8
+ import {
9
+ IconAngle,
10
+ IconAxisX,
11
+ IconAxisY,
12
+ IconFlipHorizontal,
13
+ IconFlipVertical,
14
+ IconLayoutAlignBottom,
15
+ IconLayoutAlignCenter,
16
+ IconLayoutAlignLeft,
17
+ IconLayoutAlignMiddle,
18
+ IconLayoutAlignRight,
19
+ IconLayoutAlignTop,
20
+ IconLayoutDistributeHorizontal,
21
+ IconPerspective,
22
+ IconRotate3d,
23
+ } from "@tabler/icons-react";
24
+ import { useCallback, useState } from "react";
25
+
26
+ import {
27
+ Tooltip,
28
+ TooltipContent,
29
+ TooltipTrigger,
30
+ } from "@/components/ui/tooltip";
31
+ import { cn } from "@/lib/utils";
32
+
33
+ import {
34
+ ConstraintsPreview,
35
+ ConstraintsWidget,
36
+ ScrubInput,
37
+ type AlignmentMatrixValue,
38
+ type ConstraintsValue,
39
+ type ScrubInputChangeMeta,
40
+ } from "../inspector";
41
+ import type { ElementInfo } from "../types";
42
+ import {
43
+ AppearanceScrubField,
44
+ CornerRadiusControl,
45
+ } from "./appearance-properties";
46
+ import { FieldTrailer, ScrubStyleInput } from "./field-primitives";
47
+ import {
48
+ InspectorIconButton,
49
+ InspectorSegment,
50
+ SectionIconToggle,
51
+ } from "./inspector-controls";
52
+ import { authoredStyleValue } from "./interaction-state-helpers";
53
+ import { PanelSection, SubsectionLabel } from "./panel-primitives";
54
+ import { roundToOneDecimal } from "./position-helpers";
55
+ import { isMixedValue, MIXED_VALUE } from "./selection-helpers";
56
+ import type {
57
+ BreakpointOverrideFieldContext,
58
+ MotionKeyframeFieldContext,
59
+ StyleChangeHandler,
60
+ } from "./style-change-types";
61
+ import {
62
+ mergeRotationValue,
63
+ mergeTranslateFunction,
64
+ parseRotationValue,
65
+ parseScaleValue,
66
+ } from "./transform-helpers";
67
+
68
+ /** Position, size, and spacing properties */
69
+ export function PositionLayoutProperties({
70
+ element,
71
+ onStyleChange,
72
+ onAlignSelection,
73
+ motionKeyframeContext,
74
+ breakpointOverrideContext,
75
+ }: {
76
+ element: ElementInfo;
77
+ onStyleChange: StyleChangeHandler;
78
+ /**
79
+ * Moves the selection itself (Figma's real "Alignment" row semantics):
80
+ * aligns to the combined selection bounding box for a 2+ multi-selection,
81
+ * or to the parent for a single selected object. When provided, the
82
+ * alignment row's six buttons call this instead of writing flex-alignment
83
+ * properties on the selected element. See the `onAlignSelection` contract
84
+ * note above `PositionLayoutProperties` usage in this file for the exact
85
+ * edge semantics the caller (DesignEditor) must implement.
86
+ */
87
+ onAlignSelection?: (
88
+ edge: "left" | "center-h" | "right" | "top" | "center-v" | "bottom",
89
+ ) => void;
90
+ motionKeyframeContext?: MotionKeyframeFieldContext;
91
+ breakpointOverrideContext?: BreakpointOverrideFieldContext;
92
+ }) {
93
+ const t = useT();
94
+ const styles = element.computedStyles;
95
+ const constrainedPosition =
96
+ styles.position === "absolute" || styles.position === "fixed";
97
+ // NOTE: this row used to also write flex alignment (justifyContent/
98
+ // alignItems) on the selected element when it was a flex container —
99
+ // i.e. it aligned the element's own children. That duplicated exactly
100
+ // what FlexContainerControls' AutoLayoutMatrix already offers via its
101
+ // CompactAlignmentMatrix (onAlignmentChange, wired a few hundred lines
102
+ // up in this file) and was never real Figma behavior: Figma's Alignment
103
+ // row in the Position section always moves the selected object(s), not
104
+ // their children. That fallback has been removed — flex child alignment
105
+ // now lives exclusively in the auto-layout section's alignment matrix.
106
+ const handlePositionAlignH = (value: AlignmentMatrixValue["horizontal"]) => {
107
+ onAlignSelection?.(
108
+ value === "left" ? "left" : value === "right" ? "right" : "center-h",
109
+ );
110
+ };
111
+ const handlePositionAlignV = (value: AlignmentMatrixValue["vertical"]) => {
112
+ onAlignSelection?.(
113
+ value === "top" ? "top" : value === "bottom" ? "bottom" : "center-v",
114
+ );
115
+ };
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".
119
+ const authoredLeft = authoredStyleValue(element, "left");
120
+ const authoredRight = authoredStyleValue(element, "right");
121
+ const authoredTop = authoredStyleValue(element, "top");
122
+ const authoredBottom = authoredStyleValue(element, "bottom");
123
+ const authoredWidth = authoredStyleValue(element, "width");
124
+ const authoredHeight = authoredStyleValue(element, "height");
125
+ 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
+ };
150
+ const [constraintsExpanded, setConstraintsExpanded] = useState(false);
151
+ // 3D rotation/perspective progressive-disclosure expander — mirrors
152
+ // CornerRadiusControl's showIndependentCorners pattern. Default-expanded
153
+ // when the authored transform already has non-zero X/Y rotation or
154
+ // perspective, so an element edited elsewhere (e.g. by the agent) doesn't
155
+ // hide its active 3D state behind a collapsed control.
156
+ const initialTransform3DParts = parseTransform3DParts(
157
+ isMixedValue(authoredTransform) ? undefined : authoredTransform,
158
+ );
159
+ const [rotation3DExpanded, setRotation3DExpanded] = useState(
160
+ () =>
161
+ initialTransform3DParts !== null &&
162
+ isTransform3DActive(initialTransform3DParts),
163
+ );
164
+
165
+ const handleConstraintsChange = useCallback(
166
+ (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,
184
+ );
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
+ },
240
+ [
241
+ element.boundingRect.x,
242
+ element.boundingRect.y,
243
+ onStyleChange,
244
+ authoredLeft,
245
+ authoredTop,
246
+ authoredTransform,
247
+ ],
248
+ );
249
+
250
+ return (
251
+ <PanelSection
252
+ title={t("editPanel.sections.positionLayout")}
253
+ actions={
254
+ <SectionIconToggle
255
+ label={"Absolute position" /* i18n-ignore design inspector action */}
256
+ active={constrainedPosition}
257
+ onClick={() =>
258
+ onStyleChange(
259
+ "position",
260
+ constrainedPosition ? "relative" : "absolute",
261
+ )
262
+ }
263
+ >
264
+ <IconLayoutDistributeHorizontal className="size-3.5" />
265
+ </SectionIconToggle>
266
+ }
267
+ >
268
+ <div className="space-y-1.5">
269
+ <SubsectionLabel>
270
+ {"Alignment" /* i18n-ignore design inspector label */}
271
+ </SubsectionLabel>
272
+ <div className="flex items-center gap-3">
273
+ <InspectorSegment>
274
+ <InspectorIconButton
275
+ label={t("editPanel.textAligns.left")}
276
+ shortcut="⌥A"
277
+ onClick={() => handlePositionAlignH("left")}
278
+ >
279
+ <IconLayoutAlignLeft className="size-3.5" />
280
+ </InspectorIconButton>
281
+ <InspectorIconButton
282
+ label={t("editPanel.textAligns.center")}
283
+ shortcut="⌥H"
284
+ onClick={() => handlePositionAlignH("center")}
285
+ >
286
+ <IconLayoutAlignCenter className="size-3.5" />
287
+ </InspectorIconButton>
288
+ <InspectorIconButton
289
+ label={t("editPanel.textAligns.right")}
290
+ shortcut="⌥D"
291
+ onClick={() => handlePositionAlignH("right")}
292
+ >
293
+ <IconLayoutAlignRight className="size-3.5" />
294
+ </InspectorIconButton>
295
+ </InspectorSegment>
296
+ <InspectorSegment>
297
+ <InspectorIconButton
298
+ label={t("editPanel.alignSelfOptions.start")}
299
+ shortcut="⌥W"
300
+ onClick={() => handlePositionAlignV("top")}
301
+ >
302
+ <IconLayoutAlignTop className="size-3.5" />
303
+ </InspectorIconButton>
304
+ <InspectorIconButton
305
+ label={t("editPanel.alignSelfOptions.center")}
306
+ shortcut="⌥V"
307
+ onClick={() => handlePositionAlignV("middle")}
308
+ >
309
+ <IconLayoutAlignMiddle className="size-3.5" />
310
+ </InspectorIconButton>
311
+ <InspectorIconButton
312
+ label={t("editPanel.alignSelfOptions.end")}
313
+ shortcut="⌥S"
314
+ onClick={() => handlePositionAlignV("bottom")}
315
+ >
316
+ <IconLayoutAlignBottom className="size-3.5" />
317
+ </InspectorIconButton>
318
+ </InspectorSegment>
319
+ </div>
320
+ </div>
321
+
322
+ <div className="space-y-1.5">
323
+ <SubsectionLabel>{t("editPanel.labels.position")}</SubsectionLabel>
324
+ <div className="grid grid-cols-[minmax(0,1fr)_minmax(0,1fr)_1.75rem] gap-2">
325
+ <div className="group/field relative min-w-0">
326
+ <ScrubStyleInput
327
+ label="X"
328
+ ariaLabel="X-position"
329
+ tooltipLabel="X-position"
330
+ value={
331
+ isMixedValue(authoredLeft) ? MIXED_VALUE : authoredLeft || ""
332
+ }
333
+ placeholder={element.boundingRect.x}
334
+ inputClassName="h-6"
335
+ onChange={(v, meta) => {
336
+ // Typing X/Y on a static (non-positioned) element is a no-op on
337
+ // canvas unless we first give it a position to offset from —
338
+ // mirror handleConstraintsChange, which always sets
339
+ // position:absolute (the convention canvas drag/resize and
340
+ // primitive creation both use) before writing left/top.
341
+ if (!constrainedPosition) onStyleChange("position", "absolute");
342
+ onStyleChange("left", `${roundToOneDecimal(v)}px`, meta);
343
+ }}
344
+ />
345
+ <FieldTrailer
346
+ element={element}
347
+ motionCssProperty="translate"
348
+ overrideProperty="left"
349
+ motionKeyframeContext={motionKeyframeContext}
350
+ breakpointOverrideContext={breakpointOverrideContext}
351
+ className="absolute -top-3.5 right-0"
352
+ hoverRevealClassName="opacity-0 group-hover/field:opacity-100"
353
+ />
354
+ </div>
355
+ <div className="group/field relative min-w-0">
356
+ <ScrubStyleInput
357
+ label="Y"
358
+ ariaLabel="Y-position"
359
+ tooltipLabel="Y-position"
360
+ value={
361
+ isMixedValue(authoredTop) ? MIXED_VALUE : authoredTop || ""
362
+ }
363
+ placeholder={element.boundingRect.y}
364
+ inputClassName="h-6"
365
+ onChange={(v, meta) => {
366
+ if (!constrainedPosition) onStyleChange("position", "absolute");
367
+ onStyleChange("top", `${roundToOneDecimal(v)}px`, meta);
368
+ }}
369
+ />
370
+ <FieldTrailer
371
+ element={element}
372
+ motionCssProperty="translate"
373
+ overrideProperty="top"
374
+ motionKeyframeContext={motionKeyframeContext}
375
+ breakpointOverrideContext={breakpointOverrideContext}
376
+ className="absolute -top-3.5 right-0"
377
+ hoverRevealClassName="opacity-0 group-hover/field:opacity-100"
378
+ />
379
+ </div>
380
+ <Tooltip>
381
+ <TooltipTrigger asChild>
382
+ <button
383
+ type="button"
384
+ aria-label={
385
+ "Constraints" /* i18n-ignore design inspector action */
386
+ }
387
+ aria-pressed={constraintsExpanded}
388
+ onClick={() => setConstraintsExpanded((expanded) => !expanded)}
389
+ className={cn(
390
+ "flex size-7 items-center justify-center rounded-md transition-colors",
391
+ "hover:bg-[var(--design-editor-control-bg)] hover:text-foreground",
392
+ "focus:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
393
+ constraintsExpanded
394
+ ? "bg-[var(--design-editor-selection-color)] text-[var(--design-editor-accent-color)] hover:text-[var(--design-editor-accent-color)]"
395
+ : "text-muted-foreground",
396
+ )}
397
+ >
398
+ <ConstraintsPreview value={constraintsValue} />
399
+ </button>
400
+ </TooltipTrigger>
401
+ <TooltipContent>
402
+ {"Constraints" /* i18n-ignore design inspector tooltip */}
403
+ </TooltipContent>
404
+ </Tooltip>
405
+ </div>
406
+ {constraintsExpanded ? (
407
+ <ConstraintsWidget
408
+ value={constraintsValue}
409
+ onChange={handleConstraintsChange}
410
+ className="pt-1"
411
+ />
412
+ ) : null}
413
+ </div>
414
+
415
+ <div className="space-y-1.5">
416
+ <SubsectionLabel>{t("editPanel.labels.rotation")}</SubsectionLabel>
417
+ <div className="group flex items-center gap-2">
418
+ <div className="min-w-0 flex-1">
419
+ <ScrubStyleInput
420
+ label="Rotation"
421
+ ariaLabel={t("editPanel.labels.rotation")}
422
+ tooltipLabel={t("editPanel.labels.rotation")}
423
+ hideIcon={false}
424
+ icon={IconAngle}
425
+ labelClassName="[&>span]:sr-only"
426
+ // Detect the Mixed sentinel BEFORE parsing: parseRotationValue
427
+ // would silently turn "Mixed" into 0 and render "0deg" instead
428
+ // of the mixed state (mirrors the opacity field's guard).
429
+ value={
430
+ isMixedValue(styles.transform)
431
+ ? MIXED_VALUE
432
+ : `${parseRotationValue(styles.transform)}deg`
433
+ }
434
+ unit="deg"
435
+ inputClassName="h-6"
436
+ onChange={(v, meta) =>
437
+ onStyleChange(
438
+ "transform",
439
+ // From a mixed selection the sentinel is not a transform —
440
+ // treat it as absent so the typed value applies cleanly to
441
+ // every selected object instead of producing
442
+ // "Mixed rotate(…)". This field always writes the Z
443
+ // rotation — back-compat: existing designs'
444
+ // `transform: rotate()` is the Z axis. When the 3D
445
+ // expander below is active (non-zero X/Y/perspective),
446
+ // mergeRotationValue's plain rotate() slot still round-
447
+ // trips correctly since composeTransform3D always emits a
448
+ // trailing rotateZ() once 3D is active, which
449
+ // ROTATE_FN_PATTERN also matches.
450
+ mergeRotationValue(
451
+ isMixedValue(styles.transform)
452
+ ? undefined
453
+ : styles.transform,
454
+ v,
455
+ ),
456
+ meta,
457
+ )
458
+ }
459
+ />
460
+ </div>
461
+ <FieldTrailer
462
+ element={element}
463
+ motionCssProperty="rotate"
464
+ overrideProperty="transform"
465
+ motionKeyframeContext={motionKeyframeContext}
466
+ breakpointOverrideContext={breakpointOverrideContext}
467
+ hoverRevealClassName="opacity-0 group-hover:opacity-100"
468
+ />
469
+ <InspectorSegment>
470
+ <InspectorIconButton
471
+ label={t("editPanel.labels.flipHorizontal")}
472
+ onClick={() => {
473
+ const [sx, sy] = parseScaleValue(styles.scale);
474
+ onStyleChange("scale", `${sx === -1 ? 1 : -1} ${sy}`);
475
+ }}
476
+ >
477
+ <IconFlipHorizontal className="size-4" />
478
+ </InspectorIconButton>
479
+ <InspectorIconButton
480
+ label={t("editPanel.labels.flipVertical")}
481
+ onClick={() => {
482
+ const [sx, sy] = parseScaleValue(styles.scale);
483
+ onStyleChange("scale", `${sx} ${sy === -1 ? 1 : -1}`);
484
+ }}
485
+ >
486
+ <IconFlipVertical className="size-4" />
487
+ </InspectorIconButton>
488
+ </InspectorSegment>
489
+ <Tooltip>
490
+ <TooltipTrigger asChild>
491
+ <button
492
+ type="button"
493
+ aria-label={t("editPanel.labels.rotation3d")}
494
+ aria-pressed={rotation3DExpanded}
495
+ onClick={() => setRotation3DExpanded((expanded) => !expanded)}
496
+ className={cn(
497
+ "flex size-7 shrink-0 items-center justify-center rounded-md transition-colors",
498
+ "hover:bg-[var(--design-editor-control-bg)] hover:text-foreground",
499
+ "focus:outline-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]",
500
+ rotation3DExpanded
501
+ ? "bg-[var(--design-editor-selection-color)] text-[var(--design-editor-accent-color)] hover:text-[var(--design-editor-accent-color)]"
502
+ : "text-muted-foreground",
503
+ )}
504
+ >
505
+ <IconRotate3d className="size-3.5" />
506
+ </button>
507
+ </TooltipTrigger>
508
+ <TooltipContent>{t("editPanel.labels.rotation3d")}</TooltipContent>
509
+ </Tooltip>
510
+ </div>
511
+ {rotation3DExpanded ? (
512
+ <Rotation3DControls styles={styles} onStyleChange={onStyleChange} />
513
+ ) : null}
514
+ </div>
515
+ </PanelSection>
516
+ );
517
+ }
518
+
519
+ /**
520
+ * Progressive-disclosure X/Y/Z rotation + perspective controls, revealed by
521
+ * the 3D-rotation expander next to the plain (Z-axis) rotation field. See
522
+ * `composeTransform3D`/`parseTransform3DParts` (shared/canvas-math.ts) for
523
+ * the parse/compose contract this wraps.
524
+ *
525
+ * - Transform composition order: `perspective(Npx) rotateX(Xdeg)
526
+ * rotateY(Ydeg) rotateZ(Zdeg) <preserved translate/scale/etc>` — see the
527
+ * `composeTransform3D` doc comment for the full rationale (X→Y→Z is a
528
+ * common 3D-engine Euler convention; Figma hasn't published a composition
529
+ * order since 3D transforms are unshipped there as of this build).
530
+ * - When X, Y, and Perspective are all zero/empty, the composed transform is
531
+ * the plain 2D `rotate(Zdeg)` form — zero output churn for existing
532
+ * designs that never touch this expander.
533
+ * - `transform-style: preserve-3d` is intentionally NOT applied here:
534
+ * defaulting to flattened (no preserve-3d) matches the conservative,
535
+ * minimal-footprint choice for this first pass — see the build report for
536
+ * the preserve-3d-on-children tradeoff.
537
+ */
538
+ function Rotation3DControls({
539
+ styles,
540
+ onStyleChange,
541
+ }: {
542
+ styles: Record<string, string>;
543
+ onStyleChange: StyleChangeHandler;
544
+ }) {
545
+ const t = useT();
546
+ const transformMixed = isMixedValue(styles.transform);
547
+ const parts = transformMixed ? null : parseTransform3DParts(styles.transform);
548
+ // `parts === null` (and not mixed) means the authored transform is a
549
+ // matrix()/matrix3d()/rotate3d() composite (or an unrecognized token) that
550
+ // parseTransform3DParts can't safely invert into independent X/Y/Z/
551
+ // perspective fields — show the fields disabled with a note instead of
552
+ // guessing, matching how Mixed values disable commit rather than silently
553
+ // defaulting to 0. See parseTransform3DParts's doc comment.
554
+ const isCustomTransform = !transformMixed && parts === null;
555
+ const disabled = transformMixed || isCustomTransform;
556
+ const displayParts: Transform3DParts = parts ?? {
557
+ rotateX: 0,
558
+ rotateY: 0,
559
+ rotateZ: 0,
560
+ perspective: 0,
561
+ };
562
+
563
+ const commitPart = (
564
+ patch: Partial<Transform3DParts>,
565
+ meta?: ScrubInputChangeMeta,
566
+ ) => {
567
+ if (disabled) return;
568
+ const nextParts: Transform3DParts = { ...displayParts, ...patch };
569
+ onStyleChange(
570
+ "transform",
571
+ composeTransform3D(styles.transform, nextParts),
572
+ meta,
573
+ );
574
+ };
575
+
576
+ return (
577
+ <div className="space-y-1.5 pt-1">
578
+ {isCustomTransform ? (
579
+ <p className="!text-[11px] text-muted-foreground">
580
+ {t("editPanel.labels.customTransform")}
581
+ </p>
582
+ ) : null}
583
+ <div className="grid grid-cols-2 gap-1.5">
584
+ <AppearanceScrubField
585
+ label={t("editPanel.labels.rotationX")}
586
+ icon={IconAxisX}
587
+ value={transformMixed ? 0 : displayParts.rotateX}
588
+ onChange={(value, meta) => commitPart({ rotateX: value }, meta)}
589
+ mixed={transformMixed}
590
+ disabled={isCustomTransform}
591
+ step={1}
592
+ unit="deg"
593
+ precision={1}
594
+ />
595
+ <AppearanceScrubField
596
+ label={t("editPanel.labels.rotationY")}
597
+ icon={IconAxisY}
598
+ value={transformMixed ? 0 : displayParts.rotateY}
599
+ onChange={(value, meta) => commitPart({ rotateY: value }, meta)}
600
+ mixed={transformMixed}
601
+ disabled={isCustomTransform}
602
+ step={1}
603
+ unit="deg"
604
+ precision={1}
605
+ />
606
+ <ScrubInput
607
+ label={t("editPanel.labels.perspective")}
608
+ ariaLabel={t("editPanel.labels.perspective")}
609
+ tooltipLabel={t("editPanel.labels.perspectiveHint")}
610
+ icon={IconPerspective}
611
+ value={transformMixed ? 0 : displayParts.perspective}
612
+ onChange={(value, meta) =>
613
+ commitPart({ perspective: Math.max(0, value) }, meta)
614
+ }
615
+ mixed={transformMixed}
616
+ disabled={isCustomTransform}
617
+ min={0}
618
+ step={10}
619
+ unit="px"
620
+ precision={0}
621
+ className="col-span-2 gap-0"
622
+ labelClassName="h-6 w-7 justify-center gap-0 rounded-l-md rounded-r-none border border-r-0 border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] [&>span]:sr-only"
623
+ inputClassName="h-6 rounded-l-none rounded-r-md border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] shadow-none focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
624
+ />
625
+ </div>
626
+ </div>
627
+ );
628
+ }