@agent-native/core 0.80.7 → 0.80.9

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 (302) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +18 -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/mcp.ts +10 -0
  16. package/corpus/core/src/cli/skills.ts +58 -12
  17. package/corpus/core/src/client/AssistantChat.tsx +70 -27
  18. package/corpus/core/src/client/analytics.ts +3 -1
  19. package/corpus/core/src/client/error-format.ts +25 -0
  20. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +13 -11
  21. package/corpus/core/src/client/guided-questions.tsx +32 -4
  22. package/corpus/core/src/client/onboarding/OnboardingPanel.tsx +33 -1
  23. package/corpus/core/src/client/resources/BuiltinCapabilityDetail.tsx +8 -0
  24. package/corpus/core/src/client/resources/use-builtin-capabilities.ts +4 -2
  25. package/corpus/core/src/client/session-replay.ts +34 -0
  26. package/corpus/core/src/client/sharing/ShareButton.tsx +106 -21
  27. package/corpus/core/src/coding-tools/index.ts +1 -1
  28. package/corpus/core/src/mcp/index.ts +2 -0
  29. package/corpus/core/src/mcp/screen-memory-stdio.ts +290 -0
  30. package/corpus/core/src/mcp-client/builtin-capabilities.ts +13 -1
  31. package/corpus/core/src/mcp-client/index.ts +9 -0
  32. package/corpus/core/src/mcp-client/screen-memory-local.ts +461 -0
  33. package/corpus/core/src/onboarding/types.ts +7 -0
  34. package/corpus/core/src/server/agent-chat-plugin.ts +25 -0
  35. package/corpus/core/src/server/agents-bundle.ts +35 -7
  36. package/corpus/core/src/styles/agent-native.css +5 -0
  37. package/corpus/core/src/templates/default/app/global.css +35 -35
  38. package/corpus/templates/analytics/AGENTS.md +6 -0
  39. package/corpus/templates/analytics/actions/github-repo-files.ts +9 -0
  40. package/corpus/templates/analytics/app/components/layout/Layout.tsx +4 -4
  41. package/corpus/templates/analytics/app/global.css +144 -40
  42. package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +1 -1
  43. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +1 -1
  44. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +1 -1
  45. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +4 -4
  46. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -9
  47. package/corpus/templates/analytics/changelog/2026-06-29-agents-can-search-and-read-connected-github-repositories-whe.md +6 -0
  48. package/corpus/templates/analytics/changelog/2026-06-29-sessions-and-explorer-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
  49. package/corpus/templates/assets/app/components/layout/Layout.tsx +3 -1
  50. package/corpus/templates/assets/app/components/library/LibraryPresetGrid.tsx +1 -1
  51. package/corpus/templates/assets/app/global.css +99 -35
  52. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
  53. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +4 -4
  54. package/corpus/templates/assets/app/routes/library.tsx +4 -4
  55. package/corpus/templates/assets/changelog/2026-06-29-asset-library-and-detail-layouts-adapt-when-the-agent-sidebar.md +6 -0
  56. package/corpus/templates/brain/app/components/layout/Layout.tsx +1 -1
  57. package/corpus/templates/brain/app/global.css +63 -35
  58. package/corpus/templates/brain/app/routes/search.tsx +1 -1
  59. package/corpus/templates/brain/app/routes/settings.tsx +1 -1
  60. package/corpus/templates/brain/app/routes/sources.tsx +2 -2
  61. package/corpus/templates/brain/changelog/2026-06-29-search-settings-and-source-layouts-adapt-when-the-agent-sidebar.md +6 -0
  62. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +4 -4
  63. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +3 -2
  64. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +4 -2
  65. package/corpus/templates/calendar/app/global.css +69 -35
  66. package/corpus/templates/calendar/changelog/2026-06-29-event-and-find-time-layouts-adapt-when-the-agent-sidebar-is.md +6 -0
  67. package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -1
  68. package/corpus/templates/chat/app/global.css +35 -35
  69. package/corpus/templates/chat/changelog/2026-06-29-chat-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  70. package/corpus/templates/clips/AGENTS.md +13 -0
  71. package/corpus/templates/clips/actions/get-screen-memory-status.ts +12 -0
  72. package/corpus/templates/clips/actions/query-screen-memory-context.ts +29 -0
  73. package/corpus/templates/clips/actions/view-screen.ts +2 -2
  74. package/corpus/templates/clips/app/components/library/library-layout.tsx +1 -1
  75. package/corpus/templates/clips/app/global.css +47 -35
  76. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +3 -3
  77. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +45 -18
  78. package/corpus/templates/clips/changelog/2026-06-29-meeting-detail-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-06-29-screen-memory-keeps-a-local-rolling-screen-buffer-for-agent-context.md +5 -0
  80. package/corpus/templates/clips/changelog/2026-06-29-video-chat-is-easier-to-find-on-recording-pages-and-s3-setup.md +6 -0
  81. package/corpus/templates/clips/chrome-extension/src/styles.css +39 -39
  82. package/corpus/templates/clips/desktop/src/app.tsx +290 -1
  83. package/corpus/templates/clips/desktop/src/shared/config.ts +83 -0
  84. package/corpus/templates/clips/desktop/src/styles.css +67 -52
  85. package/corpus/templates/clips/desktop/src-tauri/src/config.rs +50 -0
  86. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +17 -0
  87. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +13 -13
  88. package/corpus/templates/clips/desktop/src-tauri/src/screen_memory.rs +990 -0
  89. package/corpus/templates/clips/server/plugins/onboarding.ts +2 -0
  90. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +69 -16
  91. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +3 -3
  92. package/corpus/templates/content/app/components/layout/Layout.tsx +1 -1
  93. package/corpus/templates/content/app/global.css +29 -15
  94. package/corpus/templates/content/changelog/2026-06-29-database-gallery-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  95. package/corpus/templates/content/changelog/2026-06-29-read-only-pages-no-longer-try-to-autosave-or-open-editor-onl.md +6 -0
  96. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +25 -20
  97. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +8 -0
  98. package/corpus/templates/design/.agents/skills/export-handoff/SKILL.md +8 -0
  99. package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +13 -0
  100. package/corpus/templates/design/AGENTS.md +24 -14
  101. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +1054 -0
  102. package/corpus/templates/design/actions/apply-visual-edit.ts +24 -4
  103. package/corpus/templates/design/actions/edit-design.ts +6 -3
  104. package/corpus/templates/design/actions/export-coding-handoff.ts +12 -4
  105. package/corpus/templates/design/actions/generate-design.ts +6 -21
  106. package/corpus/templates/design/actions/generate-screens.ts +48 -5
  107. package/corpus/templates/design/actions/get-design.ts +1 -0
  108. package/corpus/templates/design/actions/present-design-variants.ts +355 -30
  109. package/corpus/templates/design/actions/update-design.ts +47 -10
  110. package/corpus/templates/design/actions/update-file.ts +33 -8
  111. package/corpus/templates/design/actions/view-screen.ts +89 -31
  112. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +389 -71
  113. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +1 -1
  114. package/corpus/templates/design/app/components/design/EditPanel.tsx +596 -95
  115. package/corpus/templates/design/app/components/design/LayersPanel.tsx +428 -221
  116. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +1197 -228
  117. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +94 -217
  118. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +28 -14
  119. package/corpus/templates/design/app/components/design/index.ts +0 -1
  120. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +228 -170
  121. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +140 -13
  122. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +39 -8
  123. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +4 -2
  124. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +75 -5
  125. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +37 -12
  126. package/corpus/templates/design/app/components/design/inspector/index.ts +1 -0
  127. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +17 -4
  128. package/corpus/templates/design/app/components/design/types.ts +9 -0
  129. package/corpus/templates/design/app/components/layout/Layout.tsx +23 -5
  130. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +448 -259
  131. package/corpus/templates/design/app/global.css +77 -49
  132. package/corpus/templates/design/app/hooks/use-question-flow.ts +2 -2
  133. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +8 -1
  134. package/corpus/templates/design/app/i18n/zh-TW.ts +4 -0
  135. package/corpus/templates/design/app/i18n-data.ts +55 -3
  136. package/corpus/templates/design/app/pages/DesignEditor.tsx +2783 -658
  137. package/corpus/templates/design/app/root.tsx +1 -1
  138. package/corpus/templates/design/changelog/2026-06-29-agent-edits-to-a-design-are-no-longer-occasionally-dropped-o.md +6 -0
  139. package/corpus/templates/design/changelog/2026-06-29-box-shadow-and-gradient-colors-keep-named-css-colors-like-re.md +6 -0
  140. package/corpus/templates/design/changelog/2026-06-29-containers-no-longer-block-selecting-nested-elements-and-nor.md +6 -0
  141. package/corpus/templates/design/changelog/2026-06-29-cut-cmd-ctrl-x-now-removes-the-selected-element-to-the-clipb.md +6 -0
  142. package/corpus/templates/design/changelog/2026-06-29-deleting-with-multiple-layers-selected-in-the-layers-panel-n.md +6 -0
  143. package/corpus/templates/design/changelog/2026-06-29-design-agents-now-generate-and-refine-variants-from-stronger.md +6 -0
  144. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +6 -0
  145. package/corpus/templates/design/changelog/2026-06-29-design-questions-now-appear-as-a-cleaner-canvas-intake-with-.md +6 -0
  146. package/corpus/templates/design/changelog/2026-06-29-design-variant-directions-now-appear-as-board-screens-with-chat-buttons.md +5 -0
  147. package/corpus/templates/design/changelog/2026-06-29-duplicating-a-screen-now-gives-the-copy-its-own-layer-ids-so.md +6 -0
  148. package/corpus/templates/design/changelog/2026-06-29-element-selection-no-longer-snaps-back-to-the-previous-layer.md +6 -0
  149. package/corpus/templates/design/changelog/2026-06-29-frame-resize-measurements-now-stay-beside-the-cursor-while-d.md +6 -0
  150. package/corpus/templates/design/changelog/2026-06-29-hiding-and-re-showing-a-gradient-fill-now-preserves-each-sto.md +6 -0
  151. package/corpus/templates/design/changelog/2026-06-29-inspector-number-fields-have-cleaner-tooltips-and-draggable-.md +6 -0
  152. package/corpus/templates/design/changelog/2026-06-29-layer-locking-layer-nesting-generated-question-intake-and-im.md +6 -0
  153. package/corpus/templates/design/changelog/2026-06-29-layer-names-in-the-sidebar-can-scroll-horizontally-instead-o.md +6 -0
  154. package/corpus/templates/design/changelog/2026-06-29-layer-nesting-in-the-sidebar-now-uses-tighter-spacing-so-nam.md +6 -0
  155. package/corpus/templates/design/changelog/2026-06-29-layers-no-longer-repeat-document-wrapper-rows-when-a-screen-.md +6 -0
  156. package/corpus/templates/design/changelog/2026-06-29-overview-previews-now-keep-element-hover-and-selection-tied-.md +6 -0
  157. package/corpus/templates/design/changelog/2026-06-29-picking-a-design-direction-now-submits-to-the-agent-right-aw.md +6 -0
  158. package/corpus/templates/design/changelog/2026-06-29-pressing-escape-now-cancels-a-stuck-screen-drag-in-the-overv.md +6 -0
  159. package/corpus/templates/design/changelog/2026-06-29-screen-overview-is-easier-to-find-and-named-screens-open-dir.md +6 -0
  160. package/corpus/templates/design/changelog/2026-06-29-screen-overview-only-highlights-screens-when-the-frame-or-ti.md +6 -0
  161. package/corpus/templates/design/changelog/2026-06-29-screen-overview-previews-now-resize-to-match-the-selected-de.md +6 -0
  162. package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-now-uses-a-pointer-cursor-instead-.md +6 -0
  163. package/corpus/templates/design/changelog/2026-06-29-screen-overview-selection-outlines-now-stay-locked-to-the-se.md +6 -0
  164. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zoom-now-reports-real-screen-scale-separatel.md +6 -0
  165. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-feels-smoother-and-selection-outline.md +6 -0
  166. package/corpus/templates/design/changelog/2026-06-29-screen-overview-zooming-now-stays-on-the-overview-canvas-ins.md +6 -0
  167. package/corpus/templates/design/changelog/2026-06-29-selecting-a-nested-layer-from-all-screens-now-keeps-you-in-a.md +6 -0
  168. package/corpus/templates/design/changelog/2026-06-29-share-now-groups-link-sharing-exports-and-agent-handoff.md +6 -0
  169. package/corpus/templates/design/changelog/2026-06-29-style-changes-that-set-several-properties-at-once-fixed-size.md +6 -0
  170. package/corpus/templates/design/changelog/2026-06-29-the-design-editor-loading-skeleton-now-uses-a-softer-charcoa.md +6 -0
  171. package/corpus/templates/design/changelog/2026-06-29-the-device-preview-menu-now-sits-next-to-the-preview-button-.md +6 -0
  172. package/corpus/templates/design/changelog/2026-06-29-the-editor-no-longer-crashes-when-a-screen-ends-up-with-dupl.md +6 -0
  173. package/corpus/templates/design/changelog/2026-06-29-the-inspector-stays-read-only-while-an-empty-design-is-still.md +6 -0
  174. package/corpus/templates/design/changelog/2026-06-29-the-missing-design-view-now-matches-the-rest-of-the-editor-c.md +6 -0
  175. package/corpus/templates/design/changelog/2026-06-29-the-pen-tool-icon-and-tool-option-menus-better-match-the-edi.md +6 -0
  176. package/corpus/templates/design/changelog/2026-06-29-typography-controls-now-keep-the-font-name-visible-and-move-.md +6 -0
  177. package/corpus/templates/design/e2e/README.md +4 -3
  178. package/corpus/templates/design/e2e/global-setup.ts +24 -23
  179. package/corpus/templates/design/e2e/helpers.ts +122 -11
  180. package/corpus/templates/design/playwright.config.ts +19 -4
  181. package/corpus/templates/design/server/lib/coding-handoff.ts +126 -2
  182. package/corpus/templates/design/server/plugins/core-routes.ts +1 -2
  183. package/corpus/templates/design/server/routes/api/design-handoff/[id].zip.get.ts +86 -0
  184. package/corpus/templates/design/shared/code-layer.ts +125 -22
  185. package/corpus/templates/design/shared/color-utils.ts +159 -0
  186. package/corpus/templates/design/shared/generation-session.ts +42 -0
  187. package/corpus/templates/dispatch/app/global.css +79 -35
  188. package/corpus/templates/dispatch/app/routes/integrations.tsx +8 -7
  189. package/corpus/templates/dispatch/changelog/2026-06-29-integration-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  190. package/corpus/templates/forms/app/components/layout/Layout.tsx +6 -2
  191. package/corpus/templates/forms/app/global.css +64 -35
  192. package/corpus/templates/forms/app/pages/FormsListPage.tsx +2 -2
  193. package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +3 -3
  194. package/corpus/templates/forms/changelog/2026-06-29-form-list-and-insight-layouts-adapt-when-the-agent-sidebar.md +6 -0
  195. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +3 -1
  196. package/corpus/templates/macros/app/global.css +25 -9
  197. package/corpus/templates/macros/changelog/2026-06-29-macro-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  198. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +6 -2
  199. package/corpus/templates/mail/app/global.css +42 -36
  200. package/corpus/templates/mail/app/pages/InboxPage.tsx +1 -1
  201. package/corpus/templates/mail/changelog/2026-06-29-the-contact-panel-now-adapts-to-the-available-mail-pane-width.md +6 -0
  202. package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -1
  203. package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +6 -7
  204. package/corpus/templates/plan/app/global.css +74 -42
  205. package/corpus/templates/plan/changelog/2026-06-29-plan-document-blocks-adapt-to-the-available-document-width.md +6 -0
  206. package/corpus/templates/plan/changelog/2026-06-29-plan-titles-and-summaries-can-be-edited-inline-without-focus.md +6 -0
  207. package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -1
  208. package/corpus/templates/slides/app/global.css +49 -33
  209. package/corpus/templates/slides/changelog/2026-06-29-slide-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  210. package/corpus/templates/videos/app/components/layout/Layout.tsx +3 -1
  211. package/corpus/templates/videos/app/global.css +35 -35
  212. package/corpus/templates/videos/changelog/2026-06-29-video-layouts-adapt-when-the-agent-sidebar-is-open.md +6 -0
  213. package/dist/cli/mcp.d.ts.map +1 -1
  214. package/dist/cli/mcp.js +10 -0
  215. package/dist/cli/mcp.js.map +1 -1
  216. package/dist/cli/skills.d.ts.map +1 -1
  217. package/dist/cli/skills.js +58 -12
  218. package/dist/cli/skills.js.map +1 -1
  219. package/dist/client/AssistantChat.d.ts.map +1 -1
  220. package/dist/client/AssistantChat.js +54 -24
  221. package/dist/client/AssistantChat.js.map +1 -1
  222. package/dist/client/analytics.d.ts.map +1 -1
  223. package/dist/client/analytics.js +2 -1
  224. package/dist/client/analytics.js.map +1 -1
  225. package/dist/client/error-format.d.ts.map +1 -1
  226. package/dist/client/error-format.js +17 -0
  227. package/dist/client/error-format.js.map +1 -1
  228. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  229. package/dist/client/extensions/ExtensionViewer.js +3 -4
  230. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  231. package/dist/client/guided-questions.d.ts +4 -0
  232. package/dist/client/guided-questions.d.ts.map +1 -1
  233. package/dist/client/guided-questions.js +16 -7
  234. package/dist/client/guided-questions.js.map +1 -1
  235. package/dist/client/onboarding/OnboardingPanel.js +26 -1
  236. package/dist/client/onboarding/OnboardingPanel.js.map +1 -1
  237. package/dist/client/resources/BuiltinCapabilityDetail.d.ts.map +1 -1
  238. package/dist/client/resources/BuiltinCapabilityDetail.js +1 -1
  239. package/dist/client/resources/BuiltinCapabilityDetail.js.map +1 -1
  240. package/dist/client/resources/use-builtin-capabilities.d.ts +1 -1
  241. package/dist/client/resources/use-builtin-capabilities.d.ts.map +1 -1
  242. package/dist/client/resources/use-builtin-capabilities.js +2 -1
  243. package/dist/client/resources/use-builtin-capabilities.js.map +1 -1
  244. package/dist/client/session-replay.d.ts.map +1 -1
  245. package/dist/client/session-replay.js +31 -0
  246. package/dist/client/session-replay.js.map +1 -1
  247. package/dist/client/sharing/ShareButton.d.ts +14 -0
  248. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  249. package/dist/client/sharing/ShareButton.js +36 -7
  250. package/dist/client/sharing/ShareButton.js.map +1 -1
  251. package/dist/coding-tools/index.js +1 -1
  252. package/dist/coding-tools/index.js.map +1 -1
  253. package/dist/collab/routes.d.ts +1 -1
  254. package/dist/mcp/index.d.ts +2 -0
  255. package/dist/mcp/index.d.ts.map +1 -1
  256. package/dist/mcp/index.js +1 -0
  257. package/dist/mcp/index.js.map +1 -1
  258. package/dist/mcp/screen-memory-stdio.d.ts +6 -0
  259. package/dist/mcp/screen-memory-stdio.d.ts.map +1 -0
  260. package/dist/mcp/screen-memory-stdio.js +213 -0
  261. package/dist/mcp/screen-memory-stdio.js.map +1 -0
  262. package/dist/mcp-client/builtin-capabilities.d.ts +1 -1
  263. package/dist/mcp-client/builtin-capabilities.d.ts.map +1 -1
  264. package/dist/mcp-client/builtin-capabilities.js +10 -0
  265. package/dist/mcp-client/builtin-capabilities.js.map +1 -1
  266. package/dist/mcp-client/index.d.ts +1 -0
  267. package/dist/mcp-client/index.d.ts.map +1 -1
  268. package/dist/mcp-client/index.js +1 -0
  269. package/dist/mcp-client/index.js.map +1 -1
  270. package/dist/mcp-client/screen-memory-local.d.ts +61 -0
  271. package/dist/mcp-client/screen-memory-local.d.ts.map +1 -0
  272. package/dist/mcp-client/screen-memory-local.js +336 -0
  273. package/dist/mcp-client/screen-memory-local.js.map +1 -0
  274. package/dist/onboarding/types.d.ts +7 -0
  275. package/dist/onboarding/types.d.ts.map +1 -1
  276. package/dist/onboarding/types.js.map +1 -1
  277. package/dist/resources/handlers.d.ts +2 -2
  278. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  279. package/dist/server/agent-chat-plugin.js +26 -0
  280. package/dist/server/agent-chat-plugin.js.map +1 -1
  281. package/dist/server/agents-bundle.d.ts.map +1 -1
  282. package/dist/server/agents-bundle.js +22 -7
  283. package/dist/server/agents-bundle.js.map +1 -1
  284. package/dist/server/transcribe-voice.d.ts +1 -1
  285. package/dist/styles/agent-native.css +5 -0
  286. package/dist/templates/default/app/global.css +35 -35
  287. package/docs/content/locales/ar-SA/template-design.mdx +6 -0
  288. package/docs/content/locales/de-DE/template-design.mdx +8 -0
  289. package/docs/content/locales/es-ES/template-design.mdx +10 -2
  290. package/docs/content/locales/fr-FR/template-design.mdx +10 -2
  291. package/docs/content/locales/hi-IN/template-design.mdx +7 -0
  292. package/docs/content/locales/ja-JP/template-design.mdx +7 -0
  293. package/docs/content/locales/ko-KR/template-design.mdx +7 -0
  294. package/docs/content/locales/pt-BR/template-design.mdx +9 -2
  295. package/docs/content/locales/zh-CN/template-design.mdx +6 -0
  296. package/docs/content/locales/zh-TW/template-design.mdx +6 -0
  297. package/docs/content/template-design.mdx +9 -2
  298. package/package.json +1 -1
  299. package/src/templates/default/app/global.css +35 -35
  300. package/corpus/templates/design/app/components/design/VariantGrid.tsx +0 -189
  301. package/corpus/templates/design/app/components/design/VariantHandoffCard.tsx +0 -115
  302. package/corpus/templates/design/app/hooks/use-variant-flow.ts +0 -258
