@nowline/layout 0.8.6 → 0.10.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/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 -152
- 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 -30
- 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 -145
- 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 +4 -4
- 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 -214
- 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 -38
- 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 -205
- 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/layout-context.ts
CHANGED
|
@@ -1,19 +1,26 @@
|
|
|
1
|
-
// Layout context — shared internal types used by
|
|
2
|
-
// node files under `nodes/`. Extracted so per-entity
|
|
3
|
-
// `swimlane-node.ts`) can type their inputs without
|
|
4
|
-
//
|
|
1
|
+
// Layout context — shared internal types used by the layout phases
|
|
2
|
+
// (`phases/`) and the node files under `nodes/`. Extracted so per-entity
|
|
3
|
+
// nodes (e.g. `swimlane-node.ts`) can type their inputs without importing
|
|
4
|
+
// the phases that drive them.
|
|
5
5
|
|
|
6
|
-
import
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
LabelDeclaration,
|
|
11
|
-
ParallelBlock,
|
|
12
|
-
|
|
6
|
+
import {
|
|
7
|
+
type GroupBlock,
|
|
8
|
+
type ItemDeclaration,
|
|
9
|
+
isTeamDeclaration,
|
|
10
|
+
type LabelDeclaration,
|
|
11
|
+
type ParallelBlock,
|
|
12
|
+
type ResolvedConfig,
|
|
13
|
+
type ResolvedContent,
|
|
14
|
+
type SymbolDeclaration,
|
|
15
|
+
type TeamDeclaration,
|
|
13
16
|
} from '@nowline/core';
|
|
17
|
+
import type { AxisColumns } from './axis-columns.js';
|
|
14
18
|
import type { BandScale } from './band-scale.js';
|
|
15
|
-
import type
|
|
19
|
+
import { type resolveCalendar, resolveSizes } from './calendar.js';
|
|
20
|
+
import type { FootnoteIndex } from './nodes/footnote-node.js';
|
|
21
|
+
import type { RoadmapSchedule } from './schedule.js';
|
|
16
22
|
import type { StyleContext } from './style-resolution.js';
|
|
23
|
+
import type { Theme } from './themes/shape.js';
|
|
17
24
|
import type { TimeScale } from './time-scale.js';
|
|
18
25
|
import type {
|
|
19
26
|
MarkerRowPlacement,
|
|
@@ -24,22 +31,49 @@ import type {
|
|
|
24
31
|
ResolvedSize,
|
|
25
32
|
SlackCorridor,
|
|
26
33
|
} from './types.js';
|
|
34
|
+
import type { WaveLayoutState } from './wave-layout.js';
|
|
27
35
|
import type { WorkingCalendar } from './working-calendar.js';
|
|
28
36
|
|
|
29
37
|
/** Slim accumulator used while sequencing items into a track. */
|
|
30
38
|
export interface TrackCursor {
|
|
31
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* Where the next item begins, in time: the last child's duration end
|
|
41
|
+
* (a block's latest track end), never a caption spill. A bar spans
|
|
42
|
+
* exactly its dates, so this is also its painted end
|
|
43
|
+
* (specs/true-width-layout.md §3.1).
|
|
44
|
+
*/
|
|
32
45
|
x: number;
|
|
46
|
+
/**
|
|
47
|
+
* Where the child placed last begins, in time: an item's start date, a
|
|
48
|
+
* block's earliest child start. A container's bracket and body open
|
|
49
|
+
* here (specs/true-width-layout.md §3.3).
|
|
50
|
+
*/
|
|
51
|
+
startX: number;
|
|
33
52
|
/** Top edge of the current row. */
|
|
34
53
|
y: number;
|
|
35
54
|
/** Accumulated height of the track. */
|
|
36
55
|
height: number;
|
|
37
|
-
/** Rightmost edge reached. */
|
|
38
|
-
maxX: number;
|
|
39
56
|
}
|
|
40
57
|
|
|
41
58
|
export function newCursor(x: number, y: number): TrackCursor {
|
|
42
|
-
return { x, y, height: 0
|
|
59
|
+
return { x, startX: x, y, height: 0 };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* The working-day duration the day schedule gave `item`. Every item engine A
|
|
64
|
+
* lays out is in `schedule.byNode` (the main lanes and the first-level
|
|
65
|
+
* isolated regions it draws), so a miss is an invariant violation, a
|
|
66
|
+
* schedule built for another roadmap or a walker that skipped a node, and
|
|
67
|
+
* throws instead of falling back to a second derivation that could drift.
|
|
68
|
+
*/
|
|
69
|
+
export function scheduledItemDays(ctx: LayoutContext, item: ItemDeclaration): number {
|
|
70
|
+
const scheduled = ctx.schedule.byNode.get(item);
|
|
71
|
+
if (!scheduled) {
|
|
72
|
+
throw new Error(
|
|
73
|
+
`layout: item "${item.name ?? item.title ?? '(unnamed)'}" is not in the day schedule`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
return scheduled.days;
|
|
43
77
|
}
|
|
44
78
|
|
|
45
79
|
/**
|
|
@@ -49,10 +83,18 @@ export function newCursor(x: number, y: number): TrackCursor {
|
|
|
49
83
|
*/
|
|
50
84
|
export interface LayoutContext {
|
|
51
85
|
cal: ReturnType<typeof resolveCalendar>;
|
|
86
|
+
/**
|
|
87
|
+
* The roadmap's day schedule (engine C), shared by the main lanes and
|
|
88
|
+
* every isolated region's context. Item durations in working days come
|
|
89
|
+
* from `schedule.byNode` (`scheduledItemDays`), never from re-deriving
|
|
90
|
+
* them against the sizes (specs/scheduler-unification.md §3.4).
|
|
91
|
+
*/
|
|
92
|
+
schedule: RoadmapSchedule;
|
|
52
93
|
styleCtx: StyleContext;
|
|
53
94
|
sizes: Map<string, ResolvedSize>;
|
|
54
95
|
labels: Map<string, LabelDeclaration>;
|
|
55
|
-
|
|
96
|
+
/** Every team by id, the ones nested in another team included. */
|
|
97
|
+
teams: Map<string, TeamDeclaration>;
|
|
56
98
|
persons: Map<string, import('@nowline/core').PersonDeclaration>;
|
|
57
99
|
/**
|
|
58
100
|
* Custom `symbol` declarations from `ResolvedConfig.symbols`. Used by
|
|
@@ -67,31 +109,42 @@ export interface LayoutContext {
|
|
|
67
109
|
timeline: PositionedTimelineScale;
|
|
68
110
|
scale: TimeScale;
|
|
69
111
|
calendar: WorkingCalendar;
|
|
112
|
+
/**
|
|
113
|
+
* The natural axis as tick columns, for the caption cap at the chart's
|
|
114
|
+
* end (`measureItem`). Shared by the main lanes and every isolated
|
|
115
|
+
* region; omitted by contexts built outside the place phase, which then
|
|
116
|
+
* cap no caption.
|
|
117
|
+
*/
|
|
118
|
+
axisColumns?: AxisColumns;
|
|
70
119
|
bandScale: BandScale;
|
|
71
120
|
entityLeftEdges: Map<string, number>;
|
|
72
121
|
entityRightEdges: Map<string, number>;
|
|
122
|
+
/**
|
|
123
|
+
* Midpoint per MARKER (anchor or milestone), on the marker row.
|
|
124
|
+
* Dependency arrows from a marker start on its cut line at this `x`.
|
|
125
|
+
* Items are not in this map: their attach geometry is derived from
|
|
126
|
+
* `placedItems`.
|
|
127
|
+
*/
|
|
73
128
|
entityMidpoints: Map<string, Point>;
|
|
74
129
|
/**
|
|
75
|
-
*
|
|
76
|
-
* from
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
* the inter-column gutter. Anchors and milestones are absent —
|
|
80
|
-
* their attach geometry uses `(center.x, target.row.midY)` on
|
|
81
|
-
* the cut line, computed inline by `buildDependencies`.
|
|
130
|
+
* The ids of the anchors and milestones this context's file declares.
|
|
131
|
+
* A milestone's slack or overrun arrow from one of them starts on its
|
|
132
|
+
* row in the marker band, which only the final pack knows, so
|
|
133
|
+
* placement records the marker instead of a y.
|
|
82
134
|
*/
|
|
83
|
-
|
|
84
|
-
entityVisualRightX: Map<string, number>;
|
|
135
|
+
markerIds: ReadonlySet<string>;
|
|
85
136
|
/**
|
|
86
|
-
*
|
|
87
|
-
*
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
*
|
|
91
|
-
*
|
|
92
|
-
*
|
|
137
|
+
* Every placed item, keyed by its draw key (the explicit id, or the
|
|
138
|
+
* synthetic handle of an id-less item; see `syntheticItemKey`). The
|
|
139
|
+
* value is the same `PositionedItem` object the positioned model
|
|
140
|
+
* carries, so it always holds the item's FINAL box, however many
|
|
141
|
+
* times a retroactive row growth or a marker-band growth moved it
|
|
142
|
+
* down after placement. Dependency-arrow ports and slack-arrow attach
|
|
143
|
+
* points are derived from that box when the arrows are built (see
|
|
144
|
+
* `item-port-geometry.ts`), never captured as absolute coordinates
|
|
145
|
+
* while the item is placed.
|
|
93
146
|
*/
|
|
94
|
-
|
|
147
|
+
placedItems: Map<string, PositionedItem>;
|
|
95
148
|
/**
|
|
96
149
|
* Flow key for each item, used to dedupe milestone slack arrows.
|
|
97
150
|
* A "flow" is the deepest enclosing single-track container —
|
|
@@ -109,15 +162,6 @@ export interface LayoutContext {
|
|
|
109
162
|
* value to populate `itemFlowKey`.
|
|
110
163
|
*/
|
|
111
164
|
currentFlowKey: string;
|
|
112
|
-
/**
|
|
113
|
-
* Y coordinate where milestone slack arrows attach for each item id.
|
|
114
|
-
* Defaults to the item's row midpoint; when an item's caption spills
|
|
115
|
-
* past the bar's right edge, drops to the progress-strip's vertical
|
|
116
|
-
* center (`box.y + box.height - PROGRESS_STRIP_HEIGHT_PX / 2`) so the
|
|
117
|
-
* arrow stays clear of the spilled title/meta line and visually
|
|
118
|
-
* aligns with the bottom-edge progress bar instead.
|
|
119
|
-
*/
|
|
120
|
-
itemSlackAttachY: Map<string, number>;
|
|
121
165
|
/**
|
|
122
166
|
* Horizontal arrow corridors that the swimlane row-packer must avoid.
|
|
123
167
|
* Empty during the first layout pass; populated from the first
|
|
@@ -127,12 +171,19 @@ export interface LayoutContext {
|
|
|
127
171
|
*/
|
|
128
172
|
slackCorridors: SlackCorridor[];
|
|
129
173
|
/**
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
133
|
-
*
|
|
174
|
+
* Marker-row placement (row index + label box + side). While lanes
|
|
175
|
+
* are placed it holds a copy of the measure phase's pre-pack of every
|
|
176
|
+
* date-pinned anchor and milestone; finalize then replaces it with the
|
|
177
|
+
* final pack (f5), which adds after-only milestones and the calendar
|
|
178
|
+
* runs, before milestones are positioned (f6) and anchors built (f10).
|
|
134
179
|
*/
|
|
135
180
|
markerRowPlacements: Map<string, MarkerRowPlacement>;
|
|
181
|
+
/**
|
|
182
|
+
* The chart's top. Placement lays the chart out in local coordinates,
|
|
183
|
+
* with the top at 0; finalize translates every placed y once, by the
|
|
184
|
+
* final top, when the header rows above it stop growing (f7), and
|
|
185
|
+
* sets this to that top. A region's context keeps its local value.
|
|
186
|
+
*/
|
|
136
187
|
chartTopY: number;
|
|
137
188
|
chartBottomY: number;
|
|
138
189
|
/**
|
|
@@ -144,7 +195,6 @@ export interface LayoutContext {
|
|
|
144
195
|
* to thread the entire timeline strip.
|
|
145
196
|
*/
|
|
146
197
|
swimlaneBottomY: number;
|
|
147
|
-
chartRightX: number;
|
|
148
198
|
/**
|
|
149
199
|
* Monotonic counters for id-less `parallel` / `group` blocks. Each
|
|
150
200
|
* swimlane-loop pass resets both to zero; blocks without an explicit
|
|
@@ -153,38 +203,126 @@ export interface LayoutContext {
|
|
|
153
203
|
*/
|
|
154
204
|
nextParallelId: number;
|
|
155
205
|
nextGroupId: number;
|
|
206
|
+
/**
|
|
207
|
+
* Wave barrier state (specs/waves.md §8.4), shared by the main lanes
|
|
208
|
+
* and every isolated region's context. Undefined when the roadmap
|
|
209
|
+
* declares no waves.
|
|
210
|
+
*/
|
|
211
|
+
waves?: WaveLayoutState;
|
|
156
212
|
}
|
|
157
213
|
|
|
158
214
|
/**
|
|
159
|
-
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
162
|
-
|
|
163
|
-
|
|
215
|
+
* `teams` plus every team nested in one of them, so an `owner:` naming a
|
|
216
|
+
* nested team resolves to its title. The resolved content keys only the
|
|
217
|
+
* file's top-level declarations. A top-level id wins over a nested one.
|
|
218
|
+
*/
|
|
219
|
+
function withNestedTeams(teams: Map<string, TeamDeclaration>): Map<string, TeamDeclaration> {
|
|
220
|
+
const out = new Map(teams);
|
|
221
|
+
const visit = (team: TeamDeclaration): void => {
|
|
222
|
+
for (const member of team.content) {
|
|
223
|
+
if (!isTeamDeclaration(member)) continue;
|
|
224
|
+
if (member.name && !out.has(member.name)) out.set(member.name, member);
|
|
225
|
+
visit(member);
|
|
226
|
+
}
|
|
227
|
+
};
|
|
228
|
+
for (const team of teams.values()) visit(team);
|
|
229
|
+
return out;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** The per-file half of a context: the root file, or an isolated region. */
|
|
233
|
+
export interface ContextSource {
|
|
234
|
+
config: Pick<ResolvedConfig, 'styles' | 'defaults' | 'symbols'>;
|
|
235
|
+
content: Pick<
|
|
236
|
+
ResolvedContent,
|
|
237
|
+
'sizes' | 'labels' | 'teams' | 'persons' | 'anchors' | 'milestones'
|
|
238
|
+
>;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Build a `LayoutContext` for the root file or for an isolated region.
|
|
243
|
+
* `chart` passes the chart extents through unchanged: the root sets
|
|
244
|
+
* `chartTopY` to the local top 0 with both bottoms at 0, a region sets all
|
|
245
|
+
* three y fields to its inner start. The canvas width is not part of a
|
|
246
|
+
* context: placement never reads it (finalize owns the `CanvasExtent`).
|
|
247
|
+
* A region passes its own footnote index, numbered on after the host's,
|
|
248
|
+
* and no marker placements. The caller seeds
|
|
249
|
+
* the wave edges.
|
|
250
|
+
*/
|
|
251
|
+
export function createLayoutContext(
|
|
252
|
+
source: ContextSource,
|
|
253
|
+
shared: {
|
|
254
|
+
theme: Theme;
|
|
255
|
+
cal: ReturnType<typeof resolveCalendar>;
|
|
256
|
+
schedule: RoadmapSchedule;
|
|
257
|
+
timeline: PositionedTimelineScale;
|
|
258
|
+
scale: TimeScale;
|
|
259
|
+
calendar: WorkingCalendar;
|
|
260
|
+
axisColumns?: AxisColumns;
|
|
261
|
+
bandScale: BandScale;
|
|
262
|
+
waves?: WaveLayoutState;
|
|
263
|
+
},
|
|
264
|
+
chart: Pick<LayoutContext, 'chartTopY' | 'chartBottomY' | 'swimlaneBottomY'>,
|
|
265
|
+
init: {
|
|
266
|
+
footnotes?: FootnoteIndex;
|
|
267
|
+
markerRowPlacements?: Map<string, MarkerRowPlacement>;
|
|
268
|
+
},
|
|
269
|
+
): LayoutContext {
|
|
270
|
+
return {
|
|
271
|
+
cal: shared.cal,
|
|
272
|
+
schedule: shared.schedule,
|
|
273
|
+
styleCtx: {
|
|
274
|
+
theme: shared.theme,
|
|
275
|
+
styles: source.config.styles,
|
|
276
|
+
defaults: source.config.defaults,
|
|
277
|
+
labels: source.content.labels,
|
|
278
|
+
},
|
|
279
|
+
// Each file declares its own sizes, resolved under the host's
|
|
280
|
+
// calendar so a region's sized items render at the host's
|
|
281
|
+
// pixels-per-day scale.
|
|
282
|
+
sizes: resolveSizes(source.content.sizes, shared.cal),
|
|
283
|
+
labels: source.content.labels,
|
|
284
|
+
teams: withNestedTeams(source.content.teams),
|
|
285
|
+
persons: source.content.persons,
|
|
286
|
+
symbols: source.config.symbols,
|
|
287
|
+
footnoteIndex: init.footnotes?.index ?? new Map(),
|
|
288
|
+
footnoteHosts: init.footnotes?.hosts ?? new Map(),
|
|
289
|
+
timeline: shared.timeline,
|
|
290
|
+
scale: shared.scale,
|
|
291
|
+
calendar: shared.calendar,
|
|
292
|
+
...(shared.axisColumns ? { axisColumns: shared.axisColumns } : {}),
|
|
293
|
+
bandScale: shared.bandScale,
|
|
294
|
+
entityLeftEdges: new Map(),
|
|
295
|
+
entityRightEdges: new Map(),
|
|
296
|
+
entityMidpoints: new Map(),
|
|
297
|
+
markerIds: new Set([...source.content.anchors.keys(), ...source.content.milestones.keys()]),
|
|
298
|
+
placedItems: new Map(),
|
|
299
|
+
itemFlowKey: new Map(),
|
|
300
|
+
currentFlowKey: '',
|
|
301
|
+
slackCorridors: [],
|
|
302
|
+
markerRowPlacements: init.markerRowPlacements ?? new Map(),
|
|
303
|
+
chartTopY: chart.chartTopY,
|
|
304
|
+
chartBottomY: chart.chartBottomY,
|
|
305
|
+
swimlaneBottomY: chart.swimlaneBottomY,
|
|
306
|
+
nextParallelId: 0,
|
|
307
|
+
nextGroupId: 0,
|
|
308
|
+
// Wave floors are dates projected with the unclamped scale
|
|
309
|
+
// (specs/waves.md §5.1); a region shares the host's wave state
|
|
310
|
+
// (§8.4) so its lanes are floored and accumulate into the same pass.
|
|
311
|
+
...(shared.waves ? { waves: shared.waves } : {}),
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* The helpers the place phase injects into the nodes. Everything else a
|
|
317
|
+
* node needs it imports; `sequenceOne` (`nodes/sequence-one.ts`) stays
|
|
318
|
+
* injected because it dispatches to `GroupNode` and `ParallelNode`, which
|
|
319
|
+
* call it for their nested blocks: importing it there would form a cycle
|
|
320
|
+
* (specs/layout-phases.md §3.8, §8 decision 4).
|
|
164
321
|
*/
|
|
165
322
|
export interface LayoutHelpers {
|
|
166
|
-
sequenceItem: (
|
|
167
|
-
child: ItemDeclaration,
|
|
168
|
-
cursor: TrackCursor,
|
|
169
|
-
ctx: LayoutContext,
|
|
170
|
-
ownerOverride?: string,
|
|
171
|
-
) => PositionedItem;
|
|
172
323
|
sequenceOne: (
|
|
173
324
|
child: ItemDeclaration | GroupBlock | ParallelBlock,
|
|
174
325
|
cursor: TrackCursor,
|
|
175
326
|
ctx: LayoutContext,
|
|
176
327
|
) => PositionedTrackChild;
|
|
177
|
-
resolveChildStart: (
|
|
178
|
-
props: EntityProperty[],
|
|
179
|
-
seqDefault: number,
|
|
180
|
-
laneLeftX: number,
|
|
181
|
-
ctx: LayoutContext,
|
|
182
|
-
) => number;
|
|
183
|
-
newCursor: (x: number, y: number) => TrackCursor;
|
|
184
|
-
estimateTextWidth: (text: string, fontSize: number) => number;
|
|
185
|
-
/** Predict the extra height an item's wrapped label-chip rows will
|
|
186
|
-
* add, so callers can size the row pitch BEFORE handing off to
|
|
187
|
-
* `sequenceItem`. Returns 0 when the item's labels all fit on a
|
|
188
|
-
* single chip row. */
|
|
189
|
-
predictItemChipExtraHeight: (item: ItemDeclaration, ctx: LayoutContext) => number;
|
|
190
328
|
}
|
package/src/layout-insights.ts
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// model. Informational by default; warnings only when author intent is lost
|
|
3
3
|
// (e.g. now-line outside the date window).
|
|
4
4
|
|
|
5
|
-
import { type MessageCode, tr } from '@nowline/core';
|
|
5
|
+
import { type I1006Args, type MessageCode, tr } from '@nowline/core';
|
|
6
6
|
import { MIN_BAR_WIDTH_FOR_DOT_PX } from './item-bar-geometry.js';
|
|
7
7
|
import type {
|
|
8
8
|
PositionedItem,
|
|
@@ -32,8 +32,17 @@ function formatIsoDate(d: Date): string {
|
|
|
32
32
|
return d.toISOString().slice(0, 10);
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
+
/**
|
|
36
|
+
* The name an insight echoes for an item: its id, else its title. A title
|
|
37
|
+
* can carry explicit `\n` line breaks (they paint as separate lines in the
|
|
38
|
+
* chart), but an insight message is one line of text, so each break run
|
|
39
|
+
* collapses to a single space.
|
|
40
|
+
*/
|
|
35
41
|
function itemLabel(item: PositionedItem): string {
|
|
36
|
-
|
|
42
|
+
if (item.id !== undefined) return item.id;
|
|
43
|
+
return /[\r\n]/.test(item.title)
|
|
44
|
+
? item.title.replace(/\s*[\r\n]+\s*/g, ' ').trim()
|
|
45
|
+
: item.title;
|
|
37
46
|
}
|
|
38
47
|
|
|
39
48
|
function walkTrackChildren(
|
|
@@ -76,6 +85,27 @@ function collectItemInsights(item: PositionedItem, locale: string, out: LayoutIn
|
|
|
76
85
|
makeInsight(locale, 'NL.I1003', 'info', { name, anchor: item.overflowAnchorId }, name),
|
|
77
86
|
);
|
|
78
87
|
}
|
|
88
|
+
collectNonWorkingPinInsight(item, locale, out);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** NL.I1008 (specs/working-calendar.md §6): an `after:` date on a non-working day set the start. */
|
|
92
|
+
function collectNonWorkingPinInsight(
|
|
93
|
+
item: PositionedItem,
|
|
94
|
+
locale: string,
|
|
95
|
+
out: LayoutInsight[],
|
|
96
|
+
): void {
|
|
97
|
+
const pin = item.nonWorkingPin;
|
|
98
|
+
if (!pin) return;
|
|
99
|
+
const name = itemLabel(item);
|
|
100
|
+
out.push(
|
|
101
|
+
makeInsight(
|
|
102
|
+
locale,
|
|
103
|
+
'NL.I1008',
|
|
104
|
+
'info',
|
|
105
|
+
{ name, pin: pin.pin, key: pin.key, start: pin.start },
|
|
106
|
+
name,
|
|
107
|
+
),
|
|
108
|
+
);
|
|
79
109
|
}
|
|
80
110
|
|
|
81
111
|
function makeInsight(
|
|
@@ -125,6 +155,77 @@ function collectSwimlaneInsights(
|
|
|
125
155
|
}
|
|
126
156
|
}
|
|
127
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Pin insights on the items of an isolated region's lanes. Region items
|
|
160
|
+
* carry only the non-working pin (NL.I1008) here; the other item and lane
|
|
161
|
+
* insights stay main-lane only.
|
|
162
|
+
*/
|
|
163
|
+
function collectRegionItemInsights(
|
|
164
|
+
lane: PositionedSwimlane,
|
|
165
|
+
locale: string,
|
|
166
|
+
out: LayoutInsight[],
|
|
167
|
+
): void {
|
|
168
|
+
walkTrackChildren(lane.children, (item) => {
|
|
169
|
+
collectNonWorkingPinInsight(item, locale, out);
|
|
170
|
+
});
|
|
171
|
+
for (const nested of lane.nested) {
|
|
172
|
+
collectRegionItemInsights(nested, locale, out);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Roadmap-level wave insights (specs/waves.md WV16-WV18): the barrier
|
|
178
|
+
* driver's pass cap, empty waves (once per roadmap), and dated milestones a
|
|
179
|
+
* wave overruns. No-ops without waves.
|
|
180
|
+
*/
|
|
181
|
+
function collectWaveInsights(
|
|
182
|
+
layout: PositionedRoadmap,
|
|
183
|
+
locale: string,
|
|
184
|
+
out: LayoutInsight[],
|
|
185
|
+
): void {
|
|
186
|
+
if (layout.waveSolve?.capped) {
|
|
187
|
+
out.push(makeInsight(locale, 'NL.W1002', 'warning', { passes: layout.waveSolve.passes }));
|
|
188
|
+
}
|
|
189
|
+
const waves = layout.waves ?? [];
|
|
190
|
+
const empty = waves.filter((w) => w.empty).map((w) => w.id);
|
|
191
|
+
if (empty.length > 0) {
|
|
192
|
+
// One consolidated message: the empty waves are not drawn (§9.1).
|
|
193
|
+
const args: I1006Args = { names: empty };
|
|
194
|
+
out.push(
|
|
195
|
+
makeInsight(
|
|
196
|
+
locale,
|
|
197
|
+
'NL.I1006',
|
|
198
|
+
'info',
|
|
199
|
+
args,
|
|
200
|
+
empty.length === 1 ? empty[0] : undefined,
|
|
201
|
+
),
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
for (const m of layout.milestones) {
|
|
205
|
+
// Only dated milestones get `overrunByWave`, and each carries its
|
|
206
|
+
// own date as `overrunDate`: x cannot give it back, since a hidden
|
|
207
|
+
// day shares the next working day's x.
|
|
208
|
+
if (m.overrunByWave === undefined || m.overrunDate === undefined) continue;
|
|
209
|
+
const wave = waves.find((w) => w.id === m.overrunByWave);
|
|
210
|
+
if (!wave) continue;
|
|
211
|
+
const name = m.id ?? m.title;
|
|
212
|
+
out.push(
|
|
213
|
+
makeInsight(
|
|
214
|
+
locale,
|
|
215
|
+
'NL.I1007',
|
|
216
|
+
'info',
|
|
217
|
+
{
|
|
218
|
+
name,
|
|
219
|
+
date: m.overrunDate,
|
|
220
|
+
wave: wave.id,
|
|
221
|
+
end: formatIsoDate(wave.endDate),
|
|
222
|
+
},
|
|
223
|
+
name,
|
|
224
|
+
),
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
128
229
|
/**
|
|
129
230
|
* Collect layout-derived insights from a positioned roadmap. These describe
|
|
130
231
|
* observable reflow consequences (caption spill, lane packing, etc.), not
|
|
@@ -140,6 +241,12 @@ export function collectLayoutInsights(
|
|
|
140
241
|
for (const lane of layout.swimlanes) {
|
|
141
242
|
collectSwimlaneInsights(lane, locale, out);
|
|
142
243
|
}
|
|
244
|
+
for (const region of layout.includes) {
|
|
245
|
+
for (const lane of region.nestedSwimlanes) {
|
|
246
|
+
collectRegionItemInsights(lane, locale, out);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
collectWaveInsights(layout, locale, out);
|
|
143
250
|
|
|
144
251
|
if (context.today) {
|
|
145
252
|
const today = context.today;
|