Skip to content

Propagation and conflicts

When you drag a task, its links decide what else moves. scheduling.propagation chooses between two policies: 'preserve-lag', where the tasks after the edited one move so that every link keeps its lag, and 'none', where only the task you drag moves and the links are just drawn. Either way a link is checked: a successor that sits closer to its predecessor than the link allows is a conflict, drawn in red and reported with the change.

Terminal window
pnpm add @ganttlane/dom # vanilla
pnpm add @ganttlane/react react react-dom # React
View on GitHub
data.ts
import type { Link, Task } from '@ganttlane/core';
// A chain of four tasks and a fifth beside it. 2026-03-02 is a Monday; a task with an `end` ends on
// that day (inclusive), and the milestone has just a `start`.
export const tasks: Task[] = [
{ id: 'design', name: 'Design', start: '2026-03-02', end: '2026-03-06' },
// Starts a day after Design ends (the lag of the first link).
{ id: 'build', name: 'Build', start: '2026-03-08', end: '2026-03-14' },
// Starts two days after Build starts. Its link has no lag, so it keeps the gap it has now.
{ id: 'docs', name: 'Docs', start: '2026-03-10', end: '2026-03-16' },
// Starts the day Build ends.
{ id: 'review', name: 'Review', start: '2026-03-15', end: '2026-03-17' },
// Two days after Review ends.
{ id: 'release', name: 'Release', type: 'milestone', start: '2026-03-20' },
];
export const links: Link[] = [
{ source: 'design', target: 'build', lag: 1 }, // finish-to-start, the default type
{ source: 'build', target: 'docs', type: 'SS' }, // no lag: the gap it has when the data is loaded
{ source: 'build', target: 'review', lag: 0 },
{ source: 'review', target: 'release', lag: 2 },
];

Files

  • data.ts: four tasks in a chain and a fifth beside it, and the links with their lags.
  • options.ts: scheduling.propagation, the calendar and the view mode.
  • helpers.ts: describe, which turns a ChangeSet into a sentence, and the helpers for the buttons.
  • vanilla.ts and react.tsx: the chart, the select, the box and the buttons.

Options

Option Value What it does
scheduling.propagation 'preserve-lag' What an edit does to linked tasks: 'preserve-lag' or 'none' (the select)
calendar.weekends [] Every day is a working day, so the only conflicts are about links
viewMode 'week' One column a week: the whole plan fits
scroll.initial 'first-task' Opens on the plan; the default, 'today', is far from these dates
onBeforeTaskChange a function (in the example) Asked before a change is applied, with its conflicts; false cancels it
onTasksChange a function (in the example) One ChangeSet per change, with every task it moved and its conflicts
link.lag 1, 0, 2, none The gap a link keeps; without one it keeps the gap it has when loaded

What to try

  • Drag Design to the right with preserve-lag. Build, Docs, Review and Release all follow, each keeping its lag. The status line lists the tasks in the order the chart moved them.
  • Switch to none and drag Design again: only Design moves. As soon as Design ends less than a day (the lag of the link) before Build starts, the link turns red, Build gets a red outline, and the status line names the conflict, the gap the link needs and the gap it has.
  • Drag Review to the left, in front of the end of Build: it moves freely, and the link from Build turns red in both modes, because a link is checked against its predecessor whichever policy is on. Release follows Review with preserve-lag and stays with none. Design +3 days and Review -3 days make the same edits from code, with updateTask.
  • Tick Refuse a broken link and repeat: the before-hook sees the conflict in its argument, returns false, and the bar goes back.
  • After a conflict, drag Build (with preserve-lag): the links put Review and Release back at their lags and the conflict goes.

Notes

  • Where propagation goes. An edit travels from the edited task to its successors, never to its predecessors. A task that moves moves its own successors in turn, in one pass, so a cycle cannot loop. A locked task never moves and stops the propagation.
  • Lags. A link with a lag keeps exactly that gap; a link with none keeps the gap it had when the data was loaded (Docs stays two days behind the start of Build).
  • link-violated. A conflict is reported for an active link whose gap is below what it requires: its lag when it has one, or no more overlap than at load when it does not. It has the linkId and the two gaps in milliseconds (requiredMs, actualMs). Drawn: the link and the bar of the successor are red (data-conflict). Reported: changeSet.conflicts (in onTasksChange and onBeforeTaskChange) and record.conflicts (getTask('review').conflicts). A gap wider than the lag is not a conflict.
  • A conflict is a warning. The change is accepted, and nothing is corrected, unless your onBeforeTaskChange returns false (or a modified ChangeSet).
  • 'push-forward' is the policy that would push the successors of an edit forward until every link holds. It is not available yet: scheduling.propagation: 'push-forward' is reported as an unsupported-option issue and 'preserve-lag' is used.
  • React. scheduling is passed to <Gantt> after the spread of options, from state: in React the props are the truth, so an option changed with setOptions on the ref would be overwritten by the next render. onBeforeTaskChange is given inline and always reads the current state.