@agent-native/core 0.102.1 → 0.103.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 (295) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +15 -0
  3. package/corpus/core/docs/content/local-file-mode.mdx +99 -489
  4. package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
  5. package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
  6. package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
  7. package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
  8. package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
  9. package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
  10. package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
  11. package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
  12. package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
  13. package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
  14. package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
  15. package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
  16. package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
  17. package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
  18. package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
  19. package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
  20. package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
  21. package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
  22. package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
  23. package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
  24. package/corpus/core/docs/content/template-content.mdx +26 -42
  25. package/corpus/core/package.json +1 -1
  26. package/corpus/core/src/cli/content-local.ts +86 -18
  27. package/corpus/core/src/cli/index.ts +2 -2
  28. package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
  29. package/corpus/core/src/cli/skills.ts +47 -13
  30. package/corpus/core/src/client/AgentPanel.tsx +7 -0
  31. package/corpus/core/src/client/AssistantChat.tsx +7 -0
  32. package/corpus/core/src/client/agent-chat-adapter.ts +7 -1
  33. package/corpus/core/src/client/chat/message-components.tsx +17 -3
  34. package/corpus/core/src/client/composer/ComposerPlusMenu.tsx +96 -6
  35. package/corpus/core/src/client/composer/asset-picker-url.ts +80 -0
  36. package/corpus/core/src/client/error-format.ts +24 -0
  37. package/corpus/core/src/client/resources/McpIntegrationDialog.tsx +8 -1
  38. package/corpus/core/src/local-artifacts/index.ts +65 -0
  39. package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
  40. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
  41. package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
  42. package/corpus/templates/assets/app/i18n-data.ts +215 -0
  43. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
  44. package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
  45. package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
  46. package/corpus/templates/assets/app/routes/library.tsx +69 -0
  47. package/corpus/templates/assets/changelog/2026-07-15-fixed-top-level-image-picker-selections-returning-to-the-com.md +6 -0
  48. package/corpus/templates/clips/actions/disconnect-calendar.ts +57 -9
  49. package/corpus/templates/clips/actions/generate-workflow.ts +102 -59
  50. package/corpus/templates/clips/actions/get-recording-player-data.ts +64 -1
  51. package/corpus/templates/clips/actions/regenerate-summary.ts +7 -0
  52. package/corpus/templates/clips/actions/regenerate-title.ts +29 -11
  53. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +34 -13
  54. package/corpus/templates/clips/app/components/editor/thumbnail-picker.tsx +87 -9
  55. package/corpus/templates/clips/app/components/editor/trim-handles.tsx +15 -0
  56. package/corpus/templates/clips/app/components/editor/waveform.tsx +21 -8
  57. package/corpus/templates/clips/app/components/library/folder-tree.tsx +52 -0
  58. package/corpus/templates/clips/app/components/player/settings-panel.tsx +116 -58
  59. package/corpus/templates/clips/app/components/player/share-dialog.tsx +43 -37
  60. package/corpus/templates/clips/app/components/player/video-player.tsx +50 -2
  61. package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
  62. package/corpus/templates/clips/app/i18n/ar-SA.ts +5 -2
  63. package/corpus/templates/clips/app/i18n/de-DE.ts +6 -2
  64. package/corpus/templates/clips/app/i18n/en-US.ts +5 -2
  65. package/corpus/templates/clips/app/i18n/es-ES.ts +6 -2
  66. package/corpus/templates/clips/app/i18n/fr-FR.ts +5 -2
  67. package/corpus/templates/clips/app/i18n/hi-IN.ts +5 -2
  68. package/corpus/templates/clips/app/i18n/ja-JP.ts +6 -1
  69. package/corpus/templates/clips/app/i18n/ko-KR.ts +5 -2
  70. package/corpus/templates/clips/app/i18n/pt-BR.ts +6 -2
  71. package/corpus/templates/clips/app/i18n/zh-CN.ts +5 -2
  72. package/corpus/templates/clips/app/i18n/zh-TW.ts +5 -2
  73. package/corpus/templates/clips/app/lib/timestamp-mapping.ts +14 -0
  74. package/corpus/templates/clips/app/routes/_app.library._index.tsx +1 -2
  75. package/corpus/templates/clips/app/routes/_index.tsx +1 -2
  76. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +98 -32
  77. package/corpus/templates/clips/app/routes/share.$shareId.tsx +4 -1
  78. package/corpus/templates/clips/changelog/2026-07-15-ai-workflow-generation-now-stays-visible-avoids-duplicate-re.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-07-15-clip-playback-and-cta-controls-remain-interactive-through-ed.md +6 -0
  80. package/corpus/templates/clips/changelog/2026-07-15-clip-titles-no-longer-describe-the-product-as-a-loom-alterna.md +6 -0
  81. package/corpus/templates/clips/changelog/2026-07-15-description-regeneration-now-opens-the-agent-chat-so-progres.md +6 -0
  82. package/corpus/templates/clips/changelog/2026-07-15-direct-recording-urls-now-respect-share-link-and-password-ac.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-07-15-disconnecting-a-calendar-now-clears-unrecorded-synced-meetin.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-07-15-folders-now-expose-rename-and-delete-actions-directly-in-the.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-07-15-recording-pages-now-explain-sign-in-access-and-avoid-showing.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-07-15-the-clips-extension-quick-actions-toolbar-can-be-moved-out-o.md +6 -0
  87. package/corpus/templates/clips/changelog/2026-07-15-the-editor-now-skips-cut-sections-during-preview-and-keeps-t.md +6 -0
  88. package/corpus/templates/clips/changelog/2026-07-15-thumbnail-capture-now-updates-previews-while-scrubbing-and-l.md +6 -0
  89. package/corpus/templates/clips/changelog/2026-07-15-windows-permission-links-open-the-matching-settings-page.md +6 -0
  90. package/corpus/templates/clips/chrome-extension/src/content-script.ts +117 -2
  91. package/corpus/templates/clips/chrome-extension/src/overlay.ts +27 -0
  92. package/corpus/templates/clips/desktop/src-tauri/tauri.conf.json +1 -1
  93. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +139 -101
  94. package/corpus/templates/clips/server/lib/recording-page-access.ts +35 -0
  95. package/corpus/templates/clips/server/lib/share-password.ts +6 -3
  96. package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
  97. package/corpus/templates/content/AGENTS.md +25 -30
  98. package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
  99. package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
  100. package/corpus/templates/content/actions/_content-files.ts +338 -0
  101. package/corpus/templates/content/actions/_content-space-access.ts +135 -0
  102. package/corpus/templates/content/actions/_content-spaces.ts +640 -0
  103. package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
  104. package/corpus/templates/content/actions/_database-utils.ts +7 -1
  105. package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
  106. package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
  107. package/corpus/templates/content/actions/_property-utils.ts +9 -4
  108. package/corpus/templates/content/actions/add-database-item.ts +108 -65
  109. package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
  110. package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
  111. package/corpus/templates/content/actions/create-content-database.ts +129 -3
  112. package/corpus/templates/content/actions/create-document.ts +65 -55
  113. package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
  114. package/corpus/templates/content/actions/delete-content-database.ts +3 -0
  115. package/corpus/templates/content/actions/delete-document.ts +7 -11
  116. package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
  117. package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
  118. package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
  119. package/corpus/templates/content/actions/edit-document.ts +2 -28
  120. package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
  121. package/corpus/templates/content/actions/export-content-source.ts +47 -9
  122. package/corpus/templates/content/actions/get-document.ts +0 -14
  123. package/corpus/templates/content/actions/import-content-source.ts +43 -3
  124. package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
  125. package/corpus/templates/content/actions/list-documents.ts +12 -9
  126. package/corpus/templates/content/actions/move-document.ts +3 -14
  127. package/corpus/templates/content/actions/pull-document.ts +0 -23
  128. package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
  129. package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
  130. package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
  131. package/corpus/templates/content/actions/search-documents.ts +0 -34
  132. package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
  133. package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
  134. package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
  135. package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
  136. package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
  137. package/corpus/templates/content/actions/update-document.ts +0 -15
  138. package/corpus/templates/content/actions/view-screen.ts +2 -50
  139. package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
  140. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
  141. package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
  142. package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
  143. package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
  144. package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
  145. package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
  146. package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
  147. package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
  148. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
  149. package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
  150. package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
  151. package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
  152. package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
  153. package/corpus/templates/content/app/i18n-data.ts +75 -0
  154. package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
  155. package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
  156. package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
  157. package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
  158. package/corpus/templates/content/parity/matrix.md +2 -1
  159. package/corpus/templates/content/parity/matrix.ts +37 -1
  160. package/corpus/templates/content/scripts/dev-database.mjs +7 -5
  161. package/corpus/templates/content/server/db/schema.ts +113 -40
  162. package/corpus/templates/content/server/lib/document-context.ts +15 -1
  163. package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
  164. package/corpus/templates/content/server/plugins/db.ts +153 -0
  165. package/corpus/templates/content/shared/api.ts +17 -3
  166. package/corpus/templates/content/shared/builder-mdx.ts +163 -16
  167. package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
  168. package/corpus/templates/design/AGENTS.md +6 -1
  169. package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
  170. package/corpus/templates/design/actions/list-design-templates.ts +69 -21
  171. package/corpus/templates/design/actions/navigate.ts +2 -2
  172. package/corpus/templates/design/actions/view-screen.ts +15 -2
  173. package/corpus/templates/design/agent-native.app-skill.json +5 -0
  174. package/corpus/templates/design/app/components/design/design-canvas/external-preview.ts +7 -3
  175. package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
  176. package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
  177. package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
  178. package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
  179. package/corpus/templates/design/app/i18n-data.ts +21 -2
  180. package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
  181. package/corpus/templates/design/app/pages/Index.tsx +195 -24
  182. package/corpus/templates/design/app/pages/Templates.tsx +144 -55
  183. package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
  184. package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
  185. package/corpus/templates/design/changelog/2026-07-15-prototype-print-and-download-buttons-now-work-in-preview.md +6 -0
  186. package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
  187. package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
  188. package/corpus/templates/mail/AGENTS.md +23 -18
  189. package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
  190. package/corpus/templates/mail/actions/mark-read.ts +131 -20
  191. package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
  192. package/corpus/templates/mail/actions/move-email.ts +30 -24
  193. package/corpus/templates/mail/actions/send-email.ts +73 -68
  194. package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
  195. package/corpus/templates/mail/server/handlers/emails.ts +218 -209
  196. package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
  197. package/corpus/templates/mail/server/lib/email-state.ts +198 -113
  198. package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
  199. package/corpus/templates/mail/server/lib/jobs.ts +69 -74
  200. package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
  201. package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
  202. package/corpus/templates/slides/actions/extract-pdf.ts +1 -1
  203. package/corpus/templates/slides/actions/import-file.ts +4 -1
  204. package/corpus/templates/slides/app/components/editor/ImageGenPanel.tsx +1 -1
  205. package/corpus/templates/slides/app/components/editor/ImageSearchPanel.tsx +9 -12
  206. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +29 -21
  207. package/corpus/templates/slides/app/i18n/ar-SA.ts +1 -0
  208. package/corpus/templates/slides/app/i18n/de-DE.ts +1 -0
  209. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  210. package/corpus/templates/slides/app/i18n/es-ES.ts +1 -0
  211. package/corpus/templates/slides/app/i18n/fr-FR.ts +1 -0
  212. package/corpus/templates/slides/app/i18n/hi-IN.ts +1 -0
  213. package/corpus/templates/slides/app/i18n/ja-JP.ts +1 -0
  214. package/corpus/templates/slides/app/i18n/ko-KR.ts +1 -0
  215. package/corpus/templates/slides/app/i18n/pt-BR.ts +1 -0
  216. package/corpus/templates/slides/app/i18n/zh-CN.ts +1 -0
  217. package/corpus/templates/slides/app/i18n/zh-TW.ts +1 -0
  218. package/corpus/templates/slides/changelog/2026-07-15-image-search-and-generation-controls-now-use-the-registered-.md +6 -0
  219. package/corpus/templates/slides/changelog/2026-07-15-pdf-deck-references-can-be-imported-for-slide-inspiration-ag.md +6 -0
  220. package/corpus/templates/slides/changelog/2026-07-15-stable-style-inspector-dock.md +6 -0
  221. package/dist/cli/content-local.d.ts +1 -0
  222. package/dist/cli/content-local.d.ts.map +1 -1
  223. package/dist/cli/content-local.js +75 -17
  224. package/dist/cli/content-local.js.map +1 -1
  225. package/dist/cli/index.js +2 -2
  226. package/dist/cli/index.js.map +1 -1
  227. package/dist/cli/skills-content/content-skill.d.ts +1 -1
  228. package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
  229. package/dist/cli/skills-content/content-skill.js +36 -25
  230. package/dist/cli/skills-content/content-skill.js.map +1 -1
  231. package/dist/cli/skills.d.ts.map +1 -1
  232. package/dist/cli/skills.js +47 -14
  233. package/dist/cli/skills.js.map +1 -1
  234. package/dist/client/AgentPanel.d.ts.map +1 -1
  235. package/dist/client/AgentPanel.js +5 -0
  236. package/dist/client/AgentPanel.js.map +1 -1
  237. package/dist/client/AssistantChat.d.ts.map +1 -1
  238. package/dist/client/AssistantChat.js +7 -0
  239. package/dist/client/AssistantChat.js.map +1 -1
  240. package/dist/client/agent-chat-adapter.d.ts.map +1 -1
  241. package/dist/client/agent-chat-adapter.js +8 -1
  242. package/dist/client/agent-chat-adapter.js.map +1 -1
  243. package/dist/client/chat/message-components.d.ts +4 -0
  244. package/dist/client/chat/message-components.d.ts.map +1 -1
  245. package/dist/client/chat/message-components.js +10 -3
  246. package/dist/client/chat/message-components.js.map +1 -1
  247. package/dist/client/composer/ComposerPlusMenu.d.ts.map +1 -1
  248. package/dist/client/composer/ComposerPlusMenu.js +58 -6
  249. package/dist/client/composer/ComposerPlusMenu.js.map +1 -1
  250. package/dist/client/composer/asset-picker-url.d.ts +16 -0
  251. package/dist/client/composer/asset-picker-url.d.ts.map +1 -0
  252. package/dist/client/composer/asset-picker-url.js +64 -0
  253. package/dist/client/composer/asset-picker-url.js.map +1 -0
  254. package/dist/client/error-format.d.ts.map +1 -1
  255. package/dist/client/error-format.js +17 -0
  256. package/dist/client/error-format.js.map +1 -1
  257. package/dist/client/resources/McpIntegrationDialog.d.ts.map +1 -1
  258. package/dist/client/resources/McpIntegrationDialog.js +6 -1
  259. package/dist/client/resources/McpIntegrationDialog.js.map +1 -1
  260. package/dist/collab/awareness.d.ts +2 -2
  261. package/dist/collab/awareness.d.ts.map +1 -1
  262. package/dist/collab/struct-routes.d.ts +1 -1
  263. package/dist/local-artifacts/index.d.ts +10 -0
  264. package/dist/local-artifacts/index.d.ts.map +1 -1
  265. package/dist/local-artifacts/index.js +39 -0
  266. package/dist/local-artifacts/index.js.map +1 -1
  267. package/dist/notifications/routes.d.ts +1 -1
  268. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  269. package/dist/resources/handlers.d.ts +3 -3
  270. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  271. package/dist/server/transcribe-voice.d.ts +1 -1
  272. package/docs/content/local-file-mode.mdx +99 -489
  273. package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
  274. package/docs/content/locales/ar-SA/template-content.mdx +10 -24
  275. package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
  276. package/docs/content/locales/de-DE/template-content.mdx +10 -24
  277. package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
  278. package/docs/content/locales/es-ES/template-content.mdx +10 -24
  279. package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
  280. package/docs/content/locales/fr-FR/template-content.mdx +10 -24
  281. package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
  282. package/docs/content/locales/hi-IN/template-content.mdx +10 -24
  283. package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
  284. package/docs/content/locales/ja-JP/template-content.mdx +10 -24
  285. package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
  286. package/docs/content/locales/ko-KR/template-content.mdx +10 -24
  287. package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
  288. package/docs/content/locales/pt-BR/template-content.mdx +10 -24
  289. package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
  290. package/docs/content/locales/zh-CN/template-content.mdx +9 -24
  291. package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
  292. package/docs/content/locales/zh-TW/template-content.mdx +9 -24
  293. package/docs/content/template-content.mdx +26 -42
  294. package/package.json +1 -1
  295. package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
@@ -63,6 +63,11 @@ import {
63
63
  } from "../lib/google-auth.js";
64
64
  import { getSyntheticEmailsForView, getSnoozedThreadIds } from "../lib/jobs.js";
65
65
  import { listInboxEmails } from "../lib/list-inbox-emails.js";
66
+ import {
67
+ readLocalEmails as readEmails,
68
+ withLocalEmailMutationLock,
69
+ writeLocalEmails as writeEmails,
70
+ } from "../lib/local-email-store.js";
66
71
  import {
67
72
  bodyToHtml as outgoingBodyToHtml,
68
73
  buildRawEmail as buildOutgoingRawEmail,
@@ -294,26 +299,10 @@ const DEFAULT_SETTINGS: UserSettings = {
294
299
 
295
300
  // ─── Helpers ─────────────────────────────────────────────────────────────────
296
301
 
297
- async function readEmails(email: string): Promise<EmailMessage[]> {
298
- const data = await getUserSetting(email, "local-emails");
299
- if (data && Array.isArray((data as any).emails)) {
300
- return (data as any).emails;
301
- }
302
- return [];
303
- }
304
-
305
302
  function reqSource(event: H3Event) {
306
303
  return getHeader(event, "x-request-source") || undefined;
307
304
  }
308
305
 
309
- async function writeEmails(
310
- email: string,
311
- emails: EmailMessage[],
312
- options?: { requestSource?: string },
313
- ): Promise<void> {
314
- await putUserSetting(email, "local-emails", { emails }, options);
315
- }
316
-
317
306
  async function readGmailComposeAttachment(
318
307
  ownerEmail: string,
319
308
  requestAccountEmail: string | undefined,
@@ -779,27 +768,32 @@ export const reportSpam = defineEventHandler(async (event: H3Event) => {
779
768
  }
780
769
 
781
770
  // Local fallback: move to trash with a spam label
782
- const emails = await readEmails(email);
783
- const target = emails.find((e) => e.id === getRouterParam(event, "id"));
784
- if (!target) {
785
- setResponseStatus(event, 404);
786
- return { error: "Email not found" };
787
- }
788
- const threadId = target.threadId || target.id;
789
- for (let i = 0; i < emails.length; i++) {
790
- const eid = emails[i].threadId || emails[i].id;
791
- if (eid === threadId) {
792
- emails[i] = {
793
- ...emails[i],
794
- isTrashed: true,
795
- labelIds: [...emails[i].labelIds.filter((l) => l !== "inbox"), "spam"],
796
- };
771
+ return withLocalEmailMutationLock(email, async () => {
772
+ const emails = await readEmails(email);
773
+ const target = emails.find((e) => e.id === getRouterParam(event, "id"));
774
+ if (!target) {
775
+ setResponseStatus(event, 404);
776
+ return { error: "Email not found" };
797
777
  }
798
- }
799
- await writeEmails(email, emails, { requestSource: reqSource(event) });
800
- const labels = recomputeUnreadCounts(emails, await readLabels(email));
801
- await writeLabels(email, labels, { requestSource: reqSource(event) });
802
- return { id: getRouterParam(event, "id"), threadId, spam: true };
778
+ const threadId = target.threadId || target.id;
779
+ for (let i = 0; i < emails.length; i++) {
780
+ const eid = emails[i].threadId || emails[i].id;
781
+ if (eid === threadId) {
782
+ emails[i] = {
783
+ ...emails[i],
784
+ isTrashed: true,
785
+ labelIds: [
786
+ ...emails[i].labelIds.filter((l) => l !== "inbox"),
787
+ "spam",
788
+ ],
789
+ };
790
+ }
791
+ }
792
+ await writeEmails(email, emails, { requestSource: reqSource(event) });
793
+ const labels = recomputeUnreadCounts(emails, await readLabels(email));
794
+ await writeLabels(email, labels, { requestSource: reqSource(event) });
795
+ return { id: getRouterParam(event, "id"), threadId, spam: true };
796
+ });
803
797
  });
804
798
 
805
799
  // ─── Block sender ─────────────────────────────────────────────────────────────
@@ -888,27 +882,32 @@ export const blockSender = defineEventHandler(async (event: H3Event) => {
888
882
  });
889
883
  }
890
884
 
891
- const emails = await readEmails(email);
892
- const target = emails.find((e) => e.id === getRouterParam(event, "id"));
893
- if (!target) {
894
- setResponseStatus(event, 404);
895
- return { error: "Email not found" };
896
- }
897
- const threadId = target.threadId || target.id;
898
- for (let i = 0; i < emails.length; i++) {
899
- const eid = emails[i].threadId || emails[i].id;
900
- if (eid === threadId) {
901
- emails[i] = {
902
- ...emails[i],
903
- isTrashed: true,
904
- labelIds: [...emails[i].labelIds.filter((l) => l !== "inbox"), "spam"],
905
- };
885
+ return withLocalEmailMutationLock(email, async () => {
886
+ const emails = await readEmails(email);
887
+ const target = emails.find((e) => e.id === getRouterParam(event, "id"));
888
+ if (!target) {
889
+ setResponseStatus(event, 404);
890
+ return { error: "Email not found" };
906
891
  }
907
- }
908
- await writeEmails(email, emails, { requestSource: reqSource(event) });
909
- const labels = recomputeUnreadCounts(emails, await readLabels(email));
910
- await writeLabels(email, labels, { requestSource: reqSource(event) });
911
- return { id: getRouterParam(event, "id"), threadId, blocked: senderEmail };
892
+ const threadId = target.threadId || target.id;
893
+ for (let i = 0; i < emails.length; i++) {
894
+ const eid = emails[i].threadId || emails[i].id;
895
+ if (eid === threadId) {
896
+ emails[i] = {
897
+ ...emails[i],
898
+ isTrashed: true,
899
+ labelIds: [
900
+ ...emails[i].labelIds.filter((l) => l !== "inbox"),
901
+ "spam",
902
+ ],
903
+ };
904
+ }
905
+ }
906
+ await writeEmails(email, emails, { requestSource: reqSource(event) });
907
+ const labels = recomputeUnreadCounts(emails, await readLabels(email));
908
+ await writeLabels(email, labels, { requestSource: reqSource(event) });
909
+ return { id: getRouterParam(event, "id"), threadId, blocked: senderEmail };
910
+ });
912
911
  });
913
912
 
914
913
  // ─── Mute thread ──────────────────────────────────────────────────────────────
@@ -964,35 +963,39 @@ export const muteThread = defineEventHandler(async (event: H3Event) => {
964
963
  await writeMutedThreads(email, muted, { requestSource: reqSource(event) });
965
964
  }
966
965
 
967
- const emails = await readEmails(email);
968
- for (let i = 0; i < emails.length; i++) {
969
- const eid = emails[i].threadId || emails[i].id;
970
- if (eid === threadId) {
971
- emails[i] = {
972
- ...emails[i],
973
- isArchived: true,
974
- labelIds: emails[i].labelIds.filter((l) => l !== "inbox"),
975
- };
966
+ return withLocalEmailMutationLock(email, async () => {
967
+ const emails = await readEmails(email);
968
+ for (let i = 0; i < emails.length; i++) {
969
+ const eid = emails[i].threadId || emails[i].id;
970
+ if (eid === threadId) {
971
+ emails[i] = {
972
+ ...emails[i],
973
+ isArchived: true,
974
+ labelIds: emails[i].labelIds.filter((l) => l !== "inbox"),
975
+ };
976
+ }
976
977
  }
977
- }
978
- await writeEmails(email, emails, { requestSource: reqSource(event) });
979
- const labels = recomputeUnreadCounts(emails, await readLabels(email));
980
- await writeLabels(email, labels, { requestSource: reqSource(event) });
981
- return { threadId, muted: true };
978
+ await writeEmails(email, emails, { requestSource: reqSource(event) });
979
+ const labels = recomputeUnreadCounts(emails, await readLabels(email));
980
+ await writeLabels(email, labels, { requestSource: reqSource(event) });
981
+ return { threadId, muted: true };
982
+ });
982
983
  });
983
984
 
984
985
  // ─── Delete permanently ───────────────────────────────────────────────────────
985
986
 
986
987
  export const deleteEmail = defineEventHandler(async (event: H3Event) => {
987
988
  const email = await userEmail(event);
988
- const emails = await readEmails(email);
989
- const filtered = emails.filter((e) => e.id !== getRouterParam(event, "id"));
990
- if (filtered.length === emails.length) {
991
- setResponseStatus(event, 404);
992
- return { error: "Email not found" };
993
- }
994
- await writeEmails(email, filtered, { requestSource: reqSource(event) });
995
- return { ok: true };
989
+ return withLocalEmailMutationLock(email, async () => {
990
+ const emails = await readEmails(email);
991
+ const filtered = emails.filter((e) => e.id !== getRouterParam(event, "id"));
992
+ if (filtered.length === emails.length) {
993
+ setResponseStatus(event, 404);
994
+ return { error: "Email not found" };
995
+ }
996
+ await writeEmails(email, filtered, { requestSource: reqSource(event) });
997
+ return { ok: true };
998
+ });
996
999
  });
997
1000
 
998
1001
  // ─── Send / compose ───────────────────────────────────────────────────────────
@@ -1197,62 +1200,64 @@ export const sendEmail = defineEventHandler(async (event: H3Event) => {
1197
1200
  }
1198
1201
 
1199
1202
  // Local fallback: store as sent email
1200
- const emails = await readEmails(email);
1201
-
1202
- const newEmail: EmailMessage = {
1203
- id: `msg-${nanoid(8)}`,
1204
- threadId: replyToId
1205
- ? (emails.find((e) => e.id === replyToId)?.threadId ??
1206
- `thread-${nanoid(8)}`)
1207
- : `thread-${nanoid(8)}`,
1208
- from: { name: settings.name, email: settings.email },
1209
- to: (to as string).split(",").map((t: string) => {
1210
- const trimmed = t.trim();
1211
- return { name: trimmed, email: trimmed };
1212
- }),
1213
- ...(cc
1214
- ? {
1215
- cc: (cc as string)
1216
- .split(",")
1217
- .map((t: string) => ({ name: t.trim(), email: t.trim() })),
1218
- }
1219
- : {}),
1220
- ...(bcc
1221
- ? {
1222
- bcc: (bcc as string)
1223
- .split(",")
1224
- .map((t: string) => ({ name: t.trim(), email: t.trim() })),
1225
- }
1226
- : {}),
1227
- subject,
1228
- snippet: markdownPreviewSnippet(body),
1229
- body,
1230
- bodyHtml: outgoingBodyToHtml(body),
1231
- date: new Date().toISOString(),
1232
- isRead: true,
1233
- isStarred: false,
1234
- isSent: true,
1235
- isArchived: false,
1236
- isTrashed: false,
1237
- labelIds: ["sent"],
1238
- ...(attachments.length > 0
1239
- ? {
1240
- attachments: attachments.map((att) => ({
1241
- id: att.filename,
1242
- filename: att.originalName,
1243
- mimeType: att.mimeType,
1244
- size: att.size,
1245
- url: att.url,
1246
- })),
1247
- }
1248
- : {}),
1249
- };
1203
+ return withLocalEmailMutationLock(email, async () => {
1204
+ const emails = await readEmails(email);
1205
+
1206
+ const newEmail: EmailMessage = {
1207
+ id: `msg-${nanoid(8)}`,
1208
+ threadId: replyToId
1209
+ ? (emails.find((e) => e.id === replyToId)?.threadId ??
1210
+ `thread-${nanoid(8)}`)
1211
+ : `thread-${nanoid(8)}`,
1212
+ from: { name: settings.name, email: settings.email },
1213
+ to: (to as string).split(",").map((t: string) => {
1214
+ const trimmed = t.trim();
1215
+ return { name: trimmed, email: trimmed };
1216
+ }),
1217
+ ...(cc
1218
+ ? {
1219
+ cc: (cc as string)
1220
+ .split(",")
1221
+ .map((t: string) => ({ name: t.trim(), email: t.trim() })),
1222
+ }
1223
+ : {}),
1224
+ ...(bcc
1225
+ ? {
1226
+ bcc: (bcc as string)
1227
+ .split(",")
1228
+ .map((t: string) => ({ name: t.trim(), email: t.trim() })),
1229
+ }
1230
+ : {}),
1231
+ subject,
1232
+ snippet: markdownPreviewSnippet(body),
1233
+ body,
1234
+ bodyHtml: outgoingBodyToHtml(body),
1235
+ date: new Date().toISOString(),
1236
+ isRead: true,
1237
+ isStarred: false,
1238
+ isSent: true,
1239
+ isArchived: false,
1240
+ isTrashed: false,
1241
+ labelIds: ["sent"],
1242
+ ...(attachments.length > 0
1243
+ ? {
1244
+ attachments: attachments.map((att) => ({
1245
+ id: att.filename,
1246
+ filename: att.originalName,
1247
+ mimeType: att.mimeType,
1248
+ size: att.size,
1249
+ url: att.url,
1250
+ })),
1251
+ }
1252
+ : {}),
1253
+ };
1250
1254
 
1251
- emails.push(newEmail);
1252
- await writeEmails(email, emails, { requestSource: reqSource(event) });
1255
+ emails.push(newEmail);
1256
+ await writeEmails(email, emails, { requestSource: reqSource(event) });
1253
1257
 
1254
- setResponseStatus(event, 201);
1255
- return newEmail;
1258
+ setResponseStatus(event, 201);
1259
+ return newEmail;
1260
+ });
1256
1261
  });
