@nowline/export-mermaid 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.
package/README.md CHANGED
@@ -47,7 +47,7 @@ nowline roadmap.nowline -o roadmap.md # extension-inferred
47
47
  gantt
48
48
  title <title>
49
49
  dateFormat YYYY-MM-DD
50
- axisFormat %b %d
50
+ excludes saturday, sunday
51
51
  section <swimlane>
52
52
  Item one : done, item-one, 2026-01-06, 5d
53
53
  Item two : active, item-two, after item-one, 10d
@@ -57,7 +57,19 @@ gantt
57
57
  ```
58
58
 
59
59
  The first line of the Markdown is the roadmap title. The fenced
60
- ```` ```mermaid ```` block contains a valid `gantt` diagram; the trailing
60
+ ```` ```mermaid ```` block contains a valid `gantt` diagram. Under
61
+ `calendar:business` (the default) `excludes saturday, sunday` follows
62
+ `dateFormat`, so Mermaid counts each `Nd` in working days like the chart;
63
+ `calendar:full` and `calendar:custom` have no days off and emit no
64
+ `excludes` line, unless the file declares days off: each day of a
65
+ `non-working` closure follows the weekday names as an ISO date
66
+ (`excludes saturday, sunday, 2026-11-26, 2026-11-27`). A `working`
67
+ exception can't be written, so its days are counted as `working-day` in the
68
+ `%%` comment. Durations are the chart's own day count for the item
69
+ (sizes, `q` and `capacity:` included), written as `Nd` with up to two
70
+ decimals (`10d`, `7.5d`), so a `full` `4w` is `28d`. The roadmap start and
71
+ wave-end milestones fall on working days; anchor and milestone dates are
72
+ written as declared. The trailing
61
73
  `%%` comment line lists Nowline features that don't have a Mermaid
62
74
  equivalent and have been dropped.
63
75
 
@@ -1,7 +1,6 @@
1
1
  /**
2
- * Convert a duration literal to a Mermaid token (`5d`, `2w`, …).
3
- * Returns `undefined` when the literal is missing — caller emits no duration
4
- * and Mermaid uses its default 1d.
2
+ * `Nd` with up to two decimals, trailing zeros trimmed (`7.5d`, `7.33d`).
3
+ * Returns `undefined` for no duration; the caller falls back to `1d`.
5
4
  */
6
- export declare function durationToMermaid(literal: string | undefined): string | undefined;
5
+ export declare function daysToMermaid(days: number | undefined): string | undefined;
7
6
  //# sourceMappingURL=duration.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../src/duration.ts"],"names":[],"mappings":"AAmBA;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAwBjF"}
1
+ {"version":3,"file":"duration.d.ts","sourceRoot":"","sources":["../src/duration.ts"],"names":[],"mappings":"AASA;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAI1E"}
package/dist/duration.js CHANGED
@@ -1,49 +1,19 @@
1
- // Convert Nowline duration tokens (e.g. `2w`, `1m`, `xl`) into Mermaid's
2
- // duration syntax. Mermaid `gantt` accepts day / week / hour units.
1
+ // Mermaid task durations. The day count is engine C's `ScheduledItem.days`
2
+ // under the file's calendar (specs/working-calendar.md §8), so sizes,
3
+ // `capacity:` and every unit match the chart. It is always written as `Nd`:
4
+ // with the business `excludes` Mermaid counts those in working days, and a
5
+ // full-calendar `4w` becomes `28d`, the same span.
3
6
  //
4
7
  // Spec: specs/handoffs/m2c.md § 6 + the bridge rules in
5
8
  // specs/rendering.md § Markdown+Mermaid Bridge.
6
- const SIZE_BUCKET_DAYS = {
7
- xs: 1,
8
- sm: 3,
9
- s: 3,
10
- md: 5,
11
- m: 5,
12
- lg: 10,
13
- l: 10,
14
- xl: 15,
15
- };
16
- const NUMERIC_RE = /^(\d+(?:\.\d+)?)\s*(d|w|m|y)?$/i;
17
9
  /**
18
- * Convert a duration literal to a Mermaid token (`5d`, `2w`, …).
19
- * Returns `undefined` when the literal is missing — caller emits no duration
20
- * and Mermaid uses its default 1d.
10
+ * `Nd` with up to two decimals, trailing zeros trimmed (`7.5d`, `7.33d`).
11
+ * Returns `undefined` for no duration; the caller falls back to `1d`.
21
12
  */
22
- export function durationToMermaid(literal) {
23
- if (!literal)
13
+ export function daysToMermaid(days) {
14
+ if (days === undefined || !(days > 0))
24
15
  return undefined;
25
- const trimmed = literal.trim().toLowerCase();
26
- if (!trimmed)
27
- return undefined;
28
- // Size buckets resolve to working-day counts.
29
- if (trimmed in SIZE_BUCKET_DAYS) {
30
- return `${SIZE_BUCKET_DAYS[trimmed]}d`;
31
- }
32
- const match = NUMERIC_RE.exec(trimmed);
33
- if (!match)
34
- return undefined;
35
- const value = match[1];
36
- const unit = (match[2] ?? 'd').toLowerCase();
37
- if (unit === 'd' || unit === 'w')
38
- return `${value}${unit}`;
39
- if (unit === 'm') {
40
- const days = Math.round(Number(value) * 22);
41
- return `${days}d`;
42
- }
43
- if (unit === 'y') {
44
- const days = Math.round(Number(value) * 252);
45
- return `${days}d`;
46
- }
47
- return undefined;
16
+ // parseFloat strips trailing zeros: `7.50` → `7.5`.
17
+ return `${parseFloat(days.toFixed(2))}d`;
48
18
  }
49
19
  //# sourceMappingURL=duration.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"duration.js","sourceRoot":"","sources":["../src/duration.ts"],"names":[],"mappings":"AAAA,yEAAyE;AACzE,oEAAoE;AACpE,EAAE;AACF,wDAAwD;AACxD,gDAAgD;AAEhD,MAAM,gBAAgB,GAAqC;IACvD,EAAE,EAAE,CAAC;IACL,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,CAAC;IACJ,EAAE,EAAE,CAAC;IACL,CAAC,EAAE,CAAC;IACJ,EAAE,EAAE,EAAE;IACN,CAAC,EAAE,EAAE;IACL,EAAE,EAAE,EAAE;CACT,CAAC;AAEF,MAAM,UAAU,GAAG,iCAAiC,CAAC;AAErD;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAA2B;IACzD,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAC/B,MAAM,OAAO,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC7C,IAAI,CAAC,OAAO;QAAE,OAAO,SAAS,CAAC;IAE/B,8CAA8C;IAC9C,IAAI,OAAO,IAAI,gBAAgB,EAAE,CAAC;QAC9B,OAAO,GAAG,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC;IAC3C,CAAC;IAED,MAAM,KAAK,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IACvC,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAC7B,MAAM,KAAK,GAAG,KAAK,CAAC,CAAC,CAAC,CAAC;IACvB,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC;IAC7C,IAAI,IAAI,KAAK,GAAG,IAAI,IAAI,KAAK,GAAG;QAAE,OAAO,GAAG,KAAK,GAAG,IAAI,EAAE,CAAC;IAC3D,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,CAAC;QAC5C,OAAO,GAAG,IAAI,GAAG,CAAC;IACtB,CAAC;IACD,IAAI,IAAI,KAAK,GAAG,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC;QAC7C,OAAO,GAAG,IAAI,GAAG,CAAC;IACtB,CAAC;IACD,OAAO,SAAS,CAAC;AACrB,CAAC"}
1
+ {"version":3,"file":"duration.js","sourceRoot":"","sources":["../src/duration.ts"],"names":[],"mappings":"AAAA,2EAA2E;AAC3E,sEAAsE;AACtE,4EAA4E;AAC5E,2EAA2E;AAC3E,mDAAmD;AACnD,EAAE;AACF,wDAAwD;AACxD,gDAAgD;AAEhD;;;GAGG;AACH,MAAM,UAAU,aAAa,CAAC,IAAwB;IAClD,IAAI,IAAI,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;QAAE,OAAO,SAAS,CAAC;IACxD,oDAAoD;IACpD,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC;AAC7C,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAiDA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAKzD,MAAM,WAAW,cAAc;IAC3B,gEAAgE;IAChE,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAuCD,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CA8DxF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoEA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAYzD,MAAM,WAAW,cAAc;IAC3B,gEAAgE;IAChE,YAAY,CAAC,EAAE,OAAO,CAAC;CAC1B;AAiED,wBAAgB,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,OAAO,GAAE,cAAmB,GAAG,MAAM,CA6ExF"}
package/dist/index.js CHANGED
@@ -16,13 +16,23 @@
16
16
  // gantt
17
17
  // title <title>
18
18
  // dateFormat YYYY-MM-DD
19
- // axisFormat ...
19
+ // excludes saturday, sunday (only when the open week has days off)
20
20
  // section <swimlane>
21
21
  // <Task name>: <status>, <id>, <after-or-date>, <duration>
22
22
  // ```
23
23
  //
24
24
  // <%% lossy comment %%>
25
25
  //
26
+ // Calendar (specs/working-calendar.md §8): the file's calendar comes from
27
+ // engine C (`scheduleRoadmap`, built once per export). `excludes` lists the
28
+ // open week's non-working days, Monday first, so Mermaid counts `Nd` in
29
+ // working days; every duration is engine C's `days` written as `Nd` (a
30
+ // full-calendar `4w` is `28d`). Mermaid never checks a task's explicit start
31
+ // against `excludes`, so the dates this exporter derives are working days:
32
+ // the roadmap start is snapped to its first working day, and a wave-end
33
+ // milestone is the first working day at or after the wave's exclusive end.
34
+ // Declared anchor and milestone dates are written as given.
35
+ //
26
36
  // Every emitted task carries an explicit start token (`after <id>` or an
27
37
  // absolute `YYYY-MM-DD` date). Mermaid's positional task parser strips a
