@diagrammo/dgmo 0.8.4 → 0.8.6

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 (68) hide show
  1. package/.claude/commands/dgmo.md +300 -0
  2. package/.cursorrules +20 -2
  3. package/.github/copilot-instructions.md +20 -2
  4. package/.windsurfrules +20 -2
  5. package/AGENTS.md +23 -3
  6. package/dist/cli.cjs +191 -189
  7. package/dist/editor.cjs +5 -18
  8. package/dist/editor.cjs.map +1 -1
  9. package/dist/editor.js +5 -18
  10. package/dist/editor.js.map +1 -1
  11. package/dist/highlight.cjs +543 -0
  12. package/dist/highlight.cjs.map +1 -0
  13. package/dist/highlight.d.cts +32 -0
  14. package/dist/highlight.d.ts +32 -0
  15. package/dist/highlight.js +513 -0
  16. package/dist/highlight.js.map +1 -0
  17. package/dist/index.cjs +3253 -3356
  18. package/dist/index.cjs.map +1 -1
  19. package/dist/index.d.cts +77 -56
  20. package/dist/index.d.ts +77 -56
  21. package/dist/index.js +3247 -3349
  22. package/dist/index.js.map +1 -1
  23. package/docs/ai-integration.md +1 -1
  24. package/docs/language-reference.md +113 -33
  25. package/gallery/fixtures/boxes-and-lines.dgmo +64 -0
  26. package/gallery/fixtures/slope.dgmo +7 -6
  27. package/package.json +26 -6
  28. package/src/boxes-and-lines/collapse.ts +78 -0
  29. package/src/boxes-and-lines/layout.ts +319 -0
  30. package/src/boxes-and-lines/parser.ts +694 -0
  31. package/src/boxes-and-lines/renderer.ts +848 -0
  32. package/src/boxes-and-lines/types.ts +40 -0
  33. package/src/c4/parser.ts +10 -5
  34. package/src/c4/renderer.ts +232 -56
  35. package/src/chart.ts +9 -4
  36. package/src/cli.ts +49 -6
  37. package/src/completion.ts +25 -33
  38. package/src/d3.ts +187 -46
  39. package/src/dgmo-router.ts +3 -7
  40. package/src/echarts.ts +38 -2
  41. package/src/editor/highlight-api.ts +444 -0
  42. package/src/editor/keywords.ts +6 -19
  43. package/src/er/parser.ts +10 -4
  44. package/src/gantt/parser.ts +7 -4
  45. package/src/gantt/renderer.ts +3 -5
  46. package/src/index.ts +106 -50
  47. package/src/infra/parser.ts +7 -5
  48. package/src/infra/renderer.ts +2 -2
  49. package/src/kanban/parser.ts +7 -5
  50. package/src/kanban/renderer.ts +43 -18
  51. package/src/org/parser.ts +7 -4
  52. package/src/org/renderer.ts +40 -29
  53. package/src/sequence/parser.ts +11 -5
  54. package/src/sequence/renderer.ts +114 -45
  55. package/src/sitemap/parser.ts +8 -4
  56. package/src/sitemap/renderer.ts +137 -57
  57. package/src/utils/legend-svg.ts +44 -20
  58. package/src/utils/parsing.ts +1 -1
  59. package/src/utils/tag-groups.ts +21 -1
  60. package/gallery/fixtures/initiative-status-full.dgmo +0 -46
  61. package/gallery/fixtures/initiative-status-phases.dgmo +0 -29
  62. package/gallery/fixtures/initiative-status.dgmo +0 -9
  63. package/src/initiative-status/collapse.ts +0 -76
  64. package/src/initiative-status/filter.ts +0 -63
  65. package/src/initiative-status/layout.ts +0 -650
  66. package/src/initiative-status/parser.ts +0 -629
  67. package/src/initiative-status/renderer.ts +0 -1199
  68. package/src/initiative-status/types.ts +0 -57
package/src/index.ts CHANGED
@@ -27,13 +27,8 @@ export type { RenderCategory } from './dgmo-router';
27
27
  // Parsers
28
28
  // ============================================================
29
29
 
