@diagrammo/dgmo 0.8.21 → 0.8.23

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (114) hide show
  1. package/AGENTS.md +2 -1
  2. package/README.md +1 -0
  3. package/dist/cli.cjs +145 -93
  4. package/dist/editor.cjs +20 -3
  5. package/dist/editor.cjs.map +1 -1
  6. package/dist/editor.js +20 -3
  7. package/dist/editor.js.map +1 -1
  8. package/dist/highlight.cjs +15 -2
  9. package/dist/highlight.cjs.map +1 -1
  10. package/dist/highlight.js +15 -2
  11. package/dist/highlight.js.map +1 -1
  12. package/dist/index.cjs +20843 -14937
  13. package/dist/index.cjs.map +1 -1
  14. package/dist/index.d.cts +426 -17
  15. package/dist/index.d.ts +426 -17
  16. package/dist/index.js +20795 -14912
  17. package/dist/index.js.map +1 -1
  18. package/dist/internal.cjs +380 -0
  19. package/dist/internal.cjs.map +1 -0
  20. package/dist/internal.d.cts +179 -0
  21. package/dist/internal.d.ts +179 -0
  22. package/dist/internal.js +337 -0
  23. package/dist/internal.js.map +1 -0
  24. package/docs/guide/chart-cycle.md +156 -0
  25. package/docs/guide/chart-journey-map.md +179 -0
  26. package/docs/guide/chart-pyramid.md +111 -0
  27. package/docs/guide/chart-sitemap.md +18 -1
  28. package/docs/guide/chart-tech-radar.md +219 -0
  29. package/docs/guide/registry.json +6 -0
  30. package/docs/language-reference.md +177 -6
  31. package/gallery/fixtures/boxes-and-lines.dgmo +10 -3
  32. package/gallery/fixtures/c4-full.dgmo +2 -2
  33. package/gallery/fixtures/cycle/ooda-loop.dgmo +25 -0
  34. package/gallery/fixtures/cycle/pdca-circle-nodes.dgmo +12 -0
  35. package/gallery/fixtures/cycle/pdca-minimal.dgmo +6 -0
  36. package/gallery/fixtures/cycle/sprint-cycle-span.dgmo +17 -0
  37. package/gallery/fixtures/gantt-full.dgmo +2 -2
  38. package/gallery/fixtures/gantt.dgmo +2 -2
  39. package/gallery/fixtures/infra-full.dgmo +2 -2
  40. package/gallery/fixtures/infra.dgmo +1 -1
  41. package/gallery/fixtures/pyramid/dikw.dgmo +17 -0
  42. package/gallery/fixtures/pyramid/inverted-funnel.dgmo +16 -0
  43. package/gallery/fixtures/pyramid/minimal.dgmo +5 -0
  44. package/gallery/fixtures/sequence-tags-protocols.dgmo +2 -2
  45. package/gallery/fixtures/sequence-tags.dgmo +2 -2
  46. package/gallery/fixtures/tech-radar-dense.dgmo +77 -0
  47. package/gallery/fixtures/tech-radar.dgmo +36 -0
  48. package/gallery/fixtures/timeline.dgmo +1 -1
  49. package/package.json +11 -1
  50. package/src/boxes-and-lines/layout.ts +309 -33
  51. package/src/boxes-and-lines/parser.ts +86 -10
  52. package/src/boxes-and-lines/renderer.ts +250 -91
  53. package/src/boxes-and-lines/types.ts +1 -1
  54. package/src/c4/layout.ts +8 -8
  55. package/src/c4/parser.ts +35 -2
  56. package/src/c4/renderer.ts +19 -3
  57. package/src/c4/types.ts +1 -0
  58. package/src/chart.ts +14 -7
  59. package/src/cli.ts +5 -35
  60. package/src/completion.ts +233 -41
  61. package/src/cycle/layout.ts +723 -0
  62. package/src/cycle/parser.ts +352 -0
  63. package/src/cycle/renderer.ts +566 -0
  64. package/src/cycle/types.ts +98 -0
  65. package/src/d3.ts +107 -8
  66. package/src/dgmo-router.ts +82 -3
  67. package/src/echarts.ts +8 -5
  68. package/src/editor/dgmo.grammar +5 -1
  69. package/src/editor/dgmo.grammar.js +1 -1
  70. package/src/editor/keywords.ts +17 -0
  71. package/src/gantt/parser.ts +2 -8
  72. package/src/graph/flowchart-parser.ts +15 -21
  73. package/src/graph/state-parser.ts +5 -10
  74. package/src/index.ts +63 -2
  75. package/src/infra/layout.ts +218 -74
  76. package/src/infra/parser.ts +32 -8
  77. package/src/infra/renderer.ts +14 -8
  78. package/src/infra/types.ts +10 -3
  79. package/src/internal.ts +16 -0
  80. package/src/journey-map/layout.ts +386 -0
  81. package/src/journey-map/parser.ts +540 -0
  82. package/src/journey-map/renderer.ts +1521 -0
  83. package/src/journey-map/types.ts +47 -0
  84. package/src/kanban/parser.ts +3 -10
  85. package/src/kanban/renderer.ts +31 -15
  86. package/src/mindmap/parser.ts +12 -18
  87. package/src/mindmap/renderer.ts +14 -13
  88. package/src/mindmap/text-wrap.ts +22 -12
  89. package/src/mindmap/types.ts +2 -2
  90. package/src/org/collapse.ts +81 -0
  91. package/src/org/parser.ts +2 -6
  92. package/src/org/renderer.ts +212 -4
  93. package/src/pyramid/parser.ts +172 -0
  94. package/src/pyramid/renderer.ts +684 -0
  95. package/src/pyramid/types.ts +28 -0
  96. package/src/render.ts +2 -8
  97. package/src/sequence/parser.ts +62 -20
  98. package/src/sequence/renderer.ts +146 -40
  99. package/src/sharing.ts +1 -0
  100. package/src/sitemap/layout.ts +21 -6
  101. package/src/sitemap/parser.ts +26 -17
  102. package/src/sitemap/renderer.ts +34 -0
  103. package/src/sitemap/types.ts +1 -0
  104. package/src/tech-radar/index.ts +14 -0
  105. package/src/tech-radar/interactive.ts +1112 -0
  106. package/src/tech-radar/layout.ts +190 -0
  107. package/src/tech-radar/parser.ts +385 -0
  108. package/src/tech-radar/renderer.ts +1159 -0
  109. package/src/tech-radar/shared.ts +187 -0
  110. package/src/tech-radar/types.ts +81 -0
  111. package/src/utils/description-helpers.ts +33 -0
  112. package/src/utils/legend-layout.ts +3 -1
  113. package/src/utils/parsing.ts +47 -7
  114. package/src/utils/tag-groups.ts +46 -60
