@outbuild-company/schedule-core 1.1.0 → 1.1.2

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
@@ -44,11 +44,6 @@ interface LinkSnapshot {
44
44
  /** BE row id (distinct from the schedule-level `id`). */
45
45
  proplannerId?: number;
46
46
  }
47
- /**
48
- * Gantt-wide flags that influence scheduling decisions. Captured at the
49
- * boundary of every scheduling run.
50
- */
51
- type MutableLinkField = 'lag' | 'type';
52
47
 
53
48
  /**
54
49
  * Constraint Rules — single source of truth.
@@ -223,6 +218,8 @@ interface CoreBaselinePoint {
223
218
  workHours: number;
224
219
  versionId: number | null;
225
220
  isActiveVersion: boolean;
221
+ /** Whether this version is the one selected for visual comparison. */
222
+ isVisibleVersion: boolean;
226
223
  baseCalendarId: CalendarId | null;
227
224
  hoursPerDay: number | null;
228
225
  hoursPerWeek: number | null;
@@ -523,8 +520,25 @@ type LinksBatchOperation = {
523
520
  kind: 'delete';
524
521
  linkId: LinkId;
525
522
  };
523
+ interface PersistedEntityIdentity {
524
+ readonly id: string;
525
+ readonly proplannerId: number;
526
+ }
526
527
 
