@agent-native/core 0.80.8 → 0.80.10

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 (359) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +19 -0
  3. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +6 -0
  4. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +8 -0
  5. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +10 -2
  6. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +10 -2
  7. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +7 -0
  8. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +7 -0
  9. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +7 -0
  10. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +9 -2
  11. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +6 -0
  12. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +6 -0
  13. package/corpus/core/docs/content/template-design.mdx +9 -2
  14. package/corpus/core/package.json +1 -1
  15. package/corpus/core/src/cli/design-connect.ts +28 -2
  16. package/corpus/core/src/cli/mcp.ts +10 -0
  17. package/corpus/core/src/cli/skills.ts +103 -36
  18. package/corpus/core/src/client/error-format.ts +25 -0
  19. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +13 -11
  20. package/corpus/core/src/client/guided-questions.tsx +32 -4
  21. package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +33 -1
  22. package/corpus/core/src/client/resources/BuiltinCapabilityDetail.tsx +8 -0
  23. package/corpus/core/src/client/resources/use-builtin-capabilities.ts +4 -2
  24. package/corpus/core/src/client/session-replay.ts +34 -0
  25. package/corpus/core/src/client/sharing/ShareButton.tsx +106 -21
  26. package/corpus/core/src/coding-tools/index.ts +1 -1
  27. package/corpus/core/src/mcp/index.ts +2 -0
  28. package/corpus/core/src/mcp/screen-memory-stdio.ts +290 -0
  29. package/corpus/core/src/mcp-client/builtin-capabilities.ts +13 -1
  30. package/corpus/core/src/mcp-client/index.ts +9 -0
  31. package/corpus/core/src/mcp-client/screen-memory-local.ts +461 -0
  32. package/corpus/core/src/onboarding/types.ts +7 -0
  33. package/corpus/core/src/server/agent-chat-plugin.ts +25 -0
  34. package/corpus/core/src/server/agents-bundle.ts +35 -7
  35. package/corpus/core/src/styles/agent-native.css +5 -0
  36. package/corpus/core/src/templates/default/app/global.css +35 -35
  37. package/corpus/templates/analytics/AGENTS.md +6 -0
  38. package/corpus/templates/analytics/actions/github-repo-files.ts +9 -0
  39. package/corpus/templates/analytics/app/components/layout/Layout.tsx +4 -4
  40. package/corpus/templates/analytics/app/global.css +144 -40
  41. package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +1 -1
  42. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +1 -1
  43. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +1 -1
  44. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +4 -4
  45. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -9
  46. package/corpus/templates/analytics/changelog/2026-06-29-agents-can-search-and-read-connected-github-repositories-whe.md +6 -0
  47. package/corpus/templates/analytics/changelog/2026-06-29-sessions-and-explorer-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
  48. package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -1
  49. package/corpus/templates/assets/app/components/library/LibraryPresetGrid.tsx +1 -1
  50. package/corpus/templates/assets/app/global.css +99 -35
  51. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
  52. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +4 -4
  53. package/corpus/templates/assets/app/routes/library.tsx +4 -4
  54. package/corpus/templates/assets/changelog/2026-06-29-asset-library-and-detail-layouts-adapt-when-the-agent-sidebar.md +6 -0
  55. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
  56. package/corpus/templates/brain/app/global.css +63 -35
  57. package/corpus/templates/brain/app/routes/search.tsx +1 -1
  58. package/corpus/templates/brain/app/routes/settings.tsx +1 -1
  59. package/corpus/templates/brain/app/routes/sources.tsx +2 -2
  60. package/corpus/templates/brain/changelog/2026-06-29-search-settings-and-source-layouts-adapt-when-the-agent-sidebar.md +6 -0
  61. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +4 -4
  62. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +3 -2
  63. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -2
  64. package/corpus/templates/calendar/app/global.css +69 -35
  65. package/corpus/templates/calendar/changelog/2026-06-29-event-and-find-time-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
  66. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  67. package/corpus/templates/chat/app/global.css +35 -35
  68. package/corpus/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  69. package/corpus/templates/clips/AGENTS.md +13 -0
  70. package/corpus/templates/clips/actions/get-screen-memory-status.ts +12 -0
  71. package/corpus/templates/clips/actions/query-screen-memory-context.ts +29 -0
  72. package/corpus/templates/clips/actions/view-screen.ts +2 -2
  73. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  74. package/corpus/templates/clips/app/global.css +47 -35
  75. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +3 -3
  76. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +45 -18
  77. package/corpus/templates/clips/changelog/2026-06-29-meeting-detail-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-06-29-screen-memory-keeps-a-local-rolling-screen-buffer-for-agent-context.md +5 -0
  79. package/corpus/templates/clips/changelog/2026-06-29-video-chat-is-easier-to-find-on-recording-pages-and-s3-setup.md +6 -0
  80. package/corpus/templates/clips/chrome-extension/src/styles.css +39 -39
  81. package/corpus/templates/clips/desktop/src/app.tsx +290 -1
  82. package/corpus/templates/clips/desktop/src/shared/config.ts +83 -0
  83. package/corpus/templates/clips/desktop/src/styles.css +67 -52
  84. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +50 -0
  85. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +17 -0
  86. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +13 -13
  87. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +990 -0
  88. package/corpus/templates/clips/server/plugins/onboarding.ts +2 -0
  89. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +3 -3
  90. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  91. package/corpus/templates/content/app/global.css +29 -15
  92. package/corpus/templates/content/changelog/2026-06-29-database-gallery-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  93. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +25 -20
  94. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -0
  95. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +8 -0
  96. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +13 -0
  97. package/corpus/templates/design/AGENTS.md +20 -12
  98. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1054 -0
  99. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +717 -0
  100. package/corpus/templates/design/actions/add-breakpoint.ts +143 -0
  101. package/corpus/templates/design/actions/add-localhost-screens.ts +5 -0
  102. package/corpus/templates/design/actions/apply-a11y-fix.ts +317 -0
  103. package/corpus/templates/design/actions/apply-component-prop-edit.ts +433 -0
  104. package/corpus/templates/design/actions/apply-design-state.ts +200 -0
  105. package/corpus/templates/design/actions/apply-design-token-edit.ts +202 -0
  106. package/corpus/templates/design/actions/apply-motion-edit.ts +450 -0
  107. package/corpus/templates/design/actions/apply-shader-fill.ts +404 -0
  108. package/corpus/templates/design/actions/apply-visual-edit.ts +214 -8
  109. package/corpus/templates/design/actions/capture-design-state.ts +224 -0
  110. package/corpus/templates/design/actions/connect-builder-app.ts +162 -0
  111. package/corpus/templates/design/actions/create-component.ts +447 -0
  112. package/corpus/templates/design/actions/create-design-branch.ts +263 -0
  113. package/corpus/templates/design/actions/create-design-state.ts +162 -0
  114. package/corpus/templates/design/actions/delete-design-state.ts +55 -0
  115. package/corpus/templates/design/actions/deploy-design-preview.ts +275 -0
  116. package/corpus/templates/design/actions/export-coding-handoff.ts +12 -4
  117. package/corpus/templates/design/actions/generate-design.ts +6 -21
  118. package/corpus/templates/design/actions/generate-screens.ts +43 -2
  119. package/corpus/templates/design/actions/get-component-details.ts +242 -0
  120. package/corpus/templates/design/actions/get-design-branch-diff.ts +362 -0
  121. package/corpus/templates/design/actions/get-design-review.ts +314 -0
  122. package/corpus/templates/design/actions/get-design-surface-index.ts +582 -0
  123. package/corpus/templates/design/actions/get-design.ts +1 -0
  124. package/corpus/templates/design/actions/get-motion-timeline.ts +99 -0
  125. package/corpus/templates/design/actions/index-components.ts +258 -0
  126. package/corpus/templates/design/actions/index-design-tokens.ts +273 -0
  127. package/corpus/templates/design/actions/list-design-extensions.ts +309 -0
  128. package/corpus/templates/design/actions/list-design-source-capabilities.ts +153 -0
  129. package/corpus/templates/design/actions/list-design-states.ts +72 -0
  130. package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +298 -0
  131. package/corpus/templates/design/actions/open-component-source.ts +237 -0
  132. package/corpus/templates/design/actions/present-design-variants.ts +355 -30
  133. package/corpus/templates/design/actions/preview-component-prop-edit.ts +254 -0
  134. package/corpus/templates/design/actions/preview-design-token-edit.ts +113 -0
  135. package/corpus/templates/design/actions/preview-shader-fill.ts +189 -0
  136. package/corpus/templates/design/actions/remove-breakpoint.ts +103 -0
  137. package/corpus/templates/design/actions/remove-motion-timeline.ts +196 -0
  138. package/corpus/templates/design/actions/run-design-audit.ts +421 -0
  139. package/corpus/templates/design/actions/run-design-extension-action.ts +284 -0
  140. package/corpus/templates/design/actions/set-active-breakpoint.ts +39 -0
  141. package/corpus/templates/design/actions/view-screen.ts +75 -31
  142. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +1738 -135
  143. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +1 -1
  144. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +632 -43
  145. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +1 -1
  146. package/corpus/templates/design/app/components/design/EditPanel.tsx +1873 -132
  147. package/corpus/templates/design/app/components/design/LayersPanel.tsx +384 -219
  148. package/corpus/templates/design/app/components/design/LocalSourceEditBanner.tsx +157 -0
  149. package/corpus/templates/design/app/components/design/MotionDock.tsx +1062 -0
  150. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2103 -295
  151. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +83 -206
  152. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +759 -0
  153. package/corpus/templates/design/app/components/design/StatesPanel.tsx +579 -0
  154. package/corpus/templates/design/app/components/design/TokensPanel.tsx +573 -0
  155. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +1 -1
  156. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +241 -0
  157. package/corpus/templates/design/app/components/design/index.ts +16 -1
  158. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +238 -137
  159. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +188 -14
  160. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +24 -5
  161. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +57 -1
  162. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +145 -0
  163. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +42 -12
  164. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +37 -12
  165. package/corpus/templates/design/app/components/design/inspector/index.ts +1 -0
  166. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +12 -5
  167. package/corpus/templates/design/app/components/design/types.ts +9 -0
  168. package/corpus/templates/design/app/components/layout/Layout.tsx +23 -5
  169. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +1 -1
  170. package/corpus/templates/design/app/global.css +77 -49
  171. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  172. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +131 -0
  173. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +11 -2
  174. package/corpus/templates/design/app/i18n/zh-TW.ts +34 -0
  175. package/corpus/templates/design/app/i18n-data.ts +314 -3
  176. package/corpus/templates/design/app/pages/DesignEditor.tsx +4929 -892
  177. package/corpus/templates/design/app/root.tsx +1 -1
  178. package/corpus/templates/design/changelog/2026-06-29-added-a-review-panel-in-the-design-editor-s-inspector-with-a.md +6 -0
  179. package/corpus/templates/design/changelog/2026-06-29-containers-no-longer-block-selecting-nested-elements-and-nor.md +6 -0
  180. package/corpus/templates/design/changelog/2026-06-29-copying-and-pasting-layers-no-longer-shows-success-notificat.md +6 -0
  181. package/corpus/templates/design/changelog/2026-06-29-design-agents-now-generate-and-refine-variants-from-stronger.md +6 -0
  182. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +6 -0
  183. package/corpus/templates/design/changelog/2026-06-29-design-questions-now-appear-as-a-cleaner-canvas-intake-with-.md +6 -0
  184. package/corpus/templates/design/changelog/2026-06-29-design-variant-directions-now-appear-as-board-screens-with-chat-buttons.md +5 -0
  185. package/corpus/templates/design/changelog/2026-06-29-device-presets-in-all-screens-view-resize-the-selected-previ.md +6 -0
  186. package/corpus/templates/design/changelog/2026-06-29-element-selection-no-longer-snaps-back-to-the-previous-layer.md +6 -0
  187. package/corpus/templates/design/changelog/2026-06-29-frame-resize-measurements-now-stay-beside-the-cursor-while-d.md +6 -0
  188. package/corpus/templates/design/changelog/2026-06-29-inspector-number-fields-have-cleaner-tooltips-and-draggable-.md +6 -0
  189. package/corpus/templates/design/changelog/2026-06-29-layer-and-canvas-drags-keep-the-layer-list-stable-while-chan.md +6 -0
  190. package/corpus/templates/design/changelog/2026-06-29-layer-locking-layer-nesting-generated-question-intake-and-im.md +6 -0
  191. package/corpus/templates/design/changelog/2026-06-29-layer-moves-can-be-undone-and-redone-without-flashing-the-ca.md +6 -0
  192. package/corpus/templates/design/changelog/2026-06-29-layer-names-in-the-sidebar-can-scroll-horizontally-instead-o.md +6 -0
  193. package/corpus/templates/design/changelog/2026-06-29-layer-nesting-in-the-sidebar-now-uses-tighter-spacing-so-nam.md +6 -0
  194. package/corpus/templates/design/changelog/2026-06-29-layers-no-longer-repeat-document-wrapper-rows-when-a-screen-.md +6 -0
  195. package/corpus/templates/design/changelog/2026-06-29-overview-previews-now-keep-element-hover-and-selection-tied-.md +6 -0
  196. package/corpus/templates/design/changelog/2026-06-29-pressing-escape-now-cancels-a-stuck-screen-drag-in-the-overv.md +6 -0
  197. package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +6 -0
  198. package/corpus/templates/design/changelog/2026-06-29-screen-overview-only-highlights-screens-when-the-frame-or-ti.md +6 -0
  199. package/corpus/templates/design/changelog/2026-06-29-screen-overview-previews-now-resize-to-match-the-selected-de.md +6 -0
  200. package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-now-uses-a-pointer-cursor-instead-.md +6 -0
  201. package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-outlines-now-stay-locked-to-the-se.md +6 -0
  202. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zoom-now-reports-real-screen-scale-separatel.md +6 -0
  203. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-feels-smoother-and-selection-outline.md +6 -0
  204. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-now-stays-on-the-overview-canvas-ins.md +6 -0
  205. package/corpus/templates/design/changelog/2026-06-29-screen-previews-use-a-single-blue-hover-border-in-all-screen.md +6 -0
  206. package/corpus/templates/design/changelog/2026-06-29-selected-containers-show-draggable-padding-and-gap-guides-on.md +6 -0
  207. package/corpus/templates/design/changelog/2026-06-29-selecting-a-nested-layer-from-all-screens-now-keeps-you-in-a.md +6 -0
  208. package/corpus/templates/design/changelog/2026-06-29-share-now-groups-link-sharing-exports-and-agent-handoff.md +6 -0
  209. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-adds-a-studio-layer-with-tokens-respon.md +6 -0
  210. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-loading-skeleton-now-uses-a-softer-charcoa.md +6 -0
  211. package/corpus/templates/design/changelog/2026-06-29-the-device-preview-menu-now-sits-next-to-the-preview-button-.md +6 -0
  212. package/corpus/templates/design/changelog/2026-06-29-the-inspector-stays-read-only-while-an-empty-design-is-still.md +6 -0
  213. package/corpus/templates/design/changelog/2026-06-29-the-missing-design-view-now-matches-the-rest-of-the-editor-c.md +6 -0
  214. package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-icon-and-tool-option-menus-better-match-the-edi.md +6 -0
  215. package/corpus/templates/design/changelog/2026-06-29-typography-controls-now-keep-the-font-name-visible-and-move-.md +6 -0
  216. package/corpus/templates/design/changelog/2026-06-30-accessibility-findings-now-offer-a-one-click-fix.md +6 -0
  217. package/corpus/templates/design/changelog/2026-06-30-component-instances-now-have-editable-props-in-the-inspe.md +6 -0
  218. package/corpus/templates/design/changelog/2026-06-30-inspect-code-now-shows-the-elements-opening-tag-at-a-gla.md +6 -0
  219. package/corpus/templates/design/changelog/2026-06-30-shader-fill-presets-can-now-be-applied-to-an-element.md +6 -0
  220. package/corpus/templates/design/changelog/2026-06-30-the-motion-timeline-can-now-add-a-track-to-any-element-a.md +6 -0
  221. package/corpus/templates/design/changelog/2026-06-30-visual-editor-selection-layer-paint-and-drawing-controls-are-more-reliable.md +6 -0
  222. package/corpus/templates/design/e2e/README.md +3 -3
  223. package/corpus/templates/design/e2e/global-setup.ts +11 -22
  224. package/corpus/templates/design/e2e/helpers.ts +128 -11
  225. package/corpus/templates/design/playwright.config.ts +9 -3
  226. package/corpus/templates/design/server/db/schema.ts +123 -0
  227. package/corpus/templates/design/server/lib/coding-handoff.ts +126 -2
  228. package/corpus/templates/design/server/plugins/core-routes.ts +1 -2
  229. package/corpus/templates/design/server/plugins/db.ts +90 -0
  230. package/corpus/templates/design/server/routes/api/design-handoff/[id].zip.get.ts +86 -0
  231. package/corpus/templates/design/shared/builder-app.ts +297 -0
  232. package/corpus/templates/design/shared/capability-resolver.ts +123 -0
  233. package/corpus/templates/design/shared/capture-sanitize.ts +70 -0
  234. package/corpus/templates/design/shared/code-layer.ts +1141 -93
  235. package/corpus/templates/design/shared/component-model.ts +239 -0
  236. package/corpus/templates/design/shared/design-review.ts +275 -0
  237. package/corpus/templates/design/shared/design-source-capabilities.ts +286 -0
  238. package/corpus/templates/design/shared/design-state.ts +112 -0
  239. package/corpus/templates/design/shared/design-surface-index.ts +258 -0
  240. package/corpus/templates/design/shared/generation-session.ts +41 -0
  241. package/corpus/templates/design/shared/motion-compiler.ts +278 -0
  242. package/corpus/templates/design/shared/motion-timeline.ts +193 -0
  243. package/corpus/templates/design/shared/responsive-classes.ts +452 -0
  244. package/corpus/templates/design/shared/shader-fill.ts +323 -0
  245. package/corpus/templates/design/shared/source-mode.ts +245 -0
  246. package/corpus/templates/dispatch/app/global.css +79 -35
  247. package/corpus/templates/dispatch/app/routes/integrations.tsx +8 -7
  248. package/corpus/templates/dispatch/changelog/2026-06-29-integration-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  249. package/corpus/templates/forms/app/components/layout/Layout.tsx +6 -2
  250. package/corpus/templates/forms/app/global.css +64 -35
  251. package/corpus/templates/forms/app/pages/FormsListPage.tsx +2 -2
  252. package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +3 -3
  253. package/corpus/templates/forms/changelog/2026-06-29-form-list-and-insight-layouts-adapt-when-the-agent-sidebar.md +6 -0
  254. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +3 -1
  255. package/corpus/templates/macros/app/global.css +25 -9
  256. package/corpus/templates/macros/changelog/2026-06-29-macro-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  257. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -2
  258. package/corpus/templates/mail/app/global.css +42 -36
  259. package/corpus/templates/mail/app/pages/InboxPage.tsx +1 -1
  260. package/corpus/templates/mail/changelog/2026-06-29-the-contact-panel-now-adapts-to-the-available-mail-pane-width.md +6 -0
  261. package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -1
  262. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +6 -2
  263. package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +6 -7
  264. package/corpus/templates/plan/app/global.css +74 -42
  265. package/corpus/templates/plan/changelog/2026-06-29-plan-canvases-open-without-an-initial-pan-and-zoom-flicker.md +6 -0
  266. package/corpus/templates/plan/changelog/2026-06-29-plan-document-blocks-adapt-to-the-available-document-width.md +6 -0
  267. package/corpus/templates/plan/changelog/2026-06-29-plan-titles-and-summaries-can-be-edited-inline-without-focus.md +6 -0
  268. package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -1
  269. package/corpus/templates/slides/app/global.css +49 -33
  270. package/corpus/templates/slides/changelog/2026-06-29-slide-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  271. package/corpus/templates/videos/app/components/layout/Layout.tsx +3 -1
  272. package/corpus/templates/videos/app/global.css +35 -35
  273. package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  274. package/dist/cli/design-connect.d.ts.map +1 -1
  275. package/dist/cli/design-connect.js +28 -2
  276. package/dist/cli/design-connect.js.map +1 -1
  277. package/dist/cli/mcp.d.ts.map +1 -1
  278. package/dist/cli/mcp.js +10 -0
  279. package/dist/cli/mcp.js.map +1 -1
  280. package/dist/cli/skills.d.ts.map +1 -1
  281. package/dist/cli/skills.js +95 -33
  282. package/dist/cli/skills.js.map +1 -1
  283. package/dist/client/error-format.d.ts.map +1 -1
  284. package/dist/client/error-format.js +17 -0
  285. package/dist/client/error-format.js.map +1 -1
  286. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  287. package/dist/client/extensions/ExtensionViewer.js +3 -4
  288. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  289. package/dist/client/guided-questions.d.ts +4 -0
  290. package/dist/client/guided-questions.d.ts.map +1 -1
  291. package/dist/client/guided-questions.js +16 -7
  292. package/dist/client/guided-questions.js.map +1 -1
  293. package/dist/client/onboarding/OnboardingPanel.js +26 -1
  294. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  295. package/dist/client/resources/BuiltinCapabilityDetail.d.ts.map +1 -1
  296. package/dist/client/resources/BuiltinCapabilityDetail.js +1 -1
  297. package/dist/client/resources/BuiltinCapabilityDetail.js.map +1 -1
  298. package/dist/client/resources/use-builtin-capabilities.d.ts +1 -1
  299. package/dist/client/resources/use-builtin-capabilities.d.ts.map +1 -1
  300. package/dist/client/resources/use-builtin-capabilities.js +2 -1
  301. package/dist/client/resources/use-builtin-capabilities.js.map +1 -1
  302. package/dist/client/session-replay.d.ts.map +1 -1
  303. package/dist/client/session-replay.js +31 -0
  304. package/dist/client/session-replay.js.map +1 -1
  305. package/dist/client/sharing/ShareButton.d.ts +14 -0
  306. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  307. package/dist/client/sharing/ShareButton.js +36 -7
  308. package/dist/client/sharing/ShareButton.js.map +1 -1
  309. package/dist/coding-tools/index.js +1 -1
  310. package/dist/coding-tools/index.js.map +1 -1
  311. package/dist/mcp/index.d.ts +2 -0
  312. package/dist/mcp/index.d.ts.map +1 -1
  313. package/dist/mcp/index.js +1 -0
  314. package/dist/mcp/index.js.map +1 -1
  315. package/dist/mcp/screen-memory-stdio.d.ts +6 -0
  316. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -0
  317. package/dist/mcp/screen-memory-stdio.js +213 -0
  318. package/dist/mcp/screen-memory-stdio.js.map +1 -0
  319. package/dist/mcp-client/builtin-capabilities.d.ts +1 -1
  320. package/dist/mcp-client/builtin-capabilities.d.ts.map +1 -1
  321. package/dist/mcp-client/builtin-capabilities.js +10 -0
  322. package/dist/mcp-client/builtin-capabilities.js.map +1 -1
  323. package/dist/mcp-client/index.d.ts +1 -0
  324. package/dist/mcp-client/index.d.ts.map +1 -1
  325. package/dist/mcp-client/index.js +1 -0
  326. package/dist/mcp-client/index.js.map +1 -1
  327. package/dist/mcp-client/screen-memory-local.d.ts +61 -0
  328. package/dist/mcp-client/screen-memory-local.d.ts.map +1 -0
  329. package/dist/mcp-client/screen-memory-local.js +336 -0
  330. package/dist/mcp-client/screen-memory-local.js.map +1 -0
  331. package/dist/observability/routes.d.ts +3 -3
  332. package/dist/onboarding/types.d.ts +7 -0
  333. package/dist/onboarding/types.d.ts.map +1 -1
  334. package/dist/onboarding/types.js.map +1 -1
  335. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  336. package/dist/server/agent-chat-plugin.js +26 -0
  337. package/dist/server/agent-chat-plugin.js.map +1 -1
  338. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  339. package/dist/server/agents-bundle.d.ts.map +1 -1
  340. package/dist/server/agents-bundle.js +22 -7
  341. package/dist/server/agents-bundle.js.map +1 -1
  342. package/dist/styles/agent-native.css +5 -0
  343. package/dist/templates/default/app/global.css +35 -35
  344. package/docs/content/locales/ar-SA/template-design.mdx +6 -0
  345. package/docs/content/locales/de-DE/template-design.mdx +8 -0
  346. package/docs/content/locales/es-ES/template-design.mdx +10 -2
  347. package/docs/content/locales/fr-FR/template-design.mdx +10 -2
  348. package/docs/content/locales/hi-IN/template-design.mdx +7 -0
  349. package/docs/content/locales/ja-JP/template-design.mdx +7 -0
  350. package/docs/content/locales/ko-KR/template-design.mdx +7 -0
  351. package/docs/content/locales/pt-BR/template-design.mdx +9 -2
  352. package/docs/content/locales/zh-CN/template-design.mdx +6 -0
  353. package/docs/content/locales/zh-TW/template-design.mdx +6 -0
  354. package/docs/content/template-design.mdx +9 -2
  355. package/package.json +1 -1
  356. package/src/templates/default/app/global.css +35 -35
  357. package/corpus/templates/design/app/components/design/VariantGrid.tsx +0 -189
  358. package/corpus/templates/design/app/components/design/VariantHandoffCard.tsx +0 -115
  359. package/corpus/templates/design/app/hooks/use-variant-flow.ts +0 -342
