Repository navigation
feat(calendar): reply to invitations, and change your own settings on events you're invited to → 3.157.0-alpha (C14a-c of #884) - #977
Open
BBYU26 wants to merge 6 commits into
Conversation
PM can now send the user's reply to an invitation (Yes, No or Maybe, with a note, for one occurrence or all of a repeating one) by changing only their own entry on the guest list, read fresh from Google and landed from a fresh read. The organiser's own reply sends the whole list back and emails nobody. The calendar list now says, per row, whether it can be deleted and answered, as the gate does, and the view takes Delete from it. Nothing on screen calls the reply yet; it is probed from devtools first (L-I0). Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
An event you're invited to now has "Going?" with Yes, No and Maybe at the foot of its details, one strip per copy you can reply from (with both accounts shown, the organiser's copy and the invitation's are one event, and each is named). A repeating invitation asks This event or All events first, with focus on Back. The reply goes at once and Google tells the organiser; what it came to is said under the strip, and as a notice when the details have closed or the reply didn't go. The details read their row afresh, so a landed reply shows there. Events you declined are struck through, as Google draws them: on the Calendar tab only when every copy with your own guest entry declined, in every view and the Terminal look, and on the Focus tab, whose agenda now says which events you declined. The line under someone else's event says you can reply, or how to turn editing on to. Help, README, SECURITY and the editing setting's words mention replies. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (C14c of #884) Edit opens on someone else's event: your reply and its note sit on top, your own notifications, colour and busy or free save without an email question, and the title, time, place, description and visibility change only where the organiser lets guests change the event, under Google's warning, with Send / Don't send counting the organiser. How it repeats, who is invited and deleting stay the organiser's. A repeating invitation takes this event or all events, never this and following, and a guest never re-rules it or moves every occurrence. A refusal names the refused field's own reason. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
34 of 42 tasks
- All events from an occurrence answered on its own: the listing counts as current only once an occurrence in it is updated no earlier than the repeating event's copy read after the reply, as land_series floors a change to every occurrence. A lagging listing is read again (1 s, 3 s) and never landed with its occurrences said to have kept their own answer; never caught up, saved_pending with nothing recorded. - Going? after an All events reply that some occurrence kept its own answer against no longer shows the reply sent: the occurrence that kept No keeps No pressed, in the editor and the popover. - The reply and the note sent give way for good once the row moves on, so a change made in Google back to the earlier answer shows. - Send note sends nothing while a reply is on its way, and no longer swaps the occurrence question that reply was sent from. - Help: "On events you organise, you can add guests", and Busy or Free as What's New writes it. README: what opens where the organiser lets guests change an invitation, and what never does. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- An all-events reply PM couldn't read back yet (round 1's floor makes a lagging listing end that way) no longer shows as the clicked occurrence's own answer: from an occurrence answered on its own, "Going?" and the editor's guest list showed the reply sent while Google kept its answer, and the reply couldn't be sent to that occurrence alone. One pure helper, replyHeld (rsvp.ts), now decides for RsvpControl and EventEditor.onReply: no_change, a reply saved to this copy, or a landed all-events reply whose left_behind doesn't name the occurrence's day. - land_every_reply keeps the days of occurrences that kept their own answer when the mirror can't take the listing: Saved with MirrorRefreshPending and KeptOwnReply, the hash dropped, rather than a bare saved_pending. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
PR B of the regrouped plan for #884 (
docs/calendar-remaining-plan-2026-10-22.md): C14a, C14b and C14c, one commitper layer, then one commit for the version bump and What's New → 3.157.0-alpha.
Stacks on PR A (#976). The branch is rebased on #976's reviewed tip (
1b6d1e5, its two review rounds and theWhat's New fix). Merge #976 first; this PR's base then moves to
main.Remove from my calendar (C14d) is not here: the regroup moved it to PR G, at the top of the stack. Until then,
deleting an invitation (every scope of a repeating one included) stays refused with "Someone else organises this
event".
L-I3 matters most in the release test round (in the live tests below): its answer decides the words of the warning shown when the
organiser lets guests change the event.
What users get
repeating event, pick this occurrence or all of them. Google tells the organiser, as when you reply in Google
Calendar. If you're on the guest list from more than one of your calendars, each gets its own "Going?", named by its
calendar.
Replying needs editing turned on for that account.
included) and in Focus.
colour and Busy or Free, which emails nobody and asks nothing. If the organiser ticked "Modify event", the title,
time, place, description and visibility open too, under Google's warning, and PM asks whether Google should email
the guests. How it repeats and who's invited stay the organiser's.
editing on to reply.
Per layer
C14a: replying to invitations on the backend (
1299a7e)calendar_write/rsvp.rs(pure): finds the user's own guest-list entry (Google'sself, the calendar's ownaddress first), builds the reply body, checks whether Google already holds the reply, tidies the note (at most
1,000 characters, counted as characters), and reads the reply for the editor.
attendeesOmitted.otherwise the guest form. Never a one-entry list. The organiser's own reply emails nobody.
respond_to_calendar_event(fence and ipc-commands rows added, registered inlib.rs): editing off isanswered before any read; a fresh read decides; at most one silent rebase on a 412; a lost answer is looked up
before anything is said; it lands from a fresh read. Scopes: This and All ("following" refused; a scope on a
one-off is ignored, per the Adopted list). All re-fetches the occurrences and waits (1 s, then 3 s) for a clicked
occurrence that had the repeating event's reply.
EditFacts.invited, the blanket check split so the reply shares its first half,reply_block, and per-rowListRights(delete, answer) replacingedit_blockon the list. The calendar view's Delete now comes from thebackend's rule and appears exactly where it did. New reason
not_invited;FieldPermissions.respond(kept out ofany());EventForEdit.replyand.organizer_self.kept_own_reply: on All, occurrences answered on their own that kept their answer, their days inleft_behind("N occurrences you answered on their own kept their answer.").respondToCalendarEventwrapper, the hook'srespond,calendarEdit/rsvp.ts,and the reason and reply words in
editReasons.ts. Nothing on screen called it yet in this layer.C14b: reply from the popover; declined events struck through (
f45e8ad)RsvpControl: "Going?" (or "Going? Replying for {calendar}.") with Yes, No and Maybe, nothing pressed before areply. A one-off reply goes at once; the reply already shown sends nothing. A repeating event asks This event, All
events or Back, focus on Back; Back and Escape return to the reply chosen, and Escape doesn't close the popover.
While a reply is on its way the buttons are
aria-disabledso focus stays (SegmentedControlgainsbusy), onereply at a time, with a status line. A result that isn't a success, or one that arrives after the control has gone,
also goes to the calendar's notices, naming the event.
ResizeObserverre-places the panel; stillnon-modal. A new pure
replyCopiesoffers a reply from every copy, the one the dedup hides included.markDeclinedstrikes a merged event only when every visible copy with the user's ownentry declined. It covers chips, cards, all-day bands, Month bands, Agenda, both Terminal views, and Focus's grid
and list. Focus's list reads a new backend field
AgendaEvent.declined, combined across copies by the same rule.not_organizerwords andnotOrganizerText("can't reply here", "editing off"); thebackend's
read_only_messageuses the same words. Help,SECURITY.md, the README andEDITING_COPY.onupdated.C14c: your own settings on invitations (
93edb1e)gate.rs): the blanketNotOrganizeris gone. On someone else's event without "modify", title,time, place, description and visibility close with
NotOrganizer. How it repeats (newFieldPermissions.repeat),the guest list and delete always close; under "modify" the guest list closes with
organizer_only_guests. A lockcloses
repeattoo.EditFacts.guests_can_modifyis read from the fresh copy and from the mirror row. A newgate::reason_for(perms, field)gives a refused field its own reason; one helper servesrefused_field,send_deleteand the choices' delete.save_event; C12 already counts the organiser in the email question.Master::read_onlyno longer refuses someone else's series.invitation_checkruns beforethe guest checks: "this and following" past the first occurrence is refused (
InvitationFollowing); guestchanges, and shared fields without "modify", are refused; under "modify" a new rule or moving every occurrence's
start is refused (
InvitationRule), while a new length alone goes through.delete_planrefuses every scope. Aftera 412, a step that sends only the user's own settings goes again (it emails nobody), rather than stopping as a guest
conflict (C1-M6).
the Repeat row reads its own right and says why it's greyed; after a reply,
withReply/guestsWithReplyshowthe reply and your guest-list entry without making the draft dirty.
first).
not_organizerin C14c's words,organizer_only_guests, the group wording fornot_invited,A11_NOTE,repeatTheirsText; help, README andEDITING_COPY.on.Version (
aa7c149)3.157.0-alpha in all seven lockstep entries (lockfiles regenerated) and one What's New entry for the three layers.
Where PM differs from Google Calendar
Tests flipped on purpose
the_list_marks_what_can_be_edited→the_list_marks_what_can_be_edited_deleted_and_answered; it,the_fresh_copy_decides_the_gate,an_organisers_event_with_guests_is_editableandthe_editor_opens_with_the_users_replyread the new list rights and C14c's allow-list.send_raw_calendar_write, plus a case where one command defined twice hidesa missing one (its list check compares as sets).
gate.rs:every_blanket_reason_blocks_everythingdrops itsNotOrganizerrow;a_blanket_reason_closes_notifications_and_colouranda_reply_alone_is_not_an_editget new examples; thelocked-copy test adds
repeat.dto.rs: the reason serialisation andany()tests.series/mod.rs:a_master_pm_cant_read_or_change_says_whynow expectsNone(someone else's series is no longerrefused outright).
series/plan.rs:what_refuses_a_series_plan_and_what_only_warnsgains a title row.calendar_edit.rs:the_gate_refuses_before_any_row(busy/free is offered; a delete under "modify" saysNotOrganizer) anda_draft_touching_a_closed_field_is_refused_with_the_reason; the banned-words walk adds the twonew refusals.
editReasons.test.ts(not_invited,kept_own_reply, C14c's words),eventDraft.test.ts,CalendarView.test.tsx.the list's rights; the CalendarView and editor test mocks gain
respondToCalendarEvent,getSeriesSummary,respondandnoteReply.New tests: 60 in Rust and 79 in TypeScript across the three layers. Every new guard was broken by hand and seen
failing (the per-layer reports list them).
Live tests for Bobby
All in
docs/calendar-live-tests-2026-10-10.md, in this order. Setup: account 2 connected in the dev build withediting on, both accounts' Gmail and Google Calendar open; act only on events called "PM probe I…". For every step,
write down who was emailed and the subject (Q6).
calendar and gives the console helpers).
as yours; Yes, Maybe with a note and No with an empty note each land with the alias and the organiser unchanged;
does the note show in Google web, and does the empty note remove it; a repeated reply sends nothing; a
1,001-character note is refused.
If PM says the guest list came back shorter, stop and tell me.
email?
Google web? Ticking PM write test again shows it plain.
all after the organiser moved one; does Google web offer "This and following"?
line with editing off, Focus's list and grid.
Calendar ID, the allow-list is
PM write test,PM guest test, PM write test unticked.unchanged; does Google let a guest change visibility?
Does account 1's copy change? If it does, the warning becomes "Your changes reach everyone invited…". Then
Send, and write down every email.
refused, moving one asks the email question (did account 1's occurrence move?), a new length alone offered.
Gate
Full
just checkon the rebased branch with the version bump: passed (exit 0).-D warnings, cargo checkAfter the gate, one sentence of the What's New entry was reworded ("If you're on the guest list from more than one of
your calendars…");
just check-fastpassed on it.The rebase onto #976's tip had five conflicts, all "both sides added":
WriteWarninggainedpartly_applied(PR A)and
kept_own_reply(C14a) indto.rs,types.ts,editReasons.tsand its test, kept both; the help entry "Yourcalendar" kept PR A's "except for this and the following events" and gained C14b's and C14c's sentences.
Review
Reviewed by four areas (replying on the backend, the popover, what a guest may change, tests and copy), each finding checked by a skeptic, then fixed and re-reviewed until a round came back dry (3 rounds; 7 of 14 round-1 findings kept):
1b6b754): an "All events" reply sent from an occurrence you'd answered on its own now waits until Google's listing has caught up (the same check repeating-event saves use) before it says which occurrences kept their own answer, and otherwise says it will show after the next refresh and records nothing; Going? never shows a reply as pressed on an occurrence that kept its own answer; a reply you sent stops overriding the shown answer once Google's answer moves on, so a change made in Google wins; Send note during a reply no longer loses the note; help and README no longer suggest guests can add guests or change how an invitation repeats.66e7e98): a pending "All events" reply no longer presumes which occurrences took it.Known cost, by design: after a pending "All events" reply, an occurrence that did take it shows its old answer until the next refresh (never a reply Google doesn't hold). Live step I2-2b checks the "caught up" inference for replies.
Part of #884
🤖 Generated with Claude Code