Skip to content

fix(store): propagate organizer/attendees to recurrence-exceptions on…#8456

Open
ndo84bw wants to merge 1 commit into
nextcloud:mainfrom
ndo84bw:investigate/8450-future-update-attendee-propagation
Open

fix(store): propagate organizer/attendees to recurrence-exceptions on…#8456
ndo84bw wants to merge 1 commit into
nextcloud:mainfrom
ndo84bw:investigate/8450-future-update-attendee-propagation

Conversation

@ndo84bw

@ndo84bw ndo84bw commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #8450.

When the organizer applies "Update this and all future" to the master occurrence (NR-1) of a recurring series that already contains a moved occurrence (RECURRENCE-ID override), calendar-js' _overridePrimaryItem() copies the new state onto the master but leaves the existing override VEVENT untouched. Consequence: if the user just added the first attendee (and thus the ORGANIZER), the override ends up without either property. Two visible symptoms:

  • Attendee side: Sabre's TipBroker omits the override from the iTip REQUEST (no ATTENDEE = no recipient on that VEVENT), so the attendee's client expands the RRULE normally and shows the moved occurrence at its original time.
  • Organizer side: the Nextcloud WebUI crashes silently inside OrganizerListItem.vue (organizer.attendeeProperty.getParameterFirstValue(...) on undefined), the attendee list does not render, and the override appears empty even though Thunderbird and other CalDAV clients display it correctly.

Fix

+30 LOC in src/store/calendarObjectInstance.js: one new file-local helper syncMasterParticipantsOntoExceptions(master, exceptions) plus a four-line guarded call inside saveCalendarObjectInstance.

Immediately after eventComponent.createRecurrenceException(thisAndAllFuture) returns, if the call entered the master-in-place override path (thisAndAllFuture && isExactForkOfPrimary && primaryItem.isMasterItem()), iterate the recurrence-exceptions whose RECURRENCE-ID is at or after the master start and overwrite each exception's ATTENDEE list and ORGANIZER with clones of the master's. The RECURRENCE-ID >= master start filter is defensive: for any RFC-compliant series every exception already satisfies this, so in practice the filter selects all exceptions, but it guards against pathological data where an exception's RID precedes the current master start. ORGANIZER on the exception is only deleted when a replacement exists, to avoid introducing the same crash this PR fixes.

The fix lives in the calendar app store rather than in calendar-js's _overridePrimaryItem() for delivery speed; the same propagation arguably belongs one layer down so every consumer of createRecurrenceException(true) on a master-aligned fork benefits. A follow-up against nextcloud/calendar-js is a reasonable next step.

Trade-offs

  • Scope limited to ATTENDEE and ORGANIZER. The same shape of bug almost certainly applies to any other master-level property edit (SUMMARY, DESCRIPTION, LOCATION, ...) when "this and all future" is applied to the master in place: the override keeps its stale value while the master gets the new one. Out of scope for this PR, which targets the reported Adding an attendee to a recurring series with a moved occurrence - moved instance is invisible to the attendee, and shows no attendees to the organizer #8450 attendee-visibility regression specifically. Worth a follow-up if anyone reproduces another property surfacing the same way.
  • Wholesale replacement of override attendees, not a delta. The Nextcloud WebUI does not currently expose per-occurrence attendee editing, but other CalDAV clients (Thunderbird) do. Any per-occurrence attendee customisation a user has set on an override is overwritten by this propagation. Acceptable for the reported scenario; a future enhancement could diff against the pre-update master to preserve overrides whose attendees diverge intentionally.
  • Per-occurrence PARTSTAT, RSVP, SCHEDULE-STATUS on an override-side attendee are similarly overwritten in the same gesture. This is the intended semantics for "Update this and all future" applied to attendees.

Test plan

Manual repro on a live Nextcloud 33 / Calendar 6.4.2 instance, two real accounts (organizer + attendee), both Thunderbird CalDAV and the NC WebUI verified:

  • Create a daily/4× series with no attendees.
  • Move NR-2 (+1h), "Update this occurrence".
  • Open NR-1, add an attendee, "Update this and all future".
  • Reopen NR-2 as organizer in popover and full editor: ORGANIZER and ATTENDEE both rendered.
  • Verify the override VEVENT in the iTip REQUEST received by the attendee carries the same ATTENDEE and ORGANIZER as the master (Thunderbird as ground truth: the moved occurrence shows up with organizer and attendee on it).
  • Attendee's CalDAV-synced calendar (Thunderbird and NC WebUI) shows the moved NR-2 at the new time with the attendee listed.
  • Unit test in tests/javascript/unit/store/calendarObjectInstance.test.js — file does not exist yet; happy to add one in a follow-up if maintainer prefers that here.

Related / follow-ups

🤖 AI (if applicable)

  • The content of this PR was partly or fully generated using AI

@codecov

codecov Bot commented Jun 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 88.88889% with 2 lines in your changes missing coverage. Please review.

Files with missing lines Patch % Lines
src/store/calendarObjectInstance.js 88.88% 1 Missing and 1 partial ⚠️

📢 Thoughts on this report? Let us know!

@SebastianKrupinski

Copy link
Copy Markdown
Contributor

Hi @ndo84bw

Thank you for the PR, I will review/test this when I have some time.

… master in-place update

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Signed-off-by: Nico Donath <ndo84bw@gmx.de>
@ndo84bw ndo84bw force-pushed the investigate/8450-future-update-attendee-propagation branch from b7c2047 to 3952345 Compare June 11, 2026 08:39

@DerDreschner DerDreschner left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tested successfully

@DerDreschner DerDreschner added 3. to review Waiting for reviews bug skill:frontend Issues and PRs that require JavaScript/Vue/styling development skills labels Jun 11, 2026

@SebastianKrupinski SebastianKrupinski left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @ndo84bw

I am going to have to block this PR for now. If I am reading the code correctly, there are some side effects of this change.

When this is used on the base event (master in your words) this loops through all exemptions and overrides the attendees and organizer.

The first problem with this is that a recurring exemption can have all kinds of changes, one is that you can have a different organizer for a particular day.

The other major issue is that you are completely overriding attendee responses.

 exception.deleteAllProperties('ATTENDEE')
for (const att of masterAttendees) {
	exception.addProperty(att.clone())
}

This code deletes the property competely, and applies the cloned attendees from the base event, this means that you are applying responses from the base event to the exception.

So if a attendee responded "declined" to a exception, this would then override that with what the status is in the base event

Essentially the issue is what is already listed in your "Trade-offs" section.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

3. to review Waiting for reviews bug skill:frontend Issues and PRs that require JavaScript/Vue/styling development skills

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Adding an attendee to a recurring series with a moved occurrence - moved instance is invisible to the attendee, and shows no attendees to the organizer

3 participants