@agent-native/core 0.123.0 → 0.123.2

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 (334) hide show
  1. package/corpus/README.md +1 -1
  2. package/corpus/core/CHANGELOG.md +24 -0
  3. package/corpus/core/package.json +1 -1
  4. package/corpus/core/src/cli/templates-meta.ts +12 -0
  5. package/corpus/core/src/deploy/build.ts +8 -0
  6. package/corpus/core/src/server/builder-browser.ts +76 -0
  7. package/corpus/core/src/server/core-routes-plugin.ts +15 -5
  8. package/corpus/templates/assets/app/global.css +0 -4
  9. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +195 -167
  10. package/corpus/templates/assets/changelog/2026-07-23-drafts-preview-panel-now-fills-the-full-width-with-a-large-p.md +6 -0
  11. package/corpus/templates/content/actions/_builder-cms-source-adapter.ts +1 -0
  12. package/corpus/templates/content/actions/_builder-cms-write-client.ts +55 -9
  13. package/corpus/templates/content/actions/_database-source-utils.ts +756 -182
  14. package/corpus/templates/content/actions/change-content-database-source-role.ts +21 -20
  15. package/corpus/templates/content/actions/execute-builder-source-execution.ts +49 -8
  16. package/corpus/templates/content/actions/materialize-builder-required-fields.ts +42 -9
  17. package/corpus/templates/content/actions/prepare-builder-source-review.ts +88 -90
  18. package/corpus/templates/content/actions/preview-builder-source-review.ts +4 -3
  19. package/corpus/templates/content/actions/refresh-content-database-source.ts +36 -9
  20. package/corpus/templates/content/actions/set-content-database-source-write-mode.ts +28 -24
  21. package/corpus/templates/content/app/components/editor/BubbleToolbar.tsx +62 -12
  22. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +91 -16
  23. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +15 -1
  24. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +234 -44
  25. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +194 -16
  26. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +95 -42
  27. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +4 -2
  28. package/corpus/templates/content/app/components/editor/extensions/CodeBlockNode.tsx +10 -166
  29. package/corpus/templates/content/app/components/editor/extensions/VideoBlock.tsx +32 -8
  30. package/corpus/templates/content/app/components/editor/extensions/VideoNode.ts +14 -0
  31. package/corpus/templates/content/app/components/editor/registrySlashItems.ts +5 -1
  32. package/corpus/templates/content/app/hooks/use-content-database.ts +59 -2
  33. package/corpus/templates/content/app/hooks/use-documents.ts +18 -26
  34. package/corpus/templates/content/app/root.tsx +1 -6
  35. package/corpus/templates/content/changelog/2026-07-24-builder-can-now-publish-approved-updates-to-entries-that-are.md +6 -0
  36. package/corpus/templates/content/changelog/2026-07-24-builder-collection-loading-no-longer-restarts-from-the-begin.md +6 -0
  37. package/corpus/templates/content/changelog/2026-07-24-builder-connections-now-complete-securely-from-netlify-deplo.md +6 -0
  38. package/corpus/templates/content/changelog/2026-07-24-builder-draft-creation-now-scopes-execution-to-the-selected-.md +6 -0
  39. package/corpus/templates/content/changelog/2026-07-24-builder-review-now-distinguishes-preparing-a-safe-dry-run-from.md +6 -0
  40. package/corpus/templates/content/changelog/2026-07-24-builder-source-imports-now-recover-from-a-transient-paginati.md +6 -0
  41. package/corpus/templates/content/changelog/2026-07-24-builder-source-refresh-now-preserves-the-provider-bound-fiel.md +6 -0
  42. package/corpus/templates/content/changelog/2026-07-24-builder-update-reviews-now-open-reliably-on-large-collection.md +6 -0
  43. package/corpus/templates/content/changelog/2026-07-24-builder-write-policy-controls-now-reflect-successful-changes.md +6 -0
  44. package/corpus/templates/content/changelog/2026-07-24-date-properties-now-save-reliably-when-selected-with-the-nat.md +6 -0
  45. package/corpus/templates/content/changelog/2026-07-24-editor-slash-search-now-keeps-multi-word-block-names-open.md +6 -0
  46. package/corpus/templates/content/changelog/2026-07-24-exact-editor-slash-commands-now-execute-on-enter-even-if-the.md +6 -0
  47. package/corpus/templates/content/changelog/2026-07-24-new-collaborative-pages-now-save-their-first-edits.md +6 -0
  48. package/corpus/templates/content/changelog/2026-07-24-new-local-builder-rows-no-longer-remain-locked-behind-a-none.md +6 -0
  49. package/corpus/templates/content/changelog/2026-07-24-opening-or-closing-a-collaborative-document-no-longer-lets-u.md +6 -0
  50. package/corpus/templates/content/changelog/2026-07-24-selected-text-now-opens-the-link-editor-with-command-k-or-co.md +6 -0
  51. package/corpus/templates/content/changelog/2026-07-24-slash-menu-block-choices-no-longer-stall-pointer-interaction.md +6 -0
  52. package/corpus/templates/content/changelog/2026-07-24-uploaded-and-embedded-media-now-stay-intact-while-collaborative-document-drafts-save.md +6 -0
  53. package/corpus/templates/content/changelog/2026-07-24-video-link-insertion-now-keeps-its-source-panel-open-while-t.md +6 -0
  54. package/corpus/templates/content/changelog/2026-07-26-builder-publishing-now-shows-safe-validation-details-instead.md +6 -0
  55. package/corpus/templates/content/changelog/2026-07-26-builder-reviews-stay-responsive-on-large-collections-and-pre.md +6 -0
  56. package/corpus/templates/content/changelog/2026-07-26-collaborative-documents-no-longer-flash-duplicated-recent-bl.md +6 -0
  57. package/corpus/templates/content/changelog/2026-07-26-corrected-builder-updates-are-now-reviewed-separately-from-f.md +6 -0
  58. package/corpus/templates/content/changelog/2026-07-26-documents-now-remain-read-only-until-their-live-collaborativ.md +6 -0
  59. package/corpus/templates/content/changelog/2026-07-26-slash-command-menus-now-stay-reachable-when-editing-near-the.md +6 -0
  60. package/corpus/templates/content/changelog/2026-07-26-the-slash-menu-now-shows-one-clear-callout-choice-instead-of.md +6 -0
  61. package/corpus/templates/content/shared/api.ts +1 -0
  62. package/corpus/templates/content/shared/builder-mdx.ts +73 -16
  63. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
  64. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +177 -0
  65. package/corpus/templates/crm/.agents/skills/crm/SKILL.md +205 -0
  66. package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +220 -0
  67. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
  68. package/corpus/templates/crm/.agents/skills/sharing/SKILL.md +228 -0
  69. package/corpus/templates/crm/.agents/skills/storing-data/SKILL.md +178 -0
  70. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +122 -0
  71. package/corpus/templates/crm/.env.example +5 -0
  72. package/corpus/templates/crm/AGENTS.md +82 -0
  73. package/corpus/templates/crm/CHANGELOG.md +25 -0
  74. package/corpus/templates/crm/DEVELOPING.md +66 -0
  75. package/corpus/templates/crm/README.md +122 -0
  76. package/corpus/templates/crm/_gitignore +12 -0
  77. package/corpus/templates/crm/actions/_crm-action-utils.ts +98 -0
  78. package/corpus/templates/crm/actions/_crm-attribute-utils.ts +219 -0
  79. package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
  80. package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
  81. package/corpus/templates/crm/actions/_crm-list-utils.ts +775 -0
  82. package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
  83. package/corpus/templates/crm/actions/add-crm-record-to-list.ts +164 -0
  84. package/corpus/templates/crm/actions/apply-crm-proposals.ts +291 -0
  85. package/corpus/templates/crm/actions/archive-crm-attribute.ts +69 -0
  86. package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
  87. package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
  88. package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
  89. package/corpus/templates/crm/actions/create-crm-attribute.ts +350 -0
  90. package/corpus/templates/crm/actions/create-crm-list.ts +194 -0
  91. package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
  92. package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
  93. package/corpus/templates/crm/actions/delete-crm-saved-view.ts +68 -0
  94. package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
  95. package/corpus/templates/crm/actions/estimate-crm-enrichment.ts +229 -0
  96. package/corpus/templates/crm/actions/find-crm-duplicates.ts +87 -0
  97. package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
  98. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +35 -0
  99. package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
  100. package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
  101. package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
  102. package/corpus/templates/crm/actions/get-crm-record-page.ts +287 -0
  103. package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
  104. package/corpus/templates/crm/actions/get-crm-workspace.ts +364 -0
  105. package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
  106. package/corpus/templates/crm/actions/list-crm-attributes.ts +71 -0
  107. package/corpus/templates/crm/actions/list-crm-connections.ts +68 -0
  108. package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
  109. package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
  110. package/corpus/templates/crm/actions/list-crm-enrichment-slots.ts +28 -0
  111. package/corpus/templates/crm/actions/list-crm-list-entries.ts +192 -0
  112. package/corpus/templates/crm/actions/list-crm-lists.ts +80 -0
  113. package/corpus/templates/crm/actions/list-crm-proposals.ts +55 -0
  114. package/corpus/templates/crm/actions/list-crm-record-field-history.ts +201 -0
  115. package/corpus/templates/crm/actions/list-crm-record-values.ts +200 -0
  116. package/corpus/templates/crm/actions/list-crm-records.ts +103 -0
  117. package/corpus/templates/crm/actions/list-crm-saved-views.ts +66 -0
  118. package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
  119. package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
  120. package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
  121. package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
  122. package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
  123. package/corpus/templates/crm/actions/manage-crm-attribute-option.ts +272 -0
  124. package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
  125. package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
  126. package/corpus/templates/crm/actions/merge-crm-records.ts +691 -0
  127. package/corpus/templates/crm/actions/navigate.ts +54 -0
  128. package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
  129. package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
  130. package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
  131. package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
  132. package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
  133. package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
  134. package/corpus/templates/crm/actions/remove-crm-list-entry.ts +79 -0
  135. package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
  136. package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
  137. package/corpus/templates/crm/actions/run-crm-attribute-fill.ts +443 -0
  138. package/corpus/templates/crm/actions/run-crm-enrichment.ts +647 -0
  139. package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
  140. package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +265 -0
  141. package/corpus/templates/crm/actions/run.ts +7 -0
  142. package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
  143. package/corpus/templates/crm/actions/save-crm-saved-view.ts +296 -0
  144. package/corpus/templates/crm/actions/sync-crm.ts +146 -0
  145. package/corpus/templates/crm/actions/update-crm-attribute.ts +111 -0
  146. package/corpus/templates/crm/actions/update-crm-list-entry.ts +102 -0
  147. package/corpus/templates/crm/actions/update-crm-list.ts +97 -0
  148. package/corpus/templates/crm/actions/update-crm-record.ts +543 -0
  149. package/corpus/templates/crm/actions/view-screen.ts +324 -0
  150. package/corpus/templates/crm/agent-native.app-skill.json +69 -0
  151. package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
  152. package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +78 -0
  153. package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +220 -0
  154. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +414 -0
  155. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +648 -0
  156. package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +488 -0
  157. package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +232 -0
  158. package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +93 -0
  159. package/corpus/templates/crm/app/components/crm/Surface.tsx +97 -0
  160. package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
  161. package/corpus/templates/crm/app/components/crm/board/CrmBoard.tsx +988 -0
  162. package/corpus/templates/crm/app/components/crm/board/board-model.ts +399 -0
  163. package/corpus/templates/crm/app/components/crm/board/entry-filter.ts +150 -0
  164. package/corpus/templates/crm/app/components/crm/board/view-draft.ts +227 -0
  165. package/corpus/templates/crm/app/components/crm/grid/CrmGrid.tsx +1033 -0
  166. package/corpus/templates/crm/app/components/crm/grid/GridCell.tsx +620 -0
  167. package/corpus/templates/crm/app/components/crm/grid/clipboard.ts +84 -0
  168. package/corpus/templates/crm/app/components/crm/grid/model.ts +304 -0
  169. package/corpus/templates/crm/app/components/crm/grid/navigation.ts +220 -0
  170. package/corpus/templates/crm/app/components/crm/grid/query.ts +214 -0
  171. package/corpus/templates/crm/app/components/crm/record/AttributePanel.tsx +192 -0
  172. package/corpus/templates/crm/app/components/crm/record/FieldHistory.tsx +179 -0
  173. package/corpus/templates/crm/app/components/crm/record/RecordHeader.tsx +103 -0
  174. package/corpus/templates/crm/app/components/crm/record/RecordLists.tsx +235 -0
  175. package/corpus/templates/crm/app/components/crm/record/RecordTabs.tsx +206 -0
  176. package/corpus/templates/crm/app/components/crm/record/field-editors.tsx +245 -0
  177. package/corpus/templates/crm/app/components/crm/record/record-data.ts +603 -0
  178. package/corpus/templates/crm/app/components/crm/settings/AdvancedSettings.tsx +49 -0
  179. package/corpus/templates/crm/app/components/crm/settings/AttributeOptionsEditor.tsx +441 -0
  180. package/corpus/templates/crm/app/components/crm/settings/ConnectionSettings.tsx +169 -0
  181. package/corpus/templates/crm/app/components/crm/settings/FieldsSettings.tsx +935 -0
  182. package/corpus/templates/crm/app/components/crm/settings/ListsSettings.tsx +339 -0
  183. package/corpus/templates/crm/app/components/crm/settings/settings-admin.ts +377 -0
  184. package/corpus/templates/crm/app/components/crm/shared/AttributeValueParts.tsx +69 -0
  185. package/corpus/templates/crm/app/components/crm/shared/attribute-value.ts +591 -0
  186. package/corpus/templates/crm/app/components/layout/CrmCommandMenu.tsx +556 -0
  187. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +130 -0
  188. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +407 -0
  189. package/corpus/templates/crm/app/components/layout/command-palette-search.ts +126 -0
  190. package/corpus/templates/crm/app/components/layout/crm-ui-intents.ts +41 -0
  191. package/corpus/templates/crm/app/components/layout/sidebar-collapse.ts +40 -0
  192. package/corpus/templates/crm/app/components/layout/sidebar-lists.ts +109 -0
  193. package/corpus/templates/crm/app/components/ui/alert-dialog.tsx +1 -0
  194. package/corpus/templates/crm/app/components/ui/badge.tsx +1 -0
  195. package/corpus/templates/crm/app/components/ui/button.tsx +1 -0
  196. package/corpus/templates/crm/app/components/ui/card.tsx +1 -0
  197. package/corpus/templates/crm/app/components/ui/checkbox.tsx +1 -0
  198. package/corpus/templates/crm/app/components/ui/dialog.tsx +1 -0
  199. package/corpus/templates/crm/app/components/ui/dropdown-menu.tsx +1 -0
  200. package/corpus/templates/crm/app/components/ui/hover-card.tsx +1 -0
  201. package/corpus/templates/crm/app/components/ui/input.tsx +1 -0
  202. package/corpus/templates/crm/app/components/ui/label.tsx +1 -0
  203. package/corpus/templates/crm/app/components/ui/popover.tsx +1 -0
  204. package/corpus/templates/crm/app/components/ui/select.tsx +1 -0
  205. package/corpus/templates/crm/app/components/ui/sheet.tsx +1 -0
  206. package/corpus/templates/crm/app/components/ui/skeleton.tsx +1 -0
  207. package/corpus/templates/crm/app/components/ui/switch.tsx +1 -0
  208. package/corpus/templates/crm/app/components/ui/table.tsx +1 -0
  209. package/corpus/templates/crm/app/components/ui/tabs.tsx +1 -0
  210. package/corpus/templates/crm/app/components/ui/textarea.tsx +1 -0
  211. package/corpus/templates/crm/app/components/ui/toolkit-provider.tsx +17 -0
  212. package/corpus/templates/crm/app/components/ui/tooltip.tsx +1 -0
  213. package/corpus/templates/crm/app/design-system.ts +3 -0
  214. package/corpus/templates/crm/app/entry.client.tsx +19 -0
  215. package/corpus/templates/crm/app/entry.server.tsx +10 -0
  216. package/corpus/templates/crm/app/global.css +167 -0
  217. package/corpus/templates/crm/app/hooks/use-keyboard-shortcuts.ts +206 -0
  218. package/corpus/templates/crm/app/hooks/use-navigation-state.ts +48 -0
  219. package/corpus/templates/crm/app/i18n/en-US.ts +684 -0
  220. package/corpus/templates/crm/app/i18n/index.ts +34 -0
  221. package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
  222. package/corpus/templates/crm/app/lib/navigation.ts +17 -0
  223. package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
  224. package/corpus/templates/crm/app/lib/types.ts +172 -0
  225. package/corpus/templates/crm/app/lib/utils.ts +1 -0
  226. package/corpus/templates/crm/app/root.tsx +93 -0
  227. package/corpus/templates/crm/app/routes/_index.tsx +26 -0
  228. package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
  229. package/corpus/templates/crm/app/routes/agent.tsx +5 -0
  230. package/corpus/templates/crm/app/routes/ask.tsx +53 -0
  231. package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
  232. package/corpus/templates/crm/app/routes/lists.tsx +228 -0
  233. package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
  234. package/corpus/templates/crm/app/routes/people.tsx +41 -0
  235. package/corpus/templates/crm/app/routes/proposals.tsx +388 -0
  236. package/corpus/templates/crm/app/routes/records.$recordId.tsx +53 -0
  237. package/corpus/templates/crm/app/routes/records.tsx +64 -0
  238. package/corpus/templates/crm/app/routes/settings.tsx +118 -0
  239. package/corpus/templates/crm/app/routes/setup.tsx +308 -0
  240. package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
  241. package/corpus/templates/crm/app/routes/views.tsx +683 -0
  242. package/corpus/templates/crm/app/routes.ts +19 -0
  243. package/corpus/templates/crm/app/vite-env.d.ts +6 -0
  244. package/corpus/templates/crm/changelog/2026-07-26-any-list-or-view-can-be-shown-as-a-board-grouped-by-stage-so.md +6 -0
  245. package/corpus/templates/crm/changelog/2026-07-26-crm-now-finds-likely-duplicate-records-and-tells-you-exactly.md +6 -0
  246. package/corpus/templates/crm/changelog/2026-07-26-enrichment-now-shows-what-a-run-will-cost-before-it-starts-g.md +6 -0
  247. package/corpus/templates/crm/changelog/2026-07-26-lists-turn-any-set-of-records-into-a-pipeline-with-its-own-s.md +6 -0
  248. package/corpus/templates/crm/changelog/2026-07-26-moving-a-record-or-list-entry-into-a-stage-that-was-retired-.md +6 -0
  249. package/corpus/templates/crm/changelog/2026-07-26-opening-a-record-now-shows-one-page-with-its-fields-the-list.md +6 -0
  250. package/corpus/templates/crm/changelog/2026-07-26-press-cmd-k-to-jump-to-any-record-list-view-or-setting-and-u.md +6 -0
  251. package/corpus/templates/crm/changelog/2026-07-26-provider-record-edits-now-show-an-exact-before-after-diff-an.md +6 -0
  252. package/corpus/templates/crm/changelog/2026-07-26-record-page-attribute-labels-now-read-in-full-instead-of-bei.md +6 -0
  253. package/corpus/templates/crm/changelog/2026-07-26-records-now-use-typed-fields-currency-dates-ratings-status-s.md +6 -0
  254. package/corpus/templates/crm/changelog/2026-07-26-retyping-a-value-on-the-record-page-now-replaces-it-instead-.md +6 -0
  255. package/corpus/templates/crm/changelog/2026-07-26-settings-now-has-fields-and-lists-tabs-for-adding-fields-edi.md +6 -0
  256. package/corpus/templates/crm/changelog/2026-07-26-the-proposal-review-now-shows-the-field-that-actually-change.md +6 -0
  257. package/corpus/templates/crm/changelog/2026-07-26-the-record-grid-is-now-a-real-spreadsheet-filter-sort-and-pa.md +6 -0
  258. package/corpus/templates/crm/components.json +20 -0
  259. package/corpus/templates/crm/docs/architecture/crm-contract.md +214 -0
  260. package/corpus/templates/crm/docs/features/README.md +55 -0
  261. package/corpus/templates/crm/docs/features/f1-records-and-fields.mdx +52 -0
  262. package/corpus/templates/crm/docs/features/f2-lists-pipelines-and-views.mdx +48 -0
  263. package/corpus/templates/crm/docs/features/f3-work-and-record-quality.mdx +40 -0
  264. package/corpus/templates/crm/docs/features/f4-evidence-and-chat.mdx +47 -0
  265. package/corpus/templates/crm/learnings.defaults.md +5 -0
  266. package/corpus/templates/crm/netlify.toml +11 -0
  267. package/corpus/templates/crm/package.json +61 -0
  268. package/corpus/templates/crm/public/favicon.svg +1 -0
  269. package/corpus/templates/crm/public/icon-180.svg +1 -0
  270. package/corpus/templates/crm/public/manifest.json +11 -0
  271. package/corpus/templates/crm/react-router.config.ts +7 -0
  272. package/corpus/templates/crm/server/crm/adapter.ts +32 -0
  273. package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
  274. package/corpus/templates/crm/server/crm/crm-mirror.ts +721 -0
  275. package/corpus/templates/crm/server/crm/field-policy-attributes.ts +48 -0
  276. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1146 -0
  277. package/corpus/templates/crm/server/crm/native-adapter.ts +1810 -0
  278. package/corpus/templates/crm/server/crm/provider-record-link.ts +264 -0
  279. package/corpus/templates/crm/server/crm/read-through.ts +207 -0
  280. package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
  281. package/corpus/templates/crm/server/db/crm-store.ts +1112 -0
  282. package/corpus/templates/crm/server/db/index.ts +191 -0
  283. package/corpus/templates/crm/server/db/schema.ts +647 -0
  284. package/corpus/templates/crm/server/lib/crm-query.ts +1558 -0
  285. package/corpus/templates/crm/server/lib/dedupe.ts +714 -0
  286. package/corpus/templates/crm/server/lib/enrichment-cost.ts +381 -0
  287. package/corpus/templates/crm/server/lib/enrichment-slots.ts +519 -0
  288. package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
  289. package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
  290. package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +94 -0
  291. package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +39 -0
  292. package/corpus/templates/crm/server/lib/intelligence/summary.ts +19 -0
  293. package/corpus/templates/crm/server/lib/lifecycle.ts +634 -0
  294. package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
  295. package/corpus/templates/crm/server/middleware/auth.ts +4 -0
  296. package/corpus/templates/crm/server/plugins/agent-chat.ts +65 -0
  297. package/corpus/templates/crm/server/plugins/auth.ts +14 -0
  298. package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
  299. package/corpus/templates/crm/server/plugins/db.ts +738 -0
  300. package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
  301. package/corpus/templates/crm/shared/crm-attributes.ts +469 -0
  302. package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
  303. package/corpus/templates/crm/shared/crm-contract.ts +417 -0
  304. package/corpus/templates/crm/shared/crm-navigation.ts +190 -0
  305. package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
  306. package/corpus/templates/crm/ssr-entry.ts +11 -0
  307. package/corpus/templates/crm/tsconfig.json +24 -0
  308. package/corpus/templates/crm/vite.config.ts +15 -0
  309. package/corpus/templates/crm/vitest.config.ts +5 -0
  310. package/corpus/templates/design/app/i18n-data.ts +12 -12
  311. package/corpus/templates/design/changelog/2026-07-26-apply-design-updates-from-a-centered-top-toolbar-without-cov.md +6 -0
  312. package/corpus/toolkit/CHANGELOG.md +6 -0
  313. package/corpus/toolkit/package.json +1 -1
  314. package/corpus/toolkit/src/editor/useCollabReconcile.ts +14 -1
  315. package/dist/cli/templates-meta.d.ts.map +1 -1
  316. package/dist/cli/templates-meta.js +12 -0
  317. package/dist/cli/templates-meta.js.map +1 -1
  318. package/dist/collab/routes.d.ts +2 -2
  319. package/dist/deploy/build.js +6 -0
  320. package/dist/deploy/build.js.map +1 -1
  321. package/dist/observability/routes.d.ts +3 -3
  322. package/dist/server/builder-browser.d.ts +13 -0
  323. package/dist/server/builder-browser.d.ts.map +1 -1
  324. package/dist/server/builder-browser.js +59 -0
  325. package/dist/server/builder-browser.js.map +1 -1
  326. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  327. package/dist/server/core-routes-plugin.js +10 -5
  328. package/dist/server/core-routes-plugin.js.map +1 -1
  329. package/dist/server/transcribe-voice.d.ts +1 -1
  330. package/package.json +2 -2
  331. package/src/cli/templates-meta.ts +12 -0
  332. package/src/deploy/build.ts +8 -0
  333. package/src/server/builder-browser.ts +76 -0
  334. package/src/server/core-routes-plugin.ts +15 -5
