From 71ff8930a2ba9cfa0be65a3fea805b37a9cf8523 Mon Sep 17 00:00:00 2001 From: Linus Rath <139418639+rathlinus@users.noreply.github.com> Date: Fri, 8 May 2026 00:50:27 +0200 Subject: [PATCH] fix: apply unread/starred filters in mailbox queries --- src/api/__tests__/email.test.ts | 18 ++++++++++++++++++ src/api/email.ts | 14 +++++++++++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/src/api/__tests__/email.test.ts b/src/api/__tests__/email.test.ts index e12dfd0e..7d696335 100644 --- a/src/api/__tests__/email.test.ts +++ b/src/api/__tests__/email.test.ts @@ -90,6 +90,24 @@ describe('email operations', () => { const call = mockRequest.mock.calls[0][0][0]; expect(call[1].filter).toEqual({ inMailbox: 'mb-1', hasKeyword: '$flagged' }); }); + + it('should AND-wrap a FilterOperator instead of spreading it', async () => { + mockRequest.mockResolvedValue({ + methodResponses: [['Email/query', { ids: [], total: 0 }, '0']], + }); + + const userFilter = { + operator: 'AND', + conditions: [{ inMailbox: 'mb-1' }, { notKeyword: '$seen' }], + }; + await queryEmails('mb-1', { filter: userFilter }); + + const call = mockRequest.mock.calls[0][0][0]; + expect(call[1].filter).toEqual({ + operator: 'AND', + conditions: [{ inMailbox: 'mb-1' }, userFilter], + }); + }); }); describe('getEmails', () => { diff --git a/src/api/email.ts b/src/api/email.ts index 5622db44..362bdc66 100644 --- a/src/api/email.ts +++ b/src/api/email.ts @@ -93,10 +93,22 @@ export async function queryEmails( }, ): Promise<{ ids: string[]; total: number }> { const accountId = jmapClient.accountId; + const inMailbox = { inMailbox: mailboxId }; + const userFilter = options?.filter; + // JMAP filters are either a FilterCondition or a FilterOperator (operator + + // conditions) — never both. Spreading a FilterOperator next to `inMailbox` + // produces a hybrid object that servers reduce to the FilterCondition, + // silently dropping the operator's conditions (e.g. the "unread" toggle). + const filter = + !userFilter || Object.keys(userFilter).length === 0 + ? inMailbox + : 'operator' in userFilter + ? { operator: 'AND', conditions: [inMailbox, userFilter] } + : { ...inMailbox, ...userFilter }; const res = await jmapClient.request([ ['Email/query', { accountId, - filter: { inMailbox: mailboxId, ...options?.filter }, + filter, sort: options?.sort ?? [{ property: 'receivedAt', isAscending: false }], position: options?.position ?? 0, limit: options?.limit ?? 50,