docs: say what actually shipped, and what 1.0.0 actually is
The branch's documents describe a world where the vendored provider reached users. It never did, and several claims follow from that mistake. `ROADMAP.md`: - Phase 5's "**Breaking:** the authority and both custom permissions no longer exist — anyone who pointed DAVx5 at that authority loses it, and the release notes have to say so" is wrong in the way that *removes* work: they were added and deleted inside this unreleased cycle, so nobody could have pointed anything at them. The release notes must not warn about losing something that never shipped. The per-locale release-notes item went with it. - "Run the instrumented suite on a device … none has ever executed" was stale: 52 tests, 0 failures, Pixel 10 / API 36, 13 Aug. What is genuinely open is a re-run against the tip, since the 4 Sep commits reworked the store and added instrumented cases that have never run. Both now say so, with the ARM64 aapt exit-code trap noted where someone will hit it. - The device-verification item described upgrading from a v0.3.2 APK with seeded data, which cannot be the real path. Replaced with the four cases that matter, including the one that only exists on a device that side-loaded a dev build of this branch. - M6's Glance item claimed "deps present in build.gradle.kts" — not any more. Translations and the language picker shipped in 0.4.0 and are marked done. `OWN-STORE.md` gets a correction banner over "Migrating existing users" saying the premise is wrong, and a section for the copy that replaces it. `STORAGE-AND-SYNC.md`'s banner said the vendored-provider decision was "made, shipped, and then costed properly" — built, not shipped. `PRIVACY.md` had the opposite problem: it describes CalDAV sync, Nextcloud Login Flow v2, RFC 6764 discovery and a Keystore-held password, none of which exist in 1.0.0 — the app holds no `INTERNET` permission at all. The permissions section listed six it does not declare. Since it is a legal document users are sent to from Settings → About, section 4 is now marked as describing a planned feature, section 9 lists exactly what the manifest declares (and says what is *not* there), and the backup and crash-report sections no longer assume network access or sync bookkeeping. Kept forward-looking rather than cut, so it does not have to change underneath anyone when sync lands. **Worth a read before merging** — it is the one change here with legal weight. `fastlane/.../full_description.txt` still opened with "It works directly on an existing tasks provider (OpenTasks / tasks.org) … no own account, no own sync" as the app's premise. That is the F-Droid listing for a release whose headline is that it needs nothing installed. Rewritten, with the feature list and the no-internet-permission point that is now literally true. `README.md` and `ExportWriter`'s "ships in eleven locales" (it is three) follow.
This commit is contained in:
@@ -332,6 +332,17 @@ gain.
|
||||
|
||||
## Migrating existing users
|
||||
|
||||
> ⚠️ **Corrected, 2026-09-21.** The premise below is wrong, and it was wrong when
|
||||
> it was written: **no release ever bundled the provider.** It was added and
|
||||
> deleted inside this one unreleased cycle, so `databases/tasks.db` exists on no
|
||||
> published install and `OneShotImport` finds nothing to do for every real user.
|
||||
> Anyone on 0.3.x or 0.4.0 keeps their tasks in OpenTasks or tasks.org, which
|
||||
> `ProviderResolver.autoMode()` correctly keeps them on. The path onto the new
|
||||
> store for those users is `ExternalImport` — see
|
||||
> [Copying from an external provider](#copying-from-an-external-provider) below.
|
||||
> Everything in this section still applies to a device that ran a dev build of
|
||||
> this branch, which is why the import, its fixture and its tests stay.
|
||||
|
||||
Anyone on v0.3.x has their tasks inside the bundled provider's SQLite file at
|
||||
`/data/data/de.jeanlucmakiola.agendula/databases/tasks.db` (dmfs schema
|
||||
version 23). Removing the Gradle module does **not** remove that file — an app
|
||||
@@ -384,6 +395,38 @@ still exist:
|
||||
3. Only after a release with no import defects reported does a subsequent
|
||||
version delete `tasks.db.imported`.
|
||||
|
||||
A failure is no longer invisible, either. `runIfNeeded` used to hand back an
|
||||
`ImportResult.Failed` that `StartupGate` discarded — an upgrading user got an
|
||||
empty app, no message, and their tasks in a file only a developer could name. The
|
||||
outcome is now recorded in DataStore and logged, and Settings → Storage offers the
|
||||
retry, which is what makes `reimportFromArchive()` reachable at all.
|
||||
|
||||
### Copying from an external provider
|
||||
|
||||
The migration that actually matters for 1.0.0, since the one above serves nobody.
|
||||
`ExternalImport` (`data/tasks/transfer/`) reads the external provider through the
|
||||
existing `exportTasks` seam and writes lists, tasks and alarms into Room in one
|
||||
transaction with verified counts — the same shape as `OneShotImport`, for the
|
||||
same reason. Task `uid`s are preserved, so the rows can be attached to a CalDAV
|
||||
collection once sync lands rather than duplicating server-side.
|
||||
|
||||
- **User-initiated, once.** Settings → Storage → *Copy tasks from …*, behind a
|
||||
confirm that names the real task count. Offered only while a provider is
|
||||
installed and permitted and no copy has succeeded; a second run would duplicate
|
||||
everything, because what it writes is indistinguishable from hand-typed tasks
|
||||
the moment it finishes.
|
||||
- **A copy, not a sync.** The source is untouched, whatever syncs it keeps
|
||||
syncing it, and the two sets drift from that moment. Switching stores now asks
|
||||
first for the same reason: neither store hands its rows to the other.
|
||||
- **One-directional.** Writing a *list* into a third-party provider means
|
||||
impersonating its sync adapter, and the external store is already the one that
|
||||
can sync.
|
||||
- **What does not come across**, because the read seam is shaped for iCalendar
|
||||
output: per-occurrence `RECURRENCE-ID` overrides (a series arrives as master +
|
||||
rule), `EXDATE`, `CLASS`, `DURATION`, the per-task timezone, and a task's exact
|
||||
`PRIORITY` digit (`Priority` buckets 1–4 as HIGH). Nothing the app itself
|
||||
displays is lost.
|
||||
|
||||
This is the reason phase 5 (deleting `:provider`) ships *after* phase 4 rather
|
||||
than with it — and the reason the deletion is its own release.
|
||||
|
||||
|
||||
+36
-17
@@ -1,7 +1,7 @@
|
||||
---
|
||||
title: Privacy Policy — Agendula
|
||||
description: What Agendula does with your data. No servers, no account, no analytics — your tasks stay on your device unless you add a CalDAV server yourself.
|
||||
updated: 2026-09-15
|
||||
updated: 2026-09-21
|
||||
---
|
||||
|
||||
<!--
|
||||
@@ -21,7 +21,7 @@ updated: 2026-09-15
|
||||
render on the published page.
|
||||
-->
|
||||
|
||||
**Last updated:** 9 September 2026
|
||||
**Last updated:** 21 September 2026
|
||||
Applies to the Android app **Agendula** (package `de.jeanlucmakiola.agendula`),
|
||||
all versions and all distribution channels.
|
||||
|
||||
@@ -32,6 +32,14 @@ your device. They leave it in exactly one case: if you set up a CalDAV account
|
||||
yourself, they are synchronised with **the server you entered** — and with
|
||||
nothing and no one else. Nothing is ever sent to the developer.
|
||||
|
||||
> **As of version 1.0.0, CalDAV sync is not in the app yet.** It is designed and
|
||||
> described here so that this policy does not have to change underneath you when
|
||||
> it arrives, but the released app has **no network access of its own at all** —
|
||||
> it does not hold Android's `INTERNET` permission, so section 4 cannot happen on
|
||||
> this version. Until it ships, your tasks leave the device only if *you* export
|
||||
> them, or if a separate sync app you installed yourself syncs a task provider you
|
||||
> pointed Agendula at (section 3).
|
||||
|
||||
## 1. Controller
|
||||
|
||||
IT-Dienstleister | Jean-Luc Makiola
|
||||
@@ -64,6 +72,9 @@ All of this is verifiable in the
|
||||
|
||||
## 4. CalDAV sync — the only case where your tasks leave the device
|
||||
|
||||
*Not available in version 1.0.0 — see the note in "In short". This section
|
||||
describes how it will behave, and is published in advance deliberately.*
|
||||
|
||||
Sync is optional and off until you add an account. If you add one, everything
|
||||
below happens between your device and **the server you nominated**, and nowhere
|
||||
else.
|
||||
@@ -144,16 +155,21 @@ stored locally on your device and are removed when you uninstall the app.
|
||||
|
||||
If Android Auto Backup is enabled on your device, your tasks and settings may
|
||||
be backed up to your own Google account, under Google's terms — the developer
|
||||
has no access to it. Two things are deliberately excluded from that backup:
|
||||
your stored CalDAV password, and Agendula's per-device sync bookkeeping. After
|
||||
restoring onto a new device you therefore sign in to your server again.
|
||||
has no access to it. What travels is Agendula's own task database and your
|
||||
settings, and nothing else: the backup rules name those explicitly, which makes
|
||||
everything not named — including the archived copy the app keeps of an older
|
||||
version's database — excluded by default.
|
||||
|
||||
Once CalDAV sync ships, two further things will be kept out of that backup by
|
||||
design: the stored password, and the per-device sync bookkeeping. Restoring onto
|
||||
a new device will therefore mean signing in to your server again.
|
||||
|
||||
## 7. Crash reports
|
||||
|
||||
If Agendula crashes, it offers to report the problem. Nothing is sent
|
||||
automatically, even though the app has network access. The report is copied to
|
||||
your clipboard and your browser is opened with the project's issue tracker, the
|
||||
text pre-filled. **You see the full content, you decide whether to submit it,
|
||||
automatically — and on this version the app could not send it if it wanted to,
|
||||
having no network permission. The report is copied to your clipboard and your
|
||||
browser is opened with the project's issue tracker, the text pre-filled. **You see the full content, you decide whether to submit it,
|
||||
and you can edit or discard it.**
|
||||
|
||||
Such a report contains:
|
||||
@@ -183,11 +199,10 @@ process — it only opens the address.
|
||||
|
||||
## 9. Permissions and why they exist
|
||||
|
||||
- `INTERNET`, `ACCESS_NETWORK_STATE` — CalDAV sync with the server you
|
||||
configure, and checking whether a connection exists before trying. Without a
|
||||
CalDAV account, no connection is made.
|
||||
- `READ_SYNC_SETTINGS`, `WRITE_SYNC_SETTINGS` — register the sync account with
|
||||
Android's sync framework so it can be scheduled.
|
||||
This is the complete list the released app declares — you can check it against
|
||||
the app's entry in F-Droid, or against `app/src/main/AndroidManifest.xml` in the
|
||||
source:
|
||||
|
||||
- `POST_NOTIFICATIONS` — show reminders.
|
||||
- `USE_EXACT_ALARM`, `SCHEDULE_EXACT_ALARM` — deliver reminders at the exact
|
||||
due time.
|
||||
@@ -195,10 +210,14 @@ process — it only opens the address.
|
||||
- `org.dmfs.permission.READ_TASKS` / `WRITE_TASKS` and
|
||||
`org.tasks.permission.READ_TASKS` / `WRITE_TASKS` — optional, requested only
|
||||
if you choose the external-provider storage mode, and only for the provider
|
||||
you selected (OpenTasks or tasks.org).
|
||||
- `WAKE_LOCK`, `FOREGROUND_SERVICE` — required by the Android system component
|
||||
used for scheduled background work (WorkManager); on older Android versions
|
||||
it needs them to run an expedited sync.
|
||||
you selected (OpenTasks or tasks.org). All four are declared in the manifest
|
||||
because a manifest is static, but none is requested until you pick that mode.
|
||||
|
||||
Note what is **not** there: Agendula declares no `INTERNET` permission, so the
|
||||
released app cannot make a network connection of any kind. When CalDAV sync
|
||||
ships it will need `INTERNET` and `ACCESS_NETWORK_STATE`, and the sync-framework
|
||||
permissions to schedule itself; this section will be updated in the same release
|
||||
that adds them, never before.
|
||||
|
||||
Agendula publishes no content provider of its own and declares no permissions
|
||||
that other apps could request.
|
||||
|
||||
+68
-15
@@ -126,10 +126,14 @@ The engine exists (M1: `ReminderScheduler` + boot / provider-change re-sync,
|
||||
- ✅ Settings screen — landed early with M5 (`SettingsScreen` in the nav graph,
|
||||
reached by the gear on the lists overview). Covers theme, dynamic colour, due
|
||||
reminders (toggle + default offset + exact-alarm status), default list, and the
|
||||
add-a-subtask-row opt-out. Still ⬜ a **language** entry (deferred until there
|
||||
are translations to switch to).
|
||||
- ⬜ Glance task-list widget — deps present in `build.gradle.kts`, zero impl.
|
||||
- ⬜ Translations — only `res/values/` (English); no `values-XX`.
|
||||
add-a-subtask-row opt-out — plus the **App language** picker, which landed with
|
||||
the 0.4.0 translations.
|
||||
- ⬜ Glance task-list widget — still just an idea, and now without a foothold:
|
||||
the `glance-appwidget` / `glance-material3` dependencies were declared but never
|
||||
used by a single line, so they were shipping dead weight in the APK and have
|
||||
been removed. Re-add them with the implementation, not before.
|
||||
- ✅ Translations — German and Brazilian Portuguese, via Weblate, shipped in
|
||||
0.4.0 (`res/values-de`, `res/values-pt-rBR`, `res/xml/locales_config.xml`).
|
||||
- ⬜ Finalize F-Droid metadata, confirm CI release flow.
|
||||
|
||||
### ✅ Posture B — our own task store
|
||||
@@ -206,10 +210,13 @@ most of it broken, absent or unusable. Reasoning in
|
||||
`StorageMode.LOCAL` is gone (a stored `LOCAL` reads as `OWN`); `ProviderResolver`
|
||||
narrows to discovering external providers; `ProviderChangeReceiver` filters on
|
||||
the two external authorities only. `provider/PROVENANCE.md` is replaced by a
|
||||
postscript in `STORAGE-DECISION.md`. **Breaking:** the
|
||||
`de.jeanlucmakiola.agendula.tasks` authority and both custom permissions no
|
||||
longer exist — anyone who pointed DAVx5 at that authority loses it, and the
|
||||
release notes have to say so.
|
||||
postscript in `STORAGE-DECISION.md`. **Not a breaking change for anyone, as it
|
||||
turns out:** the `de.jeanlucmakiola.agendula.tasks` authority and its two custom
|
||||
permissions were added and deleted inside this same unreleased cycle
|
||||
(`git tag --contains` on the commit that added `:provider` comes back empty), so
|
||||
no published version ever carried them and nobody could have pointed DAVx5 at
|
||||
one. The release notes must *not* warn about losing something that never
|
||||
shipped.
|
||||
- ✅ Phase 6 — harden: `MigrationTestHelper` wired against the committed v1
|
||||
schema so v1 → v2 is cheap when sync adds columns, an Auto Backup restore test
|
||||
covering the WAL case in both directions, and a performance check at 5,000
|
||||
@@ -229,13 +236,59 @@ most of it broken, absent or unusable. Reasoning in
|
||||
stopped reading. `setCompletedInstance` now forks a `RECURRENCE-ID` override
|
||||
the way `updateInstance` does — phase 2 always specified this, only the edit
|
||||
half had it.
|
||||
- ⬜ **Run the instrumented suite on a device.** Six classes — the Room seam, the
|
||||
DAOs, the import, the migration harness, the restore path and the performance
|
||||
check — all compile and none has ever executed. Everything load-bearing about
|
||||
this migration is verified only by tests that have not run.
|
||||
- ⬜ Verify on a device: a fresh install on the Room store, and an upgrade from a
|
||||
v0.3.2 APK with seeded data landing every task, list and reminder.
|
||||
- ⬜ Per-locale release notes for the dropped authority and permissions.
|
||||
- ✅ **Ran the instrumented suite on a device** — 52 tests, 0 failures, Pixel 10
|
||||
/ API 36, 13 Aug 2026; all six classes (the Room seam, the DAOs, the import, the
|
||||
migration harness, the restore path, the performance check). Mind the ARM64
|
||||
`aapt` trap: the task can exit non-zero on a fully green run, so read
|
||||
`app/build/outputs/androidTest-results/connected/debug/*.xml` before believing
|
||||
the exit code.
|
||||
- ⬜ **Re-run it against the branch tip.** That run predates the 4 Sep commits,
|
||||
which reworked `RoomTasksDataSource`, `ModeRoutingTasksDataSource` and
|
||||
`RecurrenceExpander` and added instrumented cases of their own — so the tests
|
||||
covering the store code as it stands have still never executed.
|
||||
- ⬜ Verify on a device — and note that the upgrade path this phase was designed
|
||||
around is **not** the one real users are on. `OneShotImport` reads a bundled
|
||||
dmfs provider's `databases/tasks.db`, and no release ever bundled one, so every
|
||||
existing install's tasks sit in OpenTasks or tasks.org instead. What has to be
|
||||
checked is therefore:
|
||||
1. a fresh install landing on the Room store with nothing installed;
|
||||
2. an upgrade from the 0.4.0 APK **with OpenTasks installed and permitted** —
|
||||
`autoMode()` must keep them on External and show their tasks unchanged;
|
||||
3. Settings → Storage → *Copy tasks from …* moving those tasks into the Room
|
||||
store, then the switch to *On this device* showing them, reminders included;
|
||||
4. `OneShotImport` itself, which on a real device is only reachable by
|
||||
side-loading a dev build of this branch first.
|
||||
- ⬜ Release notes for 1.0.0. Nothing to say about the dropped authority or
|
||||
permissions (see Phase 5); what needs saying is the own store, the copy path out
|
||||
of an external provider, iCalendar export and list management.
|
||||
|
||||
### ✅ A way onto the new store for the people already using Agendula
|
||||
The migration this branch was planned around turned out to serve nobody: the
|
||||
vendored provider it reads from never shipped, so no install has the file
|
||||
`OneShotImport` looks for. Everyone on 0.4.0 keeps their tasks in OpenTasks or
|
||||
tasks.org, which `autoMode()` correctly keeps them on — and until now the only
|
||||
route to the new store was to retype everything.
|
||||
- ✅ `ExternalImport` (`data/tasks/transfer/`) copies the external provider's
|
||||
lists, tasks and alarms into Room in one transaction with verified counts, the
|
||||
same discipline as the legacy import. Task `uid`s survive, so these rows can be
|
||||
attached to a CalDAV collection once sync lands instead of duplicating.
|
||||
- ✅ Offered as **Settings → Storage → Copy tasks from …**, behind a confirm that
|
||||
names the real number of tasks, and only while a provider is installed,
|
||||
permitted, and no copy has succeeded yet — a second run would leave two of
|
||||
everything, since what it writes is indistinguishable from hand-typed tasks
|
||||
afterwards.
|
||||
- ✅ One-directional by design. Writing a *list* into a third-party provider
|
||||
means impersonating its sync adapter, and the external store is already the one
|
||||
that can sync. What does not come across is documented on the class:
|
||||
per-occurrence overrides, `EXDATE`, `CLASS`, `DURATION`, the per-task timezone,
|
||||
and a task's exact `PRIORITY` digit.
|
||||
- ✅ Switching stores at all now asks first. Neither store hands its rows to the
|
||||
other, so the app looks emptied to anyone who expected a move.
|
||||
- ✅ A failed `OneShotImport` is no longer silent. It used to return an
|
||||
`ImportResult.Failed` that every caller dropped, leaving an upgrading user an
|
||||
empty app and no explanation; it is now recorded, logged, and surfaced in
|
||||
Settings → Storage as a retry — which is also what finally makes
|
||||
`reimportFromArchive()` reachable from the app.
|
||||
|
||||
### ✅ Managing lists in the app
|
||||
Owning the store made this mandatory: there is no longer a provider app to
|
||||
|
||||
@@ -8,7 +8,9 @@
|
||||
> [`OWN-STORE.md`](OWN-STORE.md) for what replaces it. The permissions,
|
||||
> distribution, storage-mode and dead-end sections below remain accurate; treat
|
||||
> the "our own provider" sections as the historical record of a decision that was
|
||||
> made, shipped, and then costed properly.
|
||||
> made, built, and then costed properly. It never reached a release: the
|
||||
> `:provider` module was added and deleted inside this same unreleased cycle, so
|
||||
> no published version of Agendula ever carried an authority of its own.
|
||||
|
||||
> Decided direction, captured 2026-08-01. Supersedes the earlier "Posture B =
|
||||
> bundle OpenTasks" working notes, which are withdrawn (see
|
||||
|
||||
Reference in New Issue
Block a user