@agent-native/core 0.79.27 → 0.80.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (346) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +26 -0
  3. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +41 -0
  4. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +51 -0
  5. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +50 -0
  6. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +49 -0
  7. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +44 -0
  8. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +46 -0
  9. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +44 -0
  10. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +48 -0
  11. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +36 -0
  12. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +38 -0
  13. package/corpus/core/docs/content/template-design.mdx +45 -0
  14. package/corpus/core/package.json +2 -1
  15. package/corpus/core/src/agent/engine/index.ts +1 -0
  16. package/corpus/core/src/agent/engine/registry.ts +40 -0
  17. package/corpus/core/src/agent/production-agent.ts +6 -2
  18. package/corpus/core/src/cli/design-connect.ts +490 -0
  19. package/corpus/core/src/cli/index.ts +16 -0
  20. package/corpus/core/src/cli/skills.ts +126 -3
  21. package/corpus/core/src/client/AssistantChat.tsx +43 -7
  22. package/corpus/core/src/client/agent-chat.ts +27 -3
  23. package/corpus/core/src/client/blocks/library/MermaidBlock.tsx +1 -1
  24. package/corpus/core/src/client/blocks/library/diagram.tsx +2 -2
  25. package/corpus/core/src/client/blocks/library/wireframe.tsx +1 -1
  26. package/corpus/core/src/client/chat/message-components.tsx +1 -1
  27. package/corpus/core/src/client/chat-model-groups.ts +1 -2
  28. package/corpus/core/src/client/components/LiveCursorOverlay.tsx +33 -66
  29. package/corpus/core/src/client/composer/TiptapComposer.tsx +99 -24
  30. package/corpus/core/src/client/composer/useVoiceDictation.ts +429 -260
  31. package/corpus/core/src/client/frame-protocol.ts +17 -2
  32. package/corpus/core/src/client/rich-markdown-editor/RegistryBlockNode.tsx +2 -2
  33. package/corpus/core/src/client/sharing/ShareButton.tsx +20 -15
  34. package/corpus/core/src/client/use-agent-engine-configured.ts +80 -28
  35. package/corpus/core/src/server/transcribe-voice.ts +57 -22
  36. package/corpus/core/src/shared/mcp-embed-headers.ts +23 -1
  37. package/corpus/core/src/styles/agent-native.css +56 -7
  38. package/corpus/core/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  39. package/corpus/core/src/vite/client.ts +2 -0
  40. package/corpus/core/src/voice/index.ts +18 -0
  41. package/corpus/core/src/voice/voice-cleanup-prompt.ts +38 -0
  42. package/corpus/core/src/voice/voice-context.ts +217 -0
  43. package/corpus/core/src/voice/voice-replacements.ts +75 -0
  44. package/corpus/templates/analytics/AGENTS.md +4 -3
  45. package/corpus/templates/analytics/actions/list-session-recordings.ts +6 -1
  46. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +557 -400
  47. package/corpus/templates/analytics/app/global.css +0 -4
  48. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  49. package/corpus/templates/analytics/app/i18n-data.ts +20 -0
  50. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +33 -8
  51. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +7 -0
  52. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +7 -0
  53. package/corpus/templates/analytics/changelog/2026-06-28-ask-tab-now-matches-the-darker-app-background.md +6 -0
  54. package/corpus/templates/analytics/changelog/2026-06-28-dashboard-email-reports-now-use-cleaner-light-mode-snapshots.md +6 -0
  55. package/corpus/templates/analytics/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  56. package/corpus/templates/analytics/changelog/2026-06-28-sessions-now-show-only-signed-in-recordings-with-playable-re.md +6 -0
  57. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +3 -3
  58. package/corpus/templates/analytics/server/lib/dashboard-report.ts +29 -7
  59. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +5 -2
  60. package/corpus/templates/analytics/server/lib/session-replay.ts +24 -4
  61. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -4
  62. package/corpus/templates/assets/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  63. package/corpus/templates/brain/app/components/layout/Layout.tsx +29 -2
  64. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +131 -42
  65. package/corpus/templates/brain/app/i18n/zh-TW.ts +2 -0
  66. package/corpus/templates/brain/app/i18n-data.ts +20 -0
  67. package/corpus/templates/brain/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  68. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +29 -1
  69. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +417 -299
  70. package/corpus/templates/calendar/app/i18n/zh-TW.ts +2 -0
  71. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  72. package/corpus/templates/calendar/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  73. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +6 -19
  74. package/corpus/templates/chat/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  75. package/corpus/templates/clips/AGENTS.md +4 -0
  76. package/corpus/templates/clips/actions/cleanup-transcript.ts +39 -9
  77. package/corpus/templates/clips/actions/finalize-meeting.ts +81 -12
  78. package/corpus/templates/clips/actions/lib/agents-md-context.ts +4 -1
  79. package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -3
  80. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +1 -1
  81. package/corpus/templates/clips/app/components/player/share-dialog.tsx +2 -2
  82. package/corpus/templates/clips/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
  83. package/corpus/templates/clips/desktop/src/lib/personal-vocabulary.ts +13 -8
  84. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +60 -1
  85. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +1 -1
  86. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +1 -1
  87. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +5 -5
  88. package/corpus/templates/content/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  89. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +137 -0
  90. package/corpus/templates/design/AGENTS.md +95 -5
  91. package/corpus/templates/design/DESIGN.md +82 -0
  92. package/corpus/templates/design/DEVELOPING.md +43 -0
  93. package/corpus/templates/design/actions/add-localhost-screens.ts +426 -0
  94. package/corpus/templates/design/actions/apply-shader.ts +277 -0
  95. package/corpus/templates/design/actions/apply-visual-edit.ts +303 -0
  96. package/corpus/templates/design/actions/connect-localhost.ts +183 -0
  97. package/corpus/templates/design/actions/edit-design.ts +195 -42
  98. package/corpus/templates/design/actions/generate-design.ts +71 -1
  99. package/corpus/templates/design/actions/generate-screens.ts +193 -0
  100. package/corpus/templates/design/actions/get-code-layer-projection.ts +191 -0
  101. package/corpus/templates/design/actions/get-shader.ts +119 -0
  102. package/corpus/templates/design/actions/list-localhost-connections.ts +95 -0
  103. package/corpus/templates/design/actions/navigate.ts +55 -2
  104. package/corpus/templates/design/actions/view-screen.ts +68 -1
  105. package/corpus/templates/design/agent-native.app-skill.json +10 -0
  106. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +761 -0
  107. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +2057 -103
  108. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +74 -38
  109. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +399 -0
  110. package/corpus/templates/design/app/components/design/EditPanel.tsx +3185 -439
  111. package/corpus/templates/design/app/components/design/LayersPanel.tsx +1124 -0
  112. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +3116 -147
  113. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +700 -13
  114. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +107 -63
  115. package/corpus/templates/design/app/components/design/index.ts +0 -5
  116. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +286 -0
  117. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +699 -0
  118. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +461 -0
  119. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +166 -0
  120. package/corpus/templates/design/app/components/design/inspector/FigmaColorPicker.tsx +1420 -0
  121. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +420 -0
  122. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +215 -0
  123. package/corpus/templates/design/app/components/design/inspector/ShaderControls.stories.tsx +71 -0
  124. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +518 -0
  125. package/corpus/templates/design/app/components/design/inspector/ShaderEffectRow.tsx +106 -0
  126. package/corpus/templates/design/app/components/design/inspector/figma-icons.tsx +361 -0
  127. package/corpus/templates/design/app/components/design/inspector/index.ts +60 -0
  128. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +201 -0
  129. package/corpus/templates/design/app/components/design/types.ts +23 -0
  130. package/corpus/templates/design/app/components/layout/Layout.tsx +24 -13
  131. package/corpus/templates/design/app/components/layout/Sidebar.tsx +4 -4
  132. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +474 -36
  133. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +22 -5
  134. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +24 -11
  135. package/corpus/templates/design/app/global.css +121 -3
  136. package/corpus/templates/design/app/hooks/use-agent-generating.ts +4 -0
  137. package/corpus/templates/design/app/hooks/use-navigation-state.ts +130 -8
  138. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +329 -0
  139. package/corpus/templates/design/app/i18n/zh-TW.ts +264 -0
  140. package/corpus/templates/design/app/i18n-data.ts +2790 -1
  141. package/corpus/templates/design/app/pages/DesignEditor.tsx +5108 -851
  142. package/corpus/templates/design/changelog/2026-06-28-annotate-mode-now-combines-drawing-and-comment-pins-in-one-c.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-28-design-editor-panel-borders-are-cleaner-so-the-canvas-edge-s.md +6 -0
  144. package/corpus/templates/design/changelog/2026-06-28-drawing-prompts-now-reliably-open-and-submit-to-the-design-a.md +6 -0
  145. package/corpus/templates/design/changelog/2026-06-28-editor-app-menus-now-use-an-outline-agent-native-mark-with-b.md +6 -0
  146. package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +6 -0
  147. package/corpus/templates/design/changelog/2026-06-28-layer-selection-now-updates-chat-context-without-opening-the.md +6 -0
  148. package/corpus/templates/design/changelog/2026-06-28-layer-selections-now-preserve-hidden-and-locked-canvas-state.md +6 -0
  149. package/corpus/templates/design/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  150. package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +6 -0
  151. package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-handles-zooming-drawing-opening-and-dele.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-28-screen-overview-now-selects-screens-on-click-and-opens-them-.md +6 -0
  154. package/corpus/templates/design/changelog/2026-06-28-screen-overview-zooms-into-the-active-screen-with-smoother-e.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-28-selected-element-chips-in-chat-are-shorter-and-easier-to-sca.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-28-the-design-editor-toolbar-is-more-compact-and-the-app-menu-m.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-at-the-top-of-the-design-sidebar-instead-of-.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-28-tweaks-now-live-in-the-right-inspector-beside-the-design-tab.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-29-active-sidebar-rows-use-soft-highlights.md +6 -0
  160. package/corpus/templates/design/changelog/2026-06-29-agents-can-now-open-screen-overview-focus-screens-and-add.md +6 -0
  161. package/corpus/templates/design/changelog/2026-06-29-design-agents-now-edit-broad-copy-changes-more-reliably.md +6 -0
  162. package/corpus/templates/design/changelog/2026-06-29-design-editor-extensions-now-run-inline-beside-the-artboard.md +6 -0
  163. package/corpus/templates/design/changelog/2026-06-29-sidebars-use-darker-chrome-and-chat-warns-immediately.md +6 -0
  164. package/corpus/templates/design/changelog/2026-06-29-visual-edit-can-open-localhost-routes-as-url-backed-screens.md +6 -0
  165. package/corpus/templates/design/package.json +1 -0
  166. package/corpus/templates/design/server/db/schema.ts +26 -0
  167. package/corpus/templates/design/server/lib/design-snapshot.ts +1 -1
  168. package/corpus/templates/design/server/plugins/agent-chat.ts +3 -0
  169. package/corpus/templates/design/shared/api.ts +27 -0
  170. package/corpus/templates/design/shared/canvas-frames.ts +103 -0
  171. package/corpus/templates/design/shared/canvas-math.ts +1058 -0
  172. package/corpus/templates/design/shared/code-layer.ts +2212 -0
  173. package/corpus/templates/design/shared/color-utils.ts +180 -0
  174. package/corpus/templates/design/shared/generation-session.ts +61 -0
  175. package/corpus/templates/design/shared/shader-presets.ts +782 -0
  176. package/corpus/templates/design/shared/shader-safety.ts +137 -0
  177. package/corpus/templates/design/shared/source-mode.ts +237 -0
  178. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +149 -6
  179. package/corpus/templates/forms/app/i18n/ar-SA.ts +2 -0
  180. package/corpus/templates/forms/app/i18n/de-DE.ts +2 -0
  181. package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
  182. package/corpus/templates/forms/app/i18n/es-ES.ts +2 -0
  183. package/corpus/templates/forms/app/i18n/fr-FR.ts +2 -0
  184. package/corpus/templates/forms/app/i18n/hi-IN.ts +2 -0
  185. package/corpus/templates/forms/app/i18n/ja-JP.ts +2 -0
  186. package/corpus/templates/forms/app/i18n/ko-KR.ts +2 -0
  187. package/corpus/templates/forms/app/i18n/pt-BR.ts +2 -0
  188. package/corpus/templates/forms/app/i18n/zh-CN.ts +2 -0
  189. package/corpus/templates/forms/app/i18n/zh-TW.ts +2 -0
  190. package/corpus/templates/forms/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  191. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +2 -2
  192. package/corpus/templates/macros/changelog/2026-06-28-left-sidebar-footer-controls-use-less-divider-chrome.md +6 -0
  193. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +408 -247
  194. package/corpus/templates/mail/app/i18n/ar-SA.ts +4 -0
  195. package/corpus/templates/mail/app/i18n/de-DE.ts +4 -0
  196. package/corpus/templates/mail/app/i18n/en-US.ts +4 -0
  197. package/corpus/templates/mail/app/i18n/es-ES.ts +4 -0
  198. package/corpus/templates/mail/app/i18n/fr-FR.ts +4 -0
  199. package/corpus/templates/mail/app/i18n/hi-IN.ts +4 -0
  200. package/corpus/templates/mail/app/i18n/ja-JP.ts +4 -0
  201. package/corpus/templates/mail/app/i18n/ko-KR.ts +4 -0
  202. package/corpus/templates/mail/app/i18n/pt-BR.ts +4 -0
  203. package/corpus/templates/mail/app/i18n/zh-CN.ts +4 -0
  204. package/corpus/templates/mail/app/i18n/zh-TW.ts +4 -0
  205. package/corpus/templates/mail/changelog/2026-06-28-pinned-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  206. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +6 -8
  207. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +1 -1
  208. package/corpus/templates/plan/app/global.css +61 -78
  209. package/corpus/templates/plan/app/pages/PlansPage.tsx +8 -0
  210. package/corpus/templates/plan/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  211. package/corpus/templates/plan/changelog/2026-06-28-plans-use-the-shared-neutral-light-and-dark-theme.md +6 -0
  212. package/corpus/templates/plan/changelog/2026-06-28-sending-open-feedback-from-a-plan-now-opens-the-inline-plan-.md +6 -0
  213. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +5 -5
  214. package/corpus/templates/slides/changelog/2026-06-28-left-sidebar-collapse-motion-and-footer-chrome-are-quieter.md +6 -0
  215. package/corpus/templates/videos/app/components/Sidebar.tsx +2 -2
  216. package/corpus/templates/videos/app/components/layout/Layout.tsx +29 -2
  217. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +113 -30
  218. package/corpus/templates/videos/app/i18n/ar-SA.ts +2 -0
  219. package/corpus/templates/videos/app/i18n/de-DE.ts +2 -0
  220. package/corpus/templates/videos/app/i18n/en-US.ts +2 -0
  221. package/corpus/templates/videos/app/i18n/es-ES.ts +2 -0
  222. package/corpus/templates/videos/app/i18n/fr-FR.ts +2 -0
  223. package/corpus/templates/videos/app/i18n/hi-IN.ts +2 -0
  224. package/corpus/templates/videos/app/i18n/ja-JP.ts +2 -0
  225. package/corpus/templates/videos/app/i18n/ko-KR.ts +2 -0
  226. package/corpus/templates/videos/app/i18n/pt-BR.ts +2 -0
  227. package/corpus/templates/videos/app/i18n/zh-CN.ts +2 -0
  228. package/corpus/templates/videos/app/i18n/zh-TW.ts +2 -0
  229. package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +6 -0
  230. package/dist/agent/engine/index.d.ts +1 -1
  231. package/dist/agent/engine/index.d.ts.map +1 -1
  232. package/dist/agent/engine/index.js +1 -1
  233. package/dist/agent/engine/index.js.map +1 -1
  234. package/dist/agent/engine/registry.d.ts +9 -0
  235. package/dist/agent/engine/registry.d.ts.map +1 -1
  236. package/dist/agent/engine/registry.js +37 -0
  237. package/dist/agent/engine/registry.js.map +1 -1
  238. package/dist/agent/production-agent.d.ts.map +1 -1
  239. package/dist/agent/production-agent.js +4 -3
  240. package/dist/agent/production-agent.js.map +1 -1
  241. package/dist/cli/design-connect.d.ts +52 -0
  242. package/dist/cli/design-connect.d.ts.map +1 -0
  243. package/dist/cli/design-connect.js +409 -0
  244. package/dist/cli/design-connect.js.map +1 -0
  245. package/dist/cli/index.js +15 -0
  246. package/dist/cli/index.js.map +1 -1
  247. package/dist/cli/skills.d.ts +3 -0
  248. package/dist/cli/skills.d.ts.map +1 -1
  249. package/dist/cli/skills.js +121 -3
  250. package/dist/cli/skills.js.map +1 -1
  251. package/dist/client/AssistantChat.d.ts.map +1 -1
  252. package/dist/client/AssistantChat.js +38 -8
  253. package/dist/client/AssistantChat.js.map +1 -1
  254. package/dist/client/agent-chat.d.ts +6 -0
  255. package/dist/client/agent-chat.d.ts.map +1 -1
  256. package/dist/client/agent-chat.js +17 -3
  257. package/dist/client/agent-chat.js.map +1 -1
  258. package/dist/client/blocks/library/MermaidBlock.js +1 -1
  259. package/dist/client/blocks/library/MermaidBlock.js.map +1 -1
  260. package/dist/client/blocks/library/diagram.js +1 -1
  261. package/dist/client/blocks/library/diagram.js.map +1 -1
  262. package/dist/client/blocks/library/wireframe.js +1 -1
  263. package/dist/client/blocks/library/wireframe.js.map +1 -1
  264. package/dist/client/chat/message-components.js +1 -1
  265. package/dist/client/chat/message-components.js.map +1 -1
  266. package/dist/client/chat-model-groups.d.ts.map +1 -1
  267. package/dist/client/chat-model-groups.js +1 -2
  268. package/dist/client/chat-model-groups.js.map +1 -1
  269. package/dist/client/components/LiveCursorOverlay.d.ts +1 -2
  270. package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
  271. package/dist/client/components/LiveCursorOverlay.js +19 -40
  272. package/dist/client/components/LiveCursorOverlay.js.map +1 -1
  273. package/dist/client/composer/TiptapComposer.d.ts +3 -1
  274. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  275. package/dist/client/composer/TiptapComposer.js +86 -24
  276. package/dist/client/composer/TiptapComposer.js.map +1 -1
  277. package/dist/client/composer/useVoiceDictation.d.ts +9 -0
  278. package/dist/client/composer/useVoiceDictation.d.ts.map +1 -1
  279. package/dist/client/composer/useVoiceDictation.js +154 -32
  280. package/dist/client/composer/useVoiceDictation.js.map +1 -1
  281. package/dist/client/frame-protocol.d.ts +17 -2
  282. package/dist/client/frame-protocol.d.ts.map +1 -1
  283. package/dist/client/frame-protocol.js.map +1 -1
  284. package/dist/client/rich-markdown-editor/RegistryBlockNode.js +2 -2
  285. package/dist/client/rich-markdown-editor/RegistryBlockNode.js.map +1 -1
  286. package/dist/client/sharing/ShareButton.d.ts +2 -0
  287. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  288. package/dist/client/sharing/ShareButton.js +7 -6
  289. package/dist/client/sharing/ShareButton.js.map +1 -1
  290. package/dist/client/use-agent-engine-configured.d.ts +5 -0
  291. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  292. package/dist/client/use-agent-engine-configured.js +51 -25
  293. package/dist/client/use-agent-engine-configured.js.map +1 -1
  294. package/dist/collab/awareness.d.ts +2 -2
  295. package/dist/collab/awareness.d.ts.map +1 -1
  296. package/dist/collab/routes.d.ts +1 -1
  297. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  298. package/dist/notifications/routes.d.ts +3 -3
  299. package/dist/observability/routes.d.ts +8 -8
  300. package/dist/progress/routes.d.ts +1 -1
  301. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  302. package/dist/server/transcribe-voice.d.ts.map +1 -1
  303. package/dist/server/transcribe-voice.js +40 -24
  304. package/dist/server/transcribe-voice.js.map +1 -1
  305. package/dist/shared/mcp-embed-headers.d.ts +1 -0
  306. package/dist/shared/mcp-embed-headers.d.ts.map +1 -1
  307. package/dist/shared/mcp-embed-headers.js +20 -1
  308. package/dist/shared/mcp-embed-headers.js.map +1 -1
  309. package/dist/styles/agent-native.css +56 -7
  310. package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  311. package/dist/vite/client.d.ts.map +1 -1
  312. package/dist/vite/client.js +2 -0
  313. package/dist/vite/client.js.map +1 -1
  314. package/dist/voice/index.d.ts +5 -0
  315. package/dist/voice/index.d.ts.map +1 -0
  316. package/dist/voice/index.js +4 -0
  317. package/dist/voice/index.js.map +1 -0
  318. package/dist/voice/voice-cleanup-prompt.d.ts +6 -0
  319. package/dist/voice/voice-cleanup-prompt.d.ts.map +1 -0
  320. package/dist/voice/voice-cleanup-prompt.js +26 -0
  321. package/dist/voice/voice-cleanup-prompt.js.map +1 -0
  322. package/dist/voice/voice-context.d.ts +25 -0
  323. package/dist/voice/voice-context.d.ts.map +1 -0
  324. package/dist/voice/voice-context.js +156 -0
  325. package/dist/voice/voice-context.js.map +1 -0
  326. package/dist/voice/voice-replacements.d.ts +4 -0
  327. package/dist/voice/voice-replacements.d.ts.map +1 -0
  328. package/dist/voice/voice-replacements.js +52 -0
  329. package/dist/voice/voice-replacements.js.map +1 -0
  330. package/docs/content/locales/ar-SA/template-design.mdx +41 -0
  331. package/docs/content/locales/de-DE/template-design.mdx +51 -0
  332. package/docs/content/locales/es-ES/template-design.mdx +50 -0
  333. package/docs/content/locales/fr-FR/template-design.mdx +49 -0
  334. package/docs/content/locales/hi-IN/template-design.mdx +44 -0
  335. package/docs/content/locales/ja-JP/template-design.mdx +46 -0
  336. package/docs/content/locales/ko-KR/template-design.mdx +44 -0
  337. package/docs/content/locales/pt-BR/template-design.mdx +48 -0
  338. package/docs/content/locales/zh-CN/template-design.mdx +36 -0
  339. package/docs/content/locales/zh-TW/template-design.mdx +38 -0
  340. package/docs/content/template-design.mdx +45 -0
  341. package/package.json +2 -1
  342. package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +10 -0
  343. package/corpus/templates/design/app/components/design/DesignToolbar.tsx +0 -339
  344. package/corpus/templates/design/app/components/design/PresentMode.tsx +0 -68
  345. package/corpus/templates/design/app/components/design/ViewportTabs.tsx +0 -149
  346. package/corpus/templates/design/app/components/design/ZoomControls.tsx +0 -157