30
- export { parseChart } from './chart';
31
- export type {
32
- ParsedChart,
33
- ChartType,
34
- ChartDataPoint,
35
- ChartEra,
36
- } from './chart';
30
+ export { parseChart, parseDataRowValues } from './chart';
31
+ export type { ParsedChart, ChartType, ChartDataPoint, ChartEra } from './chart';
37
32
 
38
33
  export { parseExtendedChart } from './echarts';
39
34
  export type { ParsedExtendedChart, ExtendedChartType } from './echarts';
@@ -46,7 +41,13 @@ export {
46
41
  computeTimeTicks,
47
42
  formatDateLabel,
48
43
  } from './d3';
49
- export type { ParsedVisualization, VisualizationType, D3ExportDimensions, ArcLink, ArcNodeGroup } from './d3';
44
+ export type {
45
+ ParsedVisualization,
46
+ VisualizationType,
47
+ D3ExportDimensions,
48
+ ArcLink,
49
+ ArcNodeGroup,
50
+ } from './d3';
50
51
 
51
52
  export {
52
53
  parseSequenceDgmo,
@@ -99,7 +100,10 @@ export type {
99
100
  ClassLayoutEdge,
100
101
  } from './class/layout';
101
102
 
102
- export { renderClassDiagram, renderClassDiagramForExport } from './class/renderer';
103
+ export {
104
+ renderClassDiagram,
105
+ renderClassDiagramForExport,
106
+ } from './class/renderer';
103
107
 
104
108
  export { parseERDiagram, looksLikeERDiagram } from './er/parser';
105
109
 
@@ -113,11 +117,7 @@ export type {
113
117
  } from './er/types';
114
118
 
115
119
  export { layoutERDiagram } from './er/layout';
116
- export type {
117
- ERLayoutResult,
118
- ERLayoutNode,
119
- ERLayoutEdge,
120
- } from './er/layout';
120
+ export type { ERLayoutResult, ERLayoutNode, ERLayoutEdge } from './er/layout';
121
121
 
122
122
  export { renderERDiagram, renderERDiagramForExport } from './er/renderer';
123
123
 
@@ -136,10 +136,7 @@ export { parseInlineMarkdown, truncateBareUrl } from './utils/inline-markdown';
136
136
  export type { InlineSpan } from './utils/inline-markdown';
137
137
 
138
138
  export { parseOrg } from './org/parser';
139
- export type {
140
- ParsedOrg,
141
- OrgNode,
142
- } from './org/parser';
139
+ export type { ParsedOrg, OrgNode } from './org/parser';
143
140
 
144
141
  export { layoutOrg } from './org/layout';
145
142
  export type {
@@ -159,7 +156,11 @@ export type {
159
156
  KanbanTagGroup,
160
157
  KanbanTagEntry,
161
158
  } from './kanban/types';
162
- export { computeCardMove, computeCardArchive, isArchiveColumn } from './kanban/mutations';
159
+ export {
160
+ computeCardMove,
161
+ computeCardArchive,
162
+ isArchiveColumn,
163
+ } from './kanban/mutations';
163
164
  export { renderKanban, renderKanbanForExport } from './kanban/renderer';
164
165
 
165
166
  export { parseC4 } from './c4/parser';
@@ -176,7 +177,13 @@ export type {
176
177
  C4TagEntry,
177
178
  } from './c4/types';
178
179
 
179
- export { layoutC4Context, layoutC4Containers, layoutC4Components, layoutC4Deployment, rollUpContextRelationships } from './c4/layout';
180
+ export {
181
+ layoutC4Context,
182
+ layoutC4Containers,
183
+ layoutC4Components,
184
+ layoutC4Deployment,
185
+ rollUpContextRelationships,
186
+ } from './c4/layout';
180
187
  export type {
181
188
  C4LayoutResult,
182
189
  C4LayoutNode,
@@ -197,30 +204,27 @@ export {
197
204
  renderC4DeploymentForExport,
198
205
  } from './c4/renderer';
199
206
 
200
- export { parseInitiativeStatus, looksLikeInitiativeStatus } from './initiative-status/parser';
207
+ export { parseBoxesAndLines } from './boxes-and-lines/parser';
201
208
  export type {
202
- ParsedInitiativeStatus,
203
- ISNode,
204
- ISEdge,
205
- ISGroup,
206
- InitiativeStatus,
207
- } from './initiative-status/types';
208
-
209
- export { layoutInitiativeStatus } from './initiative-status/layout';
209
+ ParsedBoxesAndLines,
210
+ BLNode,
211
+ BLEdge,
212
+ BLGroup,
213
+ } from './boxes-and-lines/types';
214
+ export { layoutBoxesAndLines } from './boxes-and-lines/layout';
210
215
  export type {
211
- ISLayoutResult,
212
- ISLayoutNode,
213
- ISLayoutEdge,
214
- ISLayoutGroup,
215
- } from './initiative-status/layout';
216
-
217
- export { renderInitiativeStatus, renderInitiativeStatusForExport } from './initiative-status/renderer';
218
- export type { ISRenderOptions } from './initiative-status/renderer';
219
-
220
- export { collapseInitiativeStatus } from './initiative-status/collapse';
221
- export type { CollapseResult } from './initiative-status/collapse';
216
+ BLLayoutResult,
217
+ BLLayoutNode,
218
+ BLLayoutEdge,
219
+ BLLayoutGroup,
220
+ } from './boxes-and-lines/layout';
221
+ export {
222
+ renderBoxesAndLines,
223
+ renderBoxesAndLinesForExport,
224
+ } from './boxes-and-lines/renderer';
222
225
 
223
- export { filterInitiativeStatusByTags } from './initiative-status/filter';
226
+ export { collapseBoxesAndLines } from './boxes-and-lines/collapse';
227
+ export type { BLCollapseResult } from './boxes-and-lines/collapse';
224
228
 
225
229
  export { parseSitemap, looksLikeSitemap } from './sitemap/parser';
226
230
 
@@ -247,16 +251,42 @@ export { collapseSitemapTree } from './sitemap/collapse';
247
251
 
248
252
  // ── Infra Chart ────────────────────────────────────────────
249
253
  export { parseInfra } from './infra/parser';
250
- export type { ParsedInfra, InfraNode, InfraEdge, InfraGroup, InfraTagGroup, InfraProperty, InfraDiagnostic, InfraComputeParams, InfraBehaviorKey } from './infra/types';
254
+ export type {
255
+ ParsedInfra,
256
+ InfraNode,
257
+ InfraEdge,
258
+ InfraGroup,
259
+ InfraTagGroup,
260
+ InfraProperty,
261
+ InfraDiagnostic,
262
+ InfraComputeParams,
263
+ InfraBehaviorKey,
264
+ } from './infra/types';
251
265
  export { INFRA_BEHAVIOR_KEYS } from './infra/types';
252
266
  export { computeInfra } from './infra/compute';
253
- export type { ComputedInfraModel, ComputedInfraNode, ComputedInfraEdge, InfraLatencyPercentiles, InfraAvailabilityPercentiles, InfraCbState } from './infra/types';
267
+ export type {
268
+ ComputedInfraModel,
269
+ ComputedInfraNode,
270
+ ComputedInfraEdge,
271
+ InfraLatencyPercentiles,
272
+ InfraAvailabilityPercentiles,
273
+ InfraCbState,
274
+ } from './infra/types';
254
275
  export { validateInfra, validateComputed } from './infra/validation';
255
276
  export { inferRoles, collectDiagramRoles } from './infra/roles';
256
277
  export type { InfraRole } from './infra/roles';
257
278
  export { layoutInfra } from './infra/layout';
258
- export type { InfraLayoutResult, InfraLayoutNode, InfraLayoutEdge, InfraLayoutGroup } from './infra/layout';
259
- export { renderInfra, parseAndLayoutInfra, computeInfraLegendGroups } from './infra/renderer';
279
+ export type {
280
+ InfraLayoutResult,
281
+ InfraLayoutNode,
282
+ InfraLayoutEdge,
283
+ InfraLayoutGroup,
284
+ } from './infra/layout';
285
+ export {
286
+ renderInfra,
287
+ parseAndLayoutInfra,
288
+ computeInfraLegendGroups,
289
+ } from './infra/renderer';
260
290
  export type { InfraLegendGroup, InfraPlaybackState } from './infra/renderer';
261
291
  export type { CollapsedSitemapResult } from './sitemap/collapse';
262
292
 
@@ -264,7 +294,13 @@ export type { CollapsedSitemapResult } from './sitemap/collapse';
264
294
  export { parseGantt } from './gantt/parser';
265
295
  export { calculateSchedule } from './gantt/calculator';
266
296
  export { renderGantt, buildTagLaneRowList } from './gantt/renderer';
267
- export type { GanttInteractiveOptions, GanttRow, GanttGroupRow, GanttTaskRow, GanttLaneHeaderRow } from './gantt/renderer';
297
+ export type {
298
+ GanttInteractiveOptions,
299
+ GanttRow,
300
+ GanttGroupRow,
301
+ GanttTaskRow,
302
+ GanttLaneHeaderRow,
303
+ } from './gantt/renderer';
268
304
  export { resolveTaskName, collectTasks } from './gantt/resolver';
269
305
  export type {
270
306
  ParsedGantt,
@@ -288,7 +324,11 @@ export { collapseOrgTree } from './org/collapse';
288
324
  export type { CollapsedOrgResult } from './org/collapse';
289
325
 
290
326
  export { resolveOrgImports } from './org/resolver';
291
- export type { ReadFileFn, ResolveImportsResult, ImportSource } from './org/resolver';
327
+ export type {
328
+ ReadFileFn,
329
+ ResolveImportsResult,
330
+ ImportSource,
331
+ } from './org/resolver';
292
332
 
293
333
  export { layoutGraph } from './graph/layout';
294
334
  export type {
@@ -298,13 +338,23 @@ export type {
298
338
  LayoutGroup,
299
339
  } from './graph/layout';
300
340
 
301
- export { renderFlowchart, renderFlowchartForExport } from './graph/flowchart-renderer';
341
+ export {
342
+ renderFlowchart,
343
+ renderFlowchartForExport,
344
+ } from './graph/flowchart-renderer';
302
345
 
303
346
  // ============================================================
304
347
  // Config Builders (produce framework-specific config objects)
305
348
  // ============================================================
306
349
 
307
- export { buildExtendedChartOption, buildSimpleChartOption, renderExtendedChartForExport, getExtendedChartLegendGroups, getSimpleChartLegendGroups, computeScatterLabelGraphics } from './echarts';
350
+ export {
351
+ buildExtendedChartOption,
352
+ buildSimpleChartOption,
353
+ renderExtendedChartForExport,
354
+ getExtendedChartLegendGroups,
355
+ getSimpleChartLegendGroups,
356
+ computeScatterLabelGraphics,
357
+ } from './echarts';
308
358
  export type { ScatterLabelPoint } from './echarts';
309
359
  export { renderLegendSvg, type LegendGroupData } from './utils/legend-svg';
310
360
  export { LEGEND_HEIGHT } from './utils/legend-constants';
@@ -403,7 +453,13 @@ export {
403
453
  PIPE_METADATA,
404
454
  extractTagDeclarations,
405
455
  } from './completion';
406
- export type { DiagramSymbols, ExtractFn, DirectiveSpec, DirectiveValueSpec, PipeKeySpec } from './completion';
456
+ export type {
457
+ DiagramSymbols,
458
+ ExtractFn,
459
+ DirectiveSpec,
460
+ DirectiveValueSpec,
461
+ PipeKeySpec,
462
+ } from './completion';
407
463
 
408
464
  export { parseFirstLine, ALL_CHART_TYPES } from './utils/parsing';
409
465
 
@@ -12,7 +12,7 @@ import {
12
12
  parseFirstLine,
13
13
  OPTION_NOCOLON_RE,
14
14
  } from '../utils/parsing';
15
- import { matchTagBlockHeading } from '../utils/tag-groups';
15
+ import { matchTagBlockHeading, stripDefaultModifier } from '../utils/tag-groups';
16
16
  import type {
17
17
  ParsedInfra,
18
18
  InfraNode,
@@ -338,17 +338,19 @@ export function parseInfra(content: string): ParsedInfra {
338
338
 
339
339
  // ---- Indented lines ----
340
340
 
341
- // Tag value inside tag group — first value is the default
341
+ // Tag value inside tag group — first value is the default unless another is marked `default`
342
342
  if (currentTagGroup && indent > 0) {
343
- const tvMatch = trimmed.match(TAG_VALUE_RE);
343
+ const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
344
+ const tvMatch = cleanEntry.match(TAG_VALUE_RE);
344
345
  if (tvMatch) {
345
346
  const valueName = tvMatch[1].trim();
346
347
  currentTagGroup.values.push({
347
348
  name: valueName,
348
349
  color: tvMatch[2]?.trim(),
349
350
  });
350
- // First value is the default
351
- if (currentTagGroup.values.length === 1) {
351
+ if (isDefault) {
352
+ currentTagGroup.defaultValue = valueName;
353
+ } else if (currentTagGroup.values.length === 1) {
352
354
  currentTagGroup.defaultValue = valueName;
353
355
  }
354
356
  continue;
@@ -2069,8 +2069,8 @@ function renderLegend(
2069
2069
  }
2070
2070
 
2071
2071
  const pillXOff = isActive ? LEGEND_CAPSULE_PAD : 0;
2072
- const pillYOff = isActive ? LEGEND_CAPSULE_PAD : 0;
2073
- const pillH = LEGEND_HEIGHT - (isActive ? LEGEND_CAPSULE_PAD * 2 : 0);
2072
+ const pillYOff = LEGEND_CAPSULE_PAD;
2073
+ const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
2074
2074
 
2075
2075
  // Pill background
2076
2076
  gEl
@@ -1,7 +1,7 @@
1
1
  import type { PaletteColors } from '../palettes';
2
2
  import { makeDgmoError, formatDgmoError, suggest } from '../diagnostics';
3
3
  import { resolveColor } from '../colors';
4
- import { matchTagBlockHeading } from '../utils/tag-groups';
4
+ import { matchTagBlockHeading, stripDefaultModifier } from '../utils/tag-groups';
5
5
  import {
6
6
  measureIndent,
7
7
  extractColor,
@@ -166,11 +166,12 @@ export function parseKanban(
166
166
  }
167
167
 
168
168
  // Tag group entries (indented Value(color) under tag heading)
169
- // First entry is implicitly the default.
169
+ // First entry is the default unless another is marked `default`
170
170
  if (currentTagGroup && !contentStarted) {
171
171
  const indent = measureIndent(line);
172
172
  if (indent > 0) {
173
- const { label, color } = extractColor(trimmed, palette);
173
+ const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
174
+ const { label, color } = extractColor(cleanEntry, palette);
174
175
  if (!color) {
175
176
  warn(
176
177
  lineNumber,
@@ -178,8 +179,9 @@ export function parseKanban(
178
179
  );
179
180
  continue;
180
181
  }
181
- // First entry is the default
182
- if (currentTagGroup.entries.length === 0) {
182
+ if (isDefault) {
183
+ currentTagGroup.defaultValue = label;
184
+ } else if (currentTagGroup.entries.length === 0) {
183
185
  currentTagGroup.defaultValue = label;
184
186
  }
185
187
  currentTagGroup.entries.push({
@@ -7,7 +7,12 @@ import { FONT_FAMILY } from '../fonts';
7
7
  import type { PaletteColors } from '../palettes';
8
8
  import { mix } from '../palettes/color-utils';
9
9
  import { renderInlineText } from '../utils/inline-markdown';
10
- import type { ParsedKanban, KanbanColumn, KanbanCard, KanbanTagGroup } from './types';
10
+ import type {
11
+ ParsedKanban,
12
+ KanbanColumn,
13
+ KanbanCard,
14
+ KanbanTagGroup,
15
+ } from './types';
11
16
  import { parseKanban } from './parser';
12
17
  import { isArchiveColumn } from './mutations';
13
18
  import {
@@ -141,13 +146,18 @@ function computeLayout(
141
146
  const metaCount = tagMeta.length + card.details.length;
142
147
  const metaHeight =
143
148
  metaCount > 0
144
- ? CARD_SEPARATOR_GAP + 1 + CARD_PADDING_Y + metaCount * CARD_META_LINE_HEIGHT
149
+ ? CARD_SEPARATOR_GAP +
150
+ 1 +
151
+ CARD_PADDING_Y +
152
+ metaCount * CARD_META_LINE_HEIGHT
145
153
  : 0;
146
154
  const cardHeight = CARD_HEADER_HEIGHT + CARD_PADDING_Y + metaHeight;
147
155
 
148
156
  // Account for meta label widths
149
157
  for (const m of tagMeta) {
150
- const metaW = (m.label.length + 2 + m.value.length) * CARD_META_FONT_SIZE * 0.6 + CARD_PADDING_X * 2;
158
+ const metaW =
159
+ (m.label.length + 2 + m.value.length) * CARD_META_FONT_SIZE * 0.6 +
160
+ CARD_PADDING_X * 2;
151
161
  maxCardTextWidth = Math.max(maxCardTextWidth, metaW);
152
162
  }
153
163
 
@@ -162,7 +172,10 @@ function computeLayout(
162
172
  cardY += cardHeight + CARD_GAP;
163
173
  }
164
174
 
165
- const colWidth = Math.max(COLUMN_MIN_WIDTH, maxCardTextWidth + COLUMN_PADDING * 2);
175
+ const colWidth = Math.max(
176
+ COLUMN_MIN_WIDTH,
177
+ maxCardTextWidth + COLUMN_PADDING * 2
178
+ );
166
179
 
167
180
  // Set card widths
168
181
  for (const cl of cardLayouts) {
@@ -269,7 +282,11 @@ export function renderKanban(
269
282
  if (isActive) {
270
283
  capsuleContentWidth += 4; // gap after pill
271
284
  for (const entry of group.entries) {
272
- capsuleContentWidth += LEGEND_DOT_R * 2 + 4 + entry.value.length * LEGEND_ENTRY_FONT_SIZE * 0.6 + 8;
285
+ capsuleContentWidth +=
286
+ LEGEND_DOT_R * 2 +
287
+ 4 +
288
+ entry.value.length * LEGEND_ENTRY_FONT_SIZE * 0.6 +
289
+ 8;
273
290
  }
274
291
  }
275
292
  const capsuleWidth = capsuleContentWidth + capsulePad * 2;
@@ -293,10 +310,10 @@ export function renderKanban(
293
310
  legendContainer
294
311
  .append('rect')
295
312
  .attr('x', pillX)
296
- .attr('y', legendY + (isActive ? capsulePad : 0))
313
+ .attr('y', legendY + capsulePad)
297
314
  .attr('width', pillWidth)
298
- .attr('height', LEGEND_HEIGHT - (isActive ? capsulePad * 2 : 0))
299
- .attr('rx', (LEGEND_HEIGHT - (isActive ? capsulePad * 2 : 0)) / 2)
315
+ .attr('height', LEGEND_HEIGHT - capsulePad * 2)
316
+ .attr('rx', (LEGEND_HEIGHT - capsulePad * 2) / 2)
300
317
  .attr('fill', pillBg)
301
318
  .attr('class', 'kanban-legend-group')
302
319
  .attr('data-legend-group', group.name.toLowerCase());
@@ -345,12 +362,16 @@ export function renderKanban(
345
362
  entryG
346
363
  .append('text')
347
364
  .attr('x', entryTextX)
348
- .attr('y', legendY + LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1)
365
+ .attr(
366
+ 'y',
367
+ legendY + LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1
368
+ )
349
369
  .attr('font-size', LEGEND_ENTRY_FONT_SIZE)
350
370
  .attr('fill', palette.textMuted)
351
371
  .text(entry.value);
352
372
 
353
- entryX = entryTextX + entry.value.length * LEGEND_ENTRY_FONT_SIZE * 0.6 + 8;
373
+ entryX =
374
+ entryTextX + entry.value.length * LEGEND_ENTRY_FONT_SIZE * 0.6 + 8;
354
375
  }
355
376
  legendX += capsuleWidth + 12;
356
377
  } else {
@@ -423,10 +444,7 @@ export function renderKanban(
423
444
  .attr('x', colLayout.x + COLUMN_PADDING + nameWidth + 8)
424
445
  .attr(
425
446
  'y',
426
- colLayout.y +
427
- COLUMN_HEADER_HEIGHT / 2 +
428
- WIP_FONT_SIZE / 2 -
429
- 1
447
+ colLayout.y + COLUMN_HEADER_HEIGHT / 2 + WIP_FONT_SIZE / 2 - 1
430
448
  )
431
449
  .attr('font-size', WIP_FONT_SIZE)
432
450
  .attr('fill', wipExceeded ? palette.colors.red : palette.textMuted)
@@ -437,7 +455,11 @@ export function renderKanban(
437
455
  // Cards
438
456
  for (const cardLayout of colLayout.cardLayouts) {
439
457
  const card = cardLayout.card;
440
- const resolvedColor = resolveCardTagColor(card, parsed.tagGroups, activeTagGroup ?? null);
458
+ const resolvedColor = resolveCardTagColor(
459
+ card,
460
+ parsed.tagGroups,
461
+ activeTagGroup ?? null
462
+ );
441
463
  const tagMeta = resolveCardTagMeta(card, parsed.tagGroups);
442
464
  const hasMeta = tagMeta.length > 0 || card.details.length > 0;
443
465
 
@@ -481,7 +503,8 @@ export function renderKanban(
481
503
  .attr('stroke-width', CARD_STROKE_WIDTH);
482
504
 
483
505
  // Card title (inline markdown)
484
- const titleEl = cg.append('text')
506
+ const titleEl = cg
507
+ .append('text')
485
508
  .attr('x', cx + CARD_PADDING_X)
486
509
  .attr('y', cy + CARD_PADDING_Y + CARD_TITLE_FONT_SIZE)
487
510
  .attr('font-size', CARD_TITLE_FONT_SIZE)
@@ -513,7 +536,8 @@ export function renderKanban(
513
536
  .attr('fill', palette.textMuted)
514
537
  .text(`${meta.label}: `);
515
538
 
516
- const labelWidth = (meta.label.length + 2) * CARD_META_FONT_SIZE * 0.6;
539
+ const labelWidth =
540
+ (meta.label.length + 2) * CARD_META_FONT_SIZE * 0.6;
517
541
  cg.append('text')
518
542
  .attr('x', cx + CARD_PADDING_X + labelWidth)
519
543
  .attr('y', metaY)
@@ -526,7 +550,8 @@ export function renderKanban(
526
550
 
527
551
  // Detail lines (inline markdown)
528
552
  for (const detail of card.details) {
529
- const detailEl = cg.append('text')
553
+ const detailEl = cg
554
+ .append('text')
530
555
  .attr('x', cx + CARD_PADDING_X)
531
556
  .attr('y', metaY)
532
557
  .attr('font-size', CARD_META_FONT_SIZE)
package/src/org/parser.ts CHANGED
@@ -6,6 +6,7 @@ import {
6
6
  isTagBlockHeading,
7
7
  matchTagBlockHeading,
8
8
  validateTagValues,
9
+ stripDefaultModifier,
9
10
  } from '../utils/tag-groups';
10
11
  import {
11
12
  measureIndent,
@@ -220,11 +221,12 @@ export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
220
221
  }
221
222
 
222
223
  // Tag group entries (indented Value(color) under tag heading)
223
- // First entry is implicitly the default.
224
+ // First entry is the default unless another is marked `default`
224
225
  if (currentTagGroup && !contentStarted) {
225
226
  const indent = measureIndent(line);
226
227
  if (indent > 0) {
227
- const { label, color } = extractColor(trimmed, palette);
228
+ const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
229
+ const { label, color } = extractColor(cleanEntry, palette);
228
230
  if (!color) {
229
231
  pushError(
230
232
  lineNumber,
@@ -232,8 +234,9 @@ export function parseOrg(content: string, palette?: PaletteColors): ParsedOrg {
232
234
  );
233
235
  continue;
234
236
  }
235
- // First entry is the default
236
- if (currentTagGroup.entries.length === 0) {
237
+ if (isDefault) {
238
+ currentTagGroup.defaultValue = label;
239
+ } else if (currentTagGroup.entries.length === 0) {
237
240
  currentTagGroup.defaultValue = label;
238
241
  }
239
242
  currentTagGroup.entries.push({