Files
agendula/app/src/test/resources/vtodo/README.md
T
7c943f4831
Beta — Codeberg pre-release / detect (push) Successful in 5s
Beta — Codeberg pre-release / beta (push) Skipped
Start-based reminders and timed all-day reminders (#72, #78) (#88)
Reminders can count from a task's start as well as its due date (#72), and each reminder on an all-day task can have its own time (#78).

## Changes
- A reminder counts from the start or the due date. Tasks with only a start date get reminders. Defaults in Settings can mix both, globally and per list.
- Each reminder on an all-day task can have its own time of day. Reminders without one ring at the all-day time set in Settings. 00:00 can't be stored, so it becomes 00:01.
- The editor shows the defaults a new task, or a newly added date, will get, and keeps them in step with dates, list and all-day until you change a reminder. What it shows is what gets saved. Removing the shown defaults now leaves the task without a reminder (Room store only; OpenTasks mode still falls back to the default).
- Switching between timed and all-day converts the reminders right away in the editor. Switching straight back restores them.
- Labels name the date when a task has both: "15 minutes before the start", "On the due date at 09:00". New reminders preselect the start.
- Defaults that have already passed don't ring when the task is saved.
- Sync: reminders rounded to whole days by builds before 1.2 pick up their real time of day on the next download.

## Deviations from the issues
- The start/due anchor lives in floret-kit (`ReminderLead`, floret-kit#19) rather than being packed into minute values. That PR is additive, so Calendula is unaffected.
- `error_reminder_without_due` was renamed to `error_reminder_without_date`, and the "(before start)/(before due)" strings were replaced by full sentences. The stale keys were deleted from the translation files so lint passes, and Weblate needs to offer the new strings to translators.
- The dead single-reminder API (`setAlarm`, `alarms()`, `editable()`, `reminderFor`) was removed.
- `AlarmLift` is back to its frozen 2→3 migration state.

## Release branch
`release/v1.2.0` is merged into this branch. Through it, this PR also brings main's commits the release branch was missing, including the reminder sync from #81 that this work builds on. I resolved conflicts in the data source interface, `TaskAlarmDao`, CHANGELOG, PRIVACY.md and the floret-kit pointer by keeping both sides.

Closes #72
Closes #78

Co-authored-by: Weblate <noreply-mt-weblate@weblate.org>
Co-authored-by: Sup99999 <alelmnour1o234@proton.me>
Co-authored-by: Robin Schanbacher <robin@schanbros.de>
Co-authored-by: Knee8811 <mk-weby@protonmail.com>
Co-authored-by: Sonja Kraus <klein.felix@pm.me>
Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: Jean-Luc Makiola <business@jeanlucmakiola.de>
Reviewed-on: https://codeberg.org/jlmakiola/agendula/pulls/88
2026-10-21 18:40:28 +02:00

3.8 KiB

VTODO fixture corpus

The mapper's specification, not its regression net. Every file here is a shape that was found in the wild and that a plausible implementation gets wrong. A clean, server-generated VTODO catches nothing and is deliberately absent.

RoundTripTest asserts every fixture survives parse → map → write → serialise with no property lost, compared as a canonical multiset modulo an allowlist (PRODID, DTSTAMP, LAST-MODIFIED, SEQUENCE, VTIMEZONE bodies, fold positions, parameter quoting).

File What it is for
nextcloud-shared-confidential.ics A CLASS:CONFIDENTIAL task as Nextcloud rewrites it on GET from a shared calendar — DUE, STATUS, COMPLETED, PERCENT-COMPLETE, PRIORITY and RELATED-TO already stripped, ETag left untouched. Re-PUTting this destroys the owner's task
recurring-master-overrides.ics Master plus two RECURRENCE-ID overrides in one resource, sharing a UID
valarm-unknown-nested.ics Unknown properties inside a VALARM — the case a flat property-per-row model cannot represent. An Apple default alarm, so it stays in the residue
valarm-claimed-apple.ics The same Apple alarm without X-APPLE-DEFAULT-ALARM, so it is claimed as a reminder: RELATED=START on a task with only DUE (Apple's non-conformant shape) reads as a before-due lead, and the original must come back verbatim through the row's raw
unknown-component.ics An entirely unknown sub-component
unknown-tzid.ics A TZID no device tzdb knows, with its own VTIMEZONE
uid-special-chars.ics A UID containing / and @ — the href sanitiser must not assume it is filename-safe
rrule-due-no-dtstart.ics RRULE + DUE and no DTSTART: no well-defined RECURRENCE-ID, undefined in RFC 5545, ubiquitous in the wild
moz-alarm-props.ics X-MOZ-LASTACK / X-MOZ-SNOOZE-TIME. Dropping them causes documented Thunderbird alarm storms
apple-sort-order.ics X-APPLE-SORT-ORDER — preserve, never interpret
floating-time.ics DTSTART with neither Z nor TZID. Readable, not reproducible: it must round-trip from the residue, not be rewritten as a guess
completion-model-a.ics Completion as a RECURRENCE-ID override, master stays open — jtx Board, Thunderbird. The model we write
completion-model-b.ics Master's DUE advanced in place, completion cleared — tasks.org, Evolution, Nextcloud in practice
completion-model-c.ics STATUS:COMPLETED on the master, killing the series — Nextcloud Tasks ≤ 0.17. Always a bug; we read it, we never write it
completion-model-d.ics The completed occurrence detached as a new task with a new UID — OpenTasks
quoted-param-colon.ics A quoted parameter value containing a colon and a comma. The value separator is the first colon outside quotes
mixed-value-types.ics DTSTART;VALUE=DATE with a timed DUE. One is_all_day flag cannot author both, so the odd one out must stay in the residue rather than be flattened to a date
malformed-scalars.ics PRIORITY:11, PERCENT-COMPLETE:150, SEQUENCE:x, STATUS:X-DEFERRED, CLASS:X-INTERNAL — values no spec allows. Clamping or defaulting any of them is a silent rewrite of somebody's data
two-timezones.ics DTSTART and DUE in different zones. One timezone column cannot author both
folded-utf8.ics A line folded mid-emoji-adjacent, to prove folding counts octets and splits on character boundaries
categories-shapes.ics One CATEGORIES line with an escaped comma inside a value — the one shape the column claims, so it is re-authored and must come out identical
categories-split.ics One line per tag, a LANGUAGE parameter and a space after a comma. Read for display, but none of it is ours to rewrite