@@ -0,0 +1,1058 @@
1
+ export interface CanvasPoint {
2
+ x: number;
3
+ y: number;
4
+ }
5
+
6
+ export interface CanvasCamera {
7
+ x: number;
8
+ y: number;
9
+ zoom: number;
10
+ }
11
+
12
+ export interface CanvasSize {
13
+ width: number;
14
+ height: number;
15
+ }
16
+
17
+ export interface FrameGeometry {
18
+ x: number;
19
+ y: number;
20
+ width: number;
21
+ height: number;
22
+ }
23
+
24
+ export interface FrameEntry {
25
+ id: string;
26
+ geometry: FrameGeometry;
27
+ }
28
+
29
+ export type ResizeHandle = "nw" | "n" | "ne" | "e" | "se" | "s" | "sw" | "w";
30
+
31
+ export type FrameBoundsInput = FrameEntry | FrameGeometry;
32
+
33
+ export interface FrameBounds {
34
+ left: number;
35
+ top: number;
36
+ right: number;
37
+ bottom: number;
38
+ width: number;
39
+ height: number;
40
+ centerX: number;
41
+ centerY: number;
42
+ }
43
+
44
+ export interface AssignedCanvasRegion extends FrameGeometry {
45
+ index: number;
46
+ row: number;
47
+ column: number;
48
+ }
49
+
50
+ export interface AssignRegionsOptions {
51
+ origin?: CanvasPoint;
52
+ regionSize?: CanvasSize;
53
+ gap?: number;
54
+ columns?: number;
55
+ maxColumns?: number;
56
+ }
57
+
58
+ export interface CanvasSnapOptions {
59
+ thresholdScreenPx?: number;
60
+ zoom: number;
61
+ bypass?: boolean;
62
+ }
63
+
64
+ export interface ResizeFrameOptions {
65
+ preserveAspectRatio?: boolean;
66
+ resizeFromCenter?: boolean;
67
+ minWidth?: number;
68
+ minHeight?: number;
69
+ }
70
+
71
+ export interface ResizeGroupResult {
72
+ bounds: FrameGeometry;
73
+ frames: FrameEntry[];
74
+ }
75
+
76
+ export interface DraftGeometryOptions {
77
+ minWidth?: number;
78
+ minHeight?: number;
79
+ defaultWidth?: number;
80
+ defaultHeight?: number;
81
+ }
82
+
83
+ export interface AlignmentGuide {
84
+ orientation: "vertical" | "horizontal";
85
+ position: number;
86
+ start: number;
87
+ end: number;
88
+ }
89
+
90
+ export interface RotateFrameMetadata {
91
+ id: string;
92
+ geometry: FrameGeometry;
93
+ center: CanvasPoint;
94
+ startAngle: number;
95
+ initialRotation: number;
96
+ }
97
+
98
+ export interface RotateFrameResult {
99
+ id: string;
100
+ angle: number;
101
+ rawAngle: number;
102
+ delta: number;
103
+ snapped: boolean;
104
+ }
105
+
106
+ export interface RotationSnapOptions {
107
+ shiftKey?: boolean;
108
+ incrementDegrees?: number;
109
+ }
110
+
111
+ export interface FitViewportOptions {
112
+ paddingScreenPx?: number;
113
+ canvasPadding?: number;
114
+ minZoom?: number;
115
+ maxZoom?: number;
116
+ fallbackZoom?: number;
117
+ }
118
+
119
+ export interface RulerTick {
120
+ value: number;
121
+ position: number;
122
+ label: string;
123
+ }
124
+
125
+ export interface RulerTicks {
126
+ x: RulerTick[];
127
+ y: RulerTick[];
128
+ }
129
+
130
+ export interface RulerTickOptions {
131
+ minTickSpacingPx?: number;
132
+ canvasPadding?: number;
133
+ maxTicks?: number;
134
+ }
135
+
136
+ export type ArrowNudgeKey =
137
+ | "ArrowUp"
138
+ | "ArrowRight"
139
+ | "ArrowDown"
140
+ | "ArrowLeft";
141
+
142
+ export interface NudgeModifiers {
143
+ shiftKey?: boolean;
144
+ altKey?: boolean;
145
+ metaKey?: boolean;
146
+ ctrlKey?: boolean;
147
+ }
148
+
149
+ export interface NudgeOptions {
150
+ baseStep?: number;
151
+ shiftMultiplier?: number;
152
+ }
153
+
154
+ export interface NudgeDelta {
155
+ dx: number;
156
+ dy: number;
157
+ step: number;
158
+ snap: {
159
+ bypass: boolean;
160
+ reason: "modifier" | null;
161
+ };
162
+ }
163
+
164
+ interface SnapCandidate {
165
+ distance: number;
166
+ offset: number;
167
+ guide: AlignmentGuide;
168
+ }
169
+
170
+ export const DEFAULT_SNAP_THRESHOLD_SCREEN_PX = 6;
171
+ export const DEFAULT_ROTATION_SNAP_DEGREES = 15;
172
+ export const DEFAULT_PIXEL_GRID_MIN_ZOOM = 800;
173
+ export const MIN_CANVAS_FRAME_WIDTH = 120;
174
+ export const MIN_CANVAS_FRAME_HEIGHT = 120;
175
+ export const DEFAULT_ASSIGNED_REGION_WIDTH = 1440;
176
+ export const DEFAULT_ASSIGNED_REGION_HEIGHT = 1024;
177
+ export const DEFAULT_ASSIGNED_REGION_GAP = 320;
178
+ export const DEFAULT_ASSIGNED_REGION_MAX_COLUMNS = 3;
179
+
180
+ export function screenToCanvasPoint(
181
+ point: CanvasPoint,
182
+ camera: CanvasCamera,
183
+ surfaceOrigin: CanvasPoint = { x: 0, y: 0 },
184
+ padding = 0,
185
+ round = false,
186
+ ): CanvasPoint {
187
+ const scale = camera.zoom / 100;
188
+ if (scale === 0) return { x: 0, y: 0 };
189
+ const next = {
190
+ x: (point.x - surfaceOrigin.x - camera.x) / scale - padding,
191
+ y: (point.y - surfaceOrigin.y - camera.y) / scale - padding,
192
+ };
193
+ return round ? { x: Math.round(next.x), y: Math.round(next.y) } : next;
194
+ }
195
+
196
+ export function canvasToScreenPoint(
197
+ point: CanvasPoint,
198
+ camera: CanvasCamera,
199
+ surfaceOrigin: CanvasPoint = { x: 0, y: 0 },
200
+ padding = 0,
201
+ ): CanvasPoint {
202
+ const scale = camera.zoom / 100;
203
+ return {
204
+ x: surfaceOrigin.x + camera.x + (point.x + padding) * scale,
205
+ y: surfaceOrigin.y + camera.y + (point.y + padding) * scale,
206
+ };
207
+ }
208
+
209
+ export function getPanForZoomToCursor({
210
+ pan,
211
+ cursor,
212
+ oldZoom,
213
+ nextZoom,
214
+ }: {
215
+ pan: CanvasPoint;
216
+ cursor: CanvasPoint;
217
+ oldZoom: number;
218
+ nextZoom: number;
219
+ }): CanvasPoint {
220
+ const ratio = nextZoom / oldZoom;
221
+ return {
222
+ x: cursor.x - (cursor.x - pan.x) * ratio,
223
+ y: cursor.y - (cursor.y - pan.y) * ratio,
224
+ };
225
+ }
226
+
227
+ export function getAngleFromCenter(
228
+ center: CanvasPoint,
229
+ point: CanvasPoint,
230
+ ): number {
231
+ return radiansToDegrees(Math.atan2(point.y - center.y, point.x - center.x));
232
+ }
233
+
234
+ export function getAngleDeltaDegrees(from: number, to: number): number {
235
+ return ((((to - from) % 360) + 540) % 360) - 180;
236
+ }
237
+
238
+ export function snapAngleToIncrement(
239
+ angle: number,
240
+ {
241
+ shiftKey = false,
242
+ incrementDegrees = DEFAULT_ROTATION_SNAP_DEGREES,
243
+ }: RotationSnapOptions = {},
244
+ ): number {
245
+ if (!shiftKey || incrementDegrees <= 0) return angle;
246
+ return Math.round(angle / incrementDegrees) * incrementDegrees;
247
+ }
248
+
249
+ export function getRotateFrameMetadata(
250
+ entry: FrameEntry,
251
+ pointer: CanvasPoint,
252
+ {
253
+ center,
254
+ initialRotation = 0,
255
+ }: { center?: CanvasPoint; initialRotation?: number } = {},
256
+ ): RotateFrameMetadata {
257
+ const bounds = getFrameBounds(entry.geometry);
258
+ const rotationCenter = center ?? { x: bounds.centerX, y: bounds.centerY };
259
+ return {
260
+ id: entry.id,
261
+ geometry: entry.geometry,
262
+ center: rotationCenter,
263
+ startAngle: getAngleFromCenter(rotationCenter, pointer),
264
+ initialRotation,
265
+ };
266
+ }
267
+
268
+ export function getRotatedFrameAngle(
269
+ metadata: RotateFrameMetadata,
270
+ pointer: CanvasPoint,
271
+ options: RotationSnapOptions = {},
272
+ ): RotateFrameResult {
273
+ const currentAngle = getAngleFromCenter(metadata.center, pointer);
274
+ const delta = getAngleDeltaDegrees(metadata.startAngle, currentAngle);
275
+ const rawAngle = metadata.initialRotation + delta;
276
+ const angle = snapAngleToIncrement(rawAngle, options);
277
+ const incrementDegrees =
278
+ options.incrementDegrees ?? DEFAULT_ROTATION_SNAP_DEGREES;
279
+ return {
280
+ id: metadata.id,
281
+ angle,
282
+ rawAngle,
283
+ delta,
284
+ snapped: !!options.shiftKey && incrementDegrees > 0,
285
+ };
286
+ }
287
+
288
+ export function getFrameBounds(geometry: FrameGeometry): FrameBounds {
289
+ const width = geometry.width;
290
+ const height = geometry.height;
291
+ return {
292
+ left: geometry.x,
293
+ top: geometry.y,
294
+ right: geometry.x + width,
295
+ bottom: geometry.y + height,
296
+ width,
297
+ height,
298
+ centerX: geometry.x + width / 2,
299
+ centerY: geometry.y + height / 2,
300
+ };
301
+ }
302
+
303
+ export function getFrameGroupBounds(
304
+ frames: readonly FrameBoundsInput[],
305
+ ): FrameBounds | null {
306
+ if (frames.length === 0) return null;
307
+
308
+ const bounds = frames.map((frame) => getFrameBounds(getFrameGeometry(frame)));
309
+ const left = Math.min(...bounds.map((bound) => bound.left));
310
+ const top = Math.min(...bounds.map((bound) => bound.top));
311
+ const right = Math.max(...bounds.map((bound) => bound.right));
312
+ const bottom = Math.max(...bounds.map((bound) => bound.bottom));
313
+ return getFrameBounds({
314
+ x: left,
315
+ y: top,
316
+ width: right - left,
317
+ height: bottom - top,
318
+ });
319
+ }
320
+
321
+ export function assignRegions(
322
+ count: number,
323
+ options: AssignRegionsOptions = {},
324
+ ): AssignedCanvasRegion[] {
325
+ if (!Number.isFinite(count) || count <= 0) return [];
326
+
327
+ const total = Math.floor(count);
328
+ const origin = options.origin ?? { x: 0, y: 0 };
329
+ const width = getPositiveFiniteNumber(
330
+ options.regionSize?.width,
331
+ DEFAULT_ASSIGNED_REGION_WIDTH,
332
+ );
333
+ const height = getPositiveFiniteNumber(
334
+ options.regionSize?.height,
335
+ DEFAULT_ASSIGNED_REGION_HEIGHT,
336
+ );
337
+ const gap = Math.max(
338
+ 0,
339
+ getFiniteNumber(options.gap, DEFAULT_ASSIGNED_REGION_GAP),
340
+ );
341
+ const maxColumns = getWholeNumberAtLeast(
342
+ options.maxColumns,
343
+ DEFAULT_ASSIGNED_REGION_MAX_COLUMNS,
344
+ 1,
345
+ );
346
+ const requestedColumns =
347
+ options.columns == null
348
+ ? maxColumns
349
+ : getWholeNumberAtLeast(options.columns, maxColumns, 1);
350
+ const columns = Math.min(total, requestedColumns);
351
+
352
+ return Array.from({ length: total }, (_, index) => {
353
+ const row = Math.floor(index / columns);
354
+ const column = index % columns;
355
+ return {
356
+ index,
357
+ row,
358
+ column,
359
+ x: origin.x + column * (width + gap),
360
+ y: origin.y + row * (height + gap),
361
+ width,
362
+ height,
363
+ };
364
+ });
365
+ }
366
+
367
+ export function getCameraForBounds(
368
+ bounds: FrameBounds | FrameGeometry | null,
369
+ viewport: CanvasSize,
370
+ {
371
+ paddingScreenPx = 48,
372
+ canvasPadding = 0,
373
+ minZoom = 10,
374
+ maxZoom = 400,
375
+ fallbackZoom = 100,
376
+ }: FitViewportOptions = {},
377
+ ): CanvasCamera {
378
+ if (!bounds || viewport.width <= 0 || viewport.height <= 0) {
379
+ return { x: 0, y: 0, zoom: fallbackZoom };
380
+ }
381
+
382
+ const geometry = getBoundsGeometry(bounds);
383
+ const availableWidth = Math.max(1, viewport.width - paddingScreenPx * 2);
384
+ const availableHeight = Math.max(1, viewport.height - paddingScreenPx * 2);
385
+ const scale = Math.min(
386
+ availableWidth / Math.max(1, geometry.width),
387
+ availableHeight / Math.max(1, geometry.height),
388
+ );
389
+ const zoom = clamp(scale * 100, minZoom, maxZoom);
390
+ const nextScale = zoom / 100;
391
+
392
+ return {
393
+ x:
394
+ (viewport.width - geometry.width * nextScale) / 2 -
395
+ (geometry.x + canvasPadding) * nextScale,
396
+ y:
397
+ (viewport.height - geometry.height * nextScale) / 2 -
398
+ (geometry.y + canvasPadding) * nextScale,
399
+ zoom,
400
+ };
401
+ }
402
+
403
+ export function getRulerTicks(
404
+ camera: CanvasCamera,
405
+ viewport: CanvasSize,
406
+ options: RulerTickOptions = {},
407
+ ): RulerTicks {
408
+ return {
409
+ x: getAxisRulerTicks("x", camera, viewport.width, options),
410
+ y: getAxisRulerTicks("y", camera, viewport.height, options),
411
+ };
412
+ }
413
+
414
+ export function shouldShowPixelGrid(
415
+ zoom: number,
416
+ minZoom = DEFAULT_PIXEL_GRID_MIN_ZOOM,
417
+ ): boolean {
418
+ return zoom >= minZoom;
419
+ }
420
+
421
+ export function getNudgeDelta(
422
+ key: ArrowNudgeKey,
423
+ modifiers: NudgeModifiers = {},
424
+ { baseStep = 1, shiftMultiplier = 10 }: NudgeOptions = {},
425
+ ): NudgeDelta {
426
+ const step = baseStep * (modifiers.shiftKey ? shiftMultiplier : 1);
427
+ const vector = getNudgeVector(key);
428
+ const bypass = !!(modifiers.altKey || modifiers.metaKey || modifiers.ctrlKey);
429
+
430
+ return {
431
+ dx: vector.x * step,
432
+ dy: vector.y * step,
433
+ step,
434
+ snap: {
435
+ bypass,
436
+ reason: bypass ? "modifier" : null,
437
+ },
438
+ };
439
+ }
440
+
441
+ export function getDraftGeometryFromPoints(
442
+ start: CanvasPoint,
443
+ end: CanvasPoint,
444
+ {
445
+ minWidth = 1,
446
+ minHeight = 1,
447
+ defaultWidth,
448
+ defaultHeight,
449
+ }: DraftGeometryOptions = {},
450
+ ): FrameGeometry {
451
+ const rawWidth = Math.abs(end.x - start.x);
452
+ const rawHeight = Math.abs(end.y - start.y);
453
+ const width = Math.max(rawWidth || defaultWidth || 0, minWidth);
454
+ const height = Math.max(rawHeight || defaultHeight || 0, minHeight);
455
+ const drawingLeft = end.x < start.x;
456
+ const drawingUp = end.y < start.y;
457
+
458
+ return {
459
+ x: drawingLeft ? start.x - width : start.x,
460
+ y: drawingUp ? start.y - height : start.y,
461
+ width,
462
+ height,
463
+ };
464
+ }
465
+
466
+ export function appendPolylinePoint(
467
+ points: readonly CanvasPoint[],
468
+ nextPoint: CanvasPoint,
469
+ minDistance = 4,
470
+ ): CanvasPoint[] {
471
+ const previous = points[points.length - 1];
472
+ if (!previous) return [nextPoint];
473
+ if (
474
+ Math.hypot(nextPoint.x - previous.x, nextPoint.y - previous.y) < minDistance
475
+ ) {
476
+ return [...points];
477
+ }
478
+ return [...points, nextPoint];
479
+ }
480
+
481
+ export function computeMoveSnap(
482
+ moving: FrameEntry[],
483
+ stationary: FrameEntry[],
484
+ options: CanvasSnapOptions,
485
+ ) {
486
+ if (options.bypass) {
487
+ return { dx: 0, dy: 0, guides: [] as AlignmentGuide[] };
488
+ }
489
+
490
+ let bestX: SnapCandidate | null = null;
491
+ let bestY: SnapCandidate | null = null;
492
+ const threshold = getCanvasSnapThreshold(options);
493
+ const stationaryBounds = stationary.map((entry) => ({
494
+ ...entry,
495
+ bounds: getFrameBounds(entry.geometry),
496
+ }));
497
+
498
+ for (const entry of moving) {
499
+ const movingBounds = getFrameBounds(entry.geometry);
500
+ for (const stationaryEntry of stationaryBounds) {
501
+ bestX = getBestCandidate(
502
+ bestX,
503
+ getAxisSnapCandidates(
504
+ "x",
505
+ movingBounds,
506
+ stationaryEntry.bounds,
507
+ threshold,
508
+ ),
509
+ );
510
+ bestY = getBestCandidate(
511
+ bestY,
512
+ getAxisSnapCandidates(
513
+ "y",
514
+ movingBounds,
515
+ stationaryEntry.bounds,
516
+ threshold,
517
+ ),
518
+ );
519
+ }
520
+ }
521
+
522
+ return {
523
+ dx: bestX?.offset ?? 0,
524
+ dy: bestY?.offset ?? 0,
525
+ guides: [bestX?.guide, bestY?.guide].filter(Boolean) as AlignmentGuide[],
526
+ };
527
+ }
528
+
529
+ export function resizeFrameFromDelta(
530
+ origin: FrameGeometry,
531
+ handle: ResizeHandle,
532
+ dx: number,
533
+ dy: number,
534
+ options: ResizeFrameOptions = {},
535
+ ) {
536
+ const ratio = origin.width / Math.max(1, origin.height);
537
+ const affectsHorizontal =
538
+ handleAffectsWest(handle) || handleAffectsEast(handle);
539
+ const affectsVertical =
540
+ handleAffectsNorth(handle) || handleAffectsSouth(handle);
541
+ const horizontalDelta = handleAffectsWest(handle) ? -dx : dx;
542
+ const verticalDelta = handleAffectsNorth(handle) ? -dy : dy;
543
+ let width = affectsHorizontal
544
+ ? origin.width + horizontalDelta * (options.resizeFromCenter ? 2 : 1)
545
+ : origin.width;
546
+ let height = affectsVertical
547
+ ? origin.height + verticalDelta * (options.resizeFromCenter ? 2 : 1)
548
+ : origin.height;
549
+
550
+ if (options.preserveAspectRatio) {
551
+ if (affectsHorizontal && affectsVertical) {
552
+ const widthChange = Math.abs(width - origin.width);
553
+ const heightChange = Math.abs(height - origin.height);
554
+ if (widthChange >= heightChange) {
555
+ height = width / ratio;
556
+ } else {
557
+ width = height * ratio;
558
+ }
559
+ } else if (affectsHorizontal) {
560
+ height = width / ratio;
561
+ } else if (affectsVertical) {
562
+ width = height * ratio;
563
+ }
564
+ }
565
+
566
+ width = Math.max(options.minWidth ?? MIN_CANVAS_FRAME_WIDTH, width);
567
+ height = Math.max(options.minHeight ?? MIN_CANVAS_FRAME_HEIGHT, height);
568
+
569
+ return {
570
+ ...origin,
571
+ x: getResizedAxisStart(
572
+ origin.x,
573
+ origin.width,
574
+ width,
575
+ handleAffectsWest(handle),
576
+ handleAffectsEast(handle),
577
+ options.resizeFromCenter ||
578
+ (!affectsHorizontal && width !== origin.width),
579
+ ),
580
+ y: getResizedAxisStart(
581
+ origin.y,
582
+ origin.height,
583
+ height,
584
+ handleAffectsNorth(handle),
585
+ handleAffectsSouth(handle),
586
+ options.resizeFromCenter ||
587
+ (!affectsVertical && height !== origin.height),
588
+ ),
589
+ width,
590
+ height,
591
+ };
592
+ }
593
+
594
+ export function resizeFrameGroupFromDelta(
595
+ frames: FrameEntry[],
596
+ originBounds: FrameBounds | FrameGeometry,
597
+ handle: ResizeHandle,
598
+ dx: number,
599
+ dy: number,
600
+ options: ResizeFrameOptions = {},
601
+ ): ResizeGroupResult {
602
+ const originGeometry = getBoundsGeometry(originBounds);
603
+ const minimums = getGroupMinimumBounds(frames, originGeometry, options);
604
+ const bounds = resizeFrameFromDelta(originGeometry, handle, dx, dy, {
605
+ ...options,
606
+ minWidth: minimums.width,
607
+ minHeight: minimums.height,
608
+ });
609
+
610
+ return {
611
+ bounds,
612
+ frames: resizeFrameGroupToBounds(frames, originGeometry, bounds),
613
+ };
614
+ }
615
+
616
+ export function resizeFrameGroupToBounds(
617
+ frames: FrameEntry[],
618
+ originBounds: FrameBounds | FrameGeometry,
619
+ nextBounds: FrameBounds | FrameGeometry,
620
+ ): FrameEntry[] {
621
+ const originGeometry = getBoundsGeometry(originBounds);
622
+ const nextGeometry = getBoundsGeometry(nextBounds);
623
+ const scaleX = nextGeometry.width / Math.max(1, originGeometry.width);
624
+ const scaleY = nextGeometry.height / Math.max(1, originGeometry.height);
625
+
626
+ return frames.map((frame) => ({
627
+ id: frame.id,
628
+ geometry: {
629
+ x: nextGeometry.x + (frame.geometry.x - originGeometry.x) * scaleX,
630
+ y: nextGeometry.y + (frame.geometry.y - originGeometry.y) * scaleY,
631
+ width: frame.geometry.width * scaleX,
632
+ height: frame.geometry.height * scaleY,
633
+ },
634
+ }));
635
+ }
636
+
637
+ export function computeResizeSnap(
638
+ frame: FrameGeometry,
639
+ stationary: FrameEntry[],
640
+ handle: ResizeHandle,
641
+ options: CanvasSnapOptions,
642
+ ) {
643
+ if (options.bypass) {
644
+ return { frame, guides: [] as AlignmentGuide[] };
645
+ }
646
+
647
+ let nextFrame = frame;
648
+ const guides: AlignmentGuide[] = [];
649
+ const threshold = getCanvasSnapThreshold(options);
650
+
651
+ if (handleAffectsWest(handle) || handleAffectsEast(handle)) {
652
+ const candidate = getResizeSnapCandidate(
653
+ "x",
654
+ nextFrame,
655
+ stationary,
656
+ handle,
657
+ threshold,
658
+ );
659
+ if (candidate) {
660
+ nextFrame = applyResizeSnapOffset(
661
+ nextFrame,
662
+ handle,
663
+ "x",
664
+ candidate.offset,
665
+ );
666
+ guides.push(candidate.guide);
667
+ }
668
+ }
669
+
670
+ if (handleAffectsNorth(handle) || handleAffectsSouth(handle)) {
671
+ const candidate = getResizeSnapCandidate(
672
+ "y",
673
+ nextFrame,
674
+ stationary,
675
+ handle,
676
+ threshold,
677
+ );
678
+ if (candidate) {
679
+ nextFrame = applyResizeSnapOffset(
680
+ nextFrame,
681
+ handle,
682
+ "y",
683
+ candidate.offset,
684
+ );
685
+ guides.push(candidate.guide);
686
+ }
687
+ }
688
+
689
+ return { frame: nextFrame, guides };
690
+ }
691
+
692
+ function getResizedAxisStart(
693
+ originStart: number,
694
+ originSize: number,
695
+ nextSize: number,
696
+ affectsStart: boolean,
697
+ affectsEnd: boolean,
698
+ fromCenter: boolean,
699
+ ) {
700
+ if (fromCenter && (affectsStart || affectsEnd)) {
701
+ return originStart - (nextSize - originSize) / 2;
702
+ }
703
+ if (fromCenter) return originStart + (originSize - nextSize) / 2;
704
+ if (affectsStart) return originStart + originSize - nextSize;
705
+ return originStart;
706
+ }
707
+
708
+ function getGroupMinimumBounds(
709
+ frames: FrameEntry[],
710
+ originBounds: FrameGeometry,
711
+ options: ResizeFrameOptions,
712
+ ): CanvasSize {
713
+ const minimumFrameWidth = options.minWidth ?? MIN_CANVAS_FRAME_WIDTH;
714
+ const minimumFrameHeight = options.minHeight ?? MIN_CANVAS_FRAME_HEIGHT;
715
+ const minimumWidth = frames.reduce(
716
+ (best, frame) =>
717
+ Math.max(
718
+ best,
719
+ originBounds.width *
720
+ (minimumFrameWidth / Math.max(1, frame.geometry.width)),
721
+ ),
722
+ minimumFrameWidth,
723
+ );
724
+ const minimumHeight = frames.reduce(
725
+ (best, frame) =>
726
+ Math.max(
727
+ best,
728
+ originBounds.height *
729
+ (minimumFrameHeight / Math.max(1, frame.geometry.height)),
730
+ ),
731
+ minimumFrameHeight,
732
+ );
733
+ return { width: minimumWidth, height: minimumHeight };
734
+ }
735
+
736
+ function getCanvasSnapThreshold({
737
+ thresholdScreenPx = DEFAULT_SNAP_THRESHOLD_SCREEN_PX,
738
+ zoom,
739
+ }: {
740
+ thresholdScreenPx?: number;
741
+ zoom: number;
742
+ }) {
743
+ const scale = getCameraScale(zoom);
744
+ return thresholdScreenPx / scale;
745
+ }
746
+
747
+ function getAxisSnapCandidates(
748
+ axis: "x" | "y",
749
+ movingBounds: FrameBounds,
750
+ stationaryBounds: FrameBounds,
751
+ threshold: number,
752
+ ): SnapCandidate[] {
753
+ const movingValues =
754
+ axis === "x"
755
+ ? [movingBounds.left, movingBounds.centerX, movingBounds.right]
756
+ : [movingBounds.top, movingBounds.centerY, movingBounds.bottom];
757
+ const stationaryValues =
758
+ axis === "x"
759
+ ? [
760
+ stationaryBounds.left,
761
+ stationaryBounds.centerX,
762
+ stationaryBounds.right,
763
+ ]
764
+ : [
765
+ stationaryBounds.top,
766
+ stationaryBounds.centerY,
767
+ stationaryBounds.bottom,
768
+ ];
769
+
770
+ return movingValues.flatMap((movingValue) =>
771
+ stationaryValues
772
+ .map((stationaryValue) => {
773
+ const offset = stationaryValue - movingValue;
774
+ const distance = Math.abs(offset);
775
+ if (distance > threshold) return null;
776
+ return {
777
+ distance,
778
+ offset,
779
+ guide:
780
+ axis === "x"
781
+ ? getVerticalGuide(
782
+ stationaryValue,
783
+ movingBounds,
784
+ stationaryBounds,
785
+ )
786
+ : getHorizontalGuide(
787
+ stationaryValue,
788
+ movingBounds,
789
+ stationaryBounds,
790
+ ),
791
+ };
792
+ })
793
+ .filter(Boolean),
794
+ ) as SnapCandidate[];
795
+ }
796
+
797
+ function getBestCandidate(
798
+ current: SnapCandidate | null,
799
+ candidates: SnapCandidate[],
800
+ ) {
801
+ return candidates.reduce<SnapCandidate | null>(
802
+ (best, candidate) =>
803
+ !best || candidate.distance < best.distance ? candidate : best,
804
+ current,
805
+ );
806
+ }
807
+
808
+ function getResizeSnapCandidate(
809
+ axis: "x" | "y",
810
+ frame: FrameGeometry,
811
+ stationary: FrameEntry[],
812
+ handle: ResizeHandle,
813
+ threshold: number,
814
+ ) {
815
+ const frameBounds = getFrameBounds(frame);
816
+ const sourceValue =
817
+ axis === "x"
818
+ ? handleAffectsWest(handle)
819
+ ? frameBounds.left
820
+ : frameBounds.right
821
+ : handleAffectsNorth(handle)
822
+ ? frameBounds.top
823
+ : frameBounds.bottom;
824
+
825
+ return stationary.reduce<SnapCandidate | null>((best, entry) => {
826
+ const stationaryBounds = getFrameBounds(entry.geometry);
827
+ const targetValues =
828
+ axis === "x"
829
+ ? [
830
+ stationaryBounds.left,
831
+ stationaryBounds.centerX,
832
+ stationaryBounds.right,
833
+ ]
834
+ : [
835
+ stationaryBounds.top,
836
+ stationaryBounds.centerY,
837
+ stationaryBounds.bottom,
838
+ ];
839
+
840
+ const candidates = targetValues
841
+ .map((targetValue) => {
842
+ const offset = targetValue - sourceValue;
843
+ const distance = Math.abs(offset);
844
+ if (distance > threshold) return null;
845
+ return {
846
+ distance,
847
+ offset,
848
+ guide:
849
+ axis === "x"
850
+ ? getVerticalGuide(targetValue, frameBounds, stationaryBounds)
851
+ : getHorizontalGuide(targetValue, frameBounds, stationaryBounds),
852
+ };
853
+ })
854
+ .filter(Boolean) as SnapCandidate[];
855
+
856
+ return getBestCandidate(best, candidates);
857
+ }, null);
858
+ }
859
+
860
+ function applyResizeSnapOffset(
861
+ frame: FrameGeometry,
862
+ handle: ResizeHandle,
863
+ axis: "x" | "y",
864
+ offset: number,
865
+ ) {
866
+ if (axis === "x") {
867
+ return clampFrameSize(
868
+ handleAffectsWest(handle)
869
+ ? { ...frame, x: frame.x + offset, width: frame.width - offset }
870
+ : { ...frame, width: frame.width + offset },
871
+ handle,
872
+ );
873
+ }
874
+
875
+ return clampFrameSize(
876
+ handleAffectsNorth(handle)
877
+ ? { ...frame, y: frame.y + offset, height: frame.height - offset }
878
+ : { ...frame, height: frame.height + offset },
879
+ handle,
880
+ );
881
+ }
882
+
883
+ function clampFrameSize(frame: FrameGeometry, handle: ResizeHandle) {
884
+ let next = { ...frame };
885
+ if (next.width < MIN_CANVAS_FRAME_WIDTH) {
886
+ if (handleAffectsWest(handle)) {
887
+ next.x = next.x + next.width - MIN_CANVAS_FRAME_WIDTH;
888
+ }
889
+ next.width = MIN_CANVAS_FRAME_WIDTH;
890
+ }
891
+ if (next.height < MIN_CANVAS_FRAME_HEIGHT) {
892
+ if (handleAffectsNorth(handle)) {
893
+ next.y = next.y + next.height - MIN_CANVAS_FRAME_HEIGHT;
894
+ }
895
+ next.height = MIN_CANVAS_FRAME_HEIGHT;
896
+ }
897
+ return next;
898
+ }
899
+
900
+ function getVerticalGuide(
901
+ position: number,
902
+ movingBounds: FrameBounds,
903
+ stationaryBounds: FrameBounds,
904
+ ): AlignmentGuide {
905
+ return {
906
+ orientation: "vertical",
907
+ position,
908
+ start: Math.min(movingBounds.top, stationaryBounds.top),
909
+ end: Math.max(movingBounds.bottom, stationaryBounds.bottom),
910
+ };
911
+ }
912
+
913
+ function getHorizontalGuide(
914
+ position: number,
915
+ movingBounds: FrameBounds,
916
+ stationaryBounds: FrameBounds,
917
+ ): AlignmentGuide {
918
+ return {
919
+ orientation: "horizontal",
920
+ position,
921
+ start: Math.min(movingBounds.left, stationaryBounds.left),
922
+ end: Math.max(movingBounds.right, stationaryBounds.right),
923
+ };
924
+ }
925
+
926
+ function handleAffectsWest(handle: ResizeHandle) {
927
+ return handle.includes("w");
928
+ }
929
+
930
+ function handleAffectsEast(handle: ResizeHandle) {
931
+ return handle.includes("e");
932
+ }
933
+
934
+ function handleAffectsNorth(handle: ResizeHandle) {
935
+ return handle.includes("n");
936
+ }
937
+
938
+ function handleAffectsSouth(handle: ResizeHandle) {
939
+ return handle.includes("s");
940
+ }
941
+
942
+ function getFrameGeometry(frame: FrameBoundsInput): FrameGeometry {
943
+ return "geometry" in frame ? frame.geometry : frame;
944
+ }
945
+
946
+ function getBoundsGeometry(bounds: FrameBounds | FrameGeometry): FrameGeometry {
947
+ if ("left" in bounds) {
948
+ return {
949
+ x: bounds.left,
950
+ y: bounds.top,
951
+ width: bounds.width,
952
+ height: bounds.height,
953
+ };
954
+ }
955
+ return bounds;
956
+ }
957
+
958
+ function getAxisRulerTicks(
959
+ axis: "x" | "y",
960
+ camera: CanvasCamera,
961
+ viewportLength: number,
962
+ {
963
+ minTickSpacingPx = 64,
964
+ canvasPadding = 0,
965
+ maxTicks = 200,
966
+ }: RulerTickOptions,
967
+ ): RulerTick[] {
968
+ if (viewportLength <= 0) return [];
969
+
970
+ const scale = getCameraScale(camera.zoom);
971
+ const pan = axis === "x" ? camera.x : camera.y;
972
+ const minCanvasStep = minTickSpacingPx / scale;
973
+ const step = getNiceCanvasStep(minCanvasStep);
974
+ const start = -pan / scale - canvasPadding;
975
+ const end = (viewportLength - pan) / scale - canvasPadding;
976
+ const first = Math.ceil(start / step) * step;
977
+ const ticks: RulerTick[] = [];
978
+
979
+ for (
980
+ let value = first;
981
+ value <= end + 1e-9 && ticks.length < maxTicks;
982
+ value += step
983
+ ) {
984
+ ticks.push({
985
+ value: normalizeTickValue(value),
986
+ position: pan + (value + canvasPadding) * scale,
987
+ label: formatTickLabel(value, step),
988
+ });
989
+ }
990
+
991
+ return ticks;
992
+ }
993
+
994
+ function getNiceCanvasStep(minStep: number): number {
995
+ if (!Number.isFinite(minStep) || minStep <= 0) return 1;
996
+
997
+ const magnitude = Math.pow(10, Math.floor(Math.log10(minStep)));
998
+ for (const multiplier of [1, 2, 5, 10]) {
999
+ const step = multiplier * magnitude;
1000
+ if (step >= minStep) return step;
1001
+ }
1002
+ return 10 * magnitude;
1003
+ }
1004
+
1005
+ function formatTickLabel(value: number, step: number): string {
1006
+ const decimals = step >= 1 ? 0 : Math.ceil(Math.abs(Math.log10(step)));
1007
+ if (decimals === 0) return String(Math.round(normalizeTickValue(value)));
1008
+ const label = normalizeTickValue(value)
1009
+ .toFixed(decimals)
1010
+ .replace(/\.?0+$/, "");
1011
+ return label === "" ? "0" : label;
1012
+ }
1013
+
1014
+ function normalizeTickValue(value: number): number {
1015
+ return Object.is(value, -0) || Math.abs(value) < 1e-9 ? 0 : value;
1016
+ }
1017
+
1018
+ function getNudgeVector(key: ArrowNudgeKey): CanvasPoint {
1019
+ if (key === "ArrowUp") return { x: 0, y: -1 };
1020
+ if (key === "ArrowRight") return { x: 1, y: 0 };
1021
+ if (key === "ArrowDown") return { x: 0, y: 1 };
1022
+ return { x: -1, y: 0 };
1023
+ }
1024
+
1025
+ function getCameraScale(zoom: number): number {
1026
+ return Math.max(0.01, zoom / 100);
1027
+ }
1028
+
1029
+ function getFiniteNumber(value: number | undefined, fallback: number): number {
1030
+ return typeof value === "number" && Number.isFinite(value) ? value : fallback;
1031
+ }
1032
+
1033
+ function getPositiveFiniteNumber(
1034
+ value: number | undefined,
1035
+ fallback: number,
1036
+ ): number {
1037
+ const next = getFiniteNumber(value, fallback);
1038
+ return next > 0 ? next : fallback;
1039
+ }
1040
+
1041
+ function getWholeNumberAtLeast(
1042
+ value: number | undefined,
1043
+ fallback: number,
1044
+ minimum: number,
1045
+ ): number {
1046
+ return Math.max(
1047
+ minimum,
1048
+ Math.floor(getPositiveFiniteNumber(value, fallback)),
1049
+ );
1050
+ }
1051
+
1052
+ function radiansToDegrees(radians: number): number {
1053
+ return (radians * 180) / Math.PI;
1054
+ }
1055
+
1056
+ function clamp(value: number, min: number, max: number): number {
1057
+ return Math.min(max, Math.max(min, value));
1058
+ }