@diagrammo/dgmo 0.2.22 → 0.2.24
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/dist/cli.cjs +119 -113
- package/dist/index.cjs +6311 -2344
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +263 -2
- package/dist/index.d.ts +263 -2
- package/dist/index.js +6269 -2320
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/c4/layout.ts +2137 -0
- package/src/c4/parser.ts +809 -0
- package/src/c4/renderer.ts +1916 -0
- package/src/c4/types.ts +86 -0
- package/src/class/renderer.ts +2 -2
- package/src/cli.ts +54 -10
- package/src/d3.ts +148 -10
- package/src/dgmo-router.ts +13 -0
- package/src/er/renderer.ts +2 -2
- package/src/graph/flowchart-renderer.ts +1 -1
- package/src/index.ts +54 -0
- package/src/initiative-status/layout.ts +217 -0
- package/src/initiative-status/parser.ts +246 -0
- package/src/initiative-status/renderer.ts +837 -0
- package/src/initiative-status/types.ts +43 -0
- package/src/kanban/renderer.ts +20 -2
- package/src/org/layout.ts +34 -16
- package/src/org/renderer.ts +31 -10
- package/src/org/resolver.ts +3 -1
- package/src/render.ts +9 -1
- package/src/sequence/participant-inference.ts +1 -0
- package/src/sequence/renderer.ts +12 -6
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
// ============================================================
|
|
2
|
+
// Initiative Status Diagram — Types
|
|
3
|
+
// ============================================================
|
|
4
|
+
|
|
5
|
+
import type { DgmoError } from '../diagnostics';
|
|
6
|
+
import type { ParticipantType } from '../sequence/parser';
|
|
7
|
+
|
|
8
|
+
export type InitiativeStatus = 'done' | 'wip' | 'todo' | 'na' | null;
|
|
9
|
+
|
|
10
|
+
export const VALID_STATUSES: readonly string[] = ['done', 'wip', 'todo', 'na'];
|
|
11
|
+
|
|
12
|
+
export interface ISNode {
|
|
13
|
+
label: string;
|
|
14
|
+
status: InitiativeStatus;
|
|
15
|
+
shape: ParticipantType;
|
|
16
|
+
lineNumber: number;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface ISEdge {
|
|
20
|
+
source: string; // node label
|
|
21
|
+
target: string; // node label
|
|
22
|
+
label?: string; // e.g. "getUser"
|
|
23
|
+
status: InitiativeStatus;
|
|
24
|
+
lineNumber: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface ISGroup {
|
|
28
|
+
label: string;
|
|
29
|
+
nodeLabels: string[];
|
|
30
|
+
lineNumber: number;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface ParsedInitiativeStatus {
|
|
34
|
+
type: 'initiative-status';
|
|
35
|
+
title: string | null;
|
|
36
|
+
titleLineNumber: number | null;
|
|
37
|
+
nodes: ISNode[];
|
|
38
|
+
edges: ISEdge[];
|
|
39
|
+
groups: ISGroup[];
|
|
40
|
+
options: Record<string, string>;
|
|
41
|
+
diagnostics: DgmoError[];
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
package/src/kanban/renderer.ts
CHANGED
|
@@ -347,7 +347,12 @@ export function renderKanban(
|
|
|
347
347
|
if (isActive) {
|
|
348
348
|
let entryX = pillX + pillWidth + 4;
|
|
349
349
|
for (const entry of group.entries) {
|
|
350
|
-
svg
|
|
350
|
+
const entryG = svg
|
|
351
|
+
.append('g')
|
|
352
|
+
.attr('data-legend-entry', entry.value.toLowerCase())
|
|
353
|
+
.style('cursor', 'pointer');
|
|
354
|
+
|
|
355
|
+
entryG
|
|
351
356
|
.append('circle')
|
|
352
357
|
.attr('cx', entryX + LEGEND_DOT_R)
|
|
353
358
|
.attr('cy', legendY + LEGEND_HEIGHT / 2)
|
|
@@ -355,7 +360,7 @@ export function renderKanban(
|
|
|
355
360
|
.attr('fill', entry.color);
|
|
356
361
|
|
|
357
362
|
const entryTextX = entryX + LEGEND_DOT_R * 2 + 4;
|
|
358
|
-
|
|
363
|
+
entryG
|
|
359
364
|
.append('text')
|
|
360
365
|
.attr('x', entryTextX)
|
|
361
366
|
.attr('y', legendY + LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1)
|
|
@@ -466,6 +471,19 @@ export function renderKanban(
|
|
|
466
471
|
.attr('data-card-id', card.id)
|
|
467
472
|
.attr('data-line-number', card.lineNumber);
|
|
468
473
|
|
|
474
|
+
// Expose active tag group value for legend-entry hover dimming
|
|
475
|
+
if (activeTagGroup) {
|
|
476
|
+
const tagKey = activeTagGroup.toLowerCase();
|
|
477
|
+
const tagValue = card.tags[tagKey];
|
|
478
|
+
const group = parsed.tagGroups.find(
|
|
479
|
+
(tg) => tg.name.toLowerCase() === tagKey
|
|
480
|
+
);
|
|
481
|
+
const value = tagValue ?? group?.defaultValue;
|
|
482
|
+
if (value) {
|
|
483
|
+
cg.attr(`data-tag-${tagKey}`, value.toLowerCase());
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
|
|
469
487
|
const cx = colLayout.x + cardLayout.x;
|
|
470
488
|
const cy = colLayout.y + cardLayout.y;
|
|
471
489
|
|
package/src/org/layout.ts
CHANGED
|
@@ -52,7 +52,6 @@ export interface OrgContainerBounds {
|
|
|
52
52
|
export interface OrgLegendEntry {
|
|
53
53
|
value: string;
|
|
54
54
|
color: string;
|
|
55
|
-
isDefault?: boolean;
|
|
56
55
|
}
|
|
57
56
|
|
|
58
57
|
export interface OrgLegendGroup {
|
|
@@ -272,29 +271,34 @@ function centerHeavyChildren(node: TreeNode): void {
|
|
|
272
271
|
// Layout
|
|
273
272
|
// ============================================================
|
|
274
273
|
|
|
275
|
-
function computeLegendGroups(
|
|
274
|
+
function computeLegendGroups(
|
|
275
|
+
tagGroups: OrgTagGroup[],
|
|
276
|
+
_showEyeIcons: boolean,
|
|
277
|
+
usedValuesByGroup?: Map<string, Set<string>>
|
|
278
|
+
): OrgLegendGroup[] {
|
|
276
279
|
const groups: OrgLegendGroup[] = [];
|
|
277
280
|
|
|
278
281
|
for (const group of tagGroups) {
|
|
279
282
|
if (group.entries.length === 0) continue;
|
|
280
283
|
|
|
281
|
-
//
|
|
282
|
-
const
|
|
283
|
-
const
|
|
284
|
-
|
|
285
|
-
|
|
284
|
+
// Filter entries to only values actually used by nodes (if provided)
|
|
285
|
+
const usedValues = usedValuesByGroup?.get(group.name.toLowerCase());
|
|
286
|
+
const visibleEntries = usedValues
|
|
287
|
+
? group.entries.filter((e) => usedValues.has(e.value.toLowerCase()))
|
|
288
|
+
: group.entries;
|
|
289
|
+
if (visibleEntries.length === 0) continue;
|
|
290
|
+
|
|
291
|
+
// Pill label shows just the group name (alias is for DSL shorthand only)
|
|
292
|
+
const pillWidth = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
|
|
293
|
+
const minPillWidth = pillWidth;
|
|
286
294
|
|
|
287
295
|
// Capsule: pad + pill + gap + entries + pad
|
|
288
|
-
const isDefaultValue = group.defaultValue?.toLowerCase();
|
|
289
296
|
let entriesWidth = 0;
|
|
290
|
-
for (const entry of
|
|
291
|
-
const entryLabel = isDefaultValue === entry.value.toLowerCase()
|
|
292
|
-
? `${entry.value} (default)`
|
|
293
|
-
: entry.value;
|
|
297
|
+
for (const entry of visibleEntries) {
|
|
294
298
|
entriesWidth +=
|
|
295
299
|
LEGEND_DOT_R * 2 +
|
|
296
300
|
LEGEND_ENTRY_DOT_GAP +
|
|
297
|
-
|
|
301
|
+
entry.value.length * LEGEND_ENTRY_FONT_W +
|
|
298
302
|
LEGEND_ENTRY_TRAIL;
|
|
299
303
|
}
|
|
300
304
|
const capsuleWidth =
|
|
@@ -303,10 +307,9 @@ function computeLegendGroups(tagGroups: OrgTagGroup[], _showEyeIcons: boolean):
|
|
|
303
307
|
groups.push({
|
|
304
308
|
name: group.name,
|
|
305
309
|
alias: group.alias,
|
|
306
|
-
entries:
|
|
310
|
+
entries: visibleEntries.map((e) => ({
|
|
307
311
|
value: e.value,
|
|
308
312
|
color: e.color,
|
|
309
|
-
isDefault: group.defaultValue?.toLowerCase() === e.value.toLowerCase() || undefined,
|
|
310
313
|
})),
|
|
311
314
|
x: 0,
|
|
312
315
|
y: 0,
|
|
@@ -1101,9 +1104,24 @@ export function layoutOrg(
|
|
|
1101
1104
|
const totalWidth = finalMaxX - finalMinX + MARGIN * 2;
|
|
1102
1105
|
const totalHeight = finalMaxY - minY + MARGIN * 2;
|
|
1103
1106
|
|
|
1107
|
+
// Collect which tag group values are actually used by nodes
|
|
1108
|
+
const usedValuesByGroup = new Map<string, Set<string>>();
|
|
1109
|
+
for (const group of parsed.tagGroups) {
|
|
1110
|
+
const key = group.name.toLowerCase();
|
|
1111
|
+
const used = new Set<string>();
|
|
1112
|
+
const walk = (node: OrgNode) => {
|
|
1113
|
+
if (!node.isContainer && node.metadata[key]) {
|
|
1114
|
+
used.add(node.metadata[key].toLowerCase());
|
|
1115
|
+
}
|
|
1116
|
+
for (const child of node.children) walk(child);
|
|
1117
|
+
};
|
|
1118
|
+
for (const root of parsed.roots) walk(root);
|
|
1119
|
+
usedValuesByGroup.set(key, used);
|
|
1120
|
+
}
|
|
1121
|
+
|
|
1104
1122
|
// Compute legend for tag groups
|
|
1105
1123
|
const showEyeIcons = hiddenAttributes !== undefined;
|
|
1106
|
-
const legendGroups = computeLegendGroups(parsed.tagGroups, showEyeIcons);
|
|
1124
|
+
const legendGroups = computeLegendGroups(parsed.tagGroups, showEyeIcons, usedValuesByGroup);
|
|
1107
1125
|
let finalWidth = totalWidth;
|
|
1108
1126
|
let finalHeight = totalHeight;
|
|
1109
1127
|
|
package/src/org/renderer.ts
CHANGED
|
@@ -77,14 +77,12 @@ function nodeFill(
|
|
|
77
77
|
isDark: boolean,
|
|
78
78
|
nodeColor?: string
|
|
79
79
|
): string {
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
return mix(palette.primary, isDark ? palette.surface : palette.bg, 15);
|
|
80
|
+
const color = nodeColor ?? palette.primary;
|
|
81
|
+
return mix(color, isDark ? palette.surface : palette.bg, 25);
|
|
84
82
|
}
|
|
85
83
|
|
|
86
84
|
function nodeStroke(palette: PaletteColors, nodeColor?: string): string {
|
|
87
|
-
return nodeColor ?? palette.
|
|
85
|
+
return nodeColor ?? palette.primary;
|
|
88
86
|
}
|
|
89
87
|
|
|
90
88
|
function containerFill(
|
|
@@ -206,6 +204,15 @@ export function renderOrg(
|
|
|
206
204
|
.attr('class', 'org-container')
|
|
207
205
|
.attr('data-line-number', String(c.lineNumber)) as GSelection;
|
|
208
206
|
|
|
207
|
+
// Expose active tag group value for legend-entry hover dimming
|
|
208
|
+
if (activeTagGroup) {
|
|
209
|
+
const tagKey = activeTagGroup.toLowerCase();
|
|
210
|
+
const metaValue = c.metadata[tagKey];
|
|
211
|
+
if (metaValue) {
|
|
212
|
+
cG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
209
216
|
// Toggle attribute for containers that have (or had) children
|
|
210
217
|
if (c.hasChildren) {
|
|
211
218
|
cG.attr('data-node-toggle', c.nodeId)
|
|
@@ -333,6 +340,15 @@ export function renderOrg(
|
|
|
333
340
|
.attr('class', 'org-node')
|
|
334
341
|
.attr('data-line-number', String(node.lineNumber)) as GSelection;
|
|
335
342
|
|
|
343
|
+
// Expose active tag group value for legend-entry hover dimming
|
|
344
|
+
if (activeTagGroup) {
|
|
345
|
+
const tagKey = activeTagGroup.toLowerCase();
|
|
346
|
+
const metaValue = node.metadata[tagKey];
|
|
347
|
+
if (metaValue) {
|
|
348
|
+
nodeG.attr(`data-tag-${tagKey}`, metaValue.toLowerCase());
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
336
352
|
// Toggle attribute for nodes that have (or had) children
|
|
337
353
|
if (node.hasChildren) {
|
|
338
354
|
nodeG
|
|
@@ -464,8 +480,8 @@ export function renderOrg(
|
|
|
464
480
|
? mix(palette.surface, palette.bg, 50)
|
|
465
481
|
: mix(palette.surface, palette.bg, 30);
|
|
466
482
|
|
|
467
|
-
// Pill label:
|
|
468
|
-
const pillLabel =
|
|
483
|
+
// Pill label: just the group name (alias is for DSL shorthand only)
|
|
484
|
+
const pillLabel = group.name;
|
|
469
485
|
const pillWidth =
|
|
470
486
|
pillLabel.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
|
|
471
487
|
|
|
@@ -529,7 +545,12 @@ export function renderOrg(
|
|
|
529
545
|
if (isActive) {
|
|
530
546
|
let entryX = pillX + pillWidth + 4;
|
|
531
547
|
for (const entry of group.entries) {
|
|
532
|
-
gEl
|
|
548
|
+
const entryG = gEl
|
|
549
|
+
.append('g')
|
|
550
|
+
.attr('data-legend-entry', entry.value.toLowerCase())
|
|
551
|
+
.style('cursor', 'pointer');
|
|
552
|
+
|
|
553
|
+
entryG
|
|
533
554
|
.append('circle')
|
|
534
555
|
.attr('cx', entryX + LEGEND_DOT_R)
|
|
535
556
|
.attr('cy', LEGEND_HEIGHT / 2)
|
|
@@ -537,8 +558,8 @@ export function renderOrg(
|
|
|
537
558
|
.attr('fill', entry.color);
|
|
538
559
|
|
|
539
560
|
const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
|
|
540
|
-
const entryLabel = entry.
|
|
541
|
-
|
|
561
|
+
const entryLabel = entry.value;
|
|
562
|
+
entryG
|
|
542
563
|
.append('text')
|
|
543
564
|
.attr('x', textX)
|
|
544
565
|
.attr('y', LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1)
|
package/src/org/resolver.ts
CHANGED
|
@@ -200,6 +200,7 @@ async function resolveFile(
|
|
|
200
200
|
const bodyStartIndex = findBodyStart(lines);
|
|
201
201
|
|
|
202
202
|
// Collect header lines (chart:, title:, options, tags:)
|
|
203
|
+
let tagsLineNumber = 0; // 1-based line number of the tags: directive
|
|
203
204
|
for (let i = 0; i < bodyStartIndex; i++) {
|
|
204
205
|
const trimmed = lines[i].trim();
|
|
205
206
|
if (trimmed === '' || trimmed.startsWith('//')) {
|
|
@@ -212,6 +213,7 @@ async function resolveFile(
|
|
|
212
213
|
const tagsMatch = trimmed.match(TAGS_RE);
|
|
213
214
|
if (tagsMatch) {
|
|
214
215
|
tagsDirective = tagsMatch[1].trim();
|
|
216
|
+
tagsLineNumber = i + 1; // 1-based
|
|
215
217
|
continue;
|
|
216
218
|
}
|
|
217
219
|
|
|
@@ -228,7 +230,7 @@ async function resolveFile(
|
|
|
228
230
|
tagsFileGroups = extractTagGroups(tagsLines);
|
|
229
231
|
} catch {
|
|
230
232
|
diagnostics.push(
|
|
231
|
-
makeDgmoError(
|
|
233
|
+
makeDgmoError(tagsLineNumber, `Tags file not found: ${tagsDirective}`)
|
|
232
234
|
);
|
|
233
235
|
}
|
|
234
236
|
}
|
package/src/render.ts
CHANGED
|
@@ -49,6 +49,9 @@ export async function render(
|
|
|
49
49
|
theme?: 'light' | 'dark' | 'transparent';
|
|
50
50
|
palette?: string;
|
|
51
51
|
branding?: boolean;
|
|
52
|
+
c4Level?: 'context' | 'containers' | 'components' | 'deployment';
|
|
53
|
+
c4System?: string;
|
|
54
|
+
c4Container?: string;
|
|
52
55
|
},
|
|
53
56
|
): Promise<string> {
|
|
54
57
|
const theme = options?.theme ?? 'light';
|
|
@@ -66,5 +69,10 @@ export async function render(
|
|
|
66
69
|
|
|
67
70
|
// D3 and unknown/null frameworks both go through D3 renderer
|
|
68
71
|
await ensureDom();
|
|
69
|
-
return renderD3ForExport(content, theme, paletteColors, undefined, {
|
|
72
|
+
return renderD3ForExport(content, theme, paletteColors, undefined, {
|
|
73
|
+
branding,
|
|
74
|
+
c4Level: options?.c4Level,
|
|
75
|
+
c4System: options?.c4System,
|
|
76
|
+
c4Container: options?.c4Container,
|
|
77
|
+
});
|
|
70
78
|
}
|
|
@@ -203,6 +203,7 @@ const PARTICIPANT_RULES: readonly InferenceRule[] = [
|
|
|
203
203
|
{ pattern: /User$/i, type: 'actor' },
|
|
204
204
|
{ pattern: /Actor$/i, type: 'actor' },
|
|
205
205
|
{ pattern: /Analyst$/i, type: 'actor' },
|
|
206
|
+
{ pattern: /Staff$/i, type: 'actor' },
|
|
206
207
|
|
|
207
208
|
// ── 7. Frontend patterns ────────────────────────────────
|
|
208
209
|
{ pattern: /App$/i, type: 'frontend' },
|
package/src/sequence/renderer.ts
CHANGED
|
@@ -1925,7 +1925,8 @@ export function renderSequenceDiagram(
|
|
|
1925
1925
|
'data-line-number',
|
|
1926
1926
|
String(messages[step.messageIndex].lineNumber)
|
|
1927
1927
|
)
|
|
1928
|
-
.attr('data-msg-index', String(step.messageIndex))
|
|
1928
|
+
.attr('data-msg-index', String(step.messageIndex))
|
|
1929
|
+
.attr('data-step-index', String(i));
|
|
1929
1930
|
|
|
1930
1931
|
if (step.label) {
|
|
1931
1932
|
const labelEl = svg
|
|
@@ -1940,7 +1941,8 @@ export function renderSequenceDiagram(
|
|
|
1940
1941
|
'data-line-number',
|
|
1941
1942
|
String(messages[step.messageIndex].lineNumber)
|
|
1942
1943
|
)
|
|
1943
|
-
.attr('data-msg-index', String(step.messageIndex))
|
|
1944
|
+
.attr('data-msg-index', String(step.messageIndex))
|
|
1945
|
+
.attr('data-step-index', String(i));
|
|
1944
1946
|
renderInlineText(labelEl, step.label, palette);
|
|
1945
1947
|
}
|
|
1946
1948
|
} else {
|
|
@@ -1966,7 +1968,8 @@ export function renderSequenceDiagram(
|
|
|
1966
1968
|
'data-line-number',
|
|
1967
1969
|
String(messages[step.messageIndex].lineNumber)
|
|
1968
1970
|
)
|
|
1969
|
-
.attr('data-msg-index', String(step.messageIndex))
|
|
1971
|
+
.attr('data-msg-index', String(step.messageIndex))
|
|
1972
|
+
.attr('data-step-index', String(i));
|
|
1970
1973
|
|
|
1971
1974
|
if (step.label) {
|
|
1972
1975
|
const midX = (x1 + x2) / 2;
|
|
@@ -1982,7 +1985,8 @@ export function renderSequenceDiagram(
|
|
|
1982
1985
|
'data-line-number',
|
|
1983
1986
|
String(messages[step.messageIndex].lineNumber)
|
|
1984
1987
|
)
|
|
1985
|
-
.attr('data-msg-index', String(step.messageIndex))
|
|
1988
|
+
.attr('data-msg-index', String(step.messageIndex))
|
|
1989
|
+
.attr('data-step-index', String(i));
|
|
1986
1990
|
renderInlineText(labelEl, step.label, palette);
|
|
1987
1991
|
}
|
|
1988
1992
|
}
|
|
@@ -2011,7 +2015,8 @@ export function renderSequenceDiagram(
|
|
|
2011
2015
|
'data-line-number',
|
|
2012
2016
|
String(messages[step.messageIndex].lineNumber)
|
|
2013
2017
|
)
|
|
2014
|
-
.attr('data-msg-index', String(step.messageIndex))
|
|
2018
|
+
.attr('data-msg-index', String(step.messageIndex))
|
|
2019
|
+
.attr('data-step-index', String(i));
|
|
2015
2020
|
|
|
2016
2021
|
if (step.label) {
|
|
2017
2022
|
const midX = (x1 + x2) / 2;
|
|
@@ -2027,7 +2032,8 @@ export function renderSequenceDiagram(
|
|
|
2027
2032
|
'data-line-number',
|
|
2028
2033
|
String(messages[step.messageIndex].lineNumber)
|
|
2029
2034
|
)
|
|
2030
|
-
.attr('data-msg-index', String(step.messageIndex))
|
|
2035
|
+
.attr('data-msg-index', String(step.messageIndex))
|
|
2036
|
+
.attr('data-step-index', String(i));
|
|
2031
2037
|
renderInlineText(labelEl, step.label, palette);
|
|
2032
2038
|
}
|
|
2033
2039
|
}
|