@agent-native/core 0.137.7 → 0.138.0

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 (446) hide show
  1. package/agent-native.eject.json +2 -0
  2. package/corpus/README.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/agent-native-docs/SKILL.md +23 -3
  4. package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  5. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +15 -0
  6. package/corpus/templates/analytics/.agents/skills/frontend-design/SKILL.md +23 -2
  7. package/corpus/templates/analytics/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  8. package/corpus/templates/analytics/AGENTS.md +2 -2
  9. package/corpus/templates/analytics/actions/get-first-party-analytics-health.ts +65 -0
  10. package/corpus/templates/analytics/actions/provider-api-catalog.ts +3 -6
  11. package/corpus/templates/analytics/actions/provider-api-docs.ts +3 -6
  12. package/corpus/templates/analytics/actions/provider-api-request.ts +3 -6
  13. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +3 -3
  14. package/corpus/templates/analytics/agent-native.json +12 -0
  15. package/corpus/templates/analytics/app/i18n-data.ts +162 -0
  16. package/corpus/templates/analytics/app/pages/DataSources.tsx +126 -1
  17. package/corpus/templates/analytics/changelog/2026-08-05-analytics-now-flags-neon-pressure-and-guides-high-volume-tra.md +6 -0
  18. package/corpus/templates/analytics/changelog/2026-08-05-first-party-analytics-uses-compact-rollups-for-faster-usage-.md +6 -0
  19. package/corpus/templates/analytics/server/db/schema.ts +52 -0
  20. package/corpus/templates/analytics/server/lib/credential-keys.ts +3 -0
  21. package/corpus/templates/analytics/server/lib/first-party-analytics-health.ts +397 -0
  22. package/corpus/templates/analytics/server/lib/first-party-analytics-rollups.ts +182 -0
  23. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +86 -15
  24. package/corpus/templates/analytics/server/lib/provider-api.ts +20 -1
  25. package/corpus/templates/analytics/server/lib/replay-storage.ts +1 -1
  26. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -0
  27. package/corpus/templates/analytics/server/plugins/db.ts +60 -0
  28. package/corpus/templates/assets/.agents/skills/agent-native-docs/SKILL.md +23 -3
  29. package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  30. package/corpus/templates/assets/.agents/skills/frontend-design/SKILL.md +23 -2
  31. package/corpus/templates/assets/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  32. package/corpus/templates/assets/agent-native.json +12 -0
  33. package/corpus/templates/assets/server/lib/generation.ts +8 -8
  34. package/corpus/templates/assets/server/lib/storage.ts +1 -1
  35. package/corpus/templates/assets/server/plugins/onboarding.ts +1 -1
  36. package/corpus/templates/brain/.agents/skills/agent-native-docs/SKILL.md +23 -3
  37. package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  38. package/corpus/templates/brain/.agents/skills/frontend-design/SKILL.md +23 -2
  39. package/corpus/templates/brain/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  40. package/corpus/templates/brain/agent-native.json +12 -0
  41. package/corpus/templates/calendar/.agents/skills/agent-native-docs/SKILL.md +23 -3
  42. package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  43. package/corpus/templates/calendar/.agents/skills/frontend-design/SKILL.md +23 -2
  44. package/corpus/templates/calendar/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  45. package/corpus/templates/calendar/agent-native.json +12 -0
  46. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +23 -3
  47. package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  48. package/corpus/templates/chat/.agents/skills/frontend-design/SKILL.md +23 -2
  49. package/corpus/templates/chat/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  50. package/corpus/templates/chat/AGENTS.md +3 -0
  51. package/corpus/templates/chat/DESIGN.md +28 -0
  52. package/corpus/templates/chat/agent-native.json +12 -0
  53. package/corpus/templates/clips/.agents/skills/agent-native-docs/SKILL.md +23 -3
  54. package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  55. package/corpus/templates/clips/.agents/skills/frontend-design/SKILL.md +23 -2
  56. package/corpus/templates/clips/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  57. package/corpus/templates/clips/actions/cleanup-transcript.ts +2 -2
  58. package/corpus/templates/clips/actions/delete-recording-permanent.ts +3 -0
  59. package/corpus/templates/clips/actions/get-playback-position.ts +68 -0
  60. package/corpus/templates/clips/actions/import-loom-recording.ts +2 -2
  61. package/corpus/templates/clips/actions/save-playback-position.ts +80 -0
  62. package/corpus/templates/clips/actions/set-thumbnail.ts +1 -1
  63. package/corpus/templates/clips/agent-native.json +12 -0
  64. package/corpus/templates/clips/app/components/player/video-player.tsx +54 -5
  65. package/corpus/templates/clips/app/components/recorder/pre-record-panel.tsx +65 -18
  66. package/corpus/templates/clips/app/hooks/use-playback-position.ts +188 -0
  67. package/corpus/templates/clips/app/hooks/use-view-tracking.ts +6 -24
  68. package/corpus/templates/clips/app/i18n/en-US.ts +4 -0
  69. package/corpus/templates/clips/app/lib/playback-position.ts +99 -0
  70. package/corpus/templates/clips/app/lib/viewer-session.ts +29 -0
  71. package/corpus/templates/clips/changelog/2026-08-05-clip-buffering-now-uses-buffering-copy-after-playback.md +6 -0
  72. package/corpus/templates/clips/changelog/2026-08-05-clips-remembers-your-playback-position-when-you-reopen-a-cli.md +6 -0
  73. package/corpus/templates/clips/desktop/package.json +1 -0
  74. package/corpus/templates/clips/desktop/src/app.tsx +70 -6
  75. package/corpus/templates/clips/desktop/src/components/AlertDialog.tsx +131 -0
  76. package/corpus/templates/clips/desktop/src/styles.css +129 -0
  77. package/corpus/templates/clips/learnings.defaults.md +1 -1
  78. package/corpus/templates/clips/server/db/schema.ts +18 -0
  79. package/corpus/templates/clips/server/lib/public-agent-context.ts +1 -1
  80. package/corpus/templates/clips/server/lib/video-storage.ts +1 -1
  81. package/corpus/templates/clips/server/plugins/db.ts +15 -0
  82. package/corpus/templates/clips/server/routes/api/media/index.post.ts +1 -1
  83. package/corpus/templates/content/.agents/skills/agent-native-docs/SKILL.md +23 -3
  84. package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  85. package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +13 -0
  86. package/corpus/templates/content/.agents/skills/frontend-design/SKILL.md +23 -2
  87. package/corpus/templates/content/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  88. package/corpus/templates/content/AGENTS.md +1 -0
  89. package/corpus/templates/content/actions/_content-database-mutation-lock.ts +41 -0
  90. package/corpus/templates/content/actions/_content-database-row-migration.ts +569 -0
  91. package/corpus/templates/content/actions/_database-source-utils.ts +82 -36
  92. package/corpus/templates/content/actions/_database-utils.ts +5 -0
  93. package/corpus/templates/content/actions/_delete-content-space.ts +25 -11
  94. package/corpus/templates/content/actions/add-database-item.ts +60 -42
  95. package/corpus/templates/content/actions/attach-content-database-source.ts +2 -0
  96. package/corpus/templates/content/actions/configure-document-property.ts +99 -104
  97. package/corpus/templates/content/actions/delete-content-database.ts +19 -7
  98. package/corpus/templates/content/actions/delete-document-property.ts +47 -58
  99. package/corpus/templates/content/actions/delete-document.ts +390 -167
  100. package/corpus/templates/content/actions/disconnect-content-database-source.ts +41 -24
  101. package/corpus/templates/content/actions/duplicate-database-item.ts +63 -31
  102. package/corpus/templates/content/actions/duplicate-database-items.ts +86 -43
  103. package/corpus/templates/content/actions/duplicate-document-property.ts +37 -14
  104. package/corpus/templates/content/actions/migrate-content-database-rows.ts +830 -0
  105. package/corpus/templates/content/actions/move-database-item.ts +14 -0
  106. package/corpus/templates/content/actions/permanently-delete-document.ts +27 -6
  107. package/corpus/templates/content/actions/remove-database-items.ts +13 -0
  108. package/corpus/templates/content/actions/reorder-document-property.ts +45 -46
  109. package/corpus/templates/content/actions/set-document-property.ts +68 -28
  110. package/corpus/templates/content/actions/submit-content-database-form.ts +49 -0
  111. package/corpus/templates/content/actions/transcribe-media.ts +1 -1
  112. package/corpus/templates/content/agent-native.json +12 -0
  113. package/corpus/templates/content/app/components/editor/image-upload.ts +2 -2
  114. package/corpus/templates/content/app/i18n-data.ts +1 -1
  115. package/corpus/templates/content/changelog/2026-08-01-content-can-reorganize-every-row-in-an-existing-database-as-.md +6 -0
  116. package/corpus/templates/content/parity/matrix.md +1 -1
  117. package/corpus/templates/content/parity/matrix.ts +4 -2
  118. package/corpus/templates/content/server/db/schema.ts +30 -0
  119. package/corpus/templates/content/server/plugins/db.ts +24 -0
  120. package/corpus/templates/content/server/plugins/onboarding.ts +2 -2
  121. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +23 -3
  122. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  123. package/corpus/templates/crm/agent-native.json +12 -0
  124. package/corpus/templates/design/.agents/skills/agent-native-docs/SKILL.md +23 -3
  125. package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  126. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +2 -0
  127. package/corpus/templates/design/.agents/skills/frontend-design/SKILL.md +23 -2
  128. package/corpus/templates/design/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  129. package/corpus/templates/design/CODE_NATIVE_DESIGN_STUDIO_IMPLEMENTATION_PLAN.md +2 -2
  130. package/corpus/templates/design/DESIGN-STUDIO-PLAN.md +2 -2
  131. package/corpus/templates/design/actions/apply-design-token-edit.ts +1 -1
  132. package/corpus/templates/design/actions/capture-design-state.ts +1 -1
  133. package/corpus/templates/design/actions/connect-builder-app.ts +2 -2
  134. package/corpus/templates/design/actions/create-design-branch.ts +1 -1
  135. package/corpus/templates/design/actions/create-fusion-app.ts +3 -3
  136. package/corpus/templates/design/actions/deploy-design-preview.ts +1 -1
  137. package/corpus/templates/design/actions/generate-design.ts +3 -2
  138. package/corpus/templates/design/actions/get-component-details.ts +1 -1
  139. package/corpus/templates/design/actions/get-design-branch-diff.ts +1 -1
  140. package/corpus/templates/design/actions/index-components.ts +3 -3
  141. package/corpus/templates/design/actions/index-design-system-with-builder.ts +1 -1
  142. package/corpus/templates/design/actions/migrate-inline-design-to-app.ts +3 -3
  143. package/corpus/templates/design/actions/open-component-source.ts +1 -1
  144. package/corpus/templates/design/actions/update-design.ts +96 -14
  145. package/corpus/templates/design/agent-native.json +12 -0
  146. package/corpus/templates/design/app/components/design/DesignImportPanel.tsx +7 -2
  147. package/corpus/templates/design/app/components/design/FigmaHydrationDialog.tsx +11 -2
  148. package/corpus/templates/design/app/components/design/edit-panel/component-section.tsx +1 -1
  149. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +8 -1
  150. package/corpus/templates/design/app/i18n-data.ts +53 -22
  151. package/corpus/templates/design/app/lib/design-file-upload.ts +2 -3
  152. package/corpus/templates/design/app/lib/upload-limits.ts +4 -0
  153. package/corpus/templates/design/app/pages/DesignSystemSetup.tsx +1 -1
  154. package/corpus/templates/design/changelog/2026-08-05-chat-attachments-now-warn-before-upload-when-they-exceed-the.md +6 -0
  155. package/corpus/templates/design/changelog/2026-08-05-figma-fig-uploads-now-show-a-clear-4-mb-limit-up-front-inste.md +6 -0
  156. package/corpus/templates/design/scripts/qa-figma-stress-import.ts +1 -1
  157. package/corpus/templates/design/server/handlers/import-design-file.ts +9 -5
  158. package/corpus/templates/design/server/handlers/uploads.ts +13 -9
  159. package/corpus/templates/design/server/lib/fig-file-limits.ts +2 -1
  160. package/corpus/templates/design/server/lib/figma-image-hydration.ts +1 -1
  161. package/corpus/templates/design/server/lib/figma-node-import.ts +1 -1
  162. package/corpus/templates/design/server/lib/request-body-limits.ts +11 -0
  163. package/corpus/templates/design/shared/canvas-frames.ts +81 -0
  164. package/corpus/templates/design/shared/design-source-capabilities.ts +25 -9
  165. package/corpus/templates/dispatch/.agents/skills/agent-native-docs/SKILL.md +23 -3
  166. package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  167. package/corpus/templates/dispatch/.agents/skills/frontend-design/SKILL.md +23 -2
  168. package/corpus/templates/dispatch/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  169. package/corpus/templates/dispatch/agent-native.json +12 -0
  170. package/corpus/templates/factory/.agents/skills/agent-native-docs/SKILL.md +23 -3
  171. package/corpus/templates/factory/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  172. package/corpus/templates/factory/.agents/skills/frontend-design/SKILL.md +23 -2
  173. package/corpus/templates/factory/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  174. package/corpus/templates/factory/agent-native.json +12 -0
  175. package/corpus/templates/forms/.agents/skills/agent-native-docs/SKILL.md +23 -3
  176. package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  177. package/corpus/templates/forms/.agents/skills/frontend-design/SKILL.md +23 -2
  178. package/corpus/templates/forms/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  179. package/corpus/templates/forms/actions/export-responses.ts +1 -1
  180. package/corpus/templates/forms/agent-native.json +12 -0
  181. package/corpus/templates/macros/.agents/skills/agent-native-docs/SKILL.md +23 -3
  182. package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  183. package/corpus/templates/macros/.agents/skills/frontend-design/SKILL.md +23 -2
  184. package/corpus/templates/macros/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  185. package/corpus/templates/macros/agent-native.json +12 -0
  186. package/corpus/templates/mail/.agents/skills/agent-native-docs/SKILL.md +23 -3
  187. package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  188. package/corpus/templates/mail/.agents/skills/frontend-design/SKILL.md +23 -2
  189. package/corpus/templates/mail/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  190. package/corpus/templates/mail/agent-native.json +12 -0
  191. package/corpus/templates/mail/app/i18n/en-US.ts +1 -1
  192. package/corpus/templates/mail/server/lib/media-upload.ts +1 -1
  193. package/corpus/templates/plan/.agents/skills/agent-native-docs/SKILL.md +23 -3
  194. package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  195. package/corpus/templates/plan/.agents/skills/frontend-design/SKILL.md +23 -2
  196. package/corpus/templates/plan/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  197. package/corpus/templates/plan/agent-native.json +12 -0
  198. package/corpus/templates/plan/server/lib/plan-assets.ts +1 -1
  199. package/corpus/templates/slides/.agents/skills/agent-native-docs/SKILL.md +23 -3
  200. package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  201. package/corpus/templates/slides/.agents/skills/frontend-design/SKILL.md +23 -2
  202. package/corpus/templates/slides/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  203. package/corpus/templates/slides/actions/generate-image-api.ts +1 -1
  204. package/corpus/templates/slides/actions/generate-slides-ai.ts +1 -1
  205. package/corpus/templates/slides/actions/import-file.ts +2 -2
  206. package/corpus/templates/slides/actions/index-design-system-with-builder.ts +1 -1
  207. package/corpus/templates/slides/agent-native.json +12 -0
  208. package/corpus/templates/slides/app/components/design-system/DesignSystemSetup.tsx +1 -1
  209. package/corpus/templates/slides/app/i18n/en-US.ts +2 -2
  210. package/corpus/templates/slides/app/lib/deck-sorting.ts +19 -0
  211. package/corpus/templates/slides/app/lib/image-drop-to-agent.ts +1 -1
  212. package/corpus/templates/slides/app/pages/Index.tsx +7 -2
  213. package/corpus/templates/slides/changelog/2026-08-05-decks-are-now-sorted-by-most-recently-updated.md +6 -0
  214. package/corpus/templates/slides/server/handlers/assets.ts +1 -1
  215. package/corpus/templates/slides/server/handlers/uploads.ts +1 -1
  216. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +23 -3
  217. package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  218. package/corpus/templates/tasks/.agents/skills/frontend-design/SKILL.md +23 -2
  219. package/corpus/templates/tasks/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  220. package/corpus/templates/tasks/agent-native.json +12 -0
  221. package/dist/action.d.ts +6 -0
  222. package/dist/agent/engine/builder-engine.js +2 -2
  223. package/dist/agent/engine/credential-errors.d.ts +1 -1
  224. package/dist/agent/engine/credential-errors.js +2 -2
  225. package/dist/agent/production-agent.js +3 -2
  226. package/dist/brand-kit/fig/index.js +1 -1
  227. package/dist/cli/agent.js +45 -3
  228. package/dist/cli/code-agent-executor.js +2 -1
  229. package/dist/cli/create.d.ts +9 -1
  230. package/dist/cli/create.js +94 -1
  231. package/dist/cli/doctor.d.ts +8 -11
  232. package/dist/cli/doctor.js +92 -24
  233. package/dist/cli/index.js +6 -5
  234. package/dist/client/AgentAskPopover.d.ts +3 -1
  235. package/dist/client/AgentAskPopover.js +2 -2
  236. package/dist/client/AgentPanel.js +9 -3
  237. package/dist/client/AssistantChat.js +54 -33
  238. package/dist/client/MultiTabAssistantChat.js +1 -1
  239. package/dist/client/RouteTransitionIndicator.d.ts +7 -0
  240. package/dist/client/RouteTransitionIndicator.js +31 -0
  241. package/dist/client/agent-engine-key.js +1 -1
  242. package/dist/client/app-providers.js +11 -2
  243. package/dist/client/chat/message-components.js +1 -1
  244. package/dist/client/chat/run-recovery.js +1 -1
  245. package/dist/client/chat/tool-call-display.d.ts +3 -1
  246. package/dist/client/chat/tool-call-display.js +4 -3
  247. package/dist/client/chat-model-groups.d.ts +6 -0
  248. package/dist/client/chat-model-groups.js +11 -0
  249. package/dist/client/error-format.js +1 -1
  250. package/dist/client/i18n.js +1 -1
  251. package/dist/client/onboarding/FirstRunOnboarding.d.ts +5 -1
  252. package/dist/client/onboarding/FirstRunOnboarding.js +29 -13
  253. package/dist/client/onboarding/first-run-enabled.d.ts +6 -1
  254. package/dist/client/onboarding/first-run-enabled.js +33 -3
  255. package/dist/client/onboarding/use-onboarding.js +16 -11
  256. package/dist/client/require-session.js +13 -4
  257. package/dist/client/resources/mcp-integration-catalog.js +26 -0
  258. package/dist/client/resources/mcp-integration-logos.js +4 -0
  259. package/dist/client/route-warmup.js +56 -10
  260. package/dist/client/settings/AccountSettingsCard.js +78 -3
  261. package/dist/client/settings/SettingsPanel.js +1 -1
  262. package/dist/client/settings/VoiceTranscriptionSection.js +2 -2
  263. package/dist/client/settings/useBuilderStatus.js +1 -1
  264. package/dist/client/setup-connections/useBuilderConnectCardController.js +1 -1
  265. package/dist/client/ui/index.d.ts +1 -0
  266. package/dist/client/ui/index.js +1 -0
  267. package/dist/client/uploads/upload-editor-image.js +1 -1
  268. package/dist/client/use-chat-threads.js +3 -0
  269. package/dist/client/use-session.d.ts +11 -0
  270. package/dist/client/use-session.js +34 -6
  271. package/dist/collab/routes.d.ts +2 -2
  272. package/dist/config.d.ts +48 -0
  273. package/dist/config.js +111 -0
  274. package/dist/db/migrations.js +5 -0
  275. package/dist/eject/provider-api-definitions.d.ts +1 -1
  276. package/dist/eject/provider-api-definitions.js +1 -0
  277. package/dist/eject/remote-mcp-presets.js +1 -0
  278. package/dist/extensions/web-search-tool.js +2 -2
  279. package/dist/file-upload/actions/upload-image.js +1 -1
  280. package/dist/file-upload/registry.js +1 -1
  281. package/dist/guards/index.d.ts +3 -0
  282. package/dist/guards/index.js +2 -0
  283. package/dist/guards/no-empty-migrations.d.ts +14 -0
  284. package/dist/guards/no-empty-migrations.js +126 -0
  285. package/dist/index.browser.d.ts +1 -0
  286. package/dist/index.browser.js +3 -0
  287. package/dist/index.d.ts +1 -0
  288. package/dist/index.js +1 -0
  289. package/dist/localization/default-messages.d.ts +24 -0
  290. package/dist/localization/default-messages.js +26 -2
  291. package/dist/notifications/routes.d.ts +6 -6
  292. package/dist/onboarding/app-profile.js +1 -1
  293. package/dist/onboarding/plugin.d.ts +2 -0
  294. package/dist/onboarding/plugin.js +29 -2
  295. package/dist/progress/routes.d.ts +1 -1
  296. package/dist/provider-api/actions/custom-provider-registration.d.ts +16 -16
  297. package/dist/provider-api/actions/provider-api.d.ts +13 -13
  298. package/dist/provider-api/index.d.ts +4 -4
  299. package/dist/provider-api/index.js +35 -0
  300. package/dist/resources/handlers.d.ts +1 -1
  301. package/dist/resources/handlers.js +1 -1
  302. package/dist/scripts/docs/framework-search.d.ts +9 -0
  303. package/dist/scripts/docs/framework-search.js +239 -0
  304. package/dist/scripts/docs/index.js +1 -0
  305. package/dist/scripts/docs/search.d.ts +10 -0
  306. package/dist/scripts/docs/search.js +1 -1
  307. package/dist/scripts/docs/source-search.d.ts +10 -2
  308. package/dist/scripts/docs/source-search.js +108 -41
  309. package/dist/search-utils/index.d.ts +11 -0
  310. package/dist/search-utils/index.js +72 -0
  311. package/dist/server/action-change.d.ts +6 -0
  312. package/dist/server/action-change.js +28 -14
  313. package/dist/server/action-discovery.js +9 -0
  314. package/dist/server/action-routes.js +1 -0
  315. package/dist/server/agent-chat/browser-team-tools.js +1 -1
  316. package/dist/server/agent-chat/script-entries.js +43 -2
  317. package/dist/server/auth-login-mode.d.ts +8 -0
  318. package/dist/server/auth-login-mode.js +17 -0
  319. package/dist/server/auth.js +100 -15
  320. package/dist/server/better-auth-instance.d.ts +36 -0
  321. package/dist/server/better-auth-instance.js +25 -3
  322. package/dist/server/builder-design-systems.js +1 -1
  323. package/dist/server/core-routes-plugin.js +17 -14
  324. package/dist/server/credential-provider.js +3 -3
  325. package/dist/server/email-templates.d.ts +7 -0
  326. package/dist/server/email-templates.js +24 -0
  327. package/dist/server/email.d.ts +17 -0
  328. package/dist/server/email.js +22 -1
  329. package/dist/server/framework-request-handler.js +6 -0
  330. package/dist/server/onboarding-html.d.ts +2 -0
  331. package/dist/server/onboarding-html.js +169 -8
  332. package/dist/server/realtime-token.d.ts +1 -1
  333. package/dist/server/realtime-voice.js +1 -1
  334. package/dist/server/sentry.d.ts +1 -1
  335. package/dist/server/transcribe-voice.js +3 -3
  336. package/dist/shared/first-run-onboarding.d.ts +1 -0
  337. package/dist/shared/first-run-onboarding.js +1 -0
  338. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +23 -3
  339. package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  340. package/dist/templates/chat/.agents/skills/frontend-design/SKILL.md +23 -2
  341. package/dist/templates/chat/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  342. package/dist/templates/chat/AGENTS.md +3 -0
  343. package/dist/templates/chat/DESIGN.md +28 -0
  344. package/dist/templates/chat/agent-native.json +12 -0
  345. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +23 -3
  346. package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  347. package/dist/templates/default/.agents/skills/frontend-design/SKILL.md +23 -2
  348. package/dist/templates/default/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  349. package/dist/templates/default/AGENTS.md +4 -0
  350. package/dist/templates/default/DESIGN.md +30 -0
  351. package/dist/templates/default/agent-native.json +9 -0
  352. package/dist/templates/default/app/i18n/ar-SA.ts +2 -2
  353. package/dist/templates/default/app/i18n/de-DE.ts +2 -2
  354. package/dist/templates/default/app/i18n/en-US.ts +2 -2
  355. package/dist/templates/default/app/i18n/es-ES.ts +2 -2
  356. package/dist/templates/default/app/i18n/fr-FR.ts +2 -2
  357. package/dist/templates/default/app/i18n/hi-IN.ts +2 -2
  358. package/dist/templates/default/app/i18n/ja-JP.ts +2 -2
  359. package/dist/templates/default/app/i18n/ko-KR.ts +2 -2
  360. package/dist/templates/default/app/i18n/pt-BR.ts +2 -2
  361. package/dist/templates/default/app/i18n/zh-CN.ts +2 -2
  362. package/dist/templates/default/app/i18n/zh-TW.ts +1 -1
  363. package/dist/templates/default/package.json +1 -0
  364. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +23 -3
  365. package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  366. package/dist/templates/headless/agent-native.json +9 -0
  367. package/dist/templates/headless/package.json +2 -0
  368. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +23 -3
  369. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  370. package/dist/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +23 -2
  371. package/dist/templates/workspace-core/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  372. package/dist/templates/workspace-core/AGENTS.md +3 -0
  373. package/dist/templates/workspace-core/DESIGN.md +35 -0
  374. package/dist/templates/workspace-root/AGENTS.md +3 -0
  375. package/dist/templates/workspace-root/DESIGN.md +35 -0
  376. package/dist/templates/workspace-root/agent-native.json +9 -0
  377. package/dist/templates/workspace-root/package.json +3 -0
  378. package/dist/transcription/builder-transcription.js +1 -1
  379. package/dist/user-profile/actions/change-password.d.ts +8 -0
  380. package/dist/user-profile/actions/change-password.js +24 -0
  381. package/dist/user-profile/actions/get-auth-methods.d.ts +6 -0
  382. package/dist/user-profile/actions/get-auth-methods.js +23 -0
  383. package/dist/user-profile/actions/set-password.d.ts +7 -0
  384. package/dist/user-profile/actions/set-password.js +23 -0
  385. package/dist/vite/client.d.ts +8 -1
  386. package/dist/vite/client.js +63 -12
  387. package/dist/vite/index.d.ts +1 -0
  388. package/dist/vite/index.js +1 -0
  389. package/docs/AGENTS.md +7 -2
  390. package/docs/SKILL.md +7 -5
  391. package/docs/content/agent-native-config.mdx +165 -0
  392. package/docs/content/agent-native-toolkit.mdx +1 -1
  393. package/docs/content/deployment.mdx +7 -2
  394. package/docs/content/doctor.mdx +21 -13
  395. package/docs/content/environment-variables.mdx +158 -0
  396. package/docs/content/file-uploads.mdx +1 -1
  397. package/docs/content/getting-started.mdx +1 -1
  398. package/docs/content/key-concepts.mdx +1 -1
  399. package/docs/content/multi-app-workspace.mdx +1 -1
  400. package/docs/content/template-clips-ai-and-editing.mdx +6 -6
  401. package/docs/content/template-clips-developers.mdx +3 -3
  402. package/docs/content/toolkit-settings.mdx +1 -1
  403. package/docs/content/voice-input.mdx +4 -4
  404. package/package.json +6 -2
  405. package/src/eject/provider-api-definitions.ts +1 -0
  406. package/src/eject/remote-mcp-presets.ts +1 -0
  407. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +23 -3
  408. package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  409. package/src/templates/chat/.agents/skills/frontend-design/SKILL.md +23 -2
  410. package/src/templates/chat/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  411. package/src/templates/chat/AGENTS.md +3 -0
  412. package/src/templates/chat/DESIGN.md +28 -0
  413. package/src/templates/chat/agent-native.json +12 -0
  414. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +23 -3
  415. package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  416. package/src/templates/default/.agents/skills/frontend-design/SKILL.md +23 -2
  417. package/src/templates/default/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  418. package/src/templates/default/AGENTS.md +4 -0
  419. package/src/templates/default/DESIGN.md +30 -0
  420. package/src/templates/default/agent-native.json +9 -0
  421. package/src/templates/default/app/i18n/ar-SA.ts +2 -2
  422. package/src/templates/default/app/i18n/de-DE.ts +2 -2
  423. package/src/templates/default/app/i18n/en-US.ts +2 -2
  424. package/src/templates/default/app/i18n/es-ES.ts +2 -2
  425. package/src/templates/default/app/i18n/fr-FR.ts +2 -2
  426. package/src/templates/default/app/i18n/hi-IN.ts +2 -2
  427. package/src/templates/default/app/i18n/ja-JP.ts +2 -2
  428. package/src/templates/default/app/i18n/ko-KR.ts +2 -2
  429. package/src/templates/default/app/i18n/pt-BR.ts +2 -2
  430. package/src/templates/default/app/i18n/zh-CN.ts +2 -2
  431. package/src/templates/default/app/i18n/zh-TW.ts +1 -1
  432. package/src/templates/default/package.json +1 -0
  433. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +23 -3
  434. package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  435. package/src/templates/headless/agent-native.json +9 -0
  436. package/src/templates/headless/package.json +2 -0
  437. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +23 -3
  438. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +16 -0
  439. package/src/templates/workspace-core/.agents/skills/frontend-design/SKILL.md +23 -2
  440. package/src/templates/workspace-core/.agents/skills/frontend-design/references/visual-direction.md +99 -0
  441. package/src/templates/workspace-core/AGENTS.md +3 -0
  442. package/src/templates/workspace-core/DESIGN.md +35 -0
  443. package/src/templates/workspace-root/AGENTS.md +3 -0
  444. package/src/templates/workspace-root/DESIGN.md +35 -0
  445. package/src/templates/workspace-root/agent-native.json +9 -0
  446. package/src/templates/workspace-root/package.json +3 -0
