@outbuild-company/schedule-core 1.1.2 → 1.3.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
@@ -1,79 +1,9 @@
1
- /**
2
- * Shared types for the custom AutoScheduler and its satellite packages
3
- * (recording, replay, shadow, discovery).
4
- *
5
- * This file is the single seam between all components. All scheduler code
6
- * depends ONLY on these types — never on the DHTMLX gantt object directly.
7
- */
8
- type ActivityId$1 = string;
9
- type LinkId$1 = string;
10
- /** Value constants for link types. Matches `gantt.config.links`. */
11
1
  declare const LINK_TYPE: {
12
2
  readonly FINISH_TO_START: "0";
13
3
  readonly START_TO_START: "1";
14
4
  readonly FINISH_TO_FINISH: "2";
15
5
  readonly START_TO_FINISH: "3";
16
6
  };
17
- interface LinkSnapshot {
18
- id: LinkId$1;
19
- source: ActivityId$1;
20
- target: ActivityId$1;
21
- type: LinkType;
22
- lag: number;
23
- /**
24
- * Precomputed lag components for a VIRTUAL summary-expanded link. When
25
- * present, the link calculator uses them verbatim instead of deriving the
26
- * components from leaf durations (the normal `normalize` switch).
27
- *
28
- * These exist so an FF/SF link INTO a summary can carry the SUMMARY's
29
- * duration (`_targetLag = -summary.duration`) and the per-leaf offset from
30
- * the summary's start (`_trueLag = userLag + off_tgt(leaf)`), matching legacy
31
- * `_getImplicitLinks`/`_convertToFinishToStartLink` under
32
- * `auto_scheduling_move_projects`. Computed ONCE at expansion time
33
- * (`parent-link-expansion.ts`), never per scheduling tick.
34
- *
35
- * All three must be set together (or none): a half-populated link would mix
36
- * precomputed and derived components. Only the FF/SF-into-summary path emits
37
- * them; every other link leaves them undefined and hits the fast path.
38
- */
39
- _sourceLag?: number;
40
- _targetLag?: number;
41
- _trueLag?: number;
42
- ganttId?: number;
43
- sectorId?: number;
44
- /** BE row id (distinct from the schedule-level `id`). */
45
- proplannerId?: number;
46
- }
47
-
48
- /**
49
- * Constraint Rules — single source of truth.
50
- *
51
- * This module owns the definition of constraint types and the rules that
52
- * govern them. Both the autoscheduler and the manual scheduling pipelines
53
- * (column edits, drag, bulk) consume the same primitives from here.
54
- *
55
- * Two surfaces:
56
- *
57
- * (a) Manual scheduling — pipelines call `checkConstraintViolation()` after
58
- * a user types a new value, to decide whether to emit a warning.
59
- * Surface: scalar Date inputs, no adapter needed.
60
- *
61
- * (b) Autoscheduler — `computeConstraintBounds()` derives the four date
62
- * boundaries (earliest/latest × start/end) an activity's constraint
63
- * imposes. `isStartWithinBounds()` and `isEndWithinBounds()` are used
64
- * by `limitPlanDates()` to decide whether a candidate date (from a
65
- * link) respects the constraint.
66
- *
67
- * Domain invariant the autoscheduler must respect:
68
- * The autoscheduler never moves an activity to a date that violates that
69
- * activity's own constraint. If a cascade via a link would push the
70
- * activity outside its constraint bounds, the activity stays put.
71
- *
72
- * Manual scheduling does NOT enforce this — a user is allowed to type a
73
- * value that violates their own constraint. The pipeline applies the edit
74
- * and emits a warning; the UI decides what to do with the warning (typically
75
- * shows a "constraint violation detected" modal and offers to revert).
76
- */
77
7
 
78
8
  type ConstraintType = 'asap' | 'alap' | 'snet' | 'snlt' | 'fnet' | 'fnlt' | 'mso' | 'mfo';
