Add two-way CalDAV sync for tasks (#811) - #2280
Open
schobernoise wants to merge 1 commit into
Open
Conversation
Keeps a vault in sync with a CalDAV task list (Nextcloud, Apple Reminders, Radicale, Baikal) in both directions, as VTODOs rather than calendar events. This is a new integration alongside the Google/Microsoft export, not an extension of it: that path is one-way and models tasks as VEVENTs. Identity lives in each note's frontmatter (caldav_uid, caldav_href, caldav_etag, caldav_account, caldav_synced_at), so disconnecting and reconnecting converges instead of duplicating. ETags detect a concurrent edit; DTSTAMP/LAST-MODIFIED against the local change time decides it. Most of the logic is in pure modules under src/services/caldav/, registered in run-architecture-conformance.mjs so they are held to the stricter rules. VTODOs are modelled as content lines rather than a decoded struct, so properties TaskNotes does not map -- DESCRIPTION, ATTACH, X- properties -- survive a round trip untouched. Subtasks map to RELATED-TO;RELTYPE=PARENT. TaskNotes has no parent field: a subtask is a task whose projects field points at its parent, and both sides are many-valued, so the mapping is direct. Dependencies use the RFC 9253 temporal reltypes with GAP, which TaskDependency already matches field for field. A relation whose target has no UID yet is replayed once after the rest of the pass, and one that genuinely cannot be addressed is left out rather than guessed at. Reminders map to VALARM. TaskNotes stamps its own alarms with X-TASKNOTES-REMINDER and rewrites only those, so an alarm added on a phone is never touched. Polling asks for the collection ctag first and stops there when nothing has changed; otherwise a VTODO-filtered calendar-query returns only tasks, and returns all of them, which is what makes deletion detection safe. Task lists commonly share a collection with far more events -- one test list held 1142 resources and a single VTODO -- so walking the resource list would mean downloading the entire calendar to find nothing. Discovery climbs from the configured URL up to the account root on the original scheme. /.well-known/caldav is tried last: it frequently redirects to plain http behind a reverse proxy, and requestUrl follows redirects with no way to veto a scheme downgrade. Credentials live in Obsidian's SecretStorage, never in data.json, and are refused over anything but HTTPS except to loopback. Claude-Session: https://claude.ai/code/session_01Q8Ni1X5i6HsZaEAueYLvZd
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Keeps a vault in sync with a CalDAV task list (Nextcloud, Apple Reminders, Radicale, Baikal) in both directions, as VTODOs rather than calendar events. This is a new integration alongside the Google/Microsoft export, not an extension of it: that path is one-way and models tasks as VEVENTs.
Identity lives in each note's frontmatter (caldav_uid, caldav_href, caldav_etag, caldav_account, caldav_synced_at), so disconnecting and reconnecting converges instead of duplicating. ETags detect a concurrent edit; DTSTAMP/LAST-MODIFIED against the local change time decides it.
Most of the logic is in pure modules under src/services/caldav/, registered in run-architecture-conformance.mjs so they are held to the stricter rules. VTODOs are modelled as content lines rather than a decoded struct, so properties TaskNotes does not map -- DESCRIPTION, ATTACH, X- properties -- survive a round trip untouched.
Subtasks map to RELATED-TO;RELTYPE=PARENT. TaskNotes has no parent field: a subtask is a task whose projects field points at its parent, and both sides are many-valued, so the mapping is direct. Dependencies use the RFC 9253 temporal reltypes with GAP, which TaskDependency already matches field for field. A relation whose target has no UID yet is replayed once after the rest of the pass, and one that genuinely cannot be addressed is left out rather than guessed at.
Reminders map to VALARM. TaskNotes stamps its own alarms with X-TASKNOTES-REMINDER and rewrites only those, so an alarm added on a phone is never touched.
Polling asks for the collection ctag first and stops there when nothing has changed; otherwise a VTODO-filtered calendar-query returns only tasks, and returns all of them, which is what makes deletion detection safe. Task lists commonly share a collection with far more events -- one test list held 1142 resources and a single VTODO -- so walking the resource list would mean downloading the entire calendar to find nothing.
Discovery climbs from the configured URL up to the account root on the original scheme. /.well-known/caldav is tried last: it frequently redirects to plain http behind a reverse proxy, and requestUrl follows redirects with no way to veto a scheme downgrade.
Credentials live in Obsidian's SecretStorage, never in data.json, and are refused over anything but HTTPS except to loopback.
Claude-Session: https://claude.ai/code/session_01Q8Ni1X5i6HsZaEAueYLvZd