@agent-native/core 0.85.6 → 0.86.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 (1069) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +130 -1
  3. package/corpus/core/README.md +0 -11
  4. package/corpus/core/docs/content/cloneable-saas.mdx +0 -1
  5. package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +0 -1
  6. package/corpus/core/docs/content/locales/ar-SA/plan-plugin.mdx +10 -9
  7. package/corpus/core/docs/content/locales/ar-SA/sharing.mdx +1 -2
  8. package/corpus/core/docs/content/locales/ar-SA/skills-guide.mdx +2 -3
  9. package/corpus/core/docs/content/locales/ar-SA/template-plan.mdx +23 -7
  10. package/corpus/core/docs/content/locales/ar-SA/tracking.mdx +1 -1
  11. package/corpus/core/docs/content/locales/ar-SA/workspace-connections.mdx +11 -11
  12. package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +0 -1
  13. package/corpus/core/docs/content/locales/de-DE/plan-plugin.mdx +10 -9
  14. package/corpus/core/docs/content/locales/de-DE/sharing.mdx +1 -2
  15. package/corpus/core/docs/content/locales/de-DE/skills-guide.mdx +2 -3
  16. package/corpus/core/docs/content/locales/de-DE/template-plan.mdx +23 -7
  17. package/corpus/core/docs/content/locales/de-DE/tracking.mdx +1 -1
  18. package/corpus/core/docs/content/locales/de-DE/workspace-connections.mdx +1 -1
  19. package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +0 -1
  20. package/corpus/core/docs/content/locales/es-ES/plan-plugin.mdx +10 -9
  21. package/corpus/core/docs/content/locales/es-ES/sharing.mdx +1 -2
  22. package/corpus/core/docs/content/locales/es-ES/skills-guide.mdx +2 -3
  23. package/corpus/core/docs/content/locales/es-ES/template-plan.mdx +23 -7
  24. package/corpus/core/docs/content/locales/es-ES/tracking.mdx +1 -1
  25. package/corpus/core/docs/content/locales/es-ES/workspace-connections.mdx +1 -1
  26. package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +0 -1
  27. package/corpus/core/docs/content/locales/fr-FR/plan-plugin.mdx +10 -9
  28. package/corpus/core/docs/content/locales/fr-FR/sharing.mdx +1 -2
  29. package/corpus/core/docs/content/locales/fr-FR/skills-guide.mdx +2 -3
  30. package/corpus/core/docs/content/locales/fr-FR/template-plan.mdx +23 -7
  31. package/corpus/core/docs/content/locales/fr-FR/tracking.mdx +1 -1
  32. package/corpus/core/docs/content/locales/fr-FR/workspace-connections.mdx +1 -1
  33. package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +0 -1
  34. package/corpus/core/docs/content/locales/hi-IN/plan-plugin.mdx +10 -9
  35. package/corpus/core/docs/content/locales/hi-IN/sharing.mdx +1 -2
  36. package/corpus/core/docs/content/locales/hi-IN/skills-guide.mdx +2 -3
  37. package/corpus/core/docs/content/locales/hi-IN/template-plan.mdx +23 -7
  38. package/corpus/core/docs/content/locales/hi-IN/tracking.mdx +1 -1
  39. package/corpus/core/docs/content/locales/hi-IN/workspace-connections.mdx +1 -1
  40. package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +0 -1
  41. package/corpus/core/docs/content/locales/ja-JP/plan-plugin.mdx +10 -9
  42. package/corpus/core/docs/content/locales/ja-JP/sharing.mdx +1 -2
  43. package/corpus/core/docs/content/locales/ja-JP/skills-guide.mdx +2 -3
  44. package/corpus/core/docs/content/locales/ja-JP/template-plan.mdx +23 -7
  45. package/corpus/core/docs/content/locales/ja-JP/tracking.mdx +1 -1
  46. package/corpus/core/docs/content/locales/ja-JP/workspace-connections.mdx +1 -1
  47. package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +0 -1
  48. package/corpus/core/docs/content/locales/ko-KR/plan-plugin.mdx +10 -9
  49. package/corpus/core/docs/content/locales/ko-KR/sharing.mdx +1 -2
  50. package/corpus/core/docs/content/locales/ko-KR/skills-guide.mdx +2 -3
  51. package/corpus/core/docs/content/locales/ko-KR/template-plan.mdx +23 -7
  52. package/corpus/core/docs/content/locales/ko-KR/tracking.mdx +1 -1
  53. package/corpus/core/docs/content/locales/ko-KR/workspace-connections.mdx +1 -1
  54. package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +0 -1
  55. package/corpus/core/docs/content/locales/pt-BR/plan-plugin.mdx +10 -9
  56. package/corpus/core/docs/content/locales/pt-BR/sharing.mdx +1 -2
  57. package/corpus/core/docs/content/locales/pt-BR/skills-guide.mdx +2 -3
  58. package/corpus/core/docs/content/locales/pt-BR/template-plan.mdx +23 -7
  59. package/corpus/core/docs/content/locales/pt-BR/tracking.mdx +1 -1
  60. package/corpus/core/docs/content/locales/pt-BR/workspace-connections.mdx +1 -1
  61. package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +0 -1
  62. package/corpus/core/docs/content/locales/zh-CN/plan-plugin.mdx +10 -9
  63. package/corpus/core/docs/content/locales/zh-CN/sharing.mdx +1 -2
  64. package/corpus/core/docs/content/locales/zh-CN/skills-guide.mdx +2 -3
  65. package/corpus/core/docs/content/locales/zh-CN/template-plan.mdx +23 -7
  66. package/corpus/core/docs/content/locales/zh-CN/tracking.mdx +1 -1
  67. package/corpus/core/docs/content/locales/zh-CN/workspace-connections.mdx +1 -1
  68. package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +0 -1
  69. package/corpus/core/docs/content/locales/zh-TW/plan-plugin.mdx +10 -9
  70. package/corpus/core/docs/content/locales/zh-TW/sharing.mdx +1 -2
  71. package/corpus/core/docs/content/locales/zh-TW/skills-guide.mdx +2 -3
  72. package/corpus/core/docs/content/locales/zh-TW/template-plan.mdx +23 -7
  73. package/corpus/core/docs/content/locales/zh-TW/tracking.mdx +1 -1
  74. package/corpus/core/docs/content/locales/zh-TW/workspace-connections.mdx +1 -1
  75. package/corpus/core/docs/content/plan-plugin.mdx +12 -11
  76. package/corpus/core/docs/content/sharing.mdx +1 -2
  77. package/corpus/core/docs/content/skills-guide.mdx +4 -3
  78. package/corpus/core/docs/content/template-plan.mdx +37 -6
  79. package/corpus/core/docs/content/tracking.mdx +1 -1
  80. package/corpus/core/docs/content/workspace-connections.mdx +1 -1
  81. package/corpus/core/package.json +6 -6
  82. package/corpus/core/src/agent/run-store.ts +1 -1
  83. package/corpus/core/src/chat-threads/store.ts +73 -11
  84. package/corpus/core/src/checkpoints/store.ts +13 -2
  85. package/corpus/core/src/cli/create.ts +3 -4
  86. package/corpus/core/src/cli/design-connect.ts +469 -9
  87. package/corpus/core/src/cli/index.ts +13 -0
  88. package/corpus/core/src/cli/plan-local.ts +281 -24
  89. package/corpus/core/src/cli/skills.ts +177 -21
  90. package/corpus/core/src/cli/templates-meta.ts +0 -13
  91. package/corpus/core/src/cli/visualize-repo.ts +715 -0
  92. package/corpus/core/src/client/AssistantChat.tsx +126 -17
  93. package/corpus/core/src/client/agent-chat-adapter.ts +6 -5
  94. package/corpus/core/src/client/app-providers.tsx +2 -2
  95. package/corpus/core/src/client/collab/index.ts +31 -0
  96. package/corpus/core/src/client/components/LiveCursorOverlay.tsx +16 -1
  97. package/corpus/core/src/client/components/PresenceBar.tsx +14 -1
  98. package/corpus/core/src/client/components/RecentEditHighlights.tsx +219 -0
  99. package/corpus/core/src/client/components/RemoteSelectionRings.tsx +40 -6
  100. package/corpus/core/src/client/index.ts +48 -0
  101. package/corpus/core/src/client/org/OrgSwitcher.tsx +0 -4
  102. package/corpus/core/src/client/rich-markdown-editor/SharedRichEditor.tsx +49 -32
  103. package/corpus/core/src/client/rich-markdown-editor/index.ts +6 -0
  104. package/corpus/core/src/client/rich-markdown-editor/surgical-apply.ts +192 -0
  105. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +59 -8
  106. package/corpus/core/src/client/session-replay.ts +181 -15
  107. package/corpus/core/src/client/sse-event-processor.ts +90 -1
  108. package/corpus/core/src/client/use-action.ts +123 -25
  109. package/corpus/core/src/client/use-agent-engine-configured.ts +7 -1
  110. package/corpus/core/src/client/use-chat-threads.ts +18 -7
  111. package/corpus/core/src/client/use-db-sync.ts +104 -19
  112. package/corpus/core/src/client/use-pinch-zoom.ts +76 -11
  113. package/corpus/core/src/client/visual-style-controls.tsx +475 -0
  114. package/corpus/core/src/collab/agent-presence.ts +191 -62
  115. package/corpus/core/src/collab/awareness.ts +83 -6
  116. package/corpus/core/src/collab/client.ts +89 -146
  117. package/corpus/core/src/collab/index.ts +34 -0
  118. package/corpus/core/src/collab/presence.ts +63 -3
  119. package/corpus/core/src/collab/recent-edits.ts +171 -0
  120. package/corpus/core/src/collab/undo.ts +432 -0
  121. package/corpus/core/src/collab/ydoc-manager.ts +68 -0
  122. package/corpus/core/src/connections/catalog.ts +2 -3
  123. package/corpus/core/src/db/create-get-db.ts +86 -1
  124. package/corpus/core/src/db/ensure-additive-columns.ts +430 -0
  125. package/corpus/core/src/db/index.ts +6 -0
  126. package/corpus/core/src/db/migrations.ts +236 -35
  127. package/corpus/core/src/jobs/scheduler.ts +17 -14
  128. package/corpus/core/src/observability/traces.ts +182 -1
  129. package/corpus/core/src/org/context.ts +79 -2
  130. package/corpus/core/src/server/agent-chat-plugin.ts +10 -6
  131. package/corpus/core/src/server/auth-marketing.ts +0 -11
  132. package/corpus/core/src/server/auth.ts +11 -4
  133. package/corpus/core/src/server/collab-plugin.ts +56 -13
  134. package/corpus/core/src/server/design-token-utils.ts +1 -1
  135. package/corpus/core/src/server/fusion-app.ts +447 -0
  136. package/corpus/core/src/server/index.ts +13 -0
  137. package/corpus/core/src/server/onboarding-html.ts +3 -4
  138. package/corpus/core/src/server/poll-events.ts +14 -2
  139. package/corpus/core/src/server/poll.ts +219 -12
  140. package/corpus/core/src/server/schema-prompt.ts +28 -9
  141. package/corpus/core/src/settings/store.ts +33 -2
  142. package/corpus/core/src/sharing/actions/set-resource-visibility.ts +2 -0
  143. package/corpus/core/src/sharing/actions/share-resource.ts +3 -0
  144. package/corpus/core/src/sharing/actions/unshare-resource.ts +2 -0
  145. package/corpus/core/src/styles/agent-native.css +0 -5
  146. package/corpus/core/src/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
  147. package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
  148. package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
  149. package/corpus/core/src/templates/default/package.json +3 -3
  150. package/corpus/core/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
  151. package/corpus/core/src/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
  152. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
  153. package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
  154. package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
  155. package/corpus/core/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
  156. package/corpus/core/src/tracking/providers.ts +22 -1
  157. package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +15 -0
  158. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +20 -5
  159. package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +8 -0
  160. package/corpus/templates/analytics/AGENTS.md +33 -6
  161. package/corpus/templates/analytics/actions/navigate.ts +14 -4
  162. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
  163. package/corpus/templates/analytics/actions/view-screen.ts +57 -0
  164. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +23 -1
  165. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
  166. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +21 -0
  167. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -0
  168. package/corpus/templates/analytics/app/i18n/zh-TW.ts +15 -1
  169. package/corpus/templates/analytics/app/i18n-data.ts +151 -10
  170. package/corpus/templates/analytics/app/pages/Agents.tsx +118 -0
  171. package/corpus/templates/analytics/app/pages/DataSources.tsx +11 -1
  172. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +76 -40
  173. package/corpus/templates/analytics/app/pages/sessions/session-replay-devtools.ts +5 -0
  174. package/corpus/templates/analytics/app/routes/agents.tsx +10 -0
  175. package/corpus/templates/analytics/changelog/2026-07-03-agent-admin-tools-page.md +6 -0
  176. package/corpus/templates/analytics/changelog/2026-07-03-agent-llm-observability-dashboard.md +6 -0
  177. package/corpus/templates/analytics/changelog/2026-07-03-session-recordings-now-include-response-bodies-for-5xx-error.md +6 -0
  178. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +34 -0
  179. package/corpus/templates/analytics/docs/schemas/tracked-events.md +6 -0
  180. package/corpus/templates/analytics/package.json +3 -3
  181. package/corpus/templates/analytics/seeds/dashboards/agent-observability-llm.json +173 -0
  182. package/corpus/templates/analytics/server/jobs/analytics-alerts.ts +23 -5
  183. package/corpus/templates/analytics/server/lib/analytics-alerts.ts +58 -13
  184. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +23 -0
  185. package/corpus/templates/analytics/server/lib/dashboard-seeds.ts +5 -0
  186. package/corpus/templates/analytics/server/lib/session-replay-agent-context.ts +120 -8
  187. package/corpus/templates/analytics/server/lib/session-replay.ts +3 -2
  188. package/corpus/templates/analytics/server/plugins/db.ts +93 -2
  189. package/corpus/templates/analytics/server/routes/api/session-replay/agent-diagnostics.json.get.ts +45 -1
  190. package/corpus/templates/analytics/shared/session-replay-diagnostics.ts +10 -0
  191. package/corpus/templates/assets/.agents/skills/real-time-collab/SKILL.md +82 -19
  192. package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +15 -0
  193. package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +8 -0
  194. package/corpus/templates/assets/package.json +3 -3
  195. package/corpus/templates/assets/server/plugins/db.ts +63 -2
  196. package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +15 -0
  197. package/corpus/templates/brain/package.json +3 -3
  198. package/corpus/templates/brain/server/plugins/db.ts +75 -2
  199. package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +15 -0
  200. package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +8 -0
  201. package/corpus/templates/calendar/package.json +3 -3
  202. package/corpus/templates/calendar/server/plugins/db.ts +64 -2
  203. package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +15 -0
  204. package/corpus/templates/chat/package.json +3 -3
  205. package/corpus/templates/clips/actions/finalize-recording.ts +50 -40
  206. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +8 -6
  207. package/corpus/templates/clips/app/routes/share.$shareId.tsx +3 -3
  208. package/corpus/templates/clips/changelog/2026-07-03-mobile-recording-pages-keep-videos-in-a-16-9-player-with-act.md +6 -0
  209. package/corpus/templates/clips/desktop/src/lib/recorder.ts +245 -77
  210. package/corpus/templates/clips/package.json +3 -3
  211. package/corpus/templates/clips/server/lib/recording-upload-state.ts +56 -0
  212. package/corpus/templates/clips/server/plugins/db.ts +68 -1
  213. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +27 -3
  214. package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +15 -0
  215. package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +8 -0
  216. package/corpus/templates/content/actions/edit-document.ts +48 -1
  217. package/corpus/templates/content/actions/list-documents.ts +101 -94
  218. package/corpus/templates/content/actions/search-documents.ts +19 -3
  219. package/corpus/templates/content/actions/update-document.ts +54 -1
  220. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +127 -7
  221. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +97 -2
  222. package/corpus/templates/content/changelog/2026-07-03-agent-document-edits-now-appear-as-a-live-collaborator-prese.md +6 -0
  223. package/corpus/templates/content/changelog/2026-07-03-document-search-and-list-views-load-faster-on-large-workspac.md +6 -0
  224. package/corpus/templates/content/package.json +3 -3
  225. package/corpus/templates/content/server/plugins/db.ts +57 -1
  226. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +190 -13
  227. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +16 -4
  228. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +46 -3
  229. package/corpus/templates/design/.agents/skills/full-app-build/SKILL.md +140 -0
  230. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +20 -0
  231. package/corpus/templates/design/AGENTS.md +83 -13
  232. package/corpus/templates/design/actions/add-fusion-screens.ts +91 -0
  233. package/corpus/templates/design/actions/apply-a11y-fix.ts +5 -1
  234. package/corpus/templates/design/actions/apply-component-prop-edit.ts +24 -69
  235. package/corpus/templates/design/actions/apply-fusion-edits.ts +161 -0
  236. package/corpus/templates/design/actions/apply-motion-edit.ts +29 -2
  237. package/corpus/templates/design/actions/apply-visual-edit.ts +38 -3
  238. package/corpus/templates/design/actions/connect-localhost.ts +25 -18
  239. package/corpus/templates/design/actions/create-component.ts +5 -1
  240. package/corpus/templates/design/actions/create-fusion-app.ts +202 -0
  241. package/corpus/templates/design/actions/deploy-fusion-app.ts +123 -0
  242. package/corpus/templates/design/actions/edit-design.ts +13 -16
  243. package/corpus/templates/design/actions/export-pdf.ts +8 -1
  244. package/corpus/templates/design/actions/export-zip.ts +10 -1
  245. package/corpus/templates/design/actions/get-fusion-deploy-status.ts +86 -0
  246. package/corpus/templates/design/actions/get-motion-timeline.ts +26 -20
  247. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +10 -0
  248. package/corpus/templates/design/actions/insert-asset.ts +190 -7
  249. package/corpus/templates/design/actions/list-fusion-edits.ts +96 -0
  250. package/corpus/templates/design/actions/list-local-files.ts +133 -0
  251. package/corpus/templates/design/actions/open-visual-edit.ts +27 -5
  252. package/corpus/templates/design/actions/present-design-variants.ts +2 -2
  253. package/corpus/templates/design/actions/push-fusion-app.ts +62 -0
  254. package/corpus/templates/design/actions/queue-fusion-edit.ts +112 -0
  255. package/corpus/templates/design/actions/read-local-file.ts +139 -0
  256. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +18 -14
  257. package/corpus/templates/design/actions/run-design-audit.ts +7 -4
  258. package/corpus/templates/design/actions/send-fusion-message.ts +69 -0
  259. package/corpus/templates/design/actions/sync-fusion-app.ts +196 -0
  260. package/corpus/templates/design/actions/write-local-file.ts +162 -36
  261. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +12 -0
  262. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +600 -66
  263. package/corpus/templates/design/app/components/design/EditPanel.tsx +793 -304
  264. package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +328 -0
  265. package/corpus/templates/design/app/components/design/LayersPanel.tsx +671 -154
  266. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +1 -1
  267. package/corpus/templates/design/app/components/design/MotionDock.tsx +234 -46
  268. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2070 -420
  269. package/corpus/templates/design/app/components/design/StatesPanel.tsx +25 -2
  270. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +7 -98
  271. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +640 -72
  272. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +605 -0
  273. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +50 -26
  274. package/corpus/templates/design/app/components/design/code-workbench/ActivityBar.tsx +85 -0
  275. package/corpus/templates/design/app/components/design/code-workbench/CodeWorkbench.tsx +466 -0
  276. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +53 -0
  277. package/corpus/templates/design/app/components/design/code-workbench/commands.ts +339 -0
  278. package/corpus/templates/design/app/components/design/code-workbench/editor/Breadcrumbs.tsx +42 -0
  279. package/corpus/templates/design/app/components/design/code-workbench/editor/BufferSync.tsx +128 -0
  280. package/corpus/templates/design/app/components/design/code-workbench/editor/EditorTabs.tsx +363 -0
  281. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +268 -0
  282. package/corpus/templates/design/app/components/design/code-workbench/editor/StatusBar.tsx +208 -0
  283. package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-setup.ts +61 -0
  284. package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-theme-palette.ts +191 -0
  285. package/corpus/templates/design/app/components/design/code-workbench/editor/monaco-theme.ts +29 -0
  286. package/corpus/templates/design/app/components/design/code-workbench/editor/status-bar-lang.ts +21 -0
  287. package/corpus/templates/design/app/components/design/code-workbench/editor/tab-drag.ts +27 -0
  288. package/corpus/templates/design/app/components/design/code-workbench/explorer/ExplorerView.tsx +155 -0
  289. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +551 -0
  290. package/corpus/templates/design/app/components/design/code-workbench/explorer/file-icons.tsx +115 -0
  291. package/corpus/templates/design/app/components/design/code-workbench/format/format-on-open.ts +65 -0
  292. package/corpus/templates/design/app/components/design/code-workbench/format/prettier-format.ts +182 -0
  293. package/corpus/templates/design/app/components/design/code-workbench/model-registry.ts +133 -0
  294. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +563 -0
  295. package/corpus/templates/design/app/components/design/code-workbench/quickinput/fuzzy.ts +170 -0
  296. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +508 -0
  297. package/corpus/templates/design/app/components/design/code-workbench/search/search-engine.ts +280 -0
  298. package/corpus/templates/design/app/components/design/code-workbench/store.tsx +734 -0
  299. package/corpus/templates/design/app/components/design/code-workbench/workspace/create-providers.ts +39 -0
  300. package/corpus/templates/design/app/components/design/code-workbench/workspace/inline-provider.ts +196 -0
  301. package/corpus/templates/design/app/components/design/code-workbench/workspace/localhost-provider.ts +177 -0
  302. package/corpus/templates/design/app/components/design/code-workbench/workspace/tree.ts +110 -0
  303. package/corpus/templates/design/app/components/design/code-workbench/workspace/types.ts +103 -0
  304. package/corpus/templates/design/app/components/design/code-workbench-theme.ts +68 -0
  305. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +16 -4
  306. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +217 -113
  307. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +111 -5
  308. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -2
  309. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +79 -16
  310. package/corpus/templates/design/app/components/design/inspector/index.ts +9 -0
  311. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +67 -2
  312. package/corpus/templates/design/app/components/design/types.ts +16 -0
  313. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +80 -1
  314. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +24 -6
  315. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +15 -1
  316. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +6 -16
  317. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +77 -0
  318. package/corpus/templates/design/app/i18n/zh-TW.ts +2 -0
  319. package/corpus/templates/design/app/i18n-data.ts +31 -0
  320. package/corpus/templates/design/app/lib/design-import.ts +95 -0
  321. package/corpus/templates/design/app/pages/DesignEditor.tsx +5586 -1112
  322. package/corpus/templates/design/app/pages/Index.tsx +90 -17
  323. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +6 -0
  324. package/corpus/templates/design/changelog/2026-07-03-inspector-fixes-mixed-selections-show-mixed-instead-of-wrong.md +6 -0
  325. package/corpus/templates/design/changelog/2026-07-03-keyframe-timeline-works-reliably-drag-keyframes-smoothly-pic.md +6 -0
  326. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +6 -0
  327. package/corpus/templates/design/changelog/2026-07-03-much-faster-editor-smooth-dragging-zooming-and-panel-updates.md +6 -0
  328. package/corpus/templates/design/changelog/2026-07-03-pen-tool-refinements-click-the-first-point-to-close-with-a-d.md +6 -0
  329. package/corpus/templates/design/changelog/2026-07-03-text-editing-enter-adds-a-line-break-international-ime-typin.md +6 -0
  330. package/corpus/templates/design/changelog/2026-07-03-the-ai-now-edits-like-a-collaborator-on-canvas-its-cursor-gl.md +6 -0
  331. package/corpus/templates/design/changelog/2026-07-03-the-code-panel-is-now-a-full-vs-code-style-editor-with-a-fil.md +6 -0
  332. package/corpus/templates/design/changelog/2026-07-03-undo-and-redo-are-dependable-across-agent-edits-screen-switc.md +6 -0
  333. package/corpus/templates/design/package.json +4 -3
  334. package/corpus/templates/design/server/db/schema.ts +31 -0
  335. package/corpus/templates/design/server/lib/design-export.ts +43 -1
  336. package/corpus/templates/design/server/lib/fusion-screens.ts +246 -0
  337. package/corpus/templates/design/server/plugins/db.ts +89 -2
  338. package/corpus/templates/design/shared/board-file.ts +25 -5
  339. package/corpus/templates/design/shared/canvas-math.ts +754 -9
  340. package/corpus/templates/design/shared/code-layer.ts +304 -26
  341. package/corpus/templates/design/shared/collab-selection.ts +52 -0
  342. package/corpus/templates/design/shared/color-utils.ts +84 -0
  343. package/corpus/templates/design/shared/design-source-capabilities.ts +16 -7
  344. package/corpus/templates/design/shared/full-app.ts +126 -0
  345. package/corpus/templates/design/shared/motion-compiler.ts +75 -31
  346. package/corpus/templates/design/shared/motion-timeline.ts +335 -0
  347. package/corpus/templates/design/shared/pen-path.ts +496 -23
  348. package/corpus/templates/dispatch/package.json +3 -3
  349. package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +15 -0
  350. package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +8 -0
  351. package/corpus/templates/forms/package.json +3 -3
  352. package/corpus/templates/forms/server/plugins/db.ts +62 -2
  353. package/corpus/templates/macros/package.json +3 -3
  354. package/corpus/templates/macros/server/plugins/db.ts +63 -2
  355. package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +15 -0
  356. package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +8 -0
  357. package/corpus/templates/mail/package.json +3 -3
  358. package/corpus/templates/mail/server/lib/contact-frequency.ts +42 -15
  359. package/corpus/templates/mail/server/plugins/db.ts +63 -2
  360. package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +15 -0
  361. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +9 -3
  362. package/corpus/templates/plan/.agents/skills/visualize-repo/SKILL.md +92 -0
  363. package/corpus/templates/plan/actions/export-visual-plan.ts +5 -0
  364. package/corpus/templates/plan/actions/get-local-plan-folder.ts +4 -22
  365. package/corpus/templates/plan/actions/get-plan-version.ts +6 -16
  366. package/corpus/templates/plan/actions/list-plan-versions.ts +48 -20
  367. package/corpus/templates/plan/actions/patch-visual-plan-source.ts +41 -1
  368. package/corpus/templates/plan/actions/promote-local-plan-folder.ts +4 -22
  369. package/corpus/templates/plan/actions/update-local-plan-comments.ts +4 -2
  370. package/corpus/templates/plan/actions/update-local-plan-folder.ts +4 -22
  371. package/corpus/templates/plan/actions/update-visual-plan.ts +60 -1
  372. package/corpus/templates/plan/agent-native.app-skill.json +10 -0
  373. package/corpus/templates/plan/app/components/editor/PlanDocumentEditor.tsx +138 -39
  374. package/corpus/templates/plan/app/components/editor/usePlanUndoStack.ts +165 -23
  375. package/corpus/templates/plan/app/components/plan/PlanContentRenderer.tsx +80 -4
  376. package/corpus/templates/plan/app/global.css +11 -9
  377. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +10 -1
  378. package/corpus/templates/plan/app/hooks/use-plan-presence.ts +66 -0
  379. package/corpus/templates/plan/app/pages/PlansPage.tsx +215 -63
  380. package/corpus/templates/plan/changelog/2026-07-03-local-visual-plans-and-recaps-can-save-comments-without-sign.md +6 -0
  381. package/corpus/templates/plan/changelog/2026-07-03-plan-tabs-render-nested-text-instead-of-raw-json.md +6 -0
  382. package/corpus/templates/plan/changelog/2026-07-03-plans-are-now-fully-real-time-collaborative-multiple-people-.md +6 -0
  383. package/corpus/templates/plan/changelog/2026-07-03-plans-open-and-refresh-faster-especially-long-lived-plans-wi.md +6 -0
  384. package/corpus/templates/plan/changelog/2026-07-03-undo-now-survives-agent-edits-cmd-z-keeps-working-on-your-ow.md +6 -0
  385. package/corpus/templates/plan/package.json +5 -3
  386. package/corpus/templates/plan/server/db/schema.ts +12 -0
  387. package/corpus/templates/plan/server/lib/local-plan-kind.ts +25 -0
  388. package/corpus/templates/plan/server/lib/plan-versions.ts +83 -1
  389. package/corpus/templates/plan/server/lib/public-action-paths.ts +0 -2
  390. package/corpus/templates/plan/server/plans.ts +26 -3
  391. package/corpus/templates/plan/server/plugins/db.ts +105 -2
  392. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +8 -4
  393. package/corpus/templates/slides/AGENTS.md +4 -0
  394. package/corpus/templates/slides/actions/_agent-presence.ts +48 -0
  395. package/corpus/templates/slides/actions/add-slide.ts +16 -18
  396. package/corpus/templates/slides/actions/patch-deck.ts +4 -2
  397. package/corpus/templates/slides/actions/update-slide.ts +114 -77
  398. package/corpus/templates/slides/actions/view-screen.ts +39 -0
  399. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +47 -3
  400. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +32 -35
  401. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +625 -199
  402. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +342 -0
  403. package/corpus/templates/slides/app/components/editor/TweaksPanel.tsx +46 -96
  404. package/corpus/templates/slides/app/components/visual-editor/SaveStatusIndicator.tsx +8 -1
  405. package/corpus/templates/slides/app/context/DeckContext.tsx +589 -176
  406. package/corpus/templates/slides/app/hooks/use-deck-presence.ts +84 -42
  407. package/corpus/templates/slides/app/i18n/ar-SA.ts +46 -0
  408. package/corpus/templates/slides/app/i18n/de-DE.ts +46 -0
  409. package/corpus/templates/slides/app/i18n/en-US.ts +46 -0
  410. package/corpus/templates/slides/app/i18n/es-ES.ts +46 -0
  411. package/corpus/templates/slides/app/i18n/fr-FR.ts +46 -0
  412. package/corpus/templates/slides/app/i18n/hi-IN.ts +46 -0
  413. package/corpus/templates/slides/app/i18n/ja-JP.ts +46 -0
  414. package/corpus/templates/slides/app/i18n/ko-KR.ts +46 -0
  415. package/corpus/templates/slides/app/i18n/pt-BR.ts +46 -0
  416. package/corpus/templates/slides/app/i18n/zh-CN.ts +46 -0
  417. package/corpus/templates/slides/app/i18n/zh-TW.ts +46 -0
  418. package/corpus/templates/slides/app/pages/DeckEditor.tsx +25 -13
  419. package/corpus/templates/slides/changelog/2026-07-03-figma-style-slide-editing.md +6 -0
  420. package/corpus/templates/slides/changelog/2026-07-03-see-the-ai-working-with-you-when-the-agent-edits-a-slide-it-.md +6 -0
  421. package/corpus/templates/slides/changelog/2026-07-03-undo-redo-is-now-precise-and-safe-with-collaborators-it-only.md +6 -0
  422. package/corpus/templates/slides/package.json +3 -3
  423. package/corpus/templates/slides/server/handlers/decks.ts +31 -3
  424. package/corpus/templates/slides/server/plugins/db.ts +65 -2
  425. package/corpus/templates/videos/netlify.toml +1 -12
  426. package/dist/agent/run-store.js +1 -1
  427. package/dist/agent/run-store.js.map +1 -1
  428. package/dist/chat-threads/store.d.ts +9 -1
  429. package/dist/chat-threads/store.d.ts.map +1 -1
  430. package/dist/chat-threads/store.js +63 -11
  431. package/dist/chat-threads/store.js.map +1 -1
  432. package/dist/checkpoints/store.d.ts.map +1 -1
  433. package/dist/checkpoints/store.js +10 -2
  434. package/dist/checkpoints/store.js.map +1 -1
  435. package/dist/cli/create.d.ts.map +1 -1
  436. package/dist/cli/create.js +3 -5
  437. package/dist/cli/create.js.map +1 -1
  438. package/dist/cli/design-connect.d.ts +49 -1
  439. package/dist/cli/design-connect.d.ts.map +1 -1
  440. package/dist/cli/design-connect.js +414 -9
  441. package/dist/cli/design-connect.js.map +1 -1
  442. package/dist/cli/index.js +12 -0
  443. package/dist/cli/index.js.map +1 -1
  444. package/dist/cli/plan-local.d.ts +24 -0
  445. package/dist/cli/plan-local.d.ts.map +1 -1
  446. package/dist/cli/plan-local.js +188 -20
  447. package/dist/cli/plan-local.js.map +1 -1
  448. package/dist/cli/skills.d.ts +3 -1
  449. package/dist/cli/skills.d.ts.map +1 -1
  450. package/dist/cli/skills.js +173 -21
  451. package/dist/cli/skills.js.map +1 -1
  452. package/dist/cli/templates-meta.d.ts.map +1 -1
  453. package/dist/cli/templates-meta.js +0 -14
  454. package/dist/cli/templates-meta.js.map +1 -1
  455. package/dist/cli/visualize-repo.d.ts +49 -0
  456. package/dist/cli/visualize-repo.d.ts.map +1 -0
  457. package/dist/cli/visualize-repo.js +561 -0
  458. package/dist/cli/visualize-repo.js.map +1 -0
  459. package/dist/client/AssistantChat.d.ts.map +1 -1
  460. package/dist/client/AssistantChat.js +120 -15
  461. package/dist/client/AssistantChat.js.map +1 -1
  462. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  463. package/dist/client/agent-chat-adapter.js +6 -5
  464. package/dist/client/agent-chat-adapter.js.map +1 -1
  465. package/dist/client/app-providers.d.ts +2 -2
  466. package/dist/client/app-providers.js.map +1 -1
  467. package/dist/client/collab/index.d.ts +4 -1
  468. package/dist/client/collab/index.d.ts.map +1 -1
  469. package/dist/client/collab/index.js +3 -0
  470. package/dist/client/collab/index.js.map +1 -1
  471. package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
  472. package/dist/client/components/LiveCursorOverlay.js +11 -3
  473. package/dist/client/components/LiveCursorOverlay.js.map +1 -1
  474. package/dist/client/components/PresenceBar.d.ts.map +1 -1
  475. package/dist/client/components/PresenceBar.js +6 -1
  476. package/dist/client/components/PresenceBar.js.map +1 -1
  477. package/dist/client/components/RecentEditHighlights.d.ts +43 -0
  478. package/dist/client/components/RecentEditHighlights.d.ts.map +1 -0
  479. package/dist/client/components/RecentEditHighlights.js +146 -0
  480. package/dist/client/components/RecentEditHighlights.js.map +1 -0
  481. package/dist/client/components/RemoteSelectionRings.d.ts +9 -0
  482. package/dist/client/components/RemoteSelectionRings.d.ts.map +1 -1
  483. package/dist/client/components/RemoteSelectionRings.js +22 -8
  484. package/dist/client/components/RemoteSelectionRings.js.map +1 -1
  485. package/dist/client/index.d.ts +6 -1
  486. package/dist/client/index.d.ts.map +1 -1
  487. package/dist/client/index.js +7 -0
  488. package/dist/client/index.js.map +1 -1
  489. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  490. package/dist/client/org/OrgSwitcher.js +1 -3
  491. package/dist/client/org/OrgSwitcher.js.map +1 -1
  492. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +8 -1
  493. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  494. package/dist/client/rich-markdown-editor/SharedRichEditor.js +8 -2
  495. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  496. package/dist/client/rich-markdown-editor/index.d.ts +1 -0
  497. package/dist/client/rich-markdown-editor/index.d.ts.map +1 -1
  498. package/dist/client/rich-markdown-editor/index.js +1 -0
  499. package/dist/client/rich-markdown-editor/index.js.map +1 -1
  500. package/dist/client/rich-markdown-editor/surgical-apply.d.ts +66 -0
  501. package/dist/client/rich-markdown-editor/surgical-apply.d.ts.map +1 -0
  502. package/dist/client/rich-markdown-editor/surgical-apply.js +147 -0
  503. package/dist/client/rich-markdown-editor/surgical-apply.js.map +1 -0
  504. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +18 -2
  505. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  506. package/dist/client/rich-markdown-editor/useCollabReconcile.js +39 -8
  507. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  508. package/dist/client/session-replay.d.ts +10 -1
  509. package/dist/client/session-replay.d.ts.map +1 -1
  510. package/dist/client/session-replay.js +134 -7
  511. package/dist/client/session-replay.js.map +1 -1
  512. package/dist/client/sse-event-processor.d.ts.map +1 -1
  513. package/dist/client/sse-event-processor.js +81 -1
  514. package/dist/client/sse-event-processor.js.map +1 -1
  515. package/dist/client/use-action.d.ts +24 -0
  516. package/dist/client/use-action.d.ts.map +1 -1
  517. package/dist/client/use-action.js +109 -27
  518. package/dist/client/use-action.js.map +1 -1
  519. package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
  520. package/dist/client/use-agent-engine-configured.js +7 -1
  521. package/dist/client/use-agent-engine-configured.js.map +1 -1
  522. package/dist/client/use-chat-threads.d.ts.map +1 -1
  523. package/dist/client/use-chat-threads.js +11 -3
  524. package/dist/client/use-chat-threads.js.map +1 -1
  525. package/dist/client/use-db-sync.d.ts +36 -0
  526. package/dist/client/use-db-sync.d.ts.map +1 -1
  527. package/dist/client/use-db-sync.js +68 -12
  528. package/dist/client/use-db-sync.js.map +1 -1
  529. package/dist/client/use-pinch-zoom.d.ts.map +1 -1
  530. package/dist/client/use-pinch-zoom.js +76 -11
  531. package/dist/client/use-pinch-zoom.js.map +1 -1
  532. package/dist/client/visual-style-controls.d.ts +79 -0
  533. package/dist/client/visual-style-controls.d.ts.map +1 -0
  534. package/dist/client/visual-style-controls.js +150 -0
  535. package/dist/client/visual-style-controls.js.map +1 -0
  536. package/dist/collab/agent-presence.d.ts +51 -5
  537. package/dist/collab/agent-presence.d.ts.map +1 -1
  538. package/dist/collab/agent-presence.js +144 -58
  539. package/dist/collab/agent-presence.js.map +1 -1
  540. package/dist/collab/awareness-store.d.ts +32 -0
  541. package/dist/collab/awareness-store.d.ts.map +1 -0
  542. package/dist/collab/awareness-store.js +0 -0
  543. package/dist/collab/awareness-store.js.map +1 -0
  544. package/dist/collab/awareness.d.ts +14 -3
  545. package/dist/collab/awareness.d.ts.map +1 -1
  546. package/dist/collab/awareness.js +53 -5
  547. package/dist/collab/awareness.js.map +1 -1
  548. package/dist/collab/client.d.ts +2 -0
  549. package/dist/collab/client.d.ts.map +1 -1
  550. package/dist/collab/client.js +79 -119
  551. package/dist/collab/client.js.map +1 -1
  552. package/dist/collab/index.d.ts +3 -1
  553. package/dist/collab/index.d.ts.map +1 -1
  554. package/dist/collab/index.js +5 -1
  555. package/dist/collab/index.js.map +1 -1
  556. package/dist/collab/presence.d.ts.map +1 -1
  557. package/dist/collab/presence.js +54 -3
  558. package/dist/collab/presence.js.map +1 -1
  559. package/dist/collab/recent-edits.d.ts +87 -0
  560. package/dist/collab/recent-edits.d.ts.map +1 -0
  561. package/dist/collab/recent-edits.js +116 -0
  562. package/dist/collab/recent-edits.js.map +1 -0
  563. package/dist/collab/undo.d.ts +121 -0
  564. package/dist/collab/undo.d.ts.map +1 -0
  565. package/dist/collab/undo.js +244 -0
  566. package/dist/collab/undo.js.map +1 -0
  567. package/dist/collab/ydoc-manager.d.ts +10 -0
  568. package/dist/collab/ydoc-manager.d.ts.map +1 -1
  569. package/dist/collab/ydoc-manager.js +57 -0
  570. package/dist/collab/ydoc-manager.js.map +1 -1
  571. package/dist/connections/catalog.d.ts +2 -2
  572. package/dist/connections/catalog.d.ts.map +1 -1
  573. package/dist/connections/catalog.js +1 -1
  574. package/dist/connections/catalog.js.map +1 -1
  575. package/dist/db/create-get-db.d.ts +22 -0
  576. package/dist/db/create-get-db.d.ts.map +1 -1
  577. package/dist/db/create-get-db.js +73 -1
  578. package/dist/db/create-get-db.js.map +1 -1
  579. package/dist/db/ensure-additive-columns.d.ts +93 -0
  580. package/dist/db/ensure-additive-columns.d.ts.map +1 -0
  581. package/dist/db/ensure-additive-columns.js +355 -0
  582. package/dist/db/ensure-additive-columns.js.map +1 -0
  583. package/dist/db/index.d.ts +1 -0
  584. package/dist/db/index.d.ts.map +1 -1
  585. package/dist/db/index.js +1 -0
  586. package/dist/db/index.js.map +1 -1
  587. package/dist/db/migrations.d.ts +70 -0
  588. package/dist/db/migrations.d.ts.map +1 -1
  589. package/dist/db/migrations.js +196 -33
  590. package/dist/db/migrations.js.map +1 -1
  591. package/dist/jobs/scheduler.js +17 -10
  592. package/dist/jobs/scheduler.js.map +1 -1
  593. package/dist/notifications/routes.d.ts +3 -3
  594. package/dist/observability/routes.d.ts +5 -5
  595. package/dist/observability/traces.d.ts.map +1 -1
  596. package/dist/observability/traces.js +155 -1
  597. package/dist/observability/traces.js.map +1 -1
  598. package/dist/org/context.d.ts +7 -0
  599. package/dist/org/context.d.ts.map +1 -1
  600. package/dist/org/context.js +54 -2
  601. package/dist/org/context.js.map +1 -1
  602. package/dist/progress/routes.d.ts +1 -1
  603. package/dist/resources/handlers.d.ts +3 -3
  604. package/dist/secrets/routes.d.ts +6 -6
  605. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  606. package/dist/server/agent-chat-plugin.js +10 -6
  607. package/dist/server/agent-chat-plugin.js.map +1 -1
  608. package/dist/server/auth-marketing.d.ts.map +1 -1
  609. package/dist/server/auth-marketing.js +0 -10
  610. package/dist/server/auth-marketing.js.map +1 -1
  611. package/dist/server/auth.d.ts.map +1 -1
  612. package/dist/server/auth.js +6 -4
  613. package/dist/server/auth.js.map +1 -1
  614. package/dist/server/collab-plugin.d.ts.map +1 -1
  615. package/dist/server/collab-plugin.js +45 -13
  616. package/dist/server/collab-plugin.js.map +1 -1
  617. package/dist/server/design-token-utils.d.ts +1 -1
  618. package/dist/server/design-token-utils.js +1 -1
  619. package/dist/server/design-token-utils.js.map +1 -1
  620. package/dist/server/fusion-app.d.ts +107 -0
  621. package/dist/server/fusion-app.d.ts.map +1 -0
  622. package/dist/server/fusion-app.js +371 -0
  623. package/dist/server/fusion-app.js.map +1 -0
  624. package/dist/server/index.d.ts +1 -0
  625. package/dist/server/index.d.ts.map +1 -1
  626. package/dist/server/index.js +1 -0
  627. package/dist/server/index.js.map +1 -1
  628. package/dist/server/onboarding-html.d.ts.map +1 -1
  629. package/dist/server/onboarding-html.js +3 -4
  630. package/dist/server/onboarding-html.js.map +1 -1
  631. package/dist/server/poll-events.d.ts +2 -0
  632. package/dist/server/poll-events.d.ts.map +1 -1
  633. package/dist/server/poll-events.js +7 -2
  634. package/dist/server/poll-events.js.map +1 -1
  635. package/dist/server/poll.d.ts +47 -3
  636. package/dist/server/poll.d.ts.map +1 -1
  637. package/dist/server/poll.js +176 -10
  638. package/dist/server/poll.js.map +1 -1
  639. package/dist/server/schema-prompt.d.ts.map +1 -1
  640. package/dist/server/schema-prompt.js +24 -7
  641. package/dist/server/schema-prompt.js.map +1 -1
  642. package/dist/settings/store.d.ts.map +1 -1
  643. package/dist/settings/store.js +32 -3
  644. package/dist/settings/store.js.map +1 -1
  645. package/dist/sharing/actions/set-resource-visibility.js +2 -0
  646. package/dist/sharing/actions/set-resource-visibility.js.map +1 -1
  647. package/dist/sharing/actions/share-resource.d.ts.map +1 -1
  648. package/dist/sharing/actions/share-resource.js +3 -0
  649. package/dist/sharing/actions/share-resource.js.map +1 -1
  650. package/dist/sharing/actions/unshare-resource.js +2 -0
  651. package/dist/sharing/actions/unshare-resource.js.map +1 -1
  652. package/dist/styles/agent-native.css +0 -5
  653. package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
  654. package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
  655. package/dist/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
  656. package/dist/templates/default/package.json +3 -3
  657. package/dist/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
  658. package/dist/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
  659. package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
  660. package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
  661. package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
  662. package/dist/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
  663. package/dist/tracking/providers.d.ts.map +1 -1
  664. package/dist/tracking/providers.js +17 -1
  665. package/dist/tracking/providers.js.map +1 -1
  666. package/docs/content/cloneable-saas.mdx +0 -1
  667. package/docs/content/locales/ar-SA/cloneable-saas.mdx +0 -1
  668. package/docs/content/locales/ar-SA/plan-plugin.mdx +10 -9
  669. package/docs/content/locales/ar-SA/sharing.mdx +1 -2
  670. package/docs/content/locales/ar-SA/skills-guide.mdx +2 -3
  671. package/docs/content/locales/ar-SA/template-plan.mdx +23 -7
  672. package/docs/content/locales/ar-SA/tracking.mdx +1 -1
  673. package/docs/content/locales/ar-SA/workspace-connections.mdx +11 -11
  674. package/docs/content/locales/de-DE/cloneable-saas.mdx +0 -1
  675. package/docs/content/locales/de-DE/plan-plugin.mdx +10 -9
  676. package/docs/content/locales/de-DE/sharing.mdx +1 -2
  677. package/docs/content/locales/de-DE/skills-guide.mdx +2 -3
  678. package/docs/content/locales/de-DE/template-plan.mdx +23 -7
  679. package/docs/content/locales/de-DE/tracking.mdx +1 -1
  680. package/docs/content/locales/de-DE/workspace-connections.mdx +1 -1
  681. package/docs/content/locales/es-ES/cloneable-saas.mdx +0 -1
  682. package/docs/content/locales/es-ES/plan-plugin.mdx +10 -9
  683. package/docs/content/locales/es-ES/sharing.mdx +1 -2
  684. package/docs/content/locales/es-ES/skills-guide.mdx +2 -3
  685. package/docs/content/locales/es-ES/template-plan.mdx +23 -7
  686. package/docs/content/locales/es-ES/tracking.mdx +1 -1
  687. package/docs/content/locales/es-ES/workspace-connections.mdx +1 -1
  688. package/docs/content/locales/fr-FR/cloneable-saas.mdx +0 -1
  689. package/docs/content/locales/fr-FR/plan-plugin.mdx +10 -9
  690. package/docs/content/locales/fr-FR/sharing.mdx +1 -2
  691. package/docs/content/locales/fr-FR/skills-guide.mdx +2 -3
  692. package/docs/content/locales/fr-FR/template-plan.mdx +23 -7
  693. package/docs/content/locales/fr-FR/tracking.mdx +1 -1
  694. package/docs/content/locales/fr-FR/workspace-connections.mdx +1 -1
  695. package/docs/content/locales/hi-IN/cloneable-saas.mdx +0 -1
  696. package/docs/content/locales/hi-IN/plan-plugin.mdx +10 -9
  697. package/docs/content/locales/hi-IN/sharing.mdx +1 -2
  698. package/docs/content/locales/hi-IN/skills-guide.mdx +2 -3
  699. package/docs/content/locales/hi-IN/template-plan.mdx +23 -7
  700. package/docs/content/locales/hi-IN/tracking.mdx +1 -1
  701. package/docs/content/locales/hi-IN/workspace-connections.mdx +1 -1
  702. package/docs/content/locales/ja-JP/cloneable-saas.mdx +0 -1
  703. package/docs/content/locales/ja-JP/plan-plugin.mdx +10 -9
  704. package/docs/content/locales/ja-JP/sharing.mdx +1 -2
  705. package/docs/content/locales/ja-JP/skills-guide.mdx +2 -3
  706. package/docs/content/locales/ja-JP/template-plan.mdx +23 -7
  707. package/docs/content/locales/ja-JP/tracking.mdx +1 -1
  708. package/docs/content/locales/ja-JP/workspace-connections.mdx +1 -1
  709. package/docs/content/locales/ko-KR/cloneable-saas.mdx +0 -1
  710. package/docs/content/locales/ko-KR/plan-plugin.mdx +10 -9
  711. package/docs/content/locales/ko-KR/sharing.mdx +1 -2
  712. package/docs/content/locales/ko-KR/skills-guide.mdx +2 -3
  713. package/docs/content/locales/ko-KR/template-plan.mdx +23 -7
  714. package/docs/content/locales/ko-KR/tracking.mdx +1 -1
  715. package/docs/content/locales/ko-KR/workspace-connections.mdx +1 -1
  716. package/docs/content/locales/pt-BR/cloneable-saas.mdx +0 -1
  717. package/docs/content/locales/pt-BR/plan-plugin.mdx +10 -9
  718. package/docs/content/locales/pt-BR/sharing.mdx +1 -2
  719. package/docs/content/locales/pt-BR/skills-guide.mdx +2 -3
  720. package/docs/content/locales/pt-BR/template-plan.mdx +23 -7
  721. package/docs/content/locales/pt-BR/tracking.mdx +1 -1
  722. package/docs/content/locales/pt-BR/workspace-connections.mdx +1 -1
  723. package/docs/content/locales/zh-CN/cloneable-saas.mdx +0 -1
  724. package/docs/content/locales/zh-CN/plan-plugin.mdx +10 -9
  725. package/docs/content/locales/zh-CN/sharing.mdx +1 -2
  726. package/docs/content/locales/zh-CN/skills-guide.mdx +2 -3
  727. package/docs/content/locales/zh-CN/template-plan.mdx +23 -7
  728. package/docs/content/locales/zh-CN/tracking.mdx +1 -1
  729. package/docs/content/locales/zh-CN/workspace-connections.mdx +1 -1
  730. package/docs/content/locales/zh-TW/cloneable-saas.mdx +0 -1
  731. package/docs/content/locales/zh-TW/plan-plugin.mdx +10 -9
  732. package/docs/content/locales/zh-TW/sharing.mdx +1 -2
  733. package/docs/content/locales/zh-TW/skills-guide.mdx +2 -3
  734. package/docs/content/locales/zh-TW/template-plan.mdx +23 -7
  735. package/docs/content/locales/zh-TW/tracking.mdx +1 -1
  736. package/docs/content/locales/zh-TW/workspace-connections.mdx +1 -1
  737. package/docs/content/plan-plugin.mdx +12 -11
  738. package/docs/content/sharing.mdx +1 -2
  739. package/docs/content/skills-guide.mdx +4 -3
  740. package/docs/content/template-plan.mdx +37 -6
  741. package/docs/content/tracking.mdx +1 -1
  742. package/docs/content/workspace-connections.mdx +1 -1
  743. package/package.json +6 -6
  744. package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +82 -19
  745. package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +15 -0
  746. package/src/templates/default/.agents/skills/storing-data/SKILL.md +8 -0
  747. package/src/templates/default/package.json +3 -3
  748. package/src/templates/workspace-core/.agents/skills/client-methods/SKILL.md +8 -0
  749. package/src/templates/workspace-core/.agents/skills/observability/SKILL.md +22 -0
  750. package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +82 -19
  751. package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +15 -0
  752. package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +8 -0
  753. package/src/templates/workspace-core/.agents/skills/tracking/SKILL.md +1 -0
  754. package/corpus/core/docs/content/locales/ar-SA/template-videos.mdx +0 -336
  755. package/corpus/core/docs/content/locales/de-DE/template-videos.mdx +0 -336
  756. package/corpus/core/docs/content/locales/es-ES/template-videos.mdx +0 -336
  757. package/corpus/core/docs/content/locales/fr-FR/template-videos.mdx +0 -336
  758. package/corpus/core/docs/content/locales/hi-IN/template-videos.mdx +0 -336
  759. package/corpus/core/docs/content/locales/ja-JP/template-videos.mdx +0 -336
  760. package/corpus/core/docs/content/locales/ko-KR/template-videos.mdx +0 -336
  761. package/corpus/core/docs/content/locales/pt-BR/template-videos.mdx +0 -336
  762. package/corpus/core/docs/content/locales/zh-CN/template-videos.mdx +0 -334
  763. package/corpus/core/docs/content/locales/zh-TW/template-videos.mdx +0 -334
  764. package/corpus/core/docs/content/template-videos.mdx +0 -332
  765. package/corpus/templates/design/app/components/design/CodeWorkbenchHost.tsx +0 -697
  766. package/corpus/templates/slides/app/components/editor/CodeEditor.tsx +0 -49
  767. package/corpus/templates/videos/.agents/skills/actions/SKILL.md +0 -432
  768. package/corpus/templates/videos/.agents/skills/animation-tracks/SKILL.md +0 -99
  769. package/corpus/templates/videos/.agents/skills/capture-learnings/SKILL.md +0 -76
  770. package/corpus/templates/videos/.agents/skills/composition-management/SKILL.md +0 -106
  771. package/corpus/templates/videos/.agents/skills/create-skill/SKILL.md +0 -221
  772. package/corpus/templates/videos/.agents/skills/delegate-to-agent/SKILL.md +0 -263
  773. package/corpus/templates/videos/.agents/skills/frontend-design/SKILL.md +0 -112
  774. package/corpus/templates/videos/.agents/skills/security/SKILL.md +0 -263
  775. package/corpus/templates/videos/.agents/skills/self-modifying-code/SKILL.md +0 -88
  776. package/corpus/templates/videos/.agents/skills/shadcn-ui/SKILL.md +0 -95
  777. package/corpus/templates/videos/.builder/ANIMATED_PROPERTIES.md +0 -335
  778. package/corpus/templates/videos/.builder/KEYFRAME_SYNC.md +0 -166
  779. package/corpus/templates/videos/.builder/SEAMLESS_SYNC.md +0 -211
  780. package/corpus/templates/videos/.builder/rules/deploy-app.mdc +0 -22
  781. package/corpus/templates/videos/.builder/rules/organize-ui.mdc +0 -9
  782. package/corpus/templates/videos/.builder/skills/remotion/SKILL.md +0 -29
  783. package/corpus/templates/videos/.builder/skills/remotion/rules/animations.md +0 -27
  784. package/corpus/templates/videos/.builder/skills/remotion/rules/assets.md +0 -43
  785. package/corpus/templates/videos/.builder/skills/remotion/rules/audio.md +0 -58
  786. package/corpus/templates/videos/.builder/skills/remotion/rules/compositions.md +0 -129
  787. package/corpus/templates/videos/.builder/skills/remotion/rules/fonts.md +0 -47
  788. package/corpus/templates/videos/.builder/skills/remotion/rules/images.md +0 -41
  789. package/corpus/templates/videos/.builder/skills/remotion/rules/parameters.md +0 -59
  790. package/corpus/templates/videos/.builder/skills/remotion/rules/sequencing.md +0 -54
  791. package/corpus/templates/videos/.builder/skills/remotion/rules/tailwind.md +0 -11
  792. package/corpus/templates/videos/.builder/skills/remotion/rules/text-animations.md +0 -18
  793. package/corpus/templates/videos/.builder/skills/remotion/rules/timing.md +0 -103
  794. package/corpus/templates/videos/.builder/skills/remotion/rules/transparent-videos.md +0 -35
  795. package/corpus/templates/videos/.builder/skills/remotion/rules/trimming.md +0 -38
  796. package/corpus/templates/videos/.builder/skills/remotion/rules/videos.md +0 -54
  797. package/corpus/templates/videos/.dockerignore +0 -62
  798. package/corpus/templates/videos/.env.example +0 -4
  799. package/corpus/templates/videos/.oxfmtrc.json +0 -8
  800. package/corpus/templates/videos/AGENTS.md +0 -46
  801. package/corpus/templates/videos/CHANGELOG.md +0 -10
  802. package/corpus/templates/videos/DEVELOPING.md +0 -143
  803. package/corpus/templates/videos/README.md +0 -33
  804. package/corpus/templates/videos/actions/analyze-brand-assets.ts +0 -151
  805. package/corpus/templates/videos/actions/apply-design-system.ts +0 -35
  806. package/corpus/templates/videos/actions/create-design-system.ts +0 -84
  807. package/corpus/templates/videos/actions/create-folder.ts +0 -48
  808. package/corpus/templates/videos/actions/db-connect.ts +0 -45
  809. package/corpus/templates/videos/actions/db-health.ts +0 -24
  810. package/corpus/templates/videos/actions/db-status.ts +0 -28
  811. package/corpus/templates/videos/actions/delete-composition.ts +0 -272
  812. package/corpus/templates/videos/actions/delete-folder.ts +0 -27
  813. package/corpus/templates/videos/actions/generate-animated-component.ts +0 -470
  814. package/corpus/templates/videos/actions/get-composition.ts +0 -36
  815. package/corpus/templates/videos/actions/get-design-system.ts +0 -35
  816. package/corpus/templates/videos/actions/import-code.ts +0 -67
  817. package/corpus/templates/videos/actions/import-design-project.ts +0 -42
  818. package/corpus/templates/videos/actions/import-document.ts +0 -125
  819. package/corpus/templates/videos/actions/import-from-url.ts +0 -18
  820. package/corpus/templates/videos/actions/import-github.ts +0 -182
  821. package/corpus/templates/videos/actions/list-compositions.ts +0 -32
  822. package/corpus/templates/videos/actions/list-design-systems.ts +0 -66
  823. package/corpus/templates/videos/actions/list-folders.ts +0 -61
  824. package/corpus/templates/videos/actions/move-composition-to-folder.ts +0 -59
  825. package/corpus/templates/videos/actions/navigate.ts +0 -40
  826. package/corpus/templates/videos/actions/rename-folder.ts +0 -41
  827. package/corpus/templates/videos/actions/run.ts +0 -2
  828. package/corpus/templates/videos/actions/save-composition.ts +0 -114
  829. package/corpus/templates/videos/actions/set-default-design-system.ts +0 -40
  830. package/corpus/templates/videos/actions/update-composition.ts +0 -81
  831. package/corpus/templates/videos/actions/update-design-system.ts +0 -68
  832. package/corpus/templates/videos/actions/validate-compositions.ts +0 -502
  833. package/corpus/templates/videos/actions/view-screen.ts +0 -48
  834. package/corpus/templates/videos/app/components/CameraControls.tsx +0 -442
  835. package/corpus/templates/videos/app/components/CameraToolbar.tsx +0 -437
  836. package/corpus/templates/videos/app/components/CloudUpgrade.tsx +0 -287
  837. package/corpus/templates/videos/app/components/CollabPresenceBar.tsx +0 -81
  838. package/corpus/templates/videos/app/components/CompSettingsEditor.tsx +0 -107
  839. package/corpus/templates/videos/app/components/ComponentLibrarySidebar.tsx +0 -457
  840. package/corpus/templates/videos/app/components/ComponentPropsPanel.tsx +0 -227
  841. package/corpus/templates/videos/app/components/ComponentsSidebar.tsx +0 -70
  842. package/corpus/templates/videos/app/components/CompositionCard.tsx +0 -147
  843. package/corpus/templates/videos/app/components/CurrentElementPanel.tsx +0 -835
  844. package/corpus/templates/videos/app/components/CursorControls.tsx +0 -664
  845. package/corpus/templates/videos/app/components/CursorPositioningOverlay.tsx +0 -238
  846. package/corpus/templates/videos/app/components/LibraryFolderRow.tsx +0 -235
  847. package/corpus/templates/videos/app/components/MotionCurveSelect.tsx +0 -61
  848. package/corpus/templates/videos/app/components/NewCompositionPopover.tsx +0 -192
  849. package/corpus/templates/videos/app/components/PropsEditor.tsx +0 -210
  850. package/corpus/templates/videos/app/components/QuestionFlow.tsx +0 -40
  851. package/corpus/templates/videos/app/components/Sidebar.tsx +0 -609
  852. package/corpus/templates/videos/app/components/StudioHeader.tsx +0 -109
  853. package/corpus/templates/videos/app/components/Timeline.tsx +0 -2067
  854. package/corpus/templates/videos/app/components/TrackPropertiesPanel.tsx +0 -922
  855. package/corpus/templates/videos/app/components/TweaksPanel.tsx +0 -277
  856. package/corpus/templates/videos/app/components/VideoPlayer.tsx +0 -487
  857. package/corpus/templates/videos/app/components/design-system/DesignSystemCard.tsx +0 -134
  858. package/corpus/templates/videos/app/components/design-system/DesignSystemPreview.tsx +0 -176
  859. package/corpus/templates/videos/app/components/design-system/DesignSystemSetup.tsx +0 -707
  860. package/corpus/templates/videos/app/components/keyframes/KeyframeActionButtons.tsx +0 -78
  861. package/corpus/templates/videos/app/components/keyframes/KeyframeNavigation.tsx +0 -59
  862. package/corpus/templates/videos/app/components/keyframes/PropertyNumberInput.tsx +0 -56
  863. package/corpus/templates/videos/app/components/layout/Header.tsx +0 -64
  864. package/corpus/templates/videos/app/components/layout/HeaderActions.tsx +0 -84
  865. package/corpus/templates/videos/app/components/layout/Layout.tsx +0 -132
  866. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +0 -180
  867. package/corpus/templates/videos/app/components/ui/accordion.tsx +0 -56
  868. package/corpus/templates/videos/app/components/ui/alert-dialog.tsx +0 -139
  869. package/corpus/templates/videos/app/components/ui/alert.tsx +0 -59
  870. package/corpus/templates/videos/app/components/ui/aspect-ratio.tsx +0 -5
  871. package/corpus/templates/videos/app/components/ui/avatar.tsx +0 -48
  872. package/corpus/templates/videos/app/components/ui/badge.tsx +0 -37
  873. package/corpus/templates/videos/app/components/ui/breadcrumb.tsx +0 -115
  874. package/corpus/templates/videos/app/components/ui/button.tsx +0 -57
  875. package/corpus/templates/videos/app/components/ui/calendar.tsx +0 -68
  876. package/corpus/templates/videos/app/components/ui/card.tsx +0 -86
  877. package/corpus/templates/videos/app/components/ui/carousel.tsx +0 -260
  878. package/corpus/templates/videos/app/components/ui/chart.tsx +0 -375
  879. package/corpus/templates/videos/app/components/ui/checkbox.tsx +0 -28
  880. package/corpus/templates/videos/app/components/ui/collapsible.tsx +0 -9
  881. package/corpus/templates/videos/app/components/ui/command.tsx +0 -154
  882. package/corpus/templates/videos/app/components/ui/context-menu.tsx +0 -202
  883. package/corpus/templates/videos/app/components/ui/dialog.tsx +0 -130
  884. package/corpus/templates/videos/app/components/ui/drawer.tsx +0 -116
  885. package/corpus/templates/videos/app/components/ui/dropdown-menu.tsx +0 -212
  886. package/corpus/templates/videos/app/components/ui/form.tsx +0 -177
  887. package/corpus/templates/videos/app/components/ui/hover-card.tsx +0 -27
  888. package/corpus/templates/videos/app/components/ui/input-otp.tsx +0 -69
  889. package/corpus/templates/videos/app/components/ui/input.tsx +0 -22
  890. package/corpus/templates/videos/app/components/ui/label.tsx +0 -24
  891. package/corpus/templates/videos/app/components/ui/menubar.tsx +0 -234
  892. package/corpus/templates/videos/app/components/ui/navigation-menu.tsx +0 -128
  893. package/corpus/templates/videos/app/components/ui/pagination.tsx +0 -121
  894. package/corpus/templates/videos/app/components/ui/popover.tsx +0 -44
  895. package/corpus/templates/videos/app/components/ui/progress.tsx +0 -26
  896. package/corpus/templates/videos/app/components/ui/radio-group.tsx +0 -42
  897. package/corpus/templates/videos/app/components/ui/resizable.tsx +0 -43
  898. package/corpus/templates/videos/app/components/ui/scroll-area.tsx +0 -46
  899. package/corpus/templates/videos/app/components/ui/select.tsx +0 -158
  900. package/corpus/templates/videos/app/components/ui/separator.tsx +0 -29
  901. package/corpus/templates/videos/app/components/ui/sheet.tsx +0 -160
  902. package/corpus/templates/videos/app/components/ui/sidebar.tsx +0 -782
  903. package/corpus/templates/videos/app/components/ui/skeleton.tsx +0 -15
  904. package/corpus/templates/videos/app/components/ui/slider.tsx +0 -26
  905. package/corpus/templates/videos/app/components/ui/sonner.tsx +0 -58
  906. package/corpus/templates/videos/app/components/ui/spinner.tsx +0 -17
  907. package/corpus/templates/videos/app/components/ui/switch.tsx +0 -27
  908. package/corpus/templates/videos/app/components/ui/table.tsx +0 -117
  909. package/corpus/templates/videos/app/components/ui/tabs.tsx +0 -53
  910. package/corpus/templates/videos/app/components/ui/textarea.tsx +0 -23
  911. package/corpus/templates/videos/app/components/ui/toast.tsx +0 -127
  912. package/corpus/templates/videos/app/components/ui/toaster.tsx +0 -33
  913. package/corpus/templates/videos/app/components/ui/toggle-group.tsx +0 -59
  914. package/corpus/templates/videos/app/components/ui/toggle.tsx +0 -43
  915. package/corpus/templates/videos/app/components/ui/tooltip.tsx +0 -30
  916. package/corpus/templates/videos/app/components/ui/use-toast.ts +0 -3
  917. package/corpus/templates/videos/app/config/constants.ts +0 -204
  918. package/corpus/templates/videos/app/contexts/CompositionContext.tsx +0 -437
  919. package/corpus/templates/videos/app/contexts/CurrentElementContext.tsx +0 -247
  920. package/corpus/templates/videos/app/contexts/PlaybackContext.tsx +0 -65
  921. package/corpus/templates/videos/app/contexts/TimelineContext.tsx +0 -433
  922. package/corpus/templates/videos/app/entry.client.tsx +0 -19
  923. package/corpus/templates/videos/app/entry.server.tsx +0 -10
  924. package/corpus/templates/videos/app/global.css +0 -171
  925. package/corpus/templates/videos/app/hooks/use-composition-collab.ts +0 -269
  926. package/corpus/templates/videos/app/hooks/use-composition-design-system.ts +0 -47
  927. package/corpus/templates/videos/app/hooks/use-database-compositions.ts +0 -144
  928. package/corpus/templates/videos/app/hooks/use-db-status.ts +0 -30
  929. package/corpus/templates/videos/app/hooks/use-design-systems.ts +0 -21
  930. package/corpus/templates/videos/app/hooks/use-folders.ts +0 -199
  931. package/corpus/templates/videos/app/hooks/use-mobile.tsx +0 -21
  932. package/corpus/templates/videos/app/hooks/use-navigation-state.ts +0 -52
  933. package/corpus/templates/videos/app/hooks/use-question-flow.ts +0 -14
  934. package/corpus/templates/videos/app/hooks/use-toast.ts +0 -188
  935. package/corpus/templates/videos/app/hooks/useUnsavedChanges.ts +0 -48
  936. package/corpus/templates/videos/app/i18n/ar-SA.ts +0 -446
  937. package/corpus/templates/videos/app/i18n/de-DE.ts +0 -470
  938. package/corpus/templates/videos/app/i18n/en-US.ts +0 -452
  939. package/corpus/templates/videos/app/i18n/es-ES.ts +0 -468
  940. package/corpus/templates/videos/app/i18n/fr-FR.ts +0 -469
  941. package/corpus/templates/videos/app/i18n/hi-IN.ts +0 -446
  942. package/corpus/templates/videos/app/i18n/index.ts +0 -34
  943. package/corpus/templates/videos/app/i18n/ja-JP.ts +0 -460
  944. package/corpus/templates/videos/app/i18n/ko-KR.ts +0 -453
  945. package/corpus/templates/videos/app/i18n/pt-BR.ts +0 -461
  946. package/corpus/templates/videos/app/i18n/zh-CN.ts +0 -437
  947. package/corpus/templates/videos/app/i18n/zh-TW.ts +0 -437
  948. package/corpus/templates/videos/app/lib/database-compositions.ts +0 -67
  949. package/corpus/templates/videos/app/lib/design-systems.ts +0 -234
  950. package/corpus/templates/videos/app/lib/tab-id.ts +0 -1
  951. package/corpus/templates/videos/app/lib/utils.ts +0 -1
  952. package/corpus/templates/videos/app/pages/ComponentLibrary.tsx +0 -134
  953. package/corpus/templates/videos/app/pages/ComponentLibraryView.tsx +0 -362
  954. package/corpus/templates/videos/app/pages/CompositionView.tsx +0 -715
  955. package/corpus/templates/videos/app/pages/DesignSystems.tsx +0 -180
  956. package/corpus/templates/videos/app/pages/Index.tsx +0 -258
  957. package/corpus/templates/videos/app/pages/NewComposition.tsx +0 -95
  958. package/corpus/templates/videos/app/pages/NotFound.tsx +0 -36
  959. package/corpus/templates/videos/app/remotion/CameraHost.tsx +0 -223
  960. package/corpus/templates/videos/app/remotion/animations/useElementAnimations.ts +0 -231
  961. package/corpus/templates/videos/app/remotion/componentRegistry.ts +0 -473
  962. package/corpus/templates/videos/app/remotion/components/AnimatedElement.tsx +0 -436
  963. package/corpus/templates/videos/app/remotion/components/InteractiveElement.tsx +0 -159
  964. package/corpus/templates/videos/app/remotion/components/InteractiveZoneEditor.tsx +0 -273
  965. package/corpus/templates/videos/app/remotion/compositions/BlankComposition.tsx +0 -204
  966. package/corpus/templates/videos/app/remotion/compositions/index.ts +0 -2
  967. package/corpus/templates/videos/app/remotion/createComponentCursorTrack.ts +0 -132
  968. package/corpus/templates/videos/app/remotion/docs/CURSOR_TRACKS.md +0 -285
  969. package/corpus/templates/videos/app/remotion/easingFunctions.ts +0 -255
  970. package/corpus/templates/videos/app/remotion/hooks/createInteractiveComposition.tsx +0 -283
  971. package/corpus/templates/videos/app/remotion/hooks/useCursorHistory.ts +0 -72
  972. package/corpus/templates/videos/app/remotion/hooks/useCursorTypeFromHover.ts +0 -35
  973. package/corpus/templates/videos/app/remotion/hooks/useEditableZones.tsx +0 -267
  974. package/corpus/templates/videos/app/remotion/hooks/useHoverAnimation.ts +0 -90
  975. package/corpus/templates/videos/app/remotion/hooks/useHoverAnimationSmooth.ts +0 -146
  976. package/corpus/templates/videos/app/remotion/hooks/useInteractiveComponent.ts +0 -711
  977. package/corpus/templates/videos/app/remotion/hooks/useRegisterInteractiveElement.ts +0 -56
  978. package/corpus/templates/videos/app/remotion/library-components/Button.tsx +0 -252
  979. package/corpus/templates/videos/app/remotion/library-components/Card.tsx +0 -241
  980. package/corpus/templates/videos/app/remotion/library-components/CodePanel.tsx +0 -1005
  981. package/corpus/templates/videos/app/remotion/library-components/FileItem.tsx +0 -196
  982. package/corpus/templates/videos/app/remotion/library-components/FolderItem.tsx +0 -199
  983. package/corpus/templates/videos/app/remotion/library-components/PrimaryButton.tsx +0 -177
  984. package/corpus/templates/videos/app/remotion/library-components/SecondaryButton.tsx +0 -167
  985. package/corpus/templates/videos/app/remotion/library-components/SectionHeader.tsx +0 -108
  986. package/corpus/templates/videos/app/remotion/library-components/TEMPLATE.tsx +0 -211
  987. package/corpus/templates/videos/app/remotion/library-components/index.ts +0 -10
  988. package/corpus/templates/videos/app/remotion/registry.ts +0 -91
  989. package/corpus/templates/videos/app/remotion/testing/animationTestUtils.ts +0 -366
  990. package/corpus/templates/videos/app/remotion/trackAnimation.ts +0 -179
  991. package/corpus/templates/videos/app/remotion/trackHelpers.ts +0 -110
  992. package/corpus/templates/videos/app/remotion/ui-components/Cursor.tsx +0 -182
  993. package/corpus/templates/videos/app/remotion/ui-components/InteractiveButton.tsx +0 -116
  994. package/corpus/templates/videos/app/remotion/ui-components/InteractiveCard.tsx +0 -126
  995. package/corpus/templates/videos/app/remotion/ui-components/index.ts +0 -16
  996. package/corpus/templates/videos/app/remotion/utils/animationHelpers.ts +0 -444
  997. package/corpus/templates/videos/app/remotion/utils/interactiveElements.ts +0 -235
  998. package/corpus/templates/videos/app/remotion/utils/springAnimation.ts +0 -76
  999. package/corpus/templates/videos/app/root.tsx +0 -165
  1000. package/corpus/templates/videos/app/routes/$.tsx +0 -10
  1001. package/corpus/templates/videos/app/routes/_index.tsx +0 -25
  1002. package/corpus/templates/videos/app/routes/c.$compositionId.tsx +0 -10
  1003. package/corpus/templates/videos/app/routes/components.tsx +0 -10
  1004. package/corpus/templates/videos/app/routes/design-systems.tsx +0 -10
  1005. package/corpus/templates/videos/app/routes/extensions.$id.$slug.tsx +0 -2
  1006. package/corpus/templates/videos/app/routes/extensions.$id.tsx +0 -5
  1007. package/corpus/templates/videos/app/routes/extensions._index.tsx +0 -5
  1008. package/corpus/templates/videos/app/routes/extensions.tsx +0 -5
  1009. package/corpus/templates/videos/app/routes/settings.tsx +0 -88
  1010. package/corpus/templates/videos/app/routes/team.tsx +0 -11
  1011. package/corpus/templates/videos/app/routes.ts +0 -4
  1012. package/corpus/templates/videos/app/state.ts +0 -77
  1013. package/corpus/templates/videos/app/types/elementAnimations.ts +0 -121
  1014. package/corpus/templates/videos/app/types.ts +0 -200
  1015. package/corpus/templates/videos/app/utils/compositionHelpers.ts +0 -445
  1016. package/corpus/templates/videos/app/utils/debug.ts +0 -63
  1017. package/corpus/templates/videos/app/utils/keyframeUtils.ts +0 -331
  1018. package/corpus/templates/videos/app/utils/timelineCoordinates.ts +0 -79
  1019. package/corpus/templates/videos/app/vite-env.d.ts +0 -6
  1020. package/corpus/templates/videos/changelog/2026-06-24-a-new-settings-page-gives-quick-access-to-language-workspace.md +0 -6
  1021. package/corpus/templates/videos/changelog/2026-06-24-added-a-language-picker-and-localized-app-chrome-for-support.md +0 -6
  1022. package/corpus/templates/videos/changelog/2026-06-27-traditional-chinese-copy-uses-taiwan-terminology.md +0 -6
  1023. package/corpus/templates/videos/changelog/2026-06-28-left-sidebar-collapses-into-an-animated-icon-rail.md +0 -6
  1024. package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +0 -6
  1025. package/corpus/templates/videos/components.json +0 -20
  1026. package/corpus/templates/videos/data/sync-config.json +0 -3
  1027. package/corpus/templates/videos/docs/examples/create-composition.example.ts +0 -128
  1028. package/corpus/templates/videos/drizzle.config.ts +0 -3
  1029. package/corpus/templates/videos/learnings.defaults.md +0 -5
  1030. package/corpus/templates/videos/package.json +0 -114
  1031. package/corpus/templates/videos/public/agent-native-icon-dark.svg +0 -10
  1032. package/corpus/templates/videos/public/agent-native-icon-light.svg +0 -10
  1033. package/corpus/templates/videos/public/agent-native-logo-dark.svg +0 -21
  1034. package/corpus/templates/videos/public/agent-native-logo-light.svg +0 -21
  1035. package/corpus/templates/videos/public/favicon.svg +0 -1
  1036. package/corpus/templates/videos/public/icon-180.svg +0 -1
  1037. package/corpus/templates/videos/public/icon-192.svg +0 -1
  1038. package/corpus/templates/videos/public/icon-512.svg +0 -1
  1039. package/corpus/templates/videos/public/manifest.json +0 -21
  1040. package/corpus/templates/videos/public/placeholder.svg +0 -1
  1041. package/corpus/templates/videos/public/robots.txt +0 -14
  1042. package/corpus/templates/videos/react-router.config.ts +0 -7
  1043. package/corpus/templates/videos/server/db/index.ts +0 -37
  1044. package/corpus/templates/videos/server/db/schema.ts +0 -55
  1045. package/corpus/templates/videos/server/middleware/auth.ts +0 -15
  1046. package/corpus/templates/videos/server/plugins/agent-chat.ts +0 -29
  1047. package/corpus/templates/videos/server/plugins/auth.ts +0 -14
  1048. package/corpus/templates/videos/server/plugins/collab.ts +0 -11
  1049. package/corpus/templates/videos/server/plugins/core-routes.ts +0 -22
  1050. package/corpus/templates/videos/server/plugins/db.ts +0 -160
  1051. package/corpus/templates/videos/server/routes/[...page].get.ts +0 -5
  1052. package/corpus/templates/videos/server/routes/[...page].head.ts +0 -1
  1053. package/corpus/templates/videos/server/routes/api/save-composition-defaults.post.ts +0 -274
  1054. package/corpus/templates/videos/shared/api.ts +0 -75
  1055. package/corpus/templates/videos/ssr-entry.ts +0 -15
  1056. package/corpus/templates/videos/tsconfig.json +0 -21
  1057. package/corpus/templates/videos/vite.config.ts +0 -19
  1058. package/corpus/templates/videos/vitest.config.ts +0 -16
  1059. package/docs/content/locales/ar-SA/template-videos.mdx +0 -336
  1060. package/docs/content/locales/de-DE/template-videos.mdx +0 -336
  1061. package/docs/content/locales/es-ES/template-videos.mdx +0 -336
  1062. package/docs/content/locales/fr-FR/template-videos.mdx +0 -336
  1063. package/docs/content/locales/hi-IN/template-videos.mdx +0 -336
  1064. package/docs/content/locales/ja-JP/template-videos.mdx +0 -336
  1065. package/docs/content/locales/ko-KR/template-videos.mdx +0 -336
  1066. package/docs/content/locales/pt-BR/template-videos.mdx +0 -336
  1067. package/docs/content/locales/zh-CN/template-videos.mdx +0 -334
  1068. package/docs/content/locales/zh-TW/template-videos.mdx +0 -334
  1069. package/docs/content/template-videos.mdx +0 -332
@@ -1,2067 +0,0 @@
1
- import { useT } from "@agent-native/core/client";
2
- import {
3
- IconCamera,
4
- IconMouse,
5
- IconAlertCircle,
6
- IconTrash,
7
- IconRotate,
8
- } from "@tabler/icons-react";
9
- import { useRef, useCallback, useState, useEffect } from "react";
10
-
11
- import {
12
- AlertDialog,
13
- AlertDialogContent,
14
- AlertDialogHeader,
15
- AlertDialogTitle,
16
- AlertDialogDescription,
17
- AlertDialogFooter,
18
- AlertDialogAction,
19
- AlertDialogCancel,
20
- } from "@/components/ui/alert-dialog";
21
- import {
22
- Tooltip,
23
- TooltipContent,
24
- TooltipTrigger,
25
- } from "@/components/ui/tooltip";
26
- import { useIsMobile } from "@/hooks/use-mobile";
27
- import { cn } from "@/lib/utils";
28
- import type { AnimationTrack, EasingKey } from "@/types";
29
- import {
30
- frameToViewPct,
31
- clientXToFrame,
32
- pxDeltaToFrameDelta,
33
- clampFrame,
34
- } from "@/utils/timelineCoordinates";
35
-
36
- type DragMode = "move" | "resize-start" | "resize-end";
37
-
38
- interface DragState {
39
- trackId: string;
40
- mode: DragMode;
41
- startX: number;
42
- initialStart: number;
43
- initialEnd: number;
44
- barAreaWidth: number;
45
- }
46
-
47
- interface KeyframeDragState {
48
- originalFrame: number;
49
- currentFrame: number;
50
- startX: number;
51
- barAreaWidth: number;
52
- movingKeys?: string[]; // "trackId:frame" keys being moved
53
- originalFrames?: Map<number, number>; // original frame -> original frame
54
- }
55
-
56
- interface KeyframeConflict {
57
- trackId: string;
58
- newFrame: number;
59
- originalFrame: number;
60
- conflictingFrame: number;
61
- }
62
-
63
- export type TimelineProps = {
64
- currentFrame: number;
65
- durationInFrames: number;
66
- fps: number;
67
- onSeek: (frame: number) => void;
68
- tracks: AnimationTrack[];
69
- selectedTrackId: string | null;
70
- onSelectTrack: (id: string | null) => void;
71
- onUpdateTrack: (id: string, patch: Partial<AnimationTrack>) => void;
72
- onDeleteTrack?: (id: string) => void;
73
- // Controlled view window — lifted to CompositionView so VideoPlayer shares it
74
- viewStart: number;
75
- viewEnd: number;
76
- onViewChange: (start: number, end: number) => void;
77
- onCameraKeyframeClick?: (trackType: "camera" | "cursor") => void;
78
- isPlaying?: boolean;
79
- };
80
-
81
- const RULER_HEIGHT = 24;
82
- const TRACK_HEIGHT = 32;
83
- const LABEL_WIDTH_DESKTOP = 180;
84
- const LABEL_WIDTH_MOBILE = 100;
85
- const RANGE_BAR_HEIGHT = 12;
86
-
87
- // Expression-controlled accent (matches TrackPropertiesPanel)
88
- const EXPR_COLOR = "#60a5fa";
89
-
90
- /** True if any animatedProp on this track is driven by code */
91
- function hasExpressions(track: AnimationTrack): boolean {
92
- return !!track.animatedProps?.some((p) => p.programmatic || !!p.codeSnippet);
93
- }
94
-
95
- // Per-easing-type color tokens
96
- const EASING_COLORS: Record<
97
- EasingKey,
98
- { bg: string; border: string; text: string; activeBorder: string }
99
- > = {
100
- linear: {
101
- bg: "rgba(148,163,184,0.15)",
102
- border: "rgba(148,163,184,0.35)",
103
- activeBorder: "#94a3b8",
104
- text: "#94a3b8",
105
- },
106
- "ease-in": {
107
- bg: "rgba(59,130,246,0.15)",
108
- border: "rgba(59,130,246,0.35)",
109
- activeBorder: "#3b82f6",
110
- text: "#3b82f6",
111
- },
112
- "ease-out": {
113
- bg: "rgba(59,130,246,0.15)",
114
- border: "rgba(59,130,246,0.35)",
115
- activeBorder: "#3b82f6",
116
- text: "#3b82f6",
117
- },
118
- "ease-in-out": {
119
- bg: "rgba(37,99,235,0.15)",
120
- border: "rgba(37,99,235,0.35)",
121
- activeBorder: "#2563eb",
122
- text: "#2563eb",
123
- },
124
- spring: {
125
- bg: "rgba(251,191,36,0.15)",
126
- border: "rgba(251,191,36,0.35)",
127
- activeBorder: "#fbbf24",
128
- text: "#fbbf24",
129
- },
130
- // Power eases - blue tones
131
- "power1.in": {
132
- bg: "rgba(96,165,250,0.15)",
133
- border: "rgba(96,165,250,0.35)",
134
- activeBorder: "#60a5fa",
135
- text: "#60a5fa",
136
- },
137
- "power1.out": {
138
- bg: "rgba(59,130,246,0.15)",
139
- border: "rgba(59,130,246,0.35)",
140
- activeBorder: "#3b82f6",
141
- text: "#3b82f6",
142
- },
143
- "power1.inOut": {
144
- bg: "rgba(37,99,235,0.15)",
145
- border: "rgba(37,99,235,0.35)",
146
- activeBorder: "#2563eb",
147
- text: "#2563eb",
148
- },
149
- "power2.in": {
150
- bg: "rgba(96,165,250,0.15)",
151
- border: "rgba(96,165,250,0.35)",
152
- activeBorder: "#60a5fa",
153
- text: "#60a5fa",
154
- },
155
- "power2.out": {
156
- bg: "rgba(59,130,246,0.15)",
157
- border: "rgba(59,130,246,0.35)",
158
- activeBorder: "#3b82f6",
159
- text: "#3b82f6",
160
- },
161
- "power2.inOut": {
162
- bg: "rgba(37,99,235,0.15)",
163
- border: "rgba(37,99,235,0.35)",
164
- activeBorder: "#2563eb",
165
- text: "#2563eb",
166
- },
167
- "power3.in": {
168
- bg: "rgba(96,165,250,0.15)",
169
- border: "rgba(96,165,250,0.35)",
170
- activeBorder: "#60a5fa",
171
- text: "#60a5fa",
172
- },
173
- "power3.out": {
174
- bg: "rgba(59,130,246,0.15)",
175
- border: "rgba(59,130,246,0.35)",
176
- activeBorder: "#3b82f6",
177
- text: "#3b82f6",
178
- },
179
- "power3.inOut": {
180
- bg: "rgba(37,99,235,0.15)",
181
- border: "rgba(37,99,235,0.35)",
182
- activeBorder: "#2563eb",
183
- text: "#2563eb",
184
- },
185
- "power4.in": {
186
- bg: "rgba(96,165,250,0.15)",
187
- border: "rgba(96,165,250,0.35)",
188
- activeBorder: "#60a5fa",
189
- text: "#60a5fa",
190
- },
191
- "power4.out": {
192
- bg: "rgba(59,130,246,0.15)",
193
- border: "rgba(59,130,246,0.35)",
194
- activeBorder: "#3b82f6",
195
- text: "#3b82f6",
196
- },
197
- "power4.inOut": {
198
- bg: "rgba(37,99,235,0.15)",
199
- border: "rgba(37,99,235,0.35)",
200
- activeBorder: "#2563eb",
201
- text: "#2563eb",
202
- },
203
- // Back - blue tones
204
- "back.in": {
205
- bg: "rgba(125,211,252,0.15)",
206
- border: "rgba(125,211,252,0.35)",
207
- activeBorder: "#7DD3FC",
208
- text: "#7DD3FC",
209
- },
210
- "back.out": {
211
- bg: "rgba(0,181,255,0.15)",
212
- border: "rgba(0,181,255,0.35)",
213
- activeBorder: "#00B5FF",
214
- text: "#00B5FF",
215
- },
216
- "back.inOut": {
217
- bg: "rgba(2,132,199,0.15)",
218
- border: "rgba(2,132,199,0.35)",
219
- activeBorder: "#0284C7",
220
- text: "#0284C7",
221
- },
222
- // Bounce - green tones
223
- "bounce.in": {
224
- bg: "rgba(52,211,153,0.15)",
225
- border: "rgba(52,211,153,0.35)",
226
- activeBorder: "#34d399",
227
- text: "#34d399",
228
- },
229
- "bounce.out": {
230
- bg: "rgba(16,185,129,0.15)",
231
- border: "rgba(16,185,129,0.35)",
232
- activeBorder: "#10b981",
233
- text: "#10b981",
234
- },
235
- "bounce.inOut": {
236
- bg: "rgba(5,150,105,0.15)",
237
- border: "rgba(5,150,105,0.35)",
238
- activeBorder: "#059669",
239
- text: "#059669",
240
- },
241
- // Circ - cyan tones
242
- "circ.in": {
243
- bg: "rgba(34,211,238,0.15)",
244
- border: "rgba(34,211,238,0.35)",
245
- activeBorder: "#22d3ee",
246
- text: "#22d3ee",
247
- },
248
- "circ.out": {
249
- bg: "rgba(6,182,212,0.15)",
250
- border: "rgba(6,182,212,0.35)",
251
- activeBorder: "#06b6d4",
252
- text: "#06b6d4",
253
- },
254
- "circ.inOut": {
255
- bg: "rgba(8,145,178,0.15)",
256
- border: "rgba(8,145,178,0.35)",
257
- activeBorder: "#0891b2",
258
- text: "#0891b2",
259
- },
260
- // Elastic - pink tones
261
- "elastic.in": {
262
- bg: "rgba(244,114,182,0.15)",
263
- border: "rgba(244,114,182,0.35)",
264
- activeBorder: "#f472b6",
265
- text: "#f472b6",
266
- },
267
- "elastic.out": {
268
- bg: "rgba(236,72,153,0.15)",
269
- border: "rgba(236,72,153,0.35)",
270
- activeBorder: "#ec4899",
271
- text: "#ec4899",
272
- },
273
- "elastic.inOut": {
274
- bg: "rgba(219,39,119,0.15)",
275
- border: "rgba(219,39,119,0.35)",
276
- activeBorder: "#db2777",
277
- text: "#db2777",
278
- },
279
- // Expo - orange tones
280
- "expo.in": {
281
- bg: "rgba(251,146,60,0.15)",
282
- border: "rgba(251,146,60,0.35)",
283
- activeBorder: "#fb923c",
284
- text: "#fb923c",
285
- },
286
- "expo.out": {
287
- bg: "rgba(249,115,22,0.15)",
288
- border: "rgba(249,115,22,0.35)",
289
- activeBorder: "#f97316",
290
- text: "#f97316",
291
- },
292
- "expo.inOut": {
293
- bg: "rgba(234,88,12,0.15)",
294
- border: "rgba(234,88,12,0.35)",
295
- activeBorder: "#ea580c",
296
- text: "#ea580c",
297
- },
298
- // Sine - teal tones
299
- "sine.in": {
300
- bg: "rgba(94,234,212,0.15)",
301
- border: "rgba(94,234,212,0.35)",
302
- activeBorder: "#5eead4",
303
- text: "#5eead4",
304
- },
305
- "sine.out": {
306
- bg: "rgba(45,212,191,0.15)",
307
- border: "rgba(45,212,191,0.35)",
308
- activeBorder: "#2dd4bf",
309
- text: "#2dd4bf",
310
- },
311
- "sine.inOut": {
312
- bg: "rgba(20,184,166,0.15)",
313
- border: "rgba(20,184,166,0.35)",
314
- activeBorder: "#14b8a6",
315
- text: "#14b8a6",
316
- },
317
- };
318
-
319
- const DEFAULT_EASING_COLOR = EASING_COLORS.linear;
320
-
321
- // IconCamera track styling (blue accent)
322
- const CAMERA_COLOR = "#60a5fa";
323
-
324
- // Cursor track styling (blue accent)
325
- const CURSOR_COLOR = "#00B5FF";
326
-
327
- // ─── RangeBar ────────────────────────────────────────────────────────────────
328
- // AE/C4D-style navigator strip at the bottom of the bar area.
329
- // Left ▶ and right ◀ triangle handles define the visible window.
330
- // Drag the center region to pan. Double-click to reset to full view.
331
-
332
- interface RangeBarProps {
333
- viewStart: number;
334
- viewEnd: number;
335
- durationInFrames: number;
336
- onViewChange: (start: number, end: number) => void;
337
- }
338
-
339
- function RangeBar({
340
- viewStart,
341
- viewEnd,
342
- durationInFrames,
343
- onViewChange,
344
- }: RangeBarProps) {
345
- const containerRef = useRef<HTMLDivElement>(null);
346
- const MIN_WINDOW = Math.max(1, Math.round(durationInFrames * 0.05));
347
-
348
- const startPct = (viewStart / durationInFrames) * 100;
349
- const endPct = (viewEnd / durationInFrames) * 100;
350
-
351
- const HANDLE_W = 10;
352
-
353
- const startRangeDrag = (
354
- e: React.MouseEvent,
355
- mode: "start" | "end" | "pan",
356
- ) => {
357
- e.preventDefault();
358
- e.stopPropagation();
359
-
360
- const container = containerRef.current;
361
- if (!container) return;
362
-
363
- const rect = container.getBoundingClientRect();
364
- const startX = e.clientX;
365
- const initStart = viewStart;
366
- const initEnd = viewEnd;
367
-
368
- const onMouseMove = (ev: MouseEvent) => {
369
- const delta = ev.clientX - startX;
370
- const frameDelta = Math.round((delta / rect.width) * durationInFrames);
371
-
372
- let newStart = initStart;
373
- let newEnd = initEnd;
374
-
375
- if (mode === "start") {
376
- newStart = Math.max(
377
- 0,
378
- Math.min(initEnd - MIN_WINDOW, initStart + frameDelta),
379
- );
380
- } else if (mode === "end") {
381
- newEnd = Math.max(
382
- initStart + MIN_WINDOW,
383
- Math.min(durationInFrames, initEnd + frameDelta),
384
- );
385
- } else {
386
- const dur = initEnd - initStart;
387
- newStart = Math.max(
388
- 0,
389
- Math.min(durationInFrames - dur, initStart + frameDelta),
390
- );
391
- newEnd = newStart + dur;
392
- }
393
-
394
- onViewChange(newStart, newEnd);
395
- };
396
-
397
- const onMouseUp = () => {
398
- window.removeEventListener("mousemove", onMouseMove);
399
- window.removeEventListener("mouseup", onMouseUp);
400
- };
401
-
402
- window.addEventListener("mousemove", onMouseMove);
403
- window.addEventListener("mouseup", onMouseUp);
404
- };
405
-
406
- const isZoomed = viewStart > 0 || viewEnd < durationInFrames; // i18n-ignore scanner false positive
407
- const HANDLE_COLOR = isZoomed
408
- ? "rgba(0,181,255,0.85)"
409
- : "rgba(148,163,184,0.65)";
410
- const RANGE_COLOR = isZoomed
411
- ? "rgba(0,181,255,0.15)"
412
- : "rgba(148,163,184,0.12)";
413
- const BG_COLOR = "rgba(0,0,0,0.20)";
414
-
415
- return (
416
- <div
417
- ref={containerRef}
418
- className="w-full h-full select-none relative"
419
- onDoubleClick={(e) => {
420
- e.stopPropagation();
421
- onViewChange(0, durationInFrames);
422
- }}
423
- >
424
- {/* Background track */}
425
- <div
426
- className="absolute inset-0 rounded-sm"
427
- style={{ backgroundColor: BG_COLOR }}
428
- />
429
-
430
- {/* Active range highlight */}
431
- <div
432
- className="absolute top-0 bottom-0"
433
- style={{
434
- left: `${startPct}%`,
435
- width: `${endPct - startPct}%`,
436
- backgroundColor: RANGE_COLOR,
437
- cursor: "grab",
438
- }}
439
- onMouseDown={(e) => startRangeDrag(e, "pan")}
440
- />
441
-
442
- {/* Left handle — ▶ (inward-pointing right) */}
443
- <div
444
- className="absolute top-0 bottom-0 flex items-center justify-center cursor-ew-resize z-10"
445
- style={{
446
- left: `${startPct}%`,
447
- width: HANDLE_W,
448
- transform: "translateX(-50%)",
449
- }}
450
- onMouseDown={(e) => startRangeDrag(e, "start")}
451
- >
452
- <div
453
- style={{
454
- width: 0,
455
- height: 0,
456
- borderTop: "4px solid transparent",
457
- borderBottom: "4px solid transparent",
458
- borderLeft: `6px solid ${HANDLE_COLOR}`,
459
- }}
460
- />
461
- </div>
462
-
463
- {/* Right handle — ◀ (inward-pointing left) */}
464
- <div
465
- className="absolute top-0 bottom-0 flex items-center justify-center cursor-ew-resize z-10"
466
- style={{
467
- left: `${endPct}%`,
468
- width: HANDLE_W,
469
- transform: "translateX(-50%)",
470
- }}
471
- onMouseDown={(e) => startRangeDrag(e, "end")}
472
- >
473
- <div
474
- style={{
475
- width: 0,
476
- height: 0,
477
- borderTop: "4px solid transparent",
478
- borderBottom: "4px solid transparent",
479
- borderRight: `6px solid ${HANDLE_COLOR}`,
480
- }}
481
- />
482
- </div>
483
-
484
- {/* Zoom indicator — shows when not at full view */}
485
- {isZoomed && (
486
- <div className="absolute inset-0 flex items-center justify-center pointer-events-none">
487
- <span
488
- className="text-[7px] font-mono tracking-widest select-none uppercase"
489
- style={{ color: "rgba(0,181,255,0.4)" }}
490
- >
491
- zoom
492
- </span>
493
- </div>
494
- )}
495
- </div>
496
- );
497
- }
498
-
499
- // ─── Timeline ────────────────────────────────────────────────────────────────
500
-
501
- export function Timeline({
502
- currentFrame,
503
- durationInFrames,
504
- fps,
505
- onSeek,
506
- tracks = [],
507
- selectedTrackId = null,
508
- onSelectTrack = () => {},
509
- onUpdateTrack = () => {},
510
- onDeleteTrack,
511
- viewStart,
512
- viewEnd,
513
- onViewChange: handleViewChange,
514
- onCameraKeyframeClick,
515
- isPlaying = false,
516
- }: TimelineProps) {
517
- const t = useT();
518
- const isMobile = useIsMobile();
519
- const LABEL_WIDTH = isMobile ? LABEL_WIDTH_MOBILE : LABEL_WIDTH_DESKTOP;
520
- const barAreaRef = useRef<HTMLDivElement>(null);
521
- const isDraggingRef = useRef(false);
522
- const keyframeDragRef = useRef<KeyframeDragState | null>(null);
523
- const selectionBoxJustCompletedRef = useRef(false);
524
- const isScrubbingRef = useRef(false);
525
-
526
- // Store original state for keyframe conflict undo (replaces window-based storage)
527
- const conflictUndoStateRef = useRef<{
528
- trackId: string;
529
- originalAnimatedProps: any;
530
- } | null>(null);
531
-
532
- // Local selection state per PRD
533
- const [selectedKeyframes, setSelectedKeyframes] = useState<
534
- Map<string, Set<number>>
535
- >(new Map());
536
- const [keyframeConflict, setKeyframeConflict] =
537
- useState<KeyframeConflict | null>(null);
538
- const [selectionBox, setSelectionBox] = useState<{
539
- trackId: string;
540
- startX: number;
541
- startY: number;
542
- currentX: number;
543
- currentY: number;
544
- } | null>(null);
545
- const [contextMenu, setContextMenu] = useState<{
546
- trackId: string;
547
- x: number;
548
- y: number;
549
- } | null>(null);
550
-
551
- const viewDuration = Math.max(1, viewEnd - viewStart);
552
- const viewSeconds = viewDuration / fps;
553
- const viewStartSeconds = viewStart / fps;
554
- const viewEndSeconds = viewEnd / fps;
555
-
556
- /** Convert a frame number to a % position within the current view window */
557
- const toViewPct = useCallback(
558
- (frame: number) => frameToViewPct(frame, viewStart, viewDuration),
559
- [viewStart, viewDuration],
560
- );
561
-
562
- // Helper to check if a keyframe is selected
563
- const isKeyframeSelected = useCallback(
564
- (trackId: string, frame: number): boolean => {
565
- const trackSelection = selectedKeyframes.get(trackId);
566
- return trackSelection ? trackSelection.has(frame) : false;
567
- },
568
- [selectedKeyframes],
569
- );
570
-
571
- // ─── Ruler tick logic (adapts to zoom level) ────────────────────────────
572
- const getTickInterval = (seconds: number) => {
573
- if (seconds <= 1) return 0.1;
574
- if (seconds <= 3) return 0.5;
575
- if (seconds <= 10) return 1;
576
- if (seconds <= 30) return 2;
577
- if (seconds <= 60) return 5;
578
- return 10;
579
- };
580
-
581
- const tickInterval = getTickInterval(viewSeconds);
582
- const firstTick = Math.ceil(viewStartSeconds / tickInterval) * tickInterval;
583
- const ticks: number[] = [];
584
- for (
585
- let t = firstTick;
586
- t <= viewEndSeconds + 0.0001;
587
- t = parseFloat((t + tickInterval).toFixed(6))
588
- ) {
589
- ticks.push(t);
590
- }
591
-
592
- /** Formats a frame count as seconds with 1 decimal place — always consistent */
593
- const fmtSec = (frames: number) => `${(frames / fps).toFixed(1)}s`;
594
-
595
- const formatTickLabel = (seconds: number) => {
596
- const m = Math.floor(seconds / 60);
597
- const s = Math.floor(seconds % 60);
598
- const frac = seconds % 1;
599
- if (m > 0) return `${m}:${s.toString().padStart(2, "0")}`;
600
- if (frac > 0) return `${s}.${Math.round(frac * 10)}s`;
601
- return `${s}s`;
602
- };
603
-
604
- // ─── Bar-area scrubbing with global mouse tracking ──────────────────────────
605
- const handleBarAreaMouseDown = useCallback(
606
- (e: React.MouseEvent<HTMLDivElement>) => {
607
- // Check if clicking on a track bar - handle track selection
608
- const barEl = (e.target as Element).closest(".track-bar");
609
- if (barEl) {
610
- if (isDraggingRef.current) return;
611
- const trackId = barEl.getAttribute("data-track-id");
612
- if (!trackId) return;
613
- onSelectTrack(trackId === selectedTrackId ? null : trackId);
614
- return;
615
- }
616
-
617
- // Don't start scrubbing when clicking the range bar strip
618
- if ((e.target as Element).closest(".range-bar-zone")) return;
619
-
620
- // Don't start scrubbing if already dragging something else
621
- if (isDraggingRef.current) return;
622
-
623
- // Start scrubbing
624
- const el = barAreaRef.current;
625
- if (!el) return;
626
-
627
- e.preventDefault();
628
- isScrubbingRef.current = true;
629
-
630
- const rect = el.getBoundingClientRect();
631
-
632
- // Seek immediately on mousedown
633
- const frame = clientXToFrame(e.clientX, rect, viewStart, viewDuration);
634
- onSeek(frame);
635
-
636
- const onMouseMove = (ev: MouseEvent) => {
637
- if (!isScrubbingRef.current) return;
638
-
639
- // Update seek position based on mouse X, ignoring Y movement
640
- const el = barAreaRef.current;
641
- if (!el) return;
642
- const rect = el.getBoundingClientRect();
643
- const frame = clientXToFrame(ev.clientX, rect, viewStart, viewDuration);
644
- onSeek(frame);
645
- };
646
-
647
- const onMouseUp = () => {
648
- isScrubbingRef.current = false;
649
- window.removeEventListener("mousemove", onMouseMove);
650
- window.removeEventListener("mouseup", onMouseUp);
651
- };
652
-
653
- window.addEventListener("mousemove", onMouseMove);
654
- window.addEventListener("mouseup", onMouseUp);
655
- },
656
- [viewStart, viewDuration, onSeek, onSelectTrack, selectedTrackId],
657
- );
658
-
659
- // ─── Track bar drag / resize ─────────────────────────────────────────────
660
- const startDrag = useCallback(
661
- (e: React.MouseEvent, track: AnimationTrack, mode: DragMode) => {
662
- e.preventDefault();
663
- const el = barAreaRef.current;
664
- if (!el) return;
665
-
666
- const rect = el.getBoundingClientRect();
667
- isDraggingRef.current = false;
668
-
669
- const dragState: DragState = {
670
- trackId: track.id,
671
- mode,
672
- startX: e.clientX,
673
- initialStart: track.startFrame,
674
- initialEnd: track.endFrame,
675
- barAreaWidth: rect.width,
676
- };
677
-
678
- const onMouseMove = (ev: MouseEvent) => {
679
- const delta = ev.clientX - dragState.startX;
680
- if (Math.abs(delta) > 2) isDraggingRef.current = true;
681
-
682
- // Map pixel delta to frame delta relative to the zoomed view
683
- const frameDelta = pxDeltaToFrameDelta(
684
- delta,
685
- dragState.barAreaWidth,
686
- viewDuration,
687
- );
688
-
689
- let newStart = dragState.initialStart;
690
- let newEnd = dragState.initialEnd;
691
-
692
- if (dragState.mode === "move") {
693
- const dur = dragState.initialEnd - dragState.initialStart;
694
- newStart = Math.max(
695
- 0,
696
- Math.min(
697
- durationInFrames - dur,
698
- dragState.initialStart + frameDelta,
699
- ),
700
- );
701
- newEnd = newStart + dur;
702
- } else if (dragState.mode === "resize-start") {
703
- newStart = Math.max(
704
- 0,
705
- Math.min(
706
- dragState.initialEnd - 1,
707
- dragState.initialStart + frameDelta,
708
- ),
709
- );
710
- } else {
711
- newEnd = Math.max(
712
- dragState.initialStart + 1,
713
- Math.min(durationInFrames, dragState.initialEnd + frameDelta),
714
- );
715
- }
716
-
717
- onUpdateTrack(dragState.trackId, {
718
- startFrame: newStart,
719
- endFrame: newEnd,
720
- });
721
- };
722
-
723
- const onMouseUp = () => {
724
- window.removeEventListener("mousemove", onMouseMove);
725
- window.removeEventListener("mouseup", onMouseUp);
726
- setTimeout(() => {
727
- isDraggingRef.current = false;
728
- }, 0);
729
- };
730
-
731
- window.addEventListener("mousemove", onMouseMove);
732
- window.addEventListener("mouseup", onMouseUp);
733
- },
734
- [durationInFrames, viewDuration, onUpdateTrack],
735
- );
736
-
737
- // Playhead is only shown when the current frame falls within the view window
738
- const playheadVisible = currentFrame >= viewStart && currentFrame <= viewEnd;
739
- const playheadPct = toViewPct(currentFrame);
740
-
741
- // Delete key handler for removing selected keyframes
742
- useEffect(() => {
743
- const handleKeyDown = (e: KeyboardEvent) => {
744
- // Don't trigger if user is typing in an input/textarea
745
- const target = e.target as HTMLElement;
746
- if (
747
- target.tagName === "INPUT" ||
748
- target.tagName === "TEXTAREA" ||
749
- target.isContentEditable
750
- ) {
751
- return;
752
- }
753
-
754
- if (e.key === "Delete" || e.key === "Backspace") {
755
- // Check if any keyframes are selected
756
- if (selectedKeyframes.size === 0) return;
757
-
758
- e.preventDefault();
759
-
760
- // Remove all selected keyframes from their respective tracks
761
- selectedKeyframes.forEach((frames, trackId) => {
762
- if (frames.size === 0) return;
763
-
764
- const track = tracks.find((t) => t.id === trackId);
765
- if (!track || !track.animatedProps) return;
766
-
767
- // Remove all selected frames from all properties
768
- const updatedProps = track.animatedProps.map((prop) => {
769
- if (!prop.keyframes) return prop;
770
-
771
- const newKeyframes = prop.keyframes.filter(
772
- (kf) => !frames.has(kf.frame),
773
- );
774
-
775
- return {
776
- ...prop,
777
- keyframes: newKeyframes.length > 0 ? newKeyframes : undefined,
778
- };
779
- });
780
-
781
- onUpdateTrack(trackId, { animatedProps: updatedProps });
782
- });
783
-
784
- // Clear selection after deleting
785
- setSelectedKeyframes(new Map());
786
- }
787
- };
788
-
789
- window.addEventListener("keydown", handleKeyDown);
790
- return () => window.removeEventListener("keydown", handleKeyDown);
791
- }, [selectedKeyframes, tracks, onUpdateTrack]);
792
-
793
- // Close context menu on click outside
794
- useEffect(() => {
795
- if (!contextMenu) return;
796
-
797
- const handleClick = () => setContextMenu(null);
798
- window.addEventListener("click", handleClick);
799
- return () => window.removeEventListener("click", handleClick);
800
- }, [contextMenu]);
801
-
802
- // Clear track handler - removes all keyframes but keeps the track
803
- const handleClearTrack = useCallback(
804
- (trackId: string) => {
805
- const track = tracks.find((t) => t.id === trackId);
806
- if (!track) return;
807
-
808
- // Clear all keyframes from all properties
809
- const clearedProps = track.animatedProps?.map((prop) => ({
810
- ...prop,
811
- keyframes: [],
812
- }));
813
-
814
- onUpdateTrack(trackId, { animatedProps: clearedProps });
815
- setContextMenu(null);
816
- },
817
- [tracks, onUpdateTrack],
818
- );
819
-
820
- // Delete track handler
821
- const handleDeleteTrack = useCallback(
822
- (trackId: string) => {
823
- if (onDeleteTrack) {
824
- onDeleteTrack(trackId);
825
- }
826
- setContextMenu(null);
827
- },
828
- [onDeleteTrack],
829
- );
830
-
831
- return (
832
- <div className="w-full bg-card/60 border border-border border-t-0 rounded-b-xl overflow-hidden select-none">
833
- <div className="flex h-full overflow-hidden">
834
- {/* ── Label column ──────────────────────────────────────────────── */}
835
- <div
836
- className="flex-shrink-0 flex flex-col border-r border-border/40 bg-card/30"
837
- style={{ width: LABEL_WIDTH }}
838
- >
839
- {/* Ruler header placeholder */}
840
- <div
841
- className="border-b border-border flex items-center justify-between px-2.5"
842
- style={{ height: RULER_HEIGHT }}
843
- >
844
- <span className="text-[9px] text-muted-foreground/25 font-mono uppercase tracking-widest">
845
- Track
846
- </span>
847
- </div>
848
-
849
- {/* Range bar header placeholder */}
850
- <div
851
- className="border-b border-border flex items-center px-2.5 flex-shrink-0"
852
- style={{ height: RANGE_BAR_HEIGHT + 4 }}
853
- >
854
- <span className="text-[9px] text-muted-foreground/25 font-mono uppercase tracking-widest">
855
- View
856
- </span>
857
- </div>
858
-
859
- {/* Scrollable track labels */}
860
- <div className="flex-1 overflow-y-auto scrollbar-thin">
861
- {/* Animation track labels */}
862
- {tracks.map((track) => {
863
- const isSelected = selectedTrackId === track.id;
864
- const colors =
865
- EASING_COLORS[track.easing] ?? DEFAULT_EASING_COLOR;
866
- const isExpr = hasExpressions(track);
867
- const isCamera = track.id === "camera";
868
- const isCursor = track.id === "cursor";
869
- const isKeyframeTrack = track.startFrame === track.endFrame;
870
-
871
- return (
872
- <Tooltip key={track.id}>
873
- <TooltipTrigger asChild>
874
- <div
875
- className="flex items-center gap-1.5 px-2.5 cursor-default"
876
- style={{ height: TRACK_HEIGHT }}
877
- onContextMenu={(e) => {
878
- // Only show context menu for camera and cursor tracks
879
- if (!isCamera && !isCursor) return;
880
- e.preventDefault();
881
- setContextMenu({
882
- trackId: track.id,
883
- x: e.clientX,
884
- y: e.clientY,
885
- });
886
- }}
887
- >
888
- {isCamera ? (
889
- <IconCamera
890
- className="flex-shrink-0"
891
- size={12}
892
- style={{
893
- color: isSelected
894
- ? CAMERA_COLOR
895
- : `${CAMERA_COLOR}99`,
896
- }}
897
- />
898
- ) : isCursor ? (
899
- <IconMouse
900
- className="flex-shrink-0"
901
- size={12}
902
- style={{
903
- color: isSelected
904
- ? CURSOR_COLOR
905
- : `${CURSOR_COLOR}99`,
906
- }}
907
- />
908
- ) : isExpr ? (
909
- <span
910
- className="text-[7px] font-mono font-bold px-1 py-px rounded flex-shrink-0 uppercase tracking-wider leading-none"
911
- style={{
912
- backgroundColor: `${EXPR_COLOR}${isSelected ? "30" : "18"}`,
913
- color: isSelected ? EXPR_COLOR : `${EXPR_COLOR}99`,
914
- }}
915
- >
916
- fx
917
- </span>
918
- ) : isKeyframeTrack ? (
919
- <div
920
- className="w-2 h-2 rotate-45 flex-shrink-0"
921
- style={{
922
- backgroundColor: colors.text,
923
- opacity: isSelected ? 1 : 0.5,
924
- }}
925
- />
926
- ) : (
927
- <div
928
- className="w-1.5 h-1.5 rounded-full flex-shrink-0"
929
- style={{
930
- backgroundColor: colors.text,
931
- opacity: isSelected ? 1 : 0.5,
932
- }}
933
- />
934
- )}
935
- <span
936
- className={cn(
937
- "text-[10px] min-w-0",
938
- isSelected ? "" : "text-muted-foreground/55",
939
- )}
940
- style={{
941
- color: isSelected
942
- ? isCamera
943
- ? CAMERA_COLOR
944
- : isCursor
945
- ? CURSOR_COLOR
946
- : isExpr
947
- ? EXPR_COLOR
948
- : colors.text
949
- : undefined,
950
- // Allow the label to shrink/wrap rather than hard-truncate
951
- overflow: "hidden",
952
- textOverflow: "ellipsis",
953
- whiteSpace: "nowrap",
954
- }}
955
- >
956
- {track.label}
957
- </span>
958
- </div>
959
- </TooltipTrigger>
960
- <TooltipContent>{track.label}</TooltipContent>
961
- </Tooltip>
962
- );
963
- })}
964
- </div>
965
- </div>
966
-
967
- {/* ── Bar area (ruler + range bar + tracks + playhead) ─────────── */}
968
- <div
969
- ref={barAreaRef}
970
- className="flex-1 flex flex-col relative cursor-pointer overflow-hidden"
971
- onMouseDown={handleBarAreaMouseDown}
972
- >
973
- {/* Time ruler — shows ticks within the current view window */}
974
- <div
975
- className="relative w-full border-b border-border flex-shrink-0"
976
- style={{ height: RULER_HEIGHT }}
977
- >
978
- {ticks.map((sec) => {
979
- const pct = ((sec - viewStartSeconds) / viewSeconds) * 100;
980
- return (
981
- <div
982
- key={sec}
983
- className="absolute top-0 flex flex-col items-center"
984
- style={{ left: `${pct}%` }}
985
- >
986
- <div className="w-px h-2.5 bg-border" />
987
- <span className="text-[9px] text-muted-foreground/50 font-mono mt-0.5 -translate-x-1/2">
988
- {formatTickLabel(sec)}
989
- </span>
990
- </div>
991
- );
992
- })}
993
- {ticks.slice(0, -1).map((sec) => {
994
- const subTick = parseFloat((sec + tickInterval / 2).toFixed(6));
995
- if (subTick >= viewEndSeconds) return null;
996
- const pct = ((subTick - viewStartSeconds) / viewSeconds) * 100;
997
- return (
998
- <div
999
- key={`sub-${sec}`}
1000
- className="absolute top-0 w-px h-1.5 bg-border/40"
1001
- style={{ left: `${pct}%` }}
1002
- />
1003
- );
1004
- })}
1005
- </div>
1006
-
1007
- {/* ── Range navigator bar ──────────────────────────────────── */}
1008
- <div
1009
- className="range-bar-zone relative flex-shrink-0"
1010
- style={{ height: RANGE_BAR_HEIGHT + 4 }}
1011
- >
1012
- <RangeBar
1013
- viewStart={viewStart}
1014
- viewEnd={viewEnd}
1015
- durationInFrames={durationInFrames}
1016
- onViewChange={handleViewChange}
1017
- />
1018
- </div>
1019
-
1020
- {/* Scrollable tracks area */}
1021
- <div className="flex-1 overflow-y-auto scrollbar-thin relative">
1022
- {/* ── Animation track rows ──────────────────────────────────── */}
1023
- {tracks.map((track) => {
1024
- const isSelected = selectedTrackId === track.id;
1025
- const colors =
1026
- EASING_COLORS[track.easing] ?? DEFAULT_EASING_COLOR;
1027
- const isExpr = hasExpressions(track);
1028
- const isCamera = track.id === "camera";
1029
- const isCursor = track.id === "cursor";
1030
- const leftPct = toViewPct(track.startFrame);
1031
- const widthPct = toViewPct(track.endFrame) - leftPct;
1032
-
1033
- // Collect all keyframe frames for camera/cursor tracks
1034
- const allKeyframeFrames = new Set<number>();
1035
- if (isCamera || isCursor) {
1036
- track.animatedProps?.forEach((prop) => {
1037
- prop.keyframes?.forEach((kf) =>
1038
- allKeyframeFrames.add(kf.frame),
1039
- );
1040
- });
1041
- }
1042
-
1043
- // Camera/Cursor track: only show keyframes, no track bar
1044
- if (isCamera || isCursor) {
1045
- const trackColor = isCamera ? CAMERA_COLOR : CURSOR_COLOR;
1046
-
1047
- return (
1048
- <div
1049
- key={track.id}
1050
- className="relative w-full"
1051
- style={{ height: TRACK_HEIGHT }}
1052
- onMouseDown={(e) => {
1053
- if (isPlaying) return;
1054
- // Only start box selection if clicking directly on track (not a keyframe)
1055
- if (e.target !== e.currentTarget) return;
1056
-
1057
- e.preventDefault();
1058
- e.stopPropagation();
1059
-
1060
- const el = barAreaRef.current;
1061
- if (!el) return;
1062
- const rect = el.getBoundingClientRect();
1063
- const startX = e.clientX - rect.left;
1064
- const startY = e.clientY;
1065
-
1066
- let hasMoved = false;
1067
-
1068
- // Initialize selection box
1069
- setSelectionBox({
1070
- trackId: track.id,
1071
- startX,
1072
- startY,
1073
- currentX: startX,
1074
- currentY: startY,
1075
- });
1076
-
1077
- const handleMove = (ev: MouseEvent) => {
1078
- const currentX = ev.clientX - rect.left;
1079
- const currentY = ev.clientY;
1080
-
1081
- // Check if mouse has moved significantly (3px threshold)
1082
- const deltaX = Math.abs(currentX - startX);
1083
- const deltaY = Math.abs(currentY - startY);
1084
- if (deltaX > 3 || deltaY > 3) {
1085
- hasMoved = true;
1086
- }
1087
-
1088
- // Update selection box position
1089
- setSelectionBox((prev) =>
1090
- prev
1091
- ? {
1092
- ...prev,
1093
- currentX,
1094
- currentY,
1095
- }
1096
- : null,
1097
- );
1098
-
1099
- // Calculate which keyframes are in the box
1100
- const boxStartPct =
1101
- Math.min(startX / rect.width, currentX / rect.width) *
1102
- 100;
1103
- const boxEndPct =
1104
- Math.max(startX / rect.width, currentX / rect.width) *
1105
- 100;
1106
-
1107
- const framesInBox = new Set<number>();
1108
- allKeyframeFrames.forEach((f) => {
1109
- const framePct = toViewPct(f);
1110
- if (
1111
- framePct >= boxStartPct &&
1112
- framePct <= boxEndPct
1113
- ) {
1114
- framesInBox.add(f);
1115
- }
1116
- });
1117
-
1118
- // Update selection continuously during drag
1119
- setSelectedKeyframes((prev) => {
1120
- const next = new Map(prev);
1121
- next.set(track.id, framesInBox);
1122
- return next;
1123
- });
1124
- };
1125
-
1126
- const handleUp = () => {
1127
- window.removeEventListener("mousemove", handleMove);
1128
- window.removeEventListener("mouseup", handleUp);
1129
- setSelectionBox(null);
1130
-
1131
- // If we had a selection box drag, prevent the next onClick from clearing
1132
- if (hasMoved) {
1133
- selectionBoxJustCompletedRef.current = true;
1134
- // Reset after a short delay (after onClick would have fired)
1135
- setTimeout(() => {
1136
- selectionBoxJustCompletedRef.current = false;
1137
- }, 10);
1138
- }
1139
- };
1140
-
1141
- window.addEventListener("mousemove", handleMove);
1142
- window.addEventListener("mouseup", handleUp);
1143
- }}
1144
- onClick={(e) => {
1145
- // Clear selection when clicking empty area (unless box selection just completed)
1146
- if (
1147
- e.target === e.currentTarget &&
1148
- !selectionBoxJustCompletedRef.current
1149
- ) {
1150
- setSelectedKeyframes(new Map());
1151
- }
1152
- }}
1153
- >
1154
- <div className="absolute inset-x-0 bottom-0 h-px bg-border/20" />
1155
-
1156
- {/* Selection box rendering */}
1157
- {selectionBox &&
1158
- selectionBox.trackId === track.id &&
1159
- barAreaRef.current &&
1160
- (() => {
1161
- const rect = barAreaRef.current.getBoundingClientRect();
1162
- const left = Math.min(
1163
- selectionBox.startX,
1164
- selectionBox.currentX,
1165
- );
1166
- const width = Math.abs(
1167
- selectionBox.currentX - selectionBox.startX,
1168
- );
1169
-
1170
- return (
1171
- <div
1172
- className="absolute pointer-events-none"
1173
- style={{
1174
- left: `${(left / rect.width) * 100}%`,
1175
- width: `${(width / rect.width) * 100}%`,
1176
- top: 0,
1177
- bottom: 0,
1178
- backgroundColor: `${trackColor}1a`, // 10% opacity fill
1179
- border: `1px dashed ${trackColor}99`, // 60% opacity dashed border
1180
- borderRadius: 2,
1181
- }}
1182
- />
1183
- );
1184
- })()}
1185
-
1186
- {/* Keyframe markers only */}
1187
- {Array.from(allKeyframeFrames).map((frame) => {
1188
- const kfPct = toViewPct(frame);
1189
- if (kfPct < 0 || kfPct > 100) return null;
1190
- const isCurrentKeyframe = frame === currentFrame;
1191
-
1192
- // Determine cursor keyframe style based on which properties changed
1193
- let cursorStyle:
1194
- | "position"
1195
- | "type"
1196
- | "both"
1197
- | "clickStart"
1198
- | "clickEnd" = "position";
1199
- if (isCursor) {
1200
- const xProp = track.animatedProps?.find(
1201
- (p) => p.property === "x",
1202
- );
1203
- const yProp = track.animatedProps?.find(
1204
- (p) => p.property === "y",
1205
- );
1206
- const typeProp = track.animatedProps?.find(
1207
- (p) => p.property === "type",
1208
- );
1209
- const clickProp = track.animatedProps?.find(
1210
- (p) => p.property === "isClicking",
1211
- );
1212
-
1213
- const hasPositionKf =
1214
- xProp?.keyframes?.some((kf) => kf.frame === frame) ||
1215
- yProp?.keyframes?.some((kf) => kf.frame === frame);
1216
- const hasTypeKf = typeProp?.keyframes?.some(
1217
- (kf) => kf.frame === frame,
1218
- );
1219
- const clickKf = clickProp?.keyframes?.find(
1220
- (kf) => kf.frame === frame,
1221
- );
1222
-
1223
- // Click keyframes get their own distinct style
1224
- if (clickKf) {
1225
- cursorStyle =
1226
- clickKf.value === "1" ? "clickStart" : "clickEnd";
1227
- } else if (hasPositionKf && hasTypeKf) {
1228
- cursorStyle = "both";
1229
- } else if (hasTypeKf) {
1230
- cursorStyle = "type";
1231
- } else {
1232
- cursorStyle = "position";
1233
- }
1234
- }
1235
-
1236
- const keyframeColor = isCurrentKeyframe
1237
- ? "#ffffff"
1238
- : isCursor
1239
- ? CURSOR_COLOR
1240
- : CAMERA_COLOR;
1241
- const selected = isKeyframeSelected(track.id, frame);
1242
-
1243
- return (
1244
- <Tooltip key={frame}>
1245
- <TooltipTrigger asChild>
1246
- <div
1247
- data-keyframe="true"
1248
- className={cn(
1249
- "absolute top-1/2 -translate-y-1/2 group",
1250
- isPlaying
1251
- ? "cursor-not-allowed opacity-50"
1252
- : "cursor-grab active:cursor-grabbing",
1253
- )}
1254
- style={{
1255
- left: `${kfPct}%`,
1256
- // Extended clickable area with padding on both sides
1257
- transform: "translateY(-50%) translateX(-50%)",
1258
- width: "24px", // Wider clickable area (3× the 8px diamond)
1259
- height: "24px",
1260
- display: "flex",
1261
- alignItems: "center",
1262
- justifyContent: "center",
1263
- zIndex: selected ? 30 : 20, // Selected keyframes on top
1264
- }}
1265
- onMouseDown={(e) => {
1266
- if (isPlaying) return;
1267
- e.preventDefault();
1268
- e.stopPropagation();
1269
-
1270
- // Check if this keyframe is already in the selection
1271
- const isAlreadySelected = isKeyframeSelected(
1272
- track.id,
1273
- frame,
1274
- );
1275
-
1276
- // Determine which frames we'll be dragging
1277
- // If already selected, drag all selected frames; otherwise just this one
1278
- const framesToDrag = isAlreadySelected
1279
- ? selectedKeyframes.get(track.id) ||
1280
- new Set([frame])
1281
- : new Set([frame]);
1282
- const selectedFrames = Array.from(framesToDrag);
1283
-
1284
- // Update selection if needed (for visual feedback)
1285
- if (!isAlreadySelected) {
1286
- setSelectedKeyframes(
1287
- new Map([[track.id, new Set([frame])]]),
1288
- );
1289
- }
1290
-
1291
- // If track is camera or cursor, notify sidebar to open controls
1292
- if (isCamera && onCameraKeyframeClick) {
1293
- onCameraKeyframeClick("camera");
1294
- } else if (isCursor && onCameraKeyframeClick) {
1295
- onCameraKeyframeClick("cursor");
1296
- }
1297
-
1298
- const startX = e.clientX;
1299
- const startY = e.clientY;
1300
- let isDragging = false;
1301
-
1302
- const el = barAreaRef.current;
1303
- if (!el) return;
1304
- const rect = el.getBoundingClientRect();
1305
-
1306
- // Save original state for undo in case of conflict
1307
- conflictUndoStateRef.current = {
1308
- trackId: track.id,
1309
- originalAnimatedProps: track.animatedProps,
1310
- };
1311
-
1312
- // Store original positions of all selected keyframes
1313
- const originalFrames = new Map<
1314
- number,
1315
- number
1316
- >();
1317
- selectedFrames.forEach((f) =>
1318
- originalFrames.set(f, f),
1319
- );
1320
-
1321
- keyframeDragRef.current = {
1322
- originalFrame: frame,
1323
- currentFrame: frame,
1324
- startX,
1325
- barAreaWidth: rect.width,
1326
- movingKeys: selectedFrames.map(
1327
- (f) => `${track.id}:${f}`,
1328
- ),
1329
- originalFrames,
1330
- };
1331
-
1332
- const handleMove = (ev: MouseEvent) => {
1333
- if (!keyframeDragRef.current) return;
1334
-
1335
- // Check if moved significantly (3px threshold)
1336
- const dist = Math.sqrt(
1337
- Math.pow(ev.clientX - startX, 2) +
1338
- Math.pow(ev.clientY - startY, 2),
1339
- );
1340
- if (dist > 3) {
1341
- isDragging = true;
1342
- }
1343
-
1344
- // Calculate delta in pixels, convert to frames
1345
- const deltaX =
1346
- ev.clientX - keyframeDragRef.current.startX;
1347
- const frameChange = pxDeltaToFrameDelta(
1348
- deltaX,
1349
- keyframeDragRef.current.barAreaWidth,
1350
- viewDuration,
1351
- );
1352
-
1353
- // Calculate new positions for all selected keyframes
1354
- const newPositions = new Map<
1355
- number,
1356
- number
1357
- >();
1358
- let minNewFrame = Infinity;
1359
- let maxNewFrame = -Infinity;
1360
-
1361
- selectedFrames.forEach((origFrame) => {
1362
- const newFrame = clampFrame(
1363
- origFrame + frameChange,
1364
- durationInFrames,
1365
- );
1366
- newPositions.set(origFrame, newFrame);
1367
- minNewFrame = Math.min(
1368
- minNewFrame,
1369
- newFrame,
1370
- );
1371
- maxNewFrame = Math.max(
1372
- maxNewFrame,
1373
- newFrame,
1374
- );
1375
- });
1376
-
1377
- // Move ALL selected keyframes
1378
- if (track.animatedProps) {
1379
- const updatedProps =
1380
- track.animatedProps.map((prop) => {
1381
- if (!prop.keyframes) return prop;
1382
-
1383
- const updatedKeyframes =
1384
- prop.keyframes.map((kf) => {
1385
- const newFrame = newPositions.get(
1386
- kf.frame,
1387
- );
1388
- return newFrame !== undefined
1389
- ? { ...kf, frame: newFrame }
1390
- : kf;
1391
- });
1392
-
1393
- return {
1394
- ...prop,
1395
- keyframes: updatedKeyframes,
1396
- };
1397
- });
1398
-
1399
- onUpdateTrack(track.id, {
1400
- animatedProps: updatedProps,
1401
- });
1402
- }
1403
-
1404
- // Update current frame for the dragged keyframe
1405
- const newFrame = newPositions.get(
1406
- keyframeDragRef.current.originalFrame,
1407
- );
1408
- if (newFrame !== undefined) {
1409
- keyframeDragRef.current.currentFrame =
1410
- newFrame;
1411
- // Seek playhead to follow the dragged keyframe
1412
- onSeek(newFrame);
1413
- }
1414
- };
1415
-
1416
- const handleUp = () => {
1417
- window.removeEventListener(
1418
- "mousemove",
1419
- handleMove,
1420
- );
1421
- window.removeEventListener(
1422
- "mouseup",
1423
- handleUp,
1424
- );
1425
-
1426
- if (!keyframeDragRef.current) return;
1427
-
1428
- const finalFrame =
1429
- keyframeDragRef.current.currentFrame;
1430
-
1431
- if (!isDragging) {
1432
- // It was just a click - already handled selection above
1433
- onSeek(frame);
1434
- if (isCamera && onCameraKeyframeClick) {
1435
- onCameraKeyframeClick("camera");
1436
- } else if (
1437
- isCursor &&
1438
- onCameraKeyframeClick
1439
- ) {
1440
- onCameraKeyframeClick("cursor");
1441
- }
1442
- } else {
1443
- // Calculate delta for all keyframes
1444
- const frameChange =
1445
- finalFrame -
1446
- keyframeDragRef.current.originalFrame;
1447
-
1448
- // Build set of all new positions
1449
- const newFrameSet = new Set<number>();
1450
- selectedFrames.forEach((origFrame) => {
1451
- const newPos = clampFrame(
1452
- origFrame + frameChange,
1453
- durationInFrames,
1454
- );
1455
- newFrameSet.add(newPos);
1456
- });
1457
-
1458
- // Check for conflicts: any of the moved keyframes overlap with non-selected keyframes
1459
- if (track.animatedProps) {
1460
- for (const prop of track.animatedProps) {
1461
- if (prop.keyframes) {
1462
- // Get all keyframe positions that are NOT being moved
1463
- const staticFrames = prop.keyframes
1464
- .filter(
1465
- (kf) =>
1466
- !selectedFrames.includes(
1467
- kf.frame,
1468
- ),
1469
- )
1470
- .map((kf) => kf.frame);
1471
-
1472
- // Check if any new position conflicts with a static keyframe
1473
- for (const newPos of newFrameSet) {
1474
- if (staticFrames.includes(newPos)) {
1475
- // Conflict detected - show modal
1476
- setKeyframeConflict({
1477
- trackId: track.id,
1478
- newFrame: newPos,
1479
- originalFrame:
1480
- keyframeDragRef.current
1481
- .originalFrame,
1482
- conflictingFrame: newPos,
1483
- });
1484
- keyframeDragRef.current = null;
1485
- return; // Don't finalize - let modal handle it
1486
- }
1487
- }
1488
- }
1489
- }
1490
- }
1491
-
1492
- // Update selection to reflect new positions
1493
- setSelectedKeyframes((prev) => {
1494
- const next = new Map(prev);
1495
- next.set(track.id, newFrameSet);
1496
- return next;
1497
- });
1498
-
1499
- // No conflict - success!
1500
- conflictUndoStateRef.current = null;
1501
- }
1502
-
1503
- keyframeDragRef.current = null;
1504
- };
1505
-
1506
- window.addEventListener(
1507
- "mousemove",
1508
- handleMove,
1509
- );
1510
- window.addEventListener("mouseup", handleUp);
1511
- }}
1512
- >
1513
- <div
1514
- className={cn("w-2 h-2 rotate-45 relative")}
1515
- style={{
1516
- // Different styles for cursor keyframes based on what changed
1517
- backgroundColor: selected
1518
- ? "#ffffff" // White fill for selected keyframes
1519
- : isCursor &&
1520
- (cursorStyle === "clickStart" ||
1521
- cursorStyle === "clickEnd")
1522
- ? "#facc15" // Yellow for click keyframes
1523
- : isCursor && cursorStyle === "type"
1524
- ? "transparent"
1525
- : keyframeColor,
1526
- border:
1527
- isCursor &&
1528
- cursorStyle === "type" &&
1529
- !selected
1530
- ? `1.5px solid ${keyframeColor}`
1531
- : isCursor &&
1532
- cursorStyle === "both" &&
1533
- !selected
1534
- ? `1.5px solid #38bdf8` // Brighter blue border for both
1535
- : "none",
1536
- opacity:
1537
- isCursor && cursorStyle === "clickEnd"
1538
- ? 0.4 // Semi-transparent for click end
1539
- : 1, // Solid for everything else (including clickStart)
1540
- boxShadow: selected
1541
- ? `0 0 0 2px #ffffffcc, 0 0 8px #ffffff99` // White outline + glow for selected
1542
- : isCursor &&
1543
- (cursorStyle === "clickStart" ||
1544
- cursorStyle === "clickEnd")
1545
- ? `0 0 ${isCurrentKeyframe ? 6 : 4}px #facc15${isCurrentKeyframe ? "cc" : "88"}` // Yellow glow for click keyframes
1546
- : isCursor && cursorStyle === "type"
1547
- ? `0 0 ${isCurrentKeyframe ? 6 : 4}px ${keyframeColor}${isCurrentKeyframe ? "88" : "66"}`
1548
- : isCursor && cursorStyle === "both"
1549
- ? `0 0 ${isCurrentKeyframe ? 8 : 6}px #38bdf8${isCurrentKeyframe ? "cc" : "88"}`
1550
- : `0 0 ${isCurrentKeyframe ? 6 : 4}px ${keyframeColor}${isCurrentKeyframe ? "cc" : "88"}`,
1551
- }}
1552
- />
1553
- </div>
1554
- </TooltipTrigger>
1555
- <TooltipContent>
1556
- {isPlaying
1557
- ? "Pause to select keyframe"
1558
- : `Drag to reposition • Click to select`}
1559
- </TooltipContent>
1560
- </Tooltip>
1561
- );
1562
- })}
1563
- </div>
1564
- );
1565
- }
1566
-
1567
- // Keyframe-style track (instant state change - startFrame === endFrame)
1568
- // These render as diamond markers instead of duration bars
1569
- // Used for: tab switches, modal toggles, instant state changes
1570
- // See AGENTS.md: "Keyframe-Style Tracks" section
1571
- const isKeyframeTrack = track.startFrame === track.endFrame;
1572
-
1573
- if (isKeyframeTrack) {
1574
- const keyframePos = toViewPct(track.startFrame);
1575
- const keyframeColor = isExpr ? EXPR_COLOR : colors.text;
1576
- const keyframeFrame = track.startFrame;
1577
-
1578
- // Check if this keyframe is selected
1579
- const selectedFrames =
1580
- selectedKeyframes.get(track.id) || new Set<number>();
1581
- const keyframeSelected = selectedFrames.has(keyframeFrame);
1582
-
1583
- return (
1584
- <div
1585
- key={track.id}
1586
- className="relative w-full"
1587
- style={{ height: TRACK_HEIGHT }}
1588
- onMouseDown={(e) => {
1589
- if (isPlaying) return;
1590
- // Only start box selection if clicking directly on track (not the keyframe)
1591
- if (e.target !== e.currentTarget) return;
1592
-
1593
- e.preventDefault();
1594
- e.stopPropagation();
1595
-
1596
- const el = barAreaRef.current;
1597
- if (!el) return;
1598
- const rect = el.getBoundingClientRect();
1599
- const startX = e.clientX - rect.left;
1600
- const startY = e.clientY;
1601
-
1602
- let hasMoved = false;
1603
-
1604
- // Initialize selection box
1605
- setSelectionBox({
1606
- trackId: track.id,
1607
- startX,
1608
- startY,
1609
- currentX: startX,
1610
- currentY: startY,
1611
- });
1612
-
1613
- const handleMove = (ev: MouseEvent) => {
1614
- const currentX = ev.clientX - rect.left;
1615
- const currentY = ev.clientY;
1616
-
1617
- // Check if mouse has moved significantly (3px threshold)
1618
- const deltaX = Math.abs(currentX - startX);
1619
- const deltaY = Math.abs(currentY - startY);
1620
- if (deltaX > 3 || deltaY > 3) {
1621
- hasMoved = true;
1622
- }
1623
-
1624
- // Update selection box position
1625
- setSelectionBox((prev) =>
1626
- prev
1627
- ? {
1628
- ...prev,
1629
- currentX,
1630
- currentY,
1631
- }
1632
- : null,
1633
- );
1634
-
1635
- // Calculate which keyframes are in the box
1636
- const boxStartPct =
1637
- Math.min(startX / rect.width, currentX / rect.width) *
1638
- 100;
1639
- const boxEndPct =
1640
- Math.max(startX / rect.width, currentX / rect.width) *
1641
- 100;
1642
-
1643
- const framesInBox = new Set<number>();
1644
- const framePct = toViewPct(keyframeFrame);
1645
- if (framePct >= boxStartPct && framePct <= boxEndPct) {
1646
- framesInBox.add(keyframeFrame);
1647
- }
1648
-
1649
- // Update selection continuously during drag
1650
- setSelectedKeyframes((prev) => {
1651
- const next = new Map(prev);
1652
- next.set(track.id, framesInBox);
1653
- return next;
1654
- });
1655
- };
1656
-
1657
- const handleUp = () => {
1658
- window.removeEventListener("mousemove", handleMove);
1659
- window.removeEventListener("mouseup", handleUp);
1660
- setSelectionBox(null);
1661
-
1662
- // If we had a selection box drag, prevent the next onClick from clearing
1663
- if (hasMoved) {
1664
- selectionBoxJustCompletedRef.current = true;
1665
- // Reset after a short delay (after onClick would have fired)
1666
- setTimeout(() => {
1667
- selectionBoxJustCompletedRef.current = false;
1668
- }, 10);
1669
- }
1670
- };
1671
-
1672
- window.addEventListener("mousemove", handleMove);
1673
- window.addEventListener("mouseup", handleUp);
1674
- }}
1675
- onClick={(e) => {
1676
- // Clear selection when clicking empty area (unless box selection just completed)
1677
- if (
1678
- e.target === e.currentTarget &&
1679
- !selectionBoxJustCompletedRef.current
1680
- ) {
1681
- setSelectedKeyframes(new Map());
1682
- }
1683
- }}
1684
- >
1685
- <div className="absolute inset-x-0 bottom-0 h-px bg-border/20" />
1686
-
1687
- {/* Selection box rendering */}
1688
- {selectionBox &&
1689
- selectionBox.trackId === track.id &&
1690
- barAreaRef.current &&
1691
- (() => {
1692
- const rect = barAreaRef.current.getBoundingClientRect();
1693
- const left = Math.min(
1694
- selectionBox.startX,
1695
- selectionBox.currentX,
1696
- );
1697
- const width = Math.abs(
1698
- selectionBox.currentX - selectionBox.startX,
1699
- );
1700
-
1701
- return (
1702
- <div
1703
- className="absolute pointer-events-none"
1704
- style={{
1705
- left,
1706
- top: 0,
1707
- width,
1708
- height: TRACK_HEIGHT,
1709
- backgroundColor: "rgba(99, 102, 241, 0.15)",
1710
- border: "1px solid rgba(99, 102, 241, 0.4)",
1711
- borderRadius: "4px",
1712
- }}
1713
- />
1714
- );
1715
- })()}
1716
-
1717
- {/* Keyframe diamond marker */}
1718
- <div
1719
- className={cn(
1720
- "absolute top-1/2 -translate-y-1/2 -translate-x-1/2",
1721
- "w-2 h-2 rotate-45 cursor-pointer",
1722
- )}
1723
- style={{
1724
- left: `${keyframePos}%`,
1725
- backgroundColor: keyframeSelected
1726
- ? "#ffffff"
1727
- : keyframeColor,
1728
- border: keyframeSelected ? "none" : undefined,
1729
- boxShadow: keyframeSelected
1730
- ? `0 0 0 2px #ffffffcc, 0 0 8px #ffffff99`
1731
- : `0 0 4px ${keyframeColor}88`,
1732
- }}
1733
- onMouseDown={(e) => {
1734
- if (isPlaying) return;
1735
- e.preventDefault();
1736
- e.stopPropagation();
1737
-
1738
- const el = barAreaRef.current;
1739
- if (!el) return;
1740
- const rect = el.getBoundingClientRect();
1741
- const startX = e.clientX;
1742
- let isDragging = false;
1743
-
1744
- // Click selects the keyframe
1745
- if (!keyframeSelected) {
1746
- setSelectedKeyframes((prev) => {
1747
- const next = new Map(prev);
1748
- next.set(track.id, new Set([keyframeFrame]));
1749
- return next;
1750
- });
1751
- }
1752
-
1753
- const handleMove = (ev: MouseEvent) => {
1754
- const dist = Math.abs(ev.clientX - startX);
1755
- if (dist > 3) {
1756
- isDragging = true;
1757
- const deltaX = ev.clientX - startX;
1758
- const frameChange = pxDeltaToFrameDelta(
1759
- deltaX,
1760
- rect.width,
1761
- viewDuration,
1762
- );
1763
- const newFrame = clampFrame(
1764
- track.startFrame + frameChange,
1765
- durationInFrames,
1766
- );
1767
-
1768
- // Update both start and end to keep it as a keyframe
1769
- onUpdateTrack(track.id, {
1770
- startFrame: newFrame,
1771
- endFrame: newFrame,
1772
- });
1773
-
1774
- // Update selection to reflect new position
1775
- setSelectedKeyframes((prev) => {
1776
- const next = new Map(prev);
1777
- next.set(track.id, new Set([newFrame]));
1778
- return next;
1779
- });
1780
- }
1781
- };
1782
-
1783
- const handleUp = () => {
1784
- window.removeEventListener("mousemove", handleMove);
1785
- window.removeEventListener("mouseup", handleUp);
1786
-
1787
- if (!isDragging) {
1788
- // Just a click - select track and seek
1789
- onSelectTrack(track.id);
1790
- onSeek(track.startFrame);
1791
- }
1792
- };
1793
-
1794
- window.addEventListener("mousemove", handleMove);
1795
- window.addEventListener("mouseup", handleUp);
1796
- }}
1797
- />
1798
- </div>
1799
- );
1800
- }
1801
-
1802
- // Regular animation tracks (with duration)
1803
- return (
1804
- <div
1805
- key={track.id}
1806
- className="relative w-full"
1807
- style={{ height: TRACK_HEIGHT }}
1808
- >
1809
- <div className="absolute inset-x-0 bottom-0 h-px bg-border/20" />
1810
-
1811
- <div
1812
- className="track-bar absolute inset-y-1.5 rounded cursor-grab active:cursor-grabbing"
1813
- data-track-id={track.id}
1814
- style={{
1815
- left: `${leftPct}%`,
1816
- width: `${Math.max(widthPct, 0.5)}%`,
1817
- minWidth: 12,
1818
- backgroundColor: colors.bg,
1819
- border: `1px solid ${
1820
- isSelected ? colors.activeBorder : colors.border
1821
- }`,
1822
- opacity: isSelected ? 1 : 0.65,
1823
- boxShadow: isSelected
1824
- ? `0 0 0 1px ${colors.activeBorder}22, 0 2px 8px ${colors.activeBorder}20`
1825
- : "none",
1826
- }}
1827
- onMouseDown={(e) => {
1828
- startDrag(e, track, "move");
1829
- }}
1830
- >
1831
- {/* Left resize handle */}
1832
- <div
1833
- className="resize-handle absolute left-0 top-0 bottom-0 cursor-ew-resize z-10 flex items-center justify-center"
1834
- style={{ width: 8 }}
1835
- onMouseDown={(e) => {
1836
- e.stopPropagation();
1837
- startDrag(e, track, "resize-start");
1838
- }}
1839
- >
1840
- <div
1841
- className="w-px h-3 rounded-full"
1842
- style={{
1843
- backgroundColor: colors.text,
1844
- opacity: 0.5,
1845
- }}
1846
- />
1847
- </div>
1848
-
1849
- {/* Time range label */}
1850
- <div className="absolute inset-0 flex items-center justify-center px-2 pointer-events-none overflow-hidden">
1851
- <span
1852
- className="text-[8px] font-mono whitespace-nowrap"
1853
- style={{
1854
- color: colors.text,
1855
- opacity: isSelected ? 0.9 : 0.6,
1856
- }}
1857
- >
1858
- {fmtSec(track.startFrame)}–{fmtSec(track.endFrame)}
1859
- </span>
1860
- </div>
1861
-
1862
- {/* Right resize handle */}
1863
- <div
1864
- className="resize-handle absolute right-0 top-0 bottom-0 cursor-ew-resize z-10 flex items-center justify-center"
1865
- style={{ width: 8 }}
1866
- onMouseDown={(e) => {
1867
- e.stopPropagation();
1868
- startDrag(e, track, "resize-end");
1869
- }}
1870
- >
1871
- <div
1872
- className="w-px h-3 rounded-full"
1873
- style={{
1874
- backgroundColor: colors.text,
1875
- opacity: 0.5,
1876
- }}
1877
- />
1878
- </div>
1879
- </div>
1880
- </div>
1881
- );
1882
- })}
1883
- </div>
1884
-
1885
- {/* ── Playhead ────────────────────────────────────────────── */}
1886
- {playheadVisible && (
1887
- <div
1888
- className="absolute z-20 pointer-events-none"
1889
- style={{
1890
- left: `${playheadPct}%`,
1891
- // Span from range bar to bottom of tracks
1892
- top: RANGE_BAR_HEIGHT + 4,
1893
- bottom: 0,
1894
- }}
1895
- >
1896
- <div className="absolute inset-y-0 w-px bg-foreground/65 -translate-x-px" />
1897
- <div className="absolute top-0 -translate-x-1/2 w-2 h-3 bg-foreground rounded-b-sm" />
1898
- </div>
1899
- )}
1900
- </div>
1901
- </div>
1902
-
1903
- {/* ── Keyframe Conflict Resolution Modal ──────────────────────────── */}
1904
- <AlertDialog
1905
- open={!!keyframeConflict}
1906
- onOpenChange={(open) => {
1907
- if (!open) {
1908
- // Closing = undo
1909
- const conflictState = conflictUndoStateRef.current;
1910
- if (
1911
- keyframeConflict &&
1912
- conflictState &&
1913
- conflictState.trackId === keyframeConflict.trackId
1914
- ) {
1915
- onUpdateTrack(conflictState.trackId, {
1916
- animatedProps: conflictState.originalAnimatedProps,
1917
- });
1918
- conflictUndoStateRef.current = null;
1919
- }
1920
- setKeyframeConflict(null);
1921
- }
1922
- }}
1923
- >
1924
- <AlertDialogContent className="max-w-md">
1925
- <AlertDialogHeader>
1926
- <AlertDialogTitle className="flex items-center gap-3 text-sm">
1927
- <IconAlertCircle className="w-5 h-5 text-yellow-500 flex-shrink-0" />
1928
- {t("raw.timeline.keyframeOverlap")}
1929
- </AlertDialogTitle>
1930
- <AlertDialogDescription className="text-xs leading-relaxed">
1931
- Moving the keyframe(s) would create an overlap at frame{" "}
1932
- <span className="font-mono font-medium text-foreground">
1933
- {keyframeConflict?.newFrame}
1934
- </span>
1935
- .
1936
- </AlertDialogDescription>
1937
- </AlertDialogHeader>
1938
- <AlertDialogFooter className="flex-col sm:flex-col space-y-2 sm:space-x-0">
1939
- <AlertDialogAction
1940
- onClick={() => {
1941
- if (!keyframeConflict) return;
1942
- // Replace existing keyframes using Map-based deduplication
1943
- const targetTrack = tracks.find(
1944
- (t) => t.id === keyframeConflict.trackId,
1945
- );
1946
- if (targetTrack?.animatedProps) {
1947
- const updatedProps = targetTrack.animatedProps.map((prop) => {
1948
- if (!prop.keyframes) return prop;
1949
-
1950
- // Group keyframes by frame number
1951
- const frameMap = new Map<
1952
- number,
1953
- (typeof prop.keyframes)[0]
1954
- >();
1955
- prop.keyframes.forEach((kf) => {
1956
- // Later keyframes overwrite earlier ones at the same frame
1957
- frameMap.set(kf.frame, kf);
1958
- });
1959
-
1960
- // Convert back to array
1961
- const updatedKeyframes = Array.from(frameMap.values());
1962
- return { ...prop, keyframes: updatedKeyframes };
1963
- });
1964
- onUpdateTrack(keyframeConflict.trackId, {
1965
- animatedProps: updatedProps,
1966
- });
1967
- }
1968
- conflictUndoStateRef.current = null;
1969
- setKeyframeConflict(null);
1970
- }}
1971
- className="w-full"
1972
- >
1973
- {t("raw.timeline.replaceExistingKeyframes")}
1974
- </AlertDialogAction>
1975
- <AlertDialogCancel
1976
- onClick={() => {
1977
- // Undo - restore from saved state
1978
- const conflictState = conflictUndoStateRef.current;
1979
- if (
1980
- keyframeConflict &&
1981
- conflictState &&
1982
- conflictState.trackId === keyframeConflict.trackId
1983
- ) {
1984
- onUpdateTrack(conflictState.trackId, {
1985
- animatedProps: conflictState.originalAnimatedProps,
1986
- });
1987
- conflictUndoStateRef.current = null;
1988
- }
1989
- setKeyframeConflict(null);
1990
- }}
1991
- className="w-full"
1992
- >
1993
- {t("raw.timeline.undoMove")}
1994
- </AlertDialogCancel>
1995
- </AlertDialogFooter>
1996
- </AlertDialogContent>
1997
- </AlertDialog>
1998
-
1999
- {/* ── Context Menu for Track Management ──────────────────────────── */}
2000
- {contextMenu &&
2001
- (() => {
2002
- const trackName =
2003
- contextMenu.trackId === "camera" ? "Camera" : "Cursor";
2004
-
2005
- // Estimate menu height (header ~36px + 2 options ~28px each + padding ~8px = ~100px)
2006
- const estimatedMenuHeight = 100;
2007
- const estimatedMenuWidth = 160;
2008
- const padding = 8; // Keep menu away from edges
2009
-
2010
- // Calculate position to prevent overflow
2011
- let left = contextMenu.x;
2012
- let top = contextMenu.y;
2013
-
2014
- // Prevent right edge overflow
2015
- if (left + estimatedMenuWidth > window.innerWidth - padding) {
2016
- left = window.innerWidth - estimatedMenuWidth - padding;
2017
- }
2018
-
2019
- // Prevent bottom edge overflow - position above click if needed
2020
- if (top + estimatedMenuHeight > window.innerHeight - padding) {
2021
- top = contextMenu.y - estimatedMenuHeight;
2022
- }
2023
-
2024
- // Prevent top edge overflow (in case mouse is very near top)
2025
- if (top < padding) {
2026
- top = padding;
2027
- }
2028
-
2029
- return (
2030
- <div
2031
- className="fixed bg-card border border-border rounded-lg shadow-xl z-50 min-w-[160px] overflow-hidden"
2032
- style={{
2033
- left,
2034
- top,
2035
- }}
2036
- onClick={(e) => e.stopPropagation()}
2037
- >
2038
- {/* Header */}
2039
- <div className="px-3 py-2 bg-secondary/30 border-b border-border">
2040
- <div className="text-xs font-medium text-foreground">
2041
- {trackName} Track
2042
- </div>
2043
- </div>
2044
-
2045
- {/* Options */}
2046
- <div className="py-1">
2047
- <button
2048
- onClick={() => handleClearTrack(contextMenu.trackId)}
2049
- className="w-full px-3 py-1.5 text-left text-xs hover:bg-secondary transition-colors flex items-center gap-2 text-foreground/80 hover:text-foreground"
2050
- >
2051
- <IconRotate className="w-3.5 h-3.5 text-muted-foreground" />
2052
- {t("raw.timeline.clearTrack")}
2053
- </button>
2054
- <button
2055
- onClick={() => handleDeleteTrack(contextMenu.trackId)}
2056
- className="w-full px-3 py-1.5 text-left text-xs hover:bg-destructive/10 transition-colors flex items-center gap-2 text-foreground/80 hover:text-destructive"
2057
- >
2058
- <IconTrash className="w-3.5 h-3.5 text-muted-foreground" />
2059
- {t("raw.timeline.deleteTrack")}
2060
- </button>
2061
- </div>
2062
- </div>
2063
- );
2064
- })()}
2065
- </div>
2066
- );
2067
- }