@@ -29,7 +29,14 @@ import { TableRow } from "@tiptap/extension-table-row";
29
29
  import TaskItem from "@tiptap/extension-task-item";
30
30
  import TaskList from "@tiptap/extension-task-list";
31
31
  import { Fragment, type Node as ProseMirrorNode } from "@tiptap/pm/model";
32
- import { Plugin, PluginKey, AllSelection, Selection } from "@tiptap/pm/state";
32
+ import {
33
+ Plugin,
34
+ PluginKey,
35
+ AllSelection,
36
+ NodeSelection,
37
+ Selection,
38
+ type Transaction,
39
+ } from "@tiptap/pm/state";
33
40
  import { Decoration, DecorationSet, type EditorView } from "@tiptap/pm/view";
34
41
  import {
35
42
  useEditor,
@@ -925,6 +932,47 @@ export function shouldPersistLocalFileEditorUpdate({
925
932
  return Boolean(transactionUiEvent);
926
933
  }
927
934
 
935
+ export function shouldPersistCollaborativeEditorUpdate({
936
+ collab,
937
+ editorFocused,
938
+ userInitiated,
939
+ }: {
940
+ collab: boolean;
941
+ editorFocused: boolean;
942
+ userInitiated: boolean;
943
+ }) {
944
+ // Collaborative mount/reconcile normalization can produce a local-looking
945
+ // transaction after the remote Y.Doc has loaded. If the editor is not
946
+ // focused and no human input event preceded the transaction, it has no
947
+ // authority to overwrite SQL. Focused commands and explicit user-intent
948
+ // transactions remain persistable; structural/media actions additionally
949
+ // use their immediate proof-of-save callbacks.
950
+ return !collab || editorFocused || userInitiated;
951
+ }
952
+
953
+ export function isUserInitiatedCollaborativeEditorUpdate({
954
+ editorFocused,
955
+ explicitUserEdit,
956
+ recentUserEditIntent,
957
+ transactionUiEvent,
958
+ }: {
959
+ editorFocused: boolean;
960
+ explicitUserEdit: boolean;
961
+ recentUserEditIntent: boolean;
962
+ transactionUiEvent: unknown;
963
+ }) {
964
+ // A recent input event is useful for grouping the follow-up transactions
965
+ // produced while the editor still owns focus. Once focus has left, however,
966
+ // only provenance on this exact transaction may authorize persistence;
967
+ // otherwise mount/Yjs normalization could borrow a stale two-second intent
968
+ // window and overwrite canonical SQL.
969
+ return (
970
+ explicitUserEdit ||
971
+ Boolean(transactionUiEvent) ||
972
+ (editorFocused && recentUserEditIntent)
973
+ );
974
+ }
975
+
928
976
  function isEffectivelyEmptyEditorContent(value: string): boolean {
929
977
  const normalized = value.trim();
930
978
  return normalized === "" || normalized === "<empty-block/>";
@@ -975,19 +1023,21 @@ interface VisualEditorExtensionOptions {
975
1023
  localFilePath?: string | null;
976
1024
  referenceDepth?: number;
977
1025
  emptyBlockPlaceholder?: string;
1026
+ onMediaSourceCommitted?: (
1027
+ editor: CoreEditor,
1028
+ transaction: Transaction,
1029
+ ) => void;
978
1030
  }
979
1031
 
980
- function hasAncestorType(
1032
+ export function hasAncestorType(
981
1033
  editor: CoreEditor,
982
1034
  pos: number,
983
1035
  typeName: string,
984
1036
  ): boolean {
985
1037
  const doc = editor.state.doc;
986
- const positions = [
987
- Math.max(0, pos - 1),
988
- pos,
989
- Math.min(doc.content.size, pos + 1),
990
- ];
1038
+ const clampPosition = (candidate: number) =>
1039
+ Math.min(doc.content.size, Math.max(0, candidate));
1040
+ const positions = [...new Set([pos - 1, pos, pos + 1].map(clampPosition))];
991
1041
 
992
1042
  return positions.some((candidatePos) => {
993
1043
  const resolvedPos = doc.resolve(candidatePos);
@@ -1002,6 +1052,88 @@ function hasAncestorType(
1002
1052
 
1003
1053
  type MediaNodeType = "image" | "video" | "audio";
1004
1054
 
1055
+ const MEDIA_NODE_TYPES = new Set<MediaNodeType>(["image", "video", "audio"]);
1056
+
1057
+ function mediaSourceCounts(doc: ProseMirrorNode) {
1058
+ const counts = new Map<string, number>();
1059
+ doc.descendants((node) => {
1060
+ if (!MEDIA_NODE_TYPES.has(node.type.name as MediaNodeType)) return true;
1061
+ const src = typeof node.attrs.src === "string" ? node.attrs.src : "";
1062
+ if (!src) return false;
1063
+ const key = `${node.type.name}\u0000${src}`;
1064
+ counts.set(key, (counts.get(key) ?? 0) + 1);
1065
+ return false;
1066
+ });
1067
+ return counts;
1068
+ }
1069
+
1070
+ export function didCommitMediaSource(transaction: Transaction): boolean {
1071
+ if (!transaction.docChanged) return false;
1072
+ const before = mediaSourceCounts(transaction.before);
1073
+ const after = mediaSourceCounts(transaction.doc);
1074
+ return [...after].some(([key, count]) => count > (before.get(key) ?? 0));
1075
+ }
1076
+
1077
+ const MediaSourceCommit = Extension.create<{
1078
+ onMediaSourceCommitted?: (
1079
+ editor: CoreEditor,
1080
+ transaction: Transaction,
1081
+ ) => void;
1082
+ }>({
1083
+ name: "mediaSourceCommit",
1084
+ addOptions() {
1085
+ return { onMediaSourceCommitted: undefined };
1086
+ },
1087
+ onTransaction({ editor, transaction }) {
1088
+ if (didCommitMediaSource(transaction)) {
1089
+ this.options.onMediaSourceCommitted?.(editor, transaction);
1090
+ }
1091
+ },
1092
+ });
1093
+
1094
+ /**
1095
+ * Empty media nodes are transient editor UI, not durable document content.
1096
+ *
1097
+ * Persisting the placeholder before its async upload/link enrichment finishes
1098
+ * lets the SQL echo reconcile the empty `src` back into the live Y.Doc. That
1099
+ * can erase a successfully uploaded image or embedded video. Keep the local
1100
+ * draft out of autosave until it has a source; uploads inserted by drop/paste
1101
+ * are covered by their `uploadId` even when the selection is elsewhere.
1102
+ */
1103
+ export function shouldSkipMediaDraftPersistence(editor: CoreEditor): boolean {
1104
+ let hasPendingUpload = false;
1105
+ editor.state.doc.descendants((node) => {
1106
+ if (
1107
+ MEDIA_NODE_TYPES.has(node.type.name as MediaNodeType) &&
1108
+ Boolean(node.attrs.uploadId)
1109
+ ) {
1110
+ hasPendingUpload = true;
1111
+ return false;
1112
+ }
1113
+ return !hasPendingUpload;
1114
+ });
1115
+ if (hasPendingUpload) return true;
1116
+
1117
+ const { selection } = editor.state;
1118
+ if (!(selection instanceof NodeSelection)) return false;
1119
+ return (
1120
+ MEDIA_NODE_TYPES.has(selection.node.type.name as MediaNodeType) &&
1121
+ !selection.node.attrs.src
1122
+ );
1123
+ }
1124
+
1125
+ /**
1126
+ * Serialize only complete editor drafts. Structural slash commands explicitly
1127
+ * ask to persist after their transaction, so this guard must live in the shared
1128
+ * persistence path rather than only in `onUpdate`.
1129
+ */
1130
+ export function serializeEditorDraftForPersistence(
1131
+ editor: CoreEditor,
1132
+ ): string | null {
1133
+ if (shouldSkipMediaDraftPersistence(editor)) return null;
1134
+ return docToNfm(editor.getJSON() as any);
1135
+ }
1136
+
1005
1137
  function mediaNodeLabel(typeName: MediaNodeType) {
1006
1138
  if (typeName === "image") return "Image";
1007
1139
  if (typeName === "video") return "Video";
@@ -1305,6 +1437,7 @@ export function createVisualEditorExtensions({
1305
1437
  localFilePath,
1306
1438
  referenceDepth = 0,
1307
1439
  emptyBlockPlaceholder = DEFAULT_EMPTY_BLOCK_PLACEHOLDER,
1440
+ onMediaSourceCommitted,
1308
1441
  }: VisualEditorExtensionOptions = {}): Extensions {
1309
1442
  // Build on the SHARED editor core (StarterKit base + the Collaboration /
1310
1443
  // CollaborationCaret wiring + collab undo/redo gating + ordering), then inject
@@ -1372,6 +1505,7 @@ export function createVisualEditorExtensions({
1372
1505
  documentId,
1373
1506
  onAudioComment: onImageComment,
1374
1507
  }),
1508
+ MediaSourceCommit.configure({ onMediaSourceCommitted }),
1375
1509
  CustomTable.configure({
1376
1510
  resizable: false,
1377
1511
  HTMLAttributes: { class: "notion-table" },
@@ -1688,6 +1822,15 @@ export function VisualEditor({
1688
1822
  onSaveContentRef.current = onSaveContent;
1689
1823
  const notionPageLinksRef = useRef(notionPageLinks);
1690
1824
  notionPageLinksRef.current = notionPageLinks;
1825
+ const onMediaSourceCommittedRef = useRef<
1826
+ ((editor: CoreEditor, transaction: Transaction) => void) | null
1827
+ >(null);
1828
+ const onMediaSourceCommitted = useCallback(
1829
+ (editor: CoreEditor, transaction: Transaction) => {
1830
+ onMediaSourceCommittedRef.current?.(editor, transaction);
1831
+ },
1832
+ [],
1833
+ );
1691
1834
  const resolveNotionPageLink = useCallback((notionPageId: string) => {
1692
1835
  const normalized = notionPageId.replace(/-/g, "").toLowerCase();
1693
1836
  return (
@@ -1745,6 +1888,7 @@ export function VisualEditor({
1745
1888
  localFilePath,
1746
1889
  referenceDepth,
1747
1890
  emptyBlockPlaceholder: t("editor.emptyBlockPlaceholder"),
1891
+ onMediaSourceCommitted,
1748
1892
  }),
1749
1893
  [
1750
1894
  documentId,
@@ -1760,6 +1904,7 @@ export function VisualEditor({
1760
1904
  localFilePath,
1761
1905
  referenceDepth,
1762
1906
  t,
1907
+ onMediaSourceCommitted,
1763
1908
  ],
1764
1909
  );
1765
1910
 
@@ -1784,8 +1929,9 @@ export function VisualEditor({
1784
1929
  const guards = guardsRef.current;
1785
1930
  if (!guards) return false;
1786
1931
  try {
1787
- const normalized =
1788
- options?.markdown ?? docToNfm(editorToPersist.getJSON() as any);
1932
+ const serialized = serializeEditorDraftForPersistence(editorToPersist);
1933
+ if (serialized === null) return true;
1934
+ const normalized = options?.markdown ?? serialized;
1789
1935
  if (localFileMode && normalized === content) return true;
1790
1936
  // TipTap/Yjs can emit a local-looking empty-paragraph transaction while
1791
1937
  // an editor is mounting or reconciling. Content serializes that filler
@@ -1820,6 +1966,23 @@ export function VisualEditor({
1820
1966
  },
1821
1967
  [content, localFileMode, t],
1822
1968
  );
1969
+ onMediaSourceCommittedRef.current = async (editorToPersist, transaction) => {
1970
+ const guards = guardsRef.current;
1971
+ if (!guards || guards.shouldIgnoreUpdate(transaction)) return;
1972
+ try {
1973
+ const persisted = await persistEditorContent(editorToPersist, {
1974
+ immediate: true,
1975
+ userInitiated: true,
1976
+ });
1977
+ if (!persisted) throw new Error(t("empty.genericError"));
1978
+ } catch (error) {
1979
+ // The ordinary onUpdate path still queues its debounced retry. Keep the
1980
+ // immediate durability attempt from becoming an unhandled rejection,
1981
+ // but fail visibly instead of treating a skipped save as success.
1982
+ toast.error(t("empty.genericError"));
1983
+ console.error("Media source persistence error:", error);
1984
+ }
1985
+ };
1823
1986
 
1824
1987
  const editor = useEditor({
1825
1988
  extensions,
@@ -1973,12 +2136,27 @@ export function VisualEditor({
1973
2136
  ) {
1974
2137
  return;
1975
2138
  }
2139
+ const userInitiated = isUserInitiatedCollaborativeEditorUpdate({
2140
+ editorFocused: editor.isFocused,
2141
+ explicitUserEdit:
2142
+ transaction.getMeta(LOCAL_FILE_USER_EDIT_META) === true,
2143
+ recentUserEditIntent:
2144
+ Date.now() - lastUserEditIntentAtRef.current < 2000,
2145
+ transactionUiEvent: transaction.getMeta("uiEvent"),
2146
+ });
2147
+ if (
2148
+ !shouldPersistCollaborativeEditorUpdate({
2149
+ collab: !!ydoc,
2150
+ editorFocused: editor.isFocused,
2151
+ userInitiated,
2152
+ })
2153
+ ) {
2154
+ return;
2155
+ }
1976
2156
  if (isActiveSlashCommandDraft(editor)) return;
2157
+ if (shouldSkipMediaDraftPersistence(editor)) return;
1977
2158
  persistEditorContent(editor, {
1978
- userInitiated:
1979
- transaction.getMeta(LOCAL_FILE_USER_EDIT_META) === true ||
1980
- Date.now() - lastUserEditIntentAtRef.current < 2000 ||
1981
- Boolean(transaction.getMeta("uiEvent")),
2159
+ userInitiated,
1982
2160
  });
1983
2161
  },
1984
2162
  });
@@ -2294,9 +2472,9 @@ export function VisualEditor({
2294
2472
  editor={editor}
2295
2473
  documentId={documentId}
2296
2474
  notionPageId={notionPageId}
2297
- onDraftCommitted={() => {
2298
- void persistEditorContent(editor, { userInitiated: true });
2299
- }}
2475
+ onDraftCommitted={() =>
2476
+ persistEditorContent(editor, { userInitiated: true })
2477
+ }
2300
2478
  onDraftPersisted={(markdown) =>
2301
2479
  persistEditorContent(editor, {
2302
2480
  markdown,
@@ -159,6 +159,8 @@ import {
159
159
  TooltipTrigger,
160
160
  } from "@/components/ui/tooltip";
161
161
  import {
162
+ applyOptimisticBuilderWriteMode,
163
+ contentDatabaseQueryFilter,
162
164
  isContentDatabaseUnavailable,
163
165
  useAddDatabaseItem,
164
166
  useAddContentDatabaseSourceFieldProperty,
@@ -910,6 +912,7 @@ function DatabaseTable({
910
912
  );
911
913
  const autoContinueBuilderSourceRef = useRef<Set<string>>(new Set());
912
914
  const builderContinuationWatchdogRef = useRef<Map<string, number>>(new Map());
915
+ const builderContinuationFailureRef = useRef<Map<string, number>>(new Map());
913
916
  const refreshSourceInFlightRef = useRef<string | null>(null);
914
917
  const hydrationSourceInFlightRef = useRef<string | null>(null);
915
918
  const workspaceSelectionQueueRef = useRef(createContentSpaceSelectionQueue());
@@ -1115,14 +1118,24 @@ function DatabaseTable({
1115
1118
  ),
1116
1119
  };
1117
1120
  const runSourceRefresh = useCallback(
1118
- (sourceId: string, onError?: () => void) => {
1121
+ (
1122
+ sourceId: string,
1123
+ onError?: () => void,
1124
+ expectedBuilderContinuationOffset?: number,
1125
+ onSuccess?: () => void,
1126
+ ) => {
1119
1127
  if (!acquireDatabaseSourceOperation(refreshSourceInFlightRef, sourceId)) {
1120
1128
  return false;
1121
1129
  }
1122
1130
  refreshSource.mutate(
1123
- { documentId: document.id, sourceId },
1131
+ {
1132
+ documentId: document.id,
1133
+ sourceId,
1134
+ expectedBuilderContinuationOffset,
1135
+ },
1124
1136
  {
1125
1137
  onError,
1138
+ onSuccess,
1126
1139
  onSettled: () => {
1127
1140
  releaseDatabaseSourceOperation(refreshSourceInFlightRef, sourceId);
1128
1141
  },
@@ -1132,6 +1145,25 @@ function DatabaseTable({
1132
1145
  },
1133
1146
  [document.id, refreshSource.mutate],
1134
1147
  );
1148
+ const handleBuilderContinuationError = useCallback(
1149
+ (continuationKey: string) => {
1150
+ const failures =
1151
+ (builderContinuationFailureRef.current.get(continuationKey) ?? 0) + 1;
1152
+ builderContinuationFailureRef.current.set(continuationKey, failures);
1153
+ if (builderSourceContinuationFailureDecision(failures) === "error") {
1154
+ setBuilderContinuationClientErrorKeys((current) =>
1155
+ addUniqueKey(current, continuationKey),
1156
+ );
1157
+ }
1158
+ },
1159
+ [],
1160
+ );
1161
+ const handleBuilderContinuationSuccess = useCallback(
1162
+ (continuationKey: string) => {
1163
+ builderContinuationFailureRef.current.delete(continuationKey);
1164
+ },
1165
+ [],
1166
+ );
1135
1167
  const runBuilderHydration = useCallback(
1136
1168
  (
1137
1169
  sourceId: string,
@@ -1197,10 +1229,11 @@ function DatabaseTable({
1197
1229
  continuationKey,
1198
1230
  );
1199
1231
  if (
1200
- !runSourceRefresh(candidate.id, () =>
1201
- setBuilderContinuationClientErrorKeys((current) =>
1202
- addUniqueKey(current, continuationKey),
1203
- ),
1232
+ !runSourceRefresh(
1233
+ candidate.id,
1234
+ () => handleBuilderContinuationError(continuationKey),
1235
+ candidate.metadata.lastReadNextOffset,
1236
+ () => handleBuilderContinuationSuccess(continuationKey),
1204
1237
  )
1205
1238
  ) {
1206
1239
  autoContinueBuilderSourceRef.current.delete(continuationKey);
@@ -1210,6 +1243,8 @@ function DatabaseTable({
1210
1243
  builderSources,
1211
1244
  canEdit,
1212
1245
  isActive,
1246
+ handleBuilderContinuationError,
1247
+ handleBuilderContinuationSuccess,
1213
1248
  refreshSource.isPending,
1214
1249
  runSourceRefresh,
1215
1250
  ]);
@@ -1273,10 +1308,11 @@ function DatabaseTable({
1273
1308
  return;
1274
1309
  }
1275
1310
  if (
1276
- runSourceRefresh(candidate.id, () =>
1277
- setBuilderContinuationClientErrorKeys((current) =>
1278
- addUniqueKey(current, continuationKey),
1279
- ),
1311
+ runSourceRefresh(
1312
+ candidate.id,
1313
+ () => handleBuilderContinuationError(continuationKey),
1314
+ candidate.metadata.lastReadNextOffset,
1315
+ () => handleBuilderContinuationSuccess(continuationKey),
1280
1316
  )
1281
1317
  ) {
1282
1318
  builderContinuationWatchdogRef.current.set(
@@ -1295,6 +1331,8 @@ function DatabaseTable({
1295
1331
  builderSources,
1296
1332
  canEdit,
1297
1333
  isActive,
1334
+ handleBuilderContinuationError,
1335
+ handleBuilderContinuationSuccess,
1298
1336
  refreshSource.isPending,
1299
1337
  runSourceRefresh,
1300
1338
  ]);
@@ -2545,10 +2583,12 @@ function DatabaseTable({
2545
2583
  continuationKey,
2546
2584
  );
2547
2585
  builderContinuationWatchdogRef.current.set(continuationKey, 0);
2548
- runSourceRefresh(builderSource.id, () =>
2549
- setBuilderContinuationClientErrorKeys((current) =>
2550
- addUniqueKey(current, continuationKey),
2551
- ),
2586
+ builderContinuationFailureRef.current.delete(continuationKey);
2587
+ runSourceRefresh(
2588
+ builderSource.id,
2589
+ () => handleBuilderContinuationError(continuationKey),
2590
+ builderSource.metadata.lastReadNextOffset,
2591
+ () => handleBuilderContinuationSuccess(continuationKey),
2552
2592
  );
2553
2593
  }}
2554
2594
  />
@@ -2897,36 +2937,44 @@ function DatabaseTable({
2897
2937
  onReviewBuilderUpdate={(sourceId) => {
2898
2938
  openBuilderReview(sourceId);
2899
2939
  }}
2900
- onSetBuilderLiveWrites={(settings) =>
2901
- setSourceWriteMode.mutate(
2902
- {
2903
- documentId: document.id,
2904
- sourceId: settings.sourceId,
2905
- writeMode: settings.writeMode,
2906
- allowPublicationTransitions:
2907
- settings.writeMode === "publish_updates" &&
2908
- settings.allowPublicationTransitions === true,
2940
+ onSetBuilderLiveWrites={(settings) => {
2941
+ const request = {
2942
+ documentId: document.id,
2943
+ sourceId: settings.sourceId,
2944
+ writeMode: settings.writeMode,
2945
+ allowPublicationTransitions:
2946
+ settings.writeMode === "publish_updates" &&
2947
+ settings.allowPublicationTransitions === true,
2948
+ };
2949
+ const previous = queryClient.getQueriesData<ContentDatabaseResponse>(
2950
+ contentDatabaseQueryFilter(document.id),
2951
+ );
2952
+ queryClient.setQueriesData<ContentDatabaseResponse>(
2953
+ contentDatabaseQueryFilter(document.id),
2954
+ (current) => applyOptimisticBuilderWriteMode(current, request),
2955
+ );
2956
+ setSourceWriteMode.mutate(request, {
2957
+ onSuccess: () => {
2958
+ toast.success(dbText("builderWriteModeUpdated"), {
2959
+ description:
2960
+ settings.writeMode === "publish_updates"
2961
+ ? "Approved updates can write through to Builder while preserving publication state."
2962
+ : settings.writeMode === "stage_only"
2963
+ ? "Approved updates will stage Builder autosave revisions."
2964
+ : "Builder writes are disabled for this source.",
2965
+ });
2909
2966
  },
2910
- {
2911
- onSuccess: () => {
2912
- toast.success(dbText("builderWriteModeUpdated"), {
2913
- description:
2914
- settings.writeMode === "publish_updates"
2915
- ? "Approved updates can write through to Builder while preserving publication state."
2916
- : settings.writeMode === "stage_only"
2917
- ? "Approved updates will stage Builder autosave revisions."
2918
- : "Builder writes are disabled for this source.",
2919
- });
2920
- },
2921
- onError: (error) => {
2922
- toast.error(dbText("builderWriteModeWasNotChanged"), {
2923
- description:
2924
- error instanceof Error ? error.message : dbText("tryAgain"),
2925
- });
2926
- },
2967
+ onError: (error) => {
2968
+ for (const [queryKey, data] of previous) {
2969
+ queryClient.setQueryData(queryKey, data);
2970
+ }
2971
+ toast.error(dbText("builderWriteModeWasNotChanged"), {
2972
+ description:
2973
+ error instanceof Error ? error.message : dbText("tryAgain"),
2974
+ });
2927
2975
  },
2928
- )
2929
- }
2976
+ });
2977
+ }}
2930
2978
  sourceActionPending={
2931
2979
  attachSource.isPending ||
2932
2980
  changeSourceRole.isPending ||
@@ -2967,6 +3015,7 @@ function DatabaseTable({
2967
3015
  executeBuilderExecution.isPending ||
2968
3016
  cancelPreparedBuilderUpdate.isPending
2969
3017
  }
3018
+ executionPending={executeBuilderExecution.isPending}
2970
3019
  error={
2971
3020
  builderReviewError ??
2972
3021
  (builderReviewPreviewQuery.error instanceof Error
@@ -10196,6 +10245,10 @@ export function builderSourceContinuationWatchdogDecision(refires: number) {
10196
10245
  return "refire";
10197
10246
  }
10198
10247
 
10248
+ export function builderSourceContinuationFailureDecision(failures: number) {
10249
+ return failures <= 1 ? "retry" : "error";
10250
+ }
10251
+
10199
10252
  export function builderSourceContinuationWatchdogDelay(refires: number) {
10200
10253
  return Math.min(
10201
10254
  BUILDER_SOURCE_CONTINUATION_MAX_BACKOFF_MS,
@@ -291,6 +291,7 @@ export function BuilderSourceReviewDialog({
291
291
  source,
292
292
  canEdit,
293
293
  pending,
294
+ executionPending = false,
294
295
  batchResult = null,
295
296
  error = null,
296
297
  checkedAt,
@@ -307,6 +308,7 @@ export function BuilderSourceReviewDialog({
307
308
  source: ContentDatabaseSource | null;
308
309
  canEdit: boolean;
309
310
  pending: boolean;
311
+ executionPending?: boolean;
310
312
  // Optional so the inline-database caller (DatabaseView) that doesn't surface
311
313
  // batch results can still mount the dialog.
312
314
  batchResult?: ExecuteBuilderSourceBatchResponse | null;
@@ -485,7 +487,7 @@ export function BuilderSourceReviewDialog({
485
487
  : review?.result.status,
486
488
  );
487
489
  const footerHint = pending
488
- ? preparedForExecution
490
+ ? executionPending
489
491
  ? "Sending to Builder…"
490
492
  : "Preparing full review…"
491
493
  : hasUnconfirmedUnpublish
@@ -517,7 +519,7 @@ export function BuilderSourceReviewDialog({
517
519
  ? `Push ${selectedReviewRows.length} updates`
518
520
  : "Push update";
519
521
  const buttonLabel = pending
520
- ? preparedForExecution
522
+ ? executionPending
521
523
  ? "Sending…"
522
524
  : "Preparing…"
523
525
  : checked && review?.result.status === "running"