* Add implementation plan for calendar ICS helpers and recurring event dialog
This plan addresses two issues with calendar event drag persistence:
1. ICS data inconsistency - currently only cached fields are updated,
not the underlying ICS data. Plan includes a new ics-event-helpers.ts
module with functions to create, update, and modify ICS data.
2. Missing recurring event handling - no dialog asking whether to modify
this occurrence or all occurrences. Plan includes a confirmation
dialog component and integration with the drag persistence flow.
* Implement ICS helpers and recurring event dialog for calendar events
This change ensures calendar event modifications properly update the
underlying ICS data, not just the cached recurrenceStart/recurrenceEnd fields.
New files:
- ics-event-helpers.ts: Centralized helpers for ICS manipulation
- createICSString: Create new ICS events from scratch
- updateEventTimes: Update DTSTART/DTEND in existing ICS
- createRecurrenceException: Create EXDATE + exception event
- updateRecurringEventTimes: Shift entire recurring series
- isRecurringEvent/getRecurrenceInfo: Query recurrence status
- recurring-event-dialog.ts: Dialog asking "this occurrence only"
vs "all occurrences" when modifying recurring events
Modified files:
- mailspring-calendar.tsx: Updated _persistDragChange to use ICS helpers
and show recurring event dialog. Added separate methods for simple
event changes, single occurrence exceptions, and all-occurrences updates.
Undo/redo now properly restores ICS data.
- calendar-event-popover.tsx: Updated saveEdits to use ICS helpers
and handle recurring events with the same dialog flow.
- mailspring-exports.js/d.ts: Export ICSEventHelpers module
Fixes the issue where dragging events appeared to move them but the
underlying ICS data was not updated, causing sync inconsistencies.
* Add remaining work plan for calendar ICS implementation
Documents the prioritized list of remaining issues identified during
code review, including:
- High: recurring event deletion, quick event ICS generation, undo fix
- Medium: deprecated lifecycle, race conditions, timezone handling
- Low: validation, null checks, localization
Includes implementation details and recommended order of work.
* Add ICS generation, recurring deletion dialog, and fix deprecated lifecycle
Three improvements to calendar event handling:
1. Quick Event Creation (quick-event-popover.tsx):
- Now generates proper ICS data using ICSEventHelpers.createICSString()
- Sets ics and icsuid fields on new Event models
- Exported generateUID() from ics-event-helpers.ts
2. Recurring Event Deletion (mailspring-calendar.tsx):
- Added recurring event dialog to _onDeleteSelectedEvents
- "This occurrence only" adds EXDATE to master event
- "All occurrences" deletes the entire series
- Added addExclusionDate() helper to ics-event-helpers.ts
- Split deletion logic into _deleteEvent, _deleteOccurrence, _deleteEntireEvent
3. React Lifecycle Fix (mailspring-calendar.tsx):
- Changed deprecated componentWillMount to componentDidMount
- Improves React 18 compatibility
* Improve timezone handling and add validation in ICS helpers
Timezone improvements:
- createICALTime now properly preserves timezone from original events
- Uses convertToZone for correct timezone conversion instead of just
attaching zone after creation
- formatDateOnly now uses local date (not UTC) for all-day events to
prevent off-by-one day errors
- createICSString now accepts optional timezone parameter
- createRecurrenceException preserves master event's timezone
Validation improvements:
- Added validateTimestamps helper to check for valid positive numbers
- Validates that end time is not before start time
- Added null checks for VEVENT component in all functions that need it
- Functions now throw descriptive errors for malformed ICS
Also renamed formatDateTime to formatDateTimeUTC for clarity.
* Replace any types with proper TypeScript interfaces and extract recurring event utility
- Add UndoBlock interface to UndoRedoStore with public queueUndoBlock method
- Export UndoBlock from mailspring-exports for external use
- Replace `any[]` attendees type with EventAttendee interface
- Fix HTMLMetaElement cast in extractNotesFromDescription
- Add proper types to EventAttendeesInput props and methods
- Create recurring-event-actions.ts shared utility with:
- modifySimpleEvent: Update non-recurring events
- createOccurrenceException: Create exception for single occurrence
- modifyAllOccurrences: Shift entire recurring series
- modifyEventWithRecurringSupport: Main entry point with dialog handling
- Refactor mailspring-calendar.tsx to use shared utility (removes ~90 lines)
- Refactor calendar-event-popover.tsx to use shared utility (removes ~70 lines)
- Remove duplicate recurring event handling code from both components
* Use task-based undo for calendar events instead of custom UndoBlock
Following the established pattern from SyncbackMetadataTask, this change
enhances SyncbackEventTask to support undo by storing original event data:
- Add undoData and taskDescription attributes to SyncbackEventTask
- Add canBeUndone getter that returns true when undoData is provided
- Implement createUndoTask() that swaps current/original state (like redo)
- Update forUpdating() to accept undoData and description parameters
Update recurring-event-actions.ts to use task-based undo:
- Capture event snapshot before modifications
- Pass undoData to SyncbackEventTask.forUpdating() calls
- Pass description for undo toast display
Remove custom UndoBlock from UndoRedoStore:
- Remove exported UndoBlock interface
- Remove queueUndoBlock method
- Keep internal UndoBlock type for task-based undo
Update mailspring-calendar.tsx:
- Remove manual _registerUndoAction method
- Update keyboard handler to pass undoData to task
- Update drag handler to pass description to shared utility
- Undo is now automatically handled by UndoRedoStore._onQueue
This aligns with how other undoable tasks work (ChangeStarredTask,
SyncbackMetadataTask) and removes the need for custom undo handling.
* Add undo-redo task pattern documentation for future implementers
- Create docs/undo-redo-task-pattern.md with comprehensive guide
- Document toggle pattern (ChangeStarredTask) and snapshot pattern
- Include implementation steps, anti-patterns, and related files
- Update CLAUDE.md to reference the new documentation
* Fix recurring event handling and undo/redo issues in calendar
- Keyboard events (arrow keys) now show recurring event dialog like drag does
- Single occurrence deletion now supports undo via undoData
- Add newData snapshot to SyncbackEventTask for reliable redo
- Override createIdenticalTask to use captured snapshots
Fixes three PR review findings:
1. Arrow key changes bypassed recurring event dialog
2. _deleteOccurrence didn't pass undoData (not undoable)
3. Redo now uses captured snapshots instead of potentially stale event refs
* Improve TypeScript typing and add user error feedback
- Add type annotation to constructor props parameter
- Show error dialogs when calendar operations fail (delete, keyboard move, drag)
- Add documentation about Event.clone() deep clone requirement for undo/redo
---------
Co-authored-by: Claude <noreply@anthropic.com>
* Fix RSVP calendar event handling bugs
- Add missing return after error dialog in _onRSVP to prevent queuing
task with null organizer email
- Improve emailFromParticipantURI to parse more URI formats including
bare emails, non-mailto URIs, and embedded email patterns
- Add handling for CANCEL method to show cancellation notice instead
of RSVP buttons when event is cancelled
- Add styling for cancelled event notice
* Add EventRSVPTask sync engine specification document
Documents the RFC 5546 (iTIP) and RFC 6047 (iMIP) requirements for
properly formatting RSVP reply emails. Includes:
- Required MIME message structure with Content-Type parameters
- Required iCalendar properties for METHOD:REPLY
- Common implementation mistakes to avoid
- Complete examples and validation checklist
This specification is intended for the C++ mailsync codebase to
ensure RSVP replies are properly formatted and accepted by major
calendar providers.
* Fix EventRSVPTask to include only the replying attendee
Per RFC 5546, a METHOD:REPLY must have exactly one ATTENDEE - the
replying user. Previously, the code kept all attendees from the
original invite and only updated the self-participant's PARTSTAT.
Now we remove all other attendees from the VEVENT, keeping only
the self-participant with the updated participation status. This
satisfies the sync engine's input validation requirements.
* Fix bare email validation in emailFromParticipantURI
Use RegExpUtils.emailRegex() consistently throughout the function,
which supports international characters per RFC 6531. Changes:
- Bare email validation now uses emailRegex and verifies the match
covers the entire string to reject malformed inputs
- Fallback extraction also uses emailRegex instead of hardcoded ASCII-only pattern
- Removed redundant "after colon" special case since the fallback handles it
- Reset regex lastIndex between exec() calls since it has the 'g' flag
---------
Co-authored-by: Claude <noreply@anthropic.com>
Summary:
We originally didn't do this because creating a DOM tree was loading images.
Using range.createContextualFragment seems to do it without the tree ever
being attached.
Accompanying changes to src/pro are here:
https://phab.nylas.com/D3300https://github.com/nylas/edgehill/compare/bengotow/draft-dom-transformations?expand=1
Also rename applyTransformsToDraft => applyTransformsForSending. Needed
a new name because the function signature has changed. AFAIK there are no
open source plugins using the old functions.
Test Plan: All specs updated
Reviewers: evan, juan
Reviewed By: evan, juan
Differential Revision: https://phab.nylas.com/D3299
Summary:
This diff replaces "finalizeSessionBeforeSending" with a
plugin hook that is bidirectional and allows us to put the draft in
the "ready to send" state every time we save it, and restore it to
the "ready to edit" state every time a draft session is created to
edit it.
This diff also significantly restructures the draft tasks:
1. SyncbackDraftUploadsTask:
- ensures that `uploads` are converted to `files` and that any
existing files on the draft are part of the correct account.
1. SyncbackDraftTask:
- saves the draft, nothing else.
3. SendDraftTask
- sends the draft, nothing else.
- deletes the entire uploads directory for the draft
Test Plan: WIP
Reviewers: juan, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2753
Summary:
WIP:
This is a quick patch for Drew to make extensions async
We'll need to think through the upgrade/deprecation plan to roll out async
extensions across all of our APIs.
Test Plan: TODO
Reviewers: drew, evan, bengotow
Reviewed By: bengotow
Differential Revision: https://phab.nylas.com/D2392
Summary:
- Rewrites composer extension adpater to support all versions of the
ComposerExtension API we've ever declared. This will allow old plugins (or
plugins that haven't been reinstalled after update) to keep functioning
without breaking N1
- Adds specs
Test Plan: - Unit tests
Reviewers: evan, bengotow
Reviewed By: bengotow
Differential Revision: https://phab.nylas.com/D2399
Summary:
- Rename DraftStoreExtension to ComposerExtension
- Rename MessageStoreExtension to MessageViewExtension
- Rename ContenteditablePlugin to ContenteditableExtension
- Update Contenteditable to use new naming convention
- Adds support for extension handlers as props
- Add ExtensionRegistry to register extensions:
- ContenteditableExtensions will not be registered through the
ExtensionRegistry. They are meant for internal use, or if anyone wants
to use our Contenteditable component directly in their plugins.
- Adds specs
- Refactors internal_packages and src to use new names and new ExtensionRegistry api
- Adds deprecation util function and deprecation notices for old api methods:
- DraftStore.{registerExtension, unregisterExtension}
- MessageStore.{registerExtension, unregisterExtension}
- DraftStoreExtension.{onMouseUp, onTabDown}
- MessageStoreExtension
- Adds and updates docs
Test Plan: - Unit tests
Reviewers: bengotow, evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D2293
Summary: i accidentally messed up phab and arcanist locally, so this diff is to fix these mistakes i made. it's the combination of D2037 and D2028.
Test Plan: tested manually
Reviewers: bengotow, evan
Differential Revision: https://phab.nylas.com/D2043
Summary:
- We now make verbose log files continuously as you use the app
- We ship the logs to LogStash via S3 when an exception occurs
- We log the DatabaseStore, ActionBridge and Analytics packages
- We are now on the latest version of Electron 0.26.0
- We are now on Chrome 42 and io.js 1.4.3
- We should be setup to use ASAR soon.
Update atom.sh to reflect that we're now electron
oniguruma was unnecessary
correctly find log files that haven't been shipped yet
Fix a small issue with nodeIsVisible after upgrade to Chrome 42
Delete old logs, better logging from database store, don't ship empty logs
Test Plan: Run existing tests
Reviewers: evan
Reviewed By: evan
Differential Revision: https://phab.nylas.com/D1531
Summary:
Rename ActivityBar => DeveloperBar
Expose sync workers and make them observable
New activity sidebar that replaces momentary notifications
Updated specs
Test Plan: Run new specs!
Reviewers: evan
Reviewed By: evan
Maniphest Tasks: T1131
Differential Revision: https://phab.nylas.com/D1521