Repository navigation
feat(calendar): change repeating events, create events, repeat rules and guests → 3.155.0-alpha (C9b-1b + C10–C12 of #884) - #961
Merged
BBYU26 merged 14 commits intoOct 22, 2026
Conversation
This was referenced Oct 20, 2026
BBYU26
force-pushed
the
feat/calendar-series-dialog
branch
from
October 20, 2026 21:01
f42032a to
9afff68
Compare
…currences → 3.149.0-alpha (C9b-1b of #884) Repeating events you organise become editable from PM. Saving or deleting an occurrence opens the "which occurrences?" dialog (This event / This and following events / All events), nested in the editor, with the save sending the very changes and token the choices were asked for. A stale answer never replaces a later one, words said once are kept as a notice on every way out, and focus returns to Save. Deletes ask the same, reopen once when the backend wants a choice, and say to refresh when PM and Google disagree. A delete that leaves occurrences in Google shows them again on the read its settle starts. Backend: Recurring is gone; an unscoped save or delete of an occurrence answers NeedsChoice after the read-only gate and before any write, an unscoped delete of a series row is refused before it's held, and release builds stop refusing scoped writes and settling (the choice token is still required there). Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A delete answered "choose" reopens its dialog once, but never over a delete dialog or the editor opened meanwhile; a notice says to delete again instead (F1, F6). The reopen-once routing moves into useDeleteDialog and is tested (F8). A choice the plan changed under again says so in its own words (F11). - Only the newest read of the mirror applies or releases hidden rows, so an older read landing late can't show a deleted event again (F4). The settle reload moves into useMirrorReads, which CalendarView calls and the tests drive (F12). - A settle that ends after its delete dialog was closed closes nothing more (F5). - A delete whose choices say Google's copy doesn't repeat says to refresh at once, as the delete would only be asked for a choice (F7). - The editor's rebase after the choices catches a failed read, holds Save while it runs and puts focus back on it; a conflict's failed rebase no longer marks the save as possibly in Google (F2). Tests pin the not_repeating save, its loop guard, a failed choices read and a refused conflict's rebase (F3, F9). - What's New says "without guests", how each kind of save is changed back, and drops the promise that nothing shows twice (F10); a stale test doc names row_repeats (F13). Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A delete answered "choose" after the Calendar tab unmounted now leaves its notice in the store (a StrictMode-safe mounted ref), and the dialog never reopens over any open dialog (isAnyDialogOpen at answer time) or the event popover. Tests cover useMirrorReads' unmount guard and failure path, and both hooks' tests run under StrictMode. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
BBYU26
force-pushed
the
feat/calendar-series-dialog
branch
from
October 21, 2026 15:37
9afff68 to
77b584b
Compare
…ly → 3.149.1-alpha (C10a) (#962) * chore(calendar): the create command and its safeguards, dev builds only → 3.149.1-alpha (C10a of #884) The create command goes to Google under an id PM chose, as a one-step plan on the series machine: a lost answer is looked for, looked for again a second later, and only then posted once more under the same id. An answer PM never got is held under a retry token of PM's own (never the event's id), and a retry looks before it sends, refuses a changed draft or calendar, and caps the posts under one id at three. Google's copy lands as a created row, or not at all when it starts outside the months PM mirrors. Release builds refuse the command until the editor's create mode ships (C10b); nothing in the app changes yet. The default-length storage and overview field land here; reading it from Google is C10b's. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C10a - A new event found cancelled on the second look after a lost answer now reads as gone, as on the first look, and the exhaustive answer test pins that a copy Google showed always settles the run. - A retry never sends a new event again into a calendar PM stopped syncing; it still looks for the earlier one and keeps the token. - Tests pin the held create lapsing through the command's own prune, a re-send starting the eight hours over, a re-send meeting a cancelled copy, and a running retry's claim before its take. - The words no longer promise a dropped connection can't make a second copy (Google can't always catch a repeated id), and the 'changed' refusal says to look in Google Calendar before creating it again. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 2 of C10a - A retry refused because PM stopped syncing the calendar now says the earlier send may have landed, to look in Google Calendar or choose the calendar and Check again; the token stays held. - Comments on the create path no longer say a re-send under the same id can't make a second copy (Google can't always catch a repeated id). - What's New drops "under the same id". Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…a (C10b) (#963) * feat(calendar): create events in your Google calendars → 3.150.0-alpha (C10b of #884) "+ New event" in the calendar header, and the c key, open the full editor in create mode: a calendar picker (writable calendars on editing-on accounts, grouped by account), the last calendar created in as the default, Google's defaultEventLength per account (read at connect and at each sync), and notices for a start outside the synced months or a hidden calendar. Save, Ctrl/Cmd+S and Enter in the title share one saving guard; an answer that never arrived locks the draft as sent and offers Check again under the same retry token, and outlives the editor as a notice. Release builds no longer refuse the create command. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C10b - A held new event whose token ends unproven (lost, a changed draft, or made and since deleted) is never called "nothing saved": the discard question repeats what PM knows, and closing leaves those words as a notice. A later successful save clears them. - Tests for a Check again that ends after the editor has gone (answered, and with no answer at all) and for one with no answer while it's open. - Enter or Ctrl+S that is part of composing text (isComposing, or WebKit's key code 229) saves nothing; a held key's repeats on Ctrl+S, the title or Save send nothing more. - Save checks the discard question, a save already out and a missing zone before blurring anything; a save refused for a problem puts focus back where it was. EventEditor's Save does the same. - The calendar list stays inside the editor's dialog (Popover gains withinDialog), puts focus on the calendar picked when it opens, and hands focus back to its button on Tab, Enter and Escape. A click on a row's name or dot picks and closes. It closes for good when disabled. - The bar's Close is disabled while a save is out (Dialog gains closeDisabled), in both editors. - With no zone, the editor says a new event can't be saved. - The calendar's write notices sit under an open dialog's scrim. - The header's "why not" restarts and is said again on another click; tests for its timing, its reset, the button's secondary variant, the title's focus on open and a calendar change alone asking to discard. - createReasonFor, the header's "why not", is pure and tested. - A default event length read during a disconnect is never stored: the check is a tested helper, used at connect and at each sync. - Comments on the encrypted calendar_ui blob name the last calendar an event was created in beside the hidden set. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 2 of C10b - A date a field couldn't read now stands until that field is given a date, typed or picked: a blur, a time or the other date never ends it, so putting focus back on the field after a refused save can't let the next save go out on the old date. Each half of the event keeps its own refusal, in both editors. - Notices sit strictly above the floating briefing and below any dialog's scrim; a test checks both bounds. - A test pins that only a held token ending without proof leaves a notice; a fresh save that fails outright says nothing was saved. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 3 of C10b - A date a field couldn't read is said naming that field (Start date, End date, First day or Last day, by the event's kind), and says to type or pick that date again. The line sits under the date fields; the field is marked wrong and described by it, and the same text refused in both fields gives a line for each. A save the refusal stops puts focus on that field (the start's first), in both editors; any other refused save still leaves focus where it was. - Tests pin that closing with only a refused date standing asks before discarding, in both editors. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(calendar): each refused date's line is its own field's (C10b round 4) The same-text tests in both editors now check that the two refusal lines have different ids and that each date field is described by the line that names it. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
… as you type → 3.151.0-alpha (C10c) (#964) * feat(calendar): quick create, and the new event shows on the calendar as you type → 3.151.0-alpha (C10c of #884) Shift+C opens a small, non-modal quick-create box under "+ New event": type a title and press Enter to create it; the date, the times, all day and the calendar can be changed there, and More options hands the draft (and any retry token held) to the full editor without sending anything. A click outside or Escape closes it only while the title is empty; a click in a picker it opened, or on its ghost, isn't outside. A titled draft that goes with the Calendar tab (a click in the sidebar), nothing sent, is handed back, and the box opens on it again when the calendar does. It registers as a dialog layer, so the calendar's keys, the swipe and Ctrl+K stand down, and while a save is out nothing sends, closes or hands over again. A date its field couldn't read is said under it, naming the field, which is marked wrong; it refuses every save until the field is given a date, a save it stops puts focus there, and More options carries the refusal on. The draft is drawn on the grid as a dashed ghost in its calendar's colour (week, day, month and agenda; not in the Terminal look), and a saved one stays until a read of the mirror that started after the save lands. A screen reader names it as the new event and how far it has got: not saved yet, saving, or one PM may already have made. In Month it takes the top lane over its own days only, so bands in other weeks keep their lanes. Month packs the weeks the ghost touches with it on top and every other week as if it weren't there, so a band that runs into its days keeps its lane elsewhere and changes lane only at the edge of the ghost's weeks. Escape beside a non-modal layer (isKeyForDialogOver): a modal dialog always owns the key, and a non-modal layer such as quick create only a key pressed inside it. So a popover, or Settings' model menu, open beside quick create closes on Escape pressed in its panel, on its trigger or on the page, and Escape on the page still leaves fullscreen. Popover panels are marked with data-popover-panel. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(calendar): quick-create tests pass in any locale, and wait for the calendar's keys (C10c) The Agenda and Month tests named rows by a fixed 24-hour time, but the views say the time in the reader's own clock (formatClock), which on CI's runner reads 09:00 AM. They now build the names with that helper, as whenText.test already does. CalendarView.test's mount() also waits for the calendars' reads and the key rebinding, as C11b's does, so Shift+C can no longer arrive first and open nothing. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…fers to move its milestones → 3.152.0-alpha (C9b-2) (#965) * feat(calendar): the popover says how an event repeats, and a split offers to move its milestones → 3.152.0-alpha (C9b-2 of #884) A repeating Google event's details now say how it repeats, in Google Calendar's words, read from Google's copy of the series (a GET only, kept a while per series and forgotten by PM's own writes to it). After a "this and following" change lands, each unmet milestone linked to the event and dated from that day on (read before the first send) is offered a move to the new part, with Keep it there beside Move and Move back for 8 seconds after. The move is compare-and-set, so an old offer never undoes a change made since. A new part PM can't name says which milestones stayed. set_milestone_event now nudges the briefing and refuses an id no milestone has. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C9b-2 - F1: both claim drops (InFlight, HeldClaim) end in one release_claims, which forgets the series' words while the claims' lock is held, so an ask never finds a series let go with its words still kept. A source test pins both drops and that order. - F2/F12: the popover's re-point test answers the shown event first and the older one after; the shown words stay. - F3: a test fills the summary cache to its cap: the stale go first, then the one read longest ago, and only that one. - F4: a split offers a linked milestone by the occurrence its date comes from (that occurrence's place in the series against the split), not by its date alone; with no row on its date, the date decides. The earlier part's last day is read the same way. Tested both ways round. - F5: the landing decides which milestones stay unasked by what it saw still on the earlier part, not by slot days against moved dates; a cleanup whose answer was lost, or a landing that couldn't read Google, asks. Tested with a moved occurrence left behind, a lost cancel that landed, and a lost cancel with a stale landing. - F6: set_milestone_event's body is a relink core, tested for both refusals (and that neither changes anything); a source test pins the briefing nudge after it. - F7: tests for a new part deleted before its one GET, the user's zone deciding every day, and the earlier part's last day when an occurrence is left behind. - F8: Move back takes no press in its first half second, nor a double-click's second click, nor a held Enter's repeats. - F9: the Move back and Keep it there names start with the words they show; a test checks every move button. - F10: a test dismisses a moved notice during its Move back window; it stays gone. - F11: the re-point test checks focus goes into the panel. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 2 of C9b-2 - R2-F1: which milestones a split offers goes back to the rule Bobby decided (DECISIONS 17-10 #2): a milestone dated on or after the day the change starts, in the user's zone, is asked about; an earlier date is never asked, even when its occurrence goes to the new part. The only exception is the adopted C1-L9 one: a milestone on an occurrence left on the earlier part. milestones::linked_on_or_after is back; the occurrence slots now only serve that exception. The words in saveChoices.ts and help.ts ("on or after this day") are true again, and the doc comments in dto.rs, types.ts and useEventWrites.ts say the same. - R2-F2: a cleanup step whose answer was lost now ends Unanswered, apart from Skipped (never sent, or proved not done, including Finish's changed branch). Ran::skipped still counts both, so the mirror and the left-behind words are unchanged; Ran::not_done counts only Skipped. When the landing can't read Google, a milestone on an occurrence whose cancel surely never landed isn't offered; one whose cancel's answer was lost still is. - R2-F3: the left-behind test also runs for a user in Honolulu, where the occurrence's day differs from the series' London day, so matching milestones by the series' zone fails it. - R2-F4/F5: release_claims lets go of the claims' lock before it forgets the series' words, as the CalendarEditState lock order says; the writes count covers a keep in between. The source test now pins that order. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 3 of C9b-2 - R3-F1: the day a split offers milestones from is the day the user splits from as they see it: the earlier of the occurrence's place in the series and the day it shows on, in the user's zone. That's the "this day" the dialog names. A split from the 19-10 occurrence moved to Sunday 18-10 now offers "Launch" on 18-10, where the split carries that occurrence. A split from one moved later still goes from its place in the series. The left-behind exception still compares occurrence slots with the split point. - R3-F2: a test for the branch where the landing reads Google. The cancel's answer is lost, Google still lists the occurrence on the earlier part, and "Launch" isn't offered. Working this out from what PM knows it never sent fails the test. - R3-F3: a landed split now sends today, in the user's zone, beside the earlier part's last day. The offer and the stayed notice say "which ended on D, and shows as overdue" for a day before today, and "whose last day is D, and shows as overdue after that" for today or later. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(calendar): pin the user's zone in a split's milestone days (C9b-2 round 4) The test host's today now follows the zone it is asked for, and two Honolulu tests fail if the shown day, the offer's today or the resolving today is read in the series' zone. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…nd can create a repeating event → 3.152.1-alpha (C11a) (#966) * chore(calendar): repeat rules in words and on the wire, and the backend can create a repeating event → 3.152.1-alpha (C11a of #884) A repeat rule from the editor's set now has a wire form the webview can hold, and two pure commands give the repeat menu its ready-made rules and a rule's words, with the day its first occurrence falls on: "Weekly on Monday and Wednesday" chosen on a Tuesday starts on the Wednesday, and a first occurrence whose start or end the clocks skip is refused in words. A rule that never happens is refused before the rrule crate is asked. The create command takes a rule: the event starts on the first day the rule makes, its end moved with it, with one rule line and its zone on both timed nodes. Google answers with the repeating event itself, which is never a mirror row, so its occurrences are listed (three looks at most) and landed as created rows, or said to be outside the months PM mirrors. A repeating create counts as series-wide to a dev build's allow-list, and an empty rule is no rule wherever PM asks. Nothing in the app sends a rule yet. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C11a - A test now covers the listing loop's stop condition when a new repeating event's first look is incomplete: page 1 arrives with a next page, page 2 is refused with a 503, and the second look a second later lists all three occurrences and lands them. If the "complete" half of the stop condition is removed, the loop stops after that first look and this test fails. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
* feat(calendar): new events can repeat → 3.153.0-alpha (C11b of #884) The full editor's create mode and quick create gain a Repeat row: Google's ready-made rules for the start date, and Custom… (every N days, weeks, months or years; weekdays, a day or numbered weekday of the month; ending never, on a date or after N times). The backend's words say where a rule really starts when it moves the start, why it can't be saved, and the zone it repeats in; every way to save waits for them. A rule's own day follows a moved start date. Custom… acts only on a committed pick; from quick create it hands over to the full editor with the dialog open. Also steadies CalendarView.test's mount, whose keys could reach the view before its calendars did. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C11b The Repeat menu only ever shows the rule the draft holds, so a save sends what it shows. While a key steps the closed select, Custom… is turned off for that moment, so the step passes over it instead of landing on it; it comes back on by a timer rather than on the key coming up, which a menu the key opened can swallow. Custom… opens on any pick from the list: a click, or Enter in the list the keyboard opened. Alt+Down, and Up and Down on a Mac, open the list rather than step, so Custom… stays on offer there. Space is not a step either. The note before Save no longer says a repeating event that starts before the synced months won't show here: the backend lands its occurrences inside them, so it says only those will show. The Repeat select is never wider than its row, and the When fields can be narrower than their widest content. The Repeat row holds back its refusal while the When fields already refuse a time the clocks skip, so it is said once. New tests: arrowing to the last rule and saving sends exactly that rule; a pick of Custom… right after arrowing opens it; Space, Enter, Alt+Down and a Mac's arrows leave Custom… on offer; the moved-start line stays away when the start didn't move; and Check again (and Ctrl+S) on a held retry send exactly the held body, rule included, while the repeat's words are still to come. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 2 of C11b The Repeat menu is no longer a native select. WebKit and Chromium track a select's changes differently, so a step could land on a row without PM hearing of it: the menu could show a rule the draft didn't hold, or open Custom… on an arrow key. It is now a button that always names the rule the draft holds, opening a list inside the editor's dialog (or quick create's box), as the calendar list does: the rules as radios in Google's order, then Custom… as a button of its own. Opening puts focus on the rule picked; the arrow keys move the choice and the button follows; a click or Enter picks and closes; Tab goes from the rules to Custom… and out of the list to the button; Escape closes only the list. Custom… opens on a click, Enter or Space, never on an arrow key. A rule of its own keeps its row while the list is open, so an arrow key passing over it can come back to it. Disabled for a save, the list closes. A long rule's words wrap on the button, which is never wider than its row. Quick create keeps its smaller button, and its Custom… still carries on in the full editor. The round-1 step and timer handling, and its tests, are gone. A click on a Mac doesn't focus a button, so the Repeat and calendar buttons take focus on a click, and Escape in their lists hands it back there. New tests: the button always names the rule held; the arrow keys change the rule and the button, and never open Custom…; a change the editor doesn't take leaves the button as it was; Enter and Space on Custom… open it, as a click does; Tab in and out of the list; Escape closes only the list, in both editors; Ctrl+S straight after arrowing sends exactly the rule the button names; the list sits inside the dialog and the box; disabled closes it; presets arriving while it's open; a long rule wraps. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 3 of C11b When the ready-made rules arrive while the Repeat list is open, the row focus was on could go with them: a rule shows as its own row while they load, and becomes its preset's row once they come. Focus then fell out of the list while it stayed open, so the arrow keys did nothing and, in quick create, Tab jumped to the top of the page. Focus now goes back to the rule picked whenever the rules change and focus is no longer in the list; if it is still in the list (on Custom…, say), it stays where it is. The tests now check that the Repeat and calendar lists are mounted in the dialog itself, not just somewhere inside it. A list left inside the button's box would be cut off by the dialog's scrolling body, and no test noticed when the prop that lifts it out was removed. New tests: focus follows the rule when the rules arrive while the list is open; focus on Custom… stays there when they arrive; the Repeat list is mounted in the dialog and placed against the window; both lists in both editors are mounted in the dialog or box itself. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t sent → 3.153.1-alpha (C11c of #884) (#969) * chore(calendar): the repeating-event planners take a new rule, not yet sent → 3.153.1-alpha (C11c of #884) The series planners now take a new repeat rule or "Does not repeat" for every occurrence or from one on: the rule is rebuilt at its own start, deleted days and changed occurrences follow it where they still fall, it never repeats past a later part, and stopping keeps the first live occurrence as one event. Changing how it repeats is refused for a single occurrence and, for now, for a one-off event. Landing handles a series that became one event, and the save choices word the new warnings. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C11c A deleted extra date stays deleted when a new rule keeps it: the rebuild writes its EXDATE again, for every occurrence and from one on. A yearly-on-its-date rule picked with the first occurrence moved earlier starts where it was put, not a year later. A "this and following" row that changes every occurrence words its warnings as All, so it never speaks of a new part that isn't made. "Does not repeat" for every occurrence keeps the hedge for the other occurrences changed on their own, and says the kept one moves when the draft moved this occurrence to another day. A new daily rule with a moved day asks nothing about which days it repeats on; where it starts says it. The carry of an occurrence's own time onto a new part, the stop's own time and the all-day later-part check now have tests, and the refusal when nothing is left to keep points to Google Calendar instead of a choice that is refused. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C11c, part 2 A series that is itself a part of one changed "this and following" before, by PM's note or Google's id, is held to where its own rule ends now: a new rule for it, or from one of its occurrences on, never repeats on a day after its last occurrence, since PM can't list the parts of the series it came from. One that repeats for ever has no part after it. A new rule keeps an occurrence deleted or changed on its own on its own day wherever the rule still makes that day. Only a day it doesn't make moves with the day the draft moved, and never onto a day the old series had an occurrence of its own. The occurrence opened still goes where the draft put it, and an extra date kept takes its own changes where it is. "This and following" into another time zone measures the part before in the series' own zone, so the new part can't start on the day it still reaches. A lost answer to a new rule or to "Does not repeat" says what may be half done instead of speaking of moved occurrences. A record of "Does not repeat" from one occurrence on, left from before a restart, says to delete the event that doesn't repeat, and that occurrences changed on their own may still show. The notice after a save that left its event outside the months PM syncs says so, warns, and drops "edit it again"; "this and following" with "Does not repeat" is worded as one event that doesn't repeat. The rule paths' own guards (R2 on the occurrence and on the series, guests, formatting, the new part's id, what the kept occurrence carries, an unreadable line on a stop) now have tests. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): C11c's What's New doesn't promise stopping a repeat C11d keeps "Does not repeat" on a repeating event for dev builds until the live probe records what Google does with occurrences changed on their own, so the next update can't stop a repeat yet. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 2 of C11c A new rule keeps the slot the draft gives the occurrence opened for that occurrence alone: an occurrence deleted on that day writes no EXDATE there, and one changed on its own there is counted as lost rather than carried over it, for every occurrence and from this one on. A move into another time zone reads each occurrence's day, and how far the day moved, in the zone it moves into, so a move that keeps every moment keeps each deletion and each change at its own moment. With an old rule PM can only show as text, a deletion that would move with the days onto a day the new rule makes is refused in words, never dropped, since PM can't tell whether that day had an occurrence of its own. A part whose rule PM can only show as text, and that repeats for ever, has no later part, so a new rule for it is offered again. "This and following" picked on the first occurrence saves every occurrence, and its notice now says so; with "Does not repeat" it names the day of the one event left. The scope and token sent are unchanged. An event saved outside the months PM syncs is said to be outside even when PM's calendar couldn't be written or the re-fetch never showed the change, and the notice keeps saying the calendar will catch up after the next refresh, since the old occurrences still show until then. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 3 of C11c A new rule for every occurrence, opened on an extra date that is kept, no longer takes the rule's slot that day for it: the extra date stays where it is, so an occurrence deleted at the rule's time that day stays deleted. A move into another time zone now reads each old occurrence's day as its day in the series' own zone, moved on as far as the occurrence opened moves between the two zones. Both rules repeat by the day on the clock in their own zone, so across a clock change each occurrence keeps its place in the week: a deletion stays on its own occurrence, never on the live one beside it, and a change goes onto its own. The same reading places the one event "Does not repeat" keeps, where a new rule starts, and how far a day move takes every occurrence, for every occurrence and from this one on. Whether the old series had an occurrence of its own on a day is read on that day in its own zone. A deletion PM can't place is named by its day in the series' own zone. A Finish that lands outside the months PM syncs now says the calendar will catch up after the next refresh, as a save does, and warns: one helper words a saved outcome's warnings for both. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(calendar): review round 4 of C11c A timed series made all-day is read by its own wall days, never by the all-day start's zone. Weekly on Monday at 00:30 in London, Monday 12-10 deleted, Monday 19-10 made all-day and "Weekly on Sunday and Monday": for every occurrence the EXDATE stays on 12-10 and nothing warns that the repeat day changes; from this one on, Monday 26-10's own change goes onto the new part's Monday 26-10. Days::new comparing the zones for every kind of start moves both onto the Sunday before. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…884) (#970) * feat(calendar): change how an event repeats → 3.154.0-alpha (C11d of #884) The editor's Repeat row now works on existing events. A one-off given a repeat saves alone: its times and rule go to Google started on the first day the rule makes, its occurrences land in place of its row, and an answer that never arrives is read by meaning. On an occurrence, a new repeat or "Does not repeat" asks which occurrences, "This event" refused; the event's own rule shows in its own words and never holds a save. Every occurrence can now move between all-day and timed, or into another time zone, rebuilt from the event's own rule. Release builds hold back "Does not repeat" on a repeating event until live probe P-R2. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C11d A change between all-day and timed, or into another time zone, for every occurrence now refuses a repeating event whose first occurrence isn't on its own rule, as a move does: rebuilt from the rule, that first occurrence would go and, with a count, one more would come at the end. The one-off new part of a "Does not repeat from here on" change PM couldn't finish now waits for that change, as a repeating new part does: held, being sent or settled, or left in the journal from before a restart. A one-off given a repeat with its time left alone is saved in the zone the editor showed it in (the calendar's, else this computer's, kept with the session); only with no zone anywhere is it refused, in words about the event. The refusals for a change of kind or zone with a day move fit the "this and following" row too, and an occurrence already moved on its own gets words of its own instead of naming a move the user never made. The Repeat list keeps the event's own rule as a row once it closes on another, as Google does, so picking it again sends nothing. Switching All day on or off goes through the same path as any time change, so a picked rule follows the date it lands on. The saved notice after "Does not repeat" says one event is left, and a save PM's calendar won't show points to Google Calendar instead of saying to edit it again. New tests cover the all-day to timed rebuild both ways and in a one-off given a repeat, the times check on a lost answer, and the zone note for a rule PM can only show. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 2 of C11d A "Does not repeat" left from before a restart is now tested on its one-off new part's own editor for every reading: an original never cut refuses in the stop's words with only reads sent and keeps the record, and an original whose repeat changed in Google since is said once and forgotten, so the next save goes through. Switching every occurrence between all-day and timed, or into another time zone, on a repeating event whose first day is off its own rule now refuses in words of its own, naming no move the user never made. The stop's restart words end with making the change again rather than trying again, since the event being saved may be the one to delete. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 3 of C11d A one-off made to repeat whose series starts outside the months PM syncs no longer ignores a failed removal of its old row: when PM can't take the row off its calendar, the save says the event is outside and that PM's calendar will catch up after the next refresh, and the next sync redoes the calendar in full. A new test pins how a change of time zone sits on the reworked day handling underneath: weekly on Monday at 01:00 in London, put in New York at 01:00 on the same Monday, stays on Mondays for every occurrence and from this one on; kept at the same moment, which is Sunday in New York, it is refused in words, since the rule's own day would have to move. Test fixes: three test stand-ins for the passphrase strength check now answer in its real shape (the meter reads its warning and suggestions, which were missing), and the ghost-state test for a failed new event waits for the ghost to hear of the failure rather than reading it the moment the words appear. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 4 of C11d Moving every occurrence of a series into another zone at the same time on the same day asked whether to move every occurrence a day, and said the series' start moved. Weekly on Monday at 01:00 in London, moved to 01:00 in New York on the same Monday: what is sent was right, but the save choices asked "Move every occurrence a day later?" (18-10 to 19-10) and said the start moves from 04-10 to 05-10, because both were read in New York's days, where Monday 01:00 in London is still Sunday. Both are now raised only when a day moves on the series' own clock as well as in the new zone's days, and are worded in the series' own days, as the dialog reads them. A move into another zone at the same moment still raises neither, and a real move to another day with a new rule still raises both: Monday to Tuesday, and 05-10 to 06-10. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 5 of C11d "Does not repeat" for every occurrence no longer says the first occurrence moves when it is put in another zone at the same time on the same day past midnight. As round 4 did for a new rule, a day moves only when it moves on the series' own clock too, and the warning is worded in the series' own days. Custom... opened at a later occurrence of a repeating event now checks the end date against the event's first day instead of this occurrence's, so every occurrence can end before the one opened, as in Google Calendar. The choices still say whether each can. The occurrence's date still drives the weekday, month and year labels. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…wn → 3.154.1-alpha (C12a of #884) (#971) * chore(calendar): guests and update emails on the backend, not yet shown → 3.154.1-alpha (C12a of #884) The backend can now change an event's guest list as changes to Google's own list, keeping every guest's reply, comment, extra guests and room flag, and works out who a change or delete would email. A save or delete that would email someone and has no Send / Don't send answer for those numbers is asked about with only a read sent; one that emails nobody sends none. Repeating-event plans stamp each step with the answer where it reaches someone, bind who they email into the choice token, and never run unanswered. The editor says it rather than looping when asked. Events with guests stay read-only until the lift. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C12a A repeating-event write that sends a guest list keeps the user's guest changes on its step. After a 412 the list is built again from Google's copy then, so a reply, comment or extra guests that landed meanwhile are not overwritten. A list Google cut short since is a conflict. Under Send, a resend after a 412 stops when Google's copy would now email anyone else, or not someone it would have. The next save asks again with the new numbers, as a one-off's second round does. Under Don't send, nobody is emailed. The resend test that pinned the old behaviour is updated, and deletes of the whole series or one occurrence are covered. Guests already on Google's list are matched by address and no longer held to PM's address format, so a guest Google accepted (a team calendar with a long name) can be taken off or made optional. A one-off delete asks before the hold only when the row shows guests and nobody answered. The numbers are compared at the send against Google's copy, because the mirror can't say a list was cut short. The plan keeps the calendar's own addresses, so the 412 rule and the counts agree on who the user is. A test covers an account address on the list without Google's self mark. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * test(calendar): review round 2 of C12a The guest list sent once more after a 412 is rebuilt from Google's list, and that rebuilt write must email whom the user chose. The run test now plans the guest change under every answer (all, none, external only) and asserts the first write carries that sendUpdates and the rebuilt write carries the same one, so a fixed or dropped answer on the resend fails. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t shown → 3.154.2-alpha (C12b of #884) (#972) * chore(calendar): the guest list and the update-email question, not yet shown → 3.154.2-alpha (C12b of #884) The editor's guest pieces, built and tested but not mounted: the address check, replies as glyphs, suggestions from people already in the user's Google and Outlook calendars (never a subscription), who a change would email, and the words of the question. The draft holds guests and the three guest permissions, sends changes to Google's list, never the list, and merges a guest list Google changed meanwhile. GuestField, NotifyChoice and NotifyPrompt are new; ConfirmDialog gains confirmDisabled and initialFocus; the which-occurrences dialog asks Send or Don't send for a row that would email guests, with no answer picked. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C12b Suggestions know the user by every account's own address (from the calendar's source, since Outlook never marks the user) and by Google's self mark on the primary calendar only. A colleague whose calendar is shared into the account is now suggested like anyone else. The organiser's address now comes to the editor with Google's copy, whether or not the guest list carries it. The guest list never sends a change to it, matching the backend's check by flags and by address. The guest box won't add it either: it says "That's the organiser, who is on it already." Before, the whole save was refused. A guest added back gets their entry from Google's list as it is now, the rebased base, or comes back as a new guest. Before, they came back as they were when taken off. Removing a guest moves focus to the next row's remove button, else the one before, else the box. Keys sent during IME composition (isComposing or key code 229) are left to the input method. A long refused text wraps. Optional is filled when on and has an --ink4 edge when off. Under Modify event, the other two permissions are drawn checked whatever Google stored. Pasting splits Outlook's unquoted "Last, First <addr>" lists, and each address comes back once. After a conflict, the banner names the guests whose change was dropped instead of calling the merged list Google's version. The rebase now returns those names. The email question's backend note no longer says the user chose before, since it can be the first question. New tests cover replaying a guest removal and a required-again change onto Google's list. The delete-table row the backend doesn't have now has a test of its own. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 2 of C12b Guest suggestions no longer offer the user's own other calendars. On a calendar the user made ("Work"), the entry Google marks as the calendar's own is that calendar's group address, so it now counts as the user, as on the primary calendar. A colleague's calendar shared into the account keeps its owner suggested. The guest field treats the organiser's address as fixed even when Google sent the list without its marks: the row shows "Organiser" and offers no Optional or remove, matching what the save sends. The Optional toggle has a visible disabled look: its words dim to --ink4 and, when on, the accent fill gives way to the surface, as the remove button beside it dims. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…ails them → 3.155.0-alpha (C12c of #884) (#973) * feat(calendar): edit events with guests, and choose whether Google emails them → 3.155.0-alpha (C12c of #884) Events you organise with guests are editable now: the gate's guest refusal and its series flags are gone, the guest list sits in the editor, and every save or delete that would email someone asks Send or Don't send first, with no answer picked, for one-offs and repeating events alike. A change held part-way after a Send says which settles email guests. Help, the settings line and What's New say so. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 1 of C12c After the choices say an event no longer repeats, the one-event save that follows can be asked whether Google emails the guests. That question now opens with the backend's numbers: the editor counts it on its own, so the "doesn't repeat" before it no longer turns it into a banner no Save could get past. Closing the editor with an address it couldn't read in the guest box asks "Discard your changes?" first, as a date it couldn't read does, instead of dropping what was typed. A delete dialog the backend reopens for guests says the event wasn't deleted. When nobody had answered, it says the event has guests now; when an answer came for other numbers, it uses the editor question's neutral words, that PM checked with Google before deleting and these are the numbers it found. The editor's guest suggestions are a required prop, so a caller that leaves them out fails the type check. A Calendar tab test covers the suggestions, and a delete reopened with the backend's numbers and the account Google sends from. The held-delete test now settles with what the backend sends: no "again" when nobody answered, "again" when an answer came for other numbers. The reopened dialog carries it, and so does the notice when something else is open. Two backend tests cover what C12a's review fixes do on a series with guests: a guest list sent again after a 412 keeps a reply that landed meanwhile, and a delete from the editor answered for Google's newer list is held and deleted rather than asked again with PM's older count. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 2 of C12c A delete the backend reopens for guests nobody had answered about no longer says the event "has guests now". When Google removed the guests after the last sync, the backend asks from PM's older row, so that was false. The dialog now says "It wasn't deleted. PM found guests who would be told. Choose whether Google emails them.", which is true either way. The notice shown when another dialog is open said the same thing and now says "PM found guests who would be told. Delete it again to choose whether Google emails them." Nothing else changes: the send still checks Google's copy, so nobody is emailed who shouldn't be. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> * fix(calendar): review round 3 of C12c A guest on an occurrence that a mixed plan cancels was told they would get an update, while Google emails them a cancellation. Example: a weekly series with no series guests and an occurrence on 26-10 with a guest of its own; "This and following" from 19-10 with "Does not repeat" cuts the series and cancels 26-10, and the question said "1 guest already invited will get an update." Reach now keeps who only a delete or a cancel reaches: a merge unions both sides and drops anyone a write other than a cancel reaches on either side. The guest notice carries that count as told_cancelled (never among updated, none when the whole change cancels), and the question gives it its own line, "1 guest will be told an occurrence is cancelled.", beside the update line. A plain delete and a delete's plan keep their wording, everyone told it's cancelled; an invitation-only question is no longer asked when someone is told of a cancel. Tests: rule.rs's a_rule_change_that_reaches_an_occurrence_with_guests_asks_first asserts the new count; attendees.rs adds a_guest_only_a_cancel_reaches_is_told_of_the_cancel (both merge orders, a second write reaching the same guest, two cancels of one guest, taken off as well, plain delete and delete's plan); guests.test.ts adds the wording, the question and the delete sum. Part of #884 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
BBYU26
added this pull request to stack #975
October 22, 2026 02:51
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.
Part of #884. This PR now also carries C10a–C12c. On 2026-10-22 the stack above it (#962–#967, #969–#973) was merged into this branch, so merging this PR lands all of it: 3.149.0-alpha → 3.155.0-alpha. The branch tip has exactly the same content as #973's tip, which passed every check. Stacked on #960: the base is
chore/calendar-series-dialog-parts, and there's no closing link while the base isn't main.Already merged into this branch
Each PR has its own description, reviews and live tests:
C9b-1b (this PR's own change)
What changes for you
Saving or deleting an occurrence of a repeating event opens the "which occurrences?" dialog, as Google Calendar does: This event, This and following events or All events. Each row says what choosing it would do, and nothing is sent until you press Save or Delete. "This event" is picked for you, and focus starts on Back, so pressing Enter on open can't choose.
Backend
Recurringis gone.NeedsChoiceafter the read-only check and before any write.Decisions taken while you were away (recommendations from the spec)
Reviews
useMirrorReads' unmount and failure paths, and both hooks' tests run under StrictMode.Checks
The full
just checkpasses at f42032a: 2,109 Rust tests and 2,108 frontend tests.Live tests for you
docs/calendar-live-tests-2026-10-10.md, section "Repeating events from the dialog (C9b-1b)", D1–D13, on the dev build against the test calendar.🤖 Generated with Claude Code