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