28
38
  // leading status keyword (`done`/`active`/`crit`/`milestone`) and then reads
@@ -31,13 +41,22 @@
31
41
  // start (`:done, my-id, 2w`) collapses to two fields after the status strip,
32
42
  // so Mermaid mis-reads the id as a start date and throws `Invalid date: my-id`
33
43
  // at render time. To keep an explicit id we therefore MUST emit a start token:
34
- // - declared `after:` deps -> `after <ids>`
35
- // - otherwise a lane follower -> `after <previous item in the lane>`
36
- // - otherwise a lane/track leader -> the roadmap start date
44
+ // - `after <ids>`: the declared `after:` deps (a group or parallel id
45
+ // expanded to the tasks that end it; neither is a Mermaid task), then the
46
+ // lane chain: the previous item in the lane, or every track end when it
47
+ // follows a parallel; a first child also gets its group's or parallel's
48
+ // own `after:`. Engine C starts an item at `max(prevEnd, ...after)`.
49
+ // - with none of those, a lane/track leader -> the roadmap start date
50
+ // - a member of wave k >= 2 also gets `after <wave k-1 id>`: appended to
51
+ // its `after` token, or replacing a lane/track leader's start date
52
+ // (specs/waves.md §10)
37
53
  // This mirrors Nowline's default "each item starts after the preceding item in
38
54
  // its lane" semantics (specs/rendering.md § Item Bars).
55
+ import { isGroupBlock, isParallelBlock } from '@nowline/core';
56
+ import { parseIsoDate, trackChildren, trackNodes } from '@nowline/core/internal';
39
57
  import { displayLabel, getProp, getProps, hasProp, roadmapTitle } from '@nowline/export-core';
