@agent-native/core 0.80.4 → 0.80.6

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 (341) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +28 -2
  3. package/corpus/core/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
  4. package/corpus/core/docs/content/locales/ar-SA/template-design.mdx +1 -1
  5. package/corpus/core/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
  6. package/corpus/core/docs/content/locales/de-DE/template-design.mdx +1 -1
  7. package/corpus/core/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
  8. package/corpus/core/docs/content/locales/es-ES/template-design.mdx +1 -1
  9. package/corpus/core/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
  10. package/corpus/core/docs/content/locales/fr-FR/template-design.mdx +1 -1
  11. package/corpus/core/docs/content/locales/hi-IN/template-design.mdx +1 -1
  12. package/corpus/core/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
  13. package/corpus/core/docs/content/locales/ja-JP/template-design.mdx +1 -1
  14. package/corpus/core/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
  15. package/corpus/core/docs/content/locales/ko-KR/template-design.mdx +1 -1
  16. package/corpus/core/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
  17. package/corpus/core/docs/content/locales/pt-BR/template-design.mdx +1 -1
  18. package/corpus/core/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
  19. package/corpus/core/docs/content/locales/zh-CN/template-design.mdx +1 -1
  20. package/corpus/core/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
  21. package/corpus/core/docs/content/locales/zh-TW/template-design.mdx +1 -1
  22. package/corpus/core/docs/content/real-time-collaboration.mdx +2 -2
  23. package/corpus/core/docs/content/template-design.mdx +1 -1
  24. package/corpus/core/package.json +3 -7
  25. package/corpus/core/src/action.ts +21 -0
  26. package/corpus/core/src/agent/durable-background.ts +32 -11
  27. package/corpus/core/src/agent/production-agent.ts +11 -1
  28. package/corpus/core/src/agent/run-loop-with-resume.ts +3 -1
  29. package/corpus/core/src/brand-kit/fig/index.ts +17 -37
  30. package/corpus/core/src/cli/create.ts +1 -0
  31. package/corpus/core/src/cli/index.ts +1 -1
  32. package/corpus/core/src/cli/mcp-config-writers.ts +187 -51
  33. package/corpus/core/src/cli/skills.ts +18 -3
  34. package/corpus/core/src/client/AssistantChat.tsx +11 -1
  35. package/corpus/core/src/client/agent-chat.ts +12 -1
  36. package/corpus/core/src/client/composer/PromptComposer.tsx +100 -41
  37. package/corpus/core/src/client/org/OrgSwitcher.tsx +1 -1
  38. package/corpus/core/src/client/sharing/ShareButton.tsx +3 -27
  39. package/corpus/core/src/collab/presence.ts +1 -1
  40. package/corpus/core/src/mcp/connect-route.ts +34 -5
  41. package/corpus/core/src/server/action-discovery.ts +4 -0
  42. package/corpus/core/src/server/agent-chat-plugin.ts +20 -0
  43. package/corpus/core/src/sharing/registry.ts +17 -0
  44. package/corpus/core/src/templates/default/pnpm-workspace.yaml +1 -0
  45. package/corpus/core/src/templates/workspace-root/pnpm-workspace.yaml +1 -0
  46. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +2 -0
  47. package/corpus/templates/analytics/actions/update-dashboard.ts +8 -0
  48. package/corpus/templates/analytics/app/components/layout/Layout.tsx +1 -1
  49. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +117 -33
  50. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +4 -0
  51. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +7 -0
  52. package/corpus/templates/analytics/changelog/2026-06-27-dashboards-can-now-nest-under-a-parent-in-the-sidebar-via-a-.md +6 -0
  53. package/corpus/templates/analytics/changelog/2026-06-29-analytics-pages-now-sit-closer-under-the-header-with-tighter.md +6 -0
  54. package/corpus/templates/assets/actions/generate-image-batch.ts +3 -0
  55. package/corpus/templates/assets/actions/generate-image.ts +3 -0
  56. package/corpus/templates/assets/changelog/2026-06-29-longer-agent-generation-window.md +6 -0
  57. package/corpus/templates/assets/netlify.toml +3 -3
  58. package/corpus/templates/assets/server/plugins/agent-chat.ts +4 -1
  59. package/corpus/templates/clips/app/components/capture-install-options.tsx +7 -8
  60. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +1 -1
  61. package/corpus/templates/content/AGENTS.md +57 -44
  62. package/corpus/templates/content/README.md +16 -0
  63. package/corpus/templates/content/actions/_builder-cms-read-client.ts +125 -0
  64. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +28 -6
  65. package/corpus/templates/content/actions/_builder-cms-write-adapter.ts +238 -69
  66. package/corpus/templates/content/actions/_builder-cms-write-settings.ts +125 -43
  67. package/corpus/templates/content/actions/_database-source-utils.ts +746 -97
  68. package/corpus/templates/content/actions/_database-utils.ts +17 -14
  69. package/corpus/templates/content/actions/_federation-join.ts +14 -2
  70. package/corpus/templates/content/actions/_join-suggestion.ts +89 -14
  71. package/corpus/templates/content/actions/_local-file-documents.ts +2 -1
  72. package/corpus/templates/content/actions/attach-content-database-source.ts +124 -5
  73. package/corpus/templates/content/actions/bind-content-database-source-field.ts +256 -0
  74. package/corpus/templates/content/actions/change-content-database-source-role.ts +420 -0
  75. package/corpus/templates/content/actions/create-document.ts +8 -0
  76. package/corpus/templates/content/actions/execute-builder-source-batch.ts +282 -0
  77. package/corpus/templates/content/actions/execute-builder-source-execution.ts +220 -8
  78. package/corpus/templates/content/actions/get-content-database.ts +10 -2
  79. package/corpus/templates/content/actions/prepare-builder-source-execution.ts +19 -2
  80. package/corpus/templates/content/actions/prepare-builder-source-review.ts +47 -15
  81. package/corpus/templates/content/actions/refresh-content-database-source.ts +11 -4
  82. package/corpus/templates/content/actions/review-content-database-source-change-set.ts +6 -2
  83. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +32 -12
  84. package/corpus/templates/content/actions/stage-builder-revision.ts +14 -10
  85. package/corpus/templates/content/actions/validate-builder-source-execution.ts +25 -3
  86. package/corpus/templates/content/app/blocks/BuilderDocsBlocks.tsx +10 -0
  87. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +1 -0
  88. package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +185 -0
  89. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +1520 -553
  90. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +3 -0
  91. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +150 -1
  92. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +19 -0
  93. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +647 -66
  94. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +543 -204
  95. package/corpus/templates/content/app/components/editor/extensions/ContentReferenceNode.tsx +82 -0
  96. package/corpus/templates/content/app/global.css +9 -0
  97. package/corpus/templates/content/app/hooks/use-content-database.ts +62 -0
  98. package/corpus/templates/content/app/i18n/zh-TW.ts +76 -0
  99. package/corpus/templates/content/app/i18n-data.ts +924 -0
  100. package/corpus/templates/content/changelog/2026-06-29-builder-review-checks-now-preserve-publish-and-unpublish-cho.md +6 -0
  101. package/corpus/templates/content/changelog/2026-06-29-database-sources-can-now-be-added-as-more-rows-or-matched.md +6 -0
  102. package/corpus/templates/content/changelog/2026-06-29-fixed-loading-the-final-rows-in-larger-databases-without-bre.md +6 -0
  103. package/corpus/templates/content/changelog/2026-06-29-reusable-mdx-references-now-preview-linked-local-documents-i.md +6 -0
  104. package/corpus/templates/content/shared/api.ts +91 -1
  105. package/corpus/templates/content/shared/builder-docs-blocks.ts +8 -0
  106. package/corpus/templates/content/shared/builder-mdx.ts +141 -4
  107. package/corpus/templates/content/shared/nfm.ts +62 -0
  108. package/corpus/templates/content/vite.config.ts +14 -7
  109. package/corpus/templates/design/.agents/skills/design-systems/SKILL.md +10 -11
  110. package/corpus/templates/design/AGENTS.md +5 -0
  111. package/corpus/templates/design/_gitignore +7 -0
  112. package/corpus/templates/design/actions/apply-visual-edit.ts +22 -12
  113. package/corpus/templates/design/actions/create-design-system.ts +15 -4
  114. package/corpus/templates/design/actions/create-file.ts +20 -1
  115. package/corpus/templates/design/actions/delete-design-system.ts +2 -2
  116. package/corpus/templates/design/actions/duplicate-design.ts +35 -9
  117. package/corpus/templates/design/actions/generate-design.ts +146 -69
  118. package/corpus/templates/design/actions/generate-screens.ts +51 -19
  119. package/corpus/templates/design/actions/get-design-snapshot.ts +5 -1
  120. package/corpus/templates/design/actions/get-design.ts +2 -0
  121. package/corpus/templates/design/actions/import-design-project.ts +28 -9
  122. package/corpus/templates/design/actions/import-document.ts +22 -8
  123. package/corpus/templates/design/actions/index-design-system-with-builder.ts +6 -153
  124. package/corpus/templates/design/actions/navigate.ts +105 -54
  125. package/corpus/templates/design/actions/set-default-design-system.ts +17 -3
  126. package/corpus/templates/design/actions/show-design-questions.ts +2 -0
  127. package/corpus/templates/design/actions/update-design.ts +2 -1
  128. package/corpus/templates/design/actions/view-screen.ts +12 -6
  129. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +4 -4
  130. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +159 -41
  131. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +8 -4
  132. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +37 -42
  133. package/corpus/templates/design/app/components/design/EditPanel.tsx +336 -183
  134. package/corpus/templates/design/app/components/design/LayersPanel.tsx +153 -34
  135. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +135 -33
  136. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +6 -0
  137. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +2 -1
  138. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +4 -10
  139. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +84 -43
  140. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +4 -4
  141. package/corpus/templates/design/app/components/design/inspector/{FigmaColorPicker.tsx → DesignColorPicker.tsx} +89 -62
  142. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +14 -26
  143. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +40 -12
  144. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +13 -4
  145. package/corpus/templates/design/app/components/design/inspector/SHADER_INTEGRATION.md +13 -13
  146. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +13 -9
  147. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +13 -4
  148. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +6 -6
  149. package/corpus/templates/design/app/components/design/inspector/{figma-icons.tsx → design-icons.tsx} +2 -2
  150. package/corpus/templates/design/app/components/design/inspector/index.ts +12 -12
  151. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +5 -2
  152. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +3 -3
  153. package/corpus/templates/design/app/components/layout/Layout.tsx +5 -2
  154. package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
  155. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +167 -91
  156. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +16 -3
  157. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +15 -0
  158. package/corpus/templates/design/app/global.css +4 -0
  159. package/corpus/templates/design/app/hooks/use-design-system.ts +4 -1
  160. package/corpus/templates/design/app/hooks/use-navigation-state.ts +38 -3
  161. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +9 -2
  162. package/corpus/templates/design/app/i18n/zh-TW.ts +22 -0
  163. package/corpus/templates/design/app/i18n-data.ts +527 -0
  164. package/corpus/templates/design/app/lib/design-systems.ts +6 -4
  165. package/corpus/templates/design/app/lib/design-templates.ts +15 -3
  166. package/corpus/templates/design/app/pages/DesignEditor.tsx +641 -158
  167. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +42 -34
  168. package/corpus/templates/design/app/pages/VisualEdit.tsx +100 -0
  169. package/corpus/templates/design/app/root.tsx +42 -9
  170. package/corpus/templates/design/app/routes/visual-edit.tsx +7 -0
  171. package/corpus/templates/design/changelog/2026-06-28-live-cursors-now-keep-names-beside-a-cleaner-pointer-marker.md +1 -1
  172. package/corpus/templates/design/changelog/2026-06-28-mode-switching-now-lives-in-a-compact-bottom-right-icon-dock.md +1 -1
  173. package/corpus/templates/design/changelog/2026-06-28-visual-code-design-mode.md +6 -0
  174. package/corpus/templates/design/changelog/2026-06-29-design-generation-prompts-now-show-connect-ai-before-sending.md +6 -0
  175. package/corpus/templates/design/changelog/2026-06-29-design-mode-top-chrome-now-groups-collaborators-preview-shar.md +1 -1
  176. package/corpus/templates/design/changelog/2026-06-29-fixed-design-editor-exports-and-gradient-edits-so-multi-scre.md +6 -0
  177. package/corpus/templates/design/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
  178. package/corpus/templates/design/changelog/2026-06-29-png-export-handles-modern-css-color-functions-more-reliably.md +6 -0
  179. package/corpus/templates/design/changelog/2026-06-29-png-export-now-ignores-duplicate-clicks-while-a-download-is-.md +6 -0
  180. package/corpus/templates/design/changelog/2026-06-29-public-visual-edit-and-shared-design-links-now-invite-visito.md +6 -0
  181. package/corpus/templates/design/changelog/2026-06-29-visual-edit-actions-now-reject-missing-design-targets-before.md +6 -0
  182. package/corpus/templates/design/e2e/README.md +71 -0
  183. package/corpus/templates/design/e2e/global-setup.ts +157 -0
  184. package/corpus/templates/design/e2e/helpers.ts +148 -0
  185. package/corpus/templates/design/netlify.toml +3 -4
  186. package/corpus/templates/design/package.json +5 -1
  187. package/corpus/templates/design/playwright.config.ts +51 -0
  188. package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +121 -0
  189. package/corpus/templates/design/server/handlers/uploads.ts +89 -38
  190. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +181 -0
  191. package/corpus/templates/design/server/lib/coding-handoff.ts +33 -4
  192. package/corpus/templates/design/server/lib/design-export.ts +35 -8
  193. package/corpus/templates/design/server/plugins/agent-chat.ts +4 -0
  194. package/corpus/templates/design/server/plugins/auth.ts +10 -1
  195. package/corpus/templates/design/server/routes/api/index-design-system-with-builder.post.ts +1 -0
  196. package/corpus/templates/design/shared/api.ts +2 -0
  197. package/corpus/templates/design/shared/apply-edits.ts +12 -8
  198. package/corpus/templates/design/shared/canvas-math.ts +19 -1
  199. package/corpus/templates/design/shared/code-layer.ts +87 -28
  200. package/corpus/templates/design/shared/generation-session.ts +1 -0
  201. package/corpus/templates/design/shared/resolve-tweaks.ts +4 -3
  202. package/corpus/templates/design/shared/shader-presets.ts +10 -0
  203. package/corpus/templates/design/shared/source-mode.ts +9 -0
  204. package/corpus/templates/design/vitest.config.ts +2 -1
  205. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +1 -1
  206. package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +1 -1
  207. package/corpus/templates/plan/AGENTS.md +1 -1
  208. package/corpus/templates/plan/actions/create-plan-design.ts +1 -1
  209. package/corpus/templates/plan/actions/validate-local-plan-source.ts +2 -10
  210. package/corpus/templates/plan/app/pages/PlansPage.tsx +1 -0
  211. package/corpus/templates/plan/docs/commenting-ux-plan.md +2 -2
  212. package/corpus/templates/plan/server/plan-content.ts +1 -1
  213. package/corpus/templates/plan/server/ui-plan-html.ts +2 -2
  214. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +6 -153
  215. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +53 -38
  216. package/corpus/templates/slides/app/components/design-system/{fig-import-response.ts → builder-index-response.ts} +9 -6
  217. package/corpus/templates/slides/app/components/visual-editor/MultiSelectChip.tsx +1 -1
  218. package/corpus/templates/slides/changelog/2026-06-29-design-system-setup-now-indexes-uploaded-design-files-throug.md +6 -0
  219. package/corpus/templates/slides/changelog/2026-06-29-longer-hosted-agent-turns.md +6 -0
  220. package/corpus/templates/slides/netlify.toml +3 -6
  221. package/corpus/templates/slides/server/handlers/index-design-system-with-builder.ts +116 -0
  222. package/corpus/templates/slides/server/lib/builder-design-system-proxy.ts +179 -0
  223. package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -1
  224. package/corpus/templates/slides/server/routes/api/index-design-system-with-builder.post.ts +1 -0
  225. package/dist/action.d.ts +15 -0
  226. package/dist/action.d.ts.map +1 -1
  227. package/dist/action.js +6 -0
  228. package/dist/action.js.map +1 -1
  229. package/dist/agent/durable-background.d.ts +9 -7
  230. package/dist/agent/durable-background.d.ts.map +1 -1
  231. package/dist/agent/durable-background.js +27 -11
  232. package/dist/agent/durable-background.js.map +1 -1
  233. package/dist/agent/production-agent.d.ts +7 -0
  234. package/dist/agent/production-agent.d.ts.map +1 -1
  235. package/dist/agent/production-agent.js +4 -1
  236. package/dist/agent/production-agent.js.map +1 -1
  237. package/dist/agent/run-loop-with-resume.d.ts +2 -1
  238. package/dist/agent/run-loop-with-resume.d.ts.map +1 -1
  239. package/dist/agent/run-loop-with-resume.js +2 -2
  240. package/dist/agent/run-loop-with-resume.js.map +1 -1
  241. package/dist/brand-kit/fig/index.d.ts +4 -4
  242. package/dist/brand-kit/fig/index.d.ts.map +1 -1
  243. package/dist/brand-kit/fig/index.js +14 -27
  244. package/dist/brand-kit/fig/index.js.map +1 -1
  245. package/dist/cli/create.d.ts.map +1 -1
  246. package/dist/cli/create.js +1 -0
  247. package/dist/cli/create.js.map +1 -1
  248. package/dist/cli/index.js +1 -1
  249. package/dist/cli/index.js.map +1 -1
  250. package/dist/cli/mcp-config-writers.d.ts +15 -7
  251. package/dist/cli/mcp-config-writers.d.ts.map +1 -1
  252. package/dist/cli/mcp-config-writers.js +198 -45
  253. package/dist/cli/mcp-config-writers.js.map +1 -1
  254. package/dist/cli/skills.d.ts +1 -1
  255. package/dist/cli/skills.d.ts.map +1 -1
  256. package/dist/cli/skills.js +18 -3
  257. package/dist/cli/skills.js.map +1 -1
  258. package/dist/client/AssistantChat.d.ts.map +1 -1
  259. package/dist/client/AssistantChat.js +11 -1
  260. package/dist/client/AssistantChat.js.map +1 -1
  261. package/dist/client/agent-chat.d.ts.map +1 -1
  262. package/dist/client/agent-chat.js +15 -2
  263. package/dist/client/agent-chat.js.map +1 -1
  264. package/dist/client/composer/PromptComposer.d.ts.map +1 -1
  265. package/dist/client/composer/PromptComposer.js +35 -1
  266. package/dist/client/composer/PromptComposer.js.map +1 -1
  267. package/dist/client/org/OrgSwitcher.js +1 -1
  268. package/dist/client/org/OrgSwitcher.js.map +1 -1
  269. package/dist/client/sharing/ShareButton.d.ts +1 -1
  270. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  271. package/dist/client/sharing/ShareButton.js +2 -15
  272. package/dist/client/sharing/ShareButton.js.map +1 -1
  273. package/dist/collab/presence.d.ts +1 -1
  274. package/dist/collab/presence.js +1 -1
  275. package/dist/collab/presence.js.map +1 -1
  276. package/dist/collab/routes.d.ts +1 -1
  277. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  278. package/dist/mcp/connect-route.d.ts.map +1 -1
  279. package/dist/mcp/connect-route.js +34 -5
  280. package/dist/mcp/connect-route.js.map +1 -1
  281. package/dist/notifications/routes.d.ts +2 -2
  282. package/dist/observability/routes.d.ts +8 -8
  283. package/dist/resources/handlers.d.ts +1 -1
  284. package/dist/server/action-discovery.d.ts.map +1 -1
  285. package/dist/server/action-discovery.js +5 -0
  286. package/dist/server/action-discovery.js.map +1 -1
  287. package/dist/server/agent-chat-plugin.d.ts +6 -0
  288. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  289. package/dist/server/agent-chat-plugin.js +12 -3
  290. package/dist/server/agent-chat-plugin.js.map +1 -1
  291. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  292. package/dist/sharing/registry.d.ts.map +1 -1
  293. package/dist/sharing/registry.js +11 -0
  294. package/dist/sharing/registry.js.map +1 -1
  295. package/dist/templates/default/pnpm-workspace.yaml +1 -0
  296. package/dist/templates/workspace-root/pnpm-workspace.yaml +1 -0
  297. package/docs/content/locales/ar-SA/real-time-collaboration.mdx +2 -2
  298. package/docs/content/locales/ar-SA/template-design.mdx +1 -1
  299. package/docs/content/locales/de-DE/real-time-collaboration.mdx +3 -3
  300. package/docs/content/locales/de-DE/template-design.mdx +1 -1
  301. package/docs/content/locales/es-ES/real-time-collaboration.mdx +2 -2
  302. package/docs/content/locales/es-ES/template-design.mdx +1 -1
  303. package/docs/content/locales/fr-FR/real-time-collaboration.mdx +2 -2
  304. package/docs/content/locales/fr-FR/template-design.mdx +1 -1
  305. package/docs/content/locales/hi-IN/template-design.mdx +1 -1
  306. package/docs/content/locales/ja-JP/real-time-collaboration.mdx +2 -2
  307. package/docs/content/locales/ja-JP/template-design.mdx +1 -1
  308. package/docs/content/locales/ko-KR/real-time-collaboration.mdx +2 -2
  309. package/docs/content/locales/ko-KR/template-design.mdx +1 -1
  310. package/docs/content/locales/pt-BR/real-time-collaboration.mdx +2 -2
  311. package/docs/content/locales/pt-BR/template-design.mdx +1 -1
  312. package/docs/content/locales/zh-CN/real-time-collaboration.mdx +2 -2
  313. package/docs/content/locales/zh-CN/template-design.mdx +1 -1
  314. package/docs/content/locales/zh-TW/real-time-collaboration.mdx +2 -2
  315. package/docs/content/locales/zh-TW/template-design.mdx +1 -1
  316. package/docs/content/real-time-collaboration.mdx +2 -2
  317. package/docs/content/template-design.mdx +1 -1
  318. package/package.json +3 -7
  319. package/src/templates/default/pnpm-workspace.yaml +1 -0
  320. package/src/templates/workspace-root/pnpm-workspace.yaml +1 -0
  321. package/corpus/core/src/brand-kit/fig/decode.ts +0 -410
  322. package/corpus/core/src/brand-kit/fig/extract-design-system.ts +0 -947
  323. package/corpus/core/src/brand-kit/fig/fig-to-html.ts +0 -1906
  324. package/corpus/templates/design/actions/import-figma.ts +0 -55
  325. package/corpus/templates/design/changelog/2026-06-28-figma-style-code-design-mode.md +0 -6
  326. package/corpus/templates/design/server/handlers/import-figma-system.ts +0 -86
  327. package/corpus/templates/design/server/routes/api/import-figma-system.post.ts +0 -1
  328. package/corpus/templates/slides/server/handlers/import-figma-system.ts +0 -101
  329. package/corpus/templates/slides/server/routes/api/import-figma-system.post.ts +0 -1
  330. package/dist/brand-kit/fig/decode.d.ts +0 -33
  331. package/dist/brand-kit/fig/decode.d.ts.map +0 -1
  332. package/dist/brand-kit/fig/decode.js +0 -358
  333. package/dist/brand-kit/fig/decode.js.map +0 -1
  334. package/dist/brand-kit/fig/extract-design-system.d.ts +0 -44
  335. package/dist/brand-kit/fig/extract-design-system.d.ts.map +0 -1
  336. package/dist/brand-kit/fig/extract-design-system.js +0 -752
  337. package/dist/brand-kit/fig/extract-design-system.js.map +0 -1
  338. package/dist/brand-kit/fig/fig-to-html.d.ts +0 -246
  339. package/dist/brand-kit/fig/fig-to-html.d.ts.map +0 -1
  340. package/dist/brand-kit/fig/fig-to-html.js +0 -1506
  341. package/dist/brand-kit/fig/fig-to-html.js.map +0 -1