@@ -0,0 +1,179 @@
1
+ type DgmoSeverity = 'error' | 'warning';
2
+ interface DgmoError {
3
+ line: number;
4
+ column?: number;
5
+ message: string;
6
+ severity: DgmoSeverity;
7
+ /**
8
+ * Optional stable diagnostic code (e.g. 'E_ARROW_SUBSTRING_IN_LABEL').
9
+ * Additive; pre-existing diagnostics omit this field and existing
10
+ * substring-on-`.message` assertions keep working unchanged.
11
+ */
12
+ code?: string;
13
+ }
14
+
15
+ /**
16
+ * Parse a data row line: everything before the last numeric token(s) is the label,
17
+ * numeric tokens at the end are the values. Supports comma-separated multi-values,
18
+ * space-separated multi-values, and comma-grouped numbers (e.g., "1,087").
19
+ *
20
+ * Examples:
21
+ * "Jan 120" → { label: "Jan", values: [120] }
22
+ * "North America 250" → { label: "North America", values: [250] }
23
+ * "Q1 10, 20, 30" → { label: "Q1", values: [10, 20, 30] }
24
+ * "Q1 10 20 30" → { label: "Q1", values: [10, 20, 30] }
25
+ * "Revenue 1,200" → { label: "Revenue", values: [1200] }
26
+ * "Revenue 3,984,078.65"→ { label: "Revenue", values: [3984078.65] }
27
+ *
28
+ * Returns null if the line has no numeric value at the end.
29
+ */
30
+ declare function parseDataRowValues(line: string, options?: {
31
+ multiValue?: boolean;
32
+ expectedValues?: number;
33
+ }): {
34
+ label: string;
35
+ values: number[];
36
+ } | null;
37
+
38
+ /** A single entry inside a tag group: `Value(color)` */
39
+ interface TagEntry {
40
+ value: string;
41
+ color: string;
42
+ lineNumber: number;
43
+ }
44
+ /** A tag group block: heading + entries */
45
+ interface TagGroup {
46
+ name: string;
47
+ alias?: string;
48
+ entries: TagEntry[];
49
+ /** Default value for nodes without explicit metadata. First entry unless another is marked `default`. */
50
+ defaultValue?: string;
51
+ lineNumber: number;
52
+ }
53
+
54
+ /** @deprecated Use `TagGroup` from `utils/tag-groups` */
55
+ type KanbanTagGroup = TagGroup;
56
+ interface KanbanCard {
57
+ id: string;
58
+ title: string;
59
+ tags: Record<string, string>;
60
+ details: string[];
61
+ lineNumber: number;
62
+ endLineNumber: number;
63
+ color?: string;
64
+ }
65
+ interface KanbanColumn {
66
+ id: string;
67
+ name: string;
68
+ wipLimit?: number;
69
+ color?: string;
70
+ metadata?: Record<string, string>;
71
+ cards: KanbanCard[];
72
+ lineNumber: number;
73
+ }
74
+ interface ParsedKanban {
75
+ type: 'kanban';
76
+ title?: string;
77
+ titleLineNumber?: number;
78
+ columns: KanbanColumn[];
79
+ tagGroups: KanbanTagGroup[];
80
+ options: Record<string, string>;
81
+ diagnostics: DgmoError[];
82
+ error: string | null;
83
+ }
84
+
85
+ /**
86
+ * Compute new file content after moving a card to a different position.
87
+ *
88
+ * @param content - original file content string
89
+ * @param parsed - parsed kanban board
90
+ * @param cardId - id of the card to move
91
+ * @param targetColumnId - id of the destination column
92
+ * @param targetIndex - position within target column (0 = first card)
93
+ * @returns new content string, or null if move is invalid
94
+ */
95
+ declare function computeCardMove(content: string, parsed: ParsedKanban, cardId: string, targetColumnId: string, targetIndex: number): string | null;
96
+ /**
97
+ * Move a card to the Archive section at the end of the file.
98
+ * Creates `== Archive ==` if it doesn't exist.
99
+ *
100
+ * @returns new content string, or null if the card is not found
101
+ */
102
+ declare function computeCardArchive(content: string, parsed: ParsedKanban, cardId: string): string | null;
103
+ /** Check if a column name is the archive column (case-insensitive). */
104
+ declare function isArchiveColumn(name: string): boolean;
105
+
106
+ /**
107
+ * A message between two participants.
108
+ */
109
+ interface SequenceMessage {
110
+ from: string;
111
+ to: string;
112
+ label: string;
113
+ lineNumber: number;
114
+ async?: boolean;
115
+ /** Pipe-delimited tag metadata (e.g. `| c: Caching`) */
116
+ metadata?: Record<string, string>;
117
+ }
118
+ /**
119
+ * A conditional or loop block in the sequence diagram.
120
+ */
121
+ interface ElseIfBranch {
122
+ label: string;
123
+ children: SequenceElement[];
124
+ lineNumber: number;
125
+ }
126
+ interface SequenceBlock {
127
+ kind: 'block';
128
+ type: 'if' | 'loop' | 'parallel';
129
+ label: string;
130
+ children: SequenceElement[];
131
+ elseChildren: SequenceElement[];
132
+ elseIfBranches?: ElseIfBranch[];
133
+ elseLineNumber?: number;
134
+ lineNumber: number;
135
+ }
136
+ /**
137
+ * A labeled horizontal divider between message phases.
138
+ */
139
+ interface SequenceSection {
140
+ kind: 'section';
141
+ label: string;
142
+ lineNumber: number;
143
+ }
144
+ /**
145
+ * An annotation attached to a message, rendered as a folded-corner box.
146
+ */
147
+ interface SequenceNote {
148
+ kind: 'note';
149
+ text: string;
150
+ position: 'right' | 'left';
151
+ participantId: string;
152
+ lineNumber: number;
153
+ endLineNumber: number;
154
+ }
155
+ type SequenceElement = SequenceMessage | SequenceBlock | SequenceSection | SequenceNote;
156
+
157
+ interface SectionMessageGroup {
158
+ section: SequenceSection;
159
+ messageIndices: number[];
160
+ }
161
+ /**
162
+ * Group messages by the top-level section that precedes them.
163
+ * Messages before the first section are ungrouped (always visible).
164
+ * Only top-level sections are collapsible — sections inside blocks are excluded.
165
+ */
166
+ declare function groupMessagesBySection(elements: SequenceElement[], messages: SequenceMessage[]): SectionMessageGroup[];
167
+ /**
168
+ * Build a mapping from each note's lineNumber to the lineNumber of its
169
+ * associated message (the last message before the note in document order).
170
+ * Used by the app to expand notes when cursor is on the associated message.
171
+ */
172
+ declare function buildNoteMessageMap(elements: SequenceElement[]): Map<number, number>;
173
+ /**
174
+ * Collect all note line numbers from a sequence diagram's elements.
175
+ * Used by the app to compute the "expand all" set.
176
+ */
177
+ declare function collectNoteLineNumbers(elements: SequenceElement[]): number[];
178
+
179
+ export { buildNoteMessageMap, collectNoteLineNumbers, computeCardArchive, computeCardMove, groupMessagesBySection, isArchiveColumn, parseDataRowValues };
@@ -0,0 +1,179 @@
1
+ type DgmoSeverity = 'error' | 'warning';
2
+ interface DgmoError {
3
+ line: number;
4
+ column?: number;
5
+ message: string;
6
+ severity: DgmoSeverity;
7
+ /**
8
+ * Optional stable diagnostic code (e.g. 'E_ARROW_SUBSTRING_IN_LABEL').
9
+ * Additive; pre-existing diagnostics omit this field and existing
10
+ * substring-on-`.message` assertions keep working unchanged.
11
+ */
12
+ code?: string;
13
+ }
14
+
15
+ /**
16
+ * Parse a data row line: everything before the last numeric token(s) is the label,
17
+ * numeric tokens at the end are the values. Supports comma-separated multi-values,
18
+ * space-separated multi-values, and comma-grouped numbers (e.g., "1,087").
19
+ *
20
+ * Examples:
21
+ * "Jan 120" → { label: "Jan", values: [120] }
22
+ * "North America 250" → { label: "North America", values: [250] }
23
+ * "Q1 10, 20, 30" → { label: "Q1", values: [10, 20, 30] }
24
+ * "Q1 10 20 30" → { label: "Q1", values: [10, 20, 30] }
25
+ * "Revenue 1,200" → { label: "Revenue", values: [1200] }
26
+ * "Revenue 3,984,078.65"→ { label: "Revenue", values: [3984078.65] }
27
+ *
28
+ * Returns null if the line has no numeric value at the end.
29
+ */
30
+ declare function parseDataRowValues(line: string, options?: {
31
+ multiValue?: boolean;
32
+ expectedValues?: number;
33
+ }): {
34
+ label: string;
35
+ values: number[];
36
+ } | null;
37
+
38
+ /** A single entry inside a tag group: `Value(color)` */
39
+ interface TagEntry {
40
+ value: string;
41
+ color: string;
42
+ lineNumber: number;
43
+ }
44
+ /** A tag group block: heading + entries */
45
+ interface TagGroup {
46
+ name: string;
47
+ alias?: string;
48
+ entries: TagEntry[];
49
+ /** Default value for nodes without explicit metadata. First entry unless another is marked `default`. */
50
+ defaultValue?: string;
51
+ lineNumber: number;
52
+ }
53
+
54
+ /** @deprecated Use `TagGroup` from `utils/tag-groups` */
55
+ type KanbanTagGroup = TagGroup;
56
+ interface KanbanCard {
57
+ id: string;
58
+ title: string;
59
+ tags: Record<string, string>;
60
+ details: string[];
61
+ lineNumber: number;
62
+ endLineNumber: number;
63
+ color?: string;
64
+ }
65
+ interface KanbanColumn {
66
+ id: string;
67
+ name: string;
68
+ wipLimit?: number;
69
+ color?: string;
70
+ metadata?: Record<string, string>;
71
+ cards: KanbanCard[];
72
+ lineNumber: number;
73
+ }
74
+ interface ParsedKanban {
75
+ type: 'kanban';
76
+ title?: string;
77
+ titleLineNumber?: number;
78
+ columns: KanbanColumn[];
79
+ tagGroups: KanbanTagGroup[];
80
+ options: Record<string, string>;
81
+ diagnostics: DgmoError[];
82
+ error: string | null;
83
+ }
84
+
85
+ /**
86
+ * Compute new file content after moving a card to a different position.
87
+ *
88
+ * @param content - original file content string
89
+ * @param parsed - parsed kanban board
90
+ * @param cardId - id of the card to move
91
+ * @param targetColumnId - id of the destination column
92
+ * @param targetIndex - position within target column (0 = first card)
93
+ * @returns new content string, or null if move is invalid
94
+ */
95
+ declare function computeCardMove(content: string, parsed: ParsedKanban, cardId: string, targetColumnId: string, targetIndex: number): string | null;
96
+ /**
97
+ * Move a card to the Archive section at the end of the file.
98
+ * Creates `== Archive ==` if it doesn't exist.
99
+ *
100
+ * @returns new content string, or null if the card is not found
101
+ */
102
+ declare function computeCardArchive(content: string, parsed: ParsedKanban, cardId: string): string | null;
103
+ /** Check if a column name is the archive column (case-insensitive). */
104
+ declare function isArchiveColumn(name: string): boolean;
105
+
106
+ /**
107
+ * A message between two participants.
108
+ */
109
+ interface SequenceMessage {
110
+ from: string;
111
+ to: string;
112
+ label: string;
113
+ lineNumber: number;
114
+ async?: boolean;
115
+ /** Pipe-delimited tag metadata (e.g. `| c: Caching`) */
116
+ metadata?: Record<string, string>;
117
+ }
118
+ /**
119
+ * A conditional or loop block in the sequence diagram.
120
+ */
121
+ interface ElseIfBranch {
122
+ label: string;
123
+ children: SequenceElement[];
124
+ lineNumber: number;
125
+ }
126
+ interface SequenceBlock {
127
+ kind: 'block';
128
+ type: 'if' | 'loop' | 'parallel';
129
+ label: string;
130
+ children: SequenceElement[];
131
+ elseChildren: SequenceElement[];
132
+ elseIfBranches?: ElseIfBranch[];
133
+ elseLineNumber?: number;
134
+ lineNumber: number;
135
+ }
136
+ /**
137
+ * A labeled horizontal divider between message phases.
138
+ */
139
+ interface SequenceSection {
140
+ kind: 'section';
141
+ label: string;
142
+ lineNumber: number;
143
+ }
144
+ /**
145
+ * An annotation attached to a message, rendered as a folded-corner box.
146
+ */
147
+ interface SequenceNote {
148
+ kind: 'note';
149
+ text: string;
150
+ position: 'right' | 'left';
151
+ participantId: string;
152
+ lineNumber: number;
153
+ endLineNumber: number;
154
+ }
155
+ type SequenceElement = SequenceMessage | SequenceBlock | SequenceSection | SequenceNote;
156
+
157
+ interface SectionMessageGroup {
158
+ section: SequenceSection;
159
+ messageIndices: number[];
160
+ }
161
+ /**
162
+ * Group messages by the top-level section that precedes them.
163
+ * Messages before the first section are ungrouped (always visible).
164
+ * Only top-level sections are collapsible — sections inside blocks are excluded.
165
+ */
166
+ declare function groupMessagesBySection(elements: SequenceElement[], messages: SequenceMessage[]): SectionMessageGroup[];
167
+ /**
168
+ * Build a mapping from each note's lineNumber to the lineNumber of its
169
+ * associated message (the last message before the note in document order).
170
+ * Used by the app to expand notes when cursor is on the associated message.
171
+ */
172
+ declare function buildNoteMessageMap(elements: SequenceElement[]): Map<number, number>;
173
+ /**
174
+ * Collect all note line numbers from a sequence diagram's elements.
175
+ * Used by the app to compute the "expand all" set.
176
+ */
177
+ declare function collectNoteLineNumbers(elements: SequenceElement[]): number[];
178
+
179
+ export { buildNoteMessageMap, collectNoteLineNumbers, computeCardArchive, computeCardMove, groupMessagesBySection, isArchiveColumn, parseDataRowValues };
@@ -0,0 +1,337 @@
1
+ // src/utils/parsing.ts
2
+ function normalizeNumericToken(token) {
3
+ if (!token.includes(",") && !token.includes("_")) return null;
4
+ if (token.includes(",") && token.includes("_")) return null;
5
+ let sign = "";
6
+ let unsigned = token;
7
+ if (unsigned.startsWith("-")) {
8
+ sign = "-";
9
+ unsigned = unsigned.substring(1);
10
+ }
11
+ if (!unsigned) return null;
12
+ if (unsigned.includes(",")) {
13
+ if (/^\d{1,3}(,\d{3})+$/.test(unsigned))
14
+ return sign + unsigned.replace(/,/g, "");
15
+ if (/^\d{1,3}(,\d{3})+\.\d+$/.test(unsigned))
16
+ return sign + unsigned.replace(/,/g, "");
17
+ return null;
18
+ }
19
+ if (/^\d+(_\d+)+$/.test(unsigned)) return sign + unsigned.replace(/_/g, "");
20
+ if (/^\d+(_\d+)*\.\d+$/.test(unsigned) && unsigned.includes("_"))
21
+ return sign + unsigned.replace(/_/g, "");
22
+ return null;
23
+ }
24
+
25
+ // src/chart.ts
26
+ function parseDataRowValues(line, options) {
27
+ const segments = line.split(",");
28
+ const normalized = [];
29
+ for (let i = 0; i < segments.length; i++) {
30
+ const seg = segments[i].trim();
31
+ if (i > 0 && /^\d{3}(\.\d+)?$/.test(seg) && !/^\s/.test(segments[i])) {
32
+ const prevSeg = normalized[normalized.length - 1].trimEnd();
33
+ if (/\d{1,3}$/.test(prevSeg)) {
34
+ const prevMatch = prevSeg.match(/(\d{1,3})$/);
35
+ if (prevMatch) {
36
+ normalized[normalized.length - 1] = prevSeg + seg;
37
+ continue;
38
+ }
39
+ }
40
+ }
41
+ normalized.push(segments[i]);
42
+ }
43
+ const rebuilt = normalized.join(",");
44
+ const commaParts = rebuilt.split(",");
45
+ if (commaParts.length > 1) {
46
+ let numericCount = 0;
47
+ for (let j = commaParts.length - 1; j >= 0; j--) {
48
+ const part = normalizeNumericToken(commaParts[j].trim()) ?? commaParts[j].trim();
49
+ if (part && !isNaN(parseFloat(part)) && isFinite(Number(part))) {
50
+ numericCount++;
51
+ } else {
52
+ break;
53
+ }
54
+ }
55
+ if (numericCount > 0) {
56
+ const splitAt = commaParts.length - numericCount;
57
+ const extraValueParts = commaParts.slice(splitAt);
58
+ const firstPart = commaParts.slice(0, splitAt).join(",").trim();
59
+ const lastSpaceIdx = firstPart.lastIndexOf(" ");
60
+ if (lastSpaceIdx >= 0) {
61
+ const rawFirstVal = firstPart.substring(lastSpaceIdx + 1).trim();
62
+ const possibleFirstVal = normalizeNumericToken(rawFirstVal) ?? rawFirstVal;
63
+ if (possibleFirstVal && !isNaN(parseFloat(possibleFirstVal)) && isFinite(Number(possibleFirstVal))) {
64
+ const label2 = firstPart.substring(0, lastSpaceIdx).trim();
65
+ if (label2) {
66
+ const values = [parseFloat(possibleFirstVal)];
67
+ for (const p of extraValueParts) {
68
+ const normP = normalizeNumericToken(p.trim()) ?? p.trim();
69
+ values.push(parseFloat(normP));
70
+ }
71
+ return { label: label2, values };
72
+ }
73
+ }
74
+ }
75
+ }
76
+ }
77
+ const tokens = rebuilt.split(/\s+/);
78
+ if (tokens.length < 2) return null;
79
+ if (options?.multiValue) {
80
+ const limit = options.expectedValues ?? Infinity;
81
+ const values = [];
82
+ let idx = tokens.length - 1;
83
+ while (idx >= 1 && values.length < limit) {
84
+ const tok = tokens[idx];
85
+ const normTok = normalizeNumericToken(tok) ?? tok;
86
+ const num2 = parseFloat(normTok);
87
+ if (isNaN(num2) || !isFinite(Number(normTok))) break;
88
+ values.unshift(num2);
89
+ idx--;
90
+ }
91
+ if (values.length === 0) return null;
92
+ const label2 = tokens.slice(0, idx + 1).join(" ");
93
+ if (!label2) return null;
94
+ return { label: label2, values };
95
+ }
96
+ const lastToken = tokens[tokens.length - 1];
97
+ const normalizedLast = normalizeNumericToken(lastToken) ?? lastToken;
98
+ const num = parseFloat(normalizedLast);
99
+ if (isNaN(num) || !isFinite(Number(normalizedLast))) return null;
100
+ const label = tokens.slice(0, -1).join(" ");
101
+ if (!label) return null;
102
+ return { label, values: [num] };
103
+ }
104
+
105
+ // src/kanban/mutations.ts
106
+ var ARCHIVE_COLUMN_NAME = "archive";
107
+ function computeCardMove(content, parsed, cardId, targetColumnId, targetIndex) {
108
+ let sourceCard = null;
109
+ let sourceColumn = null;
110
+ for (const col of parsed.columns) {
111
+ for (const card of col.cards) {
112
+ if (card.id === cardId) {
113
+ sourceCard = card;
114
+ sourceColumn = col;
115
+ break;
116
+ }
117
+ }
118
+ if (sourceCard) break;
119
+ }
120
+ if (!sourceCard || !sourceColumn) return null;
121
+ const targetColumn = parsed.columns.find((c) => c.id === targetColumnId);
122
+ if (!targetColumn) return null;
123
+ const lines = content.split("\n");
124
+ const startIdx = sourceCard.lineNumber - 1;
125
+ const endIdx = sourceCard.endLineNumber - 1;
126
+ const cardLines = lines.slice(startIdx, endIdx + 1);
127
+ const withoutCard = [
128
+ ...lines.slice(0, startIdx),
129
+ ...lines.slice(endIdx + 1)
130
+ ];
131
+ let insertIdx;
132
+ const removedCount = endIdx - startIdx + 1;
133
+ const adjustLine = (ln) => {
134
+ if (ln > endIdx + 1) return ln - removedCount;
135
+ return ln;
136
+ };
137
+ if (targetIndex === 0) {
138
+ const adjColLine = adjustLine(targetColumn.lineNumber);
139
+ insertIdx = adjColLine;
140
+ } else {
141
+ const targetCards = targetColumn.cards.filter((c) => c.id !== cardId);
142
+ const clampedIdx = Math.min(targetIndex, targetCards.length);
143
+ const precedingCard = targetCards[clampedIdx - 1];
144
+ if (!precedingCard) {
145
+ const adjColLine = adjustLine(targetColumn.lineNumber);
146
+ insertIdx = adjColLine;
147
+ } else {
148
+ const adjEndLine = adjustLine(precedingCard.endLineNumber);
149
+ insertIdx = adjEndLine;
150
+ }
151
+ }
152
+ const result = [
153
+ ...withoutCard.slice(0, insertIdx),
154
+ ...cardLines,
155
+ ...withoutCard.slice(insertIdx)
156
+ ];
157
+ return result.join("\n");
158
+ }
159
+ function computeCardArchive(content, parsed, cardId) {
160
+ let sourceCard = null;
161
+ for (const col of parsed.columns) {
162
+ for (const card of col.cards) {
163
+ if (card.id === cardId) {
164
+ sourceCard = card;
165
+ break;
166
+ }
167
+ }
168
+ if (sourceCard) break;
169
+ }
170
+ if (!sourceCard) return null;
171
+ const lines = content.split("\n");
172
+ const startIdx = sourceCard.lineNumber - 1;
173
+ const endIdx = sourceCard.endLineNumber - 1;
174
+ const cardLines = lines.slice(startIdx, endIdx + 1);
175
+ const withoutCard = [
176
+ ...lines.slice(0, startIdx),
177
+ ...lines.slice(endIdx + 1)
178
+ ];
179
+ const archiveCol = parsed.columns.find(
180
+ (c) => c.name.toLowerCase() === ARCHIVE_COLUMN_NAME
181
+ );
182
+ if (archiveCol) {
183
+ const removedCount = endIdx - startIdx + 1;
184
+ let archiveEndLine = archiveCol.lineNumber;
185
+ if (archiveCol.cards.length > 0) {
186
+ const lastCard = archiveCol.cards[archiveCol.cards.length - 1];
187
+ archiveEndLine = lastCard.endLineNumber;
188
+ }
189
+ if (archiveEndLine > endIdx + 1) {
190
+ archiveEndLine -= removedCount;
191
+ }
192
+ const insertIdx = archiveEndLine;
193
+ return [
194
+ ...withoutCard.slice(0, insertIdx),
195
+ ...cardLines,
196
+ ...withoutCard.slice(insertIdx)
197
+ ].join("\n");
198
+ } else {
199
+ const trimmedEnd = withoutCard.length > 0 && withoutCard[withoutCard.length - 1].trim() === "" ? withoutCard : [...withoutCard, ""];
200
+ return [
201
+ ...trimmedEnd,
202
+ "[Archive]",
203
+ ...cardLines
204
+ ].join("\n");
205
+ }
206
+ }
207
+ function isArchiveColumn(name) {
208
+ return name.toLowerCase() === ARCHIVE_COLUMN_NAME;
209
+ }
210
+
211
+ // src/sequence/renderer.ts
212
+ import * as d3Selection from "d3-selection";
213
+
214
+ // src/sequence/parser.ts
215
+ function isSequenceBlock(el) {
216
+ return "kind" in el && el.kind === "block";
217
+ }
218
+ function isSequenceSection(el) {
219
+ return "kind" in el && el.kind === "section";
220
+ }
221
+ function isSequenceNote(el) {
222
+ return "kind" in el && el.kind === "note";
223
+ }
224
+
225
+ // src/sequence/renderer.ts
226
+ var PARTICIPANT_GAP = 160;
227
+ var PARTICIPANT_BOX_WIDTH = 120;
228
+ var NOTE_MAX_W = 200;
229
+ var NOTE_FOLD = 10;
230
+ var NOTE_PAD_H = 8;
231
+ var NOTE_GAP = 15;
232
+ var NOTE_CHAR_W = 6;
233
+ var NOTE_CHARS_PER_LINE = Math.floor(
234
+ (NOTE_MAX_W - NOTE_PAD_H * 2 - NOTE_FOLD) / NOTE_CHAR_W
235
+ );
236
+ var ACTIVATION_WIDTH = 10;
237
+ var NOTE_LANE_MAX = PARTICIPANT_GAP - ACTIVATION_WIDTH - NOTE_GAP;
238
+ var LABEL_CHAR_WIDTH = 7.5;
239
+ var LABEL_MAX_CHARS = Math.floor(
240
+ (PARTICIPANT_BOX_WIDTH - 10) / LABEL_CHAR_WIDTH
241
+ );
242
+ function groupMessagesBySection(elements, messages) {
243
+ const groups = [];
244
+ let currentGroup = null;
245
+ const collectIndices = (els) => {
246
+ const indices = [];
247
+ for (const el of els) {
248
+ if (isSequenceBlock(el)) {
249
+ indices.push(
250
+ ...collectIndices(el.children),
251
+ ...collectIndices(el.elseChildren)
252
+ );
253
+ if (el.elseIfBranches) {
254
+ for (const branch of el.elseIfBranches) {
255
+ indices.push(...collectIndices(branch.children));
256
+ }
257
+ }
258
+ } else if (isSequenceSection(el) || isSequenceNote(el)) {
259
+ continue;
260
+ } else {
261
+ const idx = messages.indexOf(el);
262
+ if (idx >= 0) indices.push(idx);
263
+ }
264
+ }
265
+ return indices;
266
+ };
267
+ for (const el of elements) {
268
+ if (isSequenceSection(el)) {
269
+ currentGroup = { section: el, messageIndices: [] };
270
+ groups.push(currentGroup);
271
+ } else if (currentGroup) {
272
+ if (isSequenceBlock(el)) {
273
+ currentGroup.messageIndices.push(...collectIndices([el]));
274
+ } else if (!isSequenceNote(el)) {
275
+ const idx = messages.indexOf(el);
276
+ if (idx >= 0) currentGroup.messageIndices.push(idx);
277
+ }
278
+ }
279
+ }
280
+ return groups;
281
+ }
282
+ function buildNoteMessageMap(elements) {
283
+ const map = /* @__PURE__ */ new Map();
284
+ let lastMessageLine = -1;
285
+ const walk = (els) => {
286
+ for (const el of els) {
287
+ if (isSequenceNote(el)) {
288
+ if (lastMessageLine >= 0) {
289
+ map.set(el.lineNumber, lastMessageLine);
290
+ }
291
+ } else if (isSequenceBlock(el)) {
292
+ walk(el.children);
293
+ if (el.elseIfBranches) {
294
+ for (const branch of el.elseIfBranches) {
295
+ walk(branch.children);
296
+ }
297
+ }
298
+ walk(el.elseChildren);
299
+ } else if (!isSequenceSection(el)) {
300
+ const msg = el;
301
+ lastMessageLine = msg.lineNumber;
302
+ }
303
+ }
304
+ };
305
+ walk(elements);
306
+ return map;
307
+ }
308
+ function collectNoteLineNumbers(elements) {
309
+ const result = [];
310
+ const walk = (els) => {
311
+ for (const el of els) {
312
+ if (isSequenceNote(el)) {
313
+ result.push(el.lineNumber);
314
+ } else if (isSequenceBlock(el)) {
315
+ walk(el.children);
316
+ if (el.elseIfBranches) {
317
+ for (const branch of el.elseIfBranches) {
318
+ walk(branch.children);
319
+ }
320
+ }
321
+ walk(el.elseChildren);
322
+ }
323
+ }
324
+ };
325
+ walk(elements);
326
+ return result;
327
+ }
328
+ export {
329
+ buildNoteMessageMap,
330
+ collectNoteLineNumbers,
331
+ computeCardArchive,
332
+ computeCardMove,
333
+ groupMessagesBySection,
334
+ isArchiveColumn,
335
+ parseDataRowValues
336
+ };
337
+ //# sourceMappingURL=internal.js.map