Completing a task in a CalDAV client wrote through to the markdown correctly, then the client silently un-completed it moments later. `to_ics` stamped DTSTAMP and LAST-MODIFIED with the wall clock on every serialization, and Radicale derives an item's ETag by hashing that serialization. So an unchanged task minted a fresh ETag on every read, at one-second granularity. A client that read the task, waited for the user to tick the checkbox, then PUT with `If-Match: <the etag it holds>` always got 412 Precondition Failed — and resolved that apparent conflict by re-downloading the server copy, discarding the completion. Persist a per-task `last_modified` in the sidecar instead, stamped only when the task's content or status actually changes, and serve DTSTAMP, LAST-MODIFIED and the Item's own last_modified from it. The ETag is now stable while the task is, and changes exactly when the task does. Schema goes to v2, migrated in place with ALTER TABLE: rebuilding the database would regenerate every UID and replace clients' whole task list. Also fixes a plain GET of an item returning 500 — Radicale asserts on `Item.last_modified`, which was never passed. Only REPORT was covered by tests, so nothing caught it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
10 KiB
10 KiB