@diagrammo/dgmo 0.30.0 → 0.32.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/.cursorrules +4 -1
- package/.github/copilot-instructions.md +4 -1
- package/.windsurfrules +4 -1
- package/README.md +21 -3
- package/SKILL.md +4 -1
- package/dist/advanced.cjs +1853 -623
- package/dist/advanced.d.cts +143 -16
- package/dist/advanced.d.ts +143 -16
- package/dist/advanced.js +1846 -623
- package/dist/auto.cjs +1640 -581
- package/dist/auto.js +99 -99
- package/dist/auto.mjs +1640 -581
- package/dist/cli.cjs +148 -147
- package/dist/index.cjs +1643 -662
- package/dist/index.js +1643 -662
- package/docs/ai-integration.md +4 -1
- package/docs/language-reference.md +282 -27
- package/gallery/fixtures/boxes-and-lines.dgmo +2 -2
- package/gallery/fixtures/c4-full.dgmo +4 -5
- package/gallery/fixtures/c4.dgmo +2 -3
- package/package.json +7 -1
- package/src/advanced.ts +10 -0
- package/src/boxes-and-lines/focus.ts +257 -0
- package/src/boxes-and-lines/layout-search.ts +345 -65
- package/src/boxes-and-lines/layout.ts +11 -1
- package/src/boxes-and-lines/parser.ts +97 -4
- package/src/boxes-and-lines/renderer.ts +111 -8
- package/src/boxes-and-lines/types.ts +9 -0
- package/src/c4/parser.ts +8 -7
- package/src/c4/renderer.ts +7 -5
- package/src/chart-type-registry.ts +129 -4
- package/src/chart-types.ts +3 -3
- package/src/chart.ts +18 -1
- package/src/class/renderer.ts +4 -2
- package/src/cli-banner.ts +107 -0
- package/src/cli.ts +13 -0
- package/src/colors.ts +247 -2
- package/src/cycle/parser.ts +2 -7
- package/src/d3.ts +67 -54
- package/src/diagnostics.ts +17 -0
- package/src/dimensions.ts +9 -13
- package/src/echarts.ts +42 -14
- package/src/er/parser.ts +6 -1
- package/src/er/renderer.ts +4 -2
- package/src/gantt/parser.ts +44 -7
- package/src/graph/flowchart-parser.ts +77 -3
- package/src/graph/flowchart-renderer.ts +4 -2
- package/src/graph/state-renderer.ts +6 -4
- package/src/infra/parser.ts +80 -0
- package/src/infra/renderer.ts +8 -4
- package/src/journey-map/parser.ts +23 -8
- package/src/journey-map/renderer.ts +1 -1
- package/src/kanban/parser.ts +8 -7
- package/src/kanban/renderer.ts +1 -1
- package/src/map/context-labels.ts +134 -27
- package/src/map/geo.ts +10 -2
- package/src/map/layout.ts +259 -4
- package/src/map/parser.ts +2 -0
- package/src/map/renderer.ts +49 -25
- package/src/map/resolver.ts +68 -19
- package/src/mindmap/parser.ts +15 -7
- package/src/mindmap/renderer.ts +55 -15
- package/src/org/parser.ts +8 -7
- package/src/org/renderer.ts +89 -127
- package/src/palettes/color-utils.ts +19 -4
- package/src/palettes/index.ts +1 -0
- package/src/pert/renderer.ts +15 -10
- package/src/pyramid/parser.ts +2 -7
- package/src/quadrant/renderer.ts +2 -2
- package/src/raci/parser.ts +2 -7
- package/src/raci/renderer.ts +5 -5
- package/src/ring/parser.ts +2 -7
- package/src/sequence/parser.ts +18 -7
- package/src/sequence/renderer.ts +4 -4
- package/src/sitemap/parser.ts +8 -7
- package/src/sitemap/renderer.ts +37 -39
- package/src/tech-radar/parser.ts +2 -7
- package/src/timeline/renderer.ts +15 -5
- package/src/utils/card.ts +183 -0
- package/src/utils/parsing.ts +13 -1
- package/src/utils/scaling.ts +38 -81
- package/src/utils/tag-groups.ts +48 -10
- package/src/utils/visual-conventions.ts +61 -0
- package/src/visualizations/parse.ts +6 -1
- package/src/wireframe/parser.ts +6 -1
package/src/org/renderer.ts
CHANGED
|
@@ -10,7 +10,12 @@ import {
|
|
|
10
10
|
} from '../utils/export-container';
|
|
11
11
|
import { ScaleContext } from '../utils/scaling';
|
|
12
12
|
import type { PaletteColors } from '../palettes';
|
|
13
|
-
import {
|
|
13
|
+
import {
|
|
14
|
+
contrastText,
|
|
15
|
+
mix,
|
|
16
|
+
shapeFill,
|
|
17
|
+
themeBaseBg,
|
|
18
|
+
} from '../palettes/color-utils';
|
|
14
19
|
import { resolveTagColor } from '../utils/tag-groups';
|
|
15
20
|
import type { ParsedOrg } from './parser';
|
|
16
21
|
import type { OrgLayoutResult } from './layout';
|
|
@@ -26,6 +31,7 @@ import {
|
|
|
26
31
|
EYE_CLOSED_PATH,
|
|
27
32
|
} from '../utils/legend-constants';
|
|
28
33
|
import { renderIntegratedLegend } from '../utils/legend-integration';
|
|
34
|
+
import { renderNodeCard, renderCollapseBar } from '../utils/card';
|
|
29
35
|
import { measureText } from '../utils/text-measure';
|
|
30
36
|
import { getMaxLegendReservedHeight } from '../utils/legend-layout';
|
|
31
37
|
import type { LegendConfig } from '../utils/legend-types';
|
|
@@ -38,23 +44,25 @@ const DIAGRAM_PADDING = 20;
|
|
|
38
44
|
const MAX_SCALE = 3;
|
|
39
45
|
import { TITLE_FONT_SIZE, TITLE_FONT_WEIGHT } from '../utils/title-constants';
|
|
40
46
|
const TITLE_HEIGHT = 30;
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
47
|
+
// Shared card / group / collapse constants (Story 111.1). org matches every
|
|
48
|
+
// convention default, so it imports the full set.
|
|
49
|
+
import {
|
|
50
|
+
LABEL_FONT_SIZE,
|
|
51
|
+
META_FONT_SIZE,
|
|
52
|
+
META_LINE_HEIGHT,
|
|
53
|
+
HEADER_HEIGHT,
|
|
54
|
+
SEPARATOR_GAP,
|
|
55
|
+
EDGE_STROKE_WIDTH,
|
|
56
|
+
NODE_STROKE_WIDTH,
|
|
57
|
+
CARD_RADIUS,
|
|
58
|
+
CONTAINER_RADIUS,
|
|
59
|
+
CONTAINER_LABEL_FONT_SIZE,
|
|
60
|
+
CONTAINER_META_FONT_SIZE,
|
|
61
|
+
CONTAINER_META_LINE_HEIGHT,
|
|
62
|
+
CONTAINER_HEADER_HEIGHT,
|
|
63
|
+
COLLAPSE_BAR_HEIGHT,
|
|
64
|
+
COLLAPSE_BAR_INSET,
|
|
65
|
+
} from '../utils/visual-conventions';
|
|
58
66
|
|
|
59
67
|
// Ancestor breadcrumb trail (focus mode)
|
|
60
68
|
const ANCESTOR_DOT_R = 4;
|
|
@@ -90,7 +98,7 @@ function containerFill(
|
|
|
90
98
|
nodeColor?: string
|
|
91
99
|
): string {
|
|
92
100
|
if (nodeColor) {
|
|
93
|
-
return mix(nodeColor,
|
|
101
|
+
return mix(nodeColor, themeBaseBg(palette, isDark), 10);
|
|
94
102
|
}
|
|
95
103
|
return mix(palette.surface, palette.bg, 40);
|
|
96
104
|
}
|
|
@@ -360,21 +368,16 @@ export function renderOrg(
|
|
|
360
368
|
}
|
|
361
369
|
|
|
362
370
|
if (!exportDims && c.hiddenCount && c.hiddenCount > 0) {
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
.attr('width', c.width - sCollapseBarInset * 2)
|
|
374
|
-
.attr('height', sCollapseBarHeight)
|
|
375
|
-
.attr('fill', containerStroke(palette, colorOff ? undefined : c.color))
|
|
376
|
-
.attr('clip-path', `url(#${clipId})`)
|
|
377
|
-
.attr('class', 'org-collapse-bar');
|
|
371
|
+
renderCollapseBar(cG, {
|
|
372
|
+
width: c.width,
|
|
373
|
+
height: c.height,
|
|
374
|
+
barHeight: sCollapseBarHeight,
|
|
375
|
+
inset: sCollapseBarInset,
|
|
376
|
+
rx: sContainerRadius,
|
|
377
|
+
fill: containerStroke(palette, colorOff ? undefined : c.color),
|
|
378
|
+
clipId: `clip-${c.nodeId}`,
|
|
379
|
+
className: 'org-collapse-bar',
|
|
380
|
+
});
|
|
378
381
|
}
|
|
379
382
|
|
|
380
383
|
// Focus icon (hover-reveal, interactive only) — for non-root containers with children
|
|
@@ -399,6 +402,13 @@ export function renderOrg(
|
|
|
399
402
|
.attr('height', iconSize + 6)
|
|
400
403
|
.attr('fill', 'transparent');
|
|
401
404
|
|
|
405
|
+
// Use the container's contrast text color so it stays legible on darker
|
|
406
|
+
// fills, mirroring the node icon.
|
|
407
|
+
const iconColor = contrastText(
|
|
408
|
+
fill,
|
|
409
|
+
palette.textOnFillLight,
|
|
410
|
+
palette.textOnFillDark
|
|
411
|
+
);
|
|
402
412
|
const cx = iconSize / 2;
|
|
403
413
|
const cy = iconSize / 2;
|
|
404
414
|
focusG
|
|
@@ -407,14 +417,14 @@ export function renderOrg(
|
|
|
407
417
|
.attr('cy', cy)
|
|
408
418
|
.attr('r', iconSize / 2 - 1)
|
|
409
419
|
.attr('fill', 'none')
|
|
410
|
-
.attr('stroke',
|
|
420
|
+
.attr('stroke', iconColor)
|
|
411
421
|
.attr('stroke-width', 1.5);
|
|
412
422
|
focusG
|
|
413
423
|
.append('circle')
|
|
414
424
|
.attr('cx', cx)
|
|
415
425
|
.attr('cy', cy)
|
|
416
426
|
.attr('r', 2)
|
|
417
|
-
.attr('fill',
|
|
427
|
+
.attr('fill', iconColor);
|
|
418
428
|
}
|
|
419
429
|
}
|
|
420
430
|
|
|
@@ -489,103 +499,52 @@ export function renderOrg(
|
|
|
489
499
|
);
|
|
490
500
|
const stroke = nodeStroke(palette, colorOff ? undefined : node.color);
|
|
491
501
|
|
|
492
|
-
const rect = nodeG
|
|
493
|
-
.append('rect')
|
|
494
|
-
.attr('x', 0)
|
|
495
|
-
.attr('y', 0)
|
|
496
|
-
.attr('width', node.width)
|
|
497
|
-
.attr('height', node.height)
|
|
498
|
-
.attr('rx', sCardRadius)
|
|
499
|
-
.attr('fill', fill)
|
|
500
|
-
.attr('stroke', stroke)
|
|
501
|
-
.attr('stroke-width', sNodeStrokeWidth);
|
|
502
|
-
|
|
503
|
-
if (node.isContainer) {
|
|
504
|
-
rect.attr('stroke-dasharray', '6 3');
|
|
505
|
-
}
|
|
506
|
-
|
|
507
502
|
const labelColor = contrastText(
|
|
508
503
|
fill,
|
|
509
504
|
palette.textOnFillLight,
|
|
510
505
|
palette.textOnFillDark
|
|
511
506
|
);
|
|
512
|
-
nodeG
|
|
513
|
-
.append('text')
|
|
514
|
-
.attr('x', node.width / 2)
|
|
515
|
-
.attr('y', sHeaderHeight / 2 + sLabelFontSize / 2 - 2)
|
|
516
|
-
.attr('text-anchor', 'middle')
|
|
517
|
-
.attr('fill', labelColor)
|
|
518
|
-
.attr('font-size', sLabelFontSize)
|
|
519
|
-
.attr('font-weight', 'bold')
|
|
520
|
-
.text(node.label);
|
|
521
507
|
|
|
522
508
|
const metaEntries = Object.entries(node.metadata);
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
nodeG
|
|
549
|
-
.append('text')
|
|
550
|
-
.attr('x', 10)
|
|
551
|
-
.attr('y', rowY)
|
|
552
|
-
.attr('fill', labelColor)
|
|
553
|
-
.attr('font-size', sMetaFontSize)
|
|
554
|
-
.text(`${displayKey}: `);
|
|
555
|
-
|
|
556
|
-
nodeG
|
|
557
|
-
.append('text')
|
|
558
|
-
.attr('x', valueX)
|
|
559
|
-
.attr('y', rowY)
|
|
560
|
-
.attr('fill', labelColor)
|
|
561
|
-
.attr('font-size', sMetaFontSize)
|
|
562
|
-
.text(value);
|
|
563
|
-
}
|
|
564
|
-
}
|
|
509
|
+
renderNodeCard(nodeG, {
|
|
510
|
+
width: node.width,
|
|
511
|
+
height: node.height,
|
|
512
|
+
rx: sCardRadius,
|
|
513
|
+
fill,
|
|
514
|
+
stroke,
|
|
515
|
+
strokeWidth: sNodeStrokeWidth,
|
|
516
|
+
...(node.isContainer && { dashed: true }),
|
|
517
|
+
label: node.label,
|
|
518
|
+
labelColor,
|
|
519
|
+
labelFontSize: sLabelFontSize,
|
|
520
|
+
headerHeight: sHeaderHeight,
|
|
521
|
+
...(metaEntries.length > 0 && {
|
|
522
|
+
meta: {
|
|
523
|
+
rows: metaEntries.map(
|
|
524
|
+
([k, value]) => [displayNames.get(k) ?? k, value] as const
|
|
525
|
+
),
|
|
526
|
+
fontSize: sMetaFontSize,
|
|
527
|
+
lineHeight: sMetaLineHeight,
|
|
528
|
+
separatorGap: sSeparatorGap,
|
|
529
|
+
separatorColor: solid ? labelColor : stroke,
|
|
530
|
+
textColor: labelColor,
|
|
531
|
+
},
|
|
532
|
+
}),
|
|
533
|
+
});
|
|
565
534
|
|
|
566
535
|
if (!exportDims && node.hiddenCount && node.hiddenCount > 0) {
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
.
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
.
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
.attr('width', node.width - sCollapseBarInset * 2)
|
|
580
|
-
.attr('height', sCollapseBarHeight)
|
|
581
|
-
.attr(
|
|
582
|
-
'fill',
|
|
583
|
-
solid
|
|
584
|
-
? labelColor
|
|
585
|
-
: nodeStroke(palette, colorOff ? undefined : node.color)
|
|
586
|
-
)
|
|
587
|
-
.attr('clip-path', `url(#${clipId})`)
|
|
588
|
-
.attr('class', 'org-collapse-bar');
|
|
536
|
+
renderCollapseBar(nodeG, {
|
|
537
|
+
width: node.width,
|
|
538
|
+
height: node.height,
|
|
539
|
+
barHeight: sCollapseBarHeight,
|
|
540
|
+
inset: sCollapseBarInset,
|
|
541
|
+
rx: sCardRadius,
|
|
542
|
+
fill: solid
|
|
543
|
+
? labelColor
|
|
544
|
+
: nodeStroke(palette, colorOff ? undefined : node.color),
|
|
545
|
+
clipId: `clip-${node.id}`,
|
|
546
|
+
className: 'org-collapse-bar',
|
|
547
|
+
});
|
|
589
548
|
}
|
|
590
549
|
|
|
591
550
|
// Focus icon (hover-reveal, interactive only) — for non-root nodes with children
|
|
@@ -611,7 +570,10 @@ export function renderOrg(
|
|
|
611
570
|
.attr('height', iconSize + 6)
|
|
612
571
|
.attr('fill', 'transparent');
|
|
613
572
|
|
|
614
|
-
// Scope/target icon: outer circle + inner dot
|
|
573
|
+
// Scope/target icon: outer circle + inner dot. Use the card's contrast
|
|
574
|
+
// text color so it stays legible on solid-fill dark cards, not just the
|
|
575
|
+
// light default surface.
|
|
576
|
+
const iconColor = labelColor;
|
|
615
577
|
const cx = iconSize / 2;
|
|
616
578
|
const cy = iconSize / 2;
|
|
617
579
|
focusG
|
|
@@ -620,14 +582,14 @@ export function renderOrg(
|
|
|
620
582
|
.attr('cy', cy)
|
|
621
583
|
.attr('r', iconSize / 2 - 1)
|
|
622
584
|
.attr('fill', 'none')
|
|
623
|
-
.attr('stroke',
|
|
585
|
+
.attr('stroke', iconColor)
|
|
624
586
|
.attr('stroke-width', 1.5);
|
|
625
587
|
focusG
|
|
626
588
|
.append('circle')
|
|
627
589
|
.attr('cx', cx)
|
|
628
590
|
.attr('cy', cy)
|
|
629
591
|
.attr('r', 2)
|
|
630
|
-
.attr('fill',
|
|
592
|
+
.attr('fill', iconColor);
|
|
631
593
|
}
|
|
632
594
|
}
|
|
633
595
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { CATEGORICAL_COLOR_ORDER } from '../colors';
|
|
1
2
|
import type { PaletteColors } from './types';
|
|
2
3
|
|
|
3
4
|
// ============================================================
|
|
@@ -304,6 +305,16 @@ export function contrastText(
|
|
|
304
305
|
* `opts.solid` (per `option solid-fill`): bypass the 25% tint and return
|
|
305
306
|
* the raw intent. Opt-in only; default behavior unchanged.
|
|
306
307
|
*/
|
|
308
|
+
/**
|
|
309
|
+
* The theme-aware base background a diagram's tinted shapes blend toward:
|
|
310
|
+
* `surface` in dark, page `bg` in light. Concentrates the
|
|
311
|
+
* `isDark ? palette.surface : palette.bg` pick repeated across ~20 renderers
|
|
312
|
+
* (Story 111.3).
|
|
313
|
+
*/
|
|
314
|
+
export function themeBaseBg(palette: PaletteColors, isDark: boolean): string {
|
|
315
|
+
return isDark ? palette.surface : palette.bg;
|
|
316
|
+
}
|
|
317
|
+
|
|
307
318
|
export function shapeFill(
|
|
308
319
|
palette: PaletteColors,
|
|
309
320
|
intent: string,
|
|
@@ -311,17 +322,21 @@ export function shapeFill(
|
|
|
311
322
|
opts?: { solid?: boolean }
|
|
312
323
|
): string {
|
|
313
324
|
if (opts?.solid) return intent;
|
|
314
|
-
return mix(intent,
|
|
325
|
+
return mix(intent, themeBaseBg(palette, isDark), 25);
|
|
315
326
|
}
|
|
316
327
|
|
|
317
328
|
// ============================================================
|
|
318
329
|
// Series Colors
|
|
319
330
|
// ============================================================
|
|
320
331
|
|
|
321
|
-
/**
|
|
332
|
+
/**
|
|
333
|
+
* Derive the 8-color series rotation from a palette's named colors, in the
|
|
334
|
+
* shared {@link CATEGORICAL_COLOR_ORDER} (RGB-seeded, max-contrast). Tag
|
|
335
|
+
* swatches and chart series colors thus share one canonical rotation.
|
|
336
|
+
*/
|
|
322
337
|
export function getSeriesColors(palette: PaletteColors): string[] {
|
|
323
338
|
const c = palette.colors;
|
|
324
|
-
return
|
|
339
|
+
return CATEGORICAL_COLOR_ORDER.map((name) => c[name]!);
|
|
325
340
|
}
|
|
326
341
|
|
|
327
342
|
/**
|
|
@@ -403,7 +418,7 @@ export function politicalTints(
|
|
|
403
418
|
isDark: boolean
|
|
404
419
|
): string[] {
|
|
405
420
|
if (count <= 0) return [];
|
|
406
|
-
const base =
|
|
421
|
+
const base = themeBaseBg(palette, isDark);
|
|
407
422
|
const c = palette.colors;
|
|
408
423
|
// Land-first: greens/earth tones lead; water-like blue & cyan trail.
|
|
409
424
|
const swatches = [
|
package/src/palettes/index.ts
CHANGED
package/src/pert/renderer.ts
CHANGED
|
@@ -33,7 +33,7 @@ import * as d3Selection from 'd3-selection';
|
|
|
33
33
|
import * as d3Shape from 'd3-shape';
|
|
34
34
|
import { FONT_FAMILY } from '../fonts';
|
|
35
35
|
import type { PaletteColors } from '../palettes';
|
|
36
|
-
import { contrastText, mix, shapeFill } from '../palettes/color-utils';
|
|
36
|
+
import { contrastText, mix, shapeFill, themeBaseBg } from '../palettes/color-utils';
|
|
37
37
|
import { ScaleContext } from '../utils/scaling';
|
|
38
38
|
import {
|
|
39
39
|
measureText,
|
|
@@ -87,7 +87,16 @@ const NODE_CELL_FONT_SIZE = 11;
|
|
|
87
87
|
// row holds the name and is taller than the corner cells so the name
|
|
88
88
|
// reads as the primary label (mirroring textbook proportions).
|
|
89
89
|
const NODE_RADIUS = 6;
|
|
90
|
-
|
|
90
|
+
// Shared card / group / collapse constants (Story 111.1). The explanatory
|
|
91
|
+
// comments below stay with the renderer; the values now live in one module.
|
|
92
|
+
import {
|
|
93
|
+
NODE_STROKE_WIDTH,
|
|
94
|
+
EDGE_STROKE_WIDTH,
|
|
95
|
+
CONTAINER_RADIUS,
|
|
96
|
+
CONTAINER_LABEL_FONT_SIZE,
|
|
97
|
+
CONTAINER_HEADER_HEIGHT,
|
|
98
|
+
COLLAPSE_BAR_HEIGHT,
|
|
99
|
+
} from '../utils/visual-conventions';
|
|
91
100
|
|
|
92
101
|
// Analysis-block chrome (Summary / Activity Risk / Completion / Field
|
|
93
102
|
// labels). These sit BELOW the diagram and shouldn't compete with it
|
|
@@ -99,7 +108,7 @@ function analysisBlockChrome(
|
|
|
99
108
|
palette: PaletteColors,
|
|
100
109
|
isDark: boolean
|
|
101
110
|
): { fill: string; stroke: string } {
|
|
102
|
-
const surfaceBg =
|
|
111
|
+
const surfaceBg = themeBaseBg(palette, isDark);
|
|
103
112
|
return {
|
|
104
113
|
fill: mix(palette.surface, palette.bg, 40),
|
|
105
114
|
stroke: mix(palette.textMuted, surfaceBg, 35),
|
|
@@ -113,18 +122,14 @@ const NODE_BOTTOM_ROW_HEIGHT = 26;
|
|
|
113
122
|
// stroke and a matching red arrowhead because the critical path is the
|
|
114
123
|
// central concept of a PERT chart, and a binary `data-critical` attr
|
|
115
124
|
// alone left it visually invisible to readers.
|
|
116
|
-
const EDGE_STROKE_WIDTH = 1.5;
|
|
117
125
|
const ARROWHEAD_W = 10;
|
|
118
126
|
const ARROWHEAD_H = 7;
|
|
119
127
|
// Group-rect treatment per §2: neutral surface fill on textMuted stroke,
|
|
120
128
|
// solid border, rx=8, top-center 13pt 'bold' label inside a reserved
|
|
121
129
|
// 28px header band — exactly matching org's container recipe.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
// Collapse-bar height — see conventions doc §3 Pattern A/B (matches
|
|
126
|
-
// org's `COLLAPSE_BAR_HEIGHT`). Universal "this is collapsed" signal.
|
|
127
|
-
const COLLAPSE_BAR_HEIGHT = 6;
|
|
130
|
+
// CONTAINER_RADIUS/LABEL_FONT_SIZE/HEADER_HEIGHT + COLLAPSE_BAR_HEIGHT now
|
|
131
|
+
// imported from utils/visual-conventions (Story 111.1). Group-rect treatment
|
|
132
|
+
// per §2; collapse-bar height matches org per §3 Pattern A/B.
|
|
128
133
|
// Always-on fade applied to bottom-20% (by duration) activity nodes
|
|
129
134
|
// so the eye is drawn to the longer, schedule-dominating work first.
|
|
130
135
|
// Less aggressive than FADE_OPACITY because these cards still need to
|
package/src/pyramid/parser.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
bareDescriptionRemovedMessage,
|
|
7
|
-
formatDgmoError,
|
|
8
7
|
makeDgmoError,
|
|
8
|
+
makeFail,
|
|
9
9
|
METADATA_DIAGNOSTIC_CODES,
|
|
10
10
|
pipeOperatorRemovedMessage,
|
|
11
11
|
} from '../diagnostics';
|
|
@@ -61,12 +61,7 @@ export function parsePyramid(content: string): ParsedPyramid {
|
|
|
61
61
|
let headerParsed = false;
|
|
62
62
|
let currentLayer: Writable<PyramidLayer> | null = null;
|
|
63
63
|
|
|
64
|
-
const fail = (
|
|
65
|
-
const diag = makeDgmoError(line, message);
|
|
66
|
-
result.diagnostics.push(diag);
|
|
67
|
-
result.error = formatDgmoError(diag);
|
|
68
|
-
return result;
|
|
69
|
-
};
|
|
64
|
+
const fail = makeFail(result);
|
|
70
65
|
|
|
71
66
|
const warn = (
|
|
72
67
|
line: number,
|
package/src/quadrant/renderer.ts
CHANGED
|
@@ -11,7 +11,7 @@ import { ScaleContext } from '../utils/scaling';
|
|
|
11
11
|
import { initD3Chart, renderChartTitle } from '../utils/d3-helpers';
|
|
12
12
|
import type { ParsedQuadrant, QuadrantLabel } from '../visualizations/types';
|
|
13
13
|
import type { PaletteColors } from '../palettes';
|
|
14
|
-
import { mix } from '../palettes/color-utils';
|
|
14
|
+
import { mix, themeBaseBg } from '../palettes/color-utils';
|
|
15
15
|
|
|
16
16
|
// Quadrant Chart Renderer
|
|
17
17
|
// ============================================================
|
|
@@ -99,7 +99,7 @@ export function renderQuadrant(
|
|
|
99
99
|
.append('g')
|
|
100
100
|
.attr('transform', `translate(${margin.left}, ${margin.top})`);
|
|
101
101
|
|
|
102
|
-
const bg =
|
|
102
|
+
const bg = themeBaseBg(palette, isDark);
|
|
103
103
|
|
|
104
104
|
// Full palette color for a quadrant (used for border and label tinting)
|
|
105
105
|
const getQuadrantColor = (
|
package/src/raci/parser.ts
CHANGED
|
@@ -19,8 +19,8 @@
|
|
|
19
19
|
// See `docs/dgmo-language-spec.md` § "RACI Matrix".
|
|
20
20
|
|
|
21
21
|
import {
|
|
22
|
-
formatDgmoError,
|
|
23
22
|
makeDgmoError,
|
|
23
|
+
makeFail,
|
|
24
24
|
METADATA_DIAGNOSTIC_CODES,
|
|
25
25
|
pipeOperatorRemovedMessage,
|
|
26
26
|
suggest,
|
|
@@ -181,12 +181,7 @@ export function parseRaci(
|
|
|
181
181
|
error: null,
|
|
182
182
|
};
|
|
183
183
|
|
|
184
|
-
const fail = (
|
|
185
|
-
const diag = makeDgmoError(line, message);
|
|
186
|
-
result.diagnostics.push(diag);
|
|
187
|
-
result.error = formatDgmoError(diag);
|
|
188
|
-
return result;
|
|
189
|
-
};
|
|
184
|
+
const fail = makeFail(result);
|
|
190
185
|
|
|
191
186
|
const warn = (line: number, message: string, code?: string): void => {
|
|
192
187
|
result.diagnostics.push(makeDgmoError(line, message, 'warning', code));
|
package/src/raci/renderer.ts
CHANGED
|
@@ -30,7 +30,7 @@ import {
|
|
|
30
30
|
TITLE_FONT_WEIGHT,
|
|
31
31
|
TITLE_Y,
|
|
32
32
|
} from '../utils/title-constants';
|
|
33
|
-
import { contrastText, mix } from '../palettes/color-utils';
|
|
33
|
+
import { contrastText, mix, themeBaseBg } from '../palettes/color-utils';
|
|
34
34
|
import type { PaletteColors } from '../palettes';
|
|
35
35
|
import type { D3ExportDimensions } from '../utils/d3-types';
|
|
36
36
|
import type {
|
|
@@ -267,7 +267,7 @@ const TINT_PCT = 25;
|
|
|
267
267
|
* kanban `CARD_RADIUS = 6`). Keeps RACI markers visually consistent
|
|
268
268
|
* with nodes elsewhere in the diagram language.
|
|
269
269
|
*/
|
|
270
|
-
|
|
270
|
+
import { NODE_STROKE_WIDTH } from '../utils/visual-conventions'; // shared (Story 111.1)
|
|
271
271
|
const NODE_RADIUS = 6;
|
|
272
272
|
|
|
273
273
|
/**
|
|
@@ -394,7 +394,7 @@ export function renderRaci(
|
|
|
394
394
|
if (tasksAll.length === 0 && parsed.phases.length === 0) return;
|
|
395
395
|
|
|
396
396
|
const solid = parsed.options['solid-fill'] === 'on';
|
|
397
|
-
const surfaceBg =
|
|
397
|
+
const surfaceBg = themeBaseBg(palette, isDark);
|
|
398
398
|
|
|
399
399
|
// --- ScaleContext: differential scaling ---
|
|
400
400
|
const roleCount = Math.max(1, parsed.roles.length);
|
|
@@ -679,10 +679,10 @@ export function renderRaci(
|
|
|
679
679
|
// each column has a subtle visual identity instead of every column
|
|
680
680
|
// reading as the same neutral gray.
|
|
681
681
|
const roleColor = parsed.roleColors[i] ?? autoAccent(i, palette);
|
|
682
|
-
const bodyFill = mix(roleColor,
|
|
682
|
+
const bodyFill = mix(roleColor, themeBaseBg(palette, isDark), 16);
|
|
683
683
|
const headerFill = mix(
|
|
684
684
|
roleColor,
|
|
685
|
-
|
|
685
|
+
themeBaseBg(palette, isDark),
|
|
686
686
|
30
|
|
687
687
|
);
|
|
688
688
|
const colG = columnsG
|
package/src/ring/parser.ts
CHANGED
|
@@ -4,8 +4,8 @@
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
bareDescriptionRemovedMessage,
|
|
7
|
-
formatDgmoError,
|
|
8
7
|
makeDgmoError,
|
|
8
|
+
makeFail,
|
|
9
9
|
METADATA_DIAGNOSTIC_CODES,
|
|
10
10
|
pipeOperatorRemovedMessage,
|
|
11
11
|
suggest,
|
|
@@ -48,12 +48,7 @@ export function parseRing(content: string): ParsedRing {
|
|
|
48
48
|
let headerParsed = false;
|
|
49
49
|
let currentLayer: Writable<RingLayer> | null = null;
|
|
50
50
|
|
|
51
|
-
const fail = (
|
|
52
|
-
const diag = makeDgmoError(line, message);
|
|
53
|
-
result.diagnostics.push(diag);
|
|
54
|
-
result.error = formatDgmoError(diag);
|
|
55
|
-
return result;
|
|
56
|
-
};
|
|
51
|
+
const fail = makeFail(result);
|
|
57
52
|
|
|
58
53
|
const warn = (
|
|
59
54
|
line: number,
|
package/src/sequence/parser.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
akaRemovedMessage,
|
|
11
11
|
formatDgmoError,
|
|
12
12
|
makeDgmoError,
|
|
13
|
+
makeFail,
|
|
13
14
|
METADATA_DIAGNOSTIC_CODES,
|
|
14
15
|
NAME_DIAGNOSTIC_CODES,
|
|
15
16
|
nameMergedMessage,
|
|
@@ -528,12 +529,7 @@ export function parseSequenceDgmo(
|
|
|
528
529
|
return nameAliasMap.get(trimmed) ?? trimmed;
|
|
529
530
|
};
|
|
530
531
|
|
|
531
|
-
const fail = (
|
|
532
|
-
const diag = makeDgmoError(line, message);
|
|
533
|
-
result.diagnostics.push(diag);
|
|
534
|
-
result.error = formatDgmoError(diag);
|
|
535
|
-
return result;
|
|
536
|
-
};
|
|
532
|
+
const fail = makeFail(result);
|
|
537
533
|
|
|
538
534
|
/** Push a recoverable error and continue parsing. */
|
|
539
535
|
const pushError = (line: number, message: string): void => {
|
|
@@ -554,6 +550,13 @@ export function parseSequenceDgmo(
|
|
|
554
550
|
const lines = content.split('\n');
|
|
555
551
|
let hasExplicitChart = false;
|
|
556
552
|
let contentStarted = false;
|
|
553
|
+
// Whether the message body has begun (first message, section, block, or note).
|
|
554
|
+
// Unlike `contentStarted` — which any declaration trips to close the
|
|
555
|
+
// options/tag-group "headers first" window — `bodyStarted` stays false through
|
|
556
|
+
// the entire declaration preamble so bare and typed participant declarations
|
|
557
|
+
// can be freely interleaved. It gates bare-name declarations: once real body
|
|
558
|
+
// content appears, a bare word is treated as a stray line, not a participant.
|
|
559
|
+
let bodyStarted = false;
|
|
557
560
|
let firstLineIndex = -1; // line index of the `sequence [Title]` first line (to skip in main loop)
|
|
558
561
|
|
|
559
562
|
// Handle first non-empty, non-comment line for `sequence Title` syntax
|
|
@@ -997,6 +1000,7 @@ export function parseSequenceDgmo(
|
|
|
997
1000
|
);
|
|
998
1001
|
}
|
|
999
1002
|
contentStarted = true;
|
|
1003
|
+
bodyStarted = true;
|
|
1000
1004
|
const section: SequenceSection = {
|
|
1001
1005
|
kind: 'section',
|
|
1002
1006
|
// Capture group 1 guaranteed present after successful match.
|
|
@@ -1301,7 +1305,7 @@ export function parseSequenceDgmo(
|
|
|
1301
1305
|
if (
|
|
1302
1306
|
/^\S+$/.test(bareCore) &&
|
|
1303
1307
|
!ARROW_PATTERN.test(bareCore) &&
|
|
1304
|
-
(inGroup || !
|
|
1308
|
+
(inGroup || !bodyStarted || bareMeta)
|
|
1305
1309
|
) {
|
|
1306
1310
|
contentStarted = true;
|
|
1307
1311
|
const id = bareCore;
|
|
@@ -1369,6 +1373,7 @@ export function parseSequenceDgmo(
|
|
|
1369
1373
|
}
|
|
1370
1374
|
if (labeledArrow) {
|
|
1371
1375
|
contentStarted = true;
|
|
1376
|
+
bodyStarted = true;
|
|
1372
1377
|
const { from, to, label: rawLabel, async: isAsync } = labeledArrow;
|
|
1373
1378
|
const fromKey = addParticipant(resolveAlias(from), lineNumber);
|
|
1374
1379
|
const toKey = addParticipant(resolveAlias(to), lineNumber);
|
|
@@ -1445,6 +1450,7 @@ export function parseSequenceDgmo(
|
|
|
1445
1450
|
const bareCall = bareCallSync || bareCallAsync;
|
|
1446
1451
|
if (bareCall) {
|
|
1447
1452
|
contentStarted = true;
|
|
1453
|
+
bodyStarted = true;
|
|
1448
1454
|
// Capture groups 1 and 2 guaranteed present after successful match.
|
|
1449
1455
|
const from = bareCall[1]!;
|
|
1450
1456
|
const to = bareCall[2]!;
|
|
@@ -1470,6 +1476,7 @@ export function parseSequenceDgmo(
|
|
|
1470
1476
|
const ifMatch = trimmed.match(/^if\s+(.+)$/i);
|
|
1471
1477
|
if (ifMatch) {
|
|
1472
1478
|
contentStarted = true;
|
|
1479
|
+
bodyStarted = true;
|
|
1473
1480
|
const block: Writable<SequenceBlock> = {
|
|
1474
1481
|
kind: 'block',
|
|
1475
1482
|
type: 'if',
|
|
@@ -1488,6 +1495,7 @@ export function parseSequenceDgmo(
|
|
|
1488
1495
|
const loopMatch = trimmed.match(/^loop\s+(.+)$/i);
|
|
1489
1496
|
if (loopMatch) {
|
|
1490
1497
|
contentStarted = true;
|
|
1498
|
+
bodyStarted = true;
|
|
1491
1499
|
const block: Writable<SequenceBlock> = {
|
|
1492
1500
|
kind: 'block',
|
|
1493
1501
|
type: 'loop',
|
|
@@ -1506,6 +1514,7 @@ export function parseSequenceDgmo(
|
|
|
1506
1514
|
const parallelMatch = trimmed.match(/^parallel(?:\s+(.+))?$/i);
|
|
1507
1515
|
if (parallelMatch) {
|
|
1508
1516
|
contentStarted = true;
|
|
1517
|
+
bodyStarted = true;
|
|
1509
1518
|
const block: Writable<SequenceBlock> = {
|
|
1510
1519
|
kind: 'block',
|
|
1511
1520
|
type: 'parallel',
|
|
@@ -1629,6 +1638,7 @@ export function parseSequenceDgmo(
|
|
|
1629
1638
|
lineNumber,
|
|
1630
1639
|
endLineNumber: lineNumber,
|
|
1631
1640
|
};
|
|
1641
|
+
bodyStarted = true;
|
|
1632
1642
|
currentContainer().push(note);
|
|
1633
1643
|
continue;
|
|
1634
1644
|
}
|
|
@@ -1654,6 +1664,7 @@ export function parseSequenceDgmo(
|
|
|
1654
1664
|
lineNumber,
|
|
1655
1665
|
endLineNumber: i + 1, // i has advanced past the body lines (1-based)
|
|
1656
1666
|
};
|
|
1667
|
+
bodyStarted = true;
|
|
1657
1668
|
currentContainer().push(note);
|
|
1658
1669
|
continue;
|
|
1659
1670
|
}
|