@outbuild-company/schedule-core 1.7.0 → 1.9.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.
package/dist/index.js CHANGED
@@ -139,12 +139,11 @@ var REJECTION_REASON = {
139
139
  ANCHOR_SIBLING_NOT_FOUND: "anchor_sibling_not_found",
140
140
  ANCHOR_SIBLING_WRONG_PARENT: "anchor_sibling_wrong_parent",
141
141
  PARENT_FROZEN_BY_SIR: "parent_frozen_by_sir",
142
- SIR_REQUEST_NOT_PENDING: "sir_request_not_pending",
143
- PREVIEW_UNSUPPORTED_ACTION: "preview_unsupported_action",
144
142
  CANNOT_MOVE_INTO_OWN_DESCENDANT: "cannot_move_into_own_descendant",
145
143
  INDENT_NO_ELIGIBLE_SIBLING: "indent_no_eligible_sibling",
146
144
  OUTDENT_ROOT_LEVEL_NOT_EDITABLE: "outdent_root_level_not_editable",
147
145
  HIERARCHY_CYCLE: "hierarchy_cycle",
146
+ SANDBOX_PERSISTENCE_FORBIDDEN: "sandbox_persistence_forbidden",
148
147
  INVALID_PROGRESS_VALUE: "invalid_progress_value_must_be_0_or_100"
149
148
  };
150
149
 
@@ -419,6 +418,107 @@ function getFieldDescriptor(field) {
419
418
  return FIELD_REGISTRY[field] ?? null;
420
419
  }
421
420
 
421
+ // src/internal/state/view-state.ts
422
+ function isRelativePlacementPinValid(activityId, pin, parentOf) {
423
+ const branchParentId = String(pin.branchParentId);
424
+ const currentParentId = String(parentOf(activityId) ?? "0");
425
+ if (currentParentId !== branchParentId) return false;
426
+ const referenceId = String(pin.referenceId);
427
+ return referenceId === branchParentId || String(parentOf(referenceId) ?? "0") === branchParentId;
428
+ }
429
+ var ViewStateStore = class {
430
+ checkedSet = /* @__PURE__ */ new Set();
431
+ hiddenSet = /* @__PURE__ */ new Set();
432
+ activeFilter = null;
433
+ activeOrder = null;
434
+ relativePlacementPins = /* @__PURE__ */ new Map();
435
+ isChecked(activityId) {
436
+ return this.checkedSet.has(activityId);
437
+ }
438
+ isVisible(activityId) {
439
+ return !this.hiddenSet.has(activityId);
440
+ }
441
+ hiddenIds() {
442
+ return [...this.hiddenSet];
443
+ }
444
+ getActiveFilter() {
445
+ return this.activeFilter;
446
+ }
447
+ setActiveFilter(nextFilter) {
448
+ this.activeFilter = nextFilter;
449
+ }
450
+ getActiveOrder() {
451
+ return this.activeOrder;
452
+ }
453
+ setActiveOrder(nextOrder) {
454
+ this.activeOrder = nextOrder;
455
+ }
456
+ setRelativePlacementPin(activityId, pin) {
457
+ this.relativePlacementPins.set(String(activityId), {
458
+ referenceId: String(pin.referenceId),
459
+ branchParentId: String(pin.branchParentId),
460
+ side: pin.side
461
+ });
462
+ }
463
+ getRelativePlacementPins() {
464
+ return this.relativePlacementPins;
465
+ }
466
+ restoreRelativePlacementPins(pins) {
467
+ this.relativePlacementPins = new Map(pins);
468
+ }
469
+ checkedIds() {
470
+ return [...this.checkedSet];
471
+ }
472
+ checkedCount() {
473
+ return this.checkedSet.size;
474
+ }
475
+ setChecked(activityId, nextChecked) {
476
+ if (nextChecked === this.checkedSet.has(activityId)) return false;
477
+ if (nextChecked) this.checkedSet.add(activityId);
478
+ else this.checkedSet.delete(activityId);
479
+ return true;
480
+ }
481
+ setVisible(activityId, nextVisible) {
482
+ if (nextVisible === !this.hiddenSet.has(activityId)) return false;
483
+ if (nextVisible) this.hiddenSet.delete(activityId);
484
+ else this.hiddenSet.add(activityId);
485
+ return true;
486
+ }
487
+ forget(activityId) {
488
+ this.checkedSet.delete(activityId);
489
+ this.hiddenSet.delete(activityId);
490
+ this.relativePlacementPins.delete(activityId);
491
+ for (const [pinnedId, pin] of this.relativePlacementPins) {
492
+ if (pin.referenceId === activityId) {
493
+ this.relativePlacementPins.delete(pinnedId);
494
+ }
495
+ }
496
+ }
497
+ hydrate(seed2) {
498
+ this.checkedSet = new Set(seed2.checkedIds ?? []);
499
+ this.hiddenSet = new Set(seed2.hiddenIds ?? []);
500
+ this.activeFilter = null;
501
+ this.activeOrder = null;
502
+ this.relativePlacementPins = /* @__PURE__ */ new Map();
503
+ }
504
+ snapshot() {
505
+ return {
506
+ checked: new Set(this.checkedSet),
507
+ hidden: new Set(this.hiddenSet),
508
+ activeFilter: this.activeFilter,
509
+ activeOrder: this.activeOrder,
510
+ relativePlacementPins: new Map(this.relativePlacementPins)
511
+ };
512
+ }
513
+ restore(snapshot) {
514
+ this.checkedSet = new Set(snapshot.checked);
515
+ this.hiddenSet = new Set(snapshot.hidden);
516
+ this.activeFilter = snapshot.activeFilter;
517
+ this.activeOrder = snapshot.activeOrder;
518
+ this.relativePlacementPins = new Map(snapshot.relativePlacementPins);
519
+ }
520
+ };
521
+
422
522
  // src/internal/filter/evaluate-filter.ts
423
523
  function toTargetDate(value) {
424
524
  if (value instanceof Date) return value;
@@ -561,13 +661,8 @@ function seedFilterProjection(input) {
561
661
  );
562
662
  }
563
663
  const isMatch = matchesFilter(activity, filter, context);
564
- const isPinned = input.pinnedVisibleIds?.has(activityId) === true;
565
- visibleReferenceCounts.set(
566
- activityId,
567
- (isMatch ? 1 : 0) + (isPinned ? 1 : 0)
568
- );
664
+ visibleReferenceCounts.set(activityId, isMatch ? 1 : 0);
569
665
  if (isMatch) matchedIds.add(activityId);
570
- if (isPinned) pinnedVisibleIds.add(activityId);
571
666
  }
572
667
  return {
573
668
  matchedIds,
@@ -577,6 +672,29 @@ function seedFilterProjection(input) {
577
672
  remainingChildren
578
673
  };
579
674
  }
