@agent-native/core 0.102.2 → 0.103.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/docs/content/local-file-mode.mdx +99 -489
- package/corpus/core/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/corpus/core/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/corpus/core/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/corpus/core/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/corpus/core/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/corpus/core/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/corpus/core/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/corpus/core/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/corpus/core/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/corpus/core/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/corpus/core/docs/content/template-content.mdx +26 -42
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/cli/content-local.ts +86 -18
- package/corpus/core/src/cli/index.ts +2 -2
- package/corpus/core/src/cli/skills-content/content-skill.ts +36 -25
- package/corpus/core/src/cli/skills.ts +47 -13
- package/corpus/core/src/local-artifacts/index.ts +65 -0
- package/corpus/core/src/vite/client.ts +2 -0
- package/corpus/templates/assets/app/components/library/GenerationPresetsPanel.tsx +325 -0
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +12 -0
- package/corpus/templates/assets/app/i18n/zh-TW.ts +21 -0
- package/corpus/templates/assets/app/i18n-data.ts +215 -0
- package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +19 -479
- package/corpus/templates/assets/app/routes/brand-kits.$id_.presets.$presetId.tsx +1 -1
- package/corpus/templates/assets/app/routes/brand-kits.$id_.settings.tsx +446 -0
- package/corpus/templates/clips/app/components/editor/editor-layout.tsx +12 -2
- package/corpus/templates/clips/app/components/player/settings-panel.tsx +30 -12
- package/corpus/templates/clips/app/components/player/video-player.tsx +5 -1
- package/corpus/templates/clips/app/components/recorder/microphone-visualizer.tsx +22 -3
- package/corpus/templates/clips/app/i18n/ar-SA.ts +3 -2
- package/corpus/templates/clips/app/i18n/de-DE.ts +4 -2
- package/corpus/templates/clips/app/i18n/en-US.ts +3 -2
- package/corpus/templates/clips/app/i18n/es-ES.ts +4 -2
- package/corpus/templates/clips/app/i18n/fr-FR.ts +3 -2
- package/corpus/templates/clips/app/i18n/hi-IN.ts +3 -2
- package/corpus/templates/clips/app/i18n/ja-JP.ts +4 -1
- package/corpus/templates/clips/app/i18n/ko-KR.ts +3 -2
- package/corpus/templates/clips/app/i18n/pt-BR.ts +4 -2
- package/corpus/templates/clips/app/i18n/zh-CN.ts +3 -2
- package/corpus/templates/clips/app/i18n/zh-TW.ts +3 -2
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +27 -1
- package/corpus/templates/clips/server/lib/share-password.ts +6 -3
- package/corpus/templates/content/.agents/skills/content/SKILL.md +26 -39
- package/corpus/templates/content/AGENTS.md +25 -30
- package/corpus/templates/content/actions/_builder-docs-client.ts +23 -0
- package/corpus/templates/content/actions/_content-database-source-adapters.ts +21 -3
- package/corpus/templates/content/actions/_content-files.ts +338 -0
- package/corpus/templates/content/actions/_content-space-access.ts +135 -0
- package/corpus/templates/content/actions/_content-spaces.ts +640 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +90 -7
- package/corpus/templates/content/actions/_database-utils.ts +7 -1
- package/corpus/templates/content/actions/_local-file-documents.ts +0 -9
- package/corpus/templates/content/actions/_local-folder-source.ts +53 -0
- package/corpus/templates/content/actions/_property-utils.ts +9 -4
- package/corpus/templates/content/actions/add-database-item.ts +108 -65
- package/corpus/templates/content/actions/backfill-content-files.ts +22 -0
- package/corpus/templates/content/actions/connect-local-folder-source.ts +221 -0
- package/corpus/templates/content/actions/create-content-database.ts +129 -3
- package/corpus/templates/content/actions/create-document.ts +65 -55
- package/corpus/templates/content/actions/create-inline-content-database.ts +6 -1
- package/corpus/templates/content/actions/delete-content-database.ts +3 -0
- package/corpus/templates/content/actions/delete-document.ts +7 -11
- package/corpus/templates/content/actions/disconnect-local-folder-source.ts +175 -0
- package/corpus/templates/content/actions/duplicate-database-item.ts +10 -0
- package/corpus/templates/content/actions/duplicate-database-items.ts +13 -0
- package/corpus/templates/content/actions/edit-document.ts +2 -28
- package/corpus/templates/content/actions/ensure-content-spaces.ts +26 -0
- package/corpus/templates/content/actions/export-content-source.ts +47 -9
- package/corpus/templates/content/actions/get-document.ts +0 -14
- package/corpus/templates/content/actions/import-content-source.ts +43 -3
- package/corpus/templates/content/actions/list-content-spaces.ts +103 -0
- package/corpus/templates/content/actions/list-documents.ts +12 -9
- package/corpus/templates/content/actions/move-document.ts +3 -14
- package/corpus/templates/content/actions/pull-document.ts +0 -23
- package/corpus/templates/content/actions/remove-local-file-source.ts +50 -29
- package/corpus/templates/content/actions/resolve-local-folder-conflict.ts +442 -0
- package/corpus/templates/content/actions/reveal-local-source-file.ts +6 -17
- package/corpus/templates/content/actions/search-documents.ts +0 -34
- package/corpus/templates/content/actions/share-local-file-document.ts +25 -1
- package/corpus/templates/content/actions/submit-content-database-form.ts +12 -0
- package/corpus/templates/content/actions/sync-local-folder-source.ts +731 -0
- package/corpus/templates/content/actions/sync-manifest-local-folder-source.ts +123 -0
- package/corpus/templates/content/actions/update-content-database-view.ts +10 -1
- package/corpus/templates/content/actions/update-document.ts +0 -15
- package/corpus/templates/content/actions/view-screen.ts +2 -50
- package/corpus/templates/content/app/components/editor/SlashCommandMenu.tsx +76 -62
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +70 -22
- package/corpus/templates/content/app/components/editor/database/grouping.ts +2 -1
- package/corpus/templates/content/app/components/editor/database/settings.tsx +23 -20
- package/corpus/templates/content/app/components/editor/database/shared.tsx +2 -0
- package/corpus/templates/content/app/components/editor/database/sidebar.tsx +330 -0
- package/corpus/templates/content/app/components/editor/database/types.ts +1 -0
- package/corpus/templates/content/app/components/editor/database/view-config.ts +3 -1
- package/corpus/templates/content/app/components/layout/Layout.tsx +25 -1
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +209 -113
- package/corpus/templates/content/app/components/sidebar/select-content-space.ts +31 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +16 -0
- package/corpus/templates/content/app/hooks/use-content-spaces.ts +39 -0
- package/corpus/templates/content/app/i18n/zh-TW.ts +11 -0
- package/corpus/templates/content/app/i18n-data.ts +75 -0
- package/corpus/templates/content/app/routes/_app.local-files.tsx +174 -65
- package/corpus/templates/content/changelog/2026-07-16-content-callouts-can-now-be-reviewed-and-published-to-builder.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-heading-5-and-heading-6-are-available-from-the-editor-slash-.md +6 -0
- package/corpus/templates/content/changelog/2026-07-16-organize-every-workspace-through-a-customizable-files-databa.md +6 -0
- package/corpus/templates/content/parity/matrix.md +2 -1
- package/corpus/templates/content/parity/matrix.ts +37 -1
- package/corpus/templates/content/scripts/dev-database.mjs +7 -5
- package/corpus/templates/content/server/db/schema.ts +113 -40
- package/corpus/templates/content/server/lib/document-context.ts +15 -1
- package/corpus/templates/content/server/lib/notion-sync.ts +6 -0
- package/corpus/templates/content/server/plugins/db.ts +153 -0
- package/corpus/templates/content/shared/api.ts +17 -3
- package/corpus/templates/content/shared/builder-mdx.ts +163 -16
- package/corpus/templates/design/.agents/skills/design-templates/SKILL.md +59 -0
- package/corpus/templates/design/AGENTS.md +6 -1
- package/corpus/templates/design/actions/create-design-from-template.ts +19 -7
- package/corpus/templates/design/actions/list-design-templates.ts +69 -21
- package/corpus/templates/design/actions/navigate.ts +2 -2
- package/corpus/templates/design/actions/view-screen.ts +15 -2
- package/corpus/templates/design/agent-native.app-skill.json +5 -0
- package/corpus/templates/design/app/components/editor/PromptDialog.tsx +281 -56
- package/corpus/templates/design/app/components/templates/TemplatePreview.tsx +14 -2
- package/corpus/templates/design/app/hooks/use-navigation-state.ts +3 -2
- package/corpus/templates/design/app/i18n/zh-TW.ts +3 -3
- package/corpus/templates/design/app/i18n-data.ts +21 -2
- package/corpus/templates/design/app/i18n-template-feature.ts +218 -10
- package/corpus/templates/design/app/pages/Index.tsx +195 -24
- package/corpus/templates/design/app/pages/Templates.tsx +144 -55
- package/corpus/templates/design/app/pages/design-editor/generation-prompt-directives.ts +12 -1
- package/corpus/templates/design/changelog/2026-07-14-templates-now-appear-in-the-new-design-picker-with-previews-.md +6 -0
- package/corpus/templates/design/changelog/2026-07-15-templates-now-open-immediately-after-copying-even-while-the-.md +6 -0
- package/corpus/templates/design/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/mail/AGENTS.md +23 -18
- package/corpus/templates/mail/actions/bulk-archive.ts +32 -28
- package/corpus/templates/mail/actions/mark-read.ts +131 -20
- package/corpus/templates/mail/actions/mark-thread-read.ts +1 -1
- package/corpus/templates/mail/actions/move-email.ts +30 -24
- package/corpus/templates/mail/actions/send-email.ts +73 -68
- package/corpus/templates/mail/changelog/2026-07-15-unread-inbox-cleanup-now-completes-reliably.md +6 -0
- package/corpus/templates/mail/server/handlers/emails.ts +218 -209
- package/corpus/templates/mail/server/lib/bulk-mark-read.ts +30 -0
- package/corpus/templates/mail/server/lib/email-state.ts +198 -113
- package/corpus/templates/mail/server/lib/google-auth.ts +218 -25
- package/corpus/templates/mail/server/lib/jobs.ts +69 -74
- package/corpus/templates/mail/server/lib/local-email-store.ts +265 -0
- package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
- package/dist/cli/content-local.d.ts +1 -0
- package/dist/cli/content-local.d.ts.map +1 -1
- package/dist/cli/content-local.js +75 -17
- package/dist/cli/content-local.js.map +1 -1
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/skills-content/content-skill.d.ts +1 -1
- package/dist/cli/skills-content/content-skill.d.ts.map +1 -1
- package/dist/cli/skills-content/content-skill.js +36 -25
- package/dist/cli/skills-content/content-skill.js.map +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +47 -14
- package/dist/cli/skills.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/local-artifacts/index.d.ts +10 -0
- package/dist/local-artifacts/index.d.ts.map +1 -1
- package/dist/local-artifacts/index.js +39 -0
- package/dist/local-artifacts/index.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +2 -0
- package/dist/vite/client.js.map +1 -1
- package/docs/content/local-file-mode.mdx +99 -489
- package/docs/content/locales/ar-SA/local-file-mode.mdx +35 -490
- package/docs/content/locales/ar-SA/template-content.mdx +10 -24
- package/docs/content/locales/de-DE/local-file-mode.mdx +37 -491
- package/docs/content/locales/de-DE/template-content.mdx +10 -24
- package/docs/content/locales/es-ES/local-file-mode.mdx +35 -493
- package/docs/content/locales/es-ES/template-content.mdx +10 -24
- package/docs/content/locales/fr-FR/local-file-mode.mdx +38 -491
- package/docs/content/locales/fr-FR/template-content.mdx +10 -24
- package/docs/content/locales/hi-IN/local-file-mode.mdx +33 -492
- package/docs/content/locales/hi-IN/template-content.mdx +10 -24
- package/docs/content/locales/ja-JP/local-file-mode.mdx +36 -492
- package/docs/content/locales/ja-JP/template-content.mdx +10 -24
- package/docs/content/locales/ko-KR/local-file-mode.mdx +35 -490
- package/docs/content/locales/ko-KR/template-content.mdx +10 -24
- package/docs/content/locales/pt-BR/local-file-mode.mdx +35 -493
- package/docs/content/locales/pt-BR/template-content.mdx +10 -24
- package/docs/content/locales/zh-CN/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-CN/template-content.mdx +9 -24
- package/docs/content/locales/zh-TW/local-file-mode.mdx +31 -489
- package/docs/content/locales/zh-TW/template-content.mdx +9 -24
- package/docs/content/template-content.mdx +26 -42
- package/package.json +1 -1
- package/corpus/templates/assets/app/components/library/EditLibraryDialog.tsx +0 -126
|
@@ -14,6 +14,7 @@ import { getUserSetting, putUserSetting } from "@agent-native/core/settings";
|
|
|
14
14
|
*/
|
|
15
15
|
import type { EmailMessage, Label } from "@shared/types.js";
|
|
16
16
|
|
|
17
|
+
import type { BulkMarkReadResult } from "./bulk-mark-read.js";
|
|
17
18
|
import {
|
|
18
19
|
createOAuth2Client,
|
|
19
20
|
gmailGetMessage,
|
|
@@ -23,6 +24,11 @@ import {
|
|
|
23
24
|
gmailUntrashThread,
|
|
24
25
|
} from "./google-api.js";
|
|
25
26
|
import { getOAuth2Credentials, isConnected } from "./google-auth.js";
|
|
27
|
+
import {
|
|
28
|
+
readLocalEmails,
|
|
29
|
+
withLocalEmailMutationLock,
|
|
30
|
+
writeLocalEmails,
|
|
31
|
+
} from "./local-email-store.js";
|
|
26
32
|
import { invalidateThreadCache } from "./thread-cache.js";
|
|
27
33
|
|
|
28
34
|
// ---------------------------------------------------------------------------
|
|
@@ -106,21 +112,6 @@ export async function getAccountToken(
|
|
|
106
112
|
// Local-mode helpers
|
|
107
113
|
// ---------------------------------------------------------------------------
|
|
108
114
|
|
|
109
|
-
async function readLocalEmails(ownerEmail: string): Promise<EmailMessage[]> {
|
|
110
|
-
const data = await getUserSetting(ownerEmail, "local-emails");
|
|
111
|
-
if (data && Array.isArray((data as any).emails)) {
|
|
112
|
-
return (data as any).emails as EmailMessage[];
|
|
113
|
-
}
|
|
114
|
-
return [];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
async function writeLocalEmails(
|
|
118
|
-
ownerEmail: string,
|
|
119
|
-
emails: EmailMessage[],
|
|
120
|
-
): Promise<void> {
|
|
121
|
-
await putUserSetting(ownerEmail, "local-emails", { emails });
|
|
122
|
-
}
|
|
123
|
-
|
|
124
115
|
async function readLocalLabels(ownerEmail: string): Promise<Label[]> {
|
|
125
116
|
const data = await getUserSetting(ownerEmail, "labels");
|
|
126
117
|
if (data && Array.isArray((data as any).labels)) {
|
|
@@ -205,24 +196,26 @@ export async function archiveEmail(
|
|
|
205
196
|
} = input;
|
|
206
197
|
|
|
207
198
|
if (!(await isConnected(ownerEmail))) {
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
199
|
+
return withLocalEmailMutationLock(ownerEmail, async () => {
|
|
200
|
+
const emails = await readLocalEmails(ownerEmail);
|
|
201
|
+
const target = emails.find((e) => e.id === id);
|
|
202
|
+
if (!target) throw new Error(`Email ${id} not found`);
|
|
203
|
+
const threadId = target.threadId || target.id;
|
|
204
|
+
const updated = emails.map((e) => {
|
|
205
|
+
if ((e.threadId || e.id) !== threadId) return e;
|
|
206
|
+
return {
|
|
207
|
+
...e,
|
|
208
|
+
isArchived: true,
|
|
209
|
+
labelIds: e.labelIds.filter((l) => l !== "inbox"),
|
|
210
|
+
};
|
|
211
|
+
});
|
|
212
|
+
await writeLocalEmails(ownerEmail, updated);
|
|
213
|
+
await writeLocalLabels(
|
|
214
|
+
ownerEmail,
|
|
215
|
+
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
216
|
+
);
|
|
217
|
+
return { id, threadId, isArchived: true };
|
|
219
218
|
});
|
|
220
|
-
await writeLocalEmails(ownerEmail, updated);
|
|
221
|
-
await writeLocalLabels(
|
|
222
|
-
ownerEmail,
|
|
223
|
-
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
224
|
-
);
|
|
225
|
-
return { id, threadId, isArchived: true };
|
|
226
219
|
}
|
|
227
220
|
|
|
228
221
|
// Try accountEmail-scoped account first, then fall through to other accounts
|
|
@@ -301,26 +294,28 @@ export async function unarchiveEmail(
|
|
|
301
294
|
const { id, ownerEmail, accountEmail } = input;
|
|
302
295
|
|
|
303
296
|
if (!(await isConnected(ownerEmail))) {
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
297
|
+
return withLocalEmailMutationLock(ownerEmail, async () => {
|
|
298
|
+
const emails = await readLocalEmails(ownerEmail);
|
|
299
|
+
const target = emails.find((e) => e.id === id);
|
|
300
|
+
if (!target) throw new Error(`Email ${id} not found`);
|
|
301
|
+
const threadId = target.threadId || target.id;
|
|
302
|
+
const updated = emails.map((e) => {
|
|
303
|
+
if ((e.threadId || e.id) !== threadId) return e;
|
|
304
|
+
return {
|
|
305
|
+
...e,
|
|
306
|
+
isArchived: false,
|
|
307
|
+
labelIds: e.labelIds.includes("inbox")
|
|
308
|
+
? e.labelIds
|
|
309
|
+
: ["inbox", ...e.labelIds],
|
|
310
|
+
};
|
|
311
|
+
});
|
|
312
|
+
await writeLocalEmails(ownerEmail, updated);
|
|
313
|
+
await writeLocalLabels(
|
|
314
|
+
ownerEmail,
|
|
315
|
+
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
316
|
+
);
|
|
317
|
+
return { id, threadId, isArchived: false };
|
|
317
318
|
});
|
|
318
|
-
await writeLocalEmails(ownerEmail, updated);
|
|
319
|
-
await writeLocalLabels(
|
|
320
|
-
ownerEmail,
|
|
321
|
-
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
322
|
-
);
|
|
323
|
-
return { id, threadId, isArchived: false };
|
|
324
319
|
}
|
|
325
320
|
|
|
326
321
|
const token = await getAccountToken(accountEmail ?? ownerEmail, ownerEmail);
|
|
@@ -371,12 +366,14 @@ export async function toggleStar(
|
|
|
371
366
|
} = input;
|
|
372
367
|
|
|
373
368
|
if (!(await isConnected(ownerEmail))) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
369
|
+
return withLocalEmailMutationLock(ownerEmail, async () => {
|
|
370
|
+
const emails = await readLocalEmails(ownerEmail);
|
|
371
|
+
const idx = emails.findIndex((e) => e.id === id);
|
|
372
|
+
if (idx === -1) throw new Error(`Email ${id} not found`);
|
|
373
|
+
emails[idx] = { ...emails[idx], isStarred };
|
|
374
|
+
await writeLocalEmails(ownerEmail, emails);
|
|
375
|
+
return { id, threadId: emails[idx].threadId, isStarred };
|
|
376
|
+
});
|
|
380
377
|
}
|
|
381
378
|
|
|
382
379
|
const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
|
|
@@ -444,20 +441,22 @@ export async function trashEmail(
|
|
|
444
441
|
const { id, ownerEmail, accountEmail } = input;
|
|
445
442
|
|
|
446
443
|
if (!(await isConnected(ownerEmail))) {
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
444
|
+
return withLocalEmailMutationLock(ownerEmail, async () => {
|
|
445
|
+
const emails = await readLocalEmails(ownerEmail);
|
|
446
|
+
const target = emails.find((e) => e.id === id);
|
|
447
|
+
if (!target) throw new Error(`Email ${id} not found`);
|
|
448
|
+
const threadId = target.threadId || target.id;
|
|
449
|
+
const updated = emails.map((e) => {
|
|
450
|
+
if ((e.threadId || e.id) !== threadId) return e;
|
|
451
|
+
return { ...e, isTrashed: true, isArchived: false };
|
|
452
|
+
});
|
|
453
|
+
await writeLocalEmails(ownerEmail, updated);
|
|
454
|
+
await writeLocalLabels(
|
|
455
|
+
ownerEmail,
|
|
456
|
+
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
457
|
+
);
|
|
458
|
+
return { id, threadId, isTrashed: true };
|
|
454
459
|
});
|
|
455
|
-
await writeLocalEmails(ownerEmail, updated);
|
|
456
|
-
await writeLocalLabels(
|
|
457
|
-
ownerEmail,
|
|
458
|
-
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
459
|
-
);
|
|
460
|
-
return { id, threadId, isTrashed: true };
|
|
461
460
|
}
|
|
462
461
|
|
|
463
462
|
const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
|
|
@@ -515,26 +514,28 @@ export async function untrashEmail(
|
|
|
515
514
|
const { id, ownerEmail, accountEmail } = input;
|
|
516
515
|
|
|
517
516
|
if (!(await isConnected(ownerEmail))) {
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
517
|
+
return withLocalEmailMutationLock(ownerEmail, async () => {
|
|
518
|
+
const emails = await readLocalEmails(ownerEmail);
|
|
519
|
+
const target = emails.find((e) => e.id === id);
|
|
520
|
+
if (!target) throw new Error(`Email ${id} not found`);
|
|
521
|
+
const threadId = target.threadId || target.id;
|
|
522
|
+
const updated = emails.map((e) => {
|
|
523
|
+
if ((e.threadId || e.id) !== threadId) return e;
|
|
524
|
+
return {
|
|
525
|
+
...e,
|
|
526
|
+
isTrashed: false,
|
|
527
|
+
labelIds: e.labelIds.includes("inbox")
|
|
528
|
+
? e.labelIds
|
|
529
|
+
: ["inbox", ...e.labelIds],
|
|
530
|
+
};
|
|
531
|
+
});
|
|
532
|
+
await writeLocalEmails(ownerEmail, updated);
|
|
533
|
+
await writeLocalLabels(
|
|
534
|
+
ownerEmail,
|
|
535
|
+
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
536
|
+
);
|
|
537
|
+
return { id, threadId, isTrashed: false };
|
|
531
538
|
});
|
|
532
|
-
await writeLocalEmails(ownerEmail, updated);
|
|
533
|
-
await writeLocalLabels(
|
|
534
|
-
ownerEmail,
|
|
535
|
-
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
536
|
-
);
|
|
537
|
-
return { id, threadId, isTrashed: false };
|
|
538
539
|
}
|
|
539
540
|
|
|
540
541
|
const token = await getAccountToken(accountEmail ?? ownerEmail, ownerEmail);
|
|
@@ -575,16 +576,18 @@ export async function markRead(input: MarkReadInput): Promise<MarkReadResult> {
|
|
|
575
576
|
const { id, ownerEmail, isRead, accountEmail } = input;
|
|
576
577
|
|
|
577
578
|
if (!(await isConnected(ownerEmail))) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
579
|
+
return withLocalEmailMutationLock(ownerEmail, async () => {
|
|
580
|
+
const emails = await readLocalEmails(ownerEmail);
|
|
581
|
+
const idx = emails.findIndex((e) => e.id === id);
|
|
582
|
+
if (idx === -1) throw new Error(`Email ${id} not found`);
|
|
583
|
+
emails[idx] = { ...emails[idx], isRead };
|
|
584
|
+
await writeLocalEmails(ownerEmail, emails);
|
|
585
|
+
await writeLocalLabels(
|
|
586
|
+
ownerEmail,
|
|
587
|
+
recomputeUnreadCounts(emails, await readLocalLabels(ownerEmail)),
|
|
588
|
+
);
|
|
589
|
+
return { id, isRead };
|
|
590
|
+
});
|
|
588
591
|
}
|
|
589
592
|
|
|
590
593
|
const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
|
|
@@ -616,6 +619,86 @@ export async function markRead(input: MarkReadInput): Promise<MarkReadResult> {
|
|
|
616
619
|
throw lastErr ?? new Error("Mark read failed");
|
|
617
620
|
}
|
|
618
621
|
|
|
622
|
+
export async function markAllLocalUnreadRead(input: {
|
|
623
|
+
ownerEmail: string;
|
|
624
|
+
accountEmail: string;
|
|
625
|
+
excludeThreadIds: string[];
|
|
626
|
+
}): Promise<BulkMarkReadResult> {
|
|
627
|
+
const { ownerEmail, accountEmail } = input;
|
|
628
|
+
if (accountEmail.toLowerCase() !== ownerEmail.toLowerCase()) {
|
|
629
|
+
throw new Error("Local mail only supports the authenticated owner account");
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
const excludedThreadIds = new Set(input.excludeThreadIds.filter(Boolean));
|
|
633
|
+
return withLocalEmailMutationLock(ownerEmail, async () => {
|
|
634
|
+
const emails = await readLocalEmails(ownerEmail);
|
|
635
|
+
const matched = emails.filter((email) => !email.isRead);
|
|
636
|
+
const excluded = matched.filter((email) =>
|
|
637
|
+
excludedThreadIds.has(email.threadId || email.id),
|
|
638
|
+
);
|
|
639
|
+
const selectedIds = new Set(
|
|
640
|
+
matched
|
|
641
|
+
.filter((email) => !excludedThreadIds.has(email.threadId || email.id))
|
|
642
|
+
.map((email) => email.id),
|
|
643
|
+
);
|
|
644
|
+
const updated = emails.map((email) =>
|
|
645
|
+
selectedIds.has(email.id) ? { ...email, isRead: true } : email,
|
|
646
|
+
);
|
|
647
|
+
|
|
648
|
+
if (selectedIds.size > 0) {
|
|
649
|
+
await writeLocalEmails(ownerEmail, updated);
|
|
650
|
+
await writeLocalLabels(
|
|
651
|
+
ownerEmail,
|
|
652
|
+
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
653
|
+
);
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
const persisted = await readLocalEmails(ownerEmail);
|
|
657
|
+
const remaining = persisted.filter((email) => !email.isRead);
|
|
658
|
+
const matchedIds = new Set(matched.map((email) => email.id));
|
|
659
|
+
const remainingProtected = remaining.filter((email) =>
|
|
660
|
+
excludedThreadIds.has(email.threadId || email.id),
|
|
661
|
+
);
|
|
662
|
+
const unexpectedRemaining = remaining.filter((email) =>
|
|
663
|
+
selectedIds.has(email.id),
|
|
664
|
+
);
|
|
665
|
+
const newUnread = remaining.filter((email) => !matchedIds.has(email.id));
|
|
666
|
+
|
|
667
|
+
return {
|
|
668
|
+
mode: "all-unread",
|
|
669
|
+
accountEmail,
|
|
670
|
+
matchedMessages: matched.length,
|
|
671
|
+
matchedThreads: new Set(
|
|
672
|
+
matched.map((email) => email.threadId || email.id),
|
|
673
|
+
).size,
|
|
674
|
+
excludedMessages: excluded.length,
|
|
675
|
+
excludedThreads: new Set(
|
|
676
|
+
excluded.map((email) => email.threadId || email.id),
|
|
677
|
+
).size,
|
|
678
|
+
changedMessages: selectedIds.size,
|
|
679
|
+
batchCount: selectedIds.size > 0 ? 1 : 0,
|
|
680
|
+
failures: [],
|
|
681
|
+
remainingUnreadMessages: remaining.length,
|
|
682
|
+
remainingUnreadThreads: new Set(
|
|
683
|
+
remaining.map((email) => email.threadId || email.id),
|
|
684
|
+
).size,
|
|
685
|
+
remainingProtectedMessages: remainingProtected.length,
|
|
686
|
+
remainingProtectedThreads: new Set(
|
|
687
|
+
remainingProtected.map((email) => email.threadId || email.id),
|
|
688
|
+
).size,
|
|
689
|
+
unexpectedUnreadMessages: unexpectedRemaining.length,
|
|
690
|
+
unexpectedUnreadThreads: new Set(
|
|
691
|
+
unexpectedRemaining.map((email) => email.threadId || email.id),
|
|
692
|
+
).size,
|
|
693
|
+
newUnreadMessages: newUnread.length,
|
|
694
|
+
newUnreadThreads: new Set(
|
|
695
|
+
newUnread.map((email) => email.threadId || email.id),
|
|
696
|
+
).size,
|
|
697
|
+
verificationComplete: unexpectedRemaining.length === 0,
|
|
698
|
+
};
|
|
699
|
+
});
|
|
700
|
+
}
|
|
701
|
+
|
|
619
702
|
// ---------------------------------------------------------------------------
|
|
620
703
|
// Mark thread read (all messages in a thread)
|
|
621
704
|
// ---------------------------------------------------------------------------
|
|
@@ -647,21 +730,23 @@ export async function markThreadRead(
|
|
|
647
730
|
const { threadId, ownerEmail, isRead, accountEmail } = input;
|
|
648
731
|
|
|
649
732
|
if (!(await isConnected(ownerEmail))) {
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
733
|
+
return withLocalEmailMutationLock(ownerEmail, async () => {
|
|
734
|
+
const emails = await readLocalEmails(ownerEmail);
|
|
735
|
+
let changed = false;
|
|
736
|
+
const updated = emails.map((e) => {
|
|
737
|
+
if ((e.threadId || e.id) !== threadId || e.isRead === isRead) return e;
|
|
738
|
+
changed = true;
|
|
739
|
+
return { ...e, isRead };
|
|
740
|
+
});
|
|
741
|
+
if (changed) {
|
|
742
|
+
await writeLocalEmails(ownerEmail, updated);
|
|
743
|
+
await writeLocalLabels(
|
|
744
|
+
ownerEmail,
|
|
745
|
+
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
746
|
+
);
|
|
747
|
+
}
|
|
748
|
+
return { threadId, isRead };
|
|
656
749
|
});
|
|
657
|
-
if (changed) {
|
|
658
|
-
await writeLocalEmails(ownerEmail, updated);
|
|
659
|
-
await writeLocalLabels(
|
|
660
|
-
ownerEmail,
|
|
661
|
-
recomputeUnreadCounts(updated, await readLocalLabels(ownerEmail)),
|
|
662
|
-
);
|
|
663
|
-
}
|
|
664
|
-
return { threadId, isRead };
|
|
665
750
|
}
|
|
666
751
|
|
|
667
752
|
const token = await getAccountToken(accountEmail ?? ownerEmail, ownerEmail);
|