@diagrammo/dgmo 0.8.14 → 0.8.16

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/index.cjs CHANGED
@@ -322,20 +322,39 @@ var init_label_layout = __esm({
322
322
  });
323
323
 
324
324
  // src/colors.ts
325
+ function isRecognizedColorName(name) {
326
+ return Object.prototype.hasOwnProperty.call(colorNames, name.toLowerCase());
327
+ }
325
328
  function resolveColor(color, palette) {
329
+ if (!color) return null;
326
330
  if (color.startsWith("#")) return null;
327
331
  const lower = color.toLowerCase();
332
+ if (!isRecognizedColorName(lower)) return null;
328
333
  if (palette) {
329
334
  const named = palette.colors[lower];
330
335
  if (named) return named;
331
336
  }
332
- if (colorNames[lower]) return colorNames[lower];
333
- return color;
337
+ return colorNames[lower];
338
+ }
339
+ function resolveColorWithDiagnostic(color, line10, diagnostics, palette) {
340
+ const resolved = resolveColor(color, palette);
341
+ if (resolved !== null) return resolved;
342
+ const hint = suggest(color, RECOGNIZED_COLOR_NAMES);
343
+ const suggestion = hint ? ` ${hint}` : "";
344
+ diagnostics.push(
345
+ makeDgmoError(
346
+ line10,
347
+ `Unknown color "${color}". Allowed: ${RECOGNIZED_COLOR_NAMES.join(", ")}.${suggestion}`,
348
+ "warning"
349
+ )
350
+ );
351
+ return void 0;
334
352
  }
