Skip to content

[scheduler-premium] Dependencies - Auto-scheduling engine (FS) - #23439

Draft
rita-codes wants to merge 34 commits into
mui:masterfrom
rita-codes:22857-scheduler-dependencies-auto-scheduling-engine-fs
Draft

[scheduler-premium] Dependencies - Auto-scheduling engine (FS)#23439
rita-codes wants to merge 34 commits into
mui:masterfrom
rita-codes:22857-scheduler-dependencies-auto-scheduling-engine-fs

Conversation

@rita-codes

@rita-codes rita-codes commented Aug 27, 2026

Copy link
Copy Markdown
Member

Closes #22857. Part of #22854. Built on top of #23389.

Adds the Finish-to-Start auto-scheduling engine: when an updateEvents batch moves an event, its successors (through the dependencies graph) are pushed later so that successor.start >= predecessor.end holds transitively, and the extra updates are emitted in the same onEventsChange batch.

Design

  • Pure engine. computeAutoSchedulingCascade (in x-scheduler-internals-premium/src/internals/utils/auto-scheduling.ts) is a pure function over the pre-update state plus the batch's updated/deleted entries. The scheduling plugin calls it from handleEventsUpdate and appends the cascade to the batch.
  • Push-only. An event only ever moves later: a successor moves only when the constraint is actually broken, moving a predecessor earlier never pulls successors back, and pre-existing violations in the data stay as-is.
  • Dropping into violation clamps forward. A batch entry that actually moves start is the user placing that event, so when it lands before an active predecessor's end it snaps to the first valid position (keeping its duration), and its successors cascade from the clamped dates. This matches the hard-constraint behavior of established Gantt tools (verified against Bryntum): the drop gesture cannot leave the dropped event's incoming arrows violated. An entry that resends unchanged dates (the event dialog does, on every save) repositions nothing and pushes nothing, an end resize is never repositioned but is pushed when its own batch creates the violation, and the clamped dates fold back into the entry itself, preserving its other properties.
  • Seeding mirrors the store. Whole-entry last-wins per id, and an allDay-only entry cascades (it moves the effective end). A timed event pushed or clamped behind an all-day predecessor lands on the first instant of the next day — the inclusive 23:59:59.999 end would not survive the second-resolution wall-time serialization.
  • Kahn topological pass over the subgraph reachable from the seeds, each node settled once with the max of its constraining predecessors' ends — O(V' + E'), no re-enqueueing (a BFS that re-enqueues is exponential on stacked diamond graphs). Members of a seedless cycle coming from cyclic props data never become ready: they stay unmoved with a dev warning, while a cycle through a batch entry is broken at that entry, with a dev warning (the UI itself can no longer create cycles since [scheduler-premium] Dependencies - Reject dependencies that would create a cycle #23389).
  • Duration policy. Timed successors keep their duration in absolute milliseconds (same policy as drag-and-drop and paste); all-day successors shift by whole days, preserving their day span.
  • Read-only blocks the batch. A cascade that would need to move a read-only event cannot restore the constraint, so the whole batch is vetoed before anything is emitted (no onEventsChange, no persistEvents, no partial application) and the rejection surfaces as a transient toast naming the blocked event. Together with the clamp, no gesture can leave a violated arrow. The veto is observable: updateEvent returns whether the batch was applied — a vetoed dialog save keeps the dialog open with the user's edits, a vetoed drop of the edited occurrence does not repoint the editing surface, and a rejected cut paste keeps the clipboard.
  • Entries resolve in the data timezone. The engine converts entry dates to the event's data timezone before normalizing, mirroring how the store applies them (dateToEventString); without it, cross-timezone all-day entries were classified against display-zone bounds and the cascade disagreed with the store's effective result. Heads-up caught while testing this: the dialog builds all-day bounds in the display timezone (computeRange), so a cross-timezone all-day save genuinely stretches the event by a day — a pre-existing upstream issue, documented (not fixed) by the mirrored tests here.
  • Always active when dependencies is provided — no flag.

The two design inputs from #22854

This settles the two points raised in the data-model PR:

  1. Cascade vs. async persistence: the synchronous atomic cascade wins. With a dataSource, the cascade is computed and emitted inside updateEvents, and persistence follows asynchronously as usual — same trade-off (and same v1 no-rollback limitation) as the cascade-delete from [scheduler-premium] Add the dependencies data model #23117.
  2. Seam signature: the seam keeps receiving the raw pre-merge UpdateEventsParameters. handleEventsUpdate now returns { updated } | void, is called before the merge loop, and the returned entries are appended to the batch (never rebuilt, preserving insertion order). The engine re-resolves the post-update dates itself from the partial changes.

Tests

  • 51 unit tests on the pure engine (chains, diamonds, all-day/timezone/DST fixtures, drop clamping, dialog resends, multi-entry batches, read-only barriers, cycles, deleted/recurring/unloaded exits).
  • 12 store-level tests on the seam, the read-only veto and its atomicity, emission batching, and the persisted cascade.
  • 3 component-level tests driving the push, the clamp, and the read-only rejection through the real drag-and-drop pipeline, plus a dialog test pinning that a vetoed save keeps the dialog open.
  • A TimelineAutoScheduling demo under docs experiments for manual testing.

