@agent-native/core 0.92.7 → 0.92.8

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 (289) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +12 -0
  3. package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
  4. package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  5. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  6. package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  7. package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  8. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  9. package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  10. package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  11. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  12. package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  13. package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  14. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  15. package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  16. package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  17. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  18. package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  19. package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  20. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  21. package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  22. package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  23. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  24. package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  25. package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  26. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  27. package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  28. package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  29. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  30. package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  31. package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  32. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  33. package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  34. package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
  35. package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
  36. package/corpus/core/package.json +1 -1
  37. package/corpus/core/src/agent/engine/builtin.ts +1 -1
  38. package/corpus/core/src/agent/model-config.ts +30 -30
  39. package/corpus/core/src/agent/production-agent.ts +129 -7
  40. package/corpus/core/src/cli/design-connect.ts +438 -83
  41. package/corpus/core/src/cli/recap.ts +2 -2
  42. package/corpus/core/src/client/AgentPanel.tsx +8 -1
  43. package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
  44. package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
  45. package/corpus/core/src/client/index.ts +4 -0
  46. package/corpus/core/src/client/org/TeamPage.tsx +41 -26
  47. package/corpus/core/src/client/use-action.ts +113 -1
  48. package/corpus/core/src/db/client.ts +12 -2
  49. package/corpus/core/src/db/create-get-db.ts +4 -1
  50. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  51. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  52. package/corpus/core/src/server/transcribe-voice.ts +1 -1
  53. package/corpus/core/src/usage/store.ts +13 -4
  54. package/corpus/core/src/vite/client.ts +38 -1
  55. package/corpus/templates/analytics/AGENTS.md +3 -0
  56. package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
  57. package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
  58. package/corpus/templates/analytics/app/global.css +11 -0
  59. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
  60. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  61. package/corpus/templates/analytics/app/i18n-data.ts +20 -0
  62. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
  63. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
  64. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
  65. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
  66. package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
  70. package/corpus/templates/analytics/server/db/index.ts +6 -0
  71. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
  72. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
  73. package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
  74. package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
  75. package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
  76. package/corpus/templates/content/app/i18n-data.ts +28 -0
  77. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
  78. package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
  79. package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
  80. package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
  81. package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
  82. package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
  83. package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
  84. package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
  85. package/corpus/templates/design/actions/capture-design-state.ts +2 -2
  86. package/corpus/templates/design/actions/connect-localhost.ts +58 -12
  87. package/corpus/templates/design/actions/create-component.ts +2 -18
  88. package/corpus/templates/design/actions/create-design-branch.ts +38 -18
  89. package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
  90. package/corpus/templates/design/actions/delete-file.ts +19 -34
  91. package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
  92. package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
  93. package/corpus/templates/design/actions/export-pdf.ts +2 -1
  94. package/corpus/templates/design/actions/export-zip.ts +9 -3
  95. package/corpus/templates/design/actions/generate-design.ts +107 -70
  96. package/corpus/templates/design/actions/get-component-details.ts +2 -18
  97. package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
  98. package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
  99. package/corpus/templates/design/actions/get-design.ts +2 -1
  100. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
  101. package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
  102. package/corpus/templates/design/actions/index-components.ts +2 -18
  103. package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
  104. package/corpus/templates/design/actions/list-local-files.ts +9 -2
  105. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
  106. package/corpus/templates/design/actions/open-component-source.ts +2 -18
  107. package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
  108. package/corpus/templates/design/actions/present-design-variants.ts +70 -70
  109. package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
  110. package/corpus/templates/design/actions/read-local-file.ts +9 -2
  111. package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
  112. package/corpus/templates/design/actions/rename-screen.ts +425 -0
  113. package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
  114. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
  115. package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
  116. package/corpus/templates/design/actions/update-design.ts +376 -61
  117. package/corpus/templates/design/actions/update-file.ts +455 -188
  118. package/corpus/templates/design/actions/write-local-file.ts +10 -2
  119. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
  120. package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
  121. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
  122. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
  123. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
  124. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
  125. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
  126. package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
  127. package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
  128. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
  129. package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
  130. package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
  131. package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
  132. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
  133. package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
  134. package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
  135. package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
  136. package/corpus/templates/design/app/components/design/index.ts +2 -2
  137. package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
  138. package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
  139. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
  140. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
  141. package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
  142. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
  143. package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
  144. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
  145. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
  146. package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
  147. package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
  148. package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
  149. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
  150. package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
  151. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
  152. package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
  153. package/corpus/templates/design/app/i18n-data.ts +223 -0
  154. package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
  155. package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
  156. package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
  157. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
  158. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
  159. package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
  160. package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
  161. package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
  162. package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
  163. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
  164. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
  165. package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
  166. package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
  167. package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
  168. package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
  169. package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
  170. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
  171. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
  172. package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
  173. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
  174. package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
  175. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
  176. package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
  177. package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
  178. package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
  179. package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
  180. package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
  181. package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
  182. package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
  183. package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
  184. package/corpus/templates/design/server/db/index.ts +2 -52
  185. package/corpus/templates/design/server/db/schema.ts +16 -0
  186. package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
  187. package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
  188. package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
  189. package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
  190. package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
  191. package/corpus/templates/design/server/plugins/db.ts +19 -0
  192. package/corpus/templates/design/server/source-workspace.ts +12 -13
  193. package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
  194. package/corpus/templates/design/shared/screen-rename.ts +42 -0
  195. package/corpus/templates/design/shared/source-mode.ts +84 -4
  196. package/dist/agent/engine/builder-engine.d.ts +1 -1
  197. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  198. package/dist/agent/engine/builtin.js +1 -1
  199. package/dist/agent/engine/builtin.js.map +1 -1
  200. package/dist/agent/model-config.d.ts +9 -9
  201. package/dist/agent/model-config.d.ts.map +1 -1
  202. package/dist/agent/model-config.js +30 -30
  203. package/dist/agent/model-config.js.map +1 -1
  204. package/dist/agent/production-agent.d.ts +60 -1
  205. package/dist/agent/production-agent.d.ts.map +1 -1
  206. package/dist/agent/production-agent.js +106 -7
  207. package/dist/agent/production-agent.js.map +1 -1
  208. package/dist/cli/design-connect.d.ts +30 -1
  209. package/dist/cli/design-connect.d.ts.map +1 -1
  210. package/dist/cli/design-connect.js +359 -75
  211. package/dist/cli/design-connect.js.map +1 -1
  212. package/dist/cli/recap.js +2 -2
  213. package/dist/cli/recap.js.map +1 -1
  214. package/dist/client/AgentPanel.d.ts.map +1 -1
  215. package/dist/client/AgentPanel.js +6 -1
  216. package/dist/client/AgentPanel.js.map +1 -1
  217. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  218. package/dist/client/agent-chat-adapter.js +10 -5
  219. package/dist/client/agent-chat-adapter.js.map +1 -1
  220. package/dist/client/composer/TiptapComposer.js +1 -1
  221. package/dist/client/composer/TiptapComposer.js.map +1 -1
  222. package/dist/client/index.d.ts +1 -1
  223. package/dist/client/index.d.ts.map +1 -1
  224. package/dist/client/index.js +1 -1
  225. package/dist/client/index.js.map +1 -1
  226. package/dist/client/org/TeamPage.d.ts.map +1 -1
  227. package/dist/client/org/TeamPage.js +6 -5
  228. package/dist/client/org/TeamPage.js.map +1 -1
  229. package/dist/client/use-action.d.ts +32 -0
  230. package/dist/client/use-action.d.ts.map +1 -1
  231. package/dist/client/use-action.js +68 -1
  232. package/dist/client/use-action.js.map +1 -1
  233. package/dist/collab/struct-routes.d.ts +1 -1
  234. package/dist/db/client.d.ts +3 -0
  235. package/dist/db/client.d.ts.map +1 -1
  236. package/dist/db/client.js +7 -2
  237. package/dist/db/client.js.map +1 -1
  238. package/dist/db/create-get-db.d.ts.map +1 -1
  239. package/dist/db/create-get-db.js +2 -2
  240. package/dist/db/create-get-db.js.map +1 -1
  241. package/dist/notifications/routes.d.ts +3 -3
  242. package/dist/resources/handlers.d.ts +2 -2
  243. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  244. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  245. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  246. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  247. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  248. package/dist/server/transcribe-voice.js +1 -1
  249. package/dist/server/transcribe-voice.js.map +1 -1
  250. package/dist/usage/store.d.ts.map +1 -1
  251. package/dist/usage/store.js +13 -4
  252. package/dist/usage/store.js.map +1 -1
  253. package/dist/vite/client.d.ts.map +1 -1
  254. package/dist/vite/client.js +36 -1
  255. package/dist/vite/client.js.map +1 -1
  256. package/docs/content/a2a-protocol.mdx +12 -0
  257. package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  258. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  259. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  260. package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  261. package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  262. package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  263. package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  264. package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  265. package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  266. package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  267. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  268. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  269. package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  270. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  271. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  272. package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  273. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  274. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  275. package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  276. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  277. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  278. package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  279. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  280. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  281. package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  282. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  283. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  284. package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  285. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  286. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  287. package/docs/content/pr-visual-recap.mdx +3 -3
  288. package/docs/content/pure-agent-apps.mdx +22 -0
  289. package/package.json +2 -2
