makiolaj b25f8b231c sync(chunk 4): incremental sync and scheduling
RFC 6578 as an optimisation on top of chunk 3's full path, and sync that runs
by itself.

- :caldav gains sync-collection, with invalidation matched on
  DAV:valid-sync-token in the body on any 4xx rather than on a status code —
  400, 403, 409 and 412 are all used in the wild, and matching the status is
  why Thunderbird never recovers from sabre's 403. No DAV:limit (Nextcloud
  regressed it to an HTML error page); 507 on our own href is truncation.
- The engine persists the token per page, after the bodies. An iteration cap
  and a no-progress guard, since the RFC never requires the token to advance.
  The full path runs anyway every 24h: a token the server accepts over a
  pruned change log returns 207, zero changes and no error, and the protocol
  gives no other way to notice.
- The three membership traps: an unknown removed href is a no-op, a
  delete-then-recreate is re-identified from the UID in the body, and a mass
  removal is refused in favour of a real listing, because ACL churn looks
  exactly like one.
- Scheduling is a PeriodicWorkRequest with a network constraint, expedited
  only for the in-app button. getForegroundInfo is implemented unconditionally
  (setExpedited falls back to a foreground service below API 31 and the
  default throws) but declares no service type, which would have pulled back
  the Android 15 dataSync budget and a Play video-demo requirement.

initialIncomplete turned out not to be needed: adopting a token only after a
full reconciliation completes removes the hazard it guarded, so there is
nothing to persist atomically with anything.

/code-review high raised 8 findings, all fixed. The two that mattered: the
cadence clock sat in the backed-up DataStore, so a restore would have made the
engine trust a stale token for a day — both sync-state stores now have their
own excluded file; and the incremental download path lacked the write-phase
guard, overwriting local edits that had never reached the server. Reasoning in
docs/SYNC-PLAN.md.

Chunk 2's on-device review is still outstanding; none of this has run on a
device or against a real server.
2026-09-07 16:18:09 +02:00
2026-09-04 14:09:25 +02:00

Agendula

A modern Material 3 Expressive task app for Android.
Keeps your tasks on your device, or on top of a tasks provider you already use. Open standards, no account required.

CI Android 10+ Kotlin + Compose Material 3 Expressive MIT License

Agendula is the task-list sibling to Calendula. Where Calendula is a pure front-end over Android's CalendarContract, Agendula keeps its own store, designed around RFC 5545's VTODO — the same tasks DAVx5 (and SmoothSync, DecSync, …) sync out of your CalDAV server. It can also read and write a tasks provider you already have, for anyone already syncing that way.

The name rhymes with its sibling on purpose: Agendula is agenda — Latin for “things to be done” — given Calendula's -ula ending. Calendula keeps your days; Agendula keeps your to-dos. (A Calendula flower head is botanically a cluster of many small florets — so the two apps are florets of one bloom.)

Where your tasks live — your choice

Where Sync Needs
On your device (default) Agendula's own database none yet — CalDAV sync of our own is planned nothing. No account, no permissions, no other app
In a provider you already use OpenTasks or tasks.org whatever syncs it for you — DAVx5 and friends that app installed, and its read/write permission

Agendula's own store is an ordinary app database — nothing is published to other apps, so there is no authority to clash over and no permission to grant. It coexists with OpenTasks rather than replacing it: installing one never breaks the other, and if you already sync through a provider, that keeps working exactly as it did.

Recurring tasks are expanded per RFC 5545, and everything the schema does not model is round-tripped verbatim rather than dropped — so passing your tasks through Agendula does not quietly lose fields a server sent.

Your tasks are exportable as standard iCalendar .ics files at any time, because data you can't take with you isn't really yours.

Status: backend complete, UI catching up. Storage, reads and writes, smart-list filtering, a self-scheduled reminder engine, and export are built and unit-tested. The Material 3 Expressive screens are being built on top, one at a time — the storage-mode picker and export screen are not there yet. See docs/ROADMAP.md for status, docs/ARCHITECTURE.md for how it's built, and docs/STORAGE-AND-SYNC.md for why storage works the way it does.

Sync sources (by design)

In external-provider mode Agendula works with anything that writes to that provider — DAVx5 (CalDAV), SmoothSync, CalDAV-Sync, DecSync CC, or any Android sync adapter — because it builds on the provider, not on any one sync app. Google Tasks / Microsoft To Do are out of scope by design (proprietary; they would mean owning a sync stack). Open standards — CalDAV / iCalendar / DecSync — are the lane.

Translations

Agendula ships in English so far, and would like not to. Translations are managed on a self-hosted Weblate, and partial ones are fine — an untranslated string simply falls back to English.

Help translate Agendula

No coding needed: register on the Weblate server, pick (or request) a language, and translate the strings in your browser. You can also reach this link in the app from the top of Settings → App language.

License

MIT — see LICENSE.

S
Description
A modern Material 3 Expressive task app for Android — sibling to Calendula, over the OpenTasks provider.
Readme MIT
1.7 MiB
v0.2.0
Latest
2026-06-27 19:27:17 +00:00
Languages
Kotlin 96.3%
Shell 2.8%
Python 0.9%