diff --git a/src/lib/server/ticktick.test.ts b/src/lib/server/ticktick.test.ts index 5b409a9..1b3c4fc 100644 --- a/src/lib/server/ticktick.test.ts +++ b/src/lib/server/ticktick.test.ts @@ -57,10 +57,13 @@ describe('ticktick mapping', () => { completedTime: '2025-01-02T09:00:00.000+0000', items: [ { id: 'i1', title: 'milk', status: 1, completedTime: '2025-01-02T08:00:00.000+0000' }, - { id: 'i2', title: 'eggs', status: 0 } + { id: 'i2', title: 'eggs', status: 0 }, + { id: 'i3', title: 'bread', status: 1, completedTime: 978307200000 } ] } as Parameters[0]); - expect(rows).toHaveLength(3); + expect(rows).toHaveLength(4); + // Placeholder completion (2001) falls back to the task's completion. + expect(rows[3].completedAt?.toISOString()).toBe('2025-01-02T09:00:00.000Z'); expect(rows[0]).toMatchObject({ title: 'Groceries', notes: 'weekly', diff --git a/src/lib/server/ticktick.ts b/src/lib/server/ticktick.ts index 86b6e79..fb1b26a 100644 --- a/src/lib/server/ticktick.ts +++ b/src/lib/server/ticktick.ts @@ -186,6 +186,12 @@ export function mapTask(t: TtTask): MappedRow[] { updatedAt: modified, ttSortOrder: t.sortOrder ?? 0 }; + // Some old checklist items carry a placeholder completedTime (2001-01-01): ignore any + // completion before the task existed. + const itemDone = (v: string | number | undefined) => { + const d = ttDate(v); + return d && d.getTime() >= created.getTime() - 86_400_000 ? d : null; + }; const items = (t.items ?? []).map((i): MappedRow => { // Checklist status: 0 open, 1 (or 2) checked. Unchecked items of a finished task // follow the task (done → done, won't do → dropped) so they don't resurface as open. @@ -205,7 +211,7 @@ export function mapTask(t: TtTask): MappedRow[] { reminderAt: null, recurrence: null, completedAt: checked - ? (ttDate(i.completedTime) ?? row.completedAt ?? modified) + ? (itemDone(i.completedTime) ?? row.completedAt ?? modified) : status === 'dropped' ? row.completedAt : null,