@nowline/layout 0.2.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/LICENSE +190 -0
- package/README.md +103 -0
- package/dist/band-scale.d.ts +56 -0
- package/dist/band-scale.d.ts.map +1 -0
- package/dist/band-scale.js +86 -0
- package/dist/band-scale.js.map +1 -0
- package/dist/calendar.d.ts +79 -0
- package/dist/calendar.d.ts.map +1 -0
- package/dist/calendar.js +210 -0
- package/dist/calendar.js.map +1 -0
- package/dist/capacity.d.ts +72 -0
- package/dist/capacity.d.ts.map +1 -0
- package/dist/capacity.js +163 -0
- package/dist/capacity.js.map +1 -0
- package/dist/dsl-utils.d.ts +5 -0
- package/dist/dsl-utils.d.ts.map +1 -0
- package/dist/dsl-utils.js +28 -0
- package/dist/dsl-utils.js.map +1 -0
- package/dist/edge-routing.d.ts +89 -0
- package/dist/edge-routing.d.ts.map +1 -0
- package/dist/edge-routing.js +435 -0
- package/dist/edge-routing.js.map +1 -0
- package/dist/frame-tab-geometry.d.ts +78 -0
- package/dist/frame-tab-geometry.d.ts.map +1 -0
- package/dist/frame-tab-geometry.js +115 -0
- package/dist/frame-tab-geometry.js.map +1 -0
- package/dist/header-card-geometry.d.ts +29 -0
- package/dist/header-card-geometry.d.ts.map +1 -0
- package/dist/header-card-geometry.js +41 -0
- package/dist/header-card-geometry.js.map +1 -0
- package/dist/i18n.d.ts +48 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +114 -0
- package/dist/i18n.js.map +1 -0
- package/dist/include-chrome-geometry.d.ts +86 -0
- package/dist/include-chrome-geometry.d.ts.map +1 -0
- package/dist/include-chrome-geometry.js +104 -0
- package/dist/include-chrome-geometry.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +10 -0
- package/dist/index.js.map +1 -0
- package/dist/item-bar-geometry.d.ts +127 -0
- package/dist/item-bar-geometry.d.ts.map +1 -0
- package/dist/item-bar-geometry.js +173 -0
- package/dist/item-bar-geometry.js.map +1 -0
- package/dist/lane-utilization.d.ts +90 -0
- package/dist/lane-utilization.d.ts.map +1 -0
- package/dist/lane-utilization.js +206 -0
- package/dist/lane-utilization.js.map +1 -0
- package/dist/layout-context.d.ts +143 -0
- package/dist/layout-context.d.ts.map +1 -0
- package/dist/layout-context.js +8 -0
- package/dist/layout-context.js.map +1 -0
- package/dist/layout.d.ts +18 -0
- package/dist/layout.d.ts.map +1 -0
- package/dist/layout.js +1213 -0
- package/dist/layout.js.map +1 -0
- package/dist/nodes/anchor-node.d.ts +16 -0
- package/dist/nodes/anchor-node.d.ts.map +1 -0
- package/dist/nodes/anchor-node.js +68 -0
- package/dist/nodes/anchor-node.js.map +1 -0
- package/dist/nodes/footnote-node.d.ts +10 -0
- package/dist/nodes/footnote-node.d.ts.map +1 -0
- package/dist/nodes/footnote-node.js +41 -0
- package/dist/nodes/footnote-node.js.map +1 -0
- package/dist/nodes/group-node.d.ts +29 -0
- package/dist/nodes/group-node.d.ts.map +1 -0
- package/dist/nodes/group-node.js +187 -0
- package/dist/nodes/group-node.js.map +1 -0
- package/dist/nodes/include-node.d.ts +16 -0
- package/dist/nodes/include-node.d.ts.map +1 -0
- package/dist/nodes/include-node.js +117 -0
- package/dist/nodes/include-node.js.map +1 -0
- package/dist/nodes/item-node.d.ts +51 -0
- package/dist/nodes/item-node.d.ts.map +1 -0
- package/dist/nodes/item-node.js +108 -0
- package/dist/nodes/item-node.js.map +1 -0
- package/dist/nodes/marker-geometry.d.ts +22 -0
- package/dist/nodes/marker-geometry.d.ts.map +1 -0
- package/dist/nodes/marker-geometry.js +38 -0
- package/dist/nodes/marker-geometry.js.map +1 -0
- package/dist/nodes/milestone-node.d.ts +48 -0
- package/dist/nodes/milestone-node.d.ts.map +1 -0
- package/dist/nodes/milestone-node.js +210 -0
- package/dist/nodes/milestone-node.js.map +1 -0
- package/dist/nodes/parallel-node.d.ts +21 -0
- package/dist/nodes/parallel-node.d.ts.map +1 -0
- package/dist/nodes/parallel-node.js +80 -0
- package/dist/nodes/parallel-node.js.map +1 -0
- package/dist/nodes/roadmap-node.d.ts +76 -0
- package/dist/nodes/roadmap-node.d.ts.map +1 -0
- package/dist/nodes/roadmap-node.js +788 -0
- package/dist/nodes/roadmap-node.js.map +1 -0
- package/dist/nodes/swimlane-node.d.ts +38 -0
- package/dist/nodes/swimlane-node.d.ts.map +1 -0
- package/dist/nodes/swimlane-node.js +308 -0
- package/dist/nodes/swimlane-node.js.map +1 -0
- package/dist/renderable.d.ts +44 -0
- package/dist/renderable.d.ts.map +1 -0
- package/dist/renderable.js +21 -0
- package/dist/renderable.js.map +1 -0
- package/dist/row-packer.d.ts +125 -0
- package/dist/row-packer.d.ts.map +1 -0
- package/dist/row-packer.js +169 -0
- package/dist/row-packer.js.map +1 -0
- package/dist/style-resolution.d.ts +14 -0
- package/dist/style-resolution.d.ts.map +1 -0
- package/dist/style-resolution.js +191 -0
- package/dist/style-resolution.js.map +1 -0
- package/dist/themes/dark.d.ts +4 -0
- package/dist/themes/dark.d.ts.map +1 -0
- package/dist/themes/dark.js +241 -0
- package/dist/themes/dark.js.map +1 -0
- package/dist/themes/index.d.ts +15 -0
- package/dist/themes/index.d.ts.map +1 -0
- package/dist/themes/index.js +30 -0
- package/dist/themes/index.js.map +1 -0
- package/dist/themes/light.d.ts +4 -0
- package/dist/themes/light.d.ts.map +1 -0
- package/dist/themes/light.js +248 -0
- package/dist/themes/light.js.map +1 -0
- package/dist/themes/shape.d.ts +194 -0
- package/dist/themes/shape.d.ts.map +1 -0
- package/dist/themes/shape.js +6 -0
- package/dist/themes/shape.js.map +1 -0
- package/dist/themes/shared.d.ts +145 -0
- package/dist/themes/shared.d.ts.map +1 -0
- package/dist/themes/shared.js +310 -0
- package/dist/themes/shared.js.map +1 -0
- package/dist/time-scale.d.ts +39 -0
- package/dist/time-scale.d.ts.map +1 -0
- package/dist/time-scale.js +62 -0
- package/dist/time-scale.js.map +1 -0
- package/dist/types.d.ts +483 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +6 -0
- package/dist/types.js.map +1 -0
- package/dist/view-preset.d.ts +23 -0
- package/dist/view-preset.d.ts.map +1 -0
- package/dist/view-preset.js +146 -0
- package/dist/view-preset.js.map +1 -0
- package/dist/working-calendar.d.ts +14 -0
- package/dist/working-calendar.d.ts.map +1 -0
- package/dist/working-calendar.js +74 -0
- package/dist/working-calendar.js.map +1 -0
- package/package.json +37 -0
- package/src/band-scale.ts +115 -0
- package/src/calendar.ts +244 -0
- package/src/capacity.ts +191 -0
- package/src/dsl-utils.ts +30 -0
- package/src/edge-routing.ts +550 -0
- package/src/frame-tab-geometry.ts +165 -0
- package/src/header-card-geometry.ts +48 -0
- package/src/i18n.ts +124 -0
- package/src/include-chrome-geometry.ts +156 -0
- package/src/index.ts +116 -0
- package/src/item-bar-geometry.ts +222 -0
- package/src/lane-utilization.ts +259 -0
- package/src/layout-context.ts +182 -0
- package/src/layout.ts +1446 -0
- package/src/nodes/anchor-node.ts +77 -0
- package/src/nodes/footnote-node.ts +60 -0
- package/src/nodes/group-node.ts +252 -0
- package/src/nodes/include-node.ts +168 -0
- package/src/nodes/item-node.ts +171 -0
- package/src/nodes/marker-geometry.ts +43 -0
- package/src/nodes/milestone-node.ts +263 -0
- package/src/nodes/parallel-node.ts +101 -0
- package/src/nodes/roadmap-node.ts +957 -0
- package/src/nodes/swimlane-node.ts +423 -0
- package/src/renderable.ts +68 -0
- package/src/row-packer.ts +271 -0
- package/src/style-resolution.ts +243 -0
- package/src/themes/dark.ts +244 -0
- package/src/themes/index.ts +36 -0
- package/src/themes/light.ts +251 -0
- package/src/themes/shape.ts +230 -0
- package/src/themes/shared.ts +369 -0
- package/src/time-scale.ts +78 -0
- package/src/types.ts +607 -0
- package/src/view-preset.ts +180 -0
- package/src/working-calendar.ts +91 -0
package/src/types.ts
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
// Positioned-model types. One type per entity in specs/rendering.md §
|
|
2
|
+
// The Positioned Model. Coordinates are in SVG user units with origin at
|
|
3
|
+
// top-left. All colors in `ResolvedStyle` are concrete hex strings baked
|
|
4
|
+
// in by style-resolution; the renderer is palette-dumb.
|
|
5
|
+
|
|
6
|
+
export type SizeBucket = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
|
|
7
|
+
export type ShadowKind = 'none' | 'subtle' | 'soft' | 'hard';
|
|
8
|
+
export type BorderKind = 'solid' | 'dashed' | 'dotted';
|
|
9
|
+
export type FontFamily = 'sans' | 'serif' | 'mono';
|
|
10
|
+
export type FontWeight = 'thin' | 'light' | 'normal' | 'bold';
|
|
11
|
+
export type BracketKind = 'none' | 'solid' | 'dashed';
|
|
12
|
+
export type HeaderPosition = 'beside' | 'above';
|
|
13
|
+
// Where the timeline date strip is rendered. Roadmap-only style property.
|
|
14
|
+
// - `top` (default) — single strip above the chart (legacy behavior)
|
|
15
|
+
// - `bottom` — single strip below the chart, no top strip
|
|
16
|
+
// - `both` — strips at both ends; tall canvases stay readable from
|
|
17
|
+
// either edge of the viewport
|
|
18
|
+
export type TimelinePosition = 'top' | 'bottom' | 'both';
|
|
19
|
+
export type StatusKind = 'planned' | 'in-progress' | 'done' | 'at-risk' | 'blocked' | 'neutral';
|
|
20
|
+
|
|
21
|
+
// The 17 style properties from specs/dsl.md § Style Properties plus header-position
|
|
22
|
+
// and the two roadmap-only readability knobs (`timeline-position`, `minor-grid`).
|
|
23
|
+
// Every one has a concrete value after resolution (theme + defaults fill gaps).
|
|
24
|
+
export interface ResolvedStyle {
|
|
25
|
+
bg: string; // hex or 'none'
|
|
26
|
+
fg: string; // hex
|
|
27
|
+
text: string; // hex
|
|
28
|
+
border: BorderKind;
|
|
29
|
+
icon: string; // identifier like 'linear' | 'github' | 'jira' | 'generic' | 'none'
|
|
30
|
+
shadow: ShadowKind;
|
|
31
|
+
font: FontFamily;
|
|
32
|
+
weight: FontWeight;
|
|
33
|
+
italic: boolean;
|
|
34
|
+
textSize: SizeBucket; // 'none'..'xl'
|
|
35
|
+
padding: SizeBucket; // 'none'..'xl'
|
|
36
|
+
spacing: SizeBucket; // 'none'..'xl'
|
|
37
|
+
headerHeight: SizeBucket; // 'none'..'xl'
|
|
38
|
+
cornerRadius: SizeBucket; // 'none'..'xl'|'full'
|
|
39
|
+
bracket: BracketKind;
|
|
40
|
+
headerPosition: HeaderPosition;
|
|
41
|
+
/**
|
|
42
|
+
* Glyph used as the suffix on capacity numbers (`5×`, `5 [person]`, etc.).
|
|
43
|
+
* Stores the raw value as the author wrote it: a built-in icon name
|
|
44
|
+
* (`'multiplier'`, `'person'`, ...), a custom symbol id declared via
|
|
45
|
+
* `symbol` in config, or an inline Unicode literal (`'💰'`). The renderer
|
|
46
|
+
* resolves built-in vs custom vs literal at paint time using
|
|
47
|
+
* `ResolvedConfig.symbols` and the `BUILTIN_CAPACITY_ICONS` set.
|
|
48
|
+
* Default `'multiplier'`.
|
|
49
|
+
*/
|
|
50
|
+
capacityIcon: string;
|
|
51
|
+
timelinePosition: TimelinePosition;
|
|
52
|
+
minorGrid: boolean;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export interface BoundingBox {
|
|
56
|
+
x: number;
|
|
57
|
+
y: number;
|
|
58
|
+
width: number;
|
|
59
|
+
height: number;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Calendar-resolved view of a `size NAME ["TITLE"] effort:LITERAL`
|
|
64
|
+
* declaration. Built once per layout from the AST `SizeDeclaration`s
|
|
65
|
+
* collected by the include-resolver and the active calendar (which
|
|
66
|
+
* decides how many days a literal like `2w` maps to). Items reference
|
|
67
|
+
* these via `LayoutContext.sizes` rather than walking the AST +
|
|
68
|
+
* calendar each time they need an effort.
|
|
69
|
+
*
|
|
70
|
+
* `effortLiteral` keeps the raw literal so callers can show the size's
|
|
71
|
+
* effort string (`"2w"`) on tooltips without re-formatting from
|
|
72
|
+
* `effortDays`.
|
|
73
|
+
*
|
|
74
|
+
* `title` is the optional author-provided display label. The on-bar
|
|
75
|
+
* size chip prefers `title` when present, falling back to `name` (the
|
|
76
|
+
* id) verbatim — both rendered with the case the author typed. See
|
|
77
|
+
* specs/rendering.md § Item size chip.
|
|
78
|
+
*/
|
|
79
|
+
export interface ResolvedSize {
|
|
80
|
+
name: string;
|
|
81
|
+
title?: string;
|
|
82
|
+
effortDays: number;
|
|
83
|
+
effortLiteral: string;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export interface Point {
|
|
87
|
+
x: number;
|
|
88
|
+
y: number;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Header + attribution mark + optional logo.
|
|
92
|
+
export interface PositionedLogo {
|
|
93
|
+
box: BoundingBox;
|
|
94
|
+
assetRef?: string; // path as declared in DSL; renderer resolves via AssetResolver
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface PositionedHeader {
|
|
98
|
+
box: BoundingBox;
|
|
99
|
+
position: HeaderPosition; // 'beside' | 'above'
|
|
100
|
+
title: string; // empty string if no title set
|
|
101
|
+
author?: string;
|
|
102
|
+
// Word-wrapped title and author lines, sized to the resolved card
|
|
103
|
+
// width (title text wraps when it exceeds HEADER_BESIDE_MAX_WIDTH_PX
|
|
104
|
+
// minus padding). The renderer stacks them inside `cardBox` and does
|
|
105
|
+
// not need to do any text measurement of its own.
|
|
106
|
+
titleLines: string[];
|
|
107
|
+
authorLines: string[];
|
|
108
|
+
// Bounding box of the visible white card inside `box`. The card hugs
|
|
109
|
+
// its content (width = max line width + padding, clamped to MIN..MAX;
|
|
110
|
+
// height grows for wrapped lines).
|
|
111
|
+
cardBox: BoundingBox;
|
|
112
|
+
logo?: PositionedLogo;
|
|
113
|
+
style: ResolvedStyle;
|
|
114
|
+
// Attribution mark (Nowline wordmark + link) lives in the top-right.
|
|
115
|
+
attributionBox: BoundingBox;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// A single tick on the timeline scale (e.g. "W1", "Q2", "Feb").
|
|
119
|
+
//
|
|
120
|
+
// `x` is the tick's BOUNDARY position (the start of the column the tick
|
|
121
|
+
// represents — also where the dotted grid line drops). `labelX` is where
|
|
122
|
+
// the label TEXT sits, centered horizontally within the column (i.e.
|
|
123
|
+
// halfway between this tick's x and the next tick's x). The final tick
|
|
124
|
+
// has no following column, so its `labelX` is undefined and the renderer
|
|
125
|
+
// skips drawing its label.
|
|
126
|
+
export interface PositionedTick {
|
|
127
|
+
x: number;
|
|
128
|
+
labelX?: number;
|
|
129
|
+
label?: string; // undefined for thinned ticks
|
|
130
|
+
major: boolean; // full-height major line vs short minor tick
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export interface PositionedTimelineScale {
|
|
134
|
+
box: BoundingBox;
|
|
135
|
+
ticks: PositionedTick[];
|
|
136
|
+
// Pixel-per-day used for all entities in the chart.
|
|
137
|
+
pixelsPerDay: number;
|
|
138
|
+
// Day 0 (the roadmap start date) is at x = originX.
|
|
139
|
+
originX: number;
|
|
140
|
+
startDate: Date;
|
|
141
|
+
endDate: Date;
|
|
142
|
+
labelStyle: ResolvedStyle;
|
|
143
|
+
// Now-pill row sits at the very top of the timeline area (above the
|
|
144
|
+
// date labels). Height is 0 when there's no now-line to draw.
|
|
145
|
+
pillRowHeight: number;
|
|
146
|
+
// Tick-label panel (the date headers). Always rendered when
|
|
147
|
+
// `timelinePosition` is `top` or `both`; height is 0 when the
|
|
148
|
+
// roadmap requested `bottom`-only and no top strip is wanted.
|
|
149
|
+
tickPanelY: number;
|
|
150
|
+
tickPanelHeight: number;
|
|
151
|
+
// Marker row sits BELOW the top tick-label panel. Anchors + milestones
|
|
152
|
+
// live here. The collision band sits ABOVE the in-row baseline so an
|
|
153
|
+
// anchor colliding with a milestone can be bumped up. Height is 0 when
|
|
154
|
+
// there are no markers to render — the renderer then omits the panel
|
|
155
|
+
// entirely so we don't reserve dead space.
|
|
156
|
+
markerRow: {
|
|
157
|
+
y: number; // y of the in-row diamond center
|
|
158
|
+
height: number; // total height of the marker row band (in-row + collision)
|
|
159
|
+
collisionY: number; // y of the bumped-up diamond center
|
|
160
|
+
};
|
|
161
|
+
// Mirrored bottom tick-label panel. Populated when the roadmap's
|
|
162
|
+
// resolved `timelinePosition` is `bottom` or `both`. Width and
|
|
163
|
+
// x match the top panel (`box.x` / `box.width`); the renderer
|
|
164
|
+
// emits the same fill, border, label color, and tick labels at
|
|
165
|
+
// `bottomTickPanelY`. No now-pill, no marker row — the bottom strip
|
|
166
|
+
// is purely a date reference for tall canvases.
|
|
167
|
+
bottomTickPanelY?: number;
|
|
168
|
+
bottomTickPanelHeight?: number;
|
|
169
|
+
// When `true`, the renderer draws a faint dotted line at every tick
|
|
170
|
+
// boundary (not just major ticks) using `theme.timeline.minorGridLine`.
|
|
171
|
+
// Mirrors the roadmap's resolved `minor-grid` style property. Default
|
|
172
|
+
// `false` preserves byte-stable output for existing roadmaps.
|
|
173
|
+
minorGrid: boolean;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* How the now-pill is positioned relative to the now-line.
|
|
178
|
+
*
|
|
179
|
+
* - `center` — pill centered on the line (default). Used when both
|
|
180
|
+
* edges have at least `NOW_PILL_WIDTH_PX/2` of clearance from the
|
|
181
|
+
* chart's left/right edges.
|
|
182
|
+
* - `flag-right` — line at the pill's left edge, pill extends to the
|
|
183
|
+
* right with the right side rounded and the label left-aligned.
|
|
184
|
+
* Used when the line lands close enough to `chartLeftX` that a
|
|
185
|
+
* centered pill would overlap the header card / canvas left edge.
|
|
186
|
+
* - `flag-left` — line at the pill's right edge, pill extends to the
|
|
187
|
+
* left with the left side rounded and the label right-aligned.
|
|
188
|
+
* Used when the line lands close enough to `chartRightX` that a
|
|
189
|
+
* centered pill would clip past the canvas right edge.
|
|
190
|
+
*
|
|
191
|
+
* In both flag modes the squared edge IS the now-line, so the pill
|
|
192
|
+
* visually anchors to the line without growing the canvas.
|
|
193
|
+
*/
|
|
194
|
+
export type NowPillMode = 'center' | 'flag-right' | 'flag-left';
|
|
195
|
+
|
|
196
|
+
export interface PositionedNowline {
|
|
197
|
+
x: number;
|
|
198
|
+
// Top of the vertical red line. Sits at the BOTTOM of the now-pill —
|
|
199
|
+
// just above the tick-label panel — so the line drops through the
|
|
200
|
+
// date headers and any marker row, into the chart.
|
|
201
|
+
topY: number;
|
|
202
|
+
// Bottom of the vertical red line (chart bottom).
|
|
203
|
+
bottomY: number;
|
|
204
|
+
// Top edge of the pill rectangle. Pill height is fixed in the
|
|
205
|
+
// renderer; the layout reserves the space at the very top of the
|
|
206
|
+
// timeline area so the pill sits ABOVE the date headers.
|
|
207
|
+
pillTopY: number;
|
|
208
|
+
/** How the pill aligns to the line (see `NowPillMode`). */
|
|
209
|
+
pillMode: NowPillMode;
|
|
210
|
+
/**
|
|
211
|
+
* Locale-resolved string painted inside the pill (`'now'` for `en-US`,
|
|
212
|
+
* `'maint.'` for `fr`). Layout owns the locale lookup; the renderer
|
|
213
|
+
* just paints the string. See `packages/layout/src/i18n.ts`.
|
|
214
|
+
*/
|
|
215
|
+
label: string;
|
|
216
|
+
/**
|
|
217
|
+
* Width (px) of the pill. Floored at `NOW_PILL_WIDTH_PX` so en-US
|
|
218
|
+
* (`'now'`) keeps its byte-stable 36 px footprint; longer locale
|
|
219
|
+
* strings (e.g. `'maint.'` for fr) grow the pill to fit instead of
|
|
220
|
+
* clipping. Computed in `buildNowline` from `estimateTextWidth(label)`.
|
|
221
|
+
*/
|
|
222
|
+
pillWidth: number;
|
|
223
|
+
style: ResolvedStyle;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
export interface PositionedItem {
|
|
227
|
+
kind: 'item';
|
|
228
|
+
id?: string;
|
|
229
|
+
title: string;
|
|
230
|
+
box: BoundingBox;
|
|
231
|
+
status: StatusKind;
|
|
232
|
+
progressFraction: number; // 0..1; 1 == fully filled
|
|
233
|
+
footnoteIndicators: number[]; // 1-based superscript numbers, empty when no footnotes
|
|
234
|
+
labelChips: PositionedLabelChip[];
|
|
235
|
+
/** True when the chip row's natural total width exceeded the bar's
|
|
236
|
+
* effective inner width and the whole row spilled past the bar's
|
|
237
|
+
* right edge. The chips' `box.x` already reflects the spilled
|
|
238
|
+
* position; this flag exists for the row-packer to reserve the
|
|
239
|
+
* spilled extent and for the renderer / debug overlays to know
|
|
240
|
+
* the row sits outside the bar's painted footprint. */
|
|
241
|
+
chipsOutside: boolean;
|
|
242
|
+
/** Logical right x reached by the chip row, INCLUDING the chips
|
|
243
|
+
* whether painted inside or outside the bar. Equals the start x
|
|
244
|
+
* when there are no chips. The row-packer's spill reservation
|
|
245
|
+
* uses this to grow the chart canvas / bump siblings. */
|
|
246
|
+
chipsRightX: number;
|
|
247
|
+
linkIcon?: LinkIconKind;
|
|
248
|
+
linkHref?: string;
|
|
249
|
+
hasOverflow: boolean; // true when before: forced the item past its natural end
|
|
250
|
+
overflowBox?: BoundingBox; // the offending tail, flagged red
|
|
251
|
+
// The id of the `before:` anchor/milestone the item overran. Used by the
|
|
252
|
+
// renderer to caption the overflow tail ("past <id>").
|
|
253
|
+
overflowAnchorId?: string;
|
|
254
|
+
owner?: string; // owner id (person/team) for annotation
|
|
255
|
+
description?: string;
|
|
256
|
+
// Pre-formatted secondary line shown under the title inside the item bar
|
|
257
|
+
// (e.g. "1w" or "2w — 50% remaining"). Layout assembles this so the
|
|
258
|
+
// renderer stays palette-and-string-dumb.
|
|
259
|
+
metaText?: string;
|
|
260
|
+
// True when the title OR the meta line is wider than the bar's inner
|
|
261
|
+
// padded width. We treat title + meta as an atomic block: if either
|
|
262
|
+
// one wouldn't fit inside the bar, BOTH get drawn beside the bar
|
|
263
|
+
// (stacked, just past its right edge) so they read as one caption
|
|
264
|
+
// rather than splitting across the bar boundary. The layout also
|
|
265
|
+
// bumps the next item to a fresh row so the spilled caption has
|
|
266
|
+
// empty space to occupy.
|
|
267
|
+
textSpills: boolean;
|
|
268
|
+
/** True when the bar is too narrow to host the status dot inside
|
|
269
|
+
* with its full inset (`MIN_BAR_WIDTH_FOR_DOT_PX`). The dot
|
|
270
|
+
* renders in the spill column to the right of the bar instead
|
|
271
|
+
* of overshooting the bar's left edge. */
|
|
272
|
+
dotSpills: boolean;
|
|
273
|
+
/** True when the bar is too narrow to host the link-icon tile
|
|
274
|
+
* inside without colliding with the status dot column
|
|
275
|
+
* (`MIN_BAR_WIDTH_FOR_LINK_AND_DOT_PX`). The icon spills out and
|
|
276
|
+
* renders ahead of the (also-spilled) title so the icon stays
|
|
277
|
+
* visually attached to the title text. Implies `textSpills`. */
|
|
278
|
+
iconSpills: boolean;
|
|
279
|
+
/** True when the bar is too narrow to host the footnote
|
|
280
|
+
* superscript at its inset-right position. The indicator(s)
|
|
281
|
+
* render in the spill column trailing the title text instead
|
|
282
|
+
* of at the bar's upper-right corner. */
|
|
283
|
+
footnoteSpills: boolean;
|
|
284
|
+
/** Pre-computed x positions for the spilled decorations. `null`
|
|
285
|
+
* when the matching `*Spills` flag is false (decoration stays
|
|
286
|
+
* inside the bar at its inset-anchored position). */
|
|
287
|
+
dotSpillCx: number | null;
|
|
288
|
+
iconSpillX: number | null;
|
|
289
|
+
/** First footnote indicator's left edge in the spill column.
|
|
290
|
+
* Subsequent indicators walk right by `ITEM_FOOTNOTE_INDICATOR_STEP_PX`. */
|
|
291
|
+
footnoteSpillStartX: number | null;
|
|
292
|
+
/** Right edge of the spilled-decoration cluster (inclusive of
|
|
293
|
+
* spilled title and footnote glyphs). Used by the row-packer
|
|
294
|
+
* to size the row's spill reservation so the next chained item
|
|
295
|
+
* doesn't land underneath. */
|
|
296
|
+
decorationsRightX: number;
|
|
297
|
+
/**
|
|
298
|
+
* Capacity suffix data when the item declares `capacity:N`. Null when
|
|
299
|
+
* the item has no capacity, the value is non-positive, or the resolved
|
|
300
|
+
* `capacity-icon` is `none` and no number should render either way.
|
|
301
|
+
*
|
|
302
|
+
* Renders alongside the item's `metaText` (or stand-alone when no meta
|
|
303
|
+
* is present) per specs/rendering.md § Item capacity suffix. The `text`
|
|
304
|
+
* is the formatted number (`'5'`, `'0.5'`, `'1.25'`); `icon` tells the
|
|
305
|
+
* renderer which glyph to draw and whether to use the SVG library, the
|
|
306
|
+
* `×` text node, or an inline literal.
|
|
307
|
+
*/
|
|
308
|
+
capacity: PositionedCapacity | null;
|
|
309
|
+
/**
|
|
310
|
+
* Resolved size when the item declared `size:NAME`. Null when the item
|
|
311
|
+
* sized itself with a literal `duration:` or didn't declare a size at
|
|
312
|
+
* all. Used by the renderer for the size chip on the meta line (m6) and
|
|
313
|
+
* by the layout for capacity-aware duration derivation (m5).
|
|
314
|
+
*/
|
|
315
|
+
size: ResolvedSize | null;
|
|
316
|
+
style: ResolvedStyle;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* Positioned capacity suffix shared by `PositionedItem` and (in m7) the lane
|
|
321
|
+
* frame-tab badge. The shape stays small and serializable: a formatted number
|
|
322
|
+
* string plus a discriminated union for the glyph. The renderer paints both.
|
|
323
|
+
*
|
|
324
|
+
* `icon === null` means the resolved `capacity-icon` was `'none'` — render
|
|
325
|
+
* the bare number with no glyph or separator.
|
|
326
|
+
*/
|
|
327
|
+
export interface PositionedCapacity {
|
|
328
|
+
/** Numeric capacity, post-percent-sugar conversion (e.g. `50%` → 0.5). */
|
|
329
|
+
value: number;
|
|
330
|
+
/** Display string per spec number-formatting rules (`'5'`, `'0.5'`). */
|
|
331
|
+
text: string;
|
|
332
|
+
/** Resolved glyph instruction, or `null` when the icon is `'none'`. */
|
|
333
|
+
icon: ResolvedCapacityIconRef | null;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/**
|
|
337
|
+
* Renderer-facing capacity-icon reference. Mirrors the layout-internal
|
|
338
|
+
* `ResolvedCapacityIcon` from `capacity.ts` but lives in the shared
|
|
339
|
+
* positioned-model types so the renderer can read it without importing
|
|
340
|
+
* layout internals.
|
|
341
|
+
*/
|
|
342
|
+
export type ResolvedCapacityIconRef =
|
|
343
|
+
| { kind: 'builtin'; name: 'multiplier' | 'person' | 'people' | 'points' | 'time' }
|
|
344
|
+
| { kind: 'literal'; text: string };
|
|
345
|
+
|
|
346
|
+
export type LinkIconKind = 'linear' | 'github' | 'jira' | 'generic' | 'none';
|
|
347
|
+
|
|
348
|
+
export interface PositionedLabelChip {
|
|
349
|
+
text: string;
|
|
350
|
+
style: ResolvedStyle;
|
|
351
|
+
// Chip box is laid out inside the item bar; coordinates are absolute.
|
|
352
|
+
box: BoundingBox;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
export interface PositionedGroup {
|
|
356
|
+
kind: 'group';
|
|
357
|
+
id?: string;
|
|
358
|
+
title?: string;
|
|
359
|
+
box: BoundingBox;
|
|
360
|
+
// Bracket is drawn on the left edge when style.bracket != 'none'.
|
|
361
|
+
children: PositionedTrackChild[];
|
|
362
|
+
style: ResolvedStyle;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
export interface PositionedParallel {
|
|
366
|
+
kind: 'parallel';
|
|
367
|
+
id?: string;
|
|
368
|
+
title?: string;
|
|
369
|
+
box: BoundingBox;
|
|
370
|
+
children: PositionedTrackChild[]; // sub-tracks stacked vertically
|
|
371
|
+
style: ResolvedStyle;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
export type PositionedTrackChild = PositionedItem | PositionedParallel | PositionedGroup;
|
|
375
|
+
|
|
376
|
+
export interface PositionedSwimlane {
|
|
377
|
+
id?: string;
|
|
378
|
+
title: string; // display name; falls back to id
|
|
379
|
+
box: BoundingBox;
|
|
380
|
+
bandIndex: number; // zero-based; even/odd drives tint
|
|
381
|
+
children: PositionedTrackChild[];
|
|
382
|
+
nested: PositionedSwimlane[]; // recursive sub-swimlanes
|
|
383
|
+
style: ResolvedStyle;
|
|
384
|
+
// Owner display string ("Platform Team", "Sam Chen") rendered inside
|
|
385
|
+
// the frame tab. Resolved from team/person id → title.
|
|
386
|
+
owner?: string;
|
|
387
|
+
// Footnote indicator numbers attached to this swimlane (via `on:` in the
|
|
388
|
+
// footnote declaration). Rendered in the upper-right of the frame tab.
|
|
389
|
+
footnoteIndicators: number[];
|
|
390
|
+
/**
|
|
391
|
+
* Lane-level capacity badge data when the swimlane declares
|
|
392
|
+
* `capacity:N`. Null when no capacity is set or the value parses to
|
|
393
|
+
* zero. The renderer paints the badge inside the frame tab after the
|
|
394
|
+
* owner badge (or after the lane title when no owner is present),
|
|
395
|
+
* per specs/rendering.md § Lane capacity badge. m8 (overload sweep)
|
|
396
|
+
* also reads `value` to compute load against item capacities.
|
|
397
|
+
*
|
|
398
|
+
* `capacity-icon:none` resolves to `icon: null` here (just the bare
|
|
399
|
+
* number renders, no glyph) but the badge still appears. Authors who
|
|
400
|
+
* want the badge fully hidden simply omit `capacity:`.
|
|
401
|
+
*/
|
|
402
|
+
capacity: PositionedCapacity | null;
|
|
403
|
+
/**
|
|
404
|
+
* Tri-state utilization underline data when the lane declares
|
|
405
|
+
* `capacity:` AND has at least one item contributing load AND has not
|
|
406
|
+
* opted out of every color band via `utilization-*-at:none`. Null
|
|
407
|
+
* otherwise — the renderer paints no underline.
|
|
408
|
+
*
|
|
409
|
+
* Computed in m12 by `computeLaneUtilization` (see
|
|
410
|
+
* `lane-utilization.ts`) per specs/rendering.md § Lane utilization
|
|
411
|
+
* underline. The renderer paints one rectangle per coalesced segment
|
|
412
|
+
* along the bottom edge of the band, colored from the resolved theme's
|
|
413
|
+
* `swimlane.utilizationOk` / `…Warn` / `…Over` tokens.
|
|
414
|
+
*/
|
|
415
|
+
utilization: PositionedLaneUtilization | null;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/**
|
|
419
|
+
* One classification for a lane utilization segment. `green` includes the
|
|
420
|
+
* zero-load case so the underline reads as a continuous health bar; the spec
|
|
421
|
+
* intentionally avoids a separate "idle" color.
|
|
422
|
+
*/
|
|
423
|
+
export type UtilizationClassification = 'green' | 'yellow' | 'red';
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* One half-open `[startX, endX)` segment of the lane's utilization underline,
|
|
427
|
+
* pre-classified for the renderer. Adjacent same-classification segments are
|
|
428
|
+
* coalesced upstream so the renderer paints one rectangle per visible color
|
|
429
|
+
* band rather than per event boundary.
|
|
430
|
+
*
|
|
431
|
+
* `load` is the absolute concurrent capacity at this segment (sum of active
|
|
432
|
+
* items' `capacity:` values). Useful for tooltips and debug overlays; the
|
|
433
|
+
* paint color is determined by `classification` alone.
|
|
434
|
+
*/
|
|
435
|
+
export interface PositionedUtilizationSegment {
|
|
436
|
+
startX: number;
|
|
437
|
+
endX: number;
|
|
438
|
+
load: number;
|
|
439
|
+
classification: UtilizationClassification;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/**
|
|
443
|
+
* Resolved utilization model for a swimlane. Carries the segment list plus
|
|
444
|
+
* the resolved thresholds and capacity so downstream consumers (renderer,
|
|
445
|
+
* tooltips, exporters) can describe the model without re-resolving config.
|
|
446
|
+
*
|
|
447
|
+
* `warnFraction` / `overFraction`: each is either a positive fraction of
|
|
448
|
+
* `capacityValue` (the lane paints that color band when load reaches the
|
|
449
|
+
* fraction) or `null` to mean "this color band is opted out via
|
|
450
|
+
* `utilization-*-at:none`".
|
|
451
|
+
*/
|
|
452
|
+
export interface PositionedLaneUtilization {
|
|
453
|
+
segments: PositionedUtilizationSegment[];
|
|
454
|
+
capacityValue: number;
|
|
455
|
+
warnFraction: number | null;
|
|
456
|
+
overFraction: number | null;
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
export interface PositionedAnchor {
|
|
460
|
+
id?: string;
|
|
461
|
+
title: string;
|
|
462
|
+
center: Point; // diamond center (post-collision-resolution)
|
|
463
|
+
radius: number;
|
|
464
|
+
style: ResolvedStyle;
|
|
465
|
+
// Non-binding predecessor edges: small arrows from prior items, drawn by renderer.
|
|
466
|
+
predecessorPoints: Point[];
|
|
467
|
+
// Vertical span of the anchor's "cut line" through the swimlane area,
|
|
468
|
+
// drawn by the renderer after items so it overlays the lane fills.
|
|
469
|
+
cutTopY: number;
|
|
470
|
+
cutBottomY: number;
|
|
471
|
+
// True when this anchor was bumped above the in-row baseline because a
|
|
472
|
+
// milestone shares the same x-column.
|
|
473
|
+
bumpedUp: boolean;
|
|
474
|
+
// Resolved label placement. The marker-row packer decides whether the
|
|
475
|
+
// title sits to the right (default) or the left of the diamond, and
|
|
476
|
+
// whether the entity drops to a lower row to avoid colliding with
|
|
477
|
+
// earlier markers' label boxes. Renderer uses `labelBox.x/y` directly
|
|
478
|
+
// (start-anchored text) — no further geometry decisions.
|
|
479
|
+
labelBox: BoundingBox;
|
|
480
|
+
labelSide: 'left' | 'right';
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
export interface PositionedMilestone {
|
|
484
|
+
id?: string;
|
|
485
|
+
title: string;
|
|
486
|
+
center: Point;
|
|
487
|
+
radius: number;
|
|
488
|
+
fixed: boolean; // true for date: style, false for after: style
|
|
489
|
+
// One slack arrow per non-binding predecessor. Each entry's (x, y) is
|
|
490
|
+
// the predecessor's right-edge midpoint; the arrow runs horizontally
|
|
491
|
+
// from there to (center.x - 6) at y. Empty / undefined when the
|
|
492
|
+
// milestone has zero or one predecessor.
|
|
493
|
+
slackArrows?: Array<{ x: number; y: number }>;
|
|
494
|
+
isOverrun: boolean; // true when the aggregated predecessor end exceeds `date:`
|
|
495
|
+
style: ResolvedStyle;
|
|
496
|
+
// Vertical span of the milestone's cut line through the swimlane area.
|
|
497
|
+
cutTopY: number;
|
|
498
|
+
cutBottomY: number;
|
|
499
|
+
// See PositionedAnchor.labelBox — same packing logic applies.
|
|
500
|
+
labelBox: BoundingBox;
|
|
501
|
+
labelSide: 'left' | 'right';
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Result of packing a marker-row entity (anchor or milestone) into the
|
|
506
|
+
* dynamic row stack. `rowIndex == 0` is the in-row baseline; positive
|
|
507
|
+
* indices push the diamond DOWN by `step` px each. The label box is
|
|
508
|
+
* absolute and already accounts for left/right side flipping when the
|
|
509
|
+
* preferred side would overflow the chart.
|
|
510
|
+
*/
|
|
511
|
+
export interface MarkerRowPlacement {
|
|
512
|
+
rowIndex: number;
|
|
513
|
+
centerY: number;
|
|
514
|
+
labelBox: BoundingBox;
|
|
515
|
+
labelSide: 'left' | 'right';
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
/**
|
|
519
|
+
* Horizontal corridor occupied by a milestone's slack arrow. Sits at
|
|
520
|
+
* the slack predecessor's row Y, running from the predecessor's right
|
|
521
|
+
* edge to the milestone's column. Items whose natural placement would
|
|
522
|
+
* intersect this band must drop to a row whose Y does not match `y`,
|
|
523
|
+
* so the arrow has clear horizontal space to travel.
|
|
524
|
+
*/
|
|
525
|
+
export interface SlackCorridor {
|
|
526
|
+
xStart: number; // slack pred's right edge (logical chart x)
|
|
527
|
+
xEnd: number; // binding pred's right edge / milestone center.x
|
|
528
|
+
y: number; // slack pred's row midpoint
|
|
529
|
+
slackPredId: string; // exempt from bumping (owns the arrow's origin)
|
|
530
|
+
milestoneId: string;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
export interface PositionedDependencyEdge {
|
|
534
|
+
fromId: string;
|
|
535
|
+
toId: string;
|
|
536
|
+
waypoints: Point[]; // first = source port; last = target port
|
|
537
|
+
/**
|
|
538
|
+
* - `normal` — orthogonal arrow drawn AFTER swimlane / item /
|
|
539
|
+
* marker fills so it sits on top of lane bands.
|
|
540
|
+
* - `overflow` — currently unused at construction; reserved for the
|
|
541
|
+
* red `before:` overrun annotation arrow.
|
|
542
|
+
* - `underBar` — channel router could not find a clear vertical
|
|
543
|
+
* gutter between the source and target columns; the arrow's
|
|
544
|
+
* vertical leg crosses one or more item bars. The renderer paints
|
|
545
|
+
* these edges BEFORE bar fills with a thinner stroke so the bar
|
|
546
|
+
* stays the visual foreground.
|
|
547
|
+
*/
|
|
548
|
+
kind: 'normal' | 'overflow' | 'underBar';
|
|
549
|
+
style: ResolvedStyle;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
export interface PositionedFootnoteIndicator {
|
|
553
|
+
// Not a separate geometry in the chart — rendered as a superscript on the
|
|
554
|
+
// host item. Kept in the model for completeness, keyed to the host item id.
|
|
555
|
+
number: number;
|
|
556
|
+
hostItemId: string;
|
|
557
|
+
style: ResolvedStyle;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
export interface PositionedFootnoteArea {
|
|
561
|
+
box: BoundingBox;
|
|
562
|
+
entries: PositionedFootnoteEntry[];
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
export interface PositionedFootnoteEntry {
|
|
566
|
+
number: number;
|
|
567
|
+
title: string;
|
|
568
|
+
description?: string;
|
|
569
|
+
style: ResolvedStyle;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
export interface PositionedIncludeRegion {
|
|
573
|
+
sourcePath: string; // relative to the parent file
|
|
574
|
+
label: string; // e.g. the child roadmap's title or basename
|
|
575
|
+
box: BoundingBox;
|
|
576
|
+
// Nested swimlanes laid out inside the region. They share the parent's
|
|
577
|
+
// timeline (originX, pixelsPerDay) so cross-region dates align with the
|
|
578
|
+
// tick row above the region.
|
|
579
|
+
nestedSwimlanes: PositionedSwimlane[];
|
|
580
|
+
style: ResolvedStyle;
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
// Top-level result handed to the renderer.
|
|
584
|
+
export interface PositionedRoadmap {
|
|
585
|
+
width: number;
|
|
586
|
+
height: number;
|
|
587
|
+
theme: 'light' | 'dark';
|
|
588
|
+
/**
|
|
589
|
+
* Resolved palette — every color the renderer reads. m2.5d moved
|
|
590
|
+
* theme resolution into the layout side, so the renderer no longer
|
|
591
|
+
* branches on `theme === 'dark'`. The `theme` field above stays for
|
|
592
|
+
* `data-theme` SVG attribution; all color decisions read `palette`.
|
|
593
|
+
*/
|
|
594
|
+
palette: import('./themes/index.js').Theme;
|
|
595
|
+
backgroundColor: string; // resolved from theme.surface.page
|
|
596
|
+
header: PositionedHeader;
|
|
597
|
+
timeline: PositionedTimelineScale;
|
|
598
|
+
nowline: PositionedNowline | null;
|
|
599
|
+
swimlanes: PositionedSwimlane[];
|
|
600
|
+
anchors: PositionedAnchor[];
|
|
601
|
+
milestones: PositionedMilestone[];
|
|
602
|
+
edges: PositionedDependencyEdge[];
|
|
603
|
+
footnotes: PositionedFootnoteArea;
|
|
604
|
+
includes: PositionedIncludeRegion[];
|
|
605
|
+
// Frame (chart area) in chart-space. Useful for renderer overlays.
|
|
606
|
+
chartBox: BoundingBox;
|
|
607
|
+
}
|