@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.
Files changed (183) hide show
  1. package/LICENSE +190 -0
  2. package/README.md +103 -0
  3. package/dist/band-scale.d.ts +56 -0
  4. package/dist/band-scale.d.ts.map +1 -0
  5. package/dist/band-scale.js +86 -0
  6. package/dist/band-scale.js.map +1 -0
  7. package/dist/calendar.d.ts +79 -0
  8. package/dist/calendar.d.ts.map +1 -0
  9. package/dist/calendar.js +210 -0
  10. package/dist/calendar.js.map +1 -0
  11. package/dist/capacity.d.ts +72 -0
  12. package/dist/capacity.d.ts.map +1 -0
  13. package/dist/capacity.js +163 -0
  14. package/dist/capacity.js.map +1 -0
  15. package/dist/dsl-utils.d.ts +5 -0
  16. package/dist/dsl-utils.d.ts.map +1 -0
  17. package/dist/dsl-utils.js +28 -0
  18. package/dist/dsl-utils.js.map +1 -0
  19. package/dist/edge-routing.d.ts +89 -0
  20. package/dist/edge-routing.d.ts.map +1 -0
  21. package/dist/edge-routing.js +435 -0
  22. package/dist/edge-routing.js.map +1 -0
  23. package/dist/frame-tab-geometry.d.ts +78 -0
  24. package/dist/frame-tab-geometry.d.ts.map +1 -0
  25. package/dist/frame-tab-geometry.js +115 -0
  26. package/dist/frame-tab-geometry.js.map +1 -0
  27. package/dist/header-card-geometry.d.ts +29 -0
  28. package/dist/header-card-geometry.d.ts.map +1 -0
  29. package/dist/header-card-geometry.js +41 -0
  30. package/dist/header-card-geometry.js.map +1 -0
  31. package/dist/i18n.d.ts +48 -0
  32. package/dist/i18n.d.ts.map +1 -0
  33. package/dist/i18n.js +114 -0
  34. package/dist/i18n.js.map +1 -0
  35. package/dist/include-chrome-geometry.d.ts +86 -0
  36. package/dist/include-chrome-geometry.d.ts.map +1 -0
  37. package/dist/include-chrome-geometry.js +104 -0
  38. package/dist/include-chrome-geometry.js.map +1 -0
  39. package/dist/index.d.ts +11 -0
  40. package/dist/index.d.ts.map +1 -0
  41. package/dist/index.js +10 -0
  42. package/dist/index.js.map +1 -0
  43. package/dist/item-bar-geometry.d.ts +127 -0
  44. package/dist/item-bar-geometry.d.ts.map +1 -0
  45. package/dist/item-bar-geometry.js +173 -0
  46. package/dist/item-bar-geometry.js.map +1 -0
  47. package/dist/lane-utilization.d.ts +90 -0
  48. package/dist/lane-utilization.d.ts.map +1 -0
  49. package/dist/lane-utilization.js +206 -0
  50. package/dist/lane-utilization.js.map +1 -0
  51. package/dist/layout-context.d.ts +143 -0
  52. package/dist/layout-context.d.ts.map +1 -0
  53. package/dist/layout-context.js +8 -0
  54. package/dist/layout-context.js.map +1 -0
  55. package/dist/layout.d.ts +18 -0
  56. package/dist/layout.d.ts.map +1 -0
  57. package/dist/layout.js +1213 -0
  58. package/dist/layout.js.map +1 -0
  59. package/dist/nodes/anchor-node.d.ts +16 -0
  60. package/dist/nodes/anchor-node.d.ts.map +1 -0
  61. package/dist/nodes/anchor-node.js +68 -0
  62. package/dist/nodes/anchor-node.js.map +1 -0
  63. package/dist/nodes/footnote-node.d.ts +10 -0
  64. package/dist/nodes/footnote-node.d.ts.map +1 -0
  65. package/dist/nodes/footnote-node.js +41 -0
  66. package/dist/nodes/footnote-node.js.map +1 -0
  67. package/dist/nodes/group-node.d.ts +29 -0
  68. package/dist/nodes/group-node.d.ts.map +1 -0
  69. package/dist/nodes/group-node.js +187 -0
  70. package/dist/nodes/group-node.js.map +1 -0
  71. package/dist/nodes/include-node.d.ts +16 -0
  72. package/dist/nodes/include-node.d.ts.map +1 -0
  73. package/dist/nodes/include-node.js +117 -0
  74. package/dist/nodes/include-node.js.map +1 -0
  75. package/dist/nodes/item-node.d.ts +51 -0
  76. package/dist/nodes/item-node.d.ts.map +1 -0
  77. package/dist/nodes/item-node.js +108 -0
  78. package/dist/nodes/item-node.js.map +1 -0
  79. package/dist/nodes/marker-geometry.d.ts +22 -0
  80. package/dist/nodes/marker-geometry.d.ts.map +1 -0
  81. package/dist/nodes/marker-geometry.js +38 -0
  82. package/dist/nodes/marker-geometry.js.map +1 -0
  83. package/dist/nodes/milestone-node.d.ts +48 -0
  84. package/dist/nodes/milestone-node.d.ts.map +1 -0
  85. package/dist/nodes/milestone-node.js +210 -0
  86. package/dist/nodes/milestone-node.js.map +1 -0
  87. package/dist/nodes/parallel-node.d.ts +21 -0
  88. package/dist/nodes/parallel-node.d.ts.map +1 -0
  89. package/dist/nodes/parallel-node.js +80 -0
  90. package/dist/nodes/parallel-node.js.map +1 -0
  91. package/dist/nodes/roadmap-node.d.ts +76 -0
  92. package/dist/nodes/roadmap-node.d.ts.map +1 -0
  93. package/dist/nodes/roadmap-node.js +788 -0
  94. package/dist/nodes/roadmap-node.js.map +1 -0
  95. package/dist/nodes/swimlane-node.d.ts +38 -0
  96. package/dist/nodes/swimlane-node.d.ts.map +1 -0
  97. package/dist/nodes/swimlane-node.js +308 -0
  98. package/dist/nodes/swimlane-node.js.map +1 -0
  99. package/dist/renderable.d.ts +44 -0
  100. package/dist/renderable.d.ts.map +1 -0
  101. package/dist/renderable.js +21 -0
  102. package/dist/renderable.js.map +1 -0
  103. package/dist/row-packer.d.ts +125 -0
  104. package/dist/row-packer.d.ts.map +1 -0
  105. package/dist/row-packer.js +169 -0
  106. package/dist/row-packer.js.map +1 -0
  107. package/dist/style-resolution.d.ts +14 -0
  108. package/dist/style-resolution.d.ts.map +1 -0
  109. package/dist/style-resolution.js +191 -0
  110. package/dist/style-resolution.js.map +1 -0
  111. package/dist/themes/dark.d.ts +4 -0
  112. package/dist/themes/dark.d.ts.map +1 -0
  113. package/dist/themes/dark.js +241 -0
  114. package/dist/themes/dark.js.map +1 -0
  115. package/dist/themes/index.d.ts +15 -0
  116. package/dist/themes/index.d.ts.map +1 -0
  117. package/dist/themes/index.js +30 -0
  118. package/dist/themes/index.js.map +1 -0
  119. package/dist/themes/light.d.ts +4 -0
  120. package/dist/themes/light.d.ts.map +1 -0
  121. package/dist/themes/light.js +248 -0
  122. package/dist/themes/light.js.map +1 -0
  123. package/dist/themes/shape.d.ts +194 -0
  124. package/dist/themes/shape.d.ts.map +1 -0
  125. package/dist/themes/shape.js +6 -0
  126. package/dist/themes/shape.js.map +1 -0
  127. package/dist/themes/shared.d.ts +145 -0
  128. package/dist/themes/shared.d.ts.map +1 -0
  129. package/dist/themes/shared.js +310 -0
  130. package/dist/themes/shared.js.map +1 -0
  131. package/dist/time-scale.d.ts +39 -0
  132. package/dist/time-scale.d.ts.map +1 -0
  133. package/dist/time-scale.js +62 -0
  134. package/dist/time-scale.js.map +1 -0
  135. package/dist/types.d.ts +483 -0
  136. package/dist/types.d.ts.map +1 -0
  137. package/dist/types.js +6 -0
  138. package/dist/types.js.map +1 -0
  139. package/dist/view-preset.d.ts +23 -0
  140. package/dist/view-preset.d.ts.map +1 -0
  141. package/dist/view-preset.js +146 -0
  142. package/dist/view-preset.js.map +1 -0
  143. package/dist/working-calendar.d.ts +14 -0
  144. package/dist/working-calendar.d.ts.map +1 -0
  145. package/dist/working-calendar.js +74 -0
  146. package/dist/working-calendar.js.map +1 -0
  147. package/package.json +37 -0
  148. package/src/band-scale.ts +115 -0
  149. package/src/calendar.ts +244 -0
  150. package/src/capacity.ts +191 -0
  151. package/src/dsl-utils.ts +30 -0
  152. package/src/edge-routing.ts +550 -0
  153. package/src/frame-tab-geometry.ts +165 -0
  154. package/src/header-card-geometry.ts +48 -0
  155. package/src/i18n.ts +124 -0
  156. package/src/include-chrome-geometry.ts +156 -0
  157. package/src/index.ts +116 -0
  158. package/src/item-bar-geometry.ts +222 -0
  159. package/src/lane-utilization.ts +259 -0
  160. package/src/layout-context.ts +182 -0
  161. package/src/layout.ts +1446 -0
  162. package/src/nodes/anchor-node.ts +77 -0
  163. package/src/nodes/footnote-node.ts +60 -0
  164. package/src/nodes/group-node.ts +252 -0
  165. package/src/nodes/include-node.ts +168 -0
  166. package/src/nodes/item-node.ts +171 -0
  167. package/src/nodes/marker-geometry.ts +43 -0
  168. package/src/nodes/milestone-node.ts +263 -0
  169. package/src/nodes/parallel-node.ts +101 -0
  170. package/src/nodes/roadmap-node.ts +957 -0
  171. package/src/nodes/swimlane-node.ts +423 -0
  172. package/src/renderable.ts +68 -0
  173. package/src/row-packer.ts +271 -0
  174. package/src/style-resolution.ts +243 -0
  175. package/src/themes/dark.ts +244 -0
  176. package/src/themes/index.ts +36 -0
  177. package/src/themes/light.ts +251 -0
  178. package/src/themes/shape.ts +230 -0
  179. package/src/themes/shared.ts +369 -0
  180. package/src/time-scale.ts +78 -0
  181. package/src/types.ts +607 -0
  182. package/src/view-preset.ts +180 -0
  183. package/src/working-calendar.ts +91 -0
