@diagrammo/dgmo 0.8.23 → 0.8.26
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/.claude/commands/dgmo.md +43 -431
- package/.cursorrules +2 -2
- package/.windsurfrules +2 -2
- package/AGENTS.md +8 -5
- package/dist/cli.cjs +119 -114
- package/dist/editor.cjs +0 -2
- package/dist/editor.cjs.map +1 -1
- package/dist/editor.js +0 -2
- package/dist/editor.js.map +1 -1
- package/dist/highlight.cjs +0 -2
- package/dist/highlight.cjs.map +1 -1
- package/dist/highlight.js +0 -2
- package/dist/highlight.js.map +1 -1
- package/dist/index.cjs +719 -281
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +105 -18
- package/dist/index.d.ts +105 -18
- package/dist/index.js +709 -280
- package/dist/index.js.map +1 -1
- package/dist/internal.cjs +348 -51
- package/dist/internal.cjs.map +1 -1
- package/dist/internal.d.cts +93 -5
- package/dist/internal.d.ts +93 -5
- package/dist/internal.js +334 -38
- package/dist/internal.js.map +1 -1
- package/docs/guide/chart-area.md +17 -17
- package/docs/guide/chart-bar-stacked.md +12 -12
- package/docs/guide/chart-doughnut.md +10 -10
- package/docs/guide/chart-funnel.md +9 -9
- package/docs/guide/chart-heatmap.md +10 -10
- package/docs/guide/chart-kanban.md +2 -0
- package/docs/guide/chart-line.md +19 -19
- package/docs/guide/chart-multi-line.md +16 -16
- package/docs/guide/chart-pie.md +11 -11
- package/docs/guide/chart-polar-area.md +10 -10
- package/docs/guide/chart-radar.md +9 -9
- package/docs/guide/chart-scatter.md +24 -27
- package/docs/guide/index.md +3 -3
- package/docs/language-reference.md +46 -25
- package/fonts/Inter-Bold.ttf +0 -0
- package/fonts/Inter-Regular.ttf +0 -0
- package/fonts/LICENSE-Inter.txt +92 -0
- package/gallery/fixtures/bar-stacked.dgmo +12 -6
- package/gallery/fixtures/heatmap.dgmo +12 -6
- package/gallery/fixtures/multi-line.dgmo +11 -7
- package/gallery/fixtures/quadrant.dgmo +8 -8
- package/gallery/fixtures/scatter.dgmo +12 -12
- package/package.json +10 -3
- package/src/boxes-and-lines/parser.ts +13 -2
- package/src/boxes-and-lines/renderer.ts +22 -13
- package/src/chart-type-scoring.ts +162 -0
- package/src/chart-types.ts +437 -0
- package/src/cli.ts +147 -66
- package/src/completion.ts +0 -4
- package/src/d3.ts +40 -2
- package/src/dgmo-router.ts +85 -130
- package/src/editor/keywords.ts +0 -2
- package/src/fonts.ts +3 -2
- package/src/gantt/parser.ts +5 -1
- package/src/index.ts +24 -1
- package/src/infra/parser.ts +1 -1
- package/src/internal.ts +6 -2
- package/src/journey-map/layout.ts +8 -6
- package/src/journey-map/parser.ts +5 -1
- package/src/kanban/parser.ts +5 -1
- package/src/org/collapse.ts +1 -4
- package/src/org/parser.ts +1 -1
- package/src/org/renderer.ts +26 -17
- package/src/sequence/parser.ts +2 -2
- package/src/sequence/participant-inference.ts +0 -1
- package/src/sequence/renderer.ts +95 -263
- package/src/sharing.ts +0 -1
- package/src/sitemap/parser.ts +1 -1
- package/src/tech-radar/layout.ts +1 -2
- package/src/tech-radar/shared.ts +1 -37
- package/src/utils/tag-groups.ts +35 -5
- package/src/wireframe/parser.ts +3 -1
- package/src/tech-radar/index.ts +0 -14
package/src/sequence/renderer.ts
CHANGED
|
@@ -62,8 +62,6 @@ const NOTE_CHAR_W = 6;
|
|
|
62
62
|
const NOTE_CHARS_PER_LINE = Math.floor(
|
|
63
63
|
(NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W
|
|
64
64
|
);
|
|
65
|
-
const COLLAPSED_NOTE_H = 20;
|
|
66
|
-
const COLLAPSED_NOTE_W = 40;
|
|
67
65
|
const ACTIVATION_WIDTH = 10;
|
|
68
66
|
const SELF_CALL_HEIGHT = 25;
|
|
69
67
|
const SELF_CALL_WIDTH = 30;
|
|
@@ -557,13 +555,8 @@ export interface SectionMessageGroup {
|
|
|
557
555
|
export interface SequenceRenderOptions {
|
|
558
556
|
collapsedSections?: Set<number>; // keyed by section lineNumber
|
|
559
557
|
collapsedGroups?: Set<number>; // keyed by group lineNumber
|
|
560
|
-
expandedNoteLines?: Set<number>; // keyed by note lineNumber; undefined = all expanded (CLI default)
|
|
561
558
|
exportWidth?: number; // Explicit width for CLI/export rendering (bypasses getBoundingClientRect)
|
|
562
559
|
activeTagGroup?: string | null; // Active tag group name for tag-driven recoloring; null = explicitly none
|
|
563
|
-
expandAllNotes?: boolean; // Whether the "Expand Notes" toggle is active
|
|
564
|
-
onExpandAllNotes?: (expand: boolean) => void; // Toggle all notes expanded/collapsed
|
|
565
|
-
controlsExpanded?: boolean; // Controls group expanded state (managed by React)
|
|
566
|
-
onToggleControlsExpand?: () => void; // Callback to toggle controls group
|
|
567
560
|
}
|
|
568
561
|
|
|
569
562
|
/**
|
|
@@ -960,15 +953,6 @@ export function renderSequenceDiagram(
|
|
|
960
953
|
const collapsedGroupIds = collapsed?.collapsedGroupIds ?? new Map();
|
|
961
954
|
|
|
962
955
|
const collapsedSections = options?.collapsedSections;
|
|
963
|
-
const expandedNoteLines = options?.expandedNoteLines;
|
|
964
|
-
const collapseNotesDisabled =
|
|
965
|
-
parsedOptions['collapse-notes']?.toLowerCase() === 'no';
|
|
966
|
-
// A note is expanded if: expandedNoteLines is undefined (CLI/export),
|
|
967
|
-
// collapse-notes: no is set, or the note's lineNumber is in the set.
|
|
968
|
-
const isNoteExpanded = (note: SequenceNote): boolean =>
|
|
969
|
-
expandedNoteLines === undefined ||
|
|
970
|
-
collapseNotesDisabled ||
|
|
971
|
-
expandedNoteLines.has(note.lineNumber);
|
|
972
956
|
|
|
973
957
|
const sourceParticipants = collapsed
|
|
974
958
|
? collapsed.participants
|
|
@@ -1232,9 +1216,7 @@ export function renderSequenceDiagram(
|
|
|
1232
1216
|
const note = els[j] as SequenceNote;
|
|
1233
1217
|
const sc = isNoteAfterSelfCall(note);
|
|
1234
1218
|
const maxW = noteEffectiveMaxW(note.participantId, note.position, sc);
|
|
1235
|
-
const noteH =
|
|
1236
|
-
? computeNoteHeight(note.text, charsForWidth(maxW))
|
|
1237
|
-
: COLLAPSED_NOTE_H;
|
|
1219
|
+
const noteH = computeNoteHeight(note.text, charsForWidth(maxW));
|
|
1238
1220
|
totalExtent += noteH + NOTE_OFFSET_BELOW;
|
|
1239
1221
|
j++;
|
|
1240
1222
|
}
|
|
@@ -1500,9 +1482,10 @@ export function renderSequenceDiagram(
|
|
|
1500
1482
|
prevNote.position,
|
|
1501
1483
|
isNoteAfterSelfCall(prevNote)
|
|
1502
1484
|
);
|
|
1503
|
-
const prevNoteH =
|
|
1504
|
-
|
|
1505
|
-
|
|
1485
|
+
const prevNoteH = computeNoteHeight(
|
|
1486
|
+
prevNote.text,
|
|
1487
|
+
charsForWidth(prevMaxW)
|
|
1488
|
+
);
|
|
1506
1489
|
noteTopY = prevNoteY + prevNoteH + NOTE_OFFSET_BELOW;
|
|
1507
1490
|
} else {
|
|
1508
1491
|
// First note after a message — use larger offset after self-calls
|
|
@@ -1539,9 +1522,7 @@ export function renderSequenceDiagram(
|
|
|
1539
1522
|
note.position,
|
|
1540
1523
|
isNoteAfterSelfCall(note)
|
|
1541
1524
|
);
|
|
1542
|
-
const noteH =
|
|
1543
|
-
? computeNoteHeight(note.text, charsForWidth(maxW))
|
|
1544
|
-
: COLLAPSED_NOTE_H;
|
|
1525
|
+
const noteH = computeNoteHeight(note.text, charsForWidth(maxW));
|
|
1545
1526
|
contentBottomY = Math.max(
|
|
1546
1527
|
contentBottomY,
|
|
1547
1528
|
noteTopY + noteH + NOTE_TRAILING_GAP
|
|
@@ -1736,32 +1717,6 @@ export function renderSequenceDiagram(
|
|
|
1736
1717
|
}
|
|
1737
1718
|
}
|
|
1738
1719
|
|
|
1739
|
-
// Collect all note line numbers (for controls group visibility + "all expanded" check)
|
|
1740
|
-
const allNoteLineNumbers: number[] = [];
|
|
1741
|
-
const collectNoteLines = (els: SequenceElement[]): void => {
|
|
1742
|
-
for (const el of els) {
|
|
1743
|
-
if (isSequenceNote(el)) {
|
|
1744
|
-
allNoteLineNumbers.push(el.lineNumber);
|
|
1745
|
-
} else if (isSequenceBlock(el)) {
|
|
1746
|
-
collectNoteLines(el.children);
|
|
1747
|
-
if ('elseChildren' in el) collectNoteLines(el.elseChildren);
|
|
1748
|
-
if ('branches' in el && Array.isArray(el.branches)) {
|
|
1749
|
-
for (const branch of el.branches) {
|
|
1750
|
-
collectNoteLines(branch.children);
|
|
1751
|
-
}
|
|
1752
|
-
}
|
|
1753
|
-
}
|
|
1754
|
-
}
|
|
1755
|
-
};
|
|
1756
|
-
collectNoteLines(elements);
|
|
1757
|
-
|
|
1758
|
-
// Show controls group only in interactive mode (expandedNoteLines defined)
|
|
1759
|
-
// when notes exist and collapse-notes is not disabled
|
|
1760
|
-
const showNotesControl =
|
|
1761
|
-
allNoteLineNumbers.length > 0 &&
|
|
1762
|
-
!collapseNotesDisabled &&
|
|
1763
|
-
expandedNoteLines !== undefined;
|
|
1764
|
-
|
|
1765
1720
|
const hasTagGroups = parsed.tagGroups.length > 0;
|
|
1766
1721
|
|
|
1767
1722
|
// Build set of collapsed group names for drill-bar rendering
|
|
@@ -2632,8 +2587,6 @@ export function renderSequenceDiagram(
|
|
|
2632
2587
|
? mix(palette.surface, palette.bg, 50)
|
|
2633
2588
|
: mix(palette.bg, palette.surface, 15);
|
|
2634
2589
|
|
|
2635
|
-
const collapsedNoteFill = mix(palette.textMuted, palette.bg, 15);
|
|
2636
|
-
|
|
2637
2590
|
const renderNoteElements = (els: SequenceElement[]): void => {
|
|
2638
2591
|
for (const el of els) {
|
|
2639
2592
|
if (isSequenceNote(el)) {
|
|
@@ -2642,168 +2595,98 @@ export function renderSequenceDiagram(
|
|
|
2642
2595
|
const noteTopY = noteYMap.get(el);
|
|
2643
2596
|
if (noteTopY === undefined) continue;
|
|
2644
2597
|
|
|
2645
|
-
const expanded = isNoteExpanded(el);
|
|
2646
2598
|
const isRight = el.position === 'right';
|
|
2599
|
+
const afterSelfCall = isNoteAfterSelfCall(el);
|
|
2600
|
+
const maxW = noteEffectiveMaxW(
|
|
2601
|
+
el.participantId,
|
|
2602
|
+
el.position,
|
|
2603
|
+
afterSelfCall
|
|
2604
|
+
);
|
|
2605
|
+
const maxChars = charsForWidth(maxW);
|
|
2606
|
+
const wrappedLines = wrapTextLines(el.text, maxChars);
|
|
2607
|
+
const noteH = wrappedLines.length * NOTE_LINE_H + NOTE_PAD_V * 2;
|
|
2608
|
+
const maxLineLen = Math.max(...wrappedLines.map((l) => l.length));
|
|
2609
|
+
const noteW = Math.min(
|
|
2610
|
+
maxW,
|
|
2611
|
+
Math.max(80, maxLineLen * NOTE_CHAR_W + NOTE_PAD_H * 2 + NOTE_FOLD)
|
|
2612
|
+
);
|
|
2613
|
+
// Shift notes past self-call loopback when applicable
|
|
2614
|
+
const rightOffset =
|
|
2615
|
+
afterSelfCall && isRight
|
|
2616
|
+
? ACTIVATION_WIDTH / 2 + SELF_CALL_WIDTH + NOTE_GAP
|
|
2617
|
+
: ACTIVATION_WIDTH + NOTE_GAP;
|
|
2618
|
+
const noteX = isRight
|
|
2619
|
+
? px + rightOffset
|
|
2620
|
+
: px - ACTIVATION_WIDTH - NOTE_GAP - noteW;
|
|
2621
|
+
|
|
2622
|
+
const noteG = svg
|
|
2623
|
+
.append('g')
|
|
2624
|
+
.attr('class', 'note')
|
|
2625
|
+
.attr('data-note-toggle', '')
|
|
2626
|
+
.attr('data-line-number', String(el.lineNumber))
|
|
2627
|
+
.attr('data-line-end', String(el.endLineNumber));
|
|
2628
|
+
|
|
2629
|
+
// Folded-corner path
|
|
2630
|
+
noteG
|
|
2631
|
+
.append('path')
|
|
2632
|
+
.attr(
|
|
2633
|
+
'd',
|
|
2634
|
+
[
|
|
2635
|
+
`M ${noteX} ${noteTopY}`,
|
|
2636
|
+
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
2637
|
+
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
2638
|
+
`L ${noteX + noteW} ${noteTopY + noteH}`,
|
|
2639
|
+
`L ${noteX} ${noteTopY + noteH}`,
|
|
2640
|
+
'Z',
|
|
2641
|
+
].join(' ')
|
|
2642
|
+
)
|
|
2643
|
+
.attr('fill', noteFill)
|
|
2644
|
+
.attr('stroke', palette.textMuted)
|
|
2645
|
+
.attr('stroke-width', 0.75)
|
|
2646
|
+
.attr('class', 'note-box');
|
|
2647
2647
|
|
|
2648
|
-
|
|
2649
|
-
|
|
2650
|
-
|
|
2651
|
-
|
|
2652
|
-
|
|
2653
|
-
|
|
2654
|
-
|
|
2655
|
-
|
|
2656
|
-
|
|
2657
|
-
|
|
2658
|
-
|
|
2659
|
-
|
|
2660
|
-
|
|
2661
|
-
|
|
2662
|
-
|
|
2663
|
-
|
|
2664
|
-
|
|
2665
|
-
|
|
2666
|
-
|
|
2667
|
-
|
|
2668
|
-
|
|
2669
|
-
const
|
|
2670
|
-
|
|
2671
|
-
|
|
2672
|
-
|
|
2673
|
-
|
|
2674
|
-
.
|
|
2675
|
-
.attr('
|
|
2676
|
-
.attr('
|
|
2677
|
-
|
|
2678
|
-
|
|
2679
|
-
|
|
2680
|
-
// Folded-corner path
|
|
2681
|
-
noteG
|
|
2682
|
-
.append('path')
|
|
2683
|
-
.attr(
|
|
2684
|
-
'd',
|
|
2685
|
-
[
|
|
2686
|
-
`M ${noteX} ${noteTopY}`,
|
|
2687
|
-
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
2688
|
-
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
2689
|
-
`L ${noteX + noteW} ${noteTopY + noteH}`,
|
|
2690
|
-
`L ${noteX} ${noteTopY + noteH}`,
|
|
2691
|
-
'Z',
|
|
2692
|
-
].join(' ')
|
|
2693
|
-
)
|
|
2694
|
-
.attr('fill', noteFill)
|
|
2695
|
-
.attr('stroke', palette.textMuted)
|
|
2696
|
-
.attr('stroke-width', 0.75)
|
|
2697
|
-
.attr('class', 'note-box');
|
|
2698
|
-
|
|
2699
|
-
// Fold triangle
|
|
2700
|
-
noteG
|
|
2701
|
-
.append('path')
|
|
2702
|
-
.attr(
|
|
2703
|
-
'd',
|
|
2704
|
-
[
|
|
2705
|
-
`M ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
2706
|
-
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY + NOTE_FOLD}`,
|
|
2707
|
-
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
2708
|
-
].join(' ')
|
|
2709
|
-
)
|
|
2710
|
-
.attr('fill', 'none')
|
|
2711
|
-
.attr('stroke', palette.textMuted)
|
|
2712
|
-
.attr('stroke-width', 0.75)
|
|
2713
|
-
.attr('class', 'note-fold');
|
|
2714
|
-
|
|
2715
|
-
// Render text with inline markdown
|
|
2716
|
-
wrappedLines.forEach((line, li) => {
|
|
2717
|
-
const textY = noteTopY + NOTE_PAD_V + (li + 1) * NOTE_LINE_H - 3;
|
|
2718
|
-
const isBullet = line.startsWith('- ');
|
|
2719
|
-
const bulletIndent = isBullet ? 10 : 0;
|
|
2720
|
-
const displayLine = isBullet ? line.slice(2) : line;
|
|
2721
|
-
const textEl = noteG
|
|
2648
|
+
// Fold triangle
|
|
2649
|
+
noteG
|
|
2650
|
+
.append('path')
|
|
2651
|
+
.attr(
|
|
2652
|
+
'd',
|
|
2653
|
+
[
|
|
2654
|
+
`M ${noteX + noteW - NOTE_FOLD} ${noteTopY}`,
|
|
2655
|
+
`L ${noteX + noteW - NOTE_FOLD} ${noteTopY + NOTE_FOLD}`,
|
|
2656
|
+
`L ${noteX + noteW} ${noteTopY + NOTE_FOLD}`,
|
|
2657
|
+
].join(' ')
|
|
2658
|
+
)
|
|
2659
|
+
.attr('fill', 'none')
|
|
2660
|
+
.attr('stroke', palette.textMuted)
|
|
2661
|
+
.attr('stroke-width', 0.75)
|
|
2662
|
+
.attr('class', 'note-fold');
|
|
2663
|
+
|
|
2664
|
+
// Render text with inline markdown
|
|
2665
|
+
wrappedLines.forEach((line, li) => {
|
|
2666
|
+
const textY = noteTopY + NOTE_PAD_V + (li + 1) * NOTE_LINE_H - 3;
|
|
2667
|
+
const isBullet = line.startsWith('- ');
|
|
2668
|
+
const bulletIndent = isBullet ? 10 : 0;
|
|
2669
|
+
const displayLine = isBullet ? line.slice(2) : line;
|
|
2670
|
+
const textEl = noteG
|
|
2671
|
+
.append('text')
|
|
2672
|
+
.attr('x', noteX + NOTE_PAD_H + bulletIndent)
|
|
2673
|
+
.attr('y', textY)
|
|
2674
|
+
.attr('fill', palette.text)
|
|
2675
|
+
.attr('font-size', NOTE_FONT_SIZE)
|
|
2676
|
+
.attr('class', 'note-text');
|
|
2677
|
+
|
|
2678
|
+
if (isBullet) {
|
|
2679
|
+
noteG
|
|
2722
2680
|
.append('text')
|
|
2723
|
-
.attr('x', noteX + NOTE_PAD_H
|
|
2681
|
+
.attr('x', noteX + NOTE_PAD_H)
|
|
2724
2682
|
.attr('y', textY)
|
|
2725
2683
|
.attr('fill', palette.text)
|
|
2726
2684
|
.attr('font-size', NOTE_FONT_SIZE)
|
|
2727
|
-
.
|
|
2728
|
-
|
|
2729
|
-
if (isBullet) {
|
|
2730
|
-
noteG
|
|
2731
|
-
.append('text')
|
|
2732
|
-
.attr('x', noteX + NOTE_PAD_H)
|
|
2733
|
-
.attr('y', textY)
|
|
2734
|
-
.attr('fill', palette.text)
|
|
2735
|
-
.attr('font-size', NOTE_FONT_SIZE)
|
|
2736
|
-
.text('\u2022');
|
|
2737
|
-
}
|
|
2738
|
-
|
|
2739
|
-
renderInlineText(textEl, displayLine, palette, NOTE_FONT_SIZE);
|
|
2740
|
-
});
|
|
2741
|
-
} else {
|
|
2742
|
-
// --- Collapsed note: compact indicator ---
|
|
2743
|
-
const cFold = 6;
|
|
2744
|
-
const afterSelfCallC = isNoteAfterSelfCall(el);
|
|
2745
|
-
const rightOffsetC =
|
|
2746
|
-
afterSelfCallC && isRight
|
|
2747
|
-
? ACTIVATION_WIDTH / 2 + SELF_CALL_WIDTH + NOTE_GAP
|
|
2748
|
-
: ACTIVATION_WIDTH + NOTE_GAP;
|
|
2749
|
-
const noteX = isRight
|
|
2750
|
-
? px + rightOffsetC
|
|
2751
|
-
: px - ACTIVATION_WIDTH - NOTE_GAP - COLLAPSED_NOTE_W;
|
|
2752
|
-
|
|
2753
|
-
const noteG = svg
|
|
2754
|
-
.append('g')
|
|
2755
|
-
.attr('class', 'note note-collapsed')
|
|
2756
|
-
.attr('data-note-toggle', '')
|
|
2757
|
-
.attr('data-line-number', String(el.lineNumber))
|
|
2758
|
-
.attr('data-line-end', String(el.endLineNumber))
|
|
2759
|
-
.style('cursor', 'pointer');
|
|
2760
|
-
|
|
2761
|
-
// Small folded-corner rectangle
|
|
2762
|
-
noteG
|
|
2763
|
-
.append('path')
|
|
2764
|
-
.attr(
|
|
2765
|
-
'd',
|
|
2766
|
-
[
|
|
2767
|
-
`M ${noteX} ${noteTopY}`,
|
|
2768
|
-
`L ${noteX + COLLAPSED_NOTE_W - cFold} ${noteTopY}`,
|
|
2769
|
-
`L ${noteX + COLLAPSED_NOTE_W} ${noteTopY + cFold}`,
|
|
2770
|
-
`L ${noteX + COLLAPSED_NOTE_W} ${noteTopY + COLLAPSED_NOTE_H}`,
|
|
2771
|
-
`L ${noteX} ${noteTopY + COLLAPSED_NOTE_H}`,
|
|
2772
|
-
'Z',
|
|
2773
|
-
].join(' ')
|
|
2774
|
-
)
|
|
2775
|
-
.attr('fill', collapsedNoteFill)
|
|
2776
|
-
.attr('stroke', palette.border)
|
|
2777
|
-
.attr('stroke-width', 0.75)
|
|
2778
|
-
.attr('class', 'note-box');
|
|
2779
|
-
|
|
2780
|
-
// Fold triangle
|
|
2781
|
-
noteG
|
|
2782
|
-
.append('path')
|
|
2783
|
-
.attr(
|
|
2784
|
-
'd',
|
|
2785
|
-
[
|
|
2786
|
-
`M ${noteX + COLLAPSED_NOTE_W - cFold} ${noteTopY}`,
|
|
2787
|
-
`L ${noteX + COLLAPSED_NOTE_W - cFold} ${noteTopY + cFold}`,
|
|
2788
|
-
`L ${noteX + COLLAPSED_NOTE_W} ${noteTopY + cFold}`,
|
|
2789
|
-
].join(' ')
|
|
2790
|
-
)
|
|
2791
|
-
.attr('fill', 'none')
|
|
2792
|
-
.attr('stroke', palette.border)
|
|
2793
|
-
.attr('stroke-width', 0.75)
|
|
2794
|
-
.attr('class', 'note-fold');
|
|
2685
|
+
.text('\u2022');
|
|
2686
|
+
}
|
|
2795
2687
|
|
|
2796
|
-
|
|
2797
|
-
|
|
2798
|
-
.append('text')
|
|
2799
|
-
.attr('x', noteX + COLLAPSED_NOTE_W / 2)
|
|
2800
|
-
.attr('y', noteTopY + COLLAPSED_NOTE_H / 2 + 3)
|
|
2801
|
-
.attr('text-anchor', 'middle')
|
|
2802
|
-
.attr('fill', palette.textMuted)
|
|
2803
|
-
.attr('font-size', 9)
|
|
2804
|
-
.attr('class', 'note-text')
|
|
2805
|
-
.text('\u2026');
|
|
2806
|
-
}
|
|
2688
|
+
renderInlineText(textEl, displayLine, palette, NOTE_FONT_SIZE);
|
|
2689
|
+
});
|
|
2807
2690
|
} else if (isSequenceBlock(el)) {
|
|
2808
2691
|
renderNoteElements(el.children);
|
|
2809
2692
|
if (el.elseIfBranches) {
|
|
@@ -2822,9 +2705,7 @@ export function renderSequenceDiagram(
|
|
|
2822
2705
|
|
|
2823
2706
|
// Render legend LAST so it sits on top of all other SVG elements
|
|
2824
2707
|
// (group boxes, lifelines, participants, etc.) and can receive clicks.
|
|
2825
|
-
if (hasTagGroups
|
|
2826
|
-
const controlsExpanded = options?.controlsExpanded ?? false;
|
|
2827
|
-
|
|
2708
|
+
if (hasTagGroups) {
|
|
2828
2709
|
const legendY = TOP_MARGIN + titleOffset;
|
|
2829
2710
|
const resolvedGroups = parsed.tagGroups
|
|
2830
2711
|
.filter((tg) => tg.entries.length > 0)
|
|
@@ -2836,41 +2717,17 @@ export function renderSequenceDiagram(
|
|
|
2836
2717
|
})),
|
|
2837
2718
|
}));
|
|
2838
2719
|
|
|
2839
|
-
const allExpanded = showNotesControl && (options?.expandAllNotes ?? false);
|
|
2840
|
-
|
|
2841
|
-
const controlsGroup = showNotesControl
|
|
2842
|
-
? {
|
|
2843
|
-
toggles: [
|
|
2844
|
-
{
|
|
2845
|
-
id: 'expand-all-notes',
|
|
2846
|
-
type: 'toggle' as const,
|
|
2847
|
-
label: 'Expand Notes',
|
|
2848
|
-
active: allExpanded,
|
|
2849
|
-
onToggle: () => {},
|
|
2850
|
-
},
|
|
2851
|
-
],
|
|
2852
|
-
}
|
|
2853
|
-
: undefined;
|
|
2854
|
-
|
|
2855
2720
|
const legendConfig: LegendConfig = {
|
|
2856
2721
|
groups: resolvedGroups,
|
|
2857
2722
|
position: { placement: 'top-center', titleRelation: 'below-title' },
|
|
2858
2723
|
mode: 'fixed',
|
|
2859
|
-
controlsGroup,
|
|
2860
2724
|
};
|
|
2861
2725
|
const legendState: LegendState = {
|
|
2862
2726
|
activeGroup: activeTagGroup ?? null,
|
|
2863
|
-
controlsExpanded,
|
|
2727
|
+
controlsExpanded: false,
|
|
2864
2728
|
};
|
|
2865
2729
|
|
|
2866
|
-
const legendCallbacks: LegendCallbacks = {
|
|
2867
|
-
onControlsExpand: () => {
|
|
2868
|
-
options?.onToggleControlsExpand?.();
|
|
2869
|
-
},
|
|
2870
|
-
onControlsToggle: (_toggleId: string, active: boolean) => {
|
|
2871
|
-
options?.onExpandAllNotes?.(active);
|
|
2872
|
-
},
|
|
2873
|
-
};
|
|
2730
|
+
const legendCallbacks: LegendCallbacks = {};
|
|
2874
2731
|
|
|
2875
2732
|
const legendG = svg
|
|
2876
2733
|
.append('g')
|
|
@@ -2891,7 +2748,7 @@ export function renderSequenceDiagram(
|
|
|
2891
2748
|
/**
|
|
2892
2749
|
* Build a mapping from each note's lineNumber to the lineNumber of its
|
|
2893
2750
|
* associated message (the last message before the note in document order).
|
|
2894
|
-
* Used by the app to
|
|
2751
|
+
* Used by the app to highlight the associated message when cursor is on a note.
|
|
2895
2752
|
*/
|
|
2896
2753
|
export function buildNoteMessageMap(
|
|
2897
2754
|
elements: SequenceElement[]
|
|
@@ -2924,31 +2781,6 @@ export function buildNoteMessageMap(
|
|
|
2924
2781
|
return map;
|
|
2925
2782
|
}
|
|
2926
2783
|
|
|
2927
|
-
/**
|
|
2928
|
-
* Collect all note line numbers from a sequence diagram's elements.
|
|
2929
|
-
* Used by the app to compute the "expand all" set.
|
|
2930
|
-
*/
|
|
2931
|
-
export function collectNoteLineNumbers(elements: SequenceElement[]): number[] {
|
|
2932
|
-
const result: number[] = [];
|
|
2933
|
-
const walk = (els: SequenceElement[]): void => {
|
|
2934
|
-
for (const el of els) {
|
|
2935
|
-
if (isSequenceNote(el)) {
|
|
2936
|
-
result.push(el.lineNumber);
|
|
2937
|
-
} else if (isSequenceBlock(el)) {
|
|
2938
|
-
walk(el.children);
|
|
2939
|
-
if (el.elseIfBranches) {
|
|
2940
|
-
for (const branch of el.elseIfBranches) {
|
|
2941
|
-
walk(branch.children);
|
|
2942
|
-
}
|
|
2943
|
-
}
|
|
2944
|
-
walk(el.elseChildren);
|
|
2945
|
-
}
|
|
2946
|
-
}
|
|
2947
|
-
};
|
|
2948
|
-
walk(elements);
|
|
2949
|
-
return result;
|
|
2950
|
-
}
|
|
2951
|
-
|
|
2952
2784
|
function renderParticipant(
|
|
2953
2785
|
svg: d3Selection.Selection<SVGSVGElement, unknown, null, undefined>,
|
|
2954
2786
|
participant: SequenceParticipant,
|
package/src/sharing.ts
CHANGED
|
@@ -21,7 +21,6 @@ export interface CompactViewState {
|
|
|
21
21
|
rm?: string; // render mode override
|
|
22
22
|
htv?: Record<string, string[]>; // hidden tag values
|
|
23
23
|
ha?: string[]; // hidden attributes
|
|
24
|
-
enl?: number[]; // expanded note lines (sequence)
|
|
25
24
|
sem?: boolean; // semantic colors (ER)
|
|
26
25
|
cm?: boolean; // compact meta (kanban)
|
|
27
26
|
c4l?: string; // C4 level
|
package/src/sitemap/parser.ts
CHANGED
|
@@ -509,7 +509,7 @@ export function parseSitemap(
|
|
|
509
509
|
};
|
|
510
510
|
collectAll(result.roots);
|
|
511
511
|
validateTagValues(allNodes, result.tagGroups, pushWarning, suggest);
|
|
512
|
-
validateTagGroupNames(result.tagGroups, pushWarning);
|
|
512
|
+
validateTagGroupNames(result.tagGroups, pushWarning, pushError);
|
|
513
513
|
}
|
|
514
514
|
|
|
515
515
|
if (
|
package/src/tech-radar/layout.ts
CHANGED
|
@@ -186,5 +186,4 @@ export function getRadarGeometry(
|
|
|
186
186
|
return { cx, cy, maxRadius, ringBandWidth: maxRadius / ringCount };
|
|
187
187
|
}
|
|
188
188
|
|
|
189
|
-
|
|
190
|
-
export { POSITION_ORDER, getQuadrantArc, BASE_BLIP_RADIUS, MIN_BLIP_RADIUS };
|
|
189
|
+
export { POSITION_ORDER, getQuadrantArc };
|
package/src/tech-radar/shared.ts
CHANGED
|
@@ -4,7 +4,7 @@ import type { PaletteColors } from '../palettes';
|
|
|
4
4
|
import type { QuadrantPosition, BlipTrend } from './types';
|
|
5
5
|
|
|
6
6
|
/** Default quadrant colors by position when not overridden. */
|
|
7
|
-
|
|
7
|
+
const DEFAULT_QUADRANT_COLORS: Record<QuadrantPosition, string> = {
|
|
8
8
|
'top-left': 'blue',
|
|
9
9
|
'top-right': 'green',
|
|
10
10
|
'bottom-left': 'red',
|
|
@@ -101,20 +101,6 @@ function renderCrescent(
|
|
|
101
101
|
.attr('stroke-linecap', 'round');
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
/** Trend indicator character for text listings. */
|
|
105
|
-
export function getTrendChar(trend: BlipTrend | null): string {
|
|
106
|
-
switch (trend) {
|
|
107
|
-
case 'new':
|
|
108
|
-
return ' ★';
|
|
109
|
-
case 'up':
|
|
110
|
-
return ' ▲';
|
|
111
|
-
case 'down':
|
|
112
|
-
return ' ▼';
|
|
113
|
-
default:
|
|
114
|
-
return '';
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
104
|
// ============================================================
|
|
119
105
|
// Shared Constants
|
|
120
106
|
// ============================================================
|
|
@@ -163,25 +149,3 @@ export function createTooltip(
|
|
|
163
149
|
container.appendChild(tip);
|
|
164
150
|
return tip;
|
|
165
151
|
}
|
|
166
|
-
|
|
167
|
-
export function showTooltip(
|
|
168
|
-
tooltip: HTMLDivElement,
|
|
169
|
-
text: string,
|
|
170
|
-
event: MouseEvent
|
|
171
|
-
): void {
|
|
172
|
-
tooltip.textContent = text;
|
|
173
|
-
tooltip.style.display = 'block';
|
|
174
|
-
const container = tooltip.parentElement!;
|
|
175
|
-
const rect = container.getBoundingClientRect();
|
|
176
|
-
let left = event.clientX - rect.left + 12;
|
|
177
|
-
let top = event.clientY - rect.top - 28;
|
|
178
|
-
const tipW = tooltip.offsetWidth;
|
|
179
|
-
if (left + tipW > rect.width) left = rect.width - tipW - 4;
|
|
180
|
-
if (top < 0) top = event.clientY - rect.top + 16;
|
|
181
|
-
tooltip.style.left = `${left}px`;
|
|
182
|
-
tooltip.style.top = `${top}px`;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
export function hideTooltip(tooltip: HTMLDivElement): void {
|
|
186
|
-
tooltip.style.display = 'none';
|
|
187
|
-
}
|
package/src/utils/tag-groups.ts
CHANGED
|
@@ -293,14 +293,32 @@ export function validateTagValues(
|
|
|
293
293
|
// ── Tag Group Name Validation ────────────────────────────
|
|
294
294
|
|
|
295
295
|
/**
|
|
296
|
-
*
|
|
297
|
-
*
|
|
298
|
-
*
|
|
296
|
+
* Valid identifier for use as a `data-tag-<name>` attribute suffix.
|
|
297
|
+
* Must start with a letter or underscore, then letters/digits/underscore/hyphen only.
|
|
298
|
+
* Spaces and punctuation are rejected because they produce invalid DOM attribute
|
|
299
|
+
* names (setAttribute throws "Invalid qualified name").
|
|
300
|
+
*/
|
|
301
|
+
const VALID_TAG_IDENT_RE = /^[A-Za-z_][A-Za-z0-9_-]*$/;
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Validate tag group names (and aliases) for reserved keywords and DOM-safe
|
|
305
|
+
* identifier syntax. Should be called alongside `validateTagValues()` in each
|
|
306
|
+
* parser's post-parse validation.
|
|
307
|
+
*
|
|
308
|
+
* - Reserved name `none` (case-insensitive) → warning
|
|
309
|
+
* - Name or alias containing chars invalid for a `data-tag-*` attribute → error
|
|
310
|
+
* (falls back to `pushWarning` if `pushError` is not supplied)
|
|
299
311
|
*/
|
|
300
312
|
export function validateTagGroupNames(
|
|
301
|
-
tagGroups: ReadonlyArray<{
|
|
302
|
-
|
|
313
|
+
tagGroups: ReadonlyArray<{
|
|
314
|
+
name: string;
|
|
315
|
+
alias?: string | null;
|
|
316
|
+
lineNumber: number;
|
|
317
|
+
}>,
|
|
318
|
+
pushWarning: (lineNumber: number, message: string) => void,
|
|
319
|
+
pushError?: (lineNumber: number, message: string) => void
|
|
303
320
|
): void {
|
|
321
|
+
const report = pushError ?? pushWarning;
|
|
304
322
|
for (const group of tagGroups) {
|
|
305
323
|
if (group.name.toLowerCase() === 'none') {
|
|
306
324
|
pushWarning(
|
|
@@ -308,6 +326,18 @@ export function validateTagGroupNames(
|
|
|
308
326
|
`'none' is a reserved keyword and cannot be used as a tag group name`
|
|
309
327
|
);
|
|
310
328
|
}
|
|
329
|
+
if (!VALID_TAG_IDENT_RE.test(group.name)) {
|
|
330
|
+
report(
|
|
331
|
+
group.lineNumber,
|
|
332
|
+
`Tag group name "${group.name}" contains invalid characters — use a single identifier (letters, digits, underscore, hyphen)`
|
|
333
|
+
);
|
|
334
|
+
}
|
|
335
|
+
if (group.alias != null && !VALID_TAG_IDENT_RE.test(group.alias)) {
|
|
336
|
+
report(
|
|
337
|
+
group.lineNumber,
|
|
338
|
+
`Tag group alias "${group.alias}" contains invalid characters — use a single identifier (letters, digits, underscore, hyphen)`
|
|
339
|
+
);
|
|
340
|
+
}
|
|
311
341
|
}
|
|
312
342
|
}
|
|
313
343
|
|
package/src/wireframe/parser.ts
CHANGED
|
@@ -871,7 +871,9 @@ export function parseWireframe(content: string): ParsedWireframe {
|
|
|
871
871
|
}
|
|
872
872
|
|
|
873
873
|
// Validate tag groups
|
|
874
|
-
validateTagGroupNames(tagGroups, pushWarning)
|
|
874
|
+
validateTagGroupNames(tagGroups, pushWarning, (line, msg) => {
|
|
875
|
+
diagnostics.push(makeDgmoError(line, msg));
|
|
876
|
+
});
|
|
875
877
|
|
|
876
878
|
const error = diagnostics.find((d) => d.severity === 'error')
|
|
877
879
|
? formatDgmoError(diagnostics.find((d) => d.severity === 'error')!)
|
package/src/tech-radar/index.ts
DELETED
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
export { parseTechRadar } from './parser';
|
|
2
|
-
export { computeRadarLayout, getRadarGeometry } from './layout';
|
|
3
|
-
export { renderTechRadar, renderTechRadarForExport } from './renderer';
|
|
4
|
-
export { renderQuadrantFocus } from './interactive';
|
|
5
|
-
export type {
|
|
6
|
-
ParsedTechRadar,
|
|
7
|
-
TechRadarRing,
|
|
8
|
-
TechRadarQuadrant,
|
|
9
|
-
TechRadarBlip,
|
|
10
|
-
TechRadarLayoutPoint,
|
|
11
|
-
QuadrantPosition,
|
|
12
|
-
BlipTrend,
|
|
13
|
-
TechRadarRenderOptions,
|
|
14
|
-
} from './types';
|