@nowline/layout 0.8.5 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/axis-columns.d.ts +70 -0
- package/dist/axis-columns.d.ts.map +1 -0
- package/dist/axis-columns.js +104 -0
- package/dist/axis-columns.js.map +1 -0
- package/dist/band-scale.d.ts.map +1 -1
- package/dist/calendar-resolver.d.ts +27 -0
- package/dist/calendar-resolver.d.ts.map +1 -0
- package/dist/calendar-resolver.js +132 -0
- package/dist/calendar-resolver.js.map +1 -0
- package/dist/calendar.d.ts +0 -19
- package/dist/calendar.d.ts.map +1 -1
- package/dist/calendar.js +4 -44
- package/dist/calendar.js.map +1 -1
- package/dist/canvas-width.d.ts +23 -0
- package/dist/canvas-width.d.ts.map +1 -0
- package/dist/canvas-width.js +26 -0
- package/dist/canvas-width.js.map +1 -0
- package/dist/capacity.d.ts +28 -0
- package/dist/capacity.d.ts.map +1 -1
- package/dist/capacity.js +64 -29
- package/dist/capacity.js.map +1 -1
- package/dist/chart-translation.d.ts +22 -0
- package/dist/chart-translation.d.ts.map +1 -0
- package/dist/chart-translation.js +83 -0
- package/dist/chart-translation.js.map +1 -0
- package/dist/container-header-geometry.d.ts +64 -0
- package/dist/container-header-geometry.d.ts.map +1 -0
- package/dist/container-header-geometry.js +89 -0
- package/dist/container-header-geometry.js.map +1 -0
- package/dist/dsl-utils.d.ts +3 -4
- package/dist/dsl-utils.d.ts.map +1 -1
- package/dist/dsl-utils.js +8 -25
- package/dist/dsl-utils.js.map +1 -1
- package/dist/edge-routing.d.ts +69 -78
- package/dist/edge-routing.d.ts.map +1 -1
- package/dist/edge-routing.js +271 -401
- package/dist/edge-routing.js.map +1 -1
- package/dist/frame-tab-geometry.d.ts +24 -14
- package/dist/frame-tab-geometry.d.ts.map +1 -1
- package/dist/frame-tab-geometry.js +69 -23
- package/dist/frame-tab-geometry.js.map +1 -1
- package/dist/header-card-geometry.d.ts +12 -0
- package/dist/header-card-geometry.d.ts.map +1 -1
- package/dist/header-card-geometry.js +14 -2
- package/dist/header-card-geometry.js.map +1 -1
- package/dist/header-logo.d.ts +28 -0
- package/dist/header-logo.d.ts.map +1 -0
- package/dist/header-logo.js +51 -0
- package/dist/header-logo.js.map +1 -0
- package/dist/i18n.d.ts +11 -0
- package/dist/i18n.d.ts.map +1 -1
- package/dist/i18n.js +11 -1
- package/dist/i18n.js.map +1 -1
- package/dist/index.d.ts +20 -8
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -7
- package/dist/index.js.map +1 -1
- package/dist/inline-date-pin-geometry.d.ts +19 -11
- package/dist/inline-date-pin-geometry.d.ts.map +1 -1
- package/dist/inline-date-pin-geometry.js +45 -46
- package/dist/inline-date-pin-geometry.js.map +1 -1
- package/dist/item-bar-geometry.d.ts +192 -4
- package/dist/item-bar-geometry.d.ts.map +1 -1
- package/dist/item-bar-geometry.js +258 -5
- package/dist/item-bar-geometry.js.map +1 -1
- package/dist/item-port-geometry.d.ts +13 -0
- package/dist/item-port-geometry.d.ts.map +1 -0
- package/dist/item-port-geometry.js +63 -0
- package/dist/item-port-geometry.js.map +1 -0
- package/dist/layout-context.d.ts +115 -51
- package/dist/layout-context.d.ts.map +1 -1
- package/dist/layout-context.js +96 -5
- package/dist/layout-context.js.map +1 -1
- package/dist/layout-insights.d.ts.map +1 -1
- package/dist/layout-insights.js +73 -1
- package/dist/layout-insights.js.map +1 -1
- package/dist/layout.d.ts +35 -0
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +18 -1315
- package/dist/layout.js.map +1 -1
- package/dist/nodes/anchor-node.d.ts.map +1 -1
- package/dist/nodes/anchor-node.js +11 -4
- package/dist/nodes/anchor-node.js.map +1 -1
- package/dist/nodes/content-extent.d.ts +24 -0
- package/dist/nodes/content-extent.d.ts.map +1 -0
- package/dist/nodes/content-extent.js +100 -0
- package/dist/nodes/content-extent.js.map +1 -0
- package/dist/nodes/dependency-edges.d.ts +6 -0
- package/dist/nodes/dependency-edges.d.ts.map +1 -0
- package/dist/nodes/dependency-edges.js +148 -0
- package/dist/nodes/dependency-edges.js.map +1 -0
- package/dist/nodes/footnote-node.d.ts +24 -3
- package/dist/nodes/footnote-node.d.ts.map +1 -1
- package/dist/nodes/footnote-node.js +129 -25
- package/dist/nodes/footnote-node.js.map +1 -1
- package/dist/nodes/group-node.d.ts +10 -21
- package/dist/nodes/group-node.d.ts.map +1 -1
- package/dist/nodes/group-node.js +79 -154
- package/dist/nodes/group-node.js.map +1 -1
- package/dist/nodes/header-node.d.ts +56 -0
- package/dist/nodes/header-node.d.ts.map +1 -0
- package/dist/nodes/header-node.js +163 -0
- package/dist/nodes/header-node.js.map +1 -0
- package/dist/nodes/include-node.d.ts +22 -12
- package/dist/nodes/include-node.d.ts.map +1 -1
- package/dist/nodes/include-node.js +95 -62
- package/dist/nodes/include-node.js.map +1 -1
- package/dist/nodes/item-measure.d.ts +135 -0
- package/dist/nodes/item-measure.d.ts.map +1 -0
- package/dist/nodes/item-measure.js +514 -0
- package/dist/nodes/item-measure.js.map +1 -0
- package/dist/nodes/item-node.d.ts +162 -9
- package/dist/nodes/item-node.d.ts.map +1 -1
- package/dist/nodes/item-node.js +256 -69
- package/dist/nodes/item-node.js.map +1 -1
- package/dist/nodes/marker-band.d.ts +118 -0
- package/dist/nodes/marker-band.d.ts.map +1 -0
- package/dist/nodes/marker-band.js +303 -0
- package/dist/nodes/marker-band.js.map +1 -0
- package/dist/nodes/marker-geometry.js +1 -1
- package/dist/nodes/marker-geometry.js.map +1 -1
- package/dist/nodes/milestone-node.d.ts +112 -15
- package/dist/nodes/milestone-node.d.ts.map +1 -1
- package/dist/nodes/milestone-node.js +246 -95
- package/dist/nodes/milestone-node.js.map +1 -1
- package/dist/nodes/nowline-node.d.ts +8 -0
- package/dist/nodes/nowline-node.d.ts.map +1 -0
- package/dist/nodes/nowline-node.js +74 -0
- package/dist/nodes/nowline-node.js.map +1 -0
- package/dist/nodes/parallel-node.d.ts +7 -12
- package/dist/nodes/parallel-node.d.ts.map +1 -1
- package/dist/nodes/parallel-node.js +66 -32
- package/dist/nodes/parallel-node.js.map +1 -1
- package/dist/nodes/region-markers.d.ts +41 -0
- package/dist/nodes/region-markers.d.ts.map +1 -0
- package/dist/nodes/region-markers.js +130 -0
- package/dist/nodes/region-markers.js.map +1 -0
- package/dist/nodes/roadmap-node.d.ts +9 -72
- package/dist/nodes/roadmap-node.d.ts.map +1 -1
- package/dist/nodes/roadmap-node.js +38 -788
- package/dist/nodes/roadmap-node.js.map +1 -1
- package/dist/nodes/row-track.d.ts +33 -0
- package/dist/nodes/row-track.d.ts.map +1 -0
- package/dist/nodes/row-track.js +170 -0
- package/dist/nodes/row-track.js.map +1 -0
- package/dist/nodes/sequence-item.d.ts +25 -0
- package/dist/nodes/sequence-item.d.ts.map +1 -0
- package/dist/nodes/sequence-item.js +396 -0
- package/dist/nodes/sequence-item.js.map +1 -0
- package/dist/nodes/sequence-one.d.ts +5 -0
- package/dist/nodes/sequence-one.d.ts.map +1 -0
- package/dist/nodes/sequence-one.js +26 -0
- package/dist/nodes/sequence-one.js.map +1 -0
- package/dist/nodes/swimlane-node.d.ts +5 -17
- package/dist/nodes/swimlane-node.d.ts.map +1 -1
- package/dist/nodes/swimlane-node.js +62 -147
- package/dist/nodes/swimlane-node.js.map +1 -1
- package/dist/nodes/wave-node.d.ts +52 -0
- package/dist/nodes/wave-node.d.ts.map +1 -0
- package/dist/nodes/wave-node.js +483 -0
- package/dist/nodes/wave-node.js.map +1 -0
- package/dist/non-working-display.d.ts +14 -0
- package/dist/non-working-display.d.ts.map +1 -0
- package/dist/non-working-display.js +28 -0
- package/dist/non-working-display.js.map +1 -0
- package/dist/phases/date-window.d.ts +48 -0
- package/dist/phases/date-window.d.ts.map +1 -0
- package/dist/phases/date-window.js +130 -0
- package/dist/phases/date-window.js.map +1 -0
- package/dist/phases/finalize.d.ts +198 -0
- package/dist/phases/finalize.d.ts.map +1 -0
- package/dist/phases/finalize.js +1058 -0
- package/dist/phases/finalize.js.map +1 -0
- package/dist/phases/measure.d.ts +77 -0
- package/dist/phases/measure.d.ts.map +1 -0
- package/dist/phases/measure.js +222 -0
- package/dist/phases/measure.js.map +1 -0
- package/dist/phases/place.d.ts +28 -0
- package/dist/phases/place.d.ts.map +1 -0
- package/dist/phases/place.js +225 -0
- package/dist/phases/place.js.map +1 -0
- package/dist/renderable.d.ts.map +1 -1
- package/dist/renderable.js +23 -3
- package/dist/renderable.js.map +1 -1
- package/dist/resolve-today.d.ts +11 -1
- package/dist/resolve-today.d.ts.map +1 -1
- package/dist/resolve-today.js +26 -12
- package/dist/resolve-today.js.map +1 -1
- package/dist/row-packer.d.ts +42 -16
- package/dist/row-packer.d.ts.map +1 -1
- package/dist/row-packer.js +51 -78
- package/dist/row-packer.js.map +1 -1
- package/dist/schedule.d.ts +58 -1
- package/dist/schedule.d.ts.map +1 -1
- package/dist/schedule.js +280 -106
- package/dist/schedule.js.map +1 -1
- package/dist/start-rules.d.ts +27 -0
- package/dist/start-rules.d.ts.map +1 -0
- package/dist/start-rules.js +91 -0
- package/dist/start-rules.js.map +1 -0
- package/dist/start-terms.d.ts +23 -0
- package/dist/start-terms.d.ts.map +1 -0
- package/dist/start-terms.js +27 -0
- package/dist/start-terms.js.map +1 -0
- package/dist/status-dot.d.ts +46 -0
- package/dist/status-dot.d.ts.map +1 -0
- package/dist/status-dot.js +78 -0
- package/dist/status-dot.js.map +1 -0
- package/dist/style-resolution.d.ts.map +1 -1
- package/dist/style-resolution.js +23 -43
- package/dist/style-resolution.js.map +1 -1
- package/dist/text-measure.d.ts +28 -0
- package/dist/text-measure.d.ts.map +1 -0
- package/dist/text-measure.js +70 -0
- package/dist/text-measure.js.map +1 -0
- package/dist/themes/contrast.d.ts +4 -0
- package/dist/themes/contrast.d.ts.map +1 -0
- package/dist/themes/contrast.js +32 -0
- package/dist/themes/contrast.js.map +1 -0
- package/dist/themes/dark.d.ts.map +1 -1
- package/dist/themes/dark.js +28 -1
- package/dist/themes/dark.js.map +1 -1
- package/dist/themes/grayscale.d.ts.map +1 -1
- package/dist/themes/grayscale.js +28 -1
- package/dist/themes/grayscale.js.map +1 -1
- package/dist/themes/index.d.ts +4 -11
- package/dist/themes/index.d.ts.map +1 -1
- package/dist/themes/index.js +6 -4
- package/dist/themes/index.js.map +1 -1
- package/dist/themes/light.d.ts.map +1 -1
- package/dist/themes/light.js +28 -1
- package/dist/themes/light.js.map +1 -1
- package/dist/themes/shape.d.ts +31 -0
- package/dist/themes/shape.d.ts.map +1 -1
- package/dist/themes/shared.d.ts +44 -50
- package/dist/themes/shared.d.ts.map +1 -1
- package/dist/themes/shared.js +79 -83
- package/dist/themes/shared.js.map +1 -1
- package/dist/time-scale.d.ts +42 -6
- package/dist/time-scale.d.ts.map +1 -1
- package/dist/time-scale.js +109 -17
- package/dist/time-scale.js.map +1 -1
- package/dist/types.d.ts +528 -11
- package/dist/types.d.ts.map +1 -1
- package/dist/view-preset.d.ts +61 -6
- package/dist/view-preset.d.ts.map +1 -1
- package/dist/view-preset.js +439 -24
- package/dist/view-preset.js.map +1 -1
- package/dist/wave-barrier.d.ts +107 -0
- package/dist/wave-barrier.d.ts.map +1 -0
- package/dist/wave-barrier.js +174 -0
- package/dist/wave-barrier.js.map +1 -0
- package/dist/wave-hatch.d.ts +4 -0
- package/dist/wave-hatch.d.ts.map +1 -0
- package/dist/wave-hatch.js +21 -0
- package/dist/wave-hatch.js.map +1 -0
- package/dist/wave-layout.d.ts +99 -0
- package/dist/wave-layout.d.ts.map +1 -0
- package/dist/wave-layout.js +130 -0
- package/dist/wave-layout.js.map +1 -0
- package/dist/working-calendar.d.ts +128 -4
- package/dist/working-calendar.d.ts.map +1 -1
- package/dist/working-calendar.js +725 -51
- package/dist/working-calendar.js.map +1 -1
- package/dist/working-span.d.ts +11 -0
- package/dist/working-span.d.ts.map +1 -0
- package/dist/working-span.js +18 -0
- package/dist/working-span.js.map +1 -0
- package/package.json +7 -7
- package/src/axis-columns.ts +140 -0
- package/src/calendar-resolver.ts +161 -0
- package/src/calendar.ts +4 -45
- package/src/canvas-width.ts +36 -0
- package/src/capacity.ts +71 -30
- package/src/chart-translation.ts +88 -0
- package/src/container-header-geometry.ts +116 -0
- package/src/dsl-utils.ts +8 -26
- package/src/edge-routing.ts +347 -491
- package/src/frame-tab-geometry.ts +78 -25
- package/src/header-card-geometry.ts +17 -2
- package/src/header-logo.ts +67 -0
- package/src/i18n.ts +22 -1
- package/src/index.ts +97 -12
- package/src/inline-date-pin-geometry.ts +55 -58
- package/src/item-bar-geometry.ts +341 -5
- package/src/item-port-geometry.ts +69 -0
- package/src/layout-context.ts +210 -72
- package/src/layout-insights.ts +109 -2
- package/src/layout.ts +57 -1530
- package/src/nodes/anchor-node.ts +11 -4
- package/src/nodes/content-extent.ts +107 -0
- package/src/nodes/dependency-edges.ts +171 -0
- package/src/nodes/footnote-node.ts +171 -31
- package/src/nodes/group-node.ts +92 -215
- package/src/nodes/header-node.ts +255 -0
- package/src/nodes/include-node.ts +124 -110
- package/src/nodes/item-measure.ts +723 -0
- package/src/nodes/item-node.ts +358 -80
- package/src/nodes/marker-band.ts +406 -0
- package/src/nodes/marker-geometry.ts +1 -1
- package/src/nodes/milestone-node.ts +321 -136
- package/src/nodes/nowline-node.ts +87 -0
- package/src/nodes/parallel-node.ts +69 -39
- package/src/nodes/region-markers.ts +194 -0
- package/src/nodes/roadmap-node.ts +46 -950
- package/src/nodes/row-track.ts +217 -0
- package/src/nodes/sequence-item.ts +433 -0
- package/src/nodes/sequence-one.ts +40 -0
- package/src/nodes/swimlane-node.ts +66 -206
- package/src/nodes/wave-node.ts +609 -0
- package/src/non-working-display.ts +32 -0
- package/src/phases/date-window.ts +191 -0
- package/src/phases/finalize.ts +1303 -0
- package/src/phases/measure.ts +329 -0
- package/src/phases/place.ts +318 -0
- package/src/renderable.ts +23 -3
- package/src/resolve-today.ts +28 -25
- package/src/row-packer.ts +85 -84
- package/src/schedule.ts +362 -114
- package/src/start-rules.ts +113 -0
- package/src/start-terms.ts +38 -0
- package/src/status-dot.ts +81 -0
- package/src/style-resolution.ts +27 -57
- package/src/text-measure.ts +71 -0
- package/src/themes/contrast.ts +34 -0
- package/src/themes/dark.ts +28 -1
- package/src/themes/grayscale.ts +28 -1
- package/src/themes/index.ts +10 -7
- package/src/themes/light.ts +28 -1
- package/src/themes/shape.ts +38 -0
- package/src/themes/shared.ts +85 -93
- package/src/time-scale.ts +117 -19
- package/src/types.ts +529 -20
- package/src/view-preset.ts +520 -27
- package/src/wave-barrier.ts +242 -0
- package/src/wave-hatch.ts +25 -0
- package/src/wave-layout.ts +203 -0
- package/src/working-calendar.ts +875 -57
- package/src/working-span.ts +19 -0
package/src/schedule.ts
CHANGED
|
@@ -1,40 +1,63 @@
|
|
|
1
1
|
// scheduleRoadmap — compute the floating calendar start/end date for every
|
|
2
2
|
// named entity (items, milestones, anchors) without running the full layout.
|
|
3
3
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
4
|
+
// This is the only day-level scheduler (specs/scheduler-unification.md): the
|
|
5
|
+
// date window reads its content day from `extent`, and the exporters read the
|
|
6
|
+
// dates. The pixel layout keeps its own placement and differs from it in the
|
|
7
|
+
// catalogued ways of `test/engine-divergences.json`. The rules, with the
|
|
8
|
+
// roadmap-start resolution of the date-window phase (`resolveDateWindow` in
|
|
9
|
+
// `phases/date-window.ts`):
|
|
8
10
|
// - sequential default — start where the previous item in the lane ended
|
|
11
|
+
// - after:[id|DATE] — start no earlier than the maximum predecessor end
|
|
12
|
+
// (work has no fixed start pin: `date:` / `start:` on an item are
|
|
13
|
+
// unknown keys, specs/dsl.md § Item Properties); a parallel ends at its
|
|
14
|
+
// latest track end, a group at its last child's end
|
|
15
|
+
// - wave barriers (roadmaps with waves only) — every start is floored by
|
|
16
|
+
// its wave's start S_k, iterated to a fixpoint by `solveWaveBarriers`
|
|
17
|
+
// (specs/waves.md §8.5)
|
|
9
18
|
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
19
|
+
// Offsets are working-day indices from the start (specs/working-calendar.md
|
|
20
|
+
// §5): dates convert with `workingIndexOf`, so an `after:` date on a
|
|
21
|
+
// non-working day starts on the next working day. A start (and an after-only milestone) is a
|
|
22
|
+
// point, `dateAtWorkingIndex`; an exclusive end is `spanEndDate`, so a
|
|
23
|
+
// Mon–Fri item ends on Saturday. Anchors and dated milestones keep their own
|
|
24
|
+
// dates. A calendar with no non-working day keeps calendar-day arithmetic.
|
|
25
|
+
//
|
|
26
|
+
// The schedule is every exporter's source for the chart's calendar and
|
|
27
|
+
// durations (handoff m2p decision 16): `calendar` is the file's resolved
|
|
28
|
+
// calendar and `ScheduledItem.days` the duration engine C placed. The XLSX
|
|
29
|
+
// exporter also reads the Start / End dates, the milestone dates and the
|
|
30
|
+
// wave spans; the Mermaid exporter reads the wave spans.
|
|
14
31
|
|
|
15
32
|
import type {
|
|
16
33
|
GroupBlock,
|
|
17
34
|
ItemDeclaration,
|
|
18
35
|
MilestoneDeclaration,
|
|
19
36
|
NowlineFile,
|
|
20
|
-
|
|
37
|
+
ResolvedContent,
|
|
21
38
|
ResolveResult,
|
|
22
39
|
SwimlaneDeclaration,
|
|
40
|
+
WavePlan,
|
|
23
41
|
} from '@nowline/core';
|
|
24
|
-
import { isGroupBlock, isItemDeclaration, isParallelBlock } from '@nowline/core';
|
|
25
|
-
import {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
} from './calendar.js';
|
|
32
|
-
import { parseDate, propValue, propValues } from './dsl-utils.js';
|
|
42
|
+
import { buildWavePlan, isGroupBlock, isItemDeclaration, isParallelBlock } from '@nowline/core';
|
|
43
|
+
import { type TrackNode, trackChildren } from '@nowline/core/internal';
|
|
44
|
+
import { deriveItemDurationDays, resolveSizes } from './calendar.js';
|
|
45
|
+
import { type ResolvedCalendar, resolveWorkingCalendar } from './calendar-resolver.js';
|
|
46
|
+
import { parseIsoDate, propValue } from './dsl-utils.js';
|
|
47
|
+
import { type AfterTerm, startTermsOf } from './start-terms.js';
|
|
48
|
+
import { solveWaveBarriers, summarizeWaves, WavePass, waveFloorDays } from './wave-barrier.js';
|
|
49
|
+
import { spanEndDate } from './working-calendar.js';
|
|
33
50
|
|
|
34
51
|
/** Per-item scheduled interval, keyed by item id (name). */
|
|
35
52
|
export interface ScheduledItem {
|
|
36
53
|
start: Date;
|
|
37
54
|
end: Date;
|
|
55
|
+
/**
|
|
56
|
+
* Duration in working days: the `duration:` literal, or the size's effort
|
|
57
|
+
* divided by `capacity:`, under the file's calendar. 0 when the item has
|
|
58
|
+
* neither, or names an undeclared size.
|
|
59
|
+
*/
|
|
60
|
+
days: number;
|
|
38
61
|
}
|
|
39
62
|
|
|
40
63
|
/**
|
|
@@ -48,6 +71,8 @@ export interface ScheduledItem {
|
|
|
48
71
|
export interface RoadmapSchedule {
|
|
49
72
|
/** Resolved roadmap start date (UTC midnight). */
|
|
50
73
|
startDate: Date;
|
|
74
|
+
/** The file's calendar, which every date and `days` here uses. */
|
|
75
|
+
calendar: ResolvedCalendar;
|
|
51
76
|
/** Named items keyed by their DSL id (`name`). */
|
|
52
77
|
items: Map<string, ScheduledItem>;
|
|
53
78
|
/** Every item keyed by AST node identity (named and anonymous). */
|
|
@@ -58,11 +83,67 @@ export interface RoadmapSchedule {
|
|
|
58
83
|
milestoneByNode: WeakMap<MilestoneDeclaration, Date>;
|
|
59
84
|
/** Named anchors: their declared or computed date. */
|
|
60
85
|
anchors: Map<string, Date>;
|
|
86
|
+
/**
|
|
87
|
+
* Waves by id, in declaration order (specs/waves.md §8.5). Present only
|
|
88
|
+
* when the roadmap declares waves.
|
|
89
|
+
*/
|
|
90
|
+
waves?: Map<string, ScheduledWave>;
|
|
91
|
+
/** The wave plan the schedule ran with; undefined without waves. */
|
|
92
|
+
plan?: WavePlan;
|
|
93
|
+
/** The `ResolveResult` this schedule was built from. */
|
|
94
|
+
resolved: ResolveResult;
|
|
95
|
+
/** What the date window needs (specs/scheduler-unification.md §3.3). */
|
|
96
|
+
extent: ScheduleExtent;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* True when some wave has a member (specs/waves.md §9.1). With none, the
|
|
101
|
+
* roadmap draws no waves: no strip, no hatch, no legend. The barrier still
|
|
102
|
+
* runs, so wave floors and `after:<wave>` keep their dates.
|
|
103
|
+
*/
|
|
104
|
+
export function hasVisibleWaves(schedule: Pick<RoadmapSchedule, 'waves'>): boolean {
|
|
105
|
+
for (const wave of schedule.waves?.values() ?? []) {
|
|
106
|
+
if (wave.memberCount > 0) return true;
|
|
107
|
+
}
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A wave's solved span (specs/waves.md §5.1). */
|
|
112
|
+
export interface ScheduledWave {
|
|
113
|
+
/** 1-based declaration order. */
|
|
114
|
+
index: number;
|
|
115
|
+
/** `S_k` (UTC midnight). */
|
|
116
|
+
start: Date;
|
|
117
|
+
/** `E_k`, exclusive (UTC midnight); equals `start` for an empty wave. */
|
|
118
|
+
end: Date;
|
|
119
|
+
/** Members placed in the main lanes and first-level isolated regions. */
|
|
120
|
+
memberCount: number;
|
|
121
|
+
/** The member that sets `E_k` (id ?? title); omitted when none ends past `S_k`. */
|
|
122
|
+
heldBy?: string;
|
|
123
|
+
/** The wave's `after:` element that set `S_k`, when it beat the previous wave's end. */
|
|
124
|
+
floorRef?: string;
|
|
61
125
|
}
|
|
62
126
|
|
|
63
127
|
export interface ScheduleOptions {
|
|
64
128
|
/** Passed as the reference date when the roadmap omits `start:`. */
|
|
65
129
|
today?: Date;
|
|
130
|
+
/**
|
|
131
|
+
* The roadmap's wave plan from `buildWavePlan(resolved)`, when the caller
|
|
132
|
+
* already built it; `null` means "built, no waves". Omitted: built here.
|
|
133
|
+
*/
|
|
134
|
+
plan?: WavePlan | null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** What the date window needs from the schedule (specs/scheduler-unification.md §3.2). */
|
|
138
|
+
export interface ScheduleExtent {
|
|
139
|
+
/**
|
|
140
|
+
* Latest working-day offset from `startDate` reached by any lane cursor
|
|
141
|
+
* (item ends and container starts), any dated anchor, any milestone, and,
|
|
142
|
+
* with waves, every `S_k` and `E_n`. Isolated regions count too: without
|
|
143
|
+
* waves every region at any depth with its anchors and milestones, with
|
|
144
|
+
* waves the first-level regions' lanes. `today` is not included.
|
|
145
|
+
*/
|
|
146
|
+
contentEndDay: number;
|
|
66
147
|
}
|
|
67
148
|
|
|
68
149
|
/**
|
|
@@ -74,144 +155,311 @@ export function scheduleRoadmap(
|
|
|
74
155
|
resolved: ResolveResult,
|
|
75
156
|
options: ScheduleOptions = {},
|
|
76
157
|
): RoadmapSchedule {
|
|
77
|
-
const
|
|
158
|
+
const resolvedCalendar = resolveWorkingCalendar(file, resolved);
|
|
159
|
+
const { config: cal, working: calendar } = resolvedCalendar;
|
|
78
160
|
const sizes = resolveSizes(resolved.content.sizes, cal);
|
|
79
161
|
|
|
80
|
-
// Resolve roadmap start date — same precedence as
|
|
162
|
+
// Resolve roadmap start date — same precedence as the date-window phase.
|
|
81
163
|
const startRaw = propValue(file.roadmapDecl?.properties ?? [], 'start');
|
|
82
|
-
const startDate =
|
|
164
|
+
const startDate = parseIsoDate(startRaw) ?? utcMidnight(options.today ?? new Date());
|
|
165
|
+
// Working-day index of a date, and back: a point and an exclusive end.
|
|
166
|
+
const dayOf = (date: Date): number => calendar.workingIndexOf(startDate, date);
|
|
167
|
+
const pointAt = (day: number): Date => calendar.dateAtWorkingIndex(startDate, day);
|
|
168
|
+
const endAt = (start: number, end: number): Date =>
|
|
169
|
+
spanEndDate(calendar, startDate, start, end);
|
|
170
|
+
|
|
171
|
+
// The latest day any lane cursor, anchor, milestone or wave edge reaches.
|
|
172
|
+
let contentEndDay = 0;
|
|
173
|
+
const reachMax = (day: number): void => {
|
|
174
|
+
contentEndDay = Math.max(contentEndDay, day);
|
|
175
|
+
};
|
|
83
176
|
|
|
84
177
|
// These maps accumulate end-day offsets (from startDate) for cross-entity
|
|
85
|
-
// `after:` resolution
|
|
86
|
-
|
|
178
|
+
// `after:` resolution. In a roadmap with waves the item maps are rebuilt on
|
|
179
|
+
// every barrier pass.
|
|
180
|
+
let itemEnd = new Map<string, number>(); // item, group, parallel or wave id → end day
|
|
87
181
|
const anchorEnd = new Map<string, number>(); // id → date day
|
|
88
182
|
const milestoneEnd = new Map<string, number>(); // id → date day
|
|
89
183
|
|
|
90
184
|
// Result maps (Date objects).
|
|
91
|
-
|
|
92
|
-
|
|
185
|
+
let itemResults = new Map<string, ScheduledItem>();
|
|
186
|
+
let itemByNode = new WeakMap<ItemDeclaration, ScheduledItem>();
|
|
93
187
|
const milestoneResults = new Map<string, Date>();
|
|
94
188
|
const milestoneByNode = new WeakMap<MilestoneDeclaration, Date>();
|
|
95
189
|
const anchorResults = new Map<string, Date>();
|
|
96
190
|
|
|
97
|
-
// Resolve a single `after:` element to a day-offset.
|
|
98
|
-
const resolveAfterDay = (ref: string): number => {
|
|
99
|
-
const inlineDate = parseDate(ref);
|
|
100
|
-
if (inlineDate) return daysBetween(startDate, inlineDate);
|
|
101
|
-
if (itemEnd.has(ref)) return itemEnd.get(ref)!;
|
|
102
|
-
if (anchorEnd.has(ref)) return anchorEnd.get(ref)!;
|
|
103
|
-
if (milestoneEnd.has(ref)) return milestoneEnd.get(ref)!;
|
|
104
|
-
return 0;
|
|
105
|
-
};
|
|
106
|
-
|
|
107
191
|
// Pre-seed named anchors (they may be referenced by item after: before we
|
|
108
192
|
// walk the lanes).
|
|
109
193
|
for (const [id, anchor] of resolved.content.anchors) {
|
|
110
|
-
const d =
|
|
194
|
+
const d = parseIsoDate(propValue(anchor.properties, 'date'));
|
|
111
195
|
if (d) {
|
|
112
|
-
const day =
|
|
196
|
+
const day = dayOf(d);
|
|
113
197
|
anchorEnd.set(id, day);
|
|
114
|
-
anchorResults.set(id,
|
|
198
|
+
anchorResults.set(id, d);
|
|
199
|
+
reachMax(day);
|
|
115
200
|
}
|
|
116
201
|
}
|
|
117
202
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
203
|
+
// The id maps one walk resolves `after:` against: the main lanes share
|
|
204
|
+
// the maps above; an isolated region gets fresh maps.
|
|
205
|
+
interface WalkScope {
|
|
206
|
+
itemEnd: Map<string, number>;
|
|
207
|
+
anchorEnd: ReadonlyMap<string, number>;
|
|
208
|
+
milestoneEnd: ReadonlyMap<string, number>;
|
|
209
|
+
sizes: typeof sizes;
|
|
210
|
+
/** Id-keyed results; omitted for a region, whose ids stay invisible. */
|
|
211
|
+
itemResults?: Map<string, ScheduledItem>;
|
|
212
|
+
/** False for a nested region: its items count in the extent only. */
|
|
213
|
+
inByNode: boolean;
|
|
214
|
+
}
|
|
215
|
+
const mainScope = (): WalkScope => ({
|
|
216
|
+
itemEnd,
|
|
217
|
+
anchorEnd,
|
|
218
|
+
milestoneEnd,
|
|
219
|
+
sizes,
|
|
220
|
+
itemResults,
|
|
221
|
+
inByNode: true,
|
|
222
|
+
});
|
|
127
223
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
if (
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
224
|
+
// `wave` is the current barrier pass, undefined when there are no waves;
|
|
225
|
+
// `reach` receives every lane cursor, so a container start that passes
|
|
226
|
+
// every child (an empty `group after:x`) still counts in the extent.
|
|
227
|
+
const makeWalker = (
|
|
228
|
+
scope: WalkScope,
|
|
229
|
+
wave: WavePass | undefined,
|
|
230
|
+
reach: (day: number) => void,
|
|
231
|
+
) => {
|
|
232
|
+
// Resolve a single `after:` element to a day-offset.
|
|
233
|
+
const resolveAfterDay = (term: AfterTerm): number => {
|
|
234
|
+
if ('date' in term) return dayOf(term.date);
|
|
235
|
+
const ref = term.ref;
|
|
236
|
+
if (scope.itemEnd.has(ref)) return scope.itemEnd.get(ref)!;
|
|
237
|
+
if (scope.anchorEnd.has(ref)) return scope.anchorEnd.get(ref)!;
|
|
238
|
+
if (scope.milestoneEnd.has(ref)) return scope.milestoneEnd.get(ref)!;
|
|
239
|
+
return 0;
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
// Walk a sequential lane or group, returning the end-day of the last
|
|
243
|
+
// child. Core's `trackChildren` says what a child is; the recursion
|
|
244
|
+
// stays here because it carries the cursor and the wave pass.
|
|
245
|
+
const walkLane = (
|
|
246
|
+
container: SwimlaneDeclaration | GroupBlock,
|
|
247
|
+
baselineEnd: number,
|
|
248
|
+
): number => {
|
|
249
|
+
let prevEnd = baselineEnd;
|
|
250
|
+
for (const child of trackChildren(container)) {
|
|
251
|
+
prevEnd = walkNode(child, prevEnd);
|
|
252
|
+
reach(prevEnd);
|
|
144
253
|
}
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
254
|
+
return prevEnd;
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
const walkNode = (node: TrackNode, prevEnd: number): number => {
|
|
258
|
+
if (isItemDeclaration(node)) {
|
|
259
|
+
const dur = deriveItemDurationDays(node.properties, scope.sizes, cal);
|
|
260
|
+
const { after } = startTermsOf(node.properties);
|
|
261
|
+
let start =
|
|
262
|
+
after.length > 0 ? Math.max(prevEnd, ...after.map(resolveAfterDay)) : prevEnd;
|
|
263
|
+
// The barrier floor is one more term in the max.
|
|
264
|
+
if (wave) start = wave.apply(node, start);
|
|
265
|
+
const end = start + dur;
|
|
266
|
+
const scheduled: ScheduledItem = {
|
|
267
|
+
start: pointAt(start),
|
|
268
|
+
end: endAt(start, end),
|
|
269
|
+
days: dur,
|
|
270
|
+
};
|
|
271
|
+
if (scope.inByNode) itemByNode.set(node, scheduled);
|
|
272
|
+
if (node.name) {
|
|
273
|
+
scope.itemEnd.set(node.name, end);
|
|
274
|
+
scope.itemResults?.set(node.name, scheduled);
|
|
275
|
+
}
|
|
276
|
+
wave?.accumulate(node, end);
|
|
277
|
+
return end;
|
|
154
278
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
if (
|
|
166
|
-
|
|
167
|
-
|
|
279
|
+
if (isParallelBlock(node)) {
|
|
280
|
+
const { after } = startTermsOf(node.properties);
|
|
281
|
+
let containerStart =
|
|
282
|
+
after.length > 0 ? Math.max(prevEnd, ...after.map(resolveAfterDay)) : prevEnd;
|
|
283
|
+
if (wave) containerStart = wave.apply(node, containerStart);
|
|
284
|
+
let parallelEnd = containerStart;
|
|
285
|
+
for (const child of trackChildren(node)) {
|
|
286
|
+
parallelEnd = Math.max(parallelEnd, walkNode(child, containerStart));
|
|
287
|
+
}
|
|
288
|
+
// `after:` a parallel waits for its latest track end.
|
|
289
|
+
if (node.name) scope.itemEnd.set(node.name, parallelEnd);
|
|
290
|
+
return parallelEnd;
|
|
291
|
+
}
|
|
292
|
+
if (isGroupBlock(node)) {
|
|
293
|
+
const { after } = startTermsOf(node.properties);
|
|
294
|
+
let containerStart =
|
|
295
|
+
after.length > 0 ? Math.max(prevEnd, ...after.map(resolveAfterDay)) : prevEnd;
|
|
296
|
+
if (wave) containerStart = wave.apply(node, containerStart);
|
|
297
|
+
// `after:` a group waits for its last child's end.
|
|
298
|
+
const groupEnd = walkLane(node, containerStart);
|
|
299
|
+
if (node.name) scope.itemEnd.set(node.name, groupEnd);
|
|
300
|
+
return groupEnd;
|
|
301
|
+
}
|
|
302
|
+
return prevEnd;
|
|
303
|
+
};
|
|
304
|
+
|
|
305
|
+
return { walkLane, resolveAfterDay };
|
|
306
|
+
};
|
|
307
|
+
|
|
308
|
+
// A milestone is a point: its `date:`, else its latest predecessor end.
|
|
309
|
+
// Returns the day, or undefined when it has neither.
|
|
310
|
+
const milestoneDay = (
|
|
311
|
+
ms: MilestoneDeclaration,
|
|
312
|
+
resolveAfterDay: (term: AfterTerm) => number,
|
|
313
|
+
): number | undefined => {
|
|
314
|
+
const d = parseIsoDate(propValue(ms.properties, 'date'));
|
|
315
|
+
if (d) return dayOf(d);
|
|
316
|
+
const { after } = startTermsOf(ms.properties);
|
|
317
|
+
return after.length > 0 ? Math.max(0, ...after.map(resolveAfterDay)) : undefined;
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
// An isolated region in a roadmap without waves: fresh id maps, its own
|
|
321
|
+
// sizes (specs/scheduler-unification.md §8, decision 2), its anchors and
|
|
322
|
+
// milestones (resolved inside the region and counted in the extent, never
|
|
323
|
+
// in the results) and its nested regions.
|
|
324
|
+
// First-level region items join `byNode`; their ids stay out of `items`.
|
|
325
|
+
const scheduleRegion = (content: ResolvedContent, firstLevel: boolean): void => {
|
|
326
|
+
const regionItemEnd = new Map<string, number>();
|
|
327
|
+
const regionAnchorEnd = new Map<string, number>();
|
|
328
|
+
const regionMilestoneEnd = new Map<string, number>();
|
|
329
|
+
for (const [id, anchor] of content.anchors) {
|
|
330
|
+
const d = parseIsoDate(propValue(anchor.properties, 'date'));
|
|
331
|
+
if (d) {
|
|
332
|
+
const day = dayOf(d);
|
|
333
|
+
regionAnchorEnd.set(id, day);
|
|
334
|
+
reachMax(day);
|
|
168
335
|
}
|
|
169
|
-
return parallelEnd;
|
|
170
336
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
337
|
+
const { walkLane, resolveAfterDay } = makeWalker(
|
|
338
|
+
{
|
|
339
|
+
itemEnd: regionItemEnd,
|
|
340
|
+
anchorEnd: regionAnchorEnd,
|
|
341
|
+
milestoneEnd: regionMilestoneEnd,
|
|
342
|
+
sizes: resolveSizes(content.sizes, cal),
|
|
343
|
+
inByNode: firstLevel,
|
|
344
|
+
},
|
|
345
|
+
undefined,
|
|
346
|
+
reachMax,
|
|
347
|
+
);
|
|
348
|
+
for (const lane of content.swimlanes.values()) walkLane(lane, 0);
|
|
349
|
+
for (const [id, ms] of content.milestones) {
|
|
350
|
+
const day = milestoneDay(ms, resolveAfterDay);
|
|
351
|
+
if (day === undefined) continue;
|
|
352
|
+
regionMilestoneEnd.set(id, day);
|
|
353
|
+
reachMax(day);
|
|
178
354
|
}
|
|
179
|
-
|
|
355
|
+
for (const nested of content.isolatedRegions) scheduleRegion(nested.content, false);
|
|
180
356
|
};
|
|
181
357
|
|
|
182
|
-
|
|
183
|
-
|
|
358
|
+
const plan = options.plan !== undefined ? (options.plan ?? undefined) : buildWavePlan(resolved);
|
|
359
|
+
let waves: Map<string, ScheduledWave> | undefined;
|
|
360
|
+
if (!plan) {
|
|
361
|
+
const { walkLane } = makeWalker(mainScope(), undefined, reachMax);
|
|
362
|
+
for (const lane of resolved.content.swimlanes.values()) {
|
|
363
|
+
walkLane(lane, 0);
|
|
364
|
+
}
|
|
365
|
+
} else {
|
|
366
|
+
// Wave barriers (specs/waves.md §8.5): iterate the lane walk to the
|
|
367
|
+
// least fixpoint. The final pass's results are the schedule.
|
|
368
|
+
const ids = plan.waves.map((w) => w.name as string);
|
|
369
|
+
const floors = waveFloorDays(plan, startDate, calendar);
|
|
370
|
+
let finalPass: WavePass | undefined;
|
|
371
|
+
// The extent of the latest pass; the final one ran with the solved S/E.
|
|
372
|
+
let passMax = 0;
|
|
373
|
+
const reachPass = (day: number): void => {
|
|
374
|
+
passMax = Math.max(passMax, day);
|
|
375
|
+
};
|
|
376
|
+
const result = solveWaveBarriers(ids.length, 0, floors, (S, E) => {
|
|
377
|
+
// Reset the item maps, keep the anchors, and seed each wave id
|
|
378
|
+
// with E_k so `after:<wave>` resolves to the wave's end.
|
|
379
|
+
const seeds = ids.map((id, i): [string, number] => [id, E[i]]);
|
|
380
|
+
itemEnd = new Map(seeds);
|
|
381
|
+
itemResults = new Map();
|
|
382
|
+
itemByNode = new WeakMap();
|
|
383
|
+
passMax = 0;
|
|
384
|
+
const pass = new WavePass(plan, S);
|
|
385
|
+
const { walkLane } = makeWalker(mainScope(), pass, reachPass);
|
|
386
|
+
for (const lane of resolved.content.swimlanes.values()) {
|
|
387
|
+
walkLane(lane, 0);
|
|
388
|
+
}
|
|
389
|
+
// Exactly one level of isolated regions, each with fresh id maps
|
|
390
|
+
// seeded only with the wave edges. Region items join the barrier
|
|
391
|
+
// and `byNode`; their ids stay out of `items`. Region anchors,
|
|
392
|
+
// region milestones and nested regions are not walked: engine A
|
|
393
|
+
// draws none of them.
|
|
394
|
+
for (const region of resolved.content.isolatedRegions) {
|
|
395
|
+
const regionWalker = makeWalker(
|
|
396
|
+
{
|
|
397
|
+
itemEnd: new Map(seeds),
|
|
398
|
+
anchorEnd: new Map(),
|
|
399
|
+
milestoneEnd: new Map(),
|
|
400
|
+
sizes: resolveSizes(region.content.sizes, cal),
|
|
401
|
+
inByNode: true,
|
|
402
|
+
},
|
|
403
|
+
pass,
|
|
404
|
+
reachPass,
|
|
405
|
+
);
|
|
406
|
+
for (const lane of region.content.swimlanes.values()) {
|
|
407
|
+
regionWalker.walkLane(lane, 0);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
finalPass = pass;
|
|
411
|
+
return pass;
|
|
412
|
+
});
|
|
413
|
+
// The content end covers the last wave's end and every wave start,
|
|
414
|
+
// so floors and empty trailing waves stay inside the window.
|
|
415
|
+
reachMax(Math.max(passMax, ...result.S, ...result.E.slice(-1)));
|
|
416
|
+
waves = new Map();
|
|
417
|
+
for (const span of summarizeWaves(plan, result, finalPass!, 0, floors)) {
|
|
418
|
+
waves.set(span.id, {
|
|
419
|
+
index: span.index,
|
|
420
|
+
start: pointAt(span.start),
|
|
421
|
+
end: endAt(span.start, span.end),
|
|
422
|
+
memberCount: span.memberCount,
|
|
423
|
+
...(span.heldBy !== undefined ? { heldBy: span.heldBy } : {}),
|
|
424
|
+
...(span.floorRef !== undefined ? { floorRef: span.floorRef } : {}),
|
|
425
|
+
});
|
|
426
|
+
}
|
|
184
427
|
}
|
|
185
428
|
|
|
186
|
-
// Milestones
|
|
187
|
-
//
|
|
429
|
+
// Milestones, after the items so after: can resolve item end-days, and
|
|
430
|
+
// wave ids their E_k.
|
|
431
|
+
const { resolveAfterDay } = makeWalker(mainScope(), undefined, reachMax);
|
|
188
432
|
for (const [id, ms] of resolved.content.milestones) {
|
|
189
|
-
const
|
|
190
|
-
if (
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
milestoneByNode.set(ms, resolved2);
|
|
433
|
+
const day = milestoneDay(ms, resolveAfterDay);
|
|
434
|
+
if (day === undefined) continue;
|
|
435
|
+
// A dated milestone keeps its own date; an after-only one is a point
|
|
436
|
+
// at the latest predecessor end: the next working day.
|
|
437
|
+
const date = parseIsoDate(propValue(ms.properties, 'date')) ?? pointAt(day);
|
|
438
|
+
milestoneEnd.set(id, day);
|
|
439
|
+
milestoneResults.set(id, date);
|
|
440
|
+
milestoneByNode.set(ms, date);
|
|
441
|
+
reachMax(day);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
// Isolated regions without waves (with waves they joined the passes).
|
|
445
|
+
if (!plan) {
|
|
446
|
+
for (const region of resolved.content.isolatedRegions) {
|
|
447
|
+
scheduleRegion(region.content, true);
|
|
205
448
|
}
|
|
206
449
|
}
|
|
207
450
|
|
|
208
451
|
return {
|
|
209
452
|
startDate,
|
|
453
|
+
calendar: resolvedCalendar,
|
|
210
454
|
items: itemResults,
|
|
211
455
|
byNode: itemByNode,
|
|
212
456
|
milestones: milestoneResults,
|
|
213
457
|
milestoneByNode,
|
|
214
458
|
anchors: anchorResults,
|
|
459
|
+
...(waves ? { waves } : {}),
|
|
460
|
+
...(plan ? { plan } : {}),
|
|
461
|
+
resolved,
|
|
462
|
+
extent: { contentEndDay },
|
|
215
463
|
};
|
|
216
464
|
}
|
|
217
465
|
|