@@ -1,16 +1,38 @@
1
1
  import { defineAction } from "@agent-native/core";
2
2
  import { writeAppState } from "@agent-native/core/application-state";
3
3
  import { assertAccess } from "@agent-native/core/sharing";
4
- import { and, eq, inArray, isNotNull, isNull, ne, or, sql } from "drizzle-orm";
4
+ import { and, eq, inArray, isNotNull, isNull, ne, or } from "drizzle-orm";
5
5
  import { z } from "zod";
6
6
 
7
7
  import { getDb, schema } from "../server/db/index.js";
8
8
  import { chunks } from "./_batch-utils.js";
9
+ import {
10
+ lockContentDatabaseMutation,
11
+ touchContentDatabase,
12
+ } from "./_content-database-mutation-lock.js";
9
13
  import { assertNotWorkspaceCatalogDocuments } from "./_content-space-catalog-guards.js";
14
+ import { lockDatabaseMemberships } from "./_database-membership-lock.js";
10
15
  import { renumberDatabaseRows } from "./_database-row-batch.js";
11
16
 
12
17
  const DELETE_BATCH_SIZE = 90;
13
18
 
19
+ export class PermanentDeleteScopeChangedError extends Error {
20
+ constructor() {
21
+ super("Document deletion scope changed; retry deletion.");
22
+ }
23
+ }
24
+
25
+ type PermanentDeleteScope = {
26
+ documentIds: string[];
27
+ ownedDatabaseIds: string[];
28
+ };
29
+
30
+ function hasSameIds(left: string[], right: string[]) {
31
+ if (left.length !== right.length) return false;
32
+ const rightIds = new Set(right);
33
+ return left.every((id) => rightIds.has(id));
34
+ }
35
+
14
36
  async function selectDocumentChildren(
15
37
  db: ReturnType<typeof getDb>,
16
38
  parentIds: string[],
@@ -97,26 +119,75 @@ async function selectDatabaseItemDocuments(
97
119
  return ownedRows.map((row) => ({ documentId: row.id }));
98
120
  }
99
121
 
100
- async function selectMembershipDatabaseIds(
122
+ async function selectDatabaseItemDocumentTrashState(
101
123
  db: ReturnType<typeof getDb>,
102
- documentIds: string[],
124
+ databaseIds: string[],
103
125
  ownerEmail: string,
104
126
  ) {
105
- const rows: Array<{ databaseId: string }> = [];
106
- for (const batch of chunks(documentIds, DELETE_BATCH_SIZE)) {
127
+ const itemDocuments = await selectDatabaseItemDocuments(
128
+ db,
129
+ databaseIds,
130
+ ownerEmail,
131
+ );
132
+ const rows: Array<{
133
+ id: string;
134
+ trashedAt: string | null;
135
+ }> = [];
136
+ for (const batch of chunks(
137
+ itemDocuments.map((item) => item.documentId),
138
+ DELETE_BATCH_SIZE,
139
+ )) {
107
140
  rows.push(
108
141
  ...(await db
109
- .select({ databaseId: schema.contentDatabaseItems.databaseId })
110
- .from(schema.contentDatabaseItems)
142
+ .select({
143
+ id: schema.documents.id,
144
+ trashedAt: schema.documents.trashedAt,
145
+ })
146
+ .from(schema.documents)
111
147
  .where(
112
148
  and(
113
- inArray(schema.contentDatabaseItems.documentId, batch),
114
- eq(schema.contentDatabaseItems.ownerEmail, ownerEmail),
149
+ inArray(schema.documents.id, batch),
150
+ eq(schema.documents.ownerEmail, ownerEmail),
115
151
  ),
116
152
  )),
117
153
  );
118
154
  }
119
- return [...new Set(rows.map((row) => row.databaseId))];
155
+ return rows;
156
+ }
157
+
158
+ async function selectMembershipsForDocuments(
159
+ db: ReturnType<typeof getDb>,
160
+ documentIds: string[],
161
+ ) {
162
+ const rows: Array<{ id: string; databaseId: string }> = [];
163
+ for (const batch of chunks(documentIds, DELETE_BATCH_SIZE)) {
164
+ rows.push(
165
+ ...(await db
166
+ .select({
167
+ id: schema.contentDatabaseItems.id,
168
+ databaseId: schema.contentDatabaseItems.databaseId,
169
+ })
170
+ .from(schema.contentDatabaseItems)
171
+ .where(inArray(schema.contentDatabaseItems.documentId, batch))),
172
+ );
173
+ }
174
+ return rows;
175
+ }
176
+
177
+ async function selectMembershipIdsForDatabases(
178
+ db: ReturnType<typeof getDb>,
179
+ databaseIds: string[],
180
+ ) {
181
+ const rows: Array<{ id: string }> = [];
182
+ for (const batch of chunks(databaseIds, DELETE_BATCH_SIZE)) {
183
+ rows.push(
184
+ ...(await db
185
+ .select({ id: schema.contentDatabaseItems.id })
186
+ .from(schema.contentDatabaseItems)
187
+ .where(inArray(schema.contentDatabaseItems.databaseId, batch))),
188
+ );
189
+ }
190
+ return rows.map((row) => row.id);
120
191
  }
121
192
 
122
193
  async function collectDocumentSubtreeForDelete(
@@ -172,84 +243,117 @@ async function collectDocumentSubtreeForDelete(
172
243
  frontier = [...next];
173
244
  }
174
245
 
175
- const collectedDocumentIds = [...documentIds];
176
- const collectedOwnedDatabaseIds = [...ownedDatabaseIds];
177
246
  return {
178
- documentIds: collectedDocumentIds,
179
- ownedDatabaseIds: collectedOwnedDatabaseIds,
180
- lockDatabaseIds: [
181
- ...new Set([
182
- ...collectedOwnedDatabaseIds,
183
- ...(await selectMembershipDatabaseIds(
184
- db,
185
- collectedDocumentIds,
186
- ownerEmail,
187
- )),
188
- ]),
189
- ],
247
+ documentIds: [...documentIds],
248
+ ownedDatabaseIds: [...ownedDatabaseIds],
190
249
  };
191
250
  }
192
251
 
193
- async function lockDatabasesAndRecollect<
194
- T extends { lockDatabaseIds: string[] },
195
- >(
252
+ async function lockPermanentDeleteScope(
196
253
  db: ReturnType<typeof getDb>,
197
- ownerEmail: string,
198
- collect: () => Promise<T>,
199
- ): Promise<T> {
200
- const lockedDatabaseIds = new Set<string>();
201
- let collected = await collect();
202
- while (true) {
203
- const unlockedDatabaseIds = collected.lockDatabaseIds.filter(
204
- (databaseId) => !lockedDatabaseIds.has(databaseId),
205
- );
206
- if (unlockedDatabaseIds.length === 0) return collected;
207
- for (const batch of chunks(unlockedDatabaseIds, DELETE_BATCH_SIZE)) {
208
- await db
209
- .update(schema.contentDatabases)
210
- .set({ updatedAt: sql`${schema.contentDatabases.updatedAt}` })
211
- .where(
212
- and(
213
- inArray(schema.contentDatabases.id, batch),
214
- eq(schema.contentDatabases.ownerEmail, ownerEmail),
215
- ),
216
- );
217
- for (const databaseId of batch) lockedDatabaseIds.add(databaseId);
218
- }
219
- collected = await collect();
254
+ collectScope: () => Promise<PermanentDeleteScope>,
255
+ ) {
256
+ const initialScope = await collectScope();
257
+ const initialMemberships = await selectMembershipsForDocuments(
258
+ db,
259
+ initialScope.documentIds,
260
+ );
261
+ const lockedDatabaseIds = [
262
+ ...new Set([
263
+ ...initialScope.ownedDatabaseIds,
264
+ ...initialMemberships.map((membership) => membership.databaseId),
265
+ ]),
266
+ ].sort();
267
+ for (const databaseId of lockedDatabaseIds) {
268
+ await lockContentDatabaseMutation(db, databaseId);
269
+ }
270
+
271
+ const reloadedScope = await collectScope();
272
+ const reloadedMemberships = await selectMembershipsForDocuments(
273
+ db,
274
+ reloadedScope.documentIds,
275
+ );
276
+ const lockedDatabaseIdSet = new Set(lockedDatabaseIds);
277
+ const uncoveredDatabaseId = [
278
+ ...reloadedScope.ownedDatabaseIds,
279
+ ...reloadedMemberships.map((membership) => membership.databaseId),
280
+ ].find((databaseId) => !lockedDatabaseIdSet.has(databaseId));
281
+ if (uncoveredDatabaseId) {
282
+ throw new PermanentDeleteScopeChangedError();
283
+ }
284
+
285
+ const membershipIds = await selectMembershipIdsForDatabases(
286
+ db,
287
+ lockedDatabaseIds,
288
+ );
289
+ await lockDatabaseMemberships(db, membershipIds);
290
+
291
+ const lockedScope = await collectScope();
292
+ const lockedMemberships = await selectMembershipsForDocuments(
293
+ db,
294
+ lockedScope.documentIds,
295
+ );
296
+ const lockedMembershipIds = await selectMembershipIdsForDatabases(
297
+ db,
298
+ lockedDatabaseIds,
299
+ );
300
+ if (
301
+ !hasSameIds(reloadedScope.documentIds, lockedScope.documentIds) ||
302
+ !hasSameIds(reloadedScope.ownedDatabaseIds, lockedScope.ownedDatabaseIds) ||
303
+ !hasSameIds(membershipIds, lockedMembershipIds) ||
304
+ [
305
+ ...lockedScope.ownedDatabaseIds,
306
+ ...lockedMemberships.map((membership) => membership.databaseId),
307
+ ].some((databaseId) => !lockedDatabaseIdSet.has(databaseId))
308
+ ) {
309
+ throw new PermanentDeleteScopeChangedError();
220
310
  }
311
+ return lockedScope;
221
312
  }
222
313
 
223
- export async function trashDocumentSubtree(
314
+ export async function lockDatabasesForTrash(
224
315
  db: ReturnType<typeof getDb>,
225
316
  id: string,
226
317
  ownerEmail: string,
227
- trashedAt = new Date().toISOString(),
228
- ): Promise<string[]> {
229
- return db.transaction((tx) =>
230
- trashDocumentSubtreeInTransaction(
231
- tx as unknown as ReturnType<typeof getDb>,
232
- id,
233
- ownerEmail,
234
- trashedAt,
235
- ),
318
+ ) {
319
+ const subtree = await collectDocumentSubtreeForDelete(db, id, ownerEmail);
320
+ const memberships = await selectMembershipsForDocuments(
321
+ db,
322
+ subtree.documentIds,
236
323
  );
324
+ const databaseIds = [
325
+ ...new Set([
326
+ ...memberships.map((membership) => membership.databaseId),
327
+ ...subtree.ownedDatabaseIds,
328
+ ]),
329
+ ].sort();
330
+ for (const databaseId of databaseIds) {
331
+ await lockContentDatabaseMutation(db, databaseId);
332
+ }
333
+ return new Set(databaseIds);
237
334
  }
238
335
 
239
- async function trashDocumentSubtreeInTransaction(
336
+ export async function trashDocumentSubtree(
240
337
  db: ReturnType<typeof getDb>,
241
338
  id: string,
242
339
  ownerEmail: string,
243
- trashedAt: string,
340
+ trashedAt = new Date().toISOString(),
341
+ lockedDatabaseIds?: ReadonlySet<string>,
244
342
  ): Promise<string[]> {
245
- // Stable-key upserts lock their canonical database row before creating or
246
- // updating children. Acquire the same locks, then collect again: an upsert
247
- // that won the lock before this transaction may have added a child after the
248
- // first traversal, while one that arrives later must wait until the database
249
- // has been marked deleted and will fail closed.
250
- const { documentIds } = await lockDatabasesAndRecollect(db, ownerEmail, () =>
251
- collectDocumentSubtreeForDelete(db, id, ownerEmail),
252
- );
343
+ const { documentIds, ownedDatabaseIds } =
344
+ await collectDocumentSubtreeForDelete(db, id, ownerEmail);
345
+ if (lockedDatabaseIds) {
346
+ const memberships = await selectMembershipsForDocuments(db, documentIds);
347
+ const unlockedDatabaseId = [
348
+ ...new Set([
349
+ ...ownedDatabaseIds,
350
+ ...memberships.map((membership) => membership.databaseId),
351
+ ]),
352
+ ].find((databaseId) => !lockedDatabaseIds.has(databaseId));
353
+ if (unlockedDatabaseId) {
354
+ throw new Error("Document subtree changed; retry deletion.");
355
+ }
356
+ }
253
357
  await assertNotWorkspaceCatalogDocuments(db, documentIds, "deleted");
254
358
 
255
359
  const independentlyTrashedDatabaseDocumentIds = new Set<string>();
@@ -291,6 +395,39 @@ async function trashDocumentSubtreeInTransaction(
291
395
  );
292
396
  }
293
397
 
398
+ const activeMemberships = await selectMembershipsForDocuments(
399
+ db,
400
+ activeDocumentIds,
401
+ );
402
+ const transitioningOwnedDatabases: Array<{ id: string }> = [];
403
+ for (const batch of chunks(activeDocumentIds, DELETE_BATCH_SIZE)) {
404
+ transitioningOwnedDatabases.push(
405
+ ...(await db
406
+ .select({ id: schema.contentDatabases.id })
407
+ .from(schema.contentDatabases)
408
+ .where(
409
+ and(
410
+ inArray(schema.contentDatabases.documentId, batch),
411
+ eq(schema.contentDatabases.ownerEmail, ownerEmail),
412
+ isNull(schema.contentDatabases.deletedAt),
413
+ ),
414
+ )),
415
+ );
416
+ }
417
+ const transitioningOwnedDatabaseIds = new Set(
418
+ transitioningOwnedDatabases.map((database) => database.id),
419
+ );
420
+ const externalDatabaseIds = [
421
+ ...new Set(
422
+ activeMemberships
423
+ .map((membership) => membership.databaseId)
424
+ .filter((databaseId) => !transitioningOwnedDatabaseIds.has(databaseId)),
425
+ ),
426
+ ].sort();
427
+ for (const databaseId of externalDatabaseIds) {
428
+ await touchContentDatabase(db, databaseId, trashedAt);
429
+ }
430
+
294
431
  for (const batch of chunks(activeDocumentIds, DELETE_BATCH_SIZE)) {
295
432
  await db
296
433
  .update(schema.documents)
@@ -322,19 +459,55 @@ export async function restoreDocumentSubtree(
322
459
  rootId: string,
323
460
  ownerEmail: string,
324
461
  ): Promise<string[]> {
325
- const documentIds = (
326
- await db
327
- .select({ id: schema.documents.id })
328
- .from(schema.documents)
329
- .where(
330
- and(
331
- eq(schema.documents.trashRootId, rootId),
332
- eq(schema.documents.ownerEmail, ownerEmail),
333
- ),
334
- )
335
- ).map((document) => document.id);
336
- if (documentIds.length === 0) return [];
462
+ const collectRestoreScope = async () => {
463
+ const documentIds = (
464
+ await db
465
+ .select({ id: schema.documents.id })
466
+ .from(schema.documents)
467
+ .where(
468
+ and(
469
+ eq(schema.documents.trashRootId, rootId),
470
+ eq(schema.documents.ownerEmail, ownerEmail),
471
+ ),
472
+ )
473
+ ).map((document) => document.id);
474
+ const memberships = await selectMembershipsForDocuments(db, documentIds);
475
+ const ownedDatabaseIds = (
476
+ await selectOwnedDatabaseIds(db, documentIds, ownerEmail)
477
+ ).map((database) => database.id);
478
+ return { documentIds, memberships, ownedDatabaseIds };
479
+ };
480
+
481
+ const initialScope = await collectRestoreScope();
482
+ if (initialScope.documentIds.length === 0) return [];
483
+ const lockedDatabaseIds = [
484
+ ...new Set([
485
+ ...initialScope.ownedDatabaseIds,
486
+ ...initialScope.memberships.map((membership) => membership.databaseId),
487
+ ]),
488
+ ].sort();
489
+ for (const databaseId of lockedDatabaseIds) {
490
+ await lockContentDatabaseMutation(db, databaseId);
491
+ }
337
492
 
493
+ const restoreScope = await collectRestoreScope();
494
+ const lockedDatabaseIdSet = new Set(lockedDatabaseIds);
495
+ const unlockedDatabaseId = [
496
+ ...new Set([
497
+ ...restoreScope.ownedDatabaseIds,
498
+ ...restoreScope.memberships.map((membership) => membership.databaseId),
499
+ ]),
500
+ ].find((databaseId) => !lockedDatabaseIdSet.has(databaseId));
501
+ if (unlockedDatabaseId) {
502
+ throw new Error("Document restore scope changed; retry restoration.");
503
+ }
504
+ await lockDatabaseMemberships(
505
+ db,
506
+ await selectMembershipIdsForDatabases(db, lockedDatabaseIds),
507
+ );
508
+
509
+ const documentIds = restoreScope.documentIds;
510
+ if (documentIds.length === 0) return [];
338
511
  const now = new Date().toISOString();
339
512
  for (const batch of chunks(documentIds, DELETE_BATCH_SIZE)) {
340
513
  await db
@@ -399,20 +572,49 @@ export async function deleteDocumentRecursive(
399
572
  id: string,
400
573
  ownerEmail: string,
401
574
  ): Promise<string[]> {
402
- return db.transaction(async (tx) => {
403
- const scopedDb = tx as unknown as ReturnType<typeof getDb>;
404
- const { documentIds, ownedDatabaseIds } = await lockDatabasesAndRecollect(
405
- scopedDb,
406
- ownerEmail,
407
- () => collectDocumentSubtreeForDelete(scopedDb, id, ownerEmail),
408
- );
409
- return deleteCollectedDocuments(
410
- scopedDb,
411
- documentIds,
412
- ownedDatabaseIds,
575
+ return db.transaction((tx) =>
576
+ deleteDocumentRootsRecursive(
577
+ tx as unknown as ReturnType<typeof getDb>,
578
+ [id],
413
579
  ownerEmail,
414
- );
415
- });
580
+ ),
581
+ );
582
+ }
583
+
584
+ export async function deleteDocumentRootsRecursive(
585
+ db: ReturnType<typeof getDb>,
586
+ rootIds: string[],
587
+ ownerEmail: string,
588
+ ): Promise<string[]> {
589
+ const collectScope = async () => {
590
+ const documentIds = new Set<string>();
591
+ const ownedDatabaseIds = new Set<string>();
592
+ for (const rootId of rootIds) {
593
+ const scope = await collectDocumentSubtreeForDelete(
594
+ db,
595
+ rootId,
596
+ ownerEmail,
597
+ );
598
+ for (const documentId of scope.documentIds) documentIds.add(documentId);
599
+ for (const databaseId of scope.ownedDatabaseIds) {
600
+ ownedDatabaseIds.add(databaseId);
601
+ }
602
+ }
603
+ return {
604
+ documentIds: [...documentIds],
605
+ ownedDatabaseIds: [...ownedDatabaseIds],
606
+ };
607
+ };
608
+ const { documentIds, ownedDatabaseIds } = await lockPermanentDeleteScope(
609
+ db,
610
+ collectScope,
611
+ );
612
+ return deleteCollectedDocuments(
613
+ db,
614
+ documentIds,
615
+ ownedDatabaseIds,
616
+ ownerEmail,
617
+ );
416
618
  }
417
619
 
418
620
  async function deleteCollectedDocuments(
@@ -515,6 +717,7 @@ async function deleteCollectedDocuments(
515
717
  ),
516
718
  );
517
719
  });
720
+
518
721
  await deleteWhereIn(documentIds, async (documentIdBatch) => {
519
722
  await db
520
723
  .delete(schema.contentDatabaseItemKeyClaims)
@@ -570,6 +773,17 @@ async function deleteCollectedDocuments(
570
773
  await db
571
774
  .delete(schema.contentDatabases)
572
775
  .where(inArray(schema.contentDatabases.id, databaseIdBatch));
776
+ // Receipts deliberately have no database foreign key. Removing them after
777
+ // the database row closes the race with a migration that already holds the
778
+ // row lock and commits its receipt before this deletion can continue.
779
+ await db
780
+ .delete(schema.contentDatabaseMigrationReceipts)
781
+ .where(
782
+ inArray(
783
+ schema.contentDatabaseMigrationReceipts.databaseId,
784
+ databaseIdBatch,
785
+ ),
786
+ );
573
787
  });
574
788
 
575
789
  await deleteWhereIn(documentIds, async (documentIdBatch) => {
@@ -629,74 +843,63 @@ export async function deleteTrashedDocumentSubtree(
629
843
  id: string,
630
844
  ownerEmail: string,
631
845
  ): Promise<string[]> {
632
- return db.transaction((tx) =>
633
- deleteTrashedDocumentSubtreeInTransaction(
634
- tx as unknown as ReturnType<typeof getDb>,
635
- id,
636
- ownerEmail,
637
- ),
638
- );
639
- }
846
+ const collectScope = async () => {
847
+ const [root] = await db
848
+ .select({ id: schema.documents.id })
849
+ .from(schema.documents)
850
+ .where(
851
+ and(
852
+ eq(schema.documents.id, id),
853
+ eq(schema.documents.ownerEmail, ownerEmail),
854
+ eq(schema.documents.trashRootId, id),
855
+ isNotNull(schema.documents.trashedAt),
856
+ ),
857
+ )
858
+ .limit(1);
859
+ if (!root) {
860
+ throw new Error(
861
+ "Document must be in Trash and be a Trash root before permanent deletion",
862
+ );
863
+ }
640
864
 
641
- async function deleteTrashedDocumentSubtreeInTransaction(
642
- db: ReturnType<typeof getDb>,
643
- id: string,
644
- ownerEmail: string,
645
- ): Promise<string[]> {
646
- const [root] = await db
647
- .select({ id: schema.documents.id })
648
- .from(schema.documents)
649
- .where(
650
- and(
651
- eq(schema.documents.id, id),
652
- eq(schema.documents.ownerEmail, ownerEmail),
653
- eq(schema.documents.trashRootId, id),
654
- isNotNull(schema.documents.trashedAt),
655
- ),
656
- )
657
- .limit(1);
658
- if (!root) {
659
- throw new Error(
660
- "Document must be in Trash and be a Trash root before permanent deletion",
865
+ const documentIds = (
866
+ await db
867
+ .select({ id: schema.documents.id })
868
+ .from(schema.documents)
869
+ .where(
870
+ and(
871
+ eq(schema.documents.ownerEmail, ownerEmail),
872
+ eq(schema.documents.trashRootId, id),
873
+ isNotNull(schema.documents.trashedAt),
874
+ ),
875
+ )
876
+ ).map((document) => document.id);
877
+ const ownedDatabaseIds = await selectOwnedDatabaseIds(
878
+ db,
879
+ documentIds,
880
+ ownerEmail,
881
+ ).then((rows) => rows.map((database) => database.id));
882
+ const documentIdSet = new Set(documentIds);
883
+ const activeOutsideScope = (
884
+ await selectDatabaseItemDocumentTrashState(
885
+ db,
886
+ ownedDatabaseIds,
887
+ ownerEmail,
888
+ )
889
+ ).find(
890
+ (document) => !documentIdSet.has(document.id) && !document.trashedAt,
661
891
  );
662
- }
892
+ if (activeOutsideScope) {
893
+ throw new Error(
894
+ "Database contains an active row outside this Trash item",
895
+ );
896
+ }
897
+ return { documentIds, ownedDatabaseIds };
898
+ };
663
899
 
664
- const { documentIds, ownedDatabaseIds } = await lockDatabasesAndRecollect(
900
+ const { documentIds, ownedDatabaseIds } = await lockPermanentDeleteScope(
665
901
  db,
666
- ownerEmail,
667
- async () => {
668
- const collectedDocumentIds = (
669
- await db
670
- .select({ id: schema.documents.id })
671
- .from(schema.documents)
672
- .where(
673
- and(
674
- eq(schema.documents.ownerEmail, ownerEmail),
675
- eq(schema.documents.trashRootId, id),
676
- isNotNull(schema.documents.trashedAt),
677
- ),
678
- )
679
- ).map((document) => document.id);
680
- const collectedDatabaseIds = await selectOwnedDatabaseIds(
681
- db,
682
- collectedDocumentIds,
683
- ownerEmail,
684
- ).then((rows) => rows.map((database) => database.id));
685
- return {
686
- documentIds: collectedDocumentIds,
687
- ownedDatabaseIds: collectedDatabaseIds,
688
- lockDatabaseIds: [
689
- ...new Set([
690
- ...collectedDatabaseIds,
691
- ...(await selectMembershipDatabaseIds(
692
- db,
693
- collectedDocumentIds,
694
- ownerEmail,
695
- )),
696
- ]),
697
- ],
698
- };
699
- },
902
+ collectScope,
700
903
  );
701
904
 
702
905
  await db
@@ -760,9 +963,19 @@ export default defineAction({
760
963
  if (!membership) {
761
964
  throw new Error("Document is not part of Favorites");
762
965
  }
763
- await db
764
- .delete(schema.contentDatabaseItems)
765
- .where(eq(schema.contentDatabaseItems.id, membership.id));
966
+ await db.transaction(async (tx) => {
967
+ await lockContentDatabaseMutation(
968
+ tx as unknown as ReturnType<typeof getDb>,
969
+ contextDatabase.id,
970
+ );
971
+ await touchContentDatabase(
972
+ tx as unknown as ReturnType<typeof getDb>,
973
+ contextDatabase.id,
974
+ );
975
+ await tx
976
+ .delete(schema.contentDatabaseItems)
977
+ .where(eq(schema.contentDatabaseItems.id, membership.id));
978
+ });
766
979
  await writeAppState("refresh-signal", { ts: Date.now() });
767
980
  return { success: true, deleted: 0, removed: 1 };
768
981
  }
@@ -777,11 +990,21 @@ export default defineAction({
777
990
  if (systemDatabase?.systemRole) {
778
991
  throw new Error("System Content database documents cannot be deleted");
779
992
  }
780
- const deleted = await trashDocumentSubtree(
781
- db,
782
- id,
783
- existing.ownerEmail as string,
784
- );
993
+ const deleted = await db.transaction(async (tx) => {
994
+ const transactionDb = tx as unknown as ReturnType<typeof getDb>;
995
+ const lockedDatabaseIds = await lockDatabasesForTrash(
996
+ transactionDb,
997
+ id,
998
+ existing.ownerEmail as string,
999
+ );
1000
+ return trashDocumentSubtree(
1001
+ transactionDb,
1002
+ id,
1003
+ existing.ownerEmail as string,
1004
+ undefined,
1005
+ lockedDatabaseIds,
1006
+ );
1007
+ });
785
1008
 
786
1009
  await writeAppState("refresh-signal", { ts: Date.now() });
787
1010