@agent-native/core 0.92.6 → 0.92.8

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 (297) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +18 -0
  3. package/corpus/core/docs/content/a2a-protocol.mdx +12 -0
  4. package/corpus/core/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  5. package/corpus/core/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  6. package/corpus/core/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  7. package/corpus/core/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  8. package/corpus/core/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  9. package/corpus/core/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  10. package/corpus/core/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  11. package/corpus/core/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  12. package/corpus/core/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  13. package/corpus/core/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  14. package/corpus/core/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  15. package/corpus/core/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  16. package/corpus/core/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  17. package/corpus/core/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  18. package/corpus/core/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  19. package/corpus/core/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  20. package/corpus/core/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  21. package/corpus/core/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  22. package/corpus/core/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  23. package/corpus/core/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  24. package/corpus/core/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  25. package/corpus/core/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  26. package/corpus/core/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  27. package/corpus/core/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  28. package/corpus/core/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  29. package/corpus/core/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  30. package/corpus/core/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  31. package/corpus/core/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  32. package/corpus/core/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  33. package/corpus/core/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  34. package/corpus/core/docs/content/pr-visual-recap.mdx +3 -3
  35. package/corpus/core/docs/content/pure-agent-apps.mdx +22 -0
  36. package/corpus/core/package.json +1 -1
  37. package/corpus/core/src/agent/engine/builtin.ts +1 -1
  38. package/corpus/core/src/agent/model-config.ts +30 -30
  39. package/corpus/core/src/agent/production-agent.ts +129 -7
  40. package/corpus/core/src/cli/design-connect.ts +438 -83
  41. package/corpus/core/src/cli/recap.ts +2 -2
  42. package/corpus/core/src/cli/sync-builder-starter-manifest.ts +1 -1
  43. package/corpus/core/src/client/AgentPanel.tsx +8 -1
  44. package/corpus/core/src/client/agent-chat-adapter.ts +12 -7
  45. package/corpus/core/src/client/composer/TiptapComposer.tsx +1 -1
  46. package/corpus/core/src/client/index.ts +4 -0
  47. package/corpus/core/src/client/org/TeamPage.tsx +41 -26
  48. package/corpus/core/src/client/use-action.ts +113 -1
  49. package/corpus/core/src/db/client.ts +12 -2
  50. package/corpus/core/src/db/create-get-db.ts +4 -1
  51. package/corpus/core/src/scripts/agent-engines/manage-agent-engine.ts +1 -1
  52. package/corpus/core/src/scripts/agent-engines/set-agent-engine.ts +1 -1
  53. package/corpus/core/src/server/transcribe-voice.ts +1 -1
  54. package/corpus/core/src/usage/store.ts +13 -4
  55. package/corpus/core/src/vite/client.ts +38 -1
  56. package/corpus/templates/analytics/AGENTS.md +3 -0
  57. package/corpus/templates/analytics/actions/get-data-program.ts +78 -0
  58. package/corpus/templates/analytics/actions/list-data-programs.ts +49 -0
  59. package/corpus/templates/analytics/app/global.css +11 -0
  60. package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +163 -17
  61. package/corpus/templates/analytics/app/i18n/zh-TW.ts +2 -0
  62. package/corpus/templates/analytics/app/i18n-data.ts +20 -0
  63. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +96 -3
  64. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +3 -1
  65. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +61 -4
  66. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +40 -2
  67. package/corpus/templates/analytics/changelog/2026-07-09-analytics-no-longer-forgets-successful-data-queries-when-a-l.md +6 -0
  68. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-keep-consistent-spacing-when-they-stack-ver.md +6 -0
  69. package/corpus/templates/analytics/changelog/2026-07-09-dashboard-charts-tables-and-extensions-can-be-selected-and-d.md +6 -0
  70. package/corpus/templates/analytics/changelog/2026-07-09-long-running-analytics-requests-now-recover-automatically-wh.md +6 -0
  71. package/corpus/templates/analytics/server/db/index.ts +6 -0
  72. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +1 -7
  73. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +102 -15
  74. package/corpus/templates/content/app/hooks/use-content-database.ts +52 -0
  75. package/corpus/templates/content/app/hooks/use-document-properties.ts +50 -6
  76. package/corpus/templates/content/app/i18n/zh-TW.ts +2 -0
  77. package/corpus/templates/content/app/i18n-data.ts +28 -0
  78. package/corpus/templates/content/changelog/2026-07-09-notion-conflict-warnings-no-longer-flash-during-normal-synce.md +6 -0
  79. package/corpus/templates/content/changelog/2026-07-09-property-menus-and-delete-confirmations-now-open-visibly-and.md +6 -0
  80. package/corpus/templates/content/server/lib/notion-sync.ts +130 -28
  81. package/corpus/templates/design/actions/add-breakpoint.ts +48 -68
  82. package/corpus/templates/design/actions/add-localhost-screens.ts +496 -61
  83. package/corpus/templates/design/actions/apply-component-prop-edit.ts +2 -18
  84. package/corpus/templates/design/actions/apply-design-token-edit.ts +51 -82
  85. package/corpus/templates/design/actions/apply-tweaks.ts +63 -41
  86. package/corpus/templates/design/actions/capture-design-state.ts +2 -2
  87. package/corpus/templates/design/actions/connect-localhost.ts +58 -12
  88. package/corpus/templates/design/actions/create-component.ts +2 -18
  89. package/corpus/templates/design/actions/create-design-branch.ts +38 -18
  90. package/corpus/templates/design/actions/create-fusion-app.ts +15 -11
  91. package/corpus/templates/design/actions/delete-file.ts +19 -34
  92. package/corpus/templates/design/actions/deploy-design-preview.ts +59 -27
  93. package/corpus/templates/design/actions/deploy-fusion-app.ts +22 -15
  94. package/corpus/templates/design/actions/export-pdf.ts +2 -1
  95. package/corpus/templates/design/actions/export-zip.ts +9 -3
  96. package/corpus/templates/design/actions/generate-design.ts +107 -70
  97. package/corpus/templates/design/actions/get-component-details.ts +2 -18
  98. package/corpus/templates/design/actions/get-design-branch-diff.ts +2 -3
  99. package/corpus/templates/design/actions/get-design-surface-index.ts +2 -18
  100. package/corpus/templates/design/actions/get-design.ts +2 -1
  101. package/corpus/templates/design/actions/grant-localhost-write-consent.ts +7 -1
  102. package/corpus/templates/design/actions/import-design-tokens.ts +114 -76
  103. package/corpus/templates/design/actions/index-components.ts +2 -18
  104. package/corpus/templates/design/actions/list-design-source-capabilities.ts +23 -28
  105. package/corpus/templates/design/actions/list-local-files.ts +9 -2
  106. package/corpus/templates/design/actions/migrate-board-objects-to-file.ts +82 -98
  107. package/corpus/templates/design/actions/open-component-source.ts +2 -18
  108. package/corpus/templates/design/actions/open-visual-edit.ts +19 -1
  109. package/corpus/templates/design/actions/present-design-variants.ts +70 -70
  110. package/corpus/templates/design/actions/preview-component-prop-edit.ts +4 -14
  111. package/corpus/templates/design/actions/read-local-file.ts +9 -2
  112. package/corpus/templates/design/actions/remove-breakpoint.ts +40 -61
  113. package/corpus/templates/design/actions/rename-screen.ts +425 -0
  114. package/corpus/templates/design/actions/request-localhost-write-consent.ts +12 -2
  115. package/corpus/templates/design/actions/revoke-localhost-write-consent.ts +9 -2
  116. package/corpus/templates/design/actions/sync-fusion-app.ts +56 -39
  117. package/corpus/templates/design/actions/update-design.ts +376 -61
  118. package/corpus/templates/design/actions/update-file.ts +455 -188
  119. package/corpus/templates/design/actions/write-local-file.ts +10 -2
  120. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +685 -608
  121. package/corpus/templates/design/app/components/design/LayersPanel.tsx +43 -15
  122. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +576 -2015
  123. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +62 -14
  124. package/corpus/templates/design/app/components/design/bridge/embedded-wheel.bridge.ts +263 -25
  125. package/corpus/templates/design/app/components/design/design-canvas/annotation-submit.ts +34 -0
  126. package/corpus/templates/design/app/components/design/design-canvas/coordinate-transforms.ts +40 -0
  127. package/corpus/templates/design/app/components/design/design-canvas/creation.ts +47 -0
  128. package/corpus/templates/design/app/components/design/design-canvas/element-payload.ts +27 -0
  129. package/corpus/templates/design/app/components/design/design-canvas/embedded-frame.ts +62 -0
  130. package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +137 -0
  131. package/corpus/templates/design/app/components/design/design-canvas/file-drop.ts +10 -0
  132. package/corpus/templates/design/app/components/design/design-canvas/hit-test.ts +23 -0
  133. package/corpus/templates/design/app/components/design/design-canvas/iframe-events.ts +23 -0
  134. package/corpus/templates/design/app/components/design/design-canvas/iframe-pan.ts +177 -0
  135. package/corpus/templates/design/app/components/design/design-canvas/motion-types.ts +7 -0
  136. package/corpus/templates/design/app/components/design/design-canvas/pending-text-edit.ts +62 -0
  137. package/corpus/templates/design/app/components/design/index.ts +2 -2
  138. package/corpus/templates/design/app/components/design/multi-screen/canvas-tools.ts +100 -0
  139. package/corpus/templates/design/app/components/design/multi-screen/coordinate-transforms.ts +81 -0
  140. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +23 -13
  141. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +238 -19
  142. package/corpus/templates/design/app/components/design/multi-screen/draft-primitives.ts +424 -0
  143. package/corpus/templates/design/app/components/design/multi-screen/frame-geometry.ts +341 -0
  144. package/corpus/templates/design/app/components/design/multi-screen/iframe-targeting.ts +52 -0
  145. package/corpus/templates/design/app/components/design/multi-screen/overview-layout.ts +62 -0
  146. package/corpus/templates/design/app/components/design/multi-screen/primitive-drop-target.ts +283 -0
  147. package/corpus/templates/design/app/components/design/multi-screen/screen-content-cache.ts +272 -0
  148. package/corpus/templates/design/app/components/design/multi-screen/types.ts +5 -1
  149. package/corpus/templates/design/app/components/design/multi-screen/wheel-gesture-state.ts +12 -0
  150. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +325 -84
  151. package/corpus/templates/design/app/hooks/use-navigation-state.ts +15 -1
  152. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +119 -47
  153. package/corpus/templates/design/app/i18n/zh-TW.ts +9 -0
  154. package/corpus/templates/design/app/i18n-data.ts +223 -0
  155. package/corpus/templates/design/app/lib/design-save-outbox.ts +379 -0
  156. package/corpus/templates/design/app/lib/png-clipboard.ts +113 -0
  157. package/corpus/templates/design/app/pages/design-editor/canvas-primitives.ts +229 -0
  158. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +918 -0
  159. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +43 -0
  160. package/corpus/templates/design/app/pages/design-editor/cross-screen-text-color.ts +336 -0
  161. package/corpus/templates/design/app/pages/design-editor/data-operations.ts +270 -0
  162. package/corpus/templates/design/app/pages/design-editor/dom-guards.ts +24 -0
  163. package/corpus/templates/design/app/pages/design-editor/dom-utils.ts +11 -0
  164. package/corpus/templates/design/app/pages/design-editor/editor-session.ts +20 -0
  165. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +408 -0
  166. package/corpus/templates/design/app/pages/design-editor/export-capture.ts +115 -0
  167. package/corpus/templates/design/app/pages/design-editor/geometry-persistence.ts +93 -0
  168. package/corpus/templates/design/app/pages/design-editor/history.ts +169 -0
  169. package/corpus/templates/design/app/pages/design-editor/layout-operations.ts +185 -0
  170. package/corpus/templates/design/app/pages/design-editor/motion-state.ts +309 -0
  171. package/corpus/templates/design/app/pages/design-editor/overview-camera.ts +438 -0
  172. package/corpus/templates/design/app/pages/design-editor/pending-edits.ts +510 -0
  173. package/corpus/templates/design/app/pages/design-editor/portable-style.ts +115 -0
  174. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +337 -0
  175. package/corpus/templates/design/app/pages/design-editor/style-utils.ts +5 -0
  176. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +92 -0
  177. package/corpus/templates/design/app/pages/design-editor/tweak-save.ts +87 -0
  178. package/corpus/templates/design/app/pages/design-editor/types.ts +36 -0
  179. package/corpus/templates/design/app/routes/design.$id.tsx +11 -1
  180. package/corpus/templates/design/changelog/2026-07-09-design-edits-canvas-positions-tools-and-tab-context-now-pers.md +6 -0
  181. package/corpus/templates/design/changelog/2026-07-09-figma-style-shortcuts-drawing-annotations-and-cross-screen-d.md +6 -0
  182. package/corpus/templates/design/changelog/2026-07-09-hand-and-scale-tools-now-keep-their-active-toolbar-identity-.md +6 -0
  183. package/corpus/templates/design/changelog/2026-07-09-large-multi-screen-designs-now-stay-responsive-during-long-c.md +6 -0
  184. package/corpus/templates/design/changelog/2026-07-09-layer-drags-now-land-exactly-where-the-insertion-indicator-s.md +6 -0
  185. package/corpus/templates/design/server/db/index.ts +2 -52
  186. package/corpus/templates/design/server/db/schema.ts +16 -0
  187. package/corpus/templates/design/server/lib/design-data-access.ts +70 -0
  188. package/corpus/templates/design/server/lib/design-data-mutation.ts +249 -0
  189. package/corpus/templates/design/server/lib/fusion-screens.ts +89 -76
  190. package/corpus/templates/design/server/lib/import-design-files.ts +76 -35
  191. package/corpus/templates/design/server/lib/verify-write-grant.ts +7 -2
  192. package/corpus/templates/design/server/plugins/db.ts +19 -0
  193. package/corpus/templates/design/server/source-workspace.ts +12 -13
  194. package/corpus/templates/design/shared/resolve-tweaks.ts +15 -0
  195. package/corpus/templates/design/shared/screen-rename.ts +42 -0
  196. package/corpus/templates/design/shared/source-mode.ts +84 -4
  197. package/dist/agent/engine/builder-engine.d.ts +1 -1
  198. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  199. package/dist/agent/engine/builtin.js +1 -1
  200. package/dist/agent/engine/builtin.js.map +1 -1
  201. package/dist/agent/model-config.d.ts +9 -9
  202. package/dist/agent/model-config.d.ts.map +1 -1
  203. package/dist/agent/model-config.js +30 -30
  204. package/dist/agent/model-config.js.map +1 -1
  205. package/dist/agent/production-agent.d.ts +60 -1
  206. package/dist/agent/production-agent.d.ts.map +1 -1
  207. package/dist/agent/production-agent.js +106 -7
  208. package/dist/agent/production-agent.js.map +1 -1
  209. package/dist/cli/design-connect.d.ts +30 -1
  210. package/dist/cli/design-connect.d.ts.map +1 -1
  211. package/dist/cli/design-connect.js +359 -75
  212. package/dist/cli/design-connect.js.map +1 -1
  213. package/dist/cli/recap.js +2 -2
  214. package/dist/cli/recap.js.map +1 -1
  215. package/dist/cli/sync-builder-starter-manifest.js +1 -1
  216. package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
  217. package/dist/client/AgentPanel.d.ts.map +1 -1
  218. package/dist/client/AgentPanel.js +6 -1
  219. package/dist/client/AgentPanel.js.map +1 -1
  220. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  221. package/dist/client/agent-chat-adapter.js +10 -5
  222. package/dist/client/agent-chat-adapter.js.map +1 -1
  223. package/dist/client/composer/TiptapComposer.js +1 -1
  224. package/dist/client/composer/TiptapComposer.js.map +1 -1
  225. package/dist/client/index.d.ts +1 -1
  226. package/dist/client/index.d.ts.map +1 -1
  227. package/dist/client/index.js +1 -1
  228. package/dist/client/index.js.map +1 -1
  229. package/dist/client/org/TeamPage.d.ts.map +1 -1
  230. package/dist/client/org/TeamPage.js +6 -5
  231. package/dist/client/org/TeamPage.js.map +1 -1
  232. package/dist/client/use-action.d.ts +32 -0
  233. package/dist/client/use-action.d.ts.map +1 -1
  234. package/dist/client/use-action.js +68 -1
  235. package/dist/client/use-action.js.map +1 -1
  236. package/dist/collab/awareness.d.ts +2 -2
  237. package/dist/collab/awareness.d.ts.map +1 -1
  238. package/dist/collab/routes.d.ts +1 -1
  239. package/dist/db/client.d.ts +3 -0
  240. package/dist/db/client.d.ts.map +1 -1
  241. package/dist/db/client.js +7 -2
  242. package/dist/db/client.js.map +1 -1
  243. package/dist/db/create-get-db.d.ts.map +1 -1
  244. package/dist/db/create-get-db.js +2 -2
  245. package/dist/db/create-get-db.js.map +1 -1
  246. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  247. package/dist/notifications/routes.d.ts +1 -1
  248. package/dist/observability/routes.d.ts +5 -5
  249. package/dist/progress/routes.d.ts +1 -1
  250. package/dist/resources/handlers.d.ts +1 -1
  251. package/dist/scripts/agent-engines/manage-agent-engine.js +1 -1
  252. package/dist/scripts/agent-engines/manage-agent-engine.js.map +1 -1
  253. package/dist/scripts/agent-engines/set-agent-engine.js +1 -1
  254. package/dist/scripts/agent-engines/set-agent-engine.js.map +1 -1
  255. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  256. package/dist/server/transcribe-voice.js +1 -1
  257. package/dist/server/transcribe-voice.js.map +1 -1
  258. package/dist/usage/store.d.ts.map +1 -1
  259. package/dist/usage/store.js +13 -4
  260. package/dist/usage/store.js.map +1 -1
  261. package/dist/vite/client.d.ts.map +1 -1
  262. package/dist/vite/client.js +36 -1
  263. package/dist/vite/client.js.map +1 -1
  264. package/docs/content/a2a-protocol.mdx +12 -0
  265. package/docs/content/locales/ar-SA/a2a-protocol.mdx +12 -0
  266. package/docs/content/locales/ar-SA/pr-visual-recap.mdx +3 -3
  267. package/docs/content/locales/ar-SA/pure-agent-apps.mdx +4 -0
  268. package/docs/content/locales/de-DE/a2a-protocol.mdx +12 -0
  269. package/docs/content/locales/de-DE/pr-visual-recap.mdx +3 -3
  270. package/docs/content/locales/de-DE/pure-agent-apps.mdx +4 -0
  271. package/docs/content/locales/es-ES/a2a-protocol.mdx +12 -0
  272. package/docs/content/locales/es-ES/pr-visual-recap.mdx +3 -3
  273. package/docs/content/locales/es-ES/pure-agent-apps.mdx +4 -0
  274. package/docs/content/locales/fr-FR/a2a-protocol.mdx +12 -0
  275. package/docs/content/locales/fr-FR/pr-visual-recap.mdx +3 -3
  276. package/docs/content/locales/fr-FR/pure-agent-apps.mdx +4 -0
  277. package/docs/content/locales/hi-IN/a2a-protocol.mdx +12 -0
  278. package/docs/content/locales/hi-IN/pr-visual-recap.mdx +3 -3
  279. package/docs/content/locales/hi-IN/pure-agent-apps.mdx +4 -0
  280. package/docs/content/locales/ja-JP/a2a-protocol.mdx +12 -0
  281. package/docs/content/locales/ja-JP/pr-visual-recap.mdx +3 -3
  282. package/docs/content/locales/ja-JP/pure-agent-apps.mdx +4 -0
  283. package/docs/content/locales/ko-KR/a2a-protocol.mdx +12 -0
  284. package/docs/content/locales/ko-KR/pr-visual-recap.mdx +3 -3
  285. package/docs/content/locales/ko-KR/pure-agent-apps.mdx +4 -0
  286. package/docs/content/locales/pt-BR/a2a-protocol.mdx +12 -0
  287. package/docs/content/locales/pt-BR/pr-visual-recap.mdx +3 -3
  288. package/docs/content/locales/pt-BR/pure-agent-apps.mdx +4 -0
  289. package/docs/content/locales/zh-CN/a2a-protocol.mdx +12 -0
  290. package/docs/content/locales/zh-CN/pr-visual-recap.mdx +3 -3
  291. package/docs/content/locales/zh-CN/pure-agent-apps.mdx +4 -0
  292. package/docs/content/locales/zh-TW/a2a-protocol.mdx +12 -0
  293. package/docs/content/locales/zh-TW/pr-visual-recap.mdx +3 -3
  294. package/docs/content/locales/zh-TW/pure-agent-apps.mdx +4 -0
  295. package/docs/content/pr-visual-recap.mdx +3 -3
  296. package/docs/content/pure-agent-apps.mdx +22 -0
  297. package/package.json +2 -2
