@agent-native/core 0.88.0 → 0.90.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (1638) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +82 -2
  3. package/corpus/core/docs/content/agent-native-toolkit.mdx +87 -146
  4. package/corpus/core/docs/content/cloneable-saas.mdx +19 -19
  5. package/corpus/core/docs/content/components.mdx +16 -4
  6. package/corpus/core/docs/content/data-programs.mdx +329 -0
  7. package/corpus/core/docs/content/deployment.mdx +9 -9
  8. package/corpus/core/docs/content/extensions.mdx +8 -7
  9. package/corpus/core/docs/content/generative-ui.mdx +5 -3
  10. package/corpus/core/docs/content/key-concepts.mdx +6 -9
  11. package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +100 -70
  12. package/corpus/core/docs/content/locales/ar-SA/cloneable-saas.mdx +19 -19
  13. package/corpus/core/docs/content/locales/ar-SA/components.mdx +12 -2
  14. package/corpus/core/docs/content/locales/ar-SA/data-programs.mdx +329 -0
  15. package/corpus/core/docs/content/locales/ar-SA/deployment.mdx +8 -9
  16. package/corpus/core/docs/content/locales/ar-SA/extensions.mdx +6 -4
  17. package/corpus/core/docs/content/locales/ar-SA/key-concepts.mdx +7 -9
  18. package/corpus/core/docs/content/locales/ar-SA/real-time-collaboration.mdx +9 -10
  19. package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +100 -74
  20. package/corpus/core/docs/content/locales/de-DE/cloneable-saas.mdx +19 -19
  21. package/corpus/core/docs/content/locales/de-DE/components.mdx +12 -2
  22. package/corpus/core/docs/content/locales/de-DE/data-programs.mdx +329 -0
  23. package/corpus/core/docs/content/locales/de-DE/deployment.mdx +8 -9
  24. package/corpus/core/docs/content/locales/de-DE/extensions.mdx +6 -4
  25. package/corpus/core/docs/content/locales/de-DE/key-concepts.mdx +8 -10
  26. package/corpus/core/docs/content/locales/de-DE/real-time-collaboration.mdx +9 -10
  27. package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +100 -74
  28. package/corpus/core/docs/content/locales/es-ES/cloneable-saas.mdx +19 -19
  29. package/corpus/core/docs/content/locales/es-ES/components.mdx +12 -2
  30. package/corpus/core/docs/content/locales/es-ES/data-programs.mdx +329 -0
  31. package/corpus/core/docs/content/locales/es-ES/deployment.mdx +8 -9
  32. package/corpus/core/docs/content/locales/es-ES/extensions.mdx +6 -4
  33. package/corpus/core/docs/content/locales/es-ES/key-concepts.mdx +8 -10
  34. package/corpus/core/docs/content/locales/es-ES/real-time-collaboration.mdx +9 -10
  35. package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +100 -76
  36. package/corpus/core/docs/content/locales/fr-FR/cloneable-saas.mdx +19 -19
  37. package/corpus/core/docs/content/locales/fr-FR/components.mdx +12 -2
  38. package/corpus/core/docs/content/locales/fr-FR/data-programs.mdx +329 -0
  39. package/corpus/core/docs/content/locales/fr-FR/deployment.mdx +8 -9
  40. package/corpus/core/docs/content/locales/fr-FR/extensions.mdx +6 -4
  41. package/corpus/core/docs/content/locales/fr-FR/key-concepts.mdx +8 -10
  42. package/corpus/core/docs/content/locales/fr-FR/real-time-collaboration.mdx +9 -10
  43. package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +100 -71
  44. package/corpus/core/docs/content/locales/hi-IN/cloneable-saas.mdx +19 -19
  45. package/corpus/core/docs/content/locales/hi-IN/components.mdx +20 -10
  46. package/corpus/core/docs/content/locales/hi-IN/data-programs.mdx +329 -0
  47. package/corpus/core/docs/content/locales/hi-IN/deployment.mdx +8 -9
  48. package/corpus/core/docs/content/locales/hi-IN/extensions.mdx +6 -4
  49. package/corpus/core/docs/content/locales/hi-IN/key-concepts.mdx +8 -10
  50. package/corpus/core/docs/content/locales/hi-IN/real-time-collaboration.mdx +9 -10
  51. package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +100 -64
  52. package/corpus/core/docs/content/locales/ja-JP/cloneable-saas.mdx +19 -19
  53. package/corpus/core/docs/content/locales/ja-JP/components.mdx +12 -2
  54. package/corpus/core/docs/content/locales/ja-JP/data-programs.mdx +329 -0
  55. package/corpus/core/docs/content/locales/ja-JP/deployment.mdx +8 -9
  56. package/corpus/core/docs/content/locales/ja-JP/extensions.mdx +6 -4
  57. package/corpus/core/docs/content/locales/ja-JP/key-concepts.mdx +9 -11
  58. package/corpus/core/docs/content/locales/ja-JP/real-time-collaboration.mdx +9 -10
  59. package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +100 -60
  60. package/corpus/core/docs/content/locales/ko-KR/cloneable-saas.mdx +19 -19
  61. package/corpus/core/docs/content/locales/ko-KR/components.mdx +20 -10
  62. package/corpus/core/docs/content/locales/ko-KR/data-programs.mdx +329 -0
  63. package/corpus/core/docs/content/locales/ko-KR/deployment.mdx +8 -9
  64. package/corpus/core/docs/content/locales/ko-KR/extensions.mdx +16 -14
  65. package/corpus/core/docs/content/locales/ko-KR/key-concepts.mdx +9 -11
  66. package/corpus/core/docs/content/locales/ko-KR/real-time-collaboration.mdx +9 -10
  67. package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +100 -74
  68. package/corpus/core/docs/content/locales/pt-BR/cloneable-saas.mdx +19 -19
  69. package/corpus/core/docs/content/locales/pt-BR/components.mdx +12 -2
  70. package/corpus/core/docs/content/locales/pt-BR/data-programs.mdx +329 -0
  71. package/corpus/core/docs/content/locales/pt-BR/deployment.mdx +8 -9
  72. package/corpus/core/docs/content/locales/pt-BR/extensions.mdx +6 -4
  73. package/corpus/core/docs/content/locales/pt-BR/key-concepts.mdx +8 -11
  74. package/corpus/core/docs/content/locales/pt-BR/real-time-collaboration.mdx +9 -10
  75. package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +100 -58
  76. package/corpus/core/docs/content/locales/zh-CN/cloneable-saas.mdx +19 -19
  77. package/corpus/core/docs/content/locales/zh-CN/components.mdx +20 -10
  78. package/corpus/core/docs/content/locales/zh-CN/data-programs.mdx +329 -0
  79. package/corpus/core/docs/content/locales/zh-CN/deployment.mdx +7 -9
  80. package/corpus/core/docs/content/locales/zh-CN/extensions.mdx +16 -14
  81. package/corpus/core/docs/content/locales/zh-CN/key-concepts.mdx +7 -11
  82. package/corpus/core/docs/content/locales/zh-CN/real-time-collaboration.mdx +10 -11
  83. package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +100 -58
  84. package/corpus/core/docs/content/locales/zh-TW/cloneable-saas.mdx +19 -19
  85. package/corpus/core/docs/content/locales/zh-TW/components.mdx +20 -10
  86. package/corpus/core/docs/content/locales/zh-TW/data-programs.mdx +329 -0
  87. package/corpus/core/docs/content/locales/zh-TW/deployment.mdx +7 -9
  88. package/corpus/core/docs/content/locales/zh-TW/extensions.mdx +16 -14
  89. package/corpus/core/docs/content/locales/zh-TW/key-concepts.mdx +7 -11
  90. package/corpus/core/docs/content/locales/zh-TW/real-time-collaboration.mdx +10 -11
  91. package/corpus/core/docs/content/real-time-collaboration.mdx +10 -11
  92. package/corpus/core/docs/content/toolkit-app-adapters.mdx +46 -0
  93. package/corpus/core/docs/content/toolkit-collaboration-ui.mdx +48 -0
  94. package/corpus/core/docs/content/toolkit-sharing-ui.mdx +33 -0
  95. package/corpus/core/docs/content/toolkit-shell-hooks.mdx +27 -0
  96. package/corpus/core/docs/content/toolkit-ui.mdx +69 -0
  97. package/corpus/core/package.json +5 -1
  98. package/corpus/core/src/agent/durable-background.ts +53 -0
  99. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +7 -1
  100. package/corpus/core/src/agent/engine/anthropic-engine.ts +29 -1
  101. package/corpus/core/src/agent/engine/builder-engine.ts +1 -0
  102. package/corpus/core/src/agent/engine/output-tokens.ts +26 -13
  103. package/corpus/core/src/agent/engine/registry.ts +45 -26
  104. package/corpus/core/src/agent/engine/translate-ai-sdk.ts +56 -7
  105. package/corpus/core/src/agent/engine/translate-anthropic.ts +43 -1
  106. package/corpus/core/src/agent/engine/types.ts +16 -0
  107. package/corpus/core/src/agent/model-config.ts +100 -0
  108. package/corpus/core/src/agent/production-agent.ts +492 -241
  109. package/corpus/core/src/agent/runtime-context.ts +50 -12
  110. package/corpus/core/src/agent/tool-result-images.ts +178 -0
  111. package/corpus/core/src/brand-kit/fig/index.ts +8 -4
  112. package/corpus/core/src/client/AgentPanel.tsx +88 -31
  113. package/corpus/core/src/client/AssistantChat.tsx +95 -46
  114. package/corpus/core/src/client/KeepTabOpenNotice.tsx +176 -0
  115. package/corpus/core/src/client/MultiTabAssistantChat.tsx +7 -0
  116. package/corpus/core/src/client/RunStuckBanner.tsx +13 -1
  117. package/corpus/core/src/client/chat/run-recovery.tsx +7 -1
  118. package/corpus/core/src/client/clipboard.ts +17 -13
  119. package/corpus/core/src/client/components/AgentPresenceChip.tsx +4 -69
  120. package/corpus/core/src/client/components/LiveCursorOverlay.tsx +5 -240
  121. package/corpus/core/src/client/components/PresenceBar.tsx +4 -346
  122. package/corpus/core/src/client/components/RecentEditHighlights.tsx +4 -219
  123. package/corpus/core/src/client/components/RemoteSelectionRings.tsx +5 -220
  124. package/corpus/core/src/client/composer/TiptapComposer.tsx +94 -38
  125. package/corpus/core/src/client/db-admin/SqlEditor.tsx +13 -3
  126. package/corpus/core/src/client/dynamic-suggestions.ts +6 -1
  127. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +0 -14
  128. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +29 -30
  129. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +2 -1
  130. package/corpus/core/src/client/host-bridge.ts +29 -8
  131. package/corpus/core/src/client/i18n.tsx +7 -1
  132. package/corpus/core/src/client/index.ts +5 -0
  133. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +34 -69
  134. package/corpus/core/src/client/rich-markdown-editor/SharedRichEditor.tsx +4 -0
  135. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +7 -1
  136. package/corpus/core/src/client/settings/SettingsPanel.tsx +409 -230
  137. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +5 -7
  138. package/corpus/core/src/client/settings/index.ts +5 -1
  139. package/corpus/core/src/client/sharing/VisibilityBadge.tsx +4 -37
  140. package/corpus/core/src/client/use-run-stuck-detection.ts +10 -1
  141. package/corpus/core/src/coding-tools/run-code.ts +473 -74
  142. package/corpus/core/src/coding-tools/sandbox/background.ts +406 -0
  143. package/corpus/core/src/coding-tools/sandbox/executions-store.ts +517 -0
  144. package/corpus/core/src/coding-tools/sandbox/index.ts +53 -13
  145. package/corpus/core/src/coding-tools/sandbox/local-child-process-adapter.ts +61 -4
  146. package/corpus/core/src/collab/client.ts +870 -562
  147. package/corpus/core/src/collab/presence.ts +12 -23
  148. package/corpus/core/src/collab/recent-edits.ts +13 -32
  149. package/corpus/core/src/data-programs/actions.ts +335 -0
  150. package/corpus/core/src/data-programs/contract.ts +284 -0
  151. package/corpus/core/src/data-programs/execute.ts +693 -0
  152. package/corpus/core/src/data-programs/index.ts +72 -0
  153. package/corpus/core/src/data-programs/schema.ts +148 -0
  154. package/corpus/core/src/data-programs/store.ts +755 -0
  155. package/corpus/core/src/deploy/build.ts +34 -0
  156. package/corpus/core/src/extensions/actions.ts +2 -2
  157. package/corpus/core/src/file-upload/builder.ts +11 -0
  158. package/corpus/core/src/integrations/webhook-handler.ts +27 -8
  159. package/corpus/core/src/local-artifacts/index.ts +1 -1
  160. package/corpus/core/src/localization/default-messages.ts +1 -2
  161. package/corpus/core/src/mcp-client/index.ts +29 -0
  162. package/corpus/core/src/onboarding/default-steps.ts +5 -2
  163. package/corpus/core/src/resources/store.ts +34 -3
  164. package/corpus/core/src/scripts/agent-engines/list-agent-engines.ts +4 -8
  165. package/corpus/core/src/scripts/agent-engines/test-agent-engine.ts +9 -2
  166. package/corpus/core/src/scripts/db/tool-mode.ts +2 -2
  167. package/corpus/core/src/scripts/resources/migrate-learnings.ts +9 -13
  168. package/corpus/core/src/server/agent-chat-plugin.ts +154 -62
  169. package/corpus/core/src/server/agent-discovery.ts +67 -7
  170. package/corpus/core/src/server/auth.ts +90 -3
  171. package/corpus/core/src/server/builder-design-systems.ts +2 -2
  172. package/corpus/core/src/server/core-routes-plugin.ts +119 -22
  173. package/corpus/core/src/server/credential-provider.ts +28 -8
  174. package/corpus/core/src/server/csrf.ts +3 -0
  175. package/corpus/core/src/server/oauth-helpers.ts +11 -1
  176. package/corpus/core/src/server/onboarding-html.ts +80 -48
  177. package/corpus/core/src/server/prompts/shared-rules.ts +2 -2
  178. package/corpus/core/src/server/transcribe-voice.ts +93 -9
  179. package/corpus/core/src/styles/agent-native.css +57 -45
  180. package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +55 -0
  181. package/corpus/core/src/templates/default/AGENTS.md +11 -14
  182. package/corpus/core/src/templates/default/actions/navigate.ts +1 -1
  183. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +0 -1
  184. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +0 -1
  185. package/corpus/core/src/templates/default/app/i18n/en-US.ts +0 -1
  186. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +0 -1
  187. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +0 -1
  188. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +0 -1
  189. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +0 -1
  190. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +0 -1
  191. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +0 -1
  192. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +0 -1
  193. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +0 -1
  194. package/corpus/core/src/templates/default/app/routes/settings.tsx +22 -33
  195. package/corpus/core/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +20 -7
  196. package/corpus/core/src/templates/headless/AGENTS.md +9 -6
  197. package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +55 -0
  198. package/corpus/core/src/templates/workspace-core/.agents/skills/data-programs/SKILL.md +128 -0
  199. package/corpus/core/src/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +10 -4
  200. package/corpus/core/src/vite/agents-bundle-plugin.ts +26 -1
  201. package/corpus/core/src/vite/client.ts +12 -0
  202. package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +55 -0
  203. package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +8 -0
  204. package/corpus/templates/analytics/.agents/skills/data-programs/SKILL.md +348 -0
  205. package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +10 -0
  206. package/corpus/templates/analytics/AGENTS.md +31 -32
  207. package/corpus/templates/analytics/actions/dashboard-mutation-api.ts +1 -1
  208. package/corpus/templates/analytics/actions/delete-data-source-credentials.ts +32 -0
  209. package/corpus/templates/analytics/actions/ensure-risk-meeting-dashboard.ts +247 -0
  210. package/corpus/templates/analytics/actions/update-data-source-credentials.ts +123 -0
  211. package/corpus/templates/analytics/app/components/InlineMetric.tsx +2 -2
  212. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +2 -7
  213. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +11 -16
  214. package/corpus/templates/analytics/app/components/dashboard/DateRangePicker.tsx +1 -2
  215. package/corpus/templates/analytics/app/components/dashboard/MetricCard.tsx +2 -7
  216. package/corpus/templates/analytics/app/components/dashboard/RecentActivity.tsx +3 -6
  217. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +1 -6
  218. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +2 -7
  219. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +11 -15
  220. package/corpus/templates/analytics/app/components/dashboard/StatsCard.tsx +1 -7
  221. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +2 -7
  222. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +16 -16
  223. package/corpus/templates/analytics/app/components/layout/Header.tsx +2 -2
  224. package/corpus/templates/analytics/app/components/layout/HeaderActions.tsx +2 -1
  225. package/corpus/templates/analytics/app/components/layout/Layout.tsx +2 -2
  226. package/corpus/templates/analytics/app/components/layout/MobileNav.tsx +33 -17
  227. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +6 -6
  228. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +4 -3
  229. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +119 -68
  230. package/corpus/templates/analytics/app/components/ui/alert-dialog.tsx +1 -0
  231. package/corpus/templates/analytics/app/components/ui/alert.tsx +1 -0
  232. package/corpus/templates/analytics/app/components/ui/avatar.tsx +1 -0
  233. package/corpus/templates/analytics/app/components/ui/badge.tsx +1 -0
  234. package/corpus/templates/analytics/app/components/ui/button.tsx +1 -0
  235. package/corpus/templates/analytics/app/components/ui/calendar.tsx +1 -1
  236. package/corpus/templates/analytics/app/components/ui/card.tsx +1 -0
  237. package/corpus/templates/analytics/app/components/ui/checkbox.tsx +1 -0
  238. package/corpus/templates/analytics/app/components/ui/collapsible.tsx +1 -0
  239. package/corpus/templates/analytics/app/components/ui/command.tsx +1 -0
  240. package/corpus/templates/analytics/app/components/ui/date-picker.tsx +1 -0
  241. package/corpus/templates/analytics/app/components/ui/dialog.tsx +1 -0
  242. package/corpus/templates/analytics/app/components/ui/dropdown-menu.tsx +1 -0
  243. package/corpus/templates/analytics/app/components/ui/input.tsx +1 -0
  244. package/corpus/templates/analytics/app/components/ui/label.tsx +1 -0
  245. package/corpus/templates/analytics/app/components/ui/popover.tsx +1 -0
  246. package/corpus/templates/analytics/app/components/ui/progress.tsx +1 -0
  247. package/corpus/templates/analytics/app/components/ui/select.tsx +1 -0
  248. package/corpus/templates/analytics/app/components/ui/sheet.tsx +1 -0
  249. package/corpus/templates/analytics/app/components/ui/skeleton.tsx +1 -0
  250. package/corpus/templates/analytics/app/components/ui/sonner.tsx +1 -0
  251. package/corpus/templates/analytics/app/components/ui/spinner.tsx +1 -0
  252. package/corpus/templates/analytics/app/components/ui/switch.tsx +1 -0
  253. package/corpus/templates/analytics/app/components/ui/table.tsx +1 -0
  254. package/corpus/templates/analytics/app/components/ui/tabs.tsx +1 -0
  255. package/corpus/templates/analytics/app/components/ui/textarea.tsx +1 -0
  256. package/corpus/templates/analytics/app/components/ui/toaster.tsx +1 -0
  257. package/corpus/templates/analytics/app/components/ui/toggle-group.tsx +1 -0
  258. package/corpus/templates/analytics/app/components/ui/toolkit-provider.tsx +12 -0
  259. package/corpus/templates/analytics/app/components/ui/tooltip.tsx +1 -0
  260. package/corpus/templates/analytics/app/global.css +26 -3
  261. package/corpus/templates/analytics/app/i18n/zh-TW.ts +27 -4
  262. package/corpus/templates/analytics/app/i18n-data.ts +277 -42
  263. package/corpus/templates/analytics/app/pages/Agents.tsx +19 -19
  264. package/corpus/templates/analytics/app/pages/DataDictionary.tsx +21 -21
  265. package/corpus/templates/analytics/app/pages/DataSources.tsx +31 -54
  266. package/corpus/templates/analytics/app/pages/NotFound.tsx +2 -1
  267. package/corpus/templates/analytics/app/pages/Placeholder.tsx +2 -1
  268. package/corpus/templates/analytics/app/pages/Settings.tsx +19 -37
  269. package/corpus/templates/analytics/app/pages/adhoc/DashboardSkeleton.tsx +3 -2
  270. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +8 -13
  271. package/corpus/templates/analytics/app/pages/adhoc/_shared/components/DateRangeInput.tsx +1 -2
  272. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ChartTypePicker.tsx +2 -4
  273. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/DateRangePicker.tsx +1 -2
  274. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventCombobox.tsx +7 -7
  275. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventPanel.tsx +2 -1
  276. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventRow.tsx +2 -1
  277. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ExplorerChart.tsx +3 -8
  278. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/FilterBuilder.tsx +5 -4
  279. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/GroupByPicker.tsx +2 -1
  280. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyCombobox.tsx +7 -7
  281. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/PropertyValueCombobox.tsx +7 -7
  282. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +1 -1
  283. package/corpus/templates/analytics/app/pages/adhoc/explorer/index.tsx +16 -16
  284. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/ChartCard.tsx +16 -21
  285. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +32 -32
  286. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +14 -14
  287. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +14 -13
  288. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +200 -52
  289. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +23 -28
  290. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewSqlPopover.tsx +13 -12
  291. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/ViewsMenu.tsx +15 -15
  292. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +32 -36
  293. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/types.ts +2 -1
  294. package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +8 -7
  295. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +19 -19
  296. package/corpus/templates/analytics/app/pages/analyses/LegacyFusionAnalysis.tsx +11 -15
  297. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +14 -14
  298. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +7 -12
  299. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +12 -12
  300. package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +25 -25
  301. package/corpus/templates/analytics/app/root.tsx +22 -19
  302. package/corpus/templates/analytics/app/routes/catalog.tsx +8 -8
  303. package/corpus/templates/analytics/changelog/2026-07-06-dashboard-lists-and-headers-stay-compact.md +6 -0
  304. package/corpus/templates/analytics/changelog/2026-07-06-dashboards-can-now-chart-data-programs-saved-server-side-scr.md +6 -0
  305. package/corpus/templates/analytics/changelog/2026-07-06-extension-pages-and-mobile-headers-now-share-one-background-shade.md +6 -0
  306. package/corpus/templates/analytics/changelog/2026-07-06-mobile-chat-header-is-more-compact-and-the-menu-button-opens-navigation.md +5 -0
  307. package/corpus/templates/analytics/learnings.defaults.md +36 -0
  308. package/corpus/templates/analytics/seeds/data-programs/risk-meeting-cohort.js +254 -0
  309. package/corpus/templates/analytics/seeds/data-programs/risk-meeting-pylon-early-warning.js +184 -0
  310. package/corpus/templates/analytics/server/handlers/sql-query.ts +1 -1
  311. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +109 -1
  312. package/corpus/templates/assets/.agents/skills/actions/SKILL.md +55 -0
  313. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +3 -2
  314. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +16 -10
  315. package/corpus/templates/assets/app/components/layout/Header.tsx +1 -1
  316. package/corpus/templates/assets/app/components/layout/PageShell.tsx +4 -4
  317. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +12 -12
  318. package/corpus/templates/assets/app/components/library/CreateLibraryDialog.tsx +8 -8
  319. package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +7 -7
  320. package/corpus/templates/assets/app/components/library/LibraryCard.tsx +8 -8
  321. package/corpus/templates/assets/app/components/library/LibraryPresetGrid.tsx +3 -2
  322. package/corpus/templates/assets/app/components/ui/alert-dialog.tsx +1 -0
  323. package/corpus/templates/assets/app/components/ui/badge.tsx +1 -0
  324. package/corpus/templates/assets/app/components/ui/button.tsx +1 -0
  325. package/corpus/templates/assets/app/components/ui/card.tsx +1 -0
  326. package/corpus/templates/assets/app/components/ui/checkbox.tsx +1 -0
  327. package/corpus/templates/assets/app/components/ui/dialog.tsx +1 -0
  328. package/corpus/templates/assets/app/components/ui/dropdown-menu.tsx +1 -0
  329. package/corpus/templates/assets/app/components/ui/input.tsx +1 -0
  330. package/corpus/templates/assets/app/components/ui/label.tsx +1 -0
  331. package/corpus/templates/assets/app/components/ui/popover.tsx +1 -0
  332. package/corpus/templates/assets/app/components/ui/select.tsx +1 -0
  333. package/corpus/templates/assets/app/components/ui/separator.tsx +1 -0
  334. package/corpus/templates/assets/app/components/ui/sheet.tsx +1 -0
  335. package/corpus/templates/assets/app/components/ui/skeleton.tsx +1 -0
  336. package/corpus/templates/assets/app/components/ui/sonner.tsx +1 -0
  337. package/corpus/templates/assets/app/components/ui/spinner.tsx +1 -0
  338. package/corpus/templates/assets/app/components/ui/tabs.tsx +1 -0
  339. package/corpus/templates/assets/app/components/ui/toolkit-provider.tsx +12 -0
  340. package/corpus/templates/assets/app/components/ui/tooltip.tsx +1 -0
  341. package/corpus/templates/assets/app/global.css +1 -0
  342. package/corpus/templates/assets/app/root.tsx +12 -9
  343. package/corpus/templates/assets/app/routes/asset.$id.tsx +19 -19
  344. package/corpus/templates/assets/app/routes/audit.tsx +15 -14
  345. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +49 -54
  346. package/corpus/templates/assets/app/routes/library.tsx +31 -31
  347. package/corpus/templates/assets/app/routes/settings.tsx +21 -35
  348. package/corpus/templates/assets/changelog/2026-07-06-mobile-layouts-no-longer-stack-two-top-headers.md +5 -0
  349. package/corpus/templates/brain/.agents/skills/actions/SKILL.md +55 -0
  350. package/corpus/templates/brain/.agents/skills/ask-across-everything/SKILL.md +6 -1
  351. package/corpus/templates/brain/.agents/skills/brain/SKILL.md +63 -5
  352. package/corpus/templates/brain/.agents/skills/ingestion-and-connectors/SKILL.md +112 -0
  353. package/corpus/templates/brain/AGENTS.md +93 -8
  354. package/corpus/templates/brain/app/components/brain/CanonicalPreviewSheet.tsx +6 -5
  355. package/corpus/templates/brain/app/components/brain/Surface.tsx +3 -8
  356. package/corpus/templates/brain/app/components/layout/Layout.tsx +7 -7
  357. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +6 -6
  358. package/corpus/templates/brain/app/components/ui/badge.tsx +1 -0
  359. package/corpus/templates/brain/app/components/ui/button.tsx +1 -0
  360. package/corpus/templates/brain/app/components/ui/card.tsx +1 -0
  361. package/corpus/templates/brain/app/components/ui/input.tsx +1 -0
  362. package/corpus/templates/brain/app/components/ui/label.tsx +1 -0
  363. package/corpus/templates/brain/app/components/ui/progress.tsx +1 -0
  364. package/corpus/templates/brain/app/components/ui/select.tsx +1 -0
  365. package/corpus/templates/brain/app/components/ui/separator.tsx +1 -0
  366. package/corpus/templates/brain/app/components/ui/sheet.tsx +1 -0
  367. package/corpus/templates/brain/app/components/ui/skeleton.tsx +1 -0
  368. package/corpus/templates/brain/app/components/ui/switch.tsx +1 -0
  369. package/corpus/templates/brain/app/components/ui/table.tsx +1 -0
  370. package/corpus/templates/brain/app/components/ui/textarea.tsx +1 -0
  371. package/corpus/templates/brain/app/components/ui/toolkit-provider.tsx +12 -0
  372. package/corpus/templates/brain/app/components/ui/tooltip.tsx +1 -0
  373. package/corpus/templates/brain/app/global.css +1 -0
  374. package/corpus/templates/brain/app/hooks/use-distillation-bridge.ts +9 -4
  375. package/corpus/templates/brain/app/root.tsx +11 -8
  376. package/corpus/templates/brain/app/routes/knowledge.tsx +19 -19
  377. package/corpus/templates/brain/app/routes/ops.tsx +18 -18
  378. package/corpus/templates/brain/app/routes/review.tsx +22 -27
  379. package/corpus/templates/brain/app/routes/search.tsx +20 -20
  380. package/corpus/templates/brain/app/routes/settings.tsx +25 -40
  381. package/corpus/templates/brain/app/routes/sources.tsx +23 -23
  382. package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +55 -0
  383. package/corpus/templates/calendar/actions/find-a-time.ts +29 -16
  384. package/corpus/templates/calendar/actions/list-bookings.ts +33 -2
  385. package/corpus/templates/calendar/actions/list-events.ts +34 -8
  386. package/corpus/templates/calendar/actions/update-calendar-visual-preferences.ts +105 -34
  387. package/corpus/templates/calendar/app/components/ThemeToggle.tsx +5 -5
  388. package/corpus/templates/calendar/app/components/TimezoneCombobox.tsx +6 -6
  389. package/corpus/templates/calendar/app/components/booking/BookingConfirmation.tsx +2 -1
  390. package/corpus/templates/calendar/app/components/booking/BookingForm.tsx +9 -8
  391. package/corpus/templates/calendar/app/components/booking/DatePicker.tsx +2 -2
  392. package/corpus/templates/calendar/app/components/booking/TimeSlotPicker.tsx +3 -2
  393. package/corpus/templates/calendar/app/components/calendar/AddCalendarDialog.tsx +10 -15
  394. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +6 -6
  395. package/corpus/templates/calendar/app/components/calendar/CommandPalette.tsx +73 -3
  396. package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +27 -27
  397. package/corpus/templates/calendar/app/components/calendar/DayView.tsx +553 -267
  398. package/corpus/templates/calendar/app/components/calendar/DeleteEventDialog.tsx +9 -12
  399. package/corpus/templates/calendar/app/components/calendar/EventAttendeesSection.tsx +10 -13
  400. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +7 -7
  401. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +21 -21
  402. package/corpus/templates/calendar/app/components/calendar/EventDialog.tsx +11 -11
  403. package/corpus/templates/calendar/app/components/calendar/EventOptionControls.tsx +9 -9
  404. package/corpus/templates/calendar/app/components/calendar/FindTimePanel.tsx +9 -9
  405. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +6 -6
  406. package/corpus/templates/calendar/app/components/calendar/GoogleSetupWizard.tsx +3 -3
  407. package/corpus/templates/calendar/app/components/calendar/GoogleSyncButton.tsx +1 -1
  408. package/corpus/templates/calendar/app/components/calendar/GuestNotificationDialog.tsx +8 -10
  409. package/corpus/templates/calendar/app/components/calendar/IntegrationsSidebar.tsx +12 -12
  410. package/corpus/templates/calendar/app/components/calendar/KeyboardShortcutsHelp.tsx +2 -2
  411. package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +93 -41
  412. package/corpus/templates/calendar/app/components/calendar/PeopleSearchDialog.tsx +6 -6
  413. package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +530 -299
  414. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +229 -134
  415. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +135 -108
  416. package/corpus/templates/calendar/app/components/ui/alert-dialog.tsx +1 -0
  417. package/corpus/templates/calendar/app/components/ui/badge.tsx +1 -0
  418. package/corpus/templates/calendar/app/components/ui/button.tsx +1 -0
  419. package/corpus/templates/calendar/app/components/ui/card.tsx +1 -0
  420. package/corpus/templates/calendar/app/components/ui/checkbox.tsx +1 -0
  421. package/corpus/templates/calendar/app/components/ui/collapsible.tsx +1 -0
  422. package/corpus/templates/calendar/app/components/ui/command.tsx +1 -0
  423. package/corpus/templates/calendar/app/components/ui/dialog.tsx +1 -0
  424. package/corpus/templates/calendar/app/components/ui/dropdown-menu.tsx +1 -0
  425. package/corpus/templates/calendar/app/components/ui/input.tsx +1 -0
  426. package/corpus/templates/calendar/app/components/ui/label.tsx +1 -0
  427. package/corpus/templates/calendar/app/components/ui/popover.tsx +1 -0
  428. package/corpus/templates/calendar/app/components/ui/radio-group.tsx +1 -0
  429. package/corpus/templates/calendar/app/components/ui/select.tsx +1 -0
  430. package/corpus/templates/calendar/app/components/ui/separator.tsx +1 -0
  431. package/corpus/templates/calendar/app/components/ui/skeleton.tsx +1 -0
  432. package/corpus/templates/calendar/app/components/ui/spinner.tsx +1 -0
  433. package/corpus/templates/calendar/app/components/ui/switch.tsx +1 -0
  434. package/corpus/templates/calendar/app/components/ui/table.tsx +1 -0
  435. package/corpus/templates/calendar/app/components/ui/tabs.tsx +1 -0
  436. package/corpus/templates/calendar/app/components/ui/textarea.tsx +1 -0
  437. package/corpus/templates/calendar/app/components/ui/toolkit-provider.tsx +12 -0
  438. package/corpus/templates/calendar/app/components/ui/tooltip.tsx +1 -0
  439. package/corpus/templates/calendar/app/global.css +1 -0
  440. package/corpus/templates/calendar/app/hooks/use-event-drag.ts +63 -9
  441. package/corpus/templates/calendar/app/hooks/use-google-auth.ts +15 -27
  442. package/corpus/templates/calendar/app/hooks/use-grid-create-drag.ts +254 -0
  443. package/corpus/templates/calendar/app/hooks/use-mobile.ts +1 -0
  444. package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +287 -21
  445. package/corpus/templates/calendar/app/hooks/use-zoom-auth.ts +56 -19
  446. package/corpus/templates/calendar/app/i18n/zh-TW.ts +3 -0
  447. package/corpus/templates/calendar/app/i18n-data.ts +31 -0
  448. package/corpus/templates/calendar/app/lib/event-colors.ts +29 -8
  449. package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +11 -11
  450. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +45 -50
  451. package/corpus/templates/calendar/app/pages/BookingPage.tsx +2 -2
  452. package/corpus/templates/calendar/app/pages/BookingsList.tsx +12 -12
  453. package/corpus/templates/calendar/app/pages/CalendarView.tsx +438 -271
  454. package/corpus/templates/calendar/app/pages/ManageBookingPage.tsx +13 -13
  455. package/corpus/templates/calendar/app/pages/Settings.tsx +15 -29
  456. package/corpus/templates/calendar/app/root.tsx +19 -15
  457. package/corpus/templates/calendar/app/routes/event.tsx +2 -2
  458. package/corpus/templates/calendar/changelog/2026-07-06-calendar-now-loads-view-preferences-much-more-efficiently.md +6 -0
  459. package/corpus/templates/calendar/changelog/2026-07-06-connected-calendars-keep-their-own-color-when-you-customize-.md +6 -0
  460. package/corpus/templates/calendar/changelog/2026-07-06-create-events-from-the-command-menu-with-natural-phrases-lik.md +6 -0
  461. package/corpus/templates/calendar/changelog/2026-07-06-drag-across-empty-week-or-day-slots-to-create-an-event-with-.md +6 -0
  462. package/corpus/templates/calendar/changelog/2026-07-06-dragging-and-resizing-events-is-much-smoother-especially-on-.md +6 -0
  463. package/corpus/templates/calendar/changelog/2026-07-06-each-connected-google-account-can-now-have-its-own-calendar-.md +6 -0
  464. package/corpus/templates/calendar/changelog/2026-07-06-events-spanning-multiple-days-now-show-on-every-day-they-cov.md +6 -0
  465. package/corpus/templates/calendar/changelog/2026-07-06-google-calendar-now-finishes-connecting-correctly-after-sign.md +6 -0
  466. package/corpus/templates/calendar/changelog/2026-07-06-public-booking-pages-respond-faster.md +6 -0
  467. package/corpus/templates/calendar/changelog/2026-07-06-the-app-header-no-longer-shows-the-global-notifications-bell.md +6 -0
  468. package/corpus/templates/calendar/package.json +1 -0
  469. package/corpus/templates/calendar/server/handlers/bookings.ts +46 -52
  470. package/corpus/templates/calendar/server/handlers/google-auth.ts +21 -5
  471. package/corpus/templates/calendar/server/lib/google-calendar.ts +32 -3
  472. package/corpus/templates/calendar/shared/calendar-view-preferences.ts +66 -2
  473. package/corpus/templates/chat/.agents/skills/actions/SKILL.md +55 -0
  474. package/corpus/templates/chat/app/components/layout/Layout.tsx +6 -6
  475. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +12 -12
  476. package/corpus/templates/chat/app/components/ui/button.tsx +1 -0
  477. package/corpus/templates/chat/app/components/ui/card.tsx +1 -0
  478. package/corpus/templates/chat/app/components/ui/dropdown-menu.tsx +1 -0
  479. package/corpus/templates/chat/app/components/ui/input.tsx +1 -0
  480. package/corpus/templates/chat/app/components/ui/label.tsx +1 -0
  481. package/corpus/templates/chat/app/components/ui/sheet.tsx +1 -0
  482. package/corpus/templates/chat/app/components/ui/toolkit-provider.tsx +12 -0
  483. package/corpus/templates/chat/app/components/ui/tooltip.tsx +1 -0
  484. package/corpus/templates/chat/app/global.css +1 -0
  485. package/corpus/templates/chat/app/root.tsx +7 -4
  486. package/corpus/templates/chat/app/routes/settings.tsx +6 -21
  487. package/corpus/templates/clips/.agents/skills/actions/SKILL.md +55 -0
  488. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +18 -4
  489. package/corpus/templates/clips/.env.example +8 -0
  490. package/corpus/templates/clips/actions/accept-invite.ts +3 -0
  491. package/corpus/templates/clips/actions/add-recording-to-space.ts +55 -27
  492. package/corpus/templates/clips/actions/add-vocabulary-term.ts +3 -1
  493. package/corpus/templates/clips/actions/archive-recording.ts +5 -12
  494. package/corpus/templates/clips/actions/cleanup-transcript.ts +5 -4
  495. package/corpus/templates/clips/actions/clear-edits.ts +42 -29
  496. package/corpus/templates/clips/actions/create-meeting.ts +87 -40
  497. package/corpus/templates/clips/actions/create-recording.ts +6 -3
  498. package/corpus/templates/clips/actions/delete-meeting.ts +25 -0
  499. package/corpus/templates/clips/actions/delete-recording-permanent.ts +3 -0
  500. package/corpus/templates/clips/actions/disconnect-calendar.ts +10 -4
  501. package/corpus/templates/clips/actions/export-insights-csv.ts +28 -8
  502. package/corpus/templates/clips/actions/finalize-meeting.ts +106 -33
  503. package/corpus/templates/clips/actions/finalize-recording.ts +201 -3
  504. package/corpus/templates/clips/actions/get-organization-insights.ts +51 -7
  505. package/corpus/templates/clips/actions/import-loom-recording.ts +17 -0
  506. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +18 -1
  507. package/corpus/templates/clips/actions/lib/create-recording-schema.ts +7 -1
  508. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +16 -0
  509. package/corpus/templates/clips/actions/list-clip-views.ts +60 -0
  510. package/corpus/templates/clips/actions/regenerate-summary.ts +10 -0
  511. package/corpus/templates/clips/actions/remove-vocabulary-term.ts +59 -0
  512. package/corpus/templates/clips/actions/request-transcript.ts +170 -25
  513. package/corpus/templates/clips/actions/restore-recording.ts +5 -12
  514. package/corpus/templates/clips/actions/save-browser-transcript.ts +13 -2
  515. package/corpus/templates/clips/actions/set-chapters.ts +6 -15
  516. package/corpus/templates/clips/actions/set-thumbnail.ts +77 -36
  517. package/corpus/templates/clips/actions/split-recording.ts +49 -35
  518. package/corpus/templates/clips/actions/stitch-recordings.ts +7 -4
  519. package/corpus/templates/clips/actions/stop-meeting-recording.ts +15 -1
  520. package/corpus/templates/clips/actions/sync-calendars.ts +10 -108
  521. package/corpus/templates/clips/actions/trash-recording.ts +43 -14
  522. package/corpus/templates/clips/actions/trim-recording.ts +48 -27
  523. package/corpus/templates/clips/actions/undo-edit.ts +49 -37
  524. package/corpus/templates/clips/app/components/capture-install-options.tsx +6 -6
  525. package/corpus/templates/clips/app/components/dictate/vocabulary-section.tsx +208 -0
  526. package/corpus/templates/clips/app/components/editable-recording-title.tsx +2 -2
  527. package/corpus/templates/clips/app/components/editor/chapters-editor.tsx +7 -7
  528. package/corpus/templates/clips/app/components/editor/editor-toolbar.tsx +26 -26
  529. package/corpus/templates/clips/app/components/editor/split-button.tsx +5 -4
  530. package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +9 -9
  531. package/corpus/templates/clips/app/components/editor/thumbnail-picker.tsx +11 -16
  532. package/corpus/templates/clips/app/components/editor/timeline.tsx +3 -3
  533. package/corpus/templates/clips/app/components/editor/transcript-editor.tsx +5 -5
  534. package/corpus/templates/clips/app/components/library/bulk-action-toolbar.tsx +4 -4
  535. package/corpus/templates/clips/app/components/library/create-space-dialog.tsx +8 -8
  536. package/corpus/templates/clips/app/components/library/empty-state.tsx +2 -1
  537. package/corpus/templates/clips/app/components/library/folder-tree.tsx +14 -14
  538. package/corpus/templates/clips/app/components/library/library-grid.tsx +24 -98
  539. package/corpus/templates/clips/app/components/library/library-layout.tsx +18 -19
  540. package/corpus/templates/clips/app/components/library/organization-switcher.tsx +11 -11
  541. package/corpus/templates/clips/app/components/library/recording-card.tsx +44 -50
  542. package/corpus/templates/clips/app/components/library/search-bar.tsx +5 -5
  543. package/corpus/templates/clips/app/components/library/sort-menu.tsx +4 -3
  544. package/corpus/templates/clips/app/components/library/tag-input.tsx +4 -4
  545. package/corpus/templates/clips/app/components/meetings/attendee-stack.tsx +2 -6
  546. package/corpus/templates/clips/app/components/meetings/auto-record-prompt.tsx +1 -1
  547. package/corpus/templates/clips/app/components/meetings/bullet-link.tsx +4 -4
  548. package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +5 -5
  549. package/corpus/templates/clips/app/components/meetings/meeting-card.tsx +3 -3
  550. package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +17 -9
  551. package/corpus/templates/clips/app/components/meetings/share-meeting-dialog.tsx +6 -11
  552. package/corpus/templates/clips/app/components/meetings/transcript-bubbles.tsx +215 -21
  553. package/corpus/templates/clips/app/components/player/access-password-prompt.tsx +3 -2
  554. package/corpus/templates/clips/app/components/player/comments-panel.tsx +14 -14
  555. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +7 -6
  556. package/corpus/templates/clips/app/components/player/insights-panel.tsx +18 -6
  557. package/corpus/templates/clips/app/components/player/player-controls.tsx +42 -36
  558. package/corpus/templates/clips/app/components/player/reactions-tray.tsx +4 -4
  559. package/corpus/templates/clips/app/components/player/settings-panel.tsx +13 -12
  560. package/corpus/templates/clips/app/components/player/share-dialog.tsx +80 -49
  561. package/corpus/templates/clips/app/components/player/sign-in-prompt-dialog.tsx +3 -2
  562. package/corpus/templates/clips/app/components/player/timestamped-comment-button.tsx +2 -2
  563. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +7 -7
  564. package/corpus/templates/clips/app/components/player/video-player.tsx +182 -15
  565. package/corpus/templates/clips/app/components/recorder/camera-visualizer.tsx +1 -1
  566. package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +1 -1
  567. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +49 -36
  568. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +285 -24
  569. package/corpus/templates/clips/app/components/recorder/recording-toolbar.tsx +6 -5
  570. package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +7 -6
  571. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +10 -10
  572. package/corpus/templates/clips/app/components/sharing/viewed-by-popover.tsx +132 -0
  573. package/corpus/templates/clips/app/components/ui/alert-dialog.tsx +1 -0
  574. package/corpus/templates/clips/app/components/ui/avatar.tsx +1 -0
  575. package/corpus/templates/clips/app/components/ui/badge.tsx +1 -0
  576. package/corpus/templates/clips/app/components/ui/button.tsx +1 -0
  577. package/corpus/templates/clips/app/components/ui/card.tsx +1 -0
  578. package/corpus/templates/clips/app/components/ui/checkbox.tsx +1 -0
  579. package/corpus/templates/clips/app/components/ui/collapsible.tsx +1 -0
  580. package/corpus/templates/clips/app/components/ui/context-menu.tsx +1 -0
  581. package/corpus/templates/clips/app/components/ui/dialog.tsx +1 -0
  582. package/corpus/templates/clips/app/components/ui/dropdown-menu.tsx +1 -0
  583. package/corpus/templates/clips/app/components/ui/input.tsx +1 -0
  584. package/corpus/templates/clips/app/components/ui/label.tsx +1 -0
  585. package/corpus/templates/clips/app/components/ui/popover.tsx +1 -0
  586. package/corpus/templates/clips/app/components/ui/scroll-area.tsx +1 -0
  587. package/corpus/templates/clips/app/components/ui/select.tsx +1 -0
  588. package/corpus/templates/clips/app/components/ui/separator.tsx +1 -0
  589. package/corpus/templates/clips/app/components/ui/sheet.tsx +1 -0
  590. package/corpus/templates/clips/app/components/ui/skeleton.tsx +1 -0
  591. package/corpus/templates/clips/app/components/ui/slider.tsx +1 -0
  592. package/corpus/templates/clips/app/components/ui/sonner.tsx +1 -0
  593. package/corpus/templates/clips/app/components/ui/spinner.tsx +1 -0
  594. package/corpus/templates/clips/app/components/ui/switch.tsx +1 -0
  595. package/corpus/templates/clips/app/components/ui/table.tsx +1 -0
  596. package/corpus/templates/clips/app/components/ui/tabs.tsx +1 -0
  597. package/corpus/templates/clips/app/components/ui/textarea.tsx +1 -0
  598. package/corpus/templates/clips/app/components/ui/toolkit-provider.tsx +12 -0
  599. package/corpus/templates/clips/app/components/ui/tooltip.tsx +1 -0
  600. package/corpus/templates/clips/app/components/workspace/branding-editor.tsx +5 -9
  601. package/corpus/templates/clips/app/components/workspace/insights-hub.tsx +9 -19
  602. package/corpus/templates/clips/app/components/workspace/invite-dialog.tsx +10 -9
  603. package/corpus/templates/clips/app/components/workspace/members-list.tsx +11 -10
  604. package/corpus/templates/clips/app/components/workspace/notifications-list.tsx +2 -1
  605. package/corpus/templates/clips/app/components/workspace/top-creators-table.tsx +3 -2
  606. package/corpus/templates/clips/app/components/workspace/top-videos-table.tsx +3 -2
  607. package/corpus/templates/clips/app/global.css +1 -0
  608. package/corpus/templates/clips/app/hooks/use-desktop-promo.ts +2 -1
  609. package/corpus/templates/clips/app/hooks/use-mobile.ts +1 -0
  610. package/corpus/templates/clips/app/hooks/use-player-shortcuts.ts +14 -10
  611. package/corpus/templates/clips/app/hooks/use-view-tracking.ts +14 -0
  612. package/corpus/templates/clips/app/i18n/ar-SA.ts +56 -0
  613. package/corpus/templates/clips/app/i18n/de-DE.ts +53 -0
  614. package/corpus/templates/clips/app/i18n/en-US.ts +52 -0
  615. package/corpus/templates/clips/app/i18n/es-ES.ts +53 -0
  616. package/corpus/templates/clips/app/i18n/fr-FR.ts +53 -0
  617. package/corpus/templates/clips/app/i18n/hi-IN.ts +52 -0
  618. package/corpus/templates/clips/app/i18n/ja-JP.ts +53 -0
  619. package/corpus/templates/clips/app/i18n/ko-KR.ts +52 -0
  620. package/corpus/templates/clips/app/i18n/pt-BR.ts +53 -0
  621. package/corpus/templates/clips/app/i18n/zh-CN.ts +51 -0
  622. package/corpus/templates/clips/app/i18n/zh-TW.ts +51 -0
  623. package/corpus/templates/clips/app/lib/recorder-preferences.ts +4 -0
  624. package/corpus/templates/clips/app/lib/timestamp-mapping.ts +5 -0
  625. package/corpus/templates/clips/app/root.tsx +26 -23
  626. package/corpus/templates/clips/app/routes/_app.dictate.tsx +39 -23
  627. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +134 -39
  628. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +20 -20
  629. package/corpus/templates/clips/app/routes/_app.notifications.tsx +3 -3
  630. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +92 -60
  631. package/corpus/templates/clips/app/routes/_app.settings.organization.tsx +8 -13
  632. package/corpus/templates/clips/app/routes/_app.spaces._index.tsx +1 -1
  633. package/corpus/templates/clips/app/routes/_app.trash.tsx +96 -31
  634. package/corpus/templates/clips/app/routes/bug-report.done.tsx +1 -1
  635. package/corpus/templates/clips/app/routes/bug-report.tsx +11 -11
  636. package/corpus/templates/clips/app/routes/download.tsx +2 -2
  637. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +2 -1
  638. package/corpus/templates/clips/app/routes/invite.$token.tsx +8 -13
  639. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +263 -251
  640. package/corpus/templates/clips/app/routes/record.tsx +28 -10
  641. package/corpus/templates/clips/app/routes/share.$shareId.tsx +51 -29
  642. package/corpus/templates/clips/app/routes/share.meeting.$meetingId.tsx +71 -16
  643. package/corpus/templates/clips/changelog/2026-07-05-mobile-clip-pages-use-a-tighter-viewer-layout-with-inline-ac.md +6 -0
  644. package/corpus/templates/clips/changelog/2026-07-06-ai-setup-now-switches-to-your-saved-provider-key-and-uses-fa.md +6 -0
  645. package/corpus/templates/clips/changelog/2026-07-06-browsers-that-can-t-play-a-clip-s-video-format-now-see-a-cle.md +6 -0
  646. package/corpus/templates/clips/changelog/2026-07-06-calendar-connections-no-longer-ask-you-to-reconnect-after-te.md +6 -0
  647. package/corpus/templates/clips/changelog/2026-07-06-chrome-extension-recordings-keep-your-selected-microphone.md +6 -0
  648. package/corpus/templates/clips/changelog/2026-07-06-clip-recordings-now-use-streaming-uploads-by-default.md +6 -0
  649. package/corpus/templates/clips/changelog/2026-07-06-clip-titles-in-the-library-grid-now-open-the-clip-instead-of.md +6 -0
  650. package/corpus/templates/clips/changelog/2026-07-06-desktop-countdown-now-dims-the-screen-before-recording.md +5 -0
  651. package/corpus/templates/clips/changelog/2026-07-06-desktop-popover-no-longer-drops-bluetooth-headphones-into-call-quality-mode.md +6 -0
  652. package/corpus/templates/clips/changelog/2026-07-06-desktop-recorder-now-refreshes-cameras-and-mics-when-the-pop.md +6 -0
  653. package/corpus/templates/clips/changelog/2026-07-06-desktop-recordings-keep-microphone-volume-clear-while-recor.md +5 -0
  654. package/corpus/templates/clips/changelog/2026-07-06-desktop-recordings-no-longer-get-stuck-optimizing-when-scree.md +6 -0
  655. package/corpus/templates/clips/changelog/2026-07-06-dictation-history-from-the-desktop-app-now-appears-in-the-di.md +6 -0
  656. package/corpus/templates/clips/changelog/2026-07-06-dictation-no-longer-overwrites-your-clipboard-and-messaging-.md +6 -0
  657. package/corpus/templates/clips/changelog/2026-07-06-double-tap-your-dictation-key-for-hands-free-dictation-tap-a.md +6 -0
  658. package/corpus/templates/clips/changelog/2026-07-06-fixed-desktop-recordings-sometimes-uploading-without-audio-a.md +6 -0
  659. package/corpus/templates/clips/changelog/2026-07-06-fixed-desktop-recordings-that-could-get-stuck-uploading-and-.md +6 -0
  660. package/corpus/templates/clips/changelog/2026-07-06-fixed-recordings-sometimes-capturing-from-the-wrong-micropho.md +6 -0
  661. package/corpus/templates/clips/changelog/2026-07-06-hands-free-dictation-and-meeting-cleanup-recover-more-reliab.md +6 -0
  662. package/corpus/templates/clips/changelog/2026-07-06-live-meetings-show-time-remaining-and-you-can-end-a-meeting-.md +6 -0
  663. package/corpus/templates/clips/changelog/2026-07-06-manage-your-personal-dictation-dictionary-from-the-dictate-t.md +6 -0
  664. package/corpus/templates/clips/changelog/2026-07-06-meetings-interrupted-by-quitting-or-crashing-now-close-out-c.md +6 -0
  665. package/corpus/templates/clips/changelog/2026-07-06-mic-only-desktop-recordings-now-use-the-selected-microphone-.md +6 -0
  666. package/corpus/templates/clips/changelog/2026-07-06-paste-your-last-dictation-anytime-with-cmd-ctrl-v-or-from-th.md +6 -0
  667. package/corpus/templates/clips/changelog/2026-07-06-press-esc-to-cancel-a-dictation-in-progress.md +6 -0
  668. package/corpus/templates/clips/changelog/2026-07-06-recordings-whose-video-file-contains-no-audio-track-no-longe.md +6 -0
  669. package/corpus/templates/clips/changelog/2026-07-06-search-inside-meeting-transcripts.md +6 -0
  670. package/corpus/templates/clips/changelog/2026-07-06-see-who-viewed-your-shared-clips-and-when-click-the-view-cou.md +6 -0
  671. package/corpus/templates/clips/changelog/2026-07-06-share-links-for-agents-now-appear-as-soon-as-the-share-menu-.md +6 -0
  672. package/corpus/templates/clips/changelog/2026-07-06-share-popovers-now-stay-above-video-playback-controls.md +6 -0
  673. package/corpus/templates/clips/changelog/2026-07-06-shared-clip-pages-now-recover-automatically-from-transient-v.md +6 -0
  674. package/corpus/templates/clips/changelog/2026-07-06-the-desktop-app-now-walks-you-through-permissions-on-first-l.md +6 -0
  675. package/corpus/templates/clips/changelog/2026-07-06-transcript-cleanup-preferences-now-save-per-user.md +6 -0
  676. package/corpus/templates/clips/changelog/2026-07-06-transcripts-now-retry-automatically-after-transient-failures.md +6 -0
  677. package/corpus/templates/clips/changelog/2026-07-06-video-compression-now-runs-in-the-background-without-blocking.md +6 -0
  678. package/corpus/templates/clips/changelog/2026-07-07-native-microphone-recordings-now-reduce-steady-background-noise.md +6 -0
  679. package/corpus/templates/clips/changelog/2026-07-07-recording-transcripts-now-line-up-with-the-video-start-afte.md +6 -0
  680. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
  681. package/corpus/templates/clips/chrome-extension/src/background.ts +141 -48
  682. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +383 -59
  683. package/corpus/templates/clips/chrome-extension/src/overlay.ts +51 -10
  684. package/corpus/templates/clips/chrome-extension/src/popup.ts +57 -6
  685. package/corpus/templates/clips/desktop/package.json +1 -1
  686. package/corpus/templates/clips/desktop/scripts/sign-macos-local.ts +67 -0
  687. package/corpus/templates/clips/desktop/src/app.tsx +38 -13
  688. package/corpus/templates/clips/desktop/src/components/SourceRow.tsx +1 -0
  689. package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +88 -28
  690. package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +165 -55
  691. package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +35 -10
  692. package/corpus/templates/clips/desktop/src/lib/backtrack.ts +12 -0
  693. package/corpus/templates/clips/desktop/src/lib/media-capture-constraints.ts +129 -34
  694. package/corpus/templates/clips/desktop/src/lib/media-device-selection.ts +157 -0
  695. package/corpus/templates/clips/desktop/src/lib/permissions.ts +2 -0
  696. package/corpus/templates/clips/desktop/src/lib/recorder.ts +109 -47
  697. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +22 -2
  698. package/corpus/templates/clips/desktop/src/lib/transcription-engine.ts +29 -1
  699. package/corpus/templates/clips/desktop/src/lib/updater.ts +6 -0
  700. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +372 -31
  701. package/corpus/templates/clips/desktop/src/overlays/finalizing.tsx +90 -0
  702. package/corpus/templates/clips/desktop/src/overlays/flow-bar.tsx +4 -6
  703. package/corpus/templates/clips/desktop/src/overlays/onboarding.tsx +214 -9
  704. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +37 -52
  705. package/corpus/templates/clips/desktop/src/styles.css +1 -1
  706. package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +7 -0
  707. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +371 -43
  708. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +79 -0
  709. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +1418 -260
  710. package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +188 -44
  711. package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +197 -8
  712. package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +64 -8
  713. package/corpus/templates/clips/desktop/src-tauri/src/state.rs +3 -2
  714. package/corpus/templates/clips/desktop/src-tauri/src/system_audio.rs +10 -0
  715. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +29 -8
  716. package/corpus/templates/clips/desktop/src-tauri/src/whisper_speech.rs +136 -13
  717. package/corpus/templates/clips/desktop/vite.config.ts +3 -0
  718. package/corpus/templates/clips/package.json +1 -1
  719. package/corpus/templates/clips/server/db/schema.ts +26 -0
  720. package/corpus/templates/clips/server/jobs/builder-media-compression.ts +41 -0
  721. package/corpus/templates/clips/server/jobs/stale-meeting-sweeper.ts +294 -0
  722. package/corpus/templates/clips/server/lib/builder-media-compression.ts +976 -0
  723. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +107 -56
  724. package/corpus/templates/clips/server/lib/env-flags.ts +4 -0
  725. package/corpus/templates/clips/server/lib/google-calendar-client.ts +8 -1
  726. package/corpus/templates/clips/server/lib/media-worker.ts +153 -0
  727. package/corpus/templates/clips/server/lib/s3-upload-provider.ts +58 -17
  728. package/corpus/templates/clips/server/lib/streaming-upload-mode.ts +17 -5
  729. package/corpus/templates/clips/server/lib/video-remux.ts +90 -0
  730. package/corpus/templates/clips/server/plugins/calendar-jobs.ts +4 -0
  731. package/corpus/templates/clips/server/plugins/db.ts +196 -0
  732. package/corpus/templates/clips/server/register-secrets.ts +78 -0
  733. package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.get.ts +15 -0
  734. package/corpus/templates/clips/server/routes/_agent-native/clips/user-prefs.put.ts +27 -0
  735. package/corpus/templates/clips/server/routes/api/media-worker/callback.post.ts +88 -0
  736. package/corpus/templates/clips/server/routes/api/public-recording.get.ts +87 -7
  737. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +7 -0
  738. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +35 -10
  739. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +24 -42
  740. package/corpus/templates/clips/server/routes/api/view-event.post.ts +29 -1
  741. package/corpus/templates/clips/shared/media-device-selection.ts +157 -0
  742. package/corpus/templates/clips/shared/media-worker-contract.ts +146 -0
  743. package/corpus/templates/content/.agents/skills/actions/SKILL.md +55 -0
  744. package/corpus/templates/content/.agents/skills/notion-integration/SKILL.md +67 -2
  745. package/corpus/templates/content/AGENTS.md +22 -14
  746. package/corpus/templates/content/actions/_local-file-documents.ts +90 -1
  747. package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
  748. package/corpus/templates/content/actions/delete-document.ts +8 -0
  749. package/corpus/templates/content/actions/remove-local-file-source.ts +100 -0
  750. package/corpus/templates/content/actions/sync-notion-comments.ts +174 -20
  751. package/corpus/templates/content/actions/update-document.ts +91 -5
  752. package/corpus/templates/content/app/blocks/SourceComponentBlock.tsx +31 -3
  753. package/corpus/templates/content/app/blocks/contentBlockRegistry.tsx +4 -4
  754. package/corpus/templates/content/app/components/EmptyState.tsx +1 -1
  755. package/corpus/templates/content/app/components/ThemeToggle.tsx +6 -6
  756. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +5 -5
  757. package/corpus/templates/content/app/components/editor/CommentsSidebar.tsx +5 -5
  758. package/corpus/templates/content/app/components/editor/ContentReferencePreview.tsx +1 -1
  759. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +10 -14630
  760. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +72 -51
  761. package/corpus/templates/content/app/components/editor/DocumentEditorSkeleton.tsx +36 -0
  762. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +29 -29
  763. package/corpus/templates/content/app/components/editor/DocumentToolbar.tsx +29 -44
  764. package/corpus/templates/content/app/components/editor/EmojiPicker.tsx +5 -4
  765. package/corpus/templates/content/app/components/editor/LinkHoverPreview.tsx +5 -4
  766. package/corpus/templates/content/app/components/editor/NotionConflictBanner.tsx +1 -1
  767. package/corpus/templates/content/app/components/editor/NotionSyncBar.tsx +2 -2
  768. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +5 -5
  769. package/corpus/templates/content/app/components/editor/TableHoverControls.tsx +13 -13
  770. package/corpus/templates/content/app/components/editor/VersionHistoryPanel.tsx +10 -10
  771. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +24 -7
  772. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +2856 -8389
  773. package/corpus/templates/content/app/components/editor/database/navigation-state.ts +36 -1
  774. package/corpus/templates/content/app/components/editor/database/settings.tsx +3224 -0
  775. package/corpus/templates/content/app/components/editor/database/shared.tsx +19 -19
  776. package/corpus/templates/content/app/components/editor/database/text.ts +23 -0
  777. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +9 -8
  778. package/corpus/templates/content/app/components/editor/extensions/AudioBlock.tsx +19 -18
  779. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +21 -20
  780. package/corpus/templates/content/app/components/editor/extensions/LocalMdxComponentNode.tsx +8 -8
  781. package/corpus/templates/content/app/components/editor/extensions/NotionExtensions.tsx +9 -0
  782. package/corpus/templates/content/app/components/editor/extensions/VideoBlock.tsx +19 -18
  783. package/corpus/templates/content/app/components/layout/Header.tsx +1 -5
  784. package/corpus/templates/content/app/components/layout/Layout.tsx +25 -8
  785. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +203 -55
  786. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +22 -22
  787. package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +11 -11
  788. package/corpus/templates/content/app/components/sidebar/document-sidebar-sections.ts +109 -1
  789. package/corpus/templates/content/app/components/ui/alert-dialog.tsx +1 -0
  790. package/corpus/templates/content/app/components/ui/button.tsx +1 -0
  791. package/corpus/templates/content/app/components/ui/dialog.tsx +1 -0
  792. package/corpus/templates/content/app/components/ui/dropdown-menu.tsx +1 -0
  793. package/corpus/templates/content/app/components/ui/input.tsx +1 -0
  794. package/corpus/templates/content/app/components/ui/label.tsx +1 -0
  795. package/corpus/templates/content/app/components/ui/popover.tsx +1 -0
  796. package/corpus/templates/content/app/components/ui/separator.tsx +1 -0
  797. package/corpus/templates/content/app/components/ui/sheet.tsx +1 -0
  798. package/corpus/templates/content/app/components/ui/skeleton.tsx +1 -0
  799. package/corpus/templates/content/app/components/ui/sonner.tsx +1 -0
  800. package/corpus/templates/content/app/components/ui/spinner.tsx +1 -0
  801. package/corpus/templates/content/app/components/ui/table.tsx +1 -0
  802. package/corpus/templates/content/app/components/ui/toaster.tsx +1 -0
  803. package/corpus/templates/content/app/components/ui/toolkit-provider.tsx +12 -0
  804. package/corpus/templates/content/app/components/ui/tooltip.tsx +1 -0
  805. package/corpus/templates/content/app/global.css +1 -0
  806. package/corpus/templates/content/app/hooks/use-documents.ts +89 -37
  807. package/corpus/templates/content/app/hooks/use-mobile.ts +1 -0
  808. package/corpus/templates/content/app/hooks/use-notion.ts +73 -5
  809. package/corpus/templates/content/app/i18n/zh-TW.ts +14 -0
  810. package/corpus/templates/content/app/i18n-data.ts +153 -0
  811. package/corpus/templates/content/app/root.tsx +35 -29
  812. package/corpus/templates/content/app/routes/_app._index.tsx +1 -1
  813. package/corpus/templates/content/app/routes/_app.local-files.tsx +268 -81
  814. package/corpus/templates/content/app/routes/_app.settings.tsx +4 -21
  815. package/corpus/templates/content/changelog/2026-07-01-builder-source-components-show-preservation-and-review-states.md +6 -0
  816. package/corpus/templates/content/changelog/2026-07-06-document-headers-and-page-bodies-now-share-one-background-shade.md +6 -0
  817. package/corpus/templates/content/changelog/2026-07-06-export-submenus-stay-visible-outside-the-page-actions-menu.md +6 -0
  818. package/corpus/templates/content/changelog/2026-07-06-fixed-collaborative-documents-reapplying-content-before-save.md +6 -0
  819. package/corpus/templates/content/changelog/2026-07-06-local-files-now-remember-sidebar-collapse-state-and-include-.md +6 -0
  820. package/corpus/templates/content/changelog/2026-07-06-notion-comment-sync-now-preserves-threads-replies-stay-attac.md +6 -0
  821. package/corpus/templates/content/changelog/2026-07-06-notion-sync-is-far-more-reliable-auto-sync-no-longer-overwri.md +6 -0
  822. package/corpus/templates/content/changelog/2026-07-06-opening-pages-now-highlights-the-sidebar-item-and-shows-the-.md +6 -0
  823. package/corpus/templates/content/changelog/2026-07-06-presence-markers-in-the-editor-are-now-compact-instead-of-co.md +6 -0
  824. package/corpus/templates/content/changelog/2026-07-06-sidebar-collapse-now-keeps-the-same-background-shade-as-the-.md +6 -0
  825. package/corpus/templates/content/changelog/2026-07-06-the-content-header-no-longer-shows-the-global-notifications-.md +6 -0
  826. package/corpus/templates/content/parity/matrix.md +24 -24
  827. package/corpus/templates/content/parity/matrix.ts +7 -3
  828. package/corpus/templates/content/server/db/schema.ts +15 -0
  829. package/corpus/templates/content/server/lib/notion-sync.ts +481 -39
  830. package/corpus/templates/content/server/lib/notion.ts +212 -48
  831. package/corpus/templates/content/server/plugins/db.ts +20 -0
  832. package/corpus/templates/content/server/routes/api/documents/[id]/notion/resolve.post.ts +14 -2
  833. package/corpus/templates/content/server/routes/api/notion/callback.get.ts +54 -6
  834. package/corpus/templates/content/shared/builder-mdx.ts +68 -12
  835. package/corpus/templates/content/shared/builder-source-component-registry.ts +177 -0
  836. package/corpus/templates/content/shared/nfm.ts +459 -66
  837. package/corpus/templates/content/shared/source-component-block.ts +72 -6
  838. package/corpus/templates/content/vite.config.ts +17 -1
  839. package/corpus/templates/design/.agents/skills/actions/SKILL.md +55 -0
  840. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +21 -12
  841. package/corpus/templates/design/AGENTS.md +3 -2
  842. package/corpus/templates/design/actions/import-design-source.ts +8 -58
  843. package/corpus/templates/design/actions/run-design-audit.ts +127 -1
  844. package/corpus/templates/design/actions/take-design-screenshot.ts +673 -0
  845. package/corpus/templates/design/app/components/ThemeToggle.tsx +6 -6
  846. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +11 -11
  847. package/corpus/templates/design/app/components/design/DesignEditorSkeleton.tsx +2 -2
  848. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +16 -21
  849. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +6 -97
  850. package/corpus/templates/design/app/components/design/DrawOverlay.tsx +7 -7
  851. package/corpus/templates/design/app/components/design/EditPanel.tsx +30 -30
  852. package/corpus/templates/design/app/components/design/FusionAppBanner.tsx +9 -8
  853. package/corpus/templates/design/app/components/design/LayersPanel.tsx +14 -14
  854. package/corpus/templates/design/app/components/design/LocalhostWriteConsentDialog.tsx +6 -5
  855. package/corpus/templates/design/app/components/design/MotionDock.tsx +15 -15
  856. package/corpus/templates/design/app/components/design/QuestionFlow.tsx +3 -3
  857. package/corpus/templates/design/app/components/design/ReviewPanel.tsx +3 -3
  858. package/corpus/templates/design/app/components/design/StatesPanel.tsx +13 -13
  859. package/corpus/templates/design/app/components/design/TokensPanel.tsx +14 -14
  860. package/corpus/templates/design/app/components/design/TweaksPanel.tsx +6 -6
  861. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +39 -9
  862. package/corpus/templates/design/app/components/design/code-workbench/ActivityBar.tsx +3 -3
  863. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +4 -7
  864. package/corpus/templates/design/app/components/design/code-workbench/editor/EditorTabs.tsx +6 -6
  865. package/corpus/templates/design/app/components/design/code-workbench/editor/MonacoHost.tsx +2 -1
  866. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +17 -17
  867. package/corpus/templates/design/app/components/design/code-workbench/search/SearchView.tsx +6 -6
  868. package/corpus/templates/design/app/components/design/inspector/AlignmentMatrix.tsx +3 -3
  869. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +7 -7
  870. package/corpus/templates/design/app/components/design/inspector/ConstraintsWidget.tsx +3 -3
  871. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +18 -18
  872. package/corpus/templates/design/app/components/design/inspector/ExportSettingsPanel.tsx +6 -6
  873. package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +6 -6
  874. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +7 -7
  875. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +7 -7
  876. package/corpus/templates/design/app/components/design/inspector/ShaderControls.tsx +14 -14
  877. package/corpus/templates/design/app/components/design/inspector/ShaderEffectRow.tsx +9 -9
  878. package/corpus/templates/design/app/components/design/inspector/ShaderFillsPanel.tsx +6 -6
  879. package/corpus/templates/design/app/components/design-system/DesignSystemCard.tsx +3 -2
  880. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +15 -18
  881. package/corpus/templates/design/app/components/layout/Header.tsx +2 -2
  882. package/corpus/templates/design/app/components/layout/Sidebar.tsx +5 -5
  883. package/corpus/templates/design/app/components/ui/alert-dialog.tsx +1 -0
  884. package/corpus/templates/design/app/components/ui/avatar.tsx +1 -0
  885. package/corpus/templates/design/app/components/ui/badge.tsx +1 -0
  886. package/corpus/templates/design/app/components/ui/button.tsx +1 -0
  887. package/corpus/templates/design/app/components/ui/card.tsx +1 -0
  888. package/corpus/templates/design/app/components/ui/checkbox.tsx +1 -0
  889. package/corpus/templates/design/app/components/ui/collapsible.tsx +1 -0
  890. package/corpus/templates/design/app/components/ui/context-menu.tsx +1 -0
  891. package/corpus/templates/design/app/components/ui/dialog.tsx +1 -0
  892. package/corpus/templates/design/app/components/ui/dropdown-menu.tsx +1 -0
  893. package/corpus/templates/design/app/components/ui/input.tsx +1 -0
  894. package/corpus/templates/design/app/components/ui/label.tsx +1 -0
  895. package/corpus/templates/design/app/components/ui/popover.tsx +1 -0
  896. package/corpus/templates/design/app/components/ui/select.tsx +1 -0
  897. package/corpus/templates/design/app/components/ui/separator.tsx +1 -0
  898. package/corpus/templates/design/app/components/ui/sheet.tsx +1 -0
  899. package/corpus/templates/design/app/components/ui/skeleton.tsx +1 -0
  900. package/corpus/templates/design/app/components/ui/slider.tsx +1 -0
  901. package/corpus/templates/design/app/components/ui/sonner.tsx +1 -0
  902. package/corpus/templates/design/app/components/ui/spinner.tsx +1 -0
  903. package/corpus/templates/design/app/components/ui/switch.tsx +1 -0
  904. package/corpus/templates/design/app/components/ui/tabs.tsx +1 -0
  905. package/corpus/templates/design/app/components/ui/textarea.tsx +1 -0
  906. package/corpus/templates/design/app/components/ui/toolkit-provider.tsx +12 -0
  907. package/corpus/templates/design/app/components/ui/tooltip.tsx +1 -0
  908. package/corpus/templates/design/app/components/visual-editor/CanvasCommentPins.tsx +7 -7
  909. package/corpus/templates/design/app/components/visual-editor/DrawOverlay.tsx +7 -7
  910. package/corpus/templates/design/app/components/visual-editor/SaveStatusIndicator.tsx +3 -3
  911. package/corpus/templates/design/app/global.css +1 -0
  912. package/corpus/templates/design/app/i18n/zh-TW.ts +3 -4
  913. package/corpus/templates/design/app/i18n-data.ts +38 -35
  914. package/corpus/templates/design/app/pages/DesignEditor.tsx +47 -51
  915. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +6 -6
  916. package/corpus/templates/design/app/pages/DesignSystems.tsx +31 -31
  917. package/corpus/templates/design/app/pages/Index.tsx +25 -29
  918. package/corpus/templates/design/app/pages/NotFound.tsx +2 -1
  919. package/corpus/templates/design/app/pages/Present.tsx +2 -1
  920. package/corpus/templates/design/app/pages/VisualEdit.tsx +2 -1
  921. package/corpus/templates/design/app/root.tsx +7 -4
  922. package/corpus/templates/design/app/routes/settings.tsx +22 -49
  923. package/corpus/templates/design/changelog/2026-07-05-code-sidebar-tabs-now-use-a-quieter-active-highlight-that-ma.md +6 -0
  924. package/corpus/templates/design/changelog/2026-07-06-fig-files-now-stay-on-the-builder-indexing-path-through-desi.md +6 -0
  925. package/corpus/templates/design/changelog/2026-07-06-mobile-layouts-no-longer-stack-two-top-headers.md +5 -0
  926. package/corpus/templates/design/changelog/2026-07-06-the-design-agent-now-visually-reviews-rendered-screenshots-o.md +6 -0
  927. package/corpus/templates/design/server/handlers/import-design-file.ts +4 -43
  928. package/corpus/templates/design/server/handlers/index-design-system-with-builder.ts +2 -2
  929. package/corpus/templates/design/server/lib/visible-clipboard-html.ts +32 -0
  930. package/corpus/templates/design/shared/design-review.ts +1 -0
  931. package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +55 -0
  932. package/corpus/templates/dispatch/actions/list-staged-datasets.ts +3 -0
  933. package/corpus/templates/dispatch/app/routes/settings.tsx +3 -17
  934. package/corpus/templates/forms/.agents/skills/actions/SKILL.md +55 -0
  935. package/corpus/templates/forms/app/components/CloudUpgrade.tsx +10 -10
  936. package/corpus/templates/forms/app/components/ThemeToggle.tsx +5 -5
  937. package/corpus/templates/forms/app/components/builder/FieldPropertiesPanel.tsx +11 -10
  938. package/corpus/templates/forms/app/components/builder/FieldRenderer.tsx +10 -13
  939. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +17 -17
  940. package/corpus/templates/forms/app/components/ui/alert-dialog.tsx +1 -0
  941. package/corpus/templates/forms/app/components/ui/badge.tsx +1 -0
  942. package/corpus/templates/forms/app/components/ui/button.tsx +1 -0
  943. package/corpus/templates/forms/app/components/ui/calendar.tsx +1 -1
  944. package/corpus/templates/forms/app/components/ui/card.tsx +1 -0
  945. package/corpus/templates/forms/app/components/ui/checkbox.tsx +1 -0
  946. package/corpus/templates/forms/app/components/ui/command.tsx +1 -1
  947. package/corpus/templates/forms/app/components/ui/dialog.tsx +1 -0
  948. package/corpus/templates/forms/app/components/ui/dropdown-menu.tsx +1 -0
  949. package/corpus/templates/forms/app/components/ui/input.tsx +1 -0
  950. package/corpus/templates/forms/app/components/ui/label.tsx +1 -0
  951. package/corpus/templates/forms/app/components/ui/popover.tsx +1 -0
  952. package/corpus/templates/forms/app/components/ui/radio-group.tsx +1 -0
  953. package/corpus/templates/forms/app/components/ui/scroll-area.tsx +1 -0
  954. package/corpus/templates/forms/app/components/ui/select.tsx +1 -0
  955. package/corpus/templates/forms/app/components/ui/separator.tsx +1 -0
  956. package/corpus/templates/forms/app/components/ui/skeleton.tsx +1 -0
  957. package/corpus/templates/forms/app/components/ui/slider.tsx +1 -0
  958. package/corpus/templates/forms/app/components/ui/switch.tsx +1 -0
  959. package/corpus/templates/forms/app/components/ui/table.tsx +1 -0
  960. package/corpus/templates/forms/app/components/ui/tabs.tsx +1 -0
  961. package/corpus/templates/forms/app/components/ui/textarea.tsx +1 -0
  962. package/corpus/templates/forms/app/components/ui/toolkit-provider.tsx +12 -0
  963. package/corpus/templates/forms/app/components/ui/tooltip.tsx +1 -0
  964. package/corpus/templates/forms/app/global.css +1 -0
  965. package/corpus/templates/forms/app/hooks/use-mobile.ts +1 -0
  966. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +0 -1
  967. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +24 -26
  968. package/corpus/templates/forms/app/pages/FormFillPage.tsx +2 -2
  969. package/corpus/templates/forms/app/pages/FormsListPage.tsx +21 -21
  970. package/corpus/templates/forms/app/pages/ResponseInsightsPage.tsx +11 -11
  971. package/corpus/templates/forms/app/pages/ResponsesPage.tsx +4 -4
  972. package/corpus/templates/forms/app/root.tsx +11 -8
  973. package/corpus/templates/forms/app/routes/_app.settings.tsx +6 -21
  974. package/corpus/templates/forms/app/routes/form-preview.tsx +3 -3
  975. package/corpus/templates/forms/changelog/2026-07-06-the-form-builder-header-no-longer-shows-the-global-notificat.md +6 -0
  976. package/corpus/templates/macros/app/components/AddExerciseDialog.tsx +1 -1
  977. package/corpus/templates/macros/app/components/AddMealDialog.tsx +1 -1
  978. package/corpus/templates/macros/app/components/AddWeightDialog.tsx +1 -1
  979. package/corpus/templates/macros/app/components/DailyProgress.tsx +7 -7
  980. package/corpus/templates/macros/app/components/WeeklyCaloriesChart.tsx +2 -1
  981. package/corpus/templates/macros/app/components/WeightCard.tsx +10 -10
  982. package/corpus/templates/macros/app/components/WeightTracker.tsx +1 -1
  983. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +6 -10
  984. package/corpus/templates/macros/app/components/ui/alert-dialog.tsx +1 -0
  985. package/corpus/templates/macros/app/components/ui/label.tsx +1 -0
  986. package/corpus/templates/macros/app/components/ui/select.tsx +1 -0
  987. package/corpus/templates/macros/app/components/ui/sheet.tsx +1 -0
  988. package/corpus/templates/macros/app/components/ui/skeleton.tsx +1 -0
  989. package/corpus/templates/macros/app/components/ui/sonner.tsx +1 -0
  990. package/corpus/templates/macros/app/components/ui/toolkit-provider.tsx +12 -0
  991. package/corpus/templates/macros/app/components/ui/tooltip.tsx +1 -0
  992. package/corpus/templates/macros/app/global.css +1 -0
  993. package/corpus/templates/macros/app/root.tsx +17 -14
  994. package/corpus/templates/macros/app/routes/_index.tsx +1 -1
  995. package/corpus/templates/macros/app/routes/analytics.tsx +8 -8
  996. package/corpus/templates/macros/app/routes/settings.tsx +4 -19
  997. package/corpus/templates/mail/.agents/skills/actions/SKILL.md +55 -0
  998. package/corpus/templates/mail/.agents/skills/contacts-and-crm/SKILL.md +67 -0
  999. package/corpus/templates/mail/.agents/skills/draft-queue/SKILL.md +35 -1
  1000. package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +74 -33
  1001. package/corpus/templates/mail/AGENTS.md +102 -16
  1002. package/corpus/templates/mail/actions/archive-email.ts +67 -11
  1003. package/corpus/templates/mail/actions/manage-snippets.ts +106 -0
  1004. package/corpus/templates/mail/actions/mark-read.ts +45 -11
  1005. package/corpus/templates/mail/actions/send-queued-drafts.ts +77 -42
  1006. package/corpus/templates/mail/actions/star-email.ts +55 -10
  1007. package/corpus/templates/mail/actions/trash-email.ts +48 -9
  1008. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +1 -1
  1009. package/corpus/templates/mail/app/components/ThemeToggle.tsx +6 -6
  1010. package/corpus/templates/mail/app/components/agent-engine-picker.tsx +10 -10
  1011. package/corpus/templates/mail/app/components/email/CodeBlockLangPicker.tsx +4 -3
  1012. package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +5 -5
  1013. package/corpus/templates/mail/app/components/email/ComposeEditor.tsx +109 -10
  1014. package/corpus/templates/mail/app/components/email/ComposeModal.tsx +44 -19
  1015. package/corpus/templates/mail/app/components/email/ComposeSlashMenu.tsx +139 -17
  1016. package/corpus/templates/mail/app/components/email/EmailList.tsx +522 -314
  1017. package/corpus/templates/mail/app/components/email/EmailListItem.tsx +130 -45
  1018. package/corpus/templates/mail/app/components/email/EmailThread.tsx +9 -9
  1019. package/corpus/templates/mail/app/components/email/InlineReplyComposer.tsx +35 -13
  1020. package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +13 -13
  1021. package/corpus/templates/mail/app/components/email/MobileActionBar.tsx +13 -13
  1022. package/corpus/templates/mail/app/components/email/RecipientInput.tsx +53 -42
  1023. package/corpus/templates/mail/app/components/email/SendLaterButton.tsx +5 -4
  1024. package/corpus/templates/mail/app/components/email/SnoozeModal.tsx +1 -1
  1025. package/corpus/templates/mail/app/components/email/SnoozePopover.tsx +6 -6
  1026. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +19 -23
  1027. package/corpus/templates/mail/app/components/layout/HeaderActions.tsx +12 -4
  1028. package/corpus/templates/mail/app/components/layout/SearchBar.tsx +138 -18
  1029. package/corpus/templates/mail/app/components/settings/GmailFiltersSection.tsx +23 -23
  1030. package/corpus/templates/mail/app/components/settings/SnippetsSection.tsx +303 -0
  1031. package/corpus/templates/mail/app/components/ui/alert-dialog.tsx +1 -0
  1032. package/corpus/templates/mail/app/components/ui/badge.tsx +1 -0
  1033. package/corpus/templates/mail/app/components/ui/button.tsx +1 -0
  1034. package/corpus/templates/mail/app/components/ui/calendar.tsx +1 -1
  1035. package/corpus/templates/mail/app/components/ui/command.tsx +1 -1
  1036. package/corpus/templates/mail/app/components/ui/dialog.tsx +1 -0
  1037. package/corpus/templates/mail/app/components/ui/drawer.tsx +1 -0
  1038. package/corpus/templates/mail/app/components/ui/dropdown-menu.tsx +1 -0
  1039. package/corpus/templates/mail/app/components/ui/popover.tsx +1 -0
  1040. package/corpus/templates/mail/app/components/ui/select.tsx +1 -0
  1041. package/corpus/templates/mail/app/components/ui/skeleton.tsx +1 -0
  1042. package/corpus/templates/mail/app/components/ui/spinner.tsx +1 -0
  1043. package/corpus/templates/mail/app/components/ui/switch.tsx +1 -0
  1044. package/corpus/templates/mail/app/components/ui/toolkit-provider.tsx +12 -0
  1045. package/corpus/templates/mail/app/components/ui/tooltip.tsx +1 -0
  1046. package/corpus/templates/mail/app/global.css +20 -3
  1047. package/corpus/templates/mail/app/hooks/use-emails.ts +187 -0
  1048. package/corpus/templates/mail/app/hooks/use-snippets.ts +103 -0
  1049. package/corpus/templates/mail/app/i18n/ar-SA.ts +17 -0
  1050. package/corpus/templates/mail/app/i18n/de-DE.ts +18 -0
  1051. package/corpus/templates/mail/app/i18n/en-US.ts +17 -0
  1052. package/corpus/templates/mail/app/i18n/es-ES.ts +17 -0
  1053. package/corpus/templates/mail/app/i18n/fr-FR.ts +18 -0
  1054. package/corpus/templates/mail/app/i18n/hi-IN.ts +17 -0
  1055. package/corpus/templates/mail/app/i18n/ja-JP.ts +18 -0
  1056. package/corpus/templates/mail/app/i18n/ko-KR.ts +17 -0
  1057. package/corpus/templates/mail/app/i18n/pt-BR.ts +17 -0
  1058. package/corpus/templates/mail/app/i18n/zh-CN.ts +16 -0
  1059. package/corpus/templates/mail/app/i18n/zh-TW.ts +16 -0
  1060. package/corpus/templates/mail/app/pages/DraftQueuePage.tsx +14 -14
  1061. package/corpus/templates/mail/app/pages/InboxPage.tsx +3 -3
  1062. package/corpus/templates/mail/app/pages/NotFound.tsx +2 -1
  1063. package/corpus/templates/mail/app/pages/SettingsPage.tsx +64 -66
  1064. package/corpus/templates/mail/app/root.tsx +21 -18
  1065. package/corpus/templates/mail/app/routes/_index.tsx +2 -1
  1066. package/corpus/templates/mail/app/routes/email.tsx +2 -2
  1067. package/corpus/templates/mail/changelog/2026-07-06-a-queued-draft-can-no-longer-be-sent-twice-by-simultaneous-s.md +6 -0
  1068. package/corpus/templates/mail/changelog/2026-07-06-bulk-archive-trash-star-and-mark-read-now-complete-in-one-fa.md +6 -0
  1069. package/corpus/templates/mail/changelog/2026-07-06-dark-mode-sidebars-and-notifications-now-use-the-softer-gray.md +6 -0
  1070. package/corpus/templates/mail/changelog/2026-07-06-inbox-automations-pick-up-new-mail-faster.md +6 -0
  1071. package/corpus/templates/mail/changelog/2026-07-06-inbox-lists-stay-fast-as-scheduled-and-snoozed-mail-accumula.md +6 -0
  1072. package/corpus/templates/mail/changelog/2026-07-06-long-inboxes-scroll-smoothly-and-stay-fast-as-more-mail-load.md +6 -0
  1073. package/corpus/templates/mail/changelog/2026-07-06-paste-or-drop-images-straight-into-the-composer-to-send-them.md +6 -0
  1074. package/corpus/templates/mail/changelog/2026-07-06-save-reusable-snippets-and-insert-them-from-the-compose-slas.md +6 -0
  1075. package/corpus/templates/mail/changelog/2026-07-06-scrolling-older-mail-keeps-loading-correctly-when-no-gmail-a.md +6 -0
  1076. package/corpus/templates/mail/changelog/2026-07-06-search-shows-instant-matches-from-already-loaded-mail-while-.md +6 -0
  1077. package/corpus/templates/mail/changelog/2026-07-06-sends-now-fail-with-a-clear-error-instead-of-quietly-missing.md +6 -0
  1078. package/corpus/templates/mail/changelog/2026-07-06-the-mail-header-no-longer-shows-the-global-notifications-bel.md +6 -0
  1079. package/corpus/templates/mail/package.json +1 -0
  1080. package/corpus/templates/mail/server/db/schema.ts +11 -0
  1081. package/corpus/templates/mail/server/handlers/emails.ts +20 -1
  1082. package/corpus/templates/mail/server/lib/automation-engine.ts +53 -25
  1083. package/corpus/templates/mail/server/lib/google-auth.ts +140 -9
  1084. package/corpus/templates/mail/server/lib/jobs.ts +22 -6
  1085. package/corpus/templates/mail/server/lib/outgoing-email.ts +11 -2
  1086. package/corpus/templates/mail/server/lib/queued-drafts.ts +145 -4
  1087. package/corpus/templates/mail/server/plugins/db.ts +29 -0
  1088. package/corpus/templates/plan/.agents/skills/actions/SKILL.md +55 -0
  1089. package/corpus/templates/plan/app/components/editor/PlanDocumentEditor.tsx +42 -3
  1090. package/corpus/templates/plan/app/components/layout/Layout.tsx +5 -4
  1091. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +20 -20
  1092. package/corpus/templates/plan/app/components/plan/CanvasArea.tsx +3 -2
  1093. package/corpus/templates/plan/app/components/plan/DocumentArea.tsx +2 -2
  1094. package/corpus/templates/plan/app/components/plan/GuestModeBanner.tsx +2 -1
  1095. package/corpus/templates/plan/app/components/plan/LocalCodebasePicker.tsx +8 -8
  1096. package/corpus/templates/plan/app/components/plan/PlanContentRenderer.tsx +6 -1
  1097. package/corpus/templates/plan/app/components/plan/PlanImageViewer.tsx +8 -12
  1098. package/corpus/templates/plan/app/components/plan/PlanMarkdownEditor.tsx +6 -1
  1099. package/corpus/templates/plan/app/components/plan/PlanVisualSurface.tsx +3 -3
  1100. package/corpus/templates/plan/app/components/plan/planBlocks.tsx +4 -4
  1101. package/corpus/templates/plan/app/components/plan/wireframe/kit/rough.tsx +61 -27
  1102. package/corpus/templates/plan/app/components/ui/alert-dialog.tsx +1 -0
  1103. package/corpus/templates/plan/app/components/ui/avatar.tsx +1 -0
  1104. package/corpus/templates/plan/app/components/ui/badge.tsx +1 -0
  1105. package/corpus/templates/plan/app/components/ui/button.tsx +1 -0
  1106. package/corpus/templates/plan/app/components/ui/card.tsx +1 -0
  1107. package/corpus/templates/plan/app/components/ui/checkbox.tsx +1 -0
  1108. package/corpus/templates/plan/app/components/ui/collapsible.tsx +1 -0
  1109. package/corpus/templates/plan/app/components/ui/dialog.tsx +1 -0
  1110. package/corpus/templates/plan/app/components/ui/dropdown-menu.tsx +1 -0
  1111. package/corpus/templates/plan/app/components/ui/input.tsx +1 -0
  1112. package/corpus/templates/plan/app/components/ui/label.tsx +1 -0
  1113. package/corpus/templates/plan/app/components/ui/popover.tsx +1 -0
  1114. package/corpus/templates/plan/app/components/ui/scroll-area.tsx +1 -0
  1115. package/corpus/templates/plan/app/components/ui/select.tsx +1 -0
  1116. package/corpus/templates/plan/app/components/ui/separator.tsx +1 -0
  1117. package/corpus/templates/plan/app/components/ui/sheet.tsx +1 -0
  1118. package/corpus/templates/plan/app/components/ui/skeleton.tsx +1 -0
  1119. package/corpus/templates/plan/app/components/ui/sonner.tsx +1 -0
  1120. package/corpus/templates/plan/app/components/ui/spinner.tsx +1 -0
  1121. package/corpus/templates/plan/app/components/ui/textarea.tsx +1 -0
  1122. package/corpus/templates/plan/app/components/ui/toggle-group.tsx +1 -0
  1123. package/corpus/templates/plan/app/components/ui/toolkit-provider.tsx +12 -0
  1124. package/corpus/templates/plan/app/components/ui/tooltip.tsx +1 -0
  1125. package/corpus/templates/plan/app/global.css +13 -0
  1126. package/corpus/templates/plan/app/hooks/use-plan-presence.ts +11 -1
  1127. package/corpus/templates/plan/app/lib/plan-access-prompt.ts +74 -0
  1128. package/corpus/templates/plan/app/lib/plan-annotation-runtime.ts +1435 -0
  1129. package/corpus/templates/plan/app/lib/plan-comment-editor-helpers.ts +142 -0
  1130. package/corpus/templates/plan/app/lib/plan-local-bridge.ts +376 -0
  1131. package/corpus/templates/plan/app/lib/plan-native-anchors.ts +682 -0
  1132. package/corpus/templates/plan/app/pages/PlansPage.tsx +216 -2747
  1133. package/corpus/templates/plan/app/root.tsx +13 -10
  1134. package/corpus/templates/plan/app/routes/_index.tsx +1 -2
  1135. package/corpus/templates/plan/app/routes/local-plans.$slug.tsx +1 -1
  1136. package/corpus/templates/plan/app/routes/plans.$id.tsx +1 -2
  1137. package/corpus/templates/plan/app/routes/plans.tsx +1 -2
  1138. package/corpus/templates/plan/app/routes/recaps.$id.tsx +1 -2
  1139. package/corpus/templates/plan/app/routes/recaps.tsx +1 -2
  1140. package/corpus/templates/plan/app/routes/settings.tsx +7 -21
  1141. package/corpus/templates/plan/changelog/2026-07-05-plan-pages-open-at-the-top-unless-a-shared-link-targets-a-sp.md +6 -0
  1142. package/corpus/templates/plan/changelog/2026-07-06-fixed-collaborative-plan-editing-so-content-no-longer-duplic.md +6 -0
  1143. package/corpus/templates/plan/changelog/2026-07-06-fixed-visual-reports-using-excessive-cpu-while-rendering-ske.md +6 -0
  1144. package/corpus/templates/slides/.agents/skills/actions/SKILL.md +55 -0
  1145. package/corpus/templates/slides/app/components/ThemeToggle.tsx +6 -6
  1146. package/corpus/templates/slides/app/components/comments/SlideCommentsPanel.tsx +5 -5
  1147. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +7 -7
  1148. package/corpus/templates/slides/app/components/deck/ExcalidrawSlide.tsx +2 -1
  1149. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +1 -1
  1150. package/corpus/templates/slides/app/components/design-system/DesignSystemCard.tsx +3 -2
  1151. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +15 -14
  1152. package/corpus/templates/slides/app/components/editor/AnimationsPanel.tsx +7 -7
  1153. package/corpus/templates/slides/app/components/editor/BlockBubbleMenu.tsx +5 -5
  1154. package/corpus/templates/slides/app/components/editor/EditorSidebar.tsx +6 -6
  1155. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +14 -14
  1156. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +10 -9
  1157. package/corpus/templates/slides/app/components/editor/GenerateSlidesDialog.tsx +6 -5
  1158. package/corpus/templates/slides/app/components/editor/HistoryPanel.tsx +12 -12
  1159. package/corpus/templates/slides/app/components/editor/ImageDropPromptPopover.tsx +4 -3
  1160. package/corpus/templates/slides/app/components/editor/ImageSearchPanel.tsx +5 -4
  1161. package/corpus/templates/slides/app/components/editor/ImportButton.tsx +4 -3
  1162. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +2 -1
  1163. package/corpus/templates/slides/app/components/editor/ShareDialog.tsx +10 -10
  1164. package/corpus/templates/slides/app/components/editor/SlideBubbleMenu.tsx +10 -10
  1165. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +6 -6
  1166. package/corpus/templates/slides/app/components/editor/SlideStyleInspector.tsx +2 -1
  1167. package/corpus/templates/slides/app/components/editor/TweaksPanel.tsx +1 -1
  1168. package/corpus/templates/slides/app/components/layout/Header.tsx +2 -2
  1169. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +5 -5
  1170. package/corpus/templates/slides/app/components/ui/alert-dialog.tsx +1 -0
  1171. package/corpus/templates/slides/app/components/ui/button.tsx +1 -0
  1172. package/corpus/templates/slides/app/components/ui/card.tsx +1 -0
  1173. package/corpus/templates/slides/app/components/ui/dialog.tsx +1 -0
  1174. package/corpus/templates/slides/app/components/ui/dropdown-menu.tsx +1 -0
  1175. package/corpus/templates/slides/app/components/ui/input.tsx +1 -0
  1176. package/corpus/templates/slides/app/components/ui/label.tsx +1 -0
  1177. package/corpus/templates/slides/app/components/ui/popover.tsx +1 -0
  1178. package/corpus/templates/slides/app/components/ui/scroll-area.tsx +1 -0
  1179. package/corpus/templates/slides/app/components/ui/select.tsx +1 -0
  1180. package/corpus/templates/slides/app/components/ui/separator.tsx +1 -0
  1181. package/corpus/templates/slides/app/components/ui/sheet.tsx +1 -0
  1182. package/corpus/templates/slides/app/components/ui/skeleton.tsx +1 -0
  1183. package/corpus/templates/slides/app/components/ui/spinner.tsx +1 -0
  1184. package/corpus/templates/slides/app/components/ui/textarea.tsx +1 -0
  1185. package/corpus/templates/slides/app/components/ui/toast.tsx +1 -0
  1186. package/corpus/templates/slides/app/components/ui/toaster.tsx +1 -0
  1187. package/corpus/templates/slides/app/components/ui/toggle-group.tsx +1 -0
  1188. package/corpus/templates/slides/app/components/ui/toolkit-provider.tsx +12 -0
  1189. package/corpus/templates/slides/app/components/ui/tooltip.tsx +1 -0
  1190. package/corpus/templates/slides/app/components/visual-editor/CanvasCommentPins.tsx +6 -6
  1191. package/corpus/templates/slides/app/components/visual-editor/DrawOverlay.tsx +7 -7
  1192. package/corpus/templates/slides/app/global.css +1 -0
  1193. package/corpus/templates/slides/app/hooks/use-toast.ts +1 -0
  1194. package/corpus/templates/slides/app/pages/DeckEditor.tsx +3 -3
  1195. package/corpus/templates/slides/app/pages/DesignSystems.tsx +1 -1
  1196. package/corpus/templates/slides/app/pages/Index.tsx +14 -17
  1197. package/corpus/templates/slides/app/root.tsx +13 -10
  1198. package/corpus/templates/slides/app/routes/settings.tsx +6 -21
  1199. package/corpus/templates/slides/changelog/2026-07-06-mobile-layouts-no-longer-stack-two-top-headers.md +5 -0
  1200. package/corpus/templates/slides/server/handlers/uploads.ts +7 -2
  1201. package/corpus/templates/slides/server/lib/chat-attachments.ts +1 -1
  1202. package/dist/agent/durable-background.d.ts +29 -0
  1203. package/dist/agent/durable-background.d.ts.map +1 -1
  1204. package/dist/agent/durable-background.js +46 -0
  1205. package/dist/agent/durable-background.js.map +1 -1
  1206. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  1207. package/dist/agent/engine/ai-sdk-engine.js +7 -2
  1208. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  1209. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  1210. package/dist/agent/engine/anthropic-engine.js +28 -2
  1211. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  1212. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  1213. package/dist/agent/engine/builder-engine.js +1 -1
  1214. package/dist/agent/engine/builder-engine.js.map +1 -1
  1215. package/dist/agent/engine/output-tokens.d.ts +3 -3
  1216. package/dist/agent/engine/output-tokens.d.ts.map +1 -1
  1217. package/dist/agent/engine/output-tokens.js +16 -14
  1218. package/dist/agent/engine/output-tokens.js.map +1 -1
  1219. package/dist/agent/engine/registry.d.ts +3 -3
  1220. package/dist/agent/engine/registry.d.ts.map +1 -1
  1221. package/dist/agent/engine/registry.js +28 -25
  1222. package/dist/agent/engine/registry.js.map +1 -1
  1223. package/dist/agent/engine/translate-ai-sdk.d.ts +14 -2
  1224. package/dist/agent/engine/translate-ai-sdk.d.ts.map +1 -1
  1225. package/dist/agent/engine/translate-ai-sdk.js +33 -18
  1226. package/dist/agent/engine/translate-ai-sdk.js.map +1 -1
  1227. package/dist/agent/engine/translate-anthropic.d.ts.map +1 -1
  1228. package/dist/agent/engine/translate-anthropic.js +42 -1
  1229. package/dist/agent/engine/translate-anthropic.js.map +1 -1
  1230. package/dist/agent/engine/types.d.ts +15 -0
  1231. package/dist/agent/engine/types.d.ts.map +1 -1
  1232. package/dist/agent/engine/types.js.map +1 -1
  1233. package/dist/agent/model-config.d.ts +8 -0
  1234. package/dist/agent/model-config.d.ts.map +1 -1
  1235. package/dist/agent/model-config.js +86 -0
  1236. package/dist/agent/model-config.js.map +1 -1
  1237. package/dist/agent/production-agent.d.ts +91 -5
  1238. package/dist/agent/production-agent.d.ts.map +1 -1
  1239. package/dist/agent/production-agent.js +389 -180
  1240. package/dist/agent/production-agent.js.map +1 -1
  1241. package/dist/agent/runtime-context.d.ts +29 -0
  1242. package/dist/agent/runtime-context.d.ts.map +1 -1
  1243. package/dist/agent/runtime-context.js +40 -11
  1244. package/dist/agent/runtime-context.js.map +1 -1
  1245. package/dist/agent/tool-result-images.d.ts +49 -0
  1246. package/dist/agent/tool-result-images.d.ts.map +1 -0
  1247. package/dist/agent/tool-result-images.js +141 -0
  1248. package/dist/agent/tool-result-images.js.map +1 -0
  1249. package/dist/brand-kit/fig/index.d.ts.map +1 -1
  1250. package/dist/brand-kit/fig/index.js +6 -3
  1251. package/dist/brand-kit/fig/index.js.map +1 -1
  1252. package/dist/client/AgentPanel.d.ts.map +1 -1
  1253. package/dist/client/AgentPanel.js +66 -17
  1254. package/dist/client/AgentPanel.js.map +1 -1
  1255. package/dist/client/AssistantChat.d.ts.map +1 -1
  1256. package/dist/client/AssistantChat.js +72 -40
  1257. package/dist/client/AssistantChat.js.map +1 -1
  1258. package/dist/client/KeepTabOpenNotice.d.ts +55 -0
  1259. package/dist/client/KeepTabOpenNotice.d.ts.map +1 -0
  1260. package/dist/client/KeepTabOpenNotice.js +85 -0
  1261. package/dist/client/KeepTabOpenNotice.js.map +1 -0
  1262. package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
  1263. package/dist/client/MultiTabAssistantChat.js +2 -1
  1264. package/dist/client/MultiTabAssistantChat.js.map +1 -1
  1265. package/dist/client/RunStuckBanner.d.ts +7 -1
  1266. package/dist/client/RunStuckBanner.d.ts.map +1 -1
  1267. package/dist/client/RunStuckBanner.js +7 -2
  1268. package/dist/client/RunStuckBanner.js.map +1 -1
  1269. package/dist/client/chat/run-recovery.d.ts.map +1 -1
  1270. package/dist/client/chat/run-recovery.js +7 -1
  1271. package/dist/client/chat/run-recovery.js.map +1 -1
  1272. package/dist/client/clipboard.d.ts.map +1 -1
  1273. package/dist/client/clipboard.js +5 -6
  1274. package/dist/client/clipboard.js.map +1 -1
  1275. package/dist/client/components/AgentPresenceChip.d.ts +1 -11
  1276. package/dist/client/components/AgentPresenceChip.d.ts.map +1 -1
  1277. package/dist/client/components/AgentPresenceChip.js +1 -41
  1278. package/dist/client/components/AgentPresenceChip.js.map +1 -1
  1279. package/dist/client/components/LiveCursorOverlay.d.ts +1 -44
  1280. package/dist/client/components/LiveCursorOverlay.d.ts.map +1 -1
  1281. package/dist/client/components/LiveCursorOverlay.js +1 -123
  1282. package/dist/client/components/LiveCursorOverlay.js.map +1 -1
  1283. package/dist/client/components/PresenceBar.d.ts +1 -26
  1284. package/dist/client/components/PresenceBar.d.ts.map +1 -1
  1285. package/dist/client/components/PresenceBar.js +1 -164
  1286. package/dist/client/components/PresenceBar.js.map +1 -1
  1287. package/dist/client/components/RecentEditHighlights.d.ts +1 -42
  1288. package/dist/client/components/RecentEditHighlights.d.ts.map +1 -1
  1289. package/dist/client/components/RecentEditHighlights.js +1 -145
  1290. package/dist/client/components/RecentEditHighlights.js.map +1 -1
  1291. package/dist/client/components/RemoteSelectionRings.d.ts +1 -51
  1292. package/dist/client/components/RemoteSelectionRings.d.ts.map +1 -1
  1293. package/dist/client/components/RemoteSelectionRings.js +1 -129
  1294. package/dist/client/components/RemoteSelectionRings.js.map +1 -1
  1295. package/dist/client/composer/TiptapComposer.d.ts +4 -0
  1296. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  1297. package/dist/client/composer/TiptapComposer.js +74 -26
  1298. package/dist/client/composer/TiptapComposer.js.map +1 -1
  1299. package/dist/client/db-admin/SqlEditor.d.ts.map +1 -1
  1300. package/dist/client/db-admin/SqlEditor.js +13 -3
  1301. package/dist/client/db-admin/SqlEditor.js.map +1 -1
  1302. package/dist/client/dynamic-suggestions.d.ts.map +1 -1
  1303. package/dist/client/dynamic-suggestions.js +7 -1
  1304. package/dist/client/dynamic-suggestions.js.map +1 -1
  1305. package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
  1306. package/dist/client/extensions/ExtensionViewer.js +7 -7
  1307. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  1308. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  1309. package/dist/client/extensions/ExtensionsListPage.js +5 -7
  1310. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  1311. package/dist/client/extensions/ExtensionsSidebarSection.d.ts.map +1 -1
  1312. package/dist/client/extensions/ExtensionsSidebarSection.js +1 -1
  1313. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  1314. package/dist/client/host-bridge.d.ts +2 -0
  1315. package/dist/client/host-bridge.d.ts.map +1 -1
  1316. package/dist/client/host-bridge.js +26 -10
  1317. package/dist/client/host-bridge.js.map +1 -1
  1318. package/dist/client/i18n.d.ts +1 -0
  1319. package/dist/client/i18n.d.ts.map +1 -1
  1320. package/dist/client/i18n.js +3 -1
  1321. package/dist/client/i18n.js.map +1 -1
  1322. package/dist/client/index.d.ts +2 -1
  1323. package/dist/client/index.d.ts.map +1 -1
  1324. package/dist/client/index.js +2 -1
  1325. package/dist/client/index.js.map +1 -1
  1326. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  1327. package/dist/client/resources/McpIntegrationDialog.js +12 -31
  1328. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  1329. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts +3 -1
  1330. package/dist/client/rich-markdown-editor/SharedRichEditor.d.ts.map +1 -1
  1331. package/dist/client/rich-markdown-editor/SharedRichEditor.js +2 -1
  1332. package/dist/client/rich-markdown-editor/SharedRichEditor.js.map +1 -1
  1333. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts +3 -1
  1334. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  1335. package/dist/client/rich-markdown-editor/useCollabReconcile.js +6 -2
  1336. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  1337. package/dist/client/settings/SettingsPanel.d.ts +3 -1
  1338. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  1339. package/dist/client/settings/SettingsPanel.js +103 -9
  1340. package/dist/client/settings/SettingsPanel.js.map +1 -1
  1341. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  1342. package/dist/client/settings/SettingsTabsPage.js +5 -5
  1343. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  1344. package/dist/client/settings/index.d.ts +1 -1
  1345. package/dist/client/settings/index.d.ts.map +1 -1
  1346. package/dist/client/settings/index.js +1 -1
  1347. package/dist/client/settings/index.js.map +1 -1
  1348. package/dist/client/sharing/VisibilityBadge.d.ts +1 -10
  1349. package/dist/client/sharing/VisibilityBadge.d.ts.map +1 -1
  1350. package/dist/client/sharing/VisibilityBadge.js +1 -18
  1351. package/dist/client/sharing/VisibilityBadge.js.map +1 -1
  1352. package/dist/client/use-run-stuck-detection.d.ts +8 -1
  1353. package/dist/client/use-run-stuck-detection.d.ts.map +1 -1
  1354. package/dist/client/use-run-stuck-detection.js +3 -2
  1355. package/dist/client/use-run-stuck-detection.js.map +1 -1
  1356. package/dist/coding-tools/run-code.d.ts +36 -0
  1357. package/dist/coding-tools/run-code.d.ts.map +1 -1
  1358. package/dist/coding-tools/run-code.js +333 -62
  1359. package/dist/coding-tools/run-code.js.map +1 -1
  1360. package/dist/coding-tools/sandbox/background.d.ts +141 -0
  1361. package/dist/coding-tools/sandbox/background.d.ts.map +1 -0
  1362. package/dist/coding-tools/sandbox/background.js +279 -0
  1363. package/dist/coding-tools/sandbox/background.js.map +1 -0
  1364. package/dist/coding-tools/sandbox/executions-store.d.ts +138 -0
  1365. package/dist/coding-tools/sandbox/executions-store.d.ts.map +1 -0
  1366. package/dist/coding-tools/sandbox/executions-store.js +380 -0
  1367. package/dist/coding-tools/sandbox/executions-store.js.map +1 -0
  1368. package/dist/coding-tools/sandbox/index.d.ts +22 -13
  1369. package/dist/coding-tools/sandbox/index.d.ts.map +1 -1
  1370. package/dist/coding-tools/sandbox/index.js +31 -13
  1371. package/dist/coding-tools/sandbox/index.js.map +1 -1
  1372. package/dist/coding-tools/sandbox/local-child-process-adapter.d.ts.map +1 -1
  1373. package/dist/coding-tools/sandbox/local-child-process-adapter.js +61 -4
  1374. package/dist/coding-tools/sandbox/local-child-process-adapter.js.map +1 -1
  1375. package/dist/collab/awareness.d.ts +2 -2
  1376. package/dist/collab/awareness.d.ts.map +1 -1
  1377. package/dist/collab/client.d.ts +16 -12
  1378. package/dist/collab/client.d.ts.map +1 -1
  1379. package/dist/collab/client.js +744 -496
  1380. package/dist/collab/client.js.map +1 -1
  1381. package/dist/collab/presence.d.ts +2 -20
  1382. package/dist/collab/presence.d.ts.map +1 -1
  1383. package/dist/collab/presence.js +3 -0
  1384. package/dist/collab/presence.js.map +1 -1
  1385. package/dist/collab/recent-edits.d.ts +2 -37
  1386. package/dist/collab/recent-edits.d.ts.map +1 -1
  1387. package/dist/collab/recent-edits.js +2 -9
  1388. package/dist/collab/recent-edits.js.map +1 -1
  1389. package/dist/collab/routes.d.ts +1 -1
  1390. package/dist/data-programs/actions.d.ts +19 -0
  1391. package/dist/data-programs/actions.d.ts.map +1 -0
  1392. package/dist/data-programs/actions.js +279 -0
  1393. package/dist/data-programs/actions.js.map +1 -0
  1394. package/dist/data-programs/contract.d.ts +70 -0
  1395. package/dist/data-programs/contract.d.ts.map +1 -0
  1396. package/dist/data-programs/contract.js +225 -0
  1397. package/dist/data-programs/contract.js.map +1 -0
  1398. package/dist/data-programs/execute.d.ts +96 -0
  1399. package/dist/data-programs/execute.d.ts.map +1 -0
  1400. package/dist/data-programs/execute.js +454 -0
  1401. package/dist/data-programs/execute.js.map +1 -0
  1402. package/dist/data-programs/index.d.ts +11 -0
  1403. package/dist/data-programs/index.d.ts.map +1 -0
  1404. package/dist/data-programs/index.js +11 -0
  1405. package/dist/data-programs/index.js.map +1 -0
  1406. package/dist/data-programs/schema.d.ts +517 -0
  1407. package/dist/data-programs/schema.d.ts.map +1 -0
  1408. package/dist/data-programs/schema.js +138 -0
  1409. package/dist/data-programs/schema.js.map +1 -0
  1410. package/dist/data-programs/store.d.ts +158 -0
  1411. package/dist/data-programs/store.d.ts.map +1 -0
  1412. package/dist/data-programs/store.js +493 -0
  1413. package/dist/data-programs/store.js.map +1 -0
  1414. package/dist/deploy/build.d.ts.map +1 -1
  1415. package/dist/deploy/build.js +34 -0
  1416. package/dist/deploy/build.js.map +1 -1
  1417. package/dist/extensions/actions.js +2 -2
  1418. package/dist/extensions/actions.js.map +1 -1
  1419. package/dist/file-upload/builder.d.ts.map +1 -1
  1420. package/dist/file-upload/builder.js +9 -0
  1421. package/dist/file-upload/builder.js.map +1 -1
  1422. package/dist/integrations/webhook-handler.d.ts.map +1 -1
  1423. package/dist/integrations/webhook-handler.js +21 -10
  1424. package/dist/integrations/webhook-handler.js.map +1 -1
  1425. package/dist/local-artifacts/index.js +1 -1
  1426. package/dist/local-artifacts/index.js.map +1 -1
  1427. package/dist/localization/default-messages.d.ts +0 -1
  1428. package/dist/localization/default-messages.d.ts.map +1 -1
  1429. package/dist/localization/default-messages.js +1 -2
  1430. package/dist/localization/default-messages.js.map +1 -1
  1431. package/dist/mcp-client/index.d.ts +9 -0
  1432. package/dist/mcp-client/index.d.ts.map +1 -1
  1433. package/dist/mcp-client/index.js +23 -0
  1434. package/dist/mcp-client/index.js.map +1 -1
  1435. package/dist/observability/routes.d.ts +5 -5
  1436. package/dist/onboarding/default-steps.d.ts.map +1 -1
  1437. package/dist/onboarding/default-steps.js +2 -2
  1438. package/dist/onboarding/default-steps.js.map +1 -1
  1439. package/dist/progress/routes.d.ts +1 -1
  1440. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  1441. package/dist/resources/store.d.ts.map +1 -1
  1442. package/dist/resources/store.js +35 -3
  1443. package/dist/resources/store.js.map +1 -1
  1444. package/dist/scripts/agent-engines/list-agent-engines.d.ts.map +1 -1
  1445. package/dist/scripts/agent-engines/list-agent-engines.js +4 -8
  1446. package/dist/scripts/agent-engines/list-agent-engines.js.map +1 -1
  1447. package/dist/scripts/agent-engines/test-agent-engine.d.ts.map +1 -1
  1448. package/dist/scripts/agent-engines/test-agent-engine.js +7 -2
  1449. package/dist/scripts/agent-engines/test-agent-engine.js.map +1 -1
  1450. package/dist/scripts/db/tool-mode.js +3 -3
  1451. package/dist/scripts/db/tool-mode.js.map +1 -1
  1452. package/dist/scripts/resources/migrate-learnings.d.ts +5 -1
  1453. package/dist/scripts/resources/migrate-learnings.d.ts.map +1 -1
  1454. package/dist/scripts/resources/migrate-learnings.js +8 -10
  1455. package/dist/scripts/resources/migrate-learnings.js.map +1 -1
  1456. package/dist/secrets/routes.d.ts +9 -9
  1457. package/dist/server/agent-chat-plugin.d.ts +20 -6
  1458. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  1459. package/dist/server/agent-chat-plugin.js +138 -59
  1460. package/dist/server/agent-chat-plugin.js.map +1 -1
  1461. package/dist/server/agent-discovery.d.ts.map +1 -1
  1462. package/dist/server/agent-discovery.js +62 -8
  1463. package/dist/server/agent-discovery.js.map +1 -1
  1464. package/dist/server/auth.d.ts.map +1 -1
  1465. package/dist/server/auth.js +75 -3
  1466. package/dist/server/auth.js.map +1 -1
  1467. package/dist/server/builder-design-systems.js +2 -2
  1468. package/dist/server/builder-design-systems.js.map +1 -1
  1469. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  1470. package/dist/server/core-routes-plugin.js +100 -20
  1471. package/dist/server/core-routes-plugin.js.map +1 -1
  1472. package/dist/server/credential-provider.d.ts +6 -3
  1473. package/dist/server/credential-provider.d.ts.map +1 -1
  1474. package/dist/server/credential-provider.js +25 -8
  1475. package/dist/server/credential-provider.js.map +1 -1
  1476. package/dist/server/csrf.d.ts.map +1 -1
  1477. package/dist/server/csrf.js +3 -0
  1478. package/dist/server/csrf.js.map +1 -1
  1479. package/dist/server/oauth-helpers.d.ts.map +1 -1
  1480. package/dist/server/oauth-helpers.js +9 -1
  1481. package/dist/server/oauth-helpers.js.map +1 -1
  1482. package/dist/server/onboarding-html.d.ts.map +1 -1
  1483. package/dist/server/onboarding-html.js +80 -48
  1484. package/dist/server/onboarding-html.js.map +1 -1
  1485. package/dist/server/prompts/shared-rules.js +2 -2
  1486. package/dist/server/prompts/shared-rules.js.map +1 -1
  1487. package/dist/server/transcribe-voice.d.ts +1 -1
  1488. package/dist/server/transcribe-voice.d.ts.map +1 -1
  1489. package/dist/server/transcribe-voice.js +77 -15
  1490. package/dist/server/transcribe-voice.js.map +1 -1
  1491. package/dist/styles/agent-native.css +57 -45
  1492. package/dist/templates/default/.agents/skills/actions/SKILL.md +55 -0
  1493. package/dist/templates/default/AGENTS.md +11 -14
  1494. package/dist/templates/default/actions/navigate.ts +1 -1
  1495. package/dist/templates/default/app/i18n/ar-SA.ts +0 -1
  1496. package/dist/templates/default/app/i18n/de-DE.ts +0 -1
  1497. package/dist/templates/default/app/i18n/en-US.ts +0 -1
  1498. package/dist/templates/default/app/i18n/es-ES.ts +0 -1
  1499. package/dist/templates/default/app/i18n/fr-FR.ts +0 -1
  1500. package/dist/templates/default/app/i18n/hi-IN.ts +0 -1
  1501. package/dist/templates/default/app/i18n/ja-JP.ts +0 -1
  1502. package/dist/templates/default/app/i18n/ko-KR.ts +0 -1
  1503. package/dist/templates/default/app/i18n/pt-BR.ts +0 -1
  1504. package/dist/templates/default/app/i18n/zh-CN.ts +0 -1
  1505. package/dist/templates/default/app/i18n/zh-TW.ts +0 -1
  1506. package/dist/templates/default/app/routes/settings.tsx +22 -33
  1507. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +20 -7
  1508. package/dist/templates/headless/AGENTS.md +9 -6
  1509. package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +55 -0
  1510. package/dist/templates/workspace-core/.agents/skills/data-programs/SKILL.md +128 -0
  1511. package/dist/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +10 -4
  1512. package/dist/vite/agents-bundle-plugin.d.ts.map +1 -1
  1513. package/dist/vite/agents-bundle-plugin.js +26 -1
  1514. package/dist/vite/agents-bundle-plugin.js.map +1 -1
  1515. package/dist/vite/client.d.ts.map +1 -1
  1516. package/dist/vite/client.js +11 -0
  1517. package/dist/vite/client.js.map +1 -1
  1518. package/docs/content/agent-native-toolkit.mdx +87 -146
  1519. package/docs/content/cloneable-saas.mdx +19 -19
  1520. package/docs/content/components.mdx +16 -4
  1521. package/docs/content/data-programs.mdx +329 -0
  1522. package/docs/content/deployment.mdx +9 -9
  1523. package/docs/content/extensions.mdx +8 -7
  1524. package/docs/content/generative-ui.mdx +5 -3
  1525. package/docs/content/key-concepts.mdx +6 -9
  1526. package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +100 -70
  1527. package/docs/content/locales/ar-SA/cloneable-saas.mdx +19 -19
  1528. package/docs/content/locales/ar-SA/components.mdx +12 -2
  1529. package/docs/content/locales/ar-SA/data-programs.mdx +329 -0
  1530. package/docs/content/locales/ar-SA/deployment.mdx +8 -9
  1531. package/docs/content/locales/ar-SA/extensions.mdx +6 -4
  1532. package/docs/content/locales/ar-SA/key-concepts.mdx +7 -9
  1533. package/docs/content/locales/ar-SA/real-time-collaboration.mdx +9 -10
  1534. package/docs/content/locales/de-DE/agent-native-toolkit.mdx +100 -74
  1535. package/docs/content/locales/de-DE/cloneable-saas.mdx +19 -19
  1536. package/docs/content/locales/de-DE/components.mdx +12 -2
  1537. package/docs/content/locales/de-DE/data-programs.mdx +329 -0
  1538. package/docs/content/locales/de-DE/deployment.mdx +8 -9
  1539. package/docs/content/locales/de-DE/extensions.mdx +6 -4
  1540. package/docs/content/locales/de-DE/key-concepts.mdx +8 -10
  1541. package/docs/content/locales/de-DE/real-time-collaboration.mdx +9 -10
  1542. package/docs/content/locales/es-ES/agent-native-toolkit.mdx +100 -74
  1543. package/docs/content/locales/es-ES/cloneable-saas.mdx +19 -19
  1544. package/docs/content/locales/es-ES/components.mdx +12 -2
  1545. package/docs/content/locales/es-ES/data-programs.mdx +329 -0
  1546. package/docs/content/locales/es-ES/deployment.mdx +8 -9
  1547. package/docs/content/locales/es-ES/extensions.mdx +6 -4
  1548. package/docs/content/locales/es-ES/key-concepts.mdx +8 -10
  1549. package/docs/content/locales/es-ES/real-time-collaboration.mdx +9 -10
  1550. package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +100 -76
  1551. package/docs/content/locales/fr-FR/cloneable-saas.mdx +19 -19
  1552. package/docs/content/locales/fr-FR/components.mdx +12 -2
  1553. package/docs/content/locales/fr-FR/data-programs.mdx +329 -0
  1554. package/docs/content/locales/fr-FR/deployment.mdx +8 -9
  1555. package/docs/content/locales/fr-FR/extensions.mdx +6 -4
  1556. package/docs/content/locales/fr-FR/key-concepts.mdx +8 -10
  1557. package/docs/content/locales/fr-FR/real-time-collaboration.mdx +9 -10
  1558. package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +100 -71
  1559. package/docs/content/locales/hi-IN/cloneable-saas.mdx +19 -19
  1560. package/docs/content/locales/hi-IN/components.mdx +20 -10
  1561. package/docs/content/locales/hi-IN/data-programs.mdx +329 -0
  1562. package/docs/content/locales/hi-IN/deployment.mdx +8 -9
  1563. package/docs/content/locales/hi-IN/extensions.mdx +6 -4
  1564. package/docs/content/locales/hi-IN/key-concepts.mdx +8 -10
  1565. package/docs/content/locales/hi-IN/real-time-collaboration.mdx +9 -10
  1566. package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +100 -64
  1567. package/docs/content/locales/ja-JP/cloneable-saas.mdx +19 -19
  1568. package/docs/content/locales/ja-JP/components.mdx +12 -2
  1569. package/docs/content/locales/ja-JP/data-programs.mdx +329 -0
  1570. package/docs/content/locales/ja-JP/deployment.mdx +8 -9
  1571. package/docs/content/locales/ja-JP/extensions.mdx +6 -4
  1572. package/docs/content/locales/ja-JP/key-concepts.mdx +9 -11
  1573. package/docs/content/locales/ja-JP/real-time-collaboration.mdx +9 -10
  1574. package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +100 -60
  1575. package/docs/content/locales/ko-KR/cloneable-saas.mdx +19 -19
  1576. package/docs/content/locales/ko-KR/components.mdx +20 -10
  1577. package/docs/content/locales/ko-KR/data-programs.mdx +329 -0
  1578. package/docs/content/locales/ko-KR/deployment.mdx +8 -9
  1579. package/docs/content/locales/ko-KR/extensions.mdx +16 -14
  1580. package/docs/content/locales/ko-KR/key-concepts.mdx +9 -11
  1581. package/docs/content/locales/ko-KR/real-time-collaboration.mdx +9 -10
  1582. package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +100 -74
  1583. package/docs/content/locales/pt-BR/cloneable-saas.mdx +19 -19
  1584. package/docs/content/locales/pt-BR/components.mdx +12 -2
  1585. package/docs/content/locales/pt-BR/data-programs.mdx +329 -0
  1586. package/docs/content/locales/pt-BR/deployment.mdx +8 -9
  1587. package/docs/content/locales/pt-BR/extensions.mdx +6 -4
  1588. package/docs/content/locales/pt-BR/key-concepts.mdx +8 -11
  1589. package/docs/content/locales/pt-BR/real-time-collaboration.mdx +9 -10
  1590. package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +100 -58
  1591. package/docs/content/locales/zh-CN/cloneable-saas.mdx +19 -19
  1592. package/docs/content/locales/zh-CN/components.mdx +20 -10
  1593. package/docs/content/locales/zh-CN/data-programs.mdx +329 -0
  1594. package/docs/content/locales/zh-CN/deployment.mdx +7 -9
  1595. package/docs/content/locales/zh-CN/extensions.mdx +16 -14
  1596. package/docs/content/locales/zh-CN/key-concepts.mdx +7 -11
  1597. package/docs/content/locales/zh-CN/real-time-collaboration.mdx +10 -11
  1598. package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +100 -58
  1599. package/docs/content/locales/zh-TW/cloneable-saas.mdx +19 -19
  1600. package/docs/content/locales/zh-TW/components.mdx +20 -10
  1601. package/docs/content/locales/zh-TW/data-programs.mdx +329 -0
  1602. package/docs/content/locales/zh-TW/deployment.mdx +7 -9
  1603. package/docs/content/locales/zh-TW/extensions.mdx +16 -14
  1604. package/docs/content/locales/zh-TW/key-concepts.mdx +7 -11
  1605. package/docs/content/locales/zh-TW/real-time-collaboration.mdx +10 -11
  1606. package/docs/content/real-time-collaboration.mdx +10 -11
  1607. package/docs/content/toolkit-app-adapters.mdx +46 -0
  1608. package/docs/content/toolkit-collaboration-ui.mdx +48 -0
  1609. package/docs/content/toolkit-sharing-ui.mdx +33 -0
  1610. package/docs/content/toolkit-shell-hooks.mdx +27 -0
  1611. package/docs/content/toolkit-ui.mdx +69 -0
  1612. package/package.json +6 -2
  1613. package/src/templates/default/.agents/skills/actions/SKILL.md +55 -0
  1614. package/src/templates/default/AGENTS.md +11 -14
  1615. package/src/templates/default/actions/navigate.spec.ts +53 -0
  1616. package/src/templates/default/actions/navigate.ts +1 -1
  1617. package/src/templates/default/app/i18n/ar-SA.ts +0 -1
  1618. package/src/templates/default/app/i18n/de-DE.ts +0 -1
  1619. package/src/templates/default/app/i18n/en-US.ts +0 -1
  1620. package/src/templates/default/app/i18n/es-ES.ts +0 -1
  1621. package/src/templates/default/app/i18n/fr-FR.ts +0 -1
  1622. package/src/templates/default/app/i18n/hi-IN.ts +0 -1
  1623. package/src/templates/default/app/i18n/ja-JP.ts +0 -1
  1624. package/src/templates/default/app/i18n/ko-KR.ts +0 -1
  1625. package/src/templates/default/app/i18n/pt-BR.ts +0 -1
  1626. package/src/templates/default/app/i18n/zh-CN.ts +0 -1
  1627. package/src/templates/default/app/i18n/zh-TW.ts +0 -1
  1628. package/src/templates/default/app/routes/settings.tsx +22 -33
  1629. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +20 -7
  1630. package/src/templates/headless/AGENTS.md +9 -6
  1631. package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +55 -0
  1632. package/src/templates/workspace-core/.agents/skills/data-programs/SKILL.md +128 -0
  1633. package/src/templates/workspace-core/.agents/skills/harness-agents/SKILL.md +10 -4
  1634. package/corpus/templates/design/server/lib/figma-import/clipboard.ts +0 -122
  1635. package/corpus/templates/design/server/lib/figma-import/decode.ts +0 -455
  1636. package/corpus/templates/design/server/lib/figma-import/processor.ts +0 -116
  1637. package/corpus/templates/design/server/lib/figma-import/render-html.ts +0 -397
  1638. package/corpus/templates/design/server/lib/figma-import/types.ts +0 -60
