Skip to content

Dependencies to summary tasks are created, drawn and saved but never enforced #91

Description

@kitikonti

Summary

A dependency whose successor is a summary task can be created through the UI, is persisted to the file, is drawn as an arrow, and is editable in the properties panel — but it never affects scheduling. The constraint is silently a no-op in both directions.

Found while reviewing #88; it is independent of that feature and reproduces on main @ dd0165e (v1.7.9).

Reproduce

  1. Create a summary task with at least one child.
  2. Create a leaf task A that ends after the summary starts.
  3. Hover A, drag from its connection handle, and drop on the summary's bar body.
  4. An arrow is drawn and an FS dependency is created.
  5. Move A later in time.

Expected: either the dependency is rejected at creation, or the summary (and its children) are pushed.
Actual: the arrow is drawn and saved, and nothing is ever rescheduled.

Why it happens

The guard is asymmetric — creation is blocked on one side only, and enforcement is missing entirely.

Site Behaviour
src/components/GanttChart/ChartCanvas.tsx:341 Summaries are excluded from taskGeometriesMap, which feeds ConnectionHandles. → a summary can never be a drag source.
src/components/GanttChart/ChartCanvas.tsx:486 onMouseUp={() => handleTaskMouseUp(task.id, "start")} is bound to the <g> wrapper of every bar, summaries included. → a summary can be a drop target.
src/hooks/useDependencyDrag.ts:98 resolveDragTargets partitions candidates purely on checkWouldCreateCycle. No task-type filter, so summaries land in validTargets.
src/store/slices/dependencySlice.ts validateNewDependency checks self-reference, existence, duplicates and cycles. No task-type guard.
src/utils/graph/dateAdjustment.ts:416 propagateDateChanges does if (task.type === "summary") continue;the constraint is never enforced.

The reverse chain is dead too: when a child moves, changedTaskIds contains the child id, and getSuccessors(child) never reaches the summary's own successors. So A → Summary → B propagates in neither direction.

Additional gaps

  • No test coverage. grep summary returns nothing in dependencySlice.test.ts, useDependencyDrag.test.ts or DependencyArrows.test.tsx.
  • File validation does not check task type. src/utils/fileOperations/validate.ts validates UUIDs, duplicate ids, type ∈ {FS,SS,FF,SF}, lag, self-reference and dangling refs — a .ownchart file containing summary dependencies in either direction loads clean.
  • Indent is unguarded. A leaf that has dependencies and is then indented into becoming a summary keeps them.

Proposed fix — forbid in both directions

Allowing summary dependencies properly would mean defining roll-up scheduling semantics ("push a summary → push all its children by the delta"), which contradicts recalculateSummaryAncestors being the sole writer of summary dates. That is a feature, not a guard.

Prior art: MS Project permits them but restricts and discourages them; Syncfusion blocks predecessors on parent tasks by default; DHTMLX allows them and moves the summary as a unit. None allow one direction while enforcing neither.

  1. validateNewDependency — add a task-type guard returning "Dependencies cannot connect summary tasks — link their child tasks instead".
  2. resolveDragTargets — place summaries in invalidTargets so they render as invalid drop targets during a drag.
  3. ChartCanvas.tsx:486 — skip the onMouseUp binding for summaries (cosmetic, but do both).
  4. validate.ts — add a SUMMARY_DEPENDENCY validation code.
  5. Guard the indent path: drop dependencies when a task becomes a summary, with a toast.

Migration

Make the file-load check a warning, not a rejection: drop the offending dependencies during deserialize, collect the task names, and show one toast — "2 dependencies involving summary tasks were removed (not supported)."

This is safe: the dropped constraints provably had no scheduling effect, so no dates change. Rejecting an entire file over a dead constraint would be hostile, and the only way a user can have one today is a body-drop onto a summary bar, which most will never have discovered.

Tests to add

  • Dragging onto a summary is rejected (drop target renders invalid).
  • addDependency rejects summary source and summary target.
  • Loading a file containing a summary dependency drops it, warns, and leaves all dates unchanged.
  • Indenting a task that has dependencies into a summary drops them.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions