@nowline/layout 0.8.6 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,31 +1,29 @@
1
1
  // ParallelNode — Renderable for a `parallel { ... }` block. Stacks
2
- // children top-to-bottom (each child owns a fresh sub-track) and reports
3
- // the union bounding box. Each child is sequenced via the injected
4
- // `deps.sequenceOne` callback, which dispatches to the appropriate
5
- // per-entity Renderable (or, transitionally, the legacy sequencer
6
- // helpers in `layout.ts`).
2
+ // children top-to-bottom inside its body (each child owns a fresh
3
+ // sub-track) under a header band, the way a group is drawn
4
+ // (specs/true-width-layout.md §3.3; `container-header-geometry.ts`). Each
5
+ // child is sequenced via the injected `helpers.sequenceOne` callback
6
+ // (`sequence-one.ts`), which dispatches to `sequenceItem`, `GroupNode` or a
7
+ // nested `ParallelNode`.
7
8
 
8
9
  import type { GroupBlock, ItemDeclaration, ParallelBlock } from '@nowline/core';
10
+ import { containerChrome } from '../container-header-geometry.js';
9
11
  import { propValues } from '../dsl-utils.js';
10
12
  import { computeContainerInlineDatePins, pickInlineDate } from '../inline-date-pin-geometry.js';
11
- import type { LayoutContext, TrackCursor } from '../layout-context.js';
13
+ import {
14
+ type LayoutContext,
15
+ type LayoutHelpers,
16
+ newCursor,
17
+ type TrackCursor,
18
+ } from '../layout-context.js';
12
19
  import { resolveStyle } from '../style-resolution.js';
13
- import { TRACK_BLOCK_TAIL_GUTTER_PX } from '../themes/shared.js';
14
20
  import type { BoundingBox, PositionedParallel, PositionedTrackChild } from '../types.js';
15
-
16
- export interface ParallelNodeDeps {
17
- sequenceOne: (
18
- child: ItemDeclaration | GroupBlock | ParallelBlock,
19
- cursor: TrackCursor,
20
- ctx: LayoutContext,
21
- ) => PositionedTrackChild;
22
- newCursor: (x: number, y: number) => TrackCursor;
23
- }
21
+ import { waveFloorX } from '../wave-layout.js';
24
22
 
