@diagrammo/dgmo 0.15.1 → 0.17.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 +9 -9
- package/dist/advanced.cjs +612 -734
- package/dist/advanced.d.cts +42 -36
- package/dist/advanced.d.ts +42 -36
- package/dist/advanced.js +612 -733
- package/dist/auto.cjs +508 -620
- package/dist/auto.js +105 -105
- package/dist/auto.mjs +508 -620
- package/dist/cli.cjs +144 -144
- package/dist/editor.cjs +8 -9
- package/dist/editor.js +8 -9
- package/dist/highlight.cjs +8 -9
- package/dist/highlight.js +8 -9
- package/dist/index.cjs +497 -608
- package/dist/index.js +497 -608
- package/dist/internal.cjs +612 -734
- package/dist/internal.d.cts +42 -36
- package/dist/internal.d.ts +42 -36
- package/dist/internal.js +612 -733
- package/dist/pert.d.cts +2 -2
- package/dist/pert.d.ts +2 -2
- package/docs/language-reference.md +97 -84
- package/docs/migration-sequence-color-to-tags.md +1 -1
- package/gallery/fixtures/area.dgmo +3 -3
- package/gallery/fixtures/bar-stacked.dgmo +5 -5
- package/gallery/fixtures/boxes-and-lines.dgmo +2 -2
- package/gallery/fixtures/c4-full.dgmo +8 -8
- package/gallery/fixtures/class-full.dgmo +2 -2
- package/gallery/fixtures/doughnut.dgmo +6 -6
- package/gallery/fixtures/flowchart-colors.dgmo +3 -3
- package/gallery/fixtures/function.dgmo +3 -3
- package/gallery/fixtures/gantt-full.dgmo +9 -9
- package/gallery/fixtures/gantt.dgmo +7 -7
- package/gallery/fixtures/infra-full.dgmo +6 -6
- package/gallery/fixtures/infra.dgmo +2 -2
- package/gallery/fixtures/kanban.dgmo +9 -9
- package/gallery/fixtures/line.dgmo +2 -2
- package/gallery/fixtures/multi-line.dgmo +3 -3
- package/gallery/fixtures/org-full.dgmo +6 -6
- package/gallery/fixtures/quadrant.dgmo +2 -2
- package/gallery/fixtures/sankey.dgmo +9 -9
- package/gallery/fixtures/scatter.dgmo +3 -3
- package/gallery/fixtures/sequence-tags-protocols.dgmo +11 -11
- package/gallery/fixtures/sequence-tags.dgmo +10 -10
- package/gallery/fixtures/sequence.dgmo +4 -4
- package/gallery/fixtures/sitemap-full.dgmo +7 -7
- package/gallery/fixtures/slope.dgmo +5 -5
- package/gallery/fixtures/spr-eras.dgmo +9 -9
- package/gallery/fixtures/timeline.dgmo +3 -3
- package/gallery/fixtures/venn.dgmo +3 -3
- package/package.json +7 -3
- package/src/advanced.ts +0 -1
- package/src/auto/index.ts +2 -2
- package/src/boxes-and-lines/layout.ts +1 -2
- package/src/boxes-and-lines/renderer.ts +5 -1
- package/src/c4/parser.ts +2 -2
- package/src/c4/renderer.ts +15 -8
- package/src/chart.ts +18 -9
- package/src/class/parser.ts +8 -7
- package/src/class/renderer.ts +17 -6
- package/src/cli.ts +8 -8
- package/src/completion.ts +14 -17
- package/src/cycle/parser.ts +15 -1
- package/src/cycle/renderer.ts +6 -3
- package/src/d3.ts +88 -49
- package/src/diagnostics.ts +20 -0
- package/src/echarts.ts +28 -11
- package/src/editor/dgmo.grammar +1 -3
- package/src/editor/dgmo.grammar.d.ts +1 -1
- package/src/editor/dgmo.grammar.js +8 -8
- package/src/editor/dgmo.grammar.terms.js +11 -12
- package/src/editor/highlight-api.ts +0 -1
- package/src/editor/highlight.ts +0 -1
- package/src/er/parser.ts +19 -12
- package/src/er/renderer.ts +19 -7
- package/src/gantt/parser.ts +1 -1
- package/src/gantt/renderer.ts +7 -4
- package/src/graph/flowchart-parser.ts +18 -84
- package/src/graph/flowchart-renderer.ts +6 -8
- package/src/graph/layout.ts +0 -2
- package/src/graph/state-parser.ts +17 -62
- package/src/graph/state-renderer.ts +3 -8
- package/src/infra/parser.ts +21 -11
- package/src/infra/renderer.ts +8 -6
- package/src/journey-map/parser.ts +11 -4
- package/src/journey-map/renderer.ts +3 -1
- package/src/kanban/parser.ts +11 -7
- package/src/kanban/renderer.ts +3 -1
- package/src/mindmap/parser.ts +4 -5
- package/src/mindmap/renderer.ts +2 -1
- package/src/org/parser.ts +3 -3
- package/src/org/renderer.ts +4 -3
- package/src/pert/analyzer.ts +10 -10
- package/src/pert/layout.ts +1 -1
- package/src/pert/parser.ts +8 -8
- package/src/pert/renderer.ts +7 -2
- package/src/pert/types.ts +1 -1
- package/src/pyramid/parser.ts +13 -1
- package/src/raci/parser.ts +42 -12
- package/src/raci/renderer.ts +2 -1
- package/src/raci/types.ts +4 -3
- package/src/ring/parser.ts +13 -1
- package/src/sequence/parser.ts +81 -23
- package/src/sequence/participant-inference.ts +18 -181
- package/src/sequence/renderer.ts +48 -137
- package/src/sitemap/layout.ts +0 -2
- package/src/sitemap/parser.ts +12 -38
- package/src/sitemap/renderer.ts +13 -13
- package/src/sitemap/types.ts +0 -1
- package/src/tech-radar/parser.ts +2 -2
- package/src/tech-radar/renderer.ts +5 -3
- package/src/tech-radar/types.ts +2 -0
- package/src/utils/arrows.ts +3 -28
- package/src/utils/extract-alias.ts +1 -1
- package/src/utils/inline-markdown.ts +1 -1
- package/src/utils/legend-d3.ts +12 -6
- package/src/utils/legend-layout.ts +1 -1
- package/src/utils/legend-types.ts +1 -1
- package/src/utils/parsing.ts +64 -35
- package/src/utils/tag-groups.ts +98 -18
- package/src/utils/time-ticks.ts +1 -1
- package/src/wireframe/parser.ts +3 -3
package/src/infra/parser.ts
CHANGED
|
@@ -14,7 +14,6 @@ import {
|
|
|
14
14
|
nameMergedMessage,
|
|
15
15
|
} from '../diagnostics';
|
|
16
16
|
import { tryStripDescriptionKeyword } from '../utils/description-helpers';
|
|
17
|
-
import { resolveColorWithDiagnostic } from '../colors';
|
|
18
17
|
import { parseInArrowLabel } from '../utils/arrows';
|
|
19
18
|
import {
|
|
20
19
|
measureIndent,
|
|
@@ -22,6 +21,7 @@ import {
|
|
|
22
21
|
OPTION_NOCOLON_RE,
|
|
23
22
|
tryParseSharedOption,
|
|
24
23
|
} from '../utils/parsing';
|
|
24
|
+
import { isRecognizedColorName } from '../colors';
|
|
25
25
|
import { normalizeName, displayName } from '../utils/name-normalize';
|
|
26
26
|
import {
|
|
27
27
|
matchTagBlockHeading,
|
|
@@ -62,9 +62,11 @@ const DEPRECATED_FANOUT_RE = /\bx(\d+)\s*$/;
|
|
|
62
62
|
const GROUP_RE =
|
|
63
63
|
/^\[([^\]]+)\]\s*(?:as\s+([A-Za-z][A-Za-z0-9_]{0,11})\s*)?(?:\|\s*(.+))?$/;
|
|
64
64
|
|
|
65
|
-
// Tag value: Name
|
|
66
|
-
//
|
|
67
|
-
|
|
65
|
+
// Tag value: `Name` or `Name color` (trailing-token color form). Color is
|
|
66
|
+
// extracted via the shared `extractColor` helper at use-site (see
|
|
67
|
+
// `dgmo/src/utils/parsing.ts:extractColor`), not via this regex. This regex
|
|
68
|
+
// just confirms the line shape is a valid tag value (no reserved sigils).
|
|
69
|
+
const TAG_VALUE_RE = /^(\w[\w\s]+?)\s*$/;
|
|
68
70
|
|
|
69
71
|
// Component line. Accepts either a quoted name ("name with | : reserved chars")
|
|
70
72
|
// or a bare name (multi-word allowed; must start with letter/underscore so digit-
|
|
@@ -445,14 +447,22 @@ export function parseInfra(content: string): ParsedInfra {
|
|
|
445
447
|
// Tag value inside tag group — first value is the default unless another is marked `default`
|
|
446
448
|
if (currentTagGroup && indent > 0) {
|
|
447
449
|
const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
450
|
+
// Trailing-token color (universal rule, §1.5): peel off a lowercase
|
|
451
|
+
// recognized color word from the end of the line. Downstream stores
|
|
452
|
+
// the raw color NAME (not the palette hex) so the renderer can resolve
|
|
453
|
+
// against whichever theme/palette is active at render time.
|
|
454
|
+
const lastSpaceIdx = cleanEntry.lastIndexOf(' ');
|
|
455
|
+
let valueName = cleanEntry;
|
|
456
|
+
let rawColor: string | undefined;
|
|
457
|
+
if (lastSpaceIdx > 0) {
|
|
458
|
+
const trailing = cleanEntry.substring(lastSpaceIdx + 1);
|
|
459
|
+
if (isRecognizedColorName(trailing)) {
|
|
460
|
+
rawColor = trailing;
|
|
461
|
+
valueName = cleanEntry.substring(0, lastSpaceIdx).trimEnd();
|
|
455
462
|
}
|
|
463
|
+
}
|
|
464
|
+
const tvMatch = valueName.match(TAG_VALUE_RE);
|
|
465
|
+
if (tvMatch || /^\w+$/.test(valueName)) {
|
|
456
466
|
currentTagGroup.values.push({
|
|
457
467
|
name: valueName,
|
|
458
468
|
color: rawColor,
|
package/src/infra/renderer.ts
CHANGED
|
@@ -1733,8 +1733,7 @@ function renderNodes(
|
|
|
1733
1733
|
}
|
|
1734
1734
|
|
|
1735
1735
|
// Role badge dots — only shown when Capabilities legend is expanded
|
|
1736
|
-
const showDots =
|
|
1737
|
-
activeGroup != null && activeGroup.toLowerCase() === 'capabilities';
|
|
1736
|
+
const showDots = activeGroup?.toLowerCase() === 'capabilities';
|
|
1738
1737
|
const roles = showDots && !node.isEdge ? inferRoles(node.properties) : [];
|
|
1739
1738
|
if (roles.length > 0) {
|
|
1740
1739
|
// Move dots up above the collapse bar for collapsed groups
|
|
@@ -2024,7 +2023,8 @@ function renderLegend(
|
|
|
2024
2023
|
palette: PaletteColors,
|
|
2025
2024
|
isDark: boolean,
|
|
2026
2025
|
activeGroup: string | null,
|
|
2027
|
-
playback?: InfraPlaybackState
|
|
2026
|
+
playback?: InfraPlaybackState,
|
|
2027
|
+
exportMode = false
|
|
2028
2028
|
) {
|
|
2029
2029
|
if (legendGroups.length === 0 && !playback) return;
|
|
2030
2030
|
|
|
@@ -2049,7 +2049,7 @@ function renderLegend(
|
|
|
2049
2049
|
const legendConfig: LegendConfig = {
|
|
2050
2050
|
groups: allGroups,
|
|
2051
2051
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
2052
|
-
mode: '
|
|
2052
|
+
mode: exportMode ? 'export' : 'preview',
|
|
2053
2053
|
showEmptyGroups: true,
|
|
2054
2054
|
};
|
|
2055
2055
|
const legendState: LegendState = { activeGroup };
|
|
@@ -2394,7 +2394,8 @@ export function renderInfra(
|
|
|
2394
2394
|
palette,
|
|
2395
2395
|
isDark,
|
|
2396
2396
|
activeGroup ?? null,
|
|
2397
|
-
playback ?? undefined
|
|
2397
|
+
playback ?? undefined,
|
|
2398
|
+
exportMode
|
|
2398
2399
|
);
|
|
2399
2400
|
// Re-enable pointer events on interactive legend elements
|
|
2400
2401
|
legendSvg
|
|
@@ -2410,7 +2411,8 @@ export function renderInfra(
|
|
|
2410
2411
|
palette,
|
|
2411
2412
|
isDark,
|
|
2412
2413
|
activeGroup ?? null,
|
|
2413
|
-
playback ?? undefined
|
|
2414
|
+
playback ?? undefined,
|
|
2415
|
+
exportMode
|
|
2414
2416
|
);
|
|
2415
2417
|
}
|
|
2416
2418
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { PaletteColors } from '../palettes';
|
|
2
|
+
import { resolveColorWithDiagnostic } from '../colors';
|
|
2
3
|
import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
|
|
3
4
|
import {
|
|
4
5
|
matchTagBlockHeading,
|
|
@@ -63,7 +64,7 @@ export function parseJourneyMap(
|
|
|
63
64
|
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
64
65
|
};
|
|
65
66
|
|
|
66
|
-
if (!content
|
|
67
|
+
if (!content?.trim()) {
|
|
67
68
|
return fail(0, 'No content provided');
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -138,8 +139,14 @@ export function parseJourneyMap(
|
|
|
138
139
|
const key = part.substring(0, colonIdx).trim().toLowerCase();
|
|
139
140
|
const value = part.substring(colonIdx + 1).trim();
|
|
140
141
|
if (key === 'color') {
|
|
141
|
-
|
|
142
|
-
personaColor =
|
|
142
|
+
// Resolve the color name directly (no synthetic parens wrap).
|
|
143
|
+
personaColor =
|
|
144
|
+
resolveColorWithDiagnostic(
|
|
145
|
+
value,
|
|
146
|
+
lineNumber,
|
|
147
|
+
result.diagnostics,
|
|
148
|
+
palette
|
|
149
|
+
) ?? undefined;
|
|
143
150
|
}
|
|
144
151
|
}
|
|
145
152
|
}
|
|
@@ -209,7 +216,7 @@ export function parseJourneyMap(
|
|
|
209
216
|
if (!color) {
|
|
210
217
|
warn(
|
|
211
218
|
lineNumber,
|
|
212
|
-
`Expected 'Value
|
|
219
|
+
`Expected 'Value color' in tag group '${currentTagGroup.name}'`
|
|
213
220
|
);
|
|
214
221
|
continue;
|
|
215
222
|
}
|
|
@@ -36,6 +36,7 @@ export interface JourneyMapInteractiveOptions {
|
|
|
36
36
|
collapsedPhases?: Set<string>;
|
|
37
37
|
/** Called when a phase is toggled */
|
|
38
38
|
onPhaseToggle?: (phaseName: string) => void;
|
|
39
|
+
exportMode?: boolean;
|
|
39
40
|
}
|
|
40
41
|
|
|
41
42
|
// ============================================================
|
|
@@ -313,7 +314,7 @@ export function renderJourneyMap(
|
|
|
313
314
|
titleRelation: 'inline-with-title',
|
|
314
315
|
},
|
|
315
316
|
titleWidth: 0,
|
|
316
|
-
mode:
|
|
317
|
+
mode: options?.exportMode ? 'export' : 'preview',
|
|
317
318
|
};
|
|
318
319
|
|
|
319
320
|
const legendState: LegendState = { activeGroup: effectiveActiveGroup };
|
|
@@ -1559,6 +1560,7 @@ export function renderJourneyMapForExport(
|
|
|
1559
1560
|
const container = document.createElement('div');
|
|
1560
1561
|
renderJourneyMap(container, parsed, palette, isDark, {
|
|
1561
1562
|
exportDims: { width: layout.totalWidth, height: layout.totalHeight },
|
|
1563
|
+
exportMode: true,
|
|
1562
1564
|
});
|
|
1563
1565
|
|
|
1564
1566
|
const svgEl = container.querySelector('svg');
|
package/src/kanban/parser.ts
CHANGED
|
@@ -26,10 +26,11 @@ import type {
|
|
|
26
26
|
// Regex patterns
|
|
27
27
|
// ============================================================
|
|
28
28
|
|
|
29
|
-
// [Column Name], [Column Name]
|
|
29
|
+
// [Column Name], [Column Name] color, [Column Name] as <alias>, [Column Name] | wip: 3, etc.
|
|
30
|
+
// Universal §1.5 trailing-token: color is a bare token after `]`.
|
|
30
31
|
// Captures: [1]=label [2]=color [3]=alias (TD-18) [4]=pipe meta
|
|
31
32
|
const COLUMN_RE =
|
|
32
|
-
/^\[(.+?)\](?:\s
|
|
33
|
+
/^\[(.+?)\](?:\s+(\S+))?(?:\s+as\s+([A-Za-z][A-Za-z0-9_]{0,11}))?\s*(?:\|\s*(.+))?$/;
|
|
33
34
|
// Legacy delimiter
|
|
34
35
|
const LEGACY_COLUMN_RE = /^==\s+(.+?)\s*(?:\[wip:\s*(\d+)\])?\s*==$/;
|
|
35
36
|
|
|
@@ -70,7 +71,7 @@ export function parseKanban(
|
|
|
70
71
|
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
71
72
|
};
|
|
72
73
|
|
|
73
|
-
if (!content
|
|
74
|
+
if (!content?.trim()) {
|
|
74
75
|
return fail(0, 'No content provided');
|
|
75
76
|
}
|
|
76
77
|
|
|
@@ -180,7 +181,7 @@ export function parseKanban(
|
|
|
180
181
|
}
|
|
181
182
|
}
|
|
182
183
|
|
|
183
|
-
// Tag group entries (indented Value
|
|
184
|
+
// Tag group entries (indented Value color under tag heading)
|
|
184
185
|
// First entry is the default unless another is marked `default`
|
|
185
186
|
if (currentTagGroup && !contentStarted) {
|
|
186
187
|
const indent = measureIndent(line);
|
|
@@ -190,7 +191,7 @@ export function parseKanban(
|
|
|
190
191
|
if (!color) {
|
|
191
192
|
warn(
|
|
192
193
|
lineNumber,
|
|
193
|
-
`Expected 'Value
|
|
194
|
+
`Expected 'Value color' in tag group '${currentTagGroup.name}'`
|
|
194
195
|
);
|
|
195
196
|
continue;
|
|
196
197
|
}
|
|
@@ -247,9 +248,12 @@ export function parseKanban(
|
|
|
247
248
|
|
|
248
249
|
columnCounter++;
|
|
249
250
|
const colName = columnMatch[1].trim();
|
|
250
|
-
|
|
251
|
+
// Trailing token after `]` must be a recognized color word (§1.5).
|
|
252
|
+
// If it isn't, the line is malformed — emit the standard diagnostic.
|
|
253
|
+
const rawTrailing = columnMatch[2]?.trim();
|
|
254
|
+
const colColor = rawTrailing
|
|
251
255
|
? resolveColorWithDiagnostic(
|
|
252
|
-
|
|
256
|
+
rawTrailing,
|
|
253
257
|
lineNumber,
|
|
254
258
|
result.diagnostics,
|
|
255
259
|
palette
|
package/src/kanban/renderer.ts
CHANGED
|
@@ -37,6 +37,7 @@ interface KanbanInteractiveOptions {
|
|
|
37
37
|
collapsedLanes?: Set<string>;
|
|
38
38
|
collapsedColumns?: Set<string>;
|
|
39
39
|
compactMeta?: boolean;
|
|
40
|
+
exportMode?: boolean;
|
|
40
41
|
}
|
|
41
42
|
|
|
42
43
|
// ============================================================
|
|
@@ -330,7 +331,7 @@ export function renderKanban(
|
|
|
330
331
|
const legendConfig: LegendConfig = {
|
|
331
332
|
groups: parsed.tagGroups,
|
|
332
333
|
position: { placement: 'top-center', titleRelation: 'inline-with-title' },
|
|
333
|
-
mode:
|
|
334
|
+
mode: options?.exportMode ? 'export' : 'preview',
|
|
334
335
|
};
|
|
335
336
|
const legendState: LegendState = { activeGroup: activeTagGroup ?? null };
|
|
336
337
|
const legendG = svg
|
|
@@ -682,6 +683,7 @@ export function renderKanbanForExport(
|
|
|
682
683
|
const container = document.createElement('div');
|
|
683
684
|
renderKanban(container, parsed, palette, isDark, {
|
|
684
685
|
exportDims: { width: layout.totalWidth, height: layout.totalHeight },
|
|
686
|
+
exportMode: true,
|
|
685
687
|
});
|
|
686
688
|
|
|
687
689
|
const svgEl = container.querySelector('svg');
|
package/src/mindmap/parser.ts
CHANGED
|
@@ -62,7 +62,7 @@ export function parseMindmap(
|
|
|
62
62
|
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
63
63
|
};
|
|
64
64
|
|
|
65
|
-
if (!content
|
|
65
|
+
if (!content?.trim()) {
|
|
66
66
|
return fail(0, 'No content provided');
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -179,7 +179,7 @@ export function parseMindmap(
|
|
|
179
179
|
}
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
// Tag group entries (indented Value
|
|
182
|
+
// Tag group entries (indented Value color under tag heading)
|
|
183
183
|
if (currentTagGroup && !contentStarted) {
|
|
184
184
|
const indent = measureIndent(line);
|
|
185
185
|
if (indent > 0) {
|
|
@@ -188,7 +188,7 @@ export function parseMindmap(
|
|
|
188
188
|
if (!color) {
|
|
189
189
|
pushError(
|
|
190
190
|
lineNumber,
|
|
191
|
-
`Expected 'Value
|
|
191
|
+
`Expected 'Value color' in tag group '${currentTagGroup.name}'`
|
|
192
192
|
);
|
|
193
193
|
continue;
|
|
194
194
|
}
|
|
@@ -278,8 +278,7 @@ export function parseMindmap(
|
|
|
278
278
|
result.diagnostics.push(diag);
|
|
279
279
|
result.error = formatDgmoError(diag);
|
|
280
280
|
} else if (
|
|
281
|
-
titleRoot &&
|
|
282
|
-
titleRoot.children.length === 0 &&
|
|
281
|
+
titleRoot?.children.length === 0 &&
|
|
283
282
|
result.roots.length === 1 &&
|
|
284
283
|
!result.error
|
|
285
284
|
) {
|
package/src/mindmap/renderer.ts
CHANGED
|
@@ -95,6 +95,7 @@ export function renderMindmap(
|
|
|
95
95
|
onToggleDescriptions?: (active: boolean) => void;
|
|
96
96
|
controlsExpanded?: boolean;
|
|
97
97
|
onToggleControlsExpand?: () => void;
|
|
98
|
+
exportMode?: boolean;
|
|
98
99
|
}
|
|
99
100
|
): void {
|
|
100
101
|
const isExport = !!exportDims;
|
|
@@ -234,7 +235,7 @@ export function renderMindmap(
|
|
|
234
235
|
};
|
|
235
236
|
}),
|
|
236
237
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
237
|
-
mode: '
|
|
238
|
+
mode: options?.exportMode ? 'export' : 'preview',
|
|
238
239
|
controlsGroup: controlsToggles,
|
|
239
240
|
};
|
|
240
241
|
const legendState: LegendState = {
|
package/src/org/parser.ts
CHANGED
|
@@ -115,7 +115,7 @@ export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
|
|
|
115
115
|
result.diagnostics.push(makeDgmoError(line, message, 'warning'));
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
-
if (!content
|
|
118
|
+
if (!content?.trim()) {
|
|
119
119
|
return fail(0, 'No content provided');
|
|
120
120
|
}
|
|
121
121
|
|
|
@@ -235,7 +235,7 @@ export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
|
|
|
235
235
|
}
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
// Tag group entries (indented Value
|
|
238
|
+
// Tag group entries (indented Value color under tag heading)
|
|
239
239
|
// First entry is the default unless another is marked `default`
|
|
240
240
|
if (currentTagGroup && !contentStarted) {
|
|
241
241
|
const indent = measureIndent(line);
|
|
@@ -245,7 +245,7 @@ export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
|
|
|
245
245
|
if (!color) {
|
|
246
246
|
pushError(
|
|
247
247
|
lineNumber,
|
|
248
|
-
`Expected 'Value
|
|
248
|
+
`Expected 'Value color' in tag group '${currentTagGroup.name}'`
|
|
249
249
|
);
|
|
250
250
|
continue;
|
|
251
251
|
}
|
package/src/org/renderer.ts
CHANGED
|
@@ -110,7 +110,8 @@ export function renderOrg(
|
|
|
110
110
|
exportDims?: { width?: number; height?: number },
|
|
111
111
|
activeTagGroup?: string | null,
|
|
112
112
|
hiddenAttributes?: Set<string>,
|
|
113
|
-
ancestorPath?: AncestorInfo[]
|
|
113
|
+
ancestorPath?: AncestorInfo[],
|
|
114
|
+
exportMode?: boolean
|
|
114
115
|
): void {
|
|
115
116
|
// Clear existing content
|
|
116
117
|
d3Selection.select(container).selectAll(':not([data-d3-tooltip])').remove();
|
|
@@ -759,7 +760,7 @@ export function renderOrg(
|
|
|
759
760
|
},
|
|
760
761
|
],
|
|
761
762
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
762
|
-
mode: '
|
|
763
|
+
mode: exportMode ? 'export' : 'preview',
|
|
763
764
|
};
|
|
764
765
|
const singleState: LegendState = { activeGroup: lg.name };
|
|
765
766
|
const groupG = legendParentBase
|
|
@@ -783,7 +784,7 @@ export function renderOrg(
|
|
|
783
784
|
const legendConfig: LegendConfig = {
|
|
784
785
|
groups,
|
|
785
786
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
786
|
-
mode: '
|
|
787
|
+
mode: exportMode ? 'export' : 'preview',
|
|
787
788
|
capsulePillAddonWidth: eyeAddonWidth,
|
|
788
789
|
};
|
|
789
790
|
const legendState: LegendState = { activeGroup: activeTagGroup ?? null };
|
package/src/pert/analyzer.ts
CHANGED
|
@@ -180,7 +180,7 @@ export function analyzePert(parsed: ParsedPert): ResolvedPert {
|
|
|
180
180
|
for (const e of edges) {
|
|
181
181
|
if (!e.lag || e.lag.amount >= 0) continue;
|
|
182
182
|
const src = activities.find((a) => a.id === e.source);
|
|
183
|
-
if (!src
|
|
183
|
+
if (!src?.duration) continue;
|
|
184
184
|
const leadDays = -toDays(e.lag, sprintDays);
|
|
185
185
|
const srcDurDays = toDays(src.duration.m, sprintDays);
|
|
186
186
|
if (e.type === 'FS' && leadDays > srcDurDays) {
|
|
@@ -923,7 +923,7 @@ export function buildSummary(input: BuildSummaryInput): CaptionRow[] | null {
|
|
|
923
923
|
// Expected duration AND each percentile latest-safe start so the
|
|
924
924
|
// caption shape stays parallel to the feasible case (one top row +
|
|
925
925
|
// three percentile sub-rows).
|
|
926
|
-
if (anchor
|
|
926
|
+
if (anchor?.kind === 'backward') {
|
|
927
927
|
return [
|
|
928
928
|
{ text: 'Expected duration: ?', level: 0 },
|
|
929
929
|
{ text: 'P50 latest-safe start: ?', level: 0 },
|
|
@@ -958,13 +958,13 @@ export function buildSummary(input: BuildSummaryInput): CaptionRow[] | null {
|
|
|
958
958
|
const sigmaParen = showMcDetail
|
|
959
959
|
? ` (± ${roundForCaption(projectSigma!)} ${pluralizeUnit(projectSigma!, unit)})`
|
|
960
960
|
: '';
|
|
961
|
-
if (anchor
|
|
961
|
+
if (anchor?.kind === 'forward') {
|
|
962
962
|
const projectMuDays = projectMu * unitToDays(unit);
|
|
963
963
|
rows.push({
|
|
964
964
|
text: `Expected finish: ${addCalendarDays(anchor.date, projectMuDays)}${sigmaParen}.`,
|
|
965
965
|
level: 0,
|
|
966
966
|
});
|
|
967
|
-
} else if (anchor
|
|
967
|
+
} else if (anchor?.kind === 'backward') {
|
|
968
968
|
const projectMuDays = projectMu * unitToDays(unit);
|
|
969
969
|
rows.push({
|
|
970
970
|
text: `Expected start: ${addCalendarDays(anchor.date, -projectMuDays)}${sigmaParen}.`,
|
|
@@ -990,13 +990,13 @@ export function buildSummary(input: BuildSummaryInput): CaptionRow[] | null {
|
|
|
990
990
|
{ pct: 80, days: monteCarloResult!.p80 },
|
|
991
991
|
{ pct: 95, days: monteCarloResult!.p95 },
|
|
992
992
|
];
|
|
993
|
-
if (anchor
|
|
993
|
+
if (anchor?.kind === 'forward') {
|
|
994
994
|
for (const { pct, days } of percentiles) {
|
|
995
995
|
const offsetDays = roundConservative(days, 'forward');
|
|
996
996
|
const date = addCalendarDays(anchor.date, offsetDays);
|
|
997
997
|
rows.push({ text: `P${pct} finish: ${date}.`, level: 1 });
|
|
998
998
|
}
|
|
999
|
-
} else if (anchor
|
|
999
|
+
} else if (anchor?.kind === 'backward') {
|
|
1000
1000
|
for (const { pct, days } of percentiles) {
|
|
1001
1001
|
const offsetDays = roundConservative(days, 'backward');
|
|
1002
1002
|
const date = addCalendarDays(anchor.date, -offsetDays);
|
|
@@ -1074,10 +1074,10 @@ export function buildProjectSubtitle(input: {
|
|
|
1074
1074
|
|
|
1075
1075
|
if (projectMu === null) {
|
|
1076
1076
|
// Anchored + TBD: keep the framing prefix, mark the math as ?.
|
|
1077
|
-
if (anchor
|
|
1077
|
+
if (anchor?.kind === 'forward') {
|
|
1078
1078
|
return `Expected finish: ? · ≈ ? ${pluralizeUnit(2, unit)} of work`;
|
|
1079
1079
|
}
|
|
1080
|
-
if (anchor
|
|
1080
|
+
if (anchor?.kind === 'backward') {
|
|
1081
1081
|
return `Expected start: ? · ≈ ? ${pluralizeUnit(2, unit)} lead time`;
|
|
1082
1082
|
}
|
|
1083
1083
|
// Unanchored + TBD: surface that the total is unknown. The per-node
|
|
@@ -1087,11 +1087,11 @@ export function buildProjectSubtitle(input: {
|
|
|
1087
1087
|
|
|
1088
1088
|
const muStr = `${roundForCaption(projectMu)} ${pluralizeUnit(projectMu, unit)}`;
|
|
1089
1089
|
|
|
1090
|
-
if (anchor
|
|
1090
|
+
if (anchor?.kind === 'forward') {
|
|
1091
1091
|
const projectMuDays = projectMu * unitToDays(unit);
|
|
1092
1092
|
return `Expected finish: ${addCalendarDays(anchor.date, projectMuDays)} · ≈ ${muStr} of work${sigmaParen}`;
|
|
1093
1093
|
}
|
|
1094
|
-
if (anchor
|
|
1094
|
+
if (anchor?.kind === 'backward') {
|
|
1095
1095
|
const projectMuDays = projectMu * unitToDays(unit);
|
|
1096
1096
|
return `Expected start: ${addCalendarDays(anchor.date, -projectMuDays)} · ≈ ${muStr} lead time${sigmaParen}`;
|
|
1097
1097
|
}
|
package/src/pert/layout.ts
CHANGED
|
@@ -208,7 +208,7 @@ function nodeDimensions(
|
|
|
208
208
|
sizing: NodeSizing,
|
|
209
209
|
overrides?: LayoutOverrides
|
|
210
210
|
): { width: number; height: number } {
|
|
211
|
-
if (overrides
|
|
211
|
+
if (overrides?.[id]) {
|
|
212
212
|
return { width: overrides[id].width, height: overrides[id].height };
|
|
213
213
|
}
|
|
214
214
|
const r = resolved.activities.find((a) => a.activity.id === id);
|
package/src/pert/parser.ts
CHANGED
|
@@ -448,7 +448,7 @@ export interface ParsePertOptions {
|
|
|
448
448
|
now?: Date;
|
|
449
449
|
/**
|
|
450
450
|
* Active palette — used when resolving color names on `tag` entries
|
|
451
|
-
* (e.g. `High
|
|
451
|
+
* (e.g. `High red` → palette.colors.red). Optional; when omitted the
|
|
452
452
|
* universal default color map is used.
|
|
453
453
|
*/
|
|
454
454
|
palette?: PaletteColors;
|
|
@@ -503,7 +503,7 @@ export function parsePert(
|
|
|
503
503
|
|
|
504
504
|
/**
|
|
505
505
|
* Tag groups declared at the top of the diagram. A `tag …` heading
|
|
506
|
-
* opens a block; entries (indented `Value
|
|
506
|
+
* opens a block; entries (indented `Value color` lines) accumulate
|
|
507
507
|
* until the first non-tag content line closes it.
|
|
508
508
|
*/
|
|
509
509
|
const tagGroups: TagGroup[] = [];
|
|
@@ -576,7 +576,7 @@ export function parsePert(
|
|
|
576
576
|
// layer is responsible for routing.
|
|
577
577
|
}
|
|
578
578
|
|
|
579
|
-
// ── Tag-block phase. `tag Priority as p\n High
|
|
579
|
+
// ── Tag-block phase. `tag Priority as p\n High red\n Low green`
|
|
580
580
|
// lives BEFORE diagram content; once any group / activity / arrow
|
|
581
581
|
// is seen, `contentStarted` flips and further `tag …` headings
|
|
582
582
|
// emit an error.
|
|
@@ -599,7 +599,7 @@ export function parsePert(
|
|
|
599
599
|
);
|
|
600
600
|
}
|
|
601
601
|
tagGroups.push(currentTagGroup);
|
|
602
|
-
// Inline values (e.g. `tag Priority as p Low
|
|
602
|
+
// Inline values (e.g. `tag Priority as p Low green, High red`).
|
|
603
603
|
if (tagBlockMatch.inlineValues) {
|
|
604
604
|
for (const raw of tagBlockMatch.inlineValues) {
|
|
605
605
|
const { text, isDefault } = stripDefaultModifier(raw);
|
|
@@ -612,7 +612,7 @@ export function parsePert(
|
|
|
612
612
|
if (!color) {
|
|
613
613
|
warn(
|
|
614
614
|
lineNumber,
|
|
615
|
-
`Expected 'Value
|
|
615
|
+
`Expected 'Value color' in tag group '${currentTagGroup.name}'`
|
|
616
616
|
);
|
|
617
617
|
continue;
|
|
618
618
|
}
|
|
@@ -624,7 +624,7 @@ export function parsePert(
|
|
|
624
624
|
}
|
|
625
625
|
continue;
|
|
626
626
|
}
|
|
627
|
-
// Indented `Value
|
|
627
|
+
// Indented `Value color` entry under an open tag block.
|
|
628
628
|
if (currentTagGroup && indent > 0) {
|
|
629
629
|
const { text, isDefault } = stripDefaultModifier(trimmed);
|
|
630
630
|
const { label, color } = extractColor(
|
|
@@ -636,7 +636,7 @@ export function parsePert(
|
|
|
636
636
|
if (!color) {
|
|
637
637
|
warn(
|
|
638
638
|
lineNumber,
|
|
639
|
-
`Expected 'Value
|
|
639
|
+
`Expected 'Value color' in tag group '${currentTagGroup.name}'`
|
|
640
640
|
);
|
|
641
641
|
continue;
|
|
642
642
|
}
|
|
@@ -823,7 +823,7 @@ export function parsePert(
|
|
|
823
823
|
const head = trimmed.slice(0, firstSpace).toLowerCase();
|
|
824
824
|
const value = trimmed.slice(firstSpace + 1).trim();
|
|
825
825
|
const hint = NEAR_DIRECTIVE_HINTS.find((h) => h.stem === head);
|
|
826
|
-
if (hint
|
|
826
|
+
if (hint?.matches.test(value)) {
|
|
827
827
|
error(
|
|
828
828
|
lineNumber,
|
|
829
829
|
`Unknown directive '${head}'. Did you mean '${hint.canonical}'?`,
|
package/src/pert/renderer.ts
CHANGED
|
@@ -388,6 +388,8 @@ export interface PertRenderOptions {
|
|
|
388
388
|
* through to the parsed `active-tag` directive.
|
|
389
389
|
*/
|
|
390
390
|
activeTagOverride?: string | null;
|
|
391
|
+
/** True when rendering for export — strips collapsed pills and cog from legend. */
|
|
392
|
+
exportMode?: boolean;
|
|
391
393
|
}
|
|
392
394
|
|
|
393
395
|
export function renderPert(
|
|
@@ -568,6 +570,7 @@ export function renderPert(
|
|
|
568
570
|
y: tagLegendY,
|
|
569
571
|
width: exportWidth,
|
|
570
572
|
activeGroup: tagLegendActive,
|
|
573
|
+
exportMode: options.exportMode,
|
|
571
574
|
});
|
|
572
575
|
}
|
|
573
576
|
|
|
@@ -681,6 +684,7 @@ export function renderPertForExport(
|
|
|
681
684
|
title: hasTitle ? parsed.title : null,
|
|
682
685
|
subtitle: resolved.projectSubtitle,
|
|
683
686
|
exportDims: { width: exportWidth, height: exportHeight },
|
|
687
|
+
exportMode: true,
|
|
684
688
|
});
|
|
685
689
|
const svgEl = container.querySelector('svg');
|
|
686
690
|
if (!svgEl) return '';
|
|
@@ -2990,6 +2994,7 @@ interface TagLegendArgs {
|
|
|
2990
2994
|
y: number;
|
|
2991
2995
|
width: number;
|
|
2992
2996
|
activeGroup: string | null;
|
|
2997
|
+
exportMode?: boolean;
|
|
2993
2998
|
}
|
|
2994
2999
|
|
|
2995
3000
|
/**
|
|
@@ -3008,7 +3013,7 @@ function renderTagLegendRow(
|
|
|
3008
3013
|
): void {
|
|
3009
3014
|
if (resolved.tagGroups.length === 0) return;
|
|
3010
3015
|
|
|
3011
|
-
const { x, y, width, activeGroup } = args;
|
|
3016
|
+
const { x, y, width, activeGroup, exportMode } = args;
|
|
3012
3017
|
const groups = resolved.tagGroups.map((g) => ({
|
|
3013
3018
|
name: g.name,
|
|
3014
3019
|
entries: g.entries.map((e) => ({ value: e.value, color: e.color })),
|
|
@@ -3024,7 +3029,7 @@ function renderTagLegendRow(
|
|
|
3024
3029
|
{
|
|
3025
3030
|
groups,
|
|
3026
3031
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
3027
|
-
mode: '
|
|
3032
|
+
mode: exportMode ? 'export' : 'preview',
|
|
3028
3033
|
},
|
|
3029
3034
|
{ activeGroup },
|
|
3030
3035
|
palette,
|
package/src/pert/types.ts
CHANGED
|
@@ -215,7 +215,7 @@ export interface ParsedPert {
|
|
|
215
215
|
groups: PertGroup[];
|
|
216
216
|
/**
|
|
217
217
|
* Tag groups declared at the top of the diagram (`tag Priority as p
|
|
218
|
-
* High
|
|
218
|
+
* High red, Low green`). Drive node fill via `resolveTagColor()`.
|
|
219
219
|
* Empty when no `tag` blocks are declared.
|
|
220
220
|
*/
|
|
221
221
|
tagGroups: TagGroup[];
|
package/src/pyramid/parser.ts
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
measureIndent,
|
|
8
8
|
parseFirstLine,
|
|
9
9
|
parsePipeMetadata,
|
|
10
|
+
peelTrailingColorName,
|
|
10
11
|
tryParseSharedOption,
|
|
11
12
|
} from '../utils/parsing';
|
|
12
13
|
import type { ParsedPyramid, PyramidLayer } from './types';
|
|
@@ -88,7 +89,7 @@ export function parsePyramid(content: string): ParsedPyramid {
|
|
|
88
89
|
// ── First line: chart type declaration ──
|
|
89
90
|
if (!headerParsed) {
|
|
90
91
|
const firstLineResult = parseFirstLine(trimmed);
|
|
91
|
-
if (firstLineResult
|
|
92
|
+
if (firstLineResult?.chartType === 'pyramid') {
|
|
92
93
|
result.title = firstLineResult.title ?? '';
|
|
93
94
|
result.titleLineNumber = lineNum;
|
|
94
95
|
headerParsed = true;
|
|
@@ -146,6 +147,17 @@ export function parsePyramid(content: string): ParsedPyramid {
|
|
|
146
147
|
continue;
|
|
147
148
|
}
|
|
148
149
|
|
|
150
|
+
// Universal trailing-token shortcut: `Label color` is equivalent to
|
|
151
|
+
// `Label | color: <name>` when color is the only metadata key (§1.5).
|
|
152
|
+
if (!color) {
|
|
153
|
+
const { label: stripped, colorName: shortcutColor } =
|
|
154
|
+
peelTrailingColorName(label);
|
|
155
|
+
if (shortcutColor) {
|
|
156
|
+
color = shortcutColor;
|
|
157
|
+
label = stripped;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
149
161
|
currentLayer = {
|
|
150
162
|
label,
|
|
151
163
|
lineNumber: lineNum,
|