Repository navigation
[scheduler][umbrella] Event scheduling — dependencies, constraints & auto-scheduling engine #22853
Description
This umbrella covers the full event-scheduling model for the Scheduler: a shared auto-scheduling engine driven by two kinds of input — dependencies (relationships between events) and constraints (placement rules for a single event).
These were previously split across #21864 (dependencies) and #21582 (constraints), but they're driven by the same engine — moving an event cascades through dependencies while respecting constraints. Tracking them together keeps that one engine coherent.
Dependencies — relationships between two events
A dependency links a predecessor to a successor and is rendered as an arrow on the timeline. It expresses how the position of one event conditions the other. There are four logical types, named by which ends (start/finish) are linked:
| Type | Meaning | Example |
|---|---|---|
| FS — Finish-to-Start | Successor can't start until predecessor finishes | "Paint the wall" can't start until "plaster" finishes. ~90% of real cases. |
| SS — Start-to-Start | Successor can't start until predecessor starts | "Pour concrete" and "smooth it" start together (smoothing trails the pour) |
| FF — Finish-to-Finish | Successor can't finish until predecessor finishes | "Review docs" can't finish until "write docs" finishes |
| SF — Start-to-Finish | Successor can't finish until predecessor starts | Shift handover: the outgoing shift can't end until the incoming one starts. Rare. |
Rendering (FS):
Predecessor ████████┐
└──▶ Successor ██████ (arrow: predecessor end → successor start)
Lag / lead — an optional offset on the relationship:
- Lag (positive): "FS + 2 days" → successor starts 2 days after the predecessor finishes.
- Lead (negative): "FS − 1 day" → the two overlap by 1 day.
Component scope: EventTimeline only (arrows only make sense on a timeline/Gantt).
Constraints — placement rules for a single event
A constraint does not relate two events. It limits where one event on its own can be placed in time. These are the per-event scheduling rules (MS Project / Bryntum terminology):
| Constraint | Meaning |
|---|---|
| ASAP — As Soon As Possible | Place it as early as possible (default) |
| ALAP — As Late As Possible | Place it as late as possible |
| SNET — Start No Earlier Than | Can't start before date X |
| SNLT — Start No Later Than | Can't start after date X |
| FNET — Finish No Earlier Than | Can't finish before date X |
| FNLT — Finish No Later Than | Can't finish after date X |
| MSO — Must Start On | Must start exactly on date X |
| MFO — Must Finish On | Must finish exactly on date X |
Example: "Inspection can't start before March 1st" (SNET) even if all preceding tasks finish earlier.
Component scope: EventTimeline + EventCalendar (a fixed-date rule applies to both).
Auto-scheduling engine — shared by both
When an event moves or resizes, the engine reschedules dependent events (following the dependency types) while respecting each event's constraints. It also rejects invalid graphs (cycles).
How they interact: you move "plaster" 3 days later → "paint" reschedules behind it (FS), but if "paint" has an SNET of March 1st, it won't move before that date even though the arrow would push it there. Dependencies push; constraints set the limits; the engine reconciles both.
Architecture — plugin-based
Dependencies live only in EventTimelinePremiumStore; constraints apply to both EventTimelinePremiumStore and EventCalendarPremiumStore. Those two premium stores don't share a premium base (timeline extends SchedulerStore directly; calendar extends the community calendar store), so shared scheduling logic has no natural home in a store class.
The solution — already used twice in the codebase — is a plugin in x-scheduler-internals-premium:
- Lazy loading: a shared
SchedulerLazyLoadingPlugin(holds astoreref, lifecycle viaDisposableStack) specialized by thin per-product subclasses. - Recurring events: an interface in the community base + implementation injected into
SchedulerStore, which delegates to it — premium logic never leaks into community.
So the scheduling engine (data model + cascade + constraint reconciliation) should be a SchedulerSchedulingPlugin in x-scheduler-internals-premium, composed by each store:
SchedulerSchedulingPlugin (x-scheduler-internals-premium — data model + engine)
├─ EventTimelinePremiumStore → dependencies + constraints
└─ EventCalendarPremiumStore → constraints only
Benefits (all proven by the existing plugins): a neutral home for the shared engine, lifecycle (dispose) for the engine's subscriptions, and premium code kept out of the community package.
The plugin is deliberately store-agnostic: if we ever decide to export a standalone Gantt component (not decided — just keeping the door open), its store would compose the same plugin and reuse the same headless TimelineGrid parts, rather than wrapping the public EventTimelinePremium. That's Bryntum's proven model — Scheduler Pro and Gantt are two products sharing one engine (ChronoGraph) and one DependencyModel. The only Timeline-bound piece today is where the dependencies state slice is declared (EventTimelinePremiumState), which is an internal, non-breaking refactor away from being shared.
Scope this quarter — FS end-to-end (vertical slice)
We ship Finish-to-Start (FS) end-to-end, including auto-scheduling: moving/resizing a predecessor reschedules its FS successors. The data model ships only what is implemented: the type union starts with FS, and the other types plus lag (with its unit) are added together with their implementations as additive changes — see #22854.
Being the first behavior, FS carries the shared foundation (data model, rendering, interaction, engine, cycle guard). Subsequent behaviors reuse it and are much lighter.
The FS behavior is broken down in the sub-issues linked to this issue (data model, render arrow, create/edit/delete, auto-scheduling engine, cycle guard).
Later behaviors (backlog)
- Dependencies: SS · FF · SF (enforcement + arrow geometry) · lag/lead time
- Constraints: SNET/SNLT · FNET/FNLT · MSO/MFO · ASAP/ALAP
Choosing the dependency type (future UX)
Two mechanisms, used together:
-
Implicit — terminal-driven. Events get terminals on both edges (start + end). The type is encoded by which edge you drag from and drop on (
fromSide/toSide):From… To… Type end (finish) start FS start start SS end end FF start end SF -
Explicit — arrow editor. Select an arrow → change its type (FS/SS/FF/SF) and
lagfrom a menu. Needed for precision and as the keyboard/a11y path (corner-drag is too fine-grained).
Groundwork already in v1: the type field exists in the data model (#22854) from day 1, starting as an FS-only union that widens per type as they are implemented (store type as the semantic truth; treat fromSide/toSide as a render/interaction detail). v1 wires only the end-edge terminal (FS) (#22856); the future is adding the start-edge terminals → unlocks SS/FF/SF by geometry, plus the editor for type + lag. No redesign needed.
Standards & foundations
What this design is based on — three different levels of grounding:
Formal industry standards (the semantics). The dependency types and terminology are not vendor inventions:
- FS/SS/FF/SF and lag/lead come from the Precedence Diagramming Method (PDM), codified in the PMI PMBOK Guide ("Sequence Activities" process), on top of the Critical Path Method (CPM) (Kelley–Walker, 1959).
- The constraint types (ASAP … MFO) are MS Project terminology adopted by the whole market.
- The DCMA 14-point assessment (original PDF) is the de-facto schedule-quality bar: it requires ≥90% of relationships to be FS (and ≤5% with lag, zero leads) — the empirical backing for the FS-first scope.
De-facto market conventions (no formal spec exists). There is no normative standard for the data model or the engine behavior; the design follows where vendors converge:
- Data model
source/target/type/lag+lagUnit= the intersection of Bryntum, DHTMLX and Syncfusion ([scheduler] Dependencies: data model #22854). - Cycle rejection — the graph must stay a DAG (Directed Acyclic Graph): all vendors ([scheduler] Dependencies: cycle guard #22858).
- Arrow rendering — SVG overlay, orthogonal routing, arrowhead at the successor's start: all vendors except Frappe ([scheduler] Dependencies: render the FS arrow #22855).
- Where the market genuinely splits — push-only vs pull-back — there is no standard to follow: v1 adopts DHTMLX's default (
gap_behavior: "preserve") and MS Project's Manually Scheduled mode, with pull-back (Bryntum's and classic MS Project auto-scheduling's default) documented as a future opt-in ([scheduler] Dependencies: auto-scheduling engine (FS) #22857).
No standard, no prior art — original design with documented rationale:
- Accessibility of dependency arrows: no WAI-ARIA pattern covers dependency graphs, and no competitor exposes the relationship to assistive tech — the
aria-hiddenarrow +aria-describedbyon the successor ([scheduler] Dependencies: render the FS arrow #22855) is our own design and a differentiation opportunity. - Recurring events × dependencies: no product in the market combines them (see Competitors below); the v1 rejection policy ([scheduler] Dependencies: data model #22854) is backed by the absence of prior art, not by prior art.
Competitors
- Bryntum (Scheduler Pro / Gantt), DHTMLX, Syncfusion — all have dependencies + constraints.
- FullCalendar has event constraints but no dependencies (open request Gantt chart arrows between events for timeline view fullcalendar/fullcalendar#4748).
- No competitor combines recurring events with dependency scheduling. Bryntum Scheduler Pro has both features but never combines them (its recurrence demo disables dependencies); DHTMLX and Syncfusion split them across products (Scheduler = recurrence, Gantt = dependencies); MS Project / Wrike escape the problem by materializing each occurrence as a standalone task. This backs the v1 policy of rejecting dependencies on recurring events — details in [scheduler] Dependencies: data model #22854.
Folds in and supersedes #21864 (dependencies) and #21582 (constraints).
Activity
rita-codes commentedon Jul 9, 2026
Note on exposure: the dependencies sub-issues (#22854–#22858) will land internal-only — no public props, no public API methods, no docs — so the incremental PRs don't surface a half-built feature to beta users. The feature becomes public in a deliberate final PR once FS works end-to-end. Mechanics in #22854.