@outbuild-company/schedule-core 1.1.1 → 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,20 +1199,8 @@ 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;
@@ -1287,51 +1239,7 @@ declare class ScheduleCore {
1287
1239
  isCustomIdInUse(customId: string, currentCustomId?: string | null): boolean;
1288
1240
  getProjectWorkHours(): ProjectWorkHours | null;
1289
1241
  getModifiedLinks(): ReadonlyArray<Readonly<Link>>;
1290
- markLinksPersisted(): void;
1291
1242
  getModifiedActivities(): ReadonlyArray<Readonly<CoreActivity>>;
1292
- markActivitiesPersisted(): void;
1293
- /**
1294
- * Reconciles the backend identities returned by a successful schedule save.
1295
- *
1296
- * This is an application-boundary acknowledgement, not an editable schedule
1297
- * gesture: it does not autoschedule, recompute, emit an undo entry, or invent
1298
- * domain changes. Every assignment is validated before the first mutation so
1299
- * a malformed/partial response cannot leave the core half-reconciled. Once
1300
- * applied, the complete live graph becomes the new save-tracker baseline.
1301
- */
1302
- acknowledgePersistence(acknowledgement: PersistenceAcknowledgement): void;
1303
- applyBaselines(points: readonly BaselinePoint[]): ReadonlyArray<Readonly<CoreActivity>>;
1304
- /**
1305
- * Whole-tree ponderator recompute (zeroes, then redistributes 100 within
1306
- * each parent from its children's active baselines, weighted by `criterion`).
1307
- * Returns the activities whose `ponderator` changed, for a prop-only repaint.
1308
- * The base rollup (`applyBaselines`) depends on fresh ponderators, so callers
1309
- * run this before it on a criterion change or baseline save.
1310
- */
1311
- recomputeAllPonderators(criterion: ActivityCreter): ReadonlyArray<Readonly<CoreActivity>>;
1312
- /**
1313
- * Recompute the weighted-progress rollup of every parent, bottom-up.
1314
- * Returns the activities whose `progress` changed, for a prop-only repaint.
1315
- */
1316
- recomputeAllProgressRollup(): ReadonlyArray<Readonly<CoreActivity>>;
1317
- /**
1318
- * Set the project's status criterion (the Baseline↔Actual toggle) and
1319
- * recompute every activity's `status` against it. Stores the criterion so
1320
- * future dispatches keep deriving status with it. Returns the activities
1321
- * whose `status` changed, for a prop-only repaint.
1322
- */
1323
- setStatusCriteria(criteria: StatusCriteria): ReadonlyArray<Readonly<CoreActivity>>;
1324
- allocateActivityId(): ActivityId;
1325
- createActivity(input: Omit<Extract<DispatchAction, {
1326
- kind: 'activity-create';
1327
- }>, 'kind'>): {
1328
- ok: true;
1329
- activity: Readonly<CoreActivity>;
1330
- changes: ChangeSet;
1331
- } | {
1332
- ok: false;
1333
- reason: string;
1334
- };
1335
1243
  private _enqueue;
1336
1244
  dispatch(action: DispatchAction, options?: DispatchOptions): Promise<DispatchResult>;
1337
1245
  private _dispatchInner;
@@ -1350,11 +1258,22 @@ declare class ScheduleCore {
1350
1258
  private _recordScheduleMutation;
1351
1259
  private _startCriticalPathForCurrentRevision;
1352
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;
1353
1267
  undo(): Promise<ChangeSet | null>;
1354
1268
  redo(): Promise<ChangeSet | null>;
1355
- clearHistory(): void;
1356
1269
  canUndo(): boolean;
1357
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;
1358
1277
  undoDepth(): number;
1359
1278
  private _resyncCustomIdTrackerFromModel;
1360
1279
  destroy(): void;
@@ -1409,25 +1328,6 @@ declare function getActiveBaseline(activity: {
1409
1328
  baselinePoints?: readonly CoreBaselinePoint[];
1410
1329
  } | null | undefined): CoreBaselinePoint | null;
1411
1330
 
1412
- /**
1413
- * Surface this function needs from ScheduleState — kept structural so the
1414
- * function is unit-testable with a plain in-memory adapter.
1415
- */
1416
- interface BaselineApplyAdapter {
1417
- forEachActivity(visit: (a: Record<string, unknown>, id: string) => void): void;
1418
- getActivity(id: string): Record<string, unknown> | null;
1419
- setActivityField(id: string, field: string, value: unknown): void;
1420
- }
1421
- /**
1422
- * Overlay baseline points onto activities, matching `point.activityId`
1423
- * (backend DB id) against `activity.proplannerId`. Writes the full group of
1424
- * points (all versions) onto `baseline_points`; the active one is selected
1425
- * downstream by `getActiveBaseline`. Activities absent from the new set get
1426
- * `baseline_points` reset to `[]`. Returns the ids of activities whose
1427
- * `baseline_points` changed (so the caller can repaint just those).
1428
- */
1429
- declare function applyBaselinePoints(adapter: BaselineApplyAdapter, points: readonly BaselinePoint[], reporter: ScheduleCoreReporter): string[];
1430
-
1431
1331
  interface CalendarLike {
1432
1332
  calculateDuration(start: Date, end: Date): number;
1433
1333
  }
@@ -1458,67 +1358,6 @@ interface ExpectedProgressAdapter {
1458
1358
  */
1459
1359
  declare function computeExpectedProgress(rootIds: readonly string[], now: Date, adapter: ExpectedProgressAdapter, defaultBaseCalendarId?: string | null): string[];
1460
1360
 
1461
- /**
1462
- * Recompute the `ponderator` of EVERY activity in the schedule, in bulk.
1463
- *
1464
- * Faithful port of `goCalculatePonderators` in
1465
- * `react_client/src/views/ganttContainer/gantt/index.js:2008-2038` (the
1466
- * ponderator part — the progress rollup and save are separate concerns the
1467
- * caller wires). Two passes, mirroring the legacy:
1468
- * 1. Zero EVERY activity's ponderator.
1469
- * 2. For each parent (activity with children), redistribute 100 among its
1470
- * children via `recomputePonderatorsForParent` (baseline-only).
1471
- *
1472
- * IMPORTANT — this is the ONLY ponderator recompute the schedule performs.
1473
- * Verified against the legacy (2026-06-04): the masterplan does NOT recompute
1474
- * ponderators on add / delete / move / inline-edit. `goCalculatePonderators`
1475
- * runs ONLY on two events (see index.js:395/443/625):
1476
- * - the project criterion (`activity_creter`) changed
1477
- * (`sectorObject.update_ponderators_masterplan`), or
1478
- * - a baseline was created / saved.
1479
- * Both events otherwise reload the schedule. So there is intentionally NO
1480
- * per-mutation cascade and NO dispatch hook — adding one would diverge from
1481
- * production (a baselined child's siblings stay as the backend computed them
1482
- * until the next of those two events). See
1483
- * [[PONDERATOR_BASELINE_MIGRATION_PLAN]].
1484
- *
1485
- * Order-independent: a project child's weight derives from descendant
1486
- * BASELINES (`getDurationRecursively`), never from other ponderators, so the
1487
- * order parents are visited in does not affect the result.
1488
- */
1489
-
1490
- declare function recomputeAllPonderators(criterion: ActivityCreter, adapter: ActivityWriter): void;
1491
-
1492
- /**
1493
- * Recompute the weighted-progress rollup of EVERY parent in the schedule,
1494
- * bottom-up. `parent.progress = Σ(child.progress × child.ponderator / 100)`.
1495
- *
1496
- * Faithful port of legacy `check_progress` (editing-flow.js:154-197) — the
1497
- * second half of `goCalculatePonderators`. After a ponderator recompute
1498
- * (baseline create/save, see `recomputeAllPonderators`), parent progress is
1499
- * stale w.r.t. the new weights; this repropagates it.
1500
- *
1501
- * PROGRESS-ONLY by design: unlike the dispatch post-processor
1502
- * `recomputeParentFromChildren` (post-processors/parent-bounds.ts), this does NOT
1503
- * touch parent start/end/duration — only `progress`. That keeps it testable
1504
- * in isolation (no calendars/dates needed) and matches what `check_progress`
1505
- * actually did.
1506
- *
1507
- * Order is load-bearing (unlike ponderators, which are order-independent): a
1508
- * grandparent's rollup consumes its children-parents' ALREADY rolled-up
1509
- * progress, so parents are visited deepest-first.
1510
- *
1511
- * Parity rules:
1512
- * - child with no/zero ponderator does not contribute;
1513
- * - a parent whose children contribute nothing keeps its progress frozen;
1514
- * - `progress` is kept a NUMBER but rounded per level to 2 decimals
1515
- * (`roundProgressPerLevel`) — legacy `check_progress` recurses on
1516
- * `toFixed(2)`, so each ancestor consumes its children's 2-decimal value
1517
- * (MORNING_QUEUE.md — JUN-02).
1518
- */
1519
-
1520
- declare function recomputeAllProgressRollup(adapter: ActivityWriter): void;
1521
-
1522
1361
  type StaticActivityDefaults = Omit<CoreActivity, 'id' | 'parentId' | 'name' | 'startDate' | 'endDate' | 'durationHours' | 'calendarId' | 'uniqueCorrelativeId'>;
1523
1362
  declare const NEW_ACTIVITY_DEFAULTS: Readonly<StaticActivityDefaults>;
1524
1363
 
@@ -1568,9 +1407,9 @@ declare function yieldToBrowser(): Promise<void>;
1568
1407
  * Does this dispatch recompute the critical path? The single authority for the
1569
1408
  * question, shared by the dispatch gate (`resolveDerivedPasses`) and the
1570
1409
  * bridge, which reads it to defer the CP out of the paint. Mirrors legacy: the
1571
- * CP re-derives on any scheduling change, and is left STALE for prop-only
1572
- * intents, the structural reparents (indent/outdent), and edits that touch only
1573
- * 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.
1574
1413
  */
1575
1414
  declare function willRunCriticalPath(action: DispatchAction): boolean;
1576
1415
 
@@ -1673,6 +1512,10 @@ declare const DEFAULT_HOURS_PER_DAY = 8;
1673
1512
 
1674
1513
  type DispatchActionKind = DispatchAction['kind'];
1675
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";
1676
1519
  readonly INLINE_EDIT: "inline-edit";
1677
1520
  readonly DATES_BATCH: "dates-batch";
1678
1521
  readonly BULK_EDIT: "bulk-edit";
@@ -1767,4 +1610,4 @@ declare const DISPATCH_TRACK_EVENT: {
1767
1610
  };
1768
1611
  type DispatchTrackEvent = (typeof DISPATCH_TRACK_EVENT)[keyof typeof DISPATCH_TRACK_EVENT];
1769
1612
 
1770
- 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 };
package/dist/index.d.ts 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,20 +1199,8 @@ 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;
@@ -1287,51 +1239,7 @@ declare class ScheduleCore {
1287
1239
  isCustomIdInUse(customId: string, currentCustomId?: string | null): boolean;
1288
1240
  getProjectWorkHours(): ProjectWorkHours | null;
1289
1241
  getModifiedLinks(): ReadonlyArray<Readonly<Link>>;
1290
- markLinksPersisted(): void;
1291
1242
  getModifiedActivities(): ReadonlyArray<Readonly<CoreActivity>>;
1292
- markActivitiesPersisted(): void;
1293
- /**
1294
- * Reconciles the backend identities returned by a successful schedule save.
1295
- *
1296
- * This is an application-boundary acknowledgement, not an editable schedule
1297
- * gesture: it does not autoschedule, recompute, emit an undo entry, or invent
1298
- * domain changes. Every assignment is validated before the first mutation so
1299
- * a malformed/partial response cannot leave the core half-reconciled. Once
1300
- * applied, the complete live graph becomes the new save-tracker baseline.
1301
- */
1302
- acknowledgePersistence(acknowledgement: PersistenceAcknowledgement): void;
1303
- applyBaselines(points: readonly BaselinePoint[]): ReadonlyArray<Readonly<CoreActivity>>;
1304
- /**
1305
- * Whole-tree ponderator recompute (zeroes, then redistributes 100 within
1306
- * each parent from its children's active baselines, weighted by `criterion`).
1307
- * Returns the activities whose `ponderator` changed, for a prop-only repaint.
1308
- * The base rollup (`applyBaselines`) depends on fresh ponderators, so callers
1309
- * run this before it on a criterion change or baseline save.
1310
- */
1311
- recomputeAllPonderators(criterion: ActivityCreter): ReadonlyArray<Readonly<CoreActivity>>;
1312
- /**
1313
- * Recompute the weighted-progress rollup of every parent, bottom-up.
1314
- * Returns the activities whose `progress` changed, for a prop-only repaint.
1315
- */
1316
- recomputeAllProgressRollup(): ReadonlyArray<Readonly<CoreActivity>>;
1317
- /**
1318
- * Set the project's status criterion (the Baseline↔Actual toggle) and
1319
- * recompute every activity's `status` against it. Stores the criterion so
1320
- * future dispatches keep deriving status with it. Returns the activities
1321
- * whose `status` changed, for a prop-only repaint.
1322
- */
1323
- setStatusCriteria(criteria: StatusCriteria): ReadonlyArray<Readonly<CoreActivity>>;
1324
- allocateActivityId(): ActivityId;
1325
- createActivity(input: Omit<Extract<DispatchAction, {
1326
- kind: 'activity-create';
1327
- }>, 'kind'>): {
1328
- ok: true;
1329
- activity: Readonly<CoreActivity>;
1330
- changes: ChangeSet;
1331
- } | {
1332
- ok: false;
1333
- reason: string;
1334
- };
1335
1243
  private _enqueue;
1336
1244
  dispatch(action: DispatchAction, options?: DispatchOptions): Promise<DispatchResult>;
1337
1245
  private _dispatchInner;
@@ -1350,11 +1258,22 @@ declare class ScheduleCore {
1350
1258
  private _recordScheduleMutation;
1351
1259
  private _startCriticalPathForCurrentRevision;
1352
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;
1353
1267
  undo(): Promise<ChangeSet | null>;
1354
1268
  redo(): Promise<ChangeSet | null>;
1355
- clearHistory(): void;
1356
1269
  canUndo(): boolean;
1357
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;
1358
1277
  undoDepth(): number;
1359
1278
  private _resyncCustomIdTrackerFromModel;
1360
1279
  destroy(): void;
@@ -1409,25 +1328,6 @@ declare function getActiveBaseline(activity: {
1409
1328
  baselinePoints?: readonly CoreBaselinePoint[];
1410
1329
  } | null | undefined): CoreBaselinePoint | null;
1411
1330
 
1412
- /**
1413
- * Surface this function needs from ScheduleState — kept structural so the
1414
- * function is unit-testable with a plain in-memory adapter.
1415
- */
1416
- interface BaselineApplyAdapter {
1417
- forEachActivity(visit: (a: Record<string, unknown>, id: string) => void): void;
1418
- getActivity(id: string): Record<string, unknown> | null;
1419
- setActivityField(id: string, field: string, value: unknown): void;
1420
- }
1421
- /**
1422
- * Overlay baseline points onto activities, matching `point.activityId`
1423
- * (backend DB id) against `activity.proplannerId`. Writes the full group of
1424
- * points (all versions) onto `baseline_points`; the active one is selected
1425
- * downstream by `getActiveBaseline`. Activities absent from the new set get
1426
- * `baseline_points` reset to `[]`. Returns the ids of activities whose
1427
- * `baseline_points` changed (so the caller can repaint just those).
1428
- */
1429
- declare function applyBaselinePoints(adapter: BaselineApplyAdapter, points: readonly BaselinePoint[], reporter: ScheduleCoreReporter): string[];
1430
-
1431
1331
  interface CalendarLike {
1432
1332
  calculateDuration(start: Date, end: Date): number;
1433
1333
  }
@@ -1458,67 +1358,6 @@ interface ExpectedProgressAdapter {
1458
1358
  */
1459
1359
  declare function computeExpectedProgress(rootIds: readonly string[], now: Date, adapter: ExpectedProgressAdapter, defaultBaseCalendarId?: string | null): string[];
1460
1360
 
1461
- /**
1462
- * Recompute the `ponderator` of EVERY activity in the schedule, in bulk.
1463
- *
1464
- * Faithful port of `goCalculatePonderators` in
1465
- * `react_client/src/views/ganttContainer/gantt/index.js:2008-2038` (the
1466
- * ponderator part — the progress rollup and save are separate concerns the
1467
- * caller wires). Two passes, mirroring the legacy:
1468
- * 1. Zero EVERY activity's ponderator.
1469
- * 2. For each parent (activity with children), redistribute 100 among its
1470
- * children via `recomputePonderatorsForParent` (baseline-only).
1471
- *
1472
- * IMPORTANT — this is the ONLY ponderator recompute the schedule performs.
1473
- * Verified against the legacy (2026-06-04): the masterplan does NOT recompute
1474
- * ponderators on add / delete / move / inline-edit. `goCalculatePonderators`
1475
- * runs ONLY on two events (see index.js:395/443/625):
1476
- * - the project criterion (`activity_creter`) changed
1477
- * (`sectorObject.update_ponderators_masterplan`), or
1478
- * - a baseline was created / saved.
1479
- * Both events otherwise reload the schedule. So there is intentionally NO
1480
- * per-mutation cascade and NO dispatch hook — adding one would diverge from
1481
- * production (a baselined child's siblings stay as the backend computed them
1482
- * until the next of those two events). See
1483
- * [[PONDERATOR_BASELINE_MIGRATION_PLAN]].
1484
- *
1485
- * Order-independent: a project child's weight derives from descendant
1486
- * BASELINES (`getDurationRecursively`), never from other ponderators, so the
1487
- * order parents are visited in does not affect the result.
1488
- */
1489
-
1490
- declare function recomputeAllPonderators(criterion: ActivityCreter, adapter: ActivityWriter): void;
1491
-
1492
- /**
1493
- * Recompute the weighted-progress rollup of EVERY parent in the schedule,
1494
- * bottom-up. `parent.progress = Σ(child.progress × child.ponderator / 100)`.
1495
- *
1496
- * Faithful port of legacy `check_progress` (editing-flow.js:154-197) — the
1497
- * second half of `goCalculatePonderators`. After a ponderator recompute
1498
- * (baseline create/save, see `recomputeAllPonderators`), parent progress is
1499
- * stale w.r.t. the new weights; this repropagates it.
1500
- *
1501
- * PROGRESS-ONLY by design: unlike the dispatch post-processor
1502
- * `recomputeParentFromChildren` (post-processors/parent-bounds.ts), this does NOT
1503
- * touch parent start/end/duration — only `progress`. That keeps it testable
1504
- * in isolation (no calendars/dates needed) and matches what `check_progress`
1505
- * actually did.
1506
- *
1507
- * Order is load-bearing (unlike ponderators, which are order-independent): a
1508
- * grandparent's rollup consumes its children-parents' ALREADY rolled-up
1509
- * progress, so parents are visited deepest-first.
1510
- *
1511
- * Parity rules:
1512
- * - child with no/zero ponderator does not contribute;
1513
- * - a parent whose children contribute nothing keeps its progress frozen;
1514
- * - `progress` is kept a NUMBER but rounded per level to 2 decimals
1515
- * (`roundProgressPerLevel`) — legacy `check_progress` recurses on
1516
- * `toFixed(2)`, so each ancestor consumes its children's 2-decimal value
1517
- * (MORNING_QUEUE.md — JUN-02).
1518
- */
1519
-
1520
- declare function recomputeAllProgressRollup(adapter: ActivityWriter): void;
1521
-
1522
1361
  type StaticActivityDefaults = Omit<CoreActivity, 'id' | 'parentId' | 'name' | 'startDate' | 'endDate' | 'durationHours' | 'calendarId' | 'uniqueCorrelativeId'>;
1523
1362
  declare const NEW_ACTIVITY_DEFAULTS: Readonly<StaticActivityDefaults>;
1524
1363
 
@@ -1568,9 +1407,9 @@ declare function yieldToBrowser(): Promise<void>;
1568
1407
  * Does this dispatch recompute the critical path? The single authority for the
1569
1408
  * question, shared by the dispatch gate (`resolveDerivedPasses`) and the
1570
1409
  * bridge, which reads it to defer the CP out of the paint. Mirrors legacy: the
1571
- * CP re-derives on any scheduling change, and is left STALE for prop-only
1572
- * intents, the structural reparents (indent/outdent), and edits that touch only
1573
- * 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.
1574
1413
  */
1575
1414
  declare function willRunCriticalPath(action: DispatchAction): boolean;
1576
1415
 
@@ -1673,6 +1512,10 @@ declare const DEFAULT_HOURS_PER_DAY = 8;
1673
1512
 
1674
1513
  type DispatchActionKind = DispatchAction['kind'];
1675
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";
1676
1519
  readonly INLINE_EDIT: "inline-edit";
1677
1520
  readonly DATES_BATCH: "dates-batch";
1678
1521
  readonly BULK_EDIT: "bulk-edit";
@@ -1767,4 +1610,4 @@ declare const DISPATCH_TRACK_EVENT: {
1767
1610
  };
1768
1611
  type DispatchTrackEvent = (typeof DISPATCH_TRACK_EVENT)[keyof typeof DISPATCH_TRACK_EVENT];
1769
1612
 
1770
- 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 };