Conversation
brhellman
force-pushed
the
accept-proposed-time
branch
2 times, most recently
from
October 1, 2026 18:18
86b61f6 to
7bf83f9
Compare
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>
brhellman
force-pushed
the
accept-proposed-time
branch
from
October 2, 2026 20:51
7bf83f9 to
35c5914
Compare
…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>
brhellman
force-pushed
the
accept-proposed-time
branch
from
October 3, 2026 01:35
35c5914 to
fea8c3a
Compare
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The other half of COUNTER (#2926): when a guest proposes a new time, the organizer's mail client should be able to take it. A COUNTER arrives as an ordinary message with an attachment, so the header now recognises
METHOD:COUNTER, shows the proposed slot (the attachment is the only place it exists - our synced copy still holds the original) with what already overlaps it (#2925) and offers Move event to this time, which rewrites DTSTART/DTEND on our copy - only the occurrence the COUNTER names, as an inline exception, when it names one - advances SEQUENCE once (#2923'sbumpEventSequence), and lets the server re-send the invitation to every guest as it does for any organizer change.Because accepting rewrites the meeting and re-invites everyone, nothing in the attachment is trusted for the decision. Its UID, ORGANIZER and ATTENDEE list are written by whoever sent it, and a UID is not a secret - it travels in the invitation to every guest and back in every reply, so a message carrying a known UID would otherwise be a one-click rewrite of the meeting from an address nobody invited.
counterProposalProblemasks both questions of our own synced copy instead: the ORGANIZER must be this account or one of its aliases (only the organizer may revise a meeting, RFC 5546 section 2.1.4), and the message's From address must be on that copy's guest list (only an attendee may counter, section 3.2.7; a message with no sender fails this the same way). When either fails the proposal still renders, with a sentence saying why it cannot be applied here. A meeting Google has re-homed onto a shared calendar (ORGANIZER rewritten to...@group.calendar.google.com) reads as somebody else's and is refused: a button not offered, not a meeting moved wrongly.Stacks on #2926 and, for
bumpEventSequence, on #2923; the branch carries a merge of the two.Verified:
counter-proposal-guard-spec(7 cases: guest of our meeting allowed, alias recognised as us, sender not a guest, message without a sender, someone else's meeting even with a guest sender, no ORGANIZER, unparseable copy refused rather than trusted) and 6 mounted header cases (the proposed slot shown and the move queued as one revision, one occurrence of a series moved, refused for someone else's meeting / a non-guest sender / no editable copy, an unapplicable proposal reported rather than thrown); 82 passing with the events package suites. 15 mutations across the guard and every header branch, plus 2 on the rebase, all red. Lint, prettier and tsc clean. Extracted from the calendar-scheduling branch; the guard was added there after review found the attachment being trusted (2026-09-16).🤖 Generated with Claude Code