@@ -57,7 +57,7 @@ import {
57
57
  componentNodeIdMatches,
58
58
  } from "../shared/component-model.js";
59
59
  import { hasCapability } from "../shared/design-source-capabilities.js";
60
- import { normalizeDesignSourceType } from "../shared/source-mode.js";
60
+ import { designSourceTypeFromData } from "../shared/source-mode.js";
61
61
  import { sourceContentHash } from "../shared/source-workspace.js";
62
62
 
63
63
  // ─── Helpers ──────────────────────────────────────────────────────────────────
@@ -241,24 +241,8 @@ export default defineAction({
241
241
  if (!access) throw new Error("Design not found");
242
242
 
243
243
  // ── Source type + capability gate ────────────────────────────────────────
244
- let rawSourceType: unknown = "inline";
245
244
  const rawData = (access.resource as { data?: unknown }).data;
246
- if (typeof rawData === "string") {
247
- try {
248
- const parsed: unknown = JSON.parse(rawData);
249
- if (
250
- parsed !== null &&
251
- typeof parsed === "object" &&
252
- "sourceType" in (parsed as object)
253
- ) {
254
- rawSourceType = (parsed as { sourceType: unknown }).sourceType;
255
- }
256
- } catch {
257
- // Default to inline.
258
- }
259
- }
260
-
261
- const sourceType = normalizeDesignSourceType(rawSourceType) ?? "inline";
245
+ const sourceType = designSourceTypeFromData(rawData);
262
246
  const caps = resolveSourceCapabilities(sourceType);
263
247
 
264
248
  // Real-app sources gate on `applyEdit` (bridge write hardening).
@@ -1,11 +1,13 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import { buildDeepLink } from "@agent-native/core/server";
3
3
  import { assertAccess } from "@agent-native/core/sharing";
4
- import { eq } from "drizzle-orm";
5
4
  import { z } from "zod";
6
5
 
7
- import { getDb, schema } from "../server/db/index.js";
8
6
  import "../server/db/index.js"; // ensure registerShareableResource runs
7
+ import {
8
+ mutateDesignData,
9
+ type DesignDataRecord,
10
+ } from "../server/lib/design-data-mutation.js";
9
11
  import {
10
12
  INLINE_DEFAULT_CAPABILITIES,
11
13
  hasCapability,
@@ -53,6 +55,25 @@ function designDeepLink(designId: string): string {
53
55
  });
54
56
  }
55
57
 
58
+ type TweakDef = Parameters<typeof resolveTweaksToCssVars>[0][number];
59
+ type TweakSelections = Record<string, string | number | boolean>;
60
+
61
+ function readTweaks(data: DesignDataRecord): TweakDef[] {
62
+ return Array.isArray(data.tweaks) ? (data.tweaks as TweakDef[]) : [];
63
+ }
64
+
65
+ function readSelections(data: DesignDataRecord): TweakSelections {
66
+ return data.tweakSelections &&
67
+ typeof data.tweakSelections === "object" &&
68
+ !Array.isArray(data.tweakSelections)
69
+ ? (data.tweakSelections as TweakSelections)
70
+ : {};
71
+ }
72
+
73
+ function selectionKeyForCssVar(tweaks: TweakDef[], cssVar: string): string {
74
+ return tweaks.find((tweak) => tweak.cssVar === cssVar)?.id ?? cssVar;
75
+ }
76
+
56
77
  // ---------------------------------------------------------------------------
57
78
  // Action — persist token edit through the Tweaks loop (Tier-A)
58
79
  // ---------------------------------------------------------------------------
@@ -93,87 +114,35 @@ export default defineAction({
93
114
  // sourceRef will be used once real write-back is implemented
94
115
  void sourceRef;
95
116
 
96
- const db = getDb();
97
- const now = new Date().toISOString();
98
-
99
- // ------------------------------------------------------------------
100
- // Load current design data
101
- // ------------------------------------------------------------------
102
- const [existingDesign] = await db
103
- .select({ data: schema.designs.data })
104
- .from(schema.designs)
105
- .where(eq(schema.designs.id, designId));
106
-
107
- let prevData: Record<string, unknown> = {};
108
- if (existingDesign?.data) {
109
- try {
110
- const parsed = JSON.parse(existingDesign.data);
111
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
112
- prevData = parsed;
117
+ // Merge against the latest designs.data revision. Each retry re-resolves
118
+ // cssVar -> tweak id so a concurrent tweak-definition change cannot leave
119
+ // the value under a stale key.
120
+ const { data: persistedData } = await mutateDesignData({
121
+ designId,
122
+ mutate: (prevData, { updatedAt }) => {
123
+ const tweaks = readTweaks(prevData);
124
+ const newSelections = { ...readSelections(prevData) };
125
+ for (const { cssVar, value } of edits) {
126
+ newSelections[selectionKeyForCssVar(tweaks, cssVar)] = value;
113
127
  }
114
- } catch {
115
- // Stale/invalid JSON — start fresh but never drop the column.
116
- }
117
- }
118
-
119
- type TweakDef = Parameters<typeof resolveTweaksToCssVars>[0][number];
120
- const tweaks: TweakDef[] = Array.isArray(prevData.tweaks)
121
- ? (prevData.tweaks as TweakDef[])
122
- : [];
123
-
124
- // Map cssVar -> tweakId for known tweaks
125
- const cssVarToTweakId = new Map<string, string>();
126
- for (const t of tweaks) {
127
- if (t.cssVar) cssVarToTweakId.set(t.cssVar, t.id);
128
- }
129
-
130
- const prevSelections: Record<string, string | number | boolean> =
131
- prevData.tweakSelections &&
132
- typeof prevData.tweakSelections === "object" &&
133
- !Array.isArray(prevData.tweakSelections)
134
- ? (prevData.tweakSelections as Record<
135
- string,
136
- string | number | boolean
137
- >)
138
- : {};
139
-
140
- // ------------------------------------------------------------------
141
- // Merge edits into tweakSelections
142
- // ------------------------------------------------------------------
143
- // For each edit:
144
- // - If the cssVar maps to a known tweak, persist by tweakId (the standard
145
- // Tweaks loop key) so apply-tweaks, get-design-snapshot, and the bridge
146
- // script all pick it up consistently.
147
- // - If the cssVar does NOT map to a known tweak, we synthesise a minimal
148
- // tweak entry and persist via the raw cssVar key so the bridge script
149
- // (`TWEAK_BRIDGE_SCRIPT`) still applies it.
150
- const newSelections = { ...prevSelections };
151
-
152
- for (const { cssVar, value } of edits) {
153
- const tweakId = cssVarToTweakId.get(cssVar);
154
- if (tweakId) {
155
- newSelections[tweakId] = value;
156
- } else {
157
- // Fallback: persist using the cssVar as the key — the bridge script
158
- // calls documentElement.style.setProperty with the key directly, so
159
- // any key starting with "--" is applied as-is.
160
- newSelections[cssVar] = value;
161
- }
162
- }
163
-
164
- // ------------------------------------------------------------------
165
- // Persist — same atomic write as apply-tweaks
166
- // ------------------------------------------------------------------
167
- const mergedData: Record<string, unknown> = {
168
- ...prevData,
169
- tweakSelections: newSelections,
170
- tweaksAppliedAt: now,
171
- };
172
-
173
- await db
174
- .update(schema.designs)
175
- .set({ data: JSON.stringify(mergedData), updatedAt: now })
176
- .where(eq(schema.designs.id, designId));
128
+ return {
129
+ ...prevData,
130
+ tweakSelections: newSelections,
131
+ tweaksAppliedAt: updatedAt,
132
+ };
133
+ },
134
+ isApplied: (data) => {
135
+ const tweaks = readTweaks(data);
136
+ const selections = readSelections(data);
137
+ return edits.every(
138
+ ({ cssVar, value }) =>
139
+ selections[selectionKeyForCssVar(tweaks, cssVar)] === value,
140
+ );
141
+ },
142
+ });
143
+
144
+ const tweaks = readTweaks(persistedData);
145
+ const newSelections = readSelections(persistedData);
177
146
 
178
147
  // Resolve to full CSS var map for the response (so the caller can send a
179
148
  // tweak-values postMessage without a separate round-trip)
@@ -1,11 +1,14 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import { buildDeepLink } from "@agent-native/core/server";
3
3
  import { assertAccess } from "@agent-native/core/sharing";
4
- import { eq } from "drizzle-orm";
5
4
  import { z } from "zod";
6
5
 
7
- import { getDb, schema } from "../server/db/index.js";
8
6
  import "../server/db/index.js"; // ensure registerShareableResource runs
7
+ import {
8
+ mutateDesignData,
9
+ type DesignDataRecord,
10
+ } from "../server/lib/design-data-mutation.js";
11
+ import { tweakSelectionsHash } from "../shared/resolve-tweaks.js";
9
12
 
10
13
  /** Editor deep link so external agents can surface "Open design". */
11
14
  function designDeepLink(designId: string): string {
@@ -16,6 +19,17 @@ function designDeepLink(designId: string): string {
16
19
  });
17
20
  }
18
21
 
22
+ export class TweakSelectionsConflictError extends Error {
23
+ readonly statusCode = 409;
24
+
25
+ constructor() {
26
+ super(
27
+ "Tweak values changed elsewhere before this edit could be saved. Refresh the design and try again.",
28
+ );
29
+ this.name = "TweakSelectionsConflictError";
30
+ }
31
+ }
32
+
19
33
  export default defineAction({
20
34
  description:
21
35
  "Persist the user's live tweak knob values (accent color, density, " +
@@ -35,53 +49,61 @@ export default defineAction({
35
49
  "e.g. { 'theme-accent': '#0EA5E9', 'border-radius': 12, " +
36
50
  "'dark-mode': true }",
37
51
  ),
52
+ expectedSelectionsHash: z
53
+ .string()
54
+ .min(1)
55
+ .max(128)
56
+ .optional()
57
+ .describe(
58
+ "Optimistic-concurrency hash of the persisted tweak selection map this full snapshot was based on.",
59
+ ),
38
60
  }),
39
- run: async ({ designId, selections }) => {
61
+ run: async ({ designId, selections, expectedSelectionsHash }) => {
40
62
  await assertAccess("design", designId, "editor");
41
63
 
42
- const db = getDb();
43
- const now = new Date().toISOString();
44
-
45
- // Additive JSON merge: keep every other key in designs.data intact, only
46
- // merge into the tweakSelections sub-object.
47
- const [existingDesign] = await db
48
- .select({ data: schema.designs.data })
49
- .from(schema.designs)
50
- .where(eq(schema.designs.id, designId));
51
-
52
- let prevData: Record<string, unknown> = {};
53
- if (existingDesign?.data) {
54
- try {
55
- const parsed = JSON.parse(existingDesign.data);
56
- if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
57
- prevData = parsed;
58
- }
59
- } catch {
60
- // Stale/invalid JSON — start fresh but never drop the column.
61
- }
62
- }
63
-
64
- const prevSelections =
65
- prevData.tweakSelections &&
66
- typeof prevData.tweakSelections === "object" &&
67
- !Array.isArray(prevData.tweakSelections)
68
- ? (prevData.tweakSelections as Record<string, unknown>)
64
+ const readSelections = (data: DesignDataRecord) =>
65
+ data.tweakSelections &&
66
+ typeof data.tweakSelections === "object" &&
67
+ !Array.isArray(data.tweakSelections)
68
+ ? (data.tweakSelections as Record<string, unknown>)
69
69
  : {};
70
+ const selectionsAreApplied = (persisted: Record<string, unknown>) =>
71
+ Object.entries(selections).every(
72
+ ([key, value]) => persisted[key] === value,
73
+ );
70
74
 
71
- const mergedData = {
72
- ...prevData,
73
- tweakSelections: { ...prevSelections, ...selections },
74
- tweaksAppliedAt: now,
75
- };
76
-
77
- await db
78
- .update(schema.designs)
79
- .set({ data: JSON.stringify(mergedData), updatedAt: now })
80
- .where(eq(schema.designs.id, designId));
75
+ // Transactional CAS merge: keep every sibling key in designs.data and
76
+ // retry against the newest revision if another editor/action writes while
77
+ // this request is in flight.
78
+ const { data: persistedData } = await mutateDesignData({
79
+ designId,
80
+ mutate: (prevData, { updatedAt }) => {
81
+ const persistedSelections = readSelections(prevData);
82
+ const alreadyApplied = selectionsAreApplied(persistedSelections);
83
+ if (
84
+ expectedSelectionsHash !== undefined &&
85
+ tweakSelectionsHash(persistedSelections) !== expectedSelectionsHash &&
86
+ !alreadyApplied
87
+ ) {
88
+ throw new TweakSelectionsConflictError();
89
+ }
90
+ if (alreadyApplied) return prevData;
91
+ return {
92
+ ...prevData,
93
+ tweakSelections: { ...persistedSelections, ...selections },
94
+ tweaksAppliedAt: updatedAt,
95
+ };
96
+ },
97
+ isApplied: (data) => {
98
+ const persistedSelections = readSelections(data);
99
+ return selectionsAreApplied(persistedSelections);
100
+ },
101
+ });
81
102
 
82
103
  return {
83
104
  designId,
84
- appliedTweaks: mergedData.tweakSelections,
105
+ appliedTweaks: readSelections(persistedData),
106
+ selectionsHash: tweakSelectionsHash(readSelections(persistedData)),
85
107
  deepLink: designDeepLink(designId),
86
108
  };
87
109
  },
@@ -13,7 +13,7 @@ import "../server/db/index.js"; // ensure registerShareableResource runs
13
13
  import { hasCapability } from "../shared/design-source-capabilities.js";
14
14
  import type { DesignSourceCapabilities } from "../shared/design-source-capabilities.js";
15
15
  import {
16
- normalizeDesignSourceType,
16
+ designSourceTypeFromData,
17
17
  resolveDescriptorCapabilities,
18
18
  type DesignSourceDescriptor,
19
19
  } from "../shared/source-mode.js";
@@ -34,7 +34,7 @@ function resolveCapabilities(
34
34
  if (parsed.capabilities && typeof parsed.capabilities === "object") {
35
35
  return parsed.capabilities as DesignSourceCapabilities;
36
36
  }
37
- const sourceType = normalizeDesignSourceType(parsed.sourceType) ?? "inline";
37
+ const sourceType = designSourceTypeFromData(parsed);
38
38
  const descriptor: DesignSourceDescriptor =
39
39
  sourceType === "fusion"
40
40
  ? { sourceType, connected: parsed.connected === true }
@@ -5,7 +5,7 @@ import {
5
5
  getRequestOrgId,
6
6
  getRequestUserEmail,
7
7
  } from "@agent-native/core/server/request-context";
8
- import { and, eq, sql } from "drizzle-orm";
8
+ import { and, eq, isNull, sql } from "drizzle-orm";
9
9
  import { z } from "zod";
10
10
 
11
11
  import { getDb, schema } from "../server/db/index.js";
@@ -91,6 +91,17 @@ function stableConnectionId(
91
91
  return `localhost_${hash}`;
92
92
  }
93
93
 
94
+ const PREVIEW_TOKEN_DOMAIN = "agent-native-design-preview-v1\0";
95
+
96
+ /** One-way compatibility derivation shared with the core design-connect CLI. */
97
+ export function derivePreviewToken(bridgeToken: string): string {
98
+ return crypto
99
+ .createHash("sha256")
100
+ .update(PREVIEW_TOKEN_DOMAIN)
101
+ .update(bridgeToken)
102
+ .digest("hex");
103
+ }
104
+
94
105
  export default defineAction({
95
106
  description:
96
107
  "Register or refresh a localhost Design source connection produced by `agent-native design connect`. Stores the dev server URL, bridge URL, route manifest, and operation capabilities so the UI can later list local-code artboards.",
@@ -133,6 +144,12 @@ export default defineAction({
133
144
  .describe(
134
145
  "The bridge's real auth token minted at bridge start. Stored on the connection so grant-localhost-write-consent can read it without minting its own.",
135
146
  ),
147
+ previewToken: z
148
+ .string()
149
+ .optional()
150
+ .describe(
151
+ "Distinct read-only token for browser preview, snapshots, and live-edit bridge registration. Omit when passing bridgeToken to derive the compatible token automatically.",
152
+ ),
136
153
  status: z
137
154
  .enum(["connected", "detected", "manual", "error"])
138
155
  .optional()
@@ -181,6 +198,12 @@ export default defineAction({
181
198
  operation,
182
199
  status: "available" as const,
183
200
  }));
201
+ const ownerOrgScope = and(
202
+ eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
203
+ orgId
204
+ ? eq(schema.designLocalhostConnections.orgId, orgId)
205
+ : isNull(schema.designLocalhostConnections.orgId),
206
+ );
184
207
 
185
208
  // The id may already be taken by a DIFFERENT user (legacy ids were derived
186
209
  // from devServerUrl + rootPath without user scoping, so two users on the
@@ -189,15 +212,21 @@ export default defineAction({
189
212
  const existing = await db
190
213
  .select({
191
214
  ownerEmail: schema.designLocalhostConnections.ownerEmail,
215
+ orgId: schema.designLocalhostConnections.orgId,
216
+ previewToken: schema.designLocalhostConnections.previewToken,
192
217
  bridgeToken: schema.designLocalhostConnections.bridgeToken,
193
218
  })
194
219
  .from(schema.designLocalhostConnections)
195
220
  .where(eq(schema.designLocalhostConnections.id, id))
196
221
  .limit(1);
197
222
 
198
- if (existing[0] && existing[0].ownerEmail !== ownerEmail) {
223
+ if (
224
+ existing[0] &&
225
+ (existing[0].ownerEmail !== ownerEmail ||
226
+ (existing[0].orgId ?? null) !== orgId)
227
+ ) {
199
228
  throw new Error(
200
- `Connection id "${id}" already belongs to another user. ` +
229
+ `Connection id "${id}" already belongs to another user or organization. ` +
201
230
  "Omit id so a per-user connection id is derived instead.",
202
231
  );
203
232
  }
@@ -209,6 +238,13 @@ export default defineAction({
209
238
  explicitToken ||
210
239
  existing[0]?.bridgeToken ||
211
240
  crypto.randomBytes(32).toString("hex");
241
+ const explicitPreviewToken =
242
+ args.previewToken?.trim() ||
243
+ (explicitToken ? derivePreviewToken(nextBridgeToken) : undefined);
244
+ const nextPreviewToken =
245
+ explicitPreviewToken ||
246
+ existing[0]?.previewToken ||
247
+ derivePreviewToken(nextBridgeToken);
212
248
  const baseValues = {
213
249
  id,
214
250
  name: args.name ?? new URL(devServerUrl).host,
@@ -232,7 +268,12 @@ export default defineAction({
232
268
  // conflict a no-op.
233
269
  await db
234
270
  .insert(schema.designLocalhostConnections)
235
- .values({ ...baseValues, bridgeToken: nextBridgeToken, createdAt: now })
271
+ .values({
272
+ ...baseValues,
273
+ previewToken: nextPreviewToken,
274
+ bridgeToken: nextBridgeToken,
275
+ createdAt: now,
276
+ })
236
277
  .onConflictDoUpdate({
237
278
  target: schema.designLocalhostConnections.id,
238
279
  set: {
@@ -240,24 +281,27 @@ export default defineAction({
240
281
  bridgeToken: explicitToken
241
282
  ? nextBridgeToken
242
283
  : sql`coalesce(${schema.designLocalhostConnections.bridgeToken}, excluded.bridge_token)`,
284
+ previewToken: explicitPreviewToken
285
+ ? nextPreviewToken
286
+ : sql`coalesce(${schema.designLocalhostConnections.previewToken}, excluded.preview_token)`,
243
287
  },
244
- setWhere: eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
288
+ setWhere: ownerOrgScope,
245
289
  });
246
290
 
247
291
  // Return the token the row actually holds (owner-scoped, so a cross-user
248
292
  // no-op never leaks another user's token), not the one we minted — so
249
293
  // concurrent callers converge on the winner (no 401 on a lost race).
250
294
  const [stored] = await db
251
- .select({ bridgeToken: schema.designLocalhostConnections.bridgeToken })
295
+ .select({
296
+ bridgeToken: schema.designLocalhostConnections.bridgeToken,
297
+ previewToken: schema.designLocalhostConnections.previewToken,
298
+ })
252
299
  .from(schema.designLocalhostConnections)
253
- .where(
254
- and(
255
- eq(schema.designLocalhostConnections.id, id),
256
- eq(schema.designLocalhostConnections.ownerEmail, ownerEmail),
257
- ),
258
- )
300
+ .where(and(eq(schema.designLocalhostConnections.id, id), ownerOrgScope))
259
301
  .limit(1);
260
302
  const effectiveBridgeToken = stored?.bridgeToken ?? nextBridgeToken;
303
+ const effectivePreviewToken =
304
+ stored?.previewToken ?? derivePreviewToken(effectiveBridgeToken);
261
305
 
262
306
  return {
263
307
  id,
@@ -271,6 +315,8 @@ export default defineAction({
271
315
  capabilities,
272
316
  status: args.status,
273
317
  lastSeenAt: now,
318
+ // Safe for Design browser previews. It cannot authorize filesystem calls.
319
+ previewToken: effectivePreviewToken,
274
320
  // Returned so the caller can start the bridge with
275
321
  // `design connect --bridge-token <this>`, matching this row.
276
322
  bridgeToken: effectiveBridgeToken,
@@ -53,7 +53,7 @@ import {
53
53
  COMPONENT_PROP_PREFIX,
54
54
  } from "../shared/component-model.js";
55
55
  import { hasCapability } from "../shared/design-source-capabilities.js";
56
- import { normalizeDesignSourceType } from "../shared/source-mode.js";
56
+ import { designSourceTypeFromData } from "../shared/source-mode.js";
57
57
 
58
58
  // ─── Pure helpers ─────────────────────────────────────────────────────────────
59
59
 
@@ -259,24 +259,8 @@ export default defineAction({
259
259
  if (!access) throw new Error("Design not found");
260
260
 
261
261
  // ── Source type + capability gate ────────────────────────────────────────
262
- let rawSourceType: unknown = "inline";
263
262
  const rawData = (access.resource as { data?: unknown }).data;
264
- if (typeof rawData === "string") {
265
- try {
266
- const parsed: unknown = JSON.parse(rawData);
267
- if (
268
- parsed !== null &&
269
- typeof parsed === "object" &&
270
- "sourceType" in (parsed as object)
271
- ) {
272
- rawSourceType = (parsed as { sourceType: unknown }).sourceType;
273
- }
274
- } catch {
275
- // Default to inline.
276
- }
277
- }
278
-
279
- const sourceType = normalizeDesignSourceType(rawSourceType) ?? "inline";
263
+ const sourceType = designSourceTypeFromData(rawData);
280
264
  const caps = resolveSourceCapabilities(sourceType);
281
265
 
282
266
  // Real-app sources gate on `applyEdit` (bridge write hardening).
@@ -36,12 +36,13 @@ import { z } from "zod";
36
36
 
37
37
  import { getDb, schema } from "../server/db/index.js";
38
38
  import "../server/db/index.js"; // ensure registerShareableResource runs
39
+ import { mutateDesignData } from "../server/lib/design-data-mutation.js";
39
40
  import {
40
41
  resolveSourceCapabilities,
41
42
  resolveFusionCapabilities,
42
43
  } from "../shared/capability-resolver.js";
43
44
  import { hasCapability } from "../shared/design-source-capabilities.js";
44
- import { normalizeDesignSourceType } from "../shared/source-mode.js";
45
+ import { designSourceTypeFromData } from "../shared/source-mode.js";
45
46
 
46
47
  // ─── Helpers ──────────────────────────────────────────────────────────────────
47
48
 
@@ -127,8 +128,7 @@ export default defineAction({
127
128
 
128
129
  // ── Source type + capability check ──────────────────────────────────────
129
130
  const designData = parseDesignData(resource.data);
130
- const sourceType =
131
- normalizeDesignSourceType(designData["sourceType"]) ?? "inline";
131
+ const sourceType = designSourceTypeFromData(designData);
132
132
 
133
133
  // For fusion sources, resolve the Builder connection status first so that
134
134
  // resolveFusionCapabilities returns the CONNECTED map (with branch/deploy
@@ -219,10 +219,6 @@ export default defineAction({
219
219
  });
220
220
 
221
221
  // ── Persist branch metadata into the design's data blob ─────────────────
222
- const existingBranches = Array.isArray(designData["branches"])
223
- ? (designData["branches"] as unknown[])
224
- : [];
225
-
226
222
  const branchEntry = {
227
223
  branchName: builderResult.branchName,
228
224
  projectId: builderResult.projectId,
@@ -233,19 +229,43 @@ export default defineAction({
233
229
  createdAt: now,
234
230
  };
235
231
 
236
- const updatedData = JSON.stringify({
237
- ...designData,
238
- branches: [...existingBranches, branchEntry],
239
- // Upgrade the source type to fusion once a Builder branch is provisioned,
240
- // so the capability matrix reflects the new real-app tier.
241
- sourceType: "fusion",
232
+ await mutateDesignData({
233
+ designId,
234
+ mutate: (current) => {
235
+ const existingBranches = Array.isArray(current.branches)
236
+ ? current.branches
237
+ : [];
238
+ const alreadyAppended = existingBranches.some(
239
+ (candidate) =>
240
+ candidate !== null &&
241
+ typeof candidate === "object" &&
242
+ !Array.isArray(candidate) &&
243
+ (candidate as Record<string, unknown>).preSnapshotVersionId ===
244
+ versionId,
245
+ );
246
+ return {
247
+ ...current,
248
+ branches: alreadyAppended
249
+ ? existingBranches
250
+ : [...existingBranches, branchEntry],
251
+ // Upgrade the source type to fusion once a Builder branch is
252
+ // provisioned so the capability matrix reflects the real-app tier.
253
+ sourceType: "fusion",
254
+ };
255
+ },
256
+ isApplied: (current) =>
257
+ current.sourceType === "fusion" &&
258
+ Array.isArray(current.branches) &&
259
+ current.branches.some(
260
+ (candidate) =>
261
+ candidate !== null &&
262
+ typeof candidate === "object" &&
263
+ !Array.isArray(candidate) &&
264
+ (candidate as Record<string, unknown>).preSnapshotVersionId ===
265
+ versionId,
266
+ ),
242
267
  });
243
268
 
244
- await db
245
- .update(schema.designs)
246
- .set({ data: updatedData, updatedAt: now })
247
- .where(eq(schema.designs.id, designId));
248
-
249
269
  return {
250
270
  designId,
251
271
  sourceType: "fusion" as const,
@@ -20,15 +20,14 @@ import {
20
20
  } from "@agent-native/core/server";
21
21
  import { getRequestUserEmail } from "@agent-native/core/server/request-context";
22
22
  import { assertAccess } from "@agent-native/core/sharing";
23
- import { eq } from "drizzle-orm";
24
23
  import { z } from "zod";
25
24
 
26
- import { getDb, schema } from "../server/db/index.js";
25
+ import { schema } from "../server/db/index.js";
27
26
  import "../server/db/index.js"; // ensure registerShareableResource runs
27
+ import { mutateDesignData } from "../server/lib/design-data-mutation.js";
28
28
  import { resolveBuilderStatus } from "../shared/builder-app.js";
29
29
  import {
30
30
  FULL_APP_BUILDING_ENABLED,
31
- parseDesignDataBlob,
32
31
  readFusionApp,
33
32
  writeFusionApp,
34
33
  } from "../shared/full-app.js";
@@ -170,23 +169,28 @@ export default defineAction({
170
169
  });
171
170
 
172
171
  const now = new Date().toISOString();
173
- const db = getDb();
174
- const nextData = writeFusionApp(parseDesignDataBlob(design.data), {
172
+ const nextFusionApp = {
175
173
  projectId: result.projectId,
176
174
  branchName: result.branchName,
177
175
  editorUrl: result.url,
178
- status: "building",
176
+ status: "building" as const,
179
177
  statusMessage:
180
178
  "App is being generated. Call sync-fusion-app to check progress.",
181
179
  createdAt: now,
182
180
  updatedAt: now,
181
+ };
182
+ await mutateDesignData({
183
+ designId,
184
+ mutate: (current) => writeFusionApp(current, nextFusionApp),
185
+ isApplied: (current) => {
186
+ const persisted = readFusionApp(current);
187
+ return (
188
+ persisted?.projectId === result.projectId &&
189
+ persisted.branchName === result.branchName
190
+ );
191
+ },
183
192
  });
184
193
 
185
- await db
186
- .update(schema.designs)
187
- .set({ data: JSON.stringify(nextData), updatedAt: now })
188
- .where(eq(schema.designs.id, designId));
189
-
190
194
  return {
191
195
  status: "building" as const,
192
196
  designId,