@outbuild-company/schedule-core 1.15.13 → 1.15.15

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
@@ -12766,258 +12766,6 @@ function normalizeActivityCreationOverrides(raw, hoursPerDay) {
12766
12766
  };
12767
12767
  }
12768
12768
 
12769
- // src/dispatch/handlers/activity-create.ts
12770
- async function createActivityCore(action, deps, opts = {}) {
12771
- const { adapter, scheduler, sector, calendars, activityIdGen, uidGen } = deps;
12772
- if (!opts.skipCacheInvalidation) scheduler.invalidateAllCaches();
12773
- const target = validateCreateTarget(action, adapter);
12774
- if (!target.ok) return target;
12775
- const { parent, anchorSiblingId, isRoot } = target;
12776
- const newId = resolveNewActivityId(
12777
- action.activityId,
12778
- adapter,
12779
- activityIdGen,
12780
- deps.isActivityIdReserved
12781
- );
12782
- const newUid = uidGen.next();
12783
- const activityInput = {
12784
- id: newId,
12785
- parentId: isRoot ? null : action.parentId,
12786
- sector: {
12787
- id: sector.id,
12788
- hoursPerDay: sector.hoursPerDay,
12789
- defaultCalendar: findDefaultCalendarId(calendars)
12790
- },
12791
- numTasksInGantt: adapter.activityCount(),
12792
- nextUniqueCorrelativeId: newUid,
12793
- activityOverrides: action.overrides ?? {}
12794
- };
12795
- const created = buildNewActivity(activityInput, {
12796
- getActivity: (id) => adapter.getActivity(id),
12797
- calculateEndDate: (params) => adapter.calculateEndDate(params),
12798
- getClosestWorkTime: (params) => adapter.getClosestWorkTime(params)
12799
- });
12800
- const newActivity = created.newTask;
12801
- newActivity.realWorkHours = computeLeafRealWork(
12802
- newActivity.workHours,
12803
- newActivity.progress
12804
- );
12805
- assignCustomId(newActivity, action, deps, anchorSiblingId, parent, opts);
12806
- const insertionCorrelativeId = opts.insertionCorrelativeId ?? computeInsertionCorrelativeId(
12807
- adapter,
12808
- isRoot ? ROOT_PARENT_ID : action.parentId,
12809
- action.afterSiblingId,
12810
- action.beforeSiblingId
12811
- );
12812
- newActivity.correlativeId = insertionCorrelativeId;
12813
- const initialTouched = /* @__PURE__ */ new Set([newId]);
12814
- if (parent) initialTouched.add(parent.id);
12815
- const parentWasLeaf = parent !== null && adapter.getChildren(parent.id).length === 0;
12816
- const parentCustomId = parentWasLeaf ? parent?.customId : null;
12817
- const beforeSnap = opts.skipBeforeSnap ? /* @__PURE__ */ new Map() : await snapshotActivities(adapter, initialTouched);
12818
- adapter.addActivity(newActivity);
12819
- applyParentMutations(action, newId, parent, adapter, parentWasLeaf);
12820
- if (parentCustomId) {
12821
- deps.customIdTracker.releaseCustomId(parentCustomId);
12822
- }
12823
- if (!opts.skipRollupCascades) {
12824
- recomputeHhCascadeForActivity(newId, adapter);
12825
- recomputeUsedCostCascadeForActivity(newId, adapter);
12826
- recomputeCostCascadeForActivity(newId, adapter);
12827
- }
12828
- const correlativeShifts = recomputeCorrelatives(
12829
- adapter,
12830
- newId,
12831
- initialTouched,
12832
- opts
12833
- );
12834
- return {
12835
- ok: true,
12836
- newId,
12837
- parent,
12838
- newActivity,
12839
- beforeSnap,
12840
- initialTouched,
12841
- correlativeShifts
12842
- };
12843
- }
12844
- function validateCreateTarget(action, adapter) {
12845
- const isRoot = isRootParent(action.parentId);
12846
- const parent = isRoot ? null : adapter.getActivity(action.parentId);
12847
- if (!isRoot && !parent) {
12848
- return { ok: false, reason: REJECTION_REASON.PARENT_NOT_FOUND };
12849
- }
12850
- const isParentFrozenBySir = parent !== null && isFrozen(parent);
12851
- if (isParentFrozenBySir) {
12852
- return { ok: false, reason: REJECTION_REASON.PARENT_FROZEN_BY_SIR };
12853
- }
12854
- const hasConflictingAnchors = action.afterSiblingId !== void 0 && action.beforeSiblingId !== void 0;
12855
- if (hasConflictingAnchors) {
12856
- return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_CONFLICT };
12857
- }
12858
- const anchorSiblingId = action.afterSiblingId ?? action.beforeSiblingId;
12859
- const anchorError = validateAnchorSibling(
12860
- action,
12861
- adapter,
12862
- isRoot,
12863
- anchorSiblingId
12864
- );
12865
- if (anchorError) return anchorError;
12866
- return { ok: true, parent, anchorSiblingId, isRoot };
12867
- }
12868
- function validateAnchorSibling(action, adapter, isRoot, anchorSiblingId) {
12869
- if (anchorSiblingId === void 0) return null;
12870
- const sibling = adapter.getActivity(anchorSiblingId);
12871
- if (!sibling) {
12872
- return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
12873
- }
12874
- const expectedSiblingParent = isRoot ? ROOT_PARENT_ID : action.parentId;
12875
- const actualSiblingParent = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
12876
- if (actualSiblingParent !== expectedSiblingParent) {
12877
- return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_WRONG_PARENT };
12878
- }
12879
- return null;
12880
- }
12881
- function resolveNewActivityId(explicitId, adapter, activityIdGen, isActivityIdReserved) {
12882
- const nextAvailableId = () => {
12883
- let candidate = activityIdGen.next();
12884
- while (adapter.activityExists(candidate) || isActivityIdReserved(String(candidate))) {
12885
- candidate = activityIdGen.next();
12886
- }
12887
- return candidate;
12888
- };
12889
- if (explicitId === void 0) return nextAvailableId();
12890
- if (adapter.activityExists(explicitId) || isActivityIdReserved(String(explicitId))) {
12891
- return nextAvailableId();
12892
- }
12893
- activityIdGen.bump(explicitId);
12894
- return explicitId;
12895
- }
12896
- function readExplicitCustomId(action) {
12897
- const value = action.overrides?.customId;
12898
- return typeof value === "string" ? value : void 0;
12899
- }
12900
- function assignCustomId(newActivity, action, deps, anchorSiblingId, parent, opts) {
12901
- const { adapter, customIdTracker } = deps;
12902
- const explicit = readExplicitCustomId(action);
12903
- if (explicit !== void 0) {
12904
- if (explicit.length === 0) return;
12905
- if (!customIdTracker.isCustomIdInUse(explicit)) {
12906
- customIdTracker.registerCustomId(explicit);
12907
- return;
12908
- }
12909
- }
12910
- const customIdAnchorId = opts.customIdReferenceId ?? anchorSiblingId;
12911
- const derivesFromAnchor = customIdAnchorId !== void 0;
12912
- const referenceActivity = derivesFromAnchor ? adapter.getActivity(customIdAnchorId) : parent;
12913
- newActivity.customId = customIdTracker.generateCustomIdForNewChild({
12914
- activity: referenceActivity ?? {},
12915
- useActivityCustomIdAsReference: derivesFromAnchor
12916
- });
12917
- }
12918
- function applyParentMutations(action, newId, parent, adapter, parentWasLeaf) {
12919
- if (!parent) return;
12920
- const parentMutations = buildParentMutations({
12921
- parentId: action.parentId,
12922
- newChildId: newId,
12923
- isCurrentlyLeaf: parentWasLeaf,
12924
- getParent: (id) => adapter.getActivity(id) ?? null
12925
- });
12926
- if (!parentMutations) return;
12927
- for (const [key, value] of Object.entries(parentMutations.fields)) {
12928
- setActivityFieldDynamic(adapter, parentMutations.parentId, key, value);
12929
- }
12930
- if (parentMutations.promotionSnapshot) {
12931
- adapter.setPromotionSnapshot(
12932
- parentMutations.parentId,
12933
- parentMutations.promotionSnapshot
12934
- );
12935
- }
12936
- }
12937
- function recomputeCorrelatives(adapter, newId, initialTouched, opts) {
12938
- if (opts.skipCorrelativeRecompute) return [];
12939
- const shifts = recomputeCorrelativeIds(adapter).filter(
12940
- (shift) => shift.activityId !== String(newId)
12941
- );
12942
- for (const shift of shifts) initialTouched.add(shift.activityId);
12943
- return shifts;
12944
- }
12945
- async function buildCreateChangeSet(adapter, action, coreResult, scheduledIds, hoursPerDay) {
12946
- const { newId, newActivity, beforeSnap, initialTouched, correlativeShifts } = coreResult;
12947
- const trackingEvent = {
12948
- name: DISPATCH_TRACK_EVENT.ACTIVITY_CREATION,
12949
- properties: {
12950
- parent_id: action.parentId,
12951
- activity_id: newId,
12952
- created_duration: newActivity.durationHours
12953
- }
12954
- };
12955
- if (action.eventSource !== void 0) {
12956
- trackingEvent.properties.event_source = action.eventSource;
12957
- }
12958
- return assembleChangeSet(adapter, {
12959
- source: action,
12960
- beforeSnap,
12961
- touchedIds: initialTouched,
12962
- scheduledIds,
12963
- correlativeShifts,
12964
- trackingEvents: [trackingEvent]});
12965
- }
12966
- async function dispatchActivityCreate(action, options, deps) {
12967
- const { adapter, scheduler, sector } = deps;
12968
- const normalized2 = normalizeActivityCreationOverrides(
12969
- action.overrides,
12970
- sector.hoursPerDay
12971
- );
12972
- if (!normalized2.ok) return normalized2;
12973
- const core = await createActivityCore(
12974
- { ...action, overrides: normalized2.value },
12975
- deps
12976
- );
12977
- if (!core.ok) return core;
12978
- const { newId } = core;
12979
- const createdAutoScheduling = core.newActivity.autoScheduling;
12980
- adapter.setActivityField(newId, "autoScheduling", false);
12981
- let scheduleOutcome;
12982
- try {
12983
- scheduleOutcome = await runPostMutation(
12984
- {
12985
- adapter,
12986
- scheduler,
12987
- sector: deps.sector,
12988
- defaultBaseCalendarId: deps.defaultBaseCalendarId
12989
- },
12990
- {
12991
- action,
12992
- autoscheduleFrom: newId,
12993
- recomputeParentsFrom: [newId],
12994
- preventSummaryTargetPullback: true,
12995
- now: deps.now,
12996
- options
12997
- }
12998
- );
12999
- } finally {
13000
- adapter.setActivityField(newId, "autoScheduling", createdAutoScheduling);
13001
- }
13002
- const { scheduledIds } = scheduleOutcome;
13003
- const changeset = await buildCreateChangeSet(
13004
- adapter,
13005
- action,
13006
- core,
13007
- scheduledIds,
13008
- sector.hoursPerDay
13009
- );
13010
- return { ok: true, changes: changeset };
13011
- }
13012
- function findDefaultCalendarId(calendars) {
13013
- for (const calendar of calendars.values()) {
13014
- if (calendar.is_default) return calendar.id;
13015
- }
13016
- const first = calendars.values().next();
13017
- if (!first.done && first.value) return first.value.id;
13018
- return "global";
13019
- }
13020
-
13021
12769
  // src/propagations/upward/recompute-rollup-cascades.ts
13022
12770
  function recomputeRollupCascadesForParent(parentId, adapter) {
13023
12771
  recomputeHhCascadeForParent(parentId, adapter);
@@ -13190,137 +12938,417 @@ async function dispatchActivityDelete(action, options, deps) {
13190
12938
  for (const linkId of incidentLinkIds) {
13191
12939
  adapter.removeLink(linkId);
13192
12940
  }
13193
- const viewStateChanges2 = captureDeletedViewState(adapter, toDelete);
13194
- let removed = 0;
13195
- for (const id of toDelete) {
13196
- removed += 1;
13197
- if (removed % DELETE_YIELD_EVERY_N === 0) await yieldToBrowser();
13198
- adapter.removeActivity(id);
12941
+ const viewStateChanges2 = captureDeletedViewState(adapter, toDelete);
12942
+ let removed = 0;
12943
+ for (const id of toDelete) {
12944
+ removed += 1;
12945
+ if (removed % DELETE_YIELD_EVERY_N === 0) await yieldToBrowser();
12946
+ adapter.removeActivity(id);
12947
+ }
12948
+ for (const parentId of parentsToCheck) {
12949
+ const parent = adapter.getActivity(parentId);
12950
+ if (!parent) continue;
12951
+ applyParentNewActivitiesCleanup(parent, newActivitiesToDelete, adapter);
12952
+ const remainingChildren = adapter.getChildren(parentId);
12953
+ const demotion = buildParentDemotionMutations({
12954
+ parent,
12955
+ remainingChildIds: remainingChildren.map(String),
12956
+ defaultDurationHours: sector.hoursPerDay,
12957
+ promotionSnapshot: adapter.getPromotionSnapshot(parentId),
12958
+ computeEndDate: (startDate, durationHours) => adapter.calculateEndDate({ startDate, durationHours, task: parent }),
12959
+ idsRemoved: toDelete
12960
+ });
12961
+ if (!demotion) {
12962
+ recomputeSingleParentDisplayBounds(parentId, adapter);
12963
+ continue;
12964
+ }
12965
+ for (const [key, value] of Object.entries(demotion.fields)) {
12966
+ setActivityFieldDynamic(adapter, parentId, key, value);
12967
+ }
12968
+ if (parent.customId == null || parent.customId === "") {
12969
+ const previousSiblingId = findPrevSiblingHelper(parentId, adapter);
12970
+ const previousCustomId = previousSiblingId ? adapter.getActivity(previousSiblingId)?.customId : null;
12971
+ const generated = customIdTracker.generateCustomIdForRestoredChild(
12972
+ typeof previousCustomId === "string" && previousCustomId ? previousCustomId : null
12973
+ );
12974
+ adapter.setActivityField(
12975
+ parentId,
12976
+ ACTIVITY_PROPERTY.CUSTOM_ID,
12977
+ generated
12978
+ );
12979
+ }
12980
+ }
12981
+ for (const parentId of parentsToCheck) {
12982
+ recomputeRollupCascadesForParent(parentId, adapter);
12983
+ }
12984
+ const correlativeShifts = recomputeCorrelativeIds(adapter);
12985
+ const { scheduledIds } = await runPostMutation(
12986
+ {
12987
+ adapter,
12988
+ scheduler,
12989
+ sector: deps.sector,
12990
+ defaultBaseCalendarId: deps.defaultBaseCalendarId
12991
+ },
12992
+ {
12993
+ action,
12994
+ autoscheduleFrom: AUTOSCHEDULE_SCOPE.ROOTS,
12995
+ recomputeParentsFrom: parentsToCheck,
12996
+ now: deps.now,
12997
+ options
12998
+ }
12999
+ );
13000
+ const trackingEvent = {
13001
+ name: DISPATCH_TRACK_EVENT.ACTIVITY_DELETION,
13002
+ properties: {
13003
+ activity_ids: [...action.activityIds],
13004
+ cascade_count: toDelete.size
13005
+ }
13006
+ };
13007
+ if (action.eventSource !== void 0) {
13008
+ trackingEvent.properties.event_source = action.eventSource;
13009
+ }
13010
+ const beforeLinksByStringId = /* @__PURE__ */ new Map();
13011
+ for (const [id, l] of beforeLinks) beforeLinksByStringId.set(id, l);
13012
+ const deletedRowsSnap = /* @__PURE__ */ new Map();
13013
+ for (const id of toDelete) {
13014
+ const snap = beforeSnap.get(String(id));
13015
+ if (snap) deletedRowsSnap.set(String(id), snap);
13016
+ }
13017
+ const changes = await assembleChangeSet(adapter, {
13018
+ source: action,
13019
+ beforeSnap,
13020
+ touchedIds,
13021
+ scheduledIds,
13022
+ sirDetection: SIR_DETECTION_PHASE.AFTER_DIFF,
13023
+ links: buildLinkDeletions(beforeLinks),
13024
+ trackingEvents: [trackingEvent],
13025
+ hoursPerDay: sector.hoursPerDay,
13026
+ correlativeShifts
13027
+ });
13028
+ return {
13029
+ ok: true,
13030
+ changes: viewStateChanges2.length > 0 ? { ...changes, viewState: viewStateChanges2 } : changes,
13031
+ __beforeSnap: deletedRowsSnap,
13032
+ __beforeLinks: beforeLinksByStringId
13033
+ };
13034
+ }
13035
+ function applyParentNewActivitiesCleanup(parent, idsRemoved, adapter) {
13036
+ const cleanup = buildParentNewActivitiesCleanup({ parent, idsRemoved });
13037
+ if (!cleanup) return;
13038
+ for (const [key, value] of Object.entries(cleanup.fields)) {
13039
+ setActivityFieldDynamic(adapter, cleanup.parentId, key, value);
13040
+ }
13041
+ }
13042
+ function captureDeletedViewState(adapter, toDelete) {
13043
+ const changes = [];
13044
+ for (const id of toDelete) {
13045
+ const wasChecked = adapter.isChecked(id);
13046
+ const wasVisible = adapter.isVisible(id);
13047
+ if (!wasChecked && wasVisible) continue;
13048
+ changes.push({
13049
+ activityId: String(id),
13050
+ ...wasChecked ? { checked: { before: true, after: false } } : {},
13051
+ ...wasVisible ? {} : { visible: { before: false, after: true } }
13052
+ });
13053
+ }
13054
+ return changes;
13055
+ }
13056
+ function buildLinkDeletions(beforeLinks) {
13057
+ const out = [];
13058
+ for (const [id, before] of beforeLinks.entries()) {
13059
+ out.push({
13060
+ id,
13061
+ kind: ENTITY_CHANGE_KIND.DELETED,
13062
+ fields: {
13063
+ source: { before: String(before.source), after: void 0 },
13064
+ target: { before: String(before.target), after: void 0 },
13065
+ type: { before: before.type, after: void 0 },
13066
+ lag: { before: Number(before.lag), after: void 0 }
13067
+ },
13068
+ after: null
13069
+ });
13070
+ }
13071
+ return out;
13072
+ }
13073
+
13074
+ // src/dispatch/handlers/activity-create.ts
13075
+ async function createActivityCore(action, deps, opts = {}) {
13076
+ const { adapter, scheduler, sector, calendars, activityIdGen, uidGen } = deps;
13077
+ if (!opts.skipCacheInvalidation) scheduler.invalidateAllCaches();
13078
+ const target = validateCreateTarget(action, adapter);
13079
+ if (!target.ok) return target;
13080
+ const { parent, anchorSiblingId, isRoot } = target;
13081
+ const newId = resolveNewActivityId(
13082
+ action.activityId,
13083
+ adapter,
13084
+ activityIdGen,
13085
+ deps.isActivityIdReserved
13086
+ );
13087
+ const newUid = uidGen.next();
13088
+ const activityInput = {
13089
+ id: newId,
13090
+ parentId: isRoot ? null : action.parentId,
13091
+ sector: {
13092
+ id: sector.id,
13093
+ hoursPerDay: sector.hoursPerDay,
13094
+ defaultCalendar: findDefaultCalendarId(calendars)
13095
+ },
13096
+ numTasksInGantt: adapter.activityCount(),
13097
+ nextUniqueCorrelativeId: newUid,
13098
+ activityOverrides: action.overrides ?? {}
13099
+ };
13100
+ const created = buildNewActivity(activityInput, {
13101
+ getActivity: (id) => adapter.getActivity(id),
13102
+ calculateEndDate: (params) => adapter.calculateEndDate(params),
13103
+ getClosestWorkTime: (params) => adapter.getClosestWorkTime(params)
13104
+ });
13105
+ const newActivity = created.newTask;
13106
+ newActivity.realWorkHours = computeLeafRealWork(
13107
+ newActivity.workHours,
13108
+ newActivity.progress
13109
+ );
13110
+ assignCustomId(newActivity, action, deps, anchorSiblingId, parent, opts);
13111
+ const insertionCorrelativeId = opts.insertionCorrelativeId ?? computeInsertionCorrelativeId(
13112
+ adapter,
13113
+ isRoot ? ROOT_PARENT_ID : action.parentId,
13114
+ action.afterSiblingId,
13115
+ action.beforeSiblingId
13116
+ );
13117
+ newActivity.correlativeId = insertionCorrelativeId;
13118
+ const initialTouched = /* @__PURE__ */ new Set([newId]);
13119
+ if (parent) initialTouched.add(parent.id);
13120
+ const parentWasLeaf = parent !== null && adapter.getChildren(parent.id).length === 0;
13121
+ const parentCustomId = parentWasLeaf ? parent?.customId : null;
13122
+ const beforeSnap = opts.skipBeforeSnap ? /* @__PURE__ */ new Map() : await snapshotActivities(adapter, initialTouched);
13123
+ adapter.addActivity(newActivity);
13124
+ const removedParentLinks = parentWasLeaf ? removeIncidentLinks(parent.id, adapter) : /* @__PURE__ */ new Map();
13125
+ applyParentMutations(action, newId, parent, adapter, parentWasLeaf);
13126
+ if (parentCustomId) {
13127
+ deps.customIdTracker.releaseCustomId(parentCustomId);
13128
+ }
13129
+ if (!opts.skipRollupCascades) {
13130
+ recomputeHhCascadeForActivity(newId, adapter);
13131
+ recomputeUsedCostCascadeForActivity(newId, adapter);
13132
+ recomputeCostCascadeForActivity(newId, adapter);
13133
+ }
13134
+ const correlativeShifts = recomputeCorrelatives(
13135
+ adapter,
13136
+ newId,
13137
+ initialTouched,
13138
+ opts
13139
+ );
13140
+ return {
13141
+ ok: true,
13142
+ newId,
13143
+ parent,
13144
+ newActivity,
13145
+ beforeSnap,
13146
+ initialTouched,
13147
+ correlativeShifts,
13148
+ removedParentLinks
13149
+ };
13150
+ }
13151
+ function removeIncidentLinks(activityId, adapter) {
13152
+ const before = /* @__PURE__ */ new Map();
13153
+ const ids = /* @__PURE__ */ new Set([
13154
+ ...adapter.getIncomingLinkIds(activityId),
13155
+ ...adapter.getOutgoingLinkIds(activityId)
13156
+ ]);
13157
+ for (const linkId of ids) {
13158
+ const link = adapter.getLink(linkId);
13159
+ if (!link) continue;
13160
+ before.set(String(linkId), { ...link });
13161
+ adapter.removeLink(linkId);
13162
+ }
13163
+ return before;
13164
+ }
13165
+ function validateCreateTarget(action, adapter) {
13166
+ const isRoot = isRootParent(action.parentId);
13167
+ const parent = isRoot ? null : adapter.getActivity(action.parentId);
13168
+ if (!isRoot && !parent) {
13169
+ return { ok: false, reason: REJECTION_REASON.PARENT_NOT_FOUND };
13170
+ }
13171
+ const isParentFrozenBySir = parent !== null && isFrozen(parent);
13172
+ if (isParentFrozenBySir) {
13173
+ return { ok: false, reason: REJECTION_REASON.PARENT_FROZEN_BY_SIR };
13174
+ }
13175
+ const hasConflictingAnchors = action.afterSiblingId !== void 0 && action.beforeSiblingId !== void 0;
13176
+ if (hasConflictingAnchors) {
13177
+ return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_CONFLICT };
13178
+ }
13179
+ const anchorSiblingId = action.afterSiblingId ?? action.beforeSiblingId;
13180
+ const anchorError = validateAnchorSibling(
13181
+ action,
13182
+ adapter,
13183
+ isRoot,
13184
+ anchorSiblingId
13185
+ );
13186
+ if (anchorError) return anchorError;
13187
+ return { ok: true, parent, anchorSiblingId, isRoot };
13188
+ }
13189
+ function validateAnchorSibling(action, adapter, isRoot, anchorSiblingId) {
13190
+ if (anchorSiblingId === void 0) return null;
13191
+ const sibling = adapter.getActivity(anchorSiblingId);
13192
+ if (!sibling) {
13193
+ return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_NOT_FOUND };
13194
+ }
13195
+ const expectedSiblingParent = isRoot ? ROOT_PARENT_ID : action.parentId;
13196
+ const actualSiblingParent = sibling.parentId == null ? ROOT_PARENT_ID : String(sibling.parentId);
13197
+ if (actualSiblingParent !== expectedSiblingParent) {
13198
+ return { ok: false, reason: REJECTION_REASON.ANCHOR_SIBLING_WRONG_PARENT };
13199
13199
  }
13200
- for (const parentId of parentsToCheck) {
13201
- const parent = adapter.getActivity(parentId);
13202
- if (!parent) continue;
13203
- applyParentNewActivitiesCleanup(parent, newActivitiesToDelete, adapter);
13204
- const remainingChildren = adapter.getChildren(parentId);
13205
- const demotion = buildParentDemotionMutations({
13206
- parent,
13207
- remainingChildIds: remainingChildren.map(String),
13208
- defaultDurationHours: sector.hoursPerDay,
13209
- promotionSnapshot: adapter.getPromotionSnapshot(parentId),
13210
- computeEndDate: (startDate, durationHours) => adapter.calculateEndDate({ startDate, durationHours, task: parent }),
13211
- idsRemoved: toDelete
13212
- });
13213
- if (!demotion) {
13214
- recomputeSingleParentDisplayBounds(parentId, adapter);
13215
- continue;
13216
- }
13217
- for (const [key, value] of Object.entries(demotion.fields)) {
13218
- setActivityFieldDynamic(adapter, parentId, key, value);
13200
+ return null;
13201
+ }
13202
+ function resolveNewActivityId(explicitId, adapter, activityIdGen, isActivityIdReserved) {
13203
+ const nextAvailableId = () => {
13204
+ let candidate = activityIdGen.next();
13205
+ while (adapter.activityExists(candidate) || isActivityIdReserved(String(candidate))) {
13206
+ candidate = activityIdGen.next();
13219
13207
  }
13220
- if (parent.customId == null || parent.customId === "") {
13221
- const previousSiblingId = findPrevSiblingHelper(parentId, adapter);
13222
- const previousCustomId = previousSiblingId ? adapter.getActivity(previousSiblingId)?.customId : null;
13223
- const generated = customIdTracker.generateCustomIdForRestoredChild(
13224
- typeof previousCustomId === "string" && previousCustomId ? previousCustomId : null
13225
- );
13226
- adapter.setActivityField(
13227
- parentId,
13228
- ACTIVITY_PROPERTY.CUSTOM_ID,
13229
- generated
13230
- );
13208
+ return candidate;
13209
+ };
13210
+ if (explicitId === void 0) return nextAvailableId();
13211
+ if (adapter.activityExists(explicitId) || isActivityIdReserved(String(explicitId))) {
13212
+ return nextAvailableId();
13213
+ }
13214
+ activityIdGen.bump(explicitId);
13215
+ return explicitId;
13216
+ }
13217
+ function readExplicitCustomId(action) {
13218
+ const value = action.overrides?.customId;
13219
+ return typeof value === "string" ? value : void 0;
13220
+ }
13221
+ function assignCustomId(newActivity, action, deps, anchorSiblingId, parent, opts) {
13222
+ const { adapter, customIdTracker } = deps;
13223
+ const explicit = readExplicitCustomId(action);
13224
+ if (explicit !== void 0) {
13225
+ if (explicit.length === 0) return;
13226
+ if (!customIdTracker.isCustomIdInUse(explicit)) {
13227
+ customIdTracker.registerCustomId(explicit);
13228
+ return;
13231
13229
  }
13232
13230
  }
13233
- for (const parentId of parentsToCheck) {
13234
- recomputeRollupCascadesForParent(parentId, adapter);
13231
+ const customIdAnchorId = opts.customIdReferenceId ?? anchorSiblingId;
13232
+ const derivesFromAnchor = customIdAnchorId !== void 0;
13233
+ const referenceActivity = derivesFromAnchor ? adapter.getActivity(customIdAnchorId) : parent;
13234
+ newActivity.customId = customIdTracker.generateCustomIdForNewChild({
13235
+ activity: referenceActivity ?? {},
13236
+ useActivityCustomIdAsReference: derivesFromAnchor
13237
+ });
13238
+ }
13239
+ function applyParentMutations(action, newId, parent, adapter, parentWasLeaf) {
13240
+ if (!parent) return;
13241
+ const parentMutations = buildParentMutations({
13242
+ parentId: action.parentId,
13243
+ newChildId: newId,
13244
+ isCurrentlyLeaf: parentWasLeaf,
13245
+ getParent: (id) => adapter.getActivity(id) ?? null
13246
+ });
13247
+ if (!parentMutations) return;
13248
+ for (const [key, value] of Object.entries(parentMutations.fields)) {
13249
+ setActivityFieldDynamic(adapter, parentMutations.parentId, key, value);
13235
13250
  }
13236
- const correlativeShifts = recomputeCorrelativeIds(adapter);
13237
- const { scheduledIds } = await runPostMutation(
13238
- {
13239
- adapter,
13240
- scheduler,
13241
- sector: deps.sector,
13242
- defaultBaseCalendarId: deps.defaultBaseCalendarId
13243
- },
13244
- {
13245
- action,
13246
- autoscheduleFrom: AUTOSCHEDULE_SCOPE.ROOTS,
13247
- recomputeParentsFrom: parentsToCheck,
13248
- now: deps.now,
13249
- options
13250
- }
13251
+ if (parentMutations.promotionSnapshot) {
13252
+ adapter.setPromotionSnapshot(
13253
+ parentMutations.parentId,
13254
+ parentMutations.promotionSnapshot
13255
+ );
13256
+ }
13257
+ }
13258
+ function recomputeCorrelatives(adapter, newId, initialTouched, opts) {
13259
+ if (opts.skipCorrelativeRecompute) return [];
13260
+ const shifts = recomputeCorrelativeIds(adapter).filter(
13261
+ (shift) => shift.activityId !== String(newId)
13251
13262
  );
13263
+ for (const shift of shifts) initialTouched.add(shift.activityId);
13264
+ return shifts;
13265
+ }
13266
+ async function buildCreateChangeSet(adapter, action, coreResult, scheduledIds, hoursPerDay) {
13267
+ const {
13268
+ newId,
13269
+ newActivity,
13270
+ beforeSnap,
13271
+ initialTouched,
13272
+ correlativeShifts,
13273
+ removedParentLinks
13274
+ } = coreResult;
13252
13275
  const trackingEvent = {
13253
- name: DISPATCH_TRACK_EVENT.ACTIVITY_DELETION,
13276
+ name: DISPATCH_TRACK_EVENT.ACTIVITY_CREATION,
13254
13277
  properties: {
13255
- activity_ids: [...action.activityIds],
13256
- cascade_count: toDelete.size
13278
+ parent_id: action.parentId,
13279
+ activity_id: newId,
13280
+ created_duration: newActivity.durationHours
13257
13281
  }
13258
13282
  };
13259
13283
  if (action.eventSource !== void 0) {
13260
13284
  trackingEvent.properties.event_source = action.eventSource;
13261
13285
  }
13262
- const beforeLinksByStringId = /* @__PURE__ */ new Map();
13263
- for (const [id, l] of beforeLinks) beforeLinksByStringId.set(id, l);
13264
- const deletedRowsSnap = /* @__PURE__ */ new Map();
13265
- for (const id of toDelete) {
13266
- const snap = beforeSnap.get(String(id));
13267
- if (snap) deletedRowsSnap.set(String(id), snap);
13268
- }
13269
- const changes = await assembleChangeSet(adapter, {
13286
+ return assembleChangeSet(adapter, {
13270
13287
  source: action,
13271
13288
  beforeSnap,
13272
- touchedIds,
13289
+ touchedIds: initialTouched,
13273
13290
  scheduledIds,
13274
- sirDetection: SIR_DETECTION_PHASE.AFTER_DIFF,
13275
- links: buildLinkDeletions(beforeLinks),
13291
+ correlativeShifts,
13276
13292
  trackingEvents: [trackingEvent],
13277
- hoursPerDay: sector.hoursPerDay,
13278
- correlativeShifts
13279
- });
13293
+ links: buildLinkDeletions(new Map(removedParentLinks))});
13294
+ }
13295
+ async function dispatchActivityCreate(action, options, deps) {
13296
+ const { adapter, scheduler, sector } = deps;
13297
+ const normalized2 = normalizeActivityCreationOverrides(
13298
+ action.overrides,
13299
+ sector.hoursPerDay
13300
+ );
13301
+ if (!normalized2.ok) return normalized2;
13302
+ const core = await createActivityCore(
13303
+ { ...action, overrides: normalized2.value },
13304
+ deps
13305
+ );
13306
+ if (!core.ok) return core;
13307
+ const { newId } = core;
13308
+ const createdAutoScheduling = core.newActivity.autoScheduling;
13309
+ adapter.setActivityField(newId, "autoScheduling", false);
13310
+ let scheduleOutcome;
13311
+ try {
13312
+ scheduleOutcome = await runPostMutation(
13313
+ {
13314
+ adapter,
13315
+ scheduler,
13316
+ sector: deps.sector,
13317
+ defaultBaseCalendarId: deps.defaultBaseCalendarId
13318
+ },
13319
+ {
13320
+ action,
13321
+ autoscheduleFrom: newId,
13322
+ recomputeParentsFrom: [newId],
13323
+ preventSummaryTargetPullback: true,
13324
+ now: deps.now,
13325
+ options
13326
+ }
13327
+ );
13328
+ } finally {
13329
+ adapter.setActivityField(newId, "autoScheduling", createdAutoScheduling);
13330
+ }
13331
+ const { scheduledIds } = scheduleOutcome;
13332
+ const changeset = await buildCreateChangeSet(
13333
+ adapter,
13334
+ action,
13335
+ core,
13336
+ scheduledIds,
13337
+ sector.hoursPerDay
13338
+ );
13280
13339
  return {
13281
13340
  ok: true,
13282
- changes: viewStateChanges2.length > 0 ? { ...changes, viewState: viewStateChanges2 } : changes,
13283
- __beforeSnap: deletedRowsSnap,
13284
- __beforeLinks: beforeLinksByStringId
13341
+ changes: changeset,
13342
+ ...core.removedParentLinks.size > 0 ? { __beforeLinks: new Map(core.removedParentLinks) } : {}
13285
13343
  };
13286
13344
  }
13287
- function applyParentNewActivitiesCleanup(parent, idsRemoved, adapter) {
13288
- const cleanup = buildParentNewActivitiesCleanup({ parent, idsRemoved });
13289
- if (!cleanup) return;
13290
- for (const [key, value] of Object.entries(cleanup.fields)) {
13291
- setActivityFieldDynamic(adapter, cleanup.parentId, key, value);
13292
- }
13293
- }
13294
- function captureDeletedViewState(adapter, toDelete) {
13295
- const changes = [];
13296
- for (const id of toDelete) {
13297
- const wasChecked = adapter.isChecked(id);
13298
- const wasVisible = adapter.isVisible(id);
13299
- if (!wasChecked && wasVisible) continue;
13300
- changes.push({
13301
- activityId: String(id),
13302
- ...wasChecked ? { checked: { before: true, after: false } } : {},
13303
- ...wasVisible ? {} : { visible: { before: false, after: true } }
13304
- });
13305
- }
13306
- return changes;
13307
- }
13308
- function buildLinkDeletions(beforeLinks) {
13309
- const out = [];
13310
- for (const [id, before] of beforeLinks.entries()) {
13311
- out.push({
13312
- id,
13313
- kind: ENTITY_CHANGE_KIND.DELETED,
13314
- fields: {
13315
- source: { before: String(before.source), after: void 0 },
13316
- target: { before: String(before.target), after: void 0 },
13317
- type: { before: before.type, after: void 0 },
13318
- lag: { before: Number(before.lag), after: void 0 }
13319
- },
13320
- after: null
13321
- });
13345
+ function findDefaultCalendarId(calendars) {
13346
+ for (const calendar of calendars.values()) {
13347
+ if (calendar.is_default) return calendar.id;
13322
13348
  }
13323
- return out;
13349
+ const first = calendars.values().next();
13350
+ if (!first.done && first.value) return first.value.id;
13351
+ return "global";
13324
13352
  }
13325
13353
 
13326
13354
  // src/dispatch/handlers/activity-batch.ts
@@ -13363,6 +13391,14 @@ async function dispatchActivityBatch(action, options, deps) {
13363
13391
  }
13364
13392
  const created = await createPlannedActivities(action, plan.plan.items, deps);
13365
13393
  if (!created.ok) return created;
13394
+ for (const [linkId, link] of created.removedParentLinks) {
13395
+ beforeLinks.set(linkId, link);
13396
+ appliedLinks.push({
13397
+ op: { kind: DISPATCH_LINK_OPERATION_KIND.DELETE, linkId },
13398
+ finalLinkId: linkId,
13399
+ rejected: null
13400
+ });
13401
+ }
13366
13402
  const correlativeShifts = isSimpleRootAppend(action) ? [] : recomputeCorrelativeIds(deps.adapter);
13367
13403
  const createdIdSet = new Set(
13368
13404
  plan.plan.items.map((item) => String(item.activityId))
@@ -13437,8 +13473,8 @@ async function dispatchActivityBatch(action, options, deps) {
13437
13473
  return {
13438
13474
  ok: true,
13439
13475
  changes: viewState.length > 0 ? { ...changes, viewState } : changes,
13440
- ...action.mode === ACTIVITY_BATCH_MODE.REPLACE ? {
13441
- __beforeSnap: beforeSnap,
13476
+ ...action.mode === ACTIVITY_BATCH_MODE.REPLACE || beforeLinks.size > 0 ? {
13477
+ ...action.mode === ACTIVITY_BATCH_MODE.REPLACE ? { __beforeSnap: beforeSnap } : {},
13442
13478
  __beforeLinks: stringKeyedLinks(beforeLinks)
13443
13479
  } : {}
13444
13480
  };
@@ -13713,6 +13749,7 @@ function interpolateCorrelative(left, right, position, count) {
13713
13749
  }
13714
13750
  async function createPlannedActivities(action, plannedItems, deps) {
13715
13751
  const childrenByParent = /* @__PURE__ */ new Map();
13752
+ const removedParentLinks = /* @__PURE__ */ new Map();
13716
13753
  const ready = [];
13717
13754
  for (const item of plannedItems) {
13718
13755
  if (item.parentBatchId === null) {
@@ -13743,6 +13780,9 @@ async function createPlannedActivities(action, plannedItems, deps) {
13743
13780
  }
13744
13781
  );
13745
13782
  if (!core.ok) return core;
13783
+ for (const [linkId, link] of core.removedParentLinks) {
13784
+ removedParentLinks.set(linkId, link);
13785
+ }
13746
13786
  for (const child of childrenByParent.get(item.input.batchId) ?? []) {
13747
13787
  ready.push(child);
13748
13788
  }
@@ -13759,7 +13799,7 @@ async function createPlannedActivities(action, plannedItems, deps) {
13759
13799
  recomputedParents.add(String(parentId));
13760
13800
  recomputeRollupCascadesForParent(parentId, deps.adapter);
13761
13801
  }
13762
- return { ok: true };
13802
+ return { ok: true, removedParentLinks };
13763
13803
  }
13764
13804
  function resolveActivityReference(reference, activityIdsByBatchId) {
13765
13805
  if (typeof reference === "string") return reference;
@@ -16210,8 +16250,12 @@ async function dispatchCalendarReconcile(action, options, deps) {
16210
16250
  if (!shouldReconcile(activity, deps)) continue;
16211
16251
  const calendar = deps.ctx.calendars.getCalendar(activity.calendarId);
16212
16252
  if (!calendar) continue;
16253
+ const startReference = calendar.isWorkTime(
16254
+ activity.startDate,
16255
+ CALENDAR_UNIT.HOUR
16256
+ ) ? startOfUtcDay4(activity.startDate) : activity.startDate;
16213
16257
  const startDate = calendar.getClosestWorkTime({
16214
- date: activity.startDate,
16258
+ date: startReference,
16215
16259
  direction: WORK_TIME_DIRECTION.FUTURE,
16216
16260
  unit: CALENDAR_UNIT.HOUR
16217
16261
  });
@@ -16259,6 +16303,11 @@ function shouldReconcile(activity, deps) {
16259
16303
  function sameInstant(first, second) {
16260
16304
  return first.getTime() === second.getTime();
16261
16305
  }
16306
+ function startOfUtcDay4(date2) {
16307
+ return new Date(
16308
+ Date.UTC(date2.getUTCFullYear(), date2.getUTCMonth(), date2.getUTCDate())
16309
+ );
16310
+ }
16262
16311
 
16263
16312
  // src/activity-copy-paste/policy.ts
16264
16313
  var ACTIVITY_COPY_FIELDS = [