1257
1262
 
1258
1263
  // ─── Save draft (persistent, Gmail-style) ─────────────────────────────────────
@@ -1353,80 +1358,82 @@ export const saveDraft = defineEventHandler(async (event: H3Event) => {
1353
1358
  }
1354
1359
 
1355
1360
  // Local fallback: save as EmailMessage with isDraft=true
1356
- const emails = await readEmails(email);
1357
- const existingIdx = draftId
1358
- ? emails.findIndex((e) => e.id === draftId && e.isDraft)
1359
- : -1;
1360
-
1361
- const draftEmail: EmailMessage = {
1362
- id: existingIdx >= 0 ? emails[existingIdx].id : `draft-${nanoid(8)}`,
1363
- threadId:
1364
- existingIdx >= 0
1365
- ? emails[existingIdx].threadId
1366
- : replyToId
1367
- ? (emails.find((e) => e.id === replyToId)?.threadId ??
1368
- `thread-${nanoid(8)}`)
1369
- : `thread-${nanoid(8)}`,
1370
- from: { name: settings.name, email: settings.email },
1371
- to: to
1372
- ? (to as string)
1373
- .split(",")
1374
- .filter((t: string) => t.trim())
1375
- .map((t: string) => ({ name: t.trim(), email: t.trim() }))
1376
- : [],
1377
- ...(cc
1378
- ? {
1379
- cc: (cc as string)
1380
- .split(",")
1381
- .filter((t: string) => t.trim())
1382
- .map((t: string) => ({ name: t.trim(), email: t.trim() })),
1383
- }
1384
- : {}),
1385
- ...(bcc
1386
- ? {
1387
- bcc: (bcc as string)
1361
+ return withLocalEmailMutationLock(email, async () => {
1362
+ const emails = await readEmails(email);
1363
+ const existingIdx = draftId
1364
+ ? emails.findIndex((e) => e.id === draftId && e.isDraft)
1365
+ : -1;
1366
+
1367
+ const draftEmail: EmailMessage = {
1368
+ id: existingIdx >= 0 ? emails[existingIdx].id : `draft-${nanoid(8)}`,
1369
+ threadId:
1370
+ existingIdx >= 0
1371
+ ? emails[existingIdx].threadId
1372
+ : replyToId
1373
+ ? (emails.find((e) => e.id === replyToId)?.threadId ??
1374
+ `thread-${nanoid(8)}`)
1375
+ : `thread-${nanoid(8)}`,
1376
+ from: { name: settings.name, email: settings.email },
1377
+ to: to
1378
+ ? (to as string)
1388
1379
  .split(",")
1389
1380
  .filter((t: string) => t.trim())
1390
- .map((t: string) => ({ name: t.trim(), email: t.trim() })),
1391
- }
1392
- : {}),
1393
- subject: subject || "(no subject)",
1394
- snippet: markdownPreviewSnippet(body || ""),
1395
- body: body || "",
1396
- bodyHtml: outgoingBodyToHtml(body || ""),
1397
- date: new Date().toISOString(),
1398
- isRead: true,
1399
- isStarred: false,
1400
- isDraft: true,
1401
- isArchived: false,
1402
- isTrashed: false,
1403
- labelIds: ["drafts"],
1404
- ...(attachments.length > 0
1405
- ? {
1406
- attachments: attachments.map((att) => ({
1407
- id: att.filename,
1408
- filename: att.originalName,
1409
- mimeType: att.mimeType,
1410
- size: att.size,
1411
- url: att.url,
1412
- })),
1413
- }
1414
- : {}),
1415
- ...(replyToId ? { replyToId } : {}),
1416
- ...(replyToThreadId ? { replyToThreadId } : {}),
1417
- };
1418
-
1419
- if (existingIdx >= 0) {
1420
- emails[existingIdx] = draftEmail;
1421
- } else {
1422
- emails.push(draftEmail);
1423
- }
1424
- await writeEmails(email, emails, { requestSource: reqSource(event) });
1381
+ .map((t: string) => ({ name: t.trim(), email: t.trim() }))
1382
+ : [],
1383
+ ...(cc
1384
+ ? {
1385
+ cc: (cc as string)
1386
+ .split(",")
1387
+ .filter((t: string) => t.trim())
1388
+ .map((t: string) => ({ name: t.trim(), email: t.trim() })),
1389
+ }
1390
+ : {}),
1391
+ ...(bcc
1392
+ ? {
1393
+ bcc: (bcc as string)
1394
+ .split(",")
1395
+ .filter((t: string) => t.trim())
1396
+ .map((t: string) => ({ name: t.trim(), email: t.trim() })),
1397
+ }
1398
+ : {}),
1399
+ subject: subject || "(no subject)",
1400
+ snippet: markdownPreviewSnippet(body || ""),
1401
+ body: body || "",
1402
+ bodyHtml: outgoingBodyToHtml(body || ""),
1403
+ date: new Date().toISOString(),
1404
+ isRead: true,
1405
+ isStarred: false,
1406
+ isDraft: true,
1407
+ isArchived: false,
1408
+ isTrashed: false,
1409
+ labelIds: ["drafts"],
1410
+ ...(attachments.length > 0
1411
+ ? {
1412
+ attachments: attachments.map((att) => ({
1413
+ id: att.filename,
1414
+ filename: att.originalName,
1415
+ mimeType: att.mimeType,
1416
+ size: att.size,
1417
+ url: att.url,
1418
+ })),
1419
+ }
1420
+ : {}),
1421
+ ...(replyToId ? { replyToId } : {}),
1422
+ ...(replyToThreadId ? { replyToThreadId } : {}),
1423
+ };
1424
+
1425
+ if (existingIdx >= 0) {
1426
+ emails[existingIdx] = draftEmail;
1427
+ } else {
1428
+ emails.push(draftEmail);
1429
+ }
1430
+ await writeEmails(email, emails, { requestSource: reqSource(event) });
1425
1431
 
1426
- return {
1427
- draftId: draftEmail.id,
1428
- [existingIdx >= 0 ? "updated" : "created"]: true,
1429
- };
1432
+ return {
1433
+ draftId: draftEmail.id,
1434
+ [existingIdx >= 0 ? "updated" : "created"]: true,
1435
+ };
1436
+ });
1430
1437
  });