@@ -0,0 +1,137 @@
1
+ export function liveEditEndpointUrl(
2
+ bridgeUrl: string,
3
+ previewUrl: string,
4
+ options: {
5
+ previewToken: string;
6
+ includeEditorBridge?: boolean;
7
+ bridgeKey?: string;
8
+ },
9
+ ): string {
10
+ const endpoint = new URL("/live-edit", bridgeUrl);
11
+ endpoint.searchParams.set("url", previewUrl);
12
+ endpoint.searchParams.set("previewToken", options.previewToken);
13
+ if (options?.includeEditorBridge === false) {
14
+ endpoint.searchParams.set("bridge", "0");
15
+ } else if (options?.bridgeKey) {
16
+ endpoint.searchParams.set("bridgeKey", options.bridgeKey);
17
+ }
18
+ return endpoint.toString();
19
+ }
20
+
21
+ export function resolveLiveEditPreviewUrl(args: {
22
+ sourceType: string | undefined;
23
+ bridgeUrl: string | undefined;
24
+ previewToken: string | undefined;
25
+ previewUrl: string | null | undefined;
26
+ bridgeKey: string;
27
+ registeredBridgeKey: string | null;
28
+ }): string | null {
29
+ if (
30
+ args.sourceType !== "localhost" ||
31
+ !args.bridgeUrl ||
32
+ !args.previewToken ||
33
+ !args.previewUrl
34
+ ) {
35
+ return null;
36
+ }
37
+ // Every localhost mode registers a keyed injected script before rendering:
38
+ // editable modes include editor chrome, while Interact/read-only modes use
39
+ // the gesture-only bridge. Waiting for the matching key prevents a raw URL
40
+ // load followed by a second proxied load (the flash/state-loss regression).
41
+ if (args.registeredBridgeKey !== args.bridgeKey) return null;
42
+ return liveEditEndpointUrl(args.bridgeUrl, args.previewUrl, {
43
+ previewToken: args.previewToken,
44
+ bridgeKey: args.bridgeKey,
45
+ });
46
+ }
47
+
48
+ export function shouldUseIframeLoadReadyFallback(
49
+ usesLiveEditEditorBridge: boolean,
50
+ ): boolean {
51
+ return !usesLiveEditEditorBridge;
52
+ }
53
+
54
+ export function shouldFetchExternalSourceSnapshot(args: {
55
+ sourceType: string | undefined;
56
+ bridgeUrl: string | undefined;
57
+ previewToken: string | undefined;
58
+ previewUrl: string | null | undefined;
59
+ hasSnapshotConsumer: boolean;
60
+ }): boolean {
61
+ return Boolean(
62
+ args.sourceType === "localhost" &&
63
+ args.bridgeUrl &&
64
+ args.previewToken &&
65
+ args.previewUrl &&
66
+ args.hasSnapshotConsumer,
67
+ );
68
+ }
69
+
70
+ const SCRIPT_TAG_RE = /<script\b([^>]*)>([\s\S]*?)<\/script\s*>/gi;
71
+ const SCRIPT_SRC_RE = /\bsrc\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s>]+))/i;
72
+
73
+ function isKnownDevRuntimeScriptSource(value: string): boolean {
74
+ try {
75
+ const parsed = new URL(value, "http://localhost");
76
+ return (
77
+ parsed.pathname === "/@vite/client" ||
78
+ parsed.pathname === "/@react-refresh" ||
79
+ parsed.pathname.includes("/__x00__react-refresh")
80
+ );
81
+ } catch {
82
+ return false;
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Remove development-server HTML transforms before treating a live snapshot as
88
+ * writable source. Vite injects its HMR client and React Refresh preamble into
89
+ * the served document; persisting either back to index.html corrupts the real
90
+ * source and can duplicate refresh runtimes on every Apply-to-source cycle.
91
+ * User-authored module scripts and inline application scripts are preserved.
92
+ */
93
+ export function sanitizeLocalhostSourceSnapshotHtml(html: string): string {
94
+ return html.replace(
95
+ SCRIPT_TAG_RE,
96
+ (fullScript, rawAttributes: string, inlineBody: string) => {
97
+ const srcMatch = rawAttributes.match(SCRIPT_SRC_RE);
98
+ const src = srcMatch?.[1] ?? srcMatch?.[2] ?? srcMatch?.[3] ?? "";
99
+ if (src && isKnownDevRuntimeScriptSource(src)) return "";
100
+
101
+ const knownRefreshPreamble =
102
+ inlineBody.includes("/@react-refresh") ||
103
+ inlineBody.includes("RefreshRuntime.injectIntoGlobalHook") ||
104
+ inlineBody.includes("__vite_plugin_react_preamble_installed__");
105
+ const knownViteClientInjection =
106
+ inlineBody.includes("/@vite/client") &&
107
+ (inlineBody.includes("import") || inlineBody.includes("__vite"));
108
+ return knownRefreshPreamble || knownViteClientInjection ? "" : fullScript;
109
+ },
110
+ );
111
+ }
112
+
113
+ const EXTERNAL_PREVIEW_IFRAME_SANDBOX =
114
+ "allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox allow-same-origin";
115
+ const EDITABLE_INLINE_IFRAME_SANDBOX =
116
+ "allow-scripts allow-popups allow-popups-to-escape-sandbox allow-same-origin";
117
+ const READ_ONLY_INLINE_IFRAME_SANDBOX =
118
+ "allow-scripts allow-popups allow-popups-to-escape-sandbox";
119
+
120
+ export function getDesignCanvasIframeSandbox(args: {
121
+ externalPreview: boolean;
122
+ readOnly: boolean;
123
+ }): string {
124
+ if (args.externalPreview) return EXTERNAL_PREVIEW_IFRAME_SANDBOX;
125
+ return args.readOnly
126
+ ? READ_ONLY_INLINE_IFRAME_SANDBOX
127
+ : EDITABLE_INLINE_IFRAME_SANDBOX;
128
+ }
129
+
130
+ const SNAPSHOT_RETRY_BASE_DELAY_MS = 1500;
131
+ const SNAPSHOT_RETRY_MAX_DELAY_MS = 15000;
132
+
133
+ export function getSnapshotRetryDelayMs(attempt: number): number {
134
+ const safeAttempt = Number.isFinite(attempt) ? Math.max(0, attempt) : 0;
135
+ const delay = SNAPSHOT_RETRY_BASE_DELAY_MS * 2 ** safeAttempt;
136
+ return Math.min(SNAPSHOT_RETRY_MAX_DELAY_MS, delay);
137
+ }
@@ -0,0 +1,10 @@
1
+ export function isOsFileDragEvent(event: {
2
+ dataTransfer: { types: readonly string[] | DOMStringList } | null;
3
+ }): boolean {
4
+ const types = event.dataTransfer?.types;
5
+ if (!types) return false;
6
+ for (let index = 0; index < types.length; index += 1) {
7
+ if (types[index] === "Files") return true;
8
+ }
9
+ return false;
10
+ }
@@ -0,0 +1,23 @@
1
+ import { hitTestBridgeScript } from "../../../../.generated/bridge/hit-test.generated";
2
+
3
+ export const LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT = `
4
+ <script data-agent-native-hit-test-bridge>
5
+ ${hitTestBridgeScript}
6
+ </script>
7
+ `;
8
+
9
+ export function appendHitTestResponder(html: string): string {
10
+ if (html.includes("</body>")) {
11
+ return html.replace(
12
+ "</body>", // i18n-ignore generated iframe HTML marker
13
+ LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT + "</body>", // i18n-ignore generated iframe HTML injection
14
+ );
15
+ }
16
+ if (html.includes("</html>")) {
17
+ return html.replace(
18
+ "</html>", // i18n-ignore generated iframe HTML marker
19
+ LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT + "</html>", // i18n-ignore generated iframe HTML injection
20
+ );
21
+ }
22
+ return html + LIGHTWEIGHT_HIT_TEST_BRIDGE_SCRIPT;
23
+ }
@@ -0,0 +1,23 @@
1
+ import type { ElementInfo } from "../types";
2
+
3
+ export interface IframeHotkeyPayload {
4
+ key: string;
5
+ code: string;
6
+ metaKey: boolean;
7
+ ctrlKey: boolean;
8
+ shiftKey: boolean;
9
+ altKey: boolean;
10
+ repeat: boolean;
11
+ }
12
+
13
+ export interface IframeFigmaClipboardPastePayload {
14
+ content: string;
15
+ }
16
+
17
+ export interface IframeContextMenuPayload {
18
+ clientX: number;
19
+ clientY: number;
20
+ viewportClientX?: number;
21
+ viewportClientY?: number;
22
+ info?: ElementInfo | null;
23
+ }
@@ -0,0 +1,177 @@
1
+ const MAX_IFRAME_PAN_COORDINATE = 100_000;
2
+ const MAX_MOUSE_BUTTONS_MASK = 31;
3
+
4
+ export type EmbeddedCanvasPanPhase = "start" | "move" | "end" | "cancel";
5
+
6
+ export interface EmbeddedCanvasPanSession {
7
+ pointerId: number;
8
+ button: 0 | 1;
9
+ }
10
+
11
+ interface EmbeddedCanvasPanMessage {
12
+ type: "embedded-canvas-pan";
13
+ phase: EmbeddedCanvasPanPhase;
14
+ pointerId: number;
15
+ button: 0 | 1;
16
+ buttons: number;
17
+ clientX: number;
18
+ clientY: number;
19
+ ctrlKey: boolean;
20
+ metaKey: boolean;
21
+ shiftKey: boolean;
22
+ altKey: boolean;
23
+ }
24
+
25
+ export interface ForwardEmbeddedCanvasPanResult {
26
+ handled: boolean;
27
+ session: EmbeddedCanvasPanSession | null;
28
+ }
29
+
30
+ function finiteNumber(value: unknown): number | null {
31
+ const number = Number(value);
32
+ return Number.isFinite(number) ? number : null;
33
+ }
34
+
35
+ function clamp(value: number, min: number, max: number): number {
36
+ return Math.max(min, Math.min(max, value));
37
+ }
38
+
39
+ function parseEmbeddedCanvasPanMessage(
40
+ value: unknown,
41
+ ): EmbeddedCanvasPanMessage | null {
42
+ if (!value || typeof value !== "object" || Array.isArray(value)) return null;
43
+ const candidate = value as Record<string, unknown>;
44
+ if (candidate.type !== "embedded-canvas-pan") return null;
45
+ const phase = candidate.phase;
46
+ if (
47
+ phase !== "start" &&
48
+ phase !== "move" &&
49
+ phase !== "end" &&
50
+ phase !== "cancel"
51
+ ) {
52
+ return null;
53
+ }
54
+ const pointerId = finiteNumber(candidate.pointerId);
55
+ const rawButton = finiteNumber(candidate.button);
56
+ const buttons = finiteNumber(candidate.buttons);
57
+ const clientX = finiteNumber(candidate.clientX);
58
+ const clientY = finiteNumber(candidate.clientY);
59
+ if (
60
+ pointerId === null ||
61
+ !Number.isInteger(pointerId) ||
62
+ pointerId < 0 ||
63
+ pointerId > 0x7fffffff ||
64
+ (rawButton !== 0 && rawButton !== 1) ||
65
+ buttons === null ||
66
+ clientX === null ||
67
+ clientY === null
68
+ ) {
69
+ return null;
70
+ }
71
+ return {
72
+ type: "embedded-canvas-pan",
73
+ phase,
74
+ pointerId,
75
+ button: rawButton,
76
+ buttons: clamp(Math.trunc(buttons), 0, MAX_MOUSE_BUTTONS_MASK),
77
+ clientX: clamp(
78
+ clientX,
79
+ -MAX_IFRAME_PAN_COORDINATE,
80
+ MAX_IFRAME_PAN_COORDINATE,
81
+ ),
82
+ clientY: clamp(
83
+ clientY,
84
+ -MAX_IFRAME_PAN_COORDINATE,
85
+ MAX_IFRAME_PAN_COORDINATE,
86
+ ),
87
+ ctrlKey: Boolean(candidate.ctrlKey),
88
+ metaKey: Boolean(candidate.metaKey),
89
+ shiftKey: Boolean(candidate.shiftKey),
90
+ altKey: Boolean(candidate.altKey),
91
+ };
92
+ }
93
+
94
+ /**
95
+ * Replays a trusted iframe bridge pan message through the parent document's
96
+ * existing mouse-drag path. The synthetic mousedown bubbles from the iframe
97
+ * element, so single-screen DesignCanvas and overview MultiScreenCanvas keep
98
+ * one authoritative pan implementation; move/end events go to `window`, where
99
+ * both implementations already install their lifetime drag listeners.
100
+ *
101
+ * The caller owns `session` and must only call this after validating the
102
+ * MessageEvent's source window + origin. Session matching rejects injected or
103
+ * reordered move/end packets before they can disturb another active gesture.
104
+ */
105
+ export function forwardEmbeddedCanvasPanMessage({
106
+ data,
107
+ iframe,
108
+ hostWindow,
109
+ session,
110
+ }: {
111
+ data: unknown;
112
+ iframe: HTMLIFrameElement;
113
+ hostWindow: Window;
114
+ session: EmbeddedCanvasPanSession | null;
115
+ }): ForwardEmbeddedCanvasPanResult {
116
+ const message = parseEmbeddedCanvasPanMessage(data);
117
+ if (!message) return { handled: false, session };
118
+
119
+ if (message.phase === "start") {
120
+ if (session) return { handled: false, session };
121
+ } else if (
122
+ !session ||
123
+ session.pointerId !== message.pointerId ||
124
+ session.button !== message.button
125
+ ) {
126
+ return { handled: false, session };
127
+ }
128
+
129
+ const frameRect = iframe.getBoundingClientRect();
130
+ const scaleX =
131
+ iframe.clientWidth > 0 && Number.isFinite(frameRect.width)
132
+ ? frameRect.width / iframe.clientWidth
133
+ : 1;
134
+ const scaleY =
135
+ iframe.clientHeight > 0 && Number.isFinite(frameRect.height)
136
+ ? frameRect.height / iframe.clientHeight
137
+ : 1;
138
+ const clientX = frameRect.left + message.clientX * scaleX;
139
+ const clientY = frameRect.top + message.clientY * scaleY;
140
+ const eventType =
141
+ message.phase === "start"
142
+ ? "mousedown"
143
+ : message.phase === "move"
144
+ ? "mousemove"
145
+ : "mouseup";
146
+ const HostMouseEvent = (hostWindow as Window & typeof globalThis).MouseEvent;
147
+ const forwarded = new HostMouseEvent(eventType, {
148
+ bubbles: true,
149
+ cancelable: true,
150
+ view: hostWindow,
151
+ button: message.button,
152
+ buttons:
153
+ message.phase === "end" || message.phase === "cancel"
154
+ ? 0
155
+ : message.buttons,
156
+ clientX,
157
+ clientY,
158
+ ctrlKey: message.ctrlKey,
159
+ metaKey: message.metaKey,
160
+ shiftKey: message.shiftKey,
161
+ altKey: message.altKey,
162
+ });
163
+
164
+ if (message.phase === "start") {
165
+ iframe.dispatchEvent(forwarded);
166
+ return {
167
+ handled: true,
168
+ session: { pointerId: message.pointerId, button: message.button },
169
+ };
170
+ }
171
+
172
+ hostWindow.dispatchEvent(forwarded);
173
+ return {
174
+ handled: true,
175
+ session: message.phase === "move" ? session : null,
176
+ };
177
+ }
@@ -0,0 +1,7 @@
1
+ export interface MotionTrackWire {
2
+ targetNodeId: string;
3
+ property: string;
4
+ keyframes: Array<{ t: number; value: string; ease?: string }>;
5
+ delayMs?: number;
6
+ durationMs?: number;
7
+ }
@@ -0,0 +1,62 @@
1
+ export type PendingTextEditKeyAction =
2
+ | { action: "buffer"; char: string }
3
+ | { action: "drop-last" }
4
+ | { action: "swallow" }
5
+ | { action: "clear-and-swallow" }
6
+ | { action: "pass" };
7
+
8
+ /**
9
+ * Run text-edit activation immediately unless it was requested from inside a
10
+ * pointer gesture's mouseup handler. Browsers dispatch the trailing `click`
11
+ * after mouseup; focusing the new editable before that click makes the click
12
+ * immediately blur it again. Deferring one task preserves the already-armed
13
+ * keystroke buffer while letting that trailing click finish first.
14
+ */
15
+ export function schedulePendingTextEditActivation(
16
+ activate: () => void,
17
+ options: {
18
+ afterPointerGesture?: boolean;
19
+ schedule?: (callback: () => void, delayMs: number) => void;
20
+ } = {},
21
+ ): void {
22
+ if (!options.afterPointerGesture) {
23
+ activate();
24
+ return;
25
+ }
26
+ const schedule =
27
+ options.schedule ??
28
+ ((callback: () => void, delayMs: number) =>
29
+ window.setTimeout(callback, delayMs));
30
+ schedule(activate, POINTER_TEXT_EDIT_ACTIVATION_DELAY_MS);
31
+ }
32
+
33
+ export function routePendingTextEditKey(event: {
34
+ key: string;
35
+ metaKey?: boolean;
36
+ ctrlKey?: boolean;
37
+ altKey?: boolean;
38
+ isComposing?: boolean;
39
+ }): PendingTextEditKeyAction {
40
+ if (event.isComposing) return { action: "pass" };
41
+ if (event.metaKey || event.ctrlKey) return { action: "pass" };
42
+ if (event.key === "Escape") return { action: "clear-and-swallow" };
43
+ if (event.key === "Backspace") return { action: "drop-last" };
44
+ if (
45
+ event.key === "Delete" ||
46
+ event.key === "Enter" ||
47
+ event.key === "Tab" ||
48
+ event.key.startsWith("Arrow")
49
+ ) {
50
+ return { action: "swallow" };
51
+ }
52
+ if (event.key.length === 1) return { action: "buffer", char: event.key };
53
+ return { action: "pass" };
54
+ }
55
+
56
+ export const PENDING_TEXT_EDIT_TIMEOUT_MS = 3000;
57
+
58
+ // Overview creation patches the target iframe document after persistence.
59
+ // Waiting through that bounded replacement avoids opening an edit session in
60
+ // the outgoing DOM (visible caret blink, then lost focus). Keystrokes remain
61
+ // lossless because DesignCanvas arms its pending buffer before scheduling.
62
+ export const POINTER_TEXT_EDIT_ACTIVATION_DELAY_MS = 300;
@@ -1,9 +1,9 @@
1
1
  export { DesignCanvas } from "./DesignCanvas";
2
+ export type { MotionTrackWire } from "./design-canvas/motion-types";
2
3
  export type {
3
- MotionTrackWire,
4
4
  IframeHotkeyPayload,
5
5
  IframeContextMenuPayload,
6
- } from "./DesignCanvas";
6
+ } from "./design-canvas/iframe-events";
7
7
  export { DeviceFrame } from "./DeviceFrame";
8
8
  export { DrawOverlay } from "./DrawOverlay";
9
9
  export { EditPanel } from "./EditPanel";
@@ -0,0 +1,100 @@
1
+ import type { MultiScreenCanvasTool, DraftCreationTool } from "./types";
2
+
3
+ export function normalizeCanvasTool(
4
+ tool: MultiScreenCanvasTool,
5
+ ): MultiScreenCanvasTool {
6
+ return tool === "rectangle" ? "rect" : tool;
7
+ }
8
+
9
+ export function getDraftCreationTool(
10
+ tool: MultiScreenCanvasTool,
11
+ ): DraftCreationTool | null {
12
+ if (
13
+ tool === "frame" ||
14
+ tool === "rect" ||
15
+ tool === "line" ||
16
+ tool === "arrow" ||
17
+ tool === "ellipse" ||
18
+ tool === "polygon" ||
19
+ tool === "star" ||
20
+ tool === "text" ||
21
+ tool === "pen"
22
+ ) {
23
+ return tool;
24
+ }
25
+ return null;
26
+ }
27
+
28
+ export function isDirectScreenHoverTarget(
29
+ target: EventTarget | null,
30
+ currentTarget: HTMLElement,
31
+ ) {
32
+ if (target === currentTarget) return true;
33
+ const element =
34
+ target && typeof (target as Element).closest === "function"
35
+ ? (target as Element)
36
+ : null;
37
+ return !!element && !element.closest("[data-screen-content]");
38
+ }
39
+
40
+ /** True only for native OS file drags, not internal HTML drag operations. */
41
+ export function isOsFileDrag(event: {
42
+ dataTransfer: { types: readonly string[] | DOMStringList } | null;
43
+ }): boolean {
44
+ const types = event.dataTransfer?.types;
45
+ if (!types) return false;
46
+ for (let index = 0; index < types.length; index += 1) {
47
+ if (types[index] === "Files") return true;
48
+ }
49
+ return false;
50
+ }
51
+
52
+ export function shouldBoardSurfaceCapturePointerEvents(args: {
53
+ tool: string;
54
+ gestureActive?: boolean;
55
+ }) {
56
+ if (args.gestureActive) return false;
57
+ const tool = normalizeCanvasTool(args.tool as MultiScreenCanvasTool);
58
+ return (
59
+ !getDraftCreationTool(tool) &&
60
+ tool !== "hand" &&
61
+ tool !== "comment" &&
62
+ tool !== "draw"
63
+ );
64
+ }
65
+
66
+ export function shouldBeginCanvasPan(args: { button: number; tool: string }) {
67
+ return (
68
+ args.button === 1 ||
69
+ (args.button === 0 &&
70
+ normalizeCanvasTool(args.tool as MultiScreenCanvasTool) === "hand")
71
+ );
72
+ }
73
+
74
+ export function shouldShowFrameFullViewButton(args: {
75
+ emphasized: boolean;
76
+ showFullView?: boolean;
77
+ childHoverActive?: boolean;
78
+ }) {
79
+ return args.emphasized || !!args.showFullView || !!args.childHoverActive;
80
+ }
81
+
82
+ export function shouldShowBreakpointMenuAffordance(args: {
83
+ canEdit: boolean;
84
+ hasRemoveOrChangeWidth: boolean;
85
+ isActive: boolean;
86
+ menuOpen: boolean;
87
+ }): boolean {
88
+ return (
89
+ args.canEdit &&
90
+ args.hasRemoveOrChangeWidth &&
91
+ (args.isActive || args.menuOpen)
92
+ );
93
+ }
94
+
95
+ export function shouldClearSelectionOnEmptyCanvasClick(gesture: {
96
+ hasMoved: boolean;
97
+ additive: boolean;
98
+ }): boolean {
99
+ return !gesture.hasMoved && !gesture.additive;
100
+ }
@@ -0,0 +1,81 @@
1
+ import { rotatePoint } from "@shared/canvas-math";
2
+
3
+ import type { FrameGeometry, Point } from "./types";
4
+
5
+ export interface ScreenViewport {
6
+ width: number;
7
+ height: number;
8
+ }
9
+
10
+ function frameCenter(frame: FrameGeometry): Point {
11
+ return {
12
+ x: frame.x + frame.width / 2,
13
+ y: frame.y + frame.height / 2,
14
+ };
15
+ }
16
+
17
+ /** Maps a point from an iframe/screen's local pixel space into board space. */
18
+ export function screenLocalPointToBoardPoint(
19
+ point: Point,
20
+ frame: FrameGeometry,
21
+ viewport: ScreenViewport,
22
+ ): Point {
23
+ const unrotated = {
24
+ x: frame.x + point.x * (frame.width / Math.max(1, viewport.width)),
25
+ y: frame.y + point.y * (frame.height / Math.max(1, viewport.height)),
26
+ };
27
+ return rotatePoint(unrotated, frameCenter(frame), frame.rotation ?? 0);
28
+ }
29
+
30
+ /** Maps a board-space point into an iframe/screen's local pixel space. */
31
+ export function boardPointToScreenLocalPoint(
32
+ point: Point,
33
+ frame: FrameGeometry,
34
+ viewport: ScreenViewport,
35
+ ): Point {
36
+ const unrotated = rotatePoint(
37
+ point,
38
+ frameCenter(frame),
39
+ -(frame.rotation ?? 0),
40
+ );
41
+ return {
42
+ x: (unrotated.x - frame.x) * (viewport.width / Math.max(1, frame.width)),
43
+ y: (unrotated.y - frame.y) * (viewport.height / Math.max(1, frame.height)),
44
+ };
45
+ }
46
+
47
+ /**
48
+ * Maps an axis-aligned screen-local rect into the board. The returned geometry
49
+ * keeps width/height in the screen's local axes and carries the screen's
50
+ * rotation, matching the CSS box needed to render/hit-test it on the board.
51
+ */
52
+ export function screenLocalRectToBoardGeometry(
53
+ rect: { left: number; top: number; width: number; height: number },
54
+ frame: FrameGeometry,
55
+ viewport: ScreenViewport,
56
+ minSize = 1,
57
+ ): FrameGeometry {
58
+ const scaleX = frame.width / Math.max(1, viewport.width);
59
+ const scaleY = frame.height / Math.max(1, viewport.height);
60
+ const width = Math.max(minSize, rect.width * scaleX);
61
+ const height = Math.max(minSize, rect.height * scaleY);
62
+ const unrotatedCenter = {
63
+ // Preserve the historical local-left/local-top mapping even when a tiny
64
+ // rect's rendered board size is clamped up to minSize.
65
+ x: frame.x + rect.left * scaleX + width / 2,
66
+ y: frame.y + rect.top * scaleY + height / 2,
67
+ };
68
+ const center = rotatePoint(
69
+ unrotatedCenter,
70
+ frameCenter(frame),
71
+ frame.rotation ?? 0,
72
+ );
73
+ const rotation = frame.rotation ?? 0;
74
+ return {
75
+ x: center.x - width / 2,
76
+ y: center.y - height / 2,
77
+ width,
78
+ height,
79
+ ...(rotation ? { rotation } : {}),
80
+ };
81
+ }