Skip to content

Conversation

@christenbc
Copy link

@christenbc christenbc commented Jan 27, 2026

Summary

This PR fixes two related issues with recurrence handling and Google Calendar sync:

  1. Time preservation: When setting a new recurrence on a task with a scheduled time, the time component was lost, causing Google Calendar events to appear as all-day instead of at the specified time.

  2. Recurrence clearing: When clearing recurrence from a task synced to Google Calendar, the recurrence rule wasn't properly removed from the calendar event.

Problem

Issue 1: Time Loss in Recurrence

Before:

  • Task has scheduled: 2026-01-27T18:39 (with time)
  • User sets weekly recurrence
  • Recurrence created: DTSTART:20260127;FREQ=WEEKLY (date-only)
  • Google Calendar shows event as all-day ❌

After:

  • Task has scheduled: 2026-01-27T18:39 (with time)
  • User sets weekly recurrence
  • Recurrence created: DTSTART:20260127T183900Z;FREQ=WEEKLY (with time)
  • Google Calendar shows event at 18:39 ✓

Issue 2: Recurrence Not Cleared from Google Calendar

Before:

  • Task has recurrence synced to Google Calendar
  • User clears recurrence from task
  • Google Calendar event still shows as recurring ❌

After:

  • Task has recurrence synced to Google Calendar
  • User clears recurrence from task
  • Google Calendar event updated with recurrence: [] to remove recurrence ✓

Root Causes

Issue 1: Time Preservation

RecurrenceContextMenu.getRecurrenceOptions() only checked for time in existing recurrence rules (currentValue). When setting a new recurrence on a task with scheduled time but no existing recurrence, it didn't check the task's scheduled date for time information.

Issue 2: Recurrence Clearing

TaskCalendarSyncService.taskToCalendarEvent() omitted the recurrence property entirely when a task had no recurrence. Google Calendar API requires an explicit empty array (recurrence: []) to remove existing recurrence rules.

Solution

Fix 1: Preserve Time from Scheduled Date

  1. Enhanced RecurrenceContextMenuOptions interface to include scheduledDate?: string
  2. Updated getRecurrenceOptions() to check scheduled date for time when no existing recurrence time exists:
    • Priority 1: Preserve time from existing recurrence (if present)
    • Priority 2: Extract time from task's scheduled date (if present)
  3. Updated CustomRecurrenceModal to accept and use scheduled date, pre-filling time field
  4. Updated all call sites (5 files) to pass task.scheduled when creating RecurrenceContextMenu

Fix 2: Explicitly Clear Recurrence

  1. Added previousTaskState map to track previous task state for detecting recurrence removal
  2. Modified taskToCalendarEvent() to accept clearRecurrence parameter
  3. Updated syncTaskToCalendar() to detect when recurrence was removed and pass clearRecurrence: true
  4. Set recurrence: [] explicitly when clearing recurrence to tell Google Calendar API to remove it

Changes Made

Files Modified

  1. src/components/RecurrenceContextMenu.ts

    • Added scheduledDate to RecurrenceContextMenuOptions interface
    • Updated getRecurrenceOptions() to check scheduled date for time
    • Updated CustomRecurrenceModal constructor and parseCurrentValue()
    • Updated showCustomRecurrenceModal() to pass scheduled date
  2. src/services/TaskCalendarSyncService.ts

    • Added previousTaskState map for tracking previous task state
    • Modified taskToCalendarEvent() to accept clearRecurrence parameter
    • Updated syncTaskToCalendar() to detect recurrence removal
    • Updated updateTaskInCalendar() and executeTaskUpdate() to track and pass previous state
  3. src/ui/TaskCard.ts

    • Added scheduledDate: task.scheduled when creating RecurrenceContextMenu
  4. src/bases/TaskListView.ts

    • Added scheduledDate: task.scheduled when creating RecurrenceContextMenu
  5. src/bases/KanbanView.ts

    • Added scheduledDate: task.scheduled when creating RecurrenceContextMenu
  6. src/components/TaskContextMenu.ts

    • Added scheduledDate: this.options.task.scheduled when creating RecurrenceContextMenu
  7. src/modals/TaskModal.ts

    • Added scheduledDate: this.scheduledDate when creating RecurrenceContextMenu

Test Files Created

  • e2e/issues/issue-clear-recurrence-google-calendar.spec.ts - Tests for recurrence clearing
  • e2e/issues/issue-preserve-time-in-recurrence.spec.ts - Tests for time preservation

Testing

Time Preservation:

  1. Create task with scheduled: 2026-01-27T18:39
  2. Set recurrence via quick option (e.g., "Weekly on Monday")
  3. Verify DTSTART includes time: DTSTART:20260127T183900Z
  4. Check Google Calendar shows event at 18:39, not all-day

Recurrence Clearing:

  1. Create recurring task synced to Google Calendar
  2. Clear recurrence from task
  3. Verify Google Calendar event is updated (no longer recurring)
  4. Verify event ID remains in task (event updated, not deleted)

Custom Recurrence Modal:

  1. Open custom recurrence modal for task with scheduled time
  2. Verify time field is pre-filled from scheduled date
  3. Set recurrence and verify time is preserved

Impact

User Experience

  • ✅ Recurring events maintain correct times in Google Calendar
  • ✅ Clearing recurrence properly updates Google Calendar events
  • ✅ Custom recurrence modal pre-fills time from scheduled date
  • ✅ Consistent behavior across all views (TaskCard, TaskListView, KanbanView, TaskModal)

Technical

  • ✅ Proper DTSTART format with time component (YYYYMMDDTHHMMSSZ)
  • ✅ Explicit recurrence clearing via empty array (recurrence: [])
  • ✅ State tracking for detecting recurrence changes
  • ✅ Backward compatible (existing recurrences continue to work)

Related Issues

Fixes issues where:

  • Setting recurrence on tasks with scheduled times loses time information
  • Clearing recurrence doesn't update Google Calendar events properly
  • Google Calendar shows all-day events instead of timed events

Checklist

  • Code compiles without errors
  • No linter errors
  • All call sites updated to pass scheduled date
  • Time preservation logic implemented
  • Recurrence clearing logic implemented

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant