@outbuild-company/schedule-core 1.27.0 → 1.29.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.cjs CHANGED
@@ -513,6 +513,8 @@ function createActivitiesApi(engine) {
513
513
  childIds: (parentId) => engine.getChildrenIds(parentId),
514
514
  hasChildren: (parentId) => engine.hasChild(parentId),
515
515
  indentTargetParentIds: (ids) => engine.getIndentTargetParentIds(ids),
516
+ leafParentIdsPromotedBy: (actions) => engine.getLeafParentIdsPromotedBy(actions),
517
+ lookaheadConfirmationFor: (actions, options) => engine.getLookaheadConfirmation(actions, options),
516
518
  isCustomIdInUse: (customId, currentCustomId) => engine.isCustomIdInUse(customId, currentCustomId ?? null),
517
519
  clipboard: () => engine.getActivityClipboardView(),
518
520
  create: (input, options) => engine.dispatch(
@@ -583,11 +585,14 @@ function createActivitiesApi(engine) {
583
585
  newValue: progress
584
586
  }),
585
587
  copy: (context = {}) => engine.dispatch({ ...context, kind: DISPATCH_ACTION_KIND.ACTIVITY_COPY }),
586
- paste: (afterSiblingId, context = {}) => engine.dispatch({
587
- ...context,
588
- kind: DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED,
589
- destination: { afterSiblingId }
590
- }),
588
+ paste: (afterSiblingId, context = {}, options) => {
589
+ const action = {
590
+ ...context,
591
+ kind: DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED,
592
+ destination: { afterSiblingId }
593
+ };
594
+ return options === void 0 ? engine.dispatch(action) : engine.dispatch(action, options);
595
+ },
591
596
  duplicate: (activityIds, context = {}) => engine.dispatch({
592
597
  ...context,
593
598
  kind: DISPATCH_ACTION_KIND.ACTIVITY_DUPLICATE,
@@ -14845,6 +14850,184 @@ function resolveStartDate(parent, fallback, override) {
14845
14850
  return fallback ? new Date(fallback) : /* @__PURE__ */ new Date();
14846
14851
  }
14847
14852
 
14853
+ // src/activity-copy-paste/paste.ts
14854
+ function resolvePasteDestination(afterSiblingId, adapter) {
14855
+ const sibling = adapter.getActivity(afterSiblingId);
14856
+ if (!sibling) {
14857
+ return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
14858
+ }
14859
+ if (sibling.parentId == null) {
14860
+ return adapter.getRootIds().length === 1 ? {
14861
+ ok: true,
14862
+ parentId: sibling.id,
14863
+ beforeSiblingId: adapter.getChildren(sibling.id)[0]
14864
+ } : { ok: true, parentId: ROOT_PARENT_ID, afterSiblingId };
14865
+ }
14866
+ if (adapter.hasChildren(sibling.id)) {
14867
+ return {
14868
+ ok: true,
14869
+ parentId: sibling.id,
14870
+ beforeSiblingId: adapter.getChildren(sibling.id)[0]
14871
+ };
14872
+ }
14873
+ return {
14874
+ ok: true,
14875
+ parentId: String(sibling.parentId),
14876
+ afterSiblingId
14877
+ };
14878
+ }
14879
+ var invariantFailure = () => ({
14880
+ ok: false,
14881
+ reason: REJECTION_REASON.ACTIVITY_PASTE_INVARIANT_FAILED
14882
+ });
14883
+ async function dispatchActivityPasteCopied(action, options, deps) {
14884
+ const clipboard = deps.activityClipboard.value;
14885
+ if (clipboard === null) {
14886
+ return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
14887
+ }
14888
+ const destination = resolvePasteDestination(
14889
+ action.destination.afterSiblingId,
14890
+ deps.adapter
14891
+ );
14892
+ if (!destination.ok) return destination;
14893
+ const beforeSnapshot = await snapshotActivities(
14894
+ deps.adapter,
14895
+ /* @__PURE__ */ new Set()
14896
+ );
14897
+ const touched = /* @__PURE__ */ new Set();
14898
+ const originalToNew = /* @__PURE__ */ new Map();
14899
+ const createdIds = [];
14900
+ let previousRootId;
14901
+ for (const copied of clipboard.activities) {
14902
+ const mappedParent = originalToNew.get(copied.originalParentId);
14903
+ const afterSiblingId = mappedParent === void 0 ? previousRootId ?? destination.afterSiblingId : void 0;
14904
+ const beforeSiblingId = mappedParent === void 0 && afterSiblingId === void 0 ? destination.beforeSiblingId : void 0;
14905
+ const created = await createActivityCore(
14906
+ {
14907
+ kind: DISPATCH_ACTION_KIND.ACTIVITY_CREATE,
14908
+ parentId: mappedParent ?? destination.parentId,
14909
+ ...afterSiblingId === void 0 ? {} : { afterSiblingId },
14910
+ ...beforeSiblingId === void 0 ? {} : { beforeSiblingId },
14911
+ overrides: cloneDomainValue(copied.overrides),
14912
+ eventSource: action.eventSource
14913
+ },
14914
+ deps,
14915
+ {
14916
+ skipCorrelativeRecompute: true,
14917
+ skipBeforeSnap: true,
14918
+ customIdReferenceId: action.destination.afterSiblingId,
14919
+ confirmedLookaheadParentIds: options.confirmedLookaheadParentIds,
14920
+ lookaheadTasksByActivityId: options.lookaheadTasksByActivityId
14921
+ }
14922
+ );
14923
+ if (!created.ok) return created;
14924
+ originalToNew.set(copied.originalId, created.newId);
14925
+ createdIds.push(created.newId);
14926
+ if (mappedParent === void 0) previousRootId = created.newId;
14927
+ }
14928
+ const correlativeShifts = recomputeCorrelativeIds(deps.adapter);
14929
+ const createdIdSet = new Set(createdIds.map(String));
14930
+ foldCorrelativeShifts(
14931
+ deps.adapter,
14932
+ correlativeShifts,
14933
+ createdIdSet,
14934
+ beforeSnapshot,
14935
+ touched
14936
+ );
14937
+ for (const createdId of createdIds) touched.add(String(createdId));
14938
+ const appliedLinks = [];
14939
+ for (const copiedLink of clipboard.links) {
14940
+ const source = originalToNew.get(copiedLink.source);
14941
+ const target = originalToNew.get(copiedLink.target);
14942
+ if (source === void 0 || target === void 0) return invariantFailure();
14943
+ const operation = {
14944
+ kind: LINK_OPERATION_KIND.CREATE,
14945
+ source,
14946
+ target,
14947
+ type: copiedLink.type,
14948
+ lag: copiedLink.lag
14949
+ };
14950
+ const result = applyLinkOperation(operation, {
14951
+ port: deps.adapter,
14952
+ newLinkId: deps.linkIdGen.next
14953
+ });
14954
+ if (!result.applied) return invariantFailure();
14955
+ appliedLinks.push({
14956
+ op: operation,
14957
+ finalLinkId: result.linkId,
14958
+ rejected: null
14959
+ });
14960
+ }
14961
+ const { scheduledIds } = await runPostMutation(
14962
+ {
14963
+ adapter: deps.adapter,
14964
+ scheduler: deps.scheduler,
14965
+ sector: deps.sector,
14966
+ defaultBaseCalendarId: deps.defaultBaseCalendarId,
14967
+ runAutomaticP6Schedule: deps.runAutomaticP6Schedule
14968
+ },
14969
+ {
14970
+ action,
14971
+ autoscheduleFrom: createdIds[0] ?? null,
14972
+ recomputeParentsFrom: createdIds,
14973
+ now: deps.now,
14974
+ options
14975
+ }
14976
+ );
14977
+ const linkChanges = buildLinkChangesForBatch(
14978
+ appliedLinks,
14979
+ /* @__PURE__ */ new Map(),
14980
+ deps.adapter
14981
+ );
14982
+ return {
14983
+ ok: true,
14984
+ changes: await assembleChangeSet(deps.adapter, {
14985
+ source: action,
14986
+ beforeSnap: beforeSnapshot,
14987
+ touchedIds: touched,
14988
+ scheduledIds,
14989
+ links: linkChanges,
14990
+ hoursPerDay: deps.sector.hoursPerDay
14991
+ })
14992
+ };
14993
+ }
14994
+
14995
+ // src/dispatch/confirmations.ts
14996
+ var LOOKAHEAD_CONFIRMATION_KIND = {
14997
+ HAS_LOOKAHEAD_TASKS: "has_lookahead_tasks"
14998
+ };
14999
+
15000
+ // src/dispatch/shared/hierarchy-cycle-guard.ts
15001
+ function reparentClosesEffectiveCycle(adapter, reparented) {
15002
+ const links = adapter.getAllLinks();
15003
+ const verdict = validateTopologyCommit({
15004
+ port: adapter,
15005
+ currentLinks: links,
15006
+ proposedLinks: links,
15007
+ proposal: { reparented }
15008
+ });
15009
+ return !verdict.ok;
15010
+ }
15011
+ function collectCyclicReparents(planned, adapter) {
15012
+ const cyclic = /* @__PURE__ */ new Set();
15013
+ if (planned.length === 0) return cyclic;
15014
+ const combined = new Map(
15015
+ planned.map((reparent) => [reparent.activityId, reparent.newParentId])
15016
+ );
15017
+ if (!reparentClosesEffectiveCycle(adapter, combined)) return cyclic;
15018
+ const accepted2 = /* @__PURE__ */ new Map();
15019
+ for (const reparent of planned) {
15020
+ const trial = new Map(accepted2);
15021
+ trial.set(reparent.activityId, reparent.newParentId);
15022
+ if (reparentClosesEffectiveCycle(adapter, trial)) {
15023
+ cyclic.add(reparent.activityId);
15024
+ continue;
15025
+ }
15026
+ accepted2.set(reparent.activityId, reparent.newParentId);
15027
+ }
15028
+ return cyclic;
15029
+ }
15030
+
14848
15031
  // src/dispatch/shared/positional.ts
14849
15032
  function computeInsertionCorrelativeId(adapter, parentId, afterSiblingId, beforeSiblingId) {
14850
15033
  const parentKey = parentId;
@@ -14929,6 +15112,161 @@ function computeFractionalCidAtIndex(adapter, parentKey, targetIndex, excludeId)
14929
15112
  return (before + after) / 2;
14930
15113
  }
14931
15114
 
15115
+ // src/dispatch/shared/indent-planning.ts
15116
+ function planIndentMoves(activityIds, adapter) {
15117
+ const selectedSet = new Set(activityIds);
15118
+ const failed = [];
15119
+ const moves = [];
15120
+ const plannedIds = /* @__PURE__ */ new Set();
15121
+ const blockingSet = new Set(selectedSet);
15122
+ const sortedIds = [...activityIds].sort((a, b) => {
15123
+ const aSnap = adapter.getActivity(a);
15124
+ const bSnap = adapter.getActivity(b);
15125
+ const ac = aSnap ? readCorrelativeId(aSnap) : NOT_SET_SORT_VALUE2;
15126
+ const bc = bSnap ? readCorrelativeId(bSnap) : NOT_SET_SORT_VALUE2;
15127
+ return ac - bc;
15128
+ });
15129
+ for (const id of sortedIds) {
15130
+ const activity = adapter.getActivity(id);
15131
+ if (!activity) {
15132
+ failed.push({
15133
+ activityId: id,
15134
+ reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
15135
+ });
15136
+ blockingSet.delete(id);
15137
+ continue;
15138
+ }
15139
+ if (hasAncestorInSet(id, plannedIds, adapter)) continue;
15140
+ const targetParentId = findPreviousNonSelectedSibling(
15141
+ id,
15142
+ blockingSet,
15143
+ adapter
15144
+ );
15145
+ if (targetParentId == null) {
15146
+ failed.push({
15147
+ activityId: id,
15148
+ reason: REJECTION_REASON.INDENT_NO_ELIGIBLE_SIBLING
15149
+ });
15150
+ blockingSet.delete(id);
15151
+ continue;
15152
+ }
15153
+ const oldParentRaw = adapter.getActivity(id)?.parentId;
15154
+ const oldParentId = isRootParent(oldParentRaw) ? ROOT_PARENT_ID : String(oldParentRaw);
15155
+ moves.push({ activityId: id, newParentId: targetParentId, oldParentId });
15156
+ plannedIds.add(id);
15157
+ }
15158
+ const cyclicMoveIds = collectCyclicReparents(moves, adapter);
15159
+ for (const cyclicId of cyclicMoveIds) {
15160
+ failed.push({
15161
+ activityId: cyclicId,
15162
+ reason: REJECTION_REASON.HIERARCHY_CYCLE
15163
+ });
15164
+ }
15165
+ const allowedMoves = moves.filter(
15166
+ (move) => !cyclicMoveIds.has(move.activityId)
15167
+ );
15168
+ return { moves, cyclicMoveIds, allowedMoves, failed };
15169
+ }
15170
+
15171
+ // src/dispatch/shared/lookahead-parent-confirmation.ts
15172
+ function lookaheadParentsConfirmation(leafParents, options) {
15173
+ const confirmed = new Set(options.confirmedLookaheadParentIds);
15174
+ const facts = options.lookaheadTasksByActivityId;
15175
+ const blocking = [
15176
+ ...new Set(
15177
+ leafParents.filter(
15178
+ (parent) => !confirmed.has(String(parent.id)) && (facts?.[String(parent.id)] ?? parent.hasLookaheadTasks)
15179
+ ).map((parent) => String(parent.id))
15180
+ )
15181
+ ];
15182
+ const [first] = blocking;
15183
+ if (first === void 0) return null;
15184
+ return {
15185
+ ok: false,
15186
+ reason: REJECTION_REASON.CONFIRMATION_REQUIRED,
15187
+ confirmation: {
15188
+ kind: LOOKAHEAD_CONFIRMATION_KIND.HAS_LOOKAHEAD_TASKS,
15189
+ activityId: first,
15190
+ ...blocking.length > 1 ? { activityIds: blocking } : {}
15191
+ }
15192
+ };
15193
+ }
15194
+ function leafParentsPromotedBy(parentIds, adapter) {
15195
+ const promoted = /* @__PURE__ */ new Map();
15196
+ for (const parentId of parentIds) {
15197
+ const parent = adapter.getActivity(parentId);
15198
+ if (!parent) continue;
15199
+ if (adapter.getChildren(parentId).length === 0)
15200
+ promoted.set(String(parent.id), parent);
15201
+ }
15202
+ return [...promoted.values()];
15203
+ }
15204
+ function leafParentIdsPromotedByActions(actions, adapter, hasActivityClipboard) {
15205
+ return leafParentsPromotedByActions(
15206
+ actions,
15207
+ adapter,
15208
+ hasActivityClipboard
15209
+ ).map((parent) => String(parent.id));
15210
+ }
15211
+ function lookaheadConfirmationForActions(actions, options, adapter, hasActivityClipboard) {
15212
+ const rejection = lookaheadParentsConfirmation(
15213
+ leafParentsPromotedByActions(actions, adapter, hasActivityClipboard),
15214
+ options
15215
+ );
15216
+ return rejection === null ? null : rejection.confirmation;
15217
+ }
15218
+ function leafParentsPromotedByActions(actions, adapter, hasActivityClipboard) {
15219
+ const parentIds = [];
15220
+ for (const action of actions) {
15221
+ parentIds.push(
15222
+ ...targetParentIds(action, adapter, hasActivityClipboard).map(String)
15223
+ );
15224
+ }
15225
+ return leafParentsPromotedBy(parentIds, adapter);
15226
+ }
15227
+ function targetParentIds(action, adapter, hasActivityClipboard) {
15228
+ switch (action.kind) {
15229
+ case DISPATCH_ACTION_KIND.ACTIVITY_CREATE:
15230
+ return [action.parentId];
15231
+ case DISPATCH_ACTION_KIND.ACTIVITY_MOVE: {
15232
+ const activity = adapter.getActivity(action.activityId);
15233
+ if (!activity) return [];
15234
+ if (normalizeParentKey(activity.parentId) === String(action.parentId)) {
15235
+ return [];
15236
+ }
15237
+ return movesIntoOwnBranch(action, adapter) ? [] : [action.parentId];
15238
+ }
15239
+ case DISPATCH_ACTION_KIND.ACTIVITY_INDENT:
15240
+ return planIndentMoves(action.activityIds, adapter).allowedMoves.map(
15241
+ (move) => move.newParentId
15242
+ );
15243
+ case DISPATCH_ACTION_KIND.ACTIVITY_PASTE_COPIED: {
15244
+ if (!hasActivityClipboard) return [];
15245
+ const destination = resolvePasteDestination(
15246
+ action.destination.afterSiblingId,
15247
+ adapter
15248
+ );
15249
+ return destination.ok ? [destination.parentId] : [];
15250
+ }
15251
+ case ACTIVITY_BATCH_ACTION_KIND:
15252
+ return action.mode === ACTIVITY_BATCH_MODE.APPEND ? action.items.flatMap(
15253
+ (item) => typeof item.parent === "string" ? [item.parent] : []
15254
+ ) : [];
15255
+ default:
15256
+ return [];
15257
+ }
15258
+ }
15259
+ function movesIntoOwnBranch(action, adapter) {
15260
+ if (isRootParent(action.parentId)) return false;
15261
+ if (action.parentId === action.activityId || adapter.isChildOf(action.parentId, action.activityId)) {
15262
+ return true;
15263
+ }
15264
+ return reparentClosesEffectiveCycle(
15265
+ adapter,
15266
+ /* @__PURE__ */ new Map([[action.activityId, action.parentId]])
15267
+ );
15268
+ }
15269
+
14932
15270
  // src/dispatch/creation-overrides.ts
14933
15271
  var PUBLIC_CREATION_OVERRIDE_FIELDS = /* @__PURE__ */ new Set([
14934
15272
  ...[...CREATION_OVERRIDE_FIELDS].filter(
@@ -15395,7 +15733,7 @@ function removeIncidentLinks(activityId, deps) {
15395
15733
  async function createActivityCore(action, deps, opts = {}) {
15396
15734
  const { adapter, scheduler, sector, calendars, activityIdGen, uidGen } = deps;
15397
15735
  if (!opts.skipCacheInvalidation) scheduler.invalidateAllCaches();
15398
- const target = validateCreateTarget(action, adapter);
15736
+ const target = validateCreateTarget(action, adapter, opts);
15399
15737
  if (!target.ok) return target;
15400
15738
  const { parent, anchorSiblingId, isRoot } = target;
15401
15739
  const newId = resolveNewActivityId(
@@ -15479,7 +15817,7 @@ async function createActivityCore(action, deps, opts = {}) {
15479
15817
  removedParentLinks
15480
15818
  };
15481
15819
  }
15482
- function validateCreateTarget(action, adapter) {
15820
+ function validateCreateTarget(action, adapter, opts) {
15483
15821
  const isRoot = isRootParent(action.parentId);
15484
15822
  const parent = isRoot ? null : adapter.getActivity(action.parentId);
15485
15823
  if (!isRoot && !parent) {
@@ -15501,6 +15839,13 @@ function validateCreateTarget(action, adapter) {
15501
15839
  anchorSiblingId
15502
15840
  );
15503
15841
  if (anchorError) return anchorError;
15842
+ if (parent !== null && !opts.parentsAlreadyChecked) {
15843
+ const rejection = lookaheadParentsConfirmation(
15844
+ leafParentsPromotedBy([action.parentId], adapter),
15845
+ opts
15846
+ );
15847
+ if (rejection) return rejection;
15848
+ }
15504
15849
  return { ok: true, parent, anchorSiblingId, isRoot };
15505
15850
  }
15506
15851
  function validateAnchorSibling(action, adapter, isRoot, anchorSiblingId) {
@@ -15599,7 +15944,11 @@ async function dispatchActivityCreate(action, options, deps) {
15599
15944
  if (!normalized2.ok) return normalized2;
15600
15945
  const core = await createActivityCore(
15601
15946
  { ...action, overrides: normalized2.value },
15602
- deps
15947
+ deps,
15948
+ {
15949
+ confirmedLookaheadParentIds: options.confirmedLookaheadParentIds,
15950
+ lookaheadTasksByActivityId: options.lookaheadTasksByActivityId
15951
+ }
15603
15952
  );
15604
15953
  if (!core.ok) return core;
15605
15954
  const { newId } = core;
@@ -15656,6 +16005,13 @@ async function dispatchActivityBatch(action, options, deps) {
15656
16005
  if (validationError) return { ok: false, reason: validationError };
15657
16006
  const plan = buildBatchPlan(action, deps);
15658
16007
  if (!plan.ok) return plan;
16008
+ if (action.mode === ACTIVITY_BATCH_MODE.APPEND) {
16009
+ const rejection = lookaheadParentsConfirmation(
16010
+ leafParentsPromotedBy(existingParentIds(plan.plan.items), deps.adapter),
16011
+ options
16012
+ );
16013
+ if (rejection) return rejection;
16014
+ }
15659
16015
  const existingActivityIds = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? new Set(deps.adapter.getAllIds().map((id) => String(id))) : /* @__PURE__ */ new Set();
15660
16016
  const beforeSnap = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? await snapshotActivities(deps.adapter, existingActivityIds) : /* @__PURE__ */ new Map();
15661
16017
  const beforeLinks = action.mode === ACTIVITY_BATCH_MODE.REPLACE ? snapshotAllLinks(deps) : /* @__PURE__ */ new Map();
@@ -15780,6 +16136,11 @@ async function dispatchActivityBatch(action, options, deps) {
15780
16136
  } : {}
15781
16137
  };
15782
16138
  }
16139
+ function existingParentIds(items) {
16140
+ return items.filter(
16141
+ (item) => item.parentBatchId === null && item.parentId !== ROOT_PARENT_ID
16142
+ ).map((item) => String(item.parentId));
16143
+ }
15783
16144
  function validateBatch(action, deps) {
15784
16145
  if (action.mode !== ACTIVITY_BATCH_MODE.APPEND && action.mode !== ACTIVITY_BATCH_MODE.REPLACE) {
15785
16146
  return REJECTION_REASON.INVALID;
@@ -16077,7 +16438,8 @@ async function createPlannedActivities(action, plannedItems, deps) {
16077
16438
  skipBeforeSnap: true,
16078
16439
  skipCacheInvalidation: true,
16079
16440
  skipRollupCascades: true,
16080
- insertionCorrelativeId: item.insertionCorrelativeId
16441
+ insertionCorrelativeId: item.insertionCorrelativeId,
16442
+ parentsAlreadyChecked: true
16081
16443
  }
16082
16444
  );
16083
16445
  if (!core.ok) return core;
@@ -16840,37 +17202,6 @@ function isAncestor(descendantId, candidateAncestorId, adapter) {
16840
17202
  return false;
16841
17203
  }
16842
17204
 
16843
- // src/dispatch/shared/hierarchy-cycle-guard.ts
16844
- function reparentClosesEffectiveCycle(adapter, reparented) {
16845
- const links = adapter.getAllLinks();
16846
- const verdict = validateTopologyCommit({
16847
- port: adapter,
16848
- currentLinks: links,
16849
- proposedLinks: links,
16850
- proposal: { reparented }
16851
- });
16852
- return !verdict.ok;
16853
- }
16854
- function collectCyclicReparents(planned, adapter) {
16855
- const cyclic = /* @__PURE__ */ new Set();
16856
- if (planned.length === 0) return cyclic;
16857
- const combined = new Map(
16858
- planned.map((reparent) => [reparent.activityId, reparent.newParentId])
16859
- );
16860
- if (!reparentClosesEffectiveCycle(adapter, combined)) return cyclic;
16861
- const accepted2 = /* @__PURE__ */ new Map();
16862
- for (const reparent of planned) {
16863
- const trial = new Map(accepted2);
16864
- trial.set(reparent.activityId, reparent.newParentId);
16865
- if (reparentClosesEffectiveCycle(adapter, trial)) {
16866
- cyclic.add(reparent.activityId);
16867
- continue;
16868
- }
16869
- accepted2.set(reparent.activityId, reparent.newParentId);
16870
- }
16871
- return cyclic;
16872
- }
16873
-
16874
17205
  // src/dispatch/handlers/activity-move.ts
16875
17206
  async function dispatchActivityMove(action, options, deps) {
16876
17207
  const { adapter, scheduler, sector } = deps;
@@ -16923,6 +17254,13 @@ async function dispatchActivityMove(action, options, deps) {
16923
17254
  return { ok: false, reason: REJECTION_REASON.HIERARCHY_CYCLE };
16924
17255
  }
16925
17256
  }
17257
+ if (parentChanged && !newParentIsRoot) {
17258
+ const rejection = lookaheadParentsConfirmation(
17259
+ leafParentsPromotedBy([action.parentId], adapter),
17260
+ options
17261
+ );
17262
+ if (rejection) return rejection;
17263
+ }
16926
17264
  const touched = /* @__PURE__ */ new Set([action.activityId]);
16927
17265
  if (parentChanged) {
16928
17266
  if (oldParentKey !== ROOT_PARENT_ID) touched.add(oldParentKey);
@@ -17082,62 +17420,6 @@ async function dispatchActivityMove(action, options, deps) {
17082
17420
  };
17083
17421
  }
17084
17422
 
17085
- // src/dispatch/shared/indent-planning.ts
17086
- function planIndentMoves(activityIds, adapter) {
17087
- const selectedSet = new Set(activityIds);
17088
- const failed = [];
17089
- const moves = [];
17090
- const plannedIds = /* @__PURE__ */ new Set();
17091
- const blockingSet = new Set(selectedSet);
17092
- const sortedIds = [...activityIds].sort((a, b) => {
17093
- const aSnap = adapter.getActivity(a);
17094
- const bSnap = adapter.getActivity(b);
17095
- const ac = aSnap ? readCorrelativeId(aSnap) : NOT_SET_SORT_VALUE2;
17096
- const bc = bSnap ? readCorrelativeId(bSnap) : NOT_SET_SORT_VALUE2;
17097
- return ac - bc;
17098
- });
17099
- for (const id of sortedIds) {
17100
- const activity = adapter.getActivity(id);
17101
- if (!activity) {
17102
- failed.push({
17103
- activityId: id,
17104
- reason: REJECTION_REASON.ACTIVITY_NOT_FOUND
17105
- });
17106
- blockingSet.delete(id);
17107
- continue;
17108
- }
17109
- if (hasAncestorInSet(id, plannedIds, adapter)) continue;
17110
- const targetParentId = findPreviousNonSelectedSibling(
17111
- id,
17112
- blockingSet,
17113
- adapter
17114
- );
17115
- if (targetParentId == null) {
17116
- failed.push({
17117
- activityId: id,
17118
- reason: REJECTION_REASON.INDENT_NO_ELIGIBLE_SIBLING
17119
- });
17120
- blockingSet.delete(id);
17121
- continue;
17122
- }
17123
- const oldParentRaw = adapter.getActivity(id)?.parentId;
17124
- const oldParentId = isRootParent(oldParentRaw) ? ROOT_PARENT_ID : String(oldParentRaw);
17125
- moves.push({ activityId: id, newParentId: targetParentId, oldParentId });
17126
- plannedIds.add(id);
17127
- }
17128
- const cyclicMoveIds = collectCyclicReparents(moves, adapter);
17129
- for (const cyclicId of cyclicMoveIds) {
17130
- failed.push({
17131
- activityId: cyclicId,
17132
- reason: REJECTION_REASON.HIERARCHY_CYCLE
17133
- });
17134
- }
17135
- const allowedMoves = moves.filter(
17136
- (move) => !cyclicMoveIds.has(move.activityId)
17137
- );
17138
- return { moves, cyclicMoveIds, allowedMoves, failed };
17139
- }
17140
-
17141
17423
  // src/dispatch/handlers/activity-indent.ts
17142
17424
  async function dispatchActivityIndent(action, options, deps) {
17143
17425
  const { adapter, scheduler, sector } = deps;
@@ -17159,6 +17441,14 @@ async function dispatchActivityIndent(action, options, deps) {
17159
17441
  }
17160
17442
  }
17161
17443
  const allowedMoves = plan.allowedMoves;
17444
+ const rejection = lookaheadParentsConfirmation(
17445
+ leafParentsPromotedBy(
17446
+ allowedMoves.map((move) => move.newParentId),
17447
+ adapter
17448
+ ),
17449
+ options
17450
+ );
17451
+ if (rejection) return rejection;
17162
17452
  const beforeSnap = await snapshotActivities(adapter, touched);
17163
17453
  for (const { activityId, newParentId } of allowedMoves) {
17164
17454
  adapter.setActivityField(activityId, ACTIVITY_PROPERTY.PARENT, newParentId);
@@ -18956,146 +19246,6 @@ function dispatchActivityClipboardImport(action, deps) {
18956
19246
  return clipboardOnlyResult(action);
18957
19247
  }
18958
19248
 
18959
- // src/activity-copy-paste/paste.ts
18960
- function resolveDestination(afterSiblingId, adapter) {
18961
- const sibling = adapter.getActivity(afterSiblingId);
18962
- if (!sibling) {
18963
- return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
18964
- }
18965
- if (sibling.parentId == null) {
18966
- return adapter.getRootIds().length === 1 ? {
18967
- ok: true,
18968
- parentId: sibling.id,
18969
- beforeSiblingId: adapter.getChildren(sibling.id)[0]
18970
- } : { ok: true, parentId: ROOT_PARENT_ID, afterSiblingId };
18971
- }
18972
- if (adapter.hasChildren(sibling.id)) {
18973
- return {
18974
- ok: true,
18975
- parentId: sibling.id,
18976
- beforeSiblingId: adapter.getChildren(sibling.id)[0]
18977
- };
18978
- }
18979
- return {
18980
- ok: true,
18981
- parentId: String(sibling.parentId),
18982
- afterSiblingId
18983
- };
18984
- }
18985
- var invariantFailure = () => ({
18986
- ok: false,
18987
- reason: REJECTION_REASON.ACTIVITY_PASTE_INVARIANT_FAILED
18988
- });
18989
- async function dispatchActivityPasteCopied(action, options, deps) {
18990
- const clipboard = deps.activityClipboard.value;
18991
- if (clipboard === null) {
18992
- return { ok: false, reason: REJECTION_REASON.CLIPBOARD_EMPTY };
18993
- }
18994
- const destination = resolveDestination(
18995
- action.destination.afterSiblingId,
18996
- deps.adapter
18997
- );
18998
- if (!destination.ok) return destination;
18999
- const beforeSnapshot = await snapshotActivities(
19000
- deps.adapter,
19001
- /* @__PURE__ */ new Set()
19002
- );
19003
- const touched = /* @__PURE__ */ new Set();
19004
- const originalToNew = /* @__PURE__ */ new Map();
19005
- const createdIds = [];
19006
- let previousRootId;
19007
- for (const copied of clipboard.activities) {
19008
- const mappedParent = originalToNew.get(copied.originalParentId);
19009
- const afterSiblingId = mappedParent === void 0 ? previousRootId ?? destination.afterSiblingId : void 0;
19010
- const beforeSiblingId = mappedParent === void 0 && afterSiblingId === void 0 ? destination.beforeSiblingId : void 0;
19011
- const created = await createActivityCore(
19012
- {
19013
- kind: DISPATCH_ACTION_KIND.ACTIVITY_CREATE,
19014
- parentId: mappedParent ?? destination.parentId,
19015
- ...afterSiblingId === void 0 ? {} : { afterSiblingId },
19016
- ...beforeSiblingId === void 0 ? {} : { beforeSiblingId },
19017
- overrides: cloneDomainValue(copied.overrides),
19018
- eventSource: action.eventSource
19019
- },
19020
- deps,
19021
- {
19022
- skipCorrelativeRecompute: true,
19023
- skipBeforeSnap: true,
19024
- customIdReferenceId: action.destination.afterSiblingId
19025
- }
19026
- );
19027
- if (!created.ok) return created;
19028
- originalToNew.set(copied.originalId, created.newId);
19029
- createdIds.push(created.newId);
19030
- if (mappedParent === void 0) previousRootId = created.newId;
19031
- }
19032
- const correlativeShifts = recomputeCorrelativeIds(deps.adapter);
19033
- const createdIdSet = new Set(createdIds.map(String));
19034
- foldCorrelativeShifts(
19035
- deps.adapter,
19036
- correlativeShifts,
19037
- createdIdSet,
19038
- beforeSnapshot,
19039
- touched
19040
- );
19041
- for (const createdId of createdIds) touched.add(String(createdId));
19042
- const appliedLinks = [];
19043
- for (const copiedLink of clipboard.links) {
19044
- const source = originalToNew.get(copiedLink.source);
19045
- const target = originalToNew.get(copiedLink.target);
19046
- if (source === void 0 || target === void 0) return invariantFailure();
19047
- const operation = {
19048
- kind: LINK_OPERATION_KIND.CREATE,
19049
- source,
19050
- target,
19051
- type: copiedLink.type,
19052
- lag: copiedLink.lag
19053
- };
19054
- const result = applyLinkOperation(operation, {
19055
- port: deps.adapter,
19056
- newLinkId: deps.linkIdGen.next
19057
- });
19058
- if (!result.applied) return invariantFailure();
19059
- appliedLinks.push({
19060
- op: operation,
19061
- finalLinkId: result.linkId,
19062
- rejected: null
19063
- });
19064
- }
19065
- const { scheduledIds } = await runPostMutation(
19066
- {
19067
- adapter: deps.adapter,
19068
- scheduler: deps.scheduler,
19069
- sector: deps.sector,
19070
- defaultBaseCalendarId: deps.defaultBaseCalendarId,
19071
- runAutomaticP6Schedule: deps.runAutomaticP6Schedule
19072
- },
19073
- {
19074
- action,
19075
- autoscheduleFrom: createdIds[0] ?? null,
19076
- recomputeParentsFrom: createdIds,
19077
- now: deps.now,
19078
- options
19079
- }
19080
- );
19081
- const linkChanges = buildLinkChangesForBatch(
19082
- appliedLinks,
19083
- /* @__PURE__ */ new Map(),
19084
- deps.adapter
19085
- );
19086
- return {
19087
- ok: true,
19088
- changes: await assembleChangeSet(deps.adapter, {
19089
- source: action,
19090
- beforeSnap: beforeSnapshot,
19091
- touchedIds: touched,
19092
- scheduledIds,
19093
- links: linkChanges,
19094
- hoursPerDay: deps.sector.hoursPerDay
19095
- })
19096
- };
19097
- }
19098
-
19099
19249
  // src/activity-copy-paste/duplicate.ts
19100
19250
  var CLONE_YIELD_EVERY_N = 200;
19101
19251
  var CREATE_OPTIONS = {
@@ -32112,6 +32262,41 @@ var ScheduleCore = class _ScheduleCore {
32112
32262
  ...new Set(plan.allowedMoves.map((move) => String(move.newParentId)))
32113
32263
  ];
32114
32264
  }
32265
+ /**
32266
+ * Lists the leaf activities the actions would turn into parents.
32267
+ *
32268
+ * Each action is checked against the current state, not against the state
32269
+ * left by the actions before it.
32270
+ *
32271
+ * @param actions - Actions the caller is about to dispatch.
32272
+ */
32273
+ getLeafParentIdsPromotedBy(actions) {
32274
+ this.assertReady();
32275
+ return leafParentIdsPromotedByActions(
32276
+ actions,
32277
+ this.coreRuntime.state,
32278
+ this._activityClipboard.value !== null
32279
+ );
32280
+ }
32281
+ /**
32282
+ * Returns the lookahead confirmation the dispatch rule would raise for the
32283
+ * actions, or `null` when none is needed. The rule code is the dispatch rule
32284
+ * and nothing changes in the schedule. Like
32285
+ * {@link ScheduleCore.getLeafParentIdsPromotedBy}, it checks each action
32286
+ * against the current state.
32287
+ *
32288
+ * @param actions - Actions the caller is about to dispatch.
32289
+ * @param options - The `confirmedLookaheadParentIds` and `lookaheadTasksByActivityId` the dispatch would receive.
32290
+ */
32291
+ getLookaheadConfirmation(actions, options = {}) {
32292
+ this.assertReady();
32293
+ return lookaheadConfirmationForActions(
32294
+ actions,
32295
+ options,
32296
+ this.coreRuntime.state,
32297
+ this._activityClipboard.value !== null
32298
+ );
32299
+ }
32115
32300
  /**
32116
32301
  * Reads defensive direct-child snapshots in correlative order.
32117
32302
  *
@@ -34157,8 +34342,8 @@ function resolveExpectedFinishDateSelection(input) {
34157
34342
  });
34158
34343
  }
34159
34344
 
34160
- // src/p6/lookahead-derived.ts
34161
- function createLookaheadDerivedFields(input) {
34345
+ // src/calendar/schedule-calendars.ts
34346
+ function createScheduleCalendars(input) {
34162
34347
  const normalized2 = normalizeCalendars(
34163
34348
  [...input.calendars],
34164
34349
  void 0,
@@ -34167,11 +34352,22 @@ function createLookaheadDerivedFields(input) {
34167
34352
  const { reader } = createCalendarSetup(
34168
34353
  serializeCalendars(normalized2.calendars, normalized2.baseCalendars)
34169
34354
  );
34355
+ return {
34356
+ defaultCalendarId: normalized2.defaultCalendarId,
34357
+ resolveCalendar(calendarId) {
34358
+ const knownCalendarId = calendarId !== null && normalized2.knownCalendarIds.has(calendarId) ? calendarId : normalized2.defaultCalendarId;
34359
+ return reader.getCalendar(knownCalendarId);
34360
+ }
34361
+ };
34362
+ }
34363
+
34364
+ // src/p6/lookahead-derived.ts
34365
+ function createLookaheadDerivedFields(input) {
34366
+ const { resolveCalendar } = createScheduleCalendars(input);
34170
34367
  return {
34171
34368
  derive({ previous, next, calendarId, dataDate, totalFloatMethod }) {
34172
34369
  if (!isP6ActivityState(next)) return null;
34173
- const knownCalendarId = calendarId !== null && normalized2.knownCalendarIds.has(calendarId) ? calendarId : normalized2.defaultCalendarId;
34174
- const calendar = reader.getCalendar(knownCalendarId);
34370
+ const calendar = resolveCalendar(calendarId);
34175
34371
  if (!calendar) return null;
34176
34372
  const { p6 } = projectDataDateLocalActivity(previous, next, {
34177
34373
  calendar,
@@ -34189,6 +34385,23 @@ function createLookaheadDerivedFields(input) {
34189
34385
  };
34190
34386
  }
34191
34387
 
34388
+ // src/calendar/reader.ts
34389
+ function createCalendarReader(input) {
34390
+ const { defaultCalendarId, resolveCalendar } = createScheduleCalendars(input);
34391
+ if (defaultCalendarId === null) {
34392
+ throw new Error("Calendar reader requires at least one calendar");
34393
+ }
34394
+ return {
34395
+ getCalendar(calendarId) {
34396
+ const calendar = resolveCalendar(calendarId);
34397
+ if (!calendar) {
34398
+ throw new Error("Resolved calendar missing after normalization");
34399
+ }
34400
+ return calendar;
34401
+ }
34402
+ };
34403
+ }
34404
+
34192
34405
  // src/p6/property-contract.ts
34193
34406
  var row = (input) => {
34194
34407
  const lifecycle = input.lifecycle ?? (input.classification === "f9_output" ? "f9" : "explicit_edit");
@@ -34828,7 +35041,6 @@ var DEFAULT_HOURS_PER_DAY = 8;
34828
35041
  // src/creation/creation-kinds.ts
34829
35042
  var CREATION_KIND = {
34830
35043
  CHILD: "child",
34831
- CHILD_CONFIRMED: "child-confirmed",
34832
35044
  LINE: "line",
34833
35045
  ROOT: "root"
34834
35046
  };
@@ -34875,6 +35087,7 @@ exports.LINK_REJECTION_REASON = LINK_REJECTION_REASON;
34875
35087
  exports.LINK_RELATIONSHIP = LINK_RELATIONSHIP;
34876
35088
  exports.LINK_TYPE = LINK_TYPE;
34877
35089
  exports.LINK_TYPE_CODE = LINK_TYPE_CODE;
35090
+ exports.LOOKAHEAD_CONFIRMATION_KIND = LOOKAHEAD_CONFIRMATION_KIND;
34878
35091
  exports.LookaheadActualTransitionError = LookaheadActualTransitionError;
34879
35092
  exports.MINIMUM_GROUP_DEPTH = MINIMUM_GROUP_DEPTH;
34880
35093
  exports.MINIMUM_GROUP_LEVEL = MINIMUM_GROUP_LEVEL;
@@ -34900,6 +35113,7 @@ exports.appendGroupRowId = appendGroupRowId;
34900
35113
  exports.buildGroupRowId = buildGroupRowId;
34901
35114
  exports.checkNoUpdatedLinks = checkNoUpdatedLinks;
34902
35115
  exports.computeExpectedProgress = computeExpectedProgress;
35116
+ exports.createCalendarReader = createCalendarReader;
34903
35117
  exports.createLookaheadDerivedFields = createLookaheadDerivedFields;
34904
35118
  exports.createOutbuildDataDateScheduleSettings = createOutbuildP6ScheduleSettings;
34905
35119
  exports.createSchedule = createSchedule;