@diagrammo/dgmo 0.8.22 → 0.8.25

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 (90) hide show
  1. package/.claude/commands/dgmo.md +60 -72
  2. package/dist/cli.cjs +123 -116
  3. package/dist/editor.cjs +3 -2
  4. package/dist/editor.cjs.map +1 -1
  5. package/dist/editor.js +3 -2
  6. package/dist/editor.js.map +1 -1
  7. package/dist/highlight.cjs +3 -2
  8. package/dist/highlight.cjs.map +1 -1
  9. package/dist/highlight.js +3 -2
  10. package/dist/highlight.js.map +1 -1
  11. package/dist/index.cjs +1649 -442
  12. package/dist/index.cjs.map +1 -1
  13. package/dist/index.d.cts +196 -23
  14. package/dist/index.d.ts +196 -23
  15. package/dist/index.js +1631 -440
  16. package/dist/index.js.map +1 -1
  17. package/dist/internal.cjs +677 -0
  18. package/dist/internal.cjs.map +1 -0
  19. package/dist/internal.d.cts +267 -0
  20. package/dist/internal.d.ts +267 -0
  21. package/dist/internal.js +633 -0
  22. package/dist/internal.js.map +1 -0
  23. package/docs/guide/chart-area.md +17 -17
  24. package/docs/guide/chart-bar-stacked.md +12 -12
  25. package/docs/guide/chart-cycle.md +156 -0
  26. package/docs/guide/chart-doughnut.md +10 -10
  27. package/docs/guide/chart-funnel.md +9 -9
  28. package/docs/guide/chart-heatmap.md +10 -10
  29. package/docs/guide/chart-journey-map.md +179 -0
  30. package/docs/guide/chart-kanban.md +2 -0
  31. package/docs/guide/chart-line.md +19 -19
  32. package/docs/guide/chart-multi-line.md +16 -16
  33. package/docs/guide/chart-pie.md +11 -11
  34. package/docs/guide/chart-polar-area.md +10 -10
  35. package/docs/guide/chart-pyramid.md +111 -0
  36. package/docs/guide/chart-radar.md +9 -9
  37. package/docs/guide/chart-scatter.md +24 -27
  38. package/docs/guide/index.md +3 -3
  39. package/docs/guide/registry.json +5 -0
  40. package/docs/language-reference.md +108 -26
  41. package/fonts/Inter-Bold.ttf +0 -0
  42. package/fonts/Inter-Regular.ttf +0 -0
  43. package/fonts/LICENSE-Inter.txt +92 -0
  44. package/gallery/fixtures/bar-stacked.dgmo +12 -6
  45. package/gallery/fixtures/heatmap.dgmo +12 -6
  46. package/gallery/fixtures/multi-line.dgmo +11 -7
  47. package/gallery/fixtures/pyramid/dikw.dgmo +17 -0
  48. package/gallery/fixtures/pyramid/inverted-funnel.dgmo +16 -0
  49. package/gallery/fixtures/pyramid/minimal.dgmo +5 -0
  50. package/gallery/fixtures/quadrant.dgmo +8 -8
  51. package/gallery/fixtures/scatter.dgmo +12 -12
  52. package/package.json +14 -2
  53. package/src/boxes-and-lines/parser.ts +13 -2
  54. package/src/boxes-and-lines/renderer.ts +22 -13
  55. package/src/chart-type-scoring.ts +162 -0
  56. package/src/chart-types.ts +437 -0
  57. package/src/cli.ts +152 -101
  58. package/src/completion.ts +9 -48
  59. package/src/cycle/layout.ts +19 -28
  60. package/src/cycle/renderer.ts +59 -32
  61. package/src/cycle/types.ts +21 -0
  62. package/src/d3.ts +30 -3
  63. package/src/dgmo-router.ts +98 -73
  64. package/src/echarts.ts +1 -1
  65. package/src/editor/keywords.ts +3 -2
  66. package/src/fonts.ts +3 -2
  67. package/src/gantt/parser.ts +5 -1
  68. package/src/index.ts +37 -3
  69. package/src/infra/parser.ts +3 -3
  70. package/src/internal.ts +20 -0
  71. package/src/journey-map/layout.ts +7 -3
  72. package/src/journey-map/parser.ts +5 -1
  73. package/src/journey-map/renderer.ts +112 -47
  74. package/src/kanban/parser.ts +5 -1
  75. package/src/org/collapse.ts +82 -4
  76. package/src/org/parser.ts +1 -1
  77. package/src/org/renderer.ts +221 -4
  78. package/src/pyramid/parser.ts +172 -0
  79. package/src/pyramid/renderer.ts +684 -0
  80. package/src/pyramid/types.ts +28 -0
  81. package/src/render.ts +2 -8
  82. package/src/sequence/parser.ts +64 -22
  83. package/src/sequence/participant-inference.ts +0 -1
  84. package/src/sequence/renderer.ts +97 -265
  85. package/src/sharing.ts +0 -1
  86. package/src/sitemap/parser.ts +1 -1
  87. package/src/tech-radar/interactive.ts +54 -0
  88. package/src/utils/parsing.ts +1 -0
  89. package/src/utils/tag-groups.ts +35 -5
  90. package/src/wireframe/parser.ts +3 -1