25
23
  export class ParallelNode {
26
24
  constructor(
27
25
  public readonly node: ParallelBlock,
28
- private readonly deps: ParallelNodeDeps,
26
+ private readonly helpers: LayoutHelpers,
29
27
  ) {}
30
28
 
31
29
  get id(): string {
@@ -34,18 +32,33 @@ export class ParallelNode {
34
32
 
35
33
  /**
36
34
  * Sequence children into stacked sub-tracks, advance the parent
37
- * `cursor` past the parallel's right edge plus
38
- * `TRACK_BLOCK_TAIL_GUTTER_PX` of breathing room, and return a
39
- * `PositionedParallel`.
35
+ * `cursor` to the parallel's end in time (the latest track end), and
36
+ * return a `PositionedParallel`.
40
37
  */
41
38
  place(cursor: TrackCursor, ctx: LayoutContext): PositionedParallel {
42
39
  const { node } = this;
43
- const { deps } = this;
40
+ const { helpers } = this;
44
41
  const style = resolveStyle('parallel', node.properties, ctx.styleCtx);
45
- const startX = cursor.x;
46
- const startY = cursor.y;
42
+ // The block's own wave and its lead wave floor the shared start
43
+ // (specs/waves.md §5.1); each track is floored again by its own
44
+ // wave where it is placed. A no-op without waves.
45
+ const startX = waveFloorX(node, cursor.x, ctx);
46
+ const afterDate = pickInlineDate(propValues(node.properties, 'after'));
47
+ const beforeDate = pickInlineDate(propValues(node.properties, 'before'));
48
+ const title = node.title ?? node.name;
49
+ const chrome = containerChrome({
50
+ drawn: true,
51
+ hasTitle: Boolean(title),
52
+ hasPins: Boolean(afterDate || beforeDate),
53
+ hasBracket: style.bracket !== 'none',
54
+ });
55
+ const firstTrackY = cursor.y + chrome.header + chrome.pad;
47
56
  const children: PositionedTrackChild[] = [];
48
- let maxRight = startX;
57
+ // The tracks' span in time: the bracket and body open on the
58
+ // earliest track start and close on the latest track end, the join
59
+ // (bars span exactly their dates, specs/true-width-layout.md §3.1).
60
+ let minTimeLeft = Number.POSITIVE_INFINITY;
61
+ let maxTimeRight = Number.NEGATIVE_INFINITY;
49
62
  let accumulatedHeight = 0;
50
63
 
51
64
  // Each child of a parallel block lives on its own sub-track,
@@ -61,47 +74,65 @@ export class ParallelNode {
61
74
  for (const child of node.content) {
62
75
  if (child.$type === 'DescriptionDirective') continue;
63
76
  ctx.currentFlowKey = `${previousFlowKey}/par:${parId}#${childIndex}`;
64
- const subCursor = deps.newCursor(startX, startY + accumulatedHeight);
65
- const positioned = deps.sequenceOne(
77
+ const subCursor = newCursor(startX, firstTrackY + accumulatedHeight);
78
+ const positioned = helpers.sequenceOne(
66
79
  child as ItemDeclaration | GroupBlock,
67
80
  subCursor,
68
81
  ctx,
69
82
  );
70
83
  children.push(positioned);
71
84
  accumulatedHeight += Math.max(ctx.bandScale.step(), subCursor.height);
72
- maxRight = Math.max(maxRight, subCursor.maxX);
85
+ minTimeLeft = Math.min(minTimeLeft, subCursor.startX);
86
+ maxTimeRight = Math.max(maxTimeRight, subCursor.x);
73
87
  childIndex++;
74
88
  }
75
89
  ctx.currentFlowKey = previousFlowKey;
90
+ if (children.length === 0) {
91
+ // Under `show` an empty box still opens on a working day.
92
+ minTimeLeft = ctx.scale.showsNonWorking ? ctx.scale.startX(startX) : startX;
93
+ maxTimeRight = minTimeLeft;
94
+ }
76
95
 
96
+ // Each track's height carries the inter-row gap below it; the body
97
+ // ends `pad` below the last track's painted bottom, and the cursor
98
+ // adds the gap back after the body.
99
+ const step = ctx.bandScale.step();
100
+ const interRowGap = step - ctx.bandScale.bandwidth();
101
+ const contentHeight = Math.max(step, accumulatedHeight) - interRowGap;
77
102
  const box: BoundingBox = {
78
- x: startX,
79
- y: startY,
80
- width: maxRight - startX,
81
- height: accumulatedHeight,
103
+ x: minTimeLeft,
104
+ y: cursor.y,
105
+ width: maxTimeRight - minTimeLeft,
106
+ height: chrome.header + chrome.pad + contentHeight + chrome.pad,
82
107
  };
83
108
 
84
- cursor.x = maxRight + TRACK_BLOCK_TAIL_GUTTER_PX;
85
- cursor.maxX = Math.max(cursor.maxX, cursor.x);
86
- cursor.height = Math.max(cursor.height, accumulatedHeight);
109
+ // No tail gutter: the parallel joins on its latest track end in
110
+ // time, as engine C does (specs/scheduler-unification.md §8
111
+ // decisions 6, 8).
112
+ cursor.startX = box.x;
113
+ cursor.x = maxTimeRight;
114
+ cursor.height = Math.max(cursor.height, box.height + interRowGap);
87
115
 
88
116
  const id = node.name;
117
+ // `after:` the parallel is a time path: its latest track end.
89
118
  if (id) {
90
119
  ctx.entityLeftEdges.set(id, box.x);
91
- ctx.entityRightEdges.set(id, box.x + box.width);
120
+ ctx.entityRightEdges.set(id, maxTimeRight);
92
121
  }
93
122
 
94
123
  const inlineDatePins = computeContainerInlineDatePins({
95
124
  box,
96
- afterDate: pickInlineDate(propValues(node.properties, 'after')),
97
- beforeDate: pickInlineDate(propValues(node.properties, 'before')),
125
+ afterDate,
126
+ beforeDate,
127
+ title,
98
128
  });
99
129
 
100
130
  return {
101
131
  kind: 'parallel',
102
132
  id,
103
- title: node.title ?? node.name,
133
+ title,
104
134
  box,
135
+ headerHeight: chrome.header,
105
136
  children,
106
137
  style,
107
138
  inlineDatePins: inlineDatePins.length > 0 ? inlineDatePins : undefined,
@@ -0,0 +1,194 @@
1
+ // Region markers — an isolated region's own anchors and milestones, drawn
2
+ // in a marker row of the region's own, under its label tab
3
+ // (specs/rendering.md § Included Roadmap Region). The row packs with the
4
+ // host's packer and label rules, bounded by the region's dashed border
5
+ // instead of the chart; the region's lanes move down by its height, and
6
+ // each cut line stops at the region's last lane.
7
+
8
+ import type { AnchorDeclaration, IsolatedRegion } from '@nowline/core';
9
+ import type { LayoutContext } from '../layout-context.js';
10
+ import { estimateTextWidth } from '../text-measure.js';
11
+ import type { PositionedAnchor, PositionedIncludeRegion, PositionedMilestone } from '../types.js';
12
+ import { buildAnchors } from './anchor-node.js';
13
+ import {
14
+ datePinnedMarkerEntries,
15
+ type MarkerEntity,
16
+ markerArrows,
17
+ markerLabelsRightX,
18
+ markerRowPlacements,
19
+ packMarkerRow,
20
+ } from './marker-band.js';
21
+ import { MARKER_ROW_PITCH_PX } from './marker-geometry.js';
22
+ import { buildMilestones, type MilestonePlacement, positionMilestones } from './milestone-node.js';
23
+
24
+ /**
25
+ * How far a region's marker labels stay inside the region box on either
26
+ * side: the renderer's 8 px border inset plus 8 px of air.
27
+ */
28
+ export const REGION_MARKER_INSET_X_PX = 16;
29
+
30
+ /** The region marker band's top, below the region's top border. */
31
+ const REGION_MARKER_BAND_TOP_PX = 8;
32
+
33
+ /** Air between the region marker band and the region's first lane. */
34
+ const REGION_MARKER_BAND_GAP_PX = 6;
35
+
36
+ /** A region's placed markers and how far its lanes move down for them. */
37
+ export interface RegionMarkers {
38
+ anchors: PositionedAnchor[];
39
+ milestones: PositionedMilestone[];
40
+ /** The lanes' shift: 0 when the region has no marker to draw. */
41
+ laneShift: number;
42
+ }
43
+
44
+ /**
45
+ * Place `region`'s anchors and milestones once its lanes are laid out
46
+ * from `lanesTopY` down to `lanesBottomY`, inside a region whose top
47
+ * border is at `regionTopY`. The pack is bounded by the region's left
48
+ * inset and by `chartRightX` (the canvas before finalize grows it) less
49
+ * the right inset; a label that fits on neither side packs to the right
50
+ * and finalize f3 grows the canvas for it (`regionMarkerCanvasRightX`).
51
+ * The caller shifts the lanes by `laneShift`.
52
+ */
53
+ export function placeRegionMarkers(
54
+ region: IsolatedRegion,
55
+ ctx: LayoutContext,
56
+ regionTopY: number,
57
+ lanesTopY: number,
58
+ lanesBottomY: number,
59
+ chartRightX: number,
60
+ ): RegionMarkers {
61
+ // A milestone `after:` an anchor reads the anchor's edge. Without waves
62
+ // the edges are already seeded for the lanes; with waves they are
63
+ // seeded here, after the lanes, so the lanes still see only the wave
64
+ // edges.
65
+ seedRegionAnchorEdges(region.content.anchors, ctx);
66
+ const placements = buildMilestones(region.content.milestones, ctx);
67
+ const entries = regionMarkerEntries(region.content.anchors, placements, ctx);
68
+ if (entries.length === 0) return { anchors: [], milestones: [], laneShift: 0 };
69
+
70
+ const leftX = REGION_MARKER_INSET_X_PX;
71
+ const packed = packMarkerRow(
72
+ entries,
73
+ leftX,
74
+ markerLabelsRightX(
75
+ entries,
76
+ leftX,
77
+ chartRightX - REGION_MARKER_INSET_X_PX,
78
+ estimateTextWidth,
79
+ ),
80
+ estimateTextWidth,
81
+ markerArrows(placements),
82
+ );
83
+ const bandTopY = regionTopY + REGION_MARKER_BAND_TOP_PX;
84
+ const laneShift =
85
+ bandTopY + packed.rowCount * MARKER_ROW_PITCH_PX + REGION_MARKER_BAND_GAP_PX - lanesTopY;
86
+ const cutBottomY = lanesBottomY + laneShift;
87
+
88
+ const rows = markerRowPlacements(packed.placements, bandTopY);
89
+ ctx.markerRowPlacements = rows;
90
+ ctx.chartTopY = lanesTopY + laneShift;
91
+ ctx.swimlaneBottomY = cutBottomY;
92
+ const anchors = buildAnchors(region.content.anchors, ctx, new Set());
93
+ // Placement read each item predecessor's y before the lanes moved; an
94
+ // arrow from a region marker starts on its row in `rows`.
95
+ const milestones = positionMilestones(
96
+ placements.map((p) => ({
97
+ ...p,
98
+ cutTopY: ctx.chartTopY,
99
+ cutBottomY,
100
+ slackArrows: p.slackArrows?.map((a) => ('y' in a ? { x: a.x, y: a.y + laneShift } : a)),
101
+ })),
102
+ rows,
103
+ );
104
+ return { anchors, milestones, laneShift };
105
+ }
106
+
107
+ /**
108
+ * The canvas right edge `region`'s marker labels need: a label that fits
109
+ * on neither side of its marker inside the region's insets grows the
110
+ * canvas until it fits on the right, the host's marker-row rule. Returns
111
+ * `chartRightX` when every label fits.
112
+ */
113
+ export function regionMarkerCanvasRightX(
114
+ region: PositionedIncludeRegion,
115
+ chartRightX: number,
116
+ ): number {
117
+ const entries: MarkerEntity[] = [
118
+ ...(region.anchors ?? []).map((a) => positionedEntry(a, false)),
119
+ ...(region.milestones ?? []).map((m) => positionedEntry(m, true)),
120
+ ];
121
+ if (entries.length === 0) return chartRightX;
122
+ const rightX = markerLabelsRightX(
123
+ entries,
124
+ region.box.x + REGION_MARKER_INSET_X_PX,
125
+ chartRightX - REGION_MARKER_INSET_X_PX,
126
+ estimateTextWidth,
127
+ );
128
+ return rightX + REGION_MARKER_INSET_X_PX;
129
+ }
130
+
131
+ /** The right end of `region`'s rightmost marker label, or 0 with none. */
132
+ export function regionMarkerLabelsRightX(region: PositionedIncludeRegion): number {
133
+ let rightX = 0;
134
+ for (const m of [...(region.anchors ?? []), ...(region.milestones ?? [])]) {
135
+ rightX = Math.max(rightX, m.labelBox.x + m.labelBox.width);
136
+ }
137
+ return rightX;
138
+ }
139
+
140
+ /**
141
+ * The host's marker order: the date-pinned anchors, the dated milestones,
142
+ * then the after-only ones, so the stable sort in `packMarkerRow` breaks
143
+ * a tie on x the same way the host's band does.
144
+ */
145
+ function regionMarkerEntries(
146
+ anchors: ReadonlyMap<string, AnchorDeclaration>,
147
+ milestones: readonly MilestonePlacement[],
148
+ ctx: LayoutContext,
149
+ ): MarkerEntity[] {
150
+ const entries: MarkerEntity[] = datePinnedMarkerEntries(anchors, new Map(), ctx.scale);
151
+ const milestoneEntry = (m: MilestonePlacement): MarkerEntity => ({
152
+ id: m.id,
153
+ centerX: m.slipX ?? m.x,
154
+ radius: m.radius,
155
+ title: m.title,
156
+ fontSize: 10,
157
+ bold: true,
158
+ ...(m.slipX !== undefined ? { slipFromX: m.x } : {}),
159
+ });
160
+ entries.push(...milestones.filter((m) => m.fixed).map(milestoneEntry));
161
+ entries.push(...milestones.filter((m) => !m.fixed).map(milestoneEntry));
162
+ return entries;
163
+ }
164
+
165
+ function positionedEntry(m: PositionedAnchor | PositionedMilestone, bold: boolean): MarkerEntity {
166
+ // A slipped milestone's label sits at its slipped diamond.
167
+ const slipX = 'slip' in m ? m.slip?.x : undefined;
168
+ return {
169
+ id: m.id ?? '',
170
+ centerX: slipX ?? m.center.x,
171
+ radius: m.radius,
172
+ title: m.title,
173
+ fontSize: 10,
174
+ bold,
175
+ // The label is forced right of the slipped diamond, as in the pack
176
+ // (`regionMarkerEntries`) and f3's host entries.
177
+ ...(slipX !== undefined ? { slipFromX: m.center.x } : {}),
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Seed a region's dated anchors into its context's edge maps at their x on
183
+ * the host's scale, the way the host seeds its own (`phases/place.ts`).
184
+ * An anchor outside the date window is skipped, as on the host.
185
+ */
186
+ export function seedRegionAnchorEdges(
187
+ anchors: ReadonlyMap<string, AnchorDeclaration>,
188
+ ctx: LayoutContext,
189
+ ): void {
190
+ for (const e of datePinnedMarkerEntries(anchors, new Map(), ctx.scale)) {
191
+ ctx.entityLeftEdges.set(e.id, e.centerX);
192
+ ctx.entityRightEdges.set(e.id, e.centerX);
193
+ }
194
+ }