@agent-native/core 0.90.1 → 0.90.3

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 (714) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +26 -0
  3. package/corpus/core/docs/content/agent-native-toolkit.mdx +87 -80
  4. package/corpus/core/docs/content/locales/ar-SA/agent-native-toolkit.mdx +89 -80
  5. package/corpus/core/docs/content/locales/de-DE/agent-native-toolkit.mdx +89 -80
  6. package/corpus/core/docs/content/locales/es-ES/agent-native-toolkit.mdx +89 -80
  7. package/corpus/core/docs/content/locales/fr-FR/agent-native-toolkit.mdx +89 -80
  8. package/corpus/core/docs/content/locales/hi-IN/agent-native-toolkit.mdx +89 -80
  9. package/corpus/core/docs/content/locales/ja-JP/agent-native-toolkit.mdx +89 -80
  10. package/corpus/core/docs/content/locales/ko-KR/agent-native-toolkit.mdx +89 -80
  11. package/corpus/core/docs/content/locales/pt-BR/agent-native-toolkit.mdx +89 -80
  12. package/corpus/core/docs/content/locales/zh-CN/agent-native-toolkit.mdx +89 -80
  13. package/corpus/core/docs/content/locales/zh-TW/agent-native-toolkit.mdx +89 -80
  14. package/corpus/core/docs/content/toolkit-agent-ux.mdx +43 -0
  15. package/corpus/core/docs/content/toolkit-app-adapters.mdx +9 -0
  16. package/corpus/core/docs/content/toolkit-collaboration-ui.mdx +12 -0
  17. package/corpus/core/docs/content/toolkit-collaboration.mdx +74 -0
  18. package/corpus/core/docs/content/toolkit-command-navigation.mdx +106 -0
  19. package/corpus/core/docs/content/toolkit-comments-review.mdx +98 -0
  20. package/corpus/core/docs/content/toolkit-history.mdx +95 -0
  21. package/corpus/core/docs/content/toolkit-observability.mdx +41 -0
  22. package/corpus/core/docs/content/toolkit-org-team.mdx +80 -0
  23. package/corpus/core/docs/content/toolkit-resources.mdx +57 -0
  24. package/corpus/core/docs/content/toolkit-settings.mdx +65 -0
  25. package/corpus/core/docs/content/toolkit-setup-connections.mdx +109 -0
  26. package/corpus/core/docs/content/toolkit-sharing-ui.mdx +18 -6
  27. package/corpus/core/docs/content/toolkit-sharing.mdx +81 -0
  28. package/corpus/core/docs/content/toolkit-shell-hooks.mdx +9 -0
  29. package/corpus/core/docs/content/toolkit-ui.mdx +9 -0
  30. package/corpus/core/package.json +35 -1
  31. package/corpus/core/src/agent/engine/ai-sdk-engine.ts +19 -1
  32. package/corpus/core/src/agent/engine/anthropic-engine.ts +25 -1
  33. package/corpus/core/src/agent/engine/registry.ts +23 -16
  34. package/corpus/core/src/agent/production-agent.ts +37 -6
  35. package/corpus/core/src/client/AgentPanel.tsx +1 -1
  36. package/corpus/core/src/client/command-navigation/index.ts +15 -0
  37. package/corpus/core/src/client/comments-review/index.ts +1 -0
  38. package/corpus/core/src/client/history/VersionHistoryPanel.tsx +145 -0
  39. package/corpus/core/src/client/history/index.ts +18 -0
  40. package/corpus/core/src/client/history/use-history.ts +86 -0
  41. package/corpus/core/src/client/index.ts +76 -9
  42. package/corpus/core/src/client/org/OrgSwitcher.tsx +6 -6
  43. package/corpus/core/src/client/org/RequireActiveOrg.tsx +2 -2
  44. package/corpus/core/src/client/org/TeamPage.tsx +3 -3
  45. package/corpus/core/src/client/org/hooks.ts +32 -0
  46. package/corpus/core/src/client/org/index.ts +30 -1
  47. package/corpus/core/src/client/org-team/index.ts +1 -0
  48. package/corpus/core/src/client/review/ReviewStatusBadge.tsx +39 -0
  49. package/corpus/core/src/client/review/ReviewThreadPanel.tsx +278 -0
  50. package/corpus/core/src/client/review/index.ts +25 -0
  51. package/corpus/core/src/client/review/use-review.ts +114 -0
  52. package/corpus/core/src/client/rich-markdown-editor/useCollabReconcile.ts +11 -6
  53. package/corpus/core/src/client/settings/index.ts +11 -1
  54. package/corpus/core/src/client/setup-connections/BuilderConnectCard.tsx +94 -0
  55. package/corpus/core/src/client/setup-connections/ProviderReadinessBadge.tsx +73 -0
  56. package/corpus/core/src/client/setup-connections/SetupConnectionsPage.tsx +94 -0
  57. package/corpus/core/src/client/setup-connections/index.ts +46 -0
  58. package/corpus/core/src/client/sharing/ShareButton.tsx +20 -14
  59. package/corpus/core/src/client/sharing/ShareDialog.tsx +3 -3
  60. package/corpus/core/src/client/use-action.ts +39 -2
  61. package/corpus/core/src/client/use-db-sync.ts +44 -18
  62. package/corpus/core/src/command-navigation/actions.ts +100 -0
  63. package/corpus/core/src/command-navigation/index.ts +12 -0
  64. package/corpus/core/src/comments-review/index.ts +1 -0
  65. package/corpus/core/src/db/create-get-db.ts +32 -2
  66. package/corpus/core/src/file-upload/builder.ts +24 -3
  67. package/corpus/core/src/file-upload/types.ts +3 -1
  68. package/corpus/core/src/history/actions/create-resource-version.ts +89 -0
  69. package/corpus/core/src/history/actions/get-resource-version.ts +60 -0
  70. package/corpus/core/src/history/actions/list-resource-history.ts +53 -0
  71. package/corpus/core/src/history/actions/list-resource-versions.ts +43 -0
  72. package/corpus/core/src/history/actions/restore-resource-version.ts +96 -0
  73. package/corpus/core/src/history/index.ts +27 -0
  74. package/corpus/core/src/history/registry.ts +99 -0
  75. package/corpus/core/src/history/store.ts +348 -0
  76. package/corpus/core/src/history/types.ts +69 -0
  77. package/corpus/core/src/index.ts +10 -0
  78. package/corpus/core/src/navigation/index.ts +170 -0
  79. package/corpus/core/src/org/index.ts +8 -0
  80. package/corpus/core/src/org/permissions.ts +30 -0
  81. package/corpus/core/src/org-team/index.ts +1 -0
  82. package/corpus/core/src/private-blob/registry.ts +1 -0
  83. package/corpus/core/src/review/actions/consume-review-feedback.ts +38 -0
  84. package/corpus/core/src/review/actions/create-review-comment.ts +107 -0
  85. package/corpus/core/src/review/actions/delete-review-comment.ts +55 -0
  86. package/corpus/core/src/review/actions/get-review-feedback.ts +54 -0
  87. package/corpus/core/src/review/actions/list-review-comments.ts +53 -0
  88. package/corpus/core/src/review/actions/reply-review-comment.ts +107 -0
  89. package/corpus/core/src/review/actions/resolve-review-thread.ts +60 -0
  90. package/corpus/core/src/review/actions/set-review-status.ts +58 -0
  91. package/corpus/core/src/review/index.ts +36 -0
  92. package/corpus/core/src/review/mentions.ts +44 -0
  93. package/corpus/core/src/review/registry.ts +99 -0
  94. package/corpus/core/src/review/store.ts +687 -0
  95. package/corpus/core/src/review/types.ts +97 -0
  96. package/corpus/core/src/server/action-discovery.ts +54 -0
  97. package/corpus/core/src/server/agent-engine-api-key-route.ts +5 -0
  98. package/corpus/core/src/server/credential-provider.ts +108 -0
  99. package/corpus/core/src/server/framework-request-handler.ts +52 -4
  100. package/corpus/core/src/setup-connections/index.ts +89 -0
  101. package/corpus/core/src/setup-connections/onboarding.ts +47 -0
  102. package/corpus/core/src/transcription/builder-transcription.ts +9 -2
  103. package/corpus/core/src/vite/action-types-plugin.ts +52 -0
  104. package/corpus/templates/analytics/app/components/layout/Layout.tsx +2 -1
  105. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +2 -2
  106. package/corpus/templates/analytics/app/global.css +22 -10
  107. package/corpus/templates/analytics/app/i18n/zh-TW.ts +6 -1
  108. package/corpus/templates/analytics/app/i18n-data.ts +81 -13
  109. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -2
  110. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
  111. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +528 -108
  112. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +466 -183
  113. package/corpus/templates/analytics/changelog/2026-07-07-session-replays-load-sooner-show-clearer-activity-timelines-.md +6 -0
  114. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +5 -0
  115. package/corpus/templates/assets/.agents/skills/logo-composite/SKILL.md +34 -6
  116. package/corpus/templates/assets/AGENTS.md +23 -4
  117. package/corpus/templates/assets/actions/_generation-preset-settings.ts +48 -0
  118. package/corpus/templates/assets/actions/_helpers.ts +8 -1
  119. package/corpus/templates/assets/actions/_preset-skeleton-validation.ts +64 -0
  120. package/corpus/templates/assets/actions/create-generation-preset.ts +11 -3
  121. package/corpus/templates/assets/actions/generate-image-batch.ts +7 -2
  122. package/corpus/templates/assets/actions/generate-image.ts +342 -25
  123. package/corpus/templates/assets/actions/get-library.ts +4 -3
  124. package/corpus/templates/assets/actions/navigate.ts +2 -1
  125. package/corpus/templates/assets/actions/refresh-generation-run.ts +5 -1
  126. package/corpus/templates/assets/actions/update-asset.ts +1 -0
  127. package/corpus/templates/assets/actions/update-generation-preset.ts +8 -1
  128. package/corpus/templates/assets/actions/view-screen.ts +8 -1
  129. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +9 -1
  130. package/corpus/templates/assets/app/hooks/use-image-model-menu.ts +1 -0
  131. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +11 -0
  132. package/corpus/templates/assets/app/i18n/zh-TW.ts +64 -0
  133. package/corpus/templates/assets/app/i18n-data.ts +650 -0
  134. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -1
  135. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +44 -33
  136. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1474 -0
  137. package/corpus/templates/assets/app/routes/library.tsx +43 -1
  138. package/corpus/templates/assets/server/lib/generation.ts +366 -44
  139. package/corpus/templates/assets/server/lib/image-processing.ts +252 -0
  140. package/corpus/templates/assets/server/lib/preset-skeleton.ts +137 -0
  141. package/corpus/templates/assets/shared/api.ts +47 -0
  142. package/corpus/templates/brain/app/routes/settings.tsx +2 -2
  143. package/corpus/templates/calendar/app/components/calendar/AttendeeAutocomplete.tsx +32 -6
  144. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +120 -40
  145. package/corpus/templates/calendar/app/hooks/use-people.ts +55 -0
  146. package/corpus/templates/calendar/changelog/2026-07-07-guest-suggestions-now-find-coworkers-from-your-google-worksp.md +6 -0
  147. package/corpus/templates/calendar/changelog/2026-07-07-zoom-stays-selected-after-connecting-it-from-a-draft-event.md +6 -0
  148. package/corpus/templates/clips/actions/create-recording.ts +5 -1
  149. package/corpus/templates/clips/actions/finalize-recording.ts +140 -1
  150. package/corpus/templates/clips/actions/import-loom-recording.ts +1 -0
  151. package/corpus/templates/clips/actions/lib/ensure-seekable-video.ts +1 -0
  152. package/corpus/templates/clips/actions/request-transcript.ts +31 -0
  153. package/corpus/templates/clips/actions/set-thumbnail.ts +2 -0
  154. package/corpus/templates/clips/app/components/library/library-layout.tsx +7 -2
  155. package/corpus/templates/clips/app/components/library/organization-switcher.tsx +2 -2
  156. package/corpus/templates/clips/app/components/library/sort-menu.tsx +1 -1
  157. package/corpus/templates/clips/app/components/player/scrubber-position.ts +9 -0
  158. package/corpus/templates/clips/app/components/player/scrubber.tsx +5 -2
  159. package/corpus/templates/clips/app/components/player/share-dialog.tsx +69 -41
  160. package/corpus/templates/clips/app/components/player/video-player.tsx +38 -1
  161. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +114 -33
  162. package/corpus/templates/clips/app/components/sharing/share-ui.tsx +3 -3
  163. package/corpus/templates/clips/app/components/sharing/slack-share-hint.tsx +9 -19
  164. package/corpus/templates/clips/app/routes/_app.library._index.tsx +27 -1
  165. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +9 -3
  166. package/corpus/templates/clips/app/routes/record.tsx +178 -80
  167. package/corpus/templates/clips/changelog/2026-07-02-recordings-now-wait-for-storage-media-to-serve-before-finishing.md +6 -0
  168. package/corpus/templates/clips/changelog/2026-07-07-a-pending-app-update-no-longer-blocks-screen-recording-updat.md +6 -0
  169. package/corpus/templates/clips/changelog/2026-07-07-chrome-extension-recordings-keep-audio-after-refreshing-tab.md +6 -0
  170. package/corpus/templates/clips/changelog/2026-07-07-clip-playback-progress-now-starts-and-finishes-at-the-right-times.md +6 -0
  171. package/corpus/templates/clips/changelog/2026-07-07-sharing-public-clips-now-stays-focused-on-the-normal-link-wi.md +6 -0
  172. package/corpus/templates/clips/chrome-extension/src/background.ts +18 -0
  173. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +23 -8
  174. package/corpus/templates/clips/desktop/src/app.tsx +40 -3
  175. package/corpus/templates/clips/desktop/src/hooks/useMediaDevices.ts +19 -7
  176. package/corpus/templates/clips/desktop/src/lib/permissions.ts +2 -0
  177. package/corpus/templates/clips/desktop/src/lib/updater.ts +25 -2
  178. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +7 -0
  179. package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +1 -0
  180. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +23 -6
  181. package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +28 -3
  182. package/corpus/templates/clips/server/routes/api/video/[recordingId].get.ts +13 -6
  183. package/corpus/templates/clips/shared/finalize-recovery.ts +35 -3
  184. package/corpus/templates/content/AGENTS.md +52 -52
  185. package/corpus/templates/content/actions/_builder-cms-read-client.ts +188 -8
  186. package/corpus/templates/content/actions/_builder-docs-client.ts +41 -3
  187. package/corpus/templates/content/actions/_database-source-utils.ts +998 -160
  188. package/corpus/templates/content/actions/_database-utils.ts +44 -4
  189. package/corpus/templates/content/actions/_notion-action-utils.ts +6 -2
  190. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +23 -1
  191. package/corpus/templates/content/actions/attach-content-database-source.ts +85 -12
  192. package/corpus/templates/content/actions/change-content-database-source-role.ts +9 -1
  193. package/corpus/templates/content/actions/delete-content-database.ts +1 -0
  194. package/corpus/templates/content/actions/delete-document.ts +337 -52
  195. package/corpus/templates/content/actions/get-document.ts +11 -2
  196. package/corpus/templates/content/actions/list-content-databases.ts +57 -9
  197. package/corpus/templates/content/actions/prepare-builder-source-review.ts +23 -6
  198. package/corpus/templates/content/actions/refresh-content-database-source.ts +26 -3
  199. package/corpus/templates/content/actions/restore-content-database.ts +1 -0
  200. package/corpus/templates/content/actions/update-document.ts +43 -0
  201. package/corpus/templates/content/app/components/editor/BuilderBodySyncingNotice.tsx +21 -0
  202. package/corpus/templates/content/app/components/editor/DocumentDatabase.tsx +0 -1
  203. package/corpus/templates/content/app/components/editor/DocumentEditor.tsx +70 -28
  204. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +69 -17
  205. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +4 -4
  206. package/corpus/templates/content/app/components/editor/body-hydration.ts +112 -0
  207. package/corpus/templates/content/app/components/editor/builder-body-hydration-pump.ts +52 -0
  208. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +9076 -2876
  209. package/corpus/templates/content/app/components/editor/database-sources/BuilderSourceReviewDialog.tsx +41 -1
  210. package/corpus/templates/content/app/components/editor/extensions/ImageBlock.tsx +36 -11
  211. package/corpus/templates/content/app/components/editor/previewDocumentSaveController.ts +21 -1
  212. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +11 -1
  213. package/corpus/templates/content/app/components/sidebar/DocumentTreeItem.tsx +11 -8
  214. package/corpus/templates/content/app/global.css +41 -0
  215. package/corpus/templates/content/app/hooks/use-content-database.ts +346 -42
  216. package/corpus/templates/content/app/hooks/use-db-sync.ts +9 -0
  217. package/corpus/templates/content/app/hooks/use-documents.ts +47 -0
  218. package/corpus/templates/content/app/hooks/use-notion.ts +12 -10
  219. package/corpus/templates/content/app/i18n/zh-TW.ts +35 -0
  220. package/corpus/templates/content/app/i18n-data.ts +355 -0
  221. package/corpus/templates/content/app/root.tsx +22 -0
  222. package/corpus/templates/content/changelog/2026-07-01-builder-database-sources-keep-loading-rows-in-the-background.md +6 -0
  223. package/corpus/templates/content/changelog/2026-07-01-builder-source-attach-starts-with-usable-rows.md +6 -0
  224. package/corpus/templates/content/changelog/2026-07-01-builder-source-attachment-now-stays-connected-after-large.md +6 -0
  225. package/corpus/templates/content/changelog/2026-07-01-builder-source-refreshes-now-show-progress-for-large-docs-blog.md +6 -0
  226. package/corpus/templates/content/changelog/2026-07-01-database-pages-now-delete-to-trash-without-route-errors.md +6 -0
  227. package/corpus/templates/content/changelog/2026-07-02-builder-source-refreshes-recover-from-stalled-background-row.md +6 -0
  228. package/corpus/templates/content/changelog/2026-07-02-builder-sourced-rows-open-with-sync-protection.md +6 -0
  229. package/corpus/templates/content/changelog/2026-07-02-database-rows-and-pages-open-faster-from-recently-loaded-dat.md +6 -0
  230. package/corpus/templates/content/changelog/2026-07-02-preview-panels-close-reliably-narrow-sidebar-row-actions-sta.md +6 -0
  231. package/corpus/templates/content/changelog/2026-07-06-builder-sync-progress-no-longer-shows-continue.md +6 -0
  232. package/corpus/templates/content/changelog/2026-07-07-permanently-deleting-trashed-databases-now-finishes-reliably.md +6 -0
  233. package/corpus/templates/content/shared/api.ts +13 -0
  234. package/corpus/templates/content/shared/builder-mdx.ts +16 -3
  235. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +23 -13
  236. package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +98 -0
  237. package/corpus/templates/design/.agents/skills/shader-fills/SKILL.md +162 -0
  238. package/corpus/templates/design/AGENTS.md +9 -2
  239. package/corpus/templates/design/actions/add-breakpoint.ts +6 -3
  240. package/corpus/templates/design/actions/apply-a11y-fix.ts +57 -37
  241. package/corpus/templates/design/actions/apply-component-prop-edit.ts +41 -20
  242. package/corpus/templates/design/actions/apply-motion-edit.ts +251 -38
  243. package/corpus/templates/design/actions/apply-shader-fill.ts +90 -66
  244. package/corpus/templates/design/actions/apply-visual-edit.ts +282 -55
  245. package/corpus/templates/design/actions/create-component.ts +29 -60
  246. package/corpus/templates/design/actions/create-file.ts +9 -2
  247. package/corpus/templates/design/actions/duplicate-design.ts +31 -2
  248. package/corpus/templates/design/actions/edit-design.ts +36 -38
  249. package/corpus/templates/design/actions/generate-design-title.ts +87 -0
  250. package/corpus/templates/design/actions/generate-design.ts +53 -18
  251. package/corpus/templates/design/actions/generate-screens.ts +108 -4
  252. package/corpus/templates/design/actions/get-motion-timeline.ts +35 -7
  253. package/corpus/templates/design/actions/insert-asset.ts +34 -29
  254. package/corpus/templates/design/actions/insert-design-native-asset.ts +129 -32
  255. package/corpus/templates/design/actions/insert-figma-library-asset.ts +35 -30
  256. package/corpus/templates/design/actions/present-design-variants.ts +7 -2
  257. package/corpus/templates/design/actions/remove-motion-timeline.ts +31 -36
  258. package/corpus/templates/design/actions/update-file.ts +190 -75
  259. package/corpus/templates/design/app/components/design/BreakpointBar.tsx +403 -0
  260. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +469 -222
  261. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +708 -53
  262. package/corpus/templates/design/app/components/design/DesignExtensionsPanel.tsx +83 -3
  263. package/corpus/templates/design/app/components/design/EditPanel.tsx +1091 -6948
  264. package/corpus/templates/design/app/components/design/LayersPanel.tsx +419 -85
  265. package/corpus/templates/design/app/components/design/MotionDock.tsx +1124 -103
  266. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +2610 -1398
  267. package/corpus/templates/design/app/components/design/ReadOnlyDesignBanner.tsx +22 -0
  268. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2182 -235
  269. package/corpus/templates/design/app/components/design/bridge/motion-preview.bridge.ts +187 -11
  270. package/corpus/templates/design/app/components/design/bridge/shader-fill-preview.bridge.ts +376 -1
  271. package/corpus/templates/design/app/components/design/bridge/shader-runtime.bridge.ts +746 -0
  272. package/corpus/templates/design/app/components/design/canvas-primitive-style.ts +17 -0
  273. package/corpus/templates/design/app/components/design/code-workbench/SideBar.tsx +1 -1
  274. package/corpus/templates/design/app/components/design/code-workbench/explorer/FileTree.tsx +1 -3
  275. package/corpus/templates/design/app/components/design/code-workbench/quickinput/QuickInput.tsx +1 -1
  276. package/corpus/templates/design/app/components/design/edit-panel/appearance-properties.tsx +518 -0
  277. package/corpus/templates/design/app/components/design/edit-panel/code-inspect-helpers.tsx +562 -0
  278. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +763 -0
  279. package/corpus/templates/design/app/components/design/edit-panel/document-colors.ts +94 -0
  280. package/corpus/templates/design/app/components/design/edit-panel/effects-properties.tsx +780 -0
  281. package/corpus/templates/design/app/components/design/edit-panel/element-classification.ts +446 -0
  282. package/corpus/templates/design/app/components/design/edit-panel/element-identity.ts +79 -0
  283. package/corpus/templates/design/app/components/design/edit-panel/field-primitives.tsx +315 -0
  284. package/corpus/templates/design/app/components/design/edit-panel/fill-gradient-helpers.ts +344 -0
  285. package/corpus/templates/design/app/components/design/edit-panel/fill-properties.tsx +594 -0
  286. package/corpus/templates/design/app/components/design/edit-panel/frame-presets-panel.tsx +119 -0
  287. package/corpus/templates/design/app/components/design/edit-panel/inspector-controls.tsx +248 -0
  288. package/corpus/templates/design/app/components/design/edit-panel/interaction-state-helpers.ts +45 -0
  289. package/corpus/templates/design/app/components/design/edit-panel/layout-properties.tsx +693 -0
  290. package/corpus/templates/design/app/components/design/edit-panel/panel-primitives.tsx +724 -0
  291. package/corpus/templates/design/app/components/design/edit-panel/position-helpers.ts +207 -0
  292. package/corpus/templates/design/app/components/design/edit-panel/position-layout-properties.tsx +628 -0
  293. package/corpus/templates/design/app/components/design/edit-panel/selection-helpers.ts +91 -0
  294. package/corpus/templates/design/app/components/design/edit-panel/stroke-properties.tsx +649 -0
  295. package/corpus/templates/design/app/components/design/edit-panel/style-change-types.ts +118 -0
  296. package/corpus/templates/design/app/components/design/edit-panel/style-options.ts +88 -0
  297. package/corpus/templates/design/app/components/design/edit-panel/transform-helpers.ts +100 -0
  298. package/corpus/templates/design/app/components/design/edit-panel/typography-helpers.ts +117 -0
  299. package/corpus/templates/design/app/components/design/edit-panel/typography-properties.tsx +501 -0
  300. package/corpus/templates/design/app/components/design/inspector/AutoLayoutMatrix.tsx +91 -40
  301. package/corpus/templates/design/app/components/design/inspector/BreakpointOverrideIndicator.tsx +82 -0
  302. package/corpus/templates/design/app/components/design/inspector/DesignColorPicker.tsx +189 -87
  303. package/corpus/templates/design/app/components/design/inspector/GlslShaderPanel.tsx +1097 -0
  304. package/corpus/templates/design/app/components/design/inspector/GradientEditor.tsx +44 -0
  305. package/corpus/templates/design/app/components/design/inspector/InteractionStatePanel.tsx +258 -0
  306. package/corpus/templates/design/app/components/design/inspector/MotionKeyframeDiamond.tsx +144 -0
  307. package/corpus/templates/design/app/components/design/inspector/ScrubInput.tsx +99 -9
  308. package/corpus/templates/design/app/components/design/inspector/frame-size-presets.ts +116 -0
  309. package/corpus/templates/design/app/components/design/inspector/index.ts +26 -0
  310. package/corpus/templates/design/app/components/design/inspector/scrub-input-utils.ts +29 -0
  311. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +171 -0
  312. package/corpus/templates/design/app/components/design/multi-screen/board-surface-html.ts +220 -0
  313. package/corpus/templates/design/app/components/design/multi-screen/chrome-transitions.ts +35 -0
  314. package/corpus/templates/design/app/components/design/multi-screen/cross-screen-drop.ts +134 -0
  315. package/corpus/templates/design/app/components/design/multi-screen/culling.ts +185 -0
  316. package/corpus/templates/design/app/components/design/multi-screen/gradient-overlay-geometry.ts +120 -0
  317. package/corpus/templates/design/app/components/design/multi-screen/types.ts +892 -0
  318. package/corpus/templates/design/app/components/design/multi-screen/vector-edit-geometry.ts +21 -0
  319. package/corpus/templates/design/app/components/design/types.ts +15 -0
  320. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +109 -5
  321. package/corpus/templates/design/app/global.css +8 -8
  322. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +259 -2
  323. package/corpus/templates/design/app/i18n/zh-TW.ts +163 -9
  324. package/corpus/templates/design/app/i18n-breakpoints.ts +205 -0
  325. package/corpus/templates/design/app/i18n-data.ts +1664 -152
  326. package/corpus/templates/design/app/i18n-motion.ts +563 -0
  327. package/corpus/templates/design/app/lib/design-import.ts +73 -0
  328. package/corpus/templates/design/app/pages/Index.tsx +41 -22
  329. package/corpus/templates/design/changelog/2026-06-29-design-inspector-controls-now-match-figmas-softer-dark-chrome.md +1 -1
  330. package/corpus/templates/design/changelog/2026-06-30-design-now-has-a-figma-style-left-rail-for-file-agent-assets.md +1 -1
  331. package/corpus/templates/design/changelog/2026-06-30-design-tools-now-use-a-quieter-figma-style-list-layout.md +1 -1
  332. package/corpus/templates/design/changelog/2026-06-30-multi-select-outlines-now-stay-consistent-across-all-screens.md +1 -1
  333. package/corpus/templates/design/changelog/2026-07-03-canvas-interactions-now-match-figma-rotation-aware-resizing-.md +1 -1
  334. package/corpus/templates/design/changelog/2026-07-03-layers-panel-matches-figma-top-layer-on-top-drag-with-auto-s.md +1 -1
  335. package/corpus/templates/design/changelog/2026-07-05-designs-get-ai-generated-names-from-your-prompt.md +6 -0
  336. package/corpus/templates/design/changelog/2026-07-05-local-code-screens-keep-their-connection-when-duplicated-and.md +6 -0
  337. package/corpus/templates/design/changelog/2026-07-05-press-a-to-draw-screens-on-the-canvas-inside-a-screen-it-dr.md +6 -0
  338. package/corpus/templates/design/changelog/2026-07-06-3d-rotation-and-perspective-controls-on-every-element.md +6 -0
  339. package/corpus/templates/design/changelog/2026-07-06-ai-generated-and-duplicated-screens-now-get-their-editor-sel.md +6 -0
  340. package/corpus/templates/design/changelog/2026-07-06-applying-shaders-and-fills-in-quick-succession-can-no-longer.md +6 -0
  341. package/corpus/templates/design/changelog/2026-07-06-breakpoint-frames-in-the-overview-now-support-click-to-selec.md +6 -0
  342. package/corpus/templates/design/changelog/2026-07-06-breakpoint-segments-in-the-inspector-now-show-a-device-icon-.md +6 -0
  343. package/corpus/templates/design/changelog/2026-07-06-breakpoint-targeting-now-lives-in-one-compact-control-in-the.md +6 -0
  344. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-can-no-longer-be-overwritten-by-stale-collabora.md +6 -0
  345. package/corpus/templates/design/changelog/2026-07-06-canvas-edits-inside-screens-save-reliably-again.md +6 -0
  346. package/corpus/templates/design/changelog/2026-07-06-cleaner-inspector-removed-the-ask-ai-box-from-the-design-tab.md +6 -0
  347. package/corpus/templates/design/changelog/2026-07-06-clicking-empty-canvas-space-now-deselects-an-element-inside-.md +6 -0
  348. package/corpus/templates/design/changelog/2026-07-06-dragging-an-element-onto-a-rectangle-now-nests-it-inside-wit.md +6 -0
  349. package/corpus/templates/design/changelog/2026-07-06-dragging-screens-and-objects-on-the-canvas-is-now-much-smoot.md +6 -0
  350. package/corpus/templates/design/changelog/2026-07-06-drawing-a-shape-or-text-on-the-canvas-no-longer-flashes-away.md +6 -0
  351. package/corpus/templates/design/changelog/2026-07-06-dropping-an-element-between-two-others-inside-a-screen-now-s.md +6 -0
  352. package/corpus/templates/design/changelog/2026-07-06-duplicating-a-screen-now-places-the-copy-exactly-where-you-d.md +6 -0
  353. package/corpus/templates/design/changelog/2026-07-06-edit-gradients-directly-on-elements-inside-screens-scale-str.md +6 -0
  354. package/corpus/templates/design/changelog/2026-07-06-edits-from-collaborators-and-the-agent-now-update-the-canvas.md +6 -0
  355. package/corpus/templates/design/changelog/2026-07-06-fixed-a-bug-where-most-style-position-and-text-edits-briefly.md +6 -0
  356. package/corpus/templates/design/changelog/2026-07-06-fixed-a-confusing-raw-error-message-when-a-file-upload-fails.md +6 -0
  357. package/corpus/templates/design/changelog/2026-07-06-fixed-a-rare-case-where-an-edit-could-be-silently-overwritte.md +6 -0
  358. package/corpus/templates/design/changelog/2026-07-06-fixed-inserted-assets-sometimes-disappearing-reappearing-or-.md +6 -0
  359. package/corpus/templates/design/changelog/2026-07-06-fixed-inspector-value-scrubbing-producing-erratic-numbers-an.md +6 -0
  360. package/corpus/templates/design/changelog/2026-07-06-fixed-padding-gap-and-min-max-size-scrubbing-not-updating-th.md +6 -0
  361. package/corpus/templates/design/changelog/2026-07-06-fixed-text-stroke-color-turning-black-and-glyphs-disappearin.md +6 -0
  362. package/corpus/templates/design/changelog/2026-07-06-fixed-the-collapsed-effects-and-stroke-inspector-sections-sh.md +6 -0
  363. package/corpus/templates/design/changelog/2026-07-06-fixed-the-new-design-dialog-closing-unexpectedly-when-using-.md +6 -0
  364. package/corpus/templates/design/changelog/2026-07-06-fixed-the-typography-section-missing-when-selecting-text-nes.md +6 -0
  365. package/corpus/templates/design/changelog/2026-07-06-full-view-no-longer-flashes-back-to-the-overview-on-entry-no.md +6 -0
  366. package/corpus/templates/design/changelog/2026-07-06-keyframe-animation-timeline-per-property-keyframes-easing-cu.md +6 -0
  367. package/corpus/templates/design/changelog/2026-07-06-moving-restyling-and-scrubbing-values-on-elements-in-ai-gene.md +6 -0
  368. package/corpus/templates/design/changelog/2026-07-06-padding-and-corner-radius-value-scrubbing-now-snaps-to-whole.md +6 -0
  369. package/corpus/templates/design/changelog/2026-07-06-padding-handles-now-only-resize-on-the-handle-line-itself-sh.md +6 -0
  370. package/corpus/templates/design/changelog/2026-07-06-preview-and-edit-hover-focus-and-pressed-states-with-real-cs.md +6 -0
  371. package/corpus/templates/design/changelog/2026-07-06-promoting-an-element-to-a-component-now-works-correctly-for-.md +6 -0
  372. package/corpus/templates/design/changelog/2026-07-06-reduced-url-history-churn-and-selection-overlay-flicker-whil.md +6 -0
  373. package/corpus/templates/design/changelog/2026-07-06-removed-the-decorative-dot-before-screen-names-in-the-overvi.md +6 -0
  374. package/corpus/templates/design/changelog/2026-07-06-responsive-breakpoints-edit-at-any-width-with-cascading-over.md +6 -0
  375. package/corpus/templates/design/changelog/2026-07-06-screen-labels-no-longer-turn-blue-when-you-hover-inside-the-.md +6 -0
  376. package/corpus/templates/design/changelog/2026-07-06-scrolling-a-screen-with-an-element-selected-stays-smooth-on-.md +6 -0
  377. package/corpus/templates/design/changelog/2026-07-06-selecting-multiple-elements-and-dragging-now-moves-the-whole.md +6 -0
  378. package/corpus/templates/design/changelog/2026-07-06-selection-borders-handles-and-value-readouts-now-stay-the-sa.md +6 -0
  379. package/corpus/templates/design/changelog/2026-07-06-selection-handles-and-editing-chrome-now-stay-a-constant-siz.md +6 -0
  380. package/corpus/templates/design/changelog/2026-07-06-shader-fills-and-effects-ai-authored-or-preset-glsl-shaders-.md +6 -0
  381. package/corpus/templates/design/changelog/2026-07-06-text-drawn-on-the-canvas-now-defaults-to-white-and-inter-in-.md +6 -0
  382. package/corpus/templates/design/changelog/2026-07-06-text-dropped-into-a-shape-now-stays-readable-instead-of-disa.md +6 -0
  383. package/corpus/templates/design/changelog/2026-07-06-the-add-tweak-controls-prompt-no-longer-jumps-to-the-corner-.md +6 -0
  384. package/corpus/templates/design/changelog/2026-07-06-the-padding-value-readout-now-stays-visible-while-you-hover-.md +6 -0
  385. package/corpus/templates/design/changelog/2026-07-06-the-selected-element-attachment-now-clears-from-the-chat-com.md +6 -0
  386. package/corpus/templates/design/changelog/2026-07-06-the-zoom-menu-is-more-compact-with-tight-rows-and-right-alig.md +6 -0
  387. package/corpus/templates/design/changelog/2026-07-06-undo-and-redo-no-longer-flash-the-screen-changes-now-apply-i.md +6 -0
  388. package/corpus/templates/design/changelog/2026-07-06-zoom-presets-like-zoom-to-50-now-stay-on-the-focused-screen-.md +6 -0
  389. package/corpus/templates/design/server/lib/import-design-files.ts +11 -2
  390. package/corpus/templates/design/server/source-workspace.ts +139 -59
  391. package/corpus/templates/design/shared/board-file.ts +11 -2
  392. package/corpus/templates/design/shared/breakpoint-media.ts +543 -0
  393. package/corpus/templates/design/shared/canvas-math.ts +354 -35
  394. package/corpus/templates/design/shared/code-layer.ts +277 -8
  395. package/corpus/templates/design/shared/design-state.ts +11 -4
  396. package/corpus/templates/design/shared/interaction-states.ts +548 -0
  397. package/corpus/templates/design/shared/motion-compiler.ts +186 -23
  398. package/corpus/templates/design/shared/motion-easing.ts +294 -0
  399. package/corpus/templates/design/shared/motion-timeline.ts +400 -34
  400. package/corpus/templates/design/shared/prompt-title.ts +95 -0
  401. package/corpus/templates/design/shared/responsive-classes.ts +448 -10
  402. package/corpus/templates/design/shared/screen-annotation.ts +46 -0
  403. package/corpus/templates/design/shared/shader-fills.ts +1009 -0
  404. package/corpus/templates/design/shared/shader-presets.ts +852 -4
  405. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +15 -2
  406. package/corpus/templates/slides/app/components/editor/ImportButton.tsx +10 -1
  407. package/corpus/templates/slides/app/lib/upload-response.ts +71 -0
  408. package/corpus/templates/slides/app/pages/DeckEditor.tsx +2 -2
  409. package/dist/agent/engine/ai-sdk-engine.d.ts.map +1 -1
  410. package/dist/agent/engine/ai-sdk-engine.js +15 -1
  411. package/dist/agent/engine/ai-sdk-engine.js.map +1 -1
  412. package/dist/agent/engine/anthropic-engine.d.ts.map +1 -1
  413. package/dist/agent/engine/anthropic-engine.js +20 -1
  414. package/dist/agent/engine/anthropic-engine.js.map +1 -1
  415. package/dist/agent/engine/registry.d.ts.map +1 -1
  416. package/dist/agent/engine/registry.js +22 -14
  417. package/dist/agent/engine/registry.js.map +1 -1
  418. package/dist/agent/production-agent.d.ts.map +1 -1
  419. package/dist/agent/production-agent.js +27 -7
  420. package/dist/agent/production-agent.js.map +1 -1
  421. package/dist/client/AgentPanel.js +1 -1
  422. package/dist/client/AgentPanel.js.map +1 -1
  423. package/dist/client/command-navigation/index.d.ts +4 -0
  424. package/dist/client/command-navigation/index.d.ts.map +1 -0
  425. package/dist/client/command-navigation/index.js +4 -0
  426. package/dist/client/command-navigation/index.js.map +1 -0
  427. package/dist/client/comments-review/index.d.ts +2 -0
  428. package/dist/client/comments-review/index.d.ts.map +1 -0
  429. package/dist/client/comments-review/index.js +2 -0
  430. package/dist/client/comments-review/index.js.map +1 -0
  431. package/dist/client/history/VersionHistoryPanel.d.ts +18 -0
  432. package/dist/client/history/VersionHistoryPanel.d.ts.map +1 -0
  433. package/dist/client/history/VersionHistoryPanel.js +24 -0
  434. package/dist/client/history/VersionHistoryPanel.js.map +1 -0
  435. package/dist/client/history/index.d.ts +3 -0
  436. package/dist/client/history/index.d.ts.map +1 -0
  437. package/dist/client/history/index.js +3 -0
  438. package/dist/client/history/index.js.map +1 -0
  439. package/dist/client/history/use-history.d.ts +46 -0
  440. package/dist/client/history/use-history.d.ts.map +1 -0
  441. package/dist/client/history/use-history.js +18 -0
  442. package/dist/client/history/use-history.js.map +1 -0
  443. package/dist/client/index.d.ts +6 -4
  444. package/dist/client/index.d.ts.map +1 -1
  445. package/dist/client/index.js +6 -4
  446. package/dist/client/index.js.map +1 -1
  447. package/dist/client/org/OrgSwitcher.js +5 -5
  448. package/dist/client/org/OrgSwitcher.js.map +1 -1
  449. package/dist/client/org/RequireActiveOrg.js +2 -2
  450. package/dist/client/org/RequireActiveOrg.js.map +1 -1
  451. package/dist/client/org/TeamPage.js +3 -3
  452. package/dist/client/org/TeamPage.js.map +1 -1
  453. package/dist/client/org/hooks.d.ts +12 -1
  454. package/dist/client/org/hooks.d.ts.map +1 -1
  455. package/dist/client/org/hooks.js +15 -0
  456. package/dist/client/org/hooks.js.map +1 -1
  457. package/dist/client/org/index.d.ts +4 -2
  458. package/dist/client/org/index.d.ts.map +1 -1
  459. package/dist/client/org/index.js +3 -1
  460. package/dist/client/org/index.js.map +1 -1
  461. package/dist/client/org-team/index.d.ts +2 -0
  462. package/dist/client/org-team/index.d.ts.map +1 -0
  463. package/dist/client/org-team/index.js +2 -0
  464. package/dist/client/org-team/index.js.map +1 -0
  465. package/dist/client/review/ReviewStatusBadge.d.ts +7 -0
  466. package/dist/client/review/ReviewStatusBadge.d.ts.map +1 -0
  467. package/dist/client/review/ReviewStatusBadge.js +19 -0
  468. package/dist/client/review/ReviewStatusBadge.js.map +1 -0
  469. package/dist/client/review/ReviewThreadPanel.d.ts +20 -0
  470. package/dist/client/review/ReviewThreadPanel.d.ts.map +1 -0
  471. package/dist/client/review/ReviewThreadPanel.js +84 -0
  472. package/dist/client/review/ReviewThreadPanel.js.map +1 -0
  473. package/dist/client/review/index.d.ts +4 -0
  474. package/dist/client/review/index.d.ts.map +1 -0
  475. package/dist/client/review/index.js +4 -0
  476. package/dist/client/review/index.js.map +1 -0
  477. package/dist/client/review/use-review.d.ts +68 -0
  478. package/dist/client/review/use-review.d.ts.map +1 -0
  479. package/dist/client/review/use-review.js +22 -0
  480. package/dist/client/review/use-review.js.map +1 -0
  481. package/dist/client/rich-markdown-editor/useCollabReconcile.d.ts.map +1 -1
  482. package/dist/client/rich-markdown-editor/useCollabReconcile.js +11 -6
  483. package/dist/client/rich-markdown-editor/useCollabReconcile.js.map +1 -1
  484. package/dist/client/settings/index.d.ts +1 -1
  485. package/dist/client/settings/index.d.ts.map +1 -1
  486. package/dist/client/settings/index.js +1 -1
  487. package/dist/client/settings/index.js.map +1 -1
  488. package/dist/client/setup-connections/BuilderConnectCard.d.ts +9 -0
  489. package/dist/client/setup-connections/BuilderConnectCard.d.ts.map +1 -0
  490. package/dist/client/setup-connections/BuilderConnectCard.js +25 -0
  491. package/dist/client/setup-connections/BuilderConnectCard.js.map +1 -0
  492. package/dist/client/setup-connections/ProviderReadinessBadge.d.ts +8 -0
  493. package/dist/client/setup-connections/ProviderReadinessBadge.d.ts.map +1 -0
  494. package/dist/client/setup-connections/ProviderReadinessBadge.js +32 -0
  495. package/dist/client/setup-connections/ProviderReadinessBadge.js.map +1 -0
  496. package/dist/client/setup-connections/SetupConnectionsPage.d.ts +17 -0
  497. package/dist/client/setup-connections/SetupConnectionsPage.d.ts.map +1 -0
  498. package/dist/client/setup-connections/SetupConnectionsPage.js +10 -0
  499. package/dist/client/setup-connections/SetupConnectionsPage.js.map +1 -0
  500. package/dist/client/setup-connections/index.d.ts +7 -0
  501. package/dist/client/setup-connections/index.d.ts.map +1 -0
  502. package/dist/client/setup-connections/index.js +7 -0
  503. package/dist/client/setup-connections/index.js.map +1 -0
  504. package/dist/client/sharing/ShareButton.d.ts +4 -4
  505. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  506. package/dist/client/sharing/ShareButton.js +13 -7
  507. package/dist/client/sharing/ShareButton.js.map +1 -1
  508. package/dist/client/sharing/ShareDialog.js +3 -3
  509. package/dist/client/sharing/ShareDialog.js.map +1 -1
  510. package/dist/client/use-action.d.ts +3 -0
  511. package/dist/client/use-action.d.ts.map +1 -1
  512. package/dist/client/use-action.js +34 -3
  513. package/dist/client/use-action.js.map +1 -1
  514. package/dist/client/use-db-sync.d.ts +4 -0
  515. package/dist/client/use-db-sync.d.ts.map +1 -1
  516. package/dist/client/use-db-sync.js +37 -18
  517. package/dist/client/use-db-sync.js.map +1 -1
  518. package/dist/collab/routes.d.ts +1 -1
  519. package/dist/command-navigation/actions.d.ts +25 -0
  520. package/dist/command-navigation/actions.d.ts.map +1 -0
  521. package/dist/command-navigation/actions.js +64 -0
  522. package/dist/command-navigation/actions.js.map +1 -0
  523. package/dist/command-navigation/index.d.ts +4 -0
  524. package/dist/command-navigation/index.d.ts.map +1 -0
  525. package/dist/command-navigation/index.js +4 -0
  526. package/dist/command-navigation/index.js.map +1 -0
  527. package/dist/comments-review/index.d.ts +2 -0
  528. package/dist/comments-review/index.d.ts.map +1 -0
  529. package/dist/comments-review/index.js +2 -0
  530. package/dist/comments-review/index.js.map +1 -0
  531. package/dist/db/create-get-db.d.ts +25 -0
  532. package/dist/db/create-get-db.d.ts.map +1 -1
  533. package/dist/db/create-get-db.js +25 -2
  534. package/dist/db/create-get-db.js.map +1 -1
  535. package/dist/file-upload/builder.d.ts.map +1 -1
  536. package/dist/file-upload/builder.js +18 -2
  537. package/dist/file-upload/builder.js.map +1 -1
  538. package/dist/file-upload/types.d.ts +3 -0
  539. package/dist/file-upload/types.d.ts.map +1 -1
  540. package/dist/file-upload/types.js.map +1 -1
  541. package/dist/history/actions/create-resource-version.d.ts +10 -0
  542. package/dist/history/actions/create-resource-version.d.ts.map +1 -0
  543. package/dist/history/actions/create-resource-version.js +65 -0
  544. package/dist/history/actions/create-resource-version.js.map +1 -0
  545. package/dist/history/actions/get-resource-version.d.ts +10 -0
  546. package/dist/history/actions/get-resource-version.d.ts.map +1 -0
  547. package/dist/history/actions/get-resource-version.js +38 -0
  548. package/dist/history/actions/get-resource-version.js.map +1 -0
  549. package/dist/history/actions/list-resource-history.d.ts +10 -0
  550. package/dist/history/actions/list-resource-history.d.ts.map +1 -0
  551. package/dist/history/actions/list-resource-history.js +45 -0
  552. package/dist/history/actions/list-resource-history.js.map +1 -0
  553. package/dist/history/actions/list-resource-versions.d.ts +10 -0
  554. package/dist/history/actions/list-resource-versions.d.ts.map +1 -0
  555. package/dist/history/actions/list-resource-versions.js +35 -0
  556. package/dist/history/actions/list-resource-versions.js.map +1 -0
  557. package/dist/history/actions/restore-resource-version.d.ts +11 -0
  558. package/dist/history/actions/restore-resource-version.d.ts.map +1 -0
  559. package/dist/history/actions/restore-resource-version.js +59 -0
  560. package/dist/history/actions/restore-resource-version.js.map +1 -0
  561. package/dist/history/index.d.ts +4 -0
  562. package/dist/history/index.d.ts.map +1 -0
  563. package/dist/history/index.js +3 -0
  564. package/dist/history/index.js.map +1 -0
  565. package/dist/history/registry.d.ts +10 -0
  566. package/dist/history/registry.d.ts.map +1 -0
  567. package/dist/history/registry.js +61 -0
  568. package/dist/history/registry.js.map +1 -0
  569. package/dist/history/store.d.ts +34 -0
  570. package/dist/history/store.d.ts.map +1 -0
  571. package/dist/history/store.js +265 -0
  572. package/dist/history/store.js.map +1 -0
  573. package/dist/history/types.d.ts +55 -0
  574. package/dist/history/types.d.ts.map +1 -0
  575. package/dist/history/types.js +2 -0
  576. package/dist/history/types.js.map +1 -0
  577. package/dist/index.d.ts +6 -0
  578. package/dist/index.d.ts.map +1 -1
  579. package/dist/index.js +6 -0
  580. package/dist/index.js.map +1 -1
  581. package/dist/navigation/index.d.ts +53 -0
  582. package/dist/navigation/index.d.ts.map +1 -0
  583. package/dist/navigation/index.js +105 -0
  584. package/dist/navigation/index.js.map +1 -0
  585. package/dist/observability/routes.d.ts +3 -3
  586. package/dist/org/index.d.ts +1 -0
  587. package/dist/org/index.d.ts.map +1 -1
  588. package/dist/org/index.js +1 -0
  589. package/dist/org/index.js.map +1 -1
  590. package/dist/org/permissions.d.ts +7 -0
  591. package/dist/org/permissions.d.ts.map +1 -0
  592. package/dist/org/permissions.js +21 -0
  593. package/dist/org/permissions.js.map +1 -0
  594. package/dist/org-team/index.d.ts +2 -0
  595. package/dist/org-team/index.d.ts.map +1 -0
  596. package/dist/org-team/index.js +2 -0
  597. package/dist/org-team/index.js.map +1 -0
  598. package/dist/private-blob/registry.d.ts.map +1 -1
  599. package/dist/private-blob/registry.js +1 -0
  600. package/dist/private-blob/registry.js.map +1 -1
  601. package/dist/resources/handlers.d.ts +2 -2
  602. package/dist/review/actions/consume-review-feedback.d.ts +9 -0
  603. package/dist/review/actions/consume-review-feedback.d.ts.map +1 -0
  604. package/dist/review/actions/consume-review-feedback.js +29 -0
  605. package/dist/review/actions/consume-review-feedback.js.map +1 -0
  606. package/dist/review/actions/create-review-comment.d.ts +18 -0
  607. package/dist/review/actions/create-review-comment.d.ts.map +1 -0
  608. package/dist/review/actions/create-review-comment.js +81 -0
  609. package/dist/review/actions/create-review-comment.js.map +1 -0
  610. package/dist/review/actions/delete-review-comment.d.ts +10 -0
  611. package/dist/review/actions/delete-review-comment.d.ts.map +1 -0
  612. package/dist/review/actions/delete-review-comment.js +45 -0
  613. package/dist/review/actions/delete-review-comment.js.map +1 -0
  614. package/dist/review/actions/get-review-feedback.d.ts +10 -0
  615. package/dist/review/actions/get-review-feedback.d.ts.map +1 -0
  616. package/dist/review/actions/get-review-feedback.js +45 -0
  617. package/dist/review/actions/get-review-feedback.js.map +1 -0
  618. package/dist/review/actions/list-review-comments.d.ts +13 -0
  619. package/dist/review/actions/list-review-comments.d.ts.map +1 -0
  620. package/dist/review/actions/list-review-comments.js +44 -0
  621. package/dist/review/actions/list-review-comments.js.map +1 -0
  622. package/dist/review/actions/reply-review-comment.d.ts +16 -0
  623. package/dist/review/actions/reply-review-comment.d.ts.map +1 -0
  624. package/dist/review/actions/reply-review-comment.js +84 -0
  625. package/dist/review/actions/reply-review-comment.js.map +1 -0
  626. package/dist/review/actions/resolve-review-thread.d.ts +11 -0
  627. package/dist/review/actions/resolve-review-thread.d.ts.map +1 -0
  628. package/dist/review/actions/resolve-review-thread.js +46 -0
  629. package/dist/review/actions/resolve-review-thread.js.map +1 -0
  630. package/dist/review/actions/set-review-status.d.ts +9 -0
  631. package/dist/review/actions/set-review-status.d.ts.map +1 -0
  632. package/dist/review/actions/set-review-status.js +43 -0
  633. package/dist/review/actions/set-review-status.js.map +1 -0
  634. package/dist/review/index.d.ts +5 -0
  635. package/dist/review/index.d.ts.map +1 -0
  636. package/dist/review/index.js +4 -0
  637. package/dist/review/index.js.map +1 -0
  638. package/dist/review/mentions.d.ts +4 -0
  639. package/dist/review/mentions.d.ts.map +1 -0
  640. package/dist/review/mentions.js +39 -0
  641. package/dist/review/mentions.js.map +1 -0
  642. package/dist/review/registry.d.ts +10 -0
  643. package/dist/review/registry.d.ts.map +1 -0
  644. package/dist/review/registry.js +61 -0
  645. package/dist/review/registry.js.map +1 -0
  646. package/dist/review/store.d.ts +63 -0
  647. package/dist/review/store.d.ts.map +1 -0
  648. package/dist/review/store.js +558 -0
  649. package/dist/review/store.js.map +1 -0
  650. package/dist/review/types.d.ts +77 -0
  651. package/dist/review/types.d.ts.map +1 -0
  652. package/dist/review/types.js +2 -0
  653. package/dist/review/types.js.map +1 -0
  654. package/dist/secrets/routes.d.ts +9 -9
  655. package/dist/server/action-discovery.d.ts.map +1 -1
  656. package/dist/server/action-discovery.js +54 -0
  657. package/dist/server/action-discovery.js.map +1 -1
  658. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  659. package/dist/server/agent-engine-api-key-route.d.ts.map +1 -1
  660. package/dist/server/agent-engine-api-key-route.js +5 -0
  661. package/dist/server/agent-engine-api-key-route.js.map +1 -1
  662. package/dist/server/credential-provider.d.ts +26 -0
  663. package/dist/server/credential-provider.d.ts.map +1 -1
  664. package/dist/server/credential-provider.js +82 -0
  665. package/dist/server/credential-provider.js.map +1 -1
  666. package/dist/server/framework-request-handler.d.ts.map +1 -1
  667. package/dist/server/framework-request-handler.js +37 -1
  668. package/dist/server/framework-request-handler.js.map +1 -1
  669. package/dist/setup-connections/index.d.ts +6 -0
  670. package/dist/setup-connections/index.d.ts.map +1 -0
  671. package/dist/setup-connections/index.js +6 -0
  672. package/dist/setup-connections/index.js.map +1 -0
  673. package/dist/setup-connections/onboarding.d.ts +14 -0
  674. package/dist/setup-connections/onboarding.d.ts.map +1 -0
  675. package/dist/setup-connections/onboarding.js +23 -0
  676. package/dist/setup-connections/onboarding.js.map +1 -0
  677. package/dist/transcription/builder-transcription.d.ts +1 -0
  678. package/dist/transcription/builder-transcription.d.ts.map +1 -1
  679. package/dist/transcription/builder-transcription.js +5 -2
  680. package/dist/transcription/builder-transcription.js.map +1 -1
  681. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  682. package/dist/vite/action-types-plugin.js +52 -0
  683. package/dist/vite/action-types-plugin.js.map +1 -1
  684. package/docs/content/agent-native-toolkit.mdx +87 -80
  685. package/docs/content/locales/ar-SA/agent-native-toolkit.mdx +89 -80
  686. package/docs/content/locales/de-DE/agent-native-toolkit.mdx +89 -80
  687. package/docs/content/locales/es-ES/agent-native-toolkit.mdx +89 -80
  688. package/docs/content/locales/fr-FR/agent-native-toolkit.mdx +89 -80
  689. package/docs/content/locales/hi-IN/agent-native-toolkit.mdx +89 -80
  690. package/docs/content/locales/ja-JP/agent-native-toolkit.mdx +89 -80
  691. package/docs/content/locales/ko-KR/agent-native-toolkit.mdx +89 -80
  692. package/docs/content/locales/pt-BR/agent-native-toolkit.mdx +89 -80
  693. package/docs/content/locales/zh-CN/agent-native-toolkit.mdx +89 -80
  694. package/docs/content/locales/zh-TW/agent-native-toolkit.mdx +89 -80
  695. package/docs/content/toolkit-agent-ux.mdx +43 -0
  696. package/docs/content/toolkit-app-adapters.mdx +9 -0
  697. package/docs/content/toolkit-collaboration-ui.mdx +12 -0
  698. package/docs/content/toolkit-collaboration.mdx +74 -0
  699. package/docs/content/toolkit-command-navigation.mdx +106 -0
  700. package/docs/content/toolkit-comments-review.mdx +98 -0
  701. package/docs/content/toolkit-history.mdx +95 -0
  702. package/docs/content/toolkit-observability.mdx +41 -0
  703. package/docs/content/toolkit-org-team.mdx +80 -0
  704. package/docs/content/toolkit-resources.mdx +57 -0
  705. package/docs/content/toolkit-settings.mdx +65 -0
  706. package/docs/content/toolkit-setup-connections.mdx +109 -0
  707. package/docs/content/toolkit-sharing-ui.mdx +18 -6
  708. package/docs/content/toolkit-sharing.mdx +81 -0
  709. package/docs/content/toolkit-shell-hooks.mdx +9 -0
  710. package/docs/content/toolkit-ui.mdx +9 -0
  711. package/package.json +36 -2
  712. package/corpus/templates/design/app/components/design/inspector/InspectorAiActions.tsx +0 -153
  713. package/corpus/templates/design/app/hooks/useAgentEditRequest.ts +0 -133
  714. package/corpus/templates/design/app/pages/DesignEditor.tsx +0 -22924