package/src/d3.ts CHANGED
@@ -21,7 +21,8 @@ export type VisualizationType =
21
21
  | 'quadrant'
22
22
  | 'sequence'
23
23
  | 'tech-radar'
24
- | 'cycle';
24
+ | 'cycle'
25
+ | 'pyramid';
25
26
 
26
27
  interface D3DataItem {
27
28
  label: string;
@@ -1505,8 +1506,15 @@ export function parseVisualization(
1505
1506
  result.diagnostics.push(makeDgmoError(line, msg, 'warning')),
1506
1507
  suggest
1507
1508
  );
1508
- validateTagGroupNames(result.timelineTagGroups, (line, msg) =>
1509
- result.diagnostics.push(makeDgmoError(line, msg, 'warning'))
1509
+ validateTagGroupNames(
1510
+ result.timelineTagGroups,
1511
+ (line, msg) =>
1512
+ result.diagnostics.push(makeDgmoError(line, msg, 'warning')),
1513
+ (line, msg) => {
1514
+ const diag = makeDgmoError(line, msg);
1515
+ result.diagnostics.push(diag);
1516
+ if (!result.error) result.error = formatDgmoError(diag);
1517
+ }
1510
1518
  );
1511
1519
  for (const group of result.timelineTagGroups) {
1512
1520
  if (!group.defaultValue) continue;
@@ -7120,6 +7128,25 @@ export async function renderForExport(
7120
7128
  return finalizeSvgExport(container, theme, effectivePalette);
7121
7129
  }
7122
7130
 
7131
+ if (detectedType === 'pyramid') {
7132
+ const { parsePyramid } = await import('./pyramid/parser');
7133
+ const { renderPyramidForExport } = await import('./pyramid/renderer');
7134
+
7135
+ const effectivePalette = await resolveExportPalette(theme, palette);
7136
+ const pyramidParsed = parsePyramid(content);
7137
+ if (pyramidParsed.error || pyramidParsed.layers.length === 0) return '';
7138
+
7139
+ const container = createExportContainer(EXPORT_WIDTH, EXPORT_HEIGHT);
7140
+ renderPyramidForExport(
7141
+ container,
7142
+ pyramidParsed,
7143
+ effectivePalette,
7144
+ theme === 'dark',
7145
+ { width: EXPORT_WIDTH, height: EXPORT_HEIGHT }
7146
+ );
7147
+ return finalizeSvgExport(container, theme, effectivePalette);
7148
+ }
7149
+
7123
7150
  const parsed = parseVisualization(content, palette);
7124
7151
  // Allow sequence diagrams through even if parseVisualization errors —
7125
7152
  // sequence is parsed by its own dedicated parser (parseSequenceDgmo)
@@ -22,9 +22,11 @@ import { parseWireframe } from './wireframe/parser';
22
22
  import { parseTechRadar } from './tech-radar/parser';
23
23
  import { parseCycle } from './cycle/parser';
24
24
  import { parseJourneyMap } from './journey-map/parser';
25
+ import { parsePyramid } from './pyramid/parser';
25
26
  import { parseFirstLine } from './utils/parsing';
26
27
  import { makeDgmoError, suggest } from './diagnostics';
27
28
  import type { DgmoError } from './diagnostics';
29
+ import { chartTypes } from './chart-types';
28
30
 
29
31
  // ============================================================
30
32
  // Content-based chart type inference helpers
@@ -142,6 +144,7 @@ const VISUALIZATION_TYPES = new Set([
142
144
  'quadrant',
143
145
  'tech-radar',
144
146
  'cycle',
147
+ 'pyramid',
145
148
  ]);
146
149
  const DIAGRAM_TYPES = new Set([
147
150
  'sequence',
@@ -190,77 +193,107 @@ export function isExtendedChartType(chartType: string): boolean {
190
193
  return EXTENDED_CHART_TYPES.has(chartType.toLowerCase());
191
194
  }
192
195
 
193
- /** Standard chart types parsed by parseChart (then rendered via ECharts). Internal use. */
194
- const STANDARD_CHART_TYPES = new Set([
195
- 'bar',
196
- 'line',
197
- 'multi-line',
198
- 'area',
199
- 'pie',
200
- 'doughnut',
201
- 'radar',
202
- 'polar-area',
203
- 'bar-stacked',
204
- ]);
205
-
206
196
  /**
207
- * Returns all supported chart type identifiers.
208
- * Useful for CLI enumeration and autocomplete.
197
+ * Returns all supported chart type identifiers in canonical (tier) order,
198
+ * derived from `chartTypes`. Consumers that need alphabetical order should
199
+ * call `.sort()` explicitly.
209
200
  */
210
201
  export function getAllChartTypes(): string[] {
211
- return [...DATA_CHART_TYPES, ...VISUALIZATION_TYPES, ...DIAGRAM_TYPES];
202
+ return chartTypes.map((c) => c.id);
212
203
  }
213
204
 
214
- // ECharts-native types parsed by parseExtendedChart
215
- const ECHART_TYPES = new Set([
216
- 'scatter',
217
- 'sankey',
218
- 'chord',
219
- 'function',
220
- 'heatmap',
221
- 'funnel',
222
- ]);
205
+ /**
206
+ * Canonical descriptions for every supported chart type. Derived from
207
+ * `chartTypes` so there is exactly one place to update when adding a new
208
+ * type. Consumed by the CLI `--chart-types` flag, the editor autocomplete
209
+ * popup, and the MCP `list_chart_types` tool.
210
+ */
211
+ export const CHART_TYPE_DESCRIPTIONS: Record<string, string> =
212
+ Object.fromEntries(chartTypes.map((c) => [c.id, c.description]));
223
213
 
224
- /** Map chart type strings to their parse function (content → { diagnostics }). */
225
- const PARSE_DISPATCH = new Map<
226
- string,
227
- (content: string) => { diagnostics: DgmoError[] }
228
- >([
229
- ['sequence', (c) => parseSequenceDgmo(c)],
230
- ['flowchart', (c) => parseFlowchart(c)],
231
- ['class', (c) => parseClassDiagram(c)],
232
- ['er', (c) => parseERDiagram(c)],
233
- ['org', (c) => parseOrg(c)],
234
- ['kanban', (c) => parseKanban(c)],
235
- ['c4', (c) => parseC4(c)],
236
- ['state', (c) => parseState(c)],
237
- ['sitemap', (c) => parseSitemap(c)],
238
- ['infra', (c) => parseInfra(c)],
239
- ['gantt', (c) => parseGantt(c)],
240
- ['boxes-and-lines', (c) => parseBoxesAndLines(c)],
241
- ['mindmap', (c) => parseMindmap(c)],
242
- ['wireframe', (c) => parseWireframe(c)],
243
- ['tech-radar', (c) => parseTechRadar(c)],
244
- ['cycle', (c) => parseCycle(c)],
245
- ['journey-map', (c) => parseJourneyMap(c)],
246
- ]);
214
+ // ============================================================
215
+ // Parser registry single source of truth for id → parser
216
+ // ============================================================
217
+
218
+ type ParseResult = { diagnostics: DgmoError[] };
219
+ type ParseFn = (content: string) => ParseResult;
247
220
 
248
221
  /**
249
- * Parse DGMO content and return diagnostics without rendering.
250
- * Useful for the CLI and editor to surface all errors before attempting render.
222
+ * Maps every chart-type id to the parser that handles it. Adding a new
223
+ * chart type means:
224
+ * 1. Add an entry here.
225
+ * 2. Add an entry to `chartTypes` in `chart-types.ts`.
226
+ *
227
+ * The `chart-types.test.ts` cross-check asserts both sets are identical;
228
+ * forgetting either side trips the test.
251
229
  */
230
+ export const chartTypeParsers: ReadonlyArray<readonly [string, ParseFn]> = [
231
+ // Structured diagrams (direct parsers)
232
+ ['sequence', parseSequenceDgmo],
233
+ ['flowchart', parseFlowchart],
234
+ ['class', parseClassDiagram],
235
+ ['er', parseERDiagram],
236
+ ['state', parseState],
237
+ ['org', parseOrg],
238
+ ['kanban', parseKanban],
239
+ ['c4', parseC4],
240
+ ['sitemap', parseSitemap],
241
+ ['infra', parseInfra],
242
+ ['gantt', parseGantt],
243
+ ['boxes-and-lines', parseBoxesAndLines],
244
+ ['mindmap', parseMindmap],
245
+ ['wireframe', parseWireframe],
246
+ ['tech-radar', parseTechRadar],
247
+ ['cycle', parseCycle],
248
+ ['journey-map', parseJourneyMap],
249
+ ['pyramid', parsePyramid],
250
+
251
+ // Standard ECharts charts (parseChart)
252
+ ['bar', parseChart],
253
+ ['line', parseChart],
254
+ ['multi-line', parseChart],
255
+ ['area', parseChart],
256
+ ['pie', parseChart],
257
+ ['doughnut', parseChart],
258
+ ['radar', parseChart],
259
+ ['polar-area', parseChart],
260
+ ['bar-stacked', parseChart],
261
+
262
+ // Extended ECharts charts (parseExtendedChart)
263
+ ['scatter', parseExtendedChart],
264
+ ['sankey', parseExtendedChart],
265
+ ['chord', parseExtendedChart],
266
+ ['function', parseExtendedChart],
267
+ ['heatmap', parseExtendedChart],
268
+ ['funnel', parseExtendedChart],
269
+
270
+ // D3 visualizations (parseVisualization)
271
+ ['slope', parseVisualization],
272
+ ['wordcloud', parseVisualization],
273
+ ['arc', parseVisualization],
274
+ ['timeline', parseVisualization],
275
+ ['venn', parseVisualization],
276
+ ['quadrant', parseVisualization],
277
+ ];
278
+
279
+ /** Ids in the same order as `chartTypeParsers`; used for cross-checks. */
280
+ export const knownChartTypeIds: readonly string[] = chartTypeParsers.map(
281
+ ([id]) => id
282
+ );
283
+
284
+ const PARSER_BY_ID: Map<string, ParseFn> = new Map(chartTypeParsers);
285
+
252
286
  /** All known chart type names for colon-pattern detection. */
253
- const ALL_KNOWN_TYPES = new Set([
254
- ...DATA_CHART_TYPES,
255
- ...VISUALIZATION_TYPES,
256
- ...DIAGRAM_TYPES,
257
- ]);
287
+ const ALL_KNOWN_TYPES: ReadonlySet<string> = new Set(knownChartTypeIds);
258
288
 
259
289
  /**
260
290
  * Parse DGMO content and return diagnostics without rendering.
261
291
  * Useful for the CLI and editor to surface all errors before attempting render.
262
292
  */
263
- export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
293
+ export function parseDgmo(content: string): {
294
+ diagnostics: DgmoError[];
295
+ chartType: string | null;
296
+ } {
264
297
  const chartType = parseDgmoChartType(content);
265
298
 
266
299
  if (!chartType) {
@@ -268,38 +301,30 @@ export function parseDgmo(content: string): { diagnostics: DgmoError[] } {
268
301
  const colonDiag = detectColonChartType(content);
269
302
  if (colonDiag) {
270
303
  const fallback = parseVisualization(content).diagnostics;
271
- return { diagnostics: [colonDiag, ...fallback] };
304
+ return { diagnostics: [colonDiag, ...fallback], chartType: null };
272
305
  }
273
306
 
274
307
  // No chart type detected — try visualization parser as fallback
275
- return { diagnostics: parseVisualization(content).diagnostics };
276
- }
277
-
278
- const directParser = PARSE_DISPATCH.get(chartType);
279
- if (directParser) {
280
- const result = directParser(content);
281
308
  return {
282
- diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
309
+ diagnostics: parseVisualization(content).diagnostics,
310
+ chartType: null,
283
311
  };
284
312
  }
285
313
 
286
- if (STANDARD_CHART_TYPES.has(chartType)) {
287
- const result = parseChart(content);
288
- return {
289
- diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
290
- };
291
- }
292
- if (ECHART_TYPES.has(chartType)) {
293
- const result = parseExtendedChart(content);
314
+ const parser = PARSER_BY_ID.get(chartType);
315
+ if (parser) {
316
+ const result = parser(content);
294
317
  return {
295
318
  diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
319
+ chartType,
296
320
  };
297
321
  }
298
322
 
299
- // Visualization types (slope, wordcloud, arc, timeline, venn, quadrant)
323
+ // Unknown id (defensive): fall through to visualization parser.
300
324
  const result = parseVisualization(content);
301
325
  return {
302
326
  diagnostics: [...result.diagnostics, ...detectEmptyContent(content)],
327
+ chartType,
303
328
  };
304
329
  }
305
330
 
package/src/echarts.ts CHANGED
@@ -1913,7 +1913,7 @@ function buildFunnelOption(
1913
1913
  bottom: 20,
1914
1914
  width: '60%',
1915
1915
  sort: 'descending' as const,
1916
- gap: 2,
1916
+ gap: 0,
1917
1917
  minSize: '8%',
1918
1918
  };
1919
1919
 
@@ -17,6 +17,7 @@ export const CHART_TYPES = new Set([
17
17
  'tech-radar',
18
18
  'mindmap',
19
19
  'journey-map',
20
+ 'pyramid',
20
21
  // Data chart types
21
22
  'bar',
22
23
  'line',
@@ -139,8 +140,6 @@ export const DIRECTIVE_KEYWORDS = new Set([
139
140
  // Sequence
140
141
  'activations',
141
142
  'no-activations',
142
- 'collapse-notes',
143
- 'no-collapse-notes',
144
143
  // Data charts
145
144
  'stacked',
146
145
  'no-label-name',
@@ -158,6 +157,8 @@ export const DIRECTIVE_KEYWORDS = new Set([
158
157
  // Layout
159
158
  'direction-tb',
160
159
  'direction-lr',
160
+ // Pyramid
161
+ 'inverted',
161
162
  // Data chart metadata
162
163
  'title',
163
164
  'series',
package/src/fonts.ts CHANGED
@@ -1,2 +1,3 @@
1
- export const FONT_FAMILY = 'Inter, system-ui, Avenir, Helvetica, Arial, sans-serif';
2
- export const DEFAULT_FONT_NAME = 'Helvetica';
1
+ export const FONT_FAMILY =
2
+ 'Inter, system-ui, Avenir, Helvetica, Arial, sans-serif';
3
+ export const DEFAULT_FONT_NAME = 'Inter';
@@ -926,7 +926,11 @@ export function parseGantt(
926
926
  result.options.sort = 'default';
927
927
  }
928
928
 
929
- validateTagGroupNames(result.tagGroups, warn);
929
+ validateTagGroupNames(result.tagGroups, warn, (line, msg) => {
930
+ const diag = makeDgmoError(line, msg);
931
+ diagnostics.push(diag);
932
+ if (!result.error) result.error = formatDgmoError(diag);
933
+ });
930
934
 
931
935
  // ── Sprint mode detection ──────────────────────────────
932
936
  const hasSprintOption =
package/src/index.ts CHANGED
@@ -23,6 +23,28 @@ export type { ParseInArrowLabelResult } from './utils/arrows';
23
23
 
24
24
  export { render } from './render';
25
25
 
26
+ // ============================================================
27
+ // Chart-type registry (single source of truth)
28
+ // ============================================================
29
+
30
+ export { chartTypes } from './chart-types';
31
+ export type { ChartTypeMeta } from './chart-types';
32
+
33
+ export {
34
+ normalize as normalizeChartTypePrompt,
35
+ matchesContiguously,
36
+ scoreChartType,
37
+ confidence as chartTypeConfidence,
38
+ suggestChartTypes,
39
+ MIN_PRIMARY_SCORE,
40
+ AMBIGUITY_THRESHOLD,
41
+ } from './chart-type-scoring';
42
+ export type {
43
+ ChartTypeScore,
44
+ Confidence as ChartTypeConfidence,
45
+ SuggestionResult as ChartTypeSuggestionResult,
46
+ } from './chart-type-scoring';
47
+
26
48
  // ============================================================
27
49
  // Router
28
50
  // ============================================================
@@ -32,6 +54,10 @@ export {
32
54
  parseDgmo,
33
55
  getRenderCategory,
34
56
  isExtendedChartType,
57
+ getAllChartTypes,
58
+ CHART_TYPE_DESCRIPTIONS,
59
+ chartTypeParsers,
60
+ knownChartTypeIds,
35
61
  } from './dgmo-router';
36
62
  export type { RenderCategory } from './dgmo-router';
37
63
 
@@ -63,6 +89,7 @@ export type {
63
89
 
64
90
  export {
65
91
  parseSequenceDgmo,
92
+ parseSequenceDgmo as parseSequenceDiagram,
66
93
  looksLikeSequence,
67
94
  isSequenceBlock,
68
95
  isSequenceNote,
@@ -331,8 +358,12 @@ export type {
331
358
  ResolvedGroup,
332
359
  } from './gantt/types';
333
360
 
334
- export { collapseOrgTree } from './org/collapse';
335
- export type { CollapsedOrgResult } from './org/collapse';
361
+ export { collapseOrgTree, focusOrgTree } from './org/collapse';
362
+ export type {
363
+ CollapsedOrgResult,
364
+ FocusOrgResult,
365
+ AncestorInfo,
366
+ } from './org/collapse';
336
367
 
337
368
  export { parseMindmap } from './mindmap/parser';
338
369
  export type {
@@ -407,6 +438,10 @@ export type {
407
438
  } from './journey-map/types';
408
439
  export type { JourneyMapInteractiveOptions } from './journey-map/renderer';
409
440
 
441
+ export { parsePyramid } from './pyramid/parser';
442
+ export { renderPyramid, renderPyramidForExport } from './pyramid/renderer';
443
+ export type { ParsedPyramid, PyramidLayer } from './pyramid/types';
444
+
410
445
  export { resolveOrgImports } from './org/resolver';
411
446
  export type {
412
447
  ReadFileFn,
@@ -485,7 +520,6 @@ export {
485
520
  applyGroupOrdering,
486
521
  groupMessagesBySection,
487
522
  buildNoteMessageMap,
488
- collectNoteLineNumbers,
489
523
  } from './sequence/renderer';
490
524
  export type {
491
525
  RenderStep,
@@ -113,8 +113,8 @@ function extractPipeMetadata(rest: string): {
113
113
  const tags: Record<string, string> = {};
114
114
  let clean = rest;
115
115
  let match: RegExpExecArray | null;
116
- const re = new RegExp(PIPE_META_RE.source, 'g');
117
- while ((match = re.exec(rest)) !== null) {
116
+ PIPE_META_RE.lastIndex = 0;
117
+ while ((match = PIPE_META_RE.exec(rest)) !== null) {
118
118
  tags[match[1].trim()] = match[2].trim();
119
119
  clean = clean.replace(match[0], '');
120
120
  }
@@ -765,7 +765,7 @@ export function parseInfra(content: string): ParsedInfra {
765
765
  }
766
766
  }
767
767
 
768
- validateTagGroupNames(result.tagGroups, warn);
768
+ validateTagGroupNames(result.tagGroups, warn, setError);
769
769
 
770
770
  return result;
771
771
  }
@@ -0,0 +1,20 @@
1
+ // ============================================================
2
+ // @diagrammo/dgmo/internal — internal helpers for app consumers.
3
+ // Not part of the public API; may change between versions.
4
+ // ============================================================
5
+
6
+ export { parseDataRowValues } from './chart';
7
+ export {
8
+ computeCardArchive,
9
+ computeCardMove,
10
+ isArchiveColumn,
11
+ } from './kanban/mutations';
12
+ export {
13
+ applyGroupOrdering,
14
+ applyPositionOverrides,
15
+ buildNoteMessageMap,
16
+ buildRenderSequence,
17
+ computeActivations,
18
+ groupMessagesBySection,
19
+ } from './sequence/renderer';
20
+ export { orderArcNodes } from './d3';
@@ -121,14 +121,18 @@ export function layoutJourneyMap(
121
121
  ? parsed.phases.flatMap((p) => p.steps)
122
122
  : parsed.steps;
123
123
 
124
- // Compute step card heights based on content (matches kanban card sizing)
124
+ // Compute step card heights based on content (matches kanban card sizing).
125
+ // Char-width constants MUST match the renderer's wrapText() in renderer.ts
126
+ // (`fontSize * 0.6`) and the title wrap (`TITLE_CHAR_WIDTH`) — otherwise the
127
+ // layout reserves too little vertical space and rendered text overflows.
125
128
  const annoIconIndent = ANNO_ICON_SIZE + ANNO_ICON_GAP;
126
129
  const annoTextW = STEP_CARD_WIDTH - CARD_PADDING_X * 2 - annoIconIndent;
127
130
  const descTextWidth = STEP_CARD_WIDTH - CARD_PADDING_X * 2;
128
- const charWidth = 4.8; // average char width at FONT_SIZE_META (10px)
131
+ const FONT_SIZE_META = 10;
132
+ const charWidth = FONT_SIZE_META * 0.6; // matches renderer wrapText()
129
133
 
130
134
  const titleTextWidth = STEP_CARD_WIDTH - CARD_PADDING_X * 2;
131
- const titleCharWidth = 6.5; // average char width at FONT_SIZE_STEP (12px)
135
+ const titleCharWidth = 6.5; // matches renderer TITLE_CHAR_WIDTH (FONT_SIZE_STEP 12px)
132
136
  const TITLE_LINE_HEIGHT = 16;
133
137
 
134
138
  const stepHeights = allSteps.map((step) => {
@@ -408,7 +408,11 @@ export function parseJourneyMap(
408
408
  return fail(1, 'No phases or steps found');
409
409
  }
410
410
 
411
- validateTagGroupNames(result.tagGroups, warn);
411
+ validateTagGroupNames(result.tagGroups, warn, (line, msg) => {
412
+ const diag = makeDgmoError(line, msg);
413
+ result.diagnostics.push(diag);
414
+ if (!result.error) result.error = formatDgmoError(diag);
415
+ });
412
416
 
413
417
  return result;
414
418
  }