1431
1438
 
1432
1439
  /**
@@ -1488,12 +1495,14 @@ export const deleteDraft = defineEventHandler(async (event: H3Event) => {
1488
1495
  }
1489
1496
 
1490
1497
  // Local fallback
1491
- const emails = await readEmails(email);
1492
- const filtered = emails.filter((e) => !(e.id === id && e.isDraft));
1493
- if (filtered.length !== emails.length) {
1494
- await writeEmails(email, filtered, { requestSource: reqSource(event) });
1495
- }
1496
- return { ok: true };
1498
+ return withLocalEmailMutationLock(email, async () => {
1499
+ const emails = await readEmails(email);
1500
+ const filtered = emails.filter((e) => !(e.id === id && e.isDraft));
1501
+ if (filtered.length !== emails.length) {
1502
+ await writeEmails(email, filtered, { requestSource: reqSource(event) });
1503
+ }
1504
+ return { ok: true };
1505
+ });
1497
1506
  });
1498
1507
 
1499
1508
  // ─── Contacts (extracted from email history) ─────────────────────────────────
@@ -0,0 +1,30 @@
1
+ export interface BulkMarkReadFailure {
2
+ id: string;
3
+ error: string;
4
+ }
5
+
6
+ export interface BulkMarkReadResult {
7
+ mode: "all-unread";
8
+ accountEmail: string;
9
+ matchedMessages: number;
10
+ matchedThreads: number;
11
+ excludedMessages: number;
12
+ excludedThreads: number;
13
+ changedMessages: number;
14
+ batchCount: number;
15
+ failures: BulkMarkReadFailure[];
16
+ remainingUnreadMessages: number | null;
17
+ remainingUnreadThreads: number | null;
18
+ remainingProtectedMessages: number | null;
19
+ remainingProtectedThreads: number | null;
20
+ /** Initially selected messages that were still unread during verification. */
21
+ unexpectedUnreadMessages: number | null;
22
+ /** Threads containing initially selected messages still unread at verification. */
23
+ unexpectedUnreadThreads: number | null;
24
+ /** Messages not present in the initial unread snapshot. */
25
+ newUnreadMessages?: number | null;
26
+ /** Threads containing messages not present in the initial unread snapshot. */
27
+ newUnreadThreads?: number | null;
28
+ verificationComplete: boolean;
29
+ verificationError?: string;
30
+ }