335
- var nord, colorNames, seriesColors;
353
+ var nord, colorNames, RECOGNIZED_COLOR_NAMES, seriesColors;
336
354
  var init_colors = __esm({
337
355
  "src/colors.ts"() {
338
356
  "use strict";
357
+ init_diagnostics();
339
358
  nord = {
340
359
  // Polar Night (dark)
341
360
  nord0: "#2e3440",
@@ -372,9 +391,23 @@ var init_colors = __esm({
372
391
  purple: nord.nord15,
373
392
  teal: nord.nord7,
374
393
  cyan: nord.nord8,
375
- lightblue: nord.nord8,
376
- gray: nord.nord3
394
+ gray: nord.nord3,
395
+ black: nord.nord0,
396
+ white: nord.nord6
377
397
  };
398
+ RECOGNIZED_COLOR_NAMES = Object.freeze([
399
+ "red",
400
+ "orange",
401
+ "yellow",
402
+ "green",
403
+ "blue",
404
+ "purple",
405
+ "teal",
406
+ "cyan",
407
+ "gray",
408
+ "black",
409
+ "white"
410
+ ]);
378
411
  seriesColors = [
379
412
  nord.nord10,
380
413
  // blue
@@ -631,7 +664,9 @@ var init_bold = __esm({
631
664
  purple: "#cc00cc",
632
665
  teal: "#008080",
633
666
  cyan: "#00cccc",
634
- gray: "#808080"
667
+ gray: "#808080",
668
+ black: "#000000",
669
+ white: "#f0f0f0"
635
670
  }
636
671
  },
637
672
  dark: {
@@ -654,7 +689,9 @@ var init_bold = __esm({
654
689
  purple: "#ff00ff",
655
690
  teal: "#00cccc",
656
691
  cyan: "#00ffff",
657
- gray: "#808080"
692
+ gray: "#808080",
693
+ black: "#111111",
694
+ white: "#ffffff"
658
695
  }
659
696
  }
660
697
  };
@@ -701,8 +738,10 @@ var init_catppuccin = __esm({
701
738
  purple: "#8839ef",
702
739
  teal: "#179299",
703
740
  cyan: "#209fb5",
704
- gray: "#9ca0b0"
741
+ gray: "#9ca0b0",
705
742
  // Latte Overlay0
743
+ black: "#4c4f69",
744
+ white: "#e6e9ef"
706
745
  }
707
746
  },
708
747
  dark: {
@@ -735,8 +774,10 @@ var init_catppuccin = __esm({
735
774
  purple: "#cba6f7",
736
775
  teal: "#94e2d5",
737
776
  cyan: "#74c7ec",
738
- gray: "#6c7086"
777
+ gray: "#6c7086",
739
778
  // Mocha Overlay0
779
+ black: "#313244",
780
+ white: "#cdd6f4"
740
781
  }
741
782
  }
742
783
  };
@@ -791,7 +832,9 @@ var init_gruvbox = __esm({
791
832
  // faded aqua
792
833
  cyan: "#427b58",
793
834
  // faded aqua
794
- gray: "#928374"
835
+ gray: "#928374",
836
+ black: "#3c3836",
837
+ white: "#ebdbb2"
795
838
  }
796
839
  },
797
840
  dark: {
@@ -832,7 +875,9 @@ var init_gruvbox = __esm({
832
875
  // bright aqua
833
876
  cyan: "#8ec07c",
834
877
  // bright aqua
835
- gray: "#928374"
878
+ gray: "#928374",
879
+ black: "#3c3836",
880
+ white: "#ebdbb2"
836
881
  }
837
882
  }
838
883
  };
@@ -887,8 +932,10 @@ var init_nord = __esm({
887
932
  // nord7
888
933
  cyan: "#88c0d0",
889
934
  // nord8
890
- gray: "#4c566a"
935
+ gray: "#4c566a",
891
936
  // nord3
937
+ black: "#2e3440",
938
+ white: "#e5e9f0"
892
939
  }
893
940
  },
894
941
  dark: {
@@ -929,8 +976,10 @@ var init_nord = __esm({
929
976
  // nord7
930
977
  cyan: "#88c0d0",
931
978
  // nord8
932
- gray: "#4c566a"
979
+ gray: "#4c566a",
933
980
  // nord3
981
+ black: "#3b4252",
982
+ white: "#eceff4"
934
983
  }
935
984
  }
936
985
  };
@@ -968,7 +1017,9 @@ var init_one_dark = __esm({
968
1017
  purple: "#a626a4",
969
1018
  teal: "#0184bc",
970
1019
  cyan: "#0997b3",
971
- gray: "#696c77"
1020
+ gray: "#696c77",
1021
+ black: "#383a42",
1022
+ white: "#f0f0f0"
972
1023
  }
973
1024
  },
974
1025
  dark: {
@@ -992,7 +1043,9 @@ var init_one_dark = __esm({
992
1043
  purple: "#c678dd",
993
1044
  teal: "#56b6c2",
994
1045
  cyan: "#56b6c2",
995
- gray: "#5c6370"
1046
+ gray: "#5c6370",
1047
+ black: "#21252b",
1048
+ white: "#abb2bf"
996
1049
  }
997
1050
  }
998
1051
  };
@@ -1047,8 +1100,10 @@ var init_rose_pine = __esm({
1047
1100
  // Pine
1048
1101
  cyan: "#56949f",
1049
1102
  // Foam
1050
- gray: "#9893a5"
1103
+ gray: "#9893a5",
1051
1104
  // Muted
1105
+ black: "#575279",
1106
+ white: "#fffaf3"
1052
1107
  }
1053
1108
  },
1054
1109
  dark: {
@@ -1089,8 +1144,10 @@ var init_rose_pine = __esm({
1089
1144
  // Pine
1090
1145
  cyan: "#9ccfd8",
1091
1146
  // Foam
1092
- gray: "#6e6a86"
1147
+ gray: "#6e6a86",
1093
1148
  // Muted
1149
+ black: "#2a273f",
1150
+ white: "#e0def4"
1094
1151
  }
1095
1152
  }
1096
1153
  };
@@ -1138,8 +1195,10 @@ var init_solarized = __esm({
1138
1195
  teal: "#2aa198",
1139
1196
  cyan: "#2aa198",
1140
1197
  // Solarized has no separate cyan — reuse teal
1141
- gray: "#586e75"
1198
+ gray: "#586e75",
1142
1199
  // base01
1200
+ black: "#657b83",
1201
+ white: "#eee8d5"
1143
1202
  }
1144
1203
  },
1145
1204
  dark: {
@@ -1172,8 +1231,10 @@ var init_solarized = __esm({
1172
1231
  purple: "#6c71c4",
1173
1232
  teal: "#2aa198",
1174
1233
  cyan: "#2aa198",
1175
- gray: "#586e75"
1234
+ gray: "#586e75",
1176
1235
  // base01
1236
+ black: "#073642",
1237
+ white: "#839496"
1177
1238
  }
1178
1239
  }
1179
1240
  };
@@ -1220,8 +1281,10 @@ var init_tokyo_night = __esm({
1220
1281
  purple: "#7847bd",
1221
1282
  teal: "#118c74",
1222
1283
  cyan: "#007197",
1223
- gray: "#8990b3"
1284
+ gray: "#8990b3",
1224
1285
  // Day dark3
1286
+ black: "#1a1b26",
1287
+ white: "#d0d5e3"
1225
1288
  }
1226
1289
  },
1227
1290
  dark: {
@@ -1254,8 +1317,10 @@ var init_tokyo_night = __esm({
1254
1317
  purple: "#bb9af7",
1255
1318
  teal: "#1abc9c",
1256
1319
  cyan: "#7dcfff",
1257
- gray: "#565f89"
1320
+ gray: "#565f89",
1258
1321
  // Night comment
1322
+ black: "#292e42",
1323
+ white: "#c0caf5"
1259
1324
  }
1260
1325
  }
1261
1326
  };
@@ -1301,7 +1366,9 @@ var init_dracula = __esm({
1301
1366
  teal: "#5ac8b8",
1302
1367
  // muted cyan toward green
1303
1368
  cyan: "#8be9fd",
1304
- gray: "#6272a4"
1369
+ gray: "#6272a4",
1370
+ black: "#282a36",
1371
+ white: "#f0f0ec"
1305
1372
  }
1306
1373
  },
1307
1374
  dark: {
@@ -1336,7 +1403,9 @@ var init_dracula = __esm({
1336
1403
  teal: "#5ac8b8",
1337
1404
  // muted cyan toward green
1338
1405
  cyan: "#8be9fd",
1339
- gray: "#6272a4"
1406
+ gray: "#6272a4",
1407
+ black: "#343746",
1408
+ white: "#f8f8f2"
1340
1409
  }
1341
1410
  }
1342
1411
  };
@@ -1382,8 +1451,10 @@ var init_monokai = __esm({
1382
1451
  teal: "#4ea8a6",
1383
1452
  // muted from cyan
1384
1453
  cyan: "#66d9ef",
1385
- gray: "#75715e"
1454
+ gray: "#75715e",
1386
1455
  // comment
1456
+ black: "#272822",
1457
+ white: "#f0efe8"
1387
1458
  }
1388
1459
  },
1389
1460
  dark: {
@@ -1416,8 +1487,10 @@ var init_monokai = __esm({
1416
1487
  teal: "#4ea8a6",
1417
1488
  // muted from cyan
1418
1489
  cyan: "#66d9ef",
1419
- gray: "#75715e"
1490
+ gray: "#75715e",
1420
1491
  // comment
1492
+ black: "#2d2e27",
1493
+ white: "#f8f8f2"
1421
1494
  }
1422
1495
  }
1423
1496
  };
@@ -1648,13 +1721,19 @@ function measureIndent(line10) {
1648
1721
  }
1649
1722
  return indent;
1650
1723
  }
1651
- function extractColor(label, palette) {
1724
+ function extractColor(label, palette, diagnostics, line10) {
1652
1725
  const m = label.match(COLOR_SUFFIX_RE);
1653
1726
  if (!m) return { label };
1654
1727
  const colorName = m[1].trim();
1728
+ let color;
1729
+ if (diagnostics && line10 !== void 0) {
1730
+ color = resolveColorWithDiagnostic(colorName, line10, diagnostics, palette);
1731
+ } else {
1732
+ color = resolveColor(colorName, palette) ?? void 0;
1733
+ }
1655
1734
  return {
1656
1735
  label: label.substring(0, m.index).trim(),
1657
- color: resolveColor(colorName, palette) ?? void 0
1736
+ color
1658
1737
  };
1659
1738
  }
1660
1739
  function parseFirstLine(line10) {
@@ -1718,7 +1797,7 @@ function collectIndentedValues(lines, startIndex) {
1718
1797
  }
1719
1798
  return { values, lineNumbers, newIndex: j - 1 };
1720
1799
  }
1721
- function parseSeriesNames(value, lines, lineIndex, palette) {
1800
+ function parseSeriesNames(value, lines, lineIndex, palette, diagnostics) {
1722
1801
  let rawNames;
1723
1802
  let series;
1724
1803
  let newIndex = lineIndex;
@@ -1736,8 +1815,14 @@ function parseSeriesNames(value, lines, lineIndex, palette) {
1736
1815
  }
1737
1816
  const names = [];
1738
1817
  const nameColors = [];
1739
- for (const raw of rawNames) {
1740
- const extracted = extractColor(raw, palette);
1818
+ for (let i = 0; i < rawNames.length; i++) {
1819
+ const raw = rawNames[i];
1820
+ const extracted = extractColor(
1821
+ raw,
1822
+ palette,
1823
+ diagnostics,
1824
+ nameLineNumbers[i]
1825
+ );
1741
1826
  nameColors.push(extracted.color);
1742
1827
  names.push(extracted.label);
1743
1828
  }
@@ -3238,7 +3323,12 @@ function parseSequenceDgmo(content) {
3238
3323
  }
3239
3324
  if (currentTagGroup && !contentStarted && measureIndent(raw) > 0) {
3240
3325
  const { text: cleanEntry, isDefault } = stripDefaultModifier(trimmed);
3241
- const { label, color } = extractColor(cleanEntry);
3326
+ const { label, color } = extractColor(
3327
+ cleanEntry,
3328
+ void 0,
3329
+ result.diagnostics,
3330
+ lineNumber
3331
+ );
3242
3332
  if (!color) {
3243
3333
  pushError(
3244
3334
  lineNumber,
@@ -3951,18 +4041,37 @@ function splitArrows(line10) {
3951
4041
  }
3952
4042
  return segments;
3953
4043
  }
3954
- function parseArrowToken(token, palette) {
4044
+ function parseArrowToken(token, palette, lineNumber, diagnostics) {
3955
4045
  if (token === "->") return {};
3956
4046
  const colorOnly = token.match(/^-\(([^)]+)\)->$/);
3957
4047
  if (colorOnly) {
3958
- return { color: resolveColor(colorOnly[1].trim(), palette) ?? void 0 };
4048
+ return {
4049
+ color: resolveColorWithDiagnostic(
4050
+ colorOnly[1].trim(),
4051
+ lineNumber,
4052
+ diagnostics,
4053
+ palette
4054
+ )
4055
+ };
3959
4056
  }
3960
4057
  const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
3961
4058
  if (m) {
3962
4059
  const label = m[1]?.trim() || void 0;
3963
- let color = m[2] ? resolveColor(m[2].trim(), palette) ?? void 0 : void 0;
4060
+ let color = m[2] ? resolveColorWithDiagnostic(
4061
+ m[2].trim(),
4062
+ lineNumber,
4063
+ diagnostics,
4064
+ palette
4065
+ ) : void 0;
3964
4066
  if (label && !color) {
3965
- color = inferArrowColor(label);
4067
+ const inferred = inferArrowColor(label);
4068
+ if (inferred)
4069
+ color = resolveColorWithDiagnostic(
4070
+ inferred,
4071
+ lineNumber,
4072
+ diagnostics,
4073
+ palette
4074
+ );
3966
4075
  }
3967
4076
  return { label, color };
3968
4077
  }
@@ -4039,7 +4148,12 @@ function parseFlowchart(content, palette) {
4039
4148
  for (let i = 0; i < segments.length; i++) {
4040
4149
  const seg = segments[i];
4041
4150
  if (seg === "->" || /^-.+->$/.test(seg)) {
4042
- pendingArrow = parseArrowToken(seg, palette);
4151
+ pendingArrow = parseArrowToken(
4152
+ seg,
4153
+ palette,
4154
+ lineNumber,
4155
+ result.diagnostics
4156
+ );
4043
4157
  continue;
4044
4158
  }
4045
4159
  const ref = parseNodeRef(seg, palette);
@@ -4233,15 +4347,27 @@ function splitArrows2(line10) {
4233
4347
  if (remaining) segments.push(remaining);
4234
4348
  return segments;
4235
4349
  }
4236
- function parseArrowToken2(token, palette) {
4350
+ function parseArrowToken2(token, palette, lineNumber, diagnostics) {
4237
4351
  if (token === "->") return {};
4238
4352
  const colorOnly = token.match(/^-\(([^)]+)\)->$/);
4239
4353
  if (colorOnly)
4240
- return { color: resolveColor(colorOnly[1].trim(), palette) ?? void 0 };
4354
+ return {
4355
+ color: resolveColorWithDiagnostic(
4356
+ colorOnly[1].trim(),
4357
+ lineNumber,
4358
+ diagnostics,
4359
+ palette
4360
+ )
4361
+ };
4241
4362
  const m = token.match(/^-(.+?)(?:\(([^)]+)\))?->$/);
4242
4363
  if (m) {
4243
4364
  const label = m[1]?.trim() || void 0;
4244
- const color = m[2] ? resolveColor(m[2].trim(), palette) ?? void 0 : void 0;
4365
+ const color = m[2] ? resolveColorWithDiagnostic(
4366
+ m[2].trim(),
4367
+ lineNumber,
4368
+ diagnostics,
4369
+ palette
4370
+ ) : void 0;
4245
4371
  return { label, color };
4246
4372
  }
4247
4373
  return {};
@@ -4345,7 +4471,12 @@ function parseState(content, palette) {
4345
4471
  if (groupMatch && groupMatch[1].trim() !== "*") {
4346
4472
  const groupLabel = groupMatch[1].trim();
4347
4473
  const groupColorName = groupMatch[2]?.trim();
4348
- const groupColor = groupColorName ? resolveColor(groupColorName, palette) : void 0;
4474
+ const groupColor = groupColorName ? resolveColorWithDiagnostic(
4475
+ groupColorName,
4476
+ lineNumber,
4477
+ result.diagnostics,
4478
+ palette
4479
+ ) : void 0;
4349
4480
  currentGroup = {
4350
4481
  id: `group:${groupLabel.toLowerCase()}`,
4351
4482
  label: groupLabel,
@@ -4403,7 +4534,12 @@ function parseState(content, palette) {
4403
4534
  for (let j = 0; j < segments.length; j++) {
4404
4535
  const seg = segments[j];
4405
4536
  if (seg === "->" || /^-.+->$/.test(seg)) {
4406
- pendingArrow = parseArrowToken2(seg, palette);
4537
+ pendingArrow = parseArrowToken2(
4538
+ seg,
4539
+ palette,
4540
+ lineNumber,
4541
+ result.diagnostics
4542
+ );
4407
4543
  continue;
4408
4544
  }
4409
4545
  const ref = parseStateNodeRef(seg, palette);
@@ -4666,7 +4802,12 @@ function parseClassDiagram(content, palette) {
4666
4802
  const bracketModifier = classDecl[5];
4667
4803
  const modifier = prefixModifier ?? bracketModifier;
4668
4804
  const colorName = classDecl[6]?.trim();
4669
- const color = colorName ? resolveColor(colorName, palette) : void 0;
4805
+ const color = colorName ? resolveColorWithDiagnostic(
4806
+ colorName,
4807
+ lineNumber,
4808
+ result.diagnostics,
4809
+ palette
4810
+ ) : void 0;
4670
4811
  const node = getOrCreateClass(name, lineNumber);
4671
4812
  if (modifier) node.modifier = modifier;
4672
4813
  if (color) node.color = color;
@@ -5036,7 +5177,12 @@ function parseERDiagram(content, palette) {
5036
5177
  if (tableDecl) {
5037
5178
  const name = tableDecl[1];
5038
5179
  const colorName = tableDecl[2]?.trim();
5039
- const color = colorName ? resolveColor(colorName, palette) : void 0;
5180
+ const color = colorName ? resolveColorWithDiagnostic(
5181
+ colorName,
5182
+ lineNumber,
5183
+ result.diagnostics,
5184
+ palette
5185
+ ) : void 0;
5040
5186
  const table = getOrCreateTable(name, lineNumber);
5041
5187
  if (color) table.color = color;
5042
5188
  table.lineNumber = lineNumber;
@@ -5164,7 +5310,7 @@ var init_parser3 = __esm({
5164
5310
  init_parsing();
5165
5311
  init_tag_groups();
5166
5312
  TABLE_DECL_RE = /^([a-zA-Z_]\w*)(?:\s*\(([^)]+)\))?(?:\s*\|(.+))?$/;
5167
- INDENT_REL_RE = /^([1*?])-(?:(.+)-)?([1*?])\s+([a-zA-Z_]\w*)\s*$/;
5313
+ INDENT_REL_RE = /^([1*?])-{1,2}(?:(.+?)-{1,2})?([1*?])\s+([a-zA-Z_]\w*)\s*$/;
5168
5314
  CONSTRAINT_MAP = {
5169
5315
  pk: "pk",
5170
5316
  fk: "fk",
@@ -5253,7 +5399,12 @@ function parseChart(content, palette) {
5253
5399
  rawEras.push({
5254
5400
  start: eraMatch[1].trim(),
5255
5401
  afterArrow,
5256
- color: eraMatch[3] ? resolveColor(eraMatch[3].trim(), palette) : null,
5402
+ color: eraMatch[3] ? resolveColorWithDiagnostic(
5403
+ eraMatch[3].trim(),
5404
+ lineNumber,
5405
+ result.diagnostics,
5406
+ palette
5407
+ ) ?? null : null,
5257
5408
  lineNumber
5258
5409
  });
5259
5410
  }
@@ -5302,11 +5453,22 @@ function parseChart(content, palette) {
5302
5453
  continue;
5303
5454
  }
5304
5455
  if (firstToken === "color") {
5305
- result.color = resolveColor(value.trim(), palette) ?? void 0;
5456
+ result.color = resolveColorWithDiagnostic(
5457
+ value.trim(),
5458
+ lineNumber,
5459
+ result.diagnostics,
5460
+ palette
5461
+ );
5306
5462
  continue;
5307
5463
  }
5308
5464
  if (firstToken === "series") {
5309
- const parsed = parseSeriesNames(value, lines, i, palette);
5465
+ const parsed = parseSeriesNames(
5466
+ value,
5467
+ lines,
5468
+ i,
5469
+ palette,
5470
+ result.diagnostics
5471
+ );
5310
5472
  i = parsed.newIndex;
5311
5473
  result.series = parsed.series;
5312
5474
  result.seriesLineNumber = lineNumber;
@@ -5727,7 +5889,12 @@ function parseExtendedChart(content, palette) {
5727
5889
  const categoryMatch = trimmed.match(/^\[(.+?)\](?:\s*\(([^)]+)\))?\s*$/);
5728
5890
  if (categoryMatch) {
5729
5891
  const catName = categoryMatch[1].trim();
5730
- const catColor = categoryMatch[2] ? resolveColor(categoryMatch[2].trim(), palette) : null;
5892
+ const catColor = categoryMatch[2] ? resolveColorWithDiagnostic(
5893
+ categoryMatch[2].trim(),
5894
+ lineNumber,
5895
+ result.diagnostics,
5896
+ palette
5897
+ ) ?? null : null;
5731
5898
  if (catColor) {
5732
5899
  if (!result.categoryColors) result.categoryColors = {};
5733
5900
  result.categoryColors[catName] = catColor;
@@ -5755,7 +5922,12 @@ function parseExtendedChart(content, palette) {
5755
5922
  if (sourceColor) result.nodeColors[source] = sourceColor;
5756
5923
  if (targetColor) result.nodeColors[target] = targetColor;
5757
5924
  }
5758
- const linkColor = rawLinkColor ? resolveColor(rawLinkColor.trim(), palette) : void 0;
5925
+ const linkColor = rawLinkColor ? resolveColorWithDiagnostic(
5926
+ rawLinkColor.trim(),
5927
+ lineNumber,
5928
+ result.diagnostics,
5929
+ palette
5930
+ ) : void 0;
5759
5931
  if (!result.links) result.links = [];
5760
5932
  result.links.push({
5761
5933
  source,
@@ -5781,7 +5953,12 @@ function parseExtendedChart(content, palette) {
5781
5953
  const dataRow2 = parseDataRowValues(strippedLine);
5782
5954
  if (dataRow2 && dataRow2.values.length === 1) {
5783
5955
  const source = sankeyStack.at(-1).name;
5784
- const linkColor = valColorMatch?.[2] ? resolveColor(valColorMatch[2].trim(), palette) : void 0;
5956
+ const linkColor = valColorMatch?.[2] ? resolveColorWithDiagnostic(
5957
+ valColorMatch[2].trim(),
5958
+ lineNumber,
5959
+ result.diagnostics,
5960
+ palette
5961
+ ) : void 0;
5785
5962
  const { label: target, color: targetColor } = extractColor(
5786
5963
  dataRow2.label,
5787
5964
  palette
@@ -8250,7 +8427,12 @@ function parseKanban(content, palette) {
8250
8427
  currentCard = null;
8251
8428
  columnCounter++;
8252
8429
  const colName = columnMatch[1].trim();
8253
- const colColor = columnMatch[2] ? resolveColor(columnMatch[2].trim(), palette) ?? void 0 : void 0;
8430
+ const colColor = columnMatch[2] ? resolveColorWithDiagnostic(
8431
+ columnMatch[2].trim(),
8432
+ lineNumber,
8433
+ result.diagnostics,
8434
+ palette
8435
+ ) : void 0;
8254
8436
  let wipLimit;
8255
8437
  const columnMetadata = {};
8256
8438
  const pipeStr = columnMatch[3];
@@ -8298,7 +8480,8 @@ function parseKanban(content, palette) {
8298
8480
  lineNumber,
8299
8481
  cardCounter,
8300
8482
  aliasMap,
8301
- palette
8483
+ palette,
8484
+ result.diagnostics
8302
8485
  );
8303
8486
  if (currentColumn.metadata) {
8304
8487
  for (const [key, value] of Object.entries(currentColumn.metadata)) {
@@ -8352,7 +8535,7 @@ function parseKanban(content, palette) {
8352
8535
  validateTagGroupNames(result.tagGroups, warn);
8353
8536
  return result;
8354
8537
  }
8355
- function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
8538
+ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette, diagnostics) {
8356
8539
  const pipeIdx = trimmed.indexOf("|");
8357
8540
  let rawTitle;
8358
8541
  let tagsStr = null;
@@ -8362,7 +8545,12 @@ function parseCardLine(trimmed, lineNumber, counter, aliasMap, palette) {
8362
8545
  } else {
8363
8546
  rawTitle = trimmed;
8364
8547
  }
8365
- const { label: title, color } = extractColor(rawTitle, palette);
8548
+ const { label: title, color } = extractColor(
8549
+ rawTitle,
8550
+ palette,
8551
+ diagnostics,
8552
+ lineNumber
8553
+ );
8366
8554
  const tags = {};
8367
8555
  if (tagsStr) {
8368
8556
  for (const part of tagsStr.split(",")) {
@@ -9097,7 +9285,7 @@ __export(parser_exports7, {
9097
9285
  looksLikeSitemap: () => looksLikeSitemap,
9098
9286
  parseSitemap: () => parseSitemap
9099
9287
  });
9100
- function parseArrowLine(trimmed, palette) {
9288
+ function parseArrowLine(trimmed, palette, lineNumber, diagnostics) {
9101
9289
  const bareMatch = trimmed.match(BARE_ARROW_RE);
9102
9290
  if (bareMatch) {
9103
9291
  const rawTarget = bareMatch[1].trim();
@@ -9110,9 +9298,21 @@ function parseArrowLine(trimmed, palette) {
9110
9298
  const arrowMatch = trimmed.match(ARROW_RE);
9111
9299
  if (arrowMatch) {
9112
9300
  const label = arrowMatch[1]?.trim() || void 0;
9113
- let color = arrowMatch[2] ? resolveColor(arrowMatch[2].trim(), palette) ?? void 0 : void 0;
9301
+ let color = arrowMatch[2] ? resolveColorWithDiagnostic(
9302
+ arrowMatch[2].trim(),
9303
+ lineNumber,
9304
+ diagnostics,
9305
+ palette
9306
+ ) : void 0;
9114
9307
  if (label && !color) {
9115
- color = inferArrowColor(label);
9308
+ const inferred = inferArrowColor(label);
9309
+ if (inferred)
9310
+ color = resolveColorWithDiagnostic(
9311
+ inferred,
9312
+ lineNumber,
9313
+ diagnostics,
9314
+ palette
9315
+ );
9116
9316
  }
9117
9317
  const rawTarget = arrowMatch[3].trim();
9118
9318
  const groupMatch = rawTarget.match(/^\[(.+)\]$/);
@@ -9276,7 +9476,12 @@ function parseSitemap(content, palette) {
9276
9476
  contentStarted = true;
9277
9477
  currentTagGroup = null;
9278
9478
  const indent = measureIndent(line10);
9279
- const arrowInfo = parseArrowLine(trimmed, palette);
9479
+ const arrowInfo = parseArrowLine(
9480
+ trimmed,
9481
+ palette,
9482
+ lineNumber,
9483
+ result.diagnostics
9484
+ );
9280
9485
  if (arrowInfo) {
9281
9486
  const source = findParentNode(indent, indentStack);
9282
9487
  if (!source) {
@@ -9297,7 +9502,12 @@ function parseSitemap(content, palette) {
9297
9502
  const metadataMatch = trimmed.includes("|") ? null : trimmed.match(METADATA_RE3);
9298
9503
  if (containerMatch) {
9299
9504
  const rawLabel = containerMatch[1].trim();
9300
- const { label, color } = extractColor(rawLabel, palette);
9505
+ const { label, color } = extractColor(
9506
+ rawLabel,
9507
+ palette,
9508
+ result.diagnostics,
9509
+ lineNumber
9510
+ );
9301
9511
  const pipeStr = containerMatch[2];
9302
9512
  const containerMetadata = {};
9303
9513
  if (pipeStr) {
@@ -9338,7 +9548,8 @@ function parseSitemap(content, palette) {
9338
9548
  palette,
9339
9549
  ++nodeCounter,
9340
9550
  aliasMap,
9341
- pushWarning
9551
+ pushWarning,
9552
+ result.diagnostics
9342
9553
  );
9343
9554
  attachNode2(node, indent, indentStack, result);
9344
9555
  labelToNode.set(node.label.toLowerCase(), node);
@@ -9352,7 +9563,8 @@ function parseSitemap(content, palette) {
9352
9563
  palette,
9353
9564
  ++nodeCounter,
9354
9565
  aliasMap,
9355
- pushWarning
9566
+ pushWarning,
9567
+ result.diagnostics
9356
9568
  );
9357
9569
  attachNode2(node, indent, indentStack, result);
9358
9570
  labelToNode.set(node.label.toLowerCase(), node);
@@ -9415,10 +9627,15 @@ function parseSitemap(content, palette) {
9415
9627
  }
9416
9628
  return result;
9417
9629
  }
9418
- function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
9630
+ function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn, diagnostics) {
9419
9631
  const segments = trimmed.split("|").map((s) => s.trim());
9420
9632
  const rawLabel = segments[0];
9421
- const { label, color } = extractColor(rawLabel, palette);
9633
+ const { label, color } = extractColor(
9634
+ rawLabel,
9635
+ palette,
9636
+ diagnostics,
9637
+ lineNumber
9638
+ );
9422
9639
  const metadata = parsePipeMetadata(
9423
9640
  segments,
9424
9641
  aliasMap,
@@ -9710,9 +9927,13 @@ function parseInfra(content) {
9710
9927
  const tvMatch = cleanEntry.match(TAG_VALUE_RE);
9711
9928
  if (tvMatch) {
9712
9929
  const valueName = tvMatch[1].trim();
9930
+ const rawColor = tvMatch[2]?.trim();
9931
+ if (rawColor) {
9932
+ resolveColorWithDiagnostic(rawColor, lineNumber, result.diagnostics);
9933
+ }
9713
9934
  currentTagGroup.values.push({
9714
9935
  name: valueName,
9715
- color: tvMatch[2]?.trim()
9936
+ color: rawColor
9716
9937
  });
9717
9938
  if (isDefault) {
9718
9939
  currentTagGroup.defaultValue = valueName;
@@ -10064,6 +10285,7 @@ var init_parser8 = __esm({
10064
10285
  "src/infra/parser.ts"() {
10065
10286
  "use strict";
10066
10287
  init_diagnostics();
10288
+ init_colors();
10067
10289
  init_parsing();
10068
10290
  init_tag_groups();
10069
10291
  init_types();
@@ -10125,9 +10347,11 @@ function buildHolidaySet(holidays) {
10125
10347
  function addBusinessDays(startDate, count, workweek, holidaySet, direction = 1) {
10126
10348
  const days = Math.round(Math.abs(count));
10127
10349
  if (days === 0) return new Date(startDate);
10350
+ if (Number.isNaN(startDate.getTime())) return new Date(startDate);
10128
10351
  const current = new Date(startDate);
10129
10352
  let remaining = days;
10130
- while (remaining > 0) {
10353
+ let safety = days * 14 + 14;
10354
+ while (remaining > 0 && safety-- > 0) {
10131
10355
  current.setDate(current.getDate() + direction);
10132
10356
  if (isWorkday(current, workweek, holidaySet)) {
10133
10357
  remaining--;
@@ -14232,6 +14456,7 @@ var init_mutations = __esm({
14232
14456
  // src/kanban/renderer.ts
14233
14457
  var renderer_exports3 = {};
14234
14458
  __export(renderer_exports3, {
14459
+ bucketCardsBySwimlane: () => bucketCardsBySwimlane,
14235
14460
  renderKanban: () => renderKanban,
14236
14461
  renderKanbanForExport: () => renderKanbanForExport
14237
14462
  });
@@ -14326,7 +14551,14 @@ function computeLayout(parsed, _palette) {
14326
14551
  const totalHeight = startY + maxColumnHeight + DIAGRAM_PADDING3;
14327
14552
  return { columns: columnLayouts, totalWidth, totalHeight };
14328
14553
  }
14329
- function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exportDims, activeTagGroup) {
14554
+ function renderKanban(container, parsed, palette, isDark, options) {
14555
+ const exportDims = options?.exportDims;
14556
+ const activeTagGroup = options?.activeTagGroup ?? null;
14557
+ const onSwimlaneChange = options?.onSwimlaneChange;
14558
+ const requestedSwimlane = options?.currentSwimlaneGroup ?? null;
14559
+ const swimlaneGroup = requestedSwimlane ? parsed.tagGroups.find(
14560
+ (g) => g.name.toLowerCase() === requestedSwimlane.toLowerCase()
14561
+ ) ?? null : null;
14330
14562
  const layout = computeLayout(parsed, palette);
14331
14563
  const width = exportDims?.width ?? layout.totalWidth;
14332
14564
  const height = exportDims?.height ?? layout.totalHeight;
@@ -14346,16 +14578,49 @@ function renderKanban(container, parsed, palette, isDark, _onNavigateToLine, exp
14346
14578
  };
14347
14579
  const legendState = { activeGroup: activeTagGroup ?? null };
14348
14580
  const legendG = svg.append("g").attr("class", "kanban-legend").attr("transform", `translate(${legendX},${legendY})`);
14581
+ const showSwimlaneIcon = !!onSwimlaneChange && !exportDims;
14582
+ const legendCallbacks = showSwimlaneIcon ? {
14583
+ onGroupRendered: (groupName, groupEl, isActive) => {
14584
+ const isCurrent = swimlaneGroup?.name.toLowerCase() === groupName.toLowerCase();
14585
+ const textW = measureLegendText(groupName, 11);
14586
+ const iconX = isActive ? 4 + 6 + textW + 6 : 8 + textW + 4;
14587
+ const iconY = (LEGEND_HEIGHT - 7) / 2;
14588
+ const iconEl = drawSwimlaneIcon(
14589
+ groupEl,
14590
+ iconX,
14591
+ iconY,
14592
+ isCurrent,
14593
+ palette
14594
+ );
14595
+ iconEl.append("title").text(`Group by ${groupName}`);
14596
+ iconEl.style("cursor", "pointer").on("click", (event) => {
14597
+ event.stopPropagation();
14598
+ onSwimlaneChange?.(isCurrent ? null : groupName);
14599
+ });
14600
+ }
14601
+ } : void 0;
14349
14602
  renderLegendD3(
14350
14603
  legendG,
14351
14604
  legendConfig,
14352
14605
  legendState,
14353
14606
  palette,
14354
14607
  isDark,
14355
- void 0,
14608
+ legendCallbacks,
14356
14609
  width - legendX - DIAGRAM_PADDING3
14357
14610
  );
14358
14611
  }
14612
+ if (swimlaneGroup) {
14613
+ renderSwimlaneBoard(
14614
+ svg,
14615
+ parsed,
14616
+ layout,
14617
+ swimlaneGroup,
14618
+ palette,
14619
+ isDark,
14620
+ activeTagGroup
14621
+ );
14622
+ return;
14623
+ }
14359
14624
  const defaultColBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
14360
14625
  const defaultColHeaderBg = isDark ? mix(palette.surface, palette.bg, 70) : mix(palette.surface, palette.bg, 50);
14361
14626
  const cardBaseBg = isDark ? palette.surface : palette.bg;
@@ -14432,14 +14697,204 @@ function renderKanbanForExport(content, theme, palette) {
14432
14697
  const isDark = theme === "dark";
14433
14698
  const layout = computeLayout(parsed, palette);
14434
14699
  const container = document.createElement("div");
14435
- renderKanban(container, parsed, palette, isDark, void 0, {
14436
- width: layout.totalWidth,
14437
- height: layout.totalHeight
14700
+ renderKanban(container, parsed, palette, isDark, {
14701
+ exportDims: { width: layout.totalWidth, height: layout.totalHeight }
14438
14702
  });
14439
14703
  const svgEl = container.querySelector("svg");
14440
14704
  return svgEl?.outerHTML ?? "";
14441
14705
  }
14442
- var d3Selection3, DIAGRAM_PADDING3, COLUMN_GAP, COLUMN_HEADER_HEIGHT, COLUMN_PADDING, COLUMN_MIN_WIDTH, CARD_HEADER_HEIGHT, CARD_META_LINE_HEIGHT, CARD_SEPARATOR_GAP, CARD_GAP, CARD_RADIUS3, CARD_PADDING_X, CARD_PADDING_Y, CARD_STROKE_WIDTH, TITLE_HEIGHT3, COLUMN_HEADER_FONT_SIZE, CARD_TITLE_FONT_SIZE, CARD_META_FONT_SIZE, WIP_FONT_SIZE, COLUMN_RADIUS, COLUMN_HEADER_RADIUS;
14706
+ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
14707
+ const iconG = parent.append("g").attr("class", "kanban-swimlane-icon").attr("transform", `translate(${x}, ${y})`);
14708
+ const color = isActive ? palette.primary : palette.textMuted;
14709
+ const opacity = isActive ? 1 : 0.35;
14710
+ const barWidths = [8, 12, 6];
14711
+ const barH = 2;
14712
+ const gap = 3;
14713
+ for (let i = 0; i < barWidths.length; i++) {
14714
+ iconG.append("rect").attr("x", 0).attr("y", i * gap).attr("width", barWidths[i]).attr("height", barH).attr("rx", 1).attr("fill", color).attr("opacity", opacity);
14715
+ }
14716
+ return iconG;
14717
+ }
14718
+ function bucketCardsBySwimlane(columns, swimlaneGroup) {
14719
+ const tagKey = swimlaneGroup.name.toLowerCase();
14720
+ const buckets = /* @__PURE__ */ new Map();
14721
+ for (const entry of swimlaneGroup.entries) {
14722
+ buckets.set(entry.value.toLowerCase(), {
14723
+ laneName: entry.value,
14724
+ laneColor: entry.color,
14725
+ isFallback: false,
14726
+ cellsByColumn: {}
14727
+ });
14728
+ }
14729
+ const fallbackKey = `__no_${tagKey}__`;
14730
+ for (const col of columns) {
14731
+ for (const card of col.cards) {
14732
+ const raw = card.tags[tagKey] ?? swimlaneGroup.defaultValue;
14733
+ let bucketKey;
14734
+ if (raw && buckets.has(raw.toLowerCase())) {
14735
+ bucketKey = raw.toLowerCase();
14736
+ } else {
14737
+ if (!buckets.has(fallbackKey)) {
14738
+ buckets.set(fallbackKey, {
14739
+ laneName: `No ${swimlaneGroup.name}`,
14740
+ isFallback: true,
14741
+ cellsByColumn: {}
14742
+ });
14743
+ }
14744
+ bucketKey = fallbackKey;
14745
+ }
14746
+ const bucket = buckets.get(bucketKey);
14747
+ (bucket.cellsByColumn[col.id] ??= []).push(card);
14748
+ }
14749
+ }
14750
+ return Array.from(buckets.values());
14751
+ }
14752
+ function computeCardHeight3(card, tagGroups) {
14753
+ const tagMeta = resolveCardTagMeta(card, tagGroups);
14754
+ const metaCount = tagMeta.length + card.details.length;
14755
+ const metaHeight = metaCount > 0 ? CARD_SEPARATOR_GAP + 1 + CARD_PADDING_Y + metaCount * CARD_META_LINE_HEIGHT : 0;
14756
+ return CARD_HEADER_HEIGHT + CARD_PADDING_Y + metaHeight;
14757
+ }
14758
+ function computeSwimlaneLayout(parsed, buckets, baseLayout) {
14759
+ const headerHeight = parsed.title ? TITLE_HEIGHT3 + 8 : 0;
14760
+ const startY = DIAGRAM_PADDING3 + headerHeight;
14761
+ const columnXs = [];
14762
+ let currentX = DIAGRAM_PADDING3 + LANE_HEADER_WIDTH;
14763
+ for (const col of baseLayout.columns) {
14764
+ columnXs.push({ column: col.column, x: currentX, width: col.width });
14765
+ currentX += col.width + COLUMN_GAP;
14766
+ }
14767
+ const totalWidth = currentX - COLUMN_GAP + DIAGRAM_PADDING3;
14768
+ const lanes = [];
14769
+ let laneY = startY + COLUMN_HEADER_HEIGHT + COLUMN_PADDING;
14770
+ const minCellH = CARD_HEADER_HEIGHT + CARD_PADDING_Y + CARD_GAP;
14771
+ for (const bucket of buckets) {
14772
+ let maxCellH = minCellH;
14773
+ const cellsTmp = [];
14774
+ for (const colInfo of columnXs) {
14775
+ const cards = bucket.cellsByColumn[colInfo.column.id] ?? [];
14776
+ let h = 0;
14777
+ for (const c of cards) {
14778
+ h += computeCardHeight3(c, parsed.tagGroups) + CARD_GAP;
14779
+ }
14780
+ h = Math.max(h - (cards.length > 0 ? CARD_GAP : 0), 0);
14781
+ cellsTmp.push({ column: colInfo.column, cards, h });
14782
+ if (h > maxCellH) maxCellH = h;
14783
+ }
14784
+ const laneHeight = Math.max(maxCellH, minCellH);
14785
+ const cells = cellsTmp.map((tmp, i) => {
14786
+ const colInfo = columnXs[i];
14787
+ const cardLayouts = [];
14788
+ let cy = 0;
14789
+ for (const card of tmp.cards) {
14790
+ const ch = computeCardHeight3(card, parsed.tagGroups);
14791
+ cardLayouts.push({
14792
+ x: colInfo.x + COLUMN_PADDING,
14793
+ y: laneY + cy,
14794
+ width: colInfo.width - COLUMN_PADDING * 2,
14795
+ height: ch,
14796
+ card
14797
+ });
14798
+ cy += ch + CARD_GAP;
14799
+ }
14800
+ return { column: tmp.column, cards: tmp.cards, cardLayouts };
14801
+ });
14802
+ lanes.push({ bucket, y: laneY, height: laneHeight, cells });
14803
+ laneY += laneHeight + LANE_GAP;
14804
+ }
14805
+ const totalHeight = laneY - LANE_GAP + COLUMN_PADDING + DIAGRAM_PADDING3;
14806
+ return { columnXs, lanes, totalWidth, totalHeight, startY };
14807
+ }
14808
+ function renderSwimlaneBoard(svg, parsed, baseLayout, swimlaneGroup, palette, isDark, activeTagGroup) {
14809
+ const visibleColumns = parsed.columns.filter((c) => !isArchiveColumn(c.name));
14810
+ const buckets = bucketCardsBySwimlane(visibleColumns, swimlaneGroup);
14811
+ const grid = computeSwimlaneLayout(parsed, buckets, baseLayout);
14812
+ const currentW = parseFloat(svg.attr("width") || "0");
14813
+ const currentH = parseFloat(svg.attr("height") || "0");
14814
+ if (grid.totalWidth > currentW) svg.attr("width", grid.totalWidth);
14815
+ if (grid.totalHeight > currentH) svg.attr("height", grid.totalHeight);
14816
+ const defaultColBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
14817
+ const defaultColHeaderBg = isDark ? mix(palette.surface, palette.bg, 70) : mix(palette.surface, palette.bg, 50);
14818
+ const cardBaseBg = isDark ? palette.surface : palette.bg;
14819
+ for (const colInfo of grid.columnXs) {
14820
+ const col = colInfo.column;
14821
+ const headerG = svg.append("g").attr("class", "kanban-column kanban-column-header").attr("data-column-id", col.id).attr("data-line-number", col.lineNumber);
14822
+ const colHeaderBg = col.color ? mix(col.color, palette.bg, 25) : defaultColHeaderBg;
14823
+ headerG.append("rect").attr("x", colInfo.x).attr("y", grid.startY).attr("width", colInfo.width).attr("height", COLUMN_HEADER_HEIGHT).attr("rx", COLUMN_HEADER_RADIUS).attr("fill", colHeaderBg);
14824
+ headerG.append("text").attr("x", colInfo.x + COLUMN_PADDING).attr(
14825
+ "y",
14826
+ grid.startY + COLUMN_HEADER_HEIGHT / 2 + COLUMN_HEADER_FONT_SIZE / 2 - 2
14827
+ ).attr("font-size", COLUMN_HEADER_FONT_SIZE).attr("font-weight", "bold").attr("fill", palette.text).text(col.name);
14828
+ }
14829
+ for (const lane of grid.lanes) {
14830
+ const laneG = svg.append("g").attr("class", "kanban-lane").attr("data-lane-name", lane.bucket.laneName);
14831
+ const headerG = laneG.append("g").attr("class", "kanban-lane-header").attr(
14832
+ "transform",
14833
+ `translate(${DIAGRAM_PADDING3}, ${lane.y - COLUMN_PADDING})`
14834
+ );
14835
+ headerG.append("rect").attr("x", 0).attr("y", 0).attr("width", LANE_HEADER_WIDTH - 8).attr("height", lane.height + COLUMN_PADDING * 2).attr("rx", COLUMN_RADIUS).attr("fill", defaultColBg);
14836
+ let labelX = 10;
14837
+ if (lane.bucket.laneColor) {
14838
+ headerG.append("circle").attr("cx", labelX + 4).attr("cy", 16).attr("r", 4).attr("fill", lane.bucket.laneColor);
14839
+ labelX += 14;
14840
+ }
14841
+ headerG.append("text").attr("x", labelX).attr("y", 20).attr("font-size", 12).attr("font-weight", "bold").attr("fill", lane.bucket.isFallback ? palette.textMuted : palette.text).text(lane.bucket.laneName);
14842
+ const totalCards = lane.cells.reduce((s, c) => s + c.cards.length, 0);
14843
+ headerG.append("text").attr("x", labelX).attr("y", 36).attr("font-size", 10).attr("fill", palette.textMuted).text(`(${totalCards})`);
14844
+ laneG.append("line").attr("x1", DIAGRAM_PADDING3 + LANE_HEADER_WIDTH).attr("x2", grid.totalWidth - DIAGRAM_PADDING3).attr("y1", lane.y + lane.height + COLUMN_PADDING).attr("y2", lane.y + lane.height + COLUMN_PADDING).attr("stroke", palette.border ?? palette.textMuted).attr("stroke-opacity", 0.4).attr("stroke-width", 1);
14845
+ for (const cell of lane.cells) {
14846
+ for (const cardLayout of cell.cardLayouts) {
14847
+ renderSwimlaneCard(
14848
+ laneG,
14849
+ cardLayout,
14850
+ parsed.tagGroups,
14851
+ activeTagGroup,
14852
+ palette,
14853
+ cardBaseBg
14854
+ );
14855
+ }
14856
+ }
14857
+ }
14858
+ }
14859
+ function renderSwimlaneCard(parent, cardLayout, tagGroups, activeTagGroup, palette, cardBaseBg) {
14860
+ const card = cardLayout.card;
14861
+ const resolvedColor = resolveCardTagColor(card, tagGroups, activeTagGroup);
14862
+ const tagMeta = resolveCardTagMeta(card, tagGroups);
14863
+ const hasMeta = tagMeta.length > 0 || card.details.length > 0;
14864
+ const cardFill = resolvedColor ? mix(resolvedColor, cardBaseBg, 15) : mix(palette.primary, cardBaseBg, 15);
14865
+ const cardStroke = resolvedColor ?? palette.textMuted;
14866
+ const cg = parent.append("g").attr("class", "kanban-card").attr("data-card-id", card.id).attr("data-line-number", card.lineNumber);
14867
+ if (activeTagGroup) {
14868
+ const tagKey = activeTagGroup.toLowerCase();
14869
+ const group = tagGroups.find((tg) => tg.name.toLowerCase() === tagKey);
14870
+ const value = card.tags[tagKey] ?? group?.defaultValue;
14871
+ if (value) {
14872
+ cg.attr(`data-tag-${tagKey}`, value.toLowerCase());
14873
+ }
14874
+ }
14875
+ const cx = cardLayout.x;
14876
+ const cy = cardLayout.y;
14877
+ cg.append("rect").attr("x", cx).attr("y", cy).attr("width", cardLayout.width).attr("height", cardLayout.height).attr("rx", CARD_RADIUS3).attr("fill", cardFill).attr("stroke", cardStroke).attr("stroke-width", CARD_STROKE_WIDTH);
14878
+ const titleEl = cg.append("text").attr("x", cx + CARD_PADDING_X).attr("y", cy + CARD_PADDING_Y + CARD_TITLE_FONT_SIZE).attr("font-size", CARD_TITLE_FONT_SIZE).attr("font-weight", "500").attr("fill", palette.text);
14879
+ renderInlineText(titleEl, card.title, palette, CARD_TITLE_FONT_SIZE);
14880
+ if (hasMeta) {
14881
+ const separatorY = cy + CARD_HEADER_HEIGHT;
14882
+ cg.append("line").attr("x1", cx).attr("y1", separatorY).attr("x2", cx + cardLayout.width).attr("y2", separatorY).attr("stroke", cardStroke).attr("stroke-opacity", 0.3).attr("stroke-width", 1);
14883
+ let metaY = separatorY + CARD_SEPARATOR_GAP + CARD_META_FONT_SIZE;
14884
+ for (const meta of tagMeta) {
14885
+ cg.append("text").attr("x", cx + CARD_PADDING_X).attr("y", metaY).attr("font-size", CARD_META_FONT_SIZE).attr("fill", palette.textMuted).text(`${meta.label}: `);
14886
+ const labelWidth = (meta.label.length + 2) * CARD_META_FONT_SIZE * 0.6;
14887
+ cg.append("text").attr("x", cx + CARD_PADDING_X + labelWidth).attr("y", metaY).attr("font-size", CARD_META_FONT_SIZE).attr("fill", palette.text).text(meta.value);
14888
+ metaY += CARD_META_LINE_HEIGHT;
14889
+ }
14890
+ for (const detail of card.details) {
14891
+ const detailEl = cg.append("text").attr("x", cx + CARD_PADDING_X).attr("y", metaY).attr("font-size", CARD_META_FONT_SIZE).attr("fill", palette.textMuted);
14892
+ renderInlineText(detailEl, detail, palette, CARD_META_FONT_SIZE);
14893
+ metaY += CARD_META_LINE_HEIGHT;
14894
+ }
14895
+ }
14896
+ }
14897
+ var d3Selection3, DIAGRAM_PADDING3, COLUMN_GAP, COLUMN_HEADER_HEIGHT, COLUMN_PADDING, COLUMN_MIN_WIDTH, CARD_HEADER_HEIGHT, CARD_META_LINE_HEIGHT, CARD_SEPARATOR_GAP, CARD_GAP, CARD_RADIUS3, CARD_PADDING_X, CARD_PADDING_Y, CARD_STROKE_WIDTH, TITLE_HEIGHT3, COLUMN_HEADER_FONT_SIZE, CARD_TITLE_FONT_SIZE, CARD_META_FONT_SIZE, WIP_FONT_SIZE, COLUMN_RADIUS, COLUMN_HEADER_RADIUS, LANE_HEADER_WIDTH, LANE_GAP;
14443
14898
  var init_renderer3 = __esm({
14444
14899
  "src/kanban/renderer.ts"() {
14445
14900
  "use strict";
@@ -14472,6 +14927,8 @@ var init_renderer3 = __esm({
14472
14927
  WIP_FONT_SIZE = 10;
14473
14928
  COLUMN_RADIUS = 8;
14474
14929
  COLUMN_HEADER_RADIUS = 8;
14930
+ LANE_HEADER_WIDTH = 140;
14931
+ LANE_GAP = 14;
14475
14932
  }
14476
14933
  });
14477
14934
 
@@ -23614,7 +24071,7 @@ function resetHighlightAll(svg, chartG) {
23614
24071
  chartG.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", 1);
23615
24072
  chartG.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
23616
24073
  }
23617
- function drawSwimlaneIcon(parent, x, y, isActive, palette) {
24074
+ function drawSwimlaneIcon2(parent, x, y, isActive, palette) {
23618
24075
  const iconG = parent.append("g").attr("class", "gantt-swimlane-icon").attr("transform", `translate(${x}, ${y})`);
23619
24076
  const color = isActive ? palette.primary : palette.textMuted;
23620
24077
  const opacity = isActive ? 1 : 0.35;
@@ -23771,7 +24228,7 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
23771
24228
  const pillXOff = LEGEND_CAPSULE_PAD;
23772
24229
  const iconX = pillXOff + textW + 3;
23773
24230
  const iconY = (LEGEND_HEIGHT - 10) / 2;
23774
- const iconEl = drawSwimlaneIcon(
24231
+ const iconEl = drawSwimlaneIcon2(
23775
24232
  groupEl,
23776
24233
  iconX,
23777
24234
  iconY,
@@ -25199,10 +25656,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25199
25656
  );
25200
25657
  if (tg) {
25201
25658
  for (const entry of tg.entries) {
25202
- tagValueToColor.set(
25203
- entry.value.toLowerCase(),
25204
- resolveColor(entry.color) ?? entry.color
25205
- );
25659
+ tagValueToColor.set(entry.value.toLowerCase(), entry.color);
25206
25660
  }
25207
25661
  }
25208
25662
  }
@@ -25608,7 +26062,7 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
25608
26062
  name: tg.name,
25609
26063
  entries: tg.entries.map((e) => ({
25610
26064
  value: e.value,
25611
- color: resolveColor(e.color) ?? e.color
26065
+ color: e.color
25612
26066
  }))
25613
26067
  }));
25614
26068
  const legendConfig = {
@@ -26189,7 +26643,6 @@ var init_renderer10 = __esm({
26189
26643
  init_color_utils();
26190
26644
  init_inline_markdown();
26191
26645
  init_fonts();
26192
- init_colors();
26193
26646
  init_parser();
26194
26647
  init_tag_resolution();
26195
26648
  init_tag_groups();
@@ -26495,12 +26948,22 @@ function parseVisualization(content, palette) {
26495
26948
  if (groupMatch) {
26496
26949
  if (result.type === "arc") {
26497
26950
  const name = groupMatch[1].trim();
26498
- const color = groupMatch[2] ? resolveColor(groupMatch[2].trim(), palette) : null;
26951
+ const color = groupMatch[2] ? resolveColorWithDiagnostic(
26952
+ groupMatch[2].trim(),
26953
+ lineNumber,
26954
+ result.diagnostics,
26955
+ palette
26956
+ ) ?? null : null;
26499
26957
  result.arcNodeGroups.push({ name, nodes: [], color, lineNumber });
26500
26958
  currentArcGroup = name;
26501
26959
  } else if (result.type === "timeline") {
26502
26960
  const name = groupMatch[1].trim();
26503
- const color = groupMatch[2] ? resolveColor(groupMatch[2].trim(), palette) : null;
26961
+ const color = groupMatch[2] ? resolveColorWithDiagnostic(
26962
+ groupMatch[2].trim(),
26963
+ lineNumber,
26964
+ result.diagnostics,
26965
+ palette
26966
+ ) ?? null : null;
26504
26967
  result.timelineGroups.push({ name, color, lineNumber });
26505
26968
  currentTimelineGroup = name;
26506
26969
  }
@@ -26528,7 +26991,12 @@ function parseVisualization(content, palette) {
26528
26991
  if (linkMatch) {
26529
26992
  const source = linkMatch[1].trim();
26530
26993
  const target = linkMatch[2].trim();
26531
- const linkColor = linkMatch[3] ? resolveColor(linkMatch[3].trim(), palette) : null;
26994
+ const linkColor = linkMatch[3] ? resolveColorWithDiagnostic(
26995
+ linkMatch[3].trim(),
26996
+ lineNumber,
26997
+ result.diagnostics,
26998
+ palette
26999
+ ) ?? null : null;
26532
27000
  result.links.push({
26533
27001
  source,
26534
27002
  target,
@@ -26565,7 +27033,12 @@ function parseVisualization(content, palette) {
26565
27033
  startDate: eraEntryMatch[1],
26566
27034
  endDate: eraEntryMatch[2],
26567
27035
  label: eraEntryMatch[3].trim(),
26568
- color: colorAnnotation ? resolveColor(colorAnnotation, palette) : null,
27036
+ color: colorAnnotation ? resolveColorWithDiagnostic(
27037
+ colorAnnotation,
27038
+ lineNumber,
27039
+ result.diagnostics,
27040
+ palette
27041
+ ) ?? null : null,
26569
27042
  lineNumber
26570
27043
  });
26571
27044
  } else {
@@ -26587,7 +27060,12 @@ function parseVisualization(content, palette) {
26587
27060
  result.timelineMarkers.push({
26588
27061
  date: markerEntryMatch[1],
26589
27062
  label: markerEntryMatch[2].trim(),
26590
- color: colorAnnotation ? resolveColor(colorAnnotation, palette) : null,
27063
+ color: colorAnnotation ? resolveColorWithDiagnostic(
27064
+ colorAnnotation,
27065
+ lineNumber,
27066
+ result.diagnostics,
27067
+ palette
27068
+ ) ?? null : null,
26591
27069
  lineNumber
26592
27070
  });
26593
27071
  } else {
@@ -26616,7 +27094,12 @@ function parseVisualization(content, palette) {
26616
27094
  startDate: eraMatch[1],
26617
27095
  endDate: eraMatch[2],
26618
27096
  label: eraMatch[3].trim(),
26619
- color: colorAnnotation ? resolveColor(colorAnnotation, palette) : null,
27097
+ color: colorAnnotation ? resolveColorWithDiagnostic(
27098
+ colorAnnotation,
27099
+ lineNumber,
27100
+ result.diagnostics,
27101
+ palette
27102
+ ) ?? null : null,
26620
27103
  lineNumber
26621
27104
  });
26622
27105
  continue;
@@ -26629,7 +27112,12 @@ function parseVisualization(content, palette) {
26629
27112
  result.timelineMarkers.push({
26630
27113
  date: markerMatch[1],
26631
27114
  label: markerMatch[2].trim(),
26632
- color: colorAnnotation ? resolveColor(colorAnnotation, palette) : null,
27115
+ color: colorAnnotation ? resolveColorWithDiagnostic(
27116
+ colorAnnotation,
27117
+ lineNumber,
27118
+ result.diagnostics,
27119
+ palette
27120
+ ) ?? null : null,
26633
27121
  lineNumber
26634
27122
  });
26635
27123
  continue;
@@ -26744,20 +27232,12 @@ function parseVisualization(content, palette) {
26744
27232
  const colorName = setDeclMatch[2]?.trim() ?? null;
26745
27233
  let color = null;
26746
27234
  if (colorName) {
26747
- const resolved = resolveColor(colorName, palette);
26748
- if (resolved === null) {
26749
- warn(
26750
- lineNumber,
26751
- `Hex colors are not supported \u2014 use named colors (blue, red, green, etc.)`
26752
- );
26753
- } else if (resolved.startsWith("#")) {
26754
- color = resolved;
26755
- } else {
26756
- warn(
26757
- lineNumber,
26758
- `Unknown color "${colorName}" on set "${name}". Using auto-assigned color.`
26759
- );
26760
- }
27235
+ color = resolveColorWithDiagnostic(
27236
+ colorName,
27237
+ lineNumber,
27238
+ result.diagnostics,
27239
+ palette
27240
+ ) ?? null;
26761
27241
  }
26762
27242
  const alias = setDeclMatch[3]?.trim() ?? null;
26763
27243
  result.vennSets.push({ name, alias, color, lineNumber });
@@ -26806,7 +27286,12 @@ function parseVisualization(content, palette) {
26806
27286
  const labelPart = quadrantMatch[2].trim();
26807
27287
  const labelColorMatch = labelPart.match(/^(.+?)\s*\(([^)]+)\)\s*$/);
26808
27288
  const text = labelColorMatch ? labelColorMatch[1].trim() : labelPart;
26809
- const color = labelColorMatch ? resolveColor(labelColorMatch[2].trim(), palette) : null;
27289
+ const color = labelColorMatch ? resolveColorWithDiagnostic(
27290
+ labelColorMatch[2].trim(),
27291
+ lineNumber,
27292
+ result.diagnostics,
27293
+ palette
27294
+ ) ?? null : null;
26810
27295
  const label = { text, color, lineNumber };
26811
27296
  if (position === "top-right") result.quadrantLabels.topRight = label;
26812
27297
  else if (position === "top-left") result.quadrantLabels.topLeft = label;
@@ -26964,7 +27449,12 @@ function parseVisualization(content, palette) {
26964
27449
  }
26965
27450
  const colorMatch = joinedLabel.match(/^(.+?)\(([^)]+)\)\s*$/);
26966
27451
  const labelPart = colorMatch ? colorMatch[1].trim() : joinedLabel;
26967
- const colorPart = colorMatch ? resolveColor(colorMatch[2].trim(), palette) : null;
27452
+ const colorPart = colorMatch ? resolveColorWithDiagnostic(
27453
+ colorMatch[2].trim(),
27454
+ lineNumber,
27455
+ result.diagnostics,
27456
+ palette
27457
+ ) ?? null : null;
26968
27458
  if (!labelPart) {
26969
27459
  warn(
26970
27460
  lineNumber,
@@ -27035,7 +27525,12 @@ function parseVisualization(content, palette) {
27035
27525
  continue;
27036
27526
  }
27037
27527
  const labelPart = colorMatch ? colorMatch[1].trim() : rawKey;
27038
- const colorPart = colorMatch ? resolveColor(colorMatch[2].trim(), palette) : null;
27528
+ const colorPart = colorMatch ? resolveColorWithDiagnostic(
27529
+ colorMatch[2].trim(),
27530
+ lineNumber,
27531
+ result.diagnostics,
27532
+ palette
27533
+ ) ?? null : null;
27039
27534
  const valuePart = line10.substring(colonIndex + 1).trim();
27040
27535
  const values = valuePart.split(",").map((v) => v.trim());
27041
27536
  const numericValues = [];
@@ -28883,7 +29378,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
28883
29378
  const mainSvg = d3Selection13.select(container).select("svg");
28884
29379
  const mainG = mainSvg.select("g");
28885
29380
  if (!mainSvg.empty() && !mainG.empty()) {
28886
- let drawSwimlaneIcon3 = function(parent, x, y, isSwimActive) {
29381
+ let drawSwimlaneIcon4 = function(parent, x, y, isSwimActive) {
28887
29382
  const iconG = parent.append("g").attr("class", "tl-swimlane-icon").attr("transform", `translate(${x}, ${y})`).style("cursor", "pointer");
28888
29383
  const barColor = isSwimActive ? palette.primary : palette.textMuted;
28889
29384
  const barOpacity = isSwimActive ? 1 : 0.35;
@@ -28975,7 +29470,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
28975
29470
  const pillXOff = LG_CAPSULE_PAD;
28976
29471
  const iconX = pillXOff + pillWidth3 + 5;
28977
29472
  const iconY = (LG_HEIGHT - 10) / 2;
28978
- const iconEl = drawSwimlaneIcon3(
29473
+ const iconEl = drawSwimlaneIcon4(
28979
29474
  groupEl,
28980
29475
  iconX,
28981
29476
  iconY,
@@ -29025,7 +29520,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
29025
29520
  el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", mix(color, bg, 30)).attr("stroke", color);
29026
29521
  });
29027
29522
  };
29028
- var drawSwimlaneIcon2 = drawSwimlaneIcon3, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
29523
+ var drawSwimlaneIcon3 = drawSwimlaneIcon4, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
29029
29524
  const legendY = title ? 50 : 10;
29030
29525
  const legendGroups = parsed.timelineTagGroups.map((g) => {
29031
29526
  const pillW = measureLegendText(g.name, LG_PILL_FONT_SIZE) + LG_PILL_PAD;
@@ -29985,19 +30480,13 @@ async function renderForExport(content, theme, palette, orgExportState, options)
29985
30480
  container2.style.position = "absolute";
29986
30481
  container2.style.left = "-9999px";
29987
30482
  document.body.appendChild(container2);
29988
- renderKanban2(
29989
- container2,
29990
- kanbanParsed,
29991
- effectivePalette2,
29992
- theme === "dark",
29993
- void 0,
29994
- void 0,
29995
- resolveActiveTagGroup(
30483
+ renderKanban2(container2, kanbanParsed, effectivePalette2, theme === "dark", {
30484
+ activeTagGroup: resolveActiveTagGroup(
29996
30485
  kanbanParsed.tagGroups,
29997
30486
  kanbanParsed.options["active-tag"],
29998
30487
  options?.tagGroup
29999
30488
  )
30000
- );
30489
+ });
30001
30490
  return finalizeSvgExport(container2, theme, effectivePalette2, options);
30002
30491
  }
30003
30492
  if (detectedType === "class") {
@@ -30489,6 +30978,7 @@ __export(index_exports, {
30489
30978
  LEGEND_HEIGHT: () => LEGEND_HEIGHT,
30490
30979
  METADATA_KEY_SET: () => METADATA_KEY_SET,
30491
30980
  PIPE_METADATA: () => PIPE_METADATA,
30981
+ RECOGNIZED_COLOR_NAMES: () => RECOGNIZED_COLOR_NAMES,
30492
30982
  RULE_COUNT: () => RULE_COUNT,
30493
30983
  addDurationToDate: () => addDurationToDate,
30494
30984
  applyGroupOrdering: () => applyGroupOrdering,
@@ -30543,6 +31033,7 @@ __export(index_exports, {
30543
31033
  injectBranding: () => injectBranding,
30544
31034
  isArchiveColumn: () => isArchiveColumn,
30545
31035
  isExtendedChartType: () => isExtendedChartType,
31036
+ isRecognizedColorName: () => isRecognizedColorName,
30546
31037
  isSequenceBlock: () => isSequenceBlock,
30547
31038
  isSequenceNote: () => isSequenceNote,
30548
31039
  isValidHex: () => isValidHex,
@@ -30634,6 +31125,7 @@ __export(index_exports, {
30634
31125
  renderVenn: () => renderVenn,
30635
31126
  renderWordCloud: () => renderWordCloud,
30636
31127
  resolveColor: () => resolveColor,
31128
+ resolveColorWithDiagnostic: () => resolveColorWithDiagnostic,
30637
31129
  resolveOrgImports: () => resolveOrgImports,
30638
31130
  resolveTaskName: () => resolveTaskName,
30639
31131
  rollUpContextRelationships: () => rollUpContextRelationships,
@@ -30783,7 +31275,11 @@ function parseQuadrant(content) {
30783
31275
  if (labelMatch) {
30784
31276
  const label = {
30785
31277
  text: labelMatch[1].trim(),
30786
- color: labelMatch[2] ? resolveColor(labelMatch[2].trim()) : null,
31278
+ color: labelMatch[2] ? resolveColorWithDiagnostic(
31279
+ labelMatch[2].trim(),
31280
+ lineNumber,
31281
+ result.diagnostics
31282
+ ) ?? null : null,
30787
31283
  lineNumber
30788
31284
  };
30789
31285
  if (position === "top-right") result.quadrants.topRight = label;
@@ -31463,10 +31959,14 @@ function encodeDiagramUrl(dsl, options) {
31463
31959
  if (options?.viewState?.theme && options.viewState.theme !== "dark") {
31464
31960
  hash += `&th=${encodeURIComponent(options.viewState.theme)}`;
31465
31961
  }
31962
+ if (options?.filename) {
31963
+ hash += `&fn=${encodeURIComponent(options.filename)}`;
31964
+ }
31466
31965
  return { url: `${baseUrl}?${hash}#${hash}` };
31467
31966
  }
31468
31967
  function decodeDiagramUrl(hash) {
31469
31968
  const empty = { dsl: "", viewState: {} };
31969
+ let filename;
31470
31970
  if (!hash) return empty;
31471
31971
  let raw = hash;
31472
31972
  if (raw.startsWith("#") || raw.startsWith("?")) {
@@ -31495,16 +31995,17 @@ function decodeDiagramUrl(hash) {
31495
31995
  if (key === "pal" && val) viewState.palette = val;
31496
31996
  if (key === "th" && (val === "light" || val === "dark"))
31497
31997
  viewState.theme = val;
31998
+ if (key === "fn" && val) filename = val;
31498
31999
  }
31499
32000
  if (payload.startsWith("dgmo=")) {
31500
32001
  payload = payload.slice(5);
31501
32002
  }
31502
- if (!payload) return { dsl: "", viewState };
32003
+ if (!payload) return { dsl: "", viewState, filename };
31503
32004
  try {
31504
32005
  const result = decompressFromEncodedURIComponent(payload);
31505
- return { dsl: result ?? "", viewState };
32006
+ return { dsl: result ?? "", viewState, filename };
31506
32007
  } catch {
31507
- return { dsl: "", viewState };
32008
+ return { dsl: "", viewState, filename };
31508
32009
  }
31509
32010
  }
31510
32011
 
@@ -32262,6 +32763,7 @@ init_branding();
32262
32763
  LEGEND_HEIGHT,
32263
32764
  METADATA_KEY_SET,
32264
32765
  PIPE_METADATA,
32766
+ RECOGNIZED_COLOR_NAMES,
32265
32767
  RULE_COUNT,
32266
32768
  addDurationToDate,
32267
32769
  applyGroupOrdering,
@@ -32316,6 +32818,7 @@ init_branding();
32316
32818
  injectBranding,
32317
32819
  isArchiveColumn,
32318
32820
  isExtendedChartType,
32821
+ isRecognizedColorName,
32319
32822
  isSequenceBlock,
32320
32823
  isSequenceNote,
32321
32824
  isValidHex,
@@ -32407,6 +32910,7 @@ init_branding();
32407
32910
  renderVenn,
32408
32911
  renderWordCloud,
32409
32912
  resolveColor,
32913
+ resolveColorWithDiagnostic,
32410
32914
  resolveOrgImports,
32411
32915
  resolveTaskName,
32412
32916
  rollUpContextRelationships,