@@ -1,11 +1,21 @@
1
1
  import { useT } from "@agent-native/core/client";
2
+ import { BUILDER_CMS_SAFE_WRITE_MODEL } from "@shared/api";
2
3
  import type {
4
+ BuilderCmsPublicationTransitionIntent,
5
+ BuilderCmsWriteEffect,
3
6
  ContentDatabaseSource,
4
- ContentDatabaseSourceChangeSet,
5
7
  ContentDatabaseSourceReviewPayload,
6
8
  DocumentPropertyValue,
9
+ ExecuteBuilderSourceBatchTransition,
10
+ ExecuteBuilderSourceBatchResponse,
7
11
  } from "@shared/api";
8
- import { IconCheck, IconX } from "@tabler/icons-react";
12
+ import {
13
+ IconAlertTriangle,
14
+ IconCheck,
15
+ IconCloudUpload,
16
+ IconX,
17
+ } from "@tabler/icons-react";
18
+ import { useEffect, useMemo, useState } from "react";
9
19
 
10
20
  import { Button } from "@/components/ui/button";
11
21
  import {
@@ -16,54 +26,190 @@ import {
16
26
  } from "@/components/ui/dialog";
17
27
  import { Spinner } from "@/components/ui/spinner";
18
28
 
19
- function sourceRiskClass(risk: ContentDatabaseSourceChangeSet["riskLevel"]) {
20
- if (risk === "high") {
21
- return "rounded border border-destructive/40 bg-destructive/10 px-1.5 py-0.5 text-destructive";
29
+ export type BuilderReviewPublicationTransitionSelection = {
30
+ publicationTransition: BuilderCmsPublicationTransitionIntent;
31
+ confirmUnpublish?: boolean;
32
+ };
33
+
34
+ export type BuilderReviewPublicationTransitionSelections = Record<
35
+ string,
36
+ BuilderReviewPublicationTransitionSelection
37
+ >;
38
+
39
+ export type BuilderReviewPublicationTransitions = Record<
40
+ string,
41
+ ExecuteBuilderSourceBatchTransition
42
+ >;
43
+
44
+ const EFFECT_LABELS: Record<
45
+ BuilderCmsWriteEffect,
46
+ { tag: string; sentence: string }
47
+ > = {
48
+ create_draft: { tag: "New", sentence: "Creates a new draft entry" },
49
+ update_in_place: { tag: "Edit", sentence: "Updates the live entry" },
50
+ autosave: { tag: "Draft", sentence: "Saves a draft revision" },
51
+ publish: { tag: "Publish", sentence: "Publishes this entry" },
52
+ unpublish: { tag: "Unpublish", sentence: "Unpublishes the live entry" },
53
+ };
54
+
55
+ export function builderReviewRowEffectLabel(effect: BuilderCmsWriteEffect) {
56
+ return EFFECT_LABELS[effect] ?? EFFECT_LABELS.update_in_place;
57
+ }
58
+
59
+ /** The effect a row will actually run, accounting for a chosen transition. */
60
+ export function builderReviewEffectiveRowEffect(
61
+ baseEffect: BuilderCmsWriteEffect,
62
+ selection?: BuilderReviewPublicationTransitionSelection,
63
+ ): BuilderCmsWriteEffect {
64
+ // A create has no Builder entry yet, so a publish/unpublish transition can't
65
+ // apply — the adapter always writes a draft (create_draft) when there's no
66
+ // entry id. Never let a transition relabel a create.
67
+ if (baseEffect === "create_draft") return "create_draft";
68
+ if (selection?.publicationTransition === "publish") return "publish";
69
+ if (selection?.publicationTransition === "unpublish") return "unpublish";
70
+ return baseEffect;
71
+ }
72
+
73
+ export function builderReviewIntentSummary(
74
+ rows: { changeSetId: string; effect: BuilderCmsWriteEffect }[],
75
+ selections: BuilderReviewPublicationTransitionSelections,
76
+ ) {
77
+ const counts: Record<BuilderCmsWriteEffect, number> = {
78
+ create_draft: 0,
79
+ update_in_place: 0,
80
+ autosave: 0,
81
+ publish: 0,
82
+ unpublish: 0,
83
+ };
84
+ for (const row of rows) {
85
+ counts[
86
+ builderReviewEffectiveRowEffect(row.effect, selections[row.changeSetId])
87
+ ] += 1;
88
+ }
89
+ const parts: string[] = [];
90
+ if (counts.create_draft) {
91
+ parts.push(
92
+ `${counts.create_draft} draft${counts.create_draft === 1 ? "" : "s"} to create`,
93
+ );
22
94
  }
23
- if (risk === "medium") {
24
- return "rounded border border-amber-300 bg-amber-50 px-1.5 py-0.5 text-amber-700";
95
+ if (counts.update_in_place) {
96
+ parts.push(
97
+ `${counts.update_in_place} update${counts.update_in_place === 1 ? "" : "s"}`,
98
+ );
25
99
  }
26
- return "rounded border border-emerald-300 bg-emerald-50 px-1.5 py-0.5 text-emerald-700";
100
+ if (counts.autosave) {
101
+ parts.push(
102
+ `${counts.autosave} draft save${counts.autosave === 1 ? "" : "s"}`,
103
+ );
104
+ }
105
+ if (counts.publish) parts.push(`${counts.publish} to publish`);
106
+ if (counts.unpublish) parts.push(`${counts.unpublish} to unpublish`);
107
+ return parts.join(" · ") || "No changes";
27
108
  }
28
109
 
29
- function sourceValueText(value: DocumentPropertyValue, emptyLabel: string) {
30
- if (value === null || value === undefined || value === "") return emptyLabel;
31
- if (Array.isArray(value)) return value.join(", ") || emptyLabel;
32
- if (typeof value === "object") return JSON.stringify(value);
33
- return String(value);
110
+ export function builderReviewDestinationLine(args: {
111
+ rows: { changeSetId: string; effect: BuilderCmsWriteEffect }[];
112
+ selections: BuilderReviewPublicationTransitionSelections;
113
+ liveWritesEnabled: boolean;
114
+ }) {
115
+ if (!args.liveWritesEnabled) {
116
+ return "Checks the update only — nothing is sent to Builder.";
117
+ }
118
+ const effects = new Set(
119
+ args.rows.map((row) =>
120
+ builderReviewEffectiveRowEffect(
121
+ row.effect,
122
+ args.selections[row.changeSetId],
123
+ ),
124
+ ),
125
+ );
126
+ if (effects.has("unpublish")) {
127
+ return "Unpublishes selected entries in Builder.";
128
+ }
129
+ if (effects.has("publish")) {
130
+ return "Publishes selected entries in Builder.";
131
+ }
132
+ if (effects.size === 1 && effects.has("create_draft")) {
133
+ return args.rows.length === 1
134
+ ? "Writes a new draft to Builder — won't publish."
135
+ : "Writes new drafts to Builder — nothing is published.";
136
+ }
137
+ return "Updates content in Builder — publication state is preserved.";
34
138
  }
35
139
 
36
- function sourceBuilderReadModeSummary(
37
- source: ContentDatabaseSource,
38
- t: ReturnType<typeof useT>,
39
- ) {
40
- if (source.metadata.readMode === "builder-api")
41
- return t("database.builderApiReadOnly");
42
- if (source.metadata.readMode === "local-fixture")
43
- return t("database.localFixture");
44
- if (source.metadata.readMode === "unconfigured")
45
- return t("database.notConfigured");
46
- if (source.metadata.readMode === "error") return t("database.readError");
47
- return t("database.localReview");
140
+ export function builderReviewResultStatus(status?: string): {
141
+ // i18n key under the `database.` namespace; resolved by the caller via t().
142
+ labelKey: string;
143
+ tone: "ok" | "warn" | "danger" | "muted";
144
+ } {
145
+ switch (status) {
146
+ case "succeeded":
147
+ return { labelKey: "pushed", tone: "ok" };
148
+ case "validated":
149
+ return { labelKey: "ready", tone: "ok" };
150
+ case "partial":
151
+ case "blocked":
152
+ return { labelKey: "needsAttention", tone: "warn" };
153
+ case "failed":
154
+ return { labelKey: "failedYouCanRetry", tone: "danger" };
155
+ case "stale":
156
+ return { labelKey: "needsAFreshReview", tone: "warn" };
157
+ case "running":
158
+ return { labelKey: "working", tone: "muted" };
159
+ case "write_disabled":
160
+ return { labelKey: "checksOnly", tone: "muted" };
161
+ default:
162
+ return { labelKey: "ready", tone: "muted" };
163
+ }
164
+ }
165
+
166
+ function resultToneClass(tone: "ok" | "warn" | "danger" | "muted") {
167
+ if (tone === "ok") return "text-emerald-600 dark:text-emerald-400";
168
+ if (tone === "warn") return "text-amber-700 dark:text-amber-300";
169
+ if (tone === "danger") return "text-destructive";
170
+ return "text-muted-foreground";
48
171
  }
49
172
 
50
- function sourcePushModeLabel(
51
- mode: ContentDatabaseSource["metadata"]["pushMode"],
52
- t: ReturnType<typeof useT>,
173
+ export function builderReviewPublicationTransitionsMap(
174
+ selections: BuilderReviewPublicationTransitionSelections,
53
175
  ) {
54
- if (mode === "autosave") return t("database.saveRevisionAutosave");
55
- if (mode === "draft") return t("database.draft");
56
- if (mode === "publish") return t("database.publish");
57
- return t("database.none");
176
+ const transitions: BuilderReviewPublicationTransitions = {};
177
+
178
+ for (const [changeSetId, selection] of Object.entries(selections)) {
179
+ if (selection.publicationTransition === "publish") {
180
+ transitions[changeSetId] = { publicationTransition: "publish" };
181
+ continue;
182
+ }
183
+
184
+ if (
185
+ selection.publicationTransition === "unpublish" &&
186
+ selection.confirmUnpublish === true
187
+ ) {
188
+ transitions[changeSetId] = {
189
+ publicationTransition: "unpublish",
190
+ confirmUnpublish: true,
191
+ };
192
+ }
193
+ }
194
+
195
+ return transitions;
58
196
  }
59
197
 
60
- function SourceMetadataRow({ label, value }: { label: string; value: string }) {
61
- return (
62
- <div className="flex min-w-0 items-start justify-between gap-3">
63
- <span className="shrink-0 text-muted-foreground">{label}</span>
64
- <span className="min-w-0 break-words text-right">{value}</span>
65
- </div>
66
- );
198
+ const ATTENTION_TAG_EFFECTS: ReadonlySet<BuilderCmsWriteEffect> = new Set([
199
+ "unpublish",
200
+ ]);
201
+
202
+ function rowEffectTagClass(effect: BuilderCmsWriteEffect) {
203
+ return ATTENTION_TAG_EFFECTS.has(effect)
204
+ ? "rounded border border-amber-300 bg-amber-50 px-1.5 py-0.5 text-amber-700 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-300"
205
+ : "rounded border border-border bg-muted/40 px-1.5 py-0.5 text-muted-foreground";
206
+ }
207
+
208
+ function sourceValueText(value: DocumentPropertyValue) {
209
+ if (value === null || value === undefined || value === "") return "empty";
210
+ if (Array.isArray(value)) return value.join(", ") || "empty";
211
+ if (typeof value === "object") return JSON.stringify(value);
212
+ return String(value);
67
213
  }
68
214
 
69
215
  export function BuilderSourceReviewDialog({
@@ -72,6 +218,7 @@ export function BuilderSourceReviewDialog({
72
218
  source,
73
219
  canEdit,
74
220
  pending,
221
+ batchResult = null,
75
222
  checkedAt,
76
223
  onClose,
77
224
  onValidate,
@@ -81,44 +228,179 @@ export function BuilderSourceReviewDialog({
81
228
  source: ContentDatabaseSource | null;
82
229
  canEdit: boolean;
83
230
  pending: boolean;
231
+ // Optional so the inline-database caller (DatabaseView) that doesn't surface
232
+ // batch results can still mount the dialog.
233
+ batchResult?: ExecuteBuilderSourceBatchResponse | null;
84
234
  checkedAt: string | null;
85
235
  onClose: () => void;
86
- onValidate: () => void;
236
+ onValidate: (transitions: BuilderReviewPublicationTransitions) => void;
87
237
  }) {
88
238
  const t = useT();
89
239
  const checked = !!checkedAt;
240
+ const safeModel =
241
+ source?.sourceType === "builder-cms" &&
242
+ source.sourceTable === BUILDER_CMS_SAFE_WRITE_MODEL;
243
+ const writeMode = source?.metadata.writeMode;
244
+ const allowPublicationTransitionControls =
245
+ safeModel &&
246
+ writeMode === "publish_updates" &&
247
+ source?.metadata.allowPublicationTransitions === true;
248
+ const reviewRows = useMemo(() => review?.rows ?? [], [review]);
249
+ const reviewRowIds = useMemo(
250
+ () => reviewRows.map((row) => row.changeSetId),
251
+ [reviewRows],
252
+ );
253
+ const reviewRowIdsKey = reviewRowIds.join("\u0000");
254
+ const [transitionSelections, setTransitionSelections] =
255
+ useState<BuilderReviewPublicationTransitionSelections>({});
256
+ useEffect(() => {
257
+ if (!open || !allowPublicationTransitionControls) {
258
+ setTransitionSelections({});
259
+ return;
260
+ }
261
+
262
+ const reviewRowIdSet = new Set(
263
+ reviewRowIdsKey ? reviewRowIdsKey.split("\u0000") : [],
264
+ );
265
+ setTransitionSelections((current) => {
266
+ const next: BuilderReviewPublicationTransitionSelections = {};
267
+ for (const [changeSetId, selection] of Object.entries(current)) {
268
+ if (reviewRowIdSet.has(changeSetId)) next[changeSetId] = selection;
269
+ }
270
+ return Object.keys(next).length === Object.keys(current).length
271
+ ? current
272
+ : next;
273
+ });
274
+ }, [allowPublicationTransitionControls, open, reviewRowIdsKey]);
275
+ const transitionMap = useMemo(
276
+ () => builderReviewPublicationTransitionsMap(transitionSelections),
277
+ [transitionSelections],
278
+ );
279
+ const intentSummary = builderReviewIntentSummary(
280
+ reviewRows,
281
+ transitionSelections,
282
+ );
283
+ const destinationLine = builderReviewDestinationLine({
284
+ rows: reviewRows,
285
+ selections: transitionSelections,
286
+ liveWritesEnabled: review?.liveWritesEnabled === true,
287
+ });
288
+ const hasUnconfirmedUnpublish = Object.values(transitionSelections).some(
289
+ (selection) =>
290
+ selection.publicationTransition === "unpublish" &&
291
+ selection.confirmUnpublish !== true,
292
+ );
293
+ const batchHasIssues =
294
+ !!batchResult &&
295
+ (batchResult.summary.blocked > 0 || batchResult.summary.failed > 0);
90
296
  const retryable =
91
297
  review?.result.status === "failed" ||
92
298
  review?.result.status === "blocked" ||
93
- review?.result.status === "stale";
299
+ review?.result.status === "stale" ||
300
+ batchHasIssues;
301
+ const unsafeLiveTarget = review?.liveWritesEnabled === true && !safeModel;
94
302
  const disabled =
95
303
  !canEdit ||
96
304
  pending ||
97
305
  (!retryable && checked) ||
98
306
  !review ||
99
- review.rows.length === 0;
100
- const footerText = pending
307
+ review.rows.length === 0 ||
308
+ unsafeLiveTarget ||
309
+ hasUnconfirmedUnpublish;
310
+ const rowTitleById = new Map(
311
+ reviewRows.map((row) => [row.changeSetId, row.title]),
312
+ );
313
+ const batchIssueResults =
314
+ batchResult?.results.filter((result) => result.status !== "succeeded") ??
315
+ [];
316
+ const resultStatus = builderReviewResultStatus(
317
+ batchResult
318
+ ? batchHasIssues
319
+ ? "partial"
320
+ : "succeeded"
321
+ : review?.result.status,
322
+ );
323
+ const footerHint = pending
101
324
  ? review?.liveWritesEnabled
102
- ? t("database.builderPreparingGate")
103
- : t("database.builderCheckingGate")
104
- : checked
105
- ? review?.result.status === "succeeded"
106
- ? t("database.builderPushedReconciled")
107
- : review?.liveWritesEnabled
108
- ? (review?.result.message ?? t("database.builderPushFinished"))
109
- : t("database.builderCheckedNothingSent")
110
- : review?.liveWritesEnabled
111
- ? t("database.builderPushWillSend")
112
- : t("database.builderWritesDisabledCheckOnly");
325
+ ? "Sending to Builder…"
326
+ : "Checking…"
327
+ : hasUnconfirmedUnpublish
328
+ ? "Confirm unpublish on the selected rows first."
329
+ : unsafeLiveTarget
330
+ ? `Live pushes are limited to the ${BUILDER_CMS_SAFE_WRITE_MODEL} test model.`
331
+ : null;
332
+ const effectiveEffects = new Set(
333
+ reviewRows.map((row) =>
334
+ builderReviewEffectiveRowEffect(
335
+ row.effect,
336
+ transitionSelections[row.changeSetId],
337
+ ),
338
+ ),
339
+ );
340
+ const pushVerb = !review?.liveWritesEnabled
341
+ ? "Check"
342
+ : effectiveEffects.has("unpublish")
343
+ ? "Unpublish"
344
+ : effectiveEffects.has("publish")
345
+ ? "Publish"
346
+ : effectiveEffects.size === 1 && effectiveEffects.has("create_draft")
347
+ ? reviewRows.length > 1
348
+ ? `Create ${reviewRows.length} drafts`
349
+ : "Create draft"
350
+ : reviewRows.length > 1
351
+ ? `Push ${reviewRows.length} updates`
352
+ : "Push update";
113
353
  const buttonLabel = pending
114
354
  ? review?.liveWritesEnabled
115
- ? t("database.pushing")
116
- : t("database.checking")
117
- : checked && review?.result.status === "succeeded"
118
- ? t("database.pushed")
119
- : checked && !retryable
120
- ? t("database.checked")
121
- : t("database.push");
355
+ ? "Working…"
356
+ : "Checking…"
357
+ : checked && batchResult
358
+ ? batchHasIssues
359
+ ? "Retry"
360
+ : "Pushed"
361
+ : checked && review?.result.status === "succeeded"
362
+ ? "Pushed"
363
+ : checked && !retryable
364
+ ? "Checked"
365
+ : pushVerb;
366
+
367
+ function setRowPublicationTransition(
368
+ changeSetId: string,
369
+ publicationTransition: BuilderCmsPublicationTransitionIntent,
370
+ ) {
371
+ setTransitionSelections((current) => {
372
+ const currentSelection = current[changeSetId];
373
+ const next = { ...current };
374
+
375
+ if (currentSelection?.publicationTransition === publicationTransition) {
376
+ delete next[changeSetId];
377
+ return next;
378
+ }
379
+
380
+ next[changeSetId] = {
381
+ publicationTransition,
382
+ confirmUnpublish:
383
+ publicationTransition === "unpublish" ? false : undefined,
384
+ };
385
+ return next;
386
+ });
387
+ }
388
+
389
+ function setRowConfirmUnpublish(changeSetId: string, confirmed: boolean) {
390
+ setTransitionSelections((current) => {
391
+ const currentSelection = current[changeSetId];
392
+ if (currentSelection?.publicationTransition !== "unpublish") {
393
+ return current;
394
+ }
395
+ return {
396
+ ...current,
397
+ [changeSetId]: {
398
+ publicationTransition: "unpublish",
399
+ confirmUnpublish: confirmed,
400
+ },
401
+ };
402
+ });
403
+ }
122
404
 
123
405
  return (
124
406
  <Dialog
@@ -140,7 +422,7 @@ export function BuilderSourceReviewDialog({
140
422
  {t("database.reviewBuilderUpdate")}
141
423
  </DialogTitle>
142
424
  <DialogDescription className="truncate text-xs text-muted-foreground">
143
- {review?.summary ?? t("database.noPendingBuilderChanges")}
425
+ {review?.summary ?? "No pending Builder changes."}
144
426
  </DialogDescription>
145
427
  </div>
146
428
  <button
@@ -161,178 +443,235 @@ export function BuilderSourceReviewDialog({
161
443
  {t("database.whatChanged")}
162
444
  </div>
163
445
  <div className="grid gap-2">
164
- {review.rows.map((row) => (
165
- <div
166
- key={row.changeSetId}
167
- className="rounded-md border border-border p-3"
168
- >
169
- <div className="flex min-w-0 items-start justify-between gap-3">
170
- <div className="min-w-0">
171
- <div className="truncate text-sm font-medium">
172
- {row.title}
173
- </div>
174
- <div className="mt-1 text-xs text-muted-foreground">
175
- {t("database.fieldChangeCount", {
176
- count: row.fieldChanges.length,
177
- })}
178
- {row.bodyChange
179
- ? ` ${t("database.plusBodyDiff")}`
180
- : ""}
446
+ {reviewRows.map((row) => {
447
+ const selection = transitionSelections[row.changeSetId];
448
+ const effect = builderReviewEffectiveRowEffect(
449
+ row.effect,
450
+ selection,
451
+ );
452
+ const { tag, sentence } =
453
+ builderReviewRowEffectLabel(effect);
454
+ const showConflict =
455
+ effect !== "create_draft" &&
456
+ row.conflictState === "source_changed";
457
+ return (
458
+ <div
459
+ key={row.changeSetId}
460
+ className="rounded-md border border-border p-3"
461
+ >
462
+ <div className="flex min-w-0 items-start justify-between gap-3">
463
+ <div className="min-w-0">
464
+ <div className="truncate text-sm font-medium">
465
+ {row.title}
466
+ </div>
467
+ <div className="mt-1 text-xs text-muted-foreground">
468
+ {sentence}
469
+ </div>
181
470
  </div>
182
- </div>
183
- <span className={sourceRiskClass(row.riskLevel)}>
184
- {t("database.riskLabel", { level: row.riskLevel })}
185
- </span>
186
- </div>
187
- <div className="mt-3 grid gap-2">
188
- {row.fieldChanges.map((field) => (
189
- <div
190
- key={`${row.changeSetId}-${field.localFieldKey}`}
191
- className="grid gap-1 rounded border border-border/70 bg-muted/20 p-2 text-xs"
471
+ <span
472
+ className={
473
+ "shrink-0 text-[11px] " +
474
+ rowEffectTagClass(effect)
475
+ }
192
476
  >
193
- <div className="font-medium">
194
- {field.propertyName ?? field.sourceFieldKey}
477
+ {tag}
478
+ </span>
479
+ </div>
480
+ <div className="mt-3 grid gap-2">
481
+ {allowPublicationTransitionControls &&
482
+ row.effect !== "create_draft" ? (
483
+ <div className="flex flex-wrap items-center gap-1.5 text-xs">
484
+ <Button
485
+ type="button"
486
+ size="sm"
487
+ variant={
488
+ transitionSelections[row.changeSetId]
489
+ ?.publicationTransition === "publish"
490
+ ? "secondary"
491
+ : "outline"
492
+ }
493
+ className="h-7 px-2 text-xs"
494
+ disabled={pending}
495
+ aria-pressed={
496
+ transitionSelections[row.changeSetId]
497
+ ?.publicationTransition === "publish"
498
+ }
499
+ onClick={() =>
500
+ setRowPublicationTransition(
501
+ row.changeSetId,
502
+ "publish",
503
+ )
504
+ }
505
+ >
506
+ Publish
507
+ </Button>
508
+ <Button
509
+ type="button"
510
+ size="sm"
511
+ variant={
512
+ transitionSelections[row.changeSetId]
513
+ ?.publicationTransition === "unpublish"
514
+ ? "destructive"
515
+ : "outline"
516
+ }
517
+ className="h-7 px-2 text-xs"
518
+ disabled={pending}
519
+ aria-pressed={
520
+ transitionSelections[row.changeSetId]
521
+ ?.publicationTransition === "unpublish"
522
+ }
523
+ onClick={() =>
524
+ setRowPublicationTransition(
525
+ row.changeSetId,
526
+ "unpublish",
527
+ )
528
+ }
529
+ >
530
+ Unpublish
531
+ </Button>
532
+ {transitionSelections[row.changeSetId]
533
+ ?.publicationTransition === "unpublish" ? (
534
+ <label className="flex items-center gap-1 rounded border border-destructive/30 bg-destructive/10 px-1.5 py-1 text-[11px] text-destructive">
535
+ <input
536
+ type="checkbox"
537
+ className="size-3 accent-destructive"
538
+ checked={
539
+ transitionSelections[row.changeSetId]
540
+ ?.confirmUnpublish === true
541
+ }
542
+ disabled={pending}
543
+ onChange={(event) =>
544
+ setRowConfirmUnpublish(
545
+ row.changeSetId,
546
+ event.currentTarget.checked,
547
+ )
548
+ }
549
+ />
550
+ {t("database.confirmUnpublish")}
551
+ </label>
552
+ ) : null}
195
553
  </div>
196
- <div className="grid gap-1 text-muted-foreground sm:grid-cols-2">
197
- <div className="min-w-0 break-words">
198
- {t("database.fromValue", {
199
- value: sourceValueText(
200
- field.currentValue,
201
- t("database.empty"),
202
- ),
203
- })}
554
+ ) : null}
555
+ {row.fieldChanges.map((field) => (
556
+ <div
557
+ key={`${row.changeSetId}-${field.localFieldKey}`}
558
+ className="grid gap-1 rounded border border-border/70 bg-muted/20 p-2 text-xs"
559
+ >
560
+ <div className="font-medium">
561
+ {field.propertyName ?? field.sourceFieldKey}
204
562
  </div>
205
- <div className="min-w-0 break-words">
206
- {t("database.toValue", {
207
- value: sourceValueText(
208
- field.proposedValue,
209
- t("database.empty"),
210
- ),
211
- })}
563
+ <div className="grid gap-1 text-muted-foreground sm:grid-cols-2">
564
+ <div className="min-w-0 break-words">
565
+ From: {sourceValueText(field.currentValue)}
566
+ </div>
567
+ <div className="min-w-0 break-words">
568
+ To: {sourceValueText(field.proposedValue)}
569
+ </div>
212
570
  </div>
213
571
  </div>
214
- </div>
215
- ))}
216
- {row.bodyChange ? (
217
- <div className="rounded border border-border/70 bg-muted/20 p-2 text-xs">
218
- <div className="font-medium">
219
- {row.bodyChange.summary}
572
+ ))}
573
+ {row.bodyChange ? (
574
+ <div className="rounded border border-border/70 bg-muted/20 p-2 text-xs">
575
+ <div className="font-medium">
576
+ {row.bodyChange.summary}
577
+ </div>
578
+ <div className="mt-1 text-muted-foreground">
579
+ {t("database.builderBodyEditsNeedSaferPath")}
580
+ </div>
220
581
  </div>
221
- <div className="mt-1 text-muted-foreground">
222
- {t("database.builderBodyEditsNeedSaferPath")}
582
+ ) : null}
583
+ {showConflict ? (
584
+ <div className="flex items-start gap-1.5 rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-700 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-300">
585
+ <IconAlertTriangle className="mt-0.5 size-3.5 shrink-0" />
586
+ <span>
587
+ {t("database.changedInBuilderSinceSync")}
588
+ </span>
223
589
  </div>
224
- </div>
225
- ) : null}
226
- {row.execution?.lastError ? (
227
- <div className="rounded border border-destructive/30 bg-destructive/10 p-2 text-xs text-destructive">
228
- {row.execution.lastError}
229
- </div>
230
- ) : null}
590
+ ) : null}
591
+ {effect === "unpublish" ? (
592
+ <div className="flex items-start gap-1.5 rounded border border-amber-300 bg-amber-50 p-2 text-xs text-amber-700 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-300">
593
+ <IconAlertTriangle className="mt-0.5 size-3.5 shrink-0" />
594
+ <span>
595
+ {t("database.thisUnpublishesTheLiveEntry")}
596
+ </span>
597
+ </div>
598
+ ) : null}
599
+ {row.execution?.lastError ? (
600
+ <div className="rounded border border-destructive/30 bg-destructive/10 p-2 text-xs text-destructive">
601
+ {row.execution.lastError}
602
+ </div>
603
+ ) : null}
604
+ </div>
231
605
  </div>
232
- </div>
233
- ))}
234
- </div>
235
- </section>
236
-
237
- <section className="grid gap-2 rounded-md border border-border p-3">
238
- <div className="text-sm font-medium">
239
- {t("database.whereItWillGo")}
240
- </div>
241
- <div className="grid gap-2 text-xs">
242
- <SourceMetadataRow
243
- label={t("database.source")}
244
- value={review.sourceName}
245
- />
246
- <SourceMetadataRow
247
- label={t("database.builderModel")}
248
- value={review.sourceTable}
249
- />
250
- <SourceMetadataRow
251
- label={t("database.pushMode")}
252
- value={sourcePushModeLabel(review.pushMode, t)}
253
- />
254
- <SourceMetadataRow
255
- label={t("database.liveWrites")}
256
- value={
257
- review.liveWritesEnabled
258
- ? t("database.enabled")
259
- : t("database.disabled")
260
- }
261
- />
262
- <SourceMetadataRow
263
- label={t("database.readMode")}
264
- value={
265
- source
266
- ? sourceBuilderReadModeSummary(source, t)
267
- : t("database.unknown")
268
- }
269
- />
606
+ );
607
+ })}
270
608
  </div>
271
609
  </section>
272
610
 
273
- <section className="grid gap-2 rounded-md border border-border p-3">
274
- <div className="text-sm font-medium">
275
- {t("database.riskCheck")}
276
- </div>
277
- <div className="flex flex-wrap gap-1.5 text-xs">
278
- <span className={sourceRiskClass(review.riskLevel)}>
279
- {t("database.riskLabel", { level: review.riskLevel })}
280
- </span>
281
- {(review.riskReasons.length
282
- ? review.riskReasons
283
- : [t("database.singleFieldDiff")]
284
- ).map((reason) => (
285
- <span
286
- key={reason}
287
- className="rounded border border-border px-1.5 py-0.5 text-muted-foreground"
288
- >
289
- {reason}
290
- </span>
291
- ))}
292
- <span className="rounded border border-border px-1.5 py-0.5 text-muted-foreground">
293
- {review.dryRunOnly
294
- ? t("database.checksOnly")
295
- : t("database.canSendToBuilder")}
296
- </span>
297
- </div>
298
- </section>
611
+ <div className="flex items-start gap-1.5 text-xs text-muted-foreground">
612
+ <IconCloudUpload className="mt-0.5 size-3.5 shrink-0" />
613
+ <span>{destinationLine}</span>
614
+ </div>
299
615
 
300
- <section className="grid gap-2 rounded-md border border-border p-3">
301
- <div className="flex items-start justify-between gap-3">
302
- <div>
303
- <div className="text-sm font-medium">
304
- {t("database.result")}
305
- </div>
306
- <div className="mt-1 text-xs text-muted-foreground">
307
- {review.result.message}
308
- </div>
616
+ {batchResult && batchIssueResults.length > 0 ? (
617
+ <section className="grid gap-1.5 rounded-md border border-amber-300 bg-amber-50 p-3 text-xs text-amber-800 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-200">
618
+ <div className="font-medium">
619
+ {t("database.needsAttentionBeforeFinish")}
309
620
  </div>
310
- <span className="shrink-0 rounded border border-border px-2 py-0.5 text-[11px] uppercase tracking-wide text-muted-foreground">
311
- {review.result.status.replace(/_/g, " ")}
312
- </span>
313
- </div>
314
- </section>
621
+ {batchIssueResults.map((result) => (
622
+ <div key={result.changeSetId} className="break-words">
623
+ <span className="font-medium">
624
+ {rowTitleById.get(result.changeSetId) ??
625
+ result.changeSetId}
626
+ </span>
627
+ {" — "}
628
+ {result.message ?? "No details returned."}
629
+ </div>
630
+ ))}
631
+ </section>
632
+ ) : null}
315
633
  </div>
316
634
  ) : (
317
635
  <div className="rounded-md border border-border p-4 text-sm text-muted-foreground">
318
- {t("database.noPendingLocalBuilderChangesYet")}
636
+ {t("database.noPendingLocalBuilderChanges")}
319
637
  </div>
320
638
  )}
321
639
  </div>
322
640
 
323
641
  <div className="flex shrink-0 items-center justify-between gap-3 border-t border-border p-3">
324
- <div className="min-w-0 text-xs text-muted-foreground">
325
- {footerText}
642
+ <div className="grid min-w-0 gap-1 text-xs text-muted-foreground">
643
+ {review ? (
644
+ <div
645
+ className={
646
+ "flex items-center gap-1.5 font-medium " +
647
+ resultToneClass(resultStatus.tone)
648
+ }
649
+ >
650
+ {checked ? (
651
+ resultStatus.tone === "ok" ? (
652
+ <IconCheck className="size-3.5 shrink-0" />
653
+ ) : (
654
+ <IconAlertTriangle className="size-3.5 shrink-0" />
655
+ )
656
+ ) : null}
657
+ <span>
658
+ {checked
659
+ ? `${t(`database.${resultStatus.labelKey}`)} · ${intentSummary}`
660
+ : intentSummary}
661
+ </span>
662
+ </div>
663
+ ) : null}
664
+ {footerHint ? <div>{footerHint}</div> : null}
326
665
  </div>
327
666
  <div className="flex shrink-0 items-center gap-2">
328
667
  <Button type="button" variant="ghost" size="sm" onClick={onClose}>
329
- {t("database.close")}
668
+ Close
330
669
  </Button>
331
670
  <Button
332
671
  type="button"
333
672
  size="sm"
334
673
  disabled={disabled}
335
- onClick={onValidate}
674
+ onClick={() => onValidate(transitionMap)}
336
675
  >
337
676
  {pending ? (
338
677
  <Spinner className="mr-1.5 size-3.5" />