@@ -0,0 +1,202 @@
1
+ import { defineAction } from "@agent-native/core";
2
+ import { buildDeepLink } from "@agent-native/core/server";
3
+ import { assertAccess } from "@agent-native/core/sharing";
4
+ import { eq } from "drizzle-orm";
5
+ import { z } from "zod";
6
+
7
+ import { getDb, schema } from "../server/db/index.js";
8
+ import "../server/db/index.js"; // ensure registerShareableResource runs
9
+ import {
10
+ INLINE_DEFAULT_CAPABILITIES,
11
+ hasCapability,
12
+ } from "../shared/design-source-capabilities.js";
13
+ import { resolveTweaksToCssVars } from "../shared/resolve-tweaks.js";
14
+
15
+ // ---------------------------------------------------------------------------
16
+ // Token-edit patch schema (mirrors preview-design-token-edit)
17
+ // ---------------------------------------------------------------------------
18
+
19
+ const tokenEditSchema = z.object({
20
+ /** The CSS custom property to update, e.g. "--primary-color". */
21
+ cssVar: z
22
+ .string()
23
+ .startsWith("--")
24
+ // The token name is spliced raw into a `:root { … }` rule, so constrain it
25
+ // to a safe CSS custom-property ident (leading "--" plus ident chars only).
26
+ .regex(
27
+ /^--[-_a-zA-Z0-9]+$/,
28
+ "cssVar must be a valid CSS custom property name (-- followed by letters, digits, hyphens, or underscores).",
29
+ )
30
+ .describe("CSS custom property to edit"),
31
+ /** New value string, e.g. "#3B82F6" or "0.75rem". */
32
+ value: z
33
+ .string()
34
+ // The value is spliced raw into the `:root { … }` block; reject characters
35
+ // that could terminate the rule (`}`) or break out of a `<style>` (`<`).
36
+ .refine((v) => !/[}<]/.test(v), 'Token value may not contain "}" or "<".')
37
+ .describe("New value for the token"),
38
+ });
39
+
40
+ /** Editor deep link so external agents can surface "Open design". */
41
+ function designDeepLink(designId: string): string {
42
+ return buildDeepLink({
43
+ app: "design",
44
+ view: "editor",
45
+ params: { designId },
46
+ });
47
+ }
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Action — persist token edit through the Tweaks loop (Tier-A)
51
+ // ---------------------------------------------------------------------------
52
+
53
+ export default defineAction({
54
+ description:
55
+ "Persist one or more design token edits for a design. " +
56
+ "Tier-A (inline/Alpine): routes the edit through the same mechanism as " +
57
+ "apply-tweaks — merges the value into designs.data.tweakSelections so the " +
58
+ "tuned token survives reload and is visible in the Tokens panel. " +
59
+ "Source file write-back (Tier-B, real apps) is gated behind a capability " +
60
+ "check and currently returns a 'planned' advisory — no file is written.",
61
+ schema: z.object({
62
+ designId: z.string().describe("Design project ID"),
63
+ edits: z
64
+ .array(tokenEditSchema)
65
+ .min(1)
66
+ .describe("One or more { cssVar, value } edits to persist"),
67
+ sourceRef: z
68
+ .string()
69
+ .optional()
70
+ .describe(
71
+ "Opaque source connection ref for real-app (localhost/fusion) " +
72
+ "write-back. Omit for inline designs.",
73
+ ),
74
+ }),
75
+ run: async ({ designId, edits, sourceRef }) => {
76
+ await assertAccess("design", designId, "editor");
77
+
78
+ // ------------------------------------------------------------------
79
+ // Capability check — real-app write-back (writeTokens) is gated
80
+ // ------------------------------------------------------------------
81
+ // TODO(fusion): When the bridge proves writeTokens capability, pass the
82
+ // resolved capabilities from the active source connection here instead of
83
+ // falling back to INLINE_DEFAULT_CAPABILITIES.
84
+ const caps = INLINE_DEFAULT_CAPABILITIES;
85
+ const canWriteTokens = hasCapability(caps, "writeTokens");
86
+ // sourceRef will be used once real write-back is implemented
87
+ void sourceRef;
88
+
89
+ const db = getDb();
90
+ const now = new Date().toISOString();
91
+
92
+ // ------------------------------------------------------------------
93
+ // Load current design data
94
+ // ------------------------------------------------------------------
95
+ const [existingDesign] = await db
96
+ .select({ data: schema.designs.data })
97
+ .from(schema.designs)
98
+ .where(eq(schema.designs.id, designId));
99
+
100
+ let prevData: Record<string, unknown> = {};
101
+ if (existingDesign?.data) {
102
+ try {
103
+ const parsed = JSON.parse(existingDesign.data);
104
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
105
+ prevData = parsed;
106
+ }
107
+ } catch {
108
+ // Stale/invalid JSON — start fresh but never drop the column.
109
+ }
110
+ }
111
+
112
+ type TweakDef = Parameters<typeof resolveTweaksToCssVars>[0][number];
113
+ const tweaks: TweakDef[] = Array.isArray(prevData.tweaks)
114
+ ? (prevData.tweaks as TweakDef[])
115
+ : [];
116
+
117
+ // Map cssVar -> tweakId for known tweaks
118
+ const cssVarToTweakId = new Map<string, string>();
119
+ for (const t of tweaks) {
120
+ if (t.cssVar) cssVarToTweakId.set(t.cssVar, t.id);
121
+ }
122
+
123
+ const prevSelections: Record<string, string | number | boolean> =
124
+ prevData.tweakSelections &&
125
+ typeof prevData.tweakSelections === "object" &&
126
+ !Array.isArray(prevData.tweakSelections)
127
+ ? (prevData.tweakSelections as Record<
128
+ string,
129
+ string | number | boolean
130
+ >)
131
+ : {};
132
+
133
+ // ------------------------------------------------------------------
134
+ // Merge edits into tweakSelections
135
+ // ------------------------------------------------------------------
136
+ // For each edit:
137
+ // - If the cssVar maps to a known tweak, persist by tweakId (the standard
138
+ // Tweaks loop key) so apply-tweaks, get-design-snapshot, and the bridge
139
+ // script all pick it up consistently.
140
+ // - If the cssVar does NOT map to a known tweak, we synthesise a minimal
141
+ // tweak entry and persist via the raw cssVar key so the bridge script
142
+ // (`TWEAK_BRIDGE_SCRIPT`) still applies it.
143
+ const newSelections = { ...prevSelections };
144
+
145
+ for (const { cssVar, value } of edits) {
146
+ const tweakId = cssVarToTweakId.get(cssVar);
147
+ if (tweakId) {
148
+ newSelections[tweakId] = value;
149
+ } else {
150
+ // Fallback: persist using the cssVar as the key — the bridge script
151
+ // calls documentElement.style.setProperty with the key directly, so
152
+ // any key starting with "--" is applied as-is.
153
+ newSelections[cssVar] = value;
154
+ }
155
+ }
156
+
157
+ // ------------------------------------------------------------------
158
+ // Persist — same atomic write as apply-tweaks
159
+ // ------------------------------------------------------------------
160
+ const mergedData: Record<string, unknown> = {
161
+ ...prevData,
162
+ tweakSelections: newSelections,
163
+ tweaksAppliedAt: now,
164
+ };
165
+
166
+ await db
167
+ .update(schema.designs)
168
+ .set({ data: JSON.stringify(mergedData), updatedAt: now })
169
+ .where(eq(schema.designs.id, designId));
170
+
171
+ // Resolve to full CSS var map for the response (so the caller can send a
172
+ // tweak-values postMessage without a separate round-trip)
173
+ const resolvedCssVars = resolveTweaksToCssVars(tweaks, newSelections);
174
+
175
+ return {
176
+ designId,
177
+ persisted: true,
178
+ /**
179
+ * Advisory when real-app source write-back is not yet available.
180
+ * `null` means the edit was fully applied via the Tweaks loop.
181
+ */
182
+ writeTokensAdvisory: canWriteTokens
183
+ ? null
184
+ : "Token source write-back is not yet available for this source tier. " +
185
+ "The value is persisted in the design's tweakSelections and reflected " +
186
+ "live via the CSS-var bridge. Connect Builder to unlock real write-back.",
187
+ /** Resolved CSS var map — pass directly to the tweak-values postMessage. */
188
+ resolvedCssVars,
189
+ deepLink: designDeepLink(designId),
190
+ };
191
+ },
192
+ link: ({ result }) => {
193
+ if (!result || typeof result !== "object") return null;
194
+ const designId = (result as { designId?: string }).designId;
195
+ if (!designId) return null;
196
+ return {
197
+ url: designDeepLink(designId),
198
+ label: "Open design",
199
+ view: "editor",
200
+ };
201
+ },
202
+ });
@@ -0,0 +1,450 @@
1
+ /**
2
+ * apply-motion-edit — ATOMIC motion timeline write (§6.3).
3
+ *
4
+ * One action does all of:
5
+ * 1. Validate the timeline against the design's source capabilities.
6
+ * 2. Persist the `motion_timeline` row (insert or update).
7
+ * 3. Compile the tracks into deterministic CSS.
8
+ * 4. Inject/replace the managed `<style data-agent-native-motion>` block inside
9
+ * the design's HTML content, using the same persist path as apply-visual-edit
10
+ * (Yjs/collab + SQL, via agentEnterDocument / applyText / seedFromText).
11
+ * 5. Update `compiledHash` on the row to guard against drift.
12
+ * 6. Return a diff summary (bytes before/after, track count, hash).
13
+ *
14
+ * Never writes unless all steps succeed. Scrubbing/preview is handled by the
15
+ * separate `motion-preview` postMessage path on the frontend — this action is
16
+ * the deliberate "Write to CSS" commit step.
17
+ */
18
+
19
+ import { defineAction } from "@agent-native/core";
20
+ import {
21
+ agentEnterDocument,
22
+ agentLeaveDocument,
23
+ applyText,
24
+ getText,
25
+ hasCollabState,
26
+ seedFromText,
27
+ } from "@agent-native/core/collab";
28
+ import {
29
+ getRequestOrgId,
30
+ getRequestUserEmail,
31
+ } from "@agent-native/core/server/request-context";
32
+ import { accessFilter, assertAccess } from "@agent-native/core/sharing";
33
+ import { and, eq } from "drizzle-orm";
34
+ import { nanoid } from "nanoid";
35
+ import { z } from "zod";
36
+
37
+ import { getDb, schema } from "../server/db/index.js";
38
+ import "../server/db/index.js"; // ensure registerShareableResource runs
39
+ import { compile } from "../shared/motion-compiler.js";
40
+ import type { MotionTrack } from "../shared/motion-timeline.js";
41
+
42
+ // ─── Zod schemas ─────────────────────────────────────────────────────────────
43
+
44
+ const keyframeSchema = z.object({
45
+ t: z
46
+ .number()
47
+ .min(0)
48
+ .max(1)
49
+ .describe("Normalised time in [0, 1] where 0 = 0% and 1 = 100%."),
50
+ value: z.string().describe("CSS property value at this keyframe."),
51
+ ease: z
52
+ .string()
53
+ .optional()
54
+ .describe(
55
+ 'Per-keyframe easing, e.g. "ease-out" or "cubic-bezier(0.4,0,0.2,1)".',
56
+ ),
57
+ });
58
+
59
+ const trackSchema = z.object({
60
+ targetNodeId: z
61
+ .string()
62
+ .describe(
63
+ "data-agent-native-node-id of the target DOM element. " +
64
+ "Must be stamped on the element (ensureCodeLayerNodeIdsInHtml).",
65
+ ),
66
+ property: z
67
+ .string()
68
+ .describe('CSS property to animate, e.g. "opacity" or "transform".'),
69
+ keyframes: z
70
+ .array(keyframeSchema)
71
+ .min(1)
72
+ .describe("At least one keyframe is required per track."),
73
+ });
74
+
75
+ // ─── Helpers ──────────────────────────────────────────────────────────────────
76
+
77
+ const MOTION_STYLE_OPEN = "<style data-agent-native-motion>";
78
+ const MOTION_STYLE_CLOSE = "</style>";
79
+
80
+ /**
81
+ * Bounded, case-insensitive matcher for any HTML `</style>` end tag
82
+ * (`</style >`, `</STYLE>`, …). Used to locate the close of the managed block
83
+ * without a naive `indexOf("</style>")` that would miss case/whitespace
84
+ * variants — and to detect any `</style` sequence smuggled into compiled CSS.
85
+ */
86
+ const STYLE_CLOSE_RE = /<\s*\/\s*style\b[^>]*>/i;
87
+
88
+ /**
89
+ * Reject `<`, `>`, and any `</style` sequence in a CSS token before it is
90
+ * compiled into the managed `<style>` block. Keyframe `value` / `ease` come
91
+ * straight from the caller and are interpolated raw into the CSS, so an
92
+ * unescaped `</style>` (or angle bracket) would break out of the style block
93
+ * and inject arbitrary markup. Returns the original string when safe; throws on
94
+ * a breakout attempt so the whole atomic write fails loudly rather than
95
+ * persisting a poisoned block.
96
+ */
97
+ function assertSafeCssToken(value: string, field: string): string {
98
+ if (/[<>]/.test(value) || /<\s*\/\s*style/i.test(value)) {
99
+ throw new Error(
100
+ `Invalid ${field}: "<", ">", and "</style" are not allowed in motion CSS values.`,
101
+ );
102
+ }
103
+ return value;
104
+ }
105
+
106
+ /**
107
+ * Validate that a CSS property name is a safe CSS identifier.
108
+ *
109
+ * Accepts standard and vendor-prefixed property names (e.g. "opacity",
110
+ * "transform", "-webkit-transform") and nothing else. Rejects any string
111
+ * containing `:`, `;`, `{`, `}`, `<`, `/`, whitespace, or other characters
112
+ * that could break out of a CSS declaration or `<style>` block context when
113
+ * the property is interpolated as `${property}: ${value};` inside
114
+ * `@keyframes`.
115
+ *
116
+ * Uses the strict CSS ident regex `^-?[a-zA-Z][a-zA-Z0-9-]*$` which covers
117
+ * every real animatable CSS property while blocking injection payloads.
118
+ */
119
+ function assertSafeCssProperty(property: string, field: string): string {
120
+ if (!/^-?[a-zA-Z][a-zA-Z0-9-]*$/.test(property)) {
121
+ throw new Error(
122
+ `Invalid ${field}: "${property}" is not a valid CSS property identifier. ` +
123
+ "Only ASCII letters, digits, hyphens, and an optional leading hyphen are allowed.",
124
+ );
125
+ }
126
+ return property;
127
+ }
128
+
129
+ /**
130
+ * Inject or replace the managed `<style data-agent-native-motion>` block in
131
+ * the HTML content. Inserts before `</head>` when not already present.
132
+ */
133
+ function injectMotionStyle(html: string, css: string): string {
134
+ const open = MOTION_STYLE_OPEN;
135
+ const close = MOTION_STYLE_CLOSE;
136
+ const openIdx = html.indexOf(open);
137
+
138
+ if (openIdx !== -1) {
139
+ // Find the matching closing tag after the open tag using a bounded regex
140
+ // (tolerates `</style >`, `</STYLE>`, etc.) instead of a literal indexOf.
141
+ const after = html.slice(openIdx + open.length);
142
+ const closeMatch = STYLE_CLOSE_RE.exec(after);
143
+ if (closeMatch) {
144
+ const closeIdx = openIdx + open.length + closeMatch.index;
145
+ // Replace the existing block.
146
+ return (
147
+ html.slice(0, openIdx) + open + "\n" + css + "\n" + html.slice(closeIdx)
148
+ );
149
+ }
150
+ }
151
+
152
+ // Not found — insert before </head> or, if there is no <head>, at the top.
153
+ const headClose = html.lastIndexOf("</head>");
154
+ const block = `${open}\n${css}\n${close}`;
155
+ if (headClose !== -1) {
156
+ return html.slice(0, headClose) + block + "\n" + html.slice(headClose);
157
+ }
158
+ return block + "\n" + html;
159
+ }
160
+
161
+ async function liveFileContent(
162
+ fileId: string,
163
+ storedContent: string,
164
+ ): Promise<string> {
165
+ try {
166
+ if (await hasCollabState(fileId)) {
167
+ const live = await getText(fileId, "content");
168
+ if (typeof live === "string") return live;
169
+ }
170
+ } catch {
171
+ // Best-effort; SQL is the fallback.
172
+ }
173
+ return storedContent;
174
+ }
175
+
176
+ async function persistFileContent(
177
+ fileId: string,
178
+ designId: string,
179
+ content: string,
180
+ now: string,
181
+ ): Promise<void> {
182
+ const db = getDb();
183
+ agentEnterDocument(fileId);
184
+ try {
185
+ await db
186
+ .update(schema.designFiles)
187
+ .set({ content, updatedAt: now })
188
+ .where(eq(schema.designFiles.id, fileId));
189
+
190
+ if (await hasCollabState(fileId)) {
191
+ await applyText(fileId, content, "content", "agent");
192
+ } else {
193
+ await seedFromText(fileId, content);
194
+ }
195
+
196
+ // guard:allow-unscoped — the action's run() asserts editor access via
197
+ // assertAccess("design", designId, "editor") before this helper is
198
+ // invoked; this only touches the addressed design row's updatedAt.
199
+ await db
200
+ .update(schema.designs)
201
+ .set({ updatedAt: now })
202
+ .where(eq(schema.designs.id, designId));
203
+ } finally {
204
+ agentLeaveDocument(fileId);
205
+ }
206
+ }
207
+
208
+ // ─── Action ───────────────────────────────────────────────────────────────────
209
+
210
+ export default defineAction({
211
+ description:
212
+ "Atomically write a motion timeline to a design. " +
213
+ "Persists the motion_timeline row, compiles tracks to CSS, injects the " +
214
+ "managed <style data-agent-native-motion> block into the design's HTML, " +
215
+ "and updates compiledHash — all in one atomic step. " +
216
+ "This is the 'Write to CSS' commit path; preview/scrubbing uses the " +
217
+ "motion-preview postMessage bridge, NOT this action.",
218
+ schema: z.object({
219
+ designId: z.string().describe("Design project ID."),
220
+ fileId: z
221
+ .string()
222
+ .optional()
223
+ .describe(
224
+ "Target design_files.id. Defaults to the design's primary index.html " +
225
+ "when omitted. Required for multi-file designs.",
226
+ ),
227
+ timelineId: z
228
+ .string()
229
+ .optional()
230
+ .describe(
231
+ "Existing motion_timeline.id to update. Omit to create a new timeline.",
232
+ ),
233
+ sourceRef: z
234
+ .string()
235
+ .optional()
236
+ .describe(
237
+ "Opaque source ref (fileId for inline, routeId for real apps). " +
238
+ "Stored on the timeline row for scoping.",
239
+ ),
240
+ tracks: z
241
+ .preprocess(
242
+ (v) => (typeof v === "string" ? JSON.parse(v) : v),
243
+ z.array(trackSchema).min(1),
244
+ )
245
+ .describe(
246
+ "Animation tracks. Each track targets one DOM element by " +
247
+ "data-agent-native-node-id and animates one CSS property.",
248
+ ),
249
+ durationMs: z
250
+ .number()
251
+ .int()
252
+ .positive()
253
+ .default(300)
254
+ .describe("Total animation duration in milliseconds."),
255
+ defaultEase: z
256
+ .string()
257
+ .default("ease")
258
+ .describe(
259
+ "Default easing applied to keyframe intervals that omit ease. " +
260
+ 'E.g. "ease", "ease-in-out", "cubic-bezier(0.4,0,0.2,1)".',
261
+ ),
262
+ label: z
263
+ .string()
264
+ .optional()
265
+ .describe("Optional human-readable label for the timeline."),
266
+ includeContent: z
267
+ .boolean()
268
+ .optional()
269
+ .default(false)
270
+ .describe("Include the full patched HTML in the response (large)."),
271
+ }),
272
+ run: async ({
273
+ designId,
274
+ fileId: fileIdInput,
275
+ timelineId,
276
+ sourceRef,
277
+ tracks,
278
+ durationMs,
279
+ defaultEase,
280
+ includeContent,
281
+ }) => {
282
+ await assertAccess("design", designId, "editor");
283
+
284
+ const db = getDb();
285
+ const now = new Date().toISOString();
286
+
287
+ // ── 1. Resolve the target design file ──────────────────────────────────
288
+ const conditions = [
289
+ accessFilter(schema.designs, schema.designShares),
290
+ eq(schema.designFiles.designId, designId),
291
+ ];
292
+ if (fileIdInput) {
293
+ conditions.push(eq(schema.designFiles.id, fileIdInput));
294
+ } else {
295
+ conditions.push(eq(schema.designFiles.filename, "index.html"));
296
+ }
297
+
298
+ const [file] = await db
299
+ .select({
300
+ id: schema.designFiles.id,
301
+ designId: schema.designFiles.designId,
302
+ filename: schema.designFiles.filename,
303
+ content: schema.designFiles.content,
304
+ })
305
+ .from(schema.designFiles)
306
+ .innerJoin(
307
+ schema.designs,
308
+ eq(schema.designFiles.designId, schema.designs.id),
309
+ )
310
+ .where(and(...conditions))
311
+ .limit(1);
312
+
313
+ if (!file) {
314
+ throw new Error(
315
+ fileIdInput
316
+ ? `Design file not found: ${fileIdInput}`
317
+ : `No index.html found for design: ${designId}`,
318
+ );
319
+ }
320
+
321
+ const fileId = file.id;
322
+ const currentContent = await liveFileContent(fileId, file.content ?? "");
323
+
324
+ // ── 2. Compile tracks → CSS ─────────────────────────────────────────────
325
+ const typedTracks = tracks as MotionTrack[];
326
+
327
+ // Reject CSS-injection vectors in caller-supplied track properties,
328
+ // keyframe values, and easing strings before they are compiled into the
329
+ // managed <style> block.
330
+ for (const track of typedTracks) {
331
+ assertSafeCssProperty(track.property, "track.property");
332
+ for (const kf of track.keyframes) {
333
+ assertSafeCssToken(kf.value, "keyframe value");
334
+ if (kf.ease !== undefined) {
335
+ assertSafeCssToken(kf.ease, "keyframe ease");
336
+ }
337
+ }
338
+ }
339
+ assertSafeCssToken(defaultEase, "defaultEase");
340
+
341
+ const { css, hash } = compile({
342
+ id: timelineId ?? "",
343
+ designId,
344
+ sourceRef: sourceRef ?? null,
345
+ filePath: null,
346
+ tracks: typedTracks,
347
+ durationMs,
348
+ defaultEase,
349
+ compiledHash: null,
350
+ createdAt: now,
351
+ updatedAt: now,
352
+ });
353
+
354
+ // ── 3. Inject the managed CSS block into the HTML ───────────────────────
355
+ const patchedContent = injectMotionStyle(currentContent, css);
356
+ const bytesBefore = currentContent.length;
357
+ const bytesAfter = patchedContent.length;
358
+
359
+ // ── 4. Pre-flight the motion_timeline row write ─────────────────────────
360
+ // Resolve everything that can fail (existence + ownership) BEFORE touching
361
+ // content, so we never persist HTML for a row that can't be written.
362
+ const tracksJson = JSON.stringify(typedTracks);
363
+ const resolvedTimelineId = timelineId ?? nanoid();
364
+
365
+ let insertOwnerEmail: string | null = null;
366
+ let insertOrgId: string | null = null;
367
+
368
+ if (timelineId) {
369
+ // Update existing row — verify it belongs to this design.
370
+ const [existing] = await db
371
+ .select({ id: schema.motionTimeline.id })
372
+ .from(schema.motionTimeline)
373
+ .where(
374
+ and(
375
+ eq(schema.motionTimeline.id, timelineId),
376
+ eq(schema.motionTimeline.designId, designId),
377
+ ),
378
+ )
379
+ .limit(1);
380
+
381
+ if (!existing) {
382
+ throw new Error(
383
+ `motion_timeline not found for this design: ${timelineId}`,
384
+ );
385
+ }
386
+ } else {
387
+ // Insert new row — derive ownership from the request context (same
388
+ // pattern as create-design-state and other create actions).
389
+ insertOwnerEmail = getRequestUserEmail() ?? null;
390
+ if (!insertOwnerEmail) throw new Error("no authenticated user");
391
+ insertOrgId = getRequestOrgId() ?? null;
392
+ }
393
+
394
+ // ── 5. Persist the motion_timeline row FIRST (atomic SQL portion) ───────
395
+ // The timeline row is written before the HTML so that a failure in the
396
+ // HTML/collab write step cannot leave the design content mutated without a
397
+ // corresponding row. The reverse (HTML first) was a false atomicity
398
+ // guarantee: if the row write failed after the HTML write, the managed
399
+ // <style> block would be permanently out of sync with the DB state.
400
+ await db.transaction(async (tx) => {
401
+ if (timelineId) {
402
+ await tx
403
+ .update(schema.motionTimeline)
404
+ .set({
405
+ tracks: tracksJson,
406
+ durationMs,
407
+ defaultEase,
408
+ compiledHash: hash,
409
+ sourceRef: sourceRef ?? null,
410
+ updatedAt: now,
411
+ })
412
+ .where(eq(schema.motionTimeline.id, timelineId));
413
+ } else {
414
+ await tx.insert(schema.motionTimeline).values({
415
+ id: resolvedTimelineId,
416
+ designId,
417
+ sourceRef: sourceRef ?? null,
418
+ filePath: null,
419
+ tracks: tracksJson,
420
+ durationMs,
421
+ defaultEase,
422
+ compiledHash: hash,
423
+ ownerEmail: insertOwnerEmail as string,
424
+ orgId: insertOrgId,
425
+ createdAt: now,
426
+ updatedAt: now,
427
+ });
428
+ }
429
+ });
430
+
431
+ // ── 6. Persist the patched HTML content SECOND (Yjs/collab + SQL) ───────
432
+ // Written after the row so a collab/SQL failure here leaves the timeline row
433
+ // accurate (correct tracks + hash) and the stale HTML can be recompiled on
434
+ // the next apply-motion-edit call via compiledHash drift detection.
435
+ await persistFileContent(fileId, designId, patchedContent, now);
436
+
437
+ return {
438
+ timelineId: resolvedTimelineId,
439
+ designId,
440
+ fileId,
441
+ trackCount: typedTracks.length,
442
+ compiledHash: hash,
443
+ bytesBefore,
444
+ bytesAfter,
445
+ bytesDelta: bytesAfter - bytesBefore,
446
+ persisted: true,
447
+ patchedContent: includeContent ? patchedContent : undefined,
448
+ };
449
+ },
450
+ });