Conversation
bcf2f38 to
020b13e
Compare
020b13e to
f05ddbd
Compare
|
This works as described on my calendar. I'm fine with guests not moving meetings they don't organize, but right now the drag just does nothing. Can a guest who tries to move one get a dialog saying the event can't be rescheduled, with a "Propose a new time" button on it? 38 of my events have a
A possible follow-up, not for this PR: telling apart guests the organizer has allowed to modify the event, and offering them a way to edit it in Google Calendar. The synced copy doesn't carry that permission, so it would need a sync engine update as well. 🤖 Generated with Claude Code |
RFC 5546 section 2.1.4: the organizer increments SEQUENCE when they change something that matters to the guests, and a guest's client ignores an update whose SEQUENCE has not advanced. Five helpers each bumped it when the property happened to be present, so one save that changed time, guests and recurrence advanced three times, and a save on an event with no SEQUENCE never advanced at all. The helpers now leave SEQUENCE alone; the caller that assembles a revision calls bumpEventSequence once, on the master or on the edited occurrence's exception, and an absent SEQUENCE counts as zero (RFC 5545 section 3.7.4). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
f05ddbd to
adb324d
Compare
|
Rebased onto #2928's new head as
Mutations run: 19 across |
…hen there is none Answering an invitation from the message did one of the two things an RSVP is: it emailed the organizer a REPLY. Under CalDAV the attendee also writes their PARTSTAT back to their own copy of the event (RFC 6638 section 3.2.5), and for a Google guest that write is what registers: against a real Google organizer the emailed REPLY was delivered and ignored, while the PARTSTAT written over CalDAV showed on the organizer's calendar within a minute. So the calendar kept showing the meeting as unanswered - or, for an invitation Google had not put on the calendar at all, showed nothing. resolveRSVPTarget picks the copy we are entitled to write to: one UID can sit on our calendar, a room's and a colleague's at once, and the header now says which calendar the answer goes to, or why it will only be emailed. When the event is on none of our calendars, accepting creates it on our own (never for an invitation naming us as ORGANIZER, which a scheduling server would turn into outbound mail). The copy's VEVENT shown and answered is the one eventForInvitation picks for the invitation, and the REPLY is addressed to the organizer the mailed invitation names, because Google rewrites ORGANIZER on a shared calendar's copy to an address nobody reads. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Answering from the message meant opening the calendar to check the slot. ICSEventHelpers.findConflicts expands the account's stored events across the slot being answered - the next occurrence of a recurring invitation, else the event itself - honouring TRANSP:TRANSPARENT, CANCELLED and this account's own DECLINED, leaving out all-day events, read-only subscribed feeds, calendars hidden in the sidebar and calendars the server says are someone else's. The header lists what overlaps, the way Google Calendar does. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Only the organizer may revise a meeting (RFC 5546 section 2.1.4), so an attendee who cannot make the slot could accept, decline, or reply by hand. iTIP's COUNTER (section 3.2.7) is the third answer - Google Calendar's "Propose a new time" - and the sync engine has accepted it since Mailspring-Sync#130. This adds the pieces the calendar's context menu (Foundry376#2928) wires up: ProposeTimePopover picks the slot (dates only for an all-day invitation) and an optional note, createCounterProposal builds the COUNTER from the invitation with only the proposer listed and one occurrence named by RECURRENCE-ID, and EventRSVPTask sends it without marking the invitation answered. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # app/spec/ics-event-helpers-spec.ts
A COUNTER that arrives by email asks us, as organizer, to move the meeting. The header shows the proposed slot and offers to move our copy to it - but only when our own synced copy says this account organizes the meeting (RFC 5546 section 2.1.4) and the message's sender is on its guest list (section 3.2.7). Nothing in the attachment is trusted for that: its UID, ORGANIZER and ATTENDEE list are the sender's, and a UID is not a secret. When either check fails the proposal still renders, with a line saying why it cannot be applied. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
An invitation that this client's own accept flow put on the calendar has no email to hand by the time someone wants to change their answer, and an attendee may not edit the meeting itself (RFC 5546 section 2.1.4). The event's context menu now carries Accept / Maybe / Decline - which both email the organizer a REPLY and write our PARTSTAT onto our copy, refusing a copy the server says is somebody else's - and Propose New Time, which sends a COUNTER naming the occurrence that was right-clicked (dates only for an all-day one) and writes nothing locally, so it is offered on a read-only calendar too. Occurrences learn isMine (this account organizes the event, or nobody does) so the menu can say Edit or View, and the organizer address is normalised once in the data source. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
RFC 5546 section 2.1.4: only the organizer revises a meeting. An attendee who edited or dragged one here changed nothing but their own copy - every other guest still held the original - and a scheduling server is entitled to reject the write. The editor and dragging now refuse a meeting this account does not organize (isMine): the card an attendee lands on offers Propose a new time in place of the pencil, and a drag or arrow key on such a meeting opens a dialog saying only the organizer can move it, with the counter-proposal on offer. isMine also counts an event whose ORGANIZER Google rewrote to a secondary calendar's own @group.calendar.google.com id, when the server says that calendar is ours. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
adb324d to
186c118
Compare
|
The double-click-opens-the-editor change is now its own PR, #2937, stacked on this one. |
RFC 5546 section 2.1.4: only the organizer revises a meeting. The editor and dragging were gated on the calendar being writable alone, so an attendee could retitle, move or re-rule a meeting somebody else organizes. The write changed nothing but their own copy - every other guest still held the original - and a server implementing scheduling (Google does) is entitled to reject it outright. In practice: an attendee drags a meeting an hour later, sees it move, and is the only person in the world who thinks it did.
CalendarEventPopover._isEditable()andcanMoveEventboth refuse a meeting this account does not organize (isMinefrom Answer or counter an invitation from the calendar's right-click menu #2928 - the ORGANIZER is us, or the event names none and is nobody's meeting but ours, or Google rewrote the ORGANIZER to a...@group.calendar.google.comid on a calendar the server (Record whether each calendar is the account's own or shared into it #2916) says is ours). A new event and a read-only calendar behave as before. The two paths share the test, so what can be dragged and what can be edited never disagree.canAttemptMove- writable calendar, not cancelled), and once it is clearly a drag the grid clears it and shows a dialog - "This meeting can't be rescheduled. Only the organizer can move it." - with Propose a new time and Cancel; the arrow keys get the same dialog. The button opens the counter-proposal picker the context menu uses.Double-clicking an event we organize to open the editor directly is its own PR, stacked on this one.
Stacks on #2928 (
isMine) and through it on the scheduling PRs; carries their commits. @manilabui - this changes the popover's render gate and the read-only card; I've kept it to those hunks. Say if it collides with what you have open.Verified: 4
canMoveEventand 2canAttemptMovecases (theirs refused; ours allowed; ours on read-only / cancelled / a preview refused; a guest's drag allowed to begin), 4 popover cases (theirs not editable; ours editable; read-only never; new event always), 5 data-source cases forisMine(no organizer; somebody else's address; the group-calendar id on a calendar the server says is ours, and not on one it says is shared;ownCalendarIdstaking onlymine),calendar-guest-move-spec(4: the drag turned into the offer only past the threshold, our own drag left alone, an arrow key, silence on a read-only calendar) and the dialog's two answers - with the popover, drag, context-menu, data-source and calendar-RSVP suites 131 passing. 21 mutations acrossisMine's terms, both gates, the drag/key interception and the dialog, all red. In a live Google account 68 of 939 events carry a group-calendar ORGANIZER; all sit on a calendar the server reports as somebody else's, so they stay not ours there. Lint, prettier and tsc clean. Extracted from the calendar-scheduling branch (the release-notes-worthy behaviour change there since 2026-08-28: attendees can no longer edit meetings they don't organize).🤖 Generated with Claude Code