From 2ea8054240eb7aec192b67ddda715ce91cc80b87 Mon Sep 17 00:00:00 2001 From: nesgarbo Date: Thu, 16 Apr 2026 15:28:15 +0200 Subject: [PATCH] fix: improve CalDAV task detection for external clients (Thunderbird) #84 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues prevented tasks created in Thunderbird (or other CalDAV clients) from appearing in the task view: 1. percentComplete was not in CALENDAR_TASK_PROPERTIES, so it was never requested from the server and the heuristic check for it was always false (dead code). 2. The hasTaskFields heuristic used strict value checks: - 'progress' in obj && typeof obj.progress === 'string' → fails when Stalwart returns progress: null instead of the RFC 8984 default "needs-action" - 'due' in obj && obj.due != null → fails when Stalwart includes due: null for tasks without a DUE date (key present, value null) RFC 8984 §5.2 defines due, progress and percentComplete as Task-only properties — a VEVENT will never include them in a JMAP response. Checking for key presence alone (even when null) is therefore a reliable discriminator, regardless of the actual value. --- lib/jmap/client.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/lib/jmap/client.ts b/lib/jmap/client.ts index aca37b16..a319553b 100644 --- a/lib/jmap/client.ts +++ b/lib/jmap/client.ts @@ -189,6 +189,7 @@ const CALENDAR_TASK_PROPERTIES = [ 'useDefaultAlerts', 'alerts', 'relatedTo', + 'percentComplete', // Task-only per RFC 8984 §5.2.4 — used in detection heuristic ] as const; /** @@ -3974,9 +3975,13 @@ export class JMAPClient implements IJMAPClient { const isExplicitTask = typeof type === 'string' && type.toLowerCase() === 'task'; // CalDAV-created tasks (e.g. Thunderbird) may lack @type or have @type // set to something other than 'Event'. Detect them by the presence of - // task-specific fields: progress, due, or percentComplete. - const hasTaskFields = ('progress' in obj && typeof obj.progress === 'string') - || ('due' in obj && obj.due != null) + // task-specific keys (due, progress, percentComplete), which RFC 8984 §5.2 + // defines as Task-only — a VEVENT will never include them in the response. + // We check for key presence (even if null) because Stalwart may return null + // instead of the RFC defaults (e.g. progress default is "needs-action"). + // @see https://www.rfc-editor.org/rfc/rfc8984#section-5.2 + const hasTaskFields = ('due' in obj) + || ('progress' in obj) || ('percentComplete' in obj); const isCalDavTask = type !== 'Event' && hasTaskFields;