Skip to content

[scheduler] Carry custom event data through the edit dialog submit#22874

Merged
rita-codes merged 6 commits into
mui:masterfrom
Anexus5919:feat/scheduler-edit-dialog-carry-custom-data
Jul 16, 2026
Merged

[scheduler] Carry custom event data through the edit dialog submit#22874
rita-codes merged 6 commits into
mui:masterfrom
Anexus5919:feat/scheduler-edit-dialog-carry-custom-data

Conversation

@Anexus5919

@Anexus5919 Anexus5919 commented Jun 17, 2026

Copy link
Copy Markdown
Contributor

Part of #22866
Closes #22869

Motivation

The event edit dialog only ever submits a fixed set of built in fields (title, description, allDay, resource, color plus start/end/rrule). Events are generic over TEvent, so a user event can carry its own custom fields. Today those fields are silently dropped on some edit paths, which blocks the broader edit dialog customization work where custom sections will read and write custom data.

This PR is the persistence slice of the umbrella. It does not touch the validation or lifecycle contract (that is the separate sub issue #22868).

The actual bug

I traced all four edit paths. Only two of them lose custom data:

Path Mechanism Custom data
Non recurring updateEvent store merges changes onto the full stored model preserved
Recurring, scope all returns an update, same merge preserved
Recurring, scope only this builds a new event from modelInBuiltInFormat dropped
Recurring, scope this and following builds a new split event from modelInBuiltInFormat dropped

modelInBuiltInFormat is a strict projection of the 16 built in SchedulerEvent keys, so the two create scopes rebuild a fresh event without any custom keys.

What changed

  • New helper getCustomEventProperties(model) in SchedulerStore.utils.ts that returns only the keys of an event model that are not part of the built in SchedulerEvent shape.
  • The store resolves custom data at confirm time. After the recurring plugin produces the split events, selectRecurringEventScope carries the current model's custom keys onto the newly created events (the only ones rebuilt from the built in shape). Non recurring and all rely on the existing { ...oldModel } store merge, which already preserves custom data.
  • No change to FormContent or the public store API.

Why confirm time, not submit time

For a recurring event the dialog submit only opens the scope dialog. The actual write happens later, when the user confirms a scope. Resolving custom data at confirm time, against the live model, avoids replaying a stale snapshot if the event's custom data changes while the dialog is open. A submit time snapshot would let the all scope overwrite newer values, so the preservation belongs in the store, not in the form.

Testing

  • Unit test for getCustomEventProperties.
  • Store level test confirming updateEvent does not strip unknown fields.
  • Dialog integration tests in the premium suite: non recurring, and recurring all, only this, this and following, each asserting the custom field survives via onEventsChange.
  • A regression test asserting the latest custom data is used when it changes while the scope dialog is open. It fails on a submit time snapshot and passes with the confirm time resolution.
  • Verified the create scope tests fail without the fix, so they genuinely guard the behavior.
  • Reproduced manually in a live EventCalendarPremium demo: editing an occurrence with a custom priority field and choosing "Only this event" produced a new event missing priority before the fix, and carrying priority after.

pnpm eslint, pnpm prettier, and TypeScript across the touched packages all pass.

  • I have followed (at least) the PR section of the contributing guide.

@Anexus5919

Copy link
Copy Markdown
Contributor Author

@rita-codes Could you please review this PR when you have a chance?
Thanks!

@code-infra-dashboard

code-infra-dashboard Bot commented Jun 17, 2026

Copy link
Copy Markdown

Deploy preview

https://deploy-preview-22874--material-ui-x.netlify.app/
QR code for https://deploy-preview-22874--material-ui-x.netlify.app/

Bundle size

Bundle Parsed size Gzip size
@mui/x-data-grid 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-pro 0B(0.00%) 0B(0.00%)
@mui/x-data-grid-premium 0B(0.00%) 0B(0.00%)
@mui/x-charts 0B(0.00%) 0B(0.00%)
@mui/x-charts-pro 0B(0.00%) 0B(0.00%)
@mui/x-charts-premium 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers 0B(0.00%) 0B(0.00%)
@mui/x-date-pickers-pro 0B(0.00%) 0B(0.00%)
@mui/x-tree-view 0B(0.00%) 0B(0.00%)
@mui/x-tree-view-pro 0B(0.00%) 0B(0.00%)
@mui/x-scheduler 🔺+188B(+0.05%) 🔺+95B(+0.10%)
@mui/x-scheduler-premium 🔺+188B(+0.04%) 🔺+76B(+0.06%)
@mui/x-chat 0B(0.00%) 0B(0.00%)
@mui/x-license 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1b584ed304

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

);

