@diagrammo/dgmo 0.8.3 → 0.8.5

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 (122) hide show
  1. package/.claude/commands/dgmo-diagram-this.md +60 -0
  2. package/.claude/commands/dgmo-document-project.md +128 -0
  3. package/.claude/commands/dgmo.md +452 -50
  4. package/.cursorrules +32 -37
  5. package/.github/copilot-instructions.md +35 -44
  6. package/.windsurfrules +32 -37
  7. package/README.md +4 -4
  8. package/dist/cli.cjs +188 -185
  9. package/dist/editor.cjs +338 -0
  10. package/dist/editor.cjs.map +1 -0
  11. package/dist/editor.d.cts +27 -0
  12. package/dist/editor.d.ts +27 -0
  13. package/dist/editor.js +307 -0
  14. package/dist/editor.js.map +1 -0
  15. package/dist/highlight.cjs +560 -0
  16. package/dist/highlight.cjs.map +1 -0
  17. package/dist/highlight.d.cts +32 -0
  18. package/dist/highlight.d.ts +32 -0
  19. package/dist/highlight.js +530 -0
  20. package/dist/highlight.js.map +1 -0
  21. package/dist/index.cjs +3467 -1078
  22. package/dist/index.cjs.map +1 -1
  23. package/dist/index.d.cts +22 -1
  24. package/dist/index.d.ts +22 -1
  25. package/dist/index.js +3466 -1078
  26. package/dist/index.js.map +1 -1
  27. package/docs/language-reference.md +46 -37
  28. package/gallery/fixtures/arc.dgmo +18 -0
  29. package/gallery/fixtures/area.dgmo +19 -0
  30. package/gallery/fixtures/bar-stacked.dgmo +10 -0
  31. package/gallery/fixtures/bar.dgmo +10 -0
  32. package/gallery/fixtures/c4-full.dgmo +52 -0
  33. package/gallery/fixtures/c4.dgmo +17 -0
  34. package/gallery/fixtures/chord.dgmo +12 -0
  35. package/gallery/fixtures/class-basic.dgmo +14 -0
  36. package/gallery/fixtures/class-full.dgmo +43 -0
  37. package/gallery/fixtures/doughnut.dgmo +8 -0
  38. package/gallery/fixtures/flowchart-basic.dgmo +3 -0
  39. package/gallery/fixtures/flowchart-colors.dgmo +5 -0
  40. package/gallery/fixtures/flowchart-complex.dgmo +17 -0
  41. package/gallery/fixtures/flowchart-decision.dgmo +5 -0
  42. package/gallery/fixtures/flowchart-full.dgmo +13 -0
  43. package/gallery/fixtures/flowchart-groups.dgmo +10 -0
  44. package/gallery/fixtures/flowchart-loop.dgmo +7 -0
  45. package/gallery/fixtures/flowchart-nested.dgmo +7 -0
  46. package/gallery/fixtures/flowchart-shapes.dgmo +5 -0
  47. package/gallery/fixtures/function.dgmo +8 -0
  48. package/gallery/fixtures/funnel.dgmo +7 -0
  49. package/gallery/fixtures/gantt-full.dgmo +49 -0
  50. package/gallery/fixtures/gantt.dgmo +42 -0
  51. package/gallery/fixtures/heatmap.dgmo +8 -0
  52. package/gallery/fixtures/infra-full.dgmo +78 -0
  53. package/gallery/fixtures/infra-overload.dgmo +25 -0
  54. package/gallery/fixtures/infra.dgmo +47 -0
  55. package/gallery/fixtures/initiative-status-full.dgmo +46 -0
  56. package/gallery/fixtures/initiative-status-phases.dgmo +29 -0
  57. package/gallery/fixtures/initiative-status.dgmo +9 -0
  58. package/gallery/fixtures/line.dgmo +19 -0
  59. package/gallery/fixtures/multi-line.dgmo +11 -0
  60. package/gallery/fixtures/org-basic.dgmo +16 -0
  61. package/gallery/fixtures/org-full.dgmo +69 -0
  62. package/gallery/fixtures/org-teams.dgmo +25 -0
  63. package/gallery/fixtures/pie.dgmo +9 -0
  64. package/gallery/fixtures/polar-area.dgmo +8 -0
  65. package/gallery/fixtures/quadrant.dgmo +18 -0
  66. package/gallery/fixtures/radar.dgmo +8 -0
  67. package/gallery/fixtures/sankey.dgmo +31 -0
  68. package/gallery/fixtures/scatter.dgmo +21 -0
  69. package/gallery/fixtures/sequence-tags-protocols.dgmo +45 -0
  70. package/gallery/fixtures/sequence-tags.dgmo +41 -0
  71. package/gallery/fixtures/sequence.dgmo +35 -0
  72. package/gallery/fixtures/sitemap-basic.dgmo +12 -0
  73. package/gallery/fixtures/sitemap-full.dgmo +156 -0
  74. package/gallery/fixtures/slope.dgmo +9 -0
  75. package/gallery/fixtures/spr-eras.dgmo +62 -0
  76. package/gallery/fixtures/state.dgmo +30 -0
  77. package/gallery/fixtures/timeline-intraday.dgmo +14 -0
  78. package/gallery/fixtures/timeline.dgmo +32 -0
  79. package/gallery/fixtures/venn.dgmo +10 -0
  80. package/gallery/fixtures/wordcloud.dgmo +24 -0
  81. package/package.json +71 -2
  82. package/src/c4/layout.ts +372 -90
  83. package/src/c4/parser.ts +100 -55
  84. package/src/chart.ts +91 -28
  85. package/src/class/parser.ts +41 -12
  86. package/src/cli.ts +211 -62
  87. package/src/completion.ts +378 -183
  88. package/src/d3.ts +1044 -303
  89. package/src/dgmo-mermaid.ts +16 -13
  90. package/src/dgmo-router.ts +69 -23
  91. package/src/echarts.ts +646 -153
  92. package/src/editor/dgmo.grammar +69 -0
  93. package/src/editor/dgmo.grammar.d.ts +2 -0
  94. package/src/editor/dgmo.grammar.js +18 -0
  95. package/src/editor/dgmo.grammar.terms.d.ts +5 -0
  96. package/src/editor/dgmo.grammar.terms.js +35 -0
  97. package/src/editor/highlight-api.ts +444 -0
  98. package/src/editor/highlight.ts +36 -0
  99. package/src/editor/index.ts +28 -0
  100. package/src/editor/keywords.ts +222 -0
  101. package/src/editor/tokens.ts +30 -0
  102. package/src/er/parser.ts +48 -14
  103. package/src/er/renderer.ts +112 -53
  104. package/src/gantt/calculator.ts +91 -29
  105. package/src/gantt/parser.ts +197 -71
  106. package/src/gantt/renderer.ts +1120 -350
  107. package/src/graph/flowchart-parser.ts +46 -25
  108. package/src/graph/state-parser.ts +47 -17
  109. package/src/index.ts +96 -31
  110. package/src/infra/parser.ts +157 -53
  111. package/src/infra/renderer.ts +723 -271
  112. package/src/initiative-status/parser.ts +138 -44
  113. package/src/kanban/parser.ts +25 -14
  114. package/src/org/layout.ts +111 -44
  115. package/src/org/parser.ts +69 -22
  116. package/src/palettes/index.ts +3 -2
  117. package/src/sequence/parser.ts +193 -61
  118. package/src/sitemap/parser.ts +65 -29
  119. package/src/utils/arrows.ts +2 -22
  120. package/src/utils/duration.ts +39 -21
  121. package/src/utils/legend-constants.ts +0 -2
  122. package/src/utils/parsing.ts +75 -31
