@outbuild-company/schedule-core 1.8.0 → 1.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.cjs CHANGED
@@ -420,6 +420,107 @@ function getFieldDescriptor(field) {
420
420
  return FIELD_REGISTRY[field] ?? null;
421
421
  }
422
422
 
423
+ // src/internal/state/view-state.ts
424
+ function isRelativePlacementPinValid(activityId, pin, parentOf) {
425
+ const branchParentId = String(pin.branchParentId);
426
+ const currentParentId = String(parentOf(activityId) ?? "0");
427
+ if (currentParentId !== branchParentId) return false;
428
+ const referenceId = String(pin.referenceId);
429
+ return referenceId === branchParentId || String(parentOf(referenceId) ?? "0") === branchParentId;
430
+ }
431
+ var ViewStateStore = class {
432
+ checkedSet = /* @__PURE__ */ new Set();
433
+ hiddenSet = /* @__PURE__ */ new Set();
434
+ activeFilter = null;
435
+ activeOrder = null;
436
+ relativePlacementPins = /* @__PURE__ */ new Map();
437
+ isChecked(activityId) {
438
+ return this.checkedSet.has(activityId);
439
+ }
440
+ isVisible(activityId) {
441
+ return !this.hiddenSet.has(activityId);
442
+ }
443
+ hiddenIds() {
444
+ return [...this.hiddenSet];
445
+ }
446
+ getActiveFilter() {
447
+ return this.activeFilter;
448
+ }
449
+ setActiveFilter(nextFilter) {
450
+ this.activeFilter = nextFilter;
451
+ }
452
+ getActiveOrder() {
453
+ return this.activeOrder;
454
+ }
455
+ setActiveOrder(nextOrder) {
456
+ this.activeOrder = nextOrder;
457
+ }
458
+ setRelativePlacementPin(activityId, pin) {
459
+ this.relativePlacementPins.set(String(activityId), {
460
+ referenceId: String(pin.referenceId),
461
+ branchParentId: String(pin.branchParentId),
462
+ side: pin.side
463
+ });
464
+ }
465
+ getRelativePlacementPins() {
466
+ return this.relativePlacementPins;
467
+ }
468
+ restoreRelativePlacementPins(pins) {
469
+ this.relativePlacementPins = new Map(pins);
470
+ }
471
+ checkedIds() {
472
+ return [...this.checkedSet];
473
+ }
474
+ checkedCount() {
475
+ return this.checkedSet.size;
476
+ }
477
+ setChecked(activityId, nextChecked) {
478
+ if (nextChecked === this.checkedSet.has(activityId)) return false;
479
+ if (nextChecked) this.checkedSet.add(activityId);
480
+ else this.checkedSet.delete(activityId);
481
+ return true;
482
+ }
483
+ setVisible(activityId, nextVisible) {
484
+ if (nextVisible === !this.hiddenSet.has(activityId)) return false;
485
+ if (nextVisible) this.hiddenSet.delete(activityId);
486
+ else this.hiddenSet.add(activityId);
487
+ return true;
488
+ }
489
+ forget(activityId) {
490
+ this.checkedSet.delete(activityId);
491
+ this.hiddenSet.delete(activityId);
492
+ this.relativePlacementPins.delete(activityId);
493
+ for (const [pinnedId, pin] of this.relativePlacementPins) {
494
+ if (pin.referenceId === activityId) {
495
+ this.relativePlacementPins.delete(pinnedId);
496
+ }
497
+ }
498
+ }
499
+ hydrate(seed2) {
500
+ this.checkedSet = new Set(seed2.checkedIds ?? []);
501
+ this.hiddenSet = new Set(seed2.hiddenIds ?? []);
502
+ this.activeFilter = null;
503
+ this.activeOrder = null;
504
+ this.relativePlacementPins = /* @__PURE__ */ new Map();
505
+ }
506
+ snapshot() {
507
+ return {
508
+ checked: new Set(this.checkedSet),
509
+ hidden: new Set(this.hiddenSet),
510
+ activeFilter: this.activeFilter,
511
+ activeOrder: this.activeOrder,
512
+ relativePlacementPins: new Map(this.relativePlacementPins)
513
+ };
514
+ }
515
+ restore(snapshot) {
516
+ this.checkedSet = new Set(snapshot.checked);
517
+ this.hiddenSet = new Set(snapshot.hidden);
518
+ this.activeFilter = snapshot.activeFilter;
519
+ this.activeOrder = snapshot.activeOrder;
520
+ this.relativePlacementPins = new Map(snapshot.relativePlacementPins);
521
+ }
522
+ };
523
+
423
524
  // src/internal/filter/evaluate-filter.ts
