Add CalDAV sync (v0.3.0) #102

Open
atayozcan wants to merge 5 commits from atayozcan/feat/caldav-sync into main
atayozcan commented 2026-04-29 00:34:00 +00:00 (Migrated from github.com)

Adds two-way CalDAV sync (Nextcloud, Radicale, SOGo, Fastmail, Apple iCloud, …) and a batch of related polish.

Refs #6, #92.

Highlights

  • CalDAV sync, with auto-discovery of VTODO calendars, push-on-edit, and a 60-second background sync loop.
  • Account credentials in the system keyring (Secret Service / cosmic-keyring), never on disk.
  • Account settings panel with a status row, helper text, last-synced relative timestamp, and a destructive Sign-out button.
  • Sync triggers everywhere: header bar icon, View menu, per-list right-click menu.
  • Due-date badge on each task row ("Today" / "Tomorrow" / weekday / YYYY-MM-DD).
  • Sort by due date; completed tasks always sink to the bottom.

Bug fixes uncovered while testing

  • Pulled VTODOs now appear immediately in the active list (SetList short-circuited on unchanged id; new Message::ReloadTasks).
  • Date dialog Complete handler routed details::update directly and dropped its outputs — now goes through Message::Details(..) so the in-memory task and sidebar refresh.
  • invalid SecondaryMap key used panic when a slotmap was rebuilt under in-flight messages — all hot-path indexing switched to .get() with bail-out.
  • Date picker stored UTC midnight; in negative-offset timezones that lands on the previous day. Now stores local-midnight and emits VALUE=DATE for all-day RFC encoding.
  • Rename / Set-Icon dialogs targeted the active list instead of the entity passed by the nav context menu.
  • CalDAV calendar URLs without a trailing slash had Url::join("uid.ics") overwrite the last segment.

Interop / RFC compliance

  • Uses icalendar::Todo::get_due() so all RFC 5545 forms (DATE, DATE-TIME UTC / floating / TZID) are accepted.
  • Textual fallback parser also accepts ISO-8601 extended forms (with separators, with offset).
  • Always emits DTSTAMP — some servers refuse VTODOs without one.
  • All-day tasks emit DUE;VALUE=DATE:… instead of DUE:…T000000Z.

Removed

  • sync_password field on TasksConfig (passwords are in the keyring now).
  • Dead sqlx dependency and Error::Sqlx variant — meaningful build-graph reduction.
  • caldav:URL description marker — replaced by List::remote_url; legacy lists migrate on first sync.
  • unsafe impl Send for List — unnecessary, PathBuf is already Send.

Release housekeeping

  • 0.3.0 metainfo entry; flatpak finish-args gain --share=network and --talk-name=org.freedesktop.secrets; <internet> flipped from offline-only to always.
  • README.md gets a CalDAV section; new CHANGELOG.md (Keep-a-Changelog).
  • About dialog reads version from CARGO_PKG_VERSION.

Tests

12 unit tests cover legacy-marker parsing, remote_url precedence, marker stripping, ISO-8601 (UTC, offset, extended), garbage rejection, and the all-day VALUE=DATE round-trip.

Notes

  • Flatpak generated-sources.json was deliberately not regenerated in this PR — happy to add a follow-up commit if you'd prefer to ship that here.
  • Google Tasks / Microsoft To Do aren't covered (no CalDAV); only what providers expose via RFC 4791.
Adds two-way CalDAV sync (Nextcloud, Radicale, SOGo, Fastmail, Apple iCloud, …) and a batch of related polish. Refs #6, #92. ## Highlights - **CalDAV sync**, with auto-discovery of `VTODO` calendars, push-on-edit, and a 60-second background sync loop. - **Account credentials in the system keyring** (Secret Service / cosmic-keyring), never on disk. - **Account settings panel** with a status row, helper text, last-synced relative timestamp, and a destructive Sign-out button. - Sync triggers everywhere: header bar icon, View menu, per-list right-click menu. - **Due-date badge** on each task row ("Today" / "Tomorrow" / weekday / `YYYY-MM-DD`). - **Sort by due date**; completed tasks always sink to the bottom. ## Bug fixes uncovered while testing - Pulled VTODOs now appear immediately in the active list (`SetList` short-circuited on unchanged id; new `Message::ReloadTasks`). - Date dialog `Complete` handler routed `details::update` directly and dropped its outputs — now goes through `Message::Details(..)` so the in-memory task and sidebar refresh. - `invalid SecondaryMap key used` panic when a slotmap was rebuilt under in-flight messages — all hot-path indexing switched to `.get()` with bail-out. - Date picker stored UTC midnight; in negative-offset timezones that lands on the previous day. Now stores local-midnight and emits `VALUE=DATE` for all-day RFC encoding. - Rename / Set-Icon dialogs targeted the active list instead of the entity passed by the nav context menu. - CalDAV calendar URLs without a trailing slash had `Url::join("uid.ics")` overwrite the last segment. ## Interop / RFC compliance - Uses `icalendar::Todo::get_due()` so all RFC 5545 forms (`DATE`, `DATE-TIME` UTC / floating / TZID) are accepted. - Textual fallback parser also accepts ISO-8601 extended forms (with separators, with offset). - Always emits `DTSTAMP` — some servers refuse VTODOs without one. - All-day tasks emit `DUE;VALUE=DATE:…` instead of `DUE:…T000000Z`. ## Removed - `sync_password` field on `TasksConfig` (passwords are in the keyring now). - Dead `sqlx` dependency and `Error::Sqlx` variant — meaningful build-graph reduction. - `caldav:URL` description marker — replaced by `List::remote_url`; legacy lists migrate on first sync. - `unsafe impl Send for List` — unnecessary, `PathBuf` is already `Send`. ## Release housekeeping - 0.3.0 metainfo entry; flatpak `finish-args` gain `--share=network` and `--talk-name=org.freedesktop.secrets`; `<internet>` flipped from `offline-only` to `always`. - `README.md` gets a CalDAV section; new `CHANGELOG.md` (Keep-a-Changelog). - About dialog reads version from `CARGO_PKG_VERSION`. ## Tests 12 unit tests cover legacy-marker parsing, `remote_url` precedence, marker stripping, ISO-8601 (UTC, offset, extended), garbage rejection, and the all-day `VALUE=DATE` round-trip. ## Notes - Flatpak `generated-sources.json` was deliberately *not* regenerated in this PR — happy to add a follow-up commit if you'd prefer to ship that here. - Google Tasks / Microsoft To Do aren't covered (no CalDAV); only what providers expose via RFC 4791.
edfloreshz commented 2026-05-01 20:20:45 +00:00 (Migrated from github.com)