79
9
  declare const CONSTRAINT_TYPE: {
@@ -87,104 +17,44 @@ declare const CONSTRAINT_TYPE: {
87
17
  readonly MFO: "mfo";
88
18
  };
89
19
 
90
- /**
91
- * Public internal model types — the normalized form the core works with
92
- * after parsing the backend payload.
93
- *
94
- * Field naming follows snake_case for entity fields (matches backend +
95
- * DHTMLX convention) and camelCase for everything else, per Q14 in
96
- * SCHEDULE_CORE_API.md.
97
- */
98
20
  type ActivityId = string;
99
21
  type LinkId = string;
100
22
  type CalendarId = string;
101
23
  type ActivityType = 'project' | 'task' | 'milestone';
102
24
 
103
- /** DHTMLX link kinds: 0 = FS, 1 = SS, 2 = FF, 3 = SF. */
104
25
  type LinkType = '0' | '1' | '2' | '3';
105
- /** Direction for closest-work-time snapping. */
106
26
  type WorkTimeDirection = 'future' | 'past';
107
27
  declare const WORK_TIME_DIRECTION: {
108
28
  readonly FUTURE: "future";
109
29
  readonly PAST: "past";
110
30
  };
111
- /** Calendar query granularity (the engine boundary adds 'minute'/'any'). */
112
31
  type CalendarUnit = 'hour' | 'day';
113
32
  declare const CALENDAR_UNIT: {
114
33
  readonly HOUR: "hour";
115
34
  readonly DAY: "day";
116
35
  };
117
- /**
118
- * Project-level criterion used to redistribute `ponderator` (weight) among
119
- * sibling activities. Backend stores it lowercase on `projects.activity_creter`
120
- * (`'duration' | 'cost' | 'hh'`, default `'duration'`); the core normalizes
121
- * to uppercase. See `getActiveBaseline` / `recomputePonderatorsForParent`.
122
- */
123
36
  type ActivityCreter = 'DURATION' | 'COST' | 'HH';
124
- /** Criterion for the `status` column: compare progress against baseline or live expected. */
125
37
  type StatusCriteria = 'Baseline' | 'Actual';
126
- /**
127
- * A versioned container of baseline points for a sector
128
- * (backend `sectorbaselineversion` model). Exactly one version per sector
129
- * has `active: true` — that is the one the ponderator computation and the
130
- * baseline columns read.
131
- */
132
38
  interface BaselineVersion {
133
39
  id?: number;
134
40
  name?: string | null;
135
- /** Marks the live baseline. Exactly one active version per sector. */
136
41
  active?: boolean;
137
42
  visible?: boolean;
138
- /** Backend extras passed through opaquely. */
139
43
  [key: string]: unknown;
140
44
  }
141
- /**
142
- * A single baseline data point for an activity, as stored by the backend
143
- * (`sectorbaselinepoint` model) and carried verbatim through the core.
144
- * Snapshots the activity's planned start/end/duration/cost/work at the
145
- * moment a baseline version was saved. The schedule reads the ACTIVE point
146
- * (see `getActiveBaseline`) to derive `ponderator` (baseline-only, no
147
- * fallback — see memory `project_ponderator_baseline_fix`).
148
- *
149
- * Casing note: the frontend baseline columns (`startBaseCol` …
150
- * `durationBaseCol`) read these fields directly off the activity, so the
151
- * core preserves the backend snake_case exactly (passthrough, not parsed).
152
- *
153
- * Unit trap: `duration` is in DAYS (backend unit), unlike the activity's
154
- * `duration` which the core models in working hours — convert with the
155
- * sector's hours-per-day before arithmetic (legacy `transformDaysToHours`).
156
- */
157
45
  interface BaselinePoint {
158
- /** Planned start, backend date string `"YYYY/MM/DD H:MM"` or ISO. */
159
46
  start_date: string;
160
- /** Planned end (inclusive). */
161
47
  end_date: string;
162
- /** Planned duration in DAYS (backend unit). */
163
48
  duration: number;
164
- /** Planned cost. May arrive as a numeric string. */
165
49
  cost: number | string;
166
- /** Planned work hours (HH). May arrive as a numeric string. */
167
50
  hh_work: number | string;
168
- /** The version this point belongs to; `.active` selects the live one. */
169
51
  sectorbaselineversion?: BaselineVersion;
170
52
  sectorbaselineversionId?: number;
171
53
  baseCalendarId?: number;
172
54
  hoursPerDay?: number;
173
55
  hoursPerWeek?: number;
174
- /** Backend extras passed through opaquely. */
175
56
  [key: string]: unknown;
176
57
  }
177
- /**
178
- * Pure-domain activity model produced by `parseFromBackend`. Snake_case
179
- * matches the backend convention. No index signature — every field is
180
- * explicit. New backend fields must be added here and preserved by
181
- * `normalize-activity.ts`.
182
- */
183
- /**
184
- * A pending SIR freezing an activity, reduced to the only field the core
185
- * needs: its id (equal to the backend SIR id and the front's Redux item id).
186
- * The schedule load already filters to pending, so presence ⇒ pending.
187
- */
188
58
  interface PendingRequest {
189
59
  id: string;
190
60
  }
@@ -218,7 +88,6 @@ interface CoreBaselinePoint {
218
88
  workHours: number;
219
89
  versionId: number | null;
220
90
  isActiveVersion: boolean;
221
- /** Whether this version is the one selected for visual comparison. */
222
91
  isVisibleVersion: boolean;
223
92
  baseCalendarId: CalendarId | null;
224
93
  hoursPerDay: number | null;
@@ -269,17 +138,6 @@ interface CoreActivity {
269
138
  expectedProgressBaseline: number | null;
270
139
  status: ActivityStatus | null;
271
140
  criticalPath: CriticalPathResult | null;
272
- /** Internal reversible leaf→project promotion state; never serialized. */
273
- promotionRestore: {
274
- type: 'task' | 'milestone';
275
- startDate: Date;
276
- endDate: Date;
277
- durationHours: number;
278
- expectedProgressBaseline: number | null;
279
- constraintType: ConstraintType;
280
- constraintDate: Date | null;
281
- progress: number;
282
- } | null;
283
141
  }
284
142
  interface Link {
285
143
  id: LinkId;
@@ -289,79 +147,25 @@ interface Link {
289
147
  lag: number;
290
148
  ganttId?: number;
291
149
  sectorId?: number;
292
- /** BE row id (distinct from the schedule-level `id`/`unique_id`). */
293
150
  proplannerId?: number;
294
151
  }
295
152
  interface CalendarWorktime {
296
- /**
297
- * Per-weekday working hours, length 7 — index 0 = Sunday.
298
- *
299
- * - `false` → non-working day
300
- * - `string[]` → working day with these `"HH:MM-HH:MM"` windows
301
- * (supports per-day variation, e.g. a short Friday,
302
- * and multi-shift split days like `["8-12","13-17"]`)
303
- *
304
- * Single source of truth: this is exactly what `parseFromBackend` emits. A
305
- * usable working day carries its resolved windows; `[]` is reserved for a
306
- * catalog calendar that declares the day as working but provides no usable
307
- * shift anywhere. Those catalog-only calendars remain selectable/persistable
308
- * but are deliberately not registered as arithmetic calendars. The vendored
309
- * work-calendar engine natively also accepts a boolean `true` per day (DHTMLX
310
- * contract), but that form never appears in the domain `Calendar`; it is
311
- * reserved for the engine-facing internals (`SnapshotWorktime`,
312
- * synthetic replay fixtures).
313
- *
314
- * The work-calendar engine consumes each weekday independently, so
315
- * different days can carry different windows.
316
- */
317
153
  days: Array<false | string[]>;
318
- /**
319
- * Default working-hour windows (`"HH:MM-HH:MM"`). Used as the engine's
320
- * global fallback. `parseFromBackend` sets it to the first usable working
321
- * day's windows, or `[]` when the calendar has no usable shift.
322
- */
323
154
  hours: string[];
324
- /**
325
- * Per-date exceptions: holidays (non-working) and date-level shift
326
- * overrides. Keys are `Date.UTC(year, month, day)` (UTC midnight epoch
327
- * ms) stringified — this matches the lookup the work-calendar engine
328
- * performs internally at `calendar.dates[dateValue]`.
329
- *
330
- * Value semantics:
331
- * - `false` → the day is non-working
332
- * - `string[]` → use these `"HH:MM-HH:MM"` windows instead
333
- * of the default `hours`
334
- *
335
- * Source: `BackendCalendarInput.exceptiondays[]` after the adapter expands
336
- * `from_date`/`to_date` ranges into individual UTC days.
337
- */
338
155
  dates?: Record<string, false | string[]>;
339
- /** Optional per-week overrides for advanced calendars. */
340
156
  customWeeks?: Record<string, unknown>;
341
157
  }
342
158
  interface Calendar {
343
159
  id: CalendarId;
344
- /** Display name. Used by the DHTMLX calendar-selector column. */
345
160
  name: string;
346
161
  worktime: CalendarWorktime;
347
162
  is_default: boolean;
348
- /**
349
- * True for "base" calendars (baseline comparison). The backend ships
350
- * these with an `id` suffixed `-base` and `baseDefault` instead of
351
- * `is_default`. The bridge uses this to set `gantt.defaultBaseCalendar`
352
- * and to keep base calendars out of the selector dropdown.
353
- */
354
163
  baseDefault?: boolean;
355
- /**
356
- * Date-keyed map of exceptions (holidays, working overrides).
357
- * Keys are `YYYY-MM-DD` UTC.
358
- */
359
164
  exceptions?: Record<string, {
360
165
  working: boolean;
361
166
  hours?: string[];
362
167
  }>;
363
168
  }
364
- /** Sector metadata the core respects from the backend. */
365
169
  interface SectorMetadata {
366
170
  id: string;
367
171
  name: string;
@@ -370,48 +174,12 @@ interface SectorMetadata {
370
174
  dateFormat: string;
371
175
  projectId: number;
372
176
  companyId: number;
373
- /**
374
- * Project's `custom_id_prefix` (Outbuild project setting). Used by the
375
- * CustomIdTracker to generate ids for new activities. Falls back to
376
- * `'A'` if not provided.
377
- */
378
177
  customIdPrefix?: string;
379
- /**
380
- * Project's `custom_id_increment` (Outbuild project setting), as a
381
- * string so leading zeros are preserved. Used by the CustomIdTracker
382
- * to choose the next suffix step. Falls back to `'10'` if not provided.
383
- */
384
178
  customIdIncrement?: string;
385
- /**
386
- * Legacy Primavera-import flag. When true, the core recomputes each
387
- * activity's `duration` from its start/end against its calendar at load
388
- * (Primavera stores end-driven durations that may not match the
389
- * calendar). Mirrors the legacy `fixPrimaveraDurations`. See CALENDARS.md.
390
- */
391
179
  updateDurationForPrimaveraEndDate?: boolean;
392
- /**
393
- * Project's `activity_creter` — criterion for ponderator redistribution
394
- * (`'DURATION' | 'COST' | 'HH'`). Lives at the project level; arrives via
395
- * the sector payload's pass-through bucket. `parseSector` always sets it,
396
- * defaulting to `'DURATION'` (the backend default) when absent/unknown.
397
- */
398
180
  activityCreter?: ActivityCreter;
399
- /**
400
- * Project's status criterion for the `status` column — `'Baseline'` compares
401
- * `progress` against `expected_progress_base`, `'Actual'` against
402
- * `expected_progress`. Mutable at runtime via `ScheduleCore.setStatusCriteria`
403
- * (the Baseline↔Actual toggle). Not a backend field (client-only session
404
- * setting); a complete sector always carries it — `parseSector` sets the
405
- * default and `resolveStatusCriteria` normalizes the client override at init.
406
- * See `docs/domain/columns/COLUMN_status.md`.
407
- */
408
181
  statusCriteria: StatusCriteria;
409
182
  }
410
- /**
411
- * A single entity mutation record emitted by a dispatch ChangeSet.
412
- * Layer-neutral: contains no dispatch-specific dependencies and is used
413
- * both by `dispatch/` and by `internal/tracking/`.
414
- */
415
183
  interface EntityChange<T> {
416
184
  id: string;
417
185
  kind: 'created' | 'updated' | 'deleted';
@@ -465,45 +233,33 @@ declare const COLUMN: {
465
233
  };
466
234
  type ColumnName = (typeof COLUMN)[keyof typeof COLUMN];
467
235
 
468
- /**
469
- * Task creation pipeline — types.
470
- *
471
- * Ports the pure parts of `src/assets/js/custom_actions/createTask/` into a
472
- * functional pipeline that replay tests (and eventually prod) can invoke
473
- * against an adapter instead of the live gantt.
474
- */
475
-
476
- /**
477
- * Closed, canonical contract for create/paste overrides: camelCase, working
478
- * hours, limited to the fields a caller may seed on a new activity. Identity
479
- * and structure (`id`/`parentId`/`correlativeId`/`uniqueCorrelativeId`),
480
- * derived/CP/baseline outputs and view-state are NOT overridable. PP2 adapts
481
- * its own vocabulary to this contract in step 9.
482
- */
483
236
  declare const CREATION_OVERRIDE_FIELD_LIST: readonly ["name", "description", "type", "startDate", "durationHours", "constraintType", "constraintDate", "calendarId", "assignedDefaultCalendar", "autoScheduling", "progress", "cost", "usedCost", "realCost", "workHours", "realWorkHours", "ponderator", "hasCustomPonderator", "customId", "subcontractId", "responsableIds", "tagIds", "isLookahead"];
484
237
  type CreationOverrideField = (typeof CREATION_OVERRIDE_FIELD_LIST)[number];
485
238
  type CreationOverrides = Partial<Pick<CoreActivity, CreationOverrideField>>;
486
239
 
487
- /**
488
- * Public dispatch types see Q8/Q9/Q10 in SCHEDULE_CORE_API.md.
489
- *
490
- * Supported kinds:
491
- * - `inline-edit` — edit a single column on an activity.
492
- * - `link-create` — add a link between two activities.
493
- * - `link-update` — change the lag and/or type of an existing link.
494
- * - `link-delete` — remove a link by id.
495
- * - `activity-create` — create a new activity (root, child, or sibling).
496
- * - `activity-delete` cascade-delete activities + incident links.
497
- * - `activity-move` — reorder among siblings or move to a different parent.
498
- * - `activity-indent` multi-select indent (children of previous non-selected sibling).
499
- * - `activity-outdent` — multi-select outdent (move to grandparent level).
500
- * - `activity-set-progress` — complete/uncomplete (0/100) with cascade.
501
- * - `selection-toggle` / `selection-replace` — checkbox selection (prop-only).
502
- * - `activity-paste` — bulk paste (N activities + M links, one atomic ChangeSet).
503
- * - `dates-batch` — K date edits as one unit of work (multi-drag).
504
- * - `bulk-edit` — K edits on any pipeline column as one unit of work (bulk editors).
505
- * - `links-batch` — K link create/update/delete as one atomic unit (mass link/unlink buttons).
506
- */
240
+ type StringOperator = 'includes' | 'notIncludes' | 'is' | 'isNot';
241
+ type NumberOperator = 'equals' | 'notEquals' | 'greaterThan' | 'lessThan' | 'greaterOrEqual' | 'lessOrEqual';
242
+ type DateOperator = 'after' | 'before';
243
+ type MembershipOperator = 'someOf' | 'notSomeOf';
244
+ type FilterOperator = StringOperator | NumberOperator | DateOperator | MembershipOperator;
245
+ type FilterField = 'name' | 'description' | 'customId' | 'correlativeId' | 'uniqueCorrelativeId' | 'progress' | 'durationDays' | 'calendarDuration' | 'cost' | 'usedCost' | 'realCost' | 'workHours' | 'realWorkHours' | 'ponderator' | 'freeSlackDays' | 'totalSlackDays' | 'expectedProgressBaseline' | 'baselineDurationDays' | 'baselineCost' | 'baselineWorkHours' | 'startDate' | 'endDate' | 'constraintDate' | 'baselineStartDate' | 'baselineEndDate' | 'earlyStart' | 'earlyFinish' | 'lateStart' | 'lateFinish' | 'responsableIds' | 'tagIds' | 'status' | 'constraintType' | 'calendarId' | 'subcontractId' | 'isCritical';
246
+ type FilterScalarValue = string | number | Date;
247
+ type FilterCriterionValue = FilterScalarValue | ReadonlyArray<string | number>;
248
+ interface FilterCriterion {
249
+ readonly field: FilterField;
250
+ readonly operator: FilterOperator;
251
+ readonly value: FilterCriterionValue;
252
+ }
253
+ interface FilterDateRange {
254
+ readonly start: Date;
255
+ readonly end: Date;
256
+ }
257
+ interface FilterState {
258
+ readonly criteria: ReadonlyArray<FilterCriterion>;
259
+ readonly logic: 'and' | 'or';
260
+ readonly dateRange?: FilterDateRange;
261
+ }
262
+
507
263
  type LinksBatchOperation = {
508
264
  kind: 'create';
509
265
  source: ActivityId;
@@ -544,13 +300,6 @@ type DispatchAction = {
544
300
  column: string;
545
301
  newValue: unknown;
546
302
  } | {
547
- /**
548
- * K ediciones de fecha como UNA unidad de trabajo (multi-drag): cada
549
- * edit corre su pipeline individual (verdict por barra), pero el
550
- * post-mutation (autoscheduler → bounds → CP) corre UNA sola vez y
551
- * sale UN ChangeSet. Patrón hermano de `activity-paste` /
552
- * `dispatchInlineEditLinks`. Track onAfterTaskDrag F2.2.
553
- */
554
303
  kind: 'dates-batch';
555
304
  edits: ReadonlyArray<{
556
305
  activityId: ActivityId;
@@ -558,21 +307,6 @@ type DispatchAction = {
558
307
  newValue: unknown;
559
308
  }>;
560
309
  } | {
561
- /**
562
- * K edits on ANY pipeline column as ONE unit of work — the
563
- * generalization of `dates-batch` to the 16 registry columns
564
- * (schedule bulk editors / modals; PLAN_convergencia_wiring §4.1).
565
- * Each edit resolves its pipeline via `findPipelineForColumn` and
566
- * runs phases 1-4 with an individual verdict (a rejected edit does
567
- * NOT abort the others — paste semantics). A column without a
568
- * pipeline (e.g. the link columns `custom_predecessors` /
569
- * `custom_sucessors`, which mutate the link graph and dispatch as
570
- * link intents) rejects ONLY that edit. The post-mutation pass
571
- * (autoscheduler → parent bounds → CP) runs ONCE at the end and a
572
- * single ChangeSet comes out = ONE undo step (never coalesces).
573
- * Supports both shapes: K activities × same column, and 1 activity
574
- * × several columns (atomic multi-field edit).
575
- */
576
310
  kind: 'bulk-edit';
577
311
  edits: ReadonlyArray<{
578
312
  activityId: ActivityId;
@@ -585,16 +319,12 @@ type DispatchAction = {
585
319
  source: ActivityId;
586
320
  target: ActivityId;
587
321
  type: LinkType;
588
- /** Lag in DAYS (public boundary unit; converted to working hours inside). */
589
322
  lag: number;
590
- /** Optional explicit id (tests pass deterministic ids). */
591
323
  linkId?: LinkId;
592
324
  } | {
593
325
  kind: 'link-update';
594
326
  linkId: LinkId;
595
- /** New type. Omit to preserve. */
596
327
  type?: LinkType;
597
- /** New lag in DAYS (public boundary unit). Omit to preserve. */
598
328
  lag?: number;
599
329
  } | {
600
330
  kind: 'link-delete';
@@ -604,254 +334,84 @@ type DispatchAction = {
604
334
  operations: ReadonlyArray<LinksBatchOperation>;
605
335
  } | {
606
336
  kind: 'activity-create';
607
- /** Where to put the new activity. Use `'0'` for root. */
608
337
  parentId: ActivityId | '0';
609
- /**
610
- * Optional — insert the new activity immediately AFTER this sibling.
611
- * If omitted (and `beforeSiblingId` also omitted), the new activity
612
- * is appended as the last child of `parentId`. The sibling must
613
- * currently be a child of `parentId`, otherwise the dispatch is
614
- * rejected. Mutually exclusive with `beforeSiblingId`.
615
- */
616
338
  afterSiblingId?: ActivityId | undefined;
617
- /**
618
- * Optional — insert the new activity immediately BEFORE this sibling.
619
- * Use case: "insert as first child" (`beforeSiblingId = currentFirstChild`)
620
- * or "insert at position N" (`beforeSiblingId = children[N]`).
621
- * The sibling must currently be a child of `parentId`, otherwise the
622
- * dispatch is rejected. Mutually exclusive with `afterSiblingId`.
623
- */
624
339
  beforeSiblingId?: ActivityId | undefined;
625
- /**
626
- * Canonical field overrides applied on top of the creation defaults —
627
- * camelCase, working hours, limited to creation-writable fields
628
- * (`CreationOverrides`). Identity/structure and derived outputs are not
629
- * overridable.
630
- */
631
340
  overrides?: CreationOverrides;
632
- /**
633
- * Deterministic activity id — used by tests and by replay flows
634
- * (paste, undo) that need to assign a specific id. If omitted, the
635
- * core's internal id generator allocates one.
636
- */
637
341
  activityId?: ActivityId;
638
- /**
639
- * Where this create originated from — passed through to the
640
- * `schedule_activity_creation` Amplitude event. Free-form string
641
- * (matches legacy `INSERT_EVENT_SOURCES`).
642
- */
643
342
  eventSource?: string | undefined;
644
343
  } | {
645
344
  kind: 'activity-delete';
646
- /**
647
- * IDs to delete. Each id triggers a cascade delete of its entire
648
- * subtree. Links incident to any deleted activity are removed too.
649
- * Operation is atomic — if any id is missing, the whole dispatch is
650
- * rejected before mutating anything.
651
- */
652
345
  activityIds: ReadonlyArray<ActivityId>;
653
346
  eventSource?: string;
654
347
  } | {
655
348
  kind: 'activity-move';
656
- /**
657
- * The activity to move. Single-id only — for batch reorder use
658
- * multiple dispatches (each one observes the post-previous state).
659
- */
660
349
  activityId: ActivityId;
661
- /** Destination parent. Same as current parent = reorder within. */
662
350
  parentId: ActivityId | '0';
663
- /**
664
- * Optional — position the moved activity immediately below this
665
- * sibling. If omitted, appended as the last child of `parentId`.
666
- * The sibling must currently be a child of `parentId`; otherwise
667
- * the dispatch is rejected. Mutually exclusive with `beforeSiblingId`.
668
- */
669
351
  afterSiblingId?: ActivityId;
670
352
  beforeSiblingId?: ActivityId;
671
353
  eventSource?: string;
672
354
  } | {
673
355
  kind: 'activity-indent';
674
- /**
675
- * IDs to indent. Multi-select honors chain semantics: each id is
676
- * moved under its closest previous sibling that is NOT in the
677
- * selected set, so consecutive selected siblings all land under the
678
- * same anchor (matches legacy `findPreviousNonSelectedSibling`).
679
- */
680
356
  activityIds: ReadonlyArray<ActivityId>;
681
357
  eventSource?: string;
682
358
  } | {
683
359
  kind: 'activity-outdent';
684
- /**
685
- * IDs to outdent. Multi-select preserves relative order among the
686
- * selected siblings via initial-index capture (matches legacy
687
- * `initialFirstSiblings` + `initialTaskIndexes` pre-loop setup).
688
- */
689
360
  activityIds: ReadonlyArray<ActivityId>;
690
361
  eventSource?: string;
691
362
  } | {
692
- /**
693
- * Set progress to 0 or 100 with recursive cascade semantics.
694
- *
695
- * Triggered by the schedule's "complete" / "uncomplete" buttons.
696
- * **Distinct intent from `inline-edit`** — bypasses the
697
- * `isSummaryActivity` gate of `progressPipeline.canEdit` because
698
- * the user is explicitly invoking the recursive operation
699
- * ("complete this parent and all its descendants"), not editing
700
- * a single cell.
701
- *
702
- * Inline edits on the progress column still use `inline-edit` with
703
- * full `canEdit`/`validate` gating (which blocks summary activities
704
- * by design). See [[complete activity business rules]] R8 and the
705
- * bifurcation rationale.
706
- *
707
- * Internally reuses the `progressPipeline.transform` so cascade,
708
- * rollup, tracking and visualization are identical to inline-edit
709
- * with newValue=0 or newValue=100.
710
- */
711
363
  kind: 'activity-set-progress';
712
364
  activityId: ActivityId;
713
- /** Strictly 0 (uncomplete) or 100 (complete). */
714
365
  newValue: 0 | 100;
715
366
  eventSource?: string;
716
367
  } | {
717
- /**
718
- * Checkbox selection toggle (2026-06-06). The core is the
719
- * authority of the check state (`checked` / `visibleChecked` /
720
- * `mustApplyVisibleChecked` written as a single bit): it runs the
721
- * full propagation (descendants cascade, frozen partition with
722
- * parent blocking, unified upward pass — see
723
- * `internal/selection/compute-selection-update.ts`) and emits a
724
- * prop-only ChangeSet the bridge applies in bulk to DHTMLX
725
- * (silent store write + viewport repaint). No autoscheduler, no
726
- * critical path.
727
- */
728
368
  kind: 'selection-toggle';
729
369
  activityId: ActivityId;
730
- /** Checkbox state AFTER the user click. */
731
370
  isChecked: boolean;
732
- /**
733
- * Ids frozen by pending SIRs, computed by the caller from the
734
- * runtime (DHTMLX task props — the core's own `pendingRequests`
735
- * is load-time-stale until TODO #10). When omitted, the core
736
- * falls back to its own snapshots.
737
- */
738
371
  frozenIds?: ReadonlyArray<ActivityId>;
739
- /**
740
- * Enable the upward auto-check of ancestors when all their
741
- * checkable children become selected. Mirrors the (inverted)
742
- * CHECKBOX_NOT_AUTOMATIC_PARENT_SELECTION flag. Default: false.
743
- */
744
372
  autoCheckParents?: boolean;
745
373
  eventSource?: string;
746
374
  } | {
747
- /**
748
- * Replace the whole selection with an exact id set — no
749
- * propagation. Used by clear / block-selection / external sync.
750
- * Unknown ids are ignored. Same prop-only ChangeSet contract as
751
- * `selection-toggle`.
752
- */
753
375
  kind: 'selection-replace';
754
376
  activityIds: ReadonlyArray<ActivityId>;
755
377
  eventSource?: string;
756
378
  } | {
757
- /**
758
- * View-model visibility set (Fase 1, 2026-07-04). ABSOLUTE set: the
759
- * UI sends the COMPLETE list of ids visible after its filters run;
760
- * the core diffs against the current `visible` bits (an id changes
761
- * when `(visible !== false) !== willBeVisible`) and emits a
762
- * prop-only ChangeSet carrying ONLY the `visible` field for the
763
- * activities that changed. Unknown ids are ignored. Same prop-only
764
- * contract as `selection-replace`: no autoscheduler, no critical
765
- * path, no undo step. Idempotent.
766
- */
767
379
  kind: 'visibility-set';
768
380
  visibleIds: ReadonlyArray<ActivityId>;
769
381
  eventSource?: string;
770
382
  } | {
771
- /**
772
- * Sync the pending-SIR set for one activity into the core
773
- * (2026-06-08). ABSOLUTE set: the bridge sends the activity's current
774
- * pending requests whenever a SIR is created/resolved in the front,
775
- * so the core's freeze bit stays fresh (closes TODO #10) and is the
776
- * standalone authority for selection gating + auto-reject. Prop-only
777
- * ChangeSet; no autoscheduler, no CP. Idempotent.
778
- */
383
+ kind: 'filter-set';
384
+ criteria: ReadonlyArray<FilterCriterion>;
385
+ logic: 'and' | 'or';
386
+ dateRange?: FilterDateRange;
387
+ eventSource?: string;
388
+ } | {
779
389
  kind: 'sir-sync';
780
390
  activityId: ActivityId;
781
391
  pendingRequests: ReadonlyArray<PendingRequest>;
782
392
  eventSource?: string;
783
393
  } | {
784
- /**
785
- * Synchronize the schedule-side flags owned by Lookahead task
786
- * creation/deletion. The Lookahead records remain at the application
787
- * boundary; the core owns these flags because they affect schedule
788
- * editing rules and the public activity view.
789
- */
790
394
  kind: 'activity-lookahead-sync';
791
395
  activityIds: ReadonlyArray<ActivityId>;
792
396
  isLookahead: boolean;
793
397
  hasLookaheadTasks: boolean;
794
398
  eventSource?: string;
795
399
  } | {
796
- /**
797
- * Bulk paste (2026-06-08). Creates N activities (preserving the
798
- * copied subtree hierarchy) plus M internal links in a SINGLE
799
- * atomic ChangeSet — the future unit of undo. Replaces the
800
- * DHTMLX-side paste that bypassed the core via `gantt.isPasting`.
801
- *
802
- * The caller (bridge) supplies, per activity, the field
803
- * `overrides` already shaped by the paste prepare step (resets +
804
- * validated catalogs). The core allocates ids/uids, re-hangs the
805
- * hierarchy via `originalId → newId`, promotes parents, runs the
806
- * hh/cost cascade + correlative sweep + autoscheduler ONCE at the
807
- * end of the batch (not per activity).
808
- *
809
- * See [[COPY_PASTE_CORE_MIGRATION_DESIGN]].
810
- */
811
400
  kind: 'activity-paste';
812
- /**
813
- * Where the pasted roots land. `{ parentId, index }` pastes INSIDE
814
- * the reference (index 0 = first child). `{ afterSiblingId }`
815
- * pastes as a sibling immediately below the reference; the parent
816
- * is derived from the sibling.
817
- */
818
401
  destination: {
819
402
  parentId: ActivityId | '0';
820
403
  index: number;
821
404
  } | {
822
405
  afterSiblingId: ActivityId;
823
406
  };
824
- /**
825
- * The activity the paste is anchored to (the single selected row).
826
- * Used as the custom_id reference for pasted children. Replaces
827
- * the legacy `gantt.pasteReferenceActivity` instance flag.
828
- */
829
407
  referenceActivityId: ActivityId;
830
- /**
831
- * Activities to create, in tree order (parents before children).
832
- * `originalId` / `originalParentId` are the ids from the copied
833
- * payload; the core remaps them to freshly allocated ids and uses
834
- * the remap to re-hang the hierarchy and the links. A pasted activity
835
- * may carry its visible baseline snapshot, but it is not enrolled in an
836
- * already-created historical baseline.
837
- */
838
408
  activities: ReadonlyArray<PastedActivityInput>;
839
- /**
840
- * Internal links to recreate. `source`/`target` reference the
841
- * ORIGINAL (copied) ids; links with an endpoint outside the
842
- * pasted set are dropped (dangling).
843
- */
844
409
  links: ReadonlyArray<PastedLinkInput>;
845
410
  eventSource?: string;
846
411
  };
847
412
  interface PastedActivityInput {
848
413
  readonly originalId: ActivityId;
849
414
  readonly originalParentId: ActivityId | '0';
850
- /**
851
- * Optional explicit destination id. Production normally omits it and lets
852
- * the adapter allocate; recorder replay supplies MAIN's observed id so the
853
- * whole-tree oracle can compare identity without an invented id remap.
854
- */
855
415
  readonly activityId?: ActivityId;
856
416
  readonly overrides: CreationOverrides;
857
417
  readonly baselineSnapshot?: ActivityBaselineSnapshot | null;
@@ -860,49 +420,20 @@ interface PastedLinkInput {
860
420
  readonly source: ActivityId;
861
421
  readonly target: ActivityId;
862
422
  readonly type: LinkType;
863
- /** Lag in DAYS (public boundary unit; converted to working hours inside). */
864
423
  readonly lag: number;
865
424
  readonly linkId?: LinkId;
866
425
  }
867
426
  interface DispatchOptions {
868
- /** Skip the autoscheduler pass after the mutation. Default: false. */
869
427
  skipAutoSchedule?: boolean;
870
- /** Skip the CP recompute after the mutation. Default: false. */
871
428
  skipCriticalPath?: boolean;
872
- /**
873
- * Unit of the INPUT `newValue` for unit-bearing inline-edit columns (today:
874
- * duration). Default 'days' (public contract; card + tests send days). The
875
- * DHTMLX grid editor produces the store unit (HOURS via formatter.parse), so
876
- * the bridge passes 'hours' for those edits → the pipeline skips the days→hours
877
- * conversion instead of double-converting.
878
- */
879
429
  inputUnit?: 'days' | 'hours';
880
430
  }
881
431
  type DispatchResult = {
882
432
  ok: true;
883
433
  changes: ChangeSet;
884
- /**
885
- * Solo los intents batch (`dates-batch` / `bulk-edit`): resultado
886
- * POR EDIT, en el orden de `edits`. Un edit rechazado no aborta a
887
- * los demás (semántica paste); el bridge re-proyecta solo los
888
- * rechazados.
889
- */
890
434
  verdicts?: ReadonlyArray<DatesBatchVerdict>;
891
- /**
892
- * INTERNAL (undo / B4). Full pre-mutation snapshot of every
893
- * deleted/structurally-removed activity row, keyed by STRING id. The
894
- * ChangeSet emits deleted rows as `{ kind:'deleted', after:null }` with no
895
- * `before`, so resurrection on undo needs this. Populated by structural
896
- * handlers (delete/move/paste); stripped before the result reaches the
897
- * bridge in the Wave-3 integration.
898
- */
899
- __beforeSnap?: ReadonlyMap<string, CoreActivity>;
900
- /**
901
- * INTERNAL (undo / B4). Incident links of deleted activities, keyed by
902
- * STRING id (EntityChange link ids are String()-normalized). Carries the
903
- * engine `LinkSnapshot` (lag in working hours) for unit-correct re-add.
904
- */
905
- __beforeLinks?: ReadonlyMap<string, LinkSnapshot>;
435
+ activityVerdicts?: ReadonlyArray<DatesBatchVerdict>;
436
+ linkVerdicts?: ReadonlyArray<LinkBatchVerdict>;
906
437
  } | {
907
438
  ok: false;
908
439
  reason: string;
@@ -917,39 +448,33 @@ type DatesBatchVerdict = {
917
448
  reason: string;
918
449
  alertKey?: string | undefined;
919
450
  };
451
+ type LinkBatchVerdict = {
452
+ operationIndex: number;
453
+ kind: LinksBatchOperation['kind'];
454
+ ok: true;
455
+ linkId: string;
456
+ } | {
457
+ operationIndex: number;
458
+ kind: LinksBatchOperation['kind'];
459
+ ok: false;
460
+ reason: string;
461
+ };
920
462
 
921
463
  interface TrackingEvent {
922
464
  name: string;
923
465
  properties: Record<string, unknown>;
924
466
  }
925
- /**
926
- * Side effects a dispatch emits that are NOT entity-field changes and NOT
927
- * Amplitude tracking — commands the bridge must act on. Today: SIR
928
- * auto-reject (a frozen activity's dates changed or it was deleted, so its
929
- * pending SIR must be rejected). The core owns the rule; the bridge consumes
930
- * the effect (Redux REJECTED; durable persistence rides the activity save).
931
- * See `SIR_MAP.md` §10.
932
- */
933
467
  type ScheduleEffect = {
934
468
  kind: 'sir-auto-reject';
935
469
  activityId: ActivityId;
936
- /** Backend SIR id == the front Redux item id (see parseFromBackend A1). */
937
470
  sirId: string;
938
471
  reason: 'date_changed' | 'activity_deleted';
939
472
  };
940
- /**
941
- * A direct user edit violated the activity's own (pre-edit) constraint. Per
942
- * the canonical model (PRD constraint violations, R2/R3): the edit APPLIES
943
- * anyway — the warning is data for the UI (constraint validation modal),
944
- * never a rejection, and it is only emitted from direct edits (the
945
- * autoscheduler never violates, R1). Decision D-A 2026-07-04.
946
- */
947
473
  interface ConstraintWarning {
948
474
  kind: 'constraint_violation';
949
475
  activityId: ActivityId;
950
476
  constraintType: ConstraintType;
951
477
  constraintDate: Date;
952
- /** The projected date that violated (start or end, per constraint type). */
953
478
  projectedDate: Date;
954
479
  messageKey: string;
955
480
  }
@@ -975,23 +500,12 @@ interface ChangeSet {
975
500
  calendars: ReadonlyArray<EntityChange<Calendar>>;
976
501
  trackingEvents: ReadonlyArray<TrackingEvent>;
977
502
  viewState?: ReadonlyArray<ViewStateChange>;
978
- /**
979
- * Optional — present only when the dispatch produced side effects. Builders
980
- * that emit none omit it; consumers read `changeSet.effects ?? []`. Kept
981
- * optional to avoid churning every ChangeSet construction site. SIR (A4).
982
- */
983
503
  effects?: ReadonlyArray<ScheduleEffect>;
984
- /**
985
- * Optional — present only when a direct edit violated a constraint (see
986
- * `ConstraintWarning`). Consumers read `changeSet.warnings ?? []`.
987
- */
988
504
  warnings?: ReadonlyArray<ConstraintWarning>;
989
505
  }
990
506
 
991
507
  interface ProjectWorkHours {
992
- /** Earliest start of the working day across the week, `"HH:MM"`. */
993
508
  startHour: string;
994
- /** Latest end of the working day across the week, `"HH:MM"`. */
995
509
  endHour: string;
996
510
  }
997
511
 
@@ -1100,102 +614,18 @@ interface BackendScheduleInput {
1100
614
  baseCalendars?: BackendCalendarInput[] | undefined;
1101
615
  }
1102
616
 
1103
- /**
1104
- * Injected reporting port. The core is UI-agnostic: it cannot `console.*`,
1105
- * show UI, or know about Sentry. When it hits a recoverable data-quality
1106
- * issue (`warn`) or a failure it could not complete (`error`), it calls this
1107
- * port; the consumer (react_client) implements it and decides the outcome
1108
- * (Sentry, toast, nothing). Same injected-port pattern as `AutoSchedulerPort`.
1109
- */
1110
617
  interface ScheduleCoreReporter {
1111
- /** Recoverable data-quality issue; the core continues with its fallback. */
1112
618
  warn(message: string, context?: unknown): void;
1113
- /** A real failure the core could not complete. */
1114
619
  error(message: string, cause?: unknown): void;
1115
620
  }
1116
621
 
1117
- /**
1118
- * initialize-core — the construction + load-pass orchestration extracted from
1119
- * the `ScheduleCore` constructor. `initializeCore(input)` performs the entire
1120
- * load sequence (parse → snapshot → state → demote → id generators →
1121
- * custom-id tracker → optional baseline block → initial passes) and returns
1122
- * the assembled internals (`CoreInternals`) for the facade to assign. The
1123
- * facade constructor is assignment-only.
1124
- *
1125
- * The load-pass ORDER here is a CONTRACT — see `CURSOR.md`
1126
- * §"Flujo de core.dispatch paso a paso" and the inline comments below. Do not
1127
- * reorder, merge, or drop any step.
1128
- *
1129
- * The public types (`ScheduleCoreStatus`, `ScheduleCoreInput`) live here so
1130
- * `initialize-core.ts` never imports from `scheduleCore.ts` (which imports
1131
- * `initializeCore`), avoiding an import cycle. `scheduleCore.ts` re-exports the
1132
- * two public types
1133
- * so `src/index.ts`'s existing `export type { ... } from './init/schedule-core.js'`
1134
- * keeps resolving unchanged.
1135
- */
1136
-
1137
622
  type ScheduleCoreStatus = 'ready' | 'destroyed' | 'poisoned';
1138
623
  interface ScheduleCoreInput extends BackendScheduleInput {
1139
- /**
1140
- * Skip the full autoscheduler pass that normally runs from the
1141
- * constructor (`runInitialPasses`).
1142
- *
1143
- * Backend data already ships with `start_date` / `end_date` / `duration`
1144
- * scheduled — running the full ASAP+ALAP graph at load is redundant and
1145
- * extremely slow on large schedules (thousands of activities + links).
1146
- *
1147
- * When `true`, the constructor still runs the lightweight bottom-up
1148
- * post-processors (`updateParentBoundsFromChildren`) so parent bounds
1149
- * and progress rollup are consistent. The autoscheduler runs lazily on
1150
- * the first `dispatch(...)` call.
1151
- *
1152
- * Default: `false` (preserve existing behaviour). The bridge in
1153
- * `react_client/` sets this to `true` for production loads.
1154
- */
1155
624
  skipInitialAutoSchedule?: boolean;
1156
- /**
1157
- * Baseline points (backend `sectorbaselinepoint[]`, the `activityponts`
1158
- * endpoint payload) to overlay at load. Each point maps to an activity by
1159
- * `point.activityId` → `activity.proplannerId`. When present, the constructor
1160
- * overlays them so the baseline columns + `expected_progress_base` can read the
1161
- * ACTIVE point. NB: ponderators are NOT recomputed at load (backend ships the
1162
- * value prod renders verbatim — recomputing diverges); `goCalculatePonderators`
1163
- * is a save-time op. Omit (or pass `[]`) for projects with no baseline.
1164
- */
1165
625
  baselinePoints?: readonly BaselinePoint[];
1166
- /**
1167
- * Clock port for time-dependent columns (`expected_progress*`). A FUNCTION
1168
- * (not a frozen instant): the core calls it FRESH each time it needs "today"
1169
- * (load + every dispatch that recomputes expected_progress), so the value
1170
- * tracks the real day even on a long-lived session — never congelado.
1171
- *
1172
- * Opt-in (no default): omit it → the core does NOT compute `expected_progress`
1173
- * (preserves the legacy path; keeps the test suite deterministic). Production
1174
- * injects `() => new Date()`; tests inject `() => fixedDate` for reproducibility.
1175
- * The core never calls `new Date()` itself — this port is the only time source.
1176
- */
1177
626
  clock?: () => Date;
1178
- /**
1179
- * Critical-path mode at LOAD. The CP is ALWAYS computed; this flag only
1180
- * decides WHEN `core.ready` is considered settled:
1181
- * - `false` (default — front): `core.ready` resolves WITHOUT the CP (fast
1182
- * load); the CP runs async and is delivered via `core.criticalPathReady`.
1183
- * - `true` (backend, no front): `core.ready` WAITS for the CP — it is part of
1184
- * the load (blocking). The caller does a single `await core.ready` and has
1185
- * the dates + CP fields ready.
1186
- */
1187
627
  criticalPathOnLoad?: boolean;
1188
- /**
1189
- * Initial status criterion (`'Baseline' | 'Actual'`) for the `status` column.
1190
- * Not a backend field (client-only view pref); the client passes the current
1191
- * value, defaulting to `'Baseline'`. Overrides the sector default when present.
1192
- * Changed at runtime via `ScheduleCore.setStatusCriteria`.
1193
- */
1194
628
  statusCriteria?: StatusCriteria;
1195
- /**
1196
- * Optional injected port for data-quality warnings and scheduling errors.
1197
- * Defaults to a no-op when absent (the core never touches console/Sentry).
1198
- */
1199
629
  reporter?: ScheduleCoreReporter;
1200
630
  }
1201
631
 
@@ -1211,7 +641,7 @@ declare class ScheduleCore {
1211
641
  get ready(): Promise<void>;
1212
642
  get criticalPathReady(): Promise<{
1213
643
  changes: ChangeSet;
1214
- }>;
644
+ } | null>;
1215
645
  get status(): ScheduleCoreStatus;
1216
646
  getSector(): Readonly<SectorMetadata>;
1217
647
  getActivityView(id: ActivityId): Readonly<CoreActivity> | null;
@@ -1224,13 +654,7 @@ declare class ScheduleCore {
1224
654
  forEachActivityId(visit: (id: string) => void): void;
1225
655
  getChildrenIds(parentId: ActivityId | '0'): ReadonlyArray<ActivityId>;
1226
656
  getSelectedActivityIds(): string[];
1227
- /**
1228
- * Every activity id in canonical DFS visual order (pre-order from roots,
1229
- * siblings by `correlative_id` ASC) — ALL activities; filtering by
1230
- * `visible` is the consumer's job. Memoized in the state and invalidated
1231
- * on any structural mutation (add/remove/reparent/renumber), so repeated
1232
- * reads between mutations are O(1).
1233
- */
657
+ getHiddenActivityIds(): string[];
1234
658
  getVisualOrderIds(): ReadonlyArray<ActivityId>;
1235
659
  hasChild(parentId: ActivityId | '0'): boolean;
1236
660
  getCalendar(id: CalendarId): Readonly<Calendar> | null;
@@ -1243,52 +667,30 @@ declare class ScheduleCore {
1243
667
  private _enqueue;
1244
668
  dispatch(action: DispatchAction, options?: DispatchOptions): Promise<DispatchResult>;
1245
669
  private _dispatchInner;
1246
- /**
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.
1252
- */
1253
670
  recomputeCriticalPath(): Promise<{
1254
671
  changes: ChangeSet;
1255
672
  } | null>;
1256
673
  isCriticalPathSettled(): boolean;
1257
674
  whenCriticalPathSettled(): Promise<void>;
675
+ private _withReappliedFilter;
676
+ private _reapplyActiveFilter;
1258
677
  private _recordScheduleMutation;
1259
678
  private _startCriticalPathForCurrentRevision;
1260
679
  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
680
  private _recomputeAfterHistoryRestore;
1267
681
  undo(): Promise<ChangeSet | null>;
1268
682
  redo(): Promise<ChangeSet | null>;
1269
683
  canUndo(): boolean;
1270
684
  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
685
  clearHistory(): void;
1277
686
  undoDepth(): number;
1278
687
  private _resyncCustomIdTrackerFromModel;
1279
688
  destroy(): void;
1280
689
  private readonly _initPromise;
1281
- private readonly _criticalPathReady;
690
+ private _criticalPathReady;
1282
691
  private readonly _ready;
1283
692
  private readonly _saveTracker;
1284
693
  private assertReady;
1285
- /**
1286
- * Revert the current dispatch's mutations from the write-capture journal. If
1287
- * the restore ITSELF throws, the state may be a third, partially-inverted
1288
- * state — worse than either endpoint — so poison the core: refuse all further
1289
- * dispatches and surface the fault so the host reloads from backend. A failed
1290
- * rollback is never swallowed.
1291
- */
1292
694
  private _rollback;
1293
695
  }
1294
696
 
@@ -1305,25 +707,6 @@ interface ParsedInput {
1305
707
  }
1306
708
  declare function parseFromBackend(input: BackendScheduleInput, reporter?: ScheduleCoreReporter): ParsedInput;
1307
709
 
1308
- /**
1309
- * Returns the ACTIVE baseline point of an activity, or null.
1310
- *
1311
- * Ported verbatim from the legacy selector used across the schedule
1312
- * (`react_client/src/views/ganttContainer/gantt/gantt.helper.js`
1313
- * calculatePonderators / getDurationRecursively, and the baseline columns
1314
- * `startBaseCol`…`durationBaseCol`):
1315
- *
1316
- * activity.baseline_points.find(b => b.sectorbaselineversion?.active)
1317
- *
1318
- * Exactly one version per sector is active. Returns null when the activity
1319
- * has no `baseline_points`, an empty array, or no point whose version is
1320
- * active — matching the legacy behavior where such activities get
1321
- * `ponderator = 0` (baseline-only, no fallback to real values; see memory
1322
- * `project_ponderator_baseline_fix`).
1323
- *
1324
- * The `active` flag is treated as truthy (not strictly `=== true`) to match
1325
- * the legacy `if (base.sectorbaselineversion.active)` check.
1326
- */
1327
710
  declare function getActiveBaseline(activity: {
1328
711
  baselinePoints?: readonly CoreBaselinePoint[];
1329
712
  } | null | undefined): CoreBaselinePoint | null;
@@ -1331,135 +714,48 @@ declare function getActiveBaseline(activity: {
1331
714
  interface CalendarLike {
1332
715
  calculateDuration(start: Date, end: Date): number;
1333
716
  }
1334
- /**
1335
- * Expected % a baseline-planned activity should have completed by `now`,
1336
- * measured in working time against its baseline calendar. Faithful port of
1337
- * legacy `calculateExpected` (lookahead-common.js): edge rules first, else the
1338
- * working-time ratio. Returns 0..100. `now` should already be the comparison
1339
- * instant the caller wants (e.g. end-of-day).
1340
- *
1341
- * Shared by the `expected_progress` (live) and `expected_progress_base` columns
1342
- * (column-owns-everything: the formula is the one piece both columns share).
1343
- */
1344
717
  declare function expectedProgressFromBaseline(start: Date, end: Date, now: Date, calendar: CalendarLike): number;
1345
718
 
1346
719
  interface ExpectedProgressAdapter {
1347
720
  getChildrenIds(parentId: string | 0): readonly string[];
1348
721
  getActivity(id: string): Record<string, unknown> | null;
1349
722
  setActivityField(id: string, field: string, value: unknown): void;
1350
- /** Resolve a baseline calendar by its engine id (`${baseCalendarId}-base`). */
1351
723
  getBaseCalendar(engineId: string): CalendarLike | null;
1352
724
  }
1353
- /**
1354
- * Walks the tree from `rootIds`, writing `expected_progress_base` on every
1355
- * activity: leaves from their baseline calendar, parents as
1356
- * `Σ(childBase × child.ponderator) / 100` (faithful port of engine.ts
1357
- * aggregateHierarchy). Returns the ids whose value changed.
1358
- */
1359
725
  declare function computeExpectedProgress(rootIds: readonly string[], now: Date, adapter: ExpectedProgressAdapter, defaultBaseCalendarId?: string | null): string[];
1360
726
 
1361
727
  type StaticActivityDefaults = Omit<CoreActivity, 'id' | 'parentId' | 'name' | 'startDate' | 'endDate' | 'durationHours' | 'calendarId' | 'uniqueCorrelativeId'>;
1362
728
  declare const NEW_ACTIVITY_DEFAULTS: Readonly<StaticActivityDefaults>;
1363
729
 
1364
- /**
1365
- * Schedule-core equivalent of the legacy frontend `checkNoUpdatedLinks`.
1366
- *
1367
- * Returns the CURRENT links that were MODIFIED relative to a `baseline` (the
1368
- * last loaded/saved link state). A link is "modified" iff its `lag` or `type`
1369
- * differs from its baseline counterpart (matched by `id`).
1370
- *
1371
- * Why only lag/type: empirically those are the only mutable fields of a link —
1372
- * `source`/`target` are identity, and the rest are backend/derived. The core
1373
- * `link-update` intent (`dispatch/types.ts`) changes exactly "lag and/or type",
1374
- * so this comparison is complete.
1375
- *
1376
- * New links (absent from baseline) and deleted links (present only in baseline)
1377
- * are NOT "modified" — those belong to the unsaved / deleted buckets. The
1378
- * baseline is supplied by the caller (the save flow's reference snapshot); per
1379
- * interface discipline this is a pure helper, not a method on the core/port.
1380
- */
1381
730
  declare function checkNoUpdatedLinks(baseline: readonly Link[], current: readonly Link[]): Link[];
1382
731
 
1383
- /**
1384
- * Schedule-core equivalent of the legacy frontend `checkNoSavedActivities` /
1385
- * `checkNoSavedLinks`: an entity is "unsaved" when it has no backend id
1386
- * (`proplannerId`) — i.e. it was created in-session and not yet persisted.
1387
- *
1388
- * Pure helper (same `!proplannerId` criterion as legacy, so 0/null/undefined
1389
- * all count as unsaved). Works on `core.getAllActivitiesView()` / `getAllLinks()`
1390
- * or on `gantt.serialize().data` — both carry `proplannerId`. Per interface
1391
- * discipline this is a helper, not a method on the core/port.
1392
- */
1393
732
  interface WithProplannerId {
1394
733
  proplannerId?: number | null;
1395
734
  [key: string]: unknown;
1396
735
  }
1397
736
  declare const getUnsavedActivities: <T extends WithProplannerId>(activities?: readonly T[]) => T[];
1398
737
 
1399
- /**
1400
- * Returns a microtask-priority yield. Resolves on the next event-loop
1401
- * tick — the browser is free to paint, handle input, or process other
1402
- * tasks in between.
1403
- */
1404
738
  declare function yieldToBrowser(): Promise<void>;
1405
739
 
1406
- /**
1407
- * Does this dispatch recompute the critical path? The single authority for the
1408
- * question, shared by the dispatch gate (`resolveDerivedPasses`) and the
1409
- * bridge, which reads it to defer the CP out of the paint. Mirrors legacy: the
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.
1413
- */
1414
740
  declare function willRunCriticalPath(action: DispatchAction): boolean;
1415
741
 
1416
- /** Activity types */
1417
742
  declare const ACTIVITY_TYPE: {
1418
743
  readonly TASK: "task";
1419
744
  readonly PROJECT: "project";
1420
745
  readonly MILESTONE: "milestone";
1421
746
  };
1422
747
 
1423
- /**
1424
- * Root-parent sentinel — single source of truth.
1425
- *
1426
- * Root-level activities have meant "root" under several shapes across the
1427
- * codebase and the recorded data: string `'0'` (post-parse domain model),
1428
- * numeric `0` (DHTMLX / engine seam), and nullish. `isRootParent` accepts
1429
- * all of them; `normalizeParentKey` collapses them to the canonical `'0'`.
1430
- *
1431
- * NOTE: `'0'` is overloaded elsewhere in the domain (FS link-type code,
1432
- * correlative ids). These constants are ONLY for the parent axis.
1433
- */
1434
- /** Canonical root-parent id in the post-parse domain model. */
1435
748
  declare const ROOT_PARENT_ID: "0";
1436
- /** True when `parent` denotes the root level, under any historical shape. */
1437
749
  declare function isRootParent(parent: unknown): boolean;
1438
- /** Collapse any root shape to the canonical `'0'`; stringify the rest. */
1439
750
  declare function normalizeParentKey(parent: unknown): string;
1440
751
 
1441
- /**
1442
- * Canonical bidirectional map between DHTMLX link kinds ('0'-'3') and the
1443
- * backend/display two-letter codes (fs/ss/ff/sf), plus the predecessor
1444
- * display-string vocabulary.
1445
- *
1446
- * Single source of truth — replaces the hand-maintained copies that lived in
1447
- * parse-predecessor-string (code→number), apply-link-operation
1448
- * (number→code), boundary/backend parseFromBackend (code→number) and
1449
- * inverses (number→code). The mutual `satisfies Record<…>` constraints make
1450
- * omitting or typo'ing a key on either side a compile error.
1451
- */
1452
-
1453
- /** Backend/display two-letter link codes. */
1454
752
  type LinkTypeCode = 'fs' | 'ss' | 'ff' | 'sf';
1455
- /** DHTMLX numeric kind → backend/display code. */
1456
753
  declare const LINK_TYPE_CODE: {
1457
754
  readonly "0": "fs";
1458
755
  readonly "1": "ss";
1459
756
  readonly "2": "ff";
1460
757
  readonly "3": "sf";
1461
758
  };
1462
- /** Backend/display code → DHTMLX numeric kind (inverse of LINK_TYPE_CODE). */
1463
759
  declare const LINK_CODE_TO_TYPE: {
1464
760
  readonly fs: "0";
1465
761
  readonly ss: "1";
@@ -1467,49 +763,10 @@ declare const LINK_CODE_TO_TYPE: {
1467
763
  readonly sf: "3";
1468
764
  };
1469
765
 
1470
- /**
1471
- * Inverse boundary maps (domain → backend vocabulary).
1472
- *
1473
- * The forward translations live in `constants.ts` (`CONSTRAINT_LABEL_MAP`)
1474
- * and `link-display.ts` (`LINK_CODE_TO_NUMBER`). The *serialization*
1475
- * core → backend is intentionally NOT owned by this package today — it
1476
- * lives in the consumer bridge (`react_client/gantt.helper.js`,
1477
- * `from_number_to_code` / `GanttConstraint` reverse lookup) because the
1478
- * bridge is the source of truth for what the server expects on save.
1479
- *
1480
- * These inverses exist so the mapping is documented and testable next to
1481
- * its forward counterpart, and so a future `core → backend` layer doesn't
1482
- * re-derive them by hand. **Internal only — not exported in `src/index.ts`.**
1483
- * See Obsidian `BOUNDARY_TRANSLATIONS.md` §"Mapeos inversos".
1484
- */
1485
-
1486
- /** Inverse of `CONSTRAINT_LABEL_MAP` — internal code → backend label. */
1487
766
  declare const CONSTRAINT_TYPE_TO_LABEL: Record<ConstraintType, string>;
1488
767
 
1489
- /**
1490
- * Working-day vocabulary — genuine cross-component primitive (creation
1491
- * defaults, internal/state context fallbacks, link lag display). The value
1492
- * is the FALLBACK only; a sector- or calendar-provided hoursPerDay always
1493
- * takes precedence at every consumer.
1494
- */
1495
768
  declare const DEFAULT_HOURS_PER_DAY = 8;
1496
769
 
1497
- /**
1498
- * Canonical catalog of `DispatchAction` kinds.
1499
- *
1500
- * The discriminated union in `types.ts` remains the source of truth; this
1501
- * module exposes it as RUNTIME vocabulary so `.js` consumers (bridge
1502
- * entry-points, guard tests) stop retyping the wire strings. Exhaustiveness
1503
- * is compile-checked HERE (not in the guard test — `tsconfig.json` excludes
1504
- * `*.test.ts` from `tsc --noEmit` and vitest does not typecheck):
1505
- * - `satisfies Record<string, DispatchActionKind>` rejects any entry whose
1506
- * value is not a union member (catalog ⊆ union);
1507
- * - `KIND_CATALOG_COVERS_UNION: Record<DispatchActionKind, true>` requires
1508
- * one entry per union member (union ⊆ catalog) — adding a kind to
1509
- * `DispatchAction` without updating this module breaks the build.
1510
- * The string VALUES are a frozen contract (see `constants.guard.test.ts`).
1511
- */
1512
-
1513
770
  type DispatchActionKind = DispatchAction['kind'];
1514
771
  declare const DISPATCH_ACTION_KIND: {
1515
772
  readonly PERSISTENCE_ACKNOWLEDGE: "persistence-acknowledge";
@@ -1533,28 +790,12 @@ declare const DISPATCH_ACTION_KIND: {
1533
790
  readonly SELECTION_TOGGLE: "selection-toggle";
1534
791
  readonly SELECTION_REPLACE: "selection-replace";
1535
792
  readonly VISIBILITY_SET: "visibility-set";
793
+ readonly FILTER_SET: "filter-set";
1536
794
  readonly SIR_SYNC: "sir-sync";
1537
795
  readonly ACTIVITY_LOOKAHEAD_SYNC: "activity-lookahead-sync";
1538
796
  };
1539
797
  declare const DISPATCH_ACTION_KINDS: ReadonlyArray<DispatchActionKind>;
1540
798
 
1541
- /**
1542
- * Canonical vocabulary of activity-creation triggers.
1543
- *
1544
- * The engine's `activity-create` intent does NOT carry this field — these
1545
- * kinds name the USER trigger the consumer's creation hub routes on before
1546
- * dispatching (`requestActivityCreation` / `CreateActivityIntent` in
1547
- * `react_client` core path). The engine owns the vocabulary so the client
1548
- * and the bridge import ONE canonical source instead of retyping the
1549
- * literals (CONSTANTES_DECISIONES paso 1). The string VALUES are a frozen
1550
- * contract — verified verbatim against the client union
1551
- * (`core/features/createActivity/intents.ts`) on 2026-07-02.
1552
- *
1553
- * - `CHILD` — per-row "+" button (runs the lookahead guard).
1554
- * - `CHILD_CONFIRMED` — lookahead-cleanup modal confirm (guard already passed).
1555
- * - `LINE` — green bar "add-task-by-line".
1556
- * - `ROOT` — generic "+" (header / create), no guard.
1557
- */
1558
799
  declare const CREATION_KIND: {
1559
800
  readonly CHILD: "child";
1560
801
  readonly CHILD_CONFIRMED: "child-confirmed";
@@ -1563,16 +804,6 @@ declare const CREATION_KIND: {
1563
804
  };
1564
805
  type CreationKind = (typeof CREATION_KIND)[keyof typeof CREATION_KIND];
1565
806
 
1566
- /**
1567
- * Canonical catalog of dispatch rejection reasons.
1568
- *
1569
- * `DispatchResult.reason` stays `string` on purpose: it is the cross-domain
1570
- * bag that also carries pipeline gate codes (`ParseErrorCode`,
1571
- * `ValidationReason`, `EditDenialReason`) and the link-engine rejected
1572
- * verdicts. This module covers the reasons DISPATCH ITSELF produces, so
1573
- * producers and tests share one compile-checked source. The string VALUES
1574
- * are a frozen contract — the bridge matches on them.
1575
- */
1576
807
  declare const REJECTION_REASON: {
1577
808
  readonly CANNOT_EDIT: "cannot_edit";
1578
809
  readonly PARSE_ERROR: "parse_error";
@@ -1596,11 +827,6 @@ declare const REJECTION_REASON: {
1596
827
  };
1597
828
  type DispatchRejectReason = (typeof REJECTION_REASON)[keyof typeof REJECTION_REASON];
1598
829
 
1599
- /**
1600
- * Amplitude event names emitted by the dispatch layer (structure mutations).
1601
- * Analytics CONTRACT — values must stay byte-identical to production.
1602
- * Column-level entry events live in `columns/shared/trackingEvents.ts`.
1603
- */
1604
830
  declare const DISPATCH_TRACK_EVENT: {
1605
831
  readonly ACTIVITY_CREATION: "schedule_activity_creation";
1606
832
  readonly ACTIVITY_DELETION: "schedule_activity_deletion";
@@ -1610,4 +836,4 @@ declare const DISPATCH_TRACK_EVENT: {
1610
836
  };
1611
837
  type DispatchTrackEvent = (typeof DISPATCH_TRACK_EVENT)[keyof typeof DISPATCH_TRACK_EVENT];
1612
838
 
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 };
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 };