40
- import { durationToMermaid } from './duration.js';
58
+ import { calendarExceptions, scheduleRoadmap, } from '@nowline/layout';
59
+ import { daysToMermaid } from './duration.js';
41
60
  function emptyCounts() {
42
61
  return {
43
62
  labels: 0,
@@ -50,12 +69,16 @@ function emptyCounts() {
50
69
  nestedSwimlanes: 0,
51
70
  description: 0,
52
71
  style: 0,
72
+ waveFloor: 0,
73
+ workingDay: 0,
53
74
  };
54
75
  }
55
76
  export function exportMermaid(inputs, options = {}) {
56
77
  const ast = inputs.ast;
57
78
  const title = roadmapTitle(ast.roadmapDecl ?? undefined);
58
- const startDate = resolveStartDate(inputs);
79
+ const schedule = inputs.schedule ?? scheduleRoadmap(ast, inputs.resolved, { today: inputs.today });
80
+ const { working } = schedule.calendar;
81
+ const startDate = formatIsoDate(working.dateAtWorkingIndex(resolveStartDate(inputs), 0));
59
82
  const drops = emptyCounts();
60
83
  const out = [];
61
84
  out.push(`# ${title}`);
@@ -64,6 +87,11 @@ export function exportMermaid(inputs, options = {}) {
64
87
  out.push('gantt');
65
88
  out.push(` title ${escapeMermaidText(title)}`);
66
89
  out.push(' dateFormat YYYY-MM-DD');
90
+ const exceptions = calendarExceptions(working, inputs.model.timeline.startDate, inputs.model.timeline.endDate);
91
+ const excludes = [...excludedWeekdays(working), ...excludedDates(exceptions)];
92
+ if (excludes.length > 0)
93
+ out.push(` excludes ${excludes.join(', ')}`);
94
+ drops.workingDay += workingDayCount(exceptions);
67
95
  // Footnotes are file-level; counted once.
68
96
  drops.footnote += ast.roadmapEntries.filter((e) => e.$type === 'FootnoteDeclaration').length;
69
97
  // Anchors → milestone entries (Mermaid doesn't support standalone anchors).
@@ -78,17 +106,20 @@ export function exportMermaid(inputs, options = {}) {
78
106
  out.push(` ${escapeTaskName(displayLabel(anchor))} :milestone, ${id}, ${date}, 0d`);
79
107
  }
80
108
  }
109
+ // Waves → one end milestone per wave, dated from the day schedule.
110
+ const waves = emitWaves(schedule, drops, out, startDate);
81
111
  // Swimlanes → sections; their items become tasks.
82
112
  const swimlanes = ast.roadmapEntries.filter((e) => e.$type === 'SwimlaneDeclaration');
113
+ const ctx = { startDate, schedule, waves, containers: namedContainers(swimlanes) };
83
114
  for (const lane of swimlanes) {
84
- emitSwimlane(lane, [lane.name ?? slugify(displayLabel(lane))], drops, out, startDate);
115
+ emitSwimlane(lane, [lane.name ?? slugify(displayLabel(lane))], drops, out, ctx);
85
116
  }
86
117
  // Top-level milestones.
87
118
  const milestones = ast.roadmapEntries.filter((e) => e.$type === 'MilestoneDeclaration');
88
119
  if (milestones.length > 0) {
89
120
  out.push(' section Milestones');
90
121
  for (const m of milestones) {
91
- emitMilestone(m, drops, out, startDate);
122
+ emitMilestone(m, drops, out, ctx);
92
123
  }
93
124
  }
94
125
  out.push('```');
@@ -101,33 +132,59 @@ export function exportMermaid(inputs, options = {}) {
101
132
  }
102
133
  return out.join('\n');
103
134
  }
104
- function emitSwimlane(lane, breadcrumb, drops, out, startDate) {
135
+ /**
136
+ * `section Waves` (specs/waves.md §10): `{title} (wave end) :milestone, {id},
137
+ * {date}, 0d` per wave, in declaration order. The date is the first working
138
+ * day at or after `E_k`, the wave's exclusive end from the day schedule: the
139
+ * instant Mermaid starts a task `after` the milestone, so `after:build` and
140
+ * the barrier tokens both open on the barrier, and on a working day, since
141
+ * Mermaid never moves an explicit date off an excluded day. Start floors
142
+ * cannot be expressed and are counted as drops. Returns undefined, emitting
143
+ * nothing, when the roadmap has no waves.
144
+ */
145
+ function emitWaves(schedule, drops, out, startDate) {
146
+ const plan = schedule.plan;
147
+ if (!plan)
148
+ return undefined;
149
+ const { working } = schedule.calendar;
150
+ const ids = plan.waves.map((w) => w.name);
151
+ out.push(' section Waves');
152
+ plan.waves.forEach((w, i) => {
153
+ const end = schedule.waves?.get(ids[i])?.end;
154
+ const date = end ? formatIsoDate(working.dateAtWorkingIndex(end, 0)) : startDate;
155
+ out.push(` ${escapeTaskName(displayLabel(w))} (wave end) :milestone, ${ids[i]}, ${date}, 0d`);
156
+ if (getProps(w, 'after').length > 0)
157
+ drops.waveFloor += 1;
158
+ });
159
+ return { plan, ids };
160
+ }
161
+ function emitSwimlane(lane, breadcrumb, drops, out, ctx) {
105
162
  const sectionLabel = breadcrumb.join('.');
106
163
  out.push(` section ${escapeMermaidText(sectionLabel)}`);
107
164
  // Each lane starts its own sequential chain; the first non-`after` item
108
165
  // anchors at the roadmap start date.
109
- const chain = { prevId: null };
166
+ const chain = { prevIds: [] };
110
167
  for (const child of lane.content) {
111
- emitSwimlaneChild(child, breadcrumb, drops, out, chain, startDate);
168
+ emitSwimlaneChild(child, breadcrumb, drops, out, chain, ctx);
112
169
  }
113
170
  // Nested swimlanes — count for the lossy report.
114
171
  // SwimlaneContent doesn't include nested swimlanes per the grammar, so
115
172
  // there's nothing to recurse into here. If `nested` lives on the
116
173
  // roadmap-level layout model, that's a separate count tracked elsewhere.
117
174
  }
118
- function emitSwimlaneChild(child, breadcrumb, drops, out, chain, startDate) {
175
+ function emitSwimlaneChild(child, breadcrumb, drops, out, chain, ctx) {
119
176
  if (child.$type === 'ItemDeclaration') {
120
- emitItem(child, drops, out, chain, startDate);
177
+ emitItem(child, drops, out, chain, ctx);
121
178
  return;
122
179
  }
123
180
  if (child.$type === 'GroupBlock') {
124
181
  drops.group += 1;
125
- emitGroup(child, breadcrumb, drops, out, chain, startDate);
182
+ emitGroup(child, breadcrumb, drops, out, chain, ctx);
126
183
  return;
127
184
  }
128
185
  if (child.$type === 'ParallelBlock') {
129
186
  drops.parallel += 1;
130
- emitParallel(child, breadcrumb, drops, out, chain, startDate);
187
+ emitParallel(child, breadcrumb, drops, out, chain, ctx);
131
188
  return;
132
189
  }
133
190
  if (child.$type === 'DescriptionDirective') {
@@ -135,67 +192,76 @@ function emitSwimlaneChild(child, breadcrumb, drops, out, chain, startDate) {
135
192
  return;
136
193
  }
137
194
  }
138
- function emitGroup(group, breadcrumb, drops, out, chain, startDate) {
195
+ function emitGroup(group, breadcrumb, drops, out, chain, ctx) {
139
196
  // A group is a visual container within a lane — its items continue the
140
- // lane's sequential chain.
197
+ // lane's sequential chain, which its own `after:` joins for the first child.
198
+ joinContainerAfter(group, chain, ctx);
141
199
  for (const child of group.content) {
142
200
  if (child.$type === 'ItemDeclaration') {
143
- emitItem(child, drops, out, chain, startDate);
201
+ emitItem(child, drops, out, chain, ctx);
144
202
  }
145
203
  else if (child.$type === 'GroupBlock') {
146
204
  drops.group += 1;
147
- emitGroup(child, breadcrumb, drops, out, chain, startDate);
205
+ emitGroup(child, breadcrumb, drops, out, chain, ctx);
148
206
  }
149
207
  else if (child.$type === 'ParallelBlock') {
150
208
  drops.parallel += 1;
151
- emitParallel(child, breadcrumb, drops, out, chain, startDate);
209
+ emitParallel(child, breadcrumb, drops, out, chain, ctx);
152
210
  }
153
211
  else if (child.$type === 'DescriptionDirective') {
154
212
  drops.description += 1;
155
213
  }
156
214
  }
157
215
  }
158
- function emitParallel(parallel, breadcrumb, drops, out, chain, startDate) {
216
+ function emitParallel(parallel, breadcrumb, drops, out, chain, ctx) {
159
217
  // Tracks run concurrently: each starts from the parallel's entry point
160
- // (the lane cursor as it was on entry), not after the previous track.
161
- const entryId = chain.prevId;
162
- let lastTrackEnd = entryId;
218
+ // (the lane cursor as it was on entry, joined by the parallel's own
219
+ // `after:`), not after the previous track.
220
+ joinContainerAfter(parallel, chain, ctx);
221
+ const entryIds = chain.prevIds;
163
222
  for (const child of parallel.content) {
164
223
  if (child.$type === 'ItemDeclaration') {
165
- const trackChain = { prevId: entryId };
166
- emitItem(child, drops, out, trackChain, startDate);
167
- lastTrackEnd = trackChain.prevId;
224
+ emitItem(child, drops, out, { prevIds: entryIds }, ctx);
168
225
  }
169
226
  else if (child.$type === 'GroupBlock') {
170
227
  drops.group += 1;
171
- const trackChain = { prevId: entryId };
172
- emitGroup(child, breadcrumb, drops, out, trackChain, startDate);
173
- lastTrackEnd = trackChain.prevId;
228
+ emitGroup(child, breadcrumb, drops, out, { prevIds: entryIds }, ctx);
174
229
  }
175
230
  else if (child.$type === 'DescriptionDirective') {
176
231
  drops.description += 1;
177
232
  }
178
233
  }
179
- // After the block, the lane continues from the last track (lossy — Mermaid
180
- // can't express "after the latest of N tracks").
181
- chain.prevId = lastTrackEnd;
234
+ // After the block, the lane continues after every track's end: Mermaid's
235
+ // `after a b` starts at the latest of them, where engine C ends a parallel.
236
+ chain.prevIds = endTaskIds(parallel);
237
+ }
238
+ /**
239
+ * Adds a group's or parallel's own `after:` ids to the chain its first children
240
+ * start from. Engine C starts a container at `max(prevEnd, ...after)`, and
241
+ * neither container is a Mermaid task, so its first children carry both. An
242
+ * inline date stays dropped: as an id it would name no task, and a leader with
243
+ * no other id would start on the day the chart renders.
244
+ */
245
+ function joinContainerAfter(container, chain, ctx) {
246
+ const refs = getProps(container, 'after').filter((raw) => !parseIsoDate(raw));
247
+ chain.prevIds = union(chain.prevIds, expandAfter(refs, ctx.containers));
182
248
  }
183
- function emitItem(item, drops, out, chain, startDate) {
249
+ function emitItem(item, drops, out, chain, ctx) {
184
250
  countDrops(item, drops);
185
- const id = item.name ?? slugify(displayLabel(item));
251
+ const id = taskIdOf(item);
186
252
  const status = mapStatus(getProp(item, 'status'));
187
- const after = getProps(item, 'after');
188
- const duration = durationToMermaid(getProp(item, 'duration') ?? getProp(item, 'size')) ?? '1d';
189
- const start = startTokenFor(after, chain, startDate);
253
+ const after = expandAfter(getProps(item, 'after'), ctx.containers);
254
+ const duration = daysToMermaid(ctx.schedule.byNode.get(item)?.days) ?? '1d';
255
+ const start = startTokenFor(after, chain, ctx.startDate, previousWaveOf(item, ctx.waves));
190
256
  const meta = [status, id, start, duration].filter((s) => s !== '').join(', ');
191
257
  out.push(` ${escapeTaskName(displayLabel(item))} :${meta}`);
192
258
  // Advance the lane cursor so the next sequential item chains after this one.
193
- chain.prevId = id;
259
+ chain.prevIds = [id];
194
260
  }
195
- function emitMilestone(milestone, drops, out, startDate) {
261
+ function emitMilestone(milestone, drops, out, ctx) {
196
262
  const id = milestone.name ?? slugify(displayLabel(milestone));
197
263
  const date = getProp(milestone, 'date');
198
- const after = getProps(milestone, 'after');
264
+ const after = expandAfter(getProps(milestone, 'after'), ctx.containers);
199
265
  let start;
200
266
  if (date) {
201
267
  start = date;
@@ -206,36 +272,127 @@ function emitMilestone(milestone, drops, out, startDate) {
206
272
  else {
207
273
  // A milestone with neither a date nor predecessors still needs a start
208
274
  // token; anchor it at the roadmap start so Mermaid renders it.
209
- start = startDate;
275
+ start = ctx.startDate;
210
276
  }
211
277
  out.push(` ${escapeTaskName(displayLabel(milestone))} :milestone, ${id}, ${start}, 0d`);
212
278
  if (hasProp(milestone, 'style'))
213
279
  drops.style += 1;
214
280
  }
215
281
  /**
216
- * Start token for a task: declared `after:` deps win, otherwise chain after the
217
- * previous item in the lane, otherwise anchor the lane/track leader at the
282
+ * Start token for a task: `after` its declared `after:` deps and the lane chain
283
+ * (the previous item, every track end after a parallel), deduplicated, as
284
+ * engine C starts it at `max(prevEnd, ...after)`. A member of wave k >= 2 also
285
+ * waits for wave k-1's end milestone (`barrier`). Mermaid's `after a b` starts
286
+ * at the latest of them. With none, a lane or track leader anchors at the
218
287
  * roadmap start date. Always non-empty so Mermaid never mis-reads the task id
219
- * as a start date.
288
+ * as a start date; a leader's start date gives way to the barrier, which is
289
+ * never earlier.
290
+ */
291
+ function startTokenFor(after, chain, startDate, barrier) {
292
+ const deps = union(union(after, chain.prevIds), barrier ? [barrier] : []);
293
+ return deps.length > 0 ? `after ${deps.join(' ')}` : startDate;
294
+ }
295
+ /** `a` then the ids of `b` it lacks, in order. */
296
+ function union(a, b) {
297
+ return [...a, ...b.filter((id) => !a.includes(id))];
298
+ }
299
+ /** An item's Mermaid task id: its own id, else a slug of its title. */
300
+ function taskIdOf(item) {
301
+ return item.name ?? slugify(displayLabel(item));
302
+ }
303
+ /** Every named group and parallel under `lanes`, by id. */
304
+ function namedContainers(lanes) {
305
+ const out = new Map();
306
+ for (const node of trackNodes(lanes)) {
307
+ if ((isGroupBlock(node) || isParallelBlock(node)) && node.name)
308
+ out.set(node.name, node);
309
+ }
310
+ return out;
311
+ }
312
+ /**
313
+ * `after:` ids with each group and parallel replaced by the tasks that end it,
314
+ * without duplicates. Neither container is a Mermaid task, and Mermaid starts
315
+ * a task with an unknown `after` id on the day it renders. Engine C ends a
316
+ * group at its last child's end and a parallel at its latest track end;
317
+ * Mermaid's `after a b` starts at the latest end of the tasks it names, so the
318
+ * expansion lands on the same day.
220
319
  */
221
- function startTokenFor(after, chain, startDate) {
222
- if (after.length > 0)
223
- return `after ${after.join(' ')}`;
224
- if (chain.prevId)
225
- return `after ${chain.prevId}`;
226
- return startDate;
320
+ function expandAfter(after, containers) {
321
+ const out = [];
322
+ for (const ref of after) {
323
+ const container = containers.get(ref);
324
+ for (const id of container ? endTaskIds(container) : [ref]) {
325
+ if (!out.includes(id))
326
+ out.push(id);
327
+ }
328
+ }
329
+ return out;
330
+ }
331
+ /** The task ids that end a track node: an item, a group's last child, or every parallel track. */
332
+ function endTaskIds(node) {
333
+ if (isGroupBlock(node)) {
334
+ const last = trackChildren(node).at(-1);
335
+ return last ? endTaskIds(last) : [];
336
+ }
337
+ if (isParallelBlock(node))
338
+ return trackChildren(node).flatMap(endTaskIds);
339
+ return [taskIdOf(node)];
340
+ }
341
+ /** The previous wave's id for a member of wave k >= 2; undefined otherwise. */
342
+ function previousWaveOf(item, waves) {
343
+ if (!waves)
344
+ return undefined;
345
+ const k = waves.plan.memberWave(item);
346
+ return k !== undefined && k >= 2 ? waves.ids[k - 2] : undefined;
227
347
  }
228
348
  /**
229
- * Absolute anchor date (`YYYY-MM-DD`) for lane leaders. Prefers the roadmap's
230
- * declared `start:`, falling back to the layout-computed timeline start (always
231
- * present) so the export renders even when the source omits `start:`.
349
+ * Absolute anchor date for lane leaders. Prefers the roadmap's declared
350
+ * `start:`, falling back to the layout-computed timeline start (always
351
+ * present) so the export renders even when the source omits `start:`. An
352
+ * impossible `start:` (`2026-02-30`, NL.E0405) falls back the same way,
353
+ * as layout treats it, rather than rolling over to March 2.
232
354
  */
233
355
  function resolveStartDate(inputs) {
234
356
  const decl = inputs.ast.roadmapDecl;
235
357
  const declared = decl ? getProp(decl, 'start') : undefined;
236
- if (declared && /^\d{4}-\d{2}-\d{2}$/.test(declared.trim()))
237
- return declared.trim();
238
- return formatIsoDate(inputs.model.timeline.startDate);
358
+ return parseIsoDate(declared?.trim()) ?? inputs.model.timeline.startDate;
359
+ }
360
+ /** Monday first, as Mermaid's docs write `excludes`. */
361
+ const WEEKDAY_NAMES_MONDAY_FIRST = [
362
+ [1, 'monday'],
363
+ [2, 'tuesday'],
364
+ [3, 'wednesday'],
365
+ [4, 'thursday'],
366
+ [5, 'friday'],
367
+ [6, 'saturday'],
368
+ [0, 'sunday'],
369
+ ];
370
+ /** The open week's non-working days, as Mermaid `excludes` names. */
371
+ function excludedWeekdays(working) {
372
+ return WEEKDAY_NAMES_MONDAY_FIRST.filter(([weekday]) => !working.workingWeekdays.has(weekday)).map(([, name]) => name);
373
+ }
374
+ const DAY_MS = 86_400_000;
375
+ /** The ISO date of every day in every non-working exception, ascending. */
376
+ function excludedDates(exceptions) {
377
+ const dates = [];
378
+ for (const exception of exceptions) {
379
+ if (exception.working)
380
+ continue;
381
+ for (let t = exception.from.getTime(); t <= exception.through.getTime(); t += DAY_MS) {
382
+ dates.push(formatIsoDate(new Date(t)));
383
+ }
384
+ }
385
+ return dates;
386
+ }
387
+ /** Days across the working exceptions. */
388
+ function workingDayCount(exceptions) {
389
+ let days = 0;
390
+ for (const exception of exceptions) {
391
+ if (!exception.working)
392
+ continue;
393
+ days += Math.round((exception.through.getTime() - exception.from.getTime()) / DAY_MS) + 1;
394
+ }
395
+ return days;
239
396
  }
240
397
  function formatIsoDate(d) {
241
398
  const y = d.getUTCFullYear();
@@ -295,6 +452,8 @@ function formatDrops(drops) {
295
452
  'nestedSwimlanes',
296
453
  'description',
297
454
  'style',
455
+ 'waveFloor',
456
+ 'workingDay',
298
457
  ];
299
458
  const parts = order
300
459
  .filter((key) => drops[key] > 0)
@@ -309,6 +468,10 @@ function formatDrops(drops) {
309
468
  function formatDropKey(key) {
310
469
  if (key === 'nestedSwimlanes')
311
470
  return 'nested-swimlanes';
471
+ if (key === 'waveFloor')
472
+ return 'wave-floor';
473
+ if (key === 'workingDay')
474
+ return 'working-day';
312
475
  return key;
313
476
  }
314
477
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,6BAA6B;AAC7B,EAAE;AACF,sDAAsD;AACtD,aAAa;AACb,4EAA4E;AAC5E,6EAA6E;AAC7E,4CAA4C;AAC5C,+EAA+E;AAC/E,EAAE;AACF,gBAAgB;AAChB,sBAAsB;AACtB,EAAE;AACF,mCAAmC;AACnC,EAAE;AACF,eAAe;AACf,UAAU;AACV,sBAAsB;AACtB,8BAA8B;AAC9B,uBAAuB;AACvB,2BAA2B;AAC3B,iEAAiE;AACjE,QAAQ;AACR,EAAE;AACF,0BAA0B;AAC1B,EAAE;AACF,yEAAyE;AACzE,yEAAyE;AACzE,6EAA6E;AAC7E,kEAAkE;AAClE,8EAA8E;AAC9E,6EAA6E;AAC7E,+EAA+E;AAC/E,+EAA+E;AAC/E,+CAA+C;AAC/C,uEAAuE;AACvE,8DAA8D;AAC9D,+EAA+E;AAC/E,wDAAwD;AAaxD,OAAO,EAAE,YAAY,EAAE,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAE9F,OAAO,EAAE,iBAAiB,EAAE,MAAM,eAAe,CAAC;AAoBlD,SAAS,WAAW;IAChB,OAAO;QACH,MAAM,EAAE,CAAC;QACT,QAAQ,EAAE,CAAC;QACX,SAAS,EAAE,CAAC;QACZ,KAAK,EAAE,CAAC;QACR,MAAM,EAAE,CAAC;QACT,QAAQ,EAAE,CAAC;QACX,KAAK,EAAE,CAAC;QACR,eAAe,EAAE,CAAC;QAClB,WAAW,EAAE,CAAC;QACd,KAAK,EAAE,CAAC;KACX,CAAC;AACN,CAAC;AAWD,MAAM,UAAU,aAAa,CAAC,MAAoB,EAAE,OAAO,GAAmB,EAAE;IAC5E,MAAM,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;IACvB,MAAM,KAAK,GAAG,YAAY,CAAC,GAAG,CAAC,WAAW,IAAI,SAAS,CAAC,CAAC;IACzD,MAAM,SAAS,GAAG,gBAAgB,CAAC,MAAM,CAAC,CAAC;IAC3C,MAAM,KAAK,GAAG,WAAW,EAAE,CAAC;IAE5B,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,GAAG,CAAC,IAAI,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC;IACvB,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACb,GAAG,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;IACvB,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;IAClB,GAAG,CAAC,IAAI,CAAC,aAAa,iBAAiB,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IAClD,GAAG,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;IAEtC,0CAA0C;IAC1C,KAAK,CAAC,QAAQ,IAAI,GAAG,CAAC,cAAc,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,qBAAqB,CAAC,CAAC,MAAM,CAAC;IAE7F,4EAA4E;IAC5E,MAAM,OAAO,GAAG,GAAG,CAAC,cAAc,CAAC,MAAM,CACrC,CAAC,CAAC,EAA0B,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,mBAAmB,CACjE,CAAC;IACF,IAAI,OAAO,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACrB,GAAG,CAAC,IAAI,CAAC,qBAAqB,CAAC,CAAC;QAChC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,mEAAmE;YACnE,mEAAmE;YACnE,MAAM,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,MAAM,CAAC,IAAI,SAAS,CAAC;YAClD,MAAM,EAAE,GAAG,MAAM,CAAC,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,CAAC;YACxD,GAAG,CAAC,IAAI,CAAC,OAAO,cAAc,CAAC,YAAY,CAAC,MAAM,CAAC,CAAC,gBAAgB,EAAE,KAAK,IAAI,MAAM,CAAC,CAAC;QAC3F,CAAC;IACL,CAAC;IAED,kDAAkD;IAClD,MAAM,SAAS,GAAG,GAAG,CAAC,cAAc,CAAC,MAAM,CACvC,CAAC,CAAC,EAA4B,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,qBAAqB,CACrE,CAAC;IACF,KAAK,MAAM,IAAI,IAAI,SAAS,EAAE,CAAC;QAC3B,YAAY,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;IAC1F,CAAC;IAED,wBAAwB;IACxB,MAAM,UAAU,GAAG,GAAG,CAAC,cAAc,CAAC,MAAM,CACxC,CAAC,CAAC,EAA6B,EAAE,CAAC,CAAC,CAAC,KAAK,KAAK,sBAAsB,CACvE,CAAC;IACF,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,GAAG,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC;QACnC,KAAK,MAAM,CAAC,IAAI,UAAU,EAAE,CAAC;YACzB,aAAa,CAAC,CAAC,EAAE,KAAK,EAAE,GAAG,EAAE,SAAS,CAAC,CAAC;QAC5C,CAAC;IACL,CAAC;IAED,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;IAEhB,IAAI,OAAO,CAAC,YAAY,KAAK,KAAK,EAAE,CAAC;QACjC,MAAM,OAAO,GAAG,WAAW,CAAC,KAAK,CAAC,CAAC;QACnC,IAAI,OAAO,EAAE,CAAC;YACV,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YACb,GAAG,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QACtB,CAAC;IACL,CAAC;IAED,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC1B,CAAC;AAED,SAAS,YAAY,CACjB,IAAyB,EACzB,UAA6B,EAC7B,KAAiB,EACjB,GAAa,EACb,SAAiB;IAEjB,MAAM,YAAY,GAAG,UAAU,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC1C,GAAG,CAAC,IAAI,CAAC,eAAe,iBAAiB,CAAC,YAAY,CAAC,EAAE,CAAC,CAAC;IAC3D,wEAAwE;IACxE,qCAAqC;IACrC,MAAM,KAAK,GAAU,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;IACtC,KAAK,MAAM,KAAK,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;QAC/B,iBAAiB,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IACvE,CAAC;IACD,iDAAiD;IACjD,uEAAuE;IACvE,iEAAiE;IACjE,yEAAyE;AAC7E,CAAC;AAED,SAAS,iBAAiB,CACtB,KAAsB,EACtB,UAA6B,EAC7B,KAAiB,EACjB,GAAa,EACb,KAAY,EACZ,SAAiB;IAEjB,IAAI,KAAK,CAAC,KAAK,KAAK,iBAAiB,EAAE,CAAC;QACpC,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAC9C,OAAO;IACX,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;QAC/B,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;QACjB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAC3D,OAAO;IACX,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,EAAE,CAAC;QAClC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;QACpB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAC9D,OAAO;IACX,CAAC;IACD,IAAI,KAAK,CAAC,KAAK,KAAK,sBAAsB,EAAE,CAAC;QACzC,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;QACvB,OAAO;IACX,CAAC;AACL,CAAC;AAED,SAAS,SAAS,CACd,KAAiB,EACjB,UAA6B,EAC7B,KAAiB,EACjB,GAAa,EACb,KAAY,EACZ,SAAiB;IAEjB,uEAAuE;IACvE,2BAA2B;IAC3B,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,OAAyB,EAAE,CAAC;QAClD,IAAI,KAAK,CAAC,KAAK,KAAK,iBAAiB,EAAE,CAAC;YACpC,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAClD,CAAC;aAAM,IAAI,KAAK,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;YACtC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;YACjB,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAC/D,CAAC;aAAM,IAAI,KAAK,CAAC,KAAK,KAAK,eAAe,EAAE,CAAC;YACzC,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;YACpB,YAAY,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;QAClE,CAAC;aAAM,IAAI,KAAK,CAAC,KAAK,KAAK,sBAAsB,EAAE,CAAC;YAChD,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;QAC3B,CAAC;IACL,CAAC;AACL,CAAC;AAED,SAAS,YAAY,CACjB,QAAuB,EACvB,UAA6B,EAC7B,KAAiB,EACjB,GAAa,EACb,KAAY,EACZ,SAAiB;IAEjB,uEAAuE;IACvE,sEAAsE;IACtE,MAAM,OAAO,GAAG,KAAK,CAAC,MAAM,CAAC;IAC7B,IAAI,YAAY,GAAG,OAAO,CAAC;IAC3B,KAAK,MAAM,KAAK,IAAI,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,IAAI,KAAK,CAAC,KAAK,KAAK,iBAAiB,EAAE,CAAC;YACpC,MAAM,UAAU,GAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC9C,QAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YACnD,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC;QACrC,CAAC;aAAM,IAAI,KAAK,CAAC,KAAK,KAAK,YAAY,EAAE,CAAC;YACtC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;YACjB,MAAM,UAAU,GAAU,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;YAC9C,SAAS,CAAC,KAAK,EAAE,UAAU,EAAE,KAAK,EAAE,GAAG,EAAE,UAAU,EAAE,SAAS,CAAC,CAAC;YAChE,YAAY,GAAG,UAAU,CAAC,MAAM,CAAC;QACrC,CAAC;aAAM,IAAI,KAAK,CAAC,KAAK,KAAK,sBAAsB,EAAE,CAAC;YAChD,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;QAC3B,CAAC;IACL,CAAC;IACD,2EAA2E;IAC3E,iDAAiD;IACjD,KAAK,CAAC,MAAM,GAAG,YAAY,CAAC;AAChC,CAAC;AAED,SAAS,QAAQ,CACb,IAAqB,EACrB,KAAiB,EACjB,GAAa,EACb,KAAY,EACZ,SAAiB;IAEjB,UAAU,CAAC,IAAI,EAAE,KAAK,CAAC,CAAC;IACxB,MAAM,EAAE,GAAG,IAAI,CAAC,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,CAAC;IACpD,MAAM,MAAM,GAAG,SAAS,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,CAAC;IAClD,MAAM,KAAK,GAAG,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACtC,MAAM,QAAQ,GAAG,iBAAiB,CAAC,OAAO,CAAC,IAAI,EAAE,UAAU,CAAC,IAAI,OAAO,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC,IAAI,IAAI,CAAC;IAC/F,MAAM,KAAK,GAAG,aAAa,CAAC,KAAK,EAAE,KAAK,EAAE,SAAS,CAAC,CAAC;IAErD,MAAM,IAAI,GAAG,CAAC,MAAM,EAAE,EAAE,EAAE,KAAK,EAAE,QAAQ,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAC9E,GAAG,CAAC,IAAI,CAAC,OAAO,cAAc,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CAAC;IAC/D,6EAA6E;IAC7E,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;AACtB,CAAC;AAED,SAAS,aAAa,CAClB,SAA+B,EAC/B,KAAiB,EACjB,GAAa,EACb,SAAiB;IAEjB,MAAM,EAAE,GAAG,SAAS,CAAC,IAAI,IAAI,OAAO,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,CAAC;IAC9D,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;IACxC,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAC3C,IAAI,KAAa,CAAC;IAClB,IAAI,IAAI,EAAE,CAAC;QACP,KAAK,GAAG,IAAI,CAAC;IACjB,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,KAAK,GAAG,SAAS,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACvC,CAAC;SAAM,CAAC;QACJ,uEAAuE;QACvE,+DAA+D;QAC/D,KAAK,GAAG,SAAS,CAAC;IACtB,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,OAAO,cAAc,CAAC,YAAY,CAAC,SAAS,CAAC,CAAC,gBAAgB,EAAE,KAAK,KAAK,MAAM,CAAC,CAAC;IAC3F,IAAI,OAAO,CAAC,SAAS,EAAE,OAAO,CAAC;QAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;AACtD,CAAC;AAED;;;;;GAKG;AACH,SAAS,aAAa,CAAC,KAAwB,EAAE,KAAY,EAAE,SAAiB;IAC5E,IAAI,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,SAAS,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;IACxD,IAAI,KAAK,CAAC,MAAM;QAAE,OAAO,SAAS,KAAK,CAAC,MAAM,EAAE,CAAC;IACjD,OAAO,SAAS,CAAC;AACrB,CAAC;AAED;;;;GAIG;AACH,SAAS,gBAAgB,CAAC,MAAoB;IAC1C,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;IACpC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IAC3D,IAAI,QAAQ,IAAI,qBAAqB,CAAC,IAAI,CAAC,QAAQ,CAAC,IAAI,EAAE,CAAC;QAAE,OAAO,QAAQ,CAAC,IAAI,EAAE,CAAC;IACpF,OAAO,aAAa,CAAC,MAAM,CAAC,KAAK,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;AAC1D,CAAC;AAED,SAAS,aAAa,CAAC,CAAO;IAC1B,MAAM,CAAC,GAAG,CAAC,CAAC,cAAc,EAAE,CAAC;IAC7B,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACpD,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,EAAE,CAAC;AAC9B,CAAC;AAED,SAAS,UAAU,CAAC,IAAqB,EAAE,KAAiB;IACxD,IAAI,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC3D,IAAI,OAAO,CAAC,IAAI,EAAE,WAAW,CAAC;QAAE,KAAK,CAAC,SAAS,IAAI,CAAC,CAAC;IACrD,IAAI,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;QAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;IAC7C,IAAI,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC,MAAM,GAAG,CAAC;QAAE,KAAK,CAAC,MAAM,IAAI,CAAC,CAAC;IAC3D,IAAI,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC;QAAE,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC;IAC7C,IAAI,IAAI,CAAC,WAAW;QAAE,KAAK,CAAC,WAAW,IAAI,CAAC,CAAC;AACjD,CAAC;AAED,SAAS,SAAS,CAAC,MAA0B;IACzC,QAAQ,MAAM,EAAE,CAAC;QACb,KAAK,MAAM;YACP,OAAO,MAAM,CAAC;QAClB,KAAK,aAAa;YACd,OAAO,QAAQ,CAAC;QACpB,KAAK,SAAS,CAAC;QACf,KAAK,SAAS;YACV,OAAO,MAAM,CAAC;QAClB;YACI,OAAO,EAAE,CAAC;IAClB,CAAC;AACL,CAAC;AAED,SAAS,OAAO,CAAC,CAAS;IACtB,OAAO,CACH,CAAC;SACI,WAAW,EAAE;SACb,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC;SAC3B,OAAO,CAAC,UAAU,EAAE,EAAE,CAAC,IAAI,MAAM,CACzC,CAAC;AACN,CAAC;AAED,SAAS,iBAAiB,CAAC,CAAS;IAChC,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;AAC9D,CAAC;AAED,SAAS,cAAc,CAAC,CAAS;IAC7B,0EAA0E;IAC1E,OAAO,CAAC,CAAC,OAAO,CAAC,UAAU,EAAE,GAAG,CAAC,CAAC,OAAO,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC;AAC7D,CAAC;AAED,SAAS,WAAW,CAAC,KAAiB;IAClC,MAAM,KAAK,GAAyB;QAChC,QAAQ;QACR,UAAU;QACV,WAAW;QACX,OAAO;QACP,QAAQ;QACR,UAAU;QACV,OAAO;QACP,iBAAiB;QACjB,aAAa;QACb,OAAO;KACV,CAAC;IACF,MAAM,KAAK,GAAG,KAAK;SACd,MAAM,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;SAC/B,GAAG,CAAC,CAAC,GAAG,EAAE,EAAE,CAAC,GAAG,aAAa,CAAC,GAAG,CAAC,KAAK,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;IAC3D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IACpC,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC;IAClE,KAAK,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACrC,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;AAC5B,CAAC;AAED,SAAS,aAAa,CAAC,GAAqB;IACxC,IAAI,GAAG,KAAK,iBAAiB;QAAE,OAAO,kBAAkB,CAAC;IACzD,OAAO,GAAG,CAAC;AACf,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nowline/export-mermaid",
3
- "version": "0.8.6",
3
+ "version": "0.9.0",
4
4
  "description": "Exports a Nowline roadmap as a Mermaid Gantt chart in Markdown.",
5
5
  "license": "Apache-2.0",
6
6
  "engines": {
@@ -21,15 +21,15 @@
21
21
  "src/"
22
22
  ],
23
23
  "dependencies": {
24
- "@nowline/core": "0.8.6",
25
- "@nowline/export-core": "0.8.6",
26
- "@nowline/layout": "0.8.6"
24
+ "@nowline/core": "0.9.0",
25
+ "@nowline/export-core": "0.9.0",
26
+ "@nowline/layout": "0.9.0"
27
27
  },
28
28
  "devDependencies": {
29
- "@types/node": "^26.6.3",
29
+ "@types/node": "^26.6.5",
30
30
  "langium": "~4.4.0",
31
31
  "typescript": "^7.0.2",
32
- "vitest": "^5.0.2"
32
+ "vitest": "^5.0.5"
33
33
  },
34
34
  "scripts": {
35
35
  "build": "tsc -b tsconfig.json",
package/src/duration.ts CHANGED
@@ -1,49 +1,18 @@
1
- // Convert Nowline duration tokens (e.g. `2w`, `1m`, `xl`) into Mermaid's
2
- // duration syntax. Mermaid `gantt` accepts day / week / hour units.
1
+ // Mermaid task durations. The day count is engine C's `ScheduledItem.days`
2
+ // under the file's calendar (specs/working-calendar.md §8), so sizes,
3
+ // `capacity:` and every unit match the chart. It is always written as `Nd`:
4
+ // with the business `excludes` Mermaid counts those in working days, and a
5
+ // full-calendar `4w` becomes `28d`, the same span.
3
6
  //
4
7
  // Spec: specs/handoffs/m2c.md § 6 + the bridge rules in
5
8
  // specs/rendering.md § Markdown+Mermaid Bridge.
6
9
 
7
- const SIZE_BUCKET_DAYS: Readonly<Record<string, number>> = {
8
- xs: 1,
9
- sm: 3,
10
- s: 3,
11
- md: 5,
12
- m: 5,
13
- lg: 10,
14
- l: 10,
15
- xl: 15,
16
- };
17
-
18
- const NUMERIC_RE = /^(\d+(?:\.\d+)?)\s*(d|w|m|y)?$/i;
19
-
20
10
  /**
21
- * Convert a duration literal to a Mermaid token (`5d`, `2w`, …).
22
- * Returns `undefined` when the literal is missing — caller emits no duration
23
- * and Mermaid uses its default 1d.
11
+ * `Nd` with up to two decimals, trailing zeros trimmed (`7.5d`, `7.33d`).
12
+ * Returns `undefined` for no duration; the caller falls back to `1d`.
24
13
  */
25
- export function durationToMermaid(literal: string | undefined): string | undefined {
26
- if (!literal) return undefined;
27
- const trimmed = literal.trim().toLowerCase();
28
- if (!trimmed) return undefined;
29
-
30
- // Size buckets resolve to working-day counts.
31
- if (trimmed in SIZE_BUCKET_DAYS) {
32
- return `${SIZE_BUCKET_DAYS[trimmed]}d`;
33
- }
34
-
35
- const match = NUMERIC_RE.exec(trimmed);
36
- if (!match) return undefined;
37
- const value = match[1];
38
- const unit = (match[2] ?? 'd').toLowerCase();
39
- if (unit === 'd' || unit === 'w') return `${value}${unit}`;
40
- if (unit === 'm') {
41
- const days = Math.round(Number(value) * 22);
42
- return `${days}d`;
43
- }
44
- if (unit === 'y') {
45
- const days = Math.round(Number(value) * 252);
46
- return `${days}d`;
47
- }
48
- return undefined;
14
+ export function daysToMermaid(days: number | undefined): string | undefined {
15
+ if (days === undefined || !(days > 0)) return undefined;
16
+ // parseFloat strips trailing zeros: `7.50` → `7.5`.
17
+ return `${parseFloat(days.toFixed(2))}d`;
49
18
  }
package/src/index.ts CHANGED
@@ -16,13 +16,23 @@
16
16
  // gantt
17
17
  // title <title>
18
18
  // dateFormat YYYY-MM-DD
19
- // axisFormat ...
19
+ // excludes saturday, sunday (only when the open week has days off)
20
20
  // section <swimlane>
21
21
  // <Task name>: <status>, <id>, <after-or-date>, <duration>
22
22
  // ```
23
23
  //
24
24
  // <%% lossy comment %%>
25
25
  //
26
+ // Calendar (specs/working-calendar.md §8): the file's calendar comes from
27
+ // engine C (`scheduleRoadmap`, built once per export). `excludes` lists the
28
+ // open week's non-working days, Monday first, so Mermaid counts `Nd` in
29
+ // working days; every duration is engine C's `days` written as `Nd` (a
30
+ // full-calendar `4w` is `28d`). Mermaid never checks a task's explicit start
31
+ // against `excludes`, so the dates this exporter derives are working days:
32
+ // the roadmap start is snapped to its first working day, and a wave-end
33
+ // milestone is the first working day at or after the wave's exclusive end.
34
+ // Declared anchor and milestone dates are written as given.
35
+ //
26
36
  // Every emitted task carries an explicit start token (`after <id>` or an
27
37
  // absolute `YYYY-MM-DD` date). Mermaid's positional task parser strips a
28
38
  // leading status keyword (`done`/`active`/`crit`/`milestone`) and then reads
@@ -31,9 +41,15 @@
31
41
  // start (`:done, my-id, 2w`) collapses to two fields after the status strip,
32
42
  // so Mermaid mis-reads the id as a start date and throws `Invalid date: my-id`
33
43
  // at render time. To keep an explicit id we therefore MUST emit a start token:
34
- // - declared `after:` deps -> `after <ids>`
35
- // - otherwise a lane follower -> `after <previous item in the lane>`
36
- // - otherwise a lane/track leader -> the roadmap start date
44
+ // - `after <ids>`: the declared `after:` deps (a group or parallel id
45
+ // expanded to the tasks that end it; neither is a Mermaid task), then the
46
+ // lane chain: the previous item in the lane, or every track end when it
47
+ // follows a parallel; a first child also gets its group's or parallel's
48
+ // own `after:`. Engine C starts an item at `max(prevEnd, ...after)`.
49
+ // - with none of those, a lane/track leader -> the roadmap start date
50
+ // - a member of wave k >= 2 also gets `after <wave k-1 id>`: appended to
51
+ // its `after` token, or replacing a lane/track leader's start date
52
+ // (specs/waves.md §10)
37
53
  // This mirrors Nowline's default "each item starts after the preceding item in
38
54
  // its lane" semantics (specs/rendering.md § Item Bars).
39
55
 
@@ -46,11 +62,21 @@ import type {
46
62
  ParallelBlock,
47
63
  SwimlaneContent,
48
64
  SwimlaneDeclaration,
65
+ WavePlan,
49
66
  } from '@nowline/core';
67
+ import { isGroupBlock, isParallelBlock } from '@nowline/core';
68
+ import { parseIsoDate, type TrackNode, trackChildren, trackNodes } from '@nowline/core/internal';
50
69
  import type { ExportInputs } from '@nowline/export-core';
51
70
  import { displayLabel, getProp, getProps, hasProp, roadmapTitle } from '@nowline/export-core';
71
+ import {
72
+ type CalendarException,
73
+ calendarExceptions,
74
+ type RoadmapSchedule,
75
+ scheduleRoadmap,
76
+ type WorkingCalendar,
77
+ } from '@nowline/layout';
52
78
 
53
- import { durationToMermaid } from './duration.js';
79
+ import { daysToMermaid } from './duration.js';
54
80
 
55
81
  export interface MermaidOptions {
56
82
  /** Append the trailing `%%` lossy comment. Defaults to true. */
@@ -68,6 +94,10 @@ interface DropCounts {
68
94
  nestedSwimlanes: number;
69
95
  description: number;
70
96
  style: number;
97
+ /** Wave start floors (a wave's `after:`); Mermaid has no "no earlier than". */
98
+ waveFloor: number;
99
+ /** Days a `working` exception opens inside a closed week; Mermaid has no `includes`. */
100
+ workingDay: number;
71
101
  }
72
102
 
73
103
  function emptyCounts(): DropCounts {
@@ -82,22 +112,47 @@ function emptyCounts(): DropCounts {
82
112
  nestedSwimlanes: 0,
83
113
  description: 0,
84
114
  style: 0,
115
+ waveFloor: 0,
116
+ workingDay: 0,
85
117
  };
86
118
  }
87
119
 
88
120
  /**
89
- * Mutable per-lane chaining cursor. `prevId` is the id the NEXT sequential
90
- * task should start after; `null` means "anchor at the roadmap start date"
91
- * (lane / parallel-track leader).
121
+ * Mutable per-lane chaining cursor. `prevIds` are the ids the NEXT sequential
122
+ * task should start after: one item, or every track end after a parallel.
123
+ * Empty means "anchor at the roadmap start date" (lane / parallel-track leader).
92
124
  */
93
125
  interface Chain {
94
- prevId: string | null;
126
+ prevIds: string[];
127
+ }
128
+
129
+ /**
130
+ * Wave barrier context (specs/waves.md §10): the plan, for membership, and
131
+ * each wave's end milestone id by 1-based index. Undefined without waves.
132
+ */
133
+ interface WaveCtx {
134
+ plan: WavePlan;
135
+ /** `ids[k - 1]` is wave k's id, which is also its end milestone's id. */
136
+ ids: readonly string[];
137
+ }
138
+
139
+ /** What every item line reads besides its own lane chain. */
140
+ interface EmitCtx {
141
+ /** The roadmap start, on a working day (`YYYY-MM-DD`). */
142
+ startDate: string;
143
+ schedule: RoadmapSchedule;
144
+ waves: WaveCtx | undefined;
145
+ /** Each named group and parallel, which `after:` expands to its end tasks. */
146
+ containers: ReadonlyMap<string, GroupBlock | ParallelBlock>;
95
147
  }
96
148
 
97
149
  export function exportMermaid(inputs: ExportInputs, options: MermaidOptions = {}): string {
98
150
  const ast = inputs.ast;
99
151
  const title = roadmapTitle(ast.roadmapDecl ?? undefined);
100
- const startDate = resolveStartDate(inputs);
152
+ const schedule =
153
+ inputs.schedule ?? scheduleRoadmap(ast, inputs.resolved, { today: inputs.today });
154
+ const { working } = schedule.calendar;
155
+ const startDate = formatIsoDate(working.dateAtWorkingIndex(resolveStartDate(inputs), 0));
101
156
  const drops = emptyCounts();
102
157
 
103
158
  const out: string[] = [];
@@ -107,6 +162,14 @@ export function exportMermaid(inputs: ExportInputs, options: MermaidOptions = {}
107
162
  out.push('gantt');
108
163
  out.push(` title ${escapeMermaidText(title)}`);
109
164
  out.push(' dateFormat YYYY-MM-DD');
165
+ const exceptions = calendarExceptions(
166
+ working,
167
+ inputs.model.timeline.startDate,
168
+ inputs.model.timeline.endDate,
169
+ );
170
+ const excludes = [...excludedWeekdays(working), ...excludedDates(exceptions)];
171
+ if (excludes.length > 0) out.push(` excludes ${excludes.join(', ')}`);
172
+ drops.workingDay += workingDayCount(exceptions);
110
173
 
111
174
  // Footnotes are file-level; counted once.
112
175
  drops.footnote += ast.roadmapEntries.filter((e) => e.$type === 'FootnoteDeclaration').length;
@@ -126,12 +189,16 @@ export function exportMermaid(inputs: ExportInputs, options: MermaidOptions = {}
126
189
  }
127
190
  }
128
191
 
192
+ // Waves → one end milestone per wave, dated from the day schedule.
193
+ const waves = emitWaves(schedule, drops, out, startDate);
194
+
129
195
  // Swimlanes → sections; their items become tasks.
130
196
  const swimlanes = ast.roadmapEntries.filter(
131
197
  (e): e is SwimlaneDeclaration => e.$type === 'SwimlaneDeclaration',
132
198
  );
199
+ const ctx: EmitCtx = { startDate, schedule, waves, containers: namedContainers(swimlanes) };
133
200
  for (const lane of swimlanes) {
134
- emitSwimlane(lane, [lane.name ?? slugify(displayLabel(lane))], drops, out, startDate);
201
+ emitSwimlane(lane, [lane.name ?? slugify(displayLabel(lane))], drops, out, ctx);
135
202
  }
136
203
 
137
204
  // Top-level milestones.
@@ -141,7 +208,7 @@ export function exportMermaid(inputs: ExportInputs, options: MermaidOptions = {}
141
208
  if (milestones.length > 0) {
142
209
  out.push(' section Milestones');
143
210
  for (const m of milestones) {
144
- emitMilestone(m, drops, out, startDate);
211
+ emitMilestone(m, drops, out, ctx);
145
212
  }
146
213
  }
147
214
 
@@ -158,20 +225,52 @@ export function exportMermaid(inputs: ExportInputs, options: MermaidOptions = {}
158
225
  return out.join('\n');
159
226
  }
160
227
 
228
+ /**
229
+ * `section Waves` (specs/waves.md §10): `{title} (wave end) :milestone, {id},
230
+ * {date}, 0d` per wave, in declaration order. The date is the first working
231
+ * day at or after `E_k`, the wave's exclusive end from the day schedule: the
232
+ * instant Mermaid starts a task `after` the milestone, so `after:build` and
233
+ * the barrier tokens both open on the barrier, and on a working day, since
234
+ * Mermaid never moves an explicit date off an excluded day. Start floors
235
+ * cannot be expressed and are counted as drops. Returns undefined, emitting
236
+ * nothing, when the roadmap has no waves.
237
+ */
238
+ function emitWaves(
239
+ schedule: RoadmapSchedule,
240
+ drops: DropCounts,
241
+ out: string[],
242
+ startDate: string,
243
+ ): WaveCtx | undefined {
244
+ const plan = schedule.plan;
245
+ if (!plan) return undefined;
246
+ const { working } = schedule.calendar;
247
+ const ids = plan.waves.map((w) => w.name as string);
248
+ out.push(' section Waves');
249
+ plan.waves.forEach((w, i) => {
250
+ const end = schedule.waves?.get(ids[i]!)?.end;
251
+ const date = end ? formatIsoDate(working.dateAtWorkingIndex(end, 0)) : startDate;
252
+ out.push(
253
+ ` ${escapeTaskName(displayLabel(w))} (wave end) :milestone, ${ids[i]}, ${date}, 0d`,
254
+ );
255
+ if (getProps(w, 'after').length > 0) drops.waveFloor += 1;
256
+ });
257
+ return { plan, ids };
258
+ }
259
+
161
260
  function emitSwimlane(
162
261
  lane: SwimlaneDeclaration,
163
262
  breadcrumb: readonly string[],
164
263
  drops: DropCounts,
165
264
  out: string[],
166
- startDate: string,
265
+ ctx: EmitCtx,
167
266
  ): void {
168
267
  const sectionLabel = breadcrumb.join('.');
169
268
  out.push(` section ${escapeMermaidText(sectionLabel)}`);
170
269
  // Each lane starts its own sequential chain; the first non-`after` item
171
270
  // anchors at the roadmap start date.
172
- const chain: Chain = { prevId: null };
271
+ const chain: Chain = { prevIds: [] };
173
272
  for (const child of lane.content) {
174
- emitSwimlaneChild(child, breadcrumb, drops, out, chain, startDate);
273
+ emitSwimlaneChild(child, breadcrumb, drops, out, chain, ctx);
175
274
  }
176
275
  // Nested swimlanes — count for the lossy report.
177
276
  // SwimlaneContent doesn't include nested swimlanes per the grammar, so
@@ -185,20 +284,20 @@ function emitSwimlaneChild(
185
284
  drops: DropCounts,
186
285
  out: string[],
187
286
  chain: Chain,
188
- startDate: string,
287
+ ctx: EmitCtx,
189
288
  ): void {
190
289
  if (child.$type === 'ItemDeclaration') {
191
- emitItem(child, drops, out, chain, startDate);
290
+ emitItem(child, drops, out, chain, ctx);
192
291
  return;
193
292
  }
194
293
  if (child.$type === 'GroupBlock') {
195
294
  drops.group += 1;
196
- emitGroup(child, breadcrumb, drops, out, chain, startDate);
295
+ emitGroup(child, breadcrumb, drops, out, chain, ctx);
197
296
  return;
198
297
  }
199
298
  if (child.$type === 'ParallelBlock') {
200
299
  drops.parallel += 1;
201
- emitParallel(child, breadcrumb, drops, out, chain, startDate);
300
+ emitParallel(child, breadcrumb, drops, out, chain, ctx);
202
301
  return;
203
302
  }
204
303
  if (child.$type === 'DescriptionDirective') {
@@ -213,19 +312,20 @@ function emitGroup(
213
312
  drops: DropCounts,
214
313
  out: string[],
215
314
  chain: Chain,
216
- startDate: string,
315
+ ctx: EmitCtx,
217
316
  ): void {
218
317
  // A group is a visual container within a lane — its items continue the
219
- // lane's sequential chain.
318
+ // lane's sequential chain, which its own `after:` joins for the first child.
319
+ joinContainerAfter(group, chain, ctx);
220
320
  for (const child of group.content as GroupContent[]) {
221
321
  if (child.$type === 'ItemDeclaration') {
222
- emitItem(child, drops, out, chain, startDate);
322
+ emitItem(child, drops, out, chain, ctx);
223
323
  } else if (child.$type === 'GroupBlock') {
224
324
  drops.group += 1;
225
- emitGroup(child, breadcrumb, drops, out, chain, startDate);
325
+ emitGroup(child, breadcrumb, drops, out, chain, ctx);
226
326
  } else if (child.$type === 'ParallelBlock') {
227
327
  drops.parallel += 1;
228
- emitParallel(child, breadcrumb, drops, out, chain, startDate);
328
+ emitParallel(child, breadcrumb, drops, out, chain, ctx);
229
329
  } else if (child.$type === 'DescriptionDirective') {
230
330
  drops.description += 1;
231
331
  }
@@ -238,29 +338,42 @@ function emitParallel(
238
338
  drops: DropCounts,
239
339
  out: string[],
240
340
  chain: Chain,
241
- startDate: string,
341
+ ctx: EmitCtx,
242
342
  ): void {
243
343
  // Tracks run concurrently: each starts from the parallel's entry point
244
- // (the lane cursor as it was on entry), not after the previous track.
245
- const entryId = chain.prevId;
246
- let lastTrackEnd = entryId;
344
+ // (the lane cursor as it was on entry, joined by the parallel's own
345
+ // `after:`), not after the previous track.
346
+ joinContainerAfter(parallel, chain, ctx);
347
+ const entryIds = chain.prevIds;
247
348
  for (const child of parallel.content) {
248
349
  if (child.$type === 'ItemDeclaration') {
249
- const trackChain: Chain = { prevId: entryId };
250
- emitItem(child, drops, out, trackChain, startDate);
251
- lastTrackEnd = trackChain.prevId;
350
+ emitItem(child, drops, out, { prevIds: entryIds }, ctx);
252
351
  } else if (child.$type === 'GroupBlock') {
253
352
  drops.group += 1;
254
- const trackChain: Chain = { prevId: entryId };
255
- emitGroup(child, breadcrumb, drops, out, trackChain, startDate);
256
- lastTrackEnd = trackChain.prevId;
353
+ emitGroup(child, breadcrumb, drops, out, { prevIds: entryIds }, ctx);
257
354
  } else if (child.$type === 'DescriptionDirective') {
258
355
  drops.description += 1;
259
356
  }
260
357
  }
261
- // After the block, the lane continues from the last track (lossy — Mermaid
262
- // can't express "after the latest of N tracks").
263
- chain.prevId = lastTrackEnd;
358
+ // After the block, the lane continues after every track's end: Mermaid's
359
+ // `after a b` starts at the latest of them, where engine C ends a parallel.
360
+ chain.prevIds = endTaskIds(parallel);
361
+ }
362
+
363
+ /**
364
+ * Adds a group's or parallel's own `after:` ids to the chain its first children
365
+ * start from. Engine C starts a container at `max(prevEnd, ...after)`, and
366
+ * neither container is a Mermaid task, so its first children carry both. An
367
+ * inline date stays dropped: as an id it would name no task, and a leader with
368
+ * no other id would start on the day the chart renders.
369
+ */
370
+ function joinContainerAfter(
371
+ container: GroupBlock | ParallelBlock,
372
+ chain: Chain,
373
+ ctx: EmitCtx,
374
+ ): void {
375
+ const refs = getProps(container, 'after').filter((raw) => !parseIsoDate(raw));
376
+ chain.prevIds = union(chain.prevIds, expandAfter(refs, ctx.containers));
264
377
  }
265
378
 
266
379
  function emitItem(
@@ -268,30 +381,30 @@ function emitItem(
268
381
  drops: DropCounts,
269
382
  out: string[],
270
383
  chain: Chain,
271
- startDate: string,
384
+ ctx: EmitCtx,
272
385
  ): void {
273
386
  countDrops(item, drops);
274
- const id = item.name ?? slugify(displayLabel(item));
387
+ const id = taskIdOf(item);
275
388
  const status = mapStatus(getProp(item, 'status'));
276
- const after = getProps(item, 'after');
277
- const duration = durationToMermaid(getProp(item, 'duration') ?? getProp(item, 'size')) ?? '1d';
278
- const start = startTokenFor(after, chain, startDate);
389
+ const after = expandAfter(getProps(item, 'after'), ctx.containers);
390
+ const duration = daysToMermaid(ctx.schedule.byNode.get(item)?.days) ?? '1d';
391
+ const start = startTokenFor(after, chain, ctx.startDate, previousWaveOf(item, ctx.waves));
279
392
 
280
393
  const meta = [status, id, start, duration].filter((s) => s !== '').join(', ');
281
394
  out.push(` ${escapeTaskName(displayLabel(item))} :${meta}`);
282
395
  // Advance the lane cursor so the next sequential item chains after this one.
283
- chain.prevId = id;
396
+ chain.prevIds = [id];
284
397
  }
285
398
 
286
399
  function emitMilestone(
287
400
  milestone: MilestoneDeclaration,
288
401
  drops: DropCounts,
289
402
  out: string[],
290
- startDate: string,
403
+ ctx: EmitCtx,
291
404
  ): void {
292
405
  const id = milestone.name ?? slugify(displayLabel(milestone));
293
406
  const date = getProp(milestone, 'date');
294
- const after = getProps(milestone, 'after');
407
+ const after = expandAfter(getProps(milestone, 'after'), ctx.containers);
295
408
  let start: string;
296
409
  if (date) {
297
410
  start = date;
@@ -300,34 +413,145 @@ function emitMilestone(
300
413
  } else {
301
414
  // A milestone with neither a date nor predecessors still needs a start
302
415
  // token; anchor it at the roadmap start so Mermaid renders it.
303
- start = startDate;
416
+ start = ctx.startDate;
304
417
  }
305
418
  out.push(` ${escapeTaskName(displayLabel(milestone))} :milestone, ${id}, ${start}, 0d`);
306
419
  if (hasProp(milestone, 'style')) drops.style += 1;
307
420
  }
308
421
 
309
422
  /**
310
- * Start token for a task: declared `after:` deps win, otherwise chain after the
311
- * previous item in the lane, otherwise anchor the lane/track leader at the
423
+ * Start token for a task: `after` its declared `after:` deps and the lane chain
424
+ * (the previous item, every track end after a parallel), deduplicated, as
425
+ * engine C starts it at `max(prevEnd, ...after)`. A member of wave k >= 2 also
426
+ * waits for wave k-1's end milestone (`barrier`). Mermaid's `after a b` starts
427
+ * at the latest of them. With none, a lane or track leader anchors at the
312
428
  * roadmap start date. Always non-empty so Mermaid never mis-reads the task id
313
- * as a start date.
429
+ * as a start date; a leader's start date gives way to the barrier, which is
430
+ * never earlier.
314
431
  */
315
- function startTokenFor(after: readonly string[], chain: Chain, startDate: string): string {
316
- if (after.length > 0) return `after ${after.join(' ')}`;
317
- if (chain.prevId) return `after ${chain.prevId}`;
318
- return startDate;
432
+ function startTokenFor(
433
+ after: readonly string[],
434
+ chain: Chain,
435
+ startDate: string,
436
+ barrier?: string,
437
+ ): string {
438
+ const deps = union(union(after, chain.prevIds), barrier ? [barrier] : []);
439
+ return deps.length > 0 ? `after ${deps.join(' ')}` : startDate;
440
+ }
441
+
442
+ /** `a` then the ids of `b` it lacks, in order. */
443
+ function union(a: readonly string[], b: readonly string[]): string[] {
444
+ return [...a, ...b.filter((id) => !a.includes(id))];
445
+ }
446
+
447
+ /** An item's Mermaid task id: its own id, else a slug of its title. */
448
+ function taskIdOf(item: ItemDeclaration): string {
449
+ return item.name ?? slugify(displayLabel(item));
450
+ }
451
+
452
+ /** Every named group and parallel under `lanes`, by id. */
453
+ function namedContainers(
454
+ lanes: readonly SwimlaneDeclaration[],
455
+ ): Map<string, GroupBlock | ParallelBlock> {
456
+ const out = new Map<string, GroupBlock | ParallelBlock>();
457
+ for (const node of trackNodes(lanes)) {
458
+ if ((isGroupBlock(node) || isParallelBlock(node)) && node.name) out.set(node.name, node);
459
+ }
460
+ return out;
319
461
  }
320
462
 
321
463
  /**
322
- * Absolute anchor date (`YYYY-MM-DD`) for lane leaders. Prefers the roadmap's
323
- * declared `start:`, falling back to the layout-computed timeline start (always
324
- * present) so the export renders even when the source omits `start:`.
464
+ * `after:` ids with each group and parallel replaced by the tasks that end it,
465
+ * without duplicates. Neither container is a Mermaid task, and Mermaid starts
466
+ * a task with an unknown `after` id on the day it renders. Engine C ends a
467
+ * group at its last child's end and a parallel at its latest track end;
468
+ * Mermaid's `after a b` starts at the latest end of the tasks it names, so the
469
+ * expansion lands on the same day.
325
470
  */
326
- function resolveStartDate(inputs: ExportInputs): string {
471
+ function expandAfter(
472
+ after: readonly string[],
473
+ containers: ReadonlyMap<string, GroupBlock | ParallelBlock>,
474
+ ): string[] {
475
+ const out: string[] = [];
476
+ for (const ref of after) {
477
+ const container = containers.get(ref);
478
+ for (const id of container ? endTaskIds(container) : [ref]) {
479
+ if (!out.includes(id)) out.push(id);
480
+ }
481
+ }
482
+ return out;
483
+ }
484
+
485
+ /** The task ids that end a track node: an item, a group's last child, or every parallel track. */
486
+ function endTaskIds(node: TrackNode): string[] {
487
+ if (isGroupBlock(node)) {
488
+ const last = trackChildren(node).at(-1);
489
+ return last ? endTaskIds(last) : [];
490
+ }
491
+ if (isParallelBlock(node)) return trackChildren(node).flatMap(endTaskIds);
492
+ return [taskIdOf(node)];
493
+ }
494
+
495
+ /** The previous wave's id for a member of wave k >= 2; undefined otherwise. */
496
+ function previousWaveOf(item: ItemDeclaration, waves: WaveCtx | undefined): string | undefined {
497
+ if (!waves) return undefined;
498
+ const k = waves.plan.memberWave(item);
499
+ return k !== undefined && k >= 2 ? waves.ids[k - 2] : undefined;
500
+ }
501
+
502
+ /**
503
+ * Absolute anchor date for lane leaders. Prefers the roadmap's declared
504
+ * `start:`, falling back to the layout-computed timeline start (always
505
+ * present) so the export renders even when the source omits `start:`. An
506
+ * impossible `start:` (`2026-02-30`, NL.E0405) falls back the same way,
507
+ * as layout treats it, rather than rolling over to March 2.
508
+ */
509
+ function resolveStartDate(inputs: ExportInputs): Date {
327
510
  const decl = inputs.ast.roadmapDecl;
328
511
  const declared = decl ? getProp(decl, 'start') : undefined;
329
- if (declared && /^\d{4}-\d{2}-\d{2}$/.test(declared.trim())) return declared.trim();
330
- return formatIsoDate(inputs.model.timeline.startDate);
512
+ return parseIsoDate(declared?.trim()) ?? inputs.model.timeline.startDate;
513
+ }
514
+
515
+ /** Monday first, as Mermaid's docs write `excludes`. */
516
+ const WEEKDAY_NAMES_MONDAY_FIRST: ReadonlyArray<[number, string]> = [
517
+ [1, 'monday'],
518
+ [2, 'tuesday'],
519
+ [3, 'wednesday'],
520
+ [4, 'thursday'],
521
+ [5, 'friday'],
522
+ [6, 'saturday'],
523
+ [0, 'sunday'],
524
+ ];
525
+
526
+ /** The open week's non-working days, as Mermaid `excludes` names. */
527
+ function excludedWeekdays(working: WorkingCalendar): string[] {
528
+ return WEEKDAY_NAMES_MONDAY_FIRST.filter(
529
+ ([weekday]) => !working.workingWeekdays.has(weekday),
530
+ ).map(([, name]) => name);
531
+ }
532
+
533
+ const DAY_MS = 86_400_000;
534
+
535
+ /** The ISO date of every day in every non-working exception, ascending. */
536
+ function excludedDates(exceptions: readonly CalendarException[]): string[] {
537
+ const dates: string[] = [];
538
+ for (const exception of exceptions) {
539
+ if (exception.working) continue;
540
+ for (let t = exception.from.getTime(); t <= exception.through.getTime(); t += DAY_MS) {
541
+ dates.push(formatIsoDate(new Date(t)));
542
+ }
543
+ }
544
+ return dates;
545
+ }
546
+
547
+ /** Days across the working exceptions. */
548
+ function workingDayCount(exceptions: readonly CalendarException[]): number {
549
+ let days = 0;
550
+ for (const exception of exceptions) {
551
+ if (!exception.working) continue;
552
+ days += Math.round((exception.through.getTime() - exception.from.getTime()) / DAY_MS) + 1;
553
+ }
554
+ return days;
331
555
  }
332
556
 
333
557
  function formatIsoDate(d: Date): string {
@@ -390,6 +614,8 @@ function formatDrops(drops: DropCounts): string | null {
390
614
  'nestedSwimlanes',
391
615
  'description',
392
616
  'style',
617
+ 'waveFloor',
618
+ 'workingDay',
393
619
  ];
394
620
  const parts = order
395
621
  .filter((key) => drops[key] > 0)
@@ -403,5 +629,7 @@ function formatDrops(drops: DropCounts): string | null {
403
629
 
404
630
  function formatDropKey(key: keyof DropCounts): string {
405
631
  if (key === 'nestedSwimlanes') return 'nested-swimlanes';
632
+ if (key === 'waveFloor') return 'wave-floor';
633
+ if (key === 'workingDay') return 'working-day';
406
634
  return key;
407
635
  }