@agent-native/core 0.92.6 → 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 (297) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +18 -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/cli/sync-builder-starter-manifest.ts +1 -1
  43. package/corpus/core/src/client/AgentPanel.tsx +8 -1
  44. package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
  45. package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
  46. package/corpus/core/src/client/index.ts +4 -0
  47. package/corpus/core/src/client/org/TeamPage.tsx +41 -26
  48. package/corpus/core/src/client/use-action.ts +113 -1
  49. package/corpus/core/src/db/client.ts +12 -2
  50. package/corpus/core/src/db/create-get-db.ts +4 -1
  51. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  52. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  53. package/corpus/core/src/server/transcribe-voice.ts +1 -1
  54. package/corpus/core/src/usage/store.ts +13 -4
  55. package/corpus/core/src/vite/client.ts +38 -1
  56. package/corpus/templates/analytics/AGENTS.md +3 -0
  57. package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
  58. package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
  59. package/corpus/templates/analytics/app/global.css +11 -0
  60. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
  61. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  62. package/corpus/templates/analytics/app/i18n-data.ts +20 -0
  63. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
  64. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
  65. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
  66. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
  67. package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
  70. package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
  71. package/corpus/templates/analytics/server/db/index.ts +6 -0
  72. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
  73. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
  74. package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
  75. package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
  76. package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
  77. package/corpus/templates/content/app/i18n-data.ts +28 -0
  78. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
  79. package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
  80. package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
  81. package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
  82. package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
  83. package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
  84. package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
  85. package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
  86. package/corpus/templates/design/actions/capture-design-state.ts +2 -2
  87. package/corpus/templates/design/actions/connect-localhost.ts +58 -12
  88. package/corpus/templates/design/actions/create-component.ts +2 -18
  89. package/corpus/templates/design/actions/create-design-branch.ts +38 -18
  90. package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
  91. package/corpus/templates/design/actions/delete-file.ts +19 -34
  92. package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
  93. package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
  94. package/corpus/templates/design/actions/export-pdf.ts +2 -1
  95. package/corpus/templates/design/actions/export-zip.ts +9 -3
  96. package/corpus/templates/design/actions/generate-design.ts +107 -70
  97. package/corpus/templates/design/actions/get-component-details.ts +2 -18
  98. package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
  99. package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
  100. package/corpus/templates/design/actions/get-design.ts +2 -1
  101. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
  102. package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
  103. package/corpus/templates/design/actions/index-components.ts +2 -18
  104. package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
  105. package/corpus/templates/design/actions/list-local-files.ts +9 -2
  106. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
  107. package/corpus/templates/design/actions/open-component-source.ts +2 -18
  108. package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
  109. package/corpus/templates/design/actions/present-design-variants.ts +70 -70
  110. package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
  111. package/corpus/templates/design/actions/read-local-file.ts +9 -2
  112. package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
  113. package/corpus/templates/design/actions/rename-screen.ts +425 -0
  114. package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
  115. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
  116. package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
  117. package/corpus/templates/design/actions/update-design.ts +376 -61
  118. package/corpus/templates/design/actions/update-file.ts +455 -188
  119. package/corpus/templates/design/actions/write-local-file.ts +10 -2
  120. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
  121. package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
  122. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
  123. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
  124. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
  125. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
  126. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
  127. package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
  128. package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
  129. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
  130. package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
  131. package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
  132. package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
  133. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
  134. package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
  135. package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
  136. package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
  137. package/corpus/templates/design/app/components/design/index.ts +2 -2
  138. package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
  139. package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
  140. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
  141. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
  142. package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
  143. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
  144. package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
  145. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
  146. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
  147. package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
  148. package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
  149. package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
  150. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
  151. package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
  152. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
  153. package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
  154. package/corpus/templates/design/app/i18n-data.ts +223 -0
  155. package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
  156. package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
  157. package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
  158. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
  159. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
  160. package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
  161. package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
  162. package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
  163. package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
  164. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
  165. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
  166. package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
  167. package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
  168. package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
  169. package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
  170. package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
  171. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
  172. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
  173. package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
  174. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
  175. package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
  176. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
  177. package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
  178. package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
  179. package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
  180. package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
  181. package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
  182. package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
  183. package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
  184. package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
  185. package/corpus/templates/design/server/db/index.ts +2 -52
  186. package/corpus/templates/design/server/db/schema.ts +16 -0
  187. package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
  188. package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
  189. package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
  190. package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
  191. package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
  192. package/corpus/templates/design/server/plugins/db.ts +19 -0
  193. package/corpus/templates/design/server/source-workspace.ts +12 -13
  194. package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
  195. package/corpus/templates/design/shared/screen-rename.ts +42 -0
  196. package/corpus/templates/design/shared/source-mode.ts +84 -4
  197. package/dist/agent/engine/builder-engine.d.ts +1 -1
  198. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  199. package/dist/agent/engine/builtin.js +1 -1
  200. package/dist/agent/engine/builtin.js.map +1 -1
  201. package/dist/agent/model-config.d.ts +9 -9
  202. package/dist/agent/model-config.d.ts.map +1 -1
  203. package/dist/agent/model-config.js +30 -30
  204. package/dist/agent/model-config.js.map +1 -1
  205. package/dist/agent/production-agent.d.ts +60 -1
  206. package/dist/agent/production-agent.d.ts.map +1 -1
  207. package/dist/agent/production-agent.js +106 -7
  208. package/dist/agent/production-agent.js.map +1 -1
  209. package/dist/cli/design-connect.d.ts +30 -1
  210. package/dist/cli/design-connect.d.ts.map +1 -1
  211. package/dist/cli/design-connect.js +359 -75
  212. package/dist/cli/design-connect.js.map +1 -1
  213. package/dist/cli/recap.js +2 -2
  214. package/dist/cli/recap.js.map +1 -1
  215. package/dist/cli/sync-builder-starter-manifest.js +1 -1
  216. package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
  217. package/dist/client/AgentPanel.d.ts.map +1 -1
  218. package/dist/client/AgentPanel.js +6 -1
  219. package/dist/client/AgentPanel.js.map +1 -1
  220. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  221. package/dist/client/agent-chat-adapter.js +10 -5
  222. package/dist/client/agent-chat-adapter.js.map +1 -1
  223. package/dist/client/composer/TiptapComposer.js +1 -1
  224. package/dist/client/composer/TiptapComposer.js.map +1 -1
  225. package/dist/client/index.d.ts +1 -1
  226. package/dist/client/index.d.ts.map +1 -1
  227. package/dist/client/index.js +1 -1
  228. package/dist/client/index.js.map +1 -1
  229. package/dist/client/org/TeamPage.d.ts.map +1 -1
  230. package/dist/client/org/TeamPage.js +6 -5
  231. package/dist/client/org/TeamPage.js.map +1 -1
  232. package/dist/client/use-action.d.ts +32 -0
  233. package/dist/client/use-action.d.ts.map +1 -1
  234. package/dist/client/use-action.js +68 -1
  235. package/dist/client/use-action.js.map +1 -1
  236. package/dist/collab/awareness.d.ts +2 -2
  237. package/dist/collab/awareness.d.ts.map +1 -1
  238. package/dist/collab/routes.d.ts +1 -1
  239. package/dist/db/client.d.ts +3 -0
  240. package/dist/db/client.d.ts.map +1 -1
  241. package/dist/db/client.js +7 -2
  242. package/dist/db/client.js.map +1 -1
  243. package/dist/db/create-get-db.d.ts.map +1 -1
  244. package/dist/db/create-get-db.js +2 -2
  245. package/dist/db/create-get-db.js.map +1 -1
  246. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  247. package/dist/notifications/routes.d.ts +1 -1
  248. package/dist/observability/routes.d.ts +5 -5
  249. package/dist/progress/routes.d.ts +1 -1
  250. package/dist/resources/handlers.d.ts +1 -1
  251. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  252. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  253. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  254. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  255. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  256. package/dist/server/transcribe-voice.js +1 -1
  257. package/dist/server/transcribe-voice.js.map +1 -1
  258. package/dist/usage/store.d.ts.map +1 -1
  259. package/dist/usage/store.js +13 -4
  260. package/dist/usage/store.js.map +1 -1
  261. package/dist/vite/client.d.ts.map +1 -1
  262. package/dist/vite/client.js +36 -1
  263. package/dist/vite/client.js.map +1 -1
  264. package/docs/content/a2a-protocol.mdx +12 -0
  265. package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  266. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  267. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  268. package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  269. package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  270. package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  271. package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  272. package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  273. package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  274. package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  275. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  276. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  277. package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  278. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  279. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  280. package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  281. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  282. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  283. package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  284. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  285. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  286. package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  287. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  288. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  289. package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  290. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  291. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  292. package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  293. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  294. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  295. package/docs/content/pr-visual-recap.mdx +3 -3
  296. package/docs/content/pure-agent-apps.mdx +22 -0
  297. package/package.json +2 -2
