@outbuild-company/schedule-core 1.28.0 → 1.29.1

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
@@ -511,6 +511,8 @@ function createActivitiesApi(engine) {
511
511
  childIds: (parentId) => engine.getChildrenIds(parentId),
512
512
  hasChildren: (parentId) => engine.hasChild(parentId),
513
513
  indentTargetParentIds: (ids) => engine.getIndentTargetParentIds(ids),
514
+ leafParentIdsPromotedBy: (actions) => engine.getLeafParentIdsPromotedBy(actions),
515
+ lookaheadConfirmationFor: (actions, options) => engine.getLookaheadConfirmation(actions, options),
514
516
  isCustomIdInUse: (customId, currentCustomId) => engine.isCustomIdInUse(customId, currentCustomId ?? null),
515
517
  clipboard: () => engine.getActivityClipboardView(),
516
518
  create: (input, options) => engine.dispatch(
@@ -581,11 +583,14 @@ function createActivitiesApi(engine) {
581
583
  newValue: progress
582
584
  }),
583
585
  copy: (context = {}) => engine.dispatch({ ...context, kind: DISPATCH_ACTION_KIND.ACTIVITY_COPY }),
584
- paste: (afterSiblingId, context = {}) => engine.dispatch({
585
- ...context,
586
- kind: DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED,
587
- destination: { afterSiblingId }
588
- }),
586
+ paste: (afterSiblingId, context = {}, options) => {
587
+ const action = {
588
+ ...context,
589
+ kind: DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED,
590
+ destination: { afterSiblingId }
591
+ };
592
+ return options === void 0 ? engine.dispatch(action) : engine.dispatch(action, options);
593
+ },
589
594
  duplicate: (activityIds, context = {}) => engine.dispatch({
590
595
  ...context,
591
596
  kind: DISPATCH_ACTION_KIND.ACTIVITY_DUPLICATE,
@@ -925,11 +930,6 @@ function diffSelections(before, after) {
925
930
  return { checked, unchecked };
926
931
  }
927
932
 
928
- // src/internal/selection/is-frozen.ts
929
- function isFrozen(activity) {
930
- return (activity.pendingRequestIds?.length ?? 0) > 0;
931
- }
932
-
933
933
  // src/dispatch/reasons.ts
934
934
  var REJECTION_REASON = {
935
935
  CANNOT_EDIT: "cannot_edit",
@@ -967,10 +967,6 @@ var REJECTION_REASON = {
967
967
  };
968
968
 
969
969
  // src/dispatch/selection.ts
970
- var snapshotIsFrozen = (adapter, id) => {
971
- const snapshot = adapter.getActivity(id);
972
- return snapshot != null && isFrozen(snapshot);
973
- };
974
970
  var buildSelectionChangeSet = (action, adapter, changed2) => {
975
971
  const viewState = [];
976
972
  const emit = (id, nextChecked) => {
@@ -995,8 +991,8 @@ function dispatchSelectionToggle(action, deps) {
995
991
  if (!adapter.getActivity(clickedId)) {
996
992
  return { ok: false, reason: REJECTION_REASON.ACTIVITY_NOT_FOUND };
997
993
  }
998
- const frozenSet = action.frozenIds ? new Set(action.frozenIds) : null;
999
- const isActivityFrozen = frozenSet ? (id) => frozenSet.has(id) : (id) => snapshotIsFrozen(adapter, id);
994
+ const frozenSet = new Set((action.frozenIds ?? []).map(String));
995
+ const isActivityFrozen = (id) => frozenSet.has(id);
1000
996
  const update = computeSelectionUpdate({
1001
997
  clickedId,
1002
998
  isChecked: action.isChecked,
@@ -1020,7 +1016,7 @@ function dispatchSelectionToggle(action, deps) {
1020
1016
  function dispatchSelectionReplace(action, deps) {
1021
1017
  const { adapter } = deps;
1022
1018
  const target = new Set(
1023
- action.activityIds.map(String).filter((id) => adapter.activityExists(id)).filter((id) => !snapshotIsFrozen(adapter, id))
1019
+ action.activityIds.map(String).filter((id) => adapter.activityExists(id))
1024
1020
  );
1025
1021
  const current = new Set(adapter.checkedIds().map(String));
1026
1022
  const changed2 = {
@@ -12467,6 +12463,11 @@ function cloneLink(link) {
12467
12463
  return { ...link };
12468
12464
  }
12469
12465
 
12466
+ // src/internal/selection/is-frozen.ts
12467
+ function isFrozen(activity) {
12468
+ return (activity.pendingRequestIds?.length ?? 0) > 0;
12469
+ }
12470
+
12470
12471
  // src/dispatch/sir-auto-reject.ts
12471
12472
  function detectSirAutoReject(beforeSnap, adapter, touchedIds) {
12472
12473
  const effects = [];
@@ -14843,6 +14844,184 @@ function resolveStartDate(parent, fallback, override) {
14843
14844
  return fallback ? new Date(fallback) : /* @__PURE__ */ new Date();
14844
14845
  }
14845
14846
 
14847
+ // src/activity-copy-paste/paste.ts
14848
+ function resolvePasteDestination(afterSiblingId, adapter) {
14849
+ const sibling = adapter.getActivity(afterSiblingId);
14850
+ if (!sibling) {
14851
+ return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
14852
+ }
14853
+ if (sibling.parentId == null) {
14854
+ return adapter.getRootIds().length === 1 ? {
14855
+ ok: true,
14856
+ parentId: sibling.id,
14857
+ beforeSiblingId: adapter.getChildren(sibling.id)[0]
14858
+ } : { ok: true, parentId: ROOT_PARENT_ID, afterSiblingId };
14859
+ }
14860
+ if (adapter.hasChildren(sibling.id)) {
14861
+ return {
14862
+ ok: true,
14863
+ parentId: sibling.id,
14864
+ beforeSiblingId: adapter.getChildren(sibling.id)[0]
14865
+ };
14866
+ }
14867
+ return {
14868
+ ok: true,
14869
+ parentId: String(sibling.parentId),
14870
+ afterSiblingId
14871
+ };
14872
+ }
14873
+ var invariantFailure = () => ({
14874
+ ok: false,
14875
+ reason: REJECTION_REASON.ACTIVITY_PASTE_INVARIANT_FAILED
14876
+ });
14877
+ async function dispatchActivityPasteCopied(action, options, deps) {
14878
+ const clipboard = deps.activityClipboard.value;
14879
+ if (clipboard === null) {
14880
+ return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
14881
+ }
14882
+ const destination = resolvePasteDestination(
14883
+ action.destination.afterSiblingId,
14884
+ deps.adapter
14885
+ );
14886
+ if (!destination.ok) return destination;
14887
+ const beforeSnapshot = await snapshotActivities(
14888
+ deps.adapter,
14889
+ /* @__PURE__ */ new Set()
14890
+ );
14891
+ const touched = /* @__PURE__ */ new Set();
14892
+ const originalToNew = /* @__PURE__ */ new Map();
14893
+ const createdIds = [];
14894
+ let previousRootId;
14895
+ for (const copied of clipboard.activities) {
14896
+ const mappedParent = originalToNew.get(copied.originalParentId);
14897
+ const afterSiblingId = mappedParent === void 0 ? previousRootId ?? destination.afterSiblingId : void 0;
14898
+ const beforeSiblingId = mappedParent === void 0 && afterSiblingId === void 0 ? destination.beforeSiblingId : void 0;
14899
+ const created = await createActivityCore(
14900
+ {
14901
+ kind: DISPATCH_ACTION_KIND.ACTIVITY_CREATE,
14902
+ parentId: mappedParent ?? destination.parentId,
14903
+ ...afterSiblingId === void 0 ? {} : { afterSiblingId },
14904
+ ...beforeSiblingId === void 0 ? {} : { beforeSiblingId },
14905
+ overrides: cloneDomainValue(copied.overrides),
14906
+ eventSource: action.eventSource
14907
+ },
14908
+ deps,
14909
+ {
14910
+ skipCorrelativeRecompute: true,
14911
+ skipBeforeSnap: true,
14912
+ customIdReferenceId: action.destination.afterSiblingId,
14913
+ confirmedLookaheadParentIds: options.confirmedLookaheadParentIds,
14914
+ lookaheadTasksByActivityId: options.lookaheadTasksByActivityId
14915
+ }
14916
+ );
14917
+ if (!created.ok) return created;
14918
+ originalToNew.set(copied.originalId, created.newId);
14919
+ createdIds.push(created.newId);
14920
+ if (mappedParent === void 0) previousRootId = created.newId;
14921
+ }
14922
+ const correlativeShifts = recomputeCorrelativeIds(deps.adapter);
14923
+ const createdIdSet = new Set(createdIds.map(String));
14924
+ foldCorrelativeShifts(
14925
+ deps.adapter,
14926
+ correlativeShifts,
14927
+ createdIdSet,
14928
+ beforeSnapshot,
14929
+ touched
14930
+ );
14931
+ for (const createdId of createdIds) touched.add(String(createdId));
14932
+ const appliedLinks = [];
14933
+ for (const copiedLink of clipboard.links) {
14934
+ const source = originalToNew.get(copiedLink.source);
14935
+ const target = originalToNew.get(copiedLink.target);
14936
+ if (source === void 0 || target === void 0) return invariantFailure();
14937
+ const operation = {
14938
+ kind: LINK_OPERATION_KIND.CREATE,
14939
+ source,
14940
+ target,
14941
+ type: copiedLink.type,
14942
+ lag: copiedLink.lag
14943
+ };
14944
+ const result = applyLinkOperation(operation, {
14945
+ port: deps.adapter,
14946
+ newLinkId: deps.linkIdGen.next
14947
+ });
14948
+ if (!result.applied) return invariantFailure();
14949
+ appliedLinks.push({
14950
+ op: operation,
14951
+ finalLinkId: result.linkId,
14952
+ rejected: null
14953
+ });
14954
+ }
14955
+ const { scheduledIds } = await runPostMutation(
14956
+ {
14957
+ adapter: deps.adapter,
14958
+ scheduler: deps.scheduler,
14959
+ sector: deps.sector,
14960
+ defaultBaseCalendarId: deps.defaultBaseCalendarId,
14961
+ runAutomaticP6Schedule: deps.runAutomaticP6Schedule
14962
+ },
14963
+ {
14964
+ action,
14965
+ autoscheduleFrom: createdIds[0] ?? null,
14966
+ recomputeParentsFrom: createdIds,
14967
+ now: deps.now,
14968
+ options
14969
+ }
14970
+ );
14971
+ const linkChanges = buildLinkChangesForBatch(
14972
+ appliedLinks,
14973
+ /* @__PURE__ */ new Map(),
14974
+ deps.adapter
14975
+ );
14976
+ return {
14977
+ ok: true,
14978
+ changes: await assembleChangeSet(deps.adapter, {
14979
+ source: action,
14980
+ beforeSnap: beforeSnapshot,
14981
+ touchedIds: touched,
14982
+ scheduledIds,
14983
+ links: linkChanges,
14984
+ hoursPerDay: deps.sector.hoursPerDay
14985
+ })
14986
+ };
14987
+ }
14988
+
14989
+ // src/dispatch/confirmations.ts
14990
+ var LOOKAHEAD_CONFIRMATION_KIND = {
14991
+ HAS_LOOKAHEAD_TASKS: "has_lookahead_tasks"
14992
+ };
14993
+
14994
+ // src/dispatch/shared/hierarchy-cycle-guard.ts
14995
+ function reparentClosesEffectiveCycle(adapter, reparented) {
14996
+ const links = adapter.getAllLinks();
14997
+ const verdict = validateTopologyCommit({
14998
+ port: adapter,
14999
+ currentLinks: links,
15000
+ proposedLinks: links,
15001
+ proposal: { reparented }
15002
+ });
15003
+ return !verdict.ok;
15004
+ }
15005
+ function collectCyclicReparents(planned, adapter) {
15006
+ const cyclic = /* @__PURE__ */ new Set();
15007
+ if (planned.length === 0) return cyclic;
15008
+ const combined = new Map(
15009
+ planned.map((reparent) => [reparent.activityId, reparent.newParentId])
15010
+ );
15011
+ if (!reparentClosesEffectiveCycle(adapter, combined)) return cyclic;
15012
+ const accepted2 = /* @__PURE__ */ new Map();
15013
+ for (const reparent of planned) {
15014
+ const trial = new Map(accepted2);
15015
+ trial.set(reparent.activityId, reparent.newParentId);
15016
+ if (reparentClosesEffectiveCycle(adapter, trial)) {
15017
+ cyclic.add(reparent.activityId);
15018
+ continue;
15019
+ }
15020
+ accepted2.set(reparent.activityId, reparent.newParentId);
15021
+ }
15022
+ return cyclic;
15023
+ }
15024
+
14846
15025
  // src/dispatch/shared/positional.ts
14847
15026
  function computeInsertionCorrelativeId(adapter, parentId, afterSiblingId, beforeSiblingId) {
14848
15027
  const parentKey = parentId;
@@ -14927,6 +15106,161 @@ function computeFractionalCidAtIndex(adapter, parentKey, targetIndex, excludeId)
14927
15106
  return (before + after) / 2;
14928
15107
  }
14929
15108
 
15109
+ // src/dispatch/shared/indent-planning.ts
15110
+ function planIndentMoves(activityIds, adapter) {
15111
+ const selectedSet = new Set(activityIds);
15112
+ const failed = [];
15113
+ const moves = [];
15114
+ const plannedIds = /* @__PURE__ */ new Set();
15115
+ const blockingSet = new Set(selectedSet);
15116
+ const sortedIds = [...activityIds].sort((a, b) => {
15117
+ const aSnap = adapter.getActivity(a);
15118
+ const bSnap = adapter.getActivity(b);
15119
+ const ac = aSnap ? readCorrelativeId(aSnap) : NOT_SET_SORT_VALUE2;
15120
+ const bc = bSnap ? readCorrelativeId(bSnap) : NOT_SET_SORT_VALUE2;
15121
+ return ac - bc;
15122
+ });
15123
+ for (const id of sortedIds) {
15124
+ const activity = adapter.getActivity(id);
15125
+ if (!activity) {
15126
+ failed.push({
15127
+ activityId: id,
15128
+ reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
15129
+ });
15130
+ blockingSet.delete(id);
15131
+ continue;
15132
+ }
15133
+ if (hasAncestorInSet(id, plannedIds, adapter)) continue;
15134
+ const targetParentId = findPreviousNonSelectedSibling(
15135
+ id,
15136
+ blockingSet,
15137
+ adapter
15138
+ );
15139
+ if (targetParentId == null) {
15140
+ failed.push({
15141
+ activityId: id,
15142
+ reason: REJECTION_REASON.INDENT_NO_ELIGIBLE_SIBLING
15143
+ });
15144
+ blockingSet.delete(id);
15145
+ continue;
15146
+ }
15147
+ const oldParentRaw = adapter.getActivity(id)?.parentId;
15148
+ const oldParentId = isRootParent(oldParentRaw) ? ROOT_PARENT_ID : String(oldParentRaw);
15149
+ moves.push({ activityId: id, newParentId: targetParentId, oldParentId });
15150
+ plannedIds.add(id);
15151
+ }
15152
+ const cyclicMoveIds = collectCyclicReparents(moves, adapter);
15153
+ for (const cyclicId of cyclicMoveIds) {
15154
+ failed.push({
15155
+ activityId: cyclicId,
15156
+ reason: REJECTION_REASON.HIERARCHY_CYCLE
15157
+ });
15158
+ }
15159
+ const allowedMoves = moves.filter(
15160
+ (move) => !cyclicMoveIds.has(move.activityId)
15161
+ );
15162
+ return { moves, cyclicMoveIds, allowedMoves, failed };
15163
+ }
15164
+
15165
+ // src/dispatch/shared/lookahead-parent-confirmation.ts
15166
+ function lookaheadParentsConfirmation(leafParents, options) {
15167
+ const confirmed = new Set(options.confirmedLookaheadParentIds);
15168
+ const facts = options.lookaheadTasksByActivityId;
15169
+ const blocking = [
15170
+ ...new Set(
15171
+ leafParents.filter(
15172
+ (parent) => !confirmed.has(String(parent.id)) && (facts?.[String(parent.id)] ?? parent.hasLookaheadTasks)
15173
+ ).map((parent) => String(parent.id))
15174
+ )
15175
+ ];
15176
+ const [first] = blocking;
15177
+ if (first === void 0) return null;
15178
+ return {
15179
+ ok: false,
15180
+ reason: REJECTION_REASON.CONFIRMATION_REQUIRED,
15181
+ confirmation: {
15182
+ kind: LOOKAHEAD_CONFIRMATION_KIND.HAS_LOOKAHEAD_TASKS,
15183
+ activityId: first,
15184
+ ...blocking.length > 1 ? { activityIds: blocking } : {}
15185
+ }
15186
+ };
15187
+ }
15188
+ function leafParentsPromotedBy(parentIds, adapter) {
15189
+ const promoted = /* @__PURE__ */ new Map();
15190
+ for (const parentId of parentIds) {
15191
+ const parent = adapter.getActivity(parentId);
15192
+ if (!parent) continue;
15193
+ if (adapter.getChildren(parentId).length === 0)
15194
+ promoted.set(String(parent.id), parent);
15195
+ }
15196
+ return [...promoted.values()];
15197
+ }
15198
+ function leafParentIdsPromotedByActions(actions, adapter, hasActivityClipboard) {
15199
+ return leafParentsPromotedByActions(
15200
+ actions,
15201
+ adapter,
15202
+ hasActivityClipboard
15203
+ ).map((parent) => String(parent.id));
15204
+ }
15205
+ function lookaheadConfirmationForActions(actions, options, adapter, hasActivityClipboard) {
15206
+ const rejection = lookaheadParentsConfirmation(
15207
+ leafParentsPromotedByActions(actions, adapter, hasActivityClipboard),
15208
+ options
15209
+ );
15210
+ return rejection === null ? null : rejection.confirmation;
15211
+ }
15212
+ function leafParentsPromotedByActions(actions, adapter, hasActivityClipboard) {
15213
+ const parentIds = [];
15214
+ for (const action of actions) {
15215
+ parentIds.push(
15216
+ ...targetParentIds(action, adapter, hasActivityClipboard).map(String)
15217
+ );
15218
+ }
15219
+ return leafParentsPromotedBy(parentIds, adapter);
15220
+ }
15221
+ function targetParentIds(action, adapter, hasActivityClipboard) {
15222
+ switch (action.kind) {
15223
+ case DISPATCH_ACTION_KIND.ACTIVITY_CREATE:
15224
+ return [action.parentId];
15225
+ case DISPATCH_ACTION_KIND.ACTIVITY_MOVE: {
15226
+ const activity = adapter.getActivity(action.activityId);
15227
+ if (!activity) return [];
15228
+ if (normalizeParentKey(activity.parentId) === String(action.parentId)) {
15229
+ return [];
15230
+ }
15231
+ return movesIntoOwnBranch(action, adapter) ? [] : [action.parentId];
15232
+ }
15233
+ case DISPATCH_ACTION_KIND.ACTIVITY_INDENT:
15234
+ return planIndentMoves(action.activityIds, adapter).allowedMoves.map(
15235
+ (move) => move.newParentId
15236
+ );
15237
+ case DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED: {
15238
+ if (!hasActivityClipboard) return [];
15239
+ const destination = resolvePasteDestination(
15240
+ action.destination.afterSiblingId,
15241
+ adapter
15242
+ );
15243
+ return destination.ok ? [destination.parentId] : [];
15244
+ }
15245
+ case ACTIVITY_BATCH_ACTION_KIND:
15246
+ return action.mode === ACTIVITY_BATCH_MODE.APPEND ? action.items.flatMap(
15247
+ (item) => typeof item.parent === "string" ? [item.parent] : []
15248
+ ) : [];
15249
+ default:
15250
+ return [];
15251
+ }
15252
+ }
15253
+ function movesIntoOwnBranch(action, adapter) {
15254
+ if (isRootParent(action.parentId)) return false;
15255
+ if (action.parentId === action.activityId || adapter.isChildOf(action.parentId, action.activityId)) {
15256
+ return true;
15257
+ }
15258
+ return reparentClosesEffectiveCycle(
15259
+ adapter,
15260
+ /* @__PURE__ */ new Map([[action.activityId, action.parentId]])
15261
+ );
15262
+ }
15263
+
14930
15264
  // src/dispatch/creation-overrides.ts
14931
15265
  var PUBLIC_CREATION_OVERRIDE_FIELDS = /* @__PURE__ */ new Set([
14932
15266
  ...[...CREATION_OVERRIDE_FIELDS].filter(
@@ -15393,7 +15727,7 @@ function removeIncidentLinks(activityId, deps) {
15393
15727
  async function createActivityCore(action, deps, opts = {}) {
15394
15728
  const { adapter, scheduler, sector, calendars, activityIdGen, uidGen } = deps;
15395
15729
  if (!opts.skipCacheInvalidation) scheduler.invalidateAllCaches();
15396
- const target = validateCreateTarget(action, adapter);
15730
+ const target = validateCreateTarget(action, adapter, opts);
15397
15731
  if (!target.ok) return target;
15398
15732
  const { parent, anchorSiblingId, isRoot } = target;
15399
15733
  const newId = resolveNewActivityId(
@@ -15477,7 +15811,7 @@ async function createActivityCore(action, deps, opts = {}) {
15477
15811
  removedParentLinks
15478
15812
  };
15479
15813
  }
15480
- function validateCreateTarget(action, adapter) {
15814
+ function validateCreateTarget(action, adapter, opts) {
15481
15815
  const isRoot = isRootParent(action.parentId);
15482
15816
  const parent = isRoot ? null : adapter.getActivity(action.parentId);
15483
15817
  if (!isRoot && !parent) {
@@ -15499,6 +15833,13 @@ function validateCreateTarget(action, adapter) {
15499
15833
  anchorSiblingId
15500
15834
  );
15501
15835
  if (anchorError) return anchorError;
15836
+ if (parent !== null && !opts.parentsAlreadyChecked) {
15837
+ const rejection = lookaheadParentsConfirmation(
15838
+ leafParentsPromotedBy([action.parentId], adapter),
15839
+ opts
15840
+ );
15841
+ if (rejection) return rejection;
15842
+ }
15502
15843
  return { ok: true, parent, anchorSiblingId, isRoot };
15503
15844
  }
15504
15845
  function validateAnchorSibling(action, adapter, isRoot, anchorSiblingId) {
@@ -15597,7 +15938,11 @@ async function dispatchActivityCreate(action, options, deps) {
15597
15938
  if (!normalized2.ok) return normalized2;
15598
15939
  const core = await createActivityCore(
15599
15940
  { ...action, overrides: normalized2.value },
15600
- deps
15941
+ deps,
15942
+ {
15943
+ confirmedLookaheadParentIds: options.confirmedLookaheadParentIds,
15944
+ lookaheadTasksByActivityId: options.lookaheadTasksByActivityId
15945
+ }
15601
15946
  );
15602
15947
  if (!core.ok) return core;
15603
15948
  const { newId } = core;
@@ -15654,6 +15999,13 @@ async function dispatchActivityBatch(action, options, deps) {
15654
15999
  if (validationError) return { ok: false, reason: validationError };
15655
16000
  const plan = buildBatchPlan(action, deps);
15656
16001
  if (!plan.ok) return plan;
16002
+ if (action.mode === ACTIVITY_BATCH_MODE.APPEND) {
16003
+ const rejection = lookaheadParentsConfirmation(
16004
+ leafParentsPromotedBy(existingParentIds(plan.plan.items), deps.adapter),
16005
+ options
16006
+ );
16007
+ if (rejection) return rejection;
16008
+ }
15657
16009
  const existingActivityIds = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? new Set(deps.adapter.getAllIds().map((id) => String(id))) : /* @__PURE__ */ new Set();
15658
16010
  const beforeSnap = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? await snapshotActivities(deps.adapter, existingActivityIds) : /* @__PURE__ */ new Map();
15659
16011
  const beforeLinks = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? snapshotAllLinks(deps) : /* @__PURE__ */ new Map();
@@ -15778,6 +16130,11 @@ async function dispatchActivityBatch(action, options, deps) {
15778
16130
  } : {}
15779
16131
  };
15780
16132
  }
16133
+ function existingParentIds(items) {
16134
+ return items.filter(
16135
+ (item) => item.parentBatchId === null && item.parentId !== ROOT_PARENT_ID
16136
+ ).map((item) => String(item.parentId));
16137
+ }
15781
16138
  function validateBatch(action, deps) {
15782
16139
  if (action.mode !== ACTIVITY_BATCH_MODE.APPEND && action.mode !== ACTIVITY_BATCH_MODE.REPLACE) {
15783
16140
  return REJECTION_REASON.INVALID;
@@ -16075,7 +16432,8 @@ async function createPlannedActivities(action, plannedItems, deps) {
16075
16432
  skipBeforeSnap: true,
16076
16433
  skipCacheInvalidation: true,
16077
16434
  skipRollupCascades: true,
16078
- insertionCorrelativeId: item.insertionCorrelativeId
16435
+ insertionCorrelativeId: item.insertionCorrelativeId,
16436
+ parentsAlreadyChecked: true
16079
16437
  }
16080
16438
  );
16081
16439
  if (!core.ok) return core;
@@ -16838,37 +17196,6 @@ function isAncestor(descendantId, candidateAncestorId, adapter) {
16838
17196
  return false;
16839
17197
  }
16840
17198
 
16841
- // src/dispatch/shared/hierarchy-cycle-guard.ts
16842
- function reparentClosesEffectiveCycle(adapter, reparented) {
16843
- const links = adapter.getAllLinks();
16844
- const verdict = validateTopologyCommit({
16845
- port: adapter,
16846
- currentLinks: links,
16847
- proposedLinks: links,
16848
- proposal: { reparented }
16849
- });
16850
- return !verdict.ok;
16851
- }
16852
- function collectCyclicReparents(planned, adapter) {
16853
- const cyclic = /* @__PURE__ */ new Set();
16854
- if (planned.length === 0) return cyclic;
16855
- const combined = new Map(
16856
- planned.map((reparent) => [reparent.activityId, reparent.newParentId])
16857
- );
16858
- if (!reparentClosesEffectiveCycle(adapter, combined)) return cyclic;
16859
- const accepted2 = /* @__PURE__ */ new Map();
16860
- for (const reparent of planned) {
16861
- const trial = new Map(accepted2);
16862
- trial.set(reparent.activityId, reparent.newParentId);
16863
- if (reparentClosesEffectiveCycle(adapter, trial)) {
16864
- cyclic.add(reparent.activityId);
16865
- continue;
16866
- }
16867
- accepted2.set(reparent.activityId, reparent.newParentId);
16868
- }
16869
- return cyclic;
16870
- }
16871
-
16872
17199
  // src/dispatch/handlers/activity-move.ts
16873
17200
  async function dispatchActivityMove(action, options, deps) {
16874
17201
  const { adapter, scheduler, sector } = deps;
@@ -16921,6 +17248,13 @@ async function dispatchActivityMove(action, options, deps) {
16921
17248
  return { ok: false, reason: REJECTION_REASON.HIERARCHY_CYCLE };
16922
17249
  }
16923
17250
  }
17251
+ if (parentChanged && !newParentIsRoot) {
17252
+ const rejection = lookaheadParentsConfirmation(
17253
+ leafParentsPromotedBy([action.parentId], adapter),
17254
+ options
17255
+ );
17256
+ if (rejection) return rejection;
17257
+ }
16924
17258
  const touched = /* @__PURE__ */ new Set([action.activityId]);
16925
17259
  if (parentChanged) {
16926
17260
  if (oldParentKey !== ROOT_PARENT_ID) touched.add(oldParentKey);
@@ -17080,62 +17414,6 @@ async function dispatchActivityMove(action, options, deps) {
17080
17414
  };
17081
17415
  }
17082
17416
 
17083
- // src/dispatch/shared/indent-planning.ts
17084
- function planIndentMoves(activityIds, adapter) {
17085
- const selectedSet = new Set(activityIds);
17086
- const failed = [];
17087
- const moves = [];
17088
- const plannedIds = /* @__PURE__ */ new Set();
17089
- const blockingSet = new Set(selectedSet);
17090
- const sortedIds = [...activityIds].sort((a, b) => {
17091
- const aSnap = adapter.getActivity(a);
17092
- const bSnap = adapter.getActivity(b);
17093
- const ac = aSnap ? readCorrelativeId(aSnap) : NOT_SET_SORT_VALUE2;
17094
- const bc = bSnap ? readCorrelativeId(bSnap) : NOT_SET_SORT_VALUE2;
17095
- return ac - bc;
17096
- });
17097
- for (const id of sortedIds) {
17098
- const activity = adapter.getActivity(id);
17099
- if (!activity) {
17100
- failed.push({
17101
- activityId: id,
17102
- reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
17103
- });
17104
- blockingSet.delete(id);
17105
- continue;
17106
- }
17107
- if (hasAncestorInSet(id, plannedIds, adapter)) continue;
17108
- const targetParentId = findPreviousNonSelectedSibling(
17109
- id,
17110
- blockingSet,
17111
- adapter
17112
- );
17113
- if (targetParentId == null) {
17114
- failed.push({
17115
- activityId: id,
17116
- reason: REJECTION_REASON.INDENT_NO_ELIGIBLE_SIBLING
17117
- });
17118
- blockingSet.delete(id);
17119
- continue;
17120
- }
17121
- const oldParentRaw = adapter.getActivity(id)?.parentId;
17122
- const oldParentId = isRootParent(oldParentRaw) ? ROOT_PARENT_ID : String(oldParentRaw);
17123
- moves.push({ activityId: id, newParentId: targetParentId, oldParentId });
17124
- plannedIds.add(id);
17125
- }
17126
- const cyclicMoveIds = collectCyclicReparents(moves, adapter);
17127
- for (const cyclicId of cyclicMoveIds) {
17128
- failed.push({
17129
- activityId: cyclicId,
17130
- reason: REJECTION_REASON.HIERARCHY_CYCLE
17131
- });
17132
- }
17133
- const allowedMoves = moves.filter(
17134
- (move) => !cyclicMoveIds.has(move.activityId)
17135
- );
17136
- return { moves, cyclicMoveIds, allowedMoves, failed };
17137
- }
17138
-
17139
17417
  // src/dispatch/handlers/activity-indent.ts
17140
17418
  async function dispatchActivityIndent(action, options, deps) {
17141
17419
  const { adapter, scheduler, sector } = deps;
@@ -17157,6 +17435,14 @@ async function dispatchActivityIndent(action, options, deps) {
17157
17435
  }
17158
17436
  }
17159
17437
  const allowedMoves = plan.allowedMoves;
17438
+ const rejection = lookaheadParentsConfirmation(
17439
+ leafParentsPromotedBy(
17440
+ allowedMoves.map((move) => move.newParentId),
17441
+ adapter
17442
+ ),
17443
+ options
17444
+ );
17445
+ if (rejection) return rejection;
17160
17446
  const beforeSnap = await snapshotActivities(adapter, touched);
17161
17447
  for (const { activityId, newParentId } of allowedMoves) {
17162
17448
  adapter.setActivityField(activityId, ACTIVITY_PROPERTY.PARENT, newParentId);
@@ -18954,146 +19240,6 @@ function dispatchActivityClipboardImport(action, deps) {
18954
19240
  return clipboardOnlyResult(action);
18955
19241
  }
18956
19242
 
18957
- // src/activity-copy-paste/paste.ts
18958
- function resolveDestination(afterSiblingId, adapter) {
18959
- const sibling = adapter.getActivity(afterSiblingId);
18960
- if (!sibling) {
18961
- return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
18962
- }
18963
- if (sibling.parentId == null) {
18964
- return adapter.getRootIds().length === 1 ? {
18965
- ok: true,
18966
- parentId: sibling.id,
18967
- beforeSiblingId: adapter.getChildren(sibling.id)[0]
18968
- } : { ok: true, parentId: ROOT_PARENT_ID, afterSiblingId };
18969
- }
18970
- if (adapter.hasChildren(sibling.id)) {
18971
- return {
18972
- ok: true,
18973
- parentId: sibling.id,
18974
- beforeSiblingId: adapter.getChildren(sibling.id)[0]
18975
- };
18976
- }
18977
- return {
18978
- ok: true,
18979
- parentId: String(sibling.parentId),
18980
- afterSiblingId
18981
- };
18982
- }
18983
- var invariantFailure = () => ({
18984
- ok: false,
18985
- reason: REJECTION_REASON.ACTIVITY_PASTE_INVARIANT_FAILED
18986
- });
18987
- async function dispatchActivityPasteCopied(action, options, deps) {
18988
- const clipboard = deps.activityClipboard.value;
18989
- if (clipboard === null) {
18990
- return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
18991
- }
18992
- const destination = resolveDestination(
18993
- action.destination.afterSiblingId,
18994
- deps.adapter
18995
- );
18996
- if (!destination.ok) return destination;
18997
- const beforeSnapshot = await snapshotActivities(
18998
- deps.adapter,
18999
- /* @__PURE__ */ new Set()
19000
- );
19001
- const touched = /* @__PURE__ */ new Set();
19002
- const originalToNew = /* @__PURE__ */ new Map();
19003
- const createdIds = [];
19004
- let previousRootId;
19005
- for (const copied of clipboard.activities) {
19006
- const mappedParent = originalToNew.get(copied.originalParentId);
19007
- const afterSiblingId = mappedParent === void 0 ? previousRootId ?? destination.afterSiblingId : void 0;
19008
- const beforeSiblingId = mappedParent === void 0 && afterSiblingId === void 0 ? destination.beforeSiblingId : void 0;
19009
- const created = await createActivityCore(
19010
- {
19011
- kind: DISPATCH_ACTION_KIND.ACTIVITY_CREATE,
19012
- parentId: mappedParent ?? destination.parentId,
19013
- ...afterSiblingId === void 0 ? {} : { afterSiblingId },
19014
- ...beforeSiblingId === void 0 ? {} : { beforeSiblingId },
19015
- overrides: cloneDomainValue(copied.overrides),
19016
- eventSource: action.eventSource
19017
- },
19018
- deps,
19019
- {
19020
- skipCorrelativeRecompute: true,
19021
- skipBeforeSnap: true,
19022
- customIdReferenceId: action.destination.afterSiblingId
19023
- }
19024
- );
19025
- if (!created.ok) return created;
19026
- originalToNew.set(copied.originalId, created.newId);
19027
- createdIds.push(created.newId);
19028
- if (mappedParent === void 0) previousRootId = created.newId;
19029
- }
19030
- const correlativeShifts = recomputeCorrelativeIds(deps.adapter);
19031
- const createdIdSet = new Set(createdIds.map(String));
19032
- foldCorrelativeShifts(
19033
- deps.adapter,
19034
- correlativeShifts,
19035
- createdIdSet,
19036
- beforeSnapshot,
19037
- touched
19038
- );
19039
- for (const createdId of createdIds) touched.add(String(createdId));
19040
- const appliedLinks = [];
19041
- for (const copiedLink of clipboard.links) {
19042
- const source = originalToNew.get(copiedLink.source);
19043
- const target = originalToNew.get(copiedLink.target);
19044
- if (source === void 0 || target === void 0) return invariantFailure();
19045
- const operation = {
19046
- kind: LINK_OPERATION_KIND.CREATE,
19047
- source,
19048
- target,
19049
- type: copiedLink.type,
19050
- lag: copiedLink.lag
19051
- };
19052
- const result = applyLinkOperation(operation, {
19053
- port: deps.adapter,
19054
- newLinkId: deps.linkIdGen.next
19055
- });
19056
- if (!result.applied) return invariantFailure();
19057
- appliedLinks.push({
19058
- op: operation,
19059
- finalLinkId: result.linkId,
19060
- rejected: null
19061
- });
19062
- }
19063
- const { scheduledIds } = await runPostMutation(
19064
- {
19065
- adapter: deps.adapter,
19066
- scheduler: deps.scheduler,
19067
- sector: deps.sector,
19068
- defaultBaseCalendarId: deps.defaultBaseCalendarId,
19069
- runAutomaticP6Schedule: deps.runAutomaticP6Schedule
19070
- },
19071
- {
19072
- action,
19073
- autoscheduleFrom: createdIds[0] ?? null,
19074
- recomputeParentsFrom: createdIds,
19075
- now: deps.now,
19076
- options
19077
- }
19078
- );
19079
- const linkChanges = buildLinkChangesForBatch(
19080
- appliedLinks,
19081
- /* @__PURE__ */ new Map(),
19082
- deps.adapter
19083
- );
19084
- return {
19085
- ok: true,
19086
- changes: await assembleChangeSet(deps.adapter, {
19087
- source: action,
19088
- beforeSnap: beforeSnapshot,
19089
- touchedIds: touched,
19090
- scheduledIds,
19091
- links: linkChanges,
19092
- hoursPerDay: deps.sector.hoursPerDay
19093
- })
19094
- };
19095
- }
19096
-
19097
19243
  // src/activity-copy-paste/duplicate.ts
19098
19244
  var CLONE_YIELD_EVERY_N = 200;
19099
19245
  var CREATE_OPTIONS = {
@@ -32110,6 +32256,41 @@ var ScheduleCore = class _ScheduleCore {
32110
32256
  ...new Set(plan.allowedMoves.map((move) => String(move.newParentId)))
32111
32257
  ];
32112
32258
  }
32259
+ /**
32260
+ * Lists the leaf activities the actions would turn into parents.
32261
+ *
32262
+ * Each action is checked against the current state, not against the state
32263
+ * left by the actions before it.
32264
+ *
32265
+ * @param actions - Actions the caller is about to dispatch.
32266
+ */
32267
+ getLeafParentIdsPromotedBy(actions) {
32268
+ this.assertReady();
32269
+ return leafParentIdsPromotedByActions(
32270
+ actions,
32271
+ this.coreRuntime.state,
32272
+ this._activityClipboard.value !== null
32273
+ );
32274
+ }
32275
+ /**
32276
+ * Returns the lookahead confirmation the dispatch rule would raise for the
32277
+ * actions, or `null` when none is needed. The rule code is the dispatch rule
32278
+ * and nothing changes in the schedule. Like
32279
+ * {@link ScheduleCore.getLeafParentIdsPromotedBy}, it checks each action
32280
+ * against the current state.
32281
+ *
32282
+ * @param actions - Actions the caller is about to dispatch.
32283
+ * @param options - The `confirmedLookaheadParentIds` and `lookaheadTasksByActivityId` the dispatch would receive.
32284
+ */
32285
+ getLookaheadConfirmation(actions, options = {}) {
32286
+ this.assertReady();
32287
+ return lookaheadConfirmationForActions(
32288
+ actions,
32289
+ options,
32290
+ this.coreRuntime.state,
32291
+ this._activityClipboard.value !== null
32292
+ );
32293
+ }
32113
32294
  /**
32114
32295
  * Reads defensive direct-child snapshots in correlative order.
32115
32296
  *
@@ -34854,11 +35035,10 @@ var DEFAULT_HOURS_PER_DAY = 8;
34854
35035
  // src/creation/creation-kinds.ts
34855
35036
  var CREATION_KIND = {
34856
35037
  CHILD: "child",
34857
- CHILD_CONFIRMED: "child-confirmed",
34858
35038
  LINE: "line",
34859
35039
  ROOT: "root"
34860
35040
  };
34861
35041
 
34862
- export { ACTIVITY_BATCH_ACTION_KIND, ACTIVITY_BATCH_MODE, ACTIVITY_DATE_EDGE, ACTIVITY_FIELD, ACTIVITY_PONDERATOR_CRITERION, ACTIVITY_PROGRESS_ORIGIN, ACTIVITY_TYPE, CALENDAR_UNIT, CHANGE_SET_SOURCE_KIND, COLUMN, COMPLETED_PROGRESS, CONFIRMATION_KIND, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CONSTRAINT_WARNING_KIND, CREATION_KIND, P6_PROPERTY_CONTRACT as DATA_DATE_PROPERTY_CONTRACT, DATE_GESTURE_KIND, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_LINK_OPERATION_KIND, DISPATCH_TRACK_EVENT, P6CapabilityError as DataDateCapabilityError, EDIT_ALERT_KEY, EDIT_DENIAL_REASON, ENTITY_CHANGE_KIND, FILTER_FIELD, FILTER_LOGIC, FILTER_OPERATOR, FILTER_REJECTION_REASON, FILTER_VALUE_KIND, FLAT_WBS_DEPTH, GROUP_ID_PREFIX, GROUP_ID_SEPARATOR, LINK_BATCH_OPERATION_KIND, LINK_CODE, LINK_CODE_TO_TYPE, LINK_REJECTION_REASON, LINK_RELATIONSHIP, LINK_TYPE, LINK_TYPE_CODE, LookaheadActualTransitionError, MINIMUM_GROUP_DEPTH, MINIMUM_GROUP_LEVEL, NEW_ACTIVITY_DEFAULTS, NOT_STARTED_PROGRESS, ORDER_DIRECTION, ORDER_MODE, REJECTION_REASON, ROOT_PARENT_ID, SCHEDULE_CORE_STATUS, SCHEDULE_EFFECT_KIND, SIR_AUTO_REJECT_REASON, STATUS, STATUS_CRITERIA, ScheduleCore, UNASSIGNED_GROUP_LABEL, UNASSIGNED_VALUE_ID, VIEW_ATTRIBUTE_KIND, VISUAL_ROW_KIND, WORK_TIME_DIRECTION, allowedSecondaryConstraintTypes, appendGroupRowId, buildGroupRowId, checkNoUpdatedLinks, computeExpectedProgress, createCalendarReader, createLookaheadDerivedFields, createOutbuildP6ScheduleSettings as createOutbuildDataDateScheduleSettings, createSchedule, expectedProgressFromBaseline, formatUtcAsP6WallDate, getActiveBaseline, getUnsavedActivities, isRootParent, isViewAttributeCriterion, isViewAttributeOrderRule, mapP6ScheduleOptions as mapDataDateScheduleOptions, normalizeParentKey, parseFromBackend, parseP6DayAsCalendarStart, parseP6WallDateAsUtc, planLookaheadProgressActuals, prepareLookaheadProgressActuals, resolveExpectedFinishDateSelection, resolveLookaheadActualDateSelection, willRunCriticalPath, yieldToBrowser };
35042
+ export { ACTIVITY_BATCH_ACTION_KIND, ACTIVITY_BATCH_MODE, ACTIVITY_DATE_EDGE, ACTIVITY_FIELD, ACTIVITY_PONDERATOR_CRITERION, ACTIVITY_PROGRESS_ORIGIN, ACTIVITY_TYPE, CALENDAR_UNIT, CHANGE_SET_SOURCE_KIND, COLUMN, COMPLETED_PROGRESS, CONFIRMATION_KIND, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CONSTRAINT_WARNING_KIND, CREATION_KIND, P6_PROPERTY_CONTRACT as DATA_DATE_PROPERTY_CONTRACT, DATE_GESTURE_KIND, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_LINK_OPERATION_KIND, DISPATCH_TRACK_EVENT, P6CapabilityError as DataDateCapabilityError, EDIT_ALERT_KEY, EDIT_DENIAL_REASON, ENTITY_CHANGE_KIND, FILTER_FIELD, FILTER_LOGIC, FILTER_OPERATOR, FILTER_REJECTION_REASON, FILTER_VALUE_KIND, FLAT_WBS_DEPTH, GROUP_ID_PREFIX, GROUP_ID_SEPARATOR, LINK_BATCH_OPERATION_KIND, LINK_CODE, LINK_CODE_TO_TYPE, LINK_REJECTION_REASON, LINK_RELATIONSHIP, LINK_TYPE, LINK_TYPE_CODE, LOOKAHEAD_CONFIRMATION_KIND, LookaheadActualTransitionError, MINIMUM_GROUP_DEPTH, MINIMUM_GROUP_LEVEL, NEW_ACTIVITY_DEFAULTS, NOT_STARTED_PROGRESS, ORDER_DIRECTION, ORDER_MODE, REJECTION_REASON, ROOT_PARENT_ID, SCHEDULE_CORE_STATUS, SCHEDULE_EFFECT_KIND, SIR_AUTO_REJECT_REASON, STATUS, STATUS_CRITERIA, ScheduleCore, UNASSIGNED_GROUP_LABEL, UNASSIGNED_VALUE_ID, VIEW_ATTRIBUTE_KIND, VISUAL_ROW_KIND, WORK_TIME_DIRECTION, allowedSecondaryConstraintTypes, appendGroupRowId, buildGroupRowId, checkNoUpdatedLinks, computeExpectedProgress, createCalendarReader, createLookaheadDerivedFields, createOutbuildP6ScheduleSettings as createOutbuildDataDateScheduleSettings, createSchedule, expectedProgressFromBaseline, formatUtcAsP6WallDate, getActiveBaseline, getUnsavedActivities, isRootParent, isViewAttributeCriterion, isViewAttributeOrderRule, mapP6ScheduleOptions as mapDataDateScheduleOptions, normalizeParentKey, parseFromBackend, parseP6DayAsCalendarStart, parseP6WallDateAsUtc, planLookaheadProgressActuals, prepareLookaheadProgressActuals, resolveExpectedFinishDateSelection, resolveLookaheadActualDateSelection, willRunCriticalPath, yieldToBrowser };
34863
35043
  //# sourceMappingURL=index.js.map
34864
35044
  //# sourceMappingURL=index.js.map