Skip to content

Commit b2c76ce

Browse files
KrisBraunclaude
andcommitted
fix(google): sync task due dates as to-do dates, not link schedules
Google Tasks were synced with a link `schedules` entry built from the task's due date. Link schedules are shared, calendar-event-shaped timing, so tasks incorrectly rendered as agenda events — and because a due date has no end time, the schedule was open-ended, which agenda views handle poorly. Tasks are personal to-dos: an open task now sets `todo: true` on the saved link (with `todoDate` when the task has a due date), which files it into the connection owner's to-do list scheduled on its due date. Completed tasks are unchanged — the "done" status already clears the owner's to-do state. Adds transformTask unit tests covering due-date, no-due-date, and completed tasks. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent b429849 commit b2c76ce

2 files changed

Lines changed: 58 additions & 6 deletions

File tree

‎connectors/google/src/tasks/sync.test.ts‎

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ import {
1919
syncBatchFn,
2020
periodicSyncBatchFn,
2121
onLinkUpdatedFn,
22+
transformTask,
2223
type SyncState,
2324
type PeriodicSyncState,
2425
type TasksSyncHost,
@@ -175,3 +176,50 @@ describe("onLinkUpdatedFn — deleted task/list (404)", () => {
175176
).rejects.toThrow(/500/);
176177
});
177178
});
179+
180+
describe("transformTask — to-do mapping (no link schedules)", () => {
181+
const baseTask = {
182+
id: "task-1",
183+
title: "Buy milk",
184+
status: "needsAction" as const,
185+
updated: "2026-07-01T12:00:00.000Z",
186+
position: "0001",
187+
selfLink: "https://tasks.googleapis.com/tasks/v1/task-1",
188+
};
189+
190+
it("maps an open task with a due date to todo + todoDate", () => {
191+
const link = transformTask(
192+
{ ...baseTask, due: "2026-07-15T00:00:00.000Z" },
193+
LIST_ID,
194+
false,
195+
[],
196+
null
197+
);
198+
expect(link.todo).toBe(true);
199+
expect(link.todoDate).toBe("2026-07-15");
200+
// Tasks must never carry link schedules: those are shared,
201+
// calendar-event-shaped, and would render the task in the agenda.
202+
expect(link.schedules).toBeUndefined();
203+
});
204+
205+
it("maps an open task without a due date to todo (Now bucket)", () => {
206+
const link = transformTask(baseTask, LIST_ID, false, [], null);
207+
expect(link.todo).toBe(true);
208+
expect(link.todoDate).toBeUndefined();
209+
expect(link.schedules).toBeUndefined();
210+
});
211+
212+
it("leaves to-do state untouched for completed tasks (done status handles it)", () => {
213+
const link = transformTask(
214+
{ ...baseTask, status: "completed", due: "2026-07-15T00:00:00.000Z" },
215+
LIST_ID,
216+
false,
217+
[],
218+
null
219+
);
220+
expect(link.status).toBe("done");
221+
expect(link.todo).toBeUndefined();
222+
expect(link.todoDate).toBeUndefined();
223+
expect(link.schedules).toBeUndefined();
224+
});
225+
});

‎connectors/google/src/tasks/sync.ts‎

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -523,13 +523,17 @@ export function transformTask(
523523
status: task.status === "completed" ? "done" : "open",
524524
notes,
525525
preview: task.notes?.slice(0, 200) || null,
526-
...(task.due
526+
// Google Tasks are the user's personal to-dos, never calendar events:
527+
// surface an open task as the connection owner's to-do (per-user
528+
// thread_state), with the due date as its to-do date when present. A
529+
// link `schedules` entry is wrong here — it creates a *shared* schedule
530+
// with calendar-event semantics, which renders the task in the agenda.
531+
// Completed tasks need nothing: the "done" status already clears the
532+
// assignee's to-do state via the platform's done-status handling.
533+
...(task.status !== "completed"
527534
? {
528-
schedules: [
529-
{
530-
start: task.due.split("T")[0],
531-
},
532-
],
535+
todo: true,
536+
...(task.due ? { todoDate: task.due.split("T")[0] } : {}),
533537
}
534538
: {}),
535539
...(initialSync ? { unread: false } : {}),

0 commit comments

Comments
 (0)