Thanks for submitting this, could you rebase this branch?

Thanks for submitting this, could you rebase this branch?
atayozcan commented 2026-05-02 01:50:20 +00:00 (Migrated from github.com)

Started the rebase and hit a wall — the upstream feat: new store system commit isn't a content change, it's a structural rewrite. On the first of 5 commits alone, ~1050 of ~1300 lines of src/app.rs ended up inside conflict markers, and most of what this PR depends on (src/app/actions.rs, src/app/dialog.rs, src/app/menu.rs, the entire src/storage/ module, chrono) has been deleted, moved into src/app/{core,dialogs,navigation,ui}/, replaced by src/services/store.rs, or swapped for jiff. Subtask handling was also removed.

A git rebase would produce conflict markers in files that no longer exist on the new tree, so I think the right move is to re-apply this PR as a port on top of the new architecture rather than force-push a rebase. Happy to do that, but wanted to check with you first:

  1. Are you open to a fresh PR built on the new services::store API (closing this one), or would you prefer a force-pushed rebase here even if it ends up being effectively a rewrite?
  2. Any guidance on where CalDAV sync should live in the new module layout (e.g. a new src/services/caldav.rs alongside store.rs)?
  3. With subtasks removed, should the CalDAV importer flatten RELATED-TO parent links, or drop tasks that have a parent?
Started the rebase and hit a wall — the upstream `feat: new store system` commit isn't a content change, it's a structural rewrite. On the first of 5 commits alone, ~1050 of ~1300 lines of `src/app.rs` ended up inside conflict markers, and most of what this PR depends on (`src/app/actions.rs`, `src/app/dialog.rs`, `src/app/menu.rs`, the entire `src/storage/` module, `chrono`) has been deleted, moved into `src/app/{core,dialogs,navigation,ui}/`, replaced by `src/services/store.rs`, or swapped for `jiff`. Subtask handling was also removed. A `git rebase` would produce conflict markers in files that no longer exist on the new tree, so I think the right move is to re-apply this PR as a port on top of the new architecture rather than force-push a rebase. Happy to do that, but wanted to check with you first: 1. Are you open to a fresh PR built on the new `services::store` API (closing this one), or would you prefer a force-pushed rebase here even if it ends up being effectively a rewrite? 2. Any guidance on where CalDAV sync should live in the new module layout (e.g. a new `src/services/caldav.rs` alongside `store.rs`)? 3. With subtasks removed, should the CalDAV importer flatten `RELATED-TO` parent links, or drop tasks that have a parent?
edfloreshz commented 2026-05-03 19:59:43 +00:00 (Migrated from github.com)

I'd appreciate it if you could respond directly, I understand you're using AI for this PR and I'm happy to discuss the changes, but I'd prefer to hear from a human.

  1. You can create a new PR if you need to and close this one.
  2. No.
  3. Sub tasks were not removed.
I'd appreciate it if you could respond directly, I understand you're using AI for this PR and I'm happy to discuss the changes, but I'd prefer to hear from a human. 1. You can create a new PR if you need to and close this one. 2. No. 3. Sub tasks were not removed.
This pull request has changes conflicting with the target branch.
  • Cargo.lock
  • Cargo.toml
  • dev.edfloreshz.Tasks.json
  • i18n/en/tasks.ftl
  • res/dev.edfloreshz.Tasks.metainfo.xml
  • src/app.rs
  • src/app/actions.rs
  • src/app/dialog.rs
  • src/app/error.rs
  • src/app/menu.rs
  • src/config.rs
  • src/core/localize.rs
  • src/core/settings/app.rs
  • src/core/settings/error.rs
  • src/core/style/segmented_control.rs
  • src/main.rs
  • src/pages/content.rs
  • src/pages/details.rs
  • src/storage/migration.rs
  • src/storage/mod.rs
  • src/storage/models/list.rs
View command line instructions

Manual merge helper

Use this merge commit message when completing the merge manually.

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin atayozcan/feat/caldav-sync:atayozcan/feat/caldav-sync
git switch atayozcan/feat/caldav-sync

Merge

Merge the changes and update on Forgejo.

Warning: The "Autodetect manual merge" setting is not enabled for this repository, you will have to mark this pull request as manually merged afterwards.

git switch main
git merge --no-ff atayozcan/feat/caldav-sync
git switch atayozcan/feat/caldav-sync
git rebase main
git switch main
git merge --ff-only atayozcan/feat/caldav-sync
git switch atayozcan/feat/caldav-sync
git rebase main
git switch main
git merge --no-ff atayozcan/feat/caldav-sync
git switch main
git merge --squash atayozcan/feat/caldav-sync
git switch main
git merge --ff-only atayozcan/feat/caldav-sync
git switch main
git merge atayozcan/feat/caldav-sync
git push origin main
Sign in to join this conversation.
No description provided.