From dbe60dd33140707c5b4dfd4ff4e3aaf4f23a5721 Mon Sep 17 00:00:00 2001 From: mARTin Date: Tue, 24 Mar 2026 11:55:39 +0100 Subject: [PATCH] fix: deduplicate recurring event masters vs expanded instances Recurring events appeared twice on their creation day because both the series master (from POST cache) and the expanded instance (from sync) had different IDs. Now filters out masters when instances exist, and also deduplicates by title+startTime+calendarId as a fallback. v1.66.1 Co-Authored-By: Claude Opus 4.6 --- package.json | 2 +- src/components/WeeklyView.tsx | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index c4e695a..bbbb992 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "my-weekly-todo-list", - "version": "1.66.0", + "version": "1.66.1", "description": "A web-based weekly task management application that organizes to-dos and calendar events in a single, intuitive weekly view", "main": "index.js", "scripts": { diff --git a/src/components/WeeklyView.tsx b/src/components/WeeklyView.tsx index 1f960f1..2695503 100644 --- a/src/components/WeeklyView.tsx +++ b/src/components/WeeklyView.tsx @@ -1613,11 +1613,29 @@ export default function WeeklyView() { const [weatherData, setWeatherData] = useState>({}); // Extend events with editable flag from connections, deduplicate by id + // Also deduplicate recurring series masters vs expanded instances: + // When a recurring event is created, the master is cached. Then the sync + // returns expanded instances with different IDs but the same recurringEventId. + // We keep instances and discard masters that overlap with them. const calendarEvents = useMemo(() => { const seen = new Set(); + const seenSlot = new Set(); + // Collect recurring event IDs that have expanded instances + const seriesWithInstances = new Set(); + for (const event of rawCalendarEvents) { + if (event.recurringEventId && event.id !== event.recurringEventId) { + seriesWithInstances.add(event.recurringEventId); + } + } return rawCalendarEvents.filter((event) => { if (seen.has(event.id)) return false; seen.add(event.id); + // Skip series master if expanded instances exist for this series + if (seriesWithInstances.has(event.id)) return false; + // Deduplicate by title+startTime+calendarId (catches optimistic add + cache read) + const slotKey = `${event.title}|${event.startTime}|${event.calendarId}`; + if (seenSlot.has(slotKey)) return false; + seenSlot.add(slotKey); return true; }).map((event) => { let isEditable = false;