@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
@@ -72,6 +72,14 @@ export interface ResizeSnapOptions extends CanvasSnapOptions {
72
72
  * sibling edges. Pass the frame's aspect ratio (width / height) *before*
73
73
  * this resize's own aspect-preserving delta was applied. */
74
74
  preserveAspectRatio?: boolean;
75
+ /** Minimum width/height the snapped result is clamped to, matching the
76
+ * same per-call minimum passed to the non-snap resize path (e.g.
77
+ * `resizeFrameFromDelta`'s `minWidth`/`minHeight`). Defaults to the
78
+ * 120px screen-frame minimum so existing callers that don't pass these
79
+ * are unaffected — pass the small per-primitive minimum (e.g. 1-8px) so
80
+ * a snap near a sibling edge doesn't force-inflate a small shape. */
81
+ minWidth?: number;
82
+ minHeight?: number;
75
83
  }
76
84
 
77
85
  export interface ResizeFrameOptions {
@@ -240,7 +248,7 @@ export const DEFAULT_ASSIGNED_REGION_MAX_COLUMNS = 3;
240
248
  * for this fix.
241
249
  */
242
250
  export const DEFAULT_CANVAS_MIN_ZOOM = 2;
243
- export const DEFAULT_CANVAS_MAX_ZOOM = 800;
251
+ export const DEFAULT_CANVAS_MAX_ZOOM = 25600;
244
252
 
245
253
  export function screenToCanvasPoint(
246
254
  point: CanvasPoint,
@@ -871,19 +879,60 @@ export function resizeFrameFromDelta(
871
879
  }
872
880
  }
873
881
 
874
- const preClampWidth = width;
875
- const preClampHeight = height;
876
- width = Math.max(options.minWidth ?? MIN_CANVAS_FRAME_WIDTH, width);
877
- height = Math.max(options.minHeight ?? MIN_CANVAS_FRAME_HEIGHT, height);
882
+ const minWidth = options.minWidth ?? MIN_CANVAS_FRAME_WIDTH;
883
+ const minHeight = options.minHeight ?? MIN_CANVAS_FRAME_HEIGHT;
884
+ // Real Figma flips a shape when a resize handle is dragged past its
885
+ // opposite edge instead of pinning the size at the minimum: the handle's
886
+ // role effectively swaps (e.g. dragging "e" left past the frame's own west
887
+ // edge starts growing the frame leftward-of-that-point, i.e. what visually
888
+ // reads as the west edge). This only makes sense for objects whose minimum
889
+ // is a small "never fully collapse" floor (draft shapes/text/frames — the
890
+ // 1-8px minimums the primitive-resize call sites pass); callers relying on
891
+ // the 120px screen-frame default keep today's pin-at-minimum clamp exactly,
892
+ // since that default represents a true hard floor a frame shouldn't shrink
893
+ // below OR flip past.
894
+ const allowFlipWidth = minWidth < MIN_CANVAS_FRAME_WIDTH;
895
+ const allowFlipHeight = minHeight < MIN_CANVAS_FRAME_HEIGHT;
896
+
897
+ // Raw signed sizes (post aspect-ratio derivation, pre min-clamp) — negative
898
+ // means the drag crossed past the opposite edge/anchor (for a directly
899
+ // dragged axis), or that a directly dragged axis on the OTHER axis flipped
900
+ // and this axis's aspect-derived magnitude inherited its sign. Only a
901
+ // directly dragged axis ("affects*") uses the anchor-swap position formula
902
+ // below; an axis the handle doesn't touch (derived purely via
903
+ // preserveAspectRatio) and a `resizeFromCenter` drag (which keeps the
904
+ // CENTER fixed rather than an opposite edge — mirroring around the anchor
905
+ // point, matching Figma's alt-resize) both always resolve through the
906
+ // existing centered-growth formula in getResizedAxisStart — but that
907
+ // formula (and the min-clamp below) still need the axis's MAGNITUDE, not
908
+ // its possibly-negative derived value, whenever this axis's own minimum is
909
+ // small enough to allow a flip at all.
910
+ const rawWidth = width;
911
+ const rawHeight = height;
912
+ const widthMagnitude = allowFlipWidth ? Math.abs(rawWidth) : rawWidth;
913
+ const heightMagnitude = allowFlipHeight ? Math.abs(rawHeight) : rawHeight;
914
+ const widthFlipped =
915
+ allowFlipWidth &&
916
+ affectsHorizontal &&
917
+ !options.resizeFromCenter &&
918
+ rawWidth < 0;
919
+ const heightFlipped =
920
+ allowFlipHeight &&
921
+ affectsVertical &&
922
+ !options.resizeFromCenter &&
923
+ rawHeight < 0;
924
+
925
+ width = Math.max(minWidth, widthMagnitude);
926
+ height = Math.max(minHeight, heightMagnitude);
878
927
 
879
928
  if (options.preserveAspectRatio) {
880
- const widthClamped = width > preClampWidth;
881
- const heightClamped = height > preClampHeight;
882
- if (widthClamped && !heightClamped) {
929
+ const widthAtMin = width > widthMagnitude;
930
+ const heightAtMin = height > heightMagnitude;
931
+ if (widthAtMin && !heightAtMin) {
883
932
  height = width / ratio;
884
- } else if (heightClamped && !widthClamped) {
933
+ } else if (heightAtMin && !widthAtMin) {
885
934
  width = height * ratio;
886
- } else if (widthClamped && heightClamped) {
935
+ } else if (widthAtMin && heightAtMin) {
887
936
  // Both axes hit their minimum; width wins as the primary authority
888
937
  height = width / ratio;
889
938
  }
@@ -891,24 +940,40 @@ export function resizeFrameFromDelta(
891
940
 
892
941
  return {
893
942
  ...origin,
894
- x: getResizedAxisStart(
895
- origin.x,
896
- origin.width,
897
- width,
898
- handleAffectsWest(handle),
899
- handleAffectsEast(handle),
900
- options.resizeFromCenter ||
901
- (!affectsHorizontal && width !== origin.width),
902
- ),
903
- y: getResizedAxisStart(
904
- origin.y,
905
- origin.height,
906
- height,
907
- handleAffectsNorth(handle),
908
- handleAffectsSouth(handle),
909
- options.resizeFromCenter ||
910
- (!affectsVertical && height !== origin.height),
911
- ),
943
+ x: widthFlipped
944
+ ? getFlippedAxisStart(
945
+ origin.x,
946
+ origin.width,
947
+ rawWidth,
948
+ width,
949
+ handleAffectsWest(handle),
950
+ )
951
+ : getResizedAxisStart(
952
+ origin.x,
953
+ origin.width,
954
+ width,
955
+ handleAffectsWest(handle),
956
+ handleAffectsEast(handle),
957
+ options.resizeFromCenter ||
958
+ (!affectsHorizontal && width !== origin.width),
959
+ ),
960
+ y: heightFlipped
961
+ ? getFlippedAxisStart(
962
+ origin.y,
963
+ origin.height,
964
+ rawHeight,
965
+ height,
966
+ handleAffectsNorth(handle),
967
+ )
968
+ : getResizedAxisStart(
969
+ origin.y,
970
+ origin.height,
971
+ height,
972
+ handleAffectsNorth(handle),
973
+ handleAffectsSouth(handle),
974
+ options.resizeFromCenter ||
975
+ (!affectsVertical && height !== origin.height),
976
+ ),
912
977
  width,
913
978
  height,
914
979
  };
@@ -1307,6 +1372,10 @@ export function computeResizeSnap(
1307
1372
  }
1308
1373
 
1309
1374
  const threshold = getCanvasSnapThreshold(options);
1375
+ const minSize: MinFrameSize = {
1376
+ minWidth: options.minWidth ?? MIN_CANVAS_FRAME_WIDTH,
1377
+ minHeight: options.minHeight ?? MIN_CANVAS_FRAME_HEIGHT,
1378
+ };
1310
1379
 
1311
1380
  if (options.preserveAspectRatio) {
1312
1381
  return computeAspectPreservingResizeSnap(
@@ -1314,6 +1383,7 @@ export function computeResizeSnap(
1314
1383
  stationary,
1315
1384
  handle,
1316
1385
  threshold,
1386
+ minSize,
1317
1387
  );
1318
1388
  }
1319
1389
 
@@ -1334,6 +1404,7 @@ export function computeResizeSnap(
1334
1404
  handle,
1335
1405
  "x",
1336
1406
  candidate.offset,
1407
+ minSize,
1337
1408
  );
1338
1409
  guides.push(candidate.guide);
1339
1410
  }
@@ -1353,6 +1424,7 @@ export function computeResizeSnap(
1353
1424
  handle,
1354
1425
  "y",
1355
1426
  candidate.offset,
1427
+ minSize,
1356
1428
  );
1357
1429
  guides.push(candidate.guide);
1358
1430
  }
@@ -1361,6 +1433,191 @@ export function computeResizeSnap(
1361
1433
  return { frame: nextFrame, guides };
1362
1434
  }
1363
1435
 
1436
+ // ---------------------------------------------------------------------------
1437
+ // 3D transform (rotateX/rotateY/rotateZ/perspective) parse + compose
1438
+ // ---------------------------------------------------------------------------
1439
+ //
1440
+ // These helpers operate on an element's authored CSS `transform` string (the
1441
+ // inspector's inline-style domain), not the frame-gesture rotation math
1442
+ // above — kept in this file per the inspector's convention of pushing pure
1443
+ // transform math out of EditPanel.tsx for testability.
1444
+ //
1445
+ // Sign convention: unlike Figma's plugin-API `rotation` (counterclockwise-
1446
+ // positive), our canonical model is real CSS, so these helpers keep the
1447
+ // standard CSS convention as-is (positive `rotateZ(deg)`/`rotateX(deg)`/
1448
+ // `rotateY(deg)` exactly as the browser interprets them) — no sign flip is
1449
+ // applied here. See EditPanel.tsx's `mergeRotationValue`/`ROTATE_FN_PATTERN`
1450
+ // for the existing (unflipped) 2D rotate() convention this extends.
1451
+
1452
+ /** The 3D rotation + perspective parts of a composed `transform` value. All
1453
+ * angles are plain CSS degrees (browser convention, not Figma's). */
1454
+ export interface Transform3DParts {
1455
+ rotateX: number;
1456
+ rotateY: number;
1457
+ rotateZ: number;
1458
+ /** `perspective(Npx)` distance in px. 0 means "no perspective" (omitted
1459
+ * from the composed string). */
1460
+ perspective: number;
1461
+ }
1462
+
1463
+ const TRANSFORM_3D_ALL_ZERO: Transform3DParts = {
1464
+ rotateX: 0,
1465
+ rotateY: 0,
1466
+ rotateZ: 0,
1467
+ perspective: 0,
1468
+ };
1469
+
1470
+ /** Matches a single `fn(value<unit>)` token, e.g. `rotateX(30deg)` or
1471
+ * `perspective(800px)`. Case-insensitive to tolerate authored CSS. */
1472
+ function matchTransformFn(
1473
+ transform: string,
1474
+ fnName: string,
1475
+ ): { value: number; unit: string } | null {
1476
+ const pattern = new RegExp(
1477
+ `${fnName}\\(\\s*([+-]?[\\d.]+(?:e[+-]?\\d+)?)([a-z%]*)\\s*\\)`,
1478
+ "i",
1479
+ );
1480
+ const match = transform.match(pattern);
1481
+ if (!match) return null;
1482
+ const value = Number(match[1]);
1483
+ if (!Number.isFinite(value)) return null;
1484
+ return { value, unit: (match[2] || "").toLowerCase() };
1485
+ }
1486
+
1487
+ function angleFnToDegrees(transform: string, fnName: string): number | null {
1488
+ const found = matchTransformFn(transform, fnName);
1489
+ if (!found) return null;
1490
+ const { value, unit } = found;
1491
+ if (unit === "" || unit === "deg") return value;
1492
+ if (unit === "rad") return value * (180 / Math.PI);
1493
+ if (unit === "turn") return value * 360;
1494
+ if (unit === "grad") return value * 0.9;
1495
+ return null;
1496
+ }
1497
+
1498
+ /**
1499
+ * Parses the 3D rotation + perspective portion of a CSS `transform` string
1500
+ * into plain degree/px numbers, ignoring any translate()/scale()/skew()
1501
+ * parts that may also be present (those are preserved separately by the
1502
+ * caller — see `composeTransform3D`).
1503
+ *
1504
+ * Returns `null` — rather than a best-effort guess — when the transform
1505
+ * contains a `matrix()`/`matrix3d()`/`rotate3d()` composite or any other
1506
+ * token this parser doesn't recognize, so callers can show a "custom
1507
+ * transform" state instead of silently misreporting angles (matches how
1508
+ * the 2D rotation field's `parseRotationValue` falls back to reading the
1509
+ * resolved matrix for *display*, but 3D composition from an arbitrary
1510
+ * matrix is not safely invertible into independent X/Y/Z/perspective
1511
+ * fields, so this parser intentionally does not attempt it).
1512
+ */
1513
+ export function parseTransform3DParts(
1514
+ transform: string | undefined,
1515
+ ): Transform3DParts | null {
1516
+ const value = transform?.trim();
1517
+ if (!value || value === "none") return { ...TRANSFORM_3D_ALL_ZERO };
1518
+
1519
+ if (/matrix3d\(|matrix\(|rotate3d\(/i.test(value)) return null;
1520
+
1521
+ const rotateX = angleFnToDegrees(value, "rotateX") ?? 0;
1522
+ const rotateY = angleFnToDegrees(value, "rotateY") ?? 0;
1523
+ // A bare rotate()/rotateZ() are equivalent for a 2D Z-axis rotation.
1524
+ const rotateZ =
1525
+ angleFnToDegrees(value, "rotateZ") ??
1526
+ angleFnToDegrees(value, "rotate") ??
1527
+ 0;
1528
+
1529
+ const perspectiveMatch = matchTransformFn(value, "perspective");
1530
+ let perspective = 0;
1531
+ if (perspectiveMatch) {
1532
+ if (perspectiveMatch.unit !== "" && perspectiveMatch.unit !== "px") {
1533
+ // Unrecognized perspective unit — bail to "custom" rather than guess.
1534
+ return null;
1535
+ }
1536
+ perspective = perspectiveMatch.value;
1537
+ }
1538
+
1539
+ return { rotateX, rotateY, rotateZ, perspective };
1540
+ }
1541
+
1542
+ /**
1543
+ * Composes a `transform` string from 3D rotation/perspective parts,
1544
+ * preserving every non-rotation, non-perspective function already present
1545
+ * (translate/scale/skew/etc.) in its original relative order, with the 3D
1546
+ * chain appended in the fixed, documented order:
1547
+ *
1548
+ * perspective(Npx) rotateX(Xdeg) rotateY(Ydeg) rotateZ(Zdeg) <rest>
1549
+ *
1550
+ * Order matters in CSS transforms (each function is applied to the
1551
+ * coordinate space produced by the ones before it, reading left to right).
1552
+ * `perspective()` must come first so it establishes the viewing distance
1553
+ * before any rotation is applied; X then Y then Z is the common 3D-engine
1554
+ * convention (e.g. Three.js's default Euler order) and is fixed here so
1555
+ * round-tripping is stable — there is no Figma precedent to match since 3D
1556
+ * transforms are unshipped there (see research-transforms3d.md).
1557
+ *
1558
+ * When `perspective` and both `rotateX`/`rotateY` are zero, this emits the
1559
+ * plain 2D form (`rotateZ(Zdeg)` merged via the existing `rotate()` slot)
1560
+ * with no `perspective()`/`rotateX()`/`rotateY()` tokens at all, so existing
1561
+ * 2D-only designs round-trip through this helper with zero output churn.
1562
+ * Callers that already have a 2D-only mergeRotationValue-style helper (see
1563
+ * EditPanel.tsx) may prefer that path for the pure-2D case; this function
1564
+ * supports it too so a single code path can serve both.
1565
+ */
1566
+ export function composeTransform3D(
1567
+ transform: string | undefined,
1568
+ parts: Transform3DParts,
1569
+ ): string {
1570
+ const base = !transform || transform === "none" ? "" : transform;
1571
+ // Strip any existing rotateX/rotateY/rotateZ/rotate/perspective/matrix3d
1572
+ // tokens so we don't compound with stale ones; everything else (translate,
1573
+ // scale, skew) is preserved as-is, in place.
1574
+ const stripped = base
1575
+ .replace(/perspective\([^)]*\)/gi, "")
1576
+ .replace(/rotate[XYZxyz]?\([^)]*\)/gi, "")
1577
+ .replace(/rotate3d\([^)]*\)/gi, "")
1578
+ .replace(/\s+/g, " ")
1579
+ .trim();
1580
+
1581
+ const rotateX = Number.isFinite(parts.rotateX) ? parts.rotateX : 0;
1582
+ const rotateY = Number.isFinite(parts.rotateY) ? parts.rotateY : 0;
1583
+ const rotateZ = Number.isFinite(parts.rotateZ) ? parts.rotateZ : 0;
1584
+ const perspective =
1585
+ Number.isFinite(parts.perspective) && parts.perspective > 0
1586
+ ? parts.perspective
1587
+ : 0;
1588
+
1589
+ const is3DActive = perspective > 0 || rotateX !== 0 || rotateY !== 0;
1590
+
1591
+ const chainTokens: string[] = [];
1592
+ if (is3DActive) {
1593
+ if (perspective > 0) chainTokens.push(`perspective(${perspective}px)`);
1594
+ if (rotateX !== 0) chainTokens.push(`rotateX(${rotateX}deg)`);
1595
+ if (rotateY !== 0) chainTokens.push(`rotateY(${rotateY}deg)`);
1596
+ // Always include rotateZ once 3D is active (even at 0deg) so the fixed
1597
+ // X/Y/Z order is unambiguous and stable to re-parse.
1598
+ chainTokens.push(`rotateZ(${rotateZ}deg)`);
1599
+ } else if (rotateZ !== 0) {
1600
+ // Zero-churn 2D form: plain rotate(), matching the existing 2D field's
1601
+ // `mergeRotationValue` output exactly.
1602
+ chainTokens.push(`rotate(${rotateZ}deg)`);
1603
+ }
1604
+
1605
+ if (chainTokens.length === 0) return stripped || "none";
1606
+ const chain = chainTokens.join(" ");
1607
+ return stripped ? `${chain} ${stripped}` : chain;
1608
+ }
1609
+
1610
+ /**
1611
+ * True when `transform` is 3D-active per `composeTransform3D`'s own
1612
+ * definition (non-zero perspective, rotateX, or rotateY) — i.e. whether
1613
+ * `transform-style: preserve-3d` / 3D inspector fields should be considered
1614
+ * "on" for this element. Exported so EditPanel doesn't need to re-derive the
1615
+ * same threshold independently.
1616
+ */
1617
+ export function isTransform3DActive(parts: Transform3DParts): boolean {
1618
+ return parts.perspective > 0 || parts.rotateX !== 0 || parts.rotateY !== 0;
1619
+ }
1620
+
1364
1621
  /**
1365
1622
  * Aspect-ratio-safe variant of the independent-axis snap above. Snapping x
1366
1623
  * and y independently can each pull toward a different sibling edge, which
@@ -1374,6 +1631,7 @@ function computeAspectPreservingResizeSnap(
1374
1631
  stationary: FrameEntry[],
1375
1632
  handle: ResizeHandle,
1376
1633
  threshold: number,
1634
+ minSize: MinFrameSize,
1377
1635
  ) {
1378
1636
  const ratio = frame.width / Math.max(1, frame.height);
1379
1637
  const xCandidate =
@@ -1403,6 +1661,7 @@ function computeAspectPreservingResizeSnap(
1403
1661
  handle,
1404
1662
  "x",
1405
1663
  xCandidate.offset,
1664
+ minSize,
1406
1665
  );
1407
1666
  const nextHeight = snappedX.width / ratio;
1408
1667
  // Matches resizeFrameFromDelta's own convention: when a handle that
@@ -1430,6 +1689,7 @@ function computeAspectPreservingResizeSnap(
1430
1689
  handle,
1431
1690
  "y",
1432
1691
  yCandidate.offset,
1692
+ minSize,
1433
1693
  );
1434
1694
  const nextWidth = snappedY.height * ratio;
1435
1695
  const rescaled = {
@@ -1466,6 +1726,44 @@ function getResizedAxisStart(
1466
1726
  return originStart;
1467
1727
  }
1468
1728
 
1729
+ /**
1730
+ * Flip-aware counterpart to `getResizedAxisStart` for a directly-dragged
1731
+ * axis whose raw (pre-clamp, pre-abs) size went negative — i.e. the handle
1732
+ * was dragged past its opposite edge/anchor. `rawSize` is the SIGNED size
1733
+ * before the `Math.abs`/minimum clamp was applied; `nextSize` is the final
1734
+ * (always positive) clamped size actually being used.
1735
+ *
1736
+ * The anchor is the edge the handle does NOT move: `affectsStart` (west/
1737
+ * north) handles anchor the end edge (`originStart + originSize`);
1738
+ * `affectsEnd` (east/south) handles anchor the start edge (`originStart`).
1739
+ * The dragged edge's raw (possibly past-anchor) position is derived from the
1740
+ * anchor and the raw signed size; the frame's new start is whichever of the
1741
+ * two edges (anchor vs. dragged) is smaller, and `nextSize` (already
1742
+ * `abs`+min-clamped) is used for the final width/height so a tiny overshoot
1743
+ * still respects the caller's own minimum floor.
1744
+ */
1745
+ function getFlippedAxisStart(
1746
+ originStart: number,
1747
+ originSize: number,
1748
+ rawSize: number,
1749
+ nextSize: number,
1750
+ affectsStart: boolean,
1751
+ ): number {
1752
+ const anchor = affectsStart ? originStart + originSize : originStart;
1753
+ const draggedEdge = affectsStart ? anchor - rawSize : anchor + rawSize;
1754
+ const start = Math.min(anchor, draggedEdge);
1755
+ // If the min-clamp floor pushed nextSize above the raw overshoot distance
1756
+ // (e.g. rawSize is only slightly negative but nextSize floors up to the
1757
+ // caller's minimum), keep the anchor fixed and extend the frame outward
1758
+ // from it by the floored size instead of trusting the (too-small) raw
1759
+ // dragged-edge position.
1760
+ const rawSpan = Math.abs(draggedEdge - anchor);
1761
+ if (nextSize > rawSpan) {
1762
+ return anchor < draggedEdge ? anchor - nextSize : anchor;
1763
+ }
1764
+ return start;
1765
+ }
1766
+
1469
1767
  function getGroupMinimumBounds(
1470
1768
  frames: FrameEntry[],
1471
1769
  originBounds: FrameGeometry,
@@ -1620,11 +1918,20 @@ function getResizeSnapCandidate(
1620
1918
  }, null);
1621
1919
  }
1622
1920
 
1921
+ interface MinFrameSize {
1922
+ minWidth: number;
1923
+ minHeight: number;
1924
+ }
1925
+
1623
1926
  function applyResizeSnapOffset(
1624
1927
  frame: FrameGeometry,
1625
1928
  handle: ResizeHandle,
1626
1929
  axis: "x" | "y",
1627
1930
  offset: number,
1931
+ minSize: MinFrameSize = {
1932
+ minWidth: MIN_CANVAS_FRAME_WIDTH,
1933
+ minHeight: MIN_CANVAS_FRAME_HEIGHT,
1934
+ },
1628
1935
  ) {
1629
1936
  if (axis === "x") {
1630
1937
  return clampFrameSize(
@@ -1632,6 +1939,7 @@ function applyResizeSnapOffset(
1632
1939
  ? { ...frame, x: frame.x + offset, width: frame.width - offset }
1633
1940
  : { ...frame, width: frame.width + offset },
1634
1941
  handle,
1942
+ minSize,
1635
1943
  );
1636
1944
  }
1637
1945
 
@@ -1640,22 +1948,33 @@ function applyResizeSnapOffset(
1640
1948
  ? { ...frame, y: frame.y + offset, height: frame.height - offset }
1641
1949
  : { ...frame, height: frame.height + offset },
1642
1950
  handle,
1951
+ minSize,
1643
1952
  );
1644
1953
  }
1645
1954
 
1646
- function clampFrameSize(frame: FrameGeometry, handle: ResizeHandle) {
1955
+ function clampFrameSize(
1956
+ frame: FrameGeometry,
1957
+ handle: ResizeHandle,
1958
+ {
1959
+ minWidth = MIN_CANVAS_FRAME_WIDTH,
1960
+ minHeight = MIN_CANVAS_FRAME_HEIGHT,
1961
+ }: {
1962
+ minWidth?: number;
1963
+ minHeight?: number;
1964
+ } = {},
1965
+ ) {
1647
1966
  let next = { ...frame };
1648
- if (next.width < MIN_CANVAS_FRAME_WIDTH) {
1967
+ if (next.width < minWidth) {
1649
1968
  if (handleAffectsWest(handle)) {
1650
- next.x = next.x + next.width - MIN_CANVAS_FRAME_WIDTH;
1969
+ next.x = next.x + next.width - minWidth;
1651
1970
  }
1652
- next.width = MIN_CANVAS_FRAME_WIDTH;
1971
+ next.width = minWidth;
1653
1972
  }
1654
- if (next.height < MIN_CANVAS_FRAME_HEIGHT) {
1973
+ if (next.height < minHeight) {
1655
1974
  if (handleAffectsNorth(handle)) {
1656
- next.y = next.y + next.height - MIN_CANVAS_FRAME_HEIGHT;
1975
+ next.y = next.y + next.height - minHeight;
1657
1976
  }
1658
- next.height = MIN_CANVAS_FRAME_HEIGHT;
1977
+ next.height = minHeight;
1659
1978
  }
1660
1979
  return next;
1661
1980
  }