@@ -0,0 +1,169 @@
1
+ import type {
2
+ CanvasFrameGeometry,
3
+ CanvasFrameGeometryById,
4
+ } from "@shared/canvas-frames";
5
+
6
+ export const MAX_DESIGN_UNDO_STACK = 50;
7
+
8
+ export interface GeometryHistorySelection {
9
+ overviewSelectedScreenIds: string[];
10
+ selectedLayerIds: string[];
11
+ activeFileId: string | null;
12
+ }
13
+
14
+ export interface GeometryHistoryEntry {
15
+ before: CanvasFrameGeometryById;
16
+ after: CanvasFrameGeometryById;
17
+ selectionBefore?: GeometryHistorySelection;
18
+ selectionAfter?: GeometryHistorySelection;
19
+ }
20
+
21
+ export interface FileCreationHistoryEntry {
22
+ filename: string;
23
+ content: string;
24
+ fileType: string;
25
+ geometry?: CanvasFrameGeometry;
26
+ }
27
+
28
+ export function pruneFileCreationHistoryStack(
29
+ stack: FileCreationHistoryEntry[],
30
+ deletedFilenames: Set<string>,
31
+ options?: { skip?: boolean },
32
+ ): { stack: FileCreationHistoryEntry[]; removed: number } {
33
+ if (options?.skip) return { stack, removed: 0 };
34
+ const next = stack.filter((entry) => !deletedFilenames.has(entry.filename));
35
+ return { stack: next, removed: stack.length - next.length };
36
+ }
37
+
38
+ export function geometryHistoryEntryTouchesFrameIds(
39
+ entry: GeometryHistoryEntry,
40
+ frameIds: Set<string>,
41
+ ) {
42
+ for (const frameId of frameIds) {
43
+ if (entry.before[frameId] || entry.after[frameId]) return true;
44
+ }
45
+ return false;
46
+ }
47
+
48
+ export function pruneGeometryHistoryEntryForDeletedFiles(
49
+ entry: GeometryHistoryEntry,
50
+ deletedFileIds: Set<string>,
51
+ ): GeometryHistoryEntry | null {
52
+ if (!geometryHistoryEntryTouchesFrameIds(entry, deletedFileIds)) {
53
+ return entry;
54
+ }
55
+ const before = { ...entry.before };
56
+ const after = { ...entry.after };
57
+ for (const frameId of deletedFileIds) {
58
+ delete before[frameId];
59
+ delete after[frameId];
60
+ }
61
+ const remainingIds = new Set([...Object.keys(before), ...Object.keys(after)]);
62
+ let hasRemainingChange = false;
63
+ for (const frameId of remainingIds) {
64
+ if (before[frameId] !== after[frameId]) {
65
+ hasRemainingChange = true;
66
+ break;
67
+ }
68
+ }
69
+ if (!hasRemainingChange) return null;
70
+ return {
71
+ before,
72
+ after,
73
+ ...(entry.selectionBefore
74
+ ? { selectionBefore: entry.selectionBefore }
75
+ : {}),
76
+ ...(entry.selectionAfter ? { selectionAfter: entry.selectionAfter } : {}),
77
+ };
78
+ }
79
+
80
+ export function applyGeometryHistoryDiff(
81
+ currentGeometry: CanvasFrameGeometryById,
82
+ entry: GeometryHistoryEntry,
83
+ direction: "undo" | "redo",
84
+ ): CanvasFrameGeometryById {
85
+ const from = direction === "undo" ? entry.after : entry.before;
86
+ const to = direction === "undo" ? entry.before : entry.after;
87
+ const touchedIds = new Set([...Object.keys(from), ...Object.keys(to)]);
88
+ const next = { ...currentGeometry };
89
+ for (const frameId of touchedIds) {
90
+ const target = to[frameId];
91
+ if (target) {
92
+ next[frameId] = target;
93
+ } else {
94
+ delete next[frameId];
95
+ }
96
+ }
97
+ return next;
98
+ }
99
+
100
+ export function removeRecentUndoRedoOrderKinds<T extends string>(
101
+ order: T[],
102
+ kind: T,
103
+ count: number,
104
+ ): T[] {
105
+ if (count <= 0) return order;
106
+ const next = [...order];
107
+ let remaining = count;
108
+ for (let index = next.length - 1; index >= 0 && remaining > 0; index -= 1) {
109
+ if (next[index] !== kind) continue;
110
+ next.splice(index, 1);
111
+ remaining -= 1;
112
+ }
113
+ return next;
114
+ }
115
+
116
+ export interface ContentHistoryChange {
117
+ fileId: string;
118
+ before: string;
119
+ after: string;
120
+ }
121
+
122
+ export interface ContentHistoryGroup {
123
+ changes: ContentHistoryChange[];
124
+ }
125
+
126
+ export type ContentHistoryEntry = ContentHistoryChange | ContentHistoryGroup;
127
+
128
+ export function getContentHistoryChanges(
129
+ entry: ContentHistoryEntry,
130
+ ): ContentHistoryChange[] {
131
+ return "changes" in entry ? entry.changes : [entry];
132
+ }
133
+
134
+ export function getAvailableContentHistoryChanges(
135
+ entry: ContentHistoryEntry,
136
+ availableFileIds: Iterable<string>,
137
+ activeFileId?: string | null,
138
+ ): ContentHistoryChange[] {
139
+ const fileIds = new Set(availableFileIds);
140
+ const activeFileIsAvailable = !!activeFileId && fileIds.has(activeFileId);
141
+ return getContentHistoryChanges(entry).filter(
142
+ (change) =>
143
+ fileIds.has(change.fileId) ||
144
+ (activeFileIsAvailable && change.fileId === activeFileId),
145
+ );
146
+ }
147
+
148
+ export function findLastContentHistoryChangeIndex(
149
+ stack: ContentHistoryChange[],
150
+ fileId?: string | null,
151
+ ) {
152
+ if (!fileId) return -1;
153
+ for (let index = stack.length - 1; index >= 0; index -= 1) {
154
+ if (stack[index]?.fileId === fileId) return index;
155
+ }
156
+ return -1;
157
+ }
158
+
159
+ export function mergeLocalContentHistoryFallback(
160
+ stack: ContentHistoryChange[],
161
+ change: ContentHistoryChange,
162
+ ): ContentHistoryChange[] {
163
+ if (change.before === change.after) return stack;
164
+ const last = stack[stack.length - 1];
165
+ if (last && last.fileId === change.fileId && last.after === change.before) {
166
+ return [...stack.slice(0, -1), { ...last, after: change.after }];
167
+ }
168
+ return [...stack.slice(-(MAX_DESIGN_UNDO_STACK - 1)), change];
169
+ }
@@ -0,0 +1,185 @@
1
+ import type {
2
+ DesignHotkeyAlignEdge,
3
+ DesignHotkeyDistributeAxis,
4
+ } from "@/hooks/useDesignHotkeys";
5
+
6
+ /**
7
+ * Generic rect shape shared by the alignment/distribute/tidy pure helpers.
8
+ * Works for overview screen frames and in-screen layer nodes.
9
+ */
10
+ export interface AlignableRect {
11
+ id: string;
12
+ x: number;
13
+ y: number;
14
+ width: number;
15
+ height: number;
16
+ }
17
+
18
+ export function computeAlignedPositions(
19
+ rects: readonly AlignableRect[],
20
+ bounds: { x: number; y: number; width: number; height: number },
21
+ edge: DesignHotkeyAlignEdge,
22
+ ): Map<string, { x: number; y: number }> {
23
+ const next = new Map<string, { x: number; y: number }>();
24
+ for (const rect of rects) {
25
+ let x = rect.x;
26
+ let y = rect.y;
27
+ switch (edge) {
28
+ case "left":
29
+ x = bounds.x;
30
+ break;
31
+ case "right":
32
+ x = bounds.x + bounds.width - rect.width;
33
+ break;
34
+ case "center-h":
35
+ x = bounds.x + (bounds.width - rect.width) / 2;
36
+ break;
37
+ case "top":
38
+ y = bounds.y;
39
+ break;
40
+ case "bottom":
41
+ y = bounds.y + bounds.height - rect.height;
42
+ break;
43
+ case "center-v":
44
+ y = bounds.y + (bounds.height - rect.height) / 2;
45
+ break;
46
+ }
47
+ x = Math.round(x);
48
+ y = Math.round(y);
49
+ if (x !== Math.round(rect.x) || y !== Math.round(rect.y)) {
50
+ next.set(rect.id, { x, y });
51
+ }
52
+ }
53
+ return next;
54
+ }
55
+
56
+ export function computeDistributedPositions(
57
+ rects: readonly AlignableRect[],
58
+ axis: DesignHotkeyDistributeAxis,
59
+ ): Map<string, { x: number; y: number }> {
60
+ const next = new Map<string, { x: number; y: number }>();
61
+ if (rects.length < 3) return next;
62
+ const sorted = [...rects].sort((a, b) =>
63
+ axis === "horizontal" ? a.x - b.x : a.y - b.y,
64
+ );
65
+ const first = sorted[0]!;
66
+ const last = sorted[sorted.length - 1]!;
67
+ const size = (rect: AlignableRect) =>
68
+ axis === "horizontal" ? rect.width : rect.height;
69
+ const start = (rect: AlignableRect) =>
70
+ axis === "horizontal" ? rect.x : rect.y;
71
+ const totalSpan = start(last) + size(last) - start(first);
72
+ const totalContentSize = sorted.reduce((sum, rect) => sum + size(rect), 0);
73
+ const gapCount = sorted.length - 1;
74
+ const gap = (totalSpan - totalContentSize) / gapCount;
75
+ let cursor = start(first) + size(first) + gap;
76
+ for (let index = 1; index < sorted.length - 1; index += 1) {
77
+ const rect = sorted[index]!;
78
+ const position = Math.round(cursor);
79
+ if (position !== Math.round(start(rect))) {
80
+ next.set(
81
+ rect.id,
82
+ axis === "horizontal"
83
+ ? { x: position, y: rect.y }
84
+ : { x: rect.x, y: position },
85
+ );
86
+ }
87
+ cursor += size(rect) + gap;
88
+ }
89
+ return next;
90
+ }
91
+
92
+ export function computeTidyPositions(
93
+ rects: readonly AlignableRect[],
94
+ ): Map<string, { x: number; y: number }> {
95
+ const next = new Map<string, { x: number; y: number }>();
96
+ if (rects.length === 0) return next;
97
+ const sorted = [...rects].sort((a, b) => a.y - b.y || a.x - b.x);
98
+ const columns = Math.max(1, Math.ceil(Math.sqrt(sorted.length)));
99
+ const median = (values: number[]): number => {
100
+ const sortedValues = [...values].sort((a, b) => a - b);
101
+ const mid = Math.floor(sortedValues.length / 2);
102
+ return sortedValues.length % 2 === 0
103
+ ? (sortedValues[mid - 1]! + sortedValues[mid]!) / 2
104
+ : sortedValues[mid]!;
105
+ };
106
+ const cellWidth = median(sorted.map((rect) => rect.width));
107
+ const cellHeight = median(sorted.map((rect) => rect.height));
108
+ let gap = 24;
109
+ const gapsFound: number[] = [];
110
+ for (let i = 0; i < sorted.length; i += 1) {
111
+ for (let j = 0; j < sorted.length; j += 1) {
112
+ if (i === j) continue;
113
+ const a = sorted[i]!;
114
+ const b = sorted[j]!;
115
+ const verticallyOverlaps = a.y < b.y + b.height && b.y < a.y + a.height;
116
+ if (!verticallyOverlaps) continue;
117
+ const candidateGap = b.x - (a.x + a.width);
118
+ if (candidateGap > 0) gapsFound.push(candidateGap);
119
+ }
120
+ }
121
+ if (gapsFound.length > 0) gap = Math.max(...gapsFound);
122
+ const originX = Math.min(...sorted.map((rect) => rect.x));
123
+ const originY = Math.min(...sorted.map((rect) => rect.y));
124
+ sorted.forEach((rect, index) => {
125
+ const col = index % columns;
126
+ const row = Math.floor(index / columns);
127
+ const x = Math.round(originX + col * (cellWidth + gap));
128
+ const y = Math.round(originY + row * (cellHeight + gap));
129
+ if (x !== Math.round(rect.x) || y !== Math.round(rect.y)) {
130
+ next.set(rect.id, { x, y });
131
+ }
132
+ });
133
+ return next;
134
+ }
135
+
136
+ export function inferAutoLayoutFromChildren(
137
+ container: { x: number; y: number; width: number; height: number },
138
+ children: readonly AlignableRect[],
139
+ ): {
140
+ direction: "row" | "column";
141
+ gap: number;
142
+ padding: number;
143
+ } {
144
+ if (children.length === 0) {
145
+ return { direction: "row", gap: 10, padding: 0 };
146
+ }
147
+ const minX = Math.min(...children.map((child) => child.x));
148
+ const maxX = Math.max(...children.map((child) => child.x + child.width));
149
+ const minY = Math.min(...children.map((child) => child.y));
150
+ const maxY = Math.max(...children.map((child) => child.y + child.height));
151
+ const spreadWidth = maxX - minX;
152
+ const spreadHeight = maxY - minY;
153
+ const direction: "row" | "column" =
154
+ spreadWidth >= spreadHeight ? "row" : "column";
155
+ const sorted = [...children].sort((a, b) =>
156
+ direction === "row" ? a.x - b.x : a.y - b.y,
157
+ );
158
+ const gaps: number[] = [];
159
+ for (let index = 1; index < sorted.length; index += 1) {
160
+ const prev = sorted[index - 1]!;
161
+ const current = sorted[index]!;
162
+ const gapValue =
163
+ direction === "row"
164
+ ? current.x - (prev.x + prev.width)
165
+ : current.y - (prev.y + prev.height);
166
+ if (Number.isFinite(gapValue) && gapValue > 0) gaps.push(gapValue);
167
+ }
168
+ const median = (values: number[]): number => {
169
+ const sortedValues = [...values].sort((a, b) => a - b);
170
+ const mid = Math.floor(sortedValues.length / 2);
171
+ return sortedValues.length % 2 === 0
172
+ ? (sortedValues[mid - 1]! + sortedValues[mid]!) / 2
173
+ : sortedValues[mid]!;
174
+ };
175
+ const gap = gaps.length > 0 ? Math.round(median(gaps)) : 10;
176
+ const insets = [
177
+ minX - container.x,
178
+ minY - container.y,
179
+ container.x + container.width - maxX,
180
+ container.y + container.height - maxY,
181
+ ].filter((inset) => Number.isFinite(inset));
182
+ const padding =
183
+ insets.length > 0 ? Math.max(0, Math.round(Math.min(...insets))) : 0;
184
+ return { direction, gap, padding };
185
+ }
@@ -0,0 +1,309 @@
1
+ import type { CodeLayerProjection } from "@shared/code-layer";
2
+ import {
3
+ applyMotionAutoKeyframe,
4
+ type MotionEase,
5
+ type MotionTrack,
6
+ } from "@shared/motion-timeline";
7
+
8
+ import type { MotionDockTrack } from "@/components/design/MotionDock";
9
+
10
+ import { camelStyleProperty } from "./style-utils";
11
+
12
+ export interface MotionTimelineRow {
13
+ id: string | null;
14
+ designId: string;
15
+ sourceRef: string | null;
16
+ filePath: string | null;
17
+ tracks: unknown;
18
+ durationMs: number;
19
+ defaultEase: string;
20
+ compiledHash: string | null;
21
+ cssHash?: string | null;
22
+ source?: "stored" | "recovered-css" | "stored-css-drift";
23
+ createdAt: string | null;
24
+ updatedAt: string | null;
25
+ }
26
+
27
+ export interface MotionTimelineQueryResult {
28
+ designId: string;
29
+ timelines: MotionTimelineRow[];
30
+ count: number;
31
+ }
32
+
33
+ function isMotionTrack(value: unknown): value is MotionTrack {
34
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
35
+ const candidate = value as {
36
+ targetNodeId?: unknown;
37
+ property?: unknown;
38
+ keyframes?: unknown;
39
+ };
40
+ return (
41
+ typeof candidate.targetNodeId === "string" &&
42
+ typeof candidate.property === "string" &&
43
+ Array.isArray(candidate.keyframes)
44
+ );
45
+ }
46
+
47
+ function normalizeMotionTracks(value: unknown): MotionTrack[] {
48
+ return Array.isArray(value) ? value.filter(isMotionTrack) : [];
49
+ }
50
+
51
+ function labelForMotionTrack(
52
+ track: MotionTrack,
53
+ projection: CodeLayerProjection,
54
+ ): string {
55
+ const node = projection.nodes.find(
56
+ (candidate) =>
57
+ candidate.dataAttributes["data-agent-native-node-id"] ===
58
+ track.targetNodeId || candidate.id === track.targetNodeId,
59
+ );
60
+ return node?.layerName || node?.tag || track.targetNodeId;
61
+ }
62
+
63
+ export function hydrateMotionDockTracks(
64
+ tracks: unknown,
65
+ projection: CodeLayerProjection,
66
+ ): MotionDockTrack[] {
67
+ return normalizeMotionTracks(tracks).map((track) => ({
68
+ ...track,
69
+ label: labelForMotionTrack(track, projection),
70
+ }));
71
+ }
72
+
73
+ export const MOTION_KEYFRAME_TIME_EPSILON = 0.002;
74
+
75
+ function motionCssPropertyName(property: string): string | null {
76
+ const trimmed = property.trim();
77
+ if (!trimmed || trimmed.startsWith("--")) return null;
78
+ const cssName = trimmed
79
+ .replace(/^css/, "")
80
+ .replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
81
+ .toLowerCase();
82
+ return /^-?[a-z][a-z0-9-]*$/i.test(cssName) ? cssName : null;
83
+ }
84
+
85
+ /**
86
+ * Properties auto-keyframe is allowed to record. Discrete / structural
87
+ * properties (display, position, overflow, font-family, …) do not animate
88
+ * meaningfully, so committing a multi-property style edit must not invent
89
+ * tracks for them.
90
+ */
91
+ const MOTION_ANIMATABLE_PROPERTIES = new Set([
92
+ "opacity",
93
+ "transform",
94
+ "translate",
95
+ "rotate",
96
+ "scale",
97
+ "filter",
98
+ "backdrop-filter",
99
+ "color",
100
+ "background-color",
101
+ "border-color",
102
+ "outline-color",
103
+ "fill",
104
+ "stroke",
105
+ "box-shadow",
106
+ "text-shadow",
107
+ "width",
108
+ "height",
109
+ "min-width",
110
+ "min-height",
111
+ "max-width",
112
+ "max-height",
113
+ "top",
114
+ "left",
115
+ "right",
116
+ "bottom",
117
+ "inset",
118
+ "border-radius",
119
+ "border-width",
120
+ "gap",
121
+ "row-gap",
122
+ "column-gap",
123
+ "font-size",
124
+ "line-height",
125
+ "letter-spacing",
126
+ "word-spacing",
127
+ "text-indent",
128
+ "background-position",
129
+ "background-size",
130
+ ]);
131
+
132
+ export function isMotionAnimatableProperty(property: string): boolean {
133
+ if (MOTION_ANIMATABLE_PROPERTIES.has(property)) return true;
134
+ return (
135
+ /^(margin|padding)(-(top|right|bottom|left|inline|block)(-(start|end))?)?$/.test(
136
+ property,
137
+ ) ||
138
+ /^border-(top|right|bottom|left)-(color|width)$/.test(property) ||
139
+ /^border-(top|bottom)-(left|right)-radius$/.test(property)
140
+ );
141
+ }
142
+
143
+ /**
144
+ * Item 7 — motion auto-key. The pure decision behind
145
+ * upsertMotionKeyframesFromStyles (DesignEditor's useCallback wrapper, which
146
+ * only resolves the DOM/projection-dependent targetNodeId and calls this):
147
+ * given a style-change batch, key every ALREADY-tracked, motion-animatable
148
+ * property on `targetNodeId` at the current playhead via
149
+ * applyMotionAutoKeyframe. Matches Figma parity — arming auto-keyframe never
150
+ * invents a new track for an untracked property; that stays a plain style
151
+ * change regardless of this function's outcome (the caller commits the style
152
+ * either way). Returns the SAME `tracks` reference when nothing changed, so
153
+ * callers can cheaply detect "no-op" via reference equality (as the
154
+ * setMotionTracks updater here does) without a separate dirty flag.
155
+ * Extracted as a standalone pure function so the armed/wiring conditions
156
+ * (property-name mapping to the shared motion catalog, playhead threading,
157
+ * per-property track lookup) are directly unit-testable — see
158
+ * DesignEditor.motion.test.ts.
159
+ */
160
+ export function applyMotionAutoKeyframesForStyles(
161
+ tracks: MotionDockTrack[],
162
+ args: {
163
+ targetNodeId: string;
164
+ styles: Record<string, string | undefined>;
165
+ playheadT: number;
166
+ timelineDurationMs: number;
167
+ defaultEase?: MotionEase;
168
+ },
169
+ ): MotionDockTrack[] {
170
+ let next: MotionDockTrack[] = tracks;
171
+ for (const [rawProperty, rawValue] of Object.entries(args.styles)) {
172
+ if (rawValue === undefined) continue;
173
+ const property = motionCssPropertyName(rawProperty);
174
+ if (!property || !isMotionAnimatableProperty(property)) continue;
175
+ const value = String(rawValue).trim();
176
+ if (!value) continue;
177
+ const keyed = applyMotionAutoKeyframe(
178
+ next,
179
+ {
180
+ targetNodeId: args.targetNodeId,
181
+ property,
182
+ value,
183
+ playheadT: args.playheadT,
184
+ timelineDurationMs: args.timelineDurationMs,
185
+ },
186
+ args.defaultEase,
187
+ );
188
+ if (keyed) next = keyed as MotionDockTrack[];
189
+ }
190
+ return next;
191
+ }
192
+
193
+ export function computedMotionStyleValue(
194
+ computedStyles: Record<string, string> | undefined,
195
+ property: string,
196
+ ): string | undefined {
197
+ if (!computedStyles) return undefined;
198
+ return (
199
+ computedStyles[property] ??
200
+ computedStyles[camelStyleProperty(property)] ??
201
+ computedStyles[
202
+ property.replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`)
203
+ ]
204
+ );
205
+ }
206
+
207
+ function defaultMotionBaseValue(property: string, nextValue: string): string {
208
+ if (property === "opacity") return "1";
209
+ if (property === "transform" || property === "filter") return "none";
210
+ return nextValue;
211
+ }
212
+
213
+ export function upsertMotionStyleKeyframes(args: {
214
+ tracks: MotionDockTrack[];
215
+ targetNodeId: string;
216
+ label: string;
217
+ styles: Record<string, string>;
218
+ computedStyles?: Record<string, string>;
219
+ playhead: number;
220
+ defaultEase?: string;
221
+ }): MotionDockTrack[] {
222
+ const t = Math.max(0, Math.min(1, args.playhead));
223
+ const ease = args.defaultEase ?? "ease";
224
+ let nextTracks = args.tracks;
225
+
226
+ for (const [rawProperty, rawValue] of Object.entries(args.styles)) {
227
+ if (rawValue === undefined) continue;
228
+ const value = String(rawValue).trim();
229
+ if (!value) continue;
230
+ const property = motionCssPropertyName(rawProperty);
231
+ if (!property) continue;
232
+ // Only record properties that actually animate; structural commits
233
+ // (display, position, overflow, …) must not invent motion tracks.
234
+ if (!isMotionAnimatableProperty(property)) continue;
235
+
236
+ const existingIndex = nextTracks.findIndex(
237
+ (track) =>
238
+ track.targetNodeId === args.targetNodeId && track.property === property,
239
+ );
240
+
241
+ if (existingIndex >= 0) {
242
+ nextTracks = nextTracks.map((track, index) => {
243
+ if (index !== existingIndex) return track;
244
+ const withoutCurrentTime = track.keyframes.filter(
245
+ (keyframe) => Math.abs(keyframe.t - t) > MOTION_KEYFRAME_TIME_EPSILON,
246
+ );
247
+ return {
248
+ ...track,
249
+ label: track.label || args.label,
250
+ keyframes: [...withoutCurrentTime, { t, value, ease }].sort(
251
+ (a, b) => a.t - b.t,
252
+ ),
253
+ };
254
+ });
255
+ continue;
256
+ }
257
+
258
+ const baseValue =
259
+ computedMotionStyleValue(args.computedStyles, property) ??
260
+ computedMotionStyleValue(args.computedStyles, rawProperty) ??
261
+ defaultMotionBaseValue(property, value);
262
+ // A brand-new track whose committed value equals its base value would be
263
+ // a from == to no-op animation — skip it instead of persisting it.
264
+ if (value === baseValue) continue;
265
+ const keyframes =
266
+ t <= MOTION_KEYFRAME_TIME_EPSILON
267
+ ? [
268
+ { t: 0, value, ease },
269
+ { t: 1, value: baseValue, ease },
270
+ ]
271
+ : t >= 1 - MOTION_KEYFRAME_TIME_EPSILON
272
+ ? [
273
+ { t: 0, value: baseValue, ease },
274
+ { t: 1, value, ease },
275
+ ]
276
+ : [
277
+ { t: 0, value: baseValue, ease },
278
+ { t, value, ease },
279
+ { t: 1, value: baseValue, ease },
280
+ ];
281
+ nextTracks = [
282
+ ...nextTracks,
283
+ {
284
+ targetNodeId: args.targetNodeId,
285
+ property,
286
+ keyframes,
287
+ label: args.label,
288
+ },
289
+ ];
290
+ }
291
+
292
+ return nextTracks;
293
+ }
294
+
295
+ export function motionTimelineFingerprint(
296
+ fileId: string,
297
+ timeline: MotionTimelineRow | null | undefined,
298
+ ): string {
299
+ if (!timeline) return `${fileId}:empty`;
300
+ return [
301
+ fileId,
302
+ timeline.id ?? "css",
303
+ timeline.updatedAt ?? "no-updated-at",
304
+ timeline.compiledHash ?? "no-compiled-hash",
305
+ timeline.cssHash ?? "no-css-hash",
306
+ timeline.source ?? "stored",
307
+ JSON.stringify(timeline.tracks),
308
+ ].join(":");
309
+ }