fix: improve CalDAV task detection for external clients (Thunderbird) #84

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.
This commit is contained in:
nesgarbo
2026-04-16 16:54:09 +02:00
committed by Linus Rath
parent 5a2e141ed6
commit 2ea8054240
+8 -3
View File
@@ -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;