@@ -2,14 +2,19 @@
2
2
  // Gantt Chart Parser
3
3
  // ============================================================
4
4
 
5
- import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
5
+ import { makeDgmoError, formatDgmoError } from '../diagnostics';
6
6
  import type { DgmoError } from '../diagnostics';
7
- import type { TagGroup, TagEntry } from '../utils/tag-groups';
7
+ import type { TagGroup } from '../utils/tag-groups';
8
8
  import { matchTagBlockHeading } from '../utils/tag-groups';
9
- import { measureIndent, extractColor, parsePipeMetadata, MULTIPLE_PIPE_ERROR, parseFirstLine, prescanOptions } from '../utils/parsing';
9
+ import {
10
+ measureIndent,
11
+ extractColor,
12
+ parsePipeMetadata,
13
+ MULTIPLE_PIPE_ERROR,
14
+ parseFirstLine,
15
+ } from '../utils/parsing';
10
16
  import { parseOffset } from '../utils/duration';
11
17
  import type { PaletteColors } from '../palettes';
12
- import { resolveColor } from '../colors';
13
18
  import { getSeriesColors } from '../palettes';
14
19
  import type {
15
20
  ParsedGantt,
@@ -17,11 +22,6 @@ import type {
17
22
  GanttTask,
18
23
  GanttGroup,
19
24
  GanttParallelBlock,
20
- GanttDependency,
21
- GanttHolidays,
22
- GanttEra,
23
- GanttMarker,
24
- GanttOptions,
25
25
  Duration,
26
26
  DurationUnit,
27
27
  Offset,
@@ -37,7 +37,8 @@ const DURATION_RE = /^(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)(\?)?\s+(.+)$/;
37
37
  const EXPLICIT_DATE_RE = /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s+(.+)$/;
38
38
 
39
39
  /** Timeline migration syntax: `2024-01-15 -> 30d Label` or `2024-01-15 14:30 -> 2h Label` */
40
- const TIMELINE_DURATION_RE = /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s*(?:->|\u2013>)\s*(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)(\?)?\s+(.+)$/;
40
+ const TIMELINE_DURATION_RE =
41
+ /^(\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?)\s*(?:->|\u2013>)\s*(\d+(?:\.\d+)?)(min|bd|d|w|m|q|y|h)(\?)?\s+(.+)$/;
41
42
 
42
43
  /** Group container: `[GroupName]` with optional pipe metadata */
43
44
  const GROUP_RE = /^\[(.+?)\]\s*(.*)$/;
@@ -49,30 +50,47 @@ const DEPENDENCY_RE = /^(?:-(.+?))?->\s*(.+)$/;
49
50
  const COMMENT_RE = /^\/\//;
50
51
 
51
52
  /** Era: `era YYYY[-MM[-DD[ HH:MM]]] -> YYYY[-MM[-DD[ HH:MM]]] Label (color?)` */
52
- const ERA_RE = /^era\s+(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s*(?:->|\u2013>)\s*(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s+(.+)$/i;
53
+ const ERA_RE =
54
+ /^era\s+(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s*(?:->|\u2013>)\s*(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s+(.+)$/i;
53
55
 
54
56
  /** Marker: `marker YYYY[-MM[-DD[ HH:MM]]] Label (color?)` */
55
- const MARKER_RE = /^marker\s+(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s+(.+)$/i;
57
+ const MARKER_RE =
58
+ /^marker\s+(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s+(.+)$/i;
56
59
 
57
60
  /** Holiday date: `2024-01-15 Label` */
58
61
  const HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2})\s+(.+)$/;
59
62
 
60
63
  /** Holiday range: `2024-12-24 -> 2024-12-31 Label` */
61
- const HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*(?:->|\u2013>)\s*(\d{4}-\d{2}-\d{2})\s+(.+)$/;
64
+ const HOLIDAY_RANGE_RE =
65
+ /^(\d{4}-\d{2}-\d{2})\s*(?:->|\u2013>)\s*(\d{4}-\d{2}-\d{2})\s+(.+)$/;
62
66
 
63
67
  /** Workweek override: `workweek sun-thu` */
64
68
  const WORKWEEK_RE = /^workweek\s+(.+)$/i;
65
69
 
66
70
  /** Era entry (inside era block, no `era` prefix): `YYYY[-MM[-DD[ HH:MM]]] -> YYYY[-MM[-DD[ HH:MM]]] Label` */
67
- const ERA_ENTRY_RE = /^(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s*(?:->|\u2013>)\s*(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s+(.+)$/;
71
+ const ERA_ENTRY_RE =
72
+ /^(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s*(?:->|\u2013>)\s*(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s+(.+)$/;
68
73
 
69
74
  /** Marker entry (inside marker block, no `marker` prefix): `YYYY[-MM[-DD[ HH:MM]]] Label` */
70
- const MARKER_ENTRY_RE = /^(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s+(.+)$/;
75
+ const MARKER_ENTRY_RE =
76
+ /^(\d{4}(?:-\d{2}(?:-\d{2}(?: \d{2}:\d{2})?)?)?)\s+(.+)$/;
71
77
 
72
78
  // Valid weekday names
73
79
  const WEEKDAY_MAP: Record<string, Weekday> = {
74
- mon: 'mon', tue: 'tue', wed: 'wed', thu: 'thu', fri: 'fri', sat: 'sat', sun: 'sun',
75
- monday: 'mon', tuesday: 'tue', wednesday: 'wed', thursday: 'thu', friday: 'fri', saturday: 'sat', sunday: 'sun',
80
+ mon: 'mon',
81
+ tue: 'tue',
82
+ wed: 'wed',
83
+ thu: 'thu',
84
+ fri: 'fri',
85
+ sat: 'sat',
86
+ sun: 'sun',
87
+ monday: 'mon',
88
+ tuesday: 'tue',
89
+ wednesday: 'wed',
90
+ thursday: 'thu',
91
+ friday: 'fri',
92
+ saturday: 'sat',
93
+ sunday: 'sun',
76
94
  };
77
95
 
78
96
  // ── Block Stack ─────────────────────────────────────────────
@@ -87,13 +105,20 @@ interface BlockEntry {
87
105
 
88
106
  // ── Parser ──────────────────────────────────────────────────
89
107
 
90
- export function parseGantt(content: string, palette?: PaletteColors): ParsedGantt {
108
+ export function parseGantt(
109
+ content: string,
110
+ palette?: PaletteColors
111
+ ): ParsedGantt {
91
112
  const lines = content.split('\n');
92
113
  const diagnostics: DgmoError[] = [];
93
114
 
94
115
  const result: ParsedGantt = {
95
116
  nodes: [],
96
- holidays: { dates: [], ranges: [], workweek: ['mon', 'tue', 'wed', 'thu', 'fri'] },
117
+ holidays: {
118
+ dates: [],
119
+ ranges: [],
120
+ workweek: ['mon', 'tue', 'wed', 'thu', 'fri'],
121
+ },
97
122
  tagGroups: [],
98
123
  eras: [],
99
124
  markers: [],
@@ -156,7 +181,6 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
156
181
  // ── State ───────────────────────────────────────────────
157
182
 
158
183
  let seenChartType = false;
159
- let inHeaderBlock = true; // options must come before content
160
184
  let inHolidaysBlock = false;
161
185
  let holidaysBlockIndent = 0;
162
186
  let inTagBlock = false;
@@ -206,7 +230,10 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
206
230
  const firstLineResult = parseFirstLine(line);
207
231
  if (firstLineResult) {
208
232
  if (firstLineResult.chartType !== 'gantt') {
209
- return fail(lineNumber, `Expected chart type "gantt", got "${firstLineResult.chartType}"`);
233
+ return fail(
234
+ lineNumber,
235
+ `Expected chart type "gantt", got "${firstLineResult.chartType}"`
236
+ );
210
237
  }
211
238
  seenChartType = true;
212
239
  if (firstLineResult.title) {
@@ -252,7 +279,10 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
252
279
  if (days) {
253
280
  result.holidays.workweek = days;
254
281
  } else {
255
- warn(lineNumber, `Invalid workweek format: "${workweekMatch[1]}". Use day range like "sun-thu" or comma-separated days.`);
282
+ warn(
283
+ lineNumber,
284
+ `Invalid workweek format: "${workweekMatch[1]}". Use day range like "sun-thu" or comma-separated days.`
285
+ );
256
286
  }
257
287
  continue;
258
288
  }
@@ -330,7 +360,10 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
330
360
  // First entry is the default (no `default` keyword needed)
331
361
  if (COMMENT_RE.test(line)) continue;
332
362
  const extracted = extractColor(line, palette);
333
- const color = extracted.color || seriesColors[currentTagGroup.entries.length % seriesColors.length] || '#888888';
363
+ const color =
364
+ extracted.color ||
365
+ seriesColors[currentTagGroup.entries.length % seriesColors.length] ||
366
+ '#888888';
334
367
  const isFirstEntry = currentTagGroup.entries.length === 0;
335
368
  currentTagGroup.entries.push({
336
369
  value: extracted.label,
@@ -369,19 +402,32 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
369
402
  let offset: Offset | undefined;
370
403
 
371
404
  if (depParts.length > 1) {
372
- const meta = parsePipeMetadata(['', ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_ERROR));
405
+ const meta = parsePipeMetadata(
406
+ ['', ...depParts.slice(1)],
407
+ aliasMap,
408
+ () => warn(lineNumber, MULTIPLE_PIPE_ERROR)
409
+ );
373
410
  if (meta.lag || meta.lead) {
374
411
  const key = meta.lag ? 'lag' : 'lead';
375
- softError(lineNumber, `"${key}" is no longer supported — use "offset: ${meta[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`);
412
+ softError(
413
+ lineNumber,
414
+ `"${key}" is no longer supported — use "offset: ${meta[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`
415
+ );
376
416
  }
377
417
  if (meta.offset) {
378
418
  const raw = meta.offset;
379
419
  if (raw.trim().startsWith('+')) {
380
- warn(lineNumber, `Invalid offset: "${raw}". Explicit "+" is not supported — use "${raw.trim().slice(1)}" instead.`);
420
+ warn(
421
+ lineNumber,
422
+ `Invalid offset: "${raw}". Explicit "+" is not supported — use "${raw.trim().slice(1)}" instead.`
423
+ );
381
424
  } else {
382
425
  offset = parseOffset(raw) ?? undefined;
383
426
  if (!offset) {
384
- warn(lineNumber, `Invalid offset: "${raw}". Expected format like "3bd", "-5d", or "0bd".`);
427
+ warn(
428
+ lineNumber,
429
+ `Invalid offset: "${raw}". Expected format like "3bd", "-5d", or "0bd".`
430
+ );
385
431
  }
386
432
  }
387
433
  }
@@ -415,13 +461,14 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
415
461
  if (line.toLowerCase() === 'holiday' || line.toLowerCase() === 'holidays') {
416
462
  inHolidaysBlock = true;
417
463
  holidaysBlockIndent = indent;
418
- inHeaderBlock = false;
419
464
  result.options.holidaysLineNumber = lineNumber;
420
465
  continue;
421
466
  }
422
467
 
423
468
  // Single-line holiday: `holiday 2024-12-25 Christmas`
424
- const holidayInlineMatch = line.match(/^holiday\s+(\d{4}-\d{2}-\d{2})\s+(.+)$/i);
469
+ const holidayInlineMatch = line.match(
470
+ /^holiday\s+(\d{4}-\d{2}-\d{2})\s+(.+)$/i
471
+ );
425
472
  if (holidayInlineMatch) {
426
473
  result.holidays.dates.push({
427
474
  date: holidayInlineMatch[1],
@@ -429,7 +476,6 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
429
476
  lineNumber,
430
477
  });
431
478
  result.options.holidaysLineNumber ??= lineNumber;
432
- inHeaderBlock = false;
433
479
  continue;
434
480
  }
435
481
 
@@ -438,7 +484,6 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
438
484
  if (tagMatch) {
439
485
  inTagBlock = true;
440
486
  tagBlockIndent = indent;
441
- inHeaderBlock = false;
442
487
  currentTagGroup = {
443
488
  name: tagMatch.name,
444
489
  alias: tagMatch.alias,
@@ -458,9 +503,11 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
458
503
  if (days) {
459
504
  result.holidays.workweek = days;
460
505
  } else {
461
- warn(lineNumber, `Invalid workweek format: "${topWorkweekMatch[1]}". Use day range like "sun-thu" or comma-separated days.`);
506
+ warn(
507
+ lineNumber,
508
+ `Invalid workweek format: "${topWorkweekMatch[1]}". Use day range like "sun-thu" or comma-separated days.`
509
+ );
462
510
  }
463
- inHeaderBlock = false;
464
511
  continue;
465
512
  }
466
513
 
@@ -468,7 +515,6 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
468
515
  if (line.toLowerCase() === 'era') {
469
516
  inEraBlock = true;
470
517
  eraBlockIndent = indent;
471
- inHeaderBlock = false;
472
518
  continue;
473
519
  }
474
520
 
@@ -484,7 +530,6 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
484
530
  color: eraExtracted.color || null,
485
531
  lineNumber,
486
532
  });
487
- inHeaderBlock = false;
488
533
  continue;
489
534
  }
490
535
 
@@ -492,7 +537,6 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
492
537
  if (line.toLowerCase() === 'marker') {
493
538
  inMarkerBlock = true;
494
539
  markerBlockIndent = indent;
495
- inHeaderBlock = false;
496
540
  continue;
497
541
  }
498
542
 
@@ -507,7 +551,6 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
507
551
  color: markerExtracted.color || null,
508
552
  lineNumber,
509
553
  });
510
- inHeaderBlock = false;
511
554
  continue;
512
555
  }
513
556
 
@@ -568,7 +611,10 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
568
611
  if (/^\d{4}-\d{2}-\d{2}(?: \d{2}:\d{2})?$/.test(value)) {
569
612
  result.options.todayMarker = value;
570
613
  } else {
571
- warn(lineNumber, `Invalid today-marker value: "${value}". Expected YYYY-MM-DD.`);
614
+ warn(
615
+ lineNumber,
616
+ `Invalid today-marker value: "${value}". Expected YYYY-MM-DD.`
617
+ );
572
618
  }
573
619
  break;
574
620
  case 'critical-path':
@@ -583,20 +629,20 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
583
629
  result.options.sort = 'tag';
584
630
  const colonIdx = value.indexOf(':');
585
631
  if (colonIdx !== -1) {
586
- result.options.defaultSwimlaneGroup = value.slice(colonIdx + 1).trim() || null;
632
+ result.options.defaultSwimlaneGroup =
633
+ value.slice(colonIdx + 1).trim() || null;
587
634
  }
588
635
  } else {
589
- warn(lineNumber, `Invalid sort value: "${value}". Expected "tag" or "tag:GroupName".`);
636
+ warn(
637
+ lineNumber,
638
+ `Invalid sort value: "${value}". Expected "tag" or "tag:GroupName".`
639
+ );
590
640
  }
591
641
  break;
592
642
  }
593
643
  continue;
594
644
  }
595
645
 
596
- inHeaderBlock = false;
597
-
598
-
599
-
600
646
  // ── Parallel block ────────────────────────────────────
601
647
 
602
648
  if (line === 'parallel') {
@@ -616,8 +662,14 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
616
662
  const groupMatch = line.match(GROUP_RE);
617
663
  if (groupMatch) {
618
664
  // Validate nesting: group under a task is invalid
619
- if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === 'task') {
620
- softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
665
+ if (
666
+ blockStack.length > 0 &&
667
+ blockStack[blockStack.length - 1].containerType === 'task'
668
+ ) {
669
+ softError(
670
+ lineNumber,
671
+ `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`
672
+ );
621
673
  continue;
622
674
  }
623
675
 
@@ -633,7 +685,11 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
633
685
  // Check if first segment after brackets is pipe metadata
634
686
  metadata = parsePipeMetadata(['', ...segments], aliasMap, pipeWarn);
635
687
  } else if (segments.length > 1) {
636
- metadata = parsePipeMetadata(['', ...segments.slice(1)], aliasMap, pipeWarn);
688
+ metadata = parsePipeMetadata(
689
+ ['', ...segments.slice(1)],
690
+ aliasMap,
691
+ pipeWarn
692
+ );
637
693
  }
638
694
 
639
695
  // Extract color from group name if present
@@ -670,11 +726,21 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
670
726
  const uncertain = !!timelineDurMatch[4];
671
727
  const labelRaw = timelineDurMatch[5];
672
728
 
673
- const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
729
+ const task = makeTask(
730
+ labelRaw,
731
+ { amount, unit },
732
+ uncertain,
733
+ lineNumber,
734
+ startDate
735
+ );
674
736
  const taskNode: GanttNode = { kind: 'task', ...task };
675
737
  currentContainer().push(taskNode);
676
738
  lastTaskNode = taskNode as GanttNode & { kind: 'task' };
677
- blockStack.push({ node: taskNode as unknown as GanttGroup, indent, containerType: 'task' });
739
+ blockStack.push({
740
+ node: taskNode as unknown as GanttGroup,
741
+ indent,
742
+ containerType: 'task',
743
+ });
678
744
  continue;
679
745
  }
680
746
 
@@ -691,7 +757,11 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
691
757
  const taskNode: GanttNode = { kind: 'task', ...task };
692
758
  currentContainer().push(taskNode);
693
759
  lastTaskNode = taskNode as GanttNode & { kind: 'task' };
694
- blockStack.push({ node: taskNode as unknown as GanttGroup, indent, containerType: 'task' });
760
+ blockStack.push({
761
+ node: taskNode as unknown as GanttGroup,
762
+ indent,
763
+ containerType: 'task',
764
+ });
695
765
  continue;
696
766
  }
697
767
 
@@ -704,13 +774,17 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
704
774
  null, // no duration — it's a date anchor / milestone
705
775
  false,
706
776
  lineNumber,
707
- explicitDateMatch[1],
777
+ explicitDateMatch[1]
708
778
  );
709
779
  // Explicit date tasks with no duration are milestones
710
780
  const taskNode: GanttNode = { kind: 'task', ...task };
711
781
  currentContainer().push(taskNode);
712
782
  lastTaskNode = taskNode as GanttNode & { kind: 'task' };
713
- blockStack.push({ node: taskNode as unknown as GanttGroup, indent, containerType: 'task' });
783
+ blockStack.push({
784
+ node: taskNode as unknown as GanttGroup,
785
+ indent,
786
+ containerType: 'task',
787
+ });
714
788
  continue;
715
789
  }
716
790
 
@@ -720,7 +794,10 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
720
794
  if (depMatch) {
721
795
  // Dependency without a task context is an error
722
796
  if (!lastTaskNode) {
723
- softError(lineNumber, `Dependency "-> ${depMatch[2]}" must be indented under a task.`);
797
+ softError(
798
+ lineNumber,
799
+ `Dependency "-> ${depMatch[2]}" must be indented under a task.`
800
+ );
724
801
  continue;
725
802
  }
726
803
  // This happens when the dep is at the same indent as the task
@@ -730,19 +807,32 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
730
807
  let offset: Offset | undefined;
731
808
 
732
809
  if (depParts.length > 1) {
733
- const meta = parsePipeMetadata(['', ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_ERROR));
810
+ const meta = parsePipeMetadata(
811
+ ['', ...depParts.slice(1)],
812
+ aliasMap,
813
+ () => warn(lineNumber, MULTIPLE_PIPE_ERROR)
814
+ );
734
815
  if (meta.lag || meta.lead) {
735
816
  const key = meta.lag ? 'lag' : 'lead';
736
- softError(lineNumber, `"${key}" is no longer supported — use "offset: ${meta[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`);
817
+ softError(
818
+ lineNumber,
819
+ `"${key}" is no longer supported — use "offset: ${meta[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`
820
+ );
737
821
  }
738
822
  if (meta.offset) {
739
823
  const raw = meta.offset;
740
824
  if (raw.trim().startsWith('+')) {
741
- warn(lineNumber, `Invalid offset: "${raw}". Explicit "+" is not supported — use "${raw.trim().slice(1)}" instead.`);
825
+ warn(
826
+ lineNumber,
827
+ `Invalid offset: "${raw}". Explicit "+" is not supported — use "${raw.trim().slice(1)}" instead.`
828
+ );
742
829
  } else {
743
830
  offset = parseOffset(raw) ?? undefined;
744
831
  if (!offset) {
745
- warn(lineNumber, `Invalid offset: "${raw}". Expected format like "3bd", "-5d", or "0bd".`);
832
+ warn(
833
+ lineNumber,
834
+ `Invalid offset: "${raw}". Expected format like "3bd", "-5d", or "0bd".`
835
+ );
746
836
  }
747
837
  }
748
838
  }
@@ -754,7 +844,10 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
754
844
 
755
845
  // ── Bare label = parse error ──────────────────────────
756
846
 
757
- softError(lineNumber, `Expected duration (e.g., "10d Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line}"`);
847
+ softError(
848
+ lineNumber,
849
+ `Expected duration (e.g., "10d Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line}"`
850
+ );
758
851
  continue;
759
852
  }
760
853
 
@@ -782,20 +875,26 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
782
875
  duration: Duration | null,
783
876
  uncertain: boolean,
784
877
  ln: number,
785
- explicitStart?: string,
878
+ explicitStart?: string
786
879
  ): GanttTask {
787
880
  const segments = labelRaw.split('|');
788
881
  const label = segments[0].trim();
789
882
 
790
883
  // Check for reserved keyword
791
884
  if (label.toLowerCase() === 'parallel') {
792
- softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
885
+ softError(
886
+ ln,
887
+ `"parallel" is a reserved keyword and cannot be used as a task name.`
888
+ );
793
889
  }
794
890
 
795
891
  // Parse pipe metadata
796
- const metadata = segments.length > 1
797
- ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_ERROR))
798
- : {};
892
+ const metadata =
893
+ segments.length > 1
894
+ ? parsePipeMetadata(segments, aliasMap, () =>
895
+ warn(ln, MULTIPLE_PIPE_ERROR)
896
+ )
897
+ : {};
799
898
 
800
899
  // Extract progress from metadata or shorthand
801
900
  let progress: number | null = null;
@@ -815,7 +914,10 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
815
914
  // Reject lag/lead — use offset instead
816
915
  if (metadata.lag || metadata.lead) {
817
916
  const key = metadata.lag ? 'lag' : 'lead';
818
- softError(ln, `"${key}" is no longer supported — use "offset: ${metadata[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`);
917
+ softError(
918
+ ln,
919
+ `"${key}" is no longer supported — use "offset: ${metadata[key]}" instead.${key === 'lead' ? ' Negate the value for lead behavior: "offset: -...".' : ''}`
920
+ );
819
921
  }
820
922
 
821
923
  // Extract task-level offset from metadata
@@ -823,11 +925,17 @@ export function parseGantt(content: string, palette?: PaletteColors): ParsedGant
823
925
  if (metadata.offset) {
824
926
  const raw = metadata.offset;
825
927
  if (raw.trim().startsWith('+')) {
826
- warn(ln, `Invalid offset: "${raw}". Explicit "+" is not supported — use "${raw.trim().slice(1)}" instead.`);
928
+ warn(
929
+ ln,
930
+ `Invalid offset: "${raw}". Explicit "+" is not supported — use "${raw.trim().slice(1)}" instead.`
931
+ );
827
932
  } else {
828
933
  taskOffset = parseOffset(raw) ?? undefined;
829
934
  if (!taskOffset) {
830
- warn(ln, `Invalid offset: "${raw}". Expected format like "3bd", "-5d", or "0bd".`);
935
+ warn(
936
+ ln,
937
+ `Invalid offset: "${raw}". Expected format like "3bd", "-5d", or "0bd".`
938
+ );
831
939
  }
832
940
  }
833
941
  delete metadata.offset;
@@ -873,7 +981,15 @@ function parseWorkweek(s: string): Weekday[] | null {
873
981
  const start = WEEKDAY_MAP[rangeParts[0].trim()];
874
982
  const end = WEEKDAY_MAP[rangeParts[1].trim()];
875
983
  if (start && end) {
876
- const allDays: Weekday[] = ['mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun'];
984
+ const allDays: Weekday[] = [
985
+ 'mon',
986
+ 'tue',
987
+ 'wed',
988
+ 'thu',
989
+ 'fri',
990
+ 'sat',
991
+ 'sun',
992
+ ];
877
993
  const startIdx = allDays.indexOf(start);
878
994
  const endIdx = allDays.indexOf(end);
879
995
  const days: Weekday[] = [];
@@ -888,7 +1004,10 @@ function parseWorkweek(s: string): Weekday[] | null {
888
1004
  }
889
1005
 
890
1006
  // Try comma-separated: "mon, tue, wed, thu, fri"
891
- const parts = s.toLowerCase().split(',').map(p => p.trim());
1007
+ const parts = s
1008
+ .toLowerCase()
1009
+ .split(',')
1010
+ .map((p) => p.trim());
892
1011
  const days: Weekday[] = [];
893
1012
  for (const part of parts) {
894
1013
  const day = WEEKDAY_MAP[part];
@@ -901,13 +1020,20 @@ function parseWorkweek(s: string): Weekday[] | null {
901
1020
  // ── Known option keys ─────────────────────────────────────
902
1021
 
903
1022
  const KNOWN_OPTIONS = new Set([
904
- 'start', 'title', 'today-marker',
905
- 'critical-path', 'dependencies', 'chart', 'sort',
1023
+ 'start',
1024
+ 'title',
1025
+ 'today-marker',
1026
+ 'critical-path',
1027
+ 'dependencies',
1028
+ 'chart',
1029
+ 'sort',
906
1030
  ]);
907
1031
 
908
1032
  /** Boolean options that can appear as bare keywords or with `no-` prefix. */
909
1033
  const KNOWN_BOOLEANS = new Set([
910
- 'critical-path', 'today-marker', 'dependencies',
1034
+ 'critical-path',
1035
+ 'today-marker',
1036
+ 'dependencies',
911
1037
  ]);
912
1038
 
913
1039
  function isKnownOption(key: string): boolean {