@@ -0,0 +1,3224 @@
1
+ import {
2
+ useBuilderConnectFlow,
3
+ useBuilderStatus,
4
+ useCodeMode,
5
+ } from "@agent-native/core/client";
6
+ import {
7
+ BUILDER_CMS_SAFE_WRITE_MODEL,
8
+ type BuilderCmsModelSummary,
9
+ type ContentDatabaseItem,
10
+ type ContentDatabaseOpenPagesIn,
11
+ type ContentDatabaseResponse,
12
+ type ContentDatabaseSource,
13
+ type ContentDatabaseSourceChangeSet,
14
+ type ContentDatabaseSourceJoinRequest,
15
+ type ContentDatabaseSourceReviewPayload,
16
+ type ContentDatabaseView,
17
+ type ContentDatabaseViewType,
18
+ type DocumentProperty,
19
+ type DocumentPropertyType,
20
+ type DocumentPropertyValue,
21
+ type SourceJoinSuggestion,
22
+ } from "@shared/api";
23
+ import { evaluateNormalizationFormula } from "@shared/properties";
24
+ import {
25
+ IconArrowLeft,
26
+ IconCheck,
27
+ IconChevronDown,
28
+ IconChevronRight,
29
+ IconExternalLink,
30
+ IconEye,
31
+ IconEyeOff,
32
+ IconFileText,
33
+ IconLayoutGrid,
34
+ IconLayoutKanban,
35
+ IconList,
36
+ IconLock,
37
+ IconPencil,
38
+ IconPlugConnected,
39
+ IconPlus,
40
+ IconRefresh,
41
+ IconSearch,
42
+ IconTimeline,
43
+ IconX,
44
+ } from "@tabler/icons-react";
45
+ import {
46
+ useEffect,
47
+ useMemo,
48
+ useRef,
49
+ useState,
50
+ type MouseEvent as ReactMouseEvent,
51
+ type ReactNode,
52
+ } from "react";
53
+ import { toast } from "sonner";
54
+
55
+ import { Button } from "@/components/ui/button";
56
+ import {
57
+ DropdownMenu,
58
+ DropdownMenuContent,
59
+ DropdownMenuItem,
60
+ DropdownMenuLabel,
61
+ DropdownMenuSeparator,
62
+ DropdownMenuSubContent,
63
+ DropdownMenuTrigger,
64
+ } from "@/components/ui/dropdown-menu";
65
+ import { Input } from "@/components/ui/input";
66
+ import { Spinner } from "@/components/ui/spinner";
67
+ import {
68
+ useAddContentDatabaseSourceFieldProperty,
69
+ useBuilderCmsModels,
70
+ useContentDatabases,
71
+ useSuggestSourceJoinKey,
72
+ } from "@/hooks/use-content-database";
73
+ import { cn } from "@/lib/utils";
74
+
75
+ import { resolveBuilderCmsWriteEffect } from "../../../../actions/_builder-cms-write-adapter.js";
76
+ import { AddProperty, TYPE_ICONS } from "../DocumentProperties";
77
+ import {
78
+ databaseViewGroupableProperties,
79
+ databaseViewGroupingProperty,
80
+ } from "./grouping";
81
+ import {
82
+ databasePropertyPickerItems,
83
+ databaseToolbarIconButtonClass,
84
+ databaseViewIcon,
85
+ } from "./shared";
86
+ import { dbText } from "./text";
87
+ import {
88
+ DATABASE_OPEN_PAGES_IN,
89
+ DATABASE_VIEW_TYPES,
90
+ type ColumnKey,
91
+ type DatabaseFilterMode,
92
+ } from "./types";
93
+ import { databaseViewDefaultName } from "./view-config";
94
+ import { isDatabasePropertyVisibleInView } from "./view-state";
95
+
96
+ export type DatabaseSettingsPanel =
97
+ | "main"
98
+ | "source"
99
+ | "layout"
100
+ | "property_visibility"
101
+ | "group";
102
+
103
+ // One step in the Sources drill-down: Sources (root, empty stack) → provider
104
+ // (Builder) → space → model leaf. The model step carries the full summary so
105
+ // the leaf can attach without re-fetching.
106
+ // A second source being added, awaiting the canonical-key confirm step.
107
+ type PendingSourceCandidate = {
108
+ sourceType: "mock-local" | "builder-cms" | "local-table";
109
+ sourceName: string;
110
+ sourceTable: string;
111
+ displayName: string;
112
+ existingSourceId?: string;
113
+ };
114
+
115
+ type SourceNavStep =
116
+ | { kind: "provider"; providerId: "builder" }
117
+ | { kind: "space"; spaceId: string; spaceName: string }
118
+ | { kind: "model"; model: BuilderCmsModelSummary }
119
+ | { kind: "addSource" }
120
+ | { kind: "secondarySource"; sourceId: string; sourceName: string }
121
+ | { kind: "keyConfirm"; candidate: PendingSourceCandidate }
122
+ | { kind: "fieldPicker"; sourceId: string; sourceName: string };
123
+
124
+ function sourceNavTitle(stack: SourceNavStep[]): string {
125
+ const top = stack[stack.length - 1];
126
+ if (!top) return dbText("sources");
127
+ if (top.kind === "provider") return "Builder";
128
+ if (top.kind === "space") return top.spaceName;
129
+ if (top.kind === "addSource") return dbText("addASource");
130
+ if (top.kind === "secondarySource") return top.sourceName;
131
+ if (top.kind === "keyConfirm") return dbText("matchExistingItemsToDetails");
132
+ if (top.kind === "fieldPicker") return dbText("chooseFields");
133
+ return top.model.displayName;
134
+ }
135
+
136
+ // The Builder "B" brand mark (first glyph of the wordmark), drawn with
137
+ // currentColor so it themes against the panel background.
138
+ function BuilderLogoMark({ className }: { className?: string }) {
139
+ return (
140
+ <svg
141
+ viewBox="0 0 71 80"
142
+ fill="currentColor"
143
+ xmlns="http://www.w3.org/2000/svg"
144
+ className={className}
145
+ aria-hidden="true"
146
+ >
147
+ <path d="M70.86 24C70.86 10.69 60.06 0 46.86 0H6.31995C2.81995 0 0 2.84031 0 6.32031C0 12.8003 13.71 17.71 13.71 40C13.71 62.29 0 67.2102 0 73.6802C0 77.1602 2.81995 80 6.31995 80H46.86C60.06 80 70.86 69.31 70.86 56C70.86 46.22 64.98 40.25 64.75 40C64.98 39.75 70.86 33.78 70.86 24ZM8.37 6.86035H46.87C51.45 6.86035 55.75 8.64037 58.99 11.8804C62.23 15.1204 64.01 19.42 64.01 24C64.01 28.58 62.3199 32.62 59.3199 35.79L8.37 6.86035ZM58.99 68.1304C55.75 71.3704 51.45 73.1504 46.87 73.1504H8.37L59.3199 44.2202C62.3199 47.3902 64.01 51.5703 64.01 56.0103C64.01 60.4503 62.23 64.8904 58.99 68.1304ZM15.83 61.02C16.24 60.17 20.58 51.74 20.58 40C20.58 28.26 16.24 19.83 15.83 18.98L52.85 40L15.83 61.02Z" />
148
+ </svg>
149
+ );
150
+ }
151
+
152
+ // The Notion logo, reusing the shared `.notion-logo-icon` styling (same mark as
153
+ // the sidebar's Notion button) so it themes consistently.
154
+ function NotionLogoMark({ className }: { className?: string }) {
155
+ return (
156
+ <svg
157
+ viewBox="0 0 100 100"
158
+ xmlns="http://www.w3.org/2000/svg"
159
+ className={cn("notion-logo-icon", className)}
160
+ aria-hidden="true"
161
+ >
162
+ <path
163
+ className="notion-logo-icon-face"
164
+ d="M6.017 4.313l55.333 -4.087c6.797 -0.583 8.543 -0.19 12.817 2.917l17.663 12.443c2.913 2.14 3.883 2.723 3.883 5.053v68.243c0 4.277 -1.553 6.807 -6.99 7.193L24.467 99.967c-4.08 0.193 -6.023 -0.39 -8.16 -3.113L3.3 79.94c-2.333 -3.113 -3.3 -5.443 -3.3 -8.167V11.113c0 -3.497 1.553 -6.413 6.017 -6.8z"
165
+ />
166
+ <path
167
+ className="notion-logo-icon-mark"
168
+ fillRule="evenodd"
169
+ clipRule="evenodd"
170
+ d="M61.35 0.227l-55.333 4.087C1.553 4.7 0 7.617 0 11.113v60.66c0 2.723 0.967 5.053 3.3 8.167l13.007 16.913c2.137 2.723 4.08 3.307 8.16 3.113l64.257 -3.89c5.433 -0.387 6.99 -2.917 6.99 -7.193V20.64c0 -2.21 -0.873 -2.847 -3.443 -4.733L74.167 3.143c-4.273 -3.107 -6.02 -3.5 -12.817 -2.917zM25.92 19.523c-5.247 0.353 -6.437 0.433 -9.417 -1.99L8.927 11.507c-0.77 -0.78 -0.383 -1.753 1.557 -1.947l53.193 -3.887c4.467 -0.39 6.793 1.167 8.54 2.527l9.123 6.61c0.39 0.197 1.36 1.36 0.193 1.36l-54.933 3.307 -0.68 0.047zM19.803 88.3V30.367c0 -2.53 0.777 -3.697 3.103 -3.893L86 22.78c2.14 -0.193 3.107 1.167 3.107 3.693v57.547c0 2.53 -0.39 4.67 -3.883 4.863l-60.377 3.5c-3.493 0.193 -5.043 -0.97 -5.043 -4.083zm59.6 -54.827c0.387 1.75 0 3.5 -1.75 3.7l-2.91 0.577v42.773c-2.527 1.36 -4.853 2.137 -6.797 2.137 -3.107 0 -3.883 -0.973 -6.21 -3.887l-19.03 -29.94v28.967l6.02 1.363s0 3.5 -4.857 3.5l-13.39 0.777c-0.39 -0.78 0 -2.723 1.357 -3.11l3.497 -0.97v-38.3L30.48 40.667c-0.39 -1.75 0.58 -4.277 3.3 -4.473l14.367 -0.967 19.8 30.327v-26.83l-5.047 -0.58c-0.39 -2.143 1.163 -3.7 3.103 -3.89l13.4 -0.78z"
171
+ />
172
+ </svg>
173
+ );
174
+ }
175
+
176
+ export function DatabaseSettingsPanelSheet({
177
+ open,
178
+ panel,
179
+ documentId,
180
+ canEdit,
181
+ activeView,
182
+ properties,
183
+ items,
184
+ source,
185
+ sources,
186
+ hiddenCount,
187
+ groupIds,
188
+ onClose,
189
+ onPanelChange,
190
+ onAttachBuilderSource,
191
+ onFederateSource,
192
+ onChangeSourceRole,
193
+ onDisconnectSecondary,
194
+ onRefreshSource,
195
+ onHydrateBuilderBodies,
196
+ onDisconnectSource,
197
+ onReviewBuilderUpdate,
198
+ onSetBuilderLiveWrites,
199
+ sourceActionPending,
200
+ onViewTypeChange,
201
+ onWrapCellsChange,
202
+ onOpenPagesInChange,
203
+ onPropertyHiddenChange,
204
+ onPropertiesHiddenChange,
205
+ onGroupByChange,
206
+ onHideEmptyGroupsChange,
207
+ onGroupsCollapsedChange,
208
+ }: {
209
+ open: boolean;
210
+ panel: DatabaseSettingsPanel;
211
+ documentId: string;
212
+ canEdit: boolean;
213
+ activeView: ContentDatabaseView;
214
+ properties: DocumentProperty[];
215
+ items: ContentDatabaseItem[];
216
+ source: ContentDatabaseSource | null;
217
+ sources: ContentDatabaseSource[];
218
+ hiddenCount: number;
219
+ groupIds: string[];
220
+ onClose: () => void;
221
+ onPanelChange: (panel: DatabaseSettingsPanel) => void;
222
+ onAttachBuilderSource: (
223
+ model: BuilderCmsModelSummary,
224
+ relationshipMode?: "items" | "details",
225
+ ) => Promise<ContentDatabaseResponse>;
226
+ onFederateSource: (
227
+ candidate: PendingSourceCandidate,
228
+ join: ContentDatabaseSourceJoinRequest,
229
+ ) => Promise<ContentDatabaseResponse>;
230
+ onChangeSourceRole: (
231
+ sourceId: string,
232
+ relationshipMode: "items" | "details",
233
+ join?: ContentDatabaseSourceJoinRequest,
234
+ ) => Promise<ContentDatabaseResponse>;
235
+ onDisconnectSecondary: (sourceId: string) => void;
236
+ onRefreshSource: (sourceId?: string) => void;
237
+ onHydrateBuilderBodies: (sourceId: string) => void;
238
+ onDisconnectSource: (sourceId?: string) => void;
239
+ onReviewBuilderUpdate: () => void;
240
+ onSetBuilderLiveWrites: (enabled: boolean) => void;
241
+ sourceActionPending: boolean;
242
+ onViewTypeChange: (type: ContentDatabaseViewType) => void;
243
+ onWrapCellsChange: (wrapCells: boolean) => void;
244
+ onOpenPagesInChange: (openPagesIn: ContentDatabaseOpenPagesIn) => void;
245
+ onPropertyHiddenChange: (propertyId: string, hidden: boolean) => void;
246
+ onPropertiesHiddenChange: (propertyIds: string[], hidden: boolean) => void;
247
+ onGroupByChange: (propertyId: string | null) => void;
248
+ onHideEmptyGroupsChange: (hideEmptyGroups: boolean) => void;
249
+ onGroupsCollapsedChange: (groupIds: string[], collapsed: boolean) => void;
250
+ }) {
251
+ // Local drill-down path *within* the Source(s) panel. Kept here (not in the
252
+ // flat panel enum) because the levels are dynamic — space/model names aren't
253
+ // known at compile time. The sheet's back button pops this stack first.
254
+ const [sourceNavStack, setSourceNavStack] = useState<SourceNavStep[]>([]);
255
+ useEffect(() => {
256
+ // Always re-enter the Sources panel at its root, and don't retain a path
257
+ // across close/reopen.
258
+ if (!open || panel !== "source") setSourceNavStack([]);
259
+ }, [open, panel]);
260
+
261
+ if (!open) return null;
262
+
263
+ const title =
264
+ panel === "main"
265
+ ? "Database settings"
266
+ : panel === "source"
267
+ ? sourceNavTitle(sourceNavStack)
268
+ : databaseSettingsPanelTitle(panel);
269
+
270
+ const handleBack = () => {
271
+ if (panel === "source" && sourceNavStack.length > 0) {
272
+ setSourceNavStack((stack) => stack.slice(0, -1));
273
+ return;
274
+ }
275
+ onPanelChange("main");
276
+ };
277
+
278
+ return (
279
+ <aside
280
+ className="fixed bottom-0 right-0 top-12 z-40 flex w-[320px] max-w-[calc(100vw-1rem)] flex-col border-l border-border bg-background shadow-[-12px_0_32px_rgba(15,23,42,0.06)]"
281
+ onClick={(event) => event.stopPropagation()}
282
+ onPointerDown={(event) => event.stopPropagation()}
283
+ >
284
+ <div className="flex h-11 shrink-0 items-center gap-2 border-b border-border/70 px-3">
285
+ {panel === "main" ? null : (
286
+ <button
287
+ type="button"
288
+ aria-label="Back"
289
+ className="flex size-7 items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
290
+ onClick={handleBack}
291
+ >
292
+ <IconArrowLeft className="size-4" />
293
+ </button>
294
+ )}
295
+ <div className="min-w-0 flex-1 truncate text-sm font-semibold">
296
+ {title}
297
+ </div>
298
+ <button
299
+ type="button"
300
+ aria-label={dbText("closeDatabaseSettings")}
301
+ className="flex size-7 items-center justify-center rounded text-muted-foreground hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
302
+ onClick={onClose}
303
+ >
304
+ <IconX className="size-4" />
305
+ </button>
306
+ </div>
307
+ <div className="min-h-0 flex-1 overflow-x-hidden overflow-y-auto p-3">
308
+ {panel === "main" ? (
309
+ <DatabaseSettingsMainPanel
310
+ activeView={activeView}
311
+ source={source}
312
+ sourceCount={sources.length || (source ? 1 : 0)}
313
+ propertyCount={properties.length}
314
+ hiddenCount={hiddenCount}
315
+ onPanelChange={onPanelChange}
316
+ />
317
+ ) : panel === "source" ? (
318
+ <DatabaseSettingsSourcePanel
319
+ source={source}
320
+ sources={sources}
321
+ documentId={documentId}
322
+ itemCount={items.length}
323
+ canEdit={canEdit}
324
+ nav={sourceNavStack}
325
+ onNavPush={(step) => setSourceNavStack((stack) => [...stack, step])}
326
+ onNavReplace={setSourceNavStack}
327
+ onAttachBuilderSource={onAttachBuilderSource}
328
+ onFederateSource={onFederateSource}
329
+ onChangeSourceRole={onChangeSourceRole}
330
+ onDisconnectSecondary={(sourceId) => {
331
+ onDisconnectSecondary(sourceId);
332
+ setSourceNavStack([]);
333
+ }}
334
+ onRefreshSource={onRefreshSource}
335
+ onHydrateBuilderBodies={onHydrateBuilderBodies}
336
+ onDisconnectSource={onDisconnectSource}
337
+ onReviewBuilderUpdate={onReviewBuilderUpdate}
338
+ onSetBuilderLiveWrites={onSetBuilderLiveWrites}
339
+ sourceActionPending={sourceActionPending}
340
+ />
341
+ ) : panel === "layout" ? (
342
+ <DatabaseSettingsLayoutPanel
343
+ activeView={activeView}
344
+ onViewTypeChange={onViewTypeChange}
345
+ onWrapCellsChange={onWrapCellsChange}
346
+ onOpenPagesInChange={onOpenPagesInChange}
347
+ />
348
+ ) : panel === "property_visibility" ? (
349
+ <DatabaseSettingsPropertyVisibilityPanel
350
+ documentId={documentId}
351
+ properties={properties}
352
+ activeView={activeView}
353
+ items={items}
354
+ source={source}
355
+ sources={sources}
356
+ hiddenCount={hiddenCount}
357
+ onPropertyHiddenChange={onPropertyHiddenChange}
358
+ onPropertiesHiddenChange={onPropertiesHiddenChange}
359
+ />
360
+ ) : panel === "group" ? (
361
+ <DatabaseSettingsGroupPanel
362
+ activeView={activeView}
363
+ properties={properties}
364
+ groupIds={groupIds}
365
+ onGroupByChange={onGroupByChange}
366
+ onHideEmptyGroupsChange={onHideEmptyGroupsChange}
367
+ onGroupsCollapsedChange={onGroupsCollapsedChange}
368
+ />
369
+ ) : null}
370
+ </div>
371
+ </aside>
372
+ );
373
+ }
374
+
375
+ function databaseSettingsPanelTitle(panel: DatabaseSettingsPanel) {
376
+ if (panel === "source") return "Source";
377
+ if (panel === "layout") return "Layout";
378
+ if (panel === "property_visibility") return "Property visibility";
379
+ if (panel === "group") return "Group";
380
+ return "Database settings";
381
+ }
382
+
383
+ function DatabaseSettingsMainPanel({
384
+ activeView,
385
+ source,
386
+ sourceCount,
387
+ propertyCount,
388
+ hiddenCount,
389
+ onPanelChange,
390
+ }: {
391
+ activeView: ContentDatabaseView;
392
+ source: ContentDatabaseSource | null;
393
+ sourceCount: number;
394
+ propertyCount: number;
395
+ hiddenCount: number;
396
+ onPanelChange: (panel: DatabaseSettingsPanel) => void;
397
+ }) {
398
+ const groupLabel = activeView.groupByPropertyId ? "On" : "";
399
+ const sourceBadgeCount = builderReviewableChangeSets(source).length;
400
+ return (
401
+ <div className="grid gap-3">
402
+ <div className="flex h-9 items-center gap-2 rounded-md border border-border bg-background px-2">
403
+ {databaseViewIconElement(
404
+ activeView.type,
405
+ "size-4 text-muted-foreground",
406
+ )}
407
+ <Input
408
+ value={activeView.name}
409
+ readOnly
410
+ aria-label={dbText("viewName")}
411
+ className="h-7 border-0 bg-transparent px-0 text-sm shadow-none focus-visible:ring-0"
412
+ />
413
+ </div>
414
+ <div className="grid gap-1">
415
+ <DatabaseSettingsRow
416
+ icon={<IconPlugConnected className="size-4" />}
417
+ label="Sources"
418
+ value={sourceCount > 0 ? `${sourceCount} connected` : "None"}
419
+ badgeCount={sourceBadgeCount}
420
+ onClick={() => onPanelChange("source")}
421
+ />
422
+ <DatabaseSettingsRow
423
+ icon={databaseViewIconElement(activeView.type)}
424
+ label="Layout"
425
+ value={databaseViewDefaultName(activeView.type)}
426
+ onClick={() => onPanelChange("layout")}
427
+ />
428
+ <DatabaseSettingsRow
429
+ icon={<IconEye className="size-4" />}
430
+ label={dbText("propertyVisibility")}
431
+ value={propertyCount > 0 ? String(propertyCount - hiddenCount) : ""}
432
+ onClick={() => onPanelChange("property_visibility")}
433
+ />
434
+ <DatabaseSettingsRow
435
+ icon={<IconLayoutKanban className="size-4" />}
436
+ label="Group"
437
+ value={groupLabel}
438
+ onClick={() => onPanelChange("group")}
439
+ />
440
+ </div>
441
+ </div>
442
+ );
443
+ }
444
+
445
+ export function builderReviewableChangeSets(
446
+ source: ContentDatabaseSource | null,
447
+ ) {
448
+ if (source?.sourceType !== "builder-cms") return [];
449
+ return source.changeSets.filter(
450
+ (changeSet) =>
451
+ changeSet.direction === "outbound" &&
452
+ (changeSet.state === "pending_push" ||
453
+ changeSet.state === "staged_revision" ||
454
+ changeSet.state === "approved"),
455
+ );
456
+ }
457
+
458
+ function sourceReviewRiskRank(
459
+ risk: ContentDatabaseSourceReviewPayload["riskLevel"],
460
+ ) {
461
+ if (risk === "high") return 3;
462
+ if (risk === "medium") return 2;
463
+ return 1;
464
+ }
465
+
466
+ function maxSourceReviewRisk(
467
+ current: ContentDatabaseSourceReviewPayload["riskLevel"],
468
+ next: ContentDatabaseSourceReviewPayload["riskLevel"],
469
+ ) {
470
+ return sourceReviewRiskRank(next) > sourceReviewRiskRank(current)
471
+ ? next
472
+ : current;
473
+ }
474
+
475
+ export function builderReviewExecutableRows(
476
+ review: ContentDatabaseSourceReviewPayload,
477
+ ) {
478
+ if (!review.liveWritesEnabled || review.result.status !== "validated") {
479
+ return [];
480
+ }
481
+ return review.rows.filter(
482
+ (row) => row.execution?.state === "ready" && row.execution.idempotencyKey,
483
+ );
484
+ }
485
+
486
+ export function builderSourceLiveWriteControlState(
487
+ source: ContentDatabaseSource | null,
488
+ ) {
489
+ const isBuilderSource = source?.sourceType === "builder-cms";
490
+ const safeTarget =
491
+ isBuilderSource && source?.sourceTable === BUILDER_CMS_SAFE_WRITE_MODEL;
492
+ const enabled = source?.capabilities.liveWritesEnabled === true;
493
+ return {
494
+ safeTarget,
495
+ enabled,
496
+ showAction: safeTarget,
497
+ actionLabel: enabled ? "Disable" : "Enable",
498
+ description: enabled
499
+ ? "Enabled for autosave writes to the Agent Native test collection."
500
+ : safeTarget
501
+ ? "Off by default. Enable only when you are ready to send autosave writes to the Agent Native test collection."
502
+ : isBuilderSource
503
+ ? "Unavailable here; live writes are locked to the Agent Native test collection."
504
+ : "Live writes are not available for this source.",
505
+ };
506
+ }
507
+
508
+ export function buildClientBuilderReviewPayload(
509
+ source: ContentDatabaseSource,
510
+ changeSets: ContentDatabaseSourceChangeSet[],
511
+ ): ContentDatabaseSourceReviewPayload {
512
+ let riskLevel: ContentDatabaseSourceReviewPayload["riskLevel"] = "low";
513
+ const riskReasons = new Set<string>();
514
+ const rows = changeSets.map((changeSet) => {
515
+ riskLevel = maxSourceReviewRisk(riskLevel, changeSet.riskLevel);
516
+ changeSet.riskReasons.forEach((reason) => riskReasons.add(reason));
517
+ if (changeSet.conflictState === "source_changed") {
518
+ riskLevel = maxSourceReviewRisk(riskLevel, "medium");
519
+ riskReasons.add("source changed");
520
+ }
521
+ const sourceRow =
522
+ source.rows.find(
523
+ (row) =>
524
+ row.documentId === changeSet.documentId ||
525
+ row.databaseItemId === changeSet.databaseItemId,
526
+ ) ?? null;
527
+ const latestExecution =
528
+ changeSet.executions[changeSet.executions.length - 1] ?? null;
529
+ const titleChange = changeSet.fieldChanges.find(
530
+ (field) => field.localFieldKey === "title",
531
+ );
532
+ const proposedTitle = titleChange?.proposedValue;
533
+
534
+ return {
535
+ changeSetId: changeSet.id,
536
+ databaseItemId: changeSet.databaseItemId,
537
+ documentId: changeSet.documentId,
538
+ title:
539
+ typeof proposedTitle === "string" && proposedTitle.trim()
540
+ ? proposedTitle
541
+ : sourceRow?.sourceDisplayKey || "Untitled",
542
+ fieldChanges: changeSet.fieldChanges,
543
+ bodyChange: changeSet.bodyChange,
544
+ riskLevel: changeSet.riskLevel,
545
+ riskReasons: changeSet.riskReasons,
546
+ conflictState: changeSet.conflictState,
547
+ effect: resolveBuilderCmsWriteEffect({ source, changeSet }),
548
+ execution: latestExecution,
549
+ };
550
+ });
551
+ const statuses = rows
552
+ .map((row) => builderExecutionDryRunStatus(row.execution?.payload ?? {}))
553
+ .filter(
554
+ (
555
+ status,
556
+ ): status is {
557
+ status: "validated" | "stale" | "blocked";
558
+ validatedAt: string | null;
559
+ } => !!status,
560
+ );
561
+ const executionStates = rows
562
+ .map((row) => row.execution?.state)
563
+ .filter(Boolean);
564
+ const hasExecutionEvidence =
565
+ statuses.length > 0 || executionStates.length > 0;
566
+ const resultStatus =
567
+ executionStates.length > 0 &&
568
+ executionStates.every((state) => state === "succeeded")
569
+ ? "succeeded"
570
+ : executionStates.includes("failed")
571
+ ? "failed"
572
+ : executionStates.includes("running")
573
+ ? "running"
574
+ : statuses.some((status) => status.status === "stale")
575
+ ? "stale"
576
+ : statuses.some((status) => status.status === "blocked")
577
+ ? "blocked"
578
+ : statuses.some((status) => status.status === "validated")
579
+ ? "validated"
580
+ : source.capabilities.liveWritesEnabled
581
+ ? "validated"
582
+ : "write_disabled";
583
+
584
+ return {
585
+ summary:
586
+ rows.length === 1
587
+ ? "1 Builder row has changes ready to review."
588
+ : `${rows.length} Builder rows have changes ready to review.`,
589
+ sourceName: source.sourceName,
590
+ sourceTable: source.sourceTable,
591
+ pushMode: source.metadata.pushMode ?? "autosave",
592
+ dryRunOnly: !source.capabilities.liveWritesEnabled,
593
+ liveWritesEnabled: source.capabilities.liveWritesEnabled,
594
+ riskLevel,
595
+ riskReasons: Array.from(riskReasons),
596
+ rows,
597
+ result: {
598
+ status: resultStatus,
599
+ message:
600
+ resultStatus === "succeeded"
601
+ ? "Pushed to Builder and reconciled locally."
602
+ : resultStatus === "failed"
603
+ ? "Builder push failed. The change remains retryable."
604
+ : resultStatus === "running"
605
+ ? "Builder push is running."
606
+ : resultStatus === "validated"
607
+ ? source.capabilities.liveWritesEnabled
608
+ ? hasExecutionEvidence
609
+ ? "Push checked successfully. Ready to send to Builder."
610
+ : "Ready to send to Builder."
611
+ : "Push checked successfully. Nothing was sent to Builder."
612
+ : resultStatus === "blocked"
613
+ ? "Push needs attention before anything can be sent to Builder."
614
+ : resultStatus === "stale"
615
+ ? "Push needs a fresh review because the plan changed."
616
+ : "Builder writes are off in this local build. Push will check the update only.",
617
+ },
618
+ };
619
+ }
620
+
621
+ function DatabaseSettingsSourcePanel({
622
+ source,
623
+ sources,
624
+ documentId,
625
+ itemCount,
626
+ canEdit,
627
+ nav,
628
+ onNavPush,
629
+ onNavReplace,
630
+ onAttachBuilderSource,
631
+ onFederateSource,
632
+ onChangeSourceRole,
633
+ onDisconnectSecondary,
634
+ onRefreshSource,
635
+ onHydrateBuilderBodies,
636
+ onDisconnectSource,
637
+ onReviewBuilderUpdate,
638
+ onSetBuilderLiveWrites,
639
+ sourceActionPending,
640
+ }: {
641
+ source: ContentDatabaseSource | null;
642
+ sources: ContentDatabaseSource[];
643
+ documentId: string;
644
+ itemCount: number;
645
+ canEdit: boolean;
646
+ nav: SourceNavStep[];
647
+ onNavPush: (step: SourceNavStep) => void;
648
+ onNavReplace: (stack: SourceNavStep[]) => void;
649
+ onAttachBuilderSource: (
650
+ model: BuilderCmsModelSummary,
651
+ relationshipMode?: "items" | "details",
652
+ ) => Promise<ContentDatabaseResponse>;
653
+ onFederateSource: (
654
+ candidate: PendingSourceCandidate,
655
+ join: ContentDatabaseSourceJoinRequest,
656
+ ) => Promise<ContentDatabaseResponse>;
657
+ onChangeSourceRole: (
658
+ sourceId: string,
659
+ relationshipMode: "items" | "details",
660
+ join?: ContentDatabaseSourceJoinRequest,
661
+ ) => Promise<ContentDatabaseResponse>;
662
+ onDisconnectSecondary: (sourceId: string) => void;
663
+ onRefreshSource: (sourceId?: string) => void;
664
+ onHydrateBuilderBodies: (sourceId: string) => void;
665
+ onDisconnectSource: (sourceId?: string) => void;
666
+ onReviewBuilderUpdate: () => void;
667
+ onSetBuilderLiveWrites: (enabled: boolean) => void;
668
+ sourceActionPending: boolean;
669
+ }) {
670
+ const outboundChangeSets =
671
+ source?.changeSets.filter(
672
+ (changeSet) => changeSet.direction === "outbound",
673
+ ) ?? [];
674
+ const reviewableBuilderChangeSets = outboundChangeSets.filter(
675
+ (changeSet) =>
676
+ changeSet.state === "pending_push" ||
677
+ changeSet.state === "staged_revision" ||
678
+ changeSet.state === "approved",
679
+ );
680
+ const conflictChangeSets =
681
+ source?.changeSets.filter(
682
+ (changeSet) => changeSet.conflictState === "source_changed",
683
+ ) ?? [];
684
+ const { isCodeMode } = useCodeMode();
685
+ const isBuilderSource = source?.sourceType === "builder-cms";
686
+ const builderStatus = useBuilderStatus();
687
+ const builderConfigured = builderStatus.status?.configured === true;
688
+ const builderOrgName = builderStatus.status?.orgName ?? null;
689
+ // Real space name(s) from the Admin API, falling back to the generic org
690
+ // name (then a constant) so the drill-down never renders a blank label.
691
+ const builderSpaces =
692
+ builderStatus.status?.spaces && builderStatus.status.spaces.length > 0
693
+ ? builderStatus.status.spaces
694
+ : builderOrgName
695
+ ? [{ id: "builder-space", name: builderOrgName }]
696
+ : [{ id: "builder-space", name: dbText("builderSpace") }];
697
+ const builderSpaceLabel = builderSpaces[0]?.name ?? builderOrgName;
698
+ const connect = useBuilderConnectFlow({
699
+ trackingSource: "database_source_panel",
700
+ onConnected: () => {
701
+ void builderStatus.refetch();
702
+ },
703
+ });
704
+ const builderSyncFailed =
705
+ isBuilderSource &&
706
+ (source?.syncState === "error" || Boolean(source?.lastError));
707
+
708
+ // Auto-sync: the manual Refresh button is gone, so pull the read-only
709
+ // snapshot when the panel opens and whenever the window regains focus.
710
+ // Throttled so rapid focus changes don't hammer Builder; the refresh
711
+ // mutation is silent (no toast), so this stays quiet in the background.
712
+ const refreshSourceRef = useRef(onRefreshSource);
713
+ refreshSourceRef.current = onRefreshSource;
714
+ const lastAutoSyncRef = useRef(0);
715
+ const autoSyncEnabled = Boolean(source) && isBuilderSource && canEdit;
716
+ useEffect(() => {
717
+ if (!autoSyncEnabled) return;
718
+ const maybeSync = () => {
719
+ const now = Date.now();
720
+ if (now - lastAutoSyncRef.current < 15_000) return;
721
+ lastAutoSyncRef.current = now;
722
+ refreshSourceRef.current();
723
+ };
724
+ maybeSync();
725
+ const onFocus = () => maybeSync();
726
+ window.addEventListener("focus", onFocus);
727
+ return () => window.removeEventListener("focus", onFocus);
728
+ }, [autoSyncEnabled]);
729
+
730
+ const top = nav[nav.length - 1];
731
+
732
+ // ── Sources list (root) ───────────────────────────────────────────────
733
+ if (!top) {
734
+ return (
735
+ <SourcesListView
736
+ source={source}
737
+ sources={sources}
738
+ builderConfigured={builderConfigured}
739
+ builderSpaceLabel={builderSpaceLabel}
740
+ reviewableCount={reviewableBuilderChangeSets.length}
741
+ onOpenBuilder={() =>
742
+ onNavPush({ kind: "provider", providerId: "builder" })
743
+ }
744
+ onOpenSecondary={(secondary) =>
745
+ onNavPush({
746
+ kind: "secondarySource",
747
+ sourceId: secondary.id,
748
+ sourceName: secondary.sourceName,
749
+ })
750
+ }
751
+ onAddSource={() => onNavPush({ kind: "addSource" })}
752
+ />
753
+ );
754
+ }
755
+
756
+ // ── Add a source → local tables picker ────────────────────────────────
757
+ if (top.kind === "addSource") {
758
+ return (
759
+ <AddSourceView
760
+ excludeDatabaseIds={[
761
+ ...(source?.databaseId ? [source.databaseId] : []),
762
+ ...sources
763
+ .filter((item) => item.sourceType === "local-table")
764
+ .map((item) => item.sourceTable),
765
+ ]}
766
+ canEdit={canEdit}
767
+ onPickLocalTable={(table) =>
768
+ onNavPush({
769
+ kind: "keyConfirm",
770
+ candidate: {
771
+ sourceType: "local-table",
772
+ sourceName: table.title,
773
+ sourceTable: table.databaseId,
774
+ displayName: table.title,
775
+ },
776
+ })
777
+ }
778
+ />
779
+ );
780
+ }
781
+
782
+ // ── Secondary (federated) source leaf ─────────────────────────────────
783
+ if (top.kind === "secondarySource") {
784
+ const secondary = sources.find((item) => item.id === top.sourceId) ?? null;
785
+ return (
786
+ <SecondarySourceLeaf
787
+ source={secondary}
788
+ canEdit={canEdit}
789
+ pending={sourceActionPending}
790
+ onAddDetails={() =>
791
+ secondary
792
+ ? onNavPush({
793
+ kind: "keyConfirm",
794
+ candidate: {
795
+ sourceType: secondary.sourceType,
796
+ sourceName: secondary.sourceName,
797
+ sourceTable: secondary.sourceTable,
798
+ displayName: secondary.sourceName,
799
+ existingSourceId: secondary.id,
800
+ },
801
+ })
802
+ : undefined
803
+ }
804
+ onAddItems={async () => {
805
+ if (!secondary) return;
806
+ await onChangeSourceRole(secondary.id, "items");
807
+ onNavReplace([]);
808
+ }}
809
+ onChooseFields={() =>
810
+ secondary
811
+ ? onNavPush({
812
+ kind: "fieldPicker",
813
+ sourceId: secondary.id,
814
+ sourceName: secondary.sourceName,
815
+ })
816
+ : undefined
817
+ }
818
+ onDisconnect={() => onDisconnectSecondary(top.sourceId)}
819
+ />
820
+ );
821
+ }
822
+
823
+ // ── Canonical-key confirm (adding a second source) ────────────────────
824
+ if (top.kind === "keyConfirm") {
825
+ return (
826
+ <CanonicalKeyConfirmView
827
+ documentId={documentId}
828
+ candidate={top.candidate}
829
+ canEdit={canEdit}
830
+ pending={sourceActionPending}
831
+ onCommit={async (join) => {
832
+ const result = top.candidate.existingSourceId
833
+ ? await onChangeSourceRole(
834
+ top.candidate.existingSourceId,
835
+ "details",
836
+ join,
837
+ )
838
+ : await onFederateSource(top.candidate, join);
839
+ const detailsSource = findDetailsSource(result, top.candidate);
840
+ onNavReplace(
841
+ detailsSource
842
+ ? [
843
+ {
844
+ kind: "fieldPicker",
845
+ sourceId: detailsSource.id,
846
+ sourceName: detailsSource.sourceName,
847
+ },
848
+ ]
849
+ : [],
850
+ );
851
+ }}
852
+ />
853
+ );
854
+ }
855
+
856
+ if (top.kind === "fieldPicker") {
857
+ const pickerSource =
858
+ sources.find((item) => item.id === top.sourceId) ?? null;
859
+ return (
860
+ <SourceDetailsFieldPicker
861
+ documentId={documentId}
862
+ source={pickerSource}
863
+ canEdit={canEdit}
864
+ pending={sourceActionPending}
865
+ onDone={() => onNavReplace([])}
866
+ />
867
+ );
868
+ }
869
+
870
+ // ── Builder provider → space list ─────────────────────────────────────
871
+ if (top.kind === "provider") {
872
+ if (!builderConfigured) {
873
+ // Don't flash "Connect Builder" at an already-connected user while the
874
+ // status is still loading — show a checking state until we actually know.
875
+ if (!builderStatus.status && builderStatus.loading) {
876
+ return (
877
+ <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
878
+ <Spinner className="size-3.5" />
879
+ {dbText("checkingBuilderConnection")}
880
+ </div>
881
+ );
882
+ }
883
+ return (
884
+ <div className="grid min-w-0 gap-3">
885
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
886
+ {dbText("connectYourBuilderAccountToBrowseItsSpaces")}
887
+ </div>
888
+ <div>
889
+ <Button
890
+ type="button"
891
+ size="sm"
892
+ disabled={!canEdit || connect.connecting}
893
+ onClick={() => connect.start()}
894
+ >
895
+ {connect.connecting ? (
896
+ <Spinner className="mr-1.5 size-3.5" />
897
+ ) : (
898
+ <IconExternalLink className="mr-1.5 size-3.5" />
899
+ )}
900
+ Connect Builder
901
+ </Button>
902
+ </div>
903
+ </div>
904
+ );
905
+ }
906
+ return (
907
+ <div className="grid min-w-0 gap-1.5">
908
+ {builderSpaces.map((space) => (
909
+ <DatabaseSettingsRow
910
+ key={space.id}
911
+ icon={<IconLayoutGrid className="size-4" />}
912
+ label={space.name}
913
+ onClick={() =>
914
+ onNavPush({
915
+ kind: "space",
916
+ spaceId: space.id,
917
+ spaceName: space.name,
918
+ })
919
+ }
920
+ />
921
+ ))}
922
+ </div>
923
+ );
924
+ }
925
+
926
+ // ── Space → model list ────────────────────────────────────────────────
927
+ if (top.kind === "space") {
928
+ return (
929
+ <BuilderSpaceModelsView
930
+ attachedModelName={
931
+ isBuilderSource ? (source?.sourceTable ?? null) : null
932
+ }
933
+ onOpenModel={(model) => onNavPush({ kind: "model", model })}
934
+ />
935
+ );
936
+ }
937
+
938
+ // ── Model leaf ────────────────────────────────────────────────────────
939
+ const model = top.model;
940
+ const isAttachedModel =
941
+ Boolean(source) && isBuilderSource && source?.sourceTable === model.name;
942
+
943
+ // Unattached model → the attach affordance (the model is already chosen by
944
+ // drilling in, so there's no model picker here).
945
+ if (!isAttachedModel || !source) {
946
+ return (
947
+ <div className="grid min-w-0 gap-3">
948
+ <div className="grid min-w-0 gap-1.5 rounded-lg border border-border bg-background p-3 text-sm">
949
+ <div className="truncate font-medium" title={model.displayName}>
950
+ {model.displayName}
951
+ </div>
952
+ <div className="flex min-w-0 flex-wrap items-center gap-1.5 text-[11px] text-muted-foreground">
953
+ <span className="rounded border border-border px-1.5 py-0.5">
954
+ {model.name}
955
+ </span>
956
+ <span className="rounded border border-border px-1.5 py-0.5">
957
+ {model.fields.length} fields
958
+ </span>
959
+ <span className="rounded border border-border px-1.5 py-0.5">
960
+ read-only
961
+ </span>
962
+ </div>
963
+ </div>
964
+ {sources.length > 0 || source ? (
965
+ <SourceRelationshipChoice
966
+ documentId={documentId}
967
+ candidate={{
968
+ sourceType: "builder-cms",
969
+ sourceName: model.displayName,
970
+ sourceTable: model.name,
971
+ displayName: model.displayName,
972
+ }}
973
+ canEdit={canEdit}
974
+ pending={sourceActionPending}
975
+ onAddDetails={() =>
976
+ onNavPush({
977
+ kind: "keyConfirm",
978
+ candidate: {
979
+ sourceType: "builder-cms",
980
+ sourceName: model.displayName,
981
+ sourceTable: model.name,
982
+ displayName: model.displayName,
983
+ },
984
+ })
985
+ }
986
+ onAddItems={async () => {
987
+ await onAttachBuilderSource(model, "items");
988
+ onNavReplace([]);
989
+ }}
990
+ />
991
+ ) : (
992
+ <div>
993
+ <Button
994
+ type="button"
995
+ size="sm"
996
+ disabled={!canEdit || sourceActionPending}
997
+ onClick={async () => {
998
+ await onAttachBuilderSource(model);
999
+ onNavReplace([]);
1000
+ }}
1001
+ >
1002
+ {sourceActionPending ? (
1003
+ <Spinner className="mr-1.5 size-3.5" />
1004
+ ) : (
1005
+ <IconPlugConnected className="mr-1.5 size-3.5" />
1006
+ )}
1007
+ Attach
1008
+ </Button>
1009
+ </div>
1010
+ )}
1011
+ </div>
1012
+ );
1013
+ }
1014
+
1015
+ // Attached model → the minimal read-only leaf panel.
1016
+ return (
1017
+ <div className="grid min-w-0 gap-4">
1018
+ <>
1019
+ <div className="grid min-w-0 gap-1.5 rounded-lg border border-border bg-background p-3 text-sm">
1020
+ <div className="flex min-w-0 items-center justify-between gap-2">
1021
+ <span className="truncate font-medium" title={source.sourceName}>
1022
+ {source.sourceName}
1023
+ </span>
1024
+ {isBuilderSource ? (
1025
+ source.capabilities.liveWritesEnabled ? (
1026
+ <span className="inline-flex shrink-0 items-center gap-1 rounded-full border border-border px-2 py-0.5 text-[11px] font-medium text-foreground">
1027
+ <IconPencil className="size-3" />
1028
+ {dbText("liveWritesOn")}
1029
+ </span>
1030
+ ) : (
1031
+ <span className="inline-flex shrink-0 items-center gap-1 rounded-full border border-border px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
1032
+ <IconLock className="size-3" />
1033
+ {dbText("readOnly")}
1034
+ </span>
1035
+ )
1036
+ ) : (
1037
+ <span className="shrink-0 rounded-full border border-border px-2 py-0.5 text-[11px] uppercase tracking-wide text-muted-foreground">
1038
+ {source.syncState}
1039
+ </span>
1040
+ )}
1041
+ </div>
1042
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
1043
+ {builderSyncFailed ? (
1044
+ <button
1045
+ type="button"
1046
+ className="inline-flex items-center gap-1 text-destructive hover:underline disabled:opacity-60"
1047
+ disabled={!canEdit || sourceActionPending}
1048
+ onClick={() => onRefreshSource(source.id)}
1049
+ >
1050
+ <IconRefresh className="size-3" />
1051
+ {dbText("couldntSyncRetry")}
1052
+ </button>
1053
+ ) : isBuilderSource ? (
1054
+ [
1055
+ builderConfigured ? (builderSpaceLabel ?? "Connected") : null,
1056
+ source.lastRefreshedAt
1057
+ ? `synced ${
1058
+ formatRelativeSyncTime(source.lastRefreshedAt) ??
1059
+ source.freshness
1060
+ }`
1061
+ : source.freshness,
1062
+ ]
1063
+ .filter(Boolean)
1064
+ .join(" · ")
1065
+ ) : (
1066
+ `Local snapshot · ${source.freshness}`
1067
+ )}
1068
+ </div>
1069
+ </div>
1070
+
1071
+ {reviewableBuilderChangeSets.length > 0 ||
1072
+ conflictChangeSets.length > 0 ? (
1073
+ <div className="grid min-w-0 gap-2 rounded-lg border border-border bg-muted/30 p-3">
1074
+ <div className="flex items-center justify-between gap-3">
1075
+ <div className="min-w-0">
1076
+ <div className="text-sm font-medium">
1077
+ {conflictChangeSets.length > 0
1078
+ ? `${conflictChangeSets.length} change${
1079
+ conflictChangeSets.length === 1 ? "" : "s"
1080
+ } need review`
1081
+ : `${reviewableBuilderChangeSets.length} change${
1082
+ reviewableBuilderChangeSets.length === 1 ? "" : "s"
1083
+ } ready to push`}
1084
+ </div>
1085
+ <div className="mt-0.5 break-words text-xs text-muted-foreground">
1086
+ {dbText("reviewBeforeTheyReachBuilder")}
1087
+ </div>
1088
+ </div>
1089
+ <Button
1090
+ type="button"
1091
+ size="sm"
1092
+ className="shrink-0"
1093
+ disabled={!canEdit || sourceActionPending}
1094
+ onClick={onReviewBuilderUpdate}
1095
+ >
1096
+ <IconCheck className="mr-1.5 size-3.5" />
1097
+ {dbText("reviewDiff")}
1098
+ </Button>
1099
+ </div>
1100
+ </div>
1101
+ ) : null}
1102
+
1103
+ {isBuilderSource && source.bodyHydration ? (
1104
+ <BuilderBodyHydrationCard
1105
+ source={source}
1106
+ canEdit={canEdit}
1107
+ pending={sourceActionPending}
1108
+ onHydrate={() => onHydrateBuilderBodies(source.id)}
1109
+ />
1110
+ ) : null}
1111
+
1112
+ {isCodeMode ? (
1113
+ <>
1114
+ <div className="grid min-w-0 gap-2 rounded-lg border border-border bg-background p-3 text-sm">
1115
+ <div className="font-medium">
1116
+ {source.sourceType === "builder-cms"
1117
+ ? "Local Builder changes"
1118
+ : "Local outbound changes"}
1119
+ </div>
1120
+ <div className="text-xs text-muted-foreground">
1121
+ {source.sourceType === "builder-cms"
1122
+ ? source.capabilities.liveWritesEnabled
1123
+ ? "Local edits can be reviewed and sent through the guarded Builder autosave path."
1124
+ : "Local edits can be staged as a Builder save revision/autosave record. Live Builder writes are disabled."
1125
+ : "No local outbound push lane is active for this mock source."}
1126
+ </div>
1127
+ <div className="grid min-w-0 gap-2">
1128
+ {outboundChangeSets.slice(0, 6).map((changeSet) => (
1129
+ <SourceChangeSetReviewCard
1130
+ key={changeSet.id}
1131
+ changeSet={changeSet}
1132
+ source={source}
1133
+ />
1134
+ ))}
1135
+ {outboundChangeSets.length === 0 ? (
1136
+ <div className="text-xs text-muted-foreground">
1137
+ {source.sourceType === "builder-cms"
1138
+ ? "No pending local Builder changes yet. Rename a source-backed row to see a local outbound diff."
1139
+ : "No local outbound changes yet."}
1140
+ </div>
1141
+ ) : null}
1142
+ </div>
1143
+ </div>
1144
+ </>
1145
+ ) : null}
1146
+
1147
+ <SourceRoleCard
1148
+ source={source}
1149
+ canAddDetails={sources.some(
1150
+ (item) => item.id !== source.id && !sourceAddsDetails(item),
1151
+ )}
1152
+ canEdit={canEdit}
1153
+ pending={sourceActionPending}
1154
+ onAddDetails={() =>
1155
+ onNavPush({
1156
+ kind: "keyConfirm",
1157
+ candidate: {
1158
+ sourceType: source.sourceType,
1159
+ sourceName: source.sourceName,
1160
+ sourceTable: source.sourceTable,
1161
+ displayName: source.sourceName,
1162
+ existingSourceId: source.id,
1163
+ },
1164
+ })
1165
+ }
1166
+ onAddItems={async () => {
1167
+ await onChangeSourceRole(source.id, "items");
1168
+ onNavReplace([]);
1169
+ }}
1170
+ onChooseFields={() =>
1171
+ onNavPush({
1172
+ kind: "fieldPicker",
1173
+ sourceId: source.id,
1174
+ sourceName: source.sourceName,
1175
+ })
1176
+ }
1177
+ />
1178
+
1179
+ <div className="rounded-lg border border-border bg-background p-3">
1180
+ <div className="text-xs font-medium">
1181
+ {dbText("disconnectSource")}
1182
+ </div>
1183
+ <div className="mt-0.5 break-words text-xs text-muted-foreground">
1184
+ {dbText("keepTheDatabaseRowsAndLocalPropertiesButRemoveSource")}
1185
+ </div>
1186
+ <Button
1187
+ type="button"
1188
+ size="sm"
1189
+ variant="outline"
1190
+ className="mt-2 h-8 text-xs text-destructive hover:text-destructive"
1191
+ disabled={!canEdit || sourceActionPending}
1192
+ onClick={() => onDisconnectSource(source.id)}
1193
+ >
1194
+ {sourceActionPending ? (
1195
+ <Spinner className="mr-1 size-3.5" />
1196
+ ) : (
1197
+ <IconX className="mr-1 size-3.5" />
1198
+ )}
1199
+ Disconnect
1200
+ </Button>
1201
+ </div>
1202
+ </>
1203
+ </div>
1204
+ );
1205
+ }
1206
+
1207
+ // Root of the Sources drill-down: third-party integrations + Agent-Native apps,
1208
+ // each provider a row. Builder is live; the rest are disabled "coming soon".
1209
+ function SourcesListView({
1210
+ source,
1211
+ sources,
1212
+ builderConfigured,
1213
+ builderSpaceLabel,
1214
+ reviewableCount,
1215
+ onOpenBuilder,
1216
+ onOpenSecondary,
1217
+ onAddSource,
1218
+ }: {
1219
+ source: ContentDatabaseSource | null;
1220
+ sources: ContentDatabaseSource[];
1221
+ builderConfigured: boolean;
1222
+ builderSpaceLabel: string | null;
1223
+ reviewableCount: number;
1224
+ onOpenBuilder: () => void;
1225
+ onOpenSecondary: (source: ContentDatabaseSource) => void;
1226
+ onAddSource: () => void;
1227
+ }) {
1228
+ const isBuilderSource = source?.sourceType === "builder-cms";
1229
+ const connectedSources =
1230
+ sources.length > 0 ? sources : source ? [source] : [];
1231
+ return (
1232
+ <div className="grid min-w-0 gap-4">
1233
+ {connectedSources.length === 0 ? (
1234
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
1235
+ {dbText("thisDatabaseIsLocalConnectASourceToMapIts")}
1236
+ </div>
1237
+ ) : (
1238
+ <div className="grid min-w-0 gap-1.5">
1239
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1240
+ {dbText("connectedSources")}
1241
+ </div>
1242
+ {connectedSources.map((connected, index) => (
1243
+ <DatabaseSettingsRow
1244
+ key={connected.id}
1245
+ icon={
1246
+ connected.sourceType === "builder-cms" ? (
1247
+ <BuilderLogoMark className="size-4" />
1248
+ ) : (
1249
+ <IconLayoutGrid className="size-4" />
1250
+ )
1251
+ }
1252
+ label={connected.sourceName}
1253
+ value={sourceRoleLabel(connected, index)}
1254
+ onClick={
1255
+ connected.metadata.federation?.role === "secondary"
1256
+ ? () => onOpenSecondary(connected)
1257
+ : connected.sourceType === "builder-cms"
1258
+ ? onOpenBuilder
1259
+ : undefined
1260
+ }
1261
+ disabled={
1262
+ connected.metadata.federation?.role !== "secondary" &&
1263
+ connected.sourceType !== "builder-cms"
1264
+ }
1265
+ />
1266
+ ))}
1267
+ <DatabaseSettingsRow
1268
+ icon={<IconPlus className="size-4" />}
1269
+ label={dbText("addAnotherSource")}
1270
+ onClick={onAddSource}
1271
+ />
1272
+ </div>
1273
+ )}
1274
+ <div className="grid min-w-0 gap-1.5">
1275
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1276
+ Integrations
1277
+ </div>
1278
+ <DatabaseSettingsRow
1279
+ icon={<BuilderLogoMark className="size-4" />}
1280
+ label="Builder"
1281
+ value={
1282
+ isBuilderSource
1283
+ ? (builderSpaceLabel ?? "Connected")
1284
+ : builderConfigured
1285
+ ? "Connected"
1286
+ : undefined
1287
+ }
1288
+ badgeCount={reviewableCount}
1289
+ onClick={onOpenBuilder}
1290
+ />
1291
+ <DatabaseSettingsRow
1292
+ icon={<NotionLogoMark className="size-4" />}
1293
+ label="Notion"
1294
+ value="Coming soon"
1295
+ disabled
1296
+ />
1297
+ </div>
1298
+ <div className="grid min-w-0 gap-1.5">
1299
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1300
+ {dbText("agentNativeApps")}
1301
+ </div>
1302
+ <DatabaseSettingsRow
1303
+ icon={<IconTimeline className="size-4" />}
1304
+ label="Analytics"
1305
+ value="Coming soon"
1306
+ disabled
1307
+ />
1308
+ </div>
1309
+ </div>
1310
+ );
1311
+ }
1312
+
1313
+ // Confirm the canonical-key join before federating a second source. The
1314
+ // heuristic proposes a key field + normalization formula per side; the user can
1315
+ // tweak the formulas and watch a live sample-match preview before committing.
1316
+ function CanonicalKeyConfirmView({
1317
+ documentId,
1318
+ candidate,
1319
+ canEdit,
1320
+ pending,
1321
+ onCommit,
1322
+ }: {
1323
+ documentId: string;
1324
+ candidate: PendingSourceCandidate;
1325
+ canEdit: boolean;
1326
+ pending: boolean;
1327
+ onCommit: (join: ContentDatabaseSourceJoinRequest) => void | Promise<void>;
1328
+ }) {
1329
+ const suggestionQuery = useSuggestSourceJoinKey({
1330
+ documentId,
1331
+ candidateSourceType: candidate.sourceType,
1332
+ candidateSourceTable: candidate.sourceTable,
1333
+ enabled: true,
1334
+ });
1335
+ const suggestion: SourceJoinSuggestion | null =
1336
+ suggestionQuery.data?.suggestion ?? null;
1337
+
1338
+ const [primaryFormula, setPrimaryFormula] = useState("");
1339
+ const [secondaryFormula, setSecondaryFormula] = useState("");
1340
+ const [primaryKeyField, setPrimaryKeyField] = useState("");
1341
+ const [secondaryKeyField, setSecondaryKeyField] = useState("");
1342
+ const [hydrated, setHydrated] = useState(false);
1343
+
1344
+ useEffect(() => {
1345
+ if (suggestion && !hydrated) {
1346
+ setPrimaryFormula(suggestion.primary.normalizationFormula);
1347
+ setSecondaryFormula(suggestion.secondary.normalizationFormula);
1348
+ setPrimaryKeyField(suggestion.primary.keyField);
1349
+ setSecondaryKeyField(suggestion.secondary.keyField);
1350
+ setHydrated(true);
1351
+ }
1352
+ }, [suggestion, hydrated]);
1353
+
1354
+ const previewRows = useMemo(() => {
1355
+ if (!suggestion) return [];
1356
+ return suggestion.sampleMatches.map((sample) => {
1357
+ const primaryNorm = evaluateNormalizationFormula(primaryFormula, {
1358
+ [primaryKeyField]: sample.primaryRaw,
1359
+ });
1360
+ const secondaryNorm = sample.secondaryRaw
1361
+ ? evaluateNormalizationFormula(secondaryFormula, {
1362
+ [secondaryKeyField]: sample.secondaryRaw,
1363
+ })
1364
+ : null;
1365
+ return {
1366
+ primaryRaw: sample.primaryRaw,
1367
+ normalized: primaryNorm,
1368
+ matched: primaryNorm !== null && primaryNorm === secondaryNorm,
1369
+ };
1370
+ });
1371
+ }, [
1372
+ suggestion,
1373
+ primaryFormula,
1374
+ secondaryFormula,
1375
+ primaryKeyField,
1376
+ secondaryKeyField,
1377
+ ]);
1378
+
1379
+ const matchedCount = previewRows.filter((row) => row.matched).length;
1380
+
1381
+ if (suggestionQuery.isLoading) {
1382
+ return (
1383
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
1384
+ <Spinner className="size-3.5" />
1385
+ {dbText("checkingHowTheseRecordsMatch")}
1386
+ </div>
1387
+ );
1388
+ }
1389
+
1390
+ if (!suggestion) {
1391
+ return (
1392
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
1393
+ {suggestionQuery.data?.message ??
1394
+ "Couldn't find a match field automatically."}
1395
+ </div>
1396
+ );
1397
+ }
1398
+
1399
+ return (
1400
+ <div className="grid min-w-0 gap-3">
1401
+ <div className="grid min-w-0 gap-1.5 rounded-lg border border-border bg-background p-3 text-sm">
1402
+ <div className="truncate font-medium" title={candidate.displayName}>
1403
+ {candidate.displayName}
1404
+ </div>
1405
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
1406
+ Match existing items to details. Suggested match:{" "}
1407
+ <span className="font-medium text-foreground">
1408
+ {suggestion.canonicalKey.label}
1409
+ </span>
1410
+ .
1411
+ </div>
1412
+ </div>
1413
+
1414
+ <div className="grid min-w-0 gap-1.5">
1415
+ <label className="px-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1416
+ {dbText("existingSourceNormalize")}
1417
+ </label>
1418
+ <Input
1419
+ value={primaryFormula}
1420
+ onChange={(event) => setPrimaryFormula(event.target.value)}
1421
+ disabled={!canEdit}
1422
+ className="font-mono text-xs"
1423
+ />
1424
+ </div>
1425
+ <div className="grid min-w-0 gap-1.5">
1426
+ <label className="px-1 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1427
+ {dbText("newSourceNormalize")}
1428
+ </label>
1429
+ <Input
1430
+ value={secondaryFormula}
1431
+ onChange={(event) => setSecondaryFormula(event.target.value)}
1432
+ disabled={!canEdit}
1433
+ className="font-mono text-xs"
1434
+ />
1435
+ </div>
1436
+
1437
+ <div className="grid min-w-0 gap-1.5 rounded-lg border border-border bg-muted/30 p-3">
1438
+ <div className="flex items-center justify-between text-xs">
1439
+ <span className="font-medium">{dbText("sampleMatches")}</span>
1440
+ <span className="text-muted-foreground">
1441
+ {matchedCount} of {previewRows.length} match
1442
+ </span>
1443
+ </div>
1444
+ <div className="grid min-w-0 gap-1">
1445
+ {previewRows.map((row, index) => (
1446
+ <div
1447
+ key={index}
1448
+ className="flex min-w-0 items-center gap-1.5 text-[11px]"
1449
+ >
1450
+ {row.matched ? (
1451
+ <IconCheck className="size-3 shrink-0 text-foreground" />
1452
+ ) : (
1453
+ <IconX className="size-3 shrink-0 text-muted-foreground" />
1454
+ )}
1455
+ <span
1456
+ className="truncate text-muted-foreground"
1457
+ title={row.primaryRaw}
1458
+ >
1459
+ {row.primaryRaw}
1460
+ </span>
1461
+ <span className="shrink-0 text-muted-foreground">→</span>
1462
+ <span
1463
+ className="truncate font-medium"
1464
+ title={row.normalized ?? ""}
1465
+ >
1466
+ {row.normalized ?? "—"}
1467
+ </span>
1468
+ </div>
1469
+ ))}
1470
+ </div>
1471
+ </div>
1472
+
1473
+ <Button
1474
+ type="button"
1475
+ size="sm"
1476
+ disabled={!canEdit || pending || matchedCount === 0}
1477
+ onClick={() =>
1478
+ onCommit({
1479
+ canonicalKey: suggestion.canonicalKey,
1480
+ primary: {
1481
+ keyField: primaryKeyField,
1482
+ normalizationFormula: primaryFormula,
1483
+ },
1484
+ secondary: {
1485
+ keyField: secondaryKeyField,
1486
+ normalizationFormula: secondaryFormula,
1487
+ },
1488
+ })
1489
+ }
1490
+ >
1491
+ {pending ? (
1492
+ <Spinner className="mr-1.5 size-3.5" />
1493
+ ) : (
1494
+ <IconPlugConnected className="mr-1.5 size-3.5" />
1495
+ )}
1496
+ Add details
1497
+ </Button>
1498
+ </div>
1499
+ );
1500
+ }
1501
+
1502
+ // Pick a second source to federate. NEXT supports local tables (any other
1503
+ // workspace database); integrations beyond Builder are coming soon.
1504
+ function AddSourceView({
1505
+ excludeDatabaseIds,
1506
+ canEdit,
1507
+ onPickLocalTable,
1508
+ }: {
1509
+ excludeDatabaseIds: string[];
1510
+ canEdit: boolean;
1511
+ onPickLocalTable: (table: {
1512
+ databaseId: string;
1513
+ documentId: string;
1514
+ title: string;
1515
+ }) => void;
1516
+ }) {
1517
+ const query = useContentDatabases({ enabled: true });
1518
+ // Exclude this database (no self-reference) and any table already federated
1519
+ // onto it — those live in the "Connected sources" group above.
1520
+ const excluded = new Set(excludeDatabaseIds);
1521
+ const tables = (query.data?.databases ?? []).filter(
1522
+ (table) => !excluded.has(table.databaseId),
1523
+ );
1524
+ return (
1525
+ <div className="grid min-w-0 gap-4">
1526
+ <div className="grid min-w-0 gap-1.5">
1527
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1528
+ {dbText("localTables")}
1529
+ </div>
1530
+ {query.isLoading ? (
1531
+ <div className="flex items-center gap-2 px-2 text-xs text-muted-foreground">
1532
+ <Spinner className="size-3.5" />
1533
+ {dbText("loadingTables")}
1534
+ </div>
1535
+ ) : tables.length === 0 ? (
1536
+ <div className="min-w-0 break-words px-2 text-xs text-muted-foreground">
1537
+ {dbText("noOtherDatabasesAvailableToAdd")}
1538
+ </div>
1539
+ ) : (
1540
+ tables.map((table) => (
1541
+ <DatabaseSettingsRow
1542
+ key={table.databaseId}
1543
+ icon={<IconLayoutGrid className="size-4" />}
1544
+ label={table.title}
1545
+ onClick={canEdit ? () => onPickLocalTable(table) : undefined}
1546
+ disabled={!canEdit}
1547
+ />
1548
+ ))
1549
+ )}
1550
+ </div>
1551
+ <div className="grid min-w-0 gap-1.5">
1552
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
1553
+ Integrations
1554
+ </div>
1555
+ <DatabaseSettingsRow
1556
+ icon={<NotionLogoMark className="size-4" />}
1557
+ label="Notion"
1558
+ value="Coming soon"
1559
+ disabled
1560
+ />
1561
+ </div>
1562
+ </div>
1563
+ );
1564
+ }
1565
+
1566
+ // A connected federated (secondary) source: read-only details + remove.
1567
+ function SecondarySourceLeaf({
1568
+ source,
1569
+ canEdit,
1570
+ pending,
1571
+ onAddDetails,
1572
+ onAddItems,
1573
+ onChooseFields,
1574
+ onDisconnect,
1575
+ }: {
1576
+ source: ContentDatabaseSource | null;
1577
+ canEdit: boolean;
1578
+ pending: boolean;
1579
+ onAddDetails: () => void;
1580
+ onAddItems: () => void | Promise<void>;
1581
+ onChooseFields: () => void;
1582
+ onDisconnect: () => void;
1583
+ }) {
1584
+ if (!source) {
1585
+ return (
1586
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
1587
+ {dbText("thisSourceIsNoLongerConnected")}
1588
+ </div>
1589
+ );
1590
+ }
1591
+ const federation = source.metadata.federation;
1592
+ const fieldCount = source.fields.length;
1593
+ return (
1594
+ <div className="grid min-w-0 gap-4">
1595
+ <div className="grid min-w-0 gap-1.5 rounded-lg border border-border bg-background p-3 text-sm">
1596
+ <div className="flex min-w-0 items-center justify-between gap-2">
1597
+ <span className="truncate font-medium" title={source.sourceName}>
1598
+ {source.sourceName}
1599
+ </span>
1600
+ <span className="inline-flex shrink-0 items-center gap-1 rounded-full border border-border px-2 py-0.5 text-[11px] font-medium text-muted-foreground">
1601
+ <IconLock className="size-3" />
1602
+ {dbText("readOnly")}
1603
+ </span>
1604
+ </div>
1605
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
1606
+ {`${sourceRoleLabel(source, 0)} · ${fieldCount} field${
1607
+ fieldCount === 1 ? "" : "s"
1608
+ }`}
1609
+ {federation?.canonicalKey?.label
1610
+ ? ` · joined on ${federation.canonicalKey.label}`
1611
+ : ""}
1612
+ </div>
1613
+ </div>
1614
+ <SourceRoleCard
1615
+ source={source}
1616
+ canEdit={canEdit}
1617
+ pending={pending}
1618
+ onAddDetails={onAddDetails}
1619
+ onAddItems={onAddItems}
1620
+ onChooseFields={onChooseFields}
1621
+ />
1622
+ {federation ? (
1623
+ <div className="grid min-w-0 gap-1 rounded-lg border border-border bg-background p-3 text-xs">
1624
+ <div className="font-medium">{dbText("matchFormula")}</div>
1625
+ <code className="block min-w-0 break-words rounded bg-muted px-1.5 py-1 font-mono text-[11px]">
1626
+ {federation.normalizationFormula}
1627
+ </code>
1628
+ </div>
1629
+ ) : null}
1630
+ <div className="rounded-lg border border-border bg-background p-3">
1631
+ <div className="text-xs font-medium">{dbText("removeThisSource")}</div>
1632
+ <div className="mt-0.5 break-words text-xs text-muted-foreground">
1633
+ Removes the federated columns&rsquo; link to this source. Your local
1634
+ rows and columns stay.
1635
+ </div>
1636
+ <Button
1637
+ type="button"
1638
+ size="sm"
1639
+ variant="outline"
1640
+ className="mt-2 h-8 text-xs text-destructive hover:text-destructive"
1641
+ disabled={!canEdit || pending}
1642
+ onClick={onDisconnect}
1643
+ >
1644
+ {pending ? (
1645
+ <Spinner className="mr-1 size-3.5" />
1646
+ ) : (
1647
+ <IconX className="mr-1 size-3.5" />
1648
+ )}
1649
+ Remove
1650
+ </Button>
1651
+ </div>
1652
+ </div>
1653
+ );
1654
+ }
1655
+
1656
+ function sourceAddsDetails(source: ContentDatabaseSource | null | undefined) {
1657
+ return source?.metadata.federation?.role === "secondary";
1658
+ }
1659
+
1660
+ function sourceRoleLabel(
1661
+ source: ContentDatabaseSource | null | undefined,
1662
+ _index: number,
1663
+ ) {
1664
+ return sourceAddsDetails(source)
1665
+ ? dbText("addingDetails")
1666
+ : dbText("addingItems");
1667
+ }
1668
+
1669
+ function detailsReasonText(suggestion: SourceJoinSuggestion | null) {
1670
+ if (!suggestion)
1671
+ return dbText("recommendedWhenCollectionDescribesExistingRows");
1672
+ const percent = Math.round(suggestion.confidence * 100);
1673
+ return dbText("recommendedBecauseSampledRowsMatchOn", {
1674
+ field: suggestion.canonicalKey.label,
1675
+ percent,
1676
+ });
1677
+ }
1678
+
1679
+ function findDetailsSource(
1680
+ response: ContentDatabaseResponse,
1681
+ candidate: PendingSourceCandidate,
1682
+ ) {
1683
+ return (
1684
+ (response.sources ?? []).find((source) => {
1685
+ if (!sourceAddsDetails(source)) return false;
1686
+ if (candidate.existingSourceId) {
1687
+ return source.id === candidate.existingSourceId;
1688
+ }
1689
+ return (
1690
+ source.sourceType === candidate.sourceType &&
1691
+ source.sourceTable === candidate.sourceTable
1692
+ );
1693
+ }) ?? null
1694
+ );
1695
+ }
1696
+
1697
+ function SourceRelationshipChoice({
1698
+ documentId,
1699
+ candidate,
1700
+ canEdit,
1701
+ pending,
1702
+ onAddDetails,
1703
+ onAddItems,
1704
+ }: {
1705
+ documentId: string;
1706
+ candidate: PendingSourceCandidate;
1707
+ canEdit: boolean;
1708
+ pending: boolean;
1709
+ onAddDetails: () => void;
1710
+ onAddItems: () => void | Promise<void>;
1711
+ }) {
1712
+ const suggestionQuery = useSuggestSourceJoinKey({
1713
+ documentId,
1714
+ candidateSourceType: candidate.sourceType,
1715
+ candidateSourceTable: candidate.sourceTable,
1716
+ enabled: true,
1717
+ });
1718
+ const suggestion = suggestionQuery.data?.suggestion ?? null;
1719
+ const detailsRecommended = Boolean(suggestion);
1720
+ return (
1721
+ <div className="grid min-w-0 gap-2">
1722
+ <button
1723
+ type="button"
1724
+ disabled={!canEdit || pending}
1725
+ className={cn(
1726
+ "grid min-w-0 gap-1 rounded-lg border p-3 text-left transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:opacity-60",
1727
+ detailsRecommended
1728
+ ? "border-foreground/30 bg-muted/30"
1729
+ : "border-border bg-background hover:bg-muted/50",
1730
+ )}
1731
+ onClick={onAddDetails}
1732
+ >
1733
+ <span className="grid min-w-0 gap-1">
1734
+ <span className="break-words text-sm font-medium leading-snug">
1735
+ {dbText("addDetailsToExistingItems")}
1736
+ </span>
1737
+ {detailsRecommended ? (
1738
+ <span className="w-fit rounded-full bg-foreground px-2 py-0.5 text-[10px] font-medium text-background">
1739
+ {dbText("recommended")}
1740
+ </span>
1741
+ ) : null}
1742
+ </span>
1743
+ <span className="break-words text-xs text-muted-foreground">
1744
+ {suggestionQuery.isLoading
1745
+ ? dbText("checkingForMatchingFields")
1746
+ : detailsReasonText(suggestion)}
1747
+ </span>
1748
+ </button>
1749
+ <button
1750
+ type="button"
1751
+ disabled={!canEdit || pending}
1752
+ className="grid min-w-0 gap-1 rounded-lg border border-border bg-background p-3 text-left transition-colors hover:bg-muted/50 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:opacity-60"
1753
+ onClick={onAddItems}
1754
+ >
1755
+ <span className="truncate text-sm font-medium">
1756
+ {dbText("addMoreItemsToThisList")}
1757
+ </span>
1758
+ <span className="break-words text-xs text-muted-foreground">
1759
+ {dbText("bestWhenSameKindAdditionalRows")}
1760
+ </span>
1761
+ </button>
1762
+ </div>
1763
+ );
1764
+ }
1765
+
1766
+ function SourceRoleCard({
1767
+ source,
1768
+ canAddDetails = true,
1769
+ canEdit,
1770
+ pending,
1771
+ onAddDetails,
1772
+ onAddItems,
1773
+ onChooseFields,
1774
+ }: {
1775
+ source: ContentDatabaseSource;
1776
+ canAddDetails?: boolean;
1777
+ canEdit: boolean;
1778
+ pending: boolean;
1779
+ onAddDetails: () => void;
1780
+ onAddItems: () => void | Promise<void>;
1781
+ onChooseFields: () => void;
1782
+ }) {
1783
+ const addingDetails = sourceAddsDetails(source);
1784
+ return (
1785
+ <div className="grid min-w-0 gap-2 rounded-lg border border-border bg-background p-3">
1786
+ <div className="grid min-w-0 gap-0.5">
1787
+ <div className="text-xs font-medium">{dbText("sourceRole")}</div>
1788
+ <div className="break-words text-xs text-muted-foreground">
1789
+ {addingDetails
1790
+ ? dbText("addingDetailsMatchedOn", {
1791
+ field:
1792
+ source.metadata.federation?.canonicalKey?.label ??
1793
+ dbText("aField"),
1794
+ })
1795
+ : dbText("addingItemsAsRows")}
1796
+ </div>
1797
+ </div>
1798
+ <div className="flex min-w-0 flex-wrap gap-2">
1799
+ {addingDetails ? (
1800
+ <>
1801
+ <Button
1802
+ type="button"
1803
+ size="sm"
1804
+ variant="outline"
1805
+ className="h-8 text-xs"
1806
+ disabled={!canEdit || pending}
1807
+ onClick={onChooseFields}
1808
+ >
1809
+ {dbText("chooseFields")}
1810
+ </Button>
1811
+ <Button
1812
+ type="button"
1813
+ size="sm"
1814
+ variant="outline"
1815
+ className="h-8 text-xs"
1816
+ disabled={!canEdit || pending}
1817
+ onClick={onAddItems}
1818
+ >
1819
+ {dbText("addAsItems")}
1820
+ </Button>
1821
+ </>
1822
+ ) : canAddDetails ? (
1823
+ <Button
1824
+ type="button"
1825
+ size="sm"
1826
+ variant="outline"
1827
+ className="h-8 text-xs"
1828
+ disabled={!canEdit || pending}
1829
+ onClick={onAddDetails}
1830
+ >
1831
+ {dbText("addDetailsInstead")}
1832
+ </Button>
1833
+ ) : (
1834
+ <div className="break-words text-xs text-muted-foreground">
1835
+ {dbText("addAnotherItemSourceBeforeChangingToDetails")}
1836
+ </div>
1837
+ )}
1838
+ </div>
1839
+ </div>
1840
+ );
1841
+ }
1842
+
1843
+ function BuilderBodyHydrationCard({
1844
+ source,
1845
+ canEdit,
1846
+ pending,
1847
+ onHydrate,
1848
+ }: {
1849
+ source: ContentDatabaseSource;
1850
+ canEdit: boolean;
1851
+ pending: boolean;
1852
+ onHydrate: () => void;
1853
+ }) {
1854
+ const summary = source.bodyHydration;
1855
+ if (!summary || summary.total === 0) return null;
1856
+ const activeCount = summary.pending + summary.hydrating;
1857
+ const needsWork = activeCount > 0 || summary.error > 0;
1858
+ const hydratedLabel = dbText("builderBodiesHydrated", {
1859
+ hydrated: summary.hydrated,
1860
+ total: summary.total,
1861
+ });
1862
+ const detail = needsWork
1863
+ ? [
1864
+ activeCount > 0
1865
+ ? dbText("builderBodiesQueued", { count: activeCount })
1866
+ : null,
1867
+ summary.error > 0
1868
+ ? dbText("builderBodySyncFailed", { count: summary.error })
1869
+ : null,
1870
+ ]
1871
+ .filter(Boolean)
1872
+ .join(" · ")
1873
+ : dbText("builderBodiesReadyLocally");
1874
+
1875
+ return (
1876
+ <div className="grid min-w-0 gap-2 rounded-lg border border-border bg-background p-3">
1877
+ <div className="flex min-w-0 items-start justify-between gap-3">
1878
+ <div className="min-w-0">
1879
+ <div className="text-xs font-medium">{dbText("builderBodySync")}</div>
1880
+ <div className="mt-0.5 text-xs text-muted-foreground">
1881
+ {hydratedLabel}
1882
+ </div>
1883
+ </div>
1884
+ {needsWork ? (
1885
+ <Button
1886
+ type="button"
1887
+ size="sm"
1888
+ variant="outline"
1889
+ className="h-7 shrink-0 px-2 text-xs"
1890
+ disabled={!canEdit || pending}
1891
+ onClick={onHydrate}
1892
+ >
1893
+ {pending ? (
1894
+ <Spinner className="mr-1 size-3.5" />
1895
+ ) : (
1896
+ <IconRefresh className="mr-1 size-3.5" />
1897
+ )}
1898
+ {summary.error > 0 ? dbText("retry") : dbText("resume")}
1899
+ </Button>
1900
+ ) : null}
1901
+ </div>
1902
+ <div className="h-1.5 overflow-hidden rounded-full bg-muted">
1903
+ <div
1904
+ className="h-full rounded-full bg-foreground transition-[width]"
1905
+ style={{
1906
+ width: `${Math.round((summary.hydrated / summary.total) * 100)}%`,
1907
+ }}
1908
+ />
1909
+ </div>
1910
+ <div className="break-words text-xs text-muted-foreground">{detail}</div>
1911
+ </div>
1912
+ );
1913
+ }
1914
+
1915
+ const DETAIL_FIELD_NAME_HINTS = [
1916
+ "name",
1917
+ "title",
1918
+ "bio",
1919
+ "description",
1920
+ "image",
1921
+ "photo",
1922
+ "avatar",
1923
+ "url",
1924
+ "handle",
1925
+ "email",
1926
+ ];
1927
+
1928
+ function shouldPreselectDetailField(
1929
+ field: ContentDatabaseSource["fields"][number],
1930
+ ) {
1931
+ const key = `${field.sourceFieldKey} ${field.sourceFieldLabel}`.toLowerCase();
1932
+ if (
1933
+ field.propertyId ||
1934
+ field.mappingType === "system" ||
1935
+ field.mappingType === "title"
1936
+ ) {
1937
+ return false;
1938
+ }
1939
+ if (/(\b|\.|_)(id|created|updated|published|rev)(\b|\.|_)/i.test(key)) {
1940
+ return false;
1941
+ }
1942
+ return DETAIL_FIELD_NAME_HINTS.some((hint) => key.includes(hint));
1943
+ }
1944
+
1945
+ function SourceDetailsFieldPicker({
1946
+ documentId,
1947
+ source,
1948
+ canEdit,
1949
+ pending,
1950
+ onDone,
1951
+ }: {
1952
+ documentId: string;
1953
+ source: ContentDatabaseSource | null;
1954
+ canEdit: boolean;
1955
+ pending: boolean;
1956
+ onDone: () => void;
1957
+ }) {
1958
+ const addField = useAddContentDatabaseSourceFieldProperty(documentId);
1959
+ const fields = useMemo(
1960
+ () =>
1961
+ (source?.fields ?? []).filter(
1962
+ (field) =>
1963
+ !field.propertyId &&
1964
+ field.mappingType !== "system" &&
1965
+ field.mappingType !== "title",
1966
+ ),
1967
+ [source],
1968
+ );
1969
+ const defaultSelectedIds = useMemo(
1970
+ () =>
1971
+ fields
1972
+ .filter(shouldPreselectDetailField)
1973
+ .slice(0, 8)
1974
+ .map((field) => field.id),
1975
+ [fields],
1976
+ );
1977
+ const [selectedIds, setSelectedIds] = useState<string[]>([]);
1978
+
1979
+ useEffect(() => {
1980
+ setSelectedIds(defaultSelectedIds);
1981
+ }, [defaultSelectedIds]);
1982
+
1983
+ if (!source) {
1984
+ return (
1985
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
1986
+ {dbText("thisSourceIsNoLongerConnected")}
1987
+ </div>
1988
+ );
1989
+ }
1990
+
1991
+ const selected = new Set(selectedIds);
1992
+ const toggleField = (fieldId: string) => {
1993
+ setSelectedIds((current) =>
1994
+ current.includes(fieldId)
1995
+ ? current.filter((id) => id !== fieldId)
1996
+ : [...current, fieldId],
1997
+ );
1998
+ };
1999
+ const addSelected = async () => {
2000
+ const sourceFieldIds = fields
2001
+ .filter((field) => selected.has(field.id))
2002
+ .map((field) => field.id);
2003
+ if (sourceFieldIds.length === 0) {
2004
+ onDone();
2005
+ return;
2006
+ }
2007
+ try {
2008
+ for (const sourceFieldId of sourceFieldIds) {
2009
+ await addField.mutateAsync({ documentId, sourceFieldId });
2010
+ }
2011
+ toast.success(dbText("detailFieldsAdded"), {
2012
+ description:
2013
+ sourceFieldIds.length === 1
2014
+ ? dbText("addedOneFieldFromSource")
2015
+ : dbText("addedFieldsFromSource", { count: sourceFieldIds.length }),
2016
+ });
2017
+ onDone();
2018
+ } catch (error) {
2019
+ toast.error(dbText("fieldsWereNotAdded"), {
2020
+ description:
2021
+ error instanceof Error ? error.message : dbText("tryAgain"),
2022
+ });
2023
+ }
2024
+ };
2025
+
2026
+ return (
2027
+ <div className="grid min-w-0 gap-3">
2028
+ <div className="grid min-w-0 gap-1 rounded-lg border border-border bg-background p-3">
2029
+ <div className="truncate text-sm font-medium" title={source.sourceName}>
2030
+ {source.sourceName}
2031
+ </div>
2032
+ <div className="break-words text-xs text-muted-foreground">
2033
+ {dbText("pickDetailsBecomeColumns")}
2034
+ </div>
2035
+ </div>
2036
+ {fields.length === 0 ? (
2037
+ <div className="break-words text-xs text-muted-foreground">
2038
+ {dbText("allAvailableDetailFieldsAlreadyVisible")}
2039
+ </div>
2040
+ ) : (
2041
+ <div className="grid min-w-0 gap-1">
2042
+ {fields.map((field) => (
2043
+ <button
2044
+ key={field.id}
2045
+ type="button"
2046
+ className="flex min-w-0 items-center gap-2 rounded-md px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2047
+ onClick={() => toggleField(field.id)}
2048
+ >
2049
+ <span
2050
+ className={cn(
2051
+ "flex size-4 shrink-0 items-center justify-center rounded border",
2052
+ selected.has(field.id)
2053
+ ? "border-[#2383e2] bg-[#2383e2] text-white"
2054
+ : "border-muted-foreground/40 text-transparent",
2055
+ )}
2056
+ >
2057
+ <IconCheck className="size-3" />
2058
+ </span>
2059
+ <span className="min-w-0 flex-1 truncate">
2060
+ {field.sourceFieldLabel}
2061
+ </span>
2062
+ <span className="shrink-0 text-[11px] text-muted-foreground">
2063
+ {field.sourceFieldType}
2064
+ </span>
2065
+ </button>
2066
+ ))}
2067
+ </div>
2068
+ )}
2069
+ <div className="flex justify-end gap-2">
2070
+ <Button
2071
+ type="button"
2072
+ size="sm"
2073
+ variant="outline"
2074
+ disabled={addField.isPending}
2075
+ onClick={onDone}
2076
+ >
2077
+ Done
2078
+ </Button>
2079
+ <Button
2080
+ type="button"
2081
+ size="sm"
2082
+ disabled={!canEdit || pending || addField.isPending}
2083
+ onClick={addSelected}
2084
+ >
2085
+ {addField.isPending ? (
2086
+ <Spinner className="mr-1.5 size-3.5" />
2087
+ ) : (
2088
+ <IconPlus className="mr-1.5 size-3.5" />
2089
+ )}
2090
+ Add selected
2091
+ </Button>
2092
+ </div>
2093
+ </div>
2094
+ );
2095
+ }
2096
+
2097
+ // A Builder space's data models, as drill-in rows. The attached model (if any)
2098
+ // is marked; selecting a row opens that model's leaf.
2099
+ function BuilderSpaceModelsView({
2100
+ attachedModelName,
2101
+ onOpenModel,
2102
+ }: {
2103
+ attachedModelName: string | null;
2104
+ onOpenModel: (model: BuilderCmsModelSummary) => void;
2105
+ }) {
2106
+ const modelsQuery = useBuilderCmsModels(true);
2107
+ const models = modelsQuery.data?.models ?? [];
2108
+ const [query, setQuery] = useState("");
2109
+
2110
+ if (modelsQuery.isLoading) {
2111
+ return (
2112
+ <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
2113
+ <Spinner className="size-3.5" />
2114
+ {dbText("loadingBuilderModels")}
2115
+ </div>
2116
+ );
2117
+ }
2118
+
2119
+ if (modelsQuery.data?.state === "unconfigured") {
2120
+ return (
2121
+ <div className="min-w-0 break-words text-xs text-muted-foreground">
2122
+ {dbText("builderIsntConnectedGoBackToConnectYour")}
2123
+ </div>
2124
+ );
2125
+ }
2126
+
2127
+ if (modelsQuery.data?.state === "error") {
2128
+ return (
2129
+ <div className="grid min-w-0 gap-2">
2130
+ <div className="text-xs text-destructive">
2131
+ {modelsQuery.data.message ?? "Builder models could not be loaded."}
2132
+ </div>
2133
+ <Button
2134
+ type="button"
2135
+ size="sm"
2136
+ variant="outline"
2137
+ onClick={() => modelsQuery.refetch()}
2138
+ >
2139
+ <IconRefresh className="mr-1.5 size-3.5" />
2140
+ Retry
2141
+ </Button>
2142
+ </div>
2143
+ );
2144
+ }
2145
+
2146
+ if (models.length === 0) {
2147
+ return (
2148
+ <div className="grid min-w-0 gap-2">
2149
+ <div className="text-xs text-muted-foreground">
2150
+ {dbText("noBuilderModelsWereFoundInThisSpace")}
2151
+ </div>
2152
+ <Button
2153
+ type="button"
2154
+ size="sm"
2155
+ variant="outline"
2156
+ disabled={modelsQuery.isFetching}
2157
+ onClick={() => modelsQuery.refetch()}
2158
+ >
2159
+ {modelsQuery.isFetching ? (
2160
+ <Spinner className="mr-1.5 size-3.5" />
2161
+ ) : (
2162
+ <IconRefresh className="mr-1.5 size-3.5" />
2163
+ )}
2164
+ Refresh
2165
+ </Button>
2166
+ </div>
2167
+ );
2168
+ }
2169
+
2170
+ const normalizedQuery = query.trim().toLowerCase();
2171
+ const matchesQuery = (model: BuilderCmsModelSummary) =>
2172
+ !normalizedQuery ||
2173
+ model.displayName.toLowerCase().includes(normalizedQuery) ||
2174
+ model.name.toLowerCase().includes(normalizedQuery);
2175
+ const filtered = models.filter(matchesQuery);
2176
+ const attachedModels = filtered.filter(
2177
+ (model) => attachedModelName === model.name,
2178
+ );
2179
+ const otherModels = filtered.filter(
2180
+ (model) => attachedModelName !== model.name,
2181
+ );
2182
+
2183
+ const renderRow = (model: BuilderCmsModelSummary) => {
2184
+ const isAttached = attachedModelName === model.name;
2185
+ return (
2186
+ <button
2187
+ key={model.id}
2188
+ type="button"
2189
+ className="flex h-9 w-full items-center gap-2 rounded-md px-2 text-left text-sm text-foreground transition-colors hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2190
+ onClick={() => onOpenModel(model)}
2191
+ >
2192
+ <span className="flex size-5 shrink-0 items-center justify-center text-muted-foreground">
2193
+ <IconList className="size-4" />
2194
+ </span>
2195
+ <span className="min-w-0 flex-1 truncate" title={model.displayName}>
2196
+ {model.displayName}
2197
+ </span>
2198
+ {isAttached ? (
2199
+ <span className="inline-flex shrink-0 items-center gap-1 rounded-full border border-border px-1.5 py-0.5 text-[10px] font-medium text-foreground">
2200
+ <IconCheck className="size-3" />
2201
+ Attached
2202
+ </span>
2203
+ ) : null}
2204
+ <IconChevronRight className="size-4 shrink-0 text-muted-foreground" />
2205
+ </button>
2206
+ );
2207
+ };
2208
+
2209
+ return (
2210
+ <div className="grid min-w-0 gap-2">
2211
+ <div className="relative min-w-0">
2212
+ <IconSearch className="pointer-events-none absolute left-2 top-1/2 size-3.5 -translate-y-1/2 text-muted-foreground" />
2213
+ <Input
2214
+ type="search"
2215
+ value={query}
2216
+ onChange={(event) => setQuery(event.target.value)}
2217
+ placeholder={dbText("searchModels")}
2218
+ aria-label={dbText("searchBuilderModels")}
2219
+ className="h-8 min-w-0 pl-7 text-sm"
2220
+ />
2221
+ </div>
2222
+
2223
+ {attachedModels.length > 0 ? (
2224
+ <div className="grid min-w-0 gap-1.5">
2225
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
2226
+ {dbText("alreadyAttached")}
2227
+ </div>
2228
+ {attachedModels.map(renderRow)}
2229
+ </div>
2230
+ ) : null}
2231
+
2232
+ <div className="grid min-w-0 gap-1.5">
2233
+ {attachedModels.length > 0 && otherModels.length > 0 ? (
2234
+ <div className="px-2 text-[11px] font-medium uppercase tracking-wide text-muted-foreground">
2235
+ {dbText("allModels")}
2236
+ </div>
2237
+ ) : null}
2238
+ {otherModels.map(renderRow)}
2239
+ {filtered.length === 0 ? (
2240
+ <div className="px-2 py-1 text-xs text-muted-foreground">
2241
+ No models match “{query.trim()}”.
2242
+ </div>
2243
+ ) : null}
2244
+ </div>
2245
+ </div>
2246
+ );
2247
+ }
2248
+
2249
+ function SourceChangeSetReviewCard({
2250
+ changeSet,
2251
+ source,
2252
+ }: {
2253
+ changeSet: ContentDatabaseSourceChangeSet;
2254
+ source: ContentDatabaseSource;
2255
+ }) {
2256
+ const latestReview =
2257
+ changeSet.reviewEvents[changeSet.reviewEvents.length - 1] ?? null;
2258
+ const latestExecution =
2259
+ changeSet.executions[changeSet.executions.length - 1] ?? null;
2260
+ const dryRunStatus = latestExecution
2261
+ ? builderExecutionDryRunStatus(latestExecution.payload)
2262
+ : null;
2263
+
2264
+ return (
2265
+ <div className="min-w-0 rounded-md border border-border/70 px-2 py-1.5">
2266
+ <div className="flex min-w-0 items-start justify-between gap-2">
2267
+ <span
2268
+ className="min-w-0 break-words font-medium leading-snug"
2269
+ title={changeSet.summary}
2270
+ >
2271
+ {changeSet.summary}
2272
+ </span>
2273
+ <span className="shrink-0 rounded border border-border px-1.5 py-0.5 text-[10px] uppercase tracking-wide text-muted-foreground">
2274
+ {changeSet.state.replace(/_/g, " ")}
2275
+ </span>
2276
+ </div>
2277
+
2278
+ <div className="mt-2 flex flex-wrap gap-1.5 text-[11px]">
2279
+ <span className={sourceRiskClass(changeSet.riskLevel)}>
2280
+ {changeSet.riskLevel} risk
2281
+ </span>
2282
+ <span className="rounded border border-border px-1.5 py-0.5 text-muted-foreground">
2283
+ {changeSet.conflictState === "source_changed"
2284
+ ? "source changed"
2285
+ : "no conflict"}
2286
+ </span>
2287
+ <span className="rounded border border-border px-1.5 py-0.5 text-muted-foreground">
2288
+ {sourcePushModeLabel(changeSet.pushMode)}
2289
+ </span>
2290
+ <span className="rounded border border-border px-1.5 py-0.5 text-muted-foreground">
2291
+ {changeSet.localOnly ? "local-only" : "external write"}
2292
+ </span>
2293
+ </div>
2294
+
2295
+ <div className="mt-2 grid min-w-0 gap-1.5">
2296
+ {changeSet.fieldChanges.slice(0, 3).map((field) => (
2297
+ <div
2298
+ key={`${changeSet.id}-${field.localFieldKey}`}
2299
+ className="min-w-0 rounded border border-border/60 bg-muted/20 p-1.5 text-xs"
2300
+ >
2301
+ <div className="font-medium">
2302
+ {field.propertyName ?? field.sourceFieldKey}
2303
+ </div>
2304
+ <div className="mt-1 grid min-w-0 gap-1 text-muted-foreground">
2305
+ <div className="min-w-0 break-words">
2306
+ Current: {sourceValueText(field.currentValue)}
2307
+ </div>
2308
+ <div className="min-w-0 break-words">
2309
+ Proposed: {sourceValueText(field.proposedValue)}
2310
+ </div>
2311
+ </div>
2312
+ </div>
2313
+ ))}
2314
+ {changeSet.fieldChanges.length > 3 ? (
2315
+ <div className="text-xs text-muted-foreground">
2316
+ +{changeSet.fieldChanges.length - 3} more field changes
2317
+ </div>
2318
+ ) : null}
2319
+ {changeSet.bodyChange ? (
2320
+ <div className="rounded border border-border/60 bg-muted/20 p-1.5 text-xs">
2321
+ <div className="font-medium">{changeSet.bodyChange.summary}</div>
2322
+ <div className="mt-1 text-muted-foreground">
2323
+ {dbText("bodyDiff")}
2324
+ </div>
2325
+ </div>
2326
+ ) : null}
2327
+ </div>
2328
+
2329
+ <div className="mt-2 break-words text-xs text-muted-foreground">
2330
+ {changeSet.riskReasons.join(", ")}
2331
+ {" • "}
2332
+ {source.capabilities.liveWritesEnabled
2333
+ ? "live writes enabled"
2334
+ : "live writes disabled"}
2335
+ {" • "}
2336
+ {formatSourceTimestamp(changeSet.updatedAt)}
2337
+ </div>
2338
+
2339
+ {latestReview ? (
2340
+ <div className="mt-2 rounded border border-border/60 bg-muted/20 p-1.5 text-xs text-muted-foreground">
2341
+ {latestReview.decision} by {latestReview.reviewerEmail}
2342
+ {" • "}
2343
+ {formatSourceTimestamp(latestReview.createdAt)}
2344
+ </div>
2345
+ ) : null}
2346
+
2347
+ {latestExecution ? (
2348
+ <div className="mt-2 rounded border border-border/60 bg-muted/20 p-1.5 text-xs">
2349
+ <div className="flex min-w-0 items-center justify-between gap-2">
2350
+ <span className="font-medium">{dbText("executionGate")}</span>
2351
+ <span className="shrink-0 text-[11px] uppercase tracking-wide text-muted-foreground">
2352
+ {latestExecution.state.replace(/_/g, " ")}
2353
+ </span>
2354
+ </div>
2355
+ <div className="mt-1 break-words text-muted-foreground">
2356
+ {latestExecution.summary}
2357
+ </div>
2358
+ {builderExecutionRequestLine(latestExecution.payload) ? (
2359
+ <div className="mt-1 break-words text-muted-foreground">
2360
+ Would call {builderExecutionRequestLine(latestExecution.payload)}
2361
+ </div>
2362
+ ) : null}
2363
+ {builderExecutionBlockers(latestExecution.payload).length > 0 ? (
2364
+ <div className="mt-1 grid gap-1 text-muted-foreground">
2365
+ {builderExecutionBlockers(latestExecution.payload)
2366
+ .slice(0, 2)
2367
+ .map((blocker) => (
2368
+ <div key={blocker} className="break-words">
2369
+ Blocked: {blocker}
2370
+ </div>
2371
+ ))}
2372
+ </div>
2373
+ ) : null}
2374
+ {dryRunStatus ? (
2375
+ <div className="mt-1 break-words text-muted-foreground">
2376
+ Dry run {dryRunStatus.status}
2377
+ {dryRunStatus.validatedAt
2378
+ ? ` • ${formatSourceTimestamp(dryRunStatus.validatedAt)}`
2379
+ : ""}
2380
+ </div>
2381
+ ) : null}
2382
+ {latestExecution.lastError ? (
2383
+ <div className="mt-1 break-words text-destructive">
2384
+ {latestExecution.lastError}
2385
+ </div>
2386
+ ) : null}
2387
+ <div className="mt-1 break-all text-muted-foreground">
2388
+ {latestExecution.idempotencyKey}
2389
+ </div>
2390
+ </div>
2391
+ ) : null}
2392
+ </div>
2393
+ );
2394
+ }
2395
+
2396
+ function SourceMetadataRow({ label, value }: { label: string; value: string }) {
2397
+ return (
2398
+ <div className="flex min-w-0 items-start justify-between gap-3 text-xs">
2399
+ <span className="shrink-0 text-muted-foreground">{label}</span>
2400
+ <span
2401
+ className="min-w-0 max-w-[65%] break-words text-right"
2402
+ title={value}
2403
+ >
2404
+ {value}
2405
+ </span>
2406
+ </div>
2407
+ );
2408
+ }
2409
+
2410
+ function sourceRiskClass(risk: ContentDatabaseSourceChangeSet["riskLevel"]) {
2411
+ return cn(
2412
+ "rounded border px-1.5 py-0.5",
2413
+ risk === "high"
2414
+ ? "border-destructive/40 bg-destructive/10 text-destructive"
2415
+ : risk === "medium"
2416
+ ? "border-amber-300 bg-amber-50 text-amber-700 dark:border-amber-800 dark:bg-amber-950/30 dark:text-amber-300"
2417
+ : "border-emerald-300 bg-emerald-50 text-emerald-700 dark:border-emerald-800 dark:bg-emerald-950/30 dark:text-emerald-300",
2418
+ );
2419
+ }
2420
+
2421
+ function sourceValueText(value: DocumentPropertyValue) {
2422
+ if (value === null || value === undefined || value === "") return "empty";
2423
+ if (Array.isArray(value)) return value.join(", ") || "empty";
2424
+ if (typeof value === "object") return JSON.stringify(value);
2425
+ return String(value);
2426
+ }
2427
+
2428
+ function builderExecutionRequestLine(payload: Record<string, unknown>) {
2429
+ const request =
2430
+ payload.request &&
2431
+ typeof payload.request === "object" &&
2432
+ !Array.isArray(payload.request)
2433
+ ? (payload.request as Record<string, unknown>)
2434
+ : null;
2435
+ const method =
2436
+ typeof request?.method === "string" ? request.method.toUpperCase() : null;
2437
+ const path = typeof request?.path === "string" ? request.path : null;
2438
+ if (!request || !method || !path) return null;
2439
+
2440
+ const query =
2441
+ request.query && typeof request.query === "object"
2442
+ ? (request.query as Record<string, unknown>)
2443
+ : null;
2444
+ const queryText = query
2445
+ ? Object.entries(query)
2446
+ .filter(
2447
+ (entry): entry is [string, string] => typeof entry[1] === "string",
2448
+ )
2449
+ .map(([key, value]) => `${key}=${value}`)
2450
+ .join("&")
2451
+ : "";
2452
+ return `${method} ${path}${queryText ? `?${queryText}` : ""}`;
2453
+ }
2454
+
2455
+ function builderExecutionBlockers(payload: Record<string, unknown>) {
2456
+ const safety =
2457
+ payload.safety &&
2458
+ typeof payload.safety === "object" &&
2459
+ !Array.isArray(payload.safety)
2460
+ ? (payload.safety as Record<string, unknown>)
2461
+ : null;
2462
+ const blockers = safety?.blockers;
2463
+ return Array.isArray(blockers)
2464
+ ? blockers.filter(
2465
+ (blocker): blocker is string => typeof blocker === "string",
2466
+ )
2467
+ : [];
2468
+ }
2469
+
2470
+ function builderExecutionDryRunStatus(payload: Record<string, unknown>) {
2471
+ const dryRun =
2472
+ payload.dryRun &&
2473
+ typeof payload.dryRun === "object" &&
2474
+ !Array.isArray(payload.dryRun)
2475
+ ? (payload.dryRun as Record<string, unknown>)
2476
+ : null;
2477
+ const status =
2478
+ dryRun?.status === "validated" ||
2479
+ dryRun?.status === "stale" ||
2480
+ dryRun?.status === "blocked"
2481
+ ? dryRun.status
2482
+ : null;
2483
+ if (!status) return null;
2484
+ return {
2485
+ status,
2486
+ validatedAt:
2487
+ typeof dryRun?.validatedAt === "string" ? dryRun.validatedAt : null,
2488
+ };
2489
+ }
2490
+
2491
+ export function formatSourceTimestamp(value: string) {
2492
+ const date = new Date(value);
2493
+ return Number.isNaN(date.getTime()) ? value : date.toLocaleString();
2494
+ }
2495
+
2496
+ function formatRelativeSyncTime(value: string | null): string | null {
2497
+ if (!value) return null;
2498
+ const ms = new Date(value).getTime();
2499
+ if (Number.isNaN(ms)) return null;
2500
+ const diff = Date.now() - ms;
2501
+ if (diff < 60_000) return "just now";
2502
+ const minutes = Math.floor(diff / 60_000);
2503
+ if (minutes < 60) return `${minutes}m ago`;
2504
+ const hours = Math.floor(minutes / 60);
2505
+ if (hours < 24) return `${hours}h ago`;
2506
+ return `${Math.floor(hours / 24)}d ago`;
2507
+ }
2508
+
2509
+ function sourceBuilderReadModeSummary(source: ContentDatabaseSource) {
2510
+ if (source.metadata.liveReadConfigured) return "Builder API read-only";
2511
+ if (source.metadata.readMode === "fixture") {
2512
+ return "Local fixture; Builder credentials unavailable";
2513
+ }
2514
+ return "Local fixture";
2515
+ }
2516
+
2517
+ function sourcePushModeLabel(
2518
+ mode: ContentDatabaseSource["metadata"]["pushMode"] | null | undefined,
2519
+ ) {
2520
+ if (mode === "autosave") return "Save revision / autosave";
2521
+ if (mode === "draft") return "Draft";
2522
+ if (mode === "publish") return "Publish";
2523
+ return "No push";
2524
+ }
2525
+
2526
+ export function sourceFieldMappingForColumn(
2527
+ source: ContentDatabaseSource | null,
2528
+ columnKey: ColumnKey,
2529
+ ) {
2530
+ if (!source) return null;
2531
+ if (columnKey === "name") {
2532
+ return (
2533
+ source.fields.find((field) => field.mappingType === "title") ??
2534
+ source.fields.find((field) => field.localFieldKey === "title") ??
2535
+ null
2536
+ );
2537
+ }
2538
+ return (
2539
+ source.fields.find((field) => field.propertyId === columnKey) ??
2540
+ source.fields.find((field) => field.localFieldKey === columnKey) ??
2541
+ null
2542
+ );
2543
+ }
2544
+
2545
+ function databaseViewIconElement(
2546
+ type: ContentDatabaseViewType,
2547
+ className = "size-4",
2548
+ ) {
2549
+ const Icon = databaseViewIcon(type);
2550
+ return <Icon className={className} />;
2551
+ }
2552
+
2553
+ function DatabaseSettingsRow({
2554
+ icon,
2555
+ label,
2556
+ value,
2557
+ badgeCount = 0,
2558
+ disabled = false,
2559
+ onClick,
2560
+ }: {
2561
+ icon: ReactNode;
2562
+ label: string;
2563
+ value?: string;
2564
+ badgeCount?: number;
2565
+ disabled?: boolean;
2566
+ onClick?: () => void;
2567
+ }) {
2568
+ return (
2569
+ <button
2570
+ type="button"
2571
+ disabled={disabled || !onClick}
2572
+ className={cn(
2573
+ "flex h-9 w-full min-w-0 items-center gap-2 rounded-md px-2 text-left text-sm transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2574
+ disabled || !onClick
2575
+ ? "cursor-default text-muted-foreground/60"
2576
+ : "text-foreground hover:bg-muted",
2577
+ )}
2578
+ onClick={(event: ReactMouseEvent<HTMLButtonElement>) => {
2579
+ event.stopPropagation();
2580
+ onClick?.();
2581
+ }}
2582
+ >
2583
+ <span className="flex size-5 shrink-0 items-center justify-center text-muted-foreground">
2584
+ {icon}
2585
+ </span>
2586
+ <span className="min-w-0 flex-1 truncate">{label}</span>
2587
+ {value ? (
2588
+ <span className="max-w-28 shrink-0 truncate text-xs text-muted-foreground">
2589
+ {value}
2590
+ </span>
2591
+ ) : null}
2592
+ {badgeCount > 0 ? (
2593
+ <span className="flex size-3.5 shrink-0 items-center justify-center rounded-full bg-foreground text-[9px] leading-none text-background">
2594
+ {badgeCount}
2595
+ </span>
2596
+ ) : null}
2597
+ {onClick && !disabled ? (
2598
+ <IconChevronRight className="size-4 shrink-0 text-muted-foreground" />
2599
+ ) : null}
2600
+ </button>
2601
+ );
2602
+ }
2603
+
2604
+ function DatabaseSettingsSwitch({
2605
+ label,
2606
+ checked,
2607
+ disabled = false,
2608
+ onCheckedChange,
2609
+ }: {
2610
+ label: string;
2611
+ checked: boolean;
2612
+ disabled?: boolean;
2613
+ onCheckedChange: (checked: boolean) => void;
2614
+ }) {
2615
+ return (
2616
+ <button
2617
+ type="button"
2618
+ role="switch"
2619
+ aria-checked={checked}
2620
+ disabled={disabled}
2621
+ className="flex h-9 w-full items-center justify-between rounded-md px-2 text-left text-sm text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:text-muted-foreground/60 disabled:hover:bg-transparent"
2622
+ onClick={() => onCheckedChange(!checked)}
2623
+ >
2624
+ <span className="truncate">{label}</span>
2625
+ <span
2626
+ className={cn(
2627
+ "relative h-5 w-9 rounded-full transition-colors",
2628
+ checked ? "bg-[#2383e2]" : "bg-muted-foreground/25",
2629
+ )}
2630
+ >
2631
+ <span
2632
+ className={cn(
2633
+ "absolute top-0.5 size-4 rounded-full bg-background shadow-sm transition-transform",
2634
+ checked ? "left-0.5 translate-x-4" : "left-0.5",
2635
+ )}
2636
+ />
2637
+ </span>
2638
+ </button>
2639
+ );
2640
+ }
2641
+
2642
+ function DatabaseSettingsLayoutPanel({
2643
+ activeView,
2644
+ onViewTypeChange,
2645
+ onWrapCellsChange,
2646
+ onOpenPagesInChange,
2647
+ }: {
2648
+ activeView: ContentDatabaseView;
2649
+ onViewTypeChange: (type: ContentDatabaseViewType) => void;
2650
+ onWrapCellsChange: (wrapCells: boolean) => void;
2651
+ onOpenPagesInChange: (openPagesIn: ContentDatabaseOpenPagesIn) => void;
2652
+ }) {
2653
+ const wrapCells = activeView.wrapCells === true;
2654
+ const openPagesIn = activeView.openPagesIn ?? "preview";
2655
+
2656
+ return (
2657
+ <div className="grid gap-4">
2658
+ <div className="grid grid-cols-3 gap-2">
2659
+ {DATABASE_VIEW_TYPES.map((type) => (
2660
+ <button
2661
+ key={type}
2662
+ type="button"
2663
+ aria-pressed={activeView.type === type}
2664
+ className={cn(
2665
+ "flex h-16 flex-col items-center justify-center gap-1 rounded-md border text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
2666
+ activeView.type === type
2667
+ ? "border-[#2383e2] bg-[#2383e2]/5 text-[#2383e2]"
2668
+ : "border-border text-muted-foreground hover:bg-muted hover:text-foreground",
2669
+ )}
2670
+ onClick={() => onViewTypeChange(type)}
2671
+ >
2672
+ {databaseViewIconElement(type, "size-4")}
2673
+ {databaseViewDefaultName(type)}
2674
+ </button>
2675
+ ))}
2676
+ </div>
2677
+ <div className="grid gap-1">
2678
+ <DatabaseSettingsSwitch
2679
+ label={dbText("wrapAllContent")}
2680
+ checked={wrapCells}
2681
+ disabled={activeView.type !== "table"}
2682
+ onCheckedChange={onWrapCellsChange}
2683
+ />
2684
+ </div>
2685
+ <DatabaseOpenPagesInSetting
2686
+ value={openPagesIn}
2687
+ onChange={onOpenPagesInChange}
2688
+ />
2689
+ </div>
2690
+ );
2691
+ }
2692
+
2693
+ function DatabaseOpenPagesInSetting({
2694
+ value,
2695
+ onChange,
2696
+ }: {
2697
+ value: ContentDatabaseOpenPagesIn;
2698
+ onChange: (value: ContentDatabaseOpenPagesIn) => void;
2699
+ }) {
2700
+ return (
2701
+ <DropdownMenu>
2702
+ <DropdownMenuTrigger asChild>
2703
+ <button
2704
+ type="button"
2705
+ className="flex h-9 w-full items-center justify-between rounded-md px-2 text-left text-sm text-foreground hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2706
+ >
2707
+ <span className="truncate">{dbText("openPagesIn")}</span>
2708
+ <span className="flex min-w-0 items-center gap-1.5 text-xs text-muted-foreground">
2709
+ <span className="max-w-28 truncate">
2710
+ {databaseOpenPagesInLabel(value)}
2711
+ </span>
2712
+ <IconChevronRight className="size-4 shrink-0" />
2713
+ </span>
2714
+ </button>
2715
+ </DropdownMenuTrigger>
2716
+ <DropdownMenuContent align="end" className="w-72 p-1">
2717
+ {DATABASE_OPEN_PAGES_IN.map((option) => {
2718
+ const Icon =
2719
+ option === "full_page" ? IconExternalLink : IconLayoutGrid;
2720
+ return (
2721
+ <DropdownMenuItem
2722
+ key={option}
2723
+ className="items-start gap-2 py-2"
2724
+ onSelect={(event) => {
2725
+ event.preventDefault();
2726
+ onChange(option);
2727
+ }}
2728
+ >
2729
+ <Icon className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
2730
+ <span className="min-w-0 flex-1">
2731
+ <span className="block text-sm font-medium">
2732
+ {databaseOpenPagesInLabel(option)}
2733
+ </span>
2734
+ <span className="block text-xs leading-4 text-muted-foreground">
2735
+ {databaseOpenPagesInDescription(option)}
2736
+ </span>
2737
+ </span>
2738
+ {value === option ? (
2739
+ <IconCheck className="mt-0.5 size-4 shrink-0 text-muted-foreground" />
2740
+ ) : null}
2741
+ </DropdownMenuItem>
2742
+ );
2743
+ })}
2744
+ </DropdownMenuContent>
2745
+ </DropdownMenu>
2746
+ );
2747
+ }
2748
+
2749
+ function DatabaseSettingsPropertyVisibilityPanel({
2750
+ documentId,
2751
+ properties,
2752
+ activeView,
2753
+ items,
2754
+ source,
2755
+ sources,
2756
+ hiddenCount,
2757
+ onPropertyHiddenChange,
2758
+ onPropertiesHiddenChange,
2759
+ }: {
2760
+ documentId: string;
2761
+ properties: DocumentProperty[];
2762
+ activeView: ContentDatabaseView;
2763
+ items: ContentDatabaseItem[];
2764
+ source: ContentDatabaseSource | null;
2765
+ sources: ContentDatabaseSource[];
2766
+ hiddenCount: number;
2767
+ onPropertyHiddenChange: (propertyId: string, hidden: boolean) => void;
2768
+ onPropertiesHiddenChange: (propertyIds: string[], hidden: boolean) => void;
2769
+ }) {
2770
+ const [query, setQuery] = useState("");
2771
+ const normalizedQuery = query.trim().toLowerCase();
2772
+ const filteredProperties = normalizedQuery
2773
+ ? properties.filter((property) =>
2774
+ property.definition.name.toLowerCase().includes(normalizedQuery),
2775
+ )
2776
+ : properties;
2777
+ const visibleCount = properties.filter((property) =>
2778
+ isDatabasePropertyVisibleInView(property, items, activeView),
2779
+ ).length;
2780
+ const propertyIds = properties.map((property) => property.definition.id);
2781
+
2782
+ return (
2783
+ <div className="grid gap-3">
2784
+ <div className="flex h-8 items-center gap-1 rounded-md border border-border bg-background px-2">
2785
+ <IconSearch className="size-3.5 shrink-0 text-muted-foreground" />
2786
+ <Input
2787
+ value={query}
2788
+ placeholder={dbText("searchProperties")}
2789
+ aria-label={dbText("searchProperties")}
2790
+ onChange={(event) => setQuery(event.target.value)}
2791
+ className="h-7 border-0 bg-transparent px-0 text-xs shadow-none focus-visible:ring-0"
2792
+ />
2793
+ </div>
2794
+ <div className="flex items-center justify-between gap-2 text-xs text-muted-foreground">
2795
+ <span>
2796
+ {visibleCount} shown, {properties.length - visibleCount} hidden
2797
+ </span>
2798
+ <div className="flex items-center gap-1">
2799
+ <Button
2800
+ type="button"
2801
+ size="sm"
2802
+ variant="ghost"
2803
+ className="h-7 px-2 text-xs"
2804
+ disabled={hiddenCount === 0}
2805
+ onClick={() => onPropertiesHiddenChange(propertyIds, false)}
2806
+ >
2807
+ {dbText("showAll")}
2808
+ </Button>
2809
+ <Button
2810
+ type="button"
2811
+ size="sm"
2812
+ variant="ghost"
2813
+ className="h-7 px-2 text-xs"
2814
+ disabled={visibleCount === 0}
2815
+ onClick={() => onPropertiesHiddenChange(propertyIds, true)}
2816
+ >
2817
+ {dbText("hideAll")}
2818
+ </Button>
2819
+ </div>
2820
+ </div>
2821
+ <div className="grid gap-1">
2822
+ {filteredProperties.map((property) => {
2823
+ const Icon = TYPE_ICONS[property.definition.type];
2824
+ const visible = isDatabasePropertyVisibleInView(
2825
+ property,
2826
+ items,
2827
+ activeView,
2828
+ );
2829
+ return (
2830
+ <button
2831
+ key={property.definition.id}
2832
+ type="button"
2833
+ className="flex h-9 items-center gap-2 rounded-md px-2 text-left text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
2834
+ onClick={() =>
2835
+ onPropertyHiddenChange(property.definition.id, visible)
2836
+ }
2837
+ >
2838
+ <Icon className="size-4 shrink-0 text-muted-foreground" />
2839
+ <span className="min-w-0 flex-1 truncate">
2840
+ {property.definition.name}
2841
+ </span>
2842
+ <span className="text-xs text-muted-foreground">
2843
+ {visible ? "Shown" : "Hidden"}
2844
+ </span>
2845
+ {visible ? (
2846
+ <IconCheck className="size-4 shrink-0 text-muted-foreground" />
2847
+ ) : null}
2848
+ </button>
2849
+ );
2850
+ })}
2851
+ {filteredProperties.length === 0 ? (
2852
+ <div className="px-2 py-4 text-sm text-muted-foreground">
2853
+ {dbText("noMatchingProperties")}
2854
+ </div>
2855
+ ) : null}
2856
+ </div>
2857
+ <div className="border-t border-border/70 pt-3">
2858
+ <AddProperty
2859
+ documentId={documentId}
2860
+ label={dbText("newProperty")}
2861
+ source={source}
2862
+ sources={sources}
2863
+ />
2864
+ </div>
2865
+ </div>
2866
+ );
2867
+ }
2868
+
2869
+ function DatabaseSettingsGroupPanel({
2870
+ activeView,
2871
+ properties,
2872
+ groupIds,
2873
+ onGroupByChange,
2874
+ onHideEmptyGroupsChange,
2875
+ onGroupsCollapsedChange,
2876
+ }: {
2877
+ activeView: Pick<
2878
+ ContentDatabaseView,
2879
+ "type" | "groupByPropertyId" | "hideEmptyGroups"
2880
+ >;
2881
+ properties: DocumentProperty[];
2882
+ groupIds: string[];
2883
+ onGroupByChange: (propertyId: string | null) => void;
2884
+ onHideEmptyGroupsChange: (hideEmptyGroups: boolean) => void;
2885
+ onGroupsCollapsedChange: (groupIds: string[], collapsed: boolean) => void;
2886
+ }) {
2887
+ const [propertyQuery, setPropertyQuery] = useState("");
2888
+ const groupableProperties = databaseViewGroupableProperties(properties);
2889
+ const groupProperty = databaseViewGroupingProperty(activeView, properties);
2890
+ const hideEmptyGroups = activeView.hideEmptyGroups === true;
2891
+ const groupPropertyItems = databasePropertyPickerItems(
2892
+ groupableProperties,
2893
+ propertyQuery,
2894
+ { includeName: false },
2895
+ );
2896
+ const canGroupView =
2897
+ activeView.type === "table" ||
2898
+ activeView.type === "list" ||
2899
+ activeView.type === "gallery";
2900
+
2901
+ return (
2902
+ <div className="grid gap-3">
2903
+ <div className="flex h-8 items-center gap-1 rounded-md border border-border bg-background px-2">
2904
+ <IconSearch className="size-3.5 shrink-0 text-muted-foreground" />
2905
+ <Input
2906
+ value={propertyQuery}
2907
+ placeholder={dbText("searchProperties")}
2908
+ aria-label={dbText("searchGroupProperties")}
2909
+ disabled={!canGroupView}
2910
+ onChange={(event) => setPropertyQuery(event.target.value)}
2911
+ className="h-7 border-0 bg-transparent px-0 text-xs shadow-none focus-visible:ring-0"
2912
+ />
2913
+ </div>
2914
+ <div className="grid gap-1">
2915
+ <button
2916
+ type="button"
2917
+ disabled={!canGroupView}
2918
+ className="flex h-9 items-center gap-2 rounded-md px-2 text-left text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:text-muted-foreground/60 disabled:hover:bg-transparent"
2919
+ onClick={() => onGroupByChange(null)}
2920
+ >
2921
+ <IconX className="size-4 text-muted-foreground" />
2922
+ <span className="flex-1">None</span>
2923
+ {!groupProperty ? (
2924
+ <IconCheck className="size-4 text-muted-foreground" />
2925
+ ) : null}
2926
+ </button>
2927
+ {groupPropertyItems.map((item) => {
2928
+ const Icon =
2929
+ item.type === "name" ? IconFileText : TYPE_ICONS[item.type];
2930
+ return (
2931
+ <button
2932
+ key={item.key}
2933
+ type="button"
2934
+ disabled={!canGroupView}
2935
+ className="flex h-9 items-center gap-2 rounded-md px-2 text-left text-sm hover:bg-muted focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-default disabled:text-muted-foreground/60 disabled:hover:bg-transparent"
2936
+ onClick={() => onGroupByChange(item.key)}
2937
+ >
2938
+ <Icon className="size-4 text-muted-foreground" />
2939
+ <span className="min-w-0 flex-1 truncate">{item.label}</span>
2940
+ {groupProperty?.definition.id === item.key ? (
2941
+ <IconCheck className="size-4 text-muted-foreground" />
2942
+ ) : null}
2943
+ </button>
2944
+ );
2945
+ })}
2946
+ </div>
2947
+ {groupableProperties.length === 0 ? (
2948
+ <div className="px-2 text-xs text-muted-foreground">
2949
+ {dbText("addAStatusSelectMultiSelectOrCheckboxPropertyToGroup")}
2950
+ </div>
2951
+ ) : null}
2952
+ {groupProperty ? (
2953
+ <div className="grid gap-1 border-t border-border/70 pt-3">
2954
+ <DatabaseSettingsSwitch
2955
+ label={dbText("hideEmptyGroups")}
2956
+ checked={hideEmptyGroups}
2957
+ onCheckedChange={onHideEmptyGroupsChange}
2958
+ />
2959
+ <div className="flex gap-1">
2960
+ <Button
2961
+ type="button"
2962
+ size="sm"
2963
+ variant="ghost"
2964
+ className="h-8 flex-1 text-xs"
2965
+ disabled={groupIds.length === 0}
2966
+ onClick={() => onGroupsCollapsedChange(groupIds, true)}
2967
+ >
2968
+ {dbText("collapseAll")}
2969
+ </Button>
2970
+ <Button
2971
+ type="button"
2972
+ size="sm"
2973
+ variant="ghost"
2974
+ className="h-8 flex-1 text-xs"
2975
+ disabled={groupIds.length === 0}
2976
+ onClick={() => onGroupsCollapsedChange(groupIds, false)}
2977
+ >
2978
+ {dbText("expandAll")}
2979
+ </Button>
2980
+ </div>
2981
+ </div>
2982
+ ) : null}
2983
+ </div>
2984
+ );
2985
+ }
2986
+
2987
+ type DatabasePropertyPickerOption = {
2988
+ key: string;
2989
+ label: string;
2990
+ type: DocumentPropertyType | "name";
2991
+ };
2992
+
2993
+ function DatabasePropertyPickerSearch({
2994
+ value,
2995
+ onChange,
2996
+ }: {
2997
+ value: string;
2998
+ onChange: (value: string) => void;
2999
+ }) {
3000
+ return (
3001
+ <div className="border-b border-border/70 p-1">
3002
+ <div className="flex h-8 items-center gap-2 rounded border border-input bg-background px-2">
3003
+ <IconSearch className="size-3.5 shrink-0 text-muted-foreground" />
3004
+ <Input
3005
+ value={value}
3006
+ onChange={(event) => onChange(event.target.value)}
3007
+ onKeyDown={(event) => event.stopPropagation()}
3008
+ placeholder={dbText("searchProperties")}
3009
+ aria-label={dbText("searchProperties")}
3010
+ className="h-6 border-0 bg-transparent px-0 text-xs shadow-none focus-visible:ring-0"
3011
+ />
3012
+ </div>
3013
+ </div>
3014
+ );
3015
+ }
3016
+
3017
+ function DatabasePropertyPickerItem({
3018
+ item,
3019
+ selected,
3020
+ onSelect,
3021
+ }: {
3022
+ item: DatabasePropertyPickerOption;
3023
+ selected: boolean;
3024
+ onSelect: (key: string, label: string) => void;
3025
+ }) {
3026
+ const Icon = item.type === "name" ? IconFileText : TYPE_ICONS[item.type];
3027
+ return (
3028
+ <DropdownMenuItem
3029
+ onSelect={(event) => {
3030
+ event.preventDefault();
3031
+ onSelect(item.key, item.label);
3032
+ }}
3033
+ >
3034
+ <Icon className="mr-2 size-4 text-muted-foreground" />
3035
+ <span className="min-w-0 flex-1 truncate">{item.label}</span>
3036
+ {selected ? <IconCheck className="size-4 text-muted-foreground" /> : null}
3037
+ </DropdownMenuItem>
3038
+ );
3039
+ }
3040
+
3041
+ export function DatabasePropertyPickerSubContent({
3042
+ properties,
3043
+ selectedKey,
3044
+ includeName,
3045
+ onSelect,
3046
+ }: {
3047
+ properties: DocumentProperty[];
3048
+ selectedKey: string;
3049
+ includeName?: boolean;
3050
+ onSelect: (key: string, label: string) => void;
3051
+ }) {
3052
+ const [query, setQuery] = useState("");
3053
+ const items = databasePropertyPickerItems(properties, query, { includeName });
3054
+
3055
+ return (
3056
+ <DropdownMenuSubContent className="max-h-80 w-64 overflow-auto">
3057
+ <DatabasePropertyPickerSearch value={query} onChange={setQuery} />
3058
+ {items.map((item) => (
3059
+ <DatabasePropertyPickerItem
3060
+ key={item.key}
3061
+ item={item}
3062
+ selected={selectedKey === item.key}
3063
+ onSelect={onSelect}
3064
+ />
3065
+ ))}
3066
+ {items.length === 0 ? (
3067
+ <div className="px-2 py-1.5 text-xs text-muted-foreground">
3068
+ {dbText("noPropertiesFound")}
3069
+ </div>
3070
+ ) : null}
3071
+ </DropdownMenuSubContent>
3072
+ );
3073
+ }
3074
+
3075
+ export function DatabaseGroupMenu({
3076
+ activeView,
3077
+ properties,
3078
+ groupIds,
3079
+ onGroupByChange,
3080
+ onHideEmptyGroupsChange,
3081
+ onGroupsCollapsedChange,
3082
+ }: {
3083
+ activeView: Pick<
3084
+ ContentDatabaseView,
3085
+ "type" | "groupByPropertyId" | "hideEmptyGroups"
3086
+ >;
3087
+ properties: DocumentProperty[];
3088
+ groupIds: string[];
3089
+ onGroupByChange: (propertyId: string | null) => void;
3090
+ onHideEmptyGroupsChange: (hideEmptyGroups: boolean) => void;
3091
+ onGroupsCollapsedChange: (groupIds: string[], collapsed: boolean) => void;
3092
+ }) {
3093
+ const groupableProperties = databaseViewGroupableProperties(properties);
3094
+ const groupProperty = databaseViewGroupingProperty(activeView, properties);
3095
+ const hideEmptyGroups = activeView.hideEmptyGroups === true;
3096
+ const [propertyQuery, setPropertyQuery] = useState("");
3097
+ const groupPropertyItems = databasePropertyPickerItems(
3098
+ groupableProperties,
3099
+ propertyQuery,
3100
+ { includeName: false },
3101
+ );
3102
+ const canGroupView =
3103
+ activeView.type === "table" ||
3104
+ activeView.type === "list" ||
3105
+ activeView.type === "gallery";
3106
+
3107
+ return (
3108
+ <DropdownMenu>
3109
+ <DropdownMenuTrigger asChild>
3110
+ <Button
3111
+ type="button"
3112
+ variant="ghost"
3113
+ size="sm"
3114
+ disabled={!canGroupView}
3115
+ aria-label={
3116
+ groupProperty
3117
+ ? `Group by ${groupProperty.definition.name}`
3118
+ : "Group"
3119
+ }
3120
+ title="Group"
3121
+ className={cn(databaseToolbarIconButtonClass(Boolean(groupProperty)))}
3122
+ >
3123
+ <IconLayoutKanban className="size-3.5" />
3124
+ </Button>
3125
+ </DropdownMenuTrigger>
3126
+ <DropdownMenuContent align="end" className="w-56">
3127
+ <DropdownMenuLabel className="text-xs text-muted-foreground">
3128
+ {dbText("groupBy")}
3129
+ </DropdownMenuLabel>
3130
+ <DropdownMenuItem
3131
+ onSelect={(event) => {
3132
+ event.preventDefault();
3133
+ onGroupByChange(null);
3134
+ }}
3135
+ >
3136
+ <span className="flex-1">None</span>
3137
+ {!groupProperty ? (
3138
+ <IconCheck className="size-4 text-muted-foreground" />
3139
+ ) : null}
3140
+ </DropdownMenuItem>
3141
+ {groupableProperties.length > 0 ? <DropdownMenuSeparator /> : null}
3142
+ {groupableProperties.length > 0 ? (
3143
+ <DatabasePropertyPickerSearch
3144
+ value={propertyQuery}
3145
+ onChange={setPropertyQuery}
3146
+ />
3147
+ ) : null}
3148
+ {groupPropertyItems.map((item) => (
3149
+ <DatabasePropertyPickerItem
3150
+ key={item.key}
3151
+ item={item}
3152
+ selected={groupProperty?.definition.id === item.key}
3153
+ onSelect={(key) => onGroupByChange(key)}
3154
+ />
3155
+ ))}
3156
+ {groupableProperties.length > 0 && groupPropertyItems.length === 0 ? (
3157
+ <div className="px-2 py-1.5 text-xs text-muted-foreground">
3158
+ {dbText("noPropertiesFound")}
3159
+ </div>
3160
+ ) : null}
3161
+ {groupProperty ? (
3162
+ <>
3163
+ <DropdownMenuSeparator />
3164
+ <DropdownMenuItem
3165
+ onSelect={(event) => {
3166
+ event.preventDefault();
3167
+ onHideEmptyGroupsChange(!hideEmptyGroups);
3168
+ }}
3169
+ >
3170
+ <IconEyeOff className="mr-2 size-4 text-muted-foreground" />
3171
+ <span className="flex-1">{dbText("hideEmptyGroups")}</span>
3172
+ {hideEmptyGroups ? (
3173
+ <IconCheck className="size-4 text-muted-foreground" />
3174
+ ) : null}
3175
+ </DropdownMenuItem>
3176
+ <DropdownMenuItem
3177
+ disabled={groupIds.length === 0}
3178
+ onSelect={(event) => {
3179
+ event.preventDefault();
3180
+ onGroupsCollapsedChange(groupIds, true);
3181
+ }}
3182
+ >
3183
+ <IconChevronRight className="mr-2 size-4 text-muted-foreground" />
3184
+ {dbText("collapseAllGroups")}
3185
+ </DropdownMenuItem>
3186
+ <DropdownMenuItem
3187
+ disabled={groupIds.length === 0}
3188
+ onSelect={(event) => {
3189
+ event.preventDefault();
3190
+ onGroupsCollapsedChange(groupIds, false);
3191
+ }}
3192
+ >
3193
+ <IconChevronDown className="mr-2 size-4 text-muted-foreground" />
3194
+ {dbText("expandAllGroups")}
3195
+ </DropdownMenuItem>
3196
+ </>
3197
+ ) : null}
3198
+ {groupableProperties.length === 0 ? (
3199
+ <div className="px-2 py-1.5 text-xs text-muted-foreground">
3200
+ {dbText("addAStatusSelectMultiSelectOrCheckboxPropertyToGroup")}
3201
+ </div>
3202
+ ) : null}
3203
+ </DropdownMenuContent>
3204
+ </DropdownMenu>
3205
+ );
3206
+ }
3207
+
3208
+ function databaseOpenPagesInLabel(value: ContentDatabaseOpenPagesIn) {
3209
+ return value === "full_page" ? "Full page" : "Side preview";
3210
+ }
3211
+
3212
+ function databaseOpenPagesInDescription(value: ContentDatabaseOpenPagesIn) {
3213
+ return value === "full_page"
3214
+ ? "Navigate to the page when opening a row."
3215
+ : "Open rows in a side panel without leaving the database.";
3216
+ }
3217
+
3218
+ export function databaseFilterModeLabel(filterMode: DatabaseFilterMode) {
3219
+ return filterMode === "or" ? "Any" : "All";
3220
+ }
3221
+
3222
+ export function databaseFilterModePhrase(filterMode: DatabaseFilterMode) {
3223
+ return filterMode === "or" ? "any filter" : "all filters";
3224
+ }