rita-codes and others added 19 commits August 20, 2026 17:46
…ate a cycle

Closes mui#22858

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpQTQZthCBgqQwXHfxzi7p
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpQTQZthCBgqQwXHfxzi7p
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpQTQZthCBgqQwXHfxzi7p
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpQTQZthCBgqQwXHfxzi7p
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpQTQZthCBgqQwXHfxzi7p
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
…heduler-dependencies-auto-scheduling-engine-fs
…ependencies-auto-scheduling-engine-fs

# Conflicts:
#	packages/x-scheduler-internals-premium/src/event-timeline-premium-selectors/eventTimelinePremiumDependencySelectors.ts
#	packages/x-scheduler-internals-premium/src/internals/plugins/SchedulerSchedulingPlugin.ts
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
@rita-codes rita-codes added plan: Premium Impact at least one Premium user. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. scope: scheduler Changes related to the scheduler. labels Aug 27, 2026
@code-infra-dashboard

code-infra-dashboard Bot commented Aug 27, 2026

Copy link
Copy Markdown

Deploy preview

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 🔺+262B(+0.07%) 🔺+80B(+0.07%)
@mui/x-scheduler-premium 🔺+3.59KB(+0.65%) 🔺+1.19KB(+0.77%)
@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.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
@rita-codes rita-codes self-assigned this Aug 27, 2026
Aligns the drop gesture with the hard-constraint behavior of established
Gantt tools: a successor dropped in violation snaps forward to the first
valid position instead of keeping a violated arrow.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
@rita-codes
rita-codes force-pushed the 22857-scheduler-dependencies-auto-scheduling-engine-fs branch from 3429194 to 61835c0 Compare August 27, 2026 16:07
rita-codes and others added 2 commits August 28, 2026 11:04
…rness

In browser mode the title-column and viewport observers deliver on the frames
after the render, outside act, and the un-acted update fails the run through
the console guard depending on runner timing. renderTimeline now waits two
native frames inside act (real rAF captured at module load, so fake-timer
tests cannot stall it) before returning.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01XxvxJpRPn5iPoo6jvxnXFc
rita-codes and others added 8 commits August 28, 2026 12:08
The muting tests merged from master still called the now-async
renderTimeline without awaiting it, leaving the absorb act floating over
the test body; drag start/end and mid-drag scrolls also perturb layout,
so withDrag and the remount test absorb the observer frames too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
…gine

- An entry only repositions (and clamps) its event when it actually moves
  the start, and only date-changing entries push their successors: the
  event dialog resends unchanged dates on every save, which used to snap
  pre-existing violations and cascade their chains on a rename.
- Seeding mirrors the store's whole-entry last-wins per id, and an
  allDay-only entry now cascades (it moves the effective end).
- An end-resized entry violated by its own batch is pushed instead of
  being left as a false "pre-existing" violation.
- A timed event lands on the first instant after an all-day predecessor:
  the inclusive 23:59:59.999 end does not survive the second-resolution
  wall-time serialization, which kept the constraint violated by 999ms.
- Breaking a cycle at an updated event now warns in dev, cascade entries
  are emitted at settle time, and the duplicated constraint loops merged.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
…o-scheduling-engine-fs' into 22857-scheduler-dependencies-auto-scheduling-engine-fs
The only import mui#23442 missed.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
The last hole in the hard-constraint story: a push into a read-only
event silently left the violated arrow behind. The engine now reports
the blocked members, the scheduling plugin vetoes the whole batch before
anything is emitted, and the rejection surfaces as the same transient
toast the create-dependency guards use. A rejected cut paste keeps the
clipboard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 31, 2026
@github-actions

Copy link
Copy Markdown
Contributor

This pull request has conflicts, please resolve those before we can evaluate the pull request.

rita-codes and others added 2 commits August 31, 2026 11:53
- Entry dates now resolve in the event's data timezone before normalizing,
  mirroring how the store applies them: cross-timezone all-day entries
  were compared in the display zone, so the engine disagreed with the
  store's effective bounds. (The dialog building all-day bounds in the
  display zone stretches such events — an upstream, pre-existing issue
  the mirrored tests document.)
- updateEvent returns whether the batch was applied: a vetoed dialog
  save keeps the dialog open with the user's edits, and a vetoed drop of
  the edited occurrence no longer repoints the editing surface.
- A cascade target missing from the lookup is skipped instead of
  crashing, every blocked read-only successor is reported, and the veto
  pins its atomicity: no persistEvents, whole-batch discard, toast
  naming the blocked event, no ghost placeholder.
- The engine JSDoc sits back on the function, the demo's read-only
  comment matches the veto, and the row-bounds test helper is shared.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01RDEgka4iDRFrenWeWFhunq
…ependencies-auto-scheduling-engine-fs

# Conflicts:
#	packages/x-scheduler-premium/src/event-timeline-premium/tests/dependencyTerminals.EventTimelinePremium.test.tsx
#	packages/x-scheduler-premium/src/event-timeline-premium/tests/dragAndDrop.EventTimelinePremium.test.tsx
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label Aug 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

plan: Premium Impact at least one Premium user. scope: scheduler Changes related to the scheduler. type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[scheduler] Dependencies: auto-scheduling engine (FS)

1 participant