fix: count a draft with a waiting send once when signing out

This commit is contained in:
Linus Rath
2026-11-03 09:16:32 +01:00
parent 5c5d982f9d
commit 5dc0f05a70
5 changed files with 15 additions and 10 deletions
@@ -22,7 +22,6 @@ const draft = (overrides: Partial<LocalDraftRow> = {}): LocalDraftRow => ({
jmap: 'j1',
subject: 'Only here',
status: 'waiting',
hasFiles: false,
createdAt: Date.UTC(2026, 10, 3),
...overrides,
});
+4 -2
View File
@@ -580,10 +580,12 @@ describe('sending a draft that\'s on the device', () => {
});
await flush();
expect(t.server.drafts()).toEqual([]);
expect(await list()).toHaveLength(1);
expect((await get(id)).list).toHaveLength(1);
// It's that message, in the outbox: not counted a second time as unsent.
expect(await list()).toEqual([]);
await t.engine.areaCall('drafts.discard', { jmap: JMAP_ACCOUNT, id });
expect(await list()).toEqual([]);
expect((await get(id)).notFound).toEqual([id]);
});
it('goes up as a draft if the send fails, so nothing depends on the outbox', async () => {
+3 -5
View File
@@ -5,8 +5,8 @@ import { upsertEmail, type EmailObject } from '../mail/records';
import { savePatch, EMAIL_TYPE } from '../outbox/email-set';
import type { ConflictSummary } from '../outbox/items';
import { applyPatch, destroyConflicts, type RecordState } from '../outbox/patches';
import { blobPartsOf, type DraftCreate } from './local-email';
import { allPending, destroyForDraft, pendingForFlush, replaceLocalRow, replacesOf, resolveStandIns, type DraftError, type DraftRow, type DraftStatus } from './pending';
import type { DraftCreate } from './local-email';
import { destroyForDraft, listedDrafts, pendingForFlush, replaceLocalRow, replacesOf, resolveStandIns, type DraftError, type DraftRow, type DraftStatus } from './pending';
export interface PendingDraft {
localId: string;
@@ -33,7 +33,6 @@ export interface LocalDraftSummary {
subject: string;
status: DraftStatus;
error?: DraftError;
hasFiles: boolean;
createdAt: number;
}
@@ -67,12 +66,11 @@ function read(ctx: StoreContext, params: { before: number }): { rows: PendingDra
}
function list(ctx: StoreContext): LocalDraftSummary[] {
return allPending(ctx.db).map((row) => ({
return listedDrafts(ctx.db).map((row) => ({
localId: row.local_id,
jmap: row.jmap,
subject: row.subject ?? '',
status: row.status,
hasFiles: blobPartsOf(JSON.parse(row.create_json) as DraftCreate).length > 0,
createdAt: Number(row.created_at),
...(row.error ? { error: JSON.parse(row.error) as DraftError } : {}),
}));
+6
View File
@@ -204,6 +204,12 @@ export function allPending(db: StorageAdapter): DraftRow[] {
return db.all<DraftRow>('SELECT * FROM draft_pending ORDER BY seq ASC');
}
/** The drafts that are only here and nothing else speaks for. One a send names is that message, in the outbox. */
export function listedDrafts(db: StorageAdapter): DraftRow[] {
const held = draftsWithQueuedSend(db);
return allPending(db).filter((row) => !held.has(row.local_id));
}
export function pendingForFlush(db: StorageAdapter): DraftRow[] {
const held = draftsWithQueuedSend(db);
return allPending(db).filter((row) => row.status !== 'refused' && !held.has(row.local_id));
+2 -2
View File
@@ -156,8 +156,8 @@ function remove(ctx: StoreContext, params: { id: number }): void {
// ---- Files attached offline ----
/** The size the page cuts a file into on its way here. */
export const HOLD_CHUNK_BYTES = 1024 * 1024;
/** The same size the page cuts a file into on its way here. */
const HOLD_CHUNK_BYTES = 1024 * 1024;
/** A file that comes in pieces: nothing can read it until the last one is in. */
function holdStart(ctx: StoreContext, params: { jmap?: string; name: string; type: string; size: number }): { blobId: string } {