527
528
  type DispatchAction = {
529
+ kind: 'persistence-acknowledge';
530
+ activities?: ReadonlyArray<PersistedEntityIdentity>;
531
+ links?: ReadonlyArray<PersistedEntityIdentity>;
532
+ } | {
533
+ kind: 'baseline-apply';
534
+ points: ReadonlyArray<BaselinePoint>;
535
+ } | {
536
+ kind: 'ponderator-criterion-set';
537
+ criterion: ActivityCreter;
538
+ } | {
539
+ kind: 'status-criteria-set';
540
+ criteria: StatusCriteria;
541
+ } | {
528
542
  kind: 'inline-edit';
529
543
  activityId: ActivityId;
530
544
  column: string;
@@ -1086,56 +1100,6 @@ interface BackendScheduleInput {
1086
1100
  baseCalendars?: BackendCalendarInput[] | undefined;
1087
1101
  }
1088
1102
 
1089
- /**
1090
- * Role interfaces (Interface Segregation).
1091
- *
1092
- * Consumers receive the smallest role they need instead of the whole
1093
- * `ScheduleState`. A pure-read consumer takes `ActivityReader`; a
1094
- * read+write consumer takes `ActivityWriter`; a consumer that drives the
1095
- * per-dispatch write journal takes `WriteJournal`. `ScheduleState`
1096
- * implements all of them, so passing it anywhere a role is expected is
1097
- * still valid — the role just narrows what the callee is allowed to touch.
1098
- *
1099
- * Signatures here are copied verbatim from `ScheduleState`; the class is
1100
- * the source of truth. If the class changes a member, change the role to
1101
- * match (not the other way around).
1102
- */
1103
-
1104
- /** READ surface over the activity/link store and the hierarchy index. */
1105
- interface ActivityReader {
1106
- getActivity(activityId: ActivityId$1): CoreActivity | null;
1107
- getLastStartDate(activityId: ActivityId$1): Date | null;
1108
- getAllActivities(): ReadonlyArray<CoreActivity>;
1109
- getAllLinks(): LinkSnapshot[];
1110
- getLink(linkId: LinkId$1): LinkSnapshot | null;
1111
- activityExists(activityId: ActivityId$1): boolean;
1112
- getChildren(parentId: ActivityId$1): ActivityId$1[];
1113
- hasChildren(parentId: ActivityId$1): boolean;
1114
- getParent(activityId: ActivityId$1): CoreActivity | null;
1115
- getParentId(activityId: ActivityId$1): ActivityId$1 | null;
1116
- getRootIds(): ReadonlyArray<ActivityId$1>;
1117
- isChildOf(childId: ActivityId$1, parentId: ActivityId$1): boolean;
1118
- getOutgoingLinkIds(activityId: ActivityId$1): ReadonlyArray<LinkId$1>;
1119
- getIncomingLinkIds(activityId: ActivityId$1): ReadonlyArray<LinkId$1>;
1120
- getAllIds(): readonly ActivityId$1[];
1121
- activityCount(): number;
1122
- forEachActivity(visit: (activity: CoreActivity, id: ActivityId$1) => void): void;
1123
- }
1124
- /** READ + WRITE surface over the store. */
1125
- interface ActivityWriter extends ActivityReader {
1126
- setActivityField<Field extends keyof CoreActivity>(activityId: ActivityId$1, field: Field, value: CoreActivity[Field]): void;
1127
- setActivityFieldDynamic(activityId: ActivityId$1, field: string, value: unknown): void;
1128
- /** Bulk write of several fields of one activity in a single getActivity +
1129
- * write-capture note (vs N setActivityField calls). */
1130
- setActivityFields(activityId: ActivityId$1, fields: Partial<CoreActivity>): void;
1131
- addLink(link: LinkSnapshot): void;
1132
- removeLink(linkId: LinkId$1): void;
1133
- setLinkField<Field extends MutableLinkField>(linkId: LinkId$1, field: Field, value: LinkSnapshot[Field]): void;
1134
- setLinkFieldDynamic(linkId: LinkId$1, field: string, value: unknown): void;
1135
- addActivity(activity: CoreActivity): void;
1136
- removeActivity(activityId: ActivityId$1): void;
1137
- }
1138
-
1139
1103
  /**
1140
1104
  * Injected reporting port. The core is UI-agnostic: it cannot `console.*`,
1141
1105
  * show UI, or know about Sentry. When it hits a recoverable data-quality
@@ -1235,25 +1199,14 @@ interface ScheduleCoreInput extends BackendScheduleInput {
1235
1199
  reporter?: ScheduleCoreReporter;
1236
1200
  }
1237
1201
 
1238
- interface PersistedEntityIdentity {
1239
- readonly id: string;
1240
- readonly proplannerId: number;
1241
- }
1242
- interface PersistenceAcknowledgement {
1243
- readonly activities?: ReadonlyArray<PersistedEntityIdentity>;
1244
- readonly links?: ReadonlyArray<PersistedEntityIdentity>;
1245
- }
1246
1202
  declare class ScheduleCore {
1247
1203
  private _status;
1248
- /** True while an async dispatch is suspended inside the write-capture window.
1249
- * A sync facade mutation running in that gap would corrupt the in-flight
1250
- * journal, so sync mutators refuse to run while it is set. */
1251
- private _dispatchInFlight;
1252
1204
  private readonly coreRuntime;
1253
1205
  private readonly _undo;
1254
1206
  private _opQueue;
1255
- private _mutationEpoch;
1256
- private _cpComputedEpoch;
1207
+ private _scheduleRevision;
1208
+ private _criticalPathRevision;
1209
+ private _activeCriticalPath;
1257
1210
  constructor(input: ScheduleCoreInput);
1258
1211
  get ready(): Promise<void>;
1259
1212
  get criticalPathReady(): Promise<{
@@ -1286,76 +1239,41 @@ declare class ScheduleCore {
1286
1239
  isCustomIdInUse(customId: string, currentCustomId?: string | null): boolean;
1287
1240
  getProjectWorkHours(): ProjectWorkHours | null;
1288
1241
  getModifiedLinks(): ReadonlyArray<Readonly<Link>>;
1289
- markLinksPersisted(): void;
1290
1242
  getModifiedActivities(): ReadonlyArray<Readonly<CoreActivity>>;
1291
- markActivitiesPersisted(): void;
1292
- /**
1293
- * Reconciles the backend identities returned by a successful schedule save.
1294
- *
1295
- * This is an application-boundary acknowledgement, not an editable schedule
1296
- * gesture: it does not autoschedule, recompute, emit an undo entry, or invent
1297
- * domain changes. Every assignment is validated before the first mutation so
1298
- * a malformed/partial response cannot leave the core half-reconciled. Once
1299
- * applied, the complete live graph becomes the new save-tracker baseline.
1300
- */
1301
- acknowledgePersistence(acknowledgement: PersistenceAcknowledgement): void;
1302
- applyBaselines(points: readonly BaselinePoint[]): ReadonlyArray<Readonly<CoreActivity>>;
1303
- /**
1304
- * Whole-tree ponderator recompute (zeroes, then redistributes 100 within
1305
- * each parent from its children's active baselines, weighted by `criterion`).
1306
- * Returns the activities whose `ponderator` changed, for a prop-only repaint.
1307
- * The base rollup (`applyBaselines`) depends on fresh ponderators, so callers
1308
- * run this before it on a criterion change or baseline save.
1309
- */
1310
- recomputeAllPonderators(criterion: ActivityCreter): ReadonlyArray<Readonly<CoreActivity>>;
1311
- /**
1312
- * Recompute the weighted-progress rollup of every parent, bottom-up.
1313
- * Returns the activities whose `progress` changed, for a prop-only repaint.
1314
- */
1315
- recomputeAllProgressRollup(): ReadonlyArray<Readonly<CoreActivity>>;
1316
- /**
1317
- * Set the project's status criterion (the Baseline↔Actual toggle) and
1318
- * recompute every activity's `status` against it. Stores the criterion so
1319
- * future dispatches keep deriving status with it. Returns the activities
1320
- * whose `status` changed, for a prop-only repaint.
1321
- */
1322
- setStatusCriteria(criteria: StatusCriteria): ReadonlyArray<Readonly<CoreActivity>>;
1323
- allocateActivityId(): ActivityId;
1324
- createActivity(input: Omit<Extract<DispatchAction, {
1325
- kind: 'activity-create';
1326
- }>, 'kind'>): {
1327
- ok: true;
1328
- activity: Readonly<CoreActivity>;
1329
- changes: ChangeSet;
1330
- } | {
1331
- ok: false;
1332
- reason: string;
1333
- };
1334
1243
  private _enqueue;
1335
1244
  dispatch(action: DispatchAction, options?: DispatchOptions): Promise<DispatchResult>;
1336
1245
  private _dispatchInner;
1337
1246
  /**
1338
- * Recompute the critical path OUT of band and return a repaintable ChangeSet
1339
- * of the 7 CP fields, or null when the CP is already fresh for the current
1340
- * state (coalesced). Enqueued on `_opQueue` so it runs serialized against
1341
- * dispatches — it reads/writes the live tree only when no mutation is in
1342
- * flight.
1343
- *
1344
- * Deferred-CP flow: dispatch with `{ skipCriticalPath: true }` (paints
1345
- * without the heavy CP pass), then call this after the paint. Bursts
1346
- * coalesce: only the first enqueued recompute after the last mutation does
1347
- * the work; later ones see `_cpComputedEpoch === _mutationEpoch` and no-op.
1247
+ * Starts or joins the Critical Path calculation for the current schedule
1248
+ * revision. Snapshot capture and the final commit are serialized with user
1249
+ * mutations, but the expensive calculation runs outside `_opQueue` against
1250
+ * an isolated state. A newer revision aborts this job and makes its result
1251
+ * ineligible to commit.
1348
1252
  */
1349
1253
  recomputeCriticalPath(): Promise<{
1350
1254
  changes: ChangeSet;
1351
1255
  } | null>;
1352
- private _recomputeCriticalPathIfStale;
1256
+ isCriticalPathSettled(): boolean;
1257
+ whenCriticalPathSettled(): Promise<void>;
1258
+ private _recordScheduleMutation;
1259
+ private _startCriticalPathForCurrentRevision;
1353
1260
  private _runCriticalPathAndCapture;
1261
+ /**
1262
+ * Undo/Redo restores the user's historical mutation while retaining current
1263
+ * non-historical truth (for example a refreshed baseline). Re-derive every
1264
+ * value that depends on both so the restored model is immediately coherent.
1265
+ */
1266
+ private _recomputeAfterHistoryRestore;
1354
1267
  undo(): Promise<ChangeSet | null>;
1355
1268
  redo(): Promise<ChangeSet | null>;
1356
- clearHistory(): void;
1357
1269
  canUndo(): boolean;
1358
1270
  canRedo(): boolean;
1271
+ /**
1272
+ * Establishes a new persistence boundary without mutating schedule state.
1273
+ * Completed saves call this synchronously so neither prior undo entries nor
1274
+ * their redo branch can cross the persisted boundary.
1275
+ */
1276
+ clearHistory(): void;
1359
1277
  undoDepth(): number;
1360
1278
  private _resyncCustomIdTrackerFromModel;
1361
1279
  destroy(): void;
@@ -1410,25 +1328,6 @@ declare function getActiveBaseline(activity: {
1410
1328
  baselinePoints?: readonly CoreBaselinePoint[];
1411
1329
  } | null | undefined): CoreBaselinePoint | null;
1412
1330
 
1413
- /**
1414
- * Surface this function needs from ScheduleState — kept structural so the
1415
- * function is unit-testable with a plain in-memory adapter.
1416
- */
1417
- interface BaselineApplyAdapter {
1418
- forEachActivity(visit: (a: Record<string, unknown>, id: string) => void): void;
1419
- getActivity(id: string): Record<string, unknown> | null;
1420
- setActivityField(id: string, field: string, value: unknown): void;
1421
- }
1422
- /**
1423
- * Overlay baseline points onto activities, matching `point.activityId`
1424
- * (backend DB id) against `activity.proplannerId`. Writes the full group of
1425
- * points (all versions) onto `baseline_points`; the active one is selected
1426
- * downstream by `getActiveBaseline`. Activities absent from the new set get
1427
- * `baseline_points` reset to `[]`. Returns the ids of activities whose
1428
- * `baseline_points` changed (so the caller can repaint just those).
1429
- */
1430
- declare function applyBaselinePoints(adapter: BaselineApplyAdapter, points: readonly BaselinePoint[], reporter: ScheduleCoreReporter): string[];
1431
-
1432
1331
  interface CalendarLike {
1433
1332
  calculateDuration(start: Date, end: Date): number;
1434
1333
  }
@@ -1459,67 +1358,6 @@ interface ExpectedProgressAdapter {
1459
1358
  */
1460
1359
  declare function computeExpectedProgress(rootIds: readonly string[], now: Date, adapter: ExpectedProgressAdapter, defaultBaseCalendarId?: string | null): string[];
1461
1360
 
1462
- /**
1463
- * Recompute the `ponderator` of EVERY activity in the schedule, in bulk.
1464
- *
1465
- * Faithful port of `goCalculatePonderators` in
1466
- * `react_client/src/views/ganttContainer/gantt/index.js:2008-2038` (the
1467
- * ponderator part — the progress rollup and save are separate concerns the
1468
- * caller wires). Two passes, mirroring the legacy:
1469
- * 1. Zero EVERY activity's ponderator.
1470
- * 2. For each parent (activity with children), redistribute 100 among its
1471
- * children via `recomputePonderatorsForParent` (baseline-only).
1472
- *
1473
- * IMPORTANT — this is the ONLY ponderator recompute the schedule performs.
1474
- * Verified against the legacy (2026-06-04): the masterplan does NOT recompute
1475
- * ponderators on add / delete / move / inline-edit. `goCalculatePonderators`
1476
- * runs ONLY on two events (see index.js:395/443/625):
1477
- * - the project criterion (`activity_creter`) changed
1478
- * (`sectorObject.update_ponderators_masterplan`), or
1479
- * - a baseline was created / saved.
1480
- * Both events otherwise reload the schedule. So there is intentionally NO
1481
- * per-mutation cascade and NO dispatch hook — adding one would diverge from
1482
- * production (a baselined child's siblings stay as the backend computed them
1483
- * until the next of those two events). See
1484
- * [[PONDERATOR_BASELINE_MIGRATION_PLAN]].
1485
- *
1486
- * Order-independent: a project child's weight derives from descendant
1487
- * BASELINES (`getDurationRecursively`), never from other ponderators, so the
1488
- * order parents are visited in does not affect the result.
1489
- */
1490
-
1491
- declare function recomputeAllPonderators(criterion: ActivityCreter, adapter: ActivityWriter): void;
1492
-
1493
- /**
1494
- * Recompute the weighted-progress rollup of EVERY parent in the schedule,
1495
- * bottom-up. `parent.progress = Σ(child.progress × child.ponderator / 100)`.
1496
- *
1497
- * Faithful port of legacy `check_progress` (editing-flow.js:154-197) — the
1498
- * second half of `goCalculatePonderators`. After a ponderator recompute
1499
- * (baseline create/save, see `recomputeAllPonderators`), parent progress is
1500
- * stale w.r.t. the new weights; this repropagates it.
1501
- *
1502
- * PROGRESS-ONLY by design: unlike the dispatch post-processor
1503
- * `recomputeParentFromChildren` (post-processors/parent-bounds.ts), this does NOT
1504
- * touch parent start/end/duration — only `progress`. That keeps it testable
1505
- * in isolation (no calendars/dates needed) and matches what `check_progress`
1506
- * actually did.
1507
- *
1508
- * Order is load-bearing (unlike ponderators, which are order-independent): a
1509
- * grandparent's rollup consumes its children-parents' ALREADY rolled-up
1510
- * progress, so parents are visited deepest-first.
1511
- *
1512
- * Parity rules:
1513
- * - child with no/zero ponderator does not contribute;
1514
- * - a parent whose children contribute nothing keeps its progress frozen;
1515
- * - `progress` is kept a NUMBER but rounded per level to 2 decimals
1516
- * (`roundProgressPerLevel`) — legacy `check_progress` recurses on
1517
- * `toFixed(2)`, so each ancestor consumes its children's 2-decimal value
1518
- * (MORNING_QUEUE.md — JUN-02).
1519
- */
1520
-
1521
- declare function recomputeAllProgressRollup(adapter: ActivityWriter): void;
1522
-
1523
1361
  type StaticActivityDefaults = Omit<CoreActivity, 'id' | 'parentId' | 'name' | 'startDate' | 'endDate' | 'durationHours' | 'calendarId' | 'uniqueCorrelativeId'>;
1524
1362
  declare const NEW_ACTIVITY_DEFAULTS: Readonly<StaticActivityDefaults>;
1525
1363
 
@@ -1569,9 +1407,9 @@ declare function yieldToBrowser(): Promise<void>;
1569
1407
  * Does this dispatch recompute the critical path? The single authority for the
1570
1408
  * question, shared by the dispatch gate (`resolveDerivedPasses`) and the
1571
1409
  * bridge, which reads it to defer the CP out of the paint. Mirrors legacy: the
1572
- * CP re-derives on any scheduling change, and is left STALE for prop-only
1573
- * intents, the structural reparents (indent/outdent), and edits that touch only
1574
- * non-scheduling columns.
1410
+ * CP re-derives on every scheduling change. Prop-only/administrative intents
1411
+ * and edits that touch only non-scheduling columns leave the current CP
1412
+ * revision intact; structural reparents are never exempt.
1575
1413
  */
1576
1414
  declare function willRunCriticalPath(action: DispatchAction): boolean;
1577
1415
 
@@ -1674,6 +1512,10 @@ declare const DEFAULT_HOURS_PER_DAY = 8;
1674
1512
 
1675
1513
  type DispatchActionKind = DispatchAction['kind'];
1676
1514
  declare const DISPATCH_ACTION_KIND: {
1515
+ readonly PERSISTENCE_ACKNOWLEDGE: "persistence-acknowledge";
1516
+ readonly BASELINE_APPLY: "baseline-apply";
1517
+ readonly PONDERATOR_CRITERION_SET: "ponderator-criterion-set";
1518
+ readonly STATUS_CRITERIA_SET: "status-criteria-set";
1677
1519
  readonly INLINE_EDIT: "inline-edit";
1678
1520
  readonly DATES_BATCH: "dates-batch";
1679
1521
  readonly BULK_EDIT: "bulk-edit";
@@ -1768,4 +1610,4 @@ declare const DISPATCH_TRACK_EVENT: {
1768
1610
  };
1769
1611
  type DispatchTrackEvent = (typeof DISPATCH_TRACK_EVENT)[keyof typeof DISPATCH_TRACK_EVENT];
1770
1612
 
1771
- 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, 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, type PersistenceAcknowledgement, 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, applyBaselinePoints, checkNoUpdatedLinks, computeExpectedProgress, expectedProgressFromBaseline, getActiveBaseline, getUnsavedActivities, isRootParent, normalizeParentKey, parseFromBackend, recomputeAllPonderators, recomputeAllProgressRollup, willRunCriticalPath, yieldToBrowser };
1613
+ 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, 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 };