gantt-lib 0.60.2 → 0.62.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- export { D as DAY_MS, a as DependencyError, L as LinkType, T as Task, P as TaskDependency, V as ValidationResult, Q as addBusinessDays, e as alignToWorkingDay, f as areTasksHierarchicallyRelated, g as buildAdjacencyList, h as buildTaskRangeFromEnd, i as buildTaskRangeFromStart, j as calculateSuccessorDate, k as cascadeByLinks, R as clampDateRangeForIncomingFS, l as clampTaskRangeForIncomingFS, m as computeLagFromDates, n as computeParentDates, o as computeParentProgress, p as detectCycles, q as findParentId, r as getAllDependencyEdges, s as getAllDescendants, t as getBusinessDayOffset, S as getBusinessDaysCount, u as getChildren, v as getDependencyLag, w as getSuccessorChain, x as getTaskDuration, y as getTransitiveCascadeChain, z as isAncestorTask, A as isTaskParent, B as moveTaskRange, C as normalizeDependencyLag, E as normalizeUTCDate, F as parseDateOnly, H as recalculateIncomingLags, I as reflowTasksOnModeSwitch, J as removeDependenciesBetweenTasks, U as resolveDateRangeFromPixels, K as shiftBusinessDayOffset, X as subtractBusinessDays, N as universalCascade, O as validateDependencies } from '../../index-CliEEiHA.mjs';
1
+ export { D as DAY_MS, a as DependencyError, L as LinkType, U as ScheduleCommandOptions, X as ScheduleCommandResult, Y as ScheduleDependency, Z as ScheduleTask, _ as ScheduleTaskUpdate, T as Task, $ as TaskDependency, V as ValidationResult, a0 as addBusinessDays, e as alignToWorkingDay, f as areTasksHierarchicallyRelated, g as buildAdjacencyList, h as buildTaskRangeFromEnd, i as buildTaskRangeFromStart, j as calculateSuccessorDate, k as cascadeByLinks, l as clampTaskRangeForIncomingFS, m as computeLagFromDates, n as computeParentDates, o as computeParentProgress, p as detectCycles, q as findParentId, r as getAllDependencyEdges, s as getAllDescendants, t as getBusinessDayOffset, a1 as getBusinessDaysCount, u as getChildren, v as getDependencyLag, w as getSuccessorChain, x as getTaskDuration, y as getTransitiveCascadeChain, z as isAncestorTask, A as isTaskParent, B as moveTaskRange, C as moveTaskWithCascade, E as normalizeDependencyLag, F as normalizeUTCDate, H as parseDateOnly, I as recalculateIncomingLags, J as recalculateProjectSchedule, K as recalculateTaskFromDependencies, N as reflowTasksOnModeSwitch, O as removeDependenciesBetweenTasks, P as resizeTaskWithCascade, Q as shiftBusinessDayOffset, a2 as subtractBusinessDays, R as universalCascade, S as validateDependencies } from '../../index-DMA7NbWe.mjs';
@@ -1 +1 @@
1
- export { D as DAY_MS, a as DependencyError, L as LinkType, T as Task, P as TaskDependency, V as ValidationResult, Q as addBusinessDays, e as alignToWorkingDay, f as areTasksHierarchicallyRelated, g as buildAdjacencyList, h as buildTaskRangeFromEnd, i as buildTaskRangeFromStart, j as calculateSuccessorDate, k as cascadeByLinks, R as clampDateRangeForIncomingFS, l as clampTaskRangeForIncomingFS, m as computeLagFromDates, n as computeParentDates, o as computeParentProgress, p as detectCycles, q as findParentId, r as getAllDependencyEdges, s as getAllDescendants, t as getBusinessDayOffset, S as getBusinessDaysCount, u as getChildren, v as getDependencyLag, w as getSuccessorChain, x as getTaskDuration, y as getTransitiveCascadeChain, z as isAncestorTask, A as isTaskParent, B as moveTaskRange, C as normalizeDependencyLag, E as normalizeUTCDate, F as parseDateOnly, H as recalculateIncomingLags, I as reflowTasksOnModeSwitch, J as removeDependenciesBetweenTasks, U as resolveDateRangeFromPixels, K as shiftBusinessDayOffset, X as subtractBusinessDays, N as universalCascade, O as validateDependencies } from '../../index-CliEEiHA.js';
1
+ export { D as DAY_MS, a as DependencyError, L as LinkType, U as ScheduleCommandOptions, X as ScheduleCommandResult, Y as ScheduleDependency, Z as ScheduleTask, _ as ScheduleTaskUpdate, T as Task, $ as TaskDependency, V as ValidationResult, a0 as addBusinessDays, e as alignToWorkingDay, f as areTasksHierarchicallyRelated, g as buildAdjacencyList, h as buildTaskRangeFromEnd, i as buildTaskRangeFromStart, j as calculateSuccessorDate, k as cascadeByLinks, l as clampTaskRangeForIncomingFS, m as computeLagFromDates, n as computeParentDates, o as computeParentProgress, p as detectCycles, q as findParentId, r as getAllDependencyEdges, s as getAllDescendants, t as getBusinessDayOffset, a1 as getBusinessDaysCount, u as getChildren, v as getDependencyLag, w as getSuccessorChain, x as getTaskDuration, y as getTransitiveCascadeChain, z as isAncestorTask, A as isTaskParent, B as moveTaskRange, C as moveTaskWithCascade, E as normalizeDependencyLag, F as normalizeUTCDate, H as parseDateOnly, I as recalculateIncomingLags, J as recalculateProjectSchedule, K as recalculateTaskFromDependencies, N as reflowTasksOnModeSwitch, O as removeDependenciesBetweenTasks, P as resizeTaskWithCascade, Q as shiftBusinessDayOffset, a2 as subtractBusinessDays, R as universalCascade, S as validateDependencies } from '../../index-DMA7NbWe.js';
@@ -29,7 +29,6 @@ __export(scheduling_exports, {
29
29
  buildTaskRangeFromStart: () => buildTaskRangeFromStart,
30
30
  calculateSuccessorDate: () => calculateSuccessorDate,
31
31
  cascadeByLinks: () => cascadeByLinks,
32
- clampDateRangeForIncomingFS: () => clampDateRangeForIncomingFS,
33
32
  clampTaskRangeForIncomingFS: () => clampTaskRangeForIncomingFS,
34
33
  computeLagFromDates: () => computeLagFromDates,
35
34
  computeParentDates: () => computeParentDates,
@@ -48,13 +47,16 @@ __export(scheduling_exports, {
48
47
  isAncestorTask: () => isAncestorTask,
49
48
  isTaskParent: () => isTaskParent,
50
49
  moveTaskRange: () => moveTaskRange,
50
+ moveTaskWithCascade: () => moveTaskWithCascade,
51
51
  normalizeDependencyLag: () => normalizeDependencyLag,
52
52
  normalizeUTCDate: () => normalizeUTCDate,
53
53
  parseDateOnly: () => parseDateOnly,
54
54
  recalculateIncomingLags: () => recalculateIncomingLags,
55
+ recalculateProjectSchedule: () => recalculateProjectSchedule,
56
+ recalculateTaskFromDependencies: () => recalculateTaskFromDependencies,
55
57
  reflowTasksOnModeSwitch: () => reflowTasksOnModeSwitch,
56
58
  removeDependenciesBetweenTasks: () => removeDependenciesBetweenTasks,
57
- resolveDateRangeFromPixels: () => resolveDateRangeFromPixels,
59
+ resizeTaskWithCascade: () => resizeTaskWithCascade,
58
60
  shiftBusinessDayOffset: () => shiftBusinessDayOffset,
59
61
  subtractBusinessDays: () => subtractBusinessDays,
60
62
  universalCascade: () => universalCascade,
@@ -485,62 +487,6 @@ function recalculateIncomingLags(task, newStartDate, newEndDate, allTasks, busin
485
487
  return { ...dep, lag: nextLag };
486
488
  });
487
489
  }
488
- function resolveDateRangeFromPixels(mode, left, width, monthStart, dayWidth, task, businessDays, weekendPredicate) {
489
- const dayOffset = Math.round(left / dayWidth);
490
- const rawStartDate = new Date(Date.UTC(
491
- monthStart.getUTCFullYear(),
492
- monthStart.getUTCMonth(),
493
- monthStart.getUTCDate() + dayOffset
494
- ));
495
- const rawEndOffset = dayOffset + Math.round(width / dayWidth) - 1;
496
- const rawEndDate = new Date(Date.UTC(
497
- monthStart.getUTCFullYear(),
498
- monthStart.getUTCMonth(),
499
- monthStart.getUTCDate() + rawEndOffset
500
- ));
501
- if (!(businessDays && weekendPredicate)) {
502
- return { start: rawStartDate, end: rawEndDate };
503
- }
504
- if (mode === "move") {
505
- const originalStart2 = new Date(task.startDate);
506
- const snapDirection2 = rawStartDate.getTime() >= originalStart2.getTime() ? 1 : -1;
507
- return moveTaskRange(
508
- task.startDate,
509
- task.endDate,
510
- rawStartDate,
511
- true,
512
- weekendPredicate,
513
- snapDirection2
514
- );
515
- }
516
- if (mode === "resize-right") {
517
- const fixedStart = new Date(task.startDate);
518
- const originalEnd = new Date(task.endDate);
519
- const snapDirection2 = rawEndDate.getTime() >= originalEnd.getTime() ? 1 : -1;
520
- const alignedEnd = alignToWorkingDay(rawEndDate, snapDirection2, weekendPredicate);
521
- const duration2 = Math.max(1, getBusinessDaysCount(fixedStart, alignedEnd, weekendPredicate));
522
- return buildTaskRangeFromStart(fixedStart, duration2, true, weekendPredicate);
523
- }
524
- const fixedEnd = new Date(task.endDate);
525
- const originalStart = new Date(task.startDate);
526
- const snapDirection = rawStartDate.getTime() >= originalStart.getTime() ? 1 : -1;
527
- const alignedStart = alignToWorkingDay(rawStartDate, snapDirection, weekendPredicate);
528
- const duration = Math.max(1, getBusinessDaysCount(alignedStart, fixedEnd, weekendPredicate));
529
- return buildTaskRangeFromEnd(fixedEnd, duration, true, weekendPredicate);
530
- }
531
- function clampDateRangeForIncomingFS(task, range, allTasks, mode, businessDays, weekendPredicate) {
532
- if (mode === "resize-right") {
533
- return range;
534
- }
535
- return clampTaskRangeForIncomingFS(
536
- task,
537
- range.start,
538
- range.end,
539
- allTasks,
540
- businessDays,
541
- weekendPredicate
542
- );
543
- }
544
490
 
545
491
  // src/core/scheduling/cascade.ts
546
492
  function getSuccessorChain(draggedTaskId, allTasks, linkTypes = ["FS"]) {
@@ -857,6 +803,312 @@ function reflowTasksOnModeSwitch(sourceTasks, toBusinessDays, weekendPredicate)
857
803
  return tasks;
858
804
  }
859
805
 
806
+ // src/core/scheduling/execute.ts
807
+ function toIsoDate(date) {
808
+ return date.toISOString().split("T")[0];
809
+ }
810
+ function createChangedResult(snapshot, nextTasks) {
811
+ const originalById = new Map(snapshot.map((task) => [task.id, task]));
812
+ const changedTasks = nextTasks.filter((task) => JSON.stringify(originalById.get(task.id)) !== JSON.stringify(task));
813
+ return {
814
+ changedTasks,
815
+ changedIds: changedTasks.map((task) => task.id)
816
+ };
817
+ }
818
+ function moveTaskWithCascade(taskId, newStart, snapshot, options) {
819
+ const task = snapshot.find((t) => t.id === taskId);
820
+ if (!task) {
821
+ return { changedTasks: [], changedIds: [] };
822
+ }
823
+ const businessDays = options?.businessDays ?? false;
824
+ const weekendPredicate = options?.weekendPredicate;
825
+ const newRange = moveTaskRange(
826
+ task.startDate,
827
+ task.endDate,
828
+ newStart,
829
+ businessDays,
830
+ weekendPredicate
831
+ );
832
+ const updatedDependencies = recalculateIncomingLags(
833
+ task,
834
+ newRange.start,
835
+ newRange.end,
836
+ snapshot,
837
+ businessDays,
838
+ weekendPredicate
839
+ );
840
+ const movedTask = {
841
+ ...task,
842
+ startDate: newRange.start.toISOString().split("T")[0],
843
+ endDate: newRange.end.toISOString().split("T")[0],
844
+ dependencies: updatedDependencies
845
+ };
846
+ if (options?.skipCascade) {
847
+ return {
848
+ changedTasks: [movedTask],
849
+ changedIds: [movedTask.id]
850
+ };
851
+ }
852
+ const cascadeResult = universalCascade(
853
+ movedTask,
854
+ newRange.start,
855
+ newRange.end,
856
+ snapshot,
857
+ businessDays,
858
+ weekendPredicate
859
+ );
860
+ const resultMap = /* @__PURE__ */ new Map();
861
+ resultMap.set(movedTask.id, movedTask);
862
+ for (const t of cascadeResult) {
863
+ resultMap.set(t.id, t);
864
+ }
865
+ const changedTasks = Array.from(resultMap.values());
866
+ return {
867
+ changedTasks,
868
+ changedIds: changedTasks.map((t) => t.id)
869
+ };
870
+ }
871
+ function resizeTaskWithCascade(taskId, anchor, newDate, snapshot, options) {
872
+ const task = snapshot.find((t) => t.id === taskId);
873
+ if (!task) {
874
+ return { changedTasks: [], changedIds: [] };
875
+ }
876
+ const businessDays = options?.businessDays ?? false;
877
+ const weekendPredicate = options?.weekendPredicate;
878
+ const originalStart = parseDateOnly(task.startDate);
879
+ const originalEnd = parseDateOnly(task.endDate);
880
+ let newRange;
881
+ if (anchor === "end") {
882
+ newRange = { start: originalStart, end: newDate };
883
+ } else {
884
+ newRange = { start: newDate, end: originalEnd };
885
+ }
886
+ const updatedDependencies = recalculateIncomingLags(
887
+ task,
888
+ newRange.start,
889
+ newRange.end,
890
+ snapshot,
891
+ businessDays,
892
+ weekendPredicate
893
+ );
894
+ const resizedTask = {
895
+ ...task,
896
+ startDate: newRange.start.toISOString().split("T")[0],
897
+ endDate: newRange.end.toISOString().split("T")[0],
898
+ dependencies: updatedDependencies
899
+ };
900
+ if (options?.skipCascade) {
901
+ return {
902
+ changedTasks: [resizedTask],
903
+ changedIds: [resizedTask.id]
904
+ };
905
+ }
906
+ const cascadeResult = universalCascade(
907
+ resizedTask,
908
+ newRange.start,
909
+ newRange.end,
910
+ snapshot,
911
+ businessDays,
912
+ weekendPredicate
913
+ );
914
+ const resultMap = /* @__PURE__ */ new Map();
915
+ resultMap.set(resizedTask.id, resizedTask);
916
+ for (const t of cascadeResult) {
917
+ resultMap.set(t.id, t);
918
+ }
919
+ const changedTasks = Array.from(resultMap.values());
920
+ return {
921
+ changedTasks,
922
+ changedIds: changedTasks.map((t) => t.id)
923
+ };
924
+ }
925
+ function recalculateTaskFromDependencies(taskId, snapshot, options) {
926
+ const task = snapshot.find((t) => t.id === taskId);
927
+ if (!task) {
928
+ return { changedTasks: [], changedIds: [] };
929
+ }
930
+ const businessDays = options?.businessDays ?? false;
931
+ const weekendPredicate = options?.weekendPredicate;
932
+ if (!task.dependencies || task.dependencies.length === 0) {
933
+ return {
934
+ changedTasks: [task],
935
+ changedIds: [task.id]
936
+ };
937
+ }
938
+ let constrainedStart = null;
939
+ let constrainedEnd = null;
940
+ for (const dep of task.dependencies) {
941
+ const predecessor = snapshot.find((t) => t.id === dep.taskId);
942
+ if (!predecessor) continue;
943
+ const predStart = parseDateOnly(predecessor.startDate);
944
+ const predEnd = parseDateOnly(predecessor.endDate);
945
+ const constraintDate = calculateSuccessorDate(
946
+ predStart,
947
+ predEnd,
948
+ dep.type,
949
+ getDependencyLag(dep),
950
+ businessDays,
951
+ weekendPredicate
952
+ );
953
+ const duration = getTaskDuration(
954
+ parseDateOnly(task.startDate),
955
+ parseDateOnly(task.endDate),
956
+ businessDays,
957
+ weekendPredicate
958
+ );
959
+ let range;
960
+ if (dep.type === "FS" || dep.type === "SS") {
961
+ range = buildTaskRangeFromStart(constraintDate, duration, businessDays, weekendPredicate);
962
+ } else {
963
+ range = buildTaskRangeFromEnd(constraintDate, duration, businessDays, weekendPredicate);
964
+ }
965
+ if (!constrainedStart || range.start.getTime() > constrainedStart.getTime()) {
966
+ constrainedStart = range.start;
967
+ constrainedEnd = range.end;
968
+ }
969
+ }
970
+ if (!constrainedStart || !constrainedEnd) {
971
+ return {
972
+ changedTasks: [task],
973
+ changedIds: [task.id]
974
+ };
975
+ }
976
+ const updatedDependencies = recalculateIncomingLags(
977
+ task,
978
+ constrainedStart,
979
+ constrainedEnd,
980
+ snapshot,
981
+ businessDays,
982
+ weekendPredicate
983
+ );
984
+ const recalculatedTask = {
985
+ ...task,
986
+ startDate: constrainedStart.toISOString().split("T")[0],
987
+ endDate: constrainedEnd.toISOString().split("T")[0],
988
+ dependencies: updatedDependencies
989
+ };
990
+ if (options?.skipCascade) {
991
+ return {
992
+ changedTasks: [recalculatedTask],
993
+ changedIds: [recalculatedTask.id]
994
+ };
995
+ }
996
+ const cascadeResult = universalCascade(
997
+ recalculatedTask,
998
+ constrainedStart,
999
+ constrainedEnd,
1000
+ snapshot,
1001
+ businessDays,
1002
+ weekendPredicate
1003
+ );
1004
+ const resultMap = /* @__PURE__ */ new Map();
1005
+ resultMap.set(recalculatedTask.id, recalculatedTask);
1006
+ for (const t of cascadeResult) {
1007
+ resultMap.set(t.id, t);
1008
+ }
1009
+ const changedTasks = Array.from(resultMap.values());
1010
+ return {
1011
+ changedTasks,
1012
+ changedIds: changedTasks.map((t) => t.id)
1013
+ };
1014
+ }
1015
+ function recalculateProjectSchedule(snapshot, options) {
1016
+ const businessDays = options?.businessDays ?? false;
1017
+ const weekendPredicate = options?.weekendPredicate;
1018
+ const workingMap = new Map(snapshot.map((task) => [task.id, { ...task }]));
1019
+ const indegree = /* @__PURE__ */ new Map();
1020
+ const successorIdsByTask = /* @__PURE__ */ new Map();
1021
+ for (const task of snapshot) {
1022
+ indegree.set(task.id, 0);
1023
+ successorIdsByTask.set(task.id, []);
1024
+ }
1025
+ for (const task of snapshot) {
1026
+ for (const dep of task.dependencies ?? []) {
1027
+ if (!workingMap.has(dep.taskId)) {
1028
+ continue;
1029
+ }
1030
+ indegree.set(task.id, (indegree.get(task.id) ?? 0) + 1);
1031
+ successorIdsByTask.get(dep.taskId)?.push(task.id);
1032
+ }
1033
+ }
1034
+ const queue = snapshot.filter((task) => (indegree.get(task.id) ?? 0) === 0).map((task) => task.id);
1035
+ while (queue.length > 0) {
1036
+ const currentId = queue.shift();
1037
+ for (const successorId of successorIdsByTask.get(currentId) ?? []) {
1038
+ const nextIndegree = (indegree.get(successorId) ?? 0) - 1;
1039
+ indegree.set(successorId, nextIndegree);
1040
+ if (nextIndegree !== 0) {
1041
+ continue;
1042
+ }
1043
+ const currentTask = workingMap.get(successorId);
1044
+ if (!currentTask || currentTask.locked || !currentTask.dependencies?.length) {
1045
+ queue.push(successorId);
1046
+ continue;
1047
+ }
1048
+ const duration = getTaskDuration(
1049
+ parseDateOnly(currentTask.startDate),
1050
+ parseDateOnly(currentTask.endDate),
1051
+ businessDays,
1052
+ weekendPredicate
1053
+ );
1054
+ let constrainedRange = null;
1055
+ for (const dep of currentTask.dependencies) {
1056
+ const predecessor = workingMap.get(dep.taskId);
1057
+ if (!predecessor) {
1058
+ continue;
1059
+ }
1060
+ const predecessorStart = parseDateOnly(predecessor.startDate);
1061
+ const predecessorEnd = parseDateOnly(predecessor.endDate);
1062
+ const constraintDate = calculateSuccessorDate(
1063
+ predecessorStart,
1064
+ predecessorEnd,
1065
+ dep.type,
1066
+ getDependencyLag(dep),
1067
+ businessDays,
1068
+ weekendPredicate
1069
+ );
1070
+ const candidateRange = dep.type === "FS" || dep.type === "SS" ? buildTaskRangeFromStart(constraintDate, duration, businessDays, weekendPredicate) : buildTaskRangeFromEnd(constraintDate, duration, businessDays, weekendPredicate);
1071
+ if (!constrainedRange || candidateRange.start.getTime() > constrainedRange.start.getTime() || candidateRange.start.getTime() === constrainedRange.start.getTime() && candidateRange.end.getTime() > constrainedRange.end.getTime()) {
1072
+ constrainedRange = candidateRange;
1073
+ }
1074
+ }
1075
+ if (!constrainedRange) {
1076
+ queue.push(successorId);
1077
+ continue;
1078
+ }
1079
+ workingMap.set(successorId, {
1080
+ ...currentTask,
1081
+ startDate: toIsoDate(constrainedRange.start),
1082
+ endDate: toIsoDate(constrainedRange.end)
1083
+ });
1084
+ queue.push(successorId);
1085
+ }
1086
+ }
1087
+ const parentsByDepth = snapshot.filter((task) => isTaskParent(task.id, snapshot)).map((task) => {
1088
+ let depth = 0;
1089
+ let current = task.parentId ? workingMap.get(task.parentId) : void 0;
1090
+ while (current) {
1091
+ depth++;
1092
+ current = current.parentId ? workingMap.get(current.parentId) : void 0;
1093
+ }
1094
+ return { taskId: task.id, depth };
1095
+ }).sort((left, right) => right.depth - left.depth);
1096
+ const workingTasks = () => Array.from(workingMap.values());
1097
+ for (const { taskId } of parentsByDepth) {
1098
+ const parent = workingMap.get(taskId);
1099
+ if (!parent || parent.locked) {
1100
+ continue;
1101
+ }
1102
+ const { startDate, endDate } = computeParentDates(taskId, workingTasks());
1103
+ workingMap.set(taskId, {
1104
+ ...parent,
1105
+ startDate: toIsoDate(startDate),
1106
+ endDate: toIsoDate(endDate)
1107
+ });
1108
+ }
1109
+ return createChangedResult(snapshot, Array.from(workingMap.values()));
1110
+ }
1111
+
860
1112
  // src/core/scheduling/validation.ts
861
1113
  function buildAdjacencyList(tasks) {
862
1114
  const graph = /* @__PURE__ */ new Map();
@@ -966,7 +1218,6 @@ function validateDependencies(tasks) {
966
1218
  buildTaskRangeFromStart,
967
1219
  calculateSuccessorDate,
968
1220
  cascadeByLinks,
969
- clampDateRangeForIncomingFS,
970
1221
  clampTaskRangeForIncomingFS,
971
1222
  computeLagFromDates,
972
1223
  computeParentDates,
@@ -985,13 +1236,16 @@ function validateDependencies(tasks) {
985
1236
  isAncestorTask,
986
1237
  isTaskParent,
987
1238
  moveTaskRange,
1239
+ moveTaskWithCascade,
988
1240
  normalizeDependencyLag,
989
1241
  normalizeUTCDate,
990
1242
  parseDateOnly,
991
1243
  recalculateIncomingLags,
1244
+ recalculateProjectSchedule,
1245
+ recalculateTaskFromDependencies,
992
1246
  reflowTasksOnModeSwitch,
993
1247
  removeDependenciesBetweenTasks,
994
- resolveDateRangeFromPixels,
1248
+ resizeTaskWithCascade,
995
1249
  shiftBusinessDayOffset,
996
1250
  subtractBusinessDays,
997
1251
  universalCascade,