@outbuild-company/schedule-core 1.3.0 → 1.4.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.d.cts CHANGED
@@ -260,6 +260,34 @@ interface FilterState {
260
260
  readonly dateRange?: FilterDateRange;
261
261
  }
262
262
 
263
+ interface OrderRule {
264
+ /**
265
+ * Deliberately FilterField, not an alias of it: the columns a user can sort by
266
+ * are the columns a user can filter by, and the filter's registry already
267
+ * resolves units, baseline snapshots and critical-path values for all of them.
268
+ * A second field vocabulary would drift from this one on the first new column.
269
+ */
270
+ readonly field: FilterField;
271
+ readonly direction: 'asc' | 'desc';
272
+ }
273
+ /**
274
+ * The active ordering, as an ordered list of keys: rule 0 decides, rule 1 breaks
275
+ * its ties, and so on. An empty list means "no user ordering", which is stored
276
+ * as null rather than an empty state (mirroring isEmptyFilter).
277
+ */
278
+ interface OrderState {
279
+ readonly rules: ReadonlyArray<OrderRule>;
280
+ }
281
+ /**
282
+ * One resequenced sibling group. Ordering is hierarchical: an activity only ever
283
+ * competes with its own siblings, so a change is expressed per parent rather
284
+ * than as a flat sequence over the whole schedule.
285
+ */
286
+ interface BranchOrderChange {
287
+ readonly parentId: string;
288
+ readonly childIds: ReadonlyArray<string>;
289
+ }
290
+
263
291
  type LinksBatchOperation = {
264
292
  kind: 'create';
265
293
  source: ActivityId;
@@ -385,6 +413,10 @@ type DispatchAction = {
385
413
  logic: 'and' | 'or';
386
414
  dateRange?: FilterDateRange;
387
415
  eventSource?: string;
416
+ } | {
417
+ kind: 'sort-set';
418
+ rules: ReadonlyArray<OrderRule>;
419
+ eventSource?: string;
388
420
  } | {
389
421
  kind: 'sir-sync';
390
422
  activityId: ActivityId;
@@ -500,6 +532,15 @@ interface ChangeSet {
500
532
  calendars: ReadonlyArray<EntityChange<Calendar>>;
501
533
  trackingEvents: ReadonlyArray<TrackingEvent>;
502
534
  viewState?: ReadonlyArray<ViewStateChange>;
535
+ /**
536
+ * Sibling groups whose display sequence changed, each already in final order.
537
+ * Carried per branch rather than as a flat sequence because ordering never
538
+ * reparents: the renderer assigns each array and repaints once.
539
+ *
540
+ * Absent when nothing resequenced. Never a partial answer: a branch listed
541
+ * here holds all of its children.
542
+ */
543
+ order?: ReadonlyArray<BranchOrderChange>;
503
544
  effects?: ReadonlyArray<ScheduleEffect>;
504
545
  warnings?: ReadonlyArray<ConstraintWarning>;
505
546
  }
@@ -655,6 +696,8 @@ declare class ScheduleCore {
655
696
  getChildrenIds(parentId: ActivityId | '0'): ReadonlyArray<ActivityId>;
656
697
  getSelectedActivityIds(): string[];
657
698
  getHiddenActivityIds(): string[];
699
+ getActiveFilter(): Readonly<FilterState> | null;
700
+ getActiveOrder(): Readonly<OrderState> | null;
658
701
  getVisualOrderIds(): ReadonlyArray<ActivityId>;
659
702
  hasChild(parentId: ActivityId | '0'): boolean;
660
703
  getCalendar(id: CalendarId): Readonly<Calendar> | null;
@@ -672,7 +715,52 @@ declare class ScheduleCore {
672
715
  } | null>;
673
716
  isCriticalPathSettled(): boolean;
674
717
  whenCriticalPathSettled(): Promise<void>;
675
- private _withReappliedFilter;
718
+ private _withReappliedViewState;
719
+ /**
720
+ * The single place the two view-state passes are chained. Dispatch, undo and
721
+ * redo all route through here: when this existed only inside the dispatch
722
+ * path, undo and redo reapplied the filter and forgot the order, so an edit
723
+ * that moved a row and was then undone left the row in its new position.
724
+ *
725
+ * The sequence between the passes is indifferent. Filter and order are
726
+ * orthogonal projections over the same tree — the filter decides which rows
727
+ * exist on screen and never reads the order; the order sequences every
728
+ * sibling group from the hierarchy index and never reads visibility. Either
729
+ * sequence produces the same ChangeSet.
730
+ *
731
+ * Both passes answer null while their state is inactive, so an unfiltered,
732
+ * unsorted schedule pays two property reads.
733
+ */
734
+ private _reapplyViewState;
735
+ /**
736
+ * Emits `order` for the branches this mutation resequenced, when no user order
737
+ * is active.
738
+ *
739
+ * The contract is that `order` reports a CHANGED SEQUENCE, not the presence of
740
+ * a sort. Tying emission to the cause instead of the effect is what produced
741
+ * the undo bug and, later, the reparent ones: without an active order a move,
742
+ * an indent, an outdent or the undo of any of them rearranged rows and said
743
+ * nothing, so an incremental consumer kept the old sequence. The undo of a
744
+ * reparent was the worst of them — it carried neither `order` nor a single
745
+ * correlativeId, so the position was not recoverable by any consumer.
746
+ *
747
+ * The touched branches are derived from the ChangeSet rather than accumulated
748
+ * in the state: an entity whose parentId or correlativeId moved, plus the
749
+ * parents of created and deleted rows, is exactly the set of branches whose
750
+ * sequence can differ. That keeps this linear in the blast radius, adds
751
+ * nothing to the write path, and cannot leak across dispatches.
752
+ */
753
+ private _emitTouchedBranchOrder;
754
+ /**
755
+ * Re-sequences the grid after any mutation that could have changed a value the
756
+ * active order sorts by. Ordering is a view over the data, so an edit that
757
+ * moves a row past its sibling must move the row, exactly as the filter makes
758
+ * a no-longer-matching row disappear.
759
+ *
760
+ * Production only re-sorts after a bar drag; diverging from that is a
761
+ * deliberate product decision, not an oversight.
762
+ */
763
+ private _reapplyActiveOrder;
676
764
  private _reapplyActiveFilter;
677
765
  private _recordScheduleMutation;
678
766
  private _startCriticalPathForCurrentRevision;
@@ -791,6 +879,7 @@ declare const DISPATCH_ACTION_KIND: {
791
879
  readonly SELECTION_REPLACE: "selection-replace";
792
880
  readonly VISIBILITY_SET: "visibility-set";
793
881
  readonly FILTER_SET: "filter-set";
882
+ readonly SORT_SET: "sort-set";
794
883
  readonly SIR_SYNC: "sir-sync";
795
884
  readonly ACTIVITY_LOOKAHEAD_SYNC: "activity-lookahead-sync";
796
885
  };
@@ -836,4 +925,4 @@ declare const DISPATCH_TRACK_EVENT: {
836
925
  };
837
926
  type DispatchTrackEvent = (typeof DISPATCH_TRACK_EVENT)[keyof typeof DISPATCH_TRACK_EVENT];
838
927
 
839
- export { ACTIVITY_TYPE, type ActivityCreter, type ActivityId, type ActivityType, type BackendActivityInput, type BackendCalendarInput, type BackendLinkInput, type BackendScheduleInput, type BackendSectorInput, type BaselinePoint, type BaselineVersion, CALENDAR_UNIT, COLUMN, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CREATION_KIND, type Calendar, type CalendarId, type CalendarUnit, type CalendarWorktime, type ChangeSet, type ColumnName, type ConstraintType, type ConstraintWarning, type CoreActivity, type CreationKind, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_TRACK_EVENT, type DispatchAction, type DispatchActionKind, type DispatchOptions, type DispatchRejectReason, type DispatchResult, type DispatchTrackEvent, type EntityChange, type FilterCriterion, type FilterDateRange, type FilterField, type FilterState, LINK_CODE_TO_TYPE, LINK_TYPE, LINK_TYPE_CODE, type Link, type LinkId, type LinkType, type LinkTypeCode, type LinksBatchOperation, NEW_ACTIVITY_DEFAULTS, type ParsedInput, type PastedActivityInput, type PastedLinkInput, type PendingRequest, type PersistedEntityIdentity, REJECTION_REASON, ROOT_PARENT_ID, ScheduleCore, type ScheduleCoreInput, type ScheduleCoreReporter, type ScheduleCoreStatus, type ScheduleEffect, type SectorMetadata, type StatusCriteria, type TrackingEvent, WORK_TIME_DIRECTION, type WorkTimeDirection, checkNoUpdatedLinks, computeExpectedProgress, expectedProgressFromBaseline, getActiveBaseline, getUnsavedActivities, isRootParent, normalizeParentKey, parseFromBackend, willRunCriticalPath, yieldToBrowser };
928
+ export { ACTIVITY_TYPE, type ActivityCreter, type ActivityId, type ActivityType, type BackendActivityInput, type BackendCalendarInput, type BackendLinkInput, type BackendScheduleInput, type BackendSectorInput, type BaselinePoint, type BaselineVersion, type BranchOrderChange, CALENDAR_UNIT, COLUMN, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CREATION_KIND, type Calendar, type CalendarId, type CalendarUnit, type CalendarWorktime, type ChangeSet, type ColumnName, type ConstraintType, type ConstraintWarning, type CoreActivity, type CreationKind, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_TRACK_EVENT, type DispatchAction, type DispatchActionKind, type DispatchOptions, type DispatchRejectReason, type DispatchResult, type DispatchTrackEvent, type EntityChange, type FilterCriterion, type FilterDateRange, type FilterField, type FilterState, LINK_CODE_TO_TYPE, LINK_TYPE, LINK_TYPE_CODE, type Link, type LinkId, type LinkType, type LinkTypeCode, type LinksBatchOperation, NEW_ACTIVITY_DEFAULTS, type OrderRule, type OrderState, type ParsedInput, type PastedActivityInput, type PastedLinkInput, type PendingRequest, type PersistedEntityIdentity, REJECTION_REASON, ROOT_PARENT_ID, ScheduleCore, type ScheduleCoreInput, type ScheduleCoreReporter, type ScheduleCoreStatus, type ScheduleEffect, type SectorMetadata, type StatusCriteria, type TrackingEvent, WORK_TIME_DIRECTION, type WorkTimeDirection, checkNoUpdatedLinks, computeExpectedProgress, expectedProgressFromBaseline, getActiveBaseline, getUnsavedActivities, isRootParent, normalizeParentKey, parseFromBackend, willRunCriticalPath, yieldToBrowser };
package/dist/index.d.ts CHANGED
@@ -260,6 +260,34 @@ interface FilterState {
260
260
  readonly dateRange?: FilterDateRange;
261
261
  }
262
262
 
263
+ interface OrderRule {
264
+ /**
265
+ * Deliberately FilterField, not an alias of it: the columns a user can sort by
266
+ * are the columns a user can filter by, and the filter's registry already
267
+ * resolves units, baseline snapshots and critical-path values for all of them.
268
+ * A second field vocabulary would drift from this one on the first new column.
269
+ */
270
+ readonly field: FilterField;
271
+ readonly direction: 'asc' | 'desc';
272
+ }
273
+ /**
274
+ * The active ordering, as an ordered list of keys: rule 0 decides, rule 1 breaks
275
+ * its ties, and so on. An empty list means "no user ordering", which is stored
276
+ * as null rather than an empty state (mirroring isEmptyFilter).
277
+ */
278
+ interface OrderState {
279
+ readonly rules: ReadonlyArray<OrderRule>;
280
+ }
281
+ /**
282
+ * One resequenced sibling group. Ordering is hierarchical: an activity only ever
283
+ * competes with its own siblings, so a change is expressed per parent rather
284
+ * than as a flat sequence over the whole schedule.
285
+ */
286
+ interface BranchOrderChange {
287
+ readonly parentId: string;
288
+ readonly childIds: ReadonlyArray<string>;
289
+ }
290
+
263
291
  type LinksBatchOperation = {
264
292
  kind: 'create';
265
293
  source: ActivityId;
@@ -385,6 +413,10 @@ type DispatchAction = {
385
413
  logic: 'and' | 'or';
386
414
  dateRange?: FilterDateRange;
387
415
  eventSource?: string;
416
+ } | {
417
+ kind: 'sort-set';
418
+ rules: ReadonlyArray<OrderRule>;
419
+ eventSource?: string;
388
420
  } | {
389
421
  kind: 'sir-sync';
390
422
  activityId: ActivityId;
@@ -500,6 +532,15 @@ interface ChangeSet {
500
532
  calendars: ReadonlyArray<EntityChange<Calendar>>;
501
533
  trackingEvents: ReadonlyArray<TrackingEvent>;
502
534
  viewState?: ReadonlyArray<ViewStateChange>;
535
+ /**
536
+ * Sibling groups whose display sequence changed, each already in final order.
537
+ * Carried per branch rather than as a flat sequence because ordering never
538
+ * reparents: the renderer assigns each array and repaints once.
539
+ *
540
+ * Absent when nothing resequenced. Never a partial answer: a branch listed
541
+ * here holds all of its children.
542
+ */
543
+ order?: ReadonlyArray<BranchOrderChange>;
503
544
  effects?: ReadonlyArray<ScheduleEffect>;
504
545
  warnings?: ReadonlyArray<ConstraintWarning>;
505
546
  }
@@ -655,6 +696,8 @@ declare class ScheduleCore {
655
696
  getChildrenIds(parentId: ActivityId | '0'): ReadonlyArray<ActivityId>;
656
697
  getSelectedActivityIds(): string[];
657
698
  getHiddenActivityIds(): string[];
699
+ getActiveFilter(): Readonly<FilterState> | null;
700
+ getActiveOrder(): Readonly<OrderState> | null;
658
701
  getVisualOrderIds(): ReadonlyArray<ActivityId>;
659
702
  hasChild(parentId: ActivityId | '0'): boolean;
660
703
  getCalendar(id: CalendarId): Readonly<Calendar> | null;
@@ -672,7 +715,52 @@ declare class ScheduleCore {
672
715
  } | null>;
673
716
  isCriticalPathSettled(): boolean;
674
717
  whenCriticalPathSettled(): Promise<void>;
675
- private _withReappliedFilter;
718
+ private _withReappliedViewState;
719
+ /**
720
+ * The single place the two view-state passes are chained. Dispatch, undo and
721
+ * redo all route through here: when this existed only inside the dispatch
722
+ * path, undo and redo reapplied the filter and forgot the order, so an edit
723
+ * that moved a row and was then undone left the row in its new position.
724
+ *
725
+ * The sequence between the passes is indifferent. Filter and order are
726
+ * orthogonal projections over the same tree — the filter decides which rows
727
+ * exist on screen and never reads the order; the order sequences every
728
+ * sibling group from the hierarchy index and never reads visibility. Either
729
+ * sequence produces the same ChangeSet.
730
+ *
731
+ * Both passes answer null while their state is inactive, so an unfiltered,
732
+ * unsorted schedule pays two property reads.
733
+ */
734
+ private _reapplyViewState;
735
+ /**
736
+ * Emits `order` for the branches this mutation resequenced, when no user order
737
+ * is active.
738
+ *
739
+ * The contract is that `order` reports a CHANGED SEQUENCE, not the presence of
740
+ * a sort. Tying emission to the cause instead of the effect is what produced
741
+ * the undo bug and, later, the reparent ones: without an active order a move,
742
+ * an indent, an outdent or the undo of any of them rearranged rows and said
743
+ * nothing, so an incremental consumer kept the old sequence. The undo of a
744
+ * reparent was the worst of them — it carried neither `order` nor a single
745
+ * correlativeId, so the position was not recoverable by any consumer.
746
+ *
747
+ * The touched branches are derived from the ChangeSet rather than accumulated
748
+ * in the state: an entity whose parentId or correlativeId moved, plus the
749
+ * parents of created and deleted rows, is exactly the set of branches whose
750
+ * sequence can differ. That keeps this linear in the blast radius, adds
751
+ * nothing to the write path, and cannot leak across dispatches.
752
+ */
753
+ private _emitTouchedBranchOrder;
754
+ /**
755
+ * Re-sequences the grid after any mutation that could have changed a value the
756
+ * active order sorts by. Ordering is a view over the data, so an edit that
757
+ * moves a row past its sibling must move the row, exactly as the filter makes
758
+ * a no-longer-matching row disappear.
759
+ *
760
+ * Production only re-sorts after a bar drag; diverging from that is a
761
+ * deliberate product decision, not an oversight.
762
+ */
763
+ private _reapplyActiveOrder;
676
764
  private _reapplyActiveFilter;
677
765
  private _recordScheduleMutation;
678
766
  private _startCriticalPathForCurrentRevision;
@@ -791,6 +879,7 @@ declare const DISPATCH_ACTION_KIND: {
791
879
  readonly SELECTION_REPLACE: "selection-replace";
792
880
  readonly VISIBILITY_SET: "visibility-set";
793
881
  readonly FILTER_SET: "filter-set";
882
+ readonly SORT_SET: "sort-set";
794
883
  readonly SIR_SYNC: "sir-sync";
795
884
  readonly ACTIVITY_LOOKAHEAD_SYNC: "activity-lookahead-sync";
796
885
  };
@@ -836,4 +925,4 @@ declare const DISPATCH_TRACK_EVENT: {
836
925
  };
837
926
  type DispatchTrackEvent = (typeof DISPATCH_TRACK_EVENT)[keyof typeof DISPATCH_TRACK_EVENT];
838
927
 
839
- export { ACTIVITY_TYPE, type ActivityCreter, type ActivityId, type ActivityType, type BackendActivityInput, type BackendCalendarInput, type BackendLinkInput, type BackendScheduleInput, type BackendSectorInput, type BaselinePoint, type BaselineVersion, CALENDAR_UNIT, COLUMN, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CREATION_KIND, type Calendar, type CalendarId, type CalendarUnit, type CalendarWorktime, type ChangeSet, type ColumnName, type ConstraintType, type ConstraintWarning, type CoreActivity, type CreationKind, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_TRACK_EVENT, type DispatchAction, type DispatchActionKind, type DispatchOptions, type DispatchRejectReason, type DispatchResult, type DispatchTrackEvent, type EntityChange, type FilterCriterion, type FilterDateRange, type FilterField, type FilterState, LINK_CODE_TO_TYPE, LINK_TYPE, LINK_TYPE_CODE, type Link, type LinkId, type LinkType, type LinkTypeCode, type LinksBatchOperation, NEW_ACTIVITY_DEFAULTS, type ParsedInput, type PastedActivityInput, type PastedLinkInput, type PendingRequest, type PersistedEntityIdentity, REJECTION_REASON, ROOT_PARENT_ID, ScheduleCore, type ScheduleCoreInput, type ScheduleCoreReporter, type ScheduleCoreStatus, type ScheduleEffect, type SectorMetadata, type StatusCriteria, type TrackingEvent, WORK_TIME_DIRECTION, type WorkTimeDirection, checkNoUpdatedLinks, computeExpectedProgress, expectedProgressFromBaseline, getActiveBaseline, getUnsavedActivities, isRootParent, normalizeParentKey, parseFromBackend, willRunCriticalPath, yieldToBrowser };
928
+ export { ACTIVITY_TYPE, type ActivityCreter, type ActivityId, type ActivityType, type BackendActivityInput, type BackendCalendarInput, type BackendLinkInput, type BackendScheduleInput, type BackendSectorInput, type BaselinePoint, type BaselineVersion, type BranchOrderChange, CALENDAR_UNIT, COLUMN, CONSTRAINT_TYPE, CONSTRAINT_TYPE_TO_LABEL, CREATION_KIND, type Calendar, type CalendarId, type CalendarUnit, type CalendarWorktime, type ChangeSet, type ColumnName, type ConstraintType, type ConstraintWarning, type CoreActivity, type CreationKind, DEFAULT_HOURS_PER_DAY, DISPATCH_ACTION_KIND, DISPATCH_ACTION_KINDS, DISPATCH_TRACK_EVENT, type DispatchAction, type DispatchActionKind, type DispatchOptions, type DispatchRejectReason, type DispatchResult, type DispatchTrackEvent, type EntityChange, type FilterCriterion, type FilterDateRange, type FilterField, type FilterState, LINK_CODE_TO_TYPE, LINK_TYPE, LINK_TYPE_CODE, type Link, type LinkId, type LinkType, type LinkTypeCode, type LinksBatchOperation, NEW_ACTIVITY_DEFAULTS, type OrderRule, type OrderState, type ParsedInput, type PastedActivityInput, type PastedLinkInput, type PendingRequest, type PersistedEntityIdentity, REJECTION_REASON, ROOT_PARENT_ID, ScheduleCore, type ScheduleCoreInput, type ScheduleCoreReporter, type ScheduleCoreStatus, type ScheduleEffect, type SectorMetadata, type StatusCriteria, type TrackingEvent, WORK_TIME_DIRECTION, type WorkTimeDirection, checkNoUpdatedLinks, computeExpectedProgress, expectedProgressFromBaseline, getActiveBaseline, getUnsavedActivities, isRootParent, normalizeParentKey, parseFromBackend, willRunCriticalPath, yieldToBrowser };