@@ -0,0 +1,483 @@
1
+ export type SizeBucket = 'none' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
2
+ export type ShadowKind = 'none' | 'subtle' | 'soft' | 'hard';
3
+ export type BorderKind = 'solid' | 'dashed' | 'dotted';
4
+ export type FontFamily = 'sans' | 'serif' | 'mono';
5
+ export type FontWeight = 'thin' | 'light' | 'normal' | 'bold';
6
+ export type BracketKind = 'none' | 'solid' | 'dashed';
7
+ export type HeaderPosition = 'beside' | 'above';
8
+ export type TimelinePosition = 'top' | 'bottom' | 'both';
9
+ export type StatusKind = 'planned' | 'in-progress' | 'done' | 'at-risk' | 'blocked' | 'neutral';
10
+ export interface ResolvedStyle {
11
+ bg: string;
12
+ fg: string;
13
+ text: string;
14
+ border: BorderKind;
15
+ icon: string;
16
+ shadow: ShadowKind;
17
+ font: FontFamily;
18
+ weight: FontWeight;
19
+ italic: boolean;
20
+ textSize: SizeBucket;
21
+ padding: SizeBucket;
22
+ spacing: SizeBucket;
23
+ headerHeight: SizeBucket;
24
+ cornerRadius: SizeBucket;
25
+ bracket: BracketKind;
26
+ headerPosition: HeaderPosition;
27
+ /**
28
+ * Glyph used as the suffix on capacity numbers (`5×`, `5 [person]`, etc.).
29
+ * Stores the raw value as the author wrote it: a built-in icon name
30
+ * (`'multiplier'`, `'person'`, ...), a custom symbol id declared via
31
+ * `symbol` in config, or an inline Unicode literal (`'💰'`). The renderer
32
+ * resolves built-in vs custom vs literal at paint time using
33
+ * `ResolvedConfig.symbols` and the `BUILTIN_CAPACITY_ICONS` set.
34
+ * Default `'multiplier'`.
35
+ */
36
+ capacityIcon: string;
37
+ timelinePosition: TimelinePosition;
38
+ minorGrid: boolean;
39
+ }
40
+ export interface BoundingBox {
41
+ x: number;
42
+ y: number;
43
+ width: number;
44
+ height: number;
45
+ }
46
+ /**
47
+ * Calendar-resolved view of a `size NAME ["TITLE"] effort:LITERAL`
48
+ * declaration. Built once per layout from the AST `SizeDeclaration`s
49
+ * collected by the include-resolver and the active calendar (which
50
+ * decides how many days a literal like `2w` maps to). Items reference
51
+ * these via `LayoutContext.sizes` rather than walking the AST +
52
+ * calendar each time they need an effort.
53
+ *
54
+ * `effortLiteral` keeps the raw literal so callers can show the size's
55
+ * effort string (`"2w"`) on tooltips without re-formatting from
56
+ * `effortDays`.
57
+ *
58
+ * `title` is the optional author-provided display label. The on-bar
59
+ * size chip prefers `title` when present, falling back to `name` (the
60
+ * id) verbatim — both rendered with the case the author typed. See
61
+ * specs/rendering.md § Item size chip.
62
+ */
63
+ export interface ResolvedSize {
64
+ name: string;
65
+ title?: string;
66
+ effortDays: number;
67
+ effortLiteral: string;
68
+ }
69
+ export interface Point {
70
+ x: number;
71
+ y: number;
72
+ }
73
+ export interface PositionedLogo {
74
+ box: BoundingBox;
75
+ assetRef?: string;
76
+ }
77
+ export interface PositionedHeader {
78
+ box: BoundingBox;
79
+ position: HeaderPosition;
80
+ title: string;
81
+ author?: string;
82
+ titleLines: string[];
83
+ authorLines: string[];
84
+ cardBox: BoundingBox;
85
+ logo?: PositionedLogo;
86
+ style: ResolvedStyle;
87
+ attributionBox: BoundingBox;
88
+ }
89
+ export interface PositionedTick {
90
+ x: number;
91
+ labelX?: number;
92
+ label?: string;
93
+ major: boolean;
94
+ }
95
+ export interface PositionedTimelineScale {
96
+ box: BoundingBox;
97
+ ticks: PositionedTick[];
98
+ pixelsPerDay: number;
99
+ originX: number;
100
+ startDate: Date;
101
+ endDate: Date;
102
+ labelStyle: ResolvedStyle;
103
+ pillRowHeight: number;
104
+ tickPanelY: number;
105
+ tickPanelHeight: number;
106
+ markerRow: {
107
+ y: number;
108
+ height: number;
109
+ collisionY: number;
110
+ };
111
+ bottomTickPanelY?: number;
112
+ bottomTickPanelHeight?: number;
113
+ minorGrid: boolean;
114
+ }
115
+ /**
116
+ * How the now-pill is positioned relative to the now-line.
117
+ *
118
+ * - `center` — pill centered on the line (default). Used when both
119
+ * edges have at least `NOW_PILL_WIDTH_PX/2` of clearance from the
120
+ * chart's left/right edges.
121
+ * - `flag-right` — line at the pill's left edge, pill extends to the
122
+ * right with the right side rounded and the label left-aligned.
123
+ * Used when the line lands close enough to `chartLeftX` that a
124
+ * centered pill would overlap the header card / canvas left edge.
125
+ * - `flag-left` — line at the pill's right edge, pill extends to the
126
+ * left with the left side rounded and the label right-aligned.
127
+ * Used when the line lands close enough to `chartRightX` that a
128
+ * centered pill would clip past the canvas right edge.
129
+ *
130
+ * In both flag modes the squared edge IS the now-line, so the pill
131
+ * visually anchors to the line without growing the canvas.
132
+ */
133
+ export type NowPillMode = 'center' | 'flag-right' | 'flag-left';
134
+ export interface PositionedNowline {
135
+ x: number;
136
+ topY: number;
137
+ bottomY: number;
138
+ pillTopY: number;
139
+ /** How the pill aligns to the line (see `NowPillMode`). */
140
+ pillMode: NowPillMode;
141
+ /**
142
+ * Locale-resolved string painted inside the pill (`'now'` for `en-US`,
143
+ * `'maint.'` for `fr`). Layout owns the locale lookup; the renderer
144
+ * just paints the string. See `packages/layout/src/i18n.ts`.
145
+ */
146
+ label: string;
147
+ /**
148
+ * Width (px) of the pill. Floored at `NOW_PILL_WIDTH_PX` so en-US
149
+ * (`'now'`) keeps its byte-stable 36 px footprint; longer locale
150
+ * strings (e.g. `'maint.'` for fr) grow the pill to fit instead of
151
+ * clipping. Computed in `buildNowline` from `estimateTextWidth(label)`.
152
+ */
153
+ pillWidth: number;
154
+ style: ResolvedStyle;
155
+ }
156
+ export interface PositionedItem {
157
+ kind: 'item';
158
+ id?: string;
159
+ title: string;
160
+ box: BoundingBox;
161
+ status: StatusKind;
162
+ progressFraction: number;
163
+ footnoteIndicators: number[];
164
+ labelChips: PositionedLabelChip[];
165
+ /** True when the chip row's natural total width exceeded the bar's
166
+ * effective inner width and the whole row spilled past the bar's
167
+ * right edge. The chips' `box.x` already reflects the spilled
168
+ * position; this flag exists for the row-packer to reserve the
169
+ * spilled extent and for the renderer / debug overlays to know
170
+ * the row sits outside the bar's painted footprint. */
171
+ chipsOutside: boolean;
172
+ /** Logical right x reached by the chip row, INCLUDING the chips
173
+ * whether painted inside or outside the bar. Equals the start x
174
+ * when there are no chips. The row-packer's spill reservation
175
+ * uses this to grow the chart canvas / bump siblings. */
176
+ chipsRightX: number;
177
+ linkIcon?: LinkIconKind;
178
+ linkHref?: string;
179
+ hasOverflow: boolean;
180
+ overflowBox?: BoundingBox;
181
+ overflowAnchorId?: string;
182
+ owner?: string;
183
+ description?: string;
184
+ metaText?: string;
185
+ textSpills: boolean;
186
+ /** True when the bar is too narrow to host the status dot inside
187
+ * with its full inset (`MIN_BAR_WIDTH_FOR_DOT_PX`). The dot
188
+ * renders in the spill column to the right of the bar instead
189
+ * of overshooting the bar's left edge. */
190
+ dotSpills: boolean;
191
+ /** True when the bar is too narrow to host the link-icon tile
192
+ * inside without colliding with the status dot column
193
+ * (`MIN_BAR_WIDTH_FOR_LINK_AND_DOT_PX`). The icon spills out and
194
+ * renders ahead of the (also-spilled) title so the icon stays
195
+ * visually attached to the title text. Implies `textSpills`. */
196
+ iconSpills: boolean;
197
+ /** True when the bar is too narrow to host the footnote
198
+ * superscript at its inset-right position. The indicator(s)
199
+ * render in the spill column trailing the title text instead
200
+ * of at the bar's upper-right corner. */
201
+ footnoteSpills: boolean;
202
+ /** Pre-computed x positions for the spilled decorations. `null`
203
+ * when the matching `*Spills` flag is false (decoration stays
204
+ * inside the bar at its inset-anchored position). */
205
+ dotSpillCx: number | null;
206
+ iconSpillX: number | null;
207
+ /** First footnote indicator's left edge in the spill column.
208
+ * Subsequent indicators walk right by `ITEM_FOOTNOTE_INDICATOR_STEP_PX`. */
209
+ footnoteSpillStartX: number | null;
210
+ /** Right edge of the spilled-decoration cluster (inclusive of
211
+ * spilled title and footnote glyphs). Used by the row-packer
212
+ * to size the row's spill reservation so the next chained item
213
+ * doesn't land underneath. */
214
+ decorationsRightX: number;
215
+ /**
216
+ * Capacity suffix data when the item declares `capacity:N`. Null when
217
+ * the item has no capacity, the value is non-positive, or the resolved
218
+ * `capacity-icon` is `none` and no number should render either way.
219
+ *
220
+ * Renders alongside the item's `metaText` (or stand-alone when no meta
221
+ * is present) per specs/rendering.md § Item capacity suffix. The `text`
222
+ * is the formatted number (`'5'`, `'0.5'`, `'1.25'`); `icon` tells the
223
+ * renderer which glyph to draw and whether to use the SVG library, the
224
+ * `×` text node, or an inline literal.
225
+ */
226
+ capacity: PositionedCapacity | null;
227
+ /**
228
+ * Resolved size when the item declared `size:NAME`. Null when the item
229
+ * sized itself with a literal `duration:` or didn't declare a size at
230
+ * all. Used by the renderer for the size chip on the meta line (m6) and
231
+ * by the layout for capacity-aware duration derivation (m5).
232
+ */
233
+ size: ResolvedSize | null;
234
+ style: ResolvedStyle;
235
+ }
236
+ /**
237
+ * Positioned capacity suffix shared by `PositionedItem` and (in m7) the lane
238
+ * frame-tab badge. The shape stays small and serializable: a formatted number
239
+ * string plus a discriminated union for the glyph. The renderer paints both.
240
+ *
241
+ * `icon === null` means the resolved `capacity-icon` was `'none'` — render
242
+ * the bare number with no glyph or separator.
243
+ */
244
+ export interface PositionedCapacity {
245
+ /** Numeric capacity, post-percent-sugar conversion (e.g. `50%` → 0.5). */
246
+ value: number;
247
+ /** Display string per spec number-formatting rules (`'5'`, `'0.5'`). */
248
+ text: string;
249
+ /** Resolved glyph instruction, or `null` when the icon is `'none'`. */
250
+ icon: ResolvedCapacityIconRef | null;
251
+ }
252
+ /**
253
+ * Renderer-facing capacity-icon reference. Mirrors the layout-internal
254
+ * `ResolvedCapacityIcon` from `capacity.ts` but lives in the shared
255
+ * positioned-model types so the renderer can read it without importing
256
+ * layout internals.
257
+ */
258
+ export type ResolvedCapacityIconRef = {
259
+ kind: 'builtin';
260
+ name: 'multiplier' | 'person' | 'people' | 'points' | 'time';
261
+ } | {
262
+ kind: 'literal';
263
+ text: string;
264
+ };
265
+ export type LinkIconKind = 'linear' | 'github' | 'jira' | 'generic' | 'none';
266
+ export interface PositionedLabelChip {
267
+ text: string;
268
+ style: ResolvedStyle;
269
+ box: BoundingBox;
270
+ }
271
+ export interface PositionedGroup {
272
+ kind: 'group';
273
+ id?: string;
274
+ title?: string;
275
+ box: BoundingBox;
276
+ children: PositionedTrackChild[];
277
+ style: ResolvedStyle;
278
+ }
279
+ export interface PositionedParallel {
280
+ kind: 'parallel';
281
+ id?: string;
282
+ title?: string;
283
+ box: BoundingBox;
284
+ children: PositionedTrackChild[];
285
+ style: ResolvedStyle;
286
+ }
287
+ export type PositionedTrackChild = PositionedItem | PositionedParallel | PositionedGroup;
288
+ export interface PositionedSwimlane {
289
+ id?: string;
290
+ title: string;
291
+ box: BoundingBox;
292
+ bandIndex: number;
293
+ children: PositionedTrackChild[];
294
+ nested: PositionedSwimlane[];
295
+ style: ResolvedStyle;
296
+ owner?: string;
297
+ footnoteIndicators: number[];
298
+ /**
299
+ * Lane-level capacity badge data when the swimlane declares
300
+ * `capacity:N`. Null when no capacity is set or the value parses to
301
+ * zero. The renderer paints the badge inside the frame tab after the
302
+ * owner badge (or after the lane title when no owner is present),
303
+ * per specs/rendering.md § Lane capacity badge. m8 (overload sweep)
304
+ * also reads `value` to compute load against item capacities.
305
+ *
306
+ * `capacity-icon:none` resolves to `icon: null` here (just the bare
307
+ * number renders, no glyph) but the badge still appears. Authors who
308
+ * want the badge fully hidden simply omit `capacity:`.
309
+ */
310
+ capacity: PositionedCapacity | null;
311
+ /**
312
+ * Tri-state utilization underline data when the lane declares
313
+ * `capacity:` AND has at least one item contributing load AND has not
314
+ * opted out of every color band via `utilization-*-at:none`. Null
315
+ * otherwise — the renderer paints no underline.
316
+ *
317
+ * Computed in m12 by `computeLaneUtilization` (see
318
+ * `lane-utilization.ts`) per specs/rendering.md § Lane utilization
319
+ * underline. The renderer paints one rectangle per coalesced segment
320
+ * along the bottom edge of the band, colored from the resolved theme's
321
+ * `swimlane.utilizationOk` / `…Warn` / `…Over` tokens.
322
+ */
323
+ utilization: PositionedLaneUtilization | null;
324
+ }
325
+ /**
326
+ * One classification for a lane utilization segment. `green` includes the
327
+ * zero-load case so the underline reads as a continuous health bar; the spec
328
+ * intentionally avoids a separate "idle" color.
329
+ */
330
+ export type UtilizationClassification = 'green' | 'yellow' | 'red';
331
+ /**
332
+ * One half-open `[startX, endX)` segment of the lane's utilization underline,
333
+ * pre-classified for the renderer. Adjacent same-classification segments are
334
+ * coalesced upstream so the renderer paints one rectangle per visible color
335
+ * band rather than per event boundary.
336
+ *
337
+ * `load` is the absolute concurrent capacity at this segment (sum of active
338
+ * items' `capacity:` values). Useful for tooltips and debug overlays; the
339
+ * paint color is determined by `classification` alone.
340
+ */
341
+ export interface PositionedUtilizationSegment {
342
+ startX: number;
343
+ endX: number;
344
+ load: number;
345
+ classification: UtilizationClassification;
346
+ }
347
+ /**
348
+ * Resolved utilization model for a swimlane. Carries the segment list plus
349
+ * the resolved thresholds and capacity so downstream consumers (renderer,
350
+ * tooltips, exporters) can describe the model without re-resolving config.
351
+ *
352
+ * `warnFraction` / `overFraction`: each is either a positive fraction of
353
+ * `capacityValue` (the lane paints that color band when load reaches the
354
+ * fraction) or `null` to mean "this color band is opted out via
355
+ * `utilization-*-at:none`".
356
+ */
357
+ export interface PositionedLaneUtilization {
358
+ segments: PositionedUtilizationSegment[];
359
+ capacityValue: number;
360
+ warnFraction: number | null;
361
+ overFraction: number | null;
362
+ }
363
+ export interface PositionedAnchor {
364
+ id?: string;
365
+ title: string;
366
+ center: Point;
367
+ radius: number;
368
+ style: ResolvedStyle;
369
+ predecessorPoints: Point[];
370
+ cutTopY: number;
371
+ cutBottomY: number;
372
+ bumpedUp: boolean;
373
+ labelBox: BoundingBox;
374
+ labelSide: 'left' | 'right';
375
+ }
376
+ export interface PositionedMilestone {
377
+ id?: string;
378
+ title: string;
379
+ center: Point;
380
+ radius: number;
381
+ fixed: boolean;
382
+ slackArrows?: Array<{
383
+ x: number;
384
+ y: number;
385
+ }>;
386
+ isOverrun: boolean;
387
+ style: ResolvedStyle;
388
+ cutTopY: number;
389
+ cutBottomY: number;
390
+ labelBox: BoundingBox;
391
+ labelSide: 'left' | 'right';
392
+ }
393
+ /**
394
+ * Result of packing a marker-row entity (anchor or milestone) into the
395
+ * dynamic row stack. `rowIndex == 0` is the in-row baseline; positive
396
+ * indices push the diamond DOWN by `step` px each. The label box is
397
+ * absolute and already accounts for left/right side flipping when the
398
+ * preferred side would overflow the chart.
399
+ */
400
+ export interface MarkerRowPlacement {
401
+ rowIndex: number;
402
+ centerY: number;
403
+ labelBox: BoundingBox;
404
+ labelSide: 'left' | 'right';
405
+ }
406
+ /**
407
+ * Horizontal corridor occupied by a milestone's slack arrow. Sits at
408
+ * the slack predecessor's row Y, running from the predecessor's right
409
+ * edge to the milestone's column. Items whose natural placement would
410
+ * intersect this band must drop to a row whose Y does not match `y`,
411
+ * so the arrow has clear horizontal space to travel.
412
+ */
413
+ export interface SlackCorridor {
414
+ xStart: number;
415
+ xEnd: number;
416
+ y: number;
417
+ slackPredId: string;
418
+ milestoneId: string;
419
+ }
420
+ export interface PositionedDependencyEdge {
421
+ fromId: string;
422
+ toId: string;
423
+ waypoints: Point[];
424
+ /**
425
+ * - `normal` — orthogonal arrow drawn AFTER swimlane / item /
426
+ * marker fills so it sits on top of lane bands.
427
+ * - `overflow` — currently unused at construction; reserved for the
428
+ * red `before:` overrun annotation arrow.
429
+ * - `underBar` — channel router could not find a clear vertical
430
+ * gutter between the source and target columns; the arrow's
431
+ * vertical leg crosses one or more item bars. The renderer paints
432
+ * these edges BEFORE bar fills with a thinner stroke so the bar
433
+ * stays the visual foreground.
434
+ */
435
+ kind: 'normal' | 'overflow' | 'underBar';
436
+ style: ResolvedStyle;
437
+ }
438
+ export interface PositionedFootnoteIndicator {
439
+ number: number;
440
+ hostItemId: string;
441
+ style: ResolvedStyle;
442
+ }
443
+ export interface PositionedFootnoteArea {
444
+ box: BoundingBox;
445
+ entries: PositionedFootnoteEntry[];
446
+ }
447
+ export interface PositionedFootnoteEntry {
448
+ number: number;
449
+ title: string;
450
+ description?: string;
451
+ style: ResolvedStyle;
452
+ }
453
+ export interface PositionedIncludeRegion {
454
+ sourcePath: string;
455
+ label: string;
456
+ box: BoundingBox;
457
+ nestedSwimlanes: PositionedSwimlane[];
458
+ style: ResolvedStyle;
459
+ }
460
+ export interface PositionedRoadmap {
461
+ width: number;
462
+ height: number;
463
+ theme: 'light' | 'dark';
464
+ /**
465
+ * Resolved palette — every color the renderer reads. m2.5d moved
466
+ * theme resolution into the layout side, so the renderer no longer
467
+ * branches on `theme === 'dark'`. The `theme` field above stays for
468
+ * `data-theme` SVG attribution; all color decisions read `palette`.
469
+ */
470
+ palette: import('./themes/index.js').Theme;
471
+ backgroundColor: string;
472
+ header: PositionedHeader;
473
+ timeline: PositionedTimelineScale;
474
+ nowline: PositionedNowline | null;
475
+ swimlanes: PositionedSwimlane[];
476
+ anchors: PositionedAnchor[];
477
+ milestones: PositionedMilestone[];
478
+ edges: PositionedDependencyEdge[];
479
+ footnotes: PositionedFootnoteArea;
480
+ includes: PositionedIncludeRegion[];
481
+ chartBox: BoundingBox;
482
+ }
483
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,6 @@
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
+ export {};
6
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,sEAAsE;AACtE,yEAAyE;AACzE,yEAAyE;AACzE,wDAAwD"}
@@ -0,0 +1,23 @@
1
+ import type { NowlineFile, ScaleBlock } from '@nowline/core';
2
+ import type { TimeScale } from './time-scale.js';
3
+ import type { PositionedTick } from './types.js';
4
+ import type { WorkingCalendar } from './working-calendar.js';
5
+ export type ScaleUnit = 'days' | 'weeks' | 'months' | 'quarters' | 'years';
6
+ export interface ViewPreset {
7
+ /** Tick stride unit (each tick is one `unit` apart). */
8
+ unit: ScaleUnit;
9
+ /** Show a label every N ticks (1 = every tick gets a label). */
10
+ labelEvery: number;
11
+ /** Pixels per `1 unit` worth of working days. */
12
+ pixelsPerUnit: number;
13
+ }
14
+ export type ScaleConfig = ViewPreset;
15
+ export declare function resolveScale(file: NowlineFile, scaleBlock: ScaleBlock | undefined): ViewPreset;
16
+ /**
17
+ * Build header ticks for the chart. The ith tick sits at
18
+ * `originX + i * stridePx`. The last tick is rendered (so the chart
19
+ * has a closing edge) but its label is suppressed because there's no
20
+ * following column.
21
+ */
22
+ export declare function buildHeaderTicks(scale: TimeScale, preset: ViewPreset, calendar: WorkingCalendar, locale?: string): PositionedTick[];
23
+ //# sourceMappingURL=view-preset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"view-preset.d.ts","sourceRoot":"","sources":["../src/view-preset.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAI7D,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,iBAAiB,CAAC;AACjD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AACjD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,MAAM,SAAS,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,GAAG,OAAO,CAAC;AAE3E,MAAM,WAAW,UAAU;IACvB,wDAAwD;IACxD,IAAI,EAAE,SAAS,CAAC;IAChB,gEAAgE;IAChE,UAAU,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,aAAa,EAAE,MAAM,CAAC;CACzB;AAKD,MAAM,MAAM,WAAW,GAAG,UAAU,CAAC;AAMrC,wBAAgB,YAAY,CAAC,IAAI,EAAE,WAAW,EAAE,UAAU,EAAE,UAAU,GAAG,SAAS,GAAG,UAAU,CAoE9F;AAmBD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC5B,KAAK,EAAE,SAAS,EAChB,MAAM,EAAE,UAAU,EAClB,QAAQ,EAAE,eAAe,EACzB,MAAM,GAAE,MAAuB,GAChC,cAAc,EAAE,CAsBlB"}
@@ -0,0 +1,146 @@
1
+ // ViewPreset — declarative configuration for the timeline header
2
+ // (tick stride, label thinning, label format). Replaces the
3
+ // imperative `for` loop and `formatTickLabel` switch in the legacy
4
+ // `timeline.ts`.
5
+ //
6
+ // `resolveScale` parses the DSL `scale:` property (and any nested
7
+ // `scale` block) into a `ViewPreset`. `buildHeaderTicks` produces
8
+ // the `PositionedTick[]` byte-stable with the legacy generator: same
9
+ // x positions, same labelX positions, same major/minor flags, same
10
+ // label text.
11
+ import { addDays } from './calendar.js';
12
+ import { DEFAULT_LOCALE, localeStrings } from './i18n.js';
13
+ import { DEFAULT_PIXELS_PER_DAY, LABEL_THINNING } from './themes/shared.js';
14
+ function stripColon(key) {
15
+ return key.endsWith(':') ? key.slice(0, -1) : key;
16
+ }
17
+ export function resolveScale(file, scaleBlock) {
18
+ const scaleProp = file.roadmapDecl?.properties.find((p) => stripColon(p.key) === 'scale');
19
+ // `scale:` accepts a unit name (`days`/`weeks`/`months`/`quarters`/`years`)
20
+ // OR a duration literal (`1w`, `2w`, `1m`, `1q`, `1y`). The literal form
21
+ // is the documented default in the DSL spec; it picks the unit and uses
22
+ // the literal's count to size the pixels-per-unit budget.
23
+ const rawScale = scaleProp?.value;
24
+ let unit = 'weeks';
25
+ let pixelsPerUnitOverride;
26
+ let labelEveryOverride;
27
+ if (rawScale) {
28
+ if (rawScale === 'days' ||
29
+ rawScale === 'weeks' ||
30
+ rawScale === 'months' ||
31
+ rawScale === 'quarters' ||
32
+ rawScale === 'years') {
33
+ unit = rawScale;
34
+ }
35
+ else {
36
+ const dur = /^(\d+)([dwmqy])$/.exec(rawScale);
37
+ if (dur) {
38
+ const n = Math.max(1, parseInt(dur[1], 10));
39
+ switch (dur[2]) {
40
+ case 'd':
41
+ unit = 'days';
42
+ break;
43
+ case 'w':
44
+ unit = 'weeks';
45
+ break;
46
+ case 'm':
47
+ unit = 'months';
48
+ break;
49
+ case 'q':
50
+ unit = 'quarters';
51
+ break;
52
+ case 'y':
53
+ unit = 'years';
54
+ break;
55
+ }
56
+ pixelsPerUnitOverride = unitPx(unit) * n;
57
+ // A literal scale like `1w` says "I want exactly one label per
58
+ // unit." Override the default thinning so every tick is named.
59
+ labelEveryOverride = 1;
60
+ }
61
+ }
62
+ }
63
+ const defaultLabelEvery = labelEveryOverride ?? LABEL_THINNING[unit] ?? 4;
64
+ if (scaleBlock) {
65
+ const unitProp = scaleBlock.properties.find((p) => stripColon(p.key) === 'unit');
66
+ const resolvedUnit = unitProp?.value ?? unit;
67
+ const labelProp = scaleBlock.properties.find((p) => stripColon(p.key) === 'label-every');
68
+ const pxProp = scaleBlock.properties.find((p) => stripColon(p.key) === 'pixels-per-unit');
69
+ const labelEvery = labelProp
70
+ ? Math.max(1, parseInt(labelProp.value, 10) || defaultLabelEvery)
71
+ : defaultLabelEvery;
72
+ const pixelsPerUnit = pxProp
73
+ ? Math.max(1, parseInt(pxProp.value, 10) || unitPx(resolvedUnit))
74
+ : (pixelsPerUnitOverride ?? unitPx(resolvedUnit));
75
+ return { unit: resolvedUnit, labelEvery, pixelsPerUnit };
76
+ }
77
+ return {
78
+ unit,
79
+ labelEvery: defaultLabelEvery,
80
+ pixelsPerUnit: pixelsPerUnitOverride ?? unitPx(unit),
81
+ };
82
+ }
83
+ function unitPx(unit) {
84
+ // Baseline pixel widths per one unit, tuned so ~6 month roadmaps fit
85
+ // comfortably in a 1200 px wide chart area.
86
+ switch (unit) {
87
+ case 'days':
88
+ return DEFAULT_PIXELS_PER_DAY;
89
+ case 'weeks':
90
+ return 40;
91
+ case 'months':
92
+ return 80;
93
+ case 'quarters':
94
+ return 160;
95
+ case 'years':
96
+ return 320;
97
+ }
98
+ }
99
+ /**
100
+ * Build header ticks for the chart. The ith tick sits at
101
+ * `originX + i * stridePx`. The last tick is rendered (so the chart
102
+ * has a closing edge) but its label is suppressed because there's no
103
+ * following column.
104
+ */
105
+ export function buildHeaderTicks(scale, preset, calendar, locale = DEFAULT_LOCALE) {
106
+ const dayPerTick = calendar.daysPerUnit(preset.unit);
107
+ const stridePx = dayPerTick * scale.pixelsPerDay;
108
+ const totalDays = Math.max(1, Math.round(scale.widthPx / scale.pixelsPerDay));
109
+ const tickCount = Math.floor(totalDays / dayPerTick) + 1;
110
+ const ticks = [];
111
+ for (let i = 0; i < tickCount; i++) {
112
+ const days = i * dayPerTick;
113
+ const x = scale.originX + days * scale.pixelsPerDay;
114
+ const isMajor = i % preset.labelEvery === 0;
115
+ const isLast = i === tickCount - 1;
116
+ ticks.push({
117
+ x,
118
+ labelX: isLast ? undefined : x + stridePx / 2,
119
+ major: isMajor,
120
+ label: isMajor && !isLast
121
+ ? formatTickLabel(preset.unit, addDays(scale.domain[0], days), i, locale)
122
+ : undefined,
123
+ });
124
+ }
125
+ return ticks;
126
+ }
127
+ function formatTickLabel(unit, date, _index, locale) {
128
+ switch (unit) {
129
+ case 'days':
130
+ return `${date.getUTCMonth() + 1}/${date.getUTCDate()}`;
131
+ case 'weeks': {
132
+ const month = date.toLocaleString(locale, { month: 'short', timeZone: 'UTC' });
133
+ const day = date.getUTCDate().toString().padStart(2, '0');
134
+ return `${month} ${day}`;
135
+ }
136
+ case 'months':
137
+ return date.toLocaleString(locale, { month: 'short', timeZone: 'UTC' });
138
+ case 'quarters': {
139
+ const q = Math.floor(date.getUTCMonth() / 3) + 1;
140
+ return `${localeStrings(locale).quarterPrefix}${q} ${date.getUTCFullYear()}`;
141
+ }
142
+ case 'years':
143
+ return `${date.getUTCFullYear()}`;
144
+ }
145
+ }
146
+ //# sourceMappingURL=view-preset.js.map