@nowline/layout 0.8.6 → 0.9.0

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