424
525
  function toTargetDate(value) {
425
526
  if (value instanceof Date) return value;
@@ -562,13 +663,8 @@ function seedFilterProjection(input) {
562
663
  );
563
664
  }
564
665
  const isMatch = matchesFilter(activity, filter, context);
565
- const isPinned = input.pinnedVisibleIds?.has(activityId) === true;
566
- visibleReferenceCounts.set(
567
- activityId,
568
- (isMatch ? 1 : 0) + (isPinned ? 1 : 0)
569
- );
666
+ visibleReferenceCounts.set(activityId, isMatch ? 1 : 0);
570
667
  if (isMatch) matchedIds.add(activityId);
571
- if (isPinned) pinnedVisibleIds.add(activityId);
572
668
  }
573
669
  return {
574
670
  matchedIds,
@@ -578,6 +674,29 @@ function seedFilterProjection(input) {
578
674
  remainingChildren
579
675
  };
580
676
  }
677
+ function applyRelativePlacementPins(seed2, pins) {
678
+ if (pins === void 0) return;
679
+ for (const [activityId, pin] of pins) {
680
+ const pinnedId = String(activityId);
681
+ const referenceId = String(pin.referenceId);
682
+ if (!seed2.parentIds.has(pinnedId) || !seed2.parentIds.has(referenceId) || !isRelativePlacementPinValid(
683
+ pinnedId,
684
+ pin,
685
+ (activityId2) => seed2.parentIds.get(activityId2) ?? null
686
+ ) || (seed2.visibleReferenceCounts.get(referenceId) ?? 0) === 0) {
687
+ continue;
688
+ }
689
+ seed2.pinnedVisibleIds.add(pinnedId);
690
+ let currentId = pinnedId;
691
+ while (currentId !== null) {
692
+ seed2.visibleReferenceCounts.set(
693
+ currentId,
694
+ (seed2.visibleReferenceCounts.get(currentId) ?? 0) + 1
695
+ );
696
+ currentId = seed2.parentIds.get(currentId) ?? null;
697
+ }
698
+ }
699
+ }
581
700
  function propagateVisibleReferenceCounts(seed2) {
582
701
  const { visibleReferenceCounts, parentIds, remainingChildren } = seed2;
583
702
  const pendingIds = [];
@@ -609,6 +728,7 @@ function collectVisibleIds(visibleReferenceCounts) {
609
728
  function buildFilterProjection(input) {
610
729
  const seed2 = seedFilterProjection(input);
611
730
  propagateVisibleReferenceCounts(seed2);
731
+ applyRelativePlacementPins(seed2, input.relativePlacementPins);
612
732
  const visibleIds = collectVisibleIds(seed2.visibleReferenceCounts);
613
733
  return {
614
734
  filter: input.filter,
@@ -796,7 +916,8 @@ function resolveFilter(adapter, filter, hoursPerDay) {
796
916
  activities,
797
917
  parentOf: (activityId) => adapter.getParentId(activityId),
798
918
  filter,
799
- context: buildFilterContext(hoursPerDay)
919
+ context: buildFilterContext(hoursPerDay),
920
+ relativePlacementPins: adapter.getRelativePlacementPins()
800
921
  });
801
922
  return { visibleIds: projection.visibleIds, projection };
802
923
  }
@@ -847,25 +968,118 @@ function normalizeParentKey(parent) {
847
968
 
848
969
  // src/internal/hierarchy/visual-order.ts
849
970
  var NOT_SET_SORT_VALUE = Number.MAX_SAFE_INTEGER;
850
- function getChildrenInVisualOrder(parentId, adapter) {
971
+ var EMPTY_RELATIVE_PLACEMENT_PINS = /* @__PURE__ */ new Map();
972
+ function buildRelativePlacementPinIndex(adapter) {
973
+ const index = /* @__PURE__ */ new Map();
974
+ for (const [activityId, pin] of adapter.getRelativePlacementPins?.() ?? []) {
975
+ const pinnedId = String(activityId);
976
+ const referenceId = String(pin.referenceId);
977
+ if (adapter.getActivity(pinnedId) === null || adapter.getActivity(referenceId) === null || !isRelativePlacementPinValid(
978
+ pinnedId,
979
+ pin,
980
+ (activityId2) => adapter.getParentId(activityId2)
981
+ )) {
982
+ continue;
983
+ }
984
+ const targetParentId = String(pin.branchParentId);
985
+ const branchPins = index.get(targetParentId) ?? /* @__PURE__ */ new Map();
986
+ branchPins.set(pinnedId, pin);
987
+ index.set(targetParentId, branchPins);
988
+ }
989
+ return index;
990
+ }
991
+ function getChildrenInVisualOrder(parentId, adapter, pinIndex = buildRelativePlacementPinIndex(adapter)) {
851
992
  const children = collectChildrenSnapshots(parentId, adapter);
852
993
  const userOrder = adapter.getOrderComparator?.() ?? null;
853
994
  children.sort(
854
995
  userOrder === null ? compareActivitiesInVisualOrder : (a, b) => userOrder(a, b) || compareActivitiesInVisualOrder(a, b)
855
996
  );
856
- return children.map((a) => String(a.id));
997
+ return applyRelativePlacementPins2(
998
+ children.map((activity) => String(activity.id)),
999
+ parentId,
1000
+ pinIndex.get(String(parentId)) ?? EMPTY_RELATIVE_PLACEMENT_PINS
1001
+ );
1002
+ }
1003
+ function applyRelativePlacementPins2(orderedIds, parentId, pins) {
1004
+ const order = buildLinkedActivityOrder(orderedIds);
1005
+ for (const [activityId, pin] of pins) {
1006
+ const pinnedId = String(activityId);
1007
+ const referenceId = String(pin.referenceId);
1008
+ const isFirstChild = referenceId === String(parentId);
1009
+ if (!order.previous.has(pinnedId) || !isFirstChild && (!order.previous.has(referenceId) || pinnedId === referenceId)) {
1010
+ continue;
1011
+ }
1012
+ detachActivity(order, pinnedId);
1013
+ if (isFirstChild) insertActivityFirst(order, pinnedId);
1014
+ else insertActivityBeside(order, pinnedId, referenceId, pin.side);
1015
+ }
1016
+ return materializeActivityOrder(order);
1017
+ }
1018
+ function buildLinkedActivityOrder(orderedIds) {
1019
+ const previous = /* @__PURE__ */ new Map();
1020
+ const next = /* @__PURE__ */ new Map();
1021
+ for (let index = 0; index < orderedIds.length; index++) {
1022
+ const activityId = orderedIds[index];
1023
+ if (activityId === void 0) continue;
1024
+ previous.set(activityId, orderedIds[index - 1] ?? null);
1025
+ next.set(activityId, orderedIds[index + 1] ?? null);
1026
+ }
1027
+ return { previous, next, head: orderedIds[0] ?? null };
1028
+ }
1029
+ function detachActivity(order, activityId) {
1030
+ const before = order.previous.get(activityId) ?? null;
1031
+ const after = order.next.get(activityId) ?? null;
1032
+ if (before === null) order.head = after;
1033
+ else order.next.set(before, after);
1034
+ if (after !== null) order.previous.set(after, before);
1035
+ }
1036
+ function insertActivityFirst(order, activityId) {
1037
+ order.previous.set(activityId, null);
1038
+ order.next.set(activityId, order.head);
1039
+ if (order.head !== null) order.previous.set(order.head, activityId);
1040
+ order.head = activityId;
1041
+ }
1042
+ function insertActivityBeside(order, activityId, referenceId, side) {
1043
+ if (side === "before") {
1044
+ const before = order.previous.get(referenceId) ?? null;
1045
+ order.previous.set(activityId, before);
1046
+ order.next.set(activityId, referenceId);
1047
+ order.previous.set(referenceId, activityId);
1048
+ if (before === null) order.head = activityId;
1049
+ else order.next.set(before, activityId);
1050
+ return;
1051
+ }
1052
+ const after = order.next.get(referenceId) ?? null;
1053
+ order.previous.set(activityId, referenceId);
1054
+ order.next.set(activityId, after);
1055
+ order.next.set(referenceId, activityId);
1056
+ if (after !== null) order.previous.set(after, activityId);
1057
+ }
1058
+ function materializeActivityOrder(order) {
1059
+ const result = [];
1060
+ let activityId = order.head;
1061
+ while (activityId !== null) {
1062
+ result.push(activityId);
1063
+ activityId = order.next.get(activityId) ?? null;
1064
+ }
1065
+ return result;
857
1066
  }
858
1067
  function iterateInVisualOrder(adapter) {
859
1068
  const result = [];
1069
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
860
1070
  const visit = (activity) => {
861
1071
  result.push(activity);
862
- const childIds = getChildrenInVisualOrder(String(activity.id), adapter);
1072
+ const childIds = getChildrenInVisualOrder(
1073
+ String(activity.id),
1074
+ adapter,
1075
+ pinIndex
1076
+ );
863
1077
  for (const childId of childIds) {
864
1078
  const child = adapter.getActivity(childId);
865
1079
  if (child) visit(child);
866
1080
  }
867
1081
  };
868
- const rootIds = getChildrenInVisualOrder(ROOT_PARENT_ID, adapter);
1082
+ const rootIds = getChildrenInVisualOrder(ROOT_PARENT_ID, adapter, pinIndex);
869
1083
  for (const rootId of rootIds) {
870
1084
  const root = adapter.getActivity(rootId);
871
1085
  if (root) visit(root);
@@ -926,8 +1140,9 @@ function setHas(set, id) {
926
1140
  // src/dispatch/shared/collect-branch-order.ts
927
1141
  function collectBranchOrder(adapter) {
928
1142
  const branches = [];
1143
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
929
1144
  const visit = (parentId) => {
930
- const childIds = getChildrenInVisualOrder(parentId, adapter);
1145
+ const childIds = getChildrenInVisualOrder(parentId, adapter, pinIndex);
931
1146
  if (childIds.length > 1) {
932
1147
  branches.push({ parentId, childIds });
933
1148
  }
@@ -940,8 +1155,9 @@ function collectBranchOrder(adapter) {
940
1155
  }
941
1156
  function collectBranchOrderForParents(adapter, parentIds) {
942
1157
  const branches = [];
1158
+ const pinIndex = buildRelativePlacementPinIndex(adapter);
943
1159
  for (const parentId of parentIds) {
944
- const childIds = getChildrenInVisualOrder(parentId, adapter);
1160
+ const childIds = getChildrenInVisualOrder(parentId, adapter, pinIndex);
945
1161
  if (childIds.length > 1) branches.push({ parentId, childIds });
946
1162
  }
947
1163
  return branches;
@@ -4086,8 +4302,8 @@ var costPipeline = {
4086
4302
  }
4087
4303
  return canEdit();
4088
4304
  },
4089
- parseInput(rawValue, activity) {
4090
- if (String(rawValue).trim() === "") return parsed(activity.cost);
4305
+ parseInput(rawValue) {
4306
+ if (String(rawValue).trim() === "") return parsed(0);
4091
4307
  const numericValue = parseFloat(String(rawValue));
4092
4308
  if (Number.isNaN(numericValue)) return parseError("not_a_number");
4093
4309
  return parsed(numericValue);
@@ -4136,8 +4352,8 @@ var usedCostPipeline = {
4136
4352
  }
4137
4353
  return canEdit();
4138
4354
  },
4139
- parseInput(rawValue, activity) {
4140
- if (String(rawValue).trim() === "") return parsed(activity.usedCost);
4355
+ parseInput(rawValue) {
4356
+ if (String(rawValue).trim() === "") return parsed(0);
4141
4357
  const numericValue = parseFloat(String(rawValue));
4142
4358
  if (Number.isNaN(numericValue)) return parseError("not_a_number");
4143
4359
  return parsed(numericValue);
@@ -17662,77 +17878,6 @@ var HierarchyIndex = class {
17662
17878
  }
17663
17879
  };
17664
17880
 
17665
- // src/internal/state/view-state.ts
17666
- var ViewStateStore = class {
17667
- checkedSet = /* @__PURE__ */ new Set();
17668
- hiddenSet = /* @__PURE__ */ new Set();
17669
- activeFilter = null;
17670
- activeOrder = null;
17671
- isChecked(activityId) {
17672
- return this.checkedSet.has(activityId);
17673
- }
17674
- isVisible(activityId) {
17675
- return !this.hiddenSet.has(activityId);
17676
- }
17677
- hiddenIds() {
17678
- return [...this.hiddenSet];
17679
- }
17680
- getActiveFilter() {
17681
- return this.activeFilter;
17682
- }
17683
- setActiveFilter(nextFilter) {
17684
- this.activeFilter = nextFilter;
17685
- }
17686
- getActiveOrder() {
17687
- return this.activeOrder;
17688
- }
17689
- setActiveOrder(nextOrder) {
17690
- this.activeOrder = nextOrder;
17691
- }
17692
- checkedIds() {
17693
- return [...this.checkedSet];
17694
- }
17695
- checkedCount() {
17696
- return this.checkedSet.size;
17697
- }
17698
- setChecked(activityId, nextChecked) {
17699
- if (nextChecked === this.checkedSet.has(activityId)) return false;
17700
- if (nextChecked) this.checkedSet.add(activityId);
17701
- else this.checkedSet.delete(activityId);
17702
- return true;
17703
- }
17704
- setVisible(activityId, nextVisible) {
17705
- if (nextVisible === !this.hiddenSet.has(activityId)) return false;
17706
- if (nextVisible) this.hiddenSet.delete(activityId);
17707
- else this.hiddenSet.add(activityId);
17708
- return true;
17709
- }
17710
- forget(activityId) {
17711
- this.checkedSet.delete(activityId);
17712
- this.hiddenSet.delete(activityId);
17713
- }
17714
- hydrate(seed2) {
17715
- this.checkedSet = new Set(seed2.checkedIds ?? []);
17716
- this.hiddenSet = new Set(seed2.hiddenIds ?? []);
17717
- this.activeFilter = null;
17718
- this.activeOrder = null;
17719
- }
17720
- snapshot() {
17721
- return {
17722
- checked: new Set(this.checkedSet),
17723
- hidden: new Set(this.hiddenSet),
17724
- activeFilter: this.activeFilter,
17725
- activeOrder: this.activeOrder
17726
- };
17727
- }
17728
- restore(snapshot) {
17729
- this.checkedSet = new Set(snapshot.checked);
17730
- this.hiddenSet = new Set(snapshot.hidden);
17731
- this.activeFilter = snapshot.activeFilter;
17732
- this.activeOrder = snapshot.activeOrder;
17733
- }
17734
- };
17735
-
17736
17881
  // src/internal/state/schedule-state.ts
17737
17882
  var ScheduleState = class _ScheduleState {
17738
17883
  _activities = /* @__PURE__ */ new Map();
@@ -17911,6 +18056,18 @@ var ScheduleState = class _ScheduleState {
17911
18056
  this._viewState.setActiveOrder(nextOrder);
17912
18057
  this.invalidateDerivedOrder();
17913
18058
  }
18059
+ setRelativePlacementPin(activityId, pin) {
18060
+ this._viewState.setRelativePlacementPin(String(activityId), pin);
18061
+ this.invalidateVisualOrderIds();
18062
+ }
18063
+ getRelativePlacementPins() {
18064
+ return this._viewState.getRelativePlacementPins();
18065
+ }
18066
+ restoreRelativePlacementPins(pins) {
18067
+ this._viewState.restoreRelativePlacementPins(pins);
18068
+ this._filterProjection = null;
18069
+ this.invalidateVisualOrderIds();
18070
+ }
17914
18071
  /**
17915
18072
  * Rebuild-on-demand, memoized: at most one build per invalidation, never one
17916
18073
  * per comparison. getChildrenInVisualOrder asks for this once and then sorts a
@@ -18117,10 +18274,14 @@ var ScheduleState = class _ScheduleState {
18117
18274
  const activity = this.getActivity(activityId);
18118
18275
  if (activity) this._writeCapture.noteRemove(String(activityId), activity);
18119
18276
  const key = String(activityId);
18120
- if (this._viewState.isChecked(key) || !this._viewState.isVisible(key)) {
18277
+ const relativePlacementPins = this._viewState.getRelativePlacementPins();
18278
+ const hasDependentPin = [...relativePlacementPins.values()].some(
18279
+ ({ referenceId }) => referenceId === key
18280
+ );
18281
+ if (this._viewState.isChecked(key) || !this._viewState.isVisible(key) || relativePlacementPins.has(key) || hasDependentPin) {
18121
18282
  this._captureViewStateOnce();
18122
- this._viewState.forget(key);
18123
18283
  }
18284
+ this._viewState.forget(key);
18124
18285
  this._activities.delete(activityId);
18125
18286
  if (this._lastStartDate.has(String(activityId))) {
18126
18287
  this._captureLastStartDateOnce();
@@ -18378,13 +18539,15 @@ function buildHistoryChangeSet(changes) {
18378
18539
  links: linksWithoutAfter
18379
18540
  });
18380
18541
  }
18381
- function buildUndoEntry(changeSet, beforeSnap, beforeLinks, afterSnap, afterLinks) {
18542
+ function buildUndoEntry(changeSet, beforeSnap, beforeLinks, afterSnap, afterLinks, relativePlacementPinsBefore, relativePlacementPinsAfter) {
18382
18543
  return {
18383
18544
  changeSet,
18384
18545
  beforeSnap,
18385
18546
  beforeLinks: resetDerivedLinkSnapshots(beforeLinks),
18386
18547
  afterSnap,
18387
- afterLinks: resetDerivedLinkSnapshots(afterLinks)
18548
+ afterLinks: resetDerivedLinkSnapshots(afterLinks),
18549
+ relativePlacementPinsBefore,
18550
+ relativePlacementPinsAfter
18388
18551
  };
18389
18552
  }
18390
18553
  function resetDerivedLinkSnapshots(snapshots) {
@@ -18687,6 +18850,8 @@ function applyChangeSetSide(state, entry, side) {
18687
18850
  );
18688
18851
  }
18689
18852
  }
18853
+ const pins = side === "before" ? entry.relativePlacementPinsBefore : entry.relativePlacementPinsAfter;
18854
+ if (pins !== void 0) state.restoreRelativePlacementPins(pins);
18690
18855
  }
18691
18856
 
18692
18857
  // src/dispatch/undo/undo-recorder.ts
@@ -21031,12 +21196,41 @@ function collectChangedActivities(changes, adapter) {
21031
21196
  }
21032
21197
  return activities;
21033
21198
  }
21199
+ function changesAffectRelativePinProjection(changes, changedActivities, adapter, pins) {
21200
+ if (pins.size === 0) return false;
21201
+ if (changes.activities.some(
21202
+ (change) => change.kind === "created" && pins.has(String(change.id))
21203
+ )) {
21204
+ return true;
21205
+ }
21206
+ const referenceIds = new Set(
21207
+ [...pins.values()].map(({ referenceId }) => String(referenceId))
21208
+ );
21209
+ for (const activity of changedActivities) {
21210
+ let activityId = String(activity.id);
21211
+ while (activityId !== null) {
21212
+ if (referenceIds.has(activityId)) return true;
21213
+ activityId = adapter.getParentId(activityId);
21214
+ }
21215
+ }
21216
+ return false;
21217
+ }
21034
21218
  function isCurrentFilterProjection(projection, filter, context) {
21035
21219
  if (projection === null || projection.filter !== filter) return false;
21036
21220
  const hasCurrentHours = projection.context.hoursPerDay === context.hoursPerDay;
21037
21221
  const hasCurrentLocale = projection.context.locale === context.locale;
21038
21222
  return hasCurrentHours && hasCurrentLocale;
21039
21223
  }
21224
+ function relativePlacementPinsEqual(left, right) {
21225
+ if (left.size !== right.size) return false;
21226
+ for (const [activityId, pin] of left) {
21227
+ const other = right.get(activityId);
21228
+ if (other === void 0 || other.referenceId !== pin.referenceId || other.branchParentId !== pin.branchParentId || other.side !== pin.side) {
21229
+ return false;
21230
+ }
21231
+ }
21232
+ return true;
21233
+ }
21040
21234
  var ScheduleCore = class _ScheduleCore {
21041
21235
  _activityClipboard = { value: null };
21042
21236
  _status = SCHEDULE_CORE_STATUS.READY;
@@ -21484,6 +21678,8 @@ var ScheduleCore = class _ScheduleCore {
21484
21678
  dateFormat: this.coreRuntime.sector.dateFormat
21485
21679
  });
21486
21680
  const historyPolicy = getDispatchHistoryPolicy(action);
21681
+ const capturesRelativePlacementPins = action.kind === "activity-create" || action.kind === "activity-delete" || action.kind === "activity-batch" && action.mode === "replace";
21682
+ const relativePlacementPinsBefore = capturesRelativePlacementPins ? new Map(this.coreRuntime.state.getRelativePlacementPins()) : void 0;
21487
21683
  const generatorSnapshot = [
21488
21684
  this.coreRuntime.activityIdGenerator.snapshot(),
21489
21685
  this.coreRuntime.linkIdGenerator.snapshot(),
@@ -21520,18 +21716,26 @@ var ScheduleCore = class _ScheduleCore {
21520
21716
  this.coreRuntime.state.endWriteCapture();
21521
21717
  this.coreRuntime.customIdTracker.commitCustomIdTransaction();
21522
21718
  }
21719
+ if (result.ok) this._recordRelativePlacementPin(action, result.changes);
21523
21720
  if (result.ok && historyPolicy === "record" && changeSetIsSubstantive(result.changes)) {
21524
21721
  const created = captureCreatedSnapshots(
21525
21722
  this.coreRuntime.state,
21526
21723
  result.changes
21527
21724
  );
21725
+ const relativePlacementPinsAfter = capturesRelativePlacementPins ? new Map(this.coreRuntime.state.getRelativePlacementPins()) : void 0;
21726
+ const relativePlacementPinsChanged = relativePlacementPinsBefore !== void 0 && relativePlacementPinsAfter !== void 0 && !relativePlacementPinsEqual(
21727
+ relativePlacementPinsBefore,
21728
+ relativePlacementPinsAfter
21729
+ );
21528
21730
  this._undo.record(
21529
21731
  buildUndoEntry(
21530
21732
  buildHistoryChangeSet(result.changes),
21531
21733
  result.__beforeSnap,
21532
21734
  result.__beforeLinks,
21533
21735
  created.afterSnap,
21534
- created.afterLinks
21736
+ created.afterLinks,
21737
+ relativePlacementPinsChanged ? relativePlacementPinsBefore : void 0,
21738
+ relativePlacementPinsChanged ? relativePlacementPinsAfter : void 0
21535
21739
  ),
21536
21740
  computeCoalesceKey(action),
21537
21741
  Date.now()
@@ -21599,6 +21803,19 @@ var ScheduleCore = class _ScheduleCore {
21599
21803
  const merged = this._reapplyViewState(result.changes);
21600
21804
  return merged === null ? result : { ...result, changes: merged };
21601
21805
  }
21806
+ _recordRelativePlacementPin(action, changes) {
21807
+ const state = this.coreRuntime.state;
21808
+ if (action.kind !== "activity-create") return;
21809
+ const referenceId = action.afterSiblingId ?? action.beforeSiblingId ?? (action.eventSource === "Creation button (+)" && action.parentId !== ROOT_PARENT_ID ? action.parentId : void 0);
21810
+ if (referenceId === void 0) return;
21811
+ const created = changes.activities.find(({ kind }) => kind === "created");
21812
+ if (created === void 0) return;
21813
+ state.setRelativePlacementPin(created.id, {
21814
+ referenceId,
21815
+ branchParentId: action.parentId,
21816
+ side: action.beforeSiblingId === void 0 ? "after" : "before"
21817
+ });
21818
+ }
21602
21819
  /**
21603
21820
  * The single place the two view-state passes are chained. Dispatch, undo and
21604
21821
  * redo all route through here: when this existed only inside the dispatch
@@ -21621,39 +21838,7 @@ var ScheduleCore = class _ScheduleCore {
21621
21838
  sequenced.changes ?? withFilter ?? changes,
21622
21839
  sequenced.touchedParents
21623
21840
  );
21624
- const reapplied = withOrder ?? sequenced.changes ?? withFilter;
21625
- const finalChanges = reapplied ?? changes;
21626
- const createdIds = finalChanges.activities.filter((change) => change.kind === "created").map((change) => String(change.id));
21627
- if (createdIds.length === 0) return reapplied;
21628
- const createdAndAncestors = new Set(createdIds);
21629
- const filterProjection = this.coreRuntime.state.getFilterProjection();
21630
- for (const createdId of createdIds) {
21631
- if (filterProjection !== null) {
21632
- filterProjection.pinnedVisibleIds.add(createdId);
21633
- }
21634
- let activityId = createdId;
21635
- while (activityId !== null && activityId !== ROOT_PARENT_ID) {
21636
- createdAndAncestors.add(activityId);
21637
- if (filterProjection !== null) {
21638
- filterProjection.visibleIds.add(activityId);
21639
- filterProjection.visibleReferenceCounts.set(
21640
- activityId,
21641
- (filterProjection.visibleReferenceCounts.get(activityId) ?? 0) + 1
21642
- );
21643
- }
21644
- activityId = this.coreRuntime.state.getParentId(activityId);
21645
- }
21646
- }
21647
- const createdVisibility = applyVisibleSet(
21648
- this.coreRuntime.state,
21649
- createdAndAncestors,
21650
- createdAndAncestors
21651
- );
21652
- if (createdVisibility.length === 0) return reapplied;
21653
- return {
21654
- ...finalChanges,
21655
- viewState: [...finalChanges.viewState ?? [], ...createdVisibility]
21656
- };
21841
+ return withOrder ?? sequenced.changes ?? withFilter;
21657
21842
  }
21658
21843
  /**
21659
21844
  * Emits `order` for the branches this mutation resequenced, when no user order
@@ -21732,16 +21917,22 @@ var ScheduleCore = class _ScheduleCore {
21732
21917
  context
21733
21918
  );
21734
21919
  const changedActivities = changesRequireFilterProjectionRebuild(changes) ? null : collectChangedActivities(changes, adapter);
21920
+ const relativePlacementPins = adapter.getRelativePlacementPins();
21735
21921
  let affectedIds;
21736
21922
  let visibleIds;
21737
- const needsProjectionRebuild = changedActivities === null || !projectionIsCurrent;
21923
+ const needsProjectionRebuild = changedActivities === null || !projectionIsCurrent || changesAffectRelativePinProjection(
21924
+ changes,
21925
+ changedActivities,
21926
+ adapter,
21927
+ relativePlacementPins
21928
+ );
21738
21929
  if (needsProjectionRebuild) {
21739
21930
  const rebuiltProjection = buildFilterProjection({
21740
21931
  activities: adapter.getAllActivities(),
21741
21932
  parentOf: (activityId) => adapter.getParentId(activityId),
21742
21933
  filter,
21743
21934
  context,
21744
- ...projectionIsCurrent ? { pinnedVisibleIds: projection.pinnedVisibleIds } : {}
21935
+ relativePlacementPins
21745
21936
  });
21746
21937
  adapter.setFilterProjection(rebuiltProjection);
21747
21938
  visibleIds = rebuiltProjection.visibleIds;