675
+ function applyRelativePlacementPins(seed2, pins) {
676
+ if (pins === void 0) return;
677
+ for (const [activityId, pin] of pins) {
678
+ const pinnedId = String(activityId);
679
+ const referenceId = String(pin.referenceId);
680
+ if (!seed2.parentIds.has(pinnedId) || !seed2.parentIds.has(referenceId) || !isRelativePlacementPinValid(
681
+ pinnedId,
682
+ pin,
683
+ (activityId2) => seed2.parentIds.get(activityId2) ?? null
684
+ ) || (seed2.visibleReferenceCounts.get(referenceId) ?? 0) === 0) {
685
+ continue;
686
+ }
687
+ seed2.pinnedVisibleIds.add(pinnedId);
688
+ let currentId = pinnedId;
689
+ while (currentId !== null) {
690
+ seed2.visibleReferenceCounts.set(
691
+ currentId,
692
+ (seed2.visibleReferenceCounts.get(currentId) ?? 0) + 1
693
+ );
694
+ currentId = seed2.parentIds.get(currentId) ?? null;
695
+ }
696
+ }
697
+ }
580
698
  function propagateVisibleReferenceCounts(seed2) {
581
699
  const { visibleReferenceCounts, parentIds, remainingChildren } = seed2;
582
700
  const pendingIds = [];
@@ -608,6 +726,7 @@ function collectVisibleIds(visibleReferenceCounts) {
608
726
  function buildFilterProjection(input) {
609
727
  const seed2 = seedFilterProjection(input);
610
728
  propagateVisibleReferenceCounts(seed2);
729
+ applyRelativePlacementPins(seed2, input.relativePlacementPins);
611
730
  const visibleIds = collectVisibleIds(seed2.visibleReferenceCounts);
612
731
  return {
613
732
  filter: input.filter,
@@ -795,7 +914,8 @@ function resolveFilter(adapter, filter, hoursPerDay) {
795
914
  activities,
796
915
  parentOf: (activityId) => adapter.getParentId(activityId),
797
916
  filter,
798
- context: buildFilterContext(hoursPerDay)
917
+ context: buildFilterContext(hoursPerDay),
918
+ relativePlacementPins: adapter.getRelativePlacementPins()
799
919
  });
800
920
  return { visibleIds: projection.visibleIds, projection };
801
921
  }
@@ -846,25 +966,118 @@ function normalizeParentKey(parent) {
846
966
 
847
967
  // src/internal/hierarchy/visual-order.ts
848
968
  var NOT_SET_SORT_VALUE = Number.MAX_SAFE_INTEGER;
849
- function getChildrenInVisualOrder(parentId, adapter) {
969
+ var EMPTY_RELATIVE_PLACEMENT_PINS = /* @__PURE__ */ new Map();
970
+ function buildRelativePlacementPinIndex(adapter) {
971
+ const index = /* @__PURE__ */ new Map();
972
+ for (const [activityId, pin] of adapter.getRelativePlacementPins?.() ?? []) {
973
+ const pinnedId = String(activityId);
974
+ const referenceId = String(pin.referenceId);
975
+ if (adapter.getActivity(pinnedId) === null || adapter.getActivity(referenceId) === null || !isRelativePlacementPinValid(
976
+ pinnedId,
977
+ pin,
978
+ (activityId2) => adapter.getParentId(activityId2)
979
+ )) {
980
+ continue;
981
+ }
982
+ const targetParentId = String(pin.branchParentId);
983
+ const branchPins = index.get(targetParentId) ?? /* @__PURE__ */ new Map();
984
+ branchPins.set(pinnedId, pin);
985
+ index.set(targetParentId, branchPins);
986
+ }
987
+ return index;
988
+ }
989
+ function getChildrenInVisualOrder(parentId, adapter, pinIndex = buildRelativePlacementPinIndex(adapter)) {
850
990
  const children = collectChildrenSnapshots(parentId, adapter);
851
991
  const userOrder = adapter.getOrderComparator?.() ?? null;
852
992
  children.sort(
853
993
  userOrder === null ? compareActivitiesInVisualOrder : (a, b) => userOrder(a, b) || compareActivitiesInVisualOrder(a, b)
854
994
  );
855
- return children.map((a) => String(a.id));
995
+ return applyRelativePlacementPins2(
996
+ children.map((activity) => String(activity.id)),
997
+ parentId,
998
+ pinIndex.get(String(parentId)) ?? EMPTY_RELATIVE_PLACEMENT_PINS
999
+ );
1000
+ }
1001
+ function applyRelativePlacementPins2(orderedIds, parentId, pins) {
1002
+ const order = buildLinkedActivityOrder(orderedIds);
1003
+ for (const [activityId, pin] of pins) {
1004
+ const pinnedId = String(activityId);
1005
+ const referenceId = String(pin.referenceId);
1006
+ const isFirstChild = referenceId === String(parentId);
1007
+ if (!order.previous.has(pinnedId) || !isFirstChild && (!order.previous.has(referenceId) || pinnedId === referenceId)) {
1008
+ continue;
1009
+ }
1010
+ detachActivity(order, pinnedId);
1011
+ if (isFirstChild) insertActivityFirst(order, pinnedId);
1012
+ else insertActivityBeside(order, pinnedId, referenceId, pin.side);
1013
+ }
1014
+ return materializeActivityOrder(order);
1015
+ }
1016
+ function buildLinkedActivityOrder(orderedIds) {
1017
+ const previous = /* @__PURE__ */ new Map();
1018
+ const next = /* @__PURE__ */ new Map();
1019
+ for (let index = 0; index < orderedIds.length; index++) {
1020
+ const activityId = orderedIds[index];
1021
+ if (activityId === void 0) continue;
1022
+ previous.set(activityId, orderedIds[index - 1] ?? null);
1023
+ next.set(activityId, orderedIds[index + 1] ?? null);
1024
+ }
1025
+ return { previous, next, head: orderedIds[0] ?? null };
1026
+ }
1027
+ function detachActivity(order, activityId) {
1028
+ const before = order.previous.get(activityId) ?? null;
1029
+ const after = order.next.get(activityId) ?? null;
1030
+ if (before === null) order.head = after;
1031
+ else order.next.set(before, after);
1032
+ if (after !== null) order.previous.set(after, before);
1033
+ }
1034
+ function insertActivityFirst(order, activityId) {
1035
+ order.previous.set(activityId, null);
1036
+ order.next.set(activityId, order.head);
1037
+ if (order.head !== null) order.previous.set(order.head, activityId);
1038
+ order.head = activityId;
1039
+ }
1040
+ function insertActivityBeside(order, activityId, referenceId, side) {
1041
+ if (side === "before") {
1042
+ const before = order.previous.get(referenceId) ?? null;
1043
+ order.previous.set(activityId, before);
1044
+ order.next.set(activityId, referenceId);
1045
+ order.previous.set(referenceId, activityId);
1046
+ if (before === null) order.head = activityId;
1047
+ else order.next.set(before, activityId);
1048
+ return;
1049
+ }
1050
+ const after = order.next.get(referenceId) ?? null;
1051
+ order.previous.set(activityId, referenceId);
1052
+ order.next.set(activityId, after);
1053
+ order.next.set(referenceId, activityId);
1054
+ if (after !== null) order.previous.set(after, activityId);
1055
+ }
1056
+ function materializeActivityOrder(order) {
1057
+ const result = [];
1058
+ let activityId = order.head;
1059
+ while (activityId !== null) {
1060
+ result.push(activityId);
1061
+ activityId = order.next.get(activityId) ?? null;
1062
+ }
1063
+ return result;
856
1064
  }
857
1065
  function iterateInVisualOrder(adapter) {
858
1066
  const result = [];
1067
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
859
1068
  const visit = (activity) => {
860
1069
  result.push(activity);
861
- const childIds = getChildrenInVisualOrder(String(activity.id), adapter);
1070
+ const childIds = getChildrenInVisualOrder(
1071
+ String(activity.id),
1072
+ adapter,
1073
+ pinIndex
1074
+ );
862
1075
  for (const childId of childIds) {
863
1076
  const child = adapter.getActivity(childId);
864
1077
  if (child) visit(child);
865
1078
  }
866
1079
  };
867
- const rootIds = getChildrenInVisualOrder(ROOT_PARENT_ID, adapter);
1080
+ const rootIds = getChildrenInVisualOrder(ROOT_PARENT_ID, adapter, pinIndex);
868
1081
  for (const rootId of rootIds) {
869
1082
  const root = adapter.getActivity(rootId);
870
1083
  if (root) visit(root);
@@ -925,8 +1138,9 @@ function setHas(set, id) {
925
1138
  // src/dispatch/shared/collect-branch-order.ts
926
1139
  function collectBranchOrder(adapter) {
927
1140
  const branches = [];
1141
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
928
1142
  const visit = (parentId) => {
929
- const childIds = getChildrenInVisualOrder(parentId, adapter);
1143
+ const childIds = getChildrenInVisualOrder(parentId, adapter, pinIndex);
930
1144
  if (childIds.length > 1) {
931
1145
  branches.push({ parentId, childIds });
932
1146
  }
@@ -939,8 +1153,9 @@ function collectBranchOrder(adapter) {
939
1153
  }
940
1154
  function collectBranchOrderForParents(adapter, parentIds) {
941
1155
  const branches = [];
1156
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
942
1157
  for (const parentId of parentIds) {
943
- const childIds = getChildrenInVisualOrder(parentId, adapter);
1158
+ const childIds = getChildrenInVisualOrder(parentId, adapter, pinIndex);
944
1159
  if (childIds.length > 1) branches.push({ parentId, childIds });
945
1160
  }
946
1161
  return branches;
@@ -1791,6 +2006,8 @@ function buildVirtualLink(originalLink, sourceId, targetId, counter, sourceLag,
1791
2006
  target: targetId,
1792
2007
  type: originalLink.type,
1793
2008
  lag: originalLink.lag,
2009
+ freeSlackHours: null,
2010
+ isCritical: false,
1794
2011
  _sourceLag: sourceLag,
1795
2012
  _targetLag: targetLag,
1796
2013
  _trueLag: trueLag,
@@ -2868,7 +3085,9 @@ function applyLinkCreates(links, deps) {
2868
3085
  source: operation.source,
2869
3086
  target: operation.target,
2870
3087
  type: operation.type,
2871
- lag: operation.lag
3088
+ lag: operation.lag,
3089
+ freeSlackHours: null,
3090
+ isCritical: false
2872
3091
  }))
2873
3092
  ];
2874
3093
  const effectiveVerdict = validateTopologyCommit({
@@ -2887,6 +3106,8 @@ function applyLinkCreates(links, deps) {
2887
3106
  target: operation.target,
2888
3107
  type: operation.type,
2889
3108
  lag: operation.lag,
3109
+ freeSlackHours: null,
3110
+ isCritical: false,
2890
3111
  ...preserved?.ganttId === void 0 ? {} : { ganttId: preserved.ganttId },
2891
3112
  ...preserved?.sectorId === void 0 ? {} : { sectorId: preserved.sectorId },
2892
3113
  ...preserved?.proplannerId === void 0 ? {} : { proplannerId: preserved.proplannerId }
@@ -2929,7 +3150,9 @@ function applyCreate(op, deps) {
2929
3150
  source: op.source,
2930
3151
  target: op.target,
2931
3152
  type: op.type,
2932
- lag: op.lag
3153
+ lag: op.lag,
3154
+ freeSlackHours: null,
3155
+ isCritical: false
2933
3156
  };
2934
3157
  const effectiveVerdict = validateTopologyCommit({
2935
3158
  port,
@@ -3443,19 +3666,44 @@ async function recomputeParentsCooperatively(parents, adapter, progressMode, opt
3443
3666
  }
3444
3667
  function findAncestorsOfDirty(adapter, dirtyIds) {
3445
3668
  const ancestors = /* @__PURE__ */ new Set();
3446
- for (const id of dirtyIds) walkAncestors(id, adapter, ancestors);
3669
+ const parentById = /* @__PURE__ */ new Map();
3670
+ for (const id of dirtyIds) {
3671
+ walkAncestors(id, adapter, ancestors, parentById);
3672
+ }
3447
3673
  if (ancestors.size === 0) return [];
3448
3674
  const ids = Array.from(ancestors);
3449
- const depths = computeDepths(ids, adapter);
3675
+ const depths = computeKnownDepths(ids, parentById);
3450
3676
  return sortDeepestFirst(ids, depths);
3451
3677
  }
3452
- function walkAncestors(id, adapter, collector) {
3678
+ function walkAncestors(id, adapter, collector, parentById) {
3453
3679
  let current = adapter.getParentId(id);
3454
3680
  while (current && !isRootParent(current)) {
3455
3681
  if (collector.has(current)) return;
3456
3682
  collector.add(current);
3457
- current = adapter.getParentId(current);
3683
+ const parent = adapter.getParentId(current);
3684
+ parentById.set(current, parent);
3685
+ current = parent;
3686
+ }
3687
+ }
3688
+ function computeKnownDepths(ids, parentById) {
3689
+ const depths = /* @__PURE__ */ new Map();
3690
+ for (const id of ids) {
3691
+ if (depths.has(id)) continue;
3692
+ const path = [];
3693
+ const inPath = /* @__PURE__ */ new Set();
3694
+ let current = id;
3695
+ while (current && !isRootParent(current) && !depths.has(current) && !inPath.has(current)) {
3696
+ path.push(current);
3697
+ inPath.add(current);
3698
+ current = parentById.get(current) ?? null;
3699
+ }
3700
+ let depth = current ? depths.get(current) ?? 0 : 0;
3701
+ for (let index = path.length - 1; index >= 0; index -= 1) {
3702
+ depth += 1;
3703
+ depths.set(path[index], depth);
3704
+ }
3458
3705
  }
3706
+ return depths;
3459
3707
  }
3460
3708
  function aggregateChildren(childrenIds, adapter, includeRealWork) {
3461
3709
  let minStart = null;
@@ -3679,19 +3927,6 @@ function durationToHours(durationDays, hoursPerDay) {
3679
3927
  return Math.round(Math.ceil(durationDays * hoursPerDay * 60) / 60);
3680
3928
  }
3681
3929
 
3682
- // src/columns/shared/error-codes.ts
3683
- var PARSE_ERROR = {
3684
- NOT_A_NUMBER: "not_a_number",
3685
- // ISSUE-057 (CP6): la unidad de una duración se declara, no se adivina.
3686
- MISSING_INPUT_UNIT: "missing_input_unit",
3687
- // D-OWNER-5: una duración en días es entera; una fracción se rechaza sin
3688
- // redondear (el redondeo silencioso era la puerta del bug de las 320 horas).
3689
- FRACTIONAL_DAYS: "fractional_days"
3690
- };
3691
- var VALIDATION_REASON = {
3692
- UNCHANGED: "unchanged",
3693
- NEGATIVE_DURATION: "negative_duration"};
3694
-
3695
3930
  // src/columns/end-date/setter.ts
3696
3931
  var setEndDate = (value) => ({
3697
3932
  endDate: value
@@ -3793,6 +4028,10 @@ var POST_PROCESSOR = {
3793
4028
  RECORD_LAST_START_DATE: "recordLastStartDate"
3794
4029
  };
3795
4030
 
4031
+ // src/columns/shared/error-codes.ts
4032
+ var VALIDATION_REASON = {
4033
+ NEGATIVE_DURATION: "negative_duration"};
4034
+
3796
4035
  // src/columns/shared/constraint-validation.ts
3797
4036
  function checkConstraintViolation2(constraintType, constraintDate, projectedDate) {
3798
4037
  return checkConstraintViolation(constraintType, constraintDate, projectedDate);
@@ -3817,18 +4056,7 @@ var durationPipeline = {
3817
4056
  },
3818
4057
  parseInput(rawValue, _activity, ctx) {
3819
4058
  const numericValue = parseFloat(String(rawValue));
3820
- if (!Number.isFinite(numericValue)) {
3821
- return parseError(PARSE_ERROR.NOT_A_NUMBER);
3822
- }
3823
- if (ctx.inputUnit === void 0) {
3824
- return parseError(PARSE_ERROR.MISSING_INPUT_UNIT);
3825
- }
3826
- if (ctx.inputUnit === "hours") {
3827
- return parsed(numericValue);
3828
- }
3829
- if (!Number.isInteger(numericValue)) {
3830
- return parseError(PARSE_ERROR.FRACTIONAL_DAYS);
3831
- }
4059
+ if (!Number.isFinite(numericValue)) return parseError("not_a_number");
3832
4060
  return parsed(durationToHours(numericValue, ctx.hoursPerDay));
3833
4061
  },
3834
4062
  validate(_activity, oldValue, newValue) {
@@ -4072,8 +4300,8 @@ var costPipeline = {
4072
4300
  }
4073
4301
  return canEdit();
4074
4302
  },
4075
- parseInput(rawValue, activity) {
4076
- if (String(rawValue).trim() === "") return parsed(activity.cost);
4303
+ parseInput(rawValue) {
4304
+ if (String(rawValue).trim() === "") return parsed(0);
4077
4305
  const numericValue = parseFloat(String(rawValue));
4078
4306
  if (Number.isNaN(numericValue)) return parseError("not_a_number");
4079
4307
  return parsed(numericValue);
@@ -4122,8 +4350,8 @@ var usedCostPipeline = {
4122
4350
  }
4123
4351
  return canEdit();
4124
4352
  },
4125
- parseInput(rawValue, activity) {
4126
- if (String(rawValue).trim() === "") return parsed(activity.usedCost);
4353
+ parseInput(rawValue) {
4354
+ if (String(rawValue).trim() === "") return parsed(0);
4127
4355
  const numericValue = parseFloat(String(rawValue));
4128
4356
  if (Number.isNaN(numericValue)) return parseError("not_a_number");
4129
4357
  return parsed(numericValue);
@@ -4864,7 +5092,9 @@ var ACTIVITY_PROPERTY = {
4864
5092
  CALENDAR_DURATION: "calendarDuration"};
4865
5093
  var LINK_PROPERTY = {
4866
5094
  TYPE: "type",
4867
- LAG: "lag"};
5095
+ LAG: "lag",
5096
+ FREE_SLACK_HOURS: "freeSlackHours",
5097
+ IS_CRITICAL: "isCritical"};
4868
5098
 
4869
5099
  // src/internal/state/dynamic-writes.ts
4870
5100
  function setActivityFieldDynamic(state, activityId, field, value) {
@@ -5720,10 +5950,11 @@ function mutateTheDateToFutureOrPastInBaseRestriction(dateBaseToCalculate, restr
5720
5950
 
5721
5951
  // src/critical-path/legacy/base/parents-calculations/forward.js
5722
5952
  var CalculateForwardParentsWithLinks = class {
5723
- constructor(linkedActivitiesData, activity, gantt) {
5953
+ constructor(linkedActivitiesData, activity, gantt, linkCandidates) {
5724
5954
  this.linkedActivitiesData = linkedActivitiesData;
5725
5955
  this.activity = activity;
5726
5956
  this.gantt = gantt;
5957
+ this.linkCandidates = linkCandidates;
5727
5958
  this.activityCalendar = this.gantt.getCalendar(this.activity.calendar_id);
5728
5959
  if (!this.activityCalendar) {
5729
5960
  throw new Error(
@@ -5768,6 +5999,10 @@ var CalculateForwardParentsWithLinks = class {
5768
5999
  continue;
5769
6000
  }
5770
6001
  if (calculation) {
6002
+ this.linkCandidates?.set(linkedActivity.linkId, {
6003
+ targetId: String(this.activity.id),
6004
+ ...calculation
6005
+ });
5771
6006
  calculations.push(calculation);
5772
6007
  }
5773
6008
  }
@@ -6215,7 +6450,8 @@ function parentWithLinks(params) {
6215
6450
  linkDirection,
6216
6451
  calculations,
6217
6452
  gantt,
6218
- direction
6453
+ direction,
6454
+ linkCandidates
6219
6455
  } = params;
6220
6456
  const linksArray = parent[linkProperty];
6221
6457
  const parentData = extractParentData(parent);
@@ -6230,7 +6466,8 @@ function parentWithLinks(params) {
6230
6466
  calculation = new forward_default(
6231
6467
  linksWithActivityData,
6232
6468
  parent,
6233
- gantt
6469
+ gantt,
6470
+ linkCandidates
6234
6471
  );
6235
6472
  calculation = calculation.calculate();
6236
6473
  } else {
@@ -6275,6 +6512,7 @@ function getLinksWithActivityDataForward(linksArray, linkDirection, calculations
6275
6512
  return null;
6276
6513
  }
6277
6514
  return {
6515
+ linkId: String(linkData.id),
6278
6516
  es: calculationsOfLinkedActivity.es,
6279
6517
  ef: calculationsOfLinkedActivity.ef,
6280
6518
  linkLag: linkData.lag,
@@ -6375,6 +6613,7 @@ var CalculationOfParent = class {
6375
6613
  this.calculationOfParentsFromLinks = params.calculationOfParentsFromLinks;
6376
6614
  this.singleParents = params.singleParents;
6377
6615
  this.parentCalculationFromChildren = params.parentCalculationFromChildren;
6616
+ this.linkCandidates = params.linkCandidates;
6378
6617
  this.gantt = params.gantt;
6379
6618
  }
6380
6619
  handleParentWithLinks() {
@@ -6422,7 +6661,8 @@ var CalculationOfParent = class {
6422
6661
  linkDirection: this.linkDirection,
6423
6662
  gantt: this.gantt,
6424
6663
  calculations: this.calculations,
6425
- direction: this.direction
6664
+ direction: this.direction,
6665
+ linkCandidates: this.linkCandidates
6426
6666
  });
6427
6667
  this.calculationsOfParentThatImpactChildrens.set(activity.id, calculation);
6428
6668
  this.calculationOfParentsFromLinks.add(activity.id);
@@ -7071,6 +7311,7 @@ var CriticalPathHelpers = class {
7071
7311
  calculationOfParentsFromLinks: this.calculationOfParentsFromLinks,
7072
7312
  singleParents: this.singleParents,
7073
7313
  parentCalculationFromChildren: this.parentCalculationFromChildren,
7314
+ linkCandidates: this.linkCandidates,
7074
7315
  gantt: this.gantt
7075
7316
  });
7076
7317
  if (this.pendingParentsWithLinks.has(activity.id)) {
@@ -7138,6 +7379,7 @@ var LinksRulesCalculationsMethods = class {
7138
7379
  this.parentsWithLinks = calculationObject.parentsWithLinks;
7139
7380
  this.parentsCalculations = calculationObject.parentsCalculations;
7140
7381
  this.gantt = calculationObject.ganttInstance;
7382
+ this.linkCandidates = calculationObject.linkCandidates;
7141
7383
  }
7142
7384
  getEsAndEfFromPreviousCalculatedActivities(activity) {
7143
7385
  const isAlap = this.alapMap.has(activity);
@@ -7309,7 +7551,12 @@ var LinksConstraintCalculator = class extends LinksRulesCalculationsMethods_defa
7309
7551
  if (!linkData) {
7310
7552
  throw new Error("Link data not found", { cause: { linkId: link } });
7311
7553
  }
7312
- return this.calculateLink(linkData);
7554
+ const calculation = this.calculateLink(linkData);
7555
+ this.linkCandidates?.set(String(linkData.id), {
7556
+ targetId: String(this.referenceActivity.id),
7557
+ ...calculation
7558
+ });
7559
+ return calculation;
7313
7560
  });
7314
7561
  const parent = Number(this.referenceActivity.parent);
7315
7562
  if (this.parentsWithLinks.has(parent)) {
@@ -7575,6 +7822,7 @@ var ForwardPath = class extends generic_calculations_default {
7575
7822
  this.totalFloatSnltFnlt = /* @__PURE__ */ new Map();
7576
7823
  this.totalFloatForMsoMfo = /* @__PURE__ */ new Map();
7577
7824
  this.totalFloatForAsap = /* @__PURE__ */ new Map();
7825
+ this.linkCandidates = /* @__PURE__ */ new Map();
7578
7826
  this.calculationOfParentsFromLinks = /* @__PURE__ */ new Set();
7579
7827
  this.parentCalculationFromChildren = /* @__PURE__ */ new Set();
7580
7828
  this.forbiddenActivitiesLinkedToParentsWithLinks = /* @__PURE__ */ new Set();
@@ -7673,7 +7921,8 @@ var ForwardPath = class extends generic_calculations_default {
7673
7921
  constraint: constraintType,
7674
7922
  parentsWithLinks: this.pendingParentsWithLinks,
7675
7923
  parentsCalculations: this.calculationsOfParentThatImpactChildrens,
7676
- ganttInstance: this.gantt
7924
+ ganttInstance: this.gantt,
7925
+ linkCandidates: this.linkCandidates
7677
7926
  }).calculate();
7678
7927
  if (constraintType === CONSTRAINT_TYPES.ASAP) {
7679
7928
  this.totalFloatForAsap.set(activity, {
@@ -7691,7 +7940,8 @@ var ForwardPath = class extends generic_calculations_default {
7691
7940
  constraint: constraintType,
7692
7941
  parentsWithLinks: this.pendingParentsWithLinks,
7693
7942
  parentsCalculations: this.calculationsOfParentThatImpactChildrens,
7694
- ganttInstance: this.gantt
7943
+ ganttInstance: this.gantt,
7944
+ linkCandidates: this.linkCandidates
7695
7945
  }).calculate());
7696
7946
  }
7697
7947
  if (constraintType === CONSTRAINT_TYPES.ALAP) {
@@ -8728,7 +8978,6 @@ var BackwardPath = class extends generic_calculations_default {
8728
8978
  }
8729
8979
  }
8730
8980
  if ([CONSTRAINT_TYPES.SNLT].includes(activity.constraint_type)) {
8731
- const constraintDate = activity.constraint_date;
8732
8981
  const parentLinkCalcualation2 = this.calculationsOfParentThatImpactChildrens.get(parent);
8733
8982
  const lsToComparison = addDurationToDate(
8734
8983
  this.gantt.getCalendar(activity.calendar_id),
@@ -8736,6 +8985,7 @@ var BackwardPath = class extends generic_calculations_default {
8736
8985
  -activity.duration,
8737
8986
  activity
8738
8987
  );
8988
+ const constraintDate = activity.constraint_date ?? lsToComparison;
8739
8989
  ls = new Date(
8740
8990
  Math.min(constraintDate.getTime(), lsToComparison.getTime())
8741
8991
  );
@@ -8747,8 +8997,8 @@ var BackwardPath = class extends generic_calculations_default {
8747
8997
  );
8748
8998
  }
8749
8999
  if ([CONSTRAINT_TYPES.FNLT].includes(activity.constraint_type)) {
8750
- const constraintDate = activity.constraint_date;
8751
9000
  const parentLinkCalcualation2 = this.calculationsOfParentThatImpactChildrens.get(parent);
9001
+ const constraintDate = activity.constraint_date ?? parentLinkCalcualation2.lf;
8752
9002
  ls = new Date(
8753
9003
  Math.min(constraintDate.getTime(), parentLinkCalcualation2.lf.getTime())
8754
9004
  );
@@ -9132,18 +9382,20 @@ var FreeFloat = class {
9132
9382
  );
9133
9383
  }
9134
9384
  calculateFreeFloatForEndOfProject(constraint) {
9385
+ const calculation = constraint === CONSTRAINT_TYPES.ALAP ? this.backwardMap.get(this.activity.id) : this.forwardMap.get(this.activity.id);
9135
9386
  return this.parseHoursToDays(
9136
9387
  this.calculationWithCalendar(
9137
9388
  this.gantt.getSubtaskDates().end_date,
9138
- constraint === CONSTRAINT_TYPES.ALAP ? this.backwardMap.get(this.activity.id).lf : this.forwardMap.get(this.activity.id).ef
9389
+ constraint === CONSTRAINT_TYPES.ALAP ? calculation?.lf : calculation?.ef
9139
9390
  )
9140
9391
  );
9141
9392
  }
9142
9393
  calculateFreeFloatOfRestriction(constraint) {
9394
+ const calculation = this.forwardMap.get(this.activity.id);
9143
9395
  return this.parseHoursToDays(
9144
9396
  this.calculationWithCalendar(
9145
9397
  this.activity.constraint_date,
9146
- constraint === CONSTRAINT_TYPES.FNLT ? this.forwardMap.get(this.activity.id).ef : this.forwardMap.get(this.activity.id).es
9398
+ constraint === CONSTRAINT_TYPES.FNLT ? calculation?.ef : calculation?.es
9147
9399
  )
9148
9400
  );
9149
9401
  }
@@ -9161,111 +9413,33 @@ var FreeFloat = class {
9161
9413
  sucessorId,
9162
9414
  predecessorId
9163
9415
  }) {
9164
- const getPredecessorDate = ({
9165
- direction,
9166
- constraint: constraint2 = CONSTRAINT_TYPES.ASAP
9167
- }) => {
9168
- if (constraint2 === CONSTRAINT_TYPES.ALAP) {
9169
- return this.backwardMap.get(predecessorId)[direction];
9416
+ const successor = (isSuscessorAlap ? this.backwardMap : this.forwardMap).get(sucessorId);
9417
+ const predecessor = (constraint === CONSTRAINT_TYPES.ALAP ? this.backwardMap : this.forwardMap).get(predecessorId);
9418
+ const predecessorStart = constraint === CONSTRAINT_TYPES.ALAP ? predecessor?.ls : predecessor?.es;
9419
+ const predecessorFinish = constraint === CONSTRAINT_TYPES.ALAP ? predecessor?.lf : predecessor?.ef;
9420
+ return {
9421
+ fs: {
9422
+ sucessor: successor?.ls ?? successor?.es,
9423
+ predecessor: predecessorFinish
9424
+ },
9425
+ ss: {
9426
+ sucessor: successor?.ls ?? successor?.es,
9427
+ predecessor: predecessorStart
9428
+ },
9429
+ ff: {
9430
+ sucessor: successor?.lf ?? successor?.ef,
9431
+ predecessor: predecessorFinish
9432
+ },
9433
+ sf: {
9434
+ sucessor: successor?.lf ?? successor?.ef,
9435
+ predecessor: predecessorStart
9170
9436
  }
9171
- return this.forwardMap.get(predecessorId)[direction];
9172
9437
  };
9173
- if (constraint === CONSTRAINT_TYPES.ASAP || constraint === CONSTRAINT_TYPES.SNET || constraint === CONSTRAINT_TYPES.FNET || constraint === CONSTRAINT_TYPES.MSO || constraint === CONSTRAINT_TYPES.MFO) {
9174
- return {
9175
- fs: {
9176
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).ls : this.forwardMap.get(sucessorId).es,
9177
- predecessor: getPredecessorDate({ direction: "ef" })
9178
- },
9179
- ss: {
9180
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).ls : this.forwardMap.get(sucessorId).es,
9181
- predecessor: getPredecessorDate({ direction: "es" })
9182
- },
9183
- ff: {
9184
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).lf : this.forwardMap.get(sucessorId).ef,
9185
- predecessor: getPredecessorDate({ direction: "ef" })
9186
- },
9187
- sf: {
9188
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).lf : this.forwardMap.get(sucessorId).ef,
9189
- predecessor: getPredecessorDate({ direction: "es" })
9190
- }
9191
- };
9192
- }
9193
- if (constraint === CONSTRAINT_TYPES.ALAP) {
9194
- return {
9195
- fs: {
9196
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).ls : this.forwardMap.get(sucessorId).es,
9197
- predecessor: getPredecessorDate({
9198
- constraint: CONSTRAINT_TYPES.ALAP,
9199
- direction: "lf"
9200
- })
9201
- },
9202
- ss: {
9203
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).ls : this.forwardMap.get(sucessorId).es,
9204
- predecessor: getPredecessorDate({
9205
- constraint: CONSTRAINT_TYPES.ALAP,
9206
- direction: "ls"
9207
- })
9208
- },
9209
- ff: {
9210
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).lf : this.forwardMap.get(sucessorId).ef,
9211
- predecessor: getPredecessorDate({
9212
- constraint: CONSTRAINT_TYPES.ALAP,
9213
- direction: "lf"
9214
- })
9215
- },
9216
- sf: {
9217
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).lf : this.forwardMap.get(sucessorId).ef,
9218
- predecessor: getPredecessorDate({
9219
- constraint: CONSTRAINT_TYPES.ALAP,
9220
- direction: "ls"
9221
- })
9222
- }
9223
- };
9224
- }
9225
- if (constraint === CONSTRAINT_TYPES.SNLT) {
9226
- return {
9227
- fs: {
9228
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).ls : this.forwardMap.get(sucessorId).es,
9229
- predecessor: getPredecessorDate({ direction: "ef" })
9230
- },
9231
- ss: {
9232
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).ls : this.forwardMap.get(sucessorId).es,
9233
- predecessor: getPredecessorDate({ direction: "es" })
9234
- },
9235
- ff: {
9236
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).lf : this.forwardMap.get(sucessorId).ef,
9237
- predecessor: getPredecessorDate({ direction: "ef" })
9238
- },
9239
- sf: {
9240
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).lf : this.forwardMap.get(sucessorId).ef,
9241
- predecessor: getPredecessorDate({ direction: "es" })
9242
- }
9243
- };
9244
- }
9245
- if (constraint === CONSTRAINT_TYPES.FNLT) {
9246
- return {
9247
- fs: {
9248
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).ls : this.forwardMap.get(sucessorId).es,
9249
- predecessor: getPredecessorDate({ direction: "ef" })
9250
- },
9251
- ss: {
9252
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).ls : this.forwardMap.get(sucessorId).es,
9253
- predecessor: getPredecessorDate({ direction: "es" })
9254
- },
9255
- ff: {
9256
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).lf : this.forwardMap.get(sucessorId).ef,
9257
- predecessor: getPredecessorDate({ direction: "ef" })
9258
- },
9259
- sf: {
9260
- sucessor: isSuscessorAlap ? this.backwardMap.get(sucessorId).lf : this.forwardMap.get(sucessorId).ef,
9261
- predecessor: getPredecessorDate({ direction: "es" })
9262
- }
9263
- };
9264
- }
9265
9438
  }
9266
9439
  calculateMsoMfo(constraint) {
9267
9440
  const CONSTRAINT_DATE2 = this.activity.constraint_date || /* @__PURE__ */ new Date();
9268
- const DATE_TO_CALCULATE = constraint === CONSTRAINT_TYPES.MSO ? this.forwardMap.get(this.activity.id).es : this.forwardMap.get(this.activity.id).ef;
9441
+ const calculation = this.forwardMap.get(this.activity.id);
9442
+ const DATE_TO_CALCULATE = constraint === CONSTRAINT_TYPES.MSO ? calculation?.es : calculation?.ef;
9269
9443
  const freeSlack = this.calculationWithCalendar(
9270
9444
  CONSTRAINT_DATE2,
9271
9445
  DATE_TO_CALCULATE
@@ -9279,6 +9453,9 @@ var FreeFloat = class {
9279
9453
  return filterValues.length ? Math.min(...filterValues) : 0;
9280
9454
  }
9281
9455
  calculationWithCalendar(dateOne, dateTwo) {
9456
+ if (!(dateOne instanceof Date) || !(dateTwo instanceof Date) || !Number.isFinite(dateOne.getTime()) || !Number.isFinite(dateTwo.getTime())) {
9457
+ return Number.NaN;
9458
+ }
9282
9459
  const key = `${dateTwo.getTime()}_${dateOne.getTime()}`;
9283
9460
  if (this.durationCache.has(key)) {
9284
9461
  return this.durationCache.get(key);
@@ -9292,7 +9469,7 @@ var FreeFloat = class {
9292
9469
  return result;
9293
9470
  }
9294
9471
  parseHoursToDays(date2) {
9295
- if (!date2) {
9472
+ if (date2 === void 0 || date2 === null) {
9296
9473
  return 0;
9297
9474
  }
9298
9475
  return date2 / this.gantt.config.hoursPerDay;
@@ -9358,6 +9535,7 @@ var CriticalPath = class {
9358
9535
  this.backwardPathCalculations = null;
9359
9536
  this.totalFloat = /* @__PURE__ */ new Map();
9360
9537
  this.freeFloat = /* @__PURE__ */ new Map();
9538
+ this.linkResidualHours = /* @__PURE__ */ new Map();
9361
9539
  this.currentCalculationId = 0;
9362
9540
  }
9363
9541
  getCurrentCalculationId = () => this.currentCalculationId;
@@ -9388,6 +9566,13 @@ var CriticalPath = class {
9388
9566
  if (!isCurrent()) return false;
9389
9567
  this.forwardPathCalculations = forwardPathCalculations;
9390
9568
  this.backwardPathCalculations = backwardPath.calculations;
9569
+ const linkResidualHours = await this.calculateLinkResidualHours(
9570
+ forwardPath.linkCandidates,
9571
+ forwardPathCalculations,
9572
+ isCurrent
9573
+ );
9574
+ if (!linkResidualHours || !isCurrent()) return false;
9575
+ this.linkResidualHours = linkResidualHours;
9391
9576
  const { totalFloatMap, freeFloatMap } = await this.calculateFloatsAsync(
9392
9577
  forwardPath,
9393
9578
  backwardPath,
@@ -9407,6 +9592,38 @@ var CriticalPath = class {
9407
9592
  throw error;
9408
9593
  }
9409
9594
  }
9595
+ async calculateLinkResidualHours(linkCandidates, forwardCalculations, isCurrent = () => true) {
9596
+ const residuals = /* @__PURE__ */ new Map();
9597
+ let sliceStartTime = performance.now();
9598
+ for (const [linkId, candidate] of linkCandidates) {
9599
+ sliceStartTime = await yieldIfNeeded2(sliceStartTime);
9600
+ if (!isCurrent()) return null;
9601
+ const targetId = Number(candidate.targetId);
9602
+ const target = this.gantt.getTask(targetId);
9603
+ const settled = forwardCalculations.get(targetId);
9604
+ const candidateEf = candidate.ef;
9605
+ const settledEf = settled?.ef;
9606
+ const calendar = target && this.gantt.getCalendar(target.calendar_id);
9607
+ if (!calendar || !(candidateEf instanceof Date) || !(settledEf instanceof Date) || !Number.isFinite(candidateEf.getTime()) || !Number.isFinite(settledEf.getTime())) {
9608
+ residuals.set(linkId, null);
9609
+ continue;
9610
+ }
9611
+ const candidateTime = candidateEf.getTime();
9612
+ const settledTime = settledEf.getTime();
9613
+ const duration = settledTime >= candidateTime ? calendar.calculateDuration({
9614
+ start_date: candidateEf,
9615
+ end_date: settledEf
9616
+ }) : -calendar.calculateDuration({
9617
+ start_date: settledEf,
9618
+ end_date: candidateEf
9619
+ });
9620
+ residuals.set(
9621
+ linkId,
9622
+ Number.isFinite(duration) ? Object.is(duration, -0) ? 0 : duration : null
9623
+ );
9624
+ }
9625
+ return residuals;
9626
+ }
9410
9627
  async calculateFloatsAsync(forward, backward, isCurrent) {
9411
9628
  try {
9412
9629
  const projectDates = this.gantt.getSubtaskDates();
@@ -9510,14 +9727,52 @@ function computeLevels(state) {
9510
9727
  for (const id of activitiesById.keys()) resolveLevel(id, /* @__PURE__ */ new Set());
9511
9728
  return levelById;
9512
9729
  }
9513
- function toLegacyTasks(state) {
9730
+ function createLegacyIds(state) {
9731
+ const canonicalIds = /* @__PURE__ */ new Set();
9732
+ state.forEachActivity((activity, id) => {
9733
+ canonicalIds.add(String(id));
9734
+ if (activity.parentId !== null) canonicalIds.add(String(activity.parentId));
9735
+ });
9736
+ const numericCounts = /* @__PURE__ */ new Map();
9737
+ for (const id of canonicalIds) {
9738
+ const numeric = Number(id);
9739
+ if (Number.isSafeInteger(numeric) && numeric !== 0) {
9740
+ numericCounts.set(numeric, (numericCounts.get(numeric) ?? 0) + 1);
9741
+ }
9742
+ }
9743
+ const canonicalToLegacy = /* @__PURE__ */ new Map();
9744
+ const legacyToCanonical = /* @__PURE__ */ new Map();
9745
+ for (const id of canonicalIds) {
9746
+ const numeric = Number(id);
9747
+ if (Number.isSafeInteger(numeric) && numeric !== 0 && numericCounts.get(numeric) === 1) {
9748
+ canonicalToLegacy.set(id, numeric);
9749
+ legacyToCanonical.set(numeric, id);
9750
+ }
9751
+ }
9752
+ let next = 1;
9753
+ const toLegacy = (rawId) => {
9754
+ const id = String(rawId);
9755
+ const existing = canonicalToLegacy.get(id);
9756
+ if (existing !== void 0) return existing;
9757
+ while (legacyToCanonical.has(next)) next += 1;
9758
+ canonicalToLegacy.set(id, next);
9759
+ legacyToCanonical.set(next, id);
9760
+ return next++;
9761
+ };
9762
+ for (const id of canonicalIds) toLegacy(id);
9763
+ return {
9764
+ toLegacy,
9765
+ toCanonical: (id) => typeof id === "number" ? legacyToCanonical.get(id) ?? null : canonicalToLegacy.has(String(id)) ? String(id) : null
9766
+ };
9767
+ }
9768
+ function toLegacyTasks(state, ids) {
9514
9769
  const levelById = computeLevels(state);
9515
9770
  const leveled = /* @__PURE__ */ new Map();
9516
9771
  state.forEachActivity((activity, id) => {
9517
9772
  const key = String(id);
9518
9773
  leveled.set(key, {
9519
- id: Number(id),
9520
- parent: activity.parentId === null ? 0 : Number(activity.parentId),
9774
+ id: ids.toLegacy(key),
9775
+ parent: activity.parentId === null ? 0 : ids.toLegacy(activity.parentId),
9521
9776
  $level: levelById.get(key) ?? 0,
9522
9777
  $source: state.getOutgoingLinkIds(key),
9523
9778
  $target: state.getIncomingLinkIds(key),
@@ -9591,7 +9846,8 @@ async function createGanttAdapter(state, hoursPerDay) {
9591
9846
  };
9592
9847
  const bounds = projectBounds(state);
9593
9848
  await yieldToBrowser();
9594
- const leveled = toLegacyTasks(state);
9849
+ const ids = createLegacyIds(state);
9850
+ const leveled = toLegacyTasks(state, ids);
9595
9851
  await yieldToBrowser();
9596
9852
  const byTime = [...leveled.values()].sort(
9597
9853
  (a, b) => (a.start_date?.getTime() ?? 0) - (b.start_date?.getTime() ?? 0)
@@ -9601,24 +9857,31 @@ async function createGanttAdapter(state, hoursPerDay) {
9601
9857
  return {
9602
9858
  fullyParsed: true,
9603
9859
  config: { hoursPerDay },
9604
- getTask: (id) => leveled.get(String(id)) ?? null,
9860
+ getTask: (id) => {
9861
+ const canonicalId = ids.toCanonical(id);
9862
+ return canonicalId === null ? null : leveled.get(canonicalId) ?? null;
9863
+ },
9605
9864
  getLink: (id) => {
9606
9865
  const link = state.getLink(id);
9607
9866
  if (!link) return null;
9608
9867
  return {
9609
9868
  ...link,
9610
- source: Number(link.source),
9611
- target: Number(link.target),
9869
+ source: ids.toLegacy(link.source),
9870
+ target: ids.toLegacy(link.target),
9612
9871
  lag: link.lag
9613
9872
  };
9614
9873
  },
9615
9874
  getCalendar: resolveCalendar,
9616
- getChildren: (parentId) => state.getChildren(parentId).map((childId) => Number(childId)),
9875
+ getChildren: (parentId) => {
9876
+ const canonicalId = ids.toCanonical(parentId);
9877
+ return canonicalId === null ? [] : state.getChildren(canonicalId).map(ids.toLegacy);
9878
+ },
9617
9879
  eachTask: (cb) => {
9618
9880
  for (const task of treeOrdered) cb(task);
9619
9881
  },
9620
9882
  getTaskByTime: () => byTime,
9621
9883
  getSubtaskDates: () => bounds,
9884
+ getCanonicalActivityId: ids.toCanonical,
9622
9885
  refreshData: () => {
9623
9886
  }
9624
9887
  };
@@ -9626,21 +9889,92 @@ async function createGanttAdapter(state, hoursPerDay) {
9626
9889
 
9627
9890
  // src/critical-path/run-critical-path.ts
9628
9891
  var CP_WRITE_YIELD_EVERY = 1e3;
9629
- function fieldsForActivity(activityId, progress, maps) {
9630
- const toDate = (value) => value == null ? null : new Date(value);
9892
+ function fieldsForLink(rawResidual, sourceIsCritical, targetIsCritical, targetProgress) {
9893
+ const freeSlackHours = typeof rawResidual === "number" && Number.isFinite(rawResidual) ? Object.is(rawResidual, -0) ? 0 : rawResidual : null;
9894
+ return {
9895
+ freeSlackHours,
9896
+ isCritical: freeSlackHours !== null && freeSlackHours <= 0 && sourceIsCritical && targetIsCritical && targetProgress === 0
9897
+ };
9898
+ }
9899
+ async function collectActivityFields(state, maps, hoursPerDay, toCanonicalActivityId, isCurrent) {
9900
+ const fields = /* @__PURE__ */ new Map();
9901
+ let sinceYield = 0;
9902
+ for (const calculationId of maps.forward?.keys() ?? []) {
9903
+ if (++sinceYield >= CP_WRITE_YIELD_EVERY) {
9904
+ sinceYield = 0;
9905
+ await yieldToBrowser();
9906
+ if (!isCurrent()) return null;
9907
+ }
9908
+ const activityId = toCanonicalActivityId(calculationId);
9909
+ if (activityId === null) continue;
9910
+ const activity = state.getActivity(activityId);
9911
+ if (!activity) continue;
9912
+ fields.set(
9913
+ String(activityId),
9914
+ fieldsForActivity(
9915
+ calculationId,
9916
+ Number(activity.progress ?? 0),
9917
+ maps,
9918
+ hoursPerDay
9919
+ )
9920
+ );
9921
+ }
9922
+ return isCurrent() ? fields : null;
9923
+ }
9924
+ async function collectLinkFields(state, linkResidualHours, activityFields, isCurrent) {
9925
+ const fields = /* @__PURE__ */ new Map();
9926
+ let sinceYield = 0;
9927
+ for (const link of state.getAllLinks()) {
9928
+ if (++sinceYield >= CP_WRITE_YIELD_EVERY) {
9929
+ sinceYield = 0;
9930
+ await yieldToBrowser();
9931
+ if (!isCurrent()) return null;
9932
+ }
9933
+ fields.set(
9934
+ String(link.id),
9935
+ fieldsForLink(
9936
+ linkResidualHours.get(String(link.id)),
9937
+ activityFields.get(link.source)?.isCritical === true,
9938
+ activityFields.get(link.target)?.isCritical === true,
9939
+ state.getActivity(link.target)?.progress
9940
+ )
9941
+ );
9942
+ }
9943
+ return isCurrent() ? fields : null;
9944
+ }
9945
+ var slackDaysToHours = (valueDays, hoursPerDay) => {
9946
+ if (typeof valueDays !== "number" || !Number.isFinite(valueDays) || !Number.isFinite(hoursPerDay) || hoursPerDay <= 0) {
9947
+ return null;
9948
+ }
9949
+ const hours = valueDays * hoursPerDay;
9950
+ if (!Number.isFinite(hours)) return null;
9951
+ return Object.is(hours, -0) ? 0 : hours;
9952
+ };
9953
+ function fieldsForActivity(activityId, progress, maps, hoursPerDay) {
9954
+ const toDate = (value) => {
9955
+ if (value == null) return null;
9956
+ const date2 = new Date(value);
9957
+ return Number.isFinite(date2.getTime()) ? date2 : null;
9958
+ };
9631
9959
  const forward = maps.forward?.get(activityId);
9632
9960
  const backward = maps.backward?.get(activityId);
9633
- const totalSlack = maps.totalFloat.get(activityId) ?? null;
9961
+ const totalSlackHours = slackDaysToHours(
9962
+ maps.totalFloat.get(activityId),
9963
+ hoursPerDay
9964
+ );
9634
9965
  return {
9635
9966
  criticalPath: {
9636
9967
  earlyStart: toDate(forward?.es),
9637
9968
  earlyFinish: toDate(forward?.ef),
9638
9969
  lateStart: toDate(backward?.ls),
9639
9970
  lateFinish: toDate(backward?.lf),
9640
- totalSlackHours: totalSlack
9971
+ totalSlackHours
9641
9972
  },
9642
- isCritical: progress < 100 && totalSlack != null && totalSlack <= 0,
9643
- freeSlackHours: maps.freeFloat.get(activityId)?.activityFreeFloat ?? null
9973
+ isCritical: progress < 100 && totalSlackHours !== null && totalSlackHours <= 0,
9974
+ freeSlackHours: slackDaysToHours(
9975
+ maps.freeFloat.get(activityId)?.activityFreeFloat,
9976
+ hoursPerDay
9977
+ )
9644
9978
  };
9645
9979
  }
9646
9980
  var datesEqual = (left, right) => left === right || left !== null && right !== null && left.getTime() === right.getTime();
@@ -9653,6 +9987,14 @@ function setCriticalPathFieldsIfChanged(state, activityId, fields) {
9653
9987
  }
9654
9988
  state.setActivityFields(activityId, fields);
9655
9989
  }
9990
+ function setCriticalPathLinkFieldsIfChanged(state, linkId, fields) {
9991
+ const link = state.getLink(linkId);
9992
+ if (!link || Object.is(link.freeSlackHours, fields.freeSlackHours) && Object.is(link.isCritical, fields.isCritical)) {
9993
+ return;
9994
+ }
9995
+ state.setLinkField(linkId, "freeSlackHours", fields.freeSlackHours);
9996
+ state.setLinkField(linkId, "isCritical", fields.isCritical);
9997
+ }
9656
9998
  async function computeCriticalPathFields(state, hoursPerDay, isCurrent = () => true) {
9657
9999
  if (!isCurrent()) return null;
9658
10000
  const gantt = await createGanttAdapter(state, hoursPerDay);
@@ -9670,37 +10012,34 @@ async function computeCriticalPathFields(state, hoursPerDay, isCurrent = () => t
9670
10012
  totalFloat: criticalPath.totalFloat,
9671
10013
  freeFloat: criticalPath.freeFloat
9672
10014
  };
9673
- const activityIds = /* @__PURE__ */ new Set([...maps.forward?.keys() ?? []]);
9674
- const fieldsByActivity = /* @__PURE__ */ new Map();
9675
- let sinceYield = 0;
9676
- for (const activityId of activityIds) {
9677
- if (++sinceYield >= CP_WRITE_YIELD_EVERY) {
9678
- sinceYield = 0;
9679
- await yieldToBrowser();
9680
- if (!isCurrent()) return null;
9681
- }
9682
- const activity = state.getActivity(activityId);
9683
- if (!activity) continue;
9684
- const progress = Number(activity.progress ?? 0);
9685
- fieldsByActivity.set(
9686
- activityId,
9687
- fieldsForActivity(activityId, progress, maps)
9688
- );
9689
- }
9690
- return isCurrent() ? fieldsByActivity : null;
9691
- }
9692
- async function applyCriticalPath(state, hoursPerDay, isCurrent = () => true) {
9693
- const fieldsByActivity = await computeCriticalPathFields(
10015
+ const activities = await collectActivityFields(
9694
10016
  state,
10017
+ maps,
9695
10018
  hoursPerDay,
10019
+ gantt.getCanonicalActivityId,
10020
+ isCurrent
10021
+ );
10022
+ if (!activities) return null;
10023
+ const links = await collectLinkFields(
10024
+ state,
10025
+ criticalPath.linkResidualHours,
10026
+ activities,
9696
10027
  isCurrent
9697
10028
  );
9698
- if (!fieldsByActivity || !isCurrent()) return false;
9699
- for (const [activityId, fields] of fieldsByActivity) {
10029
+ return links ? { activities, links } : null;
10030
+ }
10031
+ async function applyCriticalPath(state, hoursPerDay, isCurrent = () => true) {
10032
+ const fields = await computeCriticalPathFields(state, hoursPerDay, isCurrent);
10033
+ if (!fields || !isCurrent()) return false;
10034
+ for (const [activityId, activityFields] of fields.activities) {
9700
10035
  if (!isCurrent()) return false;
9701
- setCriticalPathFieldsIfChanged(state, activityId, fields);
10036
+ setCriticalPathFieldsIfChanged(state, activityId, activityFields);
9702
10037
  }
9703
- return true;
10038
+ for (const [linkId, linkFields] of fields.links) {
10039
+ if (!isCurrent()) return false;
10040
+ setCriticalPathLinkFieldsIfChanged(state, linkId, linkFields);
10041
+ }
10042
+ return true;
9704
10043
  }
9705
10044
 
9706
10045
  // src/propagations/upward/real-cost.ts
@@ -9800,9 +10139,15 @@ function isSchedulingDispatch(action, structuralStaleKinds) {
9800
10139
  return true;
9801
10140
  }
9802
10141
  function dispatchChangesSchedulingState(action) {
10142
+ if (action.kind === "activity-lookahead-sync") {
10143
+ return action.persistedProgress !== void 0;
10144
+ }
9803
10145
  return isSchedulingDispatch(action, NO_STRUCTURAL_EXEMPTIONS);
9804
10146
  }
9805
10147
  function resolveRunCriticalPath(action) {
10148
+ if (action.kind === "activity-lookahead-sync") {
10149
+ return action.persistedProgress !== void 0;
10150
+ }
9806
10151
  return isSchedulingDispatch(
9807
10152
  action,
9808
10153
  STRUCTURAL_KINDS_LEAVING_CRITICAL_PATH_STALE
@@ -10088,6 +10433,23 @@ function detectSirAutoReject(beforeSnap, adapter, touchedIds) {
10088
10433
  return effects;
10089
10434
  }
10090
10435
 
10436
+ // src/dispatch/conversion.ts
10437
+ function snapshotToLink(l) {
10438
+ const view = {
10439
+ id: String(l.id),
10440
+ source: String(l.source),
10441
+ target: String(l.target),
10442
+ type: l.type,
10443
+ lag: Number(l.lag),
10444
+ freeSlackHours: typeof l.freeSlackHours === "number" && Number.isFinite(l.freeSlackHours) ? l.freeSlackHours : null,
10445
+ isCritical: l.isCritical === true
10446
+ };
10447
+ if (l.proplannerId != null) view.proplannerId = Number(l.proplannerId);
10448
+ if (l.ganttId != null) view.ganttId = Number(l.ganttId);
10449
+ if (l.sectorId != null) view.sectorId = Number(l.sectorId);
10450
+ return view;
10451
+ }
10452
+
10091
10453
  // src/dispatch/shared/change-set.ts
10092
10454
  async function assembleChangeSet(adapter, args) {
10093
10455
  const allTouched = collectChangeSetIds(adapter, args);
@@ -10097,7 +10459,7 @@ async function assembleChangeSet(adapter, args) {
10097
10459
  const insertedIds = collectInsertedIds(captured);
10098
10460
  mergeDirtyBeforeImages(adapter, captured, insertedIds, before, allTouched);
10099
10461
  }
10100
- const sirDetection = args.preview === true ? void 0 : args.sirDetection;
10462
+ const sirDetection = args.sirDetection;
10101
10463
  const beforeDiffEffects = sirDetection === "before-diff" ? detectSirAutoReject(before, adapter, allTouched) : [];
10102
10464
  const correlativeBefore = buildCorrelativeBeforeMap(
10103
10465
  args.correlativeShifts,
@@ -10111,16 +10473,89 @@ async function assembleChangeSet(adapter, args) {
10111
10473
  );
10112
10474
  const effects = sirDetection === "after-diff" ? detectSirAutoReject(before, adapter, allTouched) : beforeDiffEffects;
10113
10475
  const warnings = args.warnings ?? [];
10476
+ const linkChanges = mergeLinkChanges(
10477
+ args.links ?? [],
10478
+ captured ? buildCapturedCriticalPathLinkChanges(adapter, captured) : []
10479
+ );
10114
10480
  return {
10115
10481
  source: args.source,
10116
10482
  activities: activityChanges,
10117
- links: args.links ?? [],
10483
+ links: linkChanges,
10118
10484
  calendars: [],
10119
10485
  trackingEvents: args.trackingEvents ?? [],
10120
10486
  ...effects.length > 0 ? { effects } : {},
10121
10487
  ...warnings.length > 0 ? { warnings } : {}
10122
10488
  };
10123
10489
  }
10490
+ function buildCapturedCriticalPathLinkChanges(adapter, captured) {
10491
+ const changes = [];
10492
+ for (const operation of captured.ops) {
10493
+ if (operation.kind !== "link-field") continue;
10494
+ const after = adapter.getLink(operation.linkId);
10495
+ if (!after) continue;
10496
+ const fields = {};
10497
+ for (const field of ["freeSlackHours", "isCritical"]) {
10498
+ if (!Object.is(operation.before[field], after[field])) {
10499
+ fields[field] = {
10500
+ before: operation.before[field],
10501
+ after: after[field]
10502
+ };
10503
+ }
10504
+ }
10505
+ if (Object.keys(fields).length === 0) continue;
10506
+ changes.push({
10507
+ id: operation.linkId,
10508
+ kind: "updated",
10509
+ fields,
10510
+ after: snapshotToLink(after)
10511
+ });
10512
+ }
10513
+ return changes;
10514
+ }
10515
+ function mergeLinkChanges(explicit, captured) {
10516
+ const merged = [];
10517
+ const indexById = /* @__PURE__ */ new Map();
10518
+ for (const change of explicit) {
10519
+ const index = indexById.get(change.id);
10520
+ if (index === void 0) {
10521
+ indexById.set(change.id, merged.length);
10522
+ merged.push(change);
10523
+ continue;
10524
+ }
10525
+ const current = merged[index];
10526
+ if ((current.kind === "updated" || current.kind === "created") && change.kind === "updated") {
10527
+ merged[index] = {
10528
+ ...current,
10529
+ fields: { ...current.fields, ...change.fields },
10530
+ after: change.after
10531
+ };
10532
+ continue;
10533
+ }
10534
+ merged[index] = change;
10535
+ }
10536
+ for (const change of captured) {
10537
+ const index = indexById.get(change.id);
10538
+ if (index === void 0) {
10539
+ indexById.set(change.id, merged.length);
10540
+ merged.push(change);
10541
+ continue;
10542
+ }
10543
+ const current = merged[index];
10544
+ if (current.kind === "deleted") continue;
10545
+ const capturedFields = current.kind === "created" ? Object.fromEntries(
10546
+ Object.entries(change.fields ?? {}).map(([field, value]) => [
10547
+ field,
10548
+ { before: void 0, after: value.after }
10549
+ ])
10550
+ ) : change.fields;
10551
+ merged[index] = {
10552
+ ...current,
10553
+ fields: { ...current.fields, ...capturedFields },
10554
+ after: change.after
10555
+ };
10556
+ }
10557
+ return merged;
10558
+ }
10124
10559
  function buildCorrelativeBeforeMap(shifts, touched) {
10125
10560
  if (!shifts || shifts.length === 0) return void 0;
10126
10561
  const correlativeBefore = /* @__PURE__ */ new Map();
@@ -10156,21 +10591,6 @@ function collectInsertedIds(captured) {
10156
10591
  );
10157
10592
  }
10158
10593
 
10159
- // src/dispatch/conversion.ts
10160
- function snapshotToLink(l) {
10161
- const view = {
10162
- id: String(l.id),
10163
- source: String(l.source),
10164
- target: String(l.target),
10165
- type: l.type,
10166
- lag: Number(l.lag)
10167
- };
10168
- if (l.proplannerId != null) view.proplannerId = Number(l.proplannerId);
10169
- if (l.ganttId != null) view.ganttId = Number(l.ganttId);
10170
- if (l.sectorId != null) view.sectorId = Number(l.sectorId);
10171
- return view;
10172
- }
10173
-
10174
10594
  // src/dispatch/shared/helpers.ts
10175
10595
  function buildLinkChangesForBatch(applied, beforeLinks, adapter) {
10176
10596
  const out = [];
@@ -10438,7 +10858,6 @@ async function dispatchInlineEdit(action, options, deps) {
10438
10858
  touchedIds,
10439
10859
  scheduledIds,
10440
10860
  sirDetection: "before-diff",
10441
- preview: options.preview,
10442
10861
  links: linkChanges,
10443
10862
  trackingEvents,
10444
10863
  ...constraintWarning ? { warnings: [constraintWarning] } : {},
@@ -10516,6 +10935,26 @@ function collectDeterministicIds(operations) {
10516
10935
  });
10517
10936
  return ids.size > 0 ? ids : void 0;
10518
10937
  }
10938
+ function resolveDeterministicIds(deterministicIds, deps) {
10939
+ if (deterministicIds === void 0) return void 0;
10940
+ const resolved = /* @__PURE__ */ new Map();
10941
+ const assigned = /* @__PURE__ */ new Set();
10942
+ const nextAvailableId = () => {
10943
+ let candidate = deps.linkIdGen.next();
10944
+ while (deps.adapter.getLink(candidate) !== null || deps.isLinkIdReserved(String(candidate)) || assigned.has(String(candidate))) {
10945
+ candidate = deps.linkIdGen.next();
10946
+ }
10947
+ return candidate;
10948
+ };
10949
+ for (const [operationIndex, explicitId] of deterministicIds) {
10950
+ const isReserved = deps.adapter.getLink(explicitId) !== null || deps.isLinkIdReserved(String(explicitId)) || assigned.has(String(explicitId));
10951
+ const linkId = isReserved ? nextAvailableId() : explicitId;
10952
+ if (!isReserved) deps.linkIdGen.bump(explicitId);
10953
+ assigned.add(String(linkId));
10954
+ resolved.set(operationIndex, linkId);
10955
+ }
10956
+ return resolved;
10957
+ }
10519
10958
  function batchOpToLinkOperation(op, hoursPerDay) {
10520
10959
  if (op.kind === "create") {
10521
10960
  return {
@@ -10548,7 +10987,7 @@ async function dispatchLinkBatch(operations, source, options, deps, deterministi
10548
10987
  operations,
10549
10988
  adapter,
10550
10989
  linkIdGen,
10551
- deterministicIds
10990
+ resolveDeterministicIds(deterministicIds, deps)
10552
10991
  );
10553
10992
  if (operations.length === 1 && applied[0] && applied[0].rejected !== null && source.kind !== "inline-edit") {
10554
10993
  return { ok: false, reason: applied[0].rejected };
@@ -10730,26 +11169,19 @@ function recomputeHhCascadeForParent(startParentId, adapter) {
10730
11169
  if (!startParentId || startParentId === "0") return;
10731
11170
  let currentParentId = String(startParentId);
10732
11171
  while (currentParentId !== null) {
10733
- const childIds = adapter.getChildren(currentParentId);
10734
- let hhSum = 0;
10735
- let rwSum = 0;
10736
- for (const childId of childIds) {
10737
- const child = adapter.getActivity(childId);
10738
- if (!child) continue;
10739
- const hh = readNumber(child, ACTIVITY_PROPERTY.WORK_HOURS);
10740
- const rw = readChildRealWork(childId, child, hh, adapter);
10741
- hhSum += hh;
10742
- rwSum += rw;
10743
- }
11172
+ const { workHours, realWorkHours } = computeParentHhRollup(
11173
+ currentParentId,
11174
+ adapter
11175
+ );
10744
11176
  adapter.setActivityField(
10745
11177
  currentParentId,
10746
11178
  ACTIVITY_PROPERTY.WORK_HOURS,
10747
- hhSum
11179
+ workHours
10748
11180
  );
10749
11181
  adapter.setActivityField(
10750
11182
  currentParentId,
10751
11183
  ACTIVITY_PROPERTY.REAL_WORK_HOURS,
10752
- rwSum
11184
+ realWorkHours
10753
11185
  );
10754
11186
  const parentSnap = adapter.getActivity(currentParentId);
10755
11187
  if (!parentSnap) break;
@@ -10765,6 +11197,40 @@ function recomputeHhCascadeForActivity(activityId, adapter) {
10765
11197
  if (!parentId || isRootParent(parentId)) return;
10766
11198
  recomputeHhCascadeForParent(parentId, adapter);
10767
11199
  }
11200
+ function recomputeRealWorkFromProgress(progressIds, parentIdsDeepestFirst, adapter) {
11201
+ for (const activityId of progressIds) {
11202
+ if (adapter.hasChildren(activityId)) continue;
11203
+ const activity = adapter.getActivity(activityId);
11204
+ if (!activity) continue;
11205
+ adapter.setActivityField(
11206
+ activityId,
11207
+ ACTIVITY_PROPERTY.REAL_WORK_HOURS,
11208
+ computeLeafRealWork(
11209
+ readNumber(activity, ACTIVITY_PROPERTY.WORK_HOURS),
11210
+ readNumber(activity, ACTIVITY_PROPERTY.PROGRESS)
11211
+ )
11212
+ );
11213
+ }
11214
+ for (const parentId of parentIdsDeepestFirst) {
11215
+ adapter.setActivityField(
11216
+ parentId,
11217
+ ACTIVITY_PROPERTY.REAL_WORK_HOURS,
11218
+ computeParentHhRollup(parentId, adapter).realWorkHours
11219
+ );
11220
+ }
11221
+ }
11222
+ function computeParentHhRollup(parentId, adapter) {
11223
+ let workHours = 0;
11224
+ let realWorkHours = 0;
11225
+ for (const childId of adapter.getChildren(parentId)) {
11226
+ const child = adapter.getActivity(childId);
11227
+ if (!child) continue;
11228
+ const childWorkHours = readNumber(child, ACTIVITY_PROPERTY.WORK_HOURS);
11229
+ workHours += childWorkHours;
11230
+ realWorkHours += readChildRealWork(childId, child, childWorkHours, adapter);
11231
+ }
11232
+ return { workHours, realWorkHours };
11233
+ }
10768
11234
  function readNumber(activity, key) {
10769
11235
  const v = activity[key];
10770
11236
  if (typeof v === "number" && Number.isFinite(v)) return v;
@@ -11137,6 +11603,36 @@ function computeFractionalCidAtIndex(adapter, parentKey, targetIndex, excludeId)
11137
11603
  return (before + after) / 2;
11138
11604
  }
11139
11605
 
11606
+ // src/dispatch/creation-overrides.ts
11607
+ var PUBLIC_CREATION_OVERRIDE_FIELDS = /* @__PURE__ */ new Set([
11608
+ ...[...CREATION_OVERRIDE_FIELDS].filter((field) => field !== "durationHours"),
11609
+ "durationDays"
11610
+ ]);
11611
+ function normalizeActivityCreationOverrides(raw, hoursPerDay) {
11612
+ if (raw === void 0) return { ok: true, value: {} };
11613
+ const prototype = Object.getPrototypeOf(raw);
11614
+ if (prototype !== null && prototype !== Object.prototype) {
11615
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
11616
+ }
11617
+ for (const key of Reflect.ownKeys(raw)) {
11618
+ if (typeof key === "symbol" || !PUBLIC_CREATION_OVERRIDE_FIELDS.has(key)) {
11619
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
11620
+ }
11621
+ }
11622
+ const { durationDays, ...rest } = raw;
11623
+ if (durationDays !== void 0 && (!Number.isFinite(durationDays) || durationDays < 0)) {
11624
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
11625
+ }
11626
+ return {
11627
+ ok: true,
11628
+ value: {
11629
+ ...rest,
11630
+ ...durationDays === 0 && (rest.type === void 0 || rest.type === "task") ? { type: "milestone" } : {},
11631
+ ...durationDays === void 0 ? {} : { durationHours: durationToHours(durationDays, hoursPerDay) }
11632
+ }
11633
+ };
11634
+ }
11635
+
11140
11636
  // src/dispatch/handlers/activity-create.ts
11141
11637
  async function createActivityCore(action, deps, opts = {}) {
11142
11638
  const { adapter, scheduler, sector, calendars, activityIdGen, uidGen } = deps;
@@ -11144,7 +11640,12 @@ async function createActivityCore(action, deps, opts = {}) {
11144
11640
  const target = validateCreateTarget(action, adapter);
11145
11641
  if (!target.ok) return target;
11146
11642
  const { parent, anchorSiblingId, isRoot } = target;
11147
- const newId = resolveNewActivityId(action.activityId, adapter, activityIdGen);
11643
+ const newId = resolveNewActivityId(
11644
+ action.activityId,
11645
+ adapter,
11646
+ activityIdGen,
11647
+ deps.isActivityIdReserved
11648
+ );
11148
11649
  const newUid = uidGen.next();
11149
11650
  const activityInput = {
11150
11651
  id: newId,
@@ -11237,9 +11738,18 @@ function validateAnchorSibling(action, adapter, isRoot, anchorSiblingId) {
11237
11738
  }
11238
11739
  return null;
11239
11740
  }
11240
- function resolveNewActivityId(explicitId, adapter, activityIdGen) {
11241
- if (explicitId === void 0) return activityIdGen.next();
11242
- if (adapter.activityExists(explicitId)) return activityIdGen.next();
11741
+ function resolveNewActivityId(explicitId, adapter, activityIdGen, isActivityIdReserved) {
11742
+ const nextAvailableId = () => {
11743
+ let candidate = activityIdGen.next();
11744
+ while (adapter.activityExists(candidate) || isActivityIdReserved(String(candidate))) {
11745
+ candidate = activityIdGen.next();
11746
+ }
11747
+ return candidate;
11748
+ };
11749
+ if (explicitId === void 0) return nextAvailableId();
11750
+ if (adapter.activityExists(explicitId) || isActivityIdReserved(String(explicitId))) {
11751
+ return nextAvailableId();
11752
+ }
11243
11753
  activityIdGen.bump(explicitId);
11244
11754
  return explicitId;
11245
11755
  }
@@ -11318,7 +11828,15 @@ async function buildCreateChangeSet(adapter, action, coreResult, scheduledIds, h
11318
11828
  }
11319
11829
  async function dispatchActivityCreate(action, options, deps) {
11320
11830
  const { adapter, scheduler, sector } = deps;
11321
- const core = await createActivityCore(action, deps);
11831
+ const normalized2 = normalizeActivityCreationOverrides(
11832
+ action.overrides,
11833
+ sector.hoursPerDay
11834
+ );
11835
+ if (!normalized2.ok) return normalized2;
11836
+ const core = await createActivityCore(
11837
+ { ...action, overrides: normalized2.value },
11838
+ deps
11839
+ );
11322
11840
  if (!core.ok) return core;
11323
11841
  const { newId } = core;
11324
11842
  const createdAutoScheduling = core.newActivity.autoScheduling;
@@ -11530,7 +12048,7 @@ async function dispatchActivityDelete(action, options, deps) {
11530
12048
  for (const linkId of incidentLinkIds) {
11531
12049
  adapter.removeLink(linkId);
11532
12050
  }
11533
- const viewStateChanges = captureDeletedViewState(adapter, toDelete);
12051
+ const viewStateChanges2 = captureDeletedViewState(adapter, toDelete);
11534
12052
  let removed = 0;
11535
12053
  for (const id of toDelete) {
11536
12054
  removed += 1;
@@ -11612,7 +12130,6 @@ async function dispatchActivityDelete(action, options, deps) {
11612
12130
  touchedIds,
11613
12131
  scheduledIds,
11614
12132
  sirDetection: "after-diff",
11615
- preview: options.preview,
11616
12133
  links: buildLinkDeletions(beforeLinks),
11617
12134
  trackingEvents: [trackingEvent],
11618
12135
  hoursPerDay: sector.hoursPerDay,
@@ -11620,7 +12137,7 @@ async function dispatchActivityDelete(action, options, deps) {
11620
12137
  });
11621
12138
  return {
11622
12139
  ok: true,
11623
- changes: viewStateChanges.length > 0 ? { ...changes, viewState: viewStateChanges } : changes,
12140
+ changes: viewStateChanges2.length > 0 ? { ...changes, viewState: viewStateChanges2 } : changes,
11624
12141
  __beforeSnap: deletedRowsSnap,
11625
12142
  __beforeLinks: beforeLinksByStringId
11626
12143
  };
@@ -11728,7 +12245,7 @@ async function dispatchActivityBatch(action, options, deps) {
11728
12245
  source: link.source,
11729
12246
  target: link.target,
11730
12247
  type: link.type,
11731
- lag: options.inputUnit === "hours" ? link.lag : lagDaysToHours(link.lag, deps.sector.hoursPerDay)
12248
+ lag: lagDaysToHours(link.lag, deps.sector.hoursPerDay)
11732
12249
  },
11733
12250
  linkId: link.linkId
11734
12251
  }));
@@ -11773,7 +12290,6 @@ async function dispatchActivityBatch(action, options, deps) {
11773
12290
  links: buildLinkChangesForBatch(appliedLinks, beforeLinks, deps.adapter),
11774
12291
  hoursPerDay: deps.sector.hoursPerDay,
11775
12292
  correlativeShifts: existingCorrelativeShifts,
11776
- preview: options.preview,
11777
12293
  ...action.mode === "replace" ? { sirDetection: "after-diff" } : {}
11778
12294
  });
11779
12295
  return {
@@ -11801,7 +12317,7 @@ function validateBatch(action, deps) {
11801
12317
  }
11802
12318
  if (item.activityId !== void 0) {
11803
12319
  const activityId = String(item.activityId);
11804
- if (activityId.length === 0 || activityId === "0" || explicitActivityIds.has(activityId) || action.mode === "append" && deps.adapter.getActivity(activityId) !== null) {
12320
+ if (activityId.length === 0 || activityId === "0" || explicitActivityIds.has(activityId)) {
11805
12321
  return REJECTION_REASON.INVALID;
11806
12322
  }
11807
12323
  explicitActivityIds.add(activityId);
@@ -11823,7 +12339,7 @@ function validateBatch(action, deps) {
11823
12339
  }
11824
12340
  if (link.linkId !== void 0) {
11825
12341
  const linkId = String(link.linkId);
11826
- if (linkId.length === 0 || explicitLinkIds.has(linkId) || action.mode === "append" && deps.adapter.getLink(linkId) !== null) {
12342
+ if (linkId.length === 0 || explicitLinkIds.has(linkId)) {
11827
12343
  return REJECTION_REASON.INVALID;
11828
12344
  }
11829
12345
  explicitLinkIds.add(linkId);
@@ -11859,24 +12375,38 @@ function hasParentCycle(items) {
11859
12375
  return false;
11860
12376
  }
11861
12377
  function buildBatchPlan(action, deps) {
12378
+ const normalizedOverrides = /* @__PURE__ */ new Map();
11862
12379
  for (const item of action.items) {
11863
- if (item.activityId !== void 0) {
11864
- deps.activityIdGen.bump(item.activityId);
11865
- }
12380
+ const normalized2 = normalizeActivityCreationOverrides(
12381
+ item.overrides,
12382
+ deps.sector.hoursPerDay
12383
+ );
12384
+ if (!normalized2.ok) return normalized2;
12385
+ normalizedOverrides.set(item.batchId, normalized2.value);
11866
12386
  }
11867
12387
  const activityIdsByBatchId = /* @__PURE__ */ new Map();
12388
+ const plannedActivityIds = /* @__PURE__ */ new Set();
11868
12389
  for (const item of action.items) {
11869
- activityIdsByBatchId.set(
11870
- item.batchId,
11871
- item.activityId ?? deps.activityIdGen.next()
12390
+ const activityId = resolvePlannedActivityId(
12391
+ item.activityId,
12392
+ action.mode,
12393
+ plannedActivityIds,
12394
+ deps
11872
12395
  );
11873
- }
11874
- for (const link of action.links) {
11875
- if (link.linkId !== void 0) deps.linkIdGen.bump(link.linkId);
11876
- }
11877
- const linkIds = action.links.map(
11878
- (link) => link.linkId ?? deps.linkIdGen.next()
11879
- );
12396
+ plannedActivityIds.add(String(activityId));
12397
+ activityIdsByBatchId.set(item.batchId, activityId);
12398
+ }
12399
+ const plannedLinkIds = /* @__PURE__ */ new Set();
12400
+ const linkIds = action.links.map((link) => {
12401
+ const linkId = resolvePlannedLinkId(
12402
+ link.linkId,
12403
+ action.mode,
12404
+ plannedLinkIds,
12405
+ deps
12406
+ );
12407
+ plannedLinkIds.add(String(linkId));
12408
+ return linkId;
12409
+ });
11880
12410
  const insertionCorrelativeIds = planInsertionCorrelatives(
11881
12411
  action,
11882
12412
  activityIdsByBatchId,
@@ -11899,7 +12429,8 @@ function buildBatchPlan(action, deps) {
11899
12429
  activityId,
11900
12430
  parentId,
11901
12431
  parentBatchId,
11902
- insertionCorrelativeId
12432
+ insertionCorrelativeId,
12433
+ overrides: normalizedOverrides.get(item.batchId) ?? {}
11903
12434
  });
11904
12435
  }
11905
12436
  const links = [];
@@ -11920,6 +12451,30 @@ function buildBatchPlan(action, deps) {
11920
12451
  }
11921
12452
  return { ok: true, plan: { items, links } };
11922
12453
  }
12454
+ function resolvePlannedActivityId(explicitId, mode, plannedIds, deps) {
12455
+ const explicitIsAvailable = explicitId !== void 0 && (mode === "replace" || deps.adapter.getActivity(explicitId) === null && !deps.isActivityIdReserved(String(explicitId))) && !plannedIds.has(String(explicitId));
12456
+ if (explicitIsAvailable) {
12457
+ deps.activityIdGen.bump(explicitId);
12458
+ return explicitId;
12459
+ }
12460
+ let candidate = deps.activityIdGen.next();
12461
+ while (plannedIds.has(String(candidate)) || mode === "append" && (deps.adapter.getActivity(candidate) !== null || deps.isActivityIdReserved(String(candidate)))) {
12462
+ candidate = deps.activityIdGen.next();
12463
+ }
12464
+ return candidate;
12465
+ }
12466
+ function resolvePlannedLinkId(explicitId, mode, plannedIds, deps) {
12467
+ const explicitIsAvailable = explicitId !== void 0 && (mode === "replace" || deps.adapter.getLink(explicitId) === null && !deps.isLinkIdReserved(String(explicitId))) && !plannedIds.has(String(explicitId));
12468
+ if (explicitIsAvailable) {
12469
+ deps.linkIdGen.bump(explicitId);
12470
+ return explicitId;
12471
+ }
12472
+ let candidate = deps.linkIdGen.next();
12473
+ while (plannedIds.has(String(candidate)) || mode === "append" && (deps.adapter.getLink(candidate) !== null || deps.isLinkIdReserved(String(candidate)))) {
12474
+ candidate = deps.linkIdGen.next();
12475
+ }
12476
+ return candidate;
12477
+ }
11923
12478
  function planInsertionCorrelatives(action, activityIdsByBatchId, deps) {
11924
12479
  if (isSimpleRootAppend(action)) {
11925
12480
  const firstCorrelativeId = deps.adapter.activityCount();
@@ -12029,7 +12584,7 @@ async function createPlannedActivities(action, plannedItems, deps) {
12029
12584
  {
12030
12585
  parentId: item.parentId,
12031
12586
  activityId: item.activityId,
12032
- ...item.input.overrides === void 0 ? {} : { overrides: item.input.overrides },
12587
+ overrides: item.overrides,
12033
12588
  ...action.eventSource === void 0 ? {} : { eventSource: action.eventSource }
12034
12589
  },
12035
12590
  deps,
@@ -12106,301 +12661,280 @@ function dispatchSirSync(action, deps) {
12106
12661
  };
12107
12662
  }
12108
12663
 
12109
- // src/dispatch/handlers/bulk-edit.ts
12110
- var CUSTOM_ID_COLUMN = COLUMN.CUSTOM_ID;
12111
- async function dispatchBulkEdit(action, options, deps) {
12112
- const { adapter, scheduler, sector } = deps;
12113
- const editsIncludeDuration = action.edits.some(
12114
- (edit) => edit.column === COLUMN.DURATION
12664
+ // src/propagations/upward/progress-rollup.ts
12665
+ function recomputeAllProgressRollup(adapter) {
12666
+ for (const parentId of parentsDeepestFirst(adapter)) {
12667
+ rollupParent(parentId, adapter);
12668
+ }
12669
+ }
12670
+ function recomputeProgressRollupForParents(parentIdsDeepestFirst, adapter) {
12671
+ for (const parentId of parentIdsDeepestFirst) {
12672
+ rollupParent(parentId, adapter);
12673
+ }
12674
+ }
12675
+ function parentsDeepestFirst(adapter) {
12676
+ const parents = /* @__PURE__ */ new Set();
12677
+ adapter.forEachActivity((_activity, id) => {
12678
+ if (adapter.getChildren(id).length > 0) parents.add(id);
12679
+ });
12680
+ const depthById = /* @__PURE__ */ new Map();
12681
+ for (const id of parents) depthById.set(id, depthOf2(id, adapter));
12682
+ return [...parents].sort(
12683
+ (a, b) => (depthById.get(b) ?? 0) - (depthById.get(a) ?? 0)
12115
12684
  );
12116
- if (editsIncludeDuration) {
12117
- scheduler.invalidateExpandedLinksCache();
12685
+ }
12686
+ function depthOf2(id, adapter) {
12687
+ let depth = 0;
12688
+ let current = id;
12689
+ while (current && current !== "0") {
12690
+ if (!adapter.getActivity(current)) break;
12691
+ depth += 1;
12692
+ current = adapter.getParentId(current);
12118
12693
  }
12119
- const verdicts = [];
12120
- const beforeSnap = /* @__PURE__ */ new Map();
12121
- const constraintPriorsByActivity = /* @__PURE__ */ new Map();
12122
- const explicitDateEditActivities = /* @__PURE__ */ new Set();
12123
- const touchedIds = /* @__PURE__ */ new Set();
12124
- const trackingEvents = [];
12125
- const linkChanges = [];
12126
- const warnings = [];
12127
- let needsAutoSchedule = false;
12128
- for (const edit of action.edits) {
12129
- const outcome = applyOneEdit(edit, deps);
12130
- verdicts.push(outcome.verdict);
12131
- if (!outcome.verdict.ok || !outcome.changes) continue;
12132
- const constraintWarning = toConstraintWarning(
12133
- edit.activityId,
12134
- outcome.changes.constraintWarning
12694
+ return depth;
12695
+ }
12696
+ function rollupParent(parentId, adapter) {
12697
+ const childIds = adapter.getChildren(parentId);
12698
+ if (childIds.length === 0) return;
12699
+ const children = [];
12700
+ for (const childId of childIds) {
12701
+ const child = adapter.getActivity(childId);
12702
+ if (!child) continue;
12703
+ children.push({
12704
+ progress: Number(child.progress ?? 0),
12705
+ ponderator: Number(child.ponderator ?? 0)
12706
+ });
12707
+ }
12708
+ const rollup = computeWeightedProgressRollup(children);
12709
+ if (rollup !== null) {
12710
+ adapter.setActivityField(
12711
+ parentId,
12712
+ COLUMN.PROGRESS,
12713
+ roundProgressPerLevel(rollup)
12135
12714
  );
12136
- if (constraintWarning) warnings.push(constraintWarning);
12137
- const editTouched = collectTouchedIds(edit.activityId, outcome.changes);
12138
- mergeBeforeSnapshots(beforeSnap, editTouched, deps);
12139
- for (const touchedId of editTouched) touchedIds.add(touchedId);
12140
- recordConstraintPrior(constraintPriorsByActivity, edit, beforeSnap);
12141
- if (isExplicitConstraintDateEdit(edit.column)) {
12142
- explicitDateEditActivities.add(String(edit.activityId));
12715
+ }
12716
+ }
12717
+
12718
+ // src/dispatch/handlers/persistence-acknowledge.ts
12719
+ function persistedOwnersOf(entities) {
12720
+ const owners = /* @__PURE__ */ new Map();
12721
+ for (const entity of entities) {
12722
+ const proplannerId = entity.proplannerId;
12723
+ if (typeof proplannerId === "number" && Number.isSafeInteger(proplannerId) && proplannerId > 0) {
12724
+ owners.set(proplannerId, String(entity.id));
12143
12725
  }
12144
- const beforeLinkLags = snapshotIncomingLinkLags(adapter, edit.activityId);
12145
- const preEditSnapshot = snapshotSingleActivity(
12146
- adapter,
12147
- String(edit.activityId)
12726
+ }
12727
+ return owners;
12728
+ }
12729
+ function assertAllNewEntitiesAcknowledged(entities, assignments, entityName) {
12730
+ const acknowledgedIds = new Set(assignments.map(({ id }) => String(id)));
12731
+ for (const entity of entities) {
12732
+ if ((entity.proplannerId == null || entity.proplannerId === 0) && !acknowledgedIds.has(String(entity.id))) {
12733
+ throw new Error(
12734
+ `persistence-acknowledge: missing ${entityName} proplannerId for "${String(entity.id)}"`
12735
+ );
12736
+ }
12737
+ }
12738
+ }
12739
+ function assertValidAssignment(assignment, entityName, readCurrent, ownerByBackendId, seenIds, seenBackendIds) {
12740
+ const id = String(assignment.id);
12741
+ if (id.length === 0 || seenIds.has(id)) {
12742
+ throw new Error(
12743
+ `persistence-acknowledge: duplicate or empty ${entityName} id "${id}"`
12148
12744
  );
12149
- const customIdBeforeApply = readLiveCustomId(edit, deps);
12150
- const changes = outcome.changes;
12151
- applyFieldChanges(adapter, edit.activityId, changes);
12152
- runPostProcessorsOnAdapter(
12153
- edit.activityId,
12154
- outcome.changes.postProcessors ?? [],
12155
- adapter,
12156
- preEditSnapshot
12745
+ }
12746
+ if (!Number.isSafeInteger(assignment.proplannerId) || assignment.proplannerId <= 0) {
12747
+ throw new Error(
12748
+ `persistence-acknowledge: invalid ${entityName} proplannerId ${String(assignment.proplannerId)}`
12157
12749
  );
12158
- syncCustomIdTracker(edit, customIdBeforeApply, deps);
12159
- linkChanges.push(
12160
- ...diffIncomingLinkLagChanges(beforeLinkLags, adapter, edit.activityId)
12750
+ }
12751
+ if (seenBackendIds.has(assignment.proplannerId)) {
12752
+ throw new Error(
12753
+ `persistence-acknowledge: duplicate ${entityName} proplannerId ${String(assignment.proplannerId)}`
12161
12754
  );
12162
- needsAutoSchedule ||= Boolean(outcome.changes.needsAutoSchedule);
12163
- for (const trackingEvent of outcome.changes.trackingEvents ?? []) {
12164
- trackingEvents.push({
12165
- name: trackingEvent.name,
12166
- properties: trackingEvent.properties ?? {}
12167
- });
12168
- }
12169
12755
  }
12170
- const postMutationArgs = {
12171
- action,
12172
- autoscheduleFrom: needsAutoSchedule ? "roots" : null,
12173
- recomputeParentsFrom: touchedIds,
12174
- now: deps.now,
12175
- options
12176
- };
12177
- const batchHasConstraintEdits = constraintPriorsByActivity.size > 0;
12178
- const { scheduledIds } = await (batchHasConstraintEdits ? settleConstraintEdits(
12179
- {
12180
- adapter,
12181
- scheduler,
12182
- sector,
12183
- defaultBaseCalendarId: deps.defaultBaseCalendarId
12184
- },
12185
- {
12186
- postMutationArgs,
12187
- revertTargets: [...constraintPriorsByActivity].filter(
12188
- ([activityId]) => !explicitDateEditActivities.has(activityId)
12189
- ).map(([activityId, priorConstraintDate]) => ({
12190
- activityId,
12191
- priorConstraintDate
12192
- })),
12193
- revert: (activityId, priorConstraintDate) => revertNoOpConstraintEdit(
12194
- activityId,
12195
- priorConstraintDate,
12196
- adapter,
12197
- deps.ctx.calendars
12198
- )
12199
- }
12200
- ) : runPostMutation(
12201
- {
12202
- adapter,
12203
- scheduler,
12204
- sector,
12205
- defaultBaseCalendarId: deps.defaultBaseCalendarId
12206
- },
12207
- postMutationArgs
12208
- ));
12209
- return {
12210
- ok: true,
12211
- verdicts,
12212
- changes: await assembleChangeSet(adapter, {
12213
- source: action,
12214
- beforeSnap,
12215
- touchedIds,
12216
- scheduledIds,
12217
- sirDetection: "before-diff",
12218
- preview: options.preview,
12219
- links: linkChanges,
12220
- trackingEvents,
12221
- warnings,
12222
- hoursPerDay: sector.hoursPerDay
12223
- })
12224
- };
12225
- }
12226
- function applyOneEdit(edit, deps) {
12227
- const { ctx } = deps;
12228
- const activityId = edit.activityId;
12229
- const pipeline = findPipelineForColumn(edit.column);
12230
- if (!pipeline) {
12231
- return {
12232
- verdict: {
12233
- activityId,
12234
- ok: false,
12235
- reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN
12236
- },
12237
- changes: null
12238
- };
12239
- }
12240
- const activitySnap = ctx.activityReader.getActivity(edit.activityId);
12241
- if (!activitySnap) {
12242
- return {
12243
- verdict: {
12244
- activityId,
12245
- ok: false,
12246
- reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
12247
- },
12248
- changes: null
12249
- };
12250
- }
12251
- const editGate = pipeline.canEdit(activitySnap, ctx.hierarchy);
12252
- if (!editGate.allowed) {
12253
- return {
12254
- verdict: {
12255
- activityId,
12256
- ok: false,
12257
- reason: editGate.reason ?? REJECTION_REASON.CANNOT_EDIT,
12258
- ...editGate.alertKey ? { alertKey: editGate.alertKey } : {}
12259
- },
12260
- changes: null
12261
- };
12262
- }
12263
- const parsed2 = pipeline.parseInput(edit.newValue, activitySnap, ctx);
12264
- if (!parsed2.ok) {
12265
- return {
12266
- verdict: {
12267
- activityId,
12268
- ok: false,
12269
- reason: parsed2.reason ?? REJECTION_REASON.PARSE_ERROR
12270
- },
12271
- changes: null
12272
- };
12756
+ const state = readCurrent(id);
12757
+ if (!state.exists) {
12758
+ throw new Error(
12759
+ `persistence-acknowledge: unknown ${entityName} id "${id}"`
12760
+ );
12273
12761
  }
12274
- const oldValue = Reflect.get(activitySnap, pipeline.targetField);
12275
- const validation = pipeline.validate(
12276
- activitySnap,
12277
- oldValue,
12278
- parsed2.value,
12279
- ctx
12280
- );
12281
- if (!validation.valid) {
12282
- return {
12283
- verdict: {
12284
- activityId,
12285
- ok: false,
12286
- reason: validation.reason ?? REJECTION_REASON.INVALID,
12287
- ...validation.notification?.messageKey ? { alertKey: validation.notification.messageKey } : {}
12288
- },
12289
- changes: null
12290
- };
12762
+ if (state.current !== null && state.current !== 0 && state.current !== assignment.proplannerId) {
12763
+ throw new Error(
12764
+ `persistence-acknowledge: ${entityName} "${id}" already has proplannerId ${String(state.current)}`
12765
+ );
12291
12766
  }
12292
- if (edit.column === CUSTOM_ID_COLUMN && parsed2.value !== null) {
12293
- const liveActivity = deps.adapter.getActivity(edit.activityId);
12294
- const oldCustomIdRaw = liveActivity ? liveActivity.customId : null;
12295
- const oldCustomId = typeof oldCustomIdRaw === "string" ? oldCustomIdRaw : null;
12296
- if (deps.customIdTracker.isCustomIdInUse(String(parsed2.value), oldCustomId)) {
12297
- return {
12298
- verdict: {
12299
- activityId,
12300
- ok: false,
12301
- reason: REJECTION_REASON.CUSTOM_ID_DUPLICATE
12302
- },
12303
- changes: null
12304
- };
12305
- }
12767
+ const existingOwner = ownerByBackendId.get(assignment.proplannerId);
12768
+ if (existingOwner !== void 0 && existingOwner !== id) {
12769
+ throw new Error(
12770
+ `persistence-acknowledge: ${entityName} proplannerId ${String(assignment.proplannerId)} already belongs to "${existingOwner}"`
12771
+ );
12306
12772
  }
12307
- const changes = pipeline.transform(
12308
- activitySnap,
12309
- parsed2.value,
12310
- ctx,
12311
- parsed2.raw
12312
- );
12313
- return { verdict: { activityId, ok: true }, changes };
12314
- }
12315
- function readLiveCustomId(edit, deps) {
12316
- if (edit.column !== CUSTOM_ID_COLUMN) return null;
12317
- const liveActivity = deps.adapter.getActivity(edit.activityId);
12318
- const rawValue = liveActivity ? liveActivity.customId : null;
12319
- return typeof rawValue === "string" ? rawValue : null;
12773
+ seenIds.add(id);
12774
+ seenBackendIds.add(assignment.proplannerId);
12320
12775
  }
12321
- function syncCustomIdTracker(edit, oldCustomId, deps) {
12322
- if (edit.column !== CUSTOM_ID_COLUMN) return;
12323
- const liveActivity = deps.adapter.getActivity(edit.activityId);
12324
- const rawValue = liveActivity ? liveActivity.customId : null;
12325
- const newCustomId = typeof rawValue === "string" ? rawValue : null;
12326
- if (oldCustomId !== newCustomId) {
12327
- deps.customIdTracker.trackCustomIdChange(oldCustomId, newCustomId);
12776
+ function assertValidAssignments(assignments, entityName, readCurrent, ownerByBackendId) {
12777
+ const seenIds = /* @__PURE__ */ new Set();
12778
+ const seenBackendIds = /* @__PURE__ */ new Set();
12779
+ for (const assignment of assignments) {
12780
+ assertValidAssignment(
12781
+ assignment,
12782
+ entityName,
12783
+ readCurrent,
12784
+ ownerByBackendId,
12785
+ seenIds,
12786
+ seenBackendIds
12787
+ );
12328
12788
  }
12329
12789
  }
12330
- function recordConstraintPrior(priors, edit, beforeSnap) {
12331
- if (!isConstraintEditColumn(edit.column)) return;
12332
- const activityKey = String(edit.activityId);
12333
- if (priors.has(activityKey)) return;
12334
- const preBatchSnapshot = beforeSnap.get(activityKey);
12335
- if (!preBatchSnapshot) return;
12336
- priors.set(activityKey, preBatchSnapshot.constraintDate ?? null);
12337
- }
12338
- function mergeBeforeSnapshots(beforeSnap, ids, deps) {
12339
- const missing = /* @__PURE__ */ new Set();
12340
- for (const candidateId of ids) {
12341
- if (!beforeSnap.has(candidateId)) missing.add(candidateId);
12342
- }
12343
- if (missing.size === 0) return;
12344
- for (const missingId of missing) {
12345
- const liveActivity = deps.adapter.getActivity(missingId);
12346
- if (liveActivity) {
12347
- beforeSnap.set(missingId, structuredCloneActivity(liveActivity));
12790
+ function assertValidReconciledProgress(entries, deps) {
12791
+ const seen = /* @__PURE__ */ new Set();
12792
+ for (const entry of entries) {
12793
+ const id = String(entry.activityId);
12794
+ if (id.length === 0 || seen.has(id)) {
12795
+ throw new Error(
12796
+ `persistence-acknowledge: duplicate or empty reconciled activity id "${id}"`
12797
+ );
12798
+ }
12799
+ if (!Number.isFinite(entry.progress) || entry.progress < 0 || entry.progress > 100) {
12800
+ throw new Error(
12801
+ `persistence-acknowledge: invalid reconciled progress ${String(entry.progress)} for "${id}"`
12802
+ );
12803
+ }
12804
+ if (!deps.adapter.getActivity(id)) {
12805
+ throw new Error(
12806
+ `persistence-acknowledge: unknown reconciled activity id "${id}"`
12807
+ );
12348
12808
  }
12809
+ seen.add(id);
12349
12810
  }
12350
12811
  }
12351
-
12352
- // src/dispatch/handlers/sir-approve.ts
12353
- async function dispatchSirApprove(action, options, deps) {
12354
- const { adapter } = deps;
12355
- const snapshot = adapter.getActivity(action.activityId);
12356
- if (!snapshot) {
12357
- return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
12358
- }
12359
- const pending = snapshot.pendingRequestIds ?? [];
12360
- if (!pending.includes(action.approvedRequestId)) {
12361
- return { ok: false, reason: REJECTION_REASON.SIR_REQUEST_NOT_PENDING };
12812
+ async function applyReconciledProgress(entries, deps) {
12813
+ const progressToApply = entries.filter((entry) => {
12814
+ const current = deps.adapter.getActivity(String(entry.activityId));
12815
+ return current !== null && current.progress !== entry.progress;
12816
+ });
12817
+ const progressIds = new Set(
12818
+ progressToApply.map((entry) => String(entry.activityId))
12819
+ );
12820
+ const ancestors = findAncestorsOfDirty(deps.adapter, progressIds);
12821
+ const touched = new Set(progressIds);
12822
+ for (const parentId of ancestors) {
12823
+ touched.add(String(parentId));
12362
12824
  }
12363
- const remaining = pending.filter((id) => id !== action.approvedRequestId);
12364
- adapter.setActivityField(action.activityId, "pendingRequestIds", remaining);
12365
- const result = await dispatchBulkEdit(
12366
- {
12367
- kind: "bulk-edit",
12368
- edits: [
12369
- {
12370
- activityId: action.activityId,
12371
- column: COLUMN.START_DATE,
12372
- newValue: action.startDate
12373
- },
12374
- {
12375
- activityId: action.activityId,
12376
- column: COLUMN.END_DATE,
12377
- newValue: action.endDate
12378
- },
12379
- // asap ÚLTIMO: gana al SNET implícito (paridad legacy).
12380
- {
12381
- activityId: action.activityId,
12382
- column: COLUMN.CONSTRAINT_TYPE,
12383
- newValue: "asap"
12384
- }
12385
- ],
12386
- eventSource: action.eventSource
12825
+ const before = await snapshotActivities(deps.adapter, touched);
12826
+ deps.adapter.batchUpdate(() => {
12827
+ for (const entry of progressToApply) {
12828
+ deps.adapter.setActivityField(
12829
+ String(entry.activityId),
12830
+ "progress",
12831
+ entry.progress
12832
+ );
12833
+ }
12834
+ recomputeProgressRollupForParents(ancestors, deps.adapter);
12835
+ recomputeRealWorkFromProgress(progressIds, ancestors, deps.adapter);
12836
+ });
12837
+ return [...await buildActivityChanges(deps.adapter, before, touched)];
12838
+ }
12839
+ async function dispatchPersistenceAcknowledge(action, deps) {
12840
+ const activities = action.activities ?? [];
12841
+ const links = action.links ?? [];
12842
+ const reconciledProgress = action.reconciledProgress ?? [];
12843
+ assertValidReconciledProgress(reconciledProgress, deps);
12844
+ const currentActivities = deps.adapter.getAllActivities();
12845
+ const currentLinks = deps.adapter.getAllLinks();
12846
+ const activityOwnerByBackendId = persistedOwnersOf(currentActivities);
12847
+ const linkOwnerByBackendId = persistedOwnersOf(currentLinks);
12848
+ assertValidAssignments(
12849
+ activities,
12850
+ "activity",
12851
+ (id) => {
12852
+ const activity = deps.adapter.getActivity(id);
12853
+ return {
12854
+ exists: activity !== null,
12855
+ current: activity?.proplannerId ?? null
12856
+ };
12387
12857
  },
12388
- options,
12858
+ activityOwnerByBackendId
12859
+ );
12860
+ assertValidAssignments(
12861
+ links,
12862
+ "link",
12863
+ (id) => {
12864
+ const link = deps.adapter.getLink(id);
12865
+ return { exists: link !== null, current: link?.proplannerId ?? null };
12866
+ },
12867
+ linkOwnerByBackendId
12868
+ );
12869
+ assertAllNewEntitiesAcknowledged(currentActivities, activities, "activity");
12870
+ assertAllNewEntitiesAcknowledged(currentLinks, links, "link");
12871
+ const activityChanges = await applyReconciledProgress(
12872
+ reconciledProgress,
12389
12873
  deps
12390
12874
  );
12391
- if (!result.ok) return result;
12392
- const hardFailure = "verdicts" in result ? result.verdicts.find(
12393
- (verdict) => !verdict.ok && verdict.reason !== VALIDATION_REASON.UNCHANGED
12394
- ) : void 0;
12395
- if (hardFailure) {
12396
- return { ok: false, reason: hardFailure.reason };
12397
- }
12398
- return { ...result, changes: { ...result.changes, source: action } };
12875
+ const linkChanges = [];
12876
+ deps.adapter.batchUpdate(() => {
12877
+ for (const assignment of activities) {
12878
+ const before = deps.adapter.getActivity(assignment.id);
12879
+ if (!before || before.proplannerId === assignment.proplannerId) continue;
12880
+ const previousProplannerId = before.proplannerId;
12881
+ deps.adapter.setActivityField(
12882
+ assignment.id,
12883
+ "proplannerId",
12884
+ assignment.proplannerId
12885
+ );
12886
+ activityChanges.push({
12887
+ id: assignment.id,
12888
+ kind: "updated",
12889
+ fields: {
12890
+ proplannerId: {
12891
+ before: previousProplannerId,
12892
+ after: assignment.proplannerId
12893
+ }
12894
+ },
12895
+ after: { ...before, proplannerId: assignment.proplannerId }
12896
+ });
12897
+ }
12898
+ for (const assignment of links) {
12899
+ const before = deps.adapter.getLink(assignment.id);
12900
+ if (!before || before.proplannerId === assignment.proplannerId) continue;
12901
+ const previousProplannerId = before.proplannerId;
12902
+ deps.adapter.setLinkProplannerId(assignment.id, assignment.proplannerId);
12903
+ linkChanges.push({
12904
+ id: assignment.id,
12905
+ kind: "updated",
12906
+ fields: {
12907
+ proplannerId: {
12908
+ before: previousProplannerId,
12909
+ after: assignment.proplannerId
12910
+ }
12911
+ },
12912
+ after: { ...before, proplannerId: assignment.proplannerId }
12913
+ });
12914
+ }
12915
+ });
12916
+ return {
12917
+ ok: true,
12918
+ changes: {
12919
+ source: action,
12920
+ activities: activityChanges,
12921
+ links: linkChanges,
12922
+ calendars: [],
12923
+ trackingEvents: []
12924
+ }
12925
+ };
12399
12926
  }
12400
12927
 
12401
12928
  // src/dispatch/handlers/activity-lookahead-sync.ts
12402
- function dispatchActivityLookaheadSync(action, deps) {
12929
+ async function dispatchActivityLookaheadSync(action, options, deps) {
12930
+ if (hasInvalidPersistedProgress(action)) {
12931
+ return { ok: false, reason: REJECTION_REASON.INVALID_VALUE };
12932
+ }
12403
12933
  const ids = [...new Set(action.activityIds.map(String))];
12934
+ const acknowledgesUnknownPersistedActivity = action.acknowledgePersistedLookahead === true && ids.some((activityId) => !deps.isActivityIdReserved(activityId));
12935
+ if (acknowledgesUnknownPersistedActivity) {
12936
+ return { ok: false, reason: REJECTION_REASON.INVALID };
12937
+ }
12404
12938
  const snapshots = ids.map((id) => deps.adapter.getActivity(id));
12405
12939
  if (snapshots.some((snapshot) => !snapshot)) {
12406
12940
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
@@ -12440,6 +12974,39 @@ function dispatchActivityLookaheadSync(action, deps) {
12440
12974
  }
12441
12975
  });
12442
12976
  }
12977
+ if (action.persistedProgress !== void 0) {
12978
+ const progressChanges = await applyReconciledProgress(
12979
+ [{ activityId: ids[0], progress: action.persistedProgress }],
12980
+ deps
12981
+ );
12982
+ if (progressChanges.length > 0) {
12983
+ await runPostMutation(
12984
+ {
12985
+ adapter: deps.adapter,
12986
+ scheduler: deps.scheduler,
12987
+ sector: deps.sector,
12988
+ defaultBaseCalendarId: deps.defaultBaseCalendarId
12989
+ },
12990
+ {
12991
+ action,
12992
+ autoscheduleFrom: null,
12993
+ recomputeParentsFrom: [],
12994
+ now: deps.now,
12995
+ options
12996
+ }
12997
+ );
12998
+ return {
12999
+ ok: true,
13000
+ changes: await assembleChangeSet(deps.adapter, {
13001
+ source: action,
13002
+ beforeSnap: /* @__PURE__ */ new Map(),
13003
+ touchedIds: [],
13004
+ scheduledIds: [],
13005
+ hoursPerDay: deps.sector.hoursPerDay
13006
+ })
13007
+ };
13008
+ }
13009
+ }
12443
13010
  return {
12444
13011
  ok: true,
12445
13012
  changes: {
@@ -12451,6 +13018,10 @@ function dispatchActivityLookaheadSync(action, deps) {
12451
13018
  }
12452
13019
  };
12453
13020
  }
13021
+ function hasInvalidPersistedProgress(action) {
13022
+ const progress = action.persistedProgress;
13023
+ return progress !== void 0 && (action.activityIds.length !== 1 || !Number.isFinite(progress) || progress < 0 || progress > 100);
13024
+ }
12454
13025
 
12455
13026
  // src/internal/hierarchy/invalid-links.ts
12456
13027
  function collectInvalidLinkIdsForCycle(movedActivityIds, adapter) {
@@ -12718,7 +13289,6 @@ async function dispatchActivityMove(action, options, deps) {
12718
13289
  touchedIds: touched,
12719
13290
  scheduledIds,
12720
13291
  sirDetection: "before-diff",
12721
- preview: options.preview,
12722
13292
  trackingEvents: [trackingEvent],
12723
13293
  hoursPerDay: sector.hoursPerDay
12724
13294
  })
@@ -13216,44 +13786,205 @@ async function dispatchActivitySetProgress(action, options, deps) {
13216
13786
  changes.postProcessors ?? [],
13217
13787
  adapter
13218
13788
  );
13219
- const { scheduledIds } = await runPostMutation(
13220
- {
13221
- adapter,
13222
- scheduler,
13223
- sector: deps.sector,
13224
- defaultBaseCalendarId: deps.defaultBaseCalendarId
13225
- },
13226
- {
13227
- action,
13228
- autoscheduleFrom: changes.needsAutoSchedule ? action.activityId : null,
13229
- recomputeParentsFrom: [],
13230
- collectDirty: (sids) => collectDirtyForInlineEdit(action.activityId, touchedIds, sids),
13231
- now: deps.now,
13232
- options
13789
+ const { scheduledIds } = await runPostMutation(
13790
+ {
13791
+ adapter,
13792
+ scheduler,
13793
+ sector: deps.sector,
13794
+ defaultBaseCalendarId: deps.defaultBaseCalendarId
13795
+ },
13796
+ {
13797
+ action,
13798
+ autoscheduleFrom: changes.needsAutoSchedule ? action.activityId : null,
13799
+ recomputeParentsFrom: [],
13800
+ collectDirty: (sids) => collectDirtyForInlineEdit(action.activityId, touchedIds, sids),
13801
+ now: deps.now,
13802
+ options
13803
+ }
13804
+ );
13805
+ const trackingEvents = (changes.trackingEvents ?? []).map((e) => ({
13806
+ name: e.name,
13807
+ properties: e.properties ?? {}
13808
+ }));
13809
+ return {
13810
+ ok: true,
13811
+ changes: await assembleChangeSet(adapter, {
13812
+ source: action,
13813
+ beforeSnap,
13814
+ touchedIds,
13815
+ scheduledIds,
13816
+ trackingEvents,
13817
+ hoursPerDay: sector.hoursPerDay
13818
+ })
13819
+ };
13820
+ }
13821
+
13822
+ // src/dispatch/handlers/dates-batch.ts
13823
+ async function dispatchDatesBatch(action, options, deps) {
13824
+ const { adapter, scheduler, sector } = deps;
13825
+ const verdicts = [];
13826
+ const beforeSnap = /* @__PURE__ */ new Map();
13827
+ const touchedIds = /* @__PURE__ */ new Set();
13828
+ const trackingEvents = [];
13829
+ const linkChanges = [];
13830
+ const warnings = [];
13831
+ let needsAutoSchedule = false;
13832
+ for (const edit of action.edits) {
13833
+ const outcome = applyOneEdit(edit, deps);
13834
+ verdicts.push(outcome.verdict);
13835
+ if (!outcome.verdict.ok || !outcome.changes) continue;
13836
+ const editTouched = collectTouchedIds(edit.activityId, outcome.changes);
13837
+ mergeBeforeSnapshots(beforeSnap, editTouched, deps);
13838
+ for (const id of editTouched) touchedIds.add(id);
13839
+ const beforeLinkLags = snapshotIncomingLinkLags(adapter, edit.activityId);
13840
+ const preEditSnapshot = snapshotSingleActivity(
13841
+ adapter,
13842
+ String(edit.activityId)
13843
+ );
13844
+ applyFieldChanges(adapter, edit.activityId, outcome.changes);
13845
+ runPostProcessorsOnAdapter(
13846
+ edit.activityId,
13847
+ outcome.changes.postProcessors ?? [],
13848
+ adapter,
13849
+ preEditSnapshot
13850
+ );
13851
+ linkChanges.push(
13852
+ ...diffIncomingLinkLagChanges(beforeLinkLags, adapter, edit.activityId)
13853
+ );
13854
+ needsAutoSchedule ||= Boolean(outcome.changes.needsAutoSchedule);
13855
+ for (const e of outcome.changes.trackingEvents ?? []) {
13856
+ trackingEvents.push({
13857
+ name: e.name,
13858
+ properties: e.properties ?? {}
13859
+ });
13860
+ }
13861
+ const constraintWarning = toConstraintWarning(
13862
+ edit.activityId,
13863
+ outcome.changes.constraintWarning
13864
+ );
13865
+ if (constraintWarning) warnings.push(constraintWarning);
13866
+ }
13867
+ const { scheduledIds } = await runPostMutation(
13868
+ {
13869
+ adapter,
13870
+ scheduler,
13871
+ sector: deps.sector,
13872
+ defaultBaseCalendarId: deps.defaultBaseCalendarId
13873
+ },
13874
+ {
13875
+ action,
13876
+ autoscheduleFrom: needsAutoSchedule ? "roots" : null,
13877
+ recomputeParentsFrom: touchedIds,
13878
+ now: deps.now,
13879
+ options
13880
+ }
13881
+ );
13882
+ return {
13883
+ ok: true,
13884
+ verdicts,
13885
+ changes: await assembleChangeSet(adapter, {
13886
+ source: action,
13887
+ beforeSnap,
13888
+ touchedIds,
13889
+ scheduledIds,
13890
+ sirDetection: "before-diff",
13891
+ links: linkChanges,
13892
+ trackingEvents,
13893
+ warnings,
13894
+ hoursPerDay: sector.hoursPerDay
13895
+ })
13896
+ };
13897
+ }
13898
+ function applyOneEdit(edit, deps) {
13899
+ const { ctx } = deps;
13900
+ const activityId = edit.activityId;
13901
+ const pipeline = findPipelineForColumn(edit.column);
13902
+ if (!pipeline) {
13903
+ throw new Error(
13904
+ `[ScheduleCore] no pipeline registered for column "${edit.column}"`
13905
+ );
13906
+ }
13907
+ const activitySnap = ctx.activityReader.getActivity(edit.activityId);
13908
+ if (!activitySnap) {
13909
+ throw new Error(`[ScheduleCore] activity "${edit.activityId}" not found`);
13910
+ }
13911
+ const editGate = pipeline.canEdit(activitySnap, ctx.hierarchy);
13912
+ if (!editGate.allowed) {
13913
+ return {
13914
+ verdict: {
13915
+ activityId,
13916
+ ok: false,
13917
+ reason: editGate.reason ?? REJECTION_REASON.CANNOT_EDIT,
13918
+ ...editGate.alertKey ? { alertKey: editGate.alertKey } : {}
13919
+ },
13920
+ changes: null
13921
+ };
13922
+ }
13923
+ const parsed2 = pipeline.parseInput(edit.newValue, activitySnap, ctx);
13924
+ if (!parsed2.ok) {
13925
+ return {
13926
+ verdict: {
13927
+ activityId,
13928
+ ok: false,
13929
+ reason: parsed2.reason ?? REJECTION_REASON.PARSE_ERROR
13930
+ },
13931
+ changes: null
13932
+ };
13933
+ }
13934
+ const oldValue = Reflect.get(activitySnap, pipeline.targetField);
13935
+ const validation = pipeline.validate(
13936
+ activitySnap,
13937
+ oldValue,
13938
+ parsed2.value,
13939
+ ctx
13940
+ );
13941
+ if (!validation.valid) {
13942
+ return {
13943
+ verdict: {
13944
+ activityId,
13945
+ ok: false,
13946
+ reason: validation.reason ?? REJECTION_REASON.INVALID,
13947
+ ...validation.notification?.messageKey ? { alertKey: validation.notification.messageKey } : {}
13948
+ },
13949
+ changes: null
13950
+ };
13951
+ }
13952
+ const changes = pipeline.transform(
13953
+ activitySnap,
13954
+ parsed2.value,
13955
+ ctx,
13956
+ parsed2.raw
13957
+ );
13958
+ return { verdict: { activityId, ok: true }, changes };
13959
+ }
13960
+ function mergeBeforeSnapshots(beforeSnap, ids, deps) {
13961
+ const missing = /* @__PURE__ */ new Set();
13962
+ for (const id of ids) {
13963
+ if (!beforeSnap.has(id)) missing.add(id);
13964
+ }
13965
+ if (missing.size === 0) return;
13966
+ for (const missingId of missing) {
13967
+ const liveActivity = deps.adapter.getActivity(missingId);
13968
+ if (liveActivity) {
13969
+ beforeSnap.set(missingId, structuredCloneActivity(liveActivity));
13233
13970
  }
13234
- );
13235
- const trackingEvents = (changes.trackingEvents ?? []).map((e) => ({
13236
- name: e.name,
13237
- properties: e.properties ?? {}
13238
- }));
13239
- return {
13240
- ok: true,
13241
- changes: await assembleChangeSet(adapter, {
13242
- source: action,
13243
- beforeSnap,
13244
- touchedIds,
13245
- scheduledIds,
13246
- trackingEvents,
13247
- hoursPerDay: sector.hoursPerDay
13248
- })
13249
- };
13971
+ }
13250
13972
  }
13251
13973
 
13252
- // src/dispatch/handlers/dates-batch.ts
13253
- async function dispatchDatesBatch(action, options, deps) {
13974
+ // src/dispatch/handlers/bulk-edit.ts
13975
+ var CUSTOM_ID_COLUMN = COLUMN.CUSTOM_ID;
13976
+ async function dispatchBulkEdit(action, options, deps) {
13254
13977
  const { adapter, scheduler, sector } = deps;
13978
+ const editsIncludeDuration = action.edits.some(
13979
+ (edit) => edit.column === COLUMN.DURATION
13980
+ );
13981
+ if (editsIncludeDuration) {
13982
+ scheduler.invalidateExpandedLinksCache();
13983
+ }
13255
13984
  const verdicts = [];
13256
13985
  const beforeSnap = /* @__PURE__ */ new Map();
13986
+ const constraintPriorsByActivity = /* @__PURE__ */ new Map();
13987
+ const explicitDateEditActivities = /* @__PURE__ */ new Set();
13257
13988
  const touchedIds = /* @__PURE__ */ new Set();
13258
13989
  const trackingEvents = [];
13259
13990
  const linkChanges = [];
@@ -13263,52 +13994,83 @@ async function dispatchDatesBatch(action, options, deps) {
13263
13994
  const outcome = applyOneEdit2(edit, deps);
13264
13995
  verdicts.push(outcome.verdict);
13265
13996
  if (!outcome.verdict.ok || !outcome.changes) continue;
13997
+ const constraintWarning = toConstraintWarning(
13998
+ edit.activityId,
13999
+ outcome.changes.constraintWarning
14000
+ );
14001
+ if (constraintWarning) warnings.push(constraintWarning);
13266
14002
  const editTouched = collectTouchedIds(edit.activityId, outcome.changes);
13267
14003
  mergeBeforeSnapshots2(beforeSnap, editTouched, deps);
13268
- for (const id of editTouched) touchedIds.add(id);
14004
+ for (const touchedId of editTouched) touchedIds.add(touchedId);
14005
+ recordConstraintPrior(constraintPriorsByActivity, edit, beforeSnap);
14006
+ if (isExplicitConstraintDateEdit(edit.column)) {
14007
+ explicitDateEditActivities.add(String(edit.activityId));
14008
+ }
13269
14009
  const beforeLinkLags = snapshotIncomingLinkLags(adapter, edit.activityId);
13270
14010
  const preEditSnapshot = snapshotSingleActivity(
13271
14011
  adapter,
13272
14012
  String(edit.activityId)
13273
14013
  );
13274
- applyFieldChanges(adapter, edit.activityId, outcome.changes);
14014
+ const customIdBeforeApply = readLiveCustomId(edit, deps);
14015
+ const changes = outcome.changes;
14016
+ applyFieldChanges(adapter, edit.activityId, changes);
13275
14017
  runPostProcessorsOnAdapter(
13276
14018
  edit.activityId,
13277
14019
  outcome.changes.postProcessors ?? [],
13278
14020
  adapter,
13279
14021
  preEditSnapshot
13280
14022
  );
14023
+ syncCustomIdTracker(edit, customIdBeforeApply, deps);
13281
14024
  linkChanges.push(
13282
14025
  ...diffIncomingLinkLagChanges(beforeLinkLags, adapter, edit.activityId)
13283
14026
  );
13284
14027
  needsAutoSchedule ||= Boolean(outcome.changes.needsAutoSchedule);
13285
- for (const e of outcome.changes.trackingEvents ?? []) {
14028
+ for (const trackingEvent of outcome.changes.trackingEvents ?? []) {
13286
14029
  trackingEvents.push({
13287
- name: e.name,
13288
- properties: e.properties ?? {}
14030
+ name: trackingEvent.name,
14031
+ properties: trackingEvent.properties ?? {}
13289
14032
  });
13290
14033
  }
13291
- const constraintWarning = toConstraintWarning(
13292
- edit.activityId,
13293
- outcome.changes.constraintWarning
13294
- );
13295
- if (constraintWarning) warnings.push(constraintWarning);
13296
14034
  }
13297
- const { scheduledIds } = await runPostMutation(
14035
+ const postMutationArgs = {
14036
+ action,
14037
+ autoscheduleFrom: needsAutoSchedule ? "roots" : null,
14038
+ recomputeParentsFrom: touchedIds,
14039
+ now: deps.now,
14040
+ options
14041
+ };
14042
+ const batchHasConstraintEdits = constraintPriorsByActivity.size > 0;
14043
+ const { scheduledIds } = await (batchHasConstraintEdits ? settleConstraintEdits(
13298
14044
  {
13299
14045
  adapter,
13300
14046
  scheduler,
13301
- sector: deps.sector,
14047
+ sector,
13302
14048
  defaultBaseCalendarId: deps.defaultBaseCalendarId
13303
14049
  },
13304
14050
  {
13305
- action,
13306
- autoscheduleFrom: needsAutoSchedule ? "roots" : null,
13307
- recomputeParentsFrom: touchedIds,
13308
- now: deps.now,
13309
- options
14051
+ postMutationArgs,
14052
+ revertTargets: [...constraintPriorsByActivity].filter(
14053
+ ([activityId]) => !explicitDateEditActivities.has(activityId)
14054
+ ).map(([activityId, priorConstraintDate]) => ({
14055
+ activityId,
14056
+ priorConstraintDate
14057
+ })),
14058
+ revert: (activityId, priorConstraintDate) => revertNoOpConstraintEdit(
14059
+ activityId,
14060
+ priorConstraintDate,
14061
+ adapter,
14062
+ deps.ctx.calendars
14063
+ )
13310
14064
  }
13311
- );
14065
+ ) : runPostMutation(
14066
+ {
14067
+ adapter,
14068
+ scheduler,
14069
+ sector,
14070
+ defaultBaseCalendarId: deps.defaultBaseCalendarId
14071
+ },
14072
+ postMutationArgs
14073
+ ));
13312
14074
  return {
13313
14075
  ok: true,
13314
14076
  verdicts,
@@ -13318,7 +14080,6 @@ async function dispatchDatesBatch(action, options, deps) {
13318
14080
  touchedIds,
13319
14081
  scheduledIds,
13320
14082
  sirDetection: "before-diff",
13321
- preview: options.preview,
13322
14083
  links: linkChanges,
13323
14084
  trackingEvents,
13324
14085
  warnings,
@@ -13331,13 +14092,25 @@ function applyOneEdit2(edit, deps) {
13331
14092
  const activityId = edit.activityId;
13332
14093
  const pipeline = findPipelineForColumn(edit.column);
13333
14094
  if (!pipeline) {
13334
- throw new Error(
13335
- `[ScheduleCore] no pipeline registered for column "${edit.column}"`
13336
- );
14095
+ return {
14096
+ verdict: {
14097
+ activityId,
14098
+ ok: false,
14099
+ reason: REJECTION_REASON.NO_PIPELINE_FOR_COLUMN
14100
+ },
14101
+ changes: null
14102
+ };
13337
14103
  }
13338
14104
  const activitySnap = ctx.activityReader.getActivity(edit.activityId);
13339
14105
  if (!activitySnap) {
13340
- throw new Error(`[ScheduleCore] activity "${edit.activityId}" not found`);
14106
+ return {
14107
+ verdict: {
14108
+ activityId,
14109
+ ok: false,
14110
+ reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
14111
+ },
14112
+ changes: null
14113
+ };
13341
14114
  }
13342
14115
  const editGate = pipeline.canEdit(activitySnap, ctx.hierarchy);
13343
14116
  if (!editGate.allowed) {
@@ -13380,6 +14153,21 @@ function applyOneEdit2(edit, deps) {
13380
14153
  changes: null
13381
14154
  };
13382
14155
  }
14156
+ if (edit.column === CUSTOM_ID_COLUMN && parsed2.value !== null) {
14157
+ const liveActivity = deps.adapter.getActivity(edit.activityId);
14158
+ const oldCustomIdRaw = liveActivity ? liveActivity.customId : null;
14159
+ const oldCustomId = typeof oldCustomIdRaw === "string" ? oldCustomIdRaw : null;
14160
+ if (deps.customIdTracker.isCustomIdInUse(String(parsed2.value), oldCustomId)) {
14161
+ return {
14162
+ verdict: {
14163
+ activityId,
14164
+ ok: false,
14165
+ reason: REJECTION_REASON.CUSTOM_ID_DUPLICATE
14166
+ },
14167
+ changes: null
14168
+ };
14169
+ }
14170
+ }
13383
14171
  const changes = pipeline.transform(
13384
14172
  activitySnap,
13385
14173
  parsed2.value,
@@ -13388,226 +14176,41 @@ function applyOneEdit2(edit, deps) {
13388
14176
  );
13389
14177
  return { verdict: { activityId, ok: true }, changes };
13390
14178
  }
13391
- function mergeBeforeSnapshots2(beforeSnap, ids, deps) {
13392
- const missing = /* @__PURE__ */ new Set();
13393
- for (const id of ids) {
13394
- if (!beforeSnap.has(id)) missing.add(id);
13395
- }
13396
- if (missing.size === 0) return;
13397
- for (const missingId of missing) {
13398
- const liveActivity = deps.adapter.getActivity(missingId);
13399
- if (liveActivity) {
13400
- beforeSnap.set(missingId, structuredCloneActivity(liveActivity));
13401
- }
13402
- }
13403
- }
13404
-
13405
- // src/dispatch/handlers/persistence-acknowledge.ts
13406
- function assertValidAssignment(assignment, entityName, readCurrent, ownerByBackendId, seenIds, seenBackendIds) {
13407
- const id = String(assignment.id);
13408
- if (id.length === 0 || seenIds.has(id)) {
13409
- throw new Error(
13410
- `persistence-acknowledge: duplicate or empty ${entityName} id "${id}"`
13411
- );
13412
- }
13413
- if (!Number.isSafeInteger(assignment.proplannerId) || assignment.proplannerId <= 0) {
13414
- throw new Error(
13415
- `persistence-acknowledge: invalid ${entityName} proplannerId ${String(assignment.proplannerId)}`
13416
- );
13417
- }
13418
- if (seenBackendIds.has(assignment.proplannerId)) {
13419
- throw new Error(
13420
- `persistence-acknowledge: duplicate ${entityName} proplannerId ${String(assignment.proplannerId)}`
13421
- );
13422
- }
13423
- const state = readCurrent(id);
13424
- if (!state.exists) {
13425
- throw new Error(
13426
- `persistence-acknowledge: unknown ${entityName} id "${id}"`
13427
- );
13428
- }
13429
- if (state.current !== null && state.current !== assignment.proplannerId) {
13430
- throw new Error(
13431
- `persistence-acknowledge: ${entityName} "${id}" already has proplannerId ${String(state.current)}`
13432
- );
13433
- }
13434
- const existingOwner = ownerByBackendId.get(assignment.proplannerId);
13435
- if (existingOwner !== void 0 && existingOwner !== id) {
13436
- throw new Error(
13437
- `persistence-acknowledge: ${entityName} proplannerId ${String(assignment.proplannerId)} already belongs to "${existingOwner}"`
13438
- );
13439
- }
13440
- seenIds.add(id);
13441
- seenBackendIds.add(assignment.proplannerId);
13442
- }
13443
- function assertValidAssignments(assignments, entityName, readCurrent, ownerByBackendId) {
13444
- const seenIds = /* @__PURE__ */ new Set();
13445
- const seenBackendIds = /* @__PURE__ */ new Set();
13446
- for (const assignment of assignments) {
13447
- assertValidAssignment(
13448
- assignment,
13449
- entityName,
13450
- readCurrent,
13451
- ownerByBackendId,
13452
- seenIds,
13453
- seenBackendIds
13454
- );
13455
- }
14179
+ function readLiveCustomId(edit, deps) {
14180
+ if (edit.column !== CUSTOM_ID_COLUMN) return null;
14181
+ const liveActivity = deps.adapter.getActivity(edit.activityId);
14182
+ const rawValue = liveActivity ? liveActivity.customId : null;
14183
+ return typeof rawValue === "string" ? rawValue : null;
13456
14184
  }
13457
- function assertValidReconciledProgress(entries, deps) {
13458
- const seen = /* @__PURE__ */ new Set();
13459
- for (const entry of entries) {
13460
- const id = String(entry.activityId);
13461
- if (id.length === 0 || seen.has(id)) {
13462
- throw new Error(
13463
- `persistence-acknowledge: duplicate or empty reconciled activity id "${id}"`
13464
- );
13465
- }
13466
- if (!Number.isFinite(entry.progress) || entry.progress < 0 || entry.progress > 100) {
13467
- throw new Error(
13468
- `persistence-acknowledge: invalid reconciled progress ${String(entry.progress)} for "${id}"`
13469
- );
13470
- }
13471
- if (!deps.adapter.getActivity(id)) {
13472
- throw new Error(
13473
- `persistence-acknowledge: unknown reconciled activity id "${id}"`
13474
- );
13475
- }
13476
- seen.add(id);
14185
+ function syncCustomIdTracker(edit, oldCustomId, deps) {
14186
+ if (edit.column !== CUSTOM_ID_COLUMN) return;
14187
+ const liveActivity = deps.adapter.getActivity(edit.activityId);
14188
+ const rawValue = liveActivity ? liveActivity.customId : null;
14189
+ const newCustomId = typeof rawValue === "string" ? rawValue : null;
14190
+ if (oldCustomId !== newCustomId) {
14191
+ deps.customIdTracker.trackCustomIdChange(oldCustomId, newCustomId);
13477
14192
  }
13478
14193
  }
13479
- async function dispatchPersistenceAcknowledge(action, deps) {
13480
- const activities = action.activities ?? [];
13481
- const links = action.links ?? [];
13482
- const reconciledProgress = action.reconciledProgress ?? [];
13483
- assertValidReconciledProgress(reconciledProgress, deps);
13484
- const activityOwnerByBackendId = /* @__PURE__ */ new Map();
13485
- for (const activity of deps.adapter.getAllActivities()) {
13486
- if (activity.proplannerId != null) {
13487
- activityOwnerByBackendId.set(activity.proplannerId, String(activity.id));
13488
- }
13489
- }
13490
- const linkOwnerByBackendId = /* @__PURE__ */ new Map();
13491
- for (const link of deps.adapter.getAllLinks()) {
13492
- if (link.proplannerId != null) {
13493
- linkOwnerByBackendId.set(link.proplannerId, String(link.id));
13494
- }
14194
+ function recordConstraintPrior(priors, edit, beforeSnap) {
14195
+ if (!isConstraintEditColumn(edit.column)) return;
14196
+ const activityKey = String(edit.activityId);
14197
+ if (priors.has(activityKey)) return;
14198
+ const preBatchSnapshot = beforeSnap.get(activityKey);
14199
+ if (!preBatchSnapshot) return;
14200
+ priors.set(activityKey, preBatchSnapshot.constraintDate ?? null);
14201
+ }
14202
+ function mergeBeforeSnapshots2(beforeSnap, ids, deps) {
14203
+ const missing = /* @__PURE__ */ new Set();
14204
+ for (const candidateId of ids) {
14205
+ if (!beforeSnap.has(candidateId)) missing.add(candidateId);
13495
14206
  }
13496
- assertValidAssignments(
13497
- activities,
13498
- "activity",
13499
- (id) => {
13500
- const activity = deps.adapter.getActivity(id);
13501
- return {
13502
- exists: activity !== null,
13503
- current: activity?.proplannerId ?? null
13504
- };
13505
- },
13506
- activityOwnerByBackendId
13507
- );
13508
- assertValidAssignments(
13509
- links,
13510
- "link",
13511
- (id) => {
13512
- const link = deps.adapter.getLink(id);
13513
- return { exists: link !== null, current: link?.proplannerId ?? null };
13514
- },
13515
- linkOwnerByBackendId
13516
- );
13517
- const activityChanges = [];
13518
- const linkChanges = [];
13519
- const progressToApply = reconciledProgress.filter((entry) => {
13520
- const current = deps.adapter.getActivity(String(entry.activityId));
13521
- return current !== null && current.progress !== entry.progress;
13522
- });
13523
- const touchedForProgress = new Set(
13524
- progressToApply.map((entry) => String(entry.activityId))
13525
- );
13526
- const parentsToRollup = /* @__PURE__ */ new Set();
13527
- for (const entry of progressToApply) {
13528
- const activity = deps.adapter.getActivity(String(entry.activityId));
13529
- if (activity && !isRootParent(activity.parentId)) {
13530
- parentsToRollup.add(String(activity.parentId));
13531
- touchedForProgress.add(String(activity.parentId));
14207
+ if (missing.size === 0) return;
14208
+ for (const missingId of missing) {
14209
+ const liveActivity = deps.adapter.getActivity(missingId);
14210
+ if (liveActivity) {
14211
+ beforeSnap.set(missingId, structuredCloneActivity(liveActivity));
13532
14212
  }
13533
14213
  }
13534
- const beforeProgressSnap = await snapshotActivities(
13535
- deps.adapter,
13536
- touchedForProgress
13537
- );
13538
- deps.adapter.batchUpdate(() => {
13539
- for (const entry of progressToApply) {
13540
- deps.adapter.setActivityField(
13541
- String(entry.activityId),
13542
- "progress",
13543
- entry.progress
13544
- );
13545
- }
13546
- for (const parentId of parentsToRollup) {
13547
- recomputeRollupCascadesForParent(parentId, deps.adapter);
13548
- }
13549
- for (const id of touchedForProgress) {
13550
- const before = beforeProgressSnap.get(id);
13551
- const after = deps.adapter.getActivity(id);
13552
- if (!before || !after || before.progress === after.progress) continue;
13553
- activityChanges.push({
13554
- id,
13555
- kind: "updated",
13556
- fields: {
13557
- progress: { before: before.progress, after: after.progress }
13558
- },
13559
- after: { ...after }
13560
- });
13561
- }
13562
- for (const assignment of activities) {
13563
- const before = deps.adapter.getActivity(assignment.id);
13564
- if (!before || before.proplannerId === assignment.proplannerId) continue;
13565
- const previousProplannerId = before.proplannerId;
13566
- deps.adapter.setActivityField(
13567
- assignment.id,
13568
- "proplannerId",
13569
- assignment.proplannerId
13570
- );
13571
- activityChanges.push({
13572
- id: assignment.id,
13573
- kind: "updated",
13574
- fields: {
13575
- proplannerId: {
13576
- before: previousProplannerId,
13577
- after: assignment.proplannerId
13578
- }
13579
- },
13580
- after: { ...before, proplannerId: assignment.proplannerId }
13581
- });
13582
- }
13583
- for (const assignment of links) {
13584
- const before = deps.adapter.getLink(assignment.id);
13585
- if (!before || before.proplannerId === assignment.proplannerId) continue;
13586
- const previousProplannerId = before.proplannerId;
13587
- deps.adapter.setLinkProplannerId(assignment.id, assignment.proplannerId);
13588
- linkChanges.push({
13589
- id: assignment.id,
13590
- kind: "updated",
13591
- fields: {
13592
- proplannerId: {
13593
- before: previousProplannerId,
13594
- after: assignment.proplannerId
13595
- }
13596
- },
13597
- after: { ...before, proplannerId: assignment.proplannerId }
13598
- });
13599
- }
13600
- });
13601
- return {
13602
- ok: true,
13603
- changes: {
13604
- source: action,
13605
- activities: activityChanges,
13606
- links: linkChanges,
13607
- calendars: [],
13608
- trackingEvents: []
13609
- }
13610
- };
13611
14214
  }
13612
14215
 
13613
14216
  // src/baselines/normalize-baseline-point.ts
@@ -13805,55 +14408,6 @@ function recomputeAllPonderators(criterion, adapter) {
13805
14408
  });
13806
14409
  }
13807
14410
 
13808
- // src/propagations/upward/progress-rollup.ts
13809
- function recomputeAllProgressRollup(adapter) {
13810
- for (const parentId of parentsDeepestFirst(adapter)) {
13811
- rollupParent(parentId, adapter);
13812
- }
13813
- }
13814
- function parentsDeepestFirst(adapter) {
13815
- const parents = /* @__PURE__ */ new Set();
13816
- adapter.forEachActivity((_activity, id) => {
13817
- if (adapter.getChildren(id).length > 0) parents.add(id);
13818
- });
13819
- const depthById = /* @__PURE__ */ new Map();
13820
- for (const id of parents) depthById.set(id, depthOf2(id, adapter));
13821
- return [...parents].sort(
13822
- (a, b) => (depthById.get(b) ?? 0) - (depthById.get(a) ?? 0)
13823
- );
13824
- }
13825
- function depthOf2(id, adapter) {
13826
- let depth = 0;
13827
- let current = id;
13828
- while (current && current !== "0") {
13829
- if (!adapter.getActivity(current)) break;
13830
- depth += 1;
13831
- current = adapter.getParentId(current);
13832
- }
13833
- return depth;
13834
- }
13835
- function rollupParent(parentId, adapter) {
13836
- const childIds = adapter.getChildren(parentId);
13837
- if (childIds.length === 0) return;
13838
- const children = [];
13839
- for (const childId of childIds) {
13840
- const child = adapter.getActivity(childId);
13841
- if (!child) continue;
13842
- children.push({
13843
- progress: Number(child.progress ?? 0),
13844
- ponderator: Number(child.ponderator ?? 0)
13845
- });
13846
- }
13847
- const rollup = computeWeightedProgressRollup(children);
13848
- if (rollup !== null) {
13849
- adapter.setActivityField(
13850
- parentId,
13851
- COLUMN.PROGRESS,
13852
- roundProgressPerLevel(rollup)
13853
- );
13854
- }
13855
- }
13856
-
13857
14411
  // src/dispatch/shared/baseline-weighted-fields.ts
13858
14412
  function recomputeBaselineWeightedFields(deps, criterion) {
13859
14413
  recomputeAllPonderators(criterion, deps.adapter);
@@ -14243,11 +14797,8 @@ async function performDispatch(action, options, deps) {
14243
14797
  if (action.kind === "sir-sync") {
14244
14798
  return dispatchSirSync(action, deps);
14245
14799
  }
14246
- if (action.kind === "sir-approve") {
14247
- return dispatchSirApprove(action, options, deps);
14248
- }
14249
14800
  if (action.kind === "activity-lookahead-sync") {
14250
- return dispatchActivityLookaheadSync(action, deps);
14801
+ return dispatchActivityLookaheadSync(action, options, deps);
14251
14802
  }
14252
14803
  if (action.kind === "inline-edit" && (action.column === COLUMN.CUSTOM_PREDECESSORS || action.column === COLUMN.CUSTOM_SUCESSORS)) {
14253
14804
  return dispatchInlineEditLinks(action, options, deps);
@@ -17325,79 +17876,8 @@ var HierarchyIndex = class {
17325
17876
  }
17326
17877
  };
17327
17878
 
17328
- // src/internal/state/view-state.ts
17329
- var ViewStateStore = class {
17330
- checkedSet = /* @__PURE__ */ new Set();
17331
- hiddenSet = /* @__PURE__ */ new Set();
17332
- activeFilter = null;
17333
- activeOrder = null;
17334
- isChecked(activityId) {
17335
- return this.checkedSet.has(activityId);
17336
- }
17337
- isVisible(activityId) {
17338
- return !this.hiddenSet.has(activityId);
17339
- }
17340
- hiddenIds() {
17341
- return [...this.hiddenSet];
17342
- }
17343
- getActiveFilter() {
17344
- return this.activeFilter;
17345
- }
17346
- setActiveFilter(nextFilter) {
17347
- this.activeFilter = nextFilter;
17348
- }
17349
- getActiveOrder() {
17350
- return this.activeOrder;
17351
- }
17352
- setActiveOrder(nextOrder) {
17353
- this.activeOrder = nextOrder;
17354
- }
17355
- checkedIds() {
17356
- return [...this.checkedSet];
17357
- }
17358
- checkedCount() {
17359
- return this.checkedSet.size;
17360
- }
17361
- setChecked(activityId, nextChecked) {
17362
- if (nextChecked === this.checkedSet.has(activityId)) return false;
17363
- if (nextChecked) this.checkedSet.add(activityId);
17364
- else this.checkedSet.delete(activityId);
17365
- return true;
17366
- }
17367
- setVisible(activityId, nextVisible) {
17368
- if (nextVisible === !this.hiddenSet.has(activityId)) return false;
17369
- if (nextVisible) this.hiddenSet.delete(activityId);
17370
- else this.hiddenSet.add(activityId);
17371
- return true;
17372
- }
17373
- forget(activityId) {
17374
- this.checkedSet.delete(activityId);
17375
- this.hiddenSet.delete(activityId);
17376
- }
17377
- hydrate(seed2) {
17378
- this.checkedSet = new Set(seed2.checkedIds ?? []);
17379
- this.hiddenSet = new Set(seed2.hiddenIds ?? []);
17380
- this.activeFilter = null;
17381
- this.activeOrder = null;
17382
- }
17383
- snapshot() {
17384
- return {
17385
- checked: new Set(this.checkedSet),
17386
- hidden: new Set(this.hiddenSet),
17387
- activeFilter: this.activeFilter,
17388
- activeOrder: this.activeOrder
17389
- };
17390
- }
17391
- restore(snapshot) {
17392
- this.checkedSet = new Set(snapshot.checked);
17393
- this.hiddenSet = new Set(snapshot.hidden);
17394
- this.activeFilter = snapshot.activeFilter;
17395
- this.activeOrder = snapshot.activeOrder;
17396
- }
17397
- };
17398
-
17399
17879
  // src/internal/state/schedule-state.ts
17400
- var ScheduleState = class {
17880
+ var ScheduleState = class _ScheduleState {
17401
17881
  _activities = /* @__PURE__ */ new Map();
17402
17882
  _lastStartDate = /* @__PURE__ */ new Map();
17403
17883
  _promotionSnapshot = /* @__PURE__ */ new Map();
@@ -17419,22 +17899,52 @@ var ScheduleState = class {
17419
17899
  _promotionSnapshotBefore = null;
17420
17900
  _calendar;
17421
17901
  _flags;
17902
+ _calendarSnapshot;
17422
17903
  constructor(snapshot, buildOrderComparator = () => null) {
17423
17904
  this._buildOrderComparator = buildOrderComparator;
17905
+ this._calendarSnapshot = cloneDomainValue(snapshot.calendars);
17424
17906
  for (const activity of snapshot.activities) {
17425
17907
  this._activities.set(activity.id, activity);
17426
17908
  }
17427
17909
  for (const link of snapshot.links) {
17428
- this._links.set(String(link.id), link);
17429
- this._indexLink(link);
17910
+ const normalized2 = {
17911
+ ...link,
17912
+ freeSlackHours: link.freeSlackHours ?? null,
17913
+ isCritical: link.isCritical === true
17914
+ };
17915
+ this._links.set(String(link.id), normalized2);
17916
+ this._indexLink(normalized2);
17430
17917
  }
17431
17918
  this._calendar = new CalendarCalculator(
17432
- createCalendarSetup(snapshot.calendars)
17919
+ createCalendarSetup(this._calendarSnapshot)
17433
17920
  );
17434
17921
  this._flags = buildFlags(snapshot);
17435
17922
  this._hierarchy = new HierarchyIndex(this._activities);
17436
17923
  this._hierarchy.rebuild();
17437
17924
  }
17925
+ fork() {
17926
+ const fork = new _ScheduleState(
17927
+ {
17928
+ activities: this.getAllActivities().map(cloneCoreActivity),
17929
+ links: this.getAllLinks().map((link) => ({ ...link })),
17930
+ calendars: cloneDomainValue(this._calendarSnapshot)
17931
+ },
17932
+ this._buildOrderComparator
17933
+ );
17934
+ fork._viewState.restore(cloneDomainValue(this._viewState.snapshot()));
17935
+ fork._lastStartDate = new Map(
17936
+ Array.from(this._lastStartDate, ([id, date2]) => [id, new Date(date2)])
17937
+ );
17938
+ fork._promotionSnapshot = new Map(
17939
+ Array.from(this._promotionSnapshot, ([id, snapshot]) => [
17940
+ id,
17941
+ cloneDomainValue(snapshot)
17942
+ ])
17943
+ );
17944
+ fork._flags = cloneDomainValue(this._flags);
17945
+ fork._filterProjection = cloneDomainValue(this._filterProjection);
17946
+ return fork;
17947
+ }
17438
17948
  getAllActivities() {
17439
17949
  return Array.from(this._activities.values());
17440
17950
  }
@@ -17544,6 +18054,18 @@ var ScheduleState = class {
17544
18054
  this._viewState.setActiveOrder(nextOrder);
17545
18055
  this.invalidateDerivedOrder();
17546
18056
  }
18057
+ setRelativePlacementPin(activityId, pin) {
18058
+ this._viewState.setRelativePlacementPin(String(activityId), pin);
18059
+ this.invalidateVisualOrderIds();
18060
+ }
18061
+ getRelativePlacementPins() {
18062
+ return this._viewState.getRelativePlacementPins();
18063
+ }
18064
+ restoreRelativePlacementPins(pins) {
18065
+ this._viewState.restoreRelativePlacementPins(pins);
18066
+ this._filterProjection = null;
18067
+ this.invalidateVisualOrderIds();
18068
+ }
17547
18069
  /**
17548
18070
  * Rebuild-on-demand, memoized: at most one build per invalidation, never one
17549
18071
  * per comparison. getChildrenInVisualOrder asks for this once and then sorts a
@@ -17692,9 +18214,14 @@ var ScheduleState = class {
17692
18214
  addLink(link) {
17693
18215
  this._writeCapture.noteLinkAdd(String(link.id));
17694
18216
  const superseded = this._links.get(String(link.id));
17695
- if (superseded) this._deindexLink(superseded);
17696
- this._links.set(String(link.id), { ...link });
17697
- this._indexLink(link);
18217
+ if (superseded) this._deindexLink(superseded);
18218
+ const normalized2 = {
18219
+ ...link,
18220
+ freeSlackHours: link.freeSlackHours ?? null,
18221
+ isCritical: link.isCritical === true
18222
+ };
18223
+ this._links.set(String(link.id), normalized2);
18224
+ this._indexLink(normalized2);
17698
18225
  }
17699
18226
  _indexLink(link) {
17700
18227
  pushLinkRef(this._outgoing, String(link.source), link.id);
@@ -17745,10 +18272,14 @@ var ScheduleState = class {
17745
18272
  const activity = this.getActivity(activityId);
17746
18273
  if (activity) this._writeCapture.noteRemove(String(activityId), activity);
17747
18274
  const key = String(activityId);
17748
- if (this._viewState.isChecked(key) || !this._viewState.isVisible(key)) {
18275
+ const relativePlacementPins = this._viewState.getRelativePlacementPins();
18276
+ const hasDependentPin = [...relativePlacementPins.values()].some(
18277
+ ({ referenceId }) => referenceId === key
18278
+ );
18279
+ if (this._viewState.isChecked(key) || !this._viewState.isVisible(key) || relativePlacementPins.has(key) || hasDependentPin) {
17749
18280
  this._captureViewStateOnce();
17750
- this._viewState.forget(key);
17751
18281
  }
18282
+ this._viewState.forget(key);
17752
18283
  this._activities.delete(activityId);
17753
18284
  if (this._lastStartDate.has(String(activityId))) {
17754
18285
  this._captureLastStartDateOnce();
@@ -17847,7 +18378,7 @@ var ScheduleState = class {
17847
18378
  };
17848
18379
  var EMPTY_LINK_IDS = [];
17849
18380
  function isMutableLinkField(field) {
17850
- return field === LINK_PROPERTY.LAG || field === LINK_PROPERTY.TYPE;
18381
+ return field === LINK_PROPERTY.LAG || field === LINK_PROPERTY.TYPE || field === LINK_PROPERTY.FREE_SLACK_HOURS || field === LINK_PROPERTY.IS_CRITICAL;
17851
18382
  }
17852
18383
  function fieldAffectsVisualOrder(field) {
17853
18384
  return field === ACTIVITY_PROPERTY.PARENT || field === ACTIVITY_PROPERTY.CORRELATIVE_ID;
@@ -17968,12 +18499,7 @@ function buildPipelineContext(adapter, options) {
17968
18499
  activityReader: createStateBackedActivityReader(adapter),
17969
18500
  dateFormat: options.dateFormat ?? "YYYY-MM-DD",
17970
18501
  customHours: options.customHours ?? {},
17971
- hoursPerDay: options.hoursPerDay,
17972
- // ISSUE-057 (CP6): sin default. Un comando de duración sin inputUnit se
17973
- // rechaza en el pipeline; el resto de columnas no lo leen. La clave se
17974
- // OMITE cuando no se declaró (exactOptionalPropertyTypes), no se pone a
17975
- // undefined.
17976
- ...options.inputUnit !== void 0 ? { inputUnit: options.inputUnit } : {}
18502
+ hoursPerDay: options.hoursPerDay
17977
18503
  };
17978
18504
  }
17979
18505
 
@@ -17983,15 +18509,26 @@ function willRunCriticalPath(action) {
17983
18509
  }
17984
18510
 
17985
18511
  // src/dispatch/undo/undo-entry.ts
17986
- function buildHistoryChangeSet(changes) {
17987
- const activitiesWithoutAfter = changes.activities.map((entry) => ({
18512
+ var DERIVED_LINK_FIELDS = /* @__PURE__ */ new Set(["freeSlackHours", "isCritical"]);
18513
+ function withoutDerivedFields(entry, derivedFields) {
18514
+ const fields = Object.fromEntries(
18515
+ Object.entries(entry.fields ?? {}).filter(
18516
+ ([field]) => !derivedFields.has(field)
18517
+ )
18518
+ );
18519
+ if (entry.kind === "updated" && Object.keys(fields).length === 0) return null;
18520
+ return {
17988
18521
  ...entry,
18522
+ ...entry.fields === void 0 ? {} : { fields },
17989
18523
  after: null
17990
- }));
17991
- const linksWithoutAfter = changes.links.map((entry) => ({
18524
+ };
18525
+ }
18526
+ function buildHistoryChangeSet(changes) {
18527
+ const activitiesWithoutAfter = changes.activities.map((entry) => ({
17992
18528
  ...entry,
17993
18529
  after: null
17994
18530
  }));
18531
+ const linksWithoutAfter = changes.links.map((entry) => withoutDerivedFields(entry, DERIVED_LINK_FIELDS)).filter((entry) => entry !== null);
17995
18532
  const source = changes.source.kind === "activity-batch" ? { ...changes.source, items: [], links: [] } : changes.source;
17996
18533
  return cloneDomainValue({
17997
18534
  ...changes,
@@ -18000,8 +18537,25 @@ function buildHistoryChangeSet(changes) {
18000
18537
  links: linksWithoutAfter
18001
18538
  });
18002
18539
  }
18003
- function buildUndoEntry(changeSet, beforeSnap, beforeLinks, afterSnap, afterLinks) {
18004
- return { changeSet, beforeSnap, beforeLinks, afterSnap, afterLinks };
18540
+ function buildUndoEntry(changeSet, beforeSnap, beforeLinks, afterSnap, afterLinks, relativePlacementPinsBefore, relativePlacementPinsAfter) {
18541
+ return {
18542
+ changeSet,
18543
+ beforeSnap,
18544
+ beforeLinks: resetDerivedLinkSnapshots(beforeLinks),
18545
+ afterSnap,
18546
+ afterLinks: resetDerivedLinkSnapshots(afterLinks),
18547
+ relativePlacementPinsBefore,
18548
+ relativePlacementPinsAfter
18549
+ };
18550
+ }
18551
+ function resetDerivedLinkSnapshots(snapshots) {
18552
+ if (!snapshots) return void 0;
18553
+ return new Map(
18554
+ Array.from(snapshots, ([id, link]) => [
18555
+ id,
18556
+ { ...link, freeSlackHours: null, isCritical: false }
18557
+ ])
18558
+ );
18005
18559
  }
18006
18560
  function captureCreatedSnapshots(state, changeSet) {
18007
18561
  const afterSnap = /* @__PURE__ */ new Map();
@@ -18053,8 +18607,7 @@ function mergeCoalesced(top, next) {
18053
18607
  };
18054
18608
  return buildUndoEntry(changeSet);
18055
18609
  }
18056
- function getDispatchHistoryPolicy(action, options) {
18057
- if (options?.preview === true) return "skip";
18610
+ function getDispatchHistoryPolicy(action) {
18058
18611
  if (action.kind === "persistence-acknowledge") return "clear-on-success";
18059
18612
  if (action.kind === "sir-sync" || action.kind === "activity-lookahead-sync" || action.kind === "baseline-apply" || action.kind === "ponderator-criterion-set" || action.kind === "status-criteria-set" || action.kind === "activity-clipboard-clear" || action.kind === "activity-copy" || action.kind === "selection-toggle" || action.kind === "selection-replace" || action.kind === "visibility-set" || action.kind === "filter-set" || action.kind === "sort-set") {
18060
18613
  return "skip";
@@ -18295,12 +18848,14 @@ function applyChangeSetSide(state, entry, side) {
18295
18848
  );
18296
18849
  }
18297
18850
  }
18851
+ const pins = side === "before" ? entry.relativePlacementPinsBefore : entry.relativePlacementPinsAfter;
18852
+ if (pins !== void 0) state.restoreRelativePlacementPins(pins);
18298
18853
  }
18299
18854
 
18300
18855
  // src/dispatch/undo/undo-recorder.ts
18301
18856
  var DEFAULT_MAX_DEPTH = 100;
18302
18857
  var COALESCE_WINDOW_MS = 600;
18303
- var UndoRecorder = class {
18858
+ var UndoRecorder = class _UndoRecorder {
18304
18859
  constructor(maxDepth = DEFAULT_MAX_DEPTH) {
18305
18860
  this.maxDepth = maxDepth;
18306
18861
  }
@@ -18309,6 +18864,14 @@ var UndoRecorder = class {
18309
18864
  redoStack = [];
18310
18865
  _lastKey = null;
18311
18866
  _lastTime = 0;
18867
+ fork() {
18868
+ const fork = new _UndoRecorder(this.maxDepth);
18869
+ fork.undoStack.push(...cloneDomainValue(this.undoStack));
18870
+ fork.redoStack.push(...cloneDomainValue(this.redoStack));
18871
+ fork._lastKey = this._lastKey;
18872
+ fork._lastTime = this._lastTime;
18873
+ return fork;
18874
+ }
18312
18875
  record(entry, coalesceKey = null, now = 0) {
18313
18876
  const top = this.undoStack[this.undoStack.length - 1];
18314
18877
  if (coalesceKey != null && coalesceKey === this._lastKey && now - this._lastTime <= COALESCE_WINDOW_MS && top !== void 0 && canCoalesce(top, entry)) {
@@ -18432,40 +18995,56 @@ function buildStateSnapshot(activities, links, calendars, baseCalendars = []) {
18432
18995
  }
18433
18996
 
18434
18997
  // src/boundary/save/activity-changes.ts
18435
- var PERSISTABLE_ACTIVITY_FIELDS = [
18436
- "id",
18437
- "proplannerId",
18438
- "parentId",
18439
- "name",
18440
- "description",
18441
- "type",
18442
- "startDate",
18443
- "endDate",
18444
- "durationHours",
18445
- "constraintType",
18446
- "constraintDate",
18447
- "autoScheduling",
18448
- "calendarId",
18449
- "assignedDefaultCalendar",
18450
- "dateOrigin",
18451
- "progress",
18452
- "cost",
18453
- "usedCost",
18454
- "realCost",
18455
- "workHours",
18456
- "realWorkHours",
18457
- "ponderator",
18458
- "hasCustomPonderator",
18459
- "correlativeId",
18460
- "uniqueCorrelativeId",
18461
- "customId",
18462
- "subcontractId",
18463
- "isLookahead",
18464
- "newActivityIds",
18465
- "hasNewActivities",
18466
- "responsableIds",
18467
- "tagIds"
18468
- ];
18998
+ var ACTIVITY_SAVE_FIELD_POLICY = {
18999
+ id: "ignored",
19000
+ proplannerId: "ignored",
19001
+ parentId: "tracked",
19002
+ name: "tracked",
19003
+ description: "tracked",
19004
+ type: "tracked",
19005
+ startDate: "tracked",
19006
+ endDate: "tracked",
19007
+ durationHours: "tracked",
19008
+ constraintType: "tracked",
19009
+ constraintDate: "tracked",
19010
+ autoScheduling: "tracked",
19011
+ calendarId: "tracked",
19012
+ assignedDefaultCalendar: "ignored",
19013
+ dateOrigin: "tracked",
19014
+ progress: "tracked",
19015
+ cost: "tracked",
19016
+ usedCost: "tracked",
19017
+ realCost: "tracked",
19018
+ workHours: "tracked",
19019
+ realWorkHours: "tracked",
19020
+ ponderator: "tracked",
19021
+ hasCustomPonderator: "tracked",
19022
+ correlativeId: "tracked",
19023
+ uniqueCorrelativeId: "tracked",
19024
+ customId: "tracked",
19025
+ subcontractId: "tracked",
19026
+ isLookahead: "tracked",
19027
+ hasLookaheadTasks: "ignored",
19028
+ newActivityIds: "tracked",
19029
+ hasNewActivities: "tracked",
19030
+ isNewActivity: "ignored",
19031
+ pendingRequestIds: "ignored",
19032
+ responsableIds: "tracked",
19033
+ tagIds: "tracked",
19034
+ baselinePoints: "ignored",
19035
+ baselineSnapshot: "ignored",
19036
+ recursiveDurationHours: "ignored",
19037
+ isCritical: "ignored",
19038
+ freeSlackHours: "ignored",
19039
+ calendarDuration: "ignored",
19040
+ expectedProgress: "ignored",
19041
+ expectedProgressBaseline: "ignored",
19042
+ status: "ignored",
19043
+ criticalPath: "ignored"
19044
+ };
19045
+ var PERSISTABLE_ACTIVITY_FIELDS = Object.keys(ACTIVITY_SAVE_FIELD_POLICY).filter((field) => {
19046
+ return ACTIVITY_SAVE_FIELD_POLICY[field] === "tracked";
19047
+ });
18469
19048
  var DATE_FIELDS2 = /* @__PURE__ */ new Set([
18470
19049
  "startDate",
18471
19050
  "endDate",
@@ -18493,7 +19072,6 @@ function snapshotFieldValue(field, value) {
18493
19072
  return value;
18494
19073
  }
18495
19074
  function activityChangedSince(current, saved) {
18496
- if (current.assignedDefaultCalendar) return true;
18497
19075
  for (const field of PERSISTABLE_ACTIVITY_FIELDS) {
18498
19076
  if (!fieldEquals(field, current[field], saved[field])) return true;
18499
19077
  }
@@ -18511,15 +19089,35 @@ function dateEquals(current, saved) {
18511
19089
  return currentTime === savedTime;
18512
19090
  }
18513
19091
  function idArrayEquals(current, saved) {
18514
- const currentIds = Array.isArray(current) ? [...current].map(String).sort() : [];
18515
- const savedIds = Array.isArray(saved) ? [...saved].map(String).sort() : [];
18516
- if (currentIds.length !== savedIds.length) return false;
18517
- return currentIds.every((value, index) => value === savedIds[index]);
19092
+ const currentIds = new Set(Array.isArray(current) ? current.map(String) : []);
19093
+ const savedIds = new Set(Array.isArray(saved) ? saved.map(String) : []);
19094
+ if (currentIds.size !== savedIds.size) return false;
19095
+ for (const currentId of currentIds) {
19096
+ if (!savedIds.has(currentId)) return false;
19097
+ }
19098
+ return true;
18518
19099
  }
18519
19100
 
18520
19101
  // src/boundary/save/link-changes.ts
19102
+ var LINK_SAVE_FIELD_POLICY = {
19103
+ id: "ignored",
19104
+ source: "tracked",
19105
+ target: "tracked",
19106
+ type: "tracked",
19107
+ lag: "tracked",
19108
+ freeSlackHours: "ignored",
19109
+ isCritical: "ignored",
19110
+ ganttId: "ignored",
19111
+ sectorId: "ignored",
19112
+ proplannerId: "ignored"
19113
+ };
19114
+ var TRACKED_LINK_FIELDS = Object.keys(LINK_SAVE_FIELD_POLICY).filter((field) => {
19115
+ return LINK_SAVE_FIELD_POLICY[field] === "tracked";
19116
+ });
18521
19117
  function linkChangedSince(baseline, current) {
18522
- return baseline.source !== current.source || baseline.target !== current.target || baseline.lag !== current.lag || baseline.type !== current.type;
19118
+ return TRACKED_LINK_FIELDS.some(
19119
+ (field) => baseline[field] !== current[field]
19120
+ );
18523
19121
  }
18524
19122
  function checkNoUpdatedLinks(baseline, current) {
18525
19123
  const baselineById = new Map(
@@ -18533,23 +19131,73 @@ function checkNoUpdatedLinks(baseline, current) {
18533
19131
  }
18534
19132
 
18535
19133
  // src/boundary/save/save-tracker.ts
18536
- var SaveTracker = class {
19134
+ function hasPersistedIdentity(proplannerId) {
19135
+ const isNewIdentity = proplannerId == null || proplannerId === 0;
19136
+ if (isNewIdentity) return false;
19137
+ const isValidPersistedIdentity = typeof proplannerId === "number" && Number.isSafeInteger(proplannerId) && proplannerId > 0;
19138
+ if (!isValidPersistedIdentity) {
19139
+ throw new Error(`invalid proplannerId ${String(proplannerId)}`);
19140
+ }
19141
+ return true;
19142
+ }
19143
+ function assertValidPersistedIdentities(activities, links) {
19144
+ for (const activity of activities) {
19145
+ hasPersistedIdentity(activity.proplannerId);
19146
+ }
19147
+ for (const link of links) {
19148
+ hasPersistedIdentity(link.proplannerId);
19149
+ }
19150
+ }
19151
+ var SaveTracker = class _SaveTracker {
19152
+ trackingEnabled;
18537
19153
  initialized = false;
18538
19154
  dirtyActivityIds = /* @__PURE__ */ new Set();
18539
19155
  dirtyLinkIds = /* @__PURE__ */ new Set();
19156
+ initialCalendarRepairIds = /* @__PURE__ */ new Set();
18540
19157
  linksAtLastSave = /* @__PURE__ */ new Map();
18541
19158
  activitiesAtLastSave = /* @__PURE__ */ new Map();
19159
+ constructor(trackingEnabled = true) {
19160
+ this.trackingEnabled = trackingEnabled;
19161
+ }
19162
+ fork() {
19163
+ const fork = new _SaveTracker(false);
19164
+ fork.initialized = this.initialized;
19165
+ for (const [linkId, link] of this.linksAtLastSave) {
19166
+ fork.linksAtLastSave.set(linkId, cloneDomainValue(link));
19167
+ }
19168
+ for (const [activityId, activity] of this.activitiesAtLastSave) {
19169
+ fork.activitiesAtLastSave.set(activityId, cloneDomainValue(activity));
19170
+ }
19171
+ return fork;
19172
+ }
18542
19173
  init(activities, links) {
19174
+ if (!this.trackingEnabled) return;
19175
+ assertValidPersistedIdentities(activities, links);
18543
19176
  this.snapshotActivities(activities);
18544
19177
  this.snapshotLinks(links);
19178
+ for (const activity of activities) {
19179
+ if (activity.assignedDefaultCalendar) {
19180
+ const activityId = String(activity.id);
19181
+ this.initialCalendarRepairIds.add(activityId);
19182
+ this.dirtyActivityIds.add(activityId);
19183
+ }
19184
+ }
18545
19185
  this.initialized = true;
18546
19186
  }
19187
+ acknowledge(activities, links) {
19188
+ if (!this.trackingEnabled) return;
19189
+ assertValidPersistedIdentities(activities, links);
19190
+ this.snapshotActivities(activities);
19191
+ this.snapshotLinks(links);
19192
+ }
18547
19193
  snapshotActivities(activities) {
19194
+ if (!this.trackingEnabled) return;
18548
19195
  this.activitiesAtLastSave.clear();
18549
19196
  this.dirtyActivityIds.clear();
19197
+ this.initialCalendarRepairIds.clear();
18550
19198
  for (const activity of activities) {
18551
19199
  const activityId = String(activity.id);
18552
- if (activity.proplannerId == null) {
19200
+ if (!hasPersistedIdentity(activity.proplannerId)) {
18553
19201
  this.dirtyActivityIds.add(activityId);
18554
19202
  continue;
18555
19203
  }
@@ -18569,11 +19217,12 @@ var SaveTracker = class {
18569
19217
  }
18570
19218
  }
18571
19219
  snapshotLinks(links) {
19220
+ if (!this.trackingEnabled) return;
18572
19221
  this.linksAtLastSave.clear();
18573
19222
  this.dirtyLinkIds.clear();
18574
19223
  for (const link of links) {
18575
19224
  const linkId = String(link.id);
18576
- if (link.proplannerId == null) {
19225
+ if (!hasPersistedIdentity(link.proplannerId)) {
18577
19226
  this.dirtyLinkIds.add(linkId);
18578
19227
  continue;
18579
19228
  }
@@ -18588,12 +19237,11 @@ var SaveTracker = class {
18588
19237
  }
18589
19238
  }
18590
19239
  recordChanges(activities, links, getActivity, getLink) {
18591
- if (!this.initialized) return;
19240
+ if (!this.trackingEnabled || !this.initialized) return;
18592
19241
  for (const change of activities) {
18593
19242
  const activityId = String(change.id);
18594
19243
  const current = getActivity(activityId);
18595
- const saved = this.activitiesAtLastSave.get(activityId);
18596
- if (current === null ? saved !== void 0 : current.proplannerId == null || saved === void 0 || activityChangedSince(current, saved.persistable)) {
19244
+ if (this.activityIsDirty(activityId, current)) {
18597
19245
  this.dirtyActivityIds.add(activityId);
18598
19246
  } else {
18599
19247
  this.dirtyActivityIds.delete(activityId);
@@ -18602,81 +19250,147 @@ var SaveTracker = class {
18602
19250
  for (const change of links) {
18603
19251
  const linkId = String(change.id);
18604
19252
  const current = getLink(linkId);
18605
- const saved = this.linksAtLastSave.get(linkId);
18606
- if (current === null ? saved !== void 0 : current.proplannerId == null || saved === void 0 || linkChangedSince(saved, current)) {
19253
+ if (this.linkIsDirty(linkId, current)) {
18607
19254
  this.dirtyLinkIds.add(linkId);
18608
19255
  } else {
18609
19256
  this.dirtyLinkIds.delete(linkId);
18610
19257
  }
18611
19258
  }
18612
19259
  }
18613
- acknowledgePersistedLookahead(activityIds, getActivity) {
18614
- if (!this.initialized) return;
18615
- for (const activityId of activityIds) {
19260
+ activityIsDirty(activityId, current) {
19261
+ const saved = this.activitiesAtLastSave.get(activityId);
19262
+ if (current === null) return saved !== void 0;
19263
+ return this.initialCalendarRepairIds.has(activityId) || !hasPersistedIdentity(current.proplannerId) || saved === void 0 || activityChangedSince(current, saved.persistable);
19264
+ }
19265
+ linkIsDirty(linkId, current) {
19266
+ const saved = this.linksAtLastSave.get(linkId);
19267
+ if (current === null) return saved !== void 0;
19268
+ return !hasPersistedIdentity(current.proplannerId) || saved === void 0 || linkChangedSince(saved, current);
19269
+ }
19270
+ acknowledgePersistedLookahead(changes, getActivity) {
19271
+ if (!this.trackingEnabled || !this.initialized) return;
19272
+ const acknowledgements = changes.map((change) => {
19273
+ const activityId = typeof change === "string" ? change : String(change.id);
18616
19274
  const current = getActivity(activityId);
18617
19275
  const saved = this.activitiesAtLastSave.get(activityId);
18618
- if (current === null || saved === void 0) continue;
19276
+ const isUnknownPersistedActivity = current === null || saved === void 0;
19277
+ if (isUnknownPersistedActivity) {
19278
+ throw new Error(`unknown persisted activity "${activityId}"`);
19279
+ }
19280
+ return {
19281
+ activityId,
19282
+ acknowledgesLookahead: typeof change === "string" || Boolean(change.fields?.isLookahead),
19283
+ acknowledgesProgress: typeof change !== "string" && Boolean(change.fields?.progress),
19284
+ current,
19285
+ saved
19286
+ };
19287
+ });
19288
+ for (const {
19289
+ activityId,
19290
+ acknowledgesLookahead,
19291
+ acknowledgesProgress,
19292
+ current,
19293
+ saved
19294
+ } of acknowledgements) {
18619
19295
  const persistable = {
18620
19296
  ...saved.persistable,
18621
- isLookahead: current.isLookahead
19297
+ ...acknowledgesLookahead ? { isLookahead: current.isLookahead } : {},
19298
+ ...acknowledgesProgress ? { progress: current.progress } : {}
18622
19299
  };
18623
19300
  this.activitiesAtLastSave.set(activityId, {
18624
19301
  ...saved,
18625
19302
  persistable
18626
19303
  });
18627
- if (current.proplannerId == null || activityChangedSince(current, persistable)) {
19304
+ if (this.initialCalendarRepairIds.has(activityId) || !hasPersistedIdentity(current.proplannerId) || activityChangedSince(current, persistable)) {
18628
19305
  this.dirtyActivityIds.add(activityId);
18629
19306
  } else {
18630
19307
  this.dirtyActivityIds.delete(activityId);
18631
19308
  }
18632
19309
  }
18633
19310
  }
19311
+ hasCheckpointActivityId(activityId) {
19312
+ return this.activitiesAtLastSave.has(activityId);
19313
+ }
19314
+ hasCheckpointLinkId(linkId) {
19315
+ return this.linksAtLastSave.has(linkId);
19316
+ }
18634
19317
  hasUnsavedChanges() {
18635
- return this.initialized && (this.dirtyActivityIds.size > 0 || this.dirtyLinkIds.size > 0);
19318
+ return this.trackingEnabled && this.initialized && (this.dirtyActivityIds.size > 0 || this.dirtyLinkIds.size > 0);
19319
+ }
19320
+ newActivities(getActivity) {
19321
+ if (!this.trackingEnabled || !this.initialized) return [];
19322
+ const activities = [];
19323
+ for (const activityId of this.dirtyActivityIds) {
19324
+ const activity = getActivity(activityId);
19325
+ const isNewActivity = activity !== null && !hasPersistedIdentity(activity.proplannerId);
19326
+ if (isNewActivity) activities.push(activity);
19327
+ }
19328
+ return activities;
19329
+ }
19330
+ newLinks(getLink) {
19331
+ if (!this.trackingEnabled || !this.initialized) return [];
19332
+ const links = [];
19333
+ for (const linkId of this.dirtyLinkIds) {
19334
+ const link = getLink(linkId);
19335
+ const isNewLink = link !== null && !hasPersistedIdentity(link.proplannerId);
19336
+ if (isNewLink) links.push(link);
19337
+ }
19338
+ return links;
19339
+ }
19340
+ deletedActivities(getActivity) {
19341
+ if (!this.trackingEnabled || !this.initialized) return [];
19342
+ const deleted = [];
19343
+ for (const activityId of this.dirtyActivityIds) {
19344
+ const saved = this.activitiesAtLastSave.get(activityId);
19345
+ if (saved && getActivity(activityId) === null) {
19346
+ deleted.push({ ...saved.deletedSnapshot });
19347
+ }
19348
+ }
19349
+ return deleted;
18636
19350
  }
18637
- deletedActivities(activities) {
18638
- if (!this.initialized) return [];
18639
- const currentIds = new Set(
18640
- activities.map((activity) => String(activity.id))
18641
- );
19351
+ deletedLinks(getLink) {
19352
+ if (!this.trackingEnabled || !this.initialized) return [];
18642
19353
  const deleted = [];
18643
- for (const [activityId, saved] of this.activitiesAtLastSave) {
18644
- if (currentIds.has(activityId)) continue;
18645
- deleted.push({ ...saved.deletedSnapshot });
19354
+ for (const linkId of this.dirtyLinkIds) {
19355
+ const saved = this.linksAtLastSave.get(linkId);
19356
+ if (saved && getLink(linkId) === null) deleted.push({ ...saved });
18646
19357
  }
18647
19358
  return deleted;
18648
19359
  }
18649
- deletedLinks(links) {
18650
- if (!this.initialized) return [];
18651
- const currentIds = new Set(links.map((link) => String(link.id)));
18652
- return Array.from(
18653
- this.linksAtLastSave.entries(),
18654
- ([linkId, saved]) => currentIds.has(linkId) ? null : { ...saved }
18655
- ).filter((link) => link !== null);
18656
- }
18657
- modifiedActivities(activities) {
18658
- if (!this.initialized) return [];
18659
- return activities.filter((activity) => {
18660
- if (activity.proplannerId == null) return false;
18661
- if (activity.assignedDefaultCalendar) return true;
18662
- const saved = this.activitiesAtLastSave.get(String(activity.id));
18663
- if (!saved) return true;
18664
- return activityChangedSince(activity, saved.persistable);
18665
- });
19360
+ modifiedActivities(getActivity) {
19361
+ if (!this.trackingEnabled || !this.initialized) return [];
19362
+ const modified = [];
19363
+ for (const activityId of this.dirtyActivityIds) {
19364
+ const activity = getActivity(activityId);
19365
+ if (activity === null || !hasPersistedIdentity(activity.proplannerId)) {
19366
+ continue;
19367
+ }
19368
+ const saved = this.activitiesAtLastSave.get(activityId);
19369
+ if (saved === void 0 || this.initialCalendarRepairIds.has(activityId) || activityChangedSince(activity, saved.persistable)) {
19370
+ modified.push(activity);
19371
+ }
19372
+ }
19373
+ return modified;
18666
19374
  }
18667
- modifiedLinks(links) {
18668
- if (!this.initialized) return [];
18669
- return links.filter((link) => {
18670
- if (link.proplannerId == null) return false;
18671
- const saved = this.linksAtLastSave.get(String(link.id));
18672
- if (!saved) return true;
18673
- return linkChangedSince(saved, link);
18674
- });
19375
+ modifiedLinks(getLink) {
19376
+ if (!this.trackingEnabled || !this.initialized) return [];
19377
+ const modified = [];
19378
+ for (const linkId of this.dirtyLinkIds) {
19379
+ const link = getLink(linkId);
19380
+ if (link === null) continue;
19381
+ if (!hasPersistedIdentity(link.proplannerId)) continue;
19382
+ const saved = this.linksAtLastSave.get(linkId);
19383
+ if (saved === void 0 || linkChangedSince(saved, link)) {
19384
+ modified.push(link);
19385
+ }
19386
+ }
19387
+ return modified;
18675
19388
  }
18676
19389
  clear() {
18677
19390
  this.initialized = false;
18678
19391
  this.dirtyActivityIds.clear();
18679
19392
  this.dirtyLinkIds.clear();
19393
+ this.initialCalendarRepairIds.clear();
18680
19394
  this.linksAtLastSave.clear();
18681
19395
  this.activitiesAtLastSave.clear();
18682
19396
  }
@@ -18730,14 +19444,14 @@ function isAutoSchedulable(progress) {
18730
19444
  }
18731
19445
  function parseConstraintLabel(label, reporter) {
18732
19446
  if (!label) return null;
18733
- const normalized = CONSTRAINT_LABEL_MAP[label.toUpperCase()];
18734
- if (normalized === void 0) {
19447
+ const normalized2 = CONSTRAINT_LABEL_MAP[label.toUpperCase()];
19448
+ if (normalized2 === void 0) {
18735
19449
  reporter.warn(
18736
19450
  `[parseFromBackend] unknown constraint label "${label}" \u2014 defaulting to "asap"`
18737
19451
  );
18738
19452
  return DEFAULT_CONSTRAINT_TYPE;
18739
19453
  }
18740
- return normalized;
19454
+ return normalized2;
18741
19455
  }
18742
19456
  function resolveConstraint(raw, reporter) {
18743
19457
  const constraintType = parseConstraintLabel(raw.constraint, reporter);
@@ -18778,6 +19492,24 @@ function parseIdList(raw) {
18778
19492
  function parseParentId(rawParentId) {
18779
19493
  return rawParentId === ROOT_PARENT_ID ? null : String(rawParentId);
18780
19494
  }
19495
+ function normalizePersistedActivityId(rawId) {
19496
+ if (rawId == null) return null;
19497
+ const isValidIdentity = typeof rawId === "number" && (rawId === 0 || Number.isSafeInteger(rawId) && rawId > 0);
19498
+ if (!isValidIdentity) {
19499
+ throw new Error(`invalid activity proplannerId ${String(rawId)}`);
19500
+ }
19501
+ return rawId;
19502
+ }
19503
+ function parseCriticality(raw, reporter) {
19504
+ if (typeof raw.is_critical === "boolean") return raw.is_critical;
19505
+ reporter.error(
19506
+ "[parseFromBackend] invalid activity is_critical \u2014 defaulting to false",
19507
+ new TypeError(
19508
+ `activity "${raw.unique_id}" received ${typeof raw.is_critical}`
19509
+ )
19510
+ );
19511
+ return false;
19512
+ }
18781
19513
  function parseBaselinePoints(raw, reporter) {
18782
19514
  if (!Array.isArray(raw)) return [];
18783
19515
  return raw.map((point) => normalizeBaselinePoint(point, reporter)).filter((point) => point !== null);
@@ -18798,9 +19530,10 @@ function parseActivity(raw, context) {
18798
19530
  const startDate = parseBackendDate(raw.start_date);
18799
19531
  const endDate = isMilestone ? startDate : parseBackendDate(raw.end_date);
18800
19532
  const durationDays = isMilestone ? 0 : Number(raw.duration);
19533
+ const isCritical = parseCriticality(raw, context.reporter);
18801
19534
  return {
18802
19535
  id: String(raw.unique_id),
18803
- proplannerId: typeof raw.id === "number" ? raw.id : null,
19536
+ proplannerId: normalizePersistedActivityId(raw.id),
18804
19537
  parentId: parseParentId(raw.parent_id),
18805
19538
  name: raw.name,
18806
19539
  description: raw.description ?? "",
@@ -18845,7 +19578,7 @@ function parseActivity(raw, context) {
18845
19578
  startDate,
18846
19579
  endDate
18847
19580
  }),
18848
- isCritical: Boolean(raw.is_critical),
19581
+ isCritical,
18849
19582
  freeSlackHours: null,
18850
19583
  expectedProgress: null,
18851
19584
  expectedProgressBaseline: null,
@@ -19102,6 +19835,14 @@ function resolveLinkType(rawType) {
19102
19835
  if (isLinkTypeCode(rawType)) return rawType;
19103
19836
  return null;
19104
19837
  }
19838
+ function normalizePersistedLinkId(rawId) {
19839
+ if (rawId == null) return void 0;
19840
+ const isValidIdentity = typeof rawId === "number" && (rawId === 0 || Number.isSafeInteger(rawId) && rawId > 0);
19841
+ if (!isValidIdentity) {
19842
+ throw new Error(`invalid link proplannerId ${String(rawId)}`);
19843
+ }
19844
+ return rawId;
19845
+ }
19105
19846
  function normalizeLinks(rawLinks, context) {
19106
19847
  const parsedLinks = [];
19107
19848
  for (const rawLink of rawLinks) {
@@ -19140,16 +19881,18 @@ function parseLink(link, activityIds, hoursPerDay) {
19140
19881
  }
19141
19882
  const ganttIdRaw = link.ganttId;
19142
19883
  const sectorIdRaw = link.sectorId;
19143
- const proplannerIdRaw = link.id;
19884
+ const proplannerId = normalizePersistedLinkId(link.id);
19144
19885
  return {
19145
19886
  id,
19146
19887
  source,
19147
19888
  target,
19148
19889
  type,
19149
19890
  lag: lagDaysToHours(Number(link.lag ?? 0), hoursPerDay),
19891
+ freeSlackHours: null,
19892
+ isCritical: false,
19150
19893
  ...typeof ganttIdRaw === "number" ? { ganttId: ganttIdRaw } : {},
19151
19894
  ...typeof sectorIdRaw === "number" ? { sectorId: sectorIdRaw } : {},
19152
- ...typeof proplannerIdRaw === "number" ? { proplannerId: proplannerIdRaw } : {}
19895
+ ...proplannerId === void 0 ? {} : { proplannerId }
19153
19896
  };
19154
19897
  }
19155
19898
  function dedupeLinksBySignature(links) {
@@ -19276,6 +20019,9 @@ function createActivityIdGenerator(options = {}) {
19276
20019
  }
19277
20020
  }
19278
20021
  return {
20022
+ fork() {
20023
+ return createActivityIdGenerator({ seed: counter });
20024
+ },
19279
20025
  next() {
19280
20026
  counter += 1;
19281
20027
  return String(counter);
@@ -19307,6 +20053,9 @@ function createLinkIdGenerator(options = {}) {
19307
20053
  }
19308
20054
  }
19309
20055
  return {
20056
+ fork() {
20057
+ return createLinkIdGenerator({ seed: counter });
20058
+ },
19310
20059
  next() {
19311
20060
  counter += 1;
19312
20061
  return String(counter);
@@ -19342,6 +20091,9 @@ function createUniqueCorrelativeIdGenerator(options = {}) {
19342
20091
  }
19343
20092
  }
19344
20093
  return {
20094
+ fork() {
20095
+ return createUniqueCorrelativeIdGenerator({ seed: counter });
20096
+ },
19345
20097
  next() {
19346
20098
  counter += 1;
19347
20099
  return String(counter);
@@ -19469,7 +20221,7 @@ function selectGenerationStrategy(customId, useActivityCustomIdAsReference) {
19469
20221
  function cloneStats(stats) {
19470
20222
  return { ...stats, usedSuffixes: new Set(stats.usedSuffixes) };
19471
20223
  }
19472
- var CustomIdTracker = class {
20224
+ var CustomIdTracker = class _CustomIdTracker {
19473
20225
  prefixMap = /* @__PURE__ */ new Map();
19474
20226
  processedIds = /* @__PURE__ */ new Set();
19475
20227
  _journalPrefixBefore = null;
@@ -19530,6 +20282,19 @@ var CustomIdTracker = class {
19530
20282
  this.defaultPrefix = options.defaultPrefix ?? DEFAULT_PREFIX;
19531
20283
  this.suffixIncrement = options.suffixIncrement ?? DEFAULT_SUFFIX_INCREMENT;
19532
20284
  }
20285
+ fork() {
20286
+ const fork = new _CustomIdTracker({
20287
+ defaultPrefix: this.defaultPrefix,
20288
+ suffixIncrement: this.suffixIncrement
20289
+ });
20290
+ for (const [prefix, stats] of this.prefixMap) {
20291
+ fork.prefixMap.set(prefix, cloneStats(stats));
20292
+ }
20293
+ for (const customId of this.processedIds) {
20294
+ fork.processedIds.add(customId);
20295
+ }
20296
+ return fork;
20297
+ }
19533
20298
  extractPrefix(customId) {
19534
20299
  return extractPrefix(customId);
19535
20300
  }
@@ -20136,6 +20901,133 @@ function initializeCore(input) {
20136
20901
  };
20137
20902
  }
20138
20903
 
20904
+ // src/previsualization/canonical-digest.ts
20905
+ function normalized(value) {
20906
+ if (value === void 0) return ["undefined"];
20907
+ if (value === null) return ["null"];
20908
+ if (typeof value === "string") return ["string", value];
20909
+ if (typeof value === "boolean") return ["boolean", value];
20910
+ if (typeof value === "number") {
20911
+ return ["number", Object.is(value, -0) ? "-0" : String(value)];
20912
+ }
20913
+ if (typeof value === "bigint") return ["bigint", String(value)];
20914
+ if (value instanceof Date) return ["date", String(value.getTime())];
20915
+ if (Array.isArray(value)) return ["array", value.map(normalized)];
20916
+ if (value instanceof Map) {
20917
+ return [
20918
+ "map",
20919
+ Array.from(value, ([key, entry]) => [
20920
+ normalized(key),
20921
+ normalized(entry)
20922
+ ]).sort(
20923
+ (left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right))
20924
+ )
20925
+ ];
20926
+ }
20927
+ if (value instanceof Set) {
20928
+ return [
20929
+ "set",
20930
+ Array.from(value, normalized).sort(
20931
+ (left, right) => JSON.stringify(left).localeCompare(JSON.stringify(right))
20932
+ )
20933
+ ];
20934
+ }
20935
+ if (typeof value === "object") {
20936
+ return [
20937
+ "object",
20938
+ Object.entries(value).sort(([left], [right]) => left.localeCompare(right)).map(([key, entry]) => [key, normalized(entry)])
20939
+ ];
20940
+ }
20941
+ throw new TypeError(`Unsupported canonical value: ${typeof value}`);
20942
+ }
20943
+ function serializeCanonicalSchedule(value) {
20944
+ return JSON.stringify(normalized(value));
20945
+ }
20946
+
20947
+ // src/previsualization/reconciliation.ts
20948
+ function entityChanges(target, source) {
20949
+ const targetById = new Map(
20950
+ target.map((entity) => [String(entity.id), entity])
20951
+ );
20952
+ const sourceById = new Map(
20953
+ source.map((entity) => [String(entity.id), entity])
20954
+ );
20955
+ const ids = [.../* @__PURE__ */ new Set([...targetById.keys(), ...sourceById.keys()])].sort();
20956
+ const changes = [];
20957
+ for (const id of ids) {
20958
+ const before = sourceById.get(id);
20959
+ const after = targetById.get(id);
20960
+ if (after === void 0) {
20961
+ changes.push({ id, kind: "deleted", after: null });
20962
+ continue;
20963
+ }
20964
+ if (before === void 0) {
20965
+ changes.push({ id, kind: "created", after: cloneDomainValue(after) });
20966
+ continue;
20967
+ }
20968
+ const fields = changedFields(before, after);
20969
+ if (Object.keys(fields).length > 0) {
20970
+ changes.push({
20971
+ id,
20972
+ kind: "updated",
20973
+ fields,
20974
+ after: cloneDomainValue(after)
20975
+ });
20976
+ }
20977
+ }
20978
+ return changes;
20979
+ }
20980
+ function changedFields(before, after) {
20981
+ const fields = {};
20982
+ const keys = [
20983
+ .../* @__PURE__ */ new Set([...Object.keys(before), ...Object.keys(after)])
20984
+ ].sort();
20985
+ for (const key of keys) {
20986
+ if (serializeCanonicalSchedule(before[key]) !== serializeCanonicalSchedule(after[key])) {
20987
+ fields[key] = {
20988
+ before: cloneDomainValue(before[key]),
20989
+ after: cloneDomainValue(after[key])
20990
+ };
20991
+ }
20992
+ }
20993
+ return fields;
20994
+ }
20995
+ function viewStateChanges(target, source) {
20996
+ const selectedBefore = new Set(source.selectedIds.map(String));
20997
+ const selectedAfter = new Set(target.selectedIds.map(String));
20998
+ const hiddenBefore = new Set(source.hiddenIds.map(String));
20999
+ const hiddenAfter = new Set(target.hiddenIds.map(String));
21000
+ const targetIds = target.activities.map(({ id }) => String(id)).sort();
21001
+ const changes = [];
21002
+ for (const activityId of targetIds) {
21003
+ const checkedBefore = selectedBefore.has(activityId);
21004
+ const checkedAfter = selectedAfter.has(activityId);
21005
+ const visibleBefore = !hiddenBefore.has(activityId);
21006
+ const visibleAfter = !hiddenAfter.has(activityId);
21007
+ if (checkedBefore === checkedAfter && visibleBefore === visibleAfter)
21008
+ continue;
21009
+ changes.push({
21010
+ activityId,
21011
+ ...checkedBefore === checkedAfter ? {} : { checked: { before: checkedBefore, after: checkedAfter } },
21012
+ ...visibleBefore === visibleAfter ? {} : { visible: { before: visibleBefore, after: visibleAfter } }
21013
+ });
21014
+ }
21015
+ return changes;
21016
+ }
21017
+ function createProjectionChangeSet(target, source) {
21018
+ const orderChanged = serializeCanonicalSchedule(target.order) !== serializeCanonicalSchedule(source.order);
21019
+ return {
21020
+ source: { kind: "init" },
21021
+ activities: entityChanges(target.activities, source.activities),
21022
+ links: entityChanges(target.links, source.links),
21023
+ calendars: entityChanges(target.calendars, source.calendars),
21024
+ trackingEvents: [],
21025
+ effects: [],
21026
+ viewState: viewStateChanges(target, source),
21027
+ ...orderChanged ? { order: cloneDomainValue(target.order) } : {}
21028
+ };
21029
+ }
21030
+
20139
21031
  // src/propagations/upward/date-gesture-preview.ts
20140
21032
  var emptyFormula = () => ({
20141
21033
  constantStart: null,
@@ -20281,7 +21173,10 @@ function evaluateEnd(formula, proposedEdgeTimeMs) {
20281
21173
  }
20282
21174
 
20283
21175
  // src/init/schedule-core.ts
20284
- var PREVIEW_DISPATCH_KINDS = /* @__PURE__ */ new Set(["inline-edit", "bulk-edit", "dates-batch"]);
21176
+ var FORK_SOURCE = /* @__PURE__ */ Symbol("ScheduleCore.fork.source");
21177
+ function acknowledgesPersistence(action) {
21178
+ return action.kind === "persistence-acknowledge" || action.kind === "activity-lookahead-sync" && action.acknowledgePersistedLookahead === true;
21179
+ }
20285
21180
  function changesRequireFilterProjectionRebuild(changes) {
20286
21181
  for (const change of changes.activities) {
20287
21182
  if (change.kind === "deleted") return true;
@@ -20299,16 +21194,46 @@ function collectChangedActivities(changes, adapter) {
20299
21194
  }
20300
21195
  return activities;
20301
21196
  }
21197
+ function changesAffectRelativePinProjection(changes, changedActivities, adapter, pins) {
21198
+ if (pins.size === 0) return false;
21199
+ if (changes.activities.some(
21200
+ (change) => change.kind === "created" && pins.has(String(change.id))
21201
+ )) {
21202
+ return true;
21203
+ }
21204
+ const referenceIds = new Set(
21205
+ [...pins.values()].map(({ referenceId }) => String(referenceId))
21206
+ );
21207
+ for (const activity of changedActivities) {
21208
+ let activityId = String(activity.id);
21209
+ while (activityId !== null) {
21210
+ if (referenceIds.has(activityId)) return true;
21211
+ activityId = adapter.getParentId(activityId);
21212
+ }
21213
+ }
21214
+ return false;
21215
+ }
20302
21216
  function isCurrentFilterProjection(projection, filter, context) {
20303
21217
  if (projection === null || projection.filter !== filter) return false;
20304
21218
  const hasCurrentHours = projection.context.hoursPerDay === context.hoursPerDay;
20305
21219
  const hasCurrentLocale = projection.context.locale === context.locale;
20306
21220
  return hasCurrentHours && hasCurrentLocale;
20307
21221
  }
20308
- var ScheduleCore = class {
21222
+ function relativePlacementPinsEqual(left, right) {
21223
+ if (left.size !== right.size) return false;
21224
+ for (const [activityId, pin] of left) {
21225
+ const other = right.get(activityId);
21226
+ if (other === void 0 || other.referenceId !== pin.referenceId || other.branchParentId !== pin.branchParentId || other.side !== pin.side) {
21227
+ return false;
21228
+ }
21229
+ }
21230
+ return true;
21231
+ }
21232
+ var ScheduleCore = class _ScheduleCore {
20309
21233
  _activityClipboard = { value: null };
20310
21234
  _status = SCHEDULE_CORE_STATUS.READY;
20311
21235
  coreRuntime;
21236
+ _isFork;
20312
21237
  _undo = new UndoRecorder();
20313
21238
  _opQueue = Promise.resolve();
20314
21239
  _scheduleRevision = 0;
@@ -20339,6 +21264,42 @@ var ScheduleCore = class {
20339
21264
  _criticalPathRevision = -1;
20340
21265
  _activeCriticalPath = null;
20341
21266
  constructor(input) {
21267
+ if (FORK_SOURCE in input) {
21268
+ this._isFork = true;
21269
+ const source = input[FORK_SOURCE];
21270
+ const state = source.coreRuntime.state.fork();
21271
+ this.coreRuntime = {
21272
+ state,
21273
+ scheduler: new AutoScheduler(state, source.coreRuntime.reporter),
21274
+ sector: cloneDomainValue(source.coreRuntime.sector),
21275
+ activityIdGenerator: source.coreRuntime.activityIdGenerator.fork(),
21276
+ linkIdGenerator: source.coreRuntime.linkIdGenerator.fork(),
21277
+ uniqueCorrelativeIdGenerator: source.coreRuntime.uniqueCorrelativeIdGenerator.fork(),
21278
+ customIdTracker: source.coreRuntime.customIdTracker.fork(),
21279
+ calendars: new Map(
21280
+ Array.from(source.coreRuntime.calendars, ([id, calendar]) => [
21281
+ id,
21282
+ cloneDomainValue(calendar)
21283
+ ])
21284
+ ),
21285
+ baseCalendars: cloneDomainValue(source.coreRuntime.baseCalendars),
21286
+ clock: source.coreRuntime.clock,
21287
+ reporter: source.coreRuntime.reporter
21288
+ };
21289
+ this._activityClipboard.value = cloneDomainValue(
21290
+ source._activityClipboard.value
21291
+ );
21292
+ this._undo = source._undo.fork();
21293
+ this._saveTracker = source._saveTracker.fork();
21294
+ this._scheduleRevision = source._scheduleRevision;
21295
+ this._pendingScheduleRevision = source._scheduleRevision;
21296
+ this._criticalPathRevision = source._criticalPathRevision;
21297
+ this._initPromise = Promise.resolve();
21298
+ this._criticalPathReady = Promise.resolve(null);
21299
+ this._ready = this._initPromise;
21300
+ return;
21301
+ }
21302
+ this._isFork = false;
20342
21303
  const initialized = initializeCore(input);
20343
21304
  this.coreRuntime = {
20344
21305
  state: initialized.state,
@@ -20375,6 +21336,15 @@ var ScheduleCore = class {
20375
21336
  get ready() {
20376
21337
  return this._ready;
20377
21338
  }
21339
+ fork() {
21340
+ return this._enqueue(async () => {
21341
+ await this._ready;
21342
+ this.assertReady();
21343
+ return new _ScheduleCore({
21344
+ [FORK_SOURCE]: this
21345
+ });
21346
+ });
21347
+ }
20378
21348
  get criticalPathReady() {
20379
21349
  return this._criticalPathReady;
20380
21350
  }
@@ -20427,6 +21397,21 @@ var ScheduleCore = class {
20427
21397
  this.assertReady();
20428
21398
  return readLink(this.coreRuntime.state, id);
20429
21399
  }
21400
+ getLinksForActivityView(activityId) {
21401
+ this.assertReady();
21402
+ const state = this.coreRuntime.state;
21403
+ const links = [];
21404
+ const seen = /* @__PURE__ */ new Set();
21405
+ const append = (linkId) => {
21406
+ if (seen.has(linkId)) return;
21407
+ seen.add(linkId);
21408
+ const link = readLink(state, linkId);
21409
+ if (link) links.push(link);
21410
+ };
21411
+ for (const linkId of state.getOutgoingLinkIds(activityId)) append(linkId);
21412
+ for (const linkId of state.getIncomingLinkIds(activityId)) append(linkId);
21413
+ return links;
21414
+ }
20430
21415
  getAllLinksView() {
20431
21416
  this.assertReady();
20432
21417
  return readAllLinks(this.coreRuntime.state);
@@ -20524,91 +21509,65 @@ var ScheduleCore = class {
20524
21509
  }
20525
21510
  getModifiedLinks() {
20526
21511
  this.assertReady();
20527
- return this._saveTracker.modifiedLinks(this.getAllLinksView());
21512
+ return this._saveTracker.modifiedLinks((linkId) => this.coreRuntime.state.getLink(linkId)).map(cloneDomainValue);
21513
+ }
21514
+ getNewLinks() {
21515
+ this.assertReady();
21516
+ return this._saveTracker.newLinks((linkId) => this.coreRuntime.state.getLink(linkId)).map(cloneDomainValue);
20528
21517
  }
20529
21518
  getModifiedActivities() {
20530
21519
  this.assertReady();
20531
- return this._saveTracker.modifiedActivities(this.coreRuntime.state.getAllActivities()).map(cloneDomainValue);
21520
+ return this._saveTracker.modifiedActivities(
21521
+ (activityId) => this.coreRuntime.state.getActivity(activityId)
21522
+ ).map(cloneDomainValue);
21523
+ }
21524
+ getNewActivities() {
21525
+ this.assertReady();
21526
+ return this._saveTracker.newActivities(
21527
+ (activityId) => this.coreRuntime.state.getActivity(activityId)
21528
+ ).map(cloneDomainValue);
20532
21529
  }
20533
21530
  hasUnsavedChanges() {
20534
21531
  this.assertReady();
20535
21532
  return this._saveTracker.hasUnsavedChanges();
20536
21533
  }
20537
- /**
20538
- * Read-only view of the internal schedule revision. It moves once per
20539
- * substantive CANONICAL mutation (dispatch, undo, redo). It does NOT move
20540
- * for a preview dispatch nor for `applyPreviewInverse`: comparing the value
20541
- * captured in `PreviewDispatchInfo.revision` against the current one
20542
- * answers "did the schedule change while the preview was open".
20543
- */
21534
+ /** Read-only revision; moves once per substantive mutation. */
20544
21535
  getScheduleRevision() {
20545
21536
  return this._scheduleRevision;
20546
21537
  }
20547
- /**
20548
- * Exit channel for a preview dispatch (`DispatchOptions.preview`): applies
20549
- * the inverse ChangeSet the preview returned, restoring the canonical
20550
- * state exactly. Like the preview itself it leaves no session trace: no
20551
- * save-tracker dirty, no undo entry, no effects, and the schedule revision
20552
- * does not move (see `getScheduleRevision`).
20553
- *
20554
- * Returns the ChangeSet to project to the view layer: the same changes as
20555
- * `inverse`, but with `after` images re-read from the restored state (the
20556
- * ones inside `inverse` were captured while the preview edit was applied).
20557
- */
20558
- applyPreviewInverse(inverse) {
20559
- return this._enqueue(async () => {
20560
- this.assertReady();
20561
- const state = this.coreRuntime.state;
20562
- const afterSnap = /* @__PURE__ */ new Map();
20563
- for (const change of inverse.activities) {
20564
- if (change.kind !== "deleted" && change.after) {
20565
- afterSnap.set(
20566
- String(change.id),
20567
- structuredCloneActivity(change.after)
20568
- );
20569
- }
20570
- }
20571
- const afterLinks = /* @__PURE__ */ new Map();
20572
- for (const change of inverse.links) {
20573
- if (change.kind !== "deleted" && change.after) {
20574
- afterLinks.set(String(change.id), { ...change.after });
20575
- }
20576
- }
20577
- const entry = buildUndoEntry(
20578
- inverse,
20579
- void 0,
20580
- void 0,
20581
- afterSnap,
20582
- afterLinks
20583
- );
20584
- applyRedo(state, entry);
20585
- if (needsResync(entry)) this._resyncCustomIdTrackerFromModel();
20586
- const restored = {
20587
- ...inverse,
20588
- activities: inverse.activities.map((change) => {
20589
- if (change.kind === "deleted") return { ...change, after: null };
20590
- const live = state.getActivity(String(change.id));
20591
- return {
20592
- ...change,
20593
- after: live ? structuredCloneActivity(live) : null
20594
- };
20595
- }),
20596
- links: inverse.links.map((change) => {
20597
- if (change.kind === "deleted") return { ...change, after: null };
20598
- const live = state.getLink(String(change.id));
20599
- return { ...change, after: live ? snapshotToLink(live) : null };
20600
- })
20601
- };
20602
- return this._reapplyViewState(restored) ?? restored;
21538
+ getCanonicalDigest() {
21539
+ this.assertReady();
21540
+ const byId = (left, right) => String(left.id).localeCompare(String(right.id));
21541
+ return serializeCanonicalSchedule({
21542
+ sector: this.getSector(),
21543
+ activities: [...this.getAllActivitiesView()].sort(byId),
21544
+ links: [...this.getAllLinksView()].sort(byId),
21545
+ calendars: [...this.getAllCalendars()].sort(byId),
21546
+ baseCalendars: [...this.getBaseCalendars()].sort(byId)
21547
+ });
21548
+ }
21549
+ createProjectionChangeSetFrom(source) {
21550
+ this.assertReady();
21551
+ source.assertReady();
21552
+ const projectionOf = (core) => ({
21553
+ activities: core.getAllActivitiesView(),
21554
+ links: core.getAllLinksView(),
21555
+ calendars: core.getAllCalendars(),
21556
+ selectedIds: core.getSelectedActivityIds(),
21557
+ hiddenIds: core.getHiddenActivityIds(),
21558
+ order: collectBranchOrder(core.coreRuntime.state)
20603
21559
  });
21560
+ return createProjectionChangeSet(projectionOf(this), projectionOf(source));
20604
21561
  }
20605
21562
  getDeletedActivitiesSinceLastSave() {
20606
21563
  this.assertReady();
20607
- return this._saveTracker.deletedActivities(this.coreRuntime.state.getAllActivities()).map(cloneDomainValue);
21564
+ return this._saveTracker.deletedActivities(
21565
+ (activityId) => this.coreRuntime.state.getActivity(activityId)
21566
+ ).map(cloneDomainValue);
20608
21567
  }
20609
21568
  getDeletedLinksSinceLastSave() {
20610
21569
  this.assertReady();
20611
- return this._saveTracker.deletedLinks(this.getAllLinksView()).map(cloneDomainValue);
21570
+ return this._saveTracker.deletedLinks((linkId) => this.coreRuntime.state.getLink(linkId)).map(cloneDomainValue);
20612
21571
  }
20613
21572
  _enqueue(work) {
20614
21573
  const workPromise = this._opQueue.then(work, work);
@@ -20684,11 +21643,11 @@ var ScheduleCore = class {
20684
21643
  });
20685
21644
  }
20686
21645
  async dispatch(action, options = {}) {
20687
- const admitted = options.preview === true ? false : this._beginScheduleMutation(action);
21646
+ const admitted = this._beginScheduleMutation(action);
20688
21647
  return this._enqueue(() => this._dispatchInner(action, options, admitted));
20689
21648
  }
20690
21649
  async applyActivityBatch(action, options = {}) {
20691
- const admitted = options.preview === true ? false : this._beginScheduleMutation(action);
21650
+ const admitted = this._beginScheduleMutation(action);
20692
21651
  return this._enqueue(() => this._dispatchInner(action, options, admitted));
20693
21652
  }
20694
21653
  async _dispatchInner(action, options = {}, admitted = false) {
@@ -20703,23 +21662,22 @@ var ScheduleCore = class {
20703
21662
  }
20704
21663
  async _runDispatch(action, options) {
20705
21664
  this.assertReady();
20706
- if (options.preview === true && !PREVIEW_DISPATCH_KINDS.has(action.kind)) {
21665
+ if (this._isFork && acknowledgesPersistence(action)) {
20707
21666
  return {
20708
21667
  result: {
20709
21668
  ok: false,
20710
- reason: REJECTION_REASON.PREVIEW_UNSUPPORTED_ACTION
21669
+ reason: REJECTION_REASON.SANDBOX_PERSISTENCE_FORBIDDEN
20711
21670
  },
20712
21671
  changedState: false
20713
21672
  };
20714
21673
  }
20715
21674
  const pipelineContext = buildPipelineContext(this.coreRuntime.state, {
20716
21675
  hoursPerDay: this.coreRuntime.sector.hoursPerDay,
20717
- dateFormat: this.coreRuntime.sector.dateFormat,
20718
- // ISSUE-057 (CP6): sin default silencioso 'days'; la clave se omite si
20719
- // no se declaró (exactOptionalPropertyTypes).
20720
- ...options.inputUnit !== void 0 ? { inputUnit: options.inputUnit } : {}
21676
+ dateFormat: this.coreRuntime.sector.dateFormat
20721
21677
  });
20722
- const historyPolicy = getDispatchHistoryPolicy(action, options);
21678
+ const historyPolicy = getDispatchHistoryPolicy(action);
21679
+ const capturesRelativePlacementPins = action.kind === "activity-create" || action.kind === "activity-delete" || action.kind === "activity-batch" && action.mode === "replace";
21680
+ const relativePlacementPinsBefore = capturesRelativePlacementPins ? new Map(this.coreRuntime.state.getRelativePlacementPins()) : void 0;
20723
21681
  const generatorSnapshot = [
20724
21682
  this.coreRuntime.activityIdGenerator.snapshot(),
20725
21683
  this.coreRuntime.linkIdGenerator.snapshot(),
@@ -20737,6 +21695,8 @@ var ScheduleCore = class {
20737
21695
  calendars: this.coreRuntime.calendars,
20738
21696
  activityIdGen: this.coreRuntime.activityIdGenerator,
20739
21697
  linkIdGen: this.coreRuntime.linkIdGenerator,
21698
+ isActivityIdReserved: (activityId) => this._saveTracker.hasCheckpointActivityId(activityId),
21699
+ isLinkIdReserved: (linkId) => this._saveTracker.hasCheckpointLinkId(linkId),
20740
21700
  uidGen: this.coreRuntime.uniqueCorrelativeIdGenerator,
20741
21701
  customIdTracker: this.coreRuntime.customIdTracker,
20742
21702
  reporter: this.coreRuntime.reporter,
@@ -20754,18 +21714,26 @@ var ScheduleCore = class {
20754
21714
  this.coreRuntime.state.endWriteCapture();
20755
21715
  this.coreRuntime.customIdTracker.commitCustomIdTransaction();
20756
21716
  }
21717
+ if (result.ok) this._recordRelativePlacementPin(action, result.changes);
20757
21718
  if (result.ok && historyPolicy === "record" && changeSetIsSubstantive(result.changes)) {
20758
21719
  const created = captureCreatedSnapshots(
20759
21720
  this.coreRuntime.state,
20760
21721
  result.changes
20761
21722
  );
21723
+ const relativePlacementPinsAfter = capturesRelativePlacementPins ? new Map(this.coreRuntime.state.getRelativePlacementPins()) : void 0;
21724
+ const relativePlacementPinsChanged = relativePlacementPinsBefore !== void 0 && relativePlacementPinsAfter !== void 0 && !relativePlacementPinsEqual(
21725
+ relativePlacementPinsBefore,
21726
+ relativePlacementPinsAfter
21727
+ );
20762
21728
  this._undo.record(
20763
21729
  buildUndoEntry(
20764
21730
  buildHistoryChangeSet(result.changes),
20765
21731
  result.__beforeSnap,
20766
21732
  result.__beforeLinks,
20767
21733
  created.afterSnap,
20768
- created.afterLinks
21734
+ created.afterLinks,
21735
+ relativePlacementPinsChanged ? relativePlacementPinsBefore : void 0,
21736
+ relativePlacementPinsChanged ? relativePlacementPinsAfter : void 0
20769
21737
  ),
20770
21738
  computeCoalesceKey(action),
20771
21739
  Date.now()
@@ -20773,17 +21741,20 @@ var ScheduleCore = class {
20773
21741
  }
20774
21742
  if (result.ok) {
20775
21743
  if (historyPolicy === "clear-on-success") {
20776
- this._saveTracker.snapshotActivities(
20777
- this.coreRuntime.state.getAllActivities()
21744
+ this._saveTracker.acknowledge(
21745
+ this.coreRuntime.state.getAllActivities(),
21746
+ this.coreRuntime.state.getAllLinks()
20778
21747
  );
20779
- this._saveTracker.snapshotLinks(this.getAllLinksView());
20780
21748
  this._undo.clear();
20781
21749
  } else if (action.kind === "activity-lookahead-sync" && action.acknowledgePersistedLookahead === true) {
20782
21750
  this._saveTracker.acknowledgePersistedLookahead(
20783
- action.activityIds,
21751
+ result.changes.activities,
20784
21752
  (activityId) => this.coreRuntime.state.getActivity(activityId)
20785
21753
  );
20786
- } else if (options.preview !== true) {
21754
+ if (action.persistedProgress !== void 0) {
21755
+ this._undo.clear();
21756
+ }
21757
+ } else {
20787
21758
  this._saveTracker.recordChanges(
20788
21759
  result.changes.activities,
20789
21760
  result.changes.links,
@@ -20794,20 +21765,6 @@ var ScheduleCore = class {
20794
21765
  }
20795
21766
  result = this._withReappliedViewState(action, result);
20796
21767
  if (!result.ok) return { result, changedState: false };
20797
- if (options.preview === true) {
20798
- const inverse = buildInverseChangeSet(
20799
- this.coreRuntime.state,
20800
- buildUndoEntry(buildHistoryChangeSet(result.changes)),
20801
- "before"
20802
- );
20803
- return {
20804
- result: {
20805
- ...toPublicDispatchResult(result),
20806
- preview: { inverse, revision: this._scheduleRevision }
20807
- },
20808
- changedState: false
20809
- };
20810
- }
20811
21768
  if (dispatchChangesSchedulingState(action) && changeSetIsSubstantive(result.changes)) {
20812
21769
  this._recordScheduleMutation(
20813
21770
  options.skipAutoSchedule !== true && options.skipCriticalPath !== true && resolveRunCriticalPath(action)
@@ -20844,6 +21801,19 @@ var ScheduleCore = class {
20844
21801
  const merged = this._reapplyViewState(result.changes);
20845
21802
  return merged === null ? result : { ...result, changes: merged };
20846
21803
  }
21804
+ _recordRelativePlacementPin(action, changes) {
21805
+ const state = this.coreRuntime.state;
21806
+ if (action.kind !== "activity-create") return;
21807
+ const referenceId = action.afterSiblingId ?? action.beforeSiblingId ?? (action.eventSource === "Creation button (+)" && action.parentId !== ROOT_PARENT_ID ? action.parentId : void 0);
21808
+ if (referenceId === void 0) return;
21809
+ const created = changes.activities.find(({ kind }) => kind === "created");
21810
+ if (created === void 0) return;
21811
+ state.setRelativePlacementPin(created.id, {
21812
+ referenceId,
21813
+ branchParentId: action.parentId,
21814
+ side: action.beforeSiblingId === void 0 ? "after" : "before"
21815
+ });
21816
+ }
20847
21817
  /**
20848
21818
  * The single place the two view-state passes are chained. Dispatch, undo and
20849
21819
  * redo all route through here: when this existed only inside the dispatch
@@ -20866,39 +21836,7 @@ var ScheduleCore = class {
20866
21836
  sequenced.changes ?? withFilter ?? changes,
20867
21837
  sequenced.touchedParents
20868
21838
  );
20869
- const reapplied = withOrder ?? sequenced.changes ?? withFilter;
20870
- const finalChanges = reapplied ?? changes;
20871
- const createdIds = finalChanges.activities.filter((change) => change.kind === "created").map((change) => String(change.id));
20872
- if (createdIds.length === 0) return reapplied;
20873
- const createdAndAncestors = new Set(createdIds);
20874
- const filterProjection = this.coreRuntime.state.getFilterProjection();
20875
- for (const createdId of createdIds) {
20876
- if (filterProjection !== null) {
20877
- filterProjection.pinnedVisibleIds.add(createdId);
20878
- }
20879
- let activityId = createdId;
20880
- while (activityId !== null && activityId !== ROOT_PARENT_ID) {
20881
- createdAndAncestors.add(activityId);
20882
- if (filterProjection !== null) {
20883
- filterProjection.visibleIds.add(activityId);
20884
- filterProjection.visibleReferenceCounts.set(
20885
- activityId,
20886
- (filterProjection.visibleReferenceCounts.get(activityId) ?? 0) + 1
20887
- );
20888
- }
20889
- activityId = this.coreRuntime.state.getParentId(activityId);
20890
- }
20891
- }
20892
- const createdVisibility = applyVisibleSet(
20893
- this.coreRuntime.state,
20894
- createdAndAncestors,
20895
- createdAndAncestors
20896
- );
20897
- if (createdVisibility.length === 0) return reapplied;
20898
- return {
20899
- ...finalChanges,
20900
- viewState: [...finalChanges.viewState ?? [], ...createdVisibility]
20901
- };
21839
+ return withOrder ?? sequenced.changes ?? withFilter;
20902
21840
  }
20903
21841
  /**
20904
21842
  * Emits `order` for the branches this mutation resequenced, when no user order
@@ -20977,16 +21915,22 @@ var ScheduleCore = class {
20977
21915
  context
20978
21916
  );
20979
21917
  const changedActivities = changesRequireFilterProjectionRebuild(changes) ? null : collectChangedActivities(changes, adapter);
21918
+ const relativePlacementPins = adapter.getRelativePlacementPins();
20980
21919
  let affectedIds;
20981
21920
  let visibleIds;
20982
- const needsProjectionRebuild = changedActivities === null || !projectionIsCurrent;
21921
+ const needsProjectionRebuild = changedActivities === null || !projectionIsCurrent || changesAffectRelativePinProjection(
21922
+ changes,
21923
+ changedActivities,
21924
+ adapter,
21925
+ relativePlacementPins
21926
+ );
20983
21927
  if (needsProjectionRebuild) {
20984
21928
  const rebuiltProjection = buildFilterProjection({
20985
21929
  activities: adapter.getAllActivities(),
20986
21930
  parentOf: (activityId) => adapter.getParentId(activityId),
20987
21931
  filter,
20988
21932
  context,
20989
- ...projectionIsCurrent ? { pinnedVisibleIds: projection.pinnedVisibleIds } : {}
21933
+ relativePlacementPins
20990
21934
  });
20991
21935
  adapter.setFilterProjection(rebuiltProjection);
20992
21936
  visibleIds = rebuiltProjection.visibleIds;
@@ -21060,13 +22004,20 @@ var ScheduleCore = class {
21060
22004
  this.coreRuntime.customIdTracker.beginCustomIdTransaction();
21061
22005
  let changes;
21062
22006
  try {
21063
- for (const [activityId, fields] of calculatedFields) {
22007
+ for (const [activityId, fields] of calculatedFields.activities) {
21064
22008
  setCriticalPathFieldsIfChanged(
21065
22009
  this.coreRuntime.state,
21066
22010
  activityId,
21067
22011
  fields
21068
22012
  );
21069
22013
  }
22014
+ for (const [linkId, fields] of calculatedFields.links) {
22015
+ setCriticalPathLinkFieldsIfChanged(
22016
+ this.coreRuntime.state,
22017
+ linkId,
22018
+ fields
22019
+ );
22020
+ }
21070
22021
  changes = await assembleChangeSet(this.coreRuntime.state, {
21071
22022
  source: { kind: "init" },
21072
22023
  beforeSnap: /* @__PURE__ */ new Map(),
@@ -21348,7 +22299,6 @@ var DISPATCH_ACTION_KIND = {
21348
22299
  FILTER_SET: "filter-set",
21349
22300
  SORT_SET: "sort-set",
21350
22301
  SIR_SYNC: "sir-sync",
21351
- SIR_APPROVE: "sir-approve",
21352
22302
  ACTIVITY_LOOKAHEAD_SYNC: "activity-lookahead-sync"
21353
22303
  };
21354
22304
  var KIND_CATALOG_COVERS_UNION = {
@@ -21378,7 +22328,6 @@ var KIND_CATALOG_COVERS_UNION = {
21378
22328
  [DISPATCH_ACTION_KIND.FILTER_SET]: true,
21379
22329
  [DISPATCH_ACTION_KIND.SORT_SET]: true,
21380
22330
  [DISPATCH_ACTION_KIND.SIR_SYNC]: true,
21381
- [DISPATCH_ACTION_KIND.SIR_APPROVE]: true,
21382
22331
  [DISPATCH_ACTION_KIND.ACTIVITY_LOOKAHEAD_SYNC]: true
21383
22332
  };
21384
22333
  var DISPATCH_ACTION_KINDS = Object.keys(KIND_CATALOG_COVERS_UNION);