@agent-native/core 0.80.11 → 0.81.1

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