const changes: SchedulerEventUpdatedProperties = {
...customData,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Avoid replaying stale custom data for recurring updates

Because these custom fields are captured when Save opens the recurring-scope dialog, they are later replayed from pendingRecurringEventOperation.changes when the user confirms a scope. If a controlled events prop (or another store update) changes the same recurring event's custom fields while the scope dialog is open, choosing a scope such as “All events” now writes this stale snapshot back over the current model; before this change that path omitted custom fields and updateEvents preserved the latest model values. Resolve the custom data at confirm time or avoid adding preserved fields to recurring updates that modify the original event.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Good catch. The submit-time snapshot could replay stale custom data on the "All events" scope. I have moved the preservation to confirm time: the dialog no longer carries custom data in changes, and selectRecurringEventScope now carries the current model's custom fields onto the events it creates. Non-recurring and "All events" rely on the existing store merge. Added a regression test that fails on the old approach and passes now.

@rita-codes

Copy link
Copy Markdown
Member

Hi @Anexus5919, apologies, this one's on us. The on hold label was only on the umbrella (#22866), not on this sub-issue, so you had no way to know. I've added it to the children now 🙏

This is genuinely great work, though. The issue is part of the edit-dialog customization umbrella, which is planned for after stable and still in the design phase (we're defining the API), so it's on hold for now.

Your approach is spot-on, so I'll leave the PR open and we'll come back to it once the design is locked. Thanks a lot for the care you put into this! 🙇‍♀️

@rita-codes rita-codes added on hold There is a blocker, we need to wait. type: new feature Expand the scope of the product to solve a new problem. scope: scheduler Changes related to the scheduler. labels Jun 18, 2026
@Anexus5919

Copy link
Copy Markdown
Contributor Author

@rita-codes

No worries at all, thanks for clarifying and for adding the label to the children 🙏

Totally understand it's on hold while the API design is being defined, that makes sense. Happy to leave it open and I'll pick it back up whenever the design is locked, including reworking it if the final API shifts. Thanks for the kind words and the thoughtful review! 🙇

@rita-codes rita-codes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

First, an update on the hold: although the umbrella (#22866) stays on hold while we design the customization API, this fix doesn't touch any of the surface being designed — it's store-internal and fixes data loss that exists today — so we're unblocking it and moving forward now.

Solid, surgical fix with genuinely guarding tests — I verified locally that the two split-scope tests fail without the store change and pass with it. One change requested before merge: the same mechanism loses custom data on two neighboring paths, and they should be covered here.

Bugs (2)

1. 🔴 duplicateEventOccurrence and pasteEvent (copy) still drop custom data through the same mechanism

Location: packages/x-scheduler-internals/src/internals/utils/SchedulerStore/SchedulerStore.ts:648

The root cause this PR fixes is "event rebuilt from modelInBuiltInFormat", and two more paths rebuild the same way: duplicateEventOccurrence (it even shares extractStandaloneEvent with the only-this scope — its JSDoc promises "the same properties as the original event except" start/end/rrule, which is currently untrue for custom fields) and pasteEvent in copy mode (cut is an update, so it already preserves). All these creation paths already tag the new event with extractedFromId, so there's a natural single place to apply the carry instead of special-casing it per path.

Failure scenario: duplicating an occurrence — or copy-pasting an event — with a custom priority field produces a new event without priority: the same data loss this PR fixes, one method below.

Fix: please extend the custom-data carry to cover duplicateEventOccurrence and pasteEvent (copy) in this PR, with tests mirroring the split-scope ones.

2. ℹ️ getCustomEventProperties classifies mapped model keys as custom when eventModelStructure is used

Location: packages/x-scheduler-internals/src/internals/utils/SchedulerStore/SchedulerStore.utils.ts:85

The helper filters the raw model by built-in property names. When a consumer maps fields via eventModelStructure (e.g. a getter reading startDate for start), the mapped source keys are not in EVENT_PROPERTIES_LOOKUP, so their stale values get carried onto the split event. Today the outcome is still correct because in createOrUpdateEventModelFromBuiltInEventModel setters run after the direct key copies and overwrite the stale values — but that's an implicit ordering dependency, not an expressed invariant. (Getter-without-setter mappings can't produce a correct split event either way, which is pre-existing.)

Failure scenario: none user-visible today; a future reordering in createOrUpdateEventModelFromBuiltInEventModel would silently make split events resurrect stale mapped values.

Fix: worth a test that exercises the carry with an eventModelStructure mapping, so the interaction is pinned rather than incidental.

Tests (1)

1. ℹ️ The confirm-time freshness test only exercises the scope that doesn't use the new code

Location: packages/x-scheduler-premium/src/event-calendar-premium/tests/EventDialog.test.tsx:2186

The "latest custom data" regression test uses scope all, whose preservation comes from the pre-existing store merge. The confirm-time lookup this PR introduces is only hit by the split scopes.

Failure scenario: a future change that snapshots custom data earlier for the split paths would slip past this test.

Fix: a variant of the same test with only-this would pin the confirm-time resolution of the code this PR actually adds.

Simplifications (0)

No findings.

Docs (0)

No findings.

Verdict

Request changes — the fix itself is correct and well-tested, but the same data loss remains on duplicateEventOccurrence and copy-paste and should be covered before this merges.

@Anexus5919

Copy link
Copy Markdown
Contributor Author

Thanks, all three are fair.

  • Moved the carry to a single place: the created loop in updateEvents, keyed on extractedFromId. That covers only-this, this-and-following, duplicate, and paste-copy at once, and I dropped the scope-specific block from selectRecurringEventScope. Added tests for duplicate and paste-copy.
  • Added a test that pins the eventModelStructure interaction (mapped keys refreshed by the setter, custom field preserved).
  • Added an only-this variant of the freshness test so it exercises the confirm-time path this PR adds. Verified the new tests fail without the carry.

@rita-codes rita-codes left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thanks for taking care of this, nice fix!!! 👏

@rita-codes
rita-codes merged commit f48e1f6 into mui:master Jul 16, 2026
23 of 24 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

scope: scheduler Changes related to the scheduler. type: new feature Expand the scope of the product to solve a new problem.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[scheduler] Edit dialog: carry custom data through submit

2 participants