@@ -1,64 +1,245 @@
1
1
  import { defineAction, embedApp } from "@agent-native/core";
2
- import { writeAppState } from "@agent-native/core/application-state";
2
+ import {
3
+ deleteAppState,
4
+ writeAppState,
5
+ writeAppStateForCurrentTab,
6
+ } from "@agent-native/core/application-state";
7
+ import { seedFromText } from "@agent-native/core/collab";
3
8
  import { buildDeepLink } from "@agent-native/core/server";
4
9
  import { assertAccess } from "@agent-native/core/sharing";
10
+ import { eq } from "drizzle-orm";
11
+ import { nanoid } from "nanoid";
5
12
  import { z } from "zod";
6
13
 
7
14
  import "../server/db/index.js"; // ensure registerShareableResource runs
15
+ import { getDb, schema } from "../server/db/index.js";
16
+ import {
17
+ mergeCanvasFramePlacements,
18
+ type CanvasFramePlacement,
19
+ } from "../shared/canvas-frames.js";
20
+
21
+ const VARIANT_GAP = 96;
22
+ const MAX_COLUMNS = 3;
23
+ const MOBILE_WIDTH = 390;
24
+ const MOBILE_HEIGHT = 844;
25
+ const TABLET_WIDTH = 768;
26
+ const TABLET_HEIGHT = 1024;
27
+ const DESKTOP_WIDTH = 1280;
28
+ const DESKTOP_HEIGHT = 900;
8
29
 
9
30
  function designDeepLink(designId: string): string {
10
31
  return buildDeepLink({
11
32
  app: "design",
12
33
  view: "editor",
13
- params: { designId },
14
- // `handoff=chat` marks a link opened from a link-only host (CLI / Codex /
15
- // Claude Code) so the editor shows a copyable paste-back summary after the
16
- // user picks — the choice can't ride a host chat bridge there.
17
- to: `/design/${encodeURIComponent(designId)}?handoff=chat`,
34
+ params: { designId, editorView: "overview" },
35
+ to: `/design/${encodeURIComponent(designId)}?view=overview`,
18
36
  });
19
37
  }
20
38
 
21
39
  const FALLBACK_INSTRUCTIONS =
22
- "If the design opens as a browser link instead of inline, the user picks a " +
23
- "direction there and the editor shows a copyable summary. Ask them to paste " +
24
- "that summary back into chat so you can continue from the chosen direction. " +
25
- 'They can also just tell you which one in words (e.g. "use variant A" / "the ' +
26
- 'editorial one") — honor that too instead of insisting on the paste-back.';
40
+ "The generated directions have been saved as normal screens on the Design " +
41
+ "overview board. The chat shows one button per screen. Ask the user to pick " +
42
+ "a screen by name if the inline buttons are not available; after they pick, " +
43
+ "delete the other variant screens and continue from the kept screen.";
27
44
 
