Files
Ben Gotow 9eea595a15 Refactor calendar event editing with ICS-based recurrence support (#2637)
* Fix calendar event deselection and recurrence editing

1. Clicking the calendar background now deselects any selected events
   and closes the popover. CalendarEvent stops click propagation so
   CalendarEventContainer's new onClick handler only fires for
   background clicks.

2. Clicking a different event while a popover is open now closes the
   popover via Actions.closePopover() in _onEventClick.

3. Changing an event's repeat/recurrence rule (e.g. None → Every Day)
   now saves correctly. The saveEdits flow applies title, location,
   description, time, and recurrence changes to the ICS data before
   queuing the syncback task. Previously only time changes were saved.

4. New events also pass the selected recurrence rule through to
   createCalendarEvent → createICSString.

5. When entering edit mode on an existing event, the repeat dropdown
   is initialized from the event's actual RRULE rather than defaulting
   to 'none'.

https://claude.ai/code/session_01LmE6MF6cLR4zdc7eA48ATW

* Fix attendees and timezone not saving in calendar event editor

Attendees:
- Add updateAttendees() to ics-event-helpers.ts that replaces all
  ATTENDEE properties on a VEVENT
- Wire saveEdits to apply attendee changes to the ICS for existing
  events (previously only new events saved attendees)

Timezone:
- Add getEventTimezone() helper to extract the TZID from an event's
  DTSTART property
- Add optional timezone field to UpdateTimesOptions; when provided,
  updateEventTimes encodes wall-clock time in the target timezone
  with a proper VTIMEZONE component (mirrors createICSString logic)
- Wire timezone through createCalendarEvent so new events use the
  selected timezone instead of hard-coded DateUtils.timeZone
- Wire timezone into saveEdits for existing events
- Initialize the timezone selector from the event's actual TZID when
  entering edit mode (was hard-coded to system timezone)
- Fix render() to call this.onEdit instead of inline setState so
  the ICS data loading (recurrence + timezone) actually runs

https://claude.ai/code/session_01LmE6MF6cLR4zdc7eA48ATW

* Fix review issues: orphaned EXDATEs and VTIMEZONE RFC compliance

- updateRecurrenceRule now removes EXDATE and RDATE properties when
  the RRULE is removed (recurring → non-recurring). These properties
  are meaningless without an RRULE per RFC 5545.

- Fix VTIMEZONE DTSTART to use local time (no Z suffix) per RFC 5545
  section 3.6.5. The Z suffix incorrectly marks it as UTC, which
  stricter CalDAV servers may reject.

https://claude.ai/code/session_01LmE6MF6cLR4zdc7eA48ATW

* Fix 5 pre-existing issues found in calendar code review

- Add missing DateUtils import in QuickEventPopover (was causing
  ReferenceError at runtime)
- Add missing isRecurring property on newEventOccurrence in
  _onCalendarDoubleClick (satisfies EventOccurrence interface)
- Memoize readOnlyCalendarIds as state computed when calendars change,
  avoiding new Set allocation on every render
- Add proper TypeScript types to CalendarEventContainer event handlers
  (MouseEvent/React.MouseEvent) and _dataFromMouseEvent parameter
- Remove dead code in _onCalendarMouseDown (unreachable condition
  since mouseIsDown is always true in mouseDown handler)

https://claude.ai/code/session_01LmE6MF6cLR4zdc7eA48ATW

* Fix PR feedback: mouseUp crash, SEQUENCE increment, recurring popover edits

1. Fix _onWindowMouseUp crash: the handler passed a native MouseEvent
   to _onCalendarMouseUp which now expects React.MouseEvent and
   accesses .nativeEvent (undefined on native events). Inline the
   mouseUp logic directly in _onWindowMouseUp to work with the native
   event.

2. Add SEQUENCE increment to updateRecurrenceRule: the other ICS
   mutation functions (updateEventTimes, addExclusionDate) already
   increment SEQUENCE, but updateRecurrenceRule did not. Per RFC 5545,
   SEQUENCE must be incremented on significant organizer changes.

3. Add recurring event support to popover saveEdits: the popover now
   shows the "this occurrence only" vs "all occurrences" dialog when
   editing a recurring event. "This occurrence" creates an exception
   event with the modified properties (title, location, description,
   attendees, times). "All occurrences" modifies the master event
   directly. Previously the popover always modified the master without
   prompting.

https://claude.ai/code/session_01LmE6MF6cLR4zdc7eA48ATW

* Fix recurring event exceptions: EXDATE TZID, expansion fallback, exception parsing

Root cause of "all occurrences disappear" bug: ICAL.js's
addPropertyWithValue('exdate', time) does NOT set the TZID parameter
on the property, even when the ICAL.Time has a timezone attached.
The EXDATE was serialized as floating time (e.g., EXDATE:20240103T090000)
while DTSTART used a TZID (e.g., DTSTART;TZID=America/New_York:...).
ical-expander could not match the floating EXDATE against the zoned
occurrences, so the exclusion was silently ignored or caused a parse
failure that produced zero occurrences.

Fix: new addExdateProperty() helper that manually creates the ICAL
Property and sets the TZID parameter when the event uses a timezone.
Updated both createRecurrenceException() and addExclusionDate() to
use this helper.

Also:
- CalendarDataSource now shows a fallback occurrence when ICS expansion
  fails, instead of silently producing zero occurrences
- Standalone exception events (separate DB records with recurrenceId)
  are now parsed directly with ICAL.js instead of ical-expander, since
  they are single-instance events with no recurrence to expand

https://claude.ai/code/session_01LmE6MF6cLR4zdc7eA48ATW

* Update lockfiles after npm install

https://claude.ai/code/session_01LmE6MF6cLR4zdc7eA48ATW

* Working

* Indent feedback

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-03-08 19:02:45 -05:00
..