Skip to content

fix(tracker): Gantt cascade — do not pull a predecessor back when its due falls on a non-working day - #11054

Merged
ArtyomSavchenko merged 1 commit into
hcengineering:developfrom
MichaelUray:fix/gantt-fs-cascade-nonworking-due
Oct 2, 2026
Merged

ArtyomSavchenko merged 1 commit into
hcengineering:developfrom
MichaelUray:fix/gantt-fs-cascade-nonworking-due

Conversation

@MichaelUray

Copy link
Copy Markdown
Contributor

Follow-up to #10992 (Gantt chart), which introduced the scheduler code changed here.

Problem

With a working-days calendar (Mon–Fri), an FS chain can end up with a successor starting on its predecessor's last day.

Example (Mon–Fri, FS chain A → B → C):

  • A Mon Oct 5 – Fri Oct 9, B Mon Oct 12 – Tue Oct 13, C Wed Oct 14 – Thu Oct 15
  • Drag A to Sun Oct 11 – Thu Oct 15

Expected: B Fri 16 – Sat 17, C Mon 19 – Tue 20.
Actual: B Thu 15 – Fri 16 (overlaps A's last day), C Mon 19 – Tue 20.

Root cause

The reverse pass in simulateCascade decides whether to pull a predecessor by comparing fsReverseAnchor(successor) with the predecessor's due date. In working-days mode fsReverseAnchor is not the inverse of fsAnchor when the predecessor ends on a non-working day: fsAnchor(Sat 17) = Mon 19, but fsReverseAnchor(Mon 19) = Fri 16 < Sat 17. So C (already correctly placed on Mon 19) pulls B back by a day. B's own link to A is never re-checked, because A is the primary edit.

Legacy calendar-day mode is not affected: its forward and reverse anchors are exact inverses.

Fix

Before pulling, check the violation with the forward anchor, the same check the outgoing pass uses (fsAnchor/ssAnchor/ffAnchor/sfAnchor against the successor's start or due date). A predecessor is pulled only if the constraint is actually violated. How far it gets pulled is unchanged.

Tests

scheduler-cascade.test.ts:

  • Working-days mode: the chain above (B Fri 16 – Sat 17, no pull-predecessor); a successor dropped on the first working day after a Saturday-ending predecessor gives no-cascade; a real violation (successor on the predecessor's Friday) still pulls the predecessor to Wed 14 – Thu 15.
  • Legacy mode: forward/reverse anchors are exact inverses for all four relation kinds and several lags; a chain push never emits pull-predecessor; existing pull behaviour is unchanged.

All gantt suites in plugins/tracker-resources and packages/gantt pass.

Out of scope: snapping drags to working days, and working-day durations for cascaded successors.

…atisfied

In working-days mode the reverse anchor is not the inverse of the forward
anchor when the predecessor's anchor falls on a non-working day:
fsAnchor(Sat) = Mon, but fsReverseAnchor(Mon) = Fri. The reverse pass of
simulateCascade only compared the reverse anchor against the predecessor,
so after an FS push that left a successor ending on a Saturday, the next
link in the chain pulled that successor back by the calendar delta. The
pulled issue then started on its own predecessor's last day, and that
upstream link was never re-checked because the primary edit is guarded.

Check the violation with the forward anchor (the same predicate the
outgoing pass uses) before pulling. Legacy calendar-day mode is unaffected
because its forward and reverse anchors are exact inverses.

Tests cover the reported chain (A -> B -> C across a weekend), a successor
dropped on the first working day after a Saturday-ending predecessor, a
real violation that must still pull, and legacy-mode regressions.

Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
@ArtyomSavchenko
ArtyomSavchenko merged commit b426f09 into hcengineering:develop Oct 2, 2026
13 checks passed
@MichaelUray
MichaelUray deleted the fix/gantt-fs-cascade-nonworking-due branch October 2, 2026 12:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants