@agent-native/core 0.80.10 → 0.81.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 (267) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +53 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/agent/production-agent.ts +108 -15
  5. package/corpus/core/src/cli/design-connect.ts +419 -19
  6. package/corpus/core/src/client/AssistantChat.tsx +123 -26
  7. package/corpus/core/src/client/ConnectBuilderCard.tsx +1 -0
  8. package/corpus/core/src/client/chat/index.ts +1 -0
  9. package/corpus/core/src/client/chat/run-recovery.tsx +9 -7
  10. package/corpus/core/src/client/chat-view-transition.ts +33 -2
  11. package/corpus/core/src/client/composer/PromptComposer.tsx +21 -2
  12. package/corpus/core/src/client/index.ts +1 -0
  13. package/corpus/core/src/client/org/OrgSwitcher.tsx +26 -4
  14. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -3
  15. package/corpus/core/src/client/sse-event-processor.ts +9 -0
  16. package/corpus/core/src/client/use-agent-chat-home-handoff.ts +31 -3
  17. package/corpus/core/src/db/client.ts +29 -2
  18. package/corpus/core/src/provider-api/index.ts +29 -0
  19. package/corpus/core/src/server/core-routes-plugin.ts +58 -20
  20. package/corpus/core/src/styles/agent-native.css +54 -0
  21. package/corpus/core/src/usage/store.ts +2 -2
  22. package/corpus/templates/analytics/app/components/layout/Layout.tsx +118 -10
  23. package/corpus/templates/analytics/app/components/layout/layout-route-policy.ts +7 -0
  24. package/corpus/templates/analytics/app/global.css +8 -0
  25. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +7 -1
  26. package/corpus/templates/analytics/app/lib/chat-handoff.ts +34 -0
  27. package/corpus/templates/analytics/app/pages/Ask.tsx +9 -18
  28. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  29. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +109 -21
  30. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +4 -6
  31. package/corpus/templates/analytics/changelog/2026-06-30-ask-opens-a-fresh-chat-unless-you-were-just-chatting-and-das.md +6 -0
  32. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-email-reports-are-more-reliable-and-use-cleaner.md +6 -0
  33. package/corpus/templates/analytics/changelog/2026-06-30-dashboard-toolbar-controls-use-a-quieter-share-button-and-ve.md +6 -0
  34. package/corpus/templates/analytics/changelog/2026-06-30-session-replay-rows-show-whole-minute-durations-in-the-playl.md +6 -0
  35. package/corpus/templates/analytics/changelog/2026-06-30-session-replays-now-render-captured-styling-and-keep-the-eve.md +6 -0
  36. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +16 -3
  37. package/corpus/templates/analytics/server/lib/dashboard-report.ts +31 -14
  38. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +9 -2
  39. package/corpus/templates/assets/AGENTS.md +4 -0
  40. package/corpus/templates/assets/actions/_tool-activity.ts +46 -0
  41. package/corpus/templates/assets/actions/generate-image-batch.ts +26 -5
  42. package/corpus/templates/assets/actions/generate-image.ts +118 -67
  43. package/corpus/templates/assets/actions/list-draft-assets.ts +50 -0
  44. package/corpus/templates/assets/actions/open-asset-picker.ts +9 -0
  45. package/corpus/templates/assets/actions/rerun-generation-run.ts +52 -31
  46. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +100 -0
  47. package/corpus/templates/assets/app/global.css +15 -0
  48. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +4 -0
  49. package/corpus/templates/assets/app/i18n/zh-TW.ts +7 -0
  50. package/corpus/templates/assets/app/i18n-data.ts +61 -0
  51. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  52. package/corpus/templates/assets/app/routes/library.tsx +233 -52
  53. package/corpus/templates/assets/changelog/2026-06-29-image-generation-can-now-render-requested-text-and-respect-b.md +6 -0
  54. package/corpus/templates/assets/changelog/2026-06-30-image-generation-no-longer-looks-stuck-while-the-provider-is-still-working.md +6 -0
  55. package/corpus/templates/assets/changelog/2026-06-30-tagged-presets-now-control-the-image-aspect-ratio.md +6 -0
  56. package/corpus/templates/assets/changelog/2026-06-30-the-assets-picker-now-supports-a-vertical-embedded-layout-fo.md +6 -0
  57. package/corpus/templates/assets/server/lib/generation.ts +135 -11
  58. package/corpus/templates/assets/shared/api.ts +4 -0
  59. package/corpus/templates/clips/actions/finalize-recording.ts +1 -12
  60. package/corpus/templates/clips/actions/list-ai-requests.ts +60 -0
  61. package/corpus/templates/clips/app/hooks/use-auto-title.ts +24 -25
  62. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +10 -5
  63. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -8
  64. package/corpus/templates/clips/changelog/2026-06-30-extension-save-recovery-avoids-false-upload-failures.md +6 -0
  65. package/corpus/templates/clips/changelog/2026-06-30-longer-chrome-extension-recordings-now-keep-saving-reliably-.md +6 -0
  66. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  67. package/corpus/templates/clips/chrome-extension/src/background.ts +19 -10
  68. package/corpus/templates/clips/chrome-extension/src/content-script.ts +38 -12
  69. package/corpus/templates/clips/chrome-extension/src/finalize-recovery.ts +224 -0
  70. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +119 -16
  71. package/corpus/templates/clips/chrome-extension/src/overlay.ts +16 -0
  72. package/corpus/templates/clips/server/lib/recording-upload-state.ts +48 -0
  73. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +9 -1
  74. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +63 -13
  75. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/status.get.ts +74 -0
  76. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +32 -0
  77. package/corpus/templates/design/AGENTS.md +27 -0
  78. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1 -1
  79. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +5 -5
  80. package/corpus/templates/design/actions/apply-component-prop-edit.ts +76 -25
  81. package/corpus/templates/design/actions/apply-design-state.ts +17 -4
  82. package/corpus/templates/design/actions/apply-motion-edit.ts +76 -115
  83. package/corpus/templates/design/actions/apply-shader-fill.ts +148 -56
  84. package/corpus/templates/design/actions/capture-design-state.ts +27 -4
  85. package/corpus/templates/design/actions/connect-localhost.ts +59 -22
  86. package/corpus/templates/design/actions/delete-design.ts +20 -0
  87. package/corpus/templates/design/actions/export-zip.ts +23 -8
  88. package/corpus/templates/design/actions/generate-design.ts +53 -1
  89. package/corpus/templates/design/actions/get-component-details.ts +21 -26
  90. package/corpus/templates/design/actions/get-motion-timeline.ts +135 -10
  91. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +116 -0
  92. package/corpus/templates/design/actions/import-design-tokens.ts +521 -0
  93. package/corpus/templates/design/actions/index-design-tokens.ts +18 -5
  94. package/corpus/templates/design/actions/insert-design-native-asset.ts +268 -0
  95. package/corpus/templates/design/actions/insert-figma-library-asset.ts +233 -0
  96. package/corpus/templates/design/actions/list-design-extensions.ts +32 -10
  97. package/corpus/templates/design/actions/list-design-native-assets.ts +105 -0
  98. package/corpus/templates/design/actions/list-design-states.ts +19 -1
  99. package/corpus/templates/design/actions/list-figma-library-assets.ts +292 -0
  100. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +238 -0
  101. package/corpus/templates/design/actions/navigate.ts +18 -3
  102. package/corpus/templates/design/actions/open-component-source.ts +7 -2
  103. package/corpus/templates/design/actions/preview-component-prop-edit.ts +32 -26
  104. package/corpus/templates/design/actions/preview-shader-fill.ts +9 -11
  105. package/corpus/templates/design/actions/provider-api-catalog.ts +2 -2
  106. package/corpus/templates/design/actions/provider-api-docs.ts +1 -1
  107. package/corpus/templates/design/actions/provider-api-request.ts +3 -3
  108. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +48 -0
  109. package/corpus/templates/design/actions/run-design-audit.ts +74 -10
  110. package/corpus/templates/design/actions/run-design-extension-action.ts +1 -1
  111. package/corpus/templates/design/actions/view-screen.ts +1 -1
  112. package/corpus/templates/design/actions/write-local-file.ts +213 -0
  113. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +244 -3197
  114. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +30 -20
  115. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +795 -289
  116. package/corpus/templates/design/app/components/design/EditPanel.tsx +635 -234
  117. package/corpus/templates/design/app/components/design/LayersPanel.tsx +124 -32
  118. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +161 -0
  119. package/corpus/templates/design/app/components/design/MotionDock.tsx +244 -266
  120. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +491 -56
  121. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +55 -67
  122. package/corpus/templates/design/app/components/design/StatesPanel.tsx +38 -63
  123. package/corpus/templates/design/app/components/design/TokensPanel.tsx +264 -8
  124. package/corpus/templates/design/app/components/design/bridge/codegen.ts +125 -0
  125. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +4037 -0
  126. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +62 -0
  127. package/corpus/templates/design/app/components/design/bridge/hit-test.bridge.ts +272 -0
  128. package/corpus/templates/design/app/components/design/bridge/nav.bridge.ts +116 -0
  129. package/corpus/templates/design/app/components/design/bridge/sample.bridge.ts +24 -0
  130. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +78 -0
  131. package/corpus/templates/design/app/components/design/bridge/tsconfig.json +17 -0
  132. package/corpus/templates/design/app/components/design/bridge/tweak.bridge.ts +28 -0
  133. package/corpus/templates/design/app/components/design/bridge/zoom.bridge.ts +43 -0
  134. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +45 -16
  135. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +88 -9
  136. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +94 -9
  137. package/corpus/templates/design/app/components/layout/Layout.tsx +19 -3
  138. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  139. package/corpus/templates/design/app/global.css +7 -0
  140. package/corpus/templates/design/app/hooks/use-navigation-state.ts +23 -0
  141. package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
  142. package/corpus/templates/design/app/i18n-data.ts +369 -0
  143. package/corpus/templates/design/app/lib/pending-generation.ts +1 -0
  144. package/corpus/templates/design/app/pages/DesignEditor.tsx +3024 -616
  145. package/corpus/templates/design/app/pages/Index.tsx +5 -1
  146. package/corpus/templates/design/changelog/2026-06-30-applying-the-one-click-fix-for-a-small-tap-target-accessibil.md +6 -0
  147. package/corpus/templates/design/changelog/2026-06-30-canvas-creation-screen-dragging-undo-redo-and-image-fill-edi.md +6 -0
  148. package/corpus/templates/design/changelog/2026-06-30-component-prop-edits-and-shader-fills-now-preserve-rapid-sam.md +6 -0
  149. package/corpus/templates/design/changelog/2026-06-30-design-can-browse-figma-library-components-and-insert-them-i.md +6 -0
  150. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +6 -0
  151. package/corpus/templates/design/changelog/2026-06-30-design-now-has-native-components-embedded-media-and-figma-im.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-30-design-preview-now-includes-a-publish-app-waitlist-option-fr.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-30-design-s-left-rail-now-matches-the-sidebar-chrome-and-uses-t.md +6 -0
  154. package/corpus/templates/design/changelog/2026-06-30-design-s-tools-create-menu-now-stays-compact-instead-of-rend.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-30-design-starter-prompts-now-begin-generating-immediately-and-.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-30-design-token-edits-now-stay-visible-in-previews-and-token-li.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-30-drawing-rectangles-on-the-all-screens-canvas-no-longer-flash.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-30-fixed-code-native-design-studio-edits-so-component-props-mot.md +6 -0
  160. package/corpus/templates/design/changelog/2026-06-30-fixed-visual-editing-hover-overlays-and-spacing-handles-so-s.md +6 -0
  161. package/corpus/templates/design/changelog/2026-06-30-motion-edits-now-save-automatically-without-a-separate-css-a.md +6 -0
  162. package/corpus/templates/design/changelog/2026-06-30-motion-timelines-now-reopen-as-editable-tracks-after-writing.md +6 -0
  163. package/corpus/templates/design/changelog/2026-06-30-new-design-prompts-with-uploaded-screenshots-work-reliably-o.md +6 -0
  164. package/corpus/templates/design/changelog/2026-06-30-screen-previews-in-all-screens-view-now-keep-rounded-outline.md +6 -0
  165. package/corpus/templates/design/changelog/2026-06-30-selection-outlines-now-sit-flush-on-rectangles-drawn-directl.md +6 -0
  166. package/corpus/templates/design/changelog/2026-06-30-share-options-use-a-tighter-design-editor-popover-with-compa.md +6 -0
  167. package/corpus/templates/design/changelog/2026-06-30-the-collapsed-motion-panel-now-stays-tucked-under-the-left-s.md +6 -0
  168. package/corpus/templates/design/changelog/2026-06-30-the-design-inspector-now-shows-states-and-review-in-a-tighte.md +6 -0
  169. package/corpus/templates/design/changelog/2026-06-30-the-sidebar-keeps-the-organization-picker-space-stable-while.md +6 -0
  170. package/corpus/templates/design/changelog/2026-06-30-tokens-can-now-be-imported-from-pasted-notes-code-files-fold.md +6 -0
  171. package/corpus/templates/design/e2e/global-setup.ts +94 -2
  172. package/corpus/templates/design/e2e/helpers.ts +168 -65
  173. package/corpus/templates/design/package.json +3 -1
  174. package/corpus/templates/design/server/db/schema.ts +23 -0
  175. package/corpus/templates/design/server/handlers/uploads.ts +22 -1
  176. package/corpus/templates/design/server/lib/provider-api.ts +7 -4
  177. package/corpus/templates/design/server/lib/verify-write-grant.ts +108 -0
  178. package/corpus/templates/design/server/plugins/db.ts +39 -0
  179. package/corpus/templates/design/server/register-secrets.ts +38 -0
  180. package/corpus/templates/design/shared/board-file.ts +228 -0
  181. package/corpus/templates/design/shared/board-objects.ts +288 -0
  182. package/corpus/templates/design/shared/code-layer.ts +52 -0
  183. package/corpus/templates/design/shared/component-model.ts +35 -0
  184. package/corpus/templates/design/shared/motion-compiler.ts +137 -15
  185. package/corpus/templates/design/shared/motion-timeline.ts +2 -2
  186. package/corpus/templates/design/shared/resolve-tweaks.ts +32 -9
  187. package/corpus/templates/design/tsconfig.json +2 -1
  188. package/corpus/templates/slides/AGENTS.md +9 -0
  189. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +4 -0
  190. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +4 -24
  191. package/corpus/templates/slides/app/lib/export-pdf-client.ts +29 -20
  192. package/corpus/templates/slides/app/lib/export-pptx-client.ts +365 -0
  193. package/corpus/templates/slides/app/pages/DeckEditor.tsx +12 -9
  194. package/corpus/templates/slides/app/vite-env.d.ts +18 -0
  195. package/corpus/templates/slides/changelog/2026-06-30-powerpoint-downloads-now-preserve-the-rendered-slide-layout-.md +6 -0
  196. package/corpus/templates/slides/changelog/2026-06-30-unavailable-decks-open-in-a-cleaner-full-height-centered-sta.md +6 -0
  197. package/corpus/templates/slides/package.json +1 -0
  198. package/corpus/templates/slides/vite.config.ts +1 -0
  199. package/dist/agent/production-agent.d.ts.map +1 -1
  200. package/dist/agent/production-agent.js +94 -10
  201. package/dist/agent/production-agent.js.map +1 -1
  202. package/dist/cli/design-connect.d.ts +25 -0
  203. package/dist/cli/design-connect.d.ts.map +1 -1
  204. package/dist/cli/design-connect.js +357 -18
  205. package/dist/cli/design-connect.js.map +1 -1
  206. package/dist/client/AssistantChat.d.ts +14 -0
  207. package/dist/client/AssistantChat.d.ts.map +1 -1
  208. package/dist/client/AssistantChat.js +115 -22
  209. package/dist/client/AssistantChat.js.map +1 -1
  210. package/dist/client/ConnectBuilderCard.d.ts.map +1 -1
  211. package/dist/client/ConnectBuilderCard.js +1 -0
  212. package/dist/client/ConnectBuilderCard.js.map +1 -1
  213. package/dist/client/chat/index.d.ts +1 -1
  214. package/dist/client/chat/index.d.ts.map +1 -1
  215. package/dist/client/chat/index.js +1 -1
  216. package/dist/client/chat/index.js.map +1 -1
  217. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  218. package/dist/client/chat/run-recovery.js +5 -5
  219. package/dist/client/chat/run-recovery.js.map +1 -1
  220. package/dist/client/chat-view-transition.d.ts +6 -0
  221. package/dist/client/chat-view-transition.d.ts.map +1 -1
  222. package/dist/client/chat-view-transition.js +23 -2
  223. package/dist/client/chat-view-transition.js.map +1 -1
  224. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  225. package/dist/client/composer/PromptComposer.js +3 -2
  226. package/dist/client/composer/PromptComposer.js.map +1 -1
  227. package/dist/client/index.d.ts +1 -1
  228. package/dist/client/index.d.ts.map +1 -1
  229. package/dist/client/index.js +1 -1
  230. package/dist/client/index.js.map +1 -1
  231. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  232. package/dist/client/org/OrgSwitcher.js +11 -4
  233. package/dist/client/org/OrgSwitcher.js.map +1 -1
  234. package/dist/client/sharing/ShareButton.js +3 -3
  235. package/dist/client/sharing/ShareButton.js.map +1 -1
  236. package/dist/client/sse-event-processor.d.ts.map +1 -1
  237. package/dist/client/sse-event-processor.js +10 -0
  238. package/dist/client/sse-event-processor.js.map +1 -1
  239. package/dist/client/use-agent-chat-home-handoff.d.ts +8 -2
  240. package/dist/client/use-agent-chat-home-handoff.d.ts.map +1 -1
  241. package/dist/client/use-agent-chat-home-handoff.js +20 -6
  242. package/dist/client/use-agent-chat-home-handoff.js.map +1 -1
  243. package/dist/collab/awareness.d.ts +2 -2
  244. package/dist/collab/awareness.d.ts.map +1 -1
  245. package/dist/collab/routes.d.ts +2 -2
  246. package/dist/db/client.d.ts +1 -0
  247. package/dist/db/client.d.ts.map +1 -1
  248. package/dist/db/client.js +28 -2
  249. package/dist/db/client.js.map +1 -1
  250. package/dist/notifications/routes.d.ts +3 -3
  251. package/dist/observability/routes.d.ts +3 -3
  252. package/dist/progress/routes.d.ts +1 -1
  253. package/dist/provider-api/index.d.ts +5 -4
  254. package/dist/provider-api/index.d.ts.map +1 -1
  255. package/dist/provider-api/index.js +28 -0
  256. package/dist/provider-api/index.js.map +1 -1
  257. package/dist/resources/handlers.d.ts +3 -3
  258. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  259. package/dist/server/core-routes-plugin.d.ts +25 -0
  260. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  261. package/dist/server/core-routes-plugin.js +43 -18
  262. package/dist/server/core-routes-plugin.js.map +1 -1
  263. package/dist/server/transcribe-voice.d.ts +1 -1
  264. package/dist/styles/agent-native.css +54 -0
  265. package/dist/usage/store.js +2 -2
  266. package/dist/usage/store.js.map +1 -1
  267. package/package.json +1 -1
@@ -4,15 +4,15 @@
4
4
  *
5
5
  * Matches the motion artboard at
6
6
  * https://plan.agent-native.com/plans/plan-88dc4a09fb0c46bc:
7
- * - Full-width collapsible dock beneath the canvas.
7
+ * - Full-width dock beneath the canvas when opened from the Layers footer.
8
8
  * - Left sidebar: animated layer rows with property sub-rows.
9
9
  * - Center: time ruler + diamond keyframes on a track grid.
10
10
  * - Playhead: draggable; scrubbing sends a preview-only `motion-preview`
11
11
  * postMessage to the canvas iframe — NEVER writes to DB.
12
- * - Top toolbar: play/pause, duration input, auto-keyframe toggle, Write to CSS.
12
+ * - Top toolbar: play/pause, duration input, auto-keyframe toggle, autosave.
13
13
  *
14
- * Write to CSS calls the parent's `onApply` prop, which should invoke
15
- * `apply-motion-edit` through `useActionMutation`.
14
+ * Track and duration edits notify the parent; the parent persists through
15
+ * `apply-motion-edit`. Scrubbing/playback stays preview-only.
16
16
  *
17
17
  * All times are normalised to [0, 1] internally; the ruler maps them to px.
18
18
  */
@@ -37,7 +37,6 @@ import {
37
37
  IconChevronRight,
38
38
  IconPlus,
39
39
  IconTrash,
40
- IconCode,
41
40
  IconRefresh,
42
41
  IconBolt,
43
42
  IconLayersSubtract,
@@ -108,17 +107,12 @@ export interface MotionDockProps {
108
107
  onTracksChange?: (tracks: MotionDockTrack[]) => void;
109
108
  /** Called when durationMs is edited. */
110
109
  onDurationChange?: (ms: number) => void;
111
- /**
112
- * Called when "Write to CSS" is clicked. The parent should call
113
- * `apply-motion-edit` via useActionMutation.
114
- */
115
- onApply?: (tracks: MotionDockTrack[], durationMs: number) => void;
116
110
  /**
117
111
  * Reference to the canvas iframe element. Used to send preview postMessages.
118
112
  * If not provided, preview messages are skipped (no crash).
119
113
  */
120
114
  canvasIframeRef?: React.RefObject<HTMLIFrameElement | null>;
121
- /** Whether the parent apply mutation is in flight. */
115
+ /** Whether the parent autosave mutation is in flight. */
122
116
  applying?: boolean;
123
117
  /**
124
118
  * The currently-selected canvas element, if any. Required to create the FIRST
@@ -139,7 +133,6 @@ export function MotionDock({
139
133
  onOpenChange,
140
134
  onTracksChange,
141
135
  onDurationChange,
142
- onApply,
143
136
  canvasIframeRef,
144
137
  applying = false,
145
138
  selectedTarget = null,
@@ -174,6 +167,14 @@ export function MotionDock({
174
167
  () => new Set(tracks.map((t) => t.targetNodeId)),
175
168
  );
176
169
 
170
+ useEffect(() => {
171
+ setExpandedNodeIds((current) => {
172
+ const next = new Set(current);
173
+ for (const track of tracks) next.add(track.targetNodeId);
174
+ return next.size === current.size ? current : next;
175
+ });
176
+ }, [tracks]);
177
+
177
178
  // Ruler / track area ref for pointer math.
178
179
  const trackAreaRef = useRef<HTMLDivElement>(null);
179
180
 
@@ -332,8 +333,8 @@ export function MotionDock({
332
333
  // ── Create a brand-new track (the "first track" path) ──────────────────────
333
334
  // This is the entry point that turns the dock from a dead end into a working
334
335
  // editor: with an element selected and no track yet, the user picks a property
335
- // preset and we seed a two-keyframe track. Once at least one track exists,
336
- // "Write to CSS" enables. Idempotent per (nodeId, property) — picking the same
336
+ // preset and we seed a two-keyframe track. The parent autosaves that valid
337
+ // track into managed CSS. Idempotent per (nodeId, property) — picking the same
337
338
  // property twice just re-expands the existing track instead of duplicating.
338
339
  const createTrack = useCallback(
339
340
  (preset: MotionPropertyPreset) => {
@@ -403,298 +404,275 @@ export function MotionDock({
403
404
  // ── Render ────────────────────────────────────────────────────────────────
404
405
  return (
405
406
  <div
407
+ aria-label="Motion dock"
406
408
  className={cn(
407
- "flex flex-col border-t border-border bg-background transition-all duration-150 select-none",
408
- isOpen ? "" : "h-8",
409
+ "flex flex-col overflow-hidden border-t bg-background transition-[height,opacity,transform,border-color] duration-200 ease-out select-none will-change-transform",
410
+ isOpen
411
+ ? "translate-y-0 border-border opacity-100"
412
+ : "translate-y-full border-transparent opacity-0 pointer-events-none",
409
413
  )}
410
- style={isOpen ? { height: dockHeight } : undefined}
414
+ style={{ height: isOpen ? dockHeight : 0 }}
411
415
  >
412
416
  {/* Resize handle */}
413
- {isOpen && (
414
- <div
415
- className="absolute -top-1 left-0 right-0 h-2 cursor-ns-resize z-10"
416
- onPointerDown={handleResizePointerDown}
417
- onPointerMove={handleResizePointerMove}
418
- onPointerUp={handleResizePointerUp}
419
- />
420
- )}
417
+ <div
418
+ className="absolute -top-1 left-0 right-0 h-2 cursor-ns-resize z-10"
419
+ onPointerDown={handleResizePointerDown}
420
+ onPointerMove={handleResizePointerMove}
421
+ onPointerUp={handleResizePointerUp}
422
+ />
421
423
 
422
424
  {/* Dock toolbar */}
423
425
  <div className="flex h-8 shrink-0 items-center gap-1 border-b border-border px-2">
424
- {/* Collapse toggle */}
425
- <Button
426
+ {/* Collapse toggle. The label and nearby space are part of the target. */}
427
+ <button
426
428
  type="button"
427
- variant="ghost"
428
- size="icon"
429
- className="size-6 shrink-0"
430
- onClick={() => setOpen(!isOpen)}
431
- aria-label={isOpen ? "Collapse motion dock" : "Expand motion dock"}
429
+ className="-ml-1 flex h-7 shrink-0 cursor-pointer items-center gap-1.5 rounded-md px-1.5 text-[11px] font-medium uppercase tracking-wide text-muted-foreground outline-none transition-colors hover:bg-accent hover:text-foreground focus-visible:ring-1 focus-visible:ring-[var(--design-editor-accent-color)]"
430
+ onClick={() => setOpen(false)}
431
+ aria-label="Collapse motion dock"
432
432
  >
433
- {isOpen ? (
434
- <IconChevronDown className="size-3.5" />
435
- ) : (
436
- <IconChevronRight className="size-3.5" />
437
- )}
438
- </Button>
433
+ <IconChevronDown className="size-3.5" />
434
+ <span>Motion</span>
435
+ </button>
436
+
437
+ <>
438
+ {/* Play / Pause */}
439
+ <Tooltip>
440
+ <TooltipTrigger asChild>
441
+ <Button
442
+ type="button"
443
+ variant="ghost"
444
+ size="icon"
445
+ className="size-6 shrink-0"
446
+ onClick={playing ? stopPlayback : startPlayback}
447
+ aria-label={playing ? "Pause" : "Play"}
448
+ >
449
+ {playing ? (
450
+ <IconPlayerPause className="size-3.5" />
451
+ ) : (
452
+ <IconPlayerPlay className="size-3.5" />
453
+ )}
454
+ </Button>
455
+ </TooltipTrigger>
456
+ <TooltipContent side="top">
457
+ {playing ? "Pause" : "Play"}
458
+ </TooltipContent>
459
+ </Tooltip>
460
+
461
+ {/* Stop / reset */}
462
+ <Tooltip>
463
+ <TooltipTrigger asChild>
464
+ <Button
465
+ type="button"
466
+ variant="ghost"
467
+ size="icon"
468
+ className="size-6 shrink-0"
469
+ onClick={() => {
470
+ stopPlayback();
471
+ setPlayhead(0);
472
+ sendPreview(0);
473
+ }}
474
+ aria-label="Reset playhead"
475
+ >
476
+ <IconPlayerStop className="size-3.5" />
477
+ </Button>
478
+ </TooltipTrigger>
479
+ <TooltipContent side="top">Reset</TooltipContent>
480
+ </Tooltip>
481
+
482
+ {/* Duration */}
483
+ <div className="flex items-center gap-1 ml-1">
484
+ <span className="text-[10px] text-muted-foreground">Duration</span>
485
+ <Input
486
+ type="number"
487
+ min={50}
488
+ step={50}
489
+ value={durationInput}
490
+ onChange={(e) => setDurationInput(e.target.value)}
491
+ onBlur={() => {
492
+ const ms = parseInt(durationInput, 10);
493
+ if (!isNaN(ms) && ms >= 50) {
494
+ onDurationChange?.(ms);
495
+ } else {
496
+ setDurationInput(String(durationMs));
497
+ }
498
+ }}
499
+ className="h-5 w-16 px-1 text-[11px]"
500
+ aria-label="Duration in ms"
501
+ />
502
+ <span className="text-[10px] text-muted-foreground">ms</span>
503
+ </div>
439
504
 
440
- <span className="text-[11px] font-medium tracking-wide text-muted-foreground uppercase mr-1">
441
- Motion
442
- </span>
505
+ {/* Add track — the "create first track" entry point. */}
506
+ <div className="ml-2">
507
+ <AddTrackMenu
508
+ selectedTarget={selectedTarget}
509
+ onCreateTrack={createTrack}
510
+ />
511
+ </div>
443
512
 
444
- {isOpen && (
445
- <>
446
- {/* Play / Pause */}
513
+ <div className="ml-auto flex items-center gap-1">
514
+ {/* Auto-keyframe toggle */}
447
515
  <Tooltip>
448
516
  <TooltipTrigger asChild>
449
517
  <Button
450
518
  type="button"
451
- variant="ghost"
519
+ variant={autoKeyframe ? "secondary" : "ghost"}
452
520
  size="icon"
453
- className="size-6 shrink-0"
454
- onClick={playing ? stopPlayback : startPlayback}
455
- aria-label={playing ? "Pause" : "Play"}
456
- >
457
- {playing ? (
458
- <IconPlayerPause className="size-3.5" />
459
- ) : (
460
- <IconPlayerPlay className="size-3.5" />
521
+ className={cn(
522
+ "size-6 shrink-0",
523
+ autoKeyframe && "text-primary",
461
524
  )}
462
- </Button>
463
- </TooltipTrigger>
464
- <TooltipContent side="top">
465
- {playing ? "Pause" : "Play"}
466
- </TooltipContent>
467
- </Tooltip>
468
-
469
- {/* Stop / reset */}
470
- <Tooltip>
471
- <TooltipTrigger asChild>
472
- <Button
473
- type="button"
474
- variant="ghost"
475
- size="icon"
476
- className="size-6 shrink-0"
477
- onClick={() => {
478
- stopPlayback();
479
- setPlayhead(0);
480
- sendPreview(0);
481
- }}
482
- aria-label="Reset playhead"
525
+ onClick={() => setAutoKeyframe((v) => !v)}
526
+ aria-label="Toggle auto-keyframe"
527
+ aria-pressed={autoKeyframe}
483
528
  >
484
- <IconPlayerStop className="size-3.5" />
529
+ <IconBolt className="size-3.5" />
485
530
  </Button>
486
531
  </TooltipTrigger>
487
- <TooltipContent side="top">Reset</TooltipContent>
532
+ <TooltipContent side="top">Auto-keyframe</TooltipContent>
488
533
  </Tooltip>
489
534
 
490
- {/* Duration */}
491
- <div className="flex items-center gap-1 ml-1">
492
- <span className="text-[10px] text-muted-foreground">
493
- Duration
494
- </span>
495
- <Input
496
- type="number"
497
- min={50}
498
- step={50}
499
- value={durationInput}
500
- onChange={(e) => setDurationInput(e.target.value)}
501
- onBlur={() => {
502
- const ms = parseInt(durationInput, 10);
503
- if (!isNaN(ms) && ms >= 50) {
504
- onDurationChange?.(ms);
505
- } else {
506
- setDurationInput(String(durationMs));
507
- }
508
- }}
509
- className="h-5 w-16 px-1 text-[11px]"
510
- aria-label="Duration in ms"
511
- />
512
- <span className="text-[10px] text-muted-foreground">ms</span>
513
- </div>
514
-
515
- {/* Add track — the "create first track" entry point. */}
516
- <div className="ml-2">
517
- <AddTrackMenu
518
- selectedTarget={selectedTarget}
519
- onCreateTrack={createTrack}
520
- />
521
- </div>
522
-
523
- <div className="ml-auto flex items-center gap-1">
524
- {/* Auto-keyframe toggle */}
525
- <Tooltip>
526
- <TooltipTrigger asChild>
527
- <Button
528
- type="button"
529
- variant={autoKeyframe ? "secondary" : "ghost"}
530
- size="icon"
531
- className={cn(
532
- "size-6 shrink-0",
533
- autoKeyframe && "text-primary",
534
- )}
535
- onClick={() => setAutoKeyframe((v) => !v)}
536
- aria-label="Toggle auto-keyframe"
537
- aria-pressed={autoKeyframe}
538
- >
539
- <IconBolt className="size-3.5" />
540
- </Button>
541
- </TooltipTrigger>
542
- <TooltipContent side="top">Auto-keyframe</TooltipContent>
543
- </Tooltip>
544
-
545
- {/* Write to CSS */}
535
+ {applying ? (
546
536
  <Tooltip>
547
537
  <TooltipTrigger asChild>
548
- <Button
549
- type="button"
550
- size="sm"
551
- className="h-6 px-2.5 text-[11px] gap-1"
552
- disabled={applying || tracks.length === 0}
553
- onClick={() => onApply?.(tracks, durationMs)}
538
+ <span
539
+ role="status"
540
+ aria-label="Saving motion"
541
+ className="flex size-6 items-center justify-center rounded text-muted-foreground"
554
542
  >
555
- {applying ? (
556
- <IconRefresh className="size-3 animate-spin" />
557
- ) : (
558
- <IconCode className="size-3" />
559
- )}
560
- Write to CSS
561
- </Button>
543
+ <IconRefresh className="size-3 animate-spin" />
544
+ </span>
562
545
  </TooltipTrigger>
563
- <TooltipContent side="top">
564
- Compile timeline → managed &lt;style
565
- data-agent-native-motion&gt; block
566
- </TooltipContent>
546
+ <TooltipContent side="top">Saving motion</TooltipContent>
567
547
  </Tooltip>
568
- </div>
569
- </>
570
- )}
548
+ ) : null}
549
+ </div>
550
+ </>
571
551
  </div>
572
552
 
573
553
  {/* Dock body */}
574
- {isOpen && (
575
- <div className="flex flex-1 overflow-hidden">
576
- {/* Layer sidebar */}
554
+ <div className="flex flex-1 overflow-hidden">
555
+ {/* Layer sidebar */}
556
+ <div
557
+ className="flex flex-col shrink-0 border-r border-border overflow-y-auto"
558
+ style={{ width: LAYER_SIDEBAR_WIDTH }}
559
+ >
560
+ {/* Ruler spacer */}
577
561
  <div
578
- className="flex flex-col shrink-0 border-r border-border overflow-y-auto"
579
- style={{ width: LAYER_SIDEBAR_WIDTH }}
580
- >
581
- {/* Ruler spacer */}
582
- <div
583
- style={{ height: RULER_HEIGHT }}
584
- className="border-b border-border"
585
- />
562
+ style={{ height: RULER_HEIGHT }}
563
+ className="border-b border-border"
564
+ />
586
565
 
587
- {layers.length === 0 ? (
588
- <div className="flex flex-col items-center justify-center flex-1 gap-3 text-center px-4 py-6">
589
- <IconLayersSubtract className="size-5 text-muted-foreground/40" />
590
- {selectedTarget ? (
591
- <>
592
- <p className="text-[11px] text-muted-foreground/70 leading-snug">
593
- Animate{" "}
594
- <span className="font-medium text-foreground/80">
595
- {selectedTarget.label}
596
- </span>
597
- . Pick a property to add the first track.
598
- </p>
599
- <AddTrackMenu
600
- selectedTarget={selectedTarget}
601
- onCreateTrack={createTrack}
602
- variant="cta"
603
- />
604
- </>
605
- ) : (
566
+ {layers.length === 0 ? (
567
+ <div className="flex flex-col items-center justify-center flex-1 gap-3 text-center px-4 py-6">
568
+ <IconLayersSubtract className="size-5 text-muted-foreground/40" />
569
+ {selectedTarget ? (
570
+ <>
606
571
  <p className="text-[11px] text-muted-foreground/70 leading-snug">
607
- Select an element on the canvas, then add a track to animate
608
- it.
572
+ Animate{" "}
573
+ <span className="font-medium text-foreground/80">
574
+ {selectedTarget.label}
575
+ </span>
576
+ . Pick a property to add the first track.
609
577
  </p>
610
- )}
611
- </div>
612
- ) : (
613
- layers.map((layer) => (
614
- <LayerGroup
615
- key={layer.nodeId}
616
- layer={layer}
617
- expanded={expandedNodeIds.has(layer.nodeId)}
618
- onToggleExpand={() =>
619
- setExpandedNodeIds((prev) => {
620
- const next = new Set(prev);
621
- if (next.has(layer.nodeId)) next.delete(layer.nodeId);
622
- else next.add(layer.nodeId);
623
- return next;
624
- })
625
- }
626
- onAddKeyframe={(track) => addKeyframe(track)}
627
- />
628
- ))
629
- )}
630
- </div>
578
+ <AddTrackMenu
579
+ selectedTarget={selectedTarget}
580
+ onCreateTrack={createTrack}
581
+ variant="cta"
582
+ />
583
+ </>
584
+ ) : (
585
+ <p className="text-[11px] text-muted-foreground/70 leading-snug">
586
+ Select an element on the canvas, then add a track to animate
587
+ it.
588
+ </p>
589
+ )}
590
+ </div>
591
+ ) : (
592
+ layers.map((layer) => (
593
+ <LayerGroup
594
+ key={layer.nodeId}
595
+ layer={layer}
596
+ expanded={expandedNodeIds.has(layer.nodeId)}
597
+ onToggleExpand={() =>
598
+ setExpandedNodeIds((prev) => {
599
+ const next = new Set(prev);
600
+ if (next.has(layer.nodeId)) next.delete(layer.nodeId);
601
+ else next.add(layer.nodeId);
602
+ return next;
603
+ })
604
+ }
605
+ onAddKeyframe={(track) => addKeyframe(track)}
606
+ />
607
+ ))
608
+ )}
609
+ </div>
631
610
 
632
- {/* Timeline / track area */}
633
- <div className="flex flex-col flex-1 overflow-hidden">
634
- {/* Ruler + playhead drag */}
635
- <div
636
- ref={trackAreaRef}
637
- className="relative shrink-0 border-b border-border cursor-col-resize"
638
- style={{ height: RULER_HEIGHT }}
639
- onPointerDown={handleRulerPointerDown}
640
- onPointerMove={handleRulerPointerMove}
641
- onPointerUp={handleRulerPointerUp}
642
- >
643
- {/* Tick marks */}
644
- {rulerTicks().map(({ t, label }) => (
645
- <div
646
- key={t}
647
- className="absolute top-0 flex flex-col items-center pointer-events-none"
648
- style={{ left: `${t * 100}%`, transform: "translateX(-50%)" }}
649
- >
650
- <span className="text-[9px] text-muted-foreground/60 leading-none mt-1">
651
- {label}
652
- </span>
653
- <div className="w-px h-2 bg-border mt-0.5" />
654
- </div>
655
- ))}
611
+ {/* Timeline / track area */}
612
+ <div className="flex flex-col flex-1 overflow-hidden">
613
+ {/* Ruler + playhead drag */}
614
+ <div
615
+ ref={trackAreaRef}
616
+ className="relative shrink-0 border-b border-border cursor-col-resize"
617
+ style={{ height: RULER_HEIGHT }}
618
+ onPointerDown={handleRulerPointerDown}
619
+ onPointerMove={handleRulerPointerMove}
620
+ onPointerUp={handleRulerPointerUp}
621
+ >
622
+ {/* Tick marks */}
623
+ {rulerTicks().map(({ t, label }) => (
624
+ <div
625
+ key={t}
626
+ className="absolute top-0 flex flex-col items-center pointer-events-none"
627
+ style={{ left: `${t * 100}%`, transform: "translateX(-50%)" }}
628
+ >
629
+ <span className="text-[9px] text-muted-foreground/60 leading-none mt-1">
630
+ {label}
631
+ </span>
632
+ <div className="w-px h-2 bg-border mt-0.5" />
633
+ </div>
634
+ ))}
656
635
 
657
- {/* Playhead */}
658
- <PlayheadLine t={playhead} height={RULER_HEIGHT} inRuler />
659
- </div>
636
+ {/* Playhead */}
637
+ <PlayheadLine t={playhead} height={RULER_HEIGHT} inRuler />
638
+ </div>
660
639
 
661
- {/* Track rows with keyframe diamonds */}
662
- <div className="relative flex-1 overflow-y-auto">
663
- {layers.map((layer) => (
664
- <LayerTrackRows
665
- key={layer.nodeId}
666
- layer={layer}
667
- expanded={expandedNodeIds.has(layer.nodeId)}
668
- playhead={playhead}
669
- onDeleteKeyframe={deleteKeyframe}
670
- onMoveKeyframe={(track, kf, newT) => {
671
- updateTrack(track.targetNodeId, track.property, (tr) => ({
672
- ...tr,
673
- keyframes: tr.keyframes.map((k) =>
674
- k === kf ? { ...k, t: newT } : k,
675
- ),
676
- }));
677
- }}
678
- />
679
- ))}
680
-
681
- {/* Playhead across track rows */}
682
- <PlayheadLine
683
- t={playhead}
684
- height={layers.reduce(
685
- (sum, layer) =>
686
- sum +
687
- ROW_HEIGHT +
688
- (expandedNodeIds.has(layer.nodeId)
689
- ? layer.tracks.length * ROW_HEIGHT
690
- : 0),
691
- 0,
692
- )}
640
+ {/* Track rows with keyframe diamonds */}
641
+ <div className="relative flex-1 overflow-y-auto">
642
+ {layers.map((layer) => (
643
+ <LayerTrackRows
644
+ key={layer.nodeId}
645
+ layer={layer}
646
+ expanded={expandedNodeIds.has(layer.nodeId)}
647
+ playhead={playhead}
648
+ onDeleteKeyframe={deleteKeyframe}
649
+ onMoveKeyframe={(track, kf, newT) => {
650
+ updateTrack(track.targetNodeId, track.property, (tr) => ({
651
+ ...tr,
652
+ keyframes: tr.keyframes.map((k) =>
653
+ k === kf ? { ...k, t: newT } : k,
654
+ ),
655
+ }));
656
+ }}
693
657
  />
694
- </div>
658
+ ))}
659
+
660
+ {/* Playhead across track rows */}
661
+ <PlayheadLine
662
+ t={playhead}
663
+ height={layers.reduce(
664
+ (sum, layer) =>
665
+ sum +
666
+ ROW_HEIGHT +
667
+ (expandedNodeIds.has(layer.nodeId)
668
+ ? layer.tracks.length * ROW_HEIGHT
669
+ : 0),
670
+ 0,
671
+ )}
672
+ />
695
673
  </div>
696
674
  </div>
697
- )}
675
+ </div>
698
676
  </div>
699
677
  );
700
678
  }