@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,173 @@
1
+ // Item-bar internal geometry — the "rails" inside an item rectangle:
2
+ // caption text (title + meta), label chips along the bottom, status
3
+ // dot at the upper-right, footnote indicators just left of the dot,
4
+ // and the link icon tile at the bottom-right.
5
+ //
6
+ // These metrics are shared between layout (label-chip placement,
7
+ // future content-aware width estimation) and renderer (text baselines
8
+ // + glyph positions). Keeping them in one module means a single edit
9
+ // re-skins every item bar consistently — the alternative is hunting
10
+ // matching `+ 12`s across two packages.
11
+ //
12
+ // Coordinate convention: every offset is **relative to the item's
13
+ // `box`** (`box.x` for X, `box.y` for Y), with `box.height` /
14
+ // `box.width` used as the "right edge" / "bottom edge" anchor where
15
+ // noted.
16
+ // ---- Caption (title + meta text) ---------------------------------
17
+ /** Horizontal inset (px) for the caption's left edge inside the bar. */
18
+ export const ITEM_CAPTION_INSET_X_PX = 12;
19
+ /**
20
+ * When the bar's title would overflow to the right, layout sets
21
+ * `textSpills` and the caption renders to the RIGHT of the bar; this
22
+ * is the gap (px) between the bar's right edge and the spilled
23
+ * caption.
24
+ */
25
+ export const ITEM_CAPTION_SPILL_GAP_PX = 6;
26
+ /** Baseline Y of the title text relative to the bar's top. */
27
+ export const ITEM_CAPTION_TITLE_BASELINE_OFFSET_PX = 20;
28
+ /** Baseline Y of the meta text (second line) relative to the bar's top. */
29
+ export const ITEM_CAPTION_META_BASELINE_OFFSET_PX = 38;
30
+ /** Font size (px) of the title text. */
31
+ export const ITEM_CAPTION_TITLE_FONT_SIZE_PX = 13;
32
+ /** Font size (px) of the meta text. */
33
+ export const ITEM_CAPTION_META_FONT_SIZE_PX = 11;
34
+ // ---- Status dot (upper-right glyph) ------------------------------
35
+ /** Distance (px) from the bar's right edge to the dot's center. */
36
+ export const ITEM_STATUS_DOT_INSET_RIGHT_PX = 12;
37
+ /** Distance (px) from the bar's top edge to the dot's center. */
38
+ export const ITEM_STATUS_DOT_INSET_TOP_PX = 12;
39
+ /** Radius (px) of the status dot. */
40
+ export const ITEM_STATUS_DOT_RADIUS_PX = 5;
41
+ // ---- Footnote indicators (numbers next to status dot) ------------
42
+ /**
43
+ * X offset (px from bar's right edge) where the rightmost footnote
44
+ * digit's anchor sits. Tuned to leave room for the status dot.
45
+ */
46
+ export const ITEM_FOOTNOTE_INDICATOR_INSET_RIGHT_PX = 22;
47
+ /** Baseline Y (px from bar's top) for footnote indicator digits. */
48
+ export const ITEM_FOOTNOTE_INDICATOR_BASELINE_OFFSET_PX = 14;
49
+ /**
50
+ * Horizontal step (px) between consecutive footnote indicators when
51
+ * an item carries more than one footnote — they walk LEFT from the
52
+ * status dot.
53
+ */
54
+ export const ITEM_FOOTNOTE_INDICATOR_STEP_PX = 8;
55
+ // ---- Link icon tile (bottom-right) -------------------------------
56
+ /** Side length (px) of the square link-icon tile. */
57
+ export const ITEM_LINK_ICON_TILE_SIZE_PX = 14;
58
+ /** Distance (px) from the bar's right/bottom edges to the tile's edge. */
59
+ export const ITEM_LINK_ICON_INSET_PX = 6;
60
+ // ---- Narrow-bar decoration spill --------------------------------
61
+ /**
62
+ * Horizontal gap (px) between consecutive decorations (status dot,
63
+ * link icon, footnote, title) when they spill into the column to
64
+ * the right of a bar that's too narrow to host them inside.
65
+ */
66
+ export const ITEM_DECORATION_SPILL_GAP_PX = 4;
67
+ /**
68
+ * Minimum bar width (px) needed to host the status dot inside the
69
+ * bar with its full inset. Below this, the dot would have to
70
+ * extend past the bar's left edge, so the dot spills into the
71
+ * caption column to the right of the bar instead.
72
+ */
73
+ export const MIN_BAR_WIDTH_FOR_DOT_PX = ITEM_STATUS_DOT_INSET_RIGHT_PX + ITEM_STATUS_DOT_RADIUS_PX;
74
+ /**
75
+ * Minimum bar width (px) needed to host the link-icon tile AND the
76
+ * status dot inside the bar with at least
77
+ * `ITEM_DECORATION_SPILL_GAP_PX` of breathing room between them.
78
+ * Below this, the link icon would visually collide with (or push
79
+ * into) the dot's column, so the icon spills out and renders ahead
80
+ * of the (also-spilled) title.
81
+ */
82
+ export const MIN_BAR_WIDTH_FOR_LINK_AND_DOT_PX = ITEM_LINK_ICON_INSET_PX +
83
+ ITEM_LINK_ICON_TILE_SIZE_PX +
84
+ ITEM_DECORATION_SPILL_GAP_PX +
85
+ ITEM_STATUS_DOT_INSET_RIGHT_PX +
86
+ ITEM_STATUS_DOT_RADIUS_PX;
87
+ /**
88
+ * Minimum bar width (px) needed to host the footnote indicator at
89
+ * its inset-right position without overshooting the bar's left
90
+ * edge or colliding with a leading link icon. Approximate width
91
+ * for one digit is 8px (font-size 10, bold).
92
+ */
93
+ export const MIN_BAR_WIDTH_FOR_FOOTNOTE_PX = ITEM_FOOTNOTE_INDICATOR_INSET_RIGHT_PX + 1;
94
+ // ---- Label chips (along the bar's bottom) ------------------------
95
+ /** Height (px) of a label chip rectangle. */
96
+ export const LABEL_CHIP_HEIGHT_PX = 13;
97
+ /**
98
+ * Vertical gap (px) between the top of the bottom progress strip and
99
+ * the BOTTOM of a label chip. Keeps chips from touching the strip and
100
+ * gives a clear visual rail.
101
+ */
102
+ export const LABEL_CHIP_GAP_ABOVE_PROGRESS_STRIP_PX = 3;
103
+ /** Horizontal gap (px) between consecutive chips in a row. */
104
+ export const LABEL_CHIP_GAP_BETWEEN_PX = 4;
105
+ /**
106
+ * Vertical gap (px) between two stacked chip rows when chips spill
107
+ * outside the bar and form a multi-row column to the right.
108
+ */
109
+ export const LABEL_CHIP_ROW_GAP_PX = 4;
110
+ /**
111
+ * Vertical row pitch (px) for a stacked chip column — chip height +
112
+ * inter-row gap.
113
+ */
114
+ export const LABEL_CHIP_ROW_STEP_PX = LABEL_CHIP_HEIGHT_PX + LABEL_CHIP_ROW_GAP_PX;
115
+ /**
116
+ * Slack budget (fraction) applied ONCE per item when chips spill
117
+ * outside the bar. If a chip would overflow its row by less than
118
+ * `SPILL_ROW_SLACK_FRACTION × chip.width`, the row is allowed to
119
+ * stretch by the overflow amount and keep the chip on it instead of
120
+ * wrapping. This rescues "lonely chip" cases where one chip just
121
+ * barely overshoots; once the slack is consumed, no further row
122
+ * expansions happen for that item.
123
+ */
124
+ export const SPILL_ROW_SLACK_FRACTION = 0.25;
125
+ /**
126
+ * Pack `chips` into rows for the SPILL column to the right of an
127
+ * item bar. Each row is capped at `barVisualWidth` (with gaps
128
+ * between chips). When a chip would overflow:
129
+ *
130
+ * - If the row is empty, place the chip anyway (a chip wider than
131
+ * the cap occupies its own row).
132
+ * - Else if the slack rule applies (`overflow ≤ 0.25 × chip.width`)
133
+ * AND the slack hasn't already been used for this item, expand
134
+ * the current row by `overflow` and keep the chip on it.
135
+ * - Else wrap the chip to a fresh row.
136
+ */
137
+ export function packSpillChips(chips, barVisualWidth) {
138
+ if (chips.length === 0)
139
+ return { rows: [[]], expanded: false };
140
+ const rows = [[]];
141
+ let used = 0;
142
+ let rowCap = barVisualWidth;
143
+ let expanded = false;
144
+ for (const chip of chips) {
145
+ const row = rows[rows.length - 1];
146
+ const needed = (row.length === 0 ? 0 : LABEL_CHIP_GAP_BETWEEN_PX) + chip.width;
147
+ const wouldBe = used + needed;
148
+ if (wouldBe <= rowCap) {
149
+ row.push(chip);
150
+ used = wouldBe;
151
+ continue;
152
+ }
153
+ if (row.length === 0) {
154
+ row.push(chip);
155
+ used = chip.width;
156
+ continue;
157
+ }
158
+ const overflow = wouldBe - rowCap;
159
+ const slack = chip.width * SPILL_ROW_SLACK_FRACTION;
160
+ if (!expanded && overflow <= slack) {
161
+ row.push(chip);
162
+ used = wouldBe;
163
+ rowCap = wouldBe;
164
+ expanded = true;
165
+ continue;
166
+ }
167
+ rows.push([chip]);
168
+ used = chip.width;
169
+ rowCap = barVisualWidth;
170
+ }
171
+ return { rows, expanded };
172
+ }
173
+ //# sourceMappingURL=item-bar-geometry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"item-bar-geometry.js","sourceRoot":"","sources":["../src/item-bar-geometry.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,90 @@
1
+ import type { DefaultDeclaration, SwimlaneDeclaration } from '@nowline/core';
2
+ import type { PositionedLaneUtilization, PositionedTrackChild, UtilizationClassification } from './types.js';
3
+ /**
4
+ * Built-in defaults used when neither the lane nor its `default swimlane`
5
+ * declares a threshold. Mirrors specs/dsl.md rule 17d.
6
+ */
7
+ export declare const DEFAULT_UTILIZATION_WARN_FRACTION = 0.8;
8
+ export declare const DEFAULT_UTILIZATION_OVER_FRACTION = 1;
9
+ /**
10
+ * One contributor to a lane's load function: a single positioned item with
11
+ * its visual horizontal span and the load it contributes during that span.
12
+ * Children inside `parallel` / `group` blocks contribute individually so
13
+ * concurrent load reads correctly (the parallel block itself spans the union
14
+ * of its children's bars but does not itself add load — only the items do).
15
+ */
16
+ interface LoadContributor {
17
+ startX: number;
18
+ endX: number;
19
+ load: number;
20
+ }
21
+ /**
22
+ * Walk the lane's positioned children and return one `LoadContributor` per
23
+ * item that contributes load. Items inside `parallel` and `group` blocks are
24
+ * descended into recursively; the block envelopes themselves contribute
25
+ * nothing (they are pure containers).
26
+ *
27
+ * `inferItemLoad` mirrors the spec's "Default capacity" table:
28
+ * - Explicit `capacity:N` → that value.
29
+ * - Sized item without explicit `capacity:` → 1 (the default for sized
30
+ * items, matching the duration-derivation default).
31
+ * - Duration-literal item without `capacity:` → 0 (the bar paints normally
32
+ * but does not contribute load — this is the legacy "uncounted" item
33
+ * family from before the size system existed).
34
+ */
35
+ export declare function collectLoadContributors(children: PositionedTrackChild[]): LoadContributor[];
36
+ /**
37
+ * Compute a lane's utilization model. Returns `null` when there is no
38
+ * meaningful underline to paint:
39
+ * - Lane has no `capacity:` (no denominator → undefined utilization).
40
+ * - Lane has no items contributing load (collectLoadContributors returns
41
+ * an empty list).
42
+ * - Both thresholds resolve to `none` (caller has opted out of every
43
+ * color band; nothing to paint).
44
+ *
45
+ * The returned `PositionedLaneUtilization` carries the resolved thresholds
46
+ * (in fraction form, or `null` for opt-out) alongside the segments so the
47
+ * renderer can render legends / tooltips without re-resolving anything.
48
+ */
49
+ export declare function computeLaneUtilization(opts: {
50
+ children: PositionedTrackChild[];
51
+ capacityValue: number;
52
+ warnFraction: number | null;
53
+ overFraction: number | null;
54
+ }): PositionedLaneUtilization | null;
55
+ /**
56
+ * Classify a single load value against the lane's resolved capacity and
57
+ * thresholds. Spec § "Load function and segmentation":
58
+ * - `u < warn-at` → green (healthy; includes `u = 0` so the underline
59
+ * stays continuous).
60
+ * - `warn-at ≤ u < over-at` → yellow.
61
+ * - `u ≥ over-at` → red.
62
+ *
63
+ * `null` thresholds disable that color band: a `null` warnFraction collapses
64
+ * to a binary green / red indicator; a `null` overFraction collapses to
65
+ * green / yellow; both `null` is unreachable here (caller short-circuits
66
+ * upstream).
67
+ */
68
+ export declare function classifyLoad(load: number, capacity: number, warnFraction: number | null, overFraction: number | null): UtilizationClassification;
69
+ /**
70
+ * Resolve the lane's effective utilization thresholds, applying the spec's
71
+ * resolution order:
72
+ *
73
+ * 1. Lane's own `utilization-warn-at:` / `utilization-over-at:` properties.
74
+ * 2. The applicable `default swimlane` declaration's properties.
75
+ * 3. Built-in defaults (`warn-at:80%`, `over-at:100%`).
76
+ *
77
+ * Each side resolves independently — a lane can pin only one threshold and
78
+ * inherit the other from defaults / built-ins. The returned `null` means
79
+ * "opted out via `none`" and disables that color band downstream.
80
+ *
81
+ * Malformed values (already reported by the validator) are treated as
82
+ * "unset" — the resolver falls through to the next layer rather than
83
+ * double-reporting.
84
+ */
85
+ export declare function resolveLaneUtilizationThresholds(lane: SwimlaneDeclaration, defaults: Map<string, DefaultDeclaration>): {
86
+ warn: number | null;
87
+ over: number | null;
88
+ };
89
+ export {};
90
+ //# sourceMappingURL=lane-utilization.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lane-utilization.d.ts","sourceRoot":"","sources":["../src/lane-utilization.ts"],"names":[],"mappings":"AAcA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AAE7E,OAAO,KAAK,EAER,yBAAyB,EACzB,oBAAoB,EAEpB,yBAAyB,EAC5B,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,eAAO,MAAM,iCAAiC,MAAM,CAAC;AACrD,eAAO,MAAM,iCAAiC,IAAM,CAAC;AAErD;;;;;;GAMG;AACH,UAAU,eAAe;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,uBAAuB,CAAC,QAAQ,EAAE,oBAAoB,EAAE,GAAG,eAAe,EAAE,CAI3F;AAyBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,sBAAsB,CAAC,IAAI,EAAE;IACzC,QAAQ,EAAE,oBAAoB,EAAE,CAAC;IACjC,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;CAC/B,GAAG,yBAAyB,GAAG,IAAI,CA+DnC;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,YAAY,CACxB,IAAI,EAAE,MAAM,EACZ,QAAQ,EAAE,MAAM,EAChB,YAAY,EAAE,MAAM,GAAG,IAAI,EAC3B,YAAY,EAAE,MAAM,GAAG,IAAI,GAC5B,yBAAyB,CAK3B;AAED;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,gCAAgC,CAC5C,IAAI,EAAE,mBAAmB,EACzB,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,kBAAkB,CAAC,GAC1C;IAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;CAAE,CAa9C"}
@@ -0,0 +1,206 @@
1
+ // Lane utilization computation per specs/rendering.md § Lane utilization
2
+ // underline.
3
+ //
4
+ // Walks a positioned lane's children (items, plus the items inside parallel
5
+ // and group blocks), collects each contributor's pixel span and capacity load,
6
+ // then sweeps the timeline to produce a list of half-open segments
7
+ // classified `green | yellow | red` against the lane's resolved capacity and
8
+ // thresholds. Adjacent same-classification segments coalesce so the renderer
9
+ // paints one rectangle per color band rather than per event boundary.
10
+ //
11
+ // Pure data in / pure data out — no AST, no theme, no side effects. Lives
12
+ // next to `capacity.ts` (the other capacity-helper module) and is tested in
13
+ // isolation.
14
+ import { propValue } from './dsl-utils.js';
15
+ /**
16
+ * Built-in defaults used when neither the lane nor its `default swimlane`
17
+ * declares a threshold. Mirrors specs/dsl.md rule 17d.
18
+ */
19
+ export const DEFAULT_UTILIZATION_WARN_FRACTION = 0.8; // 80%
20
+ export const DEFAULT_UTILIZATION_OVER_FRACTION = 1.0; // 100%
21
+ /**
22
+ * Walk the lane's positioned children and return one `LoadContributor` per
23
+ * item that contributes load. Items inside `parallel` and `group` blocks are
24
+ * descended into recursively; the block envelopes themselves contribute
25
+ * nothing (they are pure containers).
26
+ *
27
+ * `inferItemLoad` mirrors the spec's "Default capacity" table:
28
+ * - Explicit `capacity:N` → that value.
29
+ * - Sized item without explicit `capacity:` → 1 (the default for sized
30
+ * items, matching the duration-derivation default).
31
+ * - Duration-literal item without `capacity:` → 0 (the bar paints normally
32
+ * but does not contribute load — this is the legacy "uncounted" item
33
+ * family from before the size system existed).
34
+ */
35
+ export function collectLoadContributors(children) {
36
+ const out = [];
37
+ walk(children, out);
38
+ return out;
39
+ }
40
+ function walk(children, out) {
41
+ for (const child of children) {
42
+ if (child.kind === 'item') {
43
+ const load = inferItemLoad(child);
44
+ if (load > 0 && child.box.width > 0) {
45
+ out.push({
46
+ startX: child.box.x,
47
+ endX: child.box.x + child.box.width,
48
+ load,
49
+ });
50
+ }
51
+ }
52
+ else if (child.kind === 'parallel' || child.kind === 'group') {
53
+ walk(child.children, out);
54
+ }
55
+ }
56
+ }
57
+ function inferItemLoad(item) {
58
+ if (item.capacity)
59
+ return item.capacity.value;
60
+ if (item.size)
61
+ return 1;
62
+ return 0;
63
+ }
64
+ /**
65
+ * Compute a lane's utilization model. Returns `null` when there is no
66
+ * meaningful underline to paint:
67
+ * - Lane has no `capacity:` (no denominator → undefined utilization).
68
+ * - Lane has no items contributing load (collectLoadContributors returns
69
+ * an empty list).
70
+ * - Both thresholds resolve to `none` (caller has opted out of every
71
+ * color band; nothing to paint).
72
+ *
73
+ * The returned `PositionedLaneUtilization` carries the resolved thresholds
74
+ * (in fraction form, or `null` for opt-out) alongside the segments so the
75
+ * renderer can render legends / tooltips without re-resolving anything.
76
+ */
77
+ export function computeLaneUtilization(opts) {
78
+ if (opts.capacityValue <= 0)
79
+ return null;
80
+ if (opts.warnFraction === null && opts.overFraction === null)
81
+ return null;
82
+ const contributors = collectLoadContributors(opts.children);
83
+ if (contributors.length === 0)
84
+ return null;
85
+ // Build sorted unique x coordinates from every event boundary.
86
+ const xSet = new Set();
87
+ for (const c of contributors) {
88
+ xSet.add(c.startX);
89
+ xSet.add(c.endX);
90
+ }
91
+ const xs = Array.from(xSet).sort((a, b) => a - b);
92
+ if (xs.length < 2)
93
+ return null;
94
+ // Per half-open interval [xs[j], xs[j+1]) compute the active load by
95
+ // summing every contributor whose span covers the interval midpoint.
96
+ // O(n²) but n is small (typically <20 items per lane); avoids the
97
+ // bookkeeping of an event-stream sweep without a measurable cost.
98
+ const raw = [];
99
+ for (let j = 0; j < xs.length - 1; j++) {
100
+ const a = xs[j];
101
+ const b = xs[j + 1];
102
+ const mid = (a + b) / 2;
103
+ let load = 0;
104
+ for (const c of contributors) {
105
+ if (c.startX <= mid && mid < c.endX)
106
+ load += c.load;
107
+ }
108
+ raw.push({
109
+ startX: a,
110
+ endX: b,
111
+ load,
112
+ classification: classifyLoad(load, opts.capacityValue, opts.warnFraction, opts.overFraction),
113
+ });
114
+ }
115
+ // Coalesce adjacent same-classification segments so the renderer paints
116
+ // one rectangle per visible color band rather than per event boundary.
117
+ const segments = [];
118
+ for (const s of raw) {
119
+ const last = segments[segments.length - 1];
120
+ if (last && last.classification === s.classification && last.endX === s.startX) {
121
+ last.endX = s.endX;
122
+ // Keep the first interval's `load` as a representative; the
123
+ // coalesced span may have varying load within a single color
124
+ // band but renderers paint by class, not by exact load.
125
+ }
126
+ else {
127
+ segments.push({ ...s });
128
+ }
129
+ }
130
+ return {
131
+ segments,
132
+ capacityValue: opts.capacityValue,
133
+ warnFraction: opts.warnFraction,
134
+ overFraction: opts.overFraction,
135
+ };
136
+ }
137
+ /**
138
+ * Classify a single load value against the lane's resolved capacity and
139
+ * thresholds. Spec § "Load function and segmentation":
140
+ * - `u < warn-at` → green (healthy; includes `u = 0` so the underline
141
+ * stays continuous).
142
+ * - `warn-at ≤ u < over-at` → yellow.
143
+ * - `u ≥ over-at` → red.
144
+ *
145
+ * `null` thresholds disable that color band: a `null` warnFraction collapses
146
+ * to a binary green / red indicator; a `null` overFraction collapses to
147
+ * green / yellow; both `null` is unreachable here (caller short-circuits
148
+ * upstream).
149
+ */
150
+ export function classifyLoad(load, capacity, warnFraction, overFraction) {
151
+ const u = load / capacity;
152
+ if (overFraction !== null && u >= overFraction)
153
+ return 'red';
154
+ if (warnFraction !== null && u >= warnFraction)
155
+ return 'yellow';
156
+ return 'green';
157
+ }
158
+ /**
159
+ * Resolve the lane's effective utilization thresholds, applying the spec's
160
+ * resolution order:
161
+ *
162
+ * 1. Lane's own `utilization-warn-at:` / `utilization-over-at:` properties.
163
+ * 2. The applicable `default swimlane` declaration's properties.
164
+ * 3. Built-in defaults (`warn-at:80%`, `over-at:100%`).
165
+ *
166
+ * Each side resolves independently — a lane can pin only one threshold and
167
+ * inherit the other from defaults / built-ins. The returned `null` means
168
+ * "opted out via `none`" and disables that color band downstream.
169
+ *
170
+ * Malformed values (already reported by the validator) are treated as
171
+ * "unset" — the resolver falls through to the next layer rather than
172
+ * double-reporting.
173
+ */
174
+ export function resolveLaneUtilizationThresholds(lane, defaults) {
175
+ const dflt = defaults.get('swimlane');
176
+ const warn = resolveThreshold(propValue(lane.properties, 'utilization-warn-at'), dflt ? propValue(dflt.properties, 'utilization-warn-at') : undefined, DEFAULT_UTILIZATION_WARN_FRACTION);
177
+ const over = resolveThreshold(propValue(lane.properties, 'utilization-over-at'), dflt ? propValue(dflt.properties, 'utilization-over-at') : undefined, DEFAULT_UTILIZATION_OVER_FRACTION);
178
+ return { warn, over };
179
+ }
180
+ function resolveThreshold(laneVal, defaultVal, builtinFraction) {
181
+ const raw = laneVal ?? defaultVal;
182
+ const parsed = parseThresholdRaw(raw);
183
+ if (parsed.kind === 'unset')
184
+ return builtinFraction;
185
+ if (parsed.kind === 'none')
186
+ return null;
187
+ return parsed.value;
188
+ }
189
+ const PERCENT_RE = /^\d+(?:\.\d+)?%$/;
190
+ const DECIMAL_FRACTION_RE = /^\d+\.\d+$/;
191
+ function parseThresholdRaw(val) {
192
+ if (val === undefined)
193
+ return { kind: 'unset' };
194
+ if (val === 'none')
195
+ return { kind: 'none' };
196
+ if (PERCENT_RE.test(val)) {
197
+ const n = parseFloat(val) / 100;
198
+ return Number.isFinite(n) && n > 0 ? { kind: 'number', value: n } : { kind: 'unset' };
199
+ }
200
+ if (DECIMAL_FRACTION_RE.test(val)) {
201
+ const n = parseFloat(val);
202
+ return Number.isFinite(n) && n > 0 ? { kind: 'number', value: n } : { kind: 'unset' };
203
+ }
204
+ return { kind: 'unset' };
205
+ }
206
+ //# sourceMappingURL=lane-utilization.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lane-utilization.js","sourceRoot":"","sources":["../src/lane-utilization.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,143 @@
1
+ import type { EntityProperty, GroupBlock, ItemDeclaration, LabelDeclaration, ParallelBlock, SymbolDeclaration } from '@nowline/core';
2
+ import type { BandScale } from './band-scale.js';
3
+ import type { resolveCalendar } from './calendar.js';
4
+ import type { StyleContext } from './style-resolution.js';
5
+ import type { TimeScale } from './time-scale.js';
6
+ import type { MarkerRowPlacement, Point, PositionedItem, PositionedTimelineScale, PositionedTrackChild, ResolvedSize, SlackCorridor } from './types.js';
7
+ import type { WorkingCalendar } from './working-calendar.js';
8
+ /** Slim accumulator used while sequencing items into a track. */
9
+ export interface TrackCursor {
10
+ /** Left edge where the next item begins. */
11
+ x: number;
12
+ /** Top edge of the current row. */
13
+ y: number;
14
+ /** Accumulated height of the track. */
15
+ height: number;
16
+ /** Rightmost edge reached. */
17
+ maxX: number;
18
+ }
19
+ export declare function newCursor(x: number, y: number): TrackCursor;
20
+ /**
21
+ * Shared layout state passed through the per-entity sequencers and
22
+ * Renderable nodes. Owns the resolved calendar/timeline/style scope plus
23
+ * the running entity-edge maps that `after:` / `before:` references read.
24
+ */
25
+ export interface LayoutContext {
26
+ cal: ReturnType<typeof resolveCalendar>;
27
+ styleCtx: StyleContext;
28
+ sizes: Map<string, ResolvedSize>;
29
+ labels: Map<string, LabelDeclaration>;
30
+ teams: Map<string, import('@nowline/core').TeamDeclaration>;
31
+ persons: Map<string, import('@nowline/core').PersonDeclaration>;
32
+ /**
33
+ * Custom `symbol` declarations from `ResolvedConfig.symbols`. Used by
34
+ * `resolveCapacityIcon` (in `capacity.ts`) to dereference custom symbol
35
+ * ids like `capacity-icon:budget` to the symbol's `unicode:` payload.
36
+ * Empty when the file declares no symbols.
37
+ */
38
+ symbols: Map<string, SymbolDeclaration>;
39
+ footnoteIndex: Map<string, number>;
40
+ /** For each footnote id, the list of `on:` host ids it references. */
41
+ footnoteHosts: Map<string, string[]>;
42
+ timeline: PositionedTimelineScale;
43
+ scale: TimeScale;
44
+ calendar: WorkingCalendar;
45
+ bandScale: BandScale;
46
+ entityLeftEdges: Map<string, number>;
47
+ entityRightEdges: Map<string, number>;
48
+ entityMidpoints: Map<string, Point>;
49
+ /**
50
+ * Visual edges for items (entries with a painted bar). Differs
51
+ * from `entityLeftEdges`/`entityRightEdges` (logical column
52
+ * boundaries) by `ITEM_INSET_PX` on each side so dependency
53
+ * arrows attach to the painted bar edge instead of landing in
54
+ * the inter-column gutter. Anchors and milestones are absent —
55
+ * their attach geometry uses `(center.x, target.row.midY)` on
56
+ * the cut line, computed inline by `buildDependencies`.
57
+ */
58
+ entityVisualLeftX: Map<string, number>;
59
+ entityVisualRightX: Map<string, number>;
60
+ /**
61
+ * Per-item exit point for `after:` dependency arrows leaving
62
+ * this entity. Default = `(visualRight, midY)`. When the
63
+ * caption spills past the bar's right edge (`textSpills`), the
64
+ * exit drops to `(box.x + box.width / 2, box.y + box.height)`
65
+ * — the bottom-middle of the progress strip — so the arrow
66
+ * doesn't visually pierce the spilled title/meta text to the
67
+ * right of the bar.
68
+ */
69
+ itemArrowSource: Map<string, Point>;
70
+ /**
71
+ * Flow key for each item, used to dedupe milestone slack arrows.
72
+ * A "flow" is the deepest enclosing single-track container —
73
+ * swimlane root, sequential group, or one parallel sub-track.
74
+ * Two items share a flowKey iff they share that container path
75
+ * (file order already encodes their ordering, so only the
76
+ * latest predecessor in each flow contributes a slack arrow).
77
+ */
78
+ itemFlowKey: Map<string, string>;
79
+ /**
80
+ * The flow key currently being built by the swimlane walk.
81
+ * Container nodes (`SwimlaneNode`, `GroupNode`, `ParallelNode`)
82
+ * push their own segment onto this string before recursing into
83
+ * children and restore it afterward. `sequenceItem` reads this
84
+ * value to populate `itemFlowKey`.
85
+ */
86
+ currentFlowKey: string;
87
+ /**
88
+ * Y coordinate where milestone slack arrows attach for each item id.
89
+ * Defaults to the item's row midpoint; when an item's caption spills
90
+ * past the bar's right edge, drops to the progress-strip's vertical
91
+ * center (`box.y + box.height - PROGRESS_STRIP_HEIGHT_PX / 2`) so the
92
+ * arrow stays clear of the spilled title/meta line and visually
93
+ * aligns with the bottom-edge progress bar instead.
94
+ */
95
+ itemSlackAttachY: Map<string, number>;
96
+ /**
97
+ * Horizontal arrow corridors that the swimlane row-packer must avoid.
98
+ * Empty during the first layout pass; populated from the first
99
+ * pass's milestones and consulted on the second pass so the binding
100
+ * predecessor (and any unrelated overlapping item) drops to a row
101
+ * whose Y does not match the corridor.
102
+ */
103
+ slackCorridors: SlackCorridor[];
104
+ /**
105
+ * Pre-computed marker-row placement (row index + label box + side)
106
+ * for every anchor and date-pinned milestone. After-only milestones
107
+ * pack against this map at build time; date-pinned entries are
108
+ * snapshot upstream so their (Y, label) survives swimlane reflows.
109
+ */
110
+ markerRowPlacements: Map<string, MarkerRowPlacement>;
111
+ chartTopY: number;
112
+ chartBottomY: number;
113
+ /**
114
+ * Y coordinate at the bottom of the last swimlane / include region.
115
+ * Distinct from `chartBottomY`, which extends through any mirrored
116
+ * bottom timeline tick panel. Marker cut-lines (anchors, milestones)
117
+ * stop here so they never invade the bottom date strip; the now-line
118
+ * uses the wider `chartBottomY` (or the bottom panel's bottom edge)
119
+ * to thread the entire timeline strip.
120
+ */
121
+ swimlaneBottomY: number;
122
+ chartRightX: number;
123
+ }
124
+ /**
125
+ * Bundle of layout-internal helper callbacks injected from `layout.ts`
126
+ * into the per-entity Renderable nodes. Keeping the helpers in
127
+ * `layout.ts` (rather than splitting them across many tiny files) means
128
+ * the nodes stay small and importable without a runtime cycle: they
129
+ * receive the helpers at construction time via this struct.
130
+ */
131
+ export interface LayoutHelpers {
132
+ sequenceItem: (child: ItemDeclaration, cursor: TrackCursor, ctx: LayoutContext, ownerOverride?: string) => PositionedItem;
133
+ sequenceOne: (child: ItemDeclaration | GroupBlock | ParallelBlock, cursor: TrackCursor, ctx: LayoutContext) => PositionedTrackChild;
134
+ resolveChildStart: (props: EntityProperty[], seqDefault: number, laneLeftX: number, ctx: LayoutContext) => number;
135
+ newCursor: (x: number, y: number) => TrackCursor;
136
+ estimateTextWidth: (text: string, fontSize: number) => number;
137
+ /** Predict the extra height an item's wrapped label-chip rows will
138
+ * add, so callers can size the row pitch BEFORE handing off to
139
+ * `sequenceItem`. Returns 0 when the item's labels all fit on a
140
+ * single chip row. */
141
+ predictItemChipExtraHeight: (item: ItemDeclaration, ctx: LayoutContext) => number;
142
+ }
143
+ //# sourceMappingURL=layout-context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-context.d.ts","sourceRoot":"","sources":["../src/layout-context.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ // Layout context — shared internal types used by `layout.ts` and the
2
+ // node files under `nodes/`. Extracted so per-entity nodes (e.g.
3
+ // `swimlane-node.ts`) can type their inputs without forcing a circular
4
+ // import on the production composition root in `layout.ts`.
5
+ export function newCursor(x, y) {
6
+ return { x, y, height: 0, maxX: x };
7
+ }
8
+ //# sourceMappingURL=layout-context.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"layout-context.js","sourceRoot":"","sources":["../src/layout-context.ts"],"names":[],"mappings":"AAAA,qEAAqE;AACrE,iEAAiE;AACjE,uEAAuE;AACvE,4DAA4D;AAqC5D,MAAM,UAAU,SAAS,CAAC,CAAS,EAAE,CAAS;IAC1C,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;AACxC,CAAC"}