28
45
  const variantSchema = z.object({
29
46
  id: z.string().min(1).describe("Stable variant id, e.g. 'minimal-focus'"),
30
47
  label: z
31
48
  .string()
32
49
  .min(1)
33
- .describe("Short user-facing variant name, e.g. 'One-Line Focus'"),
50
+ .describe("Short user-facing screen name, e.g. 'One-Line Focus'"),
34
51
  content: z
35
52
  .string()
36
53
  .min(1)
37
54
  .describe(
38
- "Complete self-contained HTML document for this variant. Inline the CSS needed for the preview; avoid relying on external CSS/script CDNs because MCP app sandboxes may block them.",
55
+ "Complete self-contained HTML document for this variant. Inline the CSS needed for the preview; avoid relying on external CSS/script CDNs because app sandboxes may block them.",
56
+ ),
57
+ width: z
58
+ .number()
59
+ .positive()
60
+ .optional()
61
+ .describe(
62
+ "Optional source viewport/artboard width for the overview frame.",
63
+ ),
64
+ height: z
65
+ .number()
66
+ .positive()
67
+ .optional()
68
+ .describe(
69
+ "Optional source viewport/artboard height for the overview frame.",
39
70
  ),
40
71
  });
41
72
 
73
+ interface VariantScreen {
74
+ id: string;
75
+ variantId: string;
76
+ label: string;
77
+ filename: string;
78
+ width: number;
79
+ height: number;
80
+ }
81
+
82
+ function isRecord(value: unknown): value is Record<string, unknown> {
83
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
84
+ }
85
+
86
+ function parseJsonRecord(value: string | null | undefined) {
87
+ if (!value) return {};
88
+ try {
89
+ const parsed = JSON.parse(value);
90
+ return isRecord(parsed) ? parsed : {};
91
+ } catch {
92
+ return {};
93
+ }
94
+ }
95
+
96
+ function slugify(value: string, fallback: string) {
97
+ return (
98
+ value
99
+ .trim()
100
+ .toLowerCase()
101
+ .replace(/[^a-z0-9]+/g, "-")
102
+ .replace(/^-+|-+$/g, "")
103
+ .slice(0, 52) || fallback
104
+ );
105
+ }
106
+
107
+ function optionName(index: number) {
108
+ return `Option ${String.fromCharCode(65 + index)}`;
109
+ }
110
+
111
+ function uniqueFilename(preferred: string, used: Set<string>) {
112
+ const dot = preferred.lastIndexOf(".");
113
+ const stem = dot > 0 ? preferred.slice(0, dot) : preferred;
114
+ const ext = dot > 0 ? preferred.slice(dot) : ".html";
115
+ let candidate = `${stem}${ext}`;
116
+ let suffix = 2;
117
+ while (used.has(candidate)) {
118
+ candidate = `${stem}-${suffix}${ext}`;
119
+ suffix += 1;
120
+ }
121
+ used.add(candidate);
122
+ return candidate;
123
+ }
124
+
125
+ function firstCssPixelValue(content: string, property: string) {
126
+ const escaped = property.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
127
+ const pattern = new RegExp(`${escaped}\\s*:\\s*(\\d{2,4})px`, "i");
128
+ const match = content.match(pattern);
129
+ if (!match) return undefined;
130
+ const value = Number(match[1]);
131
+ return Number.isFinite(value) ? value : undefined;
132
+ }
133
+
134
+ function boundedDimension(value: unknown, min: number, max: number) {
135
+ return typeof value === "number" && Number.isFinite(value)
136
+ ? Math.min(max, Math.max(min, Math.round(value)))
137
+ : undefined;
138
+ }
139
+
140
+ function inferVariantSize(variant: z.infer<typeof variantSchema>) {
141
+ const explicitWidth = boundedDimension(variant.width, 240, 1920);
142
+ const explicitHeight = boundedDimension(variant.height, 240, 3000);
143
+ if (explicitWidth && explicitHeight) {
144
+ return { width: explicitWidth, height: explicitHeight };
145
+ }
146
+
147
+ const content = variant.content;
148
+ const cssWidth =
149
+ firstCssPixelValue(content, "width") ??
150
+ firstCssPixelValue(content, "max-width") ??
151
+ firstCssPixelValue(content, "min-width");
152
+ const cssHeight =
153
+ firstCssPixelValue(content, "height") ??
154
+ firstCssPixelValue(content, "min-height");
155
+ const inferredWidth = boundedDimension(cssWidth, 240, 1920);
156
+ const inferredHeight = boundedDimension(cssHeight, 240, 3000);
157
+ if (inferredWidth && inferredWidth <= 560) {
158
+ return {
159
+ width: explicitWidth ?? inferredWidth,
160
+ height: explicitHeight ?? inferredHeight ?? MOBILE_HEIGHT,
161
+ };
162
+ }
163
+ if (inferredWidth && inferredHeight) {
164
+ return {
165
+ width: explicitWidth ?? inferredWidth,
166
+ height: explicitHeight ?? inferredHeight,
167
+ };
168
+ }
169
+
170
+ const lowercase = content.toLowerCase();
171
+ if (
172
+ /\b(?:mobile|phone|iphone|android)\b/.test(lowercase) ||
173
+ /\b(?:max-w-sm|max-w-md|w-\[(?:360|375|390|393|414)px\])\b/.test(lowercase)
174
+ ) {
175
+ return {
176
+ width: explicitWidth ?? MOBILE_WIDTH,
177
+ height: explicitHeight ?? MOBILE_HEIGHT,
178
+ };
179
+ }
180
+ if (/\b(?:tablet|ipad)\b/.test(lowercase)) {
181
+ return {
182
+ width: explicitWidth ?? TABLET_WIDTH,
183
+ height: explicitHeight ?? TABLET_HEIGHT,
184
+ };
185
+ }
186
+
187
+ return {
188
+ width: explicitWidth ?? DESKTOP_WIDTH,
189
+ height: explicitHeight ?? DESKTOP_HEIGHT,
190
+ };
191
+ }
192
+
193
+ function placeVariantScreens(screens: VariantScreen[]) {
194
+ const placements: CanvasFramePlacement[] = [];
195
+ const columns = Math.min(MAX_COLUMNS, Math.max(1, screens.length));
196
+ let rowY = 0;
197
+
198
+ for (let rowStart = 0; rowStart < screens.length; rowStart += columns) {
199
+ const row = screens.slice(rowStart, rowStart + columns);
200
+ let x = 0;
201
+ let rowHeight = 0;
202
+
203
+ for (const [offset, screen] of row.entries()) {
204
+ placements.push({
205
+ fileId: screen.id,
206
+ filename: screen.filename,
207
+ x,
208
+ y: rowY,
209
+ width: screen.width,
210
+ height: screen.height,
211
+ z: rowStart + offset,
212
+ });
213
+ x += screen.width + VARIANT_GAP;
214
+ rowHeight = Math.max(rowHeight, screen.height);
215
+ }
216
+
217
+ rowY += rowHeight + VARIANT_GAP;
218
+ }
219
+
220
+ return placements;
221
+ }
222
+
42
223
  export default defineAction({
43
224
  description:
44
- "Present generated design directions in the Design editor so the user can " +
45
- "visually compare options and pick one. Provide 2-5 variants (3 is the " +
46
- "sweet spot). Use this for design exploration before calling " +
47
- "generate-design. The user's choice is persisted automatically by the app; " +
48
- "if it opens as a browser link, they paste a copyable summary back to chat " +
49
- 'or simply tell you which one (e.g. "use variant A").',
225
+ "Present generated design directions as normal screens on the Design " +
226
+ "overview board and ask the user to choose one with inline chat buttons. " +
227
+ "Provide 2-5 variants (3 is the sweet spot). Use this for design " +
228
+ "exploration before follow-up refinement. After the user's choice, keep " +
229
+ "the chosen screen, delete the other generated variant screens, and " +
230
+ "continue from the kept screen.",
50
231
  schema: z.object({
51
232
  designId: z.string().describe("Design project ID to show variants for"),
52
233
  prompt: z
53
234
  .string()
54
235
  .optional()
55
- .describe("Caption shown above the variant grid"),
236
+ .describe("Caption shown in chat above the variant choice buttons"),
56
237
  variants: z
57
238
  .array(variantSchema)
58
239
  .min(2)
59
240
  .max(5)
60
241
  .describe(
61
- "2-5 concise, visually distinct generated design options to preview side by side (3 is the sweet spot). Inline CSS so all options render in the MCP app preview.",
242
+ "2-5 concise, visually distinct generated design options to place as overview screens (3 is the sweet spot). Inline CSS so all options render in the app preview.",
62
243
  ),
63
244
  }),
64
245
  mcpApp: {
@@ -66,30 +247,174 @@ export default defineAction({
66
247
  resource: embedApp({
67
248
  title: "Design directions",
68
249
  description:
69
- "Open the Design editor with a visual picker for generated variants.",
250
+ "Open the Design editor with generated directions on the overview board.",
70
251
  iframeTitle: "Agent-Native Design",
71
- openLabel: "Open design directions",
252
+ openLabel: "Open screen overview",
72
253
  height: 720,
73
254
  }),
74
255
  },
75
256
  run: async ({ designId, prompt, variants }) => {
76
257
  await assertAccess("design", designId, "editor");
77
258
 
78
- await writeAppState("design-variants", {
259
+ const db = getDb();
260
+ const now = new Date().toISOString();
261
+ const [design] = await db
262
+ .select({ data: schema.designs.data })
263
+ .from(schema.designs)
264
+ .where(eq(schema.designs.id, designId))
265
+ .limit(1);
266
+ const existingFiles = await db
267
+ .select()
268
+ .from(schema.designFiles)
269
+ .where(eq(schema.designFiles.designId, designId));
270
+ const usedFilenames = new Set(existingFiles.map((file) => file.filename));
271
+ const variantSetId = nanoid();
272
+ const screens: VariantScreen[] = [];
273
+
274
+ for (let index = 0; index < variants.length; index += 1) {
275
+ const variant = variants[index]!;
276
+ const label = variant.label.trim() || optionName(index);
277
+ const slug = slugify(label, `option-${index + 1}`);
278
+ const preferredFilename = `variant-${slug}.html`;
279
+ const filename = uniqueFilename(preferredFilename, usedFilenames);
280
+ const fileId = nanoid();
281
+ const { width, height } = inferVariantSize(variant);
282
+
283
+ await db.insert(schema.designFiles).values({
284
+ id: fileId,
285
+ designId,
286
+ filename,
287
+ fileType: "html",
288
+ content: variant.content,
289
+ createdAt: now,
290
+ updatedAt: now,
291
+ });
292
+ await seedFromText(fileId, variant.content);
293
+
294
+ screens.push({
295
+ id: fileId,
296
+ variantId: variant.id,
297
+ label,
298
+ filename,
299
+ width,
300
+ height,
301
+ });
302
+ }
303
+
304
+ const placements = placeVariantScreens(screens);
305
+ await db.transaction(async (tx) => {
306
+ const [currentDesign] = await tx
307
+ .select({ data: schema.designs.data })
308
+ .from(schema.designs)
309
+ .where(eq(schema.designs.id, designId))
310
+ .limit(1);
311
+ const prevData = parseJsonRecord(currentDesign?.data ?? design?.data);
312
+ const mergedFrames = mergeCanvasFramePlacements({
313
+ existing: prevData.canvasFrames,
314
+ placements,
315
+ resolveFileId: (placement) => placement.fileId,
316
+ });
317
+ const previousMetadata = isRecord(prevData.screenMetadata)
318
+ ? { ...prevData.screenMetadata }
319
+ : {};
320
+ const previousVariantSets = isRecord(prevData.designVariantSets)
321
+ ? { ...prevData.designVariantSets }
322
+ : {};
323
+ for (const screen of screens) {
324
+ previousMetadata[screen.id] = {
325
+ sourceType: "inline",
326
+ previewState: "preview",
327
+ title: screen.label,
328
+ width: screen.width,
329
+ height: screen.height,
330
+ variantSetId,
331
+ variantId: screen.variantId,
332
+ };
333
+ }
334
+ previousVariantSets[variantSetId] = {
335
+ id: variantSetId,
336
+ prompt: prompt ?? "Pick a direction",
337
+ createdAt: now,
338
+ screens: screens.map((screen) => ({
339
+ id: screen.id,
340
+ variantId: screen.variantId,
341
+ label: screen.label,
342
+ filename: screen.filename,
343
+ width: screen.width,
344
+ height: screen.height,
345
+ })),
346
+ };
347
+
348
+ await tx
349
+ .update(schema.designs)
350
+ .set({
351
+ data: JSON.stringify({
352
+ ...prevData,
353
+ canvasFrames: mergedFrames.canvasFrames,
354
+ screenMetadata: previousMetadata,
355
+ designVariantSets: previousVariantSets,
356
+ updatedAt: now,
357
+ }),
358
+ updatedAt: now,
359
+ })
360
+ .where(eq(schema.designs.id, designId));
361
+ });
362
+
363
+ await writeAppState("navigate", {
364
+ view: "editor",
79
365
  designId,
80
- prompt: prompt ?? "Pick a direction",
81
- variants,
366
+ editorView: "overview",
367
+ path: `/design/${encodeURIComponent(designId)}?view=overview`,
368
+ });
369
+ await writeAppStateForCurrentTab("guided-questions", {
370
+ title: prompt ?? "Pick a direction",
371
+ description:
372
+ "All options are on the board. Choose the one to keep and I will continue from it.",
373
+ submitLabel: "Use selected direction",
374
+ submitMessage: "Use this design direction.",
375
+ skipLabel: "Show another set",
376
+ skipMessage: "None of these directions are right.",
377
+ questions: [
378
+ {
379
+ id: "variant",
380
+ type: "text-options",
381
+ question: "Which screen should I keep?",
382
+ required: true,
383
+ allowOther: false,
384
+ includeExplore: false,
385
+ includeDecide: false,
386
+ submitOnSelect: true,
387
+ options: screens.map((screen, index) => {
388
+ const otherScreens = screens
389
+ .filter((other) => other.id !== screen.id)
390
+ .map(
391
+ (other) =>
392
+ `${other.label} (${other.filename}, file id ${other.id})`,
393
+ )
394
+ .join("; ");
395
+ return {
396
+ label: screen.label || optionName(index),
397
+ value:
398
+ `Keep "${screen.label}" (${screen.filename}, file id ${screen.id}) ` +
399
+ `from variant set ${variantSetId}. Delete the other variant screens: ${otherScreens}.`,
400
+ };
401
+ }),
402
+ },
403
+ ],
82
404
  });
405
+ await deleteAppState("design-variants").catch(() => false);
83
406
 
84
407
  return {
85
408
  designId,
86
409
  prompt: prompt ?? "Pick a direction",
87
- count: variants.length,
88
- path: `/design/${encodeURIComponent(designId)}?handoff=chat`,
410
+ variantSetId,
411
+ count: screens.length,
412
+ screens,
413
+ path: `/design/${encodeURIComponent(designId)}?view=overview`,
89
414
  embed: true,
90
415
  fallbackInstructions: FALLBACK_INSTRUCTIONS,
91
416
  nextRequiredAction:
92
- "Wait for the user to pick a variant before refining or calling generate-design.",
417
+ "Wait for the user to pick a screen in chat. Then delete the unchosen variant screens with delete-file and continue from the chosen screen.",
93
418
  };
94
419
  },
95
420
  link: ({ result }) => {
@@ -98,7 +423,7 @@ export default defineAction({
98
423
  if (!designId) return null;
99
424
  return {
100
425
  url: designDeepLink(designId),
101
- label: "Open design directions",
426
+ label: "Open screen overview",
102
427
  view: "editor",
103
428
  };
104
429
  },
@@ -49,17 +49,54 @@ export default defineAction({
49
49
  const db = getDb();
50
50
  const now = new Date().toISOString();
51
51
 
52
- const updates: Record<string, unknown> = { updatedAt: now };
53
- if (title !== undefined) updates.title = title;
54
- if (description !== undefined) updates.description = description;
55
- if (data !== undefined) updates.data = data;
56
- if (projectType !== undefined) updates.projectType = projectType;
57
- if (designSystemId !== undefined) updates.designSystemId = designSystemId;
52
+ const asRecord = (raw: string | null | undefined) => {
53
+ if (!raw) return {} as Record<string, unknown>;
54
+ try {
55
+ const parsed = JSON.parse(raw);
56
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
57
+ ? (parsed as Record<string, unknown>)
58
+ : ({} as Record<string, unknown>);
59
+ } catch {
60
+ return {} as Record<string, unknown>;
61
+ }
62
+ };
63
+
64
+ // Read-merge-write inside one transaction so a partial `data` update can't
65
+ // race a concurrent write and either destroy framework-owned keys (e.g.
66
+ // canvasFrames/tweaks) or overwrite newer server-side keys. Mirrors
67
+ // generate-design's transactional read-merge.
68
+ await db.transaction(async (tx) => {
69
+ const updates: Record<string, unknown> = { updatedAt: now };
70
+ if (title !== undefined) updates.title = title;
71
+ if (description !== undefined) updates.description = description;
72
+ if (data !== undefined) {
73
+ const [existing] = await tx
74
+ .select({ data: schema.designs.data })
75
+ .from(schema.designs)
76
+ .where(eq(schema.designs.id, id));
77
+ const incomingParsed = JSON.parse(data);
78
+ if (
79
+ incomingParsed &&
80
+ typeof incomingParsed === "object" &&
81
+ !Array.isArray(incomingParsed)
82
+ ) {
83
+ updates.data = JSON.stringify({
84
+ ...asRecord(existing?.data),
85
+ ...(incomingParsed as Record<string, unknown>),
86
+ });
87
+ } else {
88
+ // Non-object JSON (array/primitive): keep the original verbatim write.
89
+ updates.data = data;
90
+ }
91
+ }
92
+ if (projectType !== undefined) updates.projectType = projectType;
93
+ if (designSystemId !== undefined) updates.designSystemId = designSystemId;
58
94
 
59
- await db
60
- .update(schema.designs)
61
- .set(updates)
62
- .where(eq(schema.designs.id, id));
95
+ await tx
96
+ .update(schema.designs)
97
+ .set(updates)
98
+ .where(eq(schema.designs.id, id));
99
+ });
63
100
 
64
101
  return { id, updated: true };
65
102
  },
@@ -70,15 +70,40 @@ export default defineAction({
70
70
 
71
71
  await assertAccess("design", file.designId, "editor");
72
72
 
73
- const updates: Record<string, unknown> = { updatedAt: now };
74
- if (content !== undefined) updates.content = content;
75
- if (filename !== undefined) updates.filename = filename;
76
- if (fileType !== undefined) updates.fileType = fileType;
73
+ // Reject a rename that would collide with an existing filename in the same
74
+ // design. The collision check and the write run in one transaction so they
75
+ // can't be interleaved by a concurrent rename. (A DB-level UNIQUE index on
76
+ // (designId, filename) would be the strongest guarantee but is a non-additive
77
+ // schema change on existing data, so it's deferred.)
78
+ await db.transaction(async (tx) => {
79
+ if (filename !== undefined) {
80
+ const [collision] = await tx
81
+ .select({ id: schema.designFiles.id })
82
+ .from(schema.designFiles)
83
+ .where(
84
+ and(
85
+ eq(schema.designFiles.designId, file.designId),
86
+ eq(schema.designFiles.filename, filename),
87
+ ),
88
+ )
89
+ .limit(1);
90
+ if (collision && collision.id !== id) {
91
+ throw new Error(
92
+ `File "${filename}" already exists in design ${file.designId}`,
93
+ );
94
+ }
95
+ }
77
96
 
78
- await db
79
- .update(schema.designFiles)
80
- .set(updates)
81
- .where(eq(schema.designFiles.id, id));
97
+ const updates: Record<string, unknown> = { updatedAt: now };
98
+ if (content !== undefined) updates.content = content;
99
+ if (filename !== undefined) updates.filename = filename;
100
+ if (fileType !== undefined) updates.fileType = fileType;
101
+
102
+ await tx
103
+ .update(schema.designFiles)
104
+ .set(updates)
105
+ .where(eq(schema.designFiles.id, id));
106
+ });
82
107
 
83
108
  // Push content through the collab layer so live editors see the change
84
109
  if (content !== undefined && syncCollab) {