feat(todo): a task holds reminders at a time of their own, and keeps the fields it doesn't know

ReminderTime is a day and minutes after midnight, the wall clock without
a zone as TaskDate's; a task's reminderTimes are written as
[{"day", "time"}] and left out where there are none, so older payloads
read with none. The payload's schema stays 1 (as D17's view): what a
newer version wrote at its top that this one doesn't read is kept in
TaskContent.unknown and written back beneath the fields it knows, so a
task changed here keeps it (ADR 0033, D18).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
This commit is contained in:
Tizian 「ティツィアーン」 2026-11-03 13:38:37 +01:00
commit 3c68a22a5f
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -43,6 +43,10 @@ void main() {
priority: Priority.p2,
labelIds: const ['l1', 'l2'],
reminders: const [30, 0],
reminderTimes: const [
ReminderTime(Day(2026, 11, 1), 20 * 60),
ReminderTime(Day(2026, 11, 2), 6 * 60),
],
completedAt: DateTime(2026, 11, 2, 7),
completions: [DateTime(2026, 10, 19, 7, 5), DateTime(2026, 10, 22, 6)],
hideDoneSubTasks: true,
@ -80,6 +84,10 @@ void main() {
'priority': 2,
'labelIds': ['l1', 'l2'],
'reminders': [30, 0],
'reminderTimes': [
{'day': '2026-11-01', 'time': 1200},
{'day': '2026-11-02', 'time': 360},
],
'completedAt': DateTime(2026, 11, 2, 7).toUtc().toIso8601String(),
'completions': [
DateTime(2026, 10, 19, 7, 5).toUtc().toIso8601String(),
@ -136,6 +144,16 @@ void main() {
'date': {'day': '2026-11-02', 'repeat': repeat},
},
{...base, 'date': 'today'},
for (final at in [
{'day': '2026-11-02'},
{'day': 'soon', 'time': 60},
{'day': '2026-11-02', 'time': 1440},
'2026-11-02 13:00',
])
{
...base,
'reminderTimes': [at],
},
]) {
expect(decodeTask(json(broken)), isNull, reason: '$broken');
}
@ -159,6 +177,55 @@ void main() {
}
});
test('of the schema before reminders at a time of their own reads with '
'none (D18)', () {
final older = decodeTask(
json({
'schemaVersion': 1,
'title': 'A',
'projectId': 'p',
'date': {'day': '2026-11-02', 'time': 540},
'reminders': [30],
}),
);
expect(older?.reminders, [30]);
expect(older?.reminderTimes, isEmpty);
expect(older?.unknown, isEmpty);
});
test('what a newer version wrote that this one doesn’t know is kept, '
'and written back as it was, however the task changes here (D18)', () {
final newer = {
'schemaVersion': 1,
'title': 'A',
'projectId': 'p',
'location': {'lat': 52.5, 'lon': 13.4},
'note': 'kept',
};
final read = decodeTask(json(newer))!;
expect(read.unknown, {
'location': {'lat': 52.5, 'lon': 13.4},
'note': 'kept',
});
final renamed = read.copyWith(title: 'B');
expect(written(encodeTask(renamed)), {
...newer,
'title': 'B',
'position': 0.0,
});
expect(decodeTask(encodeTask(renamed)), renamed);
});
test('a field it knows wins over one kept as unknown of the same name', () {
const task = TaskContent(
title: 'A',
projectId: 'p',
unknown: {'title': 'stale', 'extra': 1},
);
expect(written(encodeTask(task))['title'], 'A');
expect(written(encodeTask(task))['extra'], 1);
});
test('a priority it doesn’t know is none', () {
expect(
decodeTask(