* feat(gantt): working-day span helpers and prevWorkingDay
Add prevWorkingDay (mirror of nextWorkingDay), findWorkingDay (the
nearest working day in a direction, or undefined when none lies within
the 60-day search window, so a caller can tell a failed search from a
result), workingDaySpan (inclusive working-day length of a bar, never
below 1), dueForSpan / startForSpan (the far end of a bar with a given
working-day span) and workingDaysPerWeek (active weekdays of the mask).
The helpers stay bounded for degenerate input: non-finite or inverted
ranges count as one working day, ranges, spans and addWorkingDays step
counts are capped at MAX_WORKING_SPAN_DAYS (about 100 years), and
addWorkingDays treats a non-finite step count as zero instead of looping
forever.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
* feat(gantt): snap drag, resize and drop previews to working days
reduce() takes an optional WorkingCalendar. With a calendar, a single-bar
body drag lands the start on the nearest working day in the drag direction
and keeps the bar's length in working days; the start handle rounds up to
the next working day and the end handle rounds down to the previous one;
a dropped unscheduled issue starts on the next working day and lasts two
working days. A zero-delta move leaves the bar untouched. A co-drag keeps
its shared raw delta, and without a calendar the reducer is unchanged.
Degenerate calendars fall back to calendar days: a calendar without any
working weekday, a bar whose origin is not a finite range of at most
MAX_WORKING_SPAN_DAYS days, and a preview with no working day within
reach (a holiday blackout longer than the 60-day search window), so a
non-working day is never presented as a snapped result.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
* feat(tracker): snap Gantt drags to the project working days; move children and keyboard shifts by working days
GanttView passes the project calendar to the drag reducer for single
issue bars; milestones and bulk co-drags keep calendar-day drags. A
parent drag shifts its children by the parent's move measured in
working days (shiftWithPrimary), and the arrow keys move a bar by
working days (shiftScheduleDays), with Shift moving one week = the
number of active weekdays (keyboardWeekStep). The help overlay now
says "±1 week". Without a project calendar every path keeps its
calendar-day arithmetic.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
* feat(gantt): hold Shift while dragging to ignore working days
Holding Shift during a single issue bar drag, resize or sidebar drop
suspends working-day snapping for that gesture: the preview and the
committed dates move in calendar days, exactly as in a project without
working days, and the children of a dragged parent shift by the same
calendar-day delta. Pressing or releasing Shift mid-drag updates the
preview at once, so the user can decide before dropping. A "Calendar
days" hint is shown under the date pill while snapping is suspended.
The commit uses the modifier state of the release event itself, and
losing window focus counts as releasing Shift, so a Shift let go outside
the window cannot leave the drag committing calendar days.
Shift is the free modifier during a drag: Alt is already read at release
to bypass the cascade simulation (and Alt+drag is grabbed by several
Linux window managers), Cmd/Ctrl toggle the selection, and Shift only
range-selects on a plain click. A body drag still has to start without
a modifier; Shift is pressed once the bar moves.
The reducer takes `calendarDays` on `mousemove` and marks the state
`snapSuspended` only when a calendar would otherwise apply; legacy mode,
co-drags and milestones are unchanged. `dragCalendar(state, calendar)`
gives the commit the calendar for the drag's own arithmetic.
`modifierSyncMove(state, calendarDays, lastMove)` yields the move that
re-syncs a running drag with the modifier at the last pointer position.
Dependency cascades keep following the project calendar. The keyboard
help lists the new shortcut; strings are added for all tracker locales.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
* test(gantt): make the working-day cap tests deterministic
The addWorkingDays cap test asserted a wall-clock bound (< 2000 ms) that
failed on a slower CI runner. Assert the exact results instead: each loop
step moves the cursor by one calendar day, so the distance of the result
from the input is a deterministic count of the iterations. Cover the
safety bound of calendars without working weekdays with small step counts
and drop the costly 70-holiday walk, which cuts the suite's slowest tests
from ~950 ms to ~250 ms.
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
---------
Signed-off-by: Michael Uray <michaeluray@users.noreply.github.com>
Co-authored-by: Michael Uray <michaeluray@users.noreply.github.com>