Reviewed the plan against the code it describes. Two design holes and five errors. Instance identity was the real one. The plan deleted the materialised instances table without saying what replaces the instance row id, which TasksRepositoryImpl.updateTask passes to updateInstance and which ListsScreen keys a lazy list by. Two occurrences of one series can appear in the same list, so taskId alone is not unique and a hash of (taskId, start) can collide - as a Compose key that is a visible bug. Task.id is dropped for occurrenceStart, updateInstance takes (taskId, occurrenceStart, form), and External mode maps back to a real instance row with one query. This is the single seam change, and the plan's "TasksDataSource unchanged" claim was wrong. Local lists had no account name. TaskList.accountName is non-null, ListsViewModel groups by it and ListsScreen renders it as a section header, so a null account_id must still report "Local". The unique index was wrong: overrides share their master's UID, so unique (list_id, uid) would reject the rows the recurrence design depends on. It needs recurrence_id in the key. Phase 0 broke background reminders. It dropped our authority from ProviderChangeReceiver's manifest filter while the provider was still the store, and renamed StorageMode.LOCAL to OWN four phases before OWN meant Room. Both moved to phase 5. Parity against the provider was overclaimed: the provider materialises one occurrence, so multi-occurrence expansion has nothing to compare against and is tested against RFC 5545 directly. The phases sum to 6.5-7 weeks, not the 6-6.5 stated, and the difference from STORAGE-DECISION.md's 4.5-6 is now explained rather than left as a contradiction. Gaps closed: WAL vs Auto Backup (checkpoint on ON_STOP, sidecars in the backup rules, tested in phase 6), cascade rules for master_id and parent_id, Instant type converters, a rollback path that re-runs the import from tasks.db.imported, the release note for dropping the authority and its permissions, and ICalendarWriter.uidFor's synthesis branch becoming External-only. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
109 lines
5.7 KiB
Markdown
109 lines
5.7 KiB
Markdown
## On-device work (USB-connected phone)
|
||
|
||
A physical phone is connected over USB. Rules:
|
||
|
||
- **Install when asked** — if the user says to install/deploy on device, do it
|
||
(build + `adb install`). That's the one action you may take on your own.
|
||
- **Do nothing else on the device unprompted.** Do not launch the app, take
|
||
screenshots, dump/read logcat, poke UI, or otherwise "test" or verify on the
|
||
device on your own initiative — even to confirm a change works.
|
||
- Read logs, capture screenshots, and inspect on-device behaviour **only when the
|
||
user explicitly asks for it, each time.** The user drives when it's time to
|
||
look; wait for that instruction.
|
||
|
||
## UI / design conventions
|
||
|
||
- Material 3 Expressive throughout. Consult the `material-3` skill before
|
||
designing anything new; prefer M3 tokens/components over hardcoded colours.
|
||
- **Selection pickers are full-screen** — browse-style "choose one" surfaces
|
||
(visibility, reminder, recurrence rule, colour, calendar, add-field, plus the
|
||
Settings pickers) use floret-kit's `FullScreenPicker` / `OptionPicker` (a
|
||
full-bleed sheet with a pinned title bar and connected grouped rows); a picker
|
||
that needs a commit/extra action passes it via the picker's `actions` slot.
|
||
The exception is the **recurring scope choosers** — the "this / this & following
|
||
/ all" prompts shown when you *save an edit to*, *drag* or *delete* a recurring
|
||
event — which stay compact `OptionCard`-in-`AlertDialog` popups (a quick 2–3 option
|
||
decision reads better as a popup than a near-empty full screen). `AlertDialog`
|
||
is otherwise only for plain confirmations. Radio/text-list dialogs are banned.
|
||
|
||
## Releases
|
||
|
||
The committed `versionCode` / `versionName` in `app/build.gradle.kts` **are the
|
||
release trigger**: merging a bumped `versionName` into `main` runs
|
||
`.gitea/workflows/release.yaml`, which builds, signs, publishes to the
|
||
self-hosted F-Droid repo, then mints the `vX.Y.Z` tag + release. `versionCode` is
|
||
pinned to `MAJOR*10000 + MINOR*100 + PATCH` (e.g. 2.13.0 → 21300). Full process
|
||
in `docs/RELEASING.md`. **Never tag/release UI changes before on-device review
|
||
and explicit go-ahead.**
|
||
|
||
Release builds are kept **F-Droid reproducible** — `vcsInfo`, `dependenciesInfo`,
|
||
and the AGP metadata block are deliberately disabled in `build.gradle.kts`; don't
|
||
re-enable them. Use the `releaseTest` build type (R8-shrunk twin, debug-signed,
|
||
own applicationId suffix) to smoke-test a release candidate on-device.
|
||
|
||
### Per-version changelogs are written by hand, in every locale
|
||
|
||
`fastlane/metadata/android/<locale>/changelogs/<versionCode>.txt` is the
|
||
"What's New" both F-Droid and Play publish. **There is no auto-translation
|
||
layer** — Weblate owns `values-*/strings.xml` only, not the fastlane tree — so
|
||
when cutting a release you write these files yourself, one per locale, each a
|
||
short summary under **500 characters** (Play's hard cap, applied per locale;
|
||
F-Droid truncates in-client).
|
||
|
||
Write one for **every language the app ships** (`app/src/main/res/values-*`),
|
||
using store locale codes: `en-US`, `en-GB`, `de-DE`, `es-ES`, `fr-FR`, `it-IT`,
|
||
`pl-PL`, `pt-PT`, `ru-RU`, `zh-CN`, `ar`. Missing locales aren't fatal — both
|
||
stores fall back — but see the `en-GB` trap below.
|
||
|
||
**`en-GB` is the Play Console's default language.** Play's fallback is the
|
||
*default locale*, not `en-US`, so a release with only an `en-US` changelog ships
|
||
with **no "What's New" at all** — this is why the latest release had none.
|
||
`en-GB` must exist.
|
||
|
||
The rest of the plumbing is already locale-agnostic: `sync_changelog_to_fastlane.sh`
|
||
seeds `en-US` only and never overwrites a committed file, while
|
||
`fastlane_to_fdroid_localized.sh` and `supply` pick up every locale that has a
|
||
`changelogs/` dir. So the files are the whole job.
|
||
|
||
## Forge / `tea` CLI
|
||
|
||
**Codeberg is canonical** (`codeberg.org/jlmakiola/calendula`) for git, issues,
|
||
PRs, tags and releases — including the `floret-kit` submodule. The self-hosted
|
||
Gitea instance is **build infrastructure only**: signing key, F-Droid publishing,
|
||
release pipeline.
|
||
|
||
Use the **`tea` CLI** for forge interaction — not raw API calls. Note the flag is
|
||
a *subcommand* flag, not global: `tea pulls list --login codeberg`, never
|
||
`tea --login codeberg pulls list`. Two accounts, neither default:
|
||
|
||
- **Everything → `codeberg`** (user `jlmakiola`). PRs, issues, releases, repo
|
||
settings. `tea pulls create --login codeberg ...`
|
||
- **Build infra only → `jeanluc`** (`gitea.jeanlucmakiola.de`, user `makiolaj`).
|
||
Release-pipeline runs, Actions secrets. `tea ... --login jeanluc`
|
||
|
||
Workflows are split by directory and this is load-bearing — Forgejo's lookup is
|
||
first-match-wins across `.forgejo/` → `.gitea/` → `.github/`:
|
||
|
||
- `.forgejo/workflows/` runs on **Codeberg** (`ci.yaml`, `translations.yaml`) and
|
||
must reference **no secrets** — that's what makes fork PRs safe.
|
||
- `.gitea/workflows/` runs on **Gitea** (`release.yaml`, `renovate.yml`) and is
|
||
where every secret lives.
|
||
|
||
Don't add a workflow without deciding which side it belongs on.
|
||
|
||
## Translations
|
||
|
||
Community translations are managed on a self-hosted **Weblate**, which owns all
|
||
`values-*` files (including German — API only, never hand-edit). Partial
|
||
translations are expected (`MissingTranslation` is informational, not fatal);
|
||
extra/stale keys stay fatal.
|
||
|
||
|
||
## Git Operations
|
||
|
||
Use a commit format which references the issues, dont add any Co-Authered by Claude lines, and don't write extensive commit and merge messages, simple human ones suffice, for prs, stuff like testing etc, isnt interesting write what has chnaged, and if deviated from the underling issues pls explain why, add a closes issue line at the end of all prs.
|
||
|
||
## Comments
|
||
|
||
Don't add extensive code comments, methode discription, so as a Java Doc is fine, but no extensive explanbanitory conmments
|