@@ -1,5 +1,5 @@
1
1
  import { deleteCollabState, releaseDoc } from "@agent-native/core/collab";
2
- import { and, asc, eq, inArray, isNull, sql } from "drizzle-orm";
2
+ import { and, asc, eq, inArray, isNull, notInArray, sql } from "drizzle-orm";
3
3
 
4
4
  import { getDb, schema } from "../server/db/index.js";
5
5
  import type {
@@ -42,6 +42,7 @@ import {
42
42
  builderMdxBodyToBuilderBlocks,
43
43
  } from "../shared/builder-mdx.js";
44
44
  import {
45
+ normalizePropertyValue,
45
46
  parsePropertyOptions,
46
47
  serializePropertyOptions,
47
48
  serializePropertyValue,
@@ -49,8 +50,10 @@ import {
49
50
  } from "../shared/properties.js";
50
51
  import { sanitizeNormalizationFormula } from "../shared/properties.js";
51
52
  import {
53
+ readBuilderCmsContentEntry,
52
54
  readBuilderCmsContentEntries,
53
55
  readBuilderCmsModelFields,
56
+ type BuilderCmsReadProgress,
54
57
  type BuilderCmsReadState,
55
58
  } from "./_builder-cms-read-client.js";
56
59
  import {
@@ -70,6 +73,7 @@ import {
70
73
  } from "./_builder-cms-source-adapter.js";
71
74
  import { mergeBuilderCmsWriteSettingsIntoJson } from "./_builder-cms-write-settings.js";
72
75
  import { listPropertiesForDatabase, nanoid } from "./_property-utils.js";
76
+ import { isEffectivelyEmptyDocumentContent } from "./update-document.js";
73
77
 
74
78
  type ContentDatabaseRow = typeof schema.contentDatabases.$inferSelect;
75
79
  type ContentDatabaseSourceRowDb =
@@ -116,6 +120,13 @@ type SourceMetadataRecord = {
116
120
  liveReadConfigured?: boolean;
117
121
  lastReadEntryCount?: number;
118
122
  lastReadMatchedRowCount?: number;
123
+ lastReadLimit?: number;
124
+ lastReadFetchedEntryCount?: number;
125
+ lastReadPartial?: boolean;
126
+ lastReadHasMore?: boolean;
127
+ lastReadNextOffset?: number;
128
+ activeReadSourceRowIds?: string[];
129
+ sourceFetchState?: "idle" | "fetching" | "error" | string;
119
130
  allowDraftWrites?: boolean;
120
131
  allowPublishWrites?: boolean;
121
132
  allowedWriteModes?: ContentDatabaseSourcePushMode[];
@@ -464,6 +475,17 @@ function sameSourceFieldValue(a: unknown, b: unknown): boolean {
464
475
  return normalize(a) === normalize(b);
465
476
  }
466
477
 
478
+ function sameMappedSourceFieldValue(
479
+ localValue: unknown,
480
+ sourceValue: unknown,
481
+ type: DocumentProperty["definition"]["type"] | null | undefined,
482
+ ): boolean {
483
+ const normalizedSourceValue = type
484
+ ? normalizePropertyValue(type, sourceValue)
485
+ : sourceValue;
486
+ return sameSourceFieldValue(localValue, normalizedSourceValue);
487
+ }
488
+
467
489
  function stringSourceValue(
468
490
  values: Record<string, DocumentPropertyValue>,
469
491
  key: string,
@@ -477,10 +499,53 @@ function bodyExcerpt(value: string | null | undefined) {
477
499
  return excerpt || null;
478
500
  }
479
501
 
502
+ function normalizeBuilderBodyBaselineContent(value: string | null | undefined) {
503
+ return (value ?? "").replace(/\r\n/g, "\n").trim();
504
+ }
505
+
480
506
  const BUILDER_BODY_HYDRATION_BACKGROUND_PRIORITY = 10;
481
507
  const BUILDER_BODY_HYDRATION_OPEN_PRIORITY = 0;
482
508
  const BUILDER_BODY_HYDRATION_BATCH_LIMIT = 8;
483
- const BUILDER_BODY_HYDRATION_CODEC_VERSION = "readable-native-images-v4";
509
+ const BUILDER_BODY_HYDRATION_MAX_ATTEMPTS = 5;
510
+ const BUILDER_BODY_HYDRATION_CODEC_VERSION = "readable-native-images-v5";
511
+ const BUILDER_CMS_REFRESH_INITIAL_PAGES = 1;
512
+ const BUILDER_BODY_NOT_AVAILABLE_ERROR = "body not yet available from Builder";
513
+
514
+ function builderBodyHydrationDelayMs() {
515
+ if (
516
+ process.env.NODE_ENV === "production" ||
517
+ process.env.CI ||
518
+ process.env.VITEST
519
+ ) {
520
+ return 0;
521
+ }
522
+ const parsed = Number(process.env.BUILDER_BODY_HYDRATION_DELAY_MS ?? "0");
523
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : 0;
524
+ }
525
+
526
+ function sleep(ms: number) {
527
+ return new Promise((resolve) => setTimeout(resolve, ms));
528
+ }
529
+
530
+ export function builderBodyHydrationPriorityForRequest(args: {
531
+ documentId?: string | null;
532
+ }) {
533
+ return args.documentId
534
+ ? BUILDER_BODY_HYDRATION_OPEN_PRIORITY
535
+ : BUILDER_BODY_HYDRATION_BACKGROUND_PRIORITY;
536
+ }
537
+
538
+ export function sortBuilderBodyHydrationQueueForProcessing<
539
+ T extends { priority: number; createdAt: string },
540
+ >(rows: T[]): T[] {
541
+ return [...rows].sort(
542
+ (a, b) => a.priority - b.priority || a.createdAt.localeCompare(b.createdAt),
543
+ );
544
+ }
545
+
546
+ export function builderBodyHydrationAttemptIsTerminal(attempts: number) {
547
+ return attempts >= BUILDER_BODY_HYDRATION_MAX_ATTEMPTS;
548
+ }
484
549
 
485
550
  async function builderBodySnapshotForEntry(entry: BuilderCmsSourceEntry) {
486
551
  if (!entry.rawEntry) return null;
@@ -615,6 +680,13 @@ function builderStoredBodyIsStale(args: {
615
680
  );
616
681
  }
617
682
 
683
+ function builderEntryHasBodyContent(entry: BuilderCmsSourceEntry | null) {
684
+ return !!stringSourceValue(
685
+ entry?.sourceValues ?? {},
686
+ BUILDER_CMS_BODY_CONTENT_KEY,
687
+ )?.trim();
688
+ }
689
+
618
690
  async function readableBuilderBodyFromStoredLossless(args: {
619
691
  losslessContent: string | null;
620
692
  sidecarsJson: string | null;
@@ -688,6 +760,37 @@ function builderEntryFromSourceRow(args: {
688
760
  };
689
761
  }
690
762
 
763
+ async function readBuilderEntryWithLiveBodyFromSourceRow(args: {
764
+ row: Pick<
765
+ ContentDatabaseSourceRecordRowDb,
766
+ "sourceRowId" | "sourceValuesJson"
767
+ >;
768
+ sourceTable: string;
769
+ fallbackTitle: string;
770
+ }): Promise<BuilderCmsSourceEntry | null> {
771
+ const sourceValues =
772
+ parseObject<Record<string, DocumentPropertyValue>>(
773
+ args.row.sourceValuesJson,
774
+ ) ?? {};
775
+ const liveEntry = await readBuilderCmsContentEntry({
776
+ model: args.sourceTable,
777
+ entryId: args.row.sourceRowId,
778
+ }).catch(() => null);
779
+ if (!liveEntry || liveEntry.id !== args.row.sourceRowId) return null;
780
+ const entryWithStoredValues = {
781
+ ...liveEntry,
782
+ title: liveEntry.title || args.fallbackTitle,
783
+ sourceValues: {
784
+ ...sourceValues,
785
+ ...liveEntry.sourceValues,
786
+ },
787
+ };
788
+ const refreshedEntry = await refreshBuilderBodySourceValuesFromStoredLossless(
789
+ await withBuilderBodySourceValues(entryWithStoredValues),
790
+ );
791
+ return builderEntryHasBodyContent(refreshedEntry) ? refreshedEntry : null;
792
+ }
793
+
691
794
  export async function enqueueBuilderBodyHydration(args: {
692
795
  sourceId: string;
693
796
  ownerEmail: string;
@@ -701,7 +804,9 @@ export async function enqueueBuilderBodyHydration(args: {
701
804
  }) {
702
805
  const db = getDb();
703
806
  const sourceRowId = args.entry.id;
704
- const priority = args.priority ?? BUILDER_BODY_HYDRATION_BACKGROUND_PRIORITY;
807
+ const priority =
808
+ args.priority ??
809
+ builderBodyHydrationPriorityForRequest({ documentId: null });
705
810
  const [existing] = await db
706
811
  .select()
707
812
  .from(schema.contentDatabaseBodyHydrationQueue)
@@ -711,6 +816,13 @@ export async function enqueueBuilderBodyHydration(args: {
711
816
  args.databaseItemId,
712
817
  ),
713
818
  );
819
+ const existingEntry = existing ? parseHydrationEntry(existing) : null;
820
+ const shouldPreserveExistingEntry =
821
+ builderEntryHasBodyContent(existingEntry) &&
822
+ !builderEntryHasBodyContent(args.entry);
823
+ const sourceEntryJson = shouldPreserveExistingEntry
824
+ ? existing!.sourceEntryJson
825
+ : JSON.stringify(args.entry);
714
826
  const values = {
715
827
  ownerEmail: args.ownerEmail,
716
828
  orgId: args.orgId,
@@ -719,7 +831,7 @@ export async function enqueueBuilderBodyHydration(args: {
719
831
  documentId: args.documentId,
720
832
  sourceRowId,
721
833
  sourceTable: args.sourceTable,
722
- sourceEntryJson: JSON.stringify(args.entry),
834
+ sourceEntryJson,
723
835
  priority: Math.min(existing?.priority ?? priority, priority),
724
836
  lastError: null,
725
837
  updatedAt: args.now,
@@ -764,6 +876,7 @@ export async function enqueueBuilderBodyHydrationForItems(args: {
764
876
  if (
765
877
  item.bodyHydration?.status === "hydrated" &&
766
878
  item.bodyHydration.version === builderBodyHydrationVersion(entry) &&
879
+ !isEffectivelyEmptyDocumentContent(item.document.content) &&
767
880
  !builderBodyIsRawPlaceholderOnly(item.document.content)
768
881
  ) {
769
882
  continue;
@@ -782,7 +895,63 @@ export async function enqueueBuilderBodyHydrationForItems(args: {
782
895
  void processBuilderBodyHydrationQueue({
783
896
  sourceId: args.sourceId,
784
897
  limit: BUILDER_BODY_HYDRATION_BATCH_LIMIT,
785
- }).catch(() => undefined);
898
+ }).catch((error) => {
899
+ console.error("Builder body hydration background kick failed", error);
900
+ });
901
+ }
902
+
903
+ async function enqueueEmptyHydratedBuilderBodiesFromStoredRows(args: {
904
+ source: ContentDatabaseSourceRowDb;
905
+ now: string;
906
+ }) {
907
+ const db = getDb();
908
+ const rows = await db
909
+ .select({
910
+ item: schema.contentDatabaseItems,
911
+ sourceRow: schema.contentDatabaseSourceRows,
912
+ document: schema.documents,
913
+ })
914
+ .from(schema.contentDatabaseSourceRows)
915
+ .innerJoin(
916
+ schema.contentDatabaseItems,
917
+ eq(
918
+ schema.contentDatabaseItems.id,
919
+ schema.contentDatabaseSourceRows.databaseItemId,
920
+ ),
921
+ )
922
+ .innerJoin(
923
+ schema.documents,
924
+ eq(schema.documents.id, schema.contentDatabaseSourceRows.documentId),
925
+ )
926
+ .where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
927
+ for (const row of rows) {
928
+ if (
929
+ row.item.bodyHydrationStatus !== "hydrated" &&
930
+ row.item.bodyHydrationStatus !== "pending"
931
+ )
932
+ continue;
933
+ if (!isEffectivelyEmptyDocumentContent(row.document.content)) continue;
934
+ const entry = builderEntryFromSourceRow({
935
+ row: row.sourceRow,
936
+ sourceTable: args.source.sourceTable,
937
+ fallbackTitle: row.document.title,
938
+ });
939
+ const refreshedEntry = entry
940
+ ? await refreshBuilderBodySourceValuesFromStoredLossless(entry)
941
+ : null;
942
+ if (!refreshedEntry || !builderEntryHasBodyContent(refreshedEntry))
943
+ continue;
944
+ await enqueueBuilderBodyHydration({
945
+ sourceId: args.source.id,
946
+ ownerEmail: row.item.ownerEmail,
947
+ orgId: row.item.orgId,
948
+ databaseItemId: row.item.id,
949
+ documentId: row.item.documentId,
950
+ sourceTable: args.source.sourceTable,
951
+ entry: refreshedEntry,
952
+ now: args.now,
953
+ });
954
+ }
786
955
  }
787
956
 
788
957
  function parseHydrationEntry(
@@ -829,34 +998,152 @@ async function processBuilderBodyHydrationJob(
829
998
 
830
999
  const entry = parseHydrationEntry(row);
831
1000
  if (!entry) throw new Error("Builder body hydration entry is missing.");
832
- const entryWithBody = await refreshBuilderBodySourceValuesFromStoredLossless(
1001
+ let activeSourceEntryJson = row.sourceEntryJson;
1002
+ let entryWithBody = await refreshBuilderBodySourceValuesFromStoredLossless(
833
1003
  await withBuilderBodySourceValues(entry),
834
1004
  );
1005
+ const [sourceRow] = await db
1006
+ .select()
1007
+ .from(schema.contentDatabaseSourceRows)
1008
+ .where(
1009
+ and(
1010
+ eq(schema.contentDatabaseSourceRows.sourceId, row.sourceId),
1011
+ eq(schema.contentDatabaseSourceRows.databaseItemId, row.databaseItemId),
1012
+ ),
1013
+ );
835
1014
  const sourceValues =
836
1015
  parseObject<Record<string, DocumentPropertyValue>>(
837
- (
838
- await db
839
- .select({
840
- sourceValuesJson: schema.contentDatabaseSourceRows.sourceValuesJson,
1016
+ sourceRow?.sourceValuesJson ?? "{}",
1017
+ ) ?? {};
1018
+ let nextValues = {
1019
+ ...sourceValues,
1020
+ ...entryWithBody.sourceValues,
1021
+ };
1022
+ let nextContent =
1023
+ stringSourceValue(nextValues, BUILDER_CMS_BODY_CONTENT_KEY) ?? "";
1024
+ if (!nextContent.trim()) {
1025
+ const rebuiltBaseEntry = sourceRow
1026
+ ? builderEntryFromSourceRow({
1027
+ row: sourceRow,
1028
+ sourceTable: row.sourceTable,
1029
+ fallbackTitle: entry.title,
1030
+ })
1031
+ : null;
1032
+ const rebuiltEntry = rebuiltBaseEntry
1033
+ ? await refreshBuilderBodySourceValuesFromStoredLossless(rebuiltBaseEntry)
1034
+ : null;
1035
+ if (rebuiltEntry) {
1036
+ const rebuiltValues = {
1037
+ ...sourceValues,
1038
+ ...rebuiltEntry.sourceValues,
1039
+ };
1040
+ const rebuiltContent =
1041
+ stringSourceValue(rebuiltValues, BUILDER_CMS_BODY_CONTENT_KEY) ?? "";
1042
+ if (rebuiltContent.trim()) {
1043
+ const rebuiltSourceEntryJson = JSON.stringify(rebuiltEntry);
1044
+ const [upgraded] = await db
1045
+ .update(schema.contentDatabaseBodyHydrationQueue)
1046
+ .set({
1047
+ sourceEntryJson: rebuiltSourceEntryJson,
1048
+ lastError: null,
1049
+ updatedAt: now,
841
1050
  })
842
- .from(schema.contentDatabaseSourceRows)
843
1051
  .where(
844
1052
  and(
845
- eq(schema.contentDatabaseSourceRows.sourceId, row.sourceId),
1053
+ eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
846
1054
  eq(
847
- schema.contentDatabaseSourceRows.databaseItemId,
848
- row.databaseItemId,
1055
+ schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
1056
+ activeSourceEntryJson,
849
1057
  ),
850
1058
  ),
851
1059
  )
852
- )[0]?.sourceValuesJson ?? "{}",
853
- ) ?? {};
854
- const nextValues = {
855
- ...sourceValues,
856
- ...entryWithBody.sourceValues,
857
- };
858
- const nextContent =
859
- stringSourceValue(nextValues, BUILDER_CMS_BODY_CONTENT_KEY) ?? "";
1060
+ .returning({ id: schema.contentDatabaseBodyHydrationQueue.id });
1061
+ if (!upgraded) return;
1062
+ activeSourceEntryJson = rebuiltSourceEntryJson;
1063
+ entryWithBody = rebuiltEntry;
1064
+ nextValues = rebuiltValues;
1065
+ nextContent = rebuiltContent;
1066
+ }
1067
+ }
1068
+ if (!nextContent.trim() && sourceRow) {
1069
+ const liveEntry = await readBuilderEntryWithLiveBodyFromSourceRow({
1070
+ row: sourceRow,
1071
+ sourceTable: row.sourceTable,
1072
+ fallbackTitle: entry.title,
1073
+ });
1074
+ if (liveEntry) {
1075
+ const liveValues = {
1076
+ ...sourceValues,
1077
+ ...liveEntry.sourceValues,
1078
+ };
1079
+ const liveContent =
1080
+ stringSourceValue(liveValues, BUILDER_CMS_BODY_CONTENT_KEY) ?? "";
1081
+ if (liveContent.trim()) {
1082
+ const liveSourceEntryJson = JSON.stringify(liveEntry);
1083
+ const [upgraded] = await db
1084
+ .update(schema.contentDatabaseBodyHydrationQueue)
1085
+ .set({
1086
+ sourceEntryJson: liveSourceEntryJson,
1087
+ lastError: null,
1088
+ updatedAt: now,
1089
+ })
1090
+ .where(
1091
+ and(
1092
+ eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
1093
+ eq(
1094
+ schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
1095
+ activeSourceEntryJson,
1096
+ ),
1097
+ ),
1098
+ )
1099
+ .returning({ id: schema.contentDatabaseBodyHydrationQueue.id });
1100
+ if (!upgraded) return;
1101
+ activeSourceEntryJson = liveSourceEntryJson;
1102
+ entryWithBody = liveEntry;
1103
+ nextValues = liveValues;
1104
+ nextContent = liveContent;
1105
+ }
1106
+ }
1107
+ }
1108
+ if (!nextContent.trim()) {
1109
+ const attempts = row.attempts + 1;
1110
+ await db.transaction(async (tx) => {
1111
+ if (builderBodyHydrationAttemptIsTerminal(attempts)) {
1112
+ await tx
1113
+ .delete(schema.contentDatabaseBodyHydrationQueue)
1114
+ .where(
1115
+ and(
1116
+ eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
1117
+ eq(
1118
+ schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
1119
+ activeSourceEntryJson,
1120
+ ),
1121
+ ),
1122
+ );
1123
+ await tx
1124
+ .update(schema.contentDatabaseItems)
1125
+ .set({
1126
+ bodyHydrationStatus: "error",
1127
+ bodyHydrationAttemptedAt: now,
1128
+ bodyHydrationError: BUILDER_BODY_NOT_AVAILABLE_ERROR,
1129
+ updatedAt: now,
1130
+ })
1131
+ .where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
1132
+ return;
1133
+ }
1134
+ await tx
1135
+ .update(schema.contentDatabaseItems)
1136
+ .set({
1137
+ bodyHydrationStatus: "pending",
1138
+ bodyHydrationAttemptedAt: now,
1139
+ bodyHydrationError: null,
1140
+ updatedAt: now,
1141
+ })
1142
+ .where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
1143
+ });
1144
+ return;
1145
+ }
1146
+ }
860
1147
  const [document] = await db
861
1148
  .select({ content: schema.documents.content })
862
1149
  .from(schema.documents)
@@ -866,6 +1153,7 @@ async function processBuilderBodyHydrationJob(
866
1153
  const currentContent = document?.content ?? "";
867
1154
  const shouldWriteBody =
868
1155
  currentContent === "" ||
1156
+ isEffectivelyEmptyDocumentContent(currentContent) ||
869
1157
  currentContent === previousContent ||
870
1158
  currentContent.trim() === "" ||
871
1159
  builderBodyIsRawPlaceholderOnly(currentContent) ||
@@ -877,30 +1165,24 @@ async function processBuilderBodyHydrationJob(
877
1165
  let wroteBody = false;
878
1166
  await db.transaction(async (tx) => {
879
1167
  if (shouldWriteBody) {
1168
+ const contentCas =
1169
+ isEffectivelyEmptyDocumentContent(currentContent) &&
1170
+ isEffectivelyEmptyDocumentContent(previousContent)
1171
+ ? inArray(schema.documents.content, ["", "<empty-block/>"])
1172
+ : eq(schema.documents.content, currentContent);
880
1173
  const [updatedDocument] = await tx
881
1174
  .update(schema.documents)
882
1175
  .set({ content: nextContent, updatedAt: now })
883
- .where(
884
- and(
885
- eq(schema.documents.id, row.documentId),
886
- eq(schema.documents.content, currentContent),
887
- ),
888
- )
1176
+ .where(and(eq(schema.documents.id, row.documentId), contentCas))
889
1177
  .returning({ id: schema.documents.id });
890
1178
  wroteBody = Boolean(updatedDocument);
891
1179
  }
892
1180
  if (shouldWriteBody && !wroteBody) {
893
- await tx
894
- .update(schema.contentDatabaseItems)
895
- .set({
896
- bodyHydrationStatus: "pending",
897
- bodyHydrationAttemptedAt: now,
898
- bodyHydrationError:
899
- "Skipped Builder body hydration because the document changed during sync.",
900
- updatedAt: now,
901
- })
902
- .where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
903
- await tx
1181
+ // Only mark the item pending if our queue row still exists. If a
1182
+ // concurrent processor already completed (and deleted) this job, it
1183
+ // owns the final `hydrated` status — resetting to pending here would
1184
+ // strand the item as a zombie (pending with no queue row).
1185
+ const [stillQueued] = await tx
904
1186
  .update(schema.contentDatabaseBodyHydrationQueue)
905
1187
  .set({
906
1188
  lastError:
@@ -912,10 +1194,23 @@ async function processBuilderBodyHydrationJob(
912
1194
  eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
913
1195
  eq(
914
1196
  schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
915
- row.sourceEntryJson,
1197
+ activeSourceEntryJson,
916
1198
  ),
917
1199
  ),
918
- );
1200
+ )
1201
+ .returning({ id: schema.contentDatabaseBodyHydrationQueue.id });
1202
+ if (stillQueued) {
1203
+ await tx
1204
+ .update(schema.contentDatabaseItems)
1205
+ .set({
1206
+ bodyHydrationStatus: "pending",
1207
+ bodyHydrationAttemptedAt: now,
1208
+ bodyHydrationError:
1209
+ "Skipped Builder body hydration because the document changed during sync.",
1210
+ updatedAt: now,
1211
+ })
1212
+ .where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
1213
+ }
919
1214
  return;
920
1215
  }
921
1216
  const [deleted] = await tx
@@ -925,7 +1220,7 @@ async function processBuilderBodyHydrationJob(
925
1220
  eq(schema.contentDatabaseBodyHydrationQueue.id, row.id),
926
1221
  eq(
927
1222
  schema.contentDatabaseBodyHydrationQueue.sourceEntryJson,
928
- row.sourceEntryJson,
1223
+ activeSourceEntryJson,
929
1224
  ),
930
1225
  ),
931
1226
  )
@@ -937,7 +1232,7 @@ async function processBuilderBodyHydrationJob(
937
1232
  .set({
938
1233
  sourceValuesJson: JSON.stringify(nextValues),
939
1234
  lastSyncedAt: now,
940
- lastSourceUpdatedAt: entry.updatedAt ?? now,
1235
+ lastSourceUpdatedAt: entryWithBody.updatedAt ?? now,
941
1236
  updatedAt: now,
942
1237
  })
943
1238
  .where(
@@ -950,15 +1245,26 @@ async function processBuilderBodyHydrationJob(
950
1245
  ),
951
1246
  );
952
1247
  }
953
- await tx
954
- .update(schema.contentDatabaseItems)
955
- .set({
956
- bodyHydrationStatus: "pending",
957
- bodyHydrationAttemptedAt: now,
958
- bodyHydrationError: null,
959
- updatedAt: now,
960
- })
961
- .where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
1248
+ // Our delete matched nothing: either a NEWER job version replaced this
1249
+ // row (same id, different sourceEntryJson — mark pending so the newer
1250
+ // job's processor owns it), or a concurrent processor completed and
1251
+ // deleted the job — in which case it already set `hydrated`, and
1252
+ // resetting to pending would strand the item with no queue row.
1253
+ const [replacedByNewerJob] = await tx
1254
+ .select({ id: schema.contentDatabaseBodyHydrationQueue.id })
1255
+ .from(schema.contentDatabaseBodyHydrationQueue)
1256
+ .where(eq(schema.contentDatabaseBodyHydrationQueue.id, row.id));
1257
+ if (replacedByNewerJob) {
1258
+ await tx
1259
+ .update(schema.contentDatabaseItems)
1260
+ .set({
1261
+ bodyHydrationStatus: "pending",
1262
+ bodyHydrationAttemptedAt: now,
1263
+ bodyHydrationError: null,
1264
+ updatedAt: now,
1265
+ })
1266
+ .where(eq(schema.contentDatabaseItems.id, row.databaseItemId));
1267
+ }
962
1268
  return;
963
1269
  }
964
1270
  await tx
@@ -966,7 +1272,7 @@ async function processBuilderBodyHydrationJob(
966
1272
  .set({
967
1273
  sourceValuesJson: JSON.stringify(nextValues),
968
1274
  lastSyncedAt: now,
969
- lastSourceUpdatedAt: entry.updatedAt ?? now,
1275
+ lastSourceUpdatedAt: entryWithBody.updatedAt ?? now,
970
1276
  updatedAt: now,
971
1277
  })
972
1278
  .where(
@@ -1130,11 +1436,14 @@ export async function processBuilderBodyHydrationQueue(args: {
1130
1436
  for (const job of jobs) {
1131
1437
  const attemptNow = new Date().toISOString();
1132
1438
  try {
1439
+ const delayMs = builderBodyHydrationDelayMs();
1440
+ if (delayMs > 0) await sleep(delayMs);
1133
1441
  await processBuilderBodyHydrationJob(job, attemptNow);
1134
1442
  succeeded += 1;
1135
1443
  } catch (error) {
1136
1444
  failed += 1;
1137
1445
  const message = error instanceof Error ? error.message : String(error);
1446
+ const attempts = job.attempts + 1;
1138
1447
  await db
1139
1448
  .update(schema.contentDatabaseItems)
1140
1449
  .set({
@@ -1144,10 +1453,16 @@ export async function processBuilderBodyHydrationQueue(args: {
1144
1453
  updatedAt: attemptNow,
1145
1454
  })
1146
1455
  .where(eq(schema.contentDatabaseItems.id, job.databaseItemId));
1456
+ if (builderBodyHydrationAttemptIsTerminal(attempts)) {
1457
+ await db
1458
+ .delete(schema.contentDatabaseBodyHydrationQueue)
1459
+ .where(eq(schema.contentDatabaseBodyHydrationQueue.id, job.id));
1460
+ continue;
1461
+ }
1147
1462
  await db
1148
1463
  .update(schema.contentDatabaseBodyHydrationQueue)
1149
1464
  .set({
1150
- attempts: job.attempts + 1,
1465
+ attempts,
1151
1466
  lastAttemptedAt: attemptNow,
1152
1467
  lastError: message,
1153
1468
  priority: job.priority + 10,
@@ -1197,7 +1512,7 @@ export async function withBuilderBodiesSourceValues(
1197
1512
  );
1198
1513
  }
1199
1514
 
1200
- async function builderBodyChangeForLocalContent(args: {
1515
+ export async function builderBodyChangeForLocalContent(args: {
1201
1516
  row: Pick<ContentDatabaseSourceRecordRowDb, "sourceValuesJson">;
1202
1517
  localContent: string | null | undefined;
1203
1518
  }): Promise<ContentDatabaseSourceBodyChange | null> {
@@ -1221,6 +1536,23 @@ async function builderBodyChangeForLocalContent(args: {
1221
1536
  stringSourceValue(sourceValues, BUILDER_CMS_BODY_SIDECARS_KEY) ?? "{}";
1222
1537
  const localContent = args.localContent ?? "";
1223
1538
  if (!currentHash && !currentContent && !localContent.trim()) return null;
1539
+ if (!currentContent?.trim() && !losslessContent?.trim()) return null;
1540
+ const normalizedLocalContent =
1541
+ normalizeBuilderBodyBaselineContent(localContent);
1542
+ if (
1543
+ normalizedLocalContent &&
1544
+ normalizedLocalContent ===
1545
+ normalizeBuilderBodyBaselineContent(currentContent)
1546
+ ) {
1547
+ return null;
1548
+ }
1549
+ if (
1550
+ normalizedLocalContent &&
1551
+ normalizedLocalContent ===
1552
+ normalizeBuilderBodyBaselineContent(losslessContent)
1553
+ ) {
1554
+ return null;
1555
+ }
1224
1556
 
1225
1557
  let sidecars: Record<string, string> = {};
1226
1558
  try {
@@ -1308,6 +1640,7 @@ export function buildBuilderLocalOutboundChangeSets(args: {
1308
1640
  localFieldKey: string;
1309
1641
  sourceFieldKey: string;
1310
1642
  sourceFieldLabel: string;
1643
+ propertyType?: DocumentProperty["definition"]["type"] | null;
1311
1644
  }>;
1312
1645
  // Row-union scoping (multi-source). Documents owned by ANOTHER source must
1313
1646
  // never be create candidates for this one — each row belongs to exactly one
@@ -1322,6 +1655,7 @@ export function buildBuilderLocalOutboundChangeSets(args: {
1322
1655
  // untagged / "Local" row falls back to the primary (allowUnsourcedCreates).
1323
1656
  taggedSourceByDocumentId?: Map<string, string>;
1324
1657
  bodyChangeByDocumentId?: Map<string, ContentDatabaseSourceBodyChange>;
1658
+ sourceImportedDocumentIds?: Set<string>;
1325
1659
  }): ContentDatabaseSourceChangeSet[] {
1326
1660
  if (normalizeSourceType(args.source.sourceType) !== "builder-cms") return [];
1327
1661
 
@@ -1366,13 +1700,19 @@ export function buildBuilderLocalOutboundChangeSets(args: {
1366
1700
  if (!rowLocalValues.has(field.localFieldKey)) continue;
1367
1701
  const localValue = rowLocalValues.get(field.localFieldKey);
1368
1702
  const baseValue = rowSourceValues[field.sourceFieldKey];
1369
- if (sameSourceFieldValue(localValue, baseValue)) continue;
1703
+ if (
1704
+ sameMappedSourceFieldValue(localValue, baseValue, field.propertyType)
1705
+ ) {
1706
+ continue;
1707
+ }
1370
1708
  fieldChanges.push({
1371
1709
  propertyId: field.propertyId,
1372
1710
  propertyName: field.sourceFieldLabel,
1373
1711
  localFieldKey: field.localFieldKey,
1374
1712
  sourceFieldKey: field.sourceFieldKey,
1375
- currentValue: (baseValue ?? null) as DocumentPropertyValue,
1713
+ currentValue: (field.propertyType
1714
+ ? normalizePropertyValue(field.propertyType, baseValue)
1715
+ : (baseValue ?? null)) as DocumentPropertyValue,
1376
1716
  proposedValue: localValue as DocumentPropertyValue,
1377
1717
  });
1378
1718
  }
@@ -1459,6 +1799,7 @@ export function buildBuilderLocalOutboundChangeSets(args: {
1459
1799
  const allowUnsourcedCreates = args.allowUnsourcedCreates ?? true;
1460
1800
  for (const item of args.databaseItems) {
1461
1801
  if (linkedDocumentIds.has(item.documentId)) continue;
1802
+ if (args.sourceImportedDocumentIds?.has(item.documentId)) continue;
1462
1803
  // Owned by another collection's row identity — not this source's to create.
1463
1804
  if (args.otherSourceDocumentIds?.has(item.documentId)) continue;
1464
1805
  const taggedSourceId = args.taggedSourceByDocumentId?.get(
@@ -1646,87 +1987,20 @@ export async function getAllContentDatabaseSourceSnapshots(
1646
1987
  return snapshots;
1647
1988
  }
1648
1989
 
1649
- async function loadSourceSnapshot(
1650
- source: ContentDatabaseSourceRowDb,
1651
- database: ContentDatabaseRow | ContentDatabase,
1652
- ): Promise<ContentDatabaseSource> {
1990
+ async function readSourceSnapshotRowsOnce(args: {
1991
+ source: ContentDatabaseSourceRowDb;
1992
+ database: ContentDatabaseRow | ContentDatabase;
1993
+ isBuilderSource: boolean;
1994
+ }) {
1653
1995
  const db = getDb();
1654
- const [
1655
- fieldRows,
1656
- rowRows,
1657
- changeRows,
1658
- reviewRows,
1659
- executionRows,
1660
- propertyDefs,
1661
- ] = await Promise.all([
1662
- db
1663
- .select()
1664
- .from(schema.contentDatabaseSourceFields)
1665
- .where(eq(schema.contentDatabaseSourceFields.sourceId, source.id))
1666
- .orderBy(asc(schema.contentDatabaseSourceFields.createdAt)),
1667
- db
1668
- .select()
1669
- .from(schema.contentDatabaseSourceRows)
1670
- .where(eq(schema.contentDatabaseSourceRows.sourceId, source.id))
1671
- .orderBy(asc(schema.contentDatabaseSourceRows.createdAt)),
1672
- db
1673
- .select()
1674
- .from(schema.contentDatabaseSourceChangeSets)
1675
- .where(eq(schema.contentDatabaseSourceChangeSets.sourceId, source.id))
1676
- .orderBy(asc(schema.contentDatabaseSourceChangeSets.createdAt)),
1677
- db
1678
- .select()
1679
- .from(schema.contentDatabaseSourceChangeReviews)
1680
- .where(eq(schema.contentDatabaseSourceChangeReviews.sourceId, source.id))
1681
- .orderBy(asc(schema.contentDatabaseSourceChangeReviews.createdAt)),
1682
- db
1683
- .select()
1684
- .from(schema.contentDatabaseSourceExecutions)
1685
- .where(eq(schema.contentDatabaseSourceExecutions.sourceId, source.id))
1686
- .orderBy(asc(schema.contentDatabaseSourceExecutions.createdAt)),
1687
- db
1688
- .select({
1689
- id: schema.documentPropertyDefinitions.id,
1690
- name: schema.documentPropertyDefinitions.name,
1691
- })
1692
- .from(schema.documentPropertyDefinitions)
1693
- .where(eq(schema.documentPropertyDefinitions.databaseId, database.id)),
1694
- ]);
1695
-
1696
- const propertyNameById = new Map(
1697
- propertyDefs.map((row) => [row.id, row.name]),
1698
- );
1699
- const fields = fieldRows.map((row) =>
1700
- serializeSourceField(
1701
- row,
1702
- row.propertyId ? (propertyNameById.get(row.propertyId) ?? null) : null,
1703
- ),
1704
- );
1705
- const rows = rowRows.map(serializeSourceRowRecord);
1706
- const storedChangeSets = changeRows.map(serializeSourceChangeSet);
1707
- const reviewEventsByChangeSetId = new Map<
1708
- string,
1709
- ContentDatabaseSourceReviewEvent[]
1710
- >();
1711
- for (const row of reviewRows) {
1712
- const events = reviewEventsByChangeSetId.get(row.changeSetId) ?? [];
1713
- events.push(serializeReviewEvent(row));
1714
- reviewEventsByChangeSetId.set(row.changeSetId, events);
1715
- }
1716
- const executionsByChangeSetId = new Map<
1717
- string,
1718
- ContentDatabaseSourceExecution[]
1719
- >();
1720
- for (const row of executionRows) {
1721
- const executions = executionsByChangeSetId.get(row.changeSetId) ?? [];
1722
- executions.push(serializeExecution(row));
1723
- executionsByChangeSetId.set(row.changeSetId, executions);
1724
- }
1725
- const isBuilderSource =
1726
- normalizeSourceType(source.sourceType) === "builder-cms";
1996
+ const rowRows = await db
1997
+ .select()
1998
+ .from(schema.contentDatabaseSourceRows)
1999
+ .where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id))
2000
+ .orderBy(asc(schema.contentDatabaseSourceRows.createdAt));
1727
2001
  // For Builder sources, load ALL database items (not just synced source rows)
1728
2002
  // so brand-new local rows (no source link) can become create_draft change-sets.
1729
- const databaseItemRows = isBuilderSource
2003
+ const databaseItemRows = args.isBuilderSource
1730
2004
  ? await db
1731
2005
  .select({
1732
2006
  id: schema.contentDatabaseItems.id,
@@ -1736,8 +2010,8 @@ async function loadSourceSnapshot(
1736
2010
  .from(schema.contentDatabaseItems)
1737
2011
  .where(
1738
2012
  and(
1739
- eq(schema.contentDatabaseItems.databaseId, database.id),
1740
- eq(schema.contentDatabaseItems.ownerEmail, source.ownerEmail),
2013
+ eq(schema.contentDatabaseItems.databaseId, args.database.id),
2014
+ eq(schema.contentDatabaseItems.ownerEmail, args.source.ownerEmail),
1741
2015
  ),
1742
2016
  )
1743
2017
  : [];
@@ -1759,18 +2033,12 @@ async function loadSourceSnapshot(
1759
2033
  .where(
1760
2034
  and(
1761
2035
  inArray(schema.documents.id, allDocumentIds),
1762
- eq(schema.documents.ownerEmail, source.ownerEmail),
2036
+ eq(schema.documents.ownerEmail, args.source.ownerEmail),
1763
2037
  ),
1764
2038
  )
1765
2039
  : [];
1766
- const documentTitleById = new Map(
1767
- rowDocuments.map((document) => [document.id, document.title]),
1768
- );
1769
- const documentContentById = new Map(
1770
- rowDocuments.map((document) => [document.id, document.content]),
1771
- );
1772
2040
  const propertyValueRows =
1773
- isBuilderSource && allDocumentIds.length > 0
2041
+ args.isBuilderSource && allDocumentIds.length > 0
1774
2042
  ? await db
1775
2043
  .select({
1776
2044
  documentId: schema.documentPropertyValues.documentId,
@@ -1781,10 +2049,182 @@ async function loadSourceSnapshot(
1781
2049
  .where(
1782
2050
  and(
1783
2051
  inArray(schema.documentPropertyValues.documentId, allDocumentIds),
1784
- eq(schema.documentPropertyValues.ownerEmail, source.ownerEmail),
2052
+ eq(
2053
+ schema.documentPropertyValues.ownerEmail,
2054
+ args.source.ownerEmail,
2055
+ ),
1785
2056
  ),
1786
2057
  )
1787
2058
  : [];
2059
+ return {
2060
+ rowRows,
2061
+ databaseItemRows,
2062
+ allDocumentIds,
2063
+ rowDocuments,
2064
+ propertyValueRows,
2065
+ };
2066
+ }
2067
+
2068
+ async function sourceSnapshotConsistencyMarker(args: {
2069
+ source: ContentDatabaseSourceRowDb;
2070
+ database: ContentDatabaseRow | ContentDatabase;
2071
+ isBuilderSource: boolean;
2072
+ }) {
2073
+ const db = getDb();
2074
+ const [rows] = await db
2075
+ .select({
2076
+ count: sql<number>`COUNT(*)`,
2077
+ maxUpdatedAt: sql<
2078
+ string | null
2079
+ >`MAX(${schema.contentDatabaseSourceRows.updatedAt})`,
2080
+ })
2081
+ .from(schema.contentDatabaseSourceRows)
2082
+ .where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
2083
+ const [items] = args.isBuilderSource
2084
+ ? await db
2085
+ .select({
2086
+ count: sql<number>`COUNT(*)`,
2087
+ maxUpdatedAt: sql<
2088
+ string | null
2089
+ >`MAX(${schema.contentDatabaseItems.updatedAt})`,
2090
+ })
2091
+ .from(schema.contentDatabaseItems)
2092
+ .where(
2093
+ and(
2094
+ eq(schema.contentDatabaseItems.databaseId, args.database.id),
2095
+ eq(schema.contentDatabaseItems.ownerEmail, args.source.ownerEmail),
2096
+ ),
2097
+ )
2098
+ : [{ count: 0, maxUpdatedAt: null }];
2099
+ return {
2100
+ rowCount: Number(rows?.count ?? 0),
2101
+ rowMaxUpdatedAt: rows?.maxUpdatedAt ?? null,
2102
+ itemCount: Number(items?.count ?? 0),
2103
+ itemMaxUpdatedAt: items?.maxUpdatedAt ?? null,
2104
+ };
2105
+ }
2106
+
2107
+ function sourceSnapshotConsistencyMarkersEqual(
2108
+ left: Awaited<ReturnType<typeof sourceSnapshotConsistencyMarker>>,
2109
+ right: Awaited<ReturnType<typeof sourceSnapshotConsistencyMarker>>,
2110
+ ) {
2111
+ return (
2112
+ left.rowCount === right.rowCount &&
2113
+ left.rowMaxUpdatedAt === right.rowMaxUpdatedAt &&
2114
+ left.itemCount === right.itemCount &&
2115
+ left.itemMaxUpdatedAt === right.itemMaxUpdatedAt
2116
+ );
2117
+ }
2118
+
2119
+ async function loadSourceSnapshotRowsOptimistically(args: {
2120
+ source: ContentDatabaseSourceRowDb;
2121
+ database: ContentDatabaseRow | ContentDatabase;
2122
+ isBuilderSource: boolean;
2123
+ }) {
2124
+ let latest: Awaited<ReturnType<typeof readSourceSnapshotRowsOnce>> | null =
2125
+ null;
2126
+ for (let attempt = 0; attempt < 3; attempt += 1) {
2127
+ const before = await sourceSnapshotConsistencyMarker(args);
2128
+ latest = await readSourceSnapshotRowsOnce(args);
2129
+ const after = await sourceSnapshotConsistencyMarker(args);
2130
+ if (sourceSnapshotConsistencyMarkersEqual(before, after)) return latest;
2131
+ }
2132
+ return latest ?? (await readSourceSnapshotRowsOnce(args));
2133
+ }
2134
+
2135
+ async function loadSourceSnapshot(
2136
+ source: ContentDatabaseSourceRowDb,
2137
+ database: ContentDatabaseRow | ContentDatabase,
2138
+ ): Promise<ContentDatabaseSource> {
2139
+ const db = getDb();
2140
+ const [fieldRows, changeRows, reviewRows, executionRows, propertyDefs] =
2141
+ await Promise.all([
2142
+ db
2143
+ .select()
2144
+ .from(schema.contentDatabaseSourceFields)
2145
+ .where(eq(schema.contentDatabaseSourceFields.sourceId, source.id))
2146
+ .orderBy(asc(schema.contentDatabaseSourceFields.createdAt)),
2147
+ db
2148
+ .select()
2149
+ .from(schema.contentDatabaseSourceChangeSets)
2150
+ .where(eq(schema.contentDatabaseSourceChangeSets.sourceId, source.id))
2151
+ .orderBy(asc(schema.contentDatabaseSourceChangeSets.createdAt)),
2152
+ db
2153
+ .select()
2154
+ .from(schema.contentDatabaseSourceChangeReviews)
2155
+ .where(
2156
+ eq(schema.contentDatabaseSourceChangeReviews.sourceId, source.id),
2157
+ )
2158
+ .orderBy(asc(schema.contentDatabaseSourceChangeReviews.createdAt)),
2159
+ db
2160
+ .select()
2161
+ .from(schema.contentDatabaseSourceExecutions)
2162
+ .where(eq(schema.contentDatabaseSourceExecutions.sourceId, source.id))
2163
+ .orderBy(asc(schema.contentDatabaseSourceExecutions.createdAt)),
2164
+ db
2165
+ .select({
2166
+ id: schema.documentPropertyDefinitions.id,
2167
+ name: schema.documentPropertyDefinitions.name,
2168
+ type: schema.documentPropertyDefinitions.type,
2169
+ })
2170
+ .from(schema.documentPropertyDefinitions)
2171
+ .where(eq(schema.documentPropertyDefinitions.databaseId, database.id)),
2172
+ ]);
2173
+
2174
+ const propertyNameById = new Map(
2175
+ propertyDefs.map((row) => [row.id, row.name]),
2176
+ );
2177
+ const propertyTypeById = new Map(
2178
+ propertyDefs.map((row) => [
2179
+ row.id,
2180
+ row.type as DocumentProperty["definition"]["type"],
2181
+ ]),
2182
+ );
2183
+ const fields = fieldRows.map((row) =>
2184
+ serializeSourceField(
2185
+ row,
2186
+ row.propertyId ? (propertyNameById.get(row.propertyId) ?? null) : null,
2187
+ ),
2188
+ );
2189
+ const storedChangeSets = changeRows.map(serializeSourceChangeSet);
2190
+ const reviewEventsByChangeSetId = new Map<
2191
+ string,
2192
+ ContentDatabaseSourceReviewEvent[]
2193
+ >();
2194
+ for (const row of reviewRows) {
2195
+ const events = reviewEventsByChangeSetId.get(row.changeSetId) ?? [];
2196
+ events.push(serializeReviewEvent(row));
2197
+ reviewEventsByChangeSetId.set(row.changeSetId, events);
2198
+ }
2199
+ const executionsByChangeSetId = new Map<
2200
+ string,
2201
+ ContentDatabaseSourceExecution[]
2202
+ >();
2203
+ for (const row of executionRows) {
2204
+ const executions = executionsByChangeSetId.get(row.changeSetId) ?? [];
2205
+ executions.push(serializeExecution(row));
2206
+ executionsByChangeSetId.set(row.changeSetId, executions);
2207
+ }
2208
+ const isBuilderSource =
2209
+ normalizeSourceType(source.sourceType) === "builder-cms";
2210
+ const {
2211
+ rowRows,
2212
+ databaseItemRows,
2213
+ allDocumentIds,
2214
+ rowDocuments,
2215
+ propertyValueRows,
2216
+ } = await loadSourceSnapshotRowsOptimistically({
2217
+ source,
2218
+ database,
2219
+ isBuilderSource,
2220
+ });
2221
+ const rows = rowRows.map(serializeSourceRowRecord);
2222
+ const documentTitleById = new Map(
2223
+ rowDocuments.map((document) => [document.id, document.title]),
2224
+ );
2225
+ const documentContentById = new Map(
2226
+ rowDocuments.map((document) => [document.id, document.content]),
2227
+ );
1788
2228
  const localValuesByDocument = new Map<string, Map<string, unknown>>();
1789
2229
  for (const valueRow of propertyValueRows) {
1790
2230
  let byField = localValuesByDocument.get(valueRow.documentId);
@@ -1807,6 +2247,9 @@ async function loadSourceSnapshot(
1807
2247
  localFieldKey: row.localFieldKey,
1808
2248
  sourceFieldKey: row.sourceFieldKey,
1809
2249
  sourceFieldLabel: row.sourceFieldLabel,
2250
+ propertyType: row.propertyId
2251
+ ? (propertyTypeById.get(row.propertyId) ?? null)
2252
+ : null,
1810
2253
  }));
1811
2254
  // Row-union ownership scoping (Builder only). Determine which documents belong
1812
2255
  // to OTHER sources and whether this source is the primary (oldest), so the
@@ -1888,9 +2331,8 @@ async function loadSourceSnapshot(
1888
2331
  await Promise.all(
1889
2332
  allDocumentIds.map(async (documentId) => {
1890
2333
  if (!hydratedDocumentIds.has(documentId)) return;
1891
- const row = sourceRowByDocumentId.get(documentId) ?? {
1892
- sourceValuesJson: "{}",
1893
- };
2334
+ const row = sourceRowByDocumentId.get(documentId);
2335
+ if (!row) return;
1894
2336
  const bodyChange = await builderBodyChangeForLocalContent({
1895
2337
  row,
1896
2338
  localContent: documentContentById.get(documentId),
@@ -1915,6 +2357,11 @@ async function loadSourceSnapshot(
1915
2357
  allowUnsourcedCreates: isPrimarySource,
1916
2358
  taggedSourceByDocumentId,
1917
2359
  bodyChangeByDocumentId,
2360
+ sourceImportedDocumentIds: new Set(
2361
+ rowRows
2362
+ .filter((row) => row.provenance === "Builder CMS read adapter")
2363
+ .map((row) => row.documentId),
2364
+ ),
1918
2365
  });
1919
2366
  const rowByDocumentId = new Map(rowRows.map((row) => [row.documentId, row]));
1920
2367
  const changeSets = [...storedChangeSets, ...localOutboundChangeSets].map(
@@ -1990,6 +2437,32 @@ async function loadSourceSnapshot(
1990
2437
  typeof metadata.lastReadMatchedRowCount === "number"
1991
2438
  ? metadata.lastReadMatchedRowCount
1992
2439
  : undefined,
2440
+ lastReadLimit:
2441
+ typeof metadata.lastReadLimit === "number"
2442
+ ? metadata.lastReadLimit
2443
+ : undefined,
2444
+ lastReadFetchedEntryCount:
2445
+ typeof metadata.lastReadFetchedEntryCount === "number"
2446
+ ? metadata.lastReadFetchedEntryCount
2447
+ : undefined,
2448
+ lastReadPartial:
2449
+ typeof metadata.lastReadPartial === "boolean"
2450
+ ? metadata.lastReadPartial
2451
+ : undefined,
2452
+ lastReadHasMore:
2453
+ typeof metadata.lastReadHasMore === "boolean"
2454
+ ? metadata.lastReadHasMore
2455
+ : undefined,
2456
+ lastReadNextOffset:
2457
+ typeof metadata.lastReadNextOffset === "number"
2458
+ ? metadata.lastReadNextOffset
2459
+ : undefined,
2460
+ sourceFetchState:
2461
+ metadata.sourceFetchState === "idle" ||
2462
+ metadata.sourceFetchState === "fetching" ||
2463
+ metadata.sourceFetchState === "error"
2464
+ ? metadata.sourceFetchState
2465
+ : undefined,
1993
2466
  allowDraftWrites: metadata.allowDraftWrites === true,
1994
2467
  allowPublishWrites: metadata.allowPublishWrites === true,
1995
2468
  allowedWriteModes: Array.isArray(metadata.allowedWriteModes)
@@ -2011,7 +2484,10 @@ async function sourceBodyHydrationSummary(args: {
2011
2484
  databaseId: string;
2012
2485
  }): Promise<ContentDatabaseBodyHydrationSummary> {
2013
2486
  const rows = await getDb()
2014
- .select({ status: schema.contentDatabaseItems.bodyHydrationStatus })
2487
+ .select({
2488
+ status: schema.contentDatabaseItems.bodyHydrationStatus,
2489
+ queueId: schema.contentDatabaseBodyHydrationQueue.id,
2490
+ })
2015
2491
  .from(schema.contentDatabaseItems)
2016
2492
  .innerJoin(
2017
2493
  schema.contentDatabaseSourceRows,
@@ -2020,6 +2496,16 @@ async function sourceBodyHydrationSummary(args: {
2020
2496
  schema.contentDatabaseItems.id,
2021
2497
  ),
2022
2498
  )
2499
+ .leftJoin(
2500
+ schema.contentDatabaseBodyHydrationQueue,
2501
+ and(
2502
+ eq(
2503
+ schema.contentDatabaseBodyHydrationQueue.databaseItemId,
2504
+ schema.contentDatabaseItems.id,
2505
+ ),
2506
+ eq(schema.contentDatabaseBodyHydrationQueue.sourceId, args.sourceId),
2507
+ ),
2508
+ )
2023
2509
  .where(
2024
2510
  and(
2025
2511
  eq(schema.contentDatabaseItems.databaseId, args.databaseId),
@@ -2034,8 +2520,9 @@ async function sourceBodyHydrationSummary(args: {
2034
2520
  total: rows.length,
2035
2521
  };
2036
2522
  for (const row of rows) {
2037
- if (row.status === "pending") summary.pending += 1;
2038
- else if (row.status === "hydrating") summary.hydrating += 1;
2523
+ if (row.status === "pending" || (!row.status && row.queueId)) {
2524
+ summary.pending += 1;
2525
+ } else if (row.status === "hydrating") summary.hydrating += 1;
2039
2526
  else if (row.status === "error") summary.error += 1;
2040
2527
  else summary.hydrated += 1;
2041
2528
  }
@@ -2117,6 +2604,9 @@ export function serializeBuilderCmsSourceReadMetadataRecord(args: {
2117
2604
  readState: BuilderCmsReadState;
2118
2605
  entryCount: number;
2119
2606
  matchedRowCount: number;
2607
+ progress?: BuilderCmsReadProgress;
2608
+ sourceFetchState?: "idle" | "fetching" | "error";
2609
+ activeReadSourceRowIds?: string[];
2120
2610
  }) {
2121
2611
  return JSON.stringify({
2122
2612
  ...builderCmsSourceMetadata(args.sourceTable),
@@ -2124,6 +2614,19 @@ export function serializeBuilderCmsSourceReadMetadataRecord(args: {
2124
2614
  liveReadConfigured: args.readState === "live",
2125
2615
  lastReadEntryCount: args.entryCount,
2126
2616
  lastReadMatchedRowCount: args.matchedRowCount,
2617
+ lastReadLimit: args.progress?.requestedLimit,
2618
+ lastReadFetchedEntryCount: args.progress?.fetchedEntryCount,
2619
+ lastReadPartial: args.progress?.partial,
2620
+ lastReadHasMore: args.progress?.hasMore,
2621
+ lastReadNextOffset: args.progress?.nextOffset,
2622
+ activeReadSourceRowIds: args.activeReadSourceRowIds,
2623
+ sourceFetchState:
2624
+ args.sourceFetchState ??
2625
+ (args.progress?.partial
2626
+ ? "fetching"
2627
+ : args.readState === "error"
2628
+ ? "error"
2629
+ : "idle"),
2127
2630
  });
2128
2631
  }
2129
2632
 
@@ -2195,6 +2698,7 @@ export async function seedMockSourceFields(args: {
2195
2698
  properties: DocumentProperty[];
2196
2699
  builderModelFields?: BuilderCmsModelFieldSummary[];
2197
2700
  builderSampleEntries?: BuilderCmsSourceEntry[];
2701
+ existingFields?: ContentDatabaseSourceFieldRowDb[];
2198
2702
  now: string;
2199
2703
  }) {
2200
2704
  const db = getDb();
@@ -2404,7 +2908,30 @@ export async function seedMockSourceFields(args: {
2404
2908
  }
2405
2909
  }
2406
2910
 
2407
- await db.insert(schema.contentDatabaseSourceFields).values(rows);
2911
+ const existingFieldBySourceKey = new Map(
2912
+ (args.existingFields ?? []).map((field) => [
2913
+ field.sourceFieldKey.trim().toLowerCase(),
2914
+ field,
2915
+ ]),
2916
+ );
2917
+ const mergedRows = rows.map((row) => {
2918
+ const existing = existingFieldBySourceKey.get(
2919
+ row.sourceFieldKey.trim().toLowerCase(),
2920
+ );
2921
+ if (!existing) return row;
2922
+ return {
2923
+ ...row,
2924
+ id: existing.id,
2925
+ propertyId: existing.propertyId ?? row.propertyId,
2926
+ localFieldKey: existing.propertyId
2927
+ ? existing.localFieldKey
2928
+ : row.localFieldKey,
2929
+ mappingType: existing.propertyId ? existing.mappingType : row.mappingType,
2930
+ createdAt: existing.createdAt,
2931
+ };
2932
+ });
2933
+
2934
+ await db.insert(schema.contentDatabaseSourceFields).values(mergedRows);
2408
2935
  }
2409
2936
 
2410
2937
  export async function seedMockSourceRows(args: {
@@ -2491,7 +3018,12 @@ export function sourceValuesForSeededSourceRow(args: {
2491
3018
  const existingSourceValues = parseObject<
2492
3019
  Record<string, DocumentPropertyValue>
2493
3020
  >(args.existingSourceValuesJson);
2494
- if (args.builderEntry?.sourceValues) return args.builderEntry.sourceValues;
3021
+ if (args.builderEntry?.sourceValues) {
3022
+ return builderSourceValuesWithPreservedBodyBaseline({
3023
+ incoming: args.builderEntry.sourceValues,
3024
+ existing: existingSourceValues,
3025
+ });
3026
+ }
2495
3027
  if (existingSourceValues) return existingSourceValues;
2496
3028
  if (args.sourceType !== "builder-cms") return {};
2497
3029
  return buildBuilderCmsFixtureEntry({
@@ -2501,6 +3033,135 @@ export function sourceValuesForSeededSourceRow(args: {
2501
3033
  }).sourceValues;
2502
3034
  }
2503
3035
 
3036
+ function builderSourceValuesWithPreservedBodyBaseline(args: {
3037
+ incoming: Record<string, DocumentPropertyValue>;
3038
+ existing?: Record<string, DocumentPropertyValue> | null;
3039
+ }) {
3040
+ const existing = args.existing;
3041
+ if (!existing) return args.incoming;
3042
+ const incomingContent = stringSourceValue(
3043
+ args.incoming,
3044
+ BUILDER_CMS_BODY_CONTENT_KEY,
3045
+ );
3046
+ const existingContent = stringSourceValue(
3047
+ existing,
3048
+ BUILDER_CMS_BODY_CONTENT_KEY,
3049
+ );
3050
+ const incomingHash = stringSourceValue(
3051
+ args.incoming,
3052
+ BUILDER_CMS_BODY_BLOCKS_HASH_KEY,
3053
+ );
3054
+ const existingHash = stringSourceValue(
3055
+ existing,
3056
+ BUILDER_CMS_BODY_BLOCKS_HASH_KEY,
3057
+ );
3058
+ if (
3059
+ incomingContent?.trim() ||
3060
+ !existingContent?.trim() ||
3061
+ (incomingHash && existingHash && incomingHash !== existingHash)
3062
+ ) {
3063
+ return args.incoming;
3064
+ }
3065
+
3066
+ const next = { ...args.incoming };
3067
+ for (const key of [
3068
+ BUILDER_CMS_BODY_CONTENT_KEY,
3069
+ BUILDER_CMS_BODY_LOSSLESS_CONTENT_KEY,
3070
+ BUILDER_CMS_BODY_READABLE_MAP_KEY,
3071
+ BUILDER_CMS_BODY_SIDECARS_KEY,
3072
+ BUILDER_CMS_BODY_BLOCKS_HASH_KEY,
3073
+ ]) {
3074
+ if (next[key] === undefined && existing[key] !== undefined) {
3075
+ next[key] = existing[key];
3076
+ }
3077
+ }
3078
+ return next;
3079
+ }
3080
+
3081
+ async function materializeSourceFieldPropertyValues(args: {
3082
+ database: ContentDatabaseRow;
3083
+ sourceId: string;
3084
+ fields: ContentDatabaseSourceFieldRowDb[];
3085
+ documentIds?: string[];
3086
+ now: string;
3087
+ }) {
3088
+ const boundFields = args.fields.filter((field) => field.propertyId);
3089
+ if (boundFields.length === 0) return;
3090
+ const db = getDb();
3091
+ const documentIdSet =
3092
+ args.documentIds && args.documentIds.length > 0
3093
+ ? new Set(args.documentIds)
3094
+ : null;
3095
+ const propertyIds = Array.from(
3096
+ new Set(boundFields.map((field) => field.propertyId!)),
3097
+ );
3098
+ const definitions = await db
3099
+ .select()
3100
+ .from(schema.documentPropertyDefinitions)
3101
+ .where(inArray(schema.documentPropertyDefinitions.id, propertyIds));
3102
+ const definitionById = new Map(
3103
+ definitions.map((definition) => [definition.id, definition]),
3104
+ );
3105
+ const sourceRows = await db
3106
+ .select()
3107
+ .from(schema.contentDatabaseSourceRows)
3108
+ .where(eq(schema.contentDatabaseSourceRows.sourceId, args.sourceId));
3109
+ const scopedRows = documentIdSet
3110
+ ? sourceRows.filter((row) => documentIdSet.has(row.documentId))
3111
+ : sourceRows;
3112
+ if (scopedRows.length === 0) return;
3113
+
3114
+ const existingValues = await db
3115
+ .select()
3116
+ .from(schema.documentPropertyValues)
3117
+ .where(inArray(schema.documentPropertyValues.propertyId, propertyIds));
3118
+ const existingByDocumentAndProperty = new Map(
3119
+ existingValues.map((value) => [
3120
+ `${value.documentId}\0${value.propertyId}`,
3121
+ value,
3122
+ ]),
3123
+ );
3124
+
3125
+ for (const row of scopedRows) {
3126
+ const sourceValues =
3127
+ parseObject<Record<string, DocumentPropertyValue>>(
3128
+ row.sourceValuesJson,
3129
+ ) ?? {};
3130
+ for (const field of boundFields) {
3131
+ const propertyId = field.propertyId!;
3132
+ const definition = definitionById.get(propertyId);
3133
+ if (!definition) continue;
3134
+ const normalized = normalizePropertyValue(
3135
+ definition.type as DocumentProperty["definition"]["type"],
3136
+ sourceValues[field.sourceFieldKey],
3137
+ );
3138
+ if (normalized === null) continue;
3139
+ const valueJson = serializePropertyValue(normalized);
3140
+ const key = `${row.documentId}\0${propertyId}`;
3141
+ const existing = existingByDocumentAndProperty.get(key);
3142
+ if (existing) {
3143
+ if (existing.valueJson === valueJson) continue;
3144
+ await db
3145
+ .update(schema.documentPropertyValues)
3146
+ .set({ valueJson, updatedAt: args.now })
3147
+ .where(eq(schema.documentPropertyValues.id, existing.id));
3148
+ } else {
3149
+ const value = {
3150
+ id: nanoid(),
3151
+ ownerEmail: args.database.ownerEmail,
3152
+ documentId: row.documentId,
3153
+ propertyId,
3154
+ valueJson,
3155
+ createdAt: args.now,
3156
+ updatedAt: args.now,
3157
+ };
3158
+ await db.insert(schema.documentPropertyValues).values(value);
3159
+ existingByDocumentAndProperty.set(key, value);
3160
+ }
3161
+ }
3162
+ }
3163
+ }
3164
+
2504
3165
  function openChangeSetKey(row: ContentDatabaseSourceChangeSetRowDb) {
2505
3166
  const fields = parseArray<ContentDatabaseSourceFieldChange>(
2506
3167
  row.fieldChangesJson,
@@ -2855,8 +3516,14 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
2855
3516
  // per-source re-import idempotency is still handled by
2856
3517
  // builderCmsEntryAlreadyRepresented (existingSourceRows).
2857
3518
  skipTitleDedup?: boolean;
2858
- }) {
2859
- if (args.entries.length === 0) return 0;
3519
+ }): Promise<{
3520
+ imported: number;
3521
+ importedEntriesByDocumentId: Map<string, BuilderCmsSourceEntry>;
3522
+ }> {
3523
+ const importedEntriesByDocumentId = new Map<string, BuilderCmsSourceEntry>();
3524
+ if (args.entries.length === 0) {
3525
+ return { imported: 0, importedEntriesByDocumentId };
3526
+ }
2860
3527
  const db = getDb();
2861
3528
  const [databaseDocument] = await db
2862
3529
  .select()
@@ -2940,36 +3607,73 @@ export async function importBuilderCmsEntriesAsDatabaseItems(args: {
2940
3607
  updatedAt: args.now,
2941
3608
  });
2942
3609
  imported += 1;
3610
+ importedEntriesByDocumentId.set(documentId, entry);
2943
3611
  }
2944
3612
 
2945
- return imported;
3613
+ return { imported, importedEntriesByDocumentId };
2946
3614
  }
2947
3615
 
2948
3616
  export async function resyncBuilderCmsSourceSnapshot(args: {
2949
3617
  database: ContentDatabaseRow;
2950
3618
  source: ContentDatabaseSourceRowDb;
2951
3619
  now: string;
3620
+ runFullRefresh?: boolean;
2952
3621
  }) {
2953
3622
  let { properties, response } = await sourceSetupPayload(args.database.id);
2954
3623
  const db = getDb();
3624
+ const sourceMetadata =
3625
+ parseObject<SourceMetadataRecord>(args.source.metadataJson) ?? {};
3626
+ const activeReadSourceRowIds =
3627
+ Array.isArray(sourceMetadata.activeReadSourceRowIds) &&
3628
+ sourceMetadata.activeReadSourceRowIds.every((id) => typeof id === "string")
3629
+ ? sourceMetadata.activeReadSourceRowIds
3630
+ : [];
3631
+ const continueOffset =
3632
+ !args.runFullRefresh &&
3633
+ sourceMetadata.sourceFetchState === "fetching" &&
3634
+ sourceMetadata.lastReadHasMore === true &&
3635
+ activeReadSourceRowIds.length > 0 &&
3636
+ typeof sourceMetadata.lastReadNextOffset === "number" &&
3637
+ sourceMetadata.lastReadNextOffset > 0
3638
+ ? sourceMetadata.lastReadNextOffset
3639
+ : 0;
2955
3640
  const builderRead = await readBuilderCmsContentEntries({
2956
3641
  model: args.source.sourceTable,
3642
+ maxPages: args.runFullRefresh
3643
+ ? undefined
3644
+ : BUILDER_CMS_REFRESH_INITIAL_PAGES,
3645
+ offset: continueOffset,
2957
3646
  });
3647
+ const incrementalRead =
3648
+ builderRead.state === "live" &&
3649
+ !args.runFullRefresh &&
3650
+ (builderRead.progress?.partial === true ||
3651
+ (builderRead.progress?.startOffset ?? 0) > 0);
2958
3652
  const builderEntries =
2959
3653
  builderRead.state === "live" ? builderRead.entries : [];
2960
3654
  let existingRows = await db
2961
3655
  .select()
2962
3656
  .from(schema.contentDatabaseSourceRows)
2963
3657
  .where(eq(schema.contentDatabaseSourceRows.sourceId, args.source.id));
3658
+ await enqueueEmptyHydratedBuilderBodiesFromStoredRows({
3659
+ source: args.source,
3660
+ now: args.now,
3661
+ });
3662
+ let importedEntriesByDocumentId = new Map<string, BuilderCmsSourceEntry>();
3663
+ const existingFields = await db
3664
+ .select()
3665
+ .from(schema.contentDatabaseSourceFields)
3666
+ .where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
2964
3667
  if (builderRead.state === "live") {
2965
- const imported = await importBuilderCmsEntriesAsDatabaseItems({
3668
+ const importResult = await importBuilderCmsEntriesAsDatabaseItems({
2966
3669
  database: args.database,
2967
3670
  entries: builderEntries,
2968
3671
  now: args.now,
2969
3672
  sourceTable: args.source.sourceTable,
2970
3673
  existingSourceRows: existingRows,
2971
3674
  });
2972
- if (imported && imported > 0) {
3675
+ importedEntriesByDocumentId = importResult.importedEntriesByDocumentId;
3676
+ if (importResult.imported > 0) {
2973
3677
  ({ properties, response } = await sourceSetupPayload(args.database.id));
2974
3678
  existingRows = await db
2975
3679
  .select()
@@ -2990,6 +3694,9 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
2990
3694
  existingRows,
2991
3695
  })
2992
3696
  : new Map<string, BuilderCmsSourceEntry>();
3697
+ for (const [documentId, entry] of importedEntriesByDocumentId) {
3698
+ builderEntriesByDocumentId.set(documentId, entry);
3699
+ }
2993
3700
  const existingBuilderRows = new Map<string, ExistingBuilderSourceRowIdentity>(
2994
3701
  existingRows.map((row) => [
2995
3702
  row.documentId,
@@ -3003,6 +3710,113 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
3003
3710
  },
3004
3711
  ]),
3005
3712
  );
3713
+
3714
+ if (incrementalRead) {
3715
+ const currentSourceRowIds = builderEntries.map((entry) => entry.id);
3716
+ const nextActiveReadSourceRowIds = Array.from(
3717
+ new Set(
3718
+ continueOffset > 0
3719
+ ? [...activeReadSourceRowIds, ...currentSourceRowIds]
3720
+ : currentSourceRowIds,
3721
+ ),
3722
+ );
3723
+ const hasMore = builderRead.progress?.hasMore === true;
3724
+
3725
+ await db
3726
+ .delete(schema.contentDatabaseSourceFields)
3727
+ .where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
3728
+ await seedMockSourceFields({
3729
+ sourceId: args.source.id,
3730
+ ownerEmail: args.database.ownerEmail,
3731
+ sourceType: "builder-cms",
3732
+ properties,
3733
+ builderModelFields,
3734
+ builderSampleEntries: builderEntries,
3735
+ existingFields,
3736
+ now: args.now,
3737
+ });
3738
+
3739
+ const itemsToLink = response.items.filter((item) =>
3740
+ builderEntriesByDocumentId.has(item.document.id),
3741
+ );
3742
+ const fetchedDocumentIds = itemsToLink.map((item) => item.document.id);
3743
+ if (fetchedDocumentIds.length > 0) {
3744
+ await db
3745
+ .delete(schema.contentDatabaseSourceRows)
3746
+ .where(
3747
+ and(
3748
+ eq(schema.contentDatabaseSourceRows.sourceId, args.source.id),
3749
+ inArray(
3750
+ schema.contentDatabaseSourceRows.documentId,
3751
+ fetchedDocumentIds,
3752
+ ),
3753
+ ),
3754
+ );
3755
+ await seedMockSourceRows({
3756
+ sourceId: args.source.id,
3757
+ ownerEmail: args.database.ownerEmail,
3758
+ sourceType: "builder-cms",
3759
+ sourceTable: args.source.sourceTable,
3760
+ items: itemsToLink,
3761
+ now: args.now,
3762
+ existingBuilderRows,
3763
+ builderEntriesByDocumentId,
3764
+ });
3765
+ const refreshedFields = await db
3766
+ .select()
3767
+ .from(schema.contentDatabaseSourceFields)
3768
+ .where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
3769
+ await materializeSourceFieldPropertyValues({
3770
+ database: args.database,
3771
+ sourceId: args.source.id,
3772
+ fields: refreshedFields,
3773
+ documentIds: fetchedDocumentIds,
3774
+ now: args.now,
3775
+ });
3776
+ await enqueueBuilderBodyHydrationForItems({
3777
+ sourceId: args.source.id,
3778
+ ownerEmail: args.database.ownerEmail,
3779
+ orgId: args.database.orgId,
3780
+ sourceTable: args.source.sourceTable,
3781
+ items: itemsToLink,
3782
+ builderEntriesByDocumentId,
3783
+ now: args.now,
3784
+ });
3785
+ }
3786
+ if (!hasMore && nextActiveReadSourceRowIds.length > 0) {
3787
+ await db
3788
+ .delete(schema.contentDatabaseSourceRows)
3789
+ .where(
3790
+ and(
3791
+ eq(schema.contentDatabaseSourceRows.sourceId, args.source.id),
3792
+ notInArray(
3793
+ schema.contentDatabaseSourceRows.sourceRowId,
3794
+ nextActiveReadSourceRowIds,
3795
+ ),
3796
+ ),
3797
+ );
3798
+ }
3799
+
3800
+ await updateBuilderCmsSourceReadMetadata({
3801
+ sourceId: args.source.id,
3802
+ sourceTable: args.source.sourceTable,
3803
+ readState: builderRead.state,
3804
+ entryCount: builderRead.entries.length,
3805
+ matchedRowCount: builderEntriesByDocumentId.size,
3806
+ fetchedAt: builderRead.fetchedAt,
3807
+ now: args.now,
3808
+ message: builderRead.message,
3809
+ progress: {
3810
+ ...builderRead.progress,
3811
+ partial: hasMore,
3812
+ },
3813
+ sourceFetchState: hasMore ? "fetching" : "idle",
3814
+ syncState: hasMore ? "refreshing" : "idle",
3815
+ activeReadSourceRowIds: hasMore ? nextActiveReadSourceRowIds : undefined,
3816
+ });
3817
+ return;
3818
+ }
3819
+
3006
3820
  await db
3007
3821
  .delete(schema.contentDatabaseSourceFields)
3008
3822
  .where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
@@ -3017,6 +3831,7 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
3017
3831
  properties,
3018
3832
  builderModelFields,
3019
3833
  builderSampleEntries: builderEntries,
3834
+ existingFields,
3020
3835
  now: args.now,
3021
3836
  });
3022
3837
  // Row-union: a resync must only (re)link items that BELONG to this source —
@@ -3051,6 +3866,17 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
3051
3866
  existingBuilderRows,
3052
3867
  builderEntriesByDocumentId,
3053
3868
  });
3869
+ const refreshedFields = await db
3870
+ .select()
3871
+ .from(schema.contentDatabaseSourceFields)
3872
+ .where(eq(schema.contentDatabaseSourceFields.sourceId, args.source.id));
3873
+ await materializeSourceFieldPropertyValues({
3874
+ database: args.database,
3875
+ sourceId: args.source.id,
3876
+ fields: refreshedFields,
3877
+ documentIds: itemsToLink.map((item) => item.document.id),
3878
+ now: args.now,
3879
+ });
3054
3880
  if (builderRead.state === "live") {
3055
3881
  await enqueueBuilderBodyHydrationForItems({
3056
3882
  sourceId: args.source.id,
@@ -3090,6 +3916,9 @@ export async function resyncBuilderCmsSourceSnapshot(args: {
3090
3916
  fetchedAt: builderRead.fetchedAt,
3091
3917
  now: args.now,
3092
3918
  message: builderRead.message,
3919
+ progress: builderRead.progress,
3920
+ sourceFetchState: builderRead.state === "error" ? "error" : "idle",
3921
+ activeReadSourceRowIds: undefined,
3093
3922
  syncState: "idle",
3094
3923
  });
3095
3924
  }
@@ -3415,6 +4244,9 @@ export async function updateBuilderCmsSourceReadMetadata(args: {
3415
4244
  fetchedAt: string;
3416
4245
  now: string;
3417
4246
  message: string | null;
4247
+ progress?: BuilderCmsReadProgress;
4248
+ sourceFetchState?: "idle" | "fetching" | "error";
4249
+ activeReadSourceRowIds?: string[];
3418
4250
  syncState?: ContentDatabaseSourceSyncState;
3419
4251
  }) {
3420
4252
  const db = getDb();
@@ -3436,13 +4268,19 @@ export async function updateBuilderCmsSourceReadMetadata(args: {
3436
4268
  readState: args.readState,
3437
4269
  entryCount: args.entryCount,
3438
4270
  matchedRowCount: args.matchedRowCount,
4271
+ progress: args.progress,
4272
+ sourceFetchState: args.sourceFetchState,
4273
+ activeReadSourceRowIds: args.activeReadSourceRowIds,
3439
4274
  }),
3440
4275
  });
3441
4276
  await db
3442
4277
  .update(schema.contentDatabaseSources)
3443
4278
  .set({
3444
4279
  syncState: args.syncState ?? "linked",
3445
- freshness: args.readState === "error" ? "stale" : "fresh",
4280
+ freshness:
4281
+ args.readState === "error" || args.progress?.partial
4282
+ ? "stale"
4283
+ : "fresh",
3446
4284
  capabilitiesJson: nextJson.capabilitiesJson,
3447
4285
  metadataJson: nextJson.metadataJson,
3448
4286
  lastRefreshedAt: args.now,