@diagrammo/dgmo 0.7.0 → 0.7.1

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.js CHANGED
@@ -1495,24 +1495,26 @@ function parseSeriesNames(value, lines, lineIndex, palette) {
1495
1495
  }
1496
1496
  return { series, names, nameColors, newIndex };
1497
1497
  }
1498
- function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
1498
+ function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map(), warnMultiplePipes) {
1499
+ if (segments.length > 2 && warnMultiplePipes) {
1500
+ warnMultiplePipes();
1501
+ }
1499
1502
  const metadata = {};
1500
- for (let j = 1; j < segments.length; j++) {
1501
- for (const part of segments[j].split(",")) {
1502
- const trimmedPart = part.trim();
1503
- if (!trimmedPart) continue;
1504
- const colonIdx = trimmedPart.indexOf(":");
1505
- if (colonIdx > 0) {
1506
- const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1507
- const key = aliasMap.get(rawKey) ?? rawKey;
1508
- const value = trimmedPart.substring(colonIdx + 1).trim();
1509
- metadata[key] = value;
1510
- }
1503
+ const raw = segments.slice(1).join(",");
1504
+ for (const part of raw.split(",")) {
1505
+ const trimmedPart = part.trim();
1506
+ if (!trimmedPart) continue;
1507
+ const colonIdx = trimmedPart.indexOf(":");
1508
+ if (colonIdx > 0) {
1509
+ const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1510
+ const key = aliasMap.get(rawKey) ?? rawKey;
1511
+ const value = trimmedPart.substring(colonIdx + 1).trim();
1512
+ metadata[key] = value;
1511
1513
  }
1512
1514
  }
1513
1515
  return metadata;
1514
1516
  }
1515
- var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
1517
+ var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE, MULTIPLE_PIPE_WARNING;
1516
1518
  var init_parsing = __esm({
1517
1519
  "src/utils/parsing.ts"() {
1518
1520
  "use strict";
@@ -1521,6 +1523,7 @@ var init_parsing = __esm({
1521
1523
  CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
1522
1524
  TITLE_RE = /^title\s*:\s*(.+)/i;
1523
1525
  OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
1526
+ MULTIPLE_PIPE_WARNING = 'Use a single "|" to start metadata, then separate items with commas.';
1524
1527
  }
1525
1528
  });
1526
1529
 
@@ -2021,12 +2024,13 @@ function parseSequenceDgmo(content) {
2021
2024
  const participantGroupMap = /* @__PURE__ */ new Map();
2022
2025
  let currentTagGroup = null;
2023
2026
  const aliasMap = /* @__PURE__ */ new Map();
2024
- const splitPipe = (text) => {
2027
+ const splitPipe = (text, ln) => {
2025
2028
  const idx = text.indexOf("|");
2026
2029
  if (idx < 0) return { core: text };
2027
2030
  const core = text.substring(0, idx).trimEnd();
2028
2031
  const segments = text.substring(idx).split("|");
2029
- const meta = parsePipeMetadata(segments, aliasMap);
2032
+ const warnFn = ln != null ? () => pushWarning(ln, MULTIPLE_PIPE_WARNING) : void 0;
2033
+ const meta = parsePipeMetadata(segments, aliasMap, warnFn);
2030
2034
  return Object.keys(meta).length > 0 ? { core, meta } : { core };
2031
2035
  };
2032
2036
  const blockStack = [];
@@ -2055,7 +2059,7 @@ function parseSequenceDgmo(content) {
2055
2059
  if (gpipeIdx >= 0) {
2056
2060
  const nameAndColor = groupName.substring(0, gpipeIdx).trimEnd();
2057
2061
  const segments = groupName.substring(gpipeIdx).split("|");
2058
- const meta = parsePipeMetadata(segments, aliasMap);
2062
+ const meta = parsePipeMetadata(segments, aliasMap, () => pushWarning(lineNumber, MULTIPLE_PIPE_WARNING));
2059
2063
  if (Object.keys(meta).length > 0) groupMeta = meta;
2060
2064
  const colorSuffix = nameAndColor.match(/^(.+?)\(([^)]+)\)$/);
2061
2065
  if (colorSuffix) {
@@ -2178,7 +2182,7 @@ function parseSequenceDgmo(content) {
2178
2182
  continue;
2179
2183
  }
2180
2184
  }
2181
- const { core: isACore, meta: isAMeta } = splitPipe(trimmed);
2185
+ const { core: isACore, meta: isAMeta } = splitPipe(trimmed, lineNumber);
2182
2186
  const isAMatch = isACore.match(IS_A_PATTERN);
2183
2187
  if (isAMatch) {
2184
2188
  contentStarted = true;
@@ -2215,7 +2219,7 @@ function parseSequenceDgmo(content) {
2215
2219
  }
2216
2220
  continue;
2217
2221
  }
2218
- const { core: posCore, meta: posMeta } = splitPipe(trimmed);
2222
+ const { core: posCore, meta: posMeta } = splitPipe(trimmed, lineNumber);
2219
2223
  const posOnlyMatch = posCore.match(POSITION_ONLY_PATTERN);
2220
2224
  if (posOnlyMatch) {
2221
2225
  contentStarted = true;
@@ -2242,7 +2246,7 @@ function parseSequenceDgmo(content) {
2242
2246
  }
2243
2247
  continue;
2244
2248
  }
2245
- const { core: colorCore, meta: colorMeta } = splitPipe(trimmed);
2249
+ const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
2246
2250
  const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
2247
2251
  if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
2248
2252
  const id = coloredMatch[1];
@@ -2270,7 +2274,7 @@ function parseSequenceDgmo(content) {
2270
2274
  continue;
2271
2275
  }
2272
2276
  {
2273
- const { core: bareCore, meta: bareMeta } = splitPipe(trimmed);
2277
+ const { core: bareCore, meta: bareMeta } = splitPipe(trimmed, lineNumber);
2274
2278
  const inGroup = activeGroup && measureIndent(raw) > 0;
2275
2279
  if (/^\S+$/.test(bareCore) && !ARROW_PATTERN.test(bareCore) && (inGroup || !contentStarted || bareMeta)) {
2276
2280
  contentStarted = true;
@@ -2306,7 +2310,7 @@ function parseSequenceDgmo(content) {
2306
2310
  }
2307
2311
  blockStack.pop();
2308
2312
  }
2309
- const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed);
2313
+ const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
2310
2314
  const asyncPrefixMatch = arrowCore.match(/^async\s+(.+)$/i);
2311
2315
  if (asyncPrefixMatch && ARROW_PATTERN.test(asyncPrefixMatch[1])) {
2312
2316
  pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
@@ -3757,7 +3761,12 @@ function parseERDiagram(content, palette) {
3757
3761
  table.lineNumber = lineNumber;
3758
3762
  const pipeStr = tableDecl[3]?.trim();
3759
3763
  if (pipeStr) {
3760
- const meta = parsePipeMetadata(["", pipeStr], aliasMap);
3764
+ const pipeSegments = pipeStr.split("|");
3765
+ const meta = parsePipeMetadata(
3766
+ ["", ...pipeSegments],
3767
+ aliasMap,
3768
+ () => result.diagnostics.push(makeDgmoError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"))
3769
+ );
3761
3770
  Object.assign(table.metadata, meta);
3762
3771
  }
3763
3772
  currentTable = table;
@@ -5694,13 +5703,13 @@ function parseOrg(content, palette) {
5694
5703
  }
5695
5704
  } else if (metadataMatch && indentStack.length === 0) {
5696
5705
  if (indent === 0) {
5697
- const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
5706
+ const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
5698
5707
  attachNode(node, indent, indentStack, result);
5699
5708
  } else {
5700
5709
  pushError(lineNumber, "Metadata has no parent node");
5701
5710
  }
5702
5711
  } else {
5703
- const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
5712
+ const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
5704
5713
  attachNode(node, indent, indentStack, result);
5705
5714
  }
5706
5715
  }
@@ -5722,11 +5731,11 @@ function parseOrg(content, palette) {
5722
5731
  }
5723
5732
  return result;
5724
5733
  }
5725
- function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
5734
+ function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
5726
5735
  const segments = trimmed.split("|").map((s) => s.trim());
5727
5736
  let rawLabel = segments[0];
5728
5737
  const { label, color } = extractColor(rawLabel, palette);
5729
- const metadata = parsePipeMetadata(segments, aliasMap);
5738
+ const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
5730
5739
  return {
5731
5740
  id: `node-${counter}`,
5732
5741
  label,
@@ -6268,7 +6277,7 @@ function parseC4(content, palette) {
6268
6277
  }
6269
6278
  const segments = trimmed.split("|").map((s) => s.trim());
6270
6279
  const nodeName = segments[0];
6271
- const metadata = parsePipeMetadata(segments, aliasMap);
6280
+ const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
6272
6281
  const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
6273
6282
  const dNode = {
6274
6283
  name: nodeName,
@@ -6421,7 +6430,7 @@ function parseC4(content, palette) {
6421
6430
  }
6422
6431
  namePart = namePart.substring(0, isAMatch.index).trim();
6423
6432
  }
6424
- const metadata = parsePipeMetadata(segments, aliasMap);
6433
+ const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
6425
6434
  const shape = explicitShape ?? inferC4Shape(namePart, metadata.tech ?? metadata.technology);
6426
6435
  const element = {
6427
6436
  name: namePart,
@@ -7208,14 +7217,14 @@ function parseSitemap(content, palette) {
7208
7217
  }
7209
7218
  } else if (metadataMatch && indentStack.length === 0) {
7210
7219
  if (indent === 0) {
7211
- const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
7220
+ const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
7212
7221
  attachNode2(node, indent, indentStack, result);
7213
7222
  labelToNode.set(node.label.toLowerCase(), node);
7214
7223
  } else {
7215
7224
  pushError(lineNumber, "Metadata has no parent node");
7216
7225
  }
7217
7226
  } else {
7218
- const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
7227
+ const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
7219
7228
  attachNode2(node, indent, indentStack, result);
7220
7229
  labelToNode.set(node.label.toLowerCase(), node);
7221
7230
  }
@@ -7257,11 +7266,11 @@ function parseSitemap(content, palette) {
7257
7266
  }
7258
7267
  return result;
7259
7268
  }
7260
- function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
7269
+ function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
7261
7270
  const segments = trimmed.split("|").map((s) => s.trim());
7262
7271
  const rawLabel = segments[0];
7263
7272
  const { label, color } = extractColor(rawLabel, palette);
7264
- const metadata = parsePipeMetadata(segments, aliasMap);
7273
+ const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
7265
7274
  return {
7266
7275
  id: `node-${counter}`,
7267
7276
  label,
@@ -7969,6 +7978,9 @@ function parseGantt(content, palette) {
7969
7978
  const warn = (line10, message) => {
7970
7979
  diagnostics.push(makeDgmoError(line10, message, "warning"));
7971
7980
  };
7981
+ const softError = (line10, message) => {
7982
+ diagnostics.push(makeDgmoError(line10, message, "error"));
7983
+ };
7972
7984
  const aliasMap = /* @__PURE__ */ new Map();
7973
7985
  const blockStack = [];
7974
7986
  const currentContainer = () => {
@@ -8102,10 +8114,10 @@ function parseGantt(content, palette) {
8102
8114
  const targetName = depParts[0].trim();
8103
8115
  let offset;
8104
8116
  if (depParts.length > 1) {
8105
- const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
8117
+ const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
8106
8118
  if (meta.lag || meta.lead) {
8107
8119
  const key = meta.lag ? "lag" : "lead";
8108
- return fail(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
8120
+ softError(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
8109
8121
  }
8110
8122
  if (meta.offset) {
8111
8123
  const raw = meta.offset;
@@ -8244,16 +8256,18 @@ function parseGantt(content, palette) {
8244
8256
  const groupMatch = line10.match(GROUP_RE2);
8245
8257
  if (groupMatch) {
8246
8258
  if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === "task") {
8247
- return fail(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
8259
+ softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
8260
+ continue;
8248
8261
  }
8249
8262
  const afterBrackets = groupMatch[2].trim();
8250
8263
  const segments = afterBrackets ? afterBrackets.split("|") : [];
8251
8264
  let metadata = {};
8252
8265
  let color = null;
8266
+ const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_WARNING);
8253
8267
  if (segments.length > 0 && segments[0].trim()) {
8254
- metadata = parsePipeMetadata(["", ...segments], aliasMap);
8268
+ metadata = parsePipeMetadata(["", ...segments], aliasMap, pipeWarn);
8255
8269
  } else if (segments.length > 1) {
8256
- metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap);
8270
+ metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap, pipeWarn);
8257
8271
  }
8258
8272
  const nameExtracted = extractColor(groupMatch[1], palette);
8259
8273
  if (nameExtracted.color) {
@@ -8284,7 +8298,6 @@ function parseGantt(content, palette) {
8284
8298
  const uncertain = !!timelineDurMatch[4];
8285
8299
  const labelRaw = timelineDurMatch[5];
8286
8300
  const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
8287
- if (result.error) return result;
8288
8301
  const taskNode = { kind: "task", ...task };
8289
8302
  currentContainer().push(taskNode);
8290
8303
  lastTaskNode = taskNode;
@@ -8298,7 +8311,6 @@ function parseGantt(content, palette) {
8298
8311
  const uncertain = !!durMatch[3];
8299
8312
  const labelRaw = durMatch[4];
8300
8313
  const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber);
8301
- if (result.error) return result;
8302
8314
  const taskNode = { kind: "task", ...task };
8303
8315
  currentContainer().push(taskNode);
8304
8316
  lastTaskNode = taskNode;
@@ -8315,7 +8327,6 @@ function parseGantt(content, palette) {
8315
8327
  lineNumber,
8316
8328
  explicitDateMatch[1]
8317
8329
  );
8318
- if (result.error) return result;
8319
8330
  const taskNode = { kind: "task", ...task };
8320
8331
  currentContainer().push(taskNode);
8321
8332
  lastTaskNode = taskNode;
@@ -8325,13 +8336,14 @@ function parseGantt(content, palette) {
8325
8336
  const depMatch = line10.match(DEPENDENCY_RE);
8326
8337
  if (depMatch) {
8327
8338
  if (!lastTaskNode) {
8328
- return fail(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
8339
+ softError(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
8340
+ continue;
8329
8341
  }
8330
8342
  const depParts = depMatch[1].split("|");
8331
8343
  const targetName = depParts[0].trim();
8332
8344
  let offset;
8333
8345
  if (depParts.length > 1) {
8334
- const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
8346
+ const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
8335
8347
  if (meta.lag || meta.lead) {
8336
8348
  const key = meta.lag ? "lag" : "lead";
8337
8349
  warn(lineNumber, `"${key}" is deprecated \u2014 use "offset: ${meta[key]}" instead.${key === "lead" ? ' Negate the value for lead behavior: "offset: -...".' : ""}`);
@@ -8351,7 +8363,8 @@ function parseGantt(content, palette) {
8351
8363
  lastTaskNode.dependencies.push({ targetName, offset, lineNumber });
8352
8364
  continue;
8353
8365
  }
8354
- return fail(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
8366
+ softError(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
8367
+ continue;
8355
8368
  }
8356
8369
  if (currentTagGroup) {
8357
8370
  result.tagGroups.push(currentTagGroup);
@@ -8365,16 +8378,16 @@ function parseGantt(content, palette) {
8365
8378
  const segments = labelRaw.split("|");
8366
8379
  const label = segments[0].trim();
8367
8380
  if (label.toLowerCase() === "parallel") {
8368
- fail(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
8381
+ softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
8369
8382
  }
8370
- const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap) : {};
8383
+ const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_WARNING)) : {};
8371
8384
  let progress = null;
8372
8385
  if (metadata.progress) {
8373
8386
  progress = parseFloat(metadata.progress);
8374
8387
  delete metadata.progress;
8375
8388
  }
8376
- for (let j = 1; j < segments.length; j++) {
8377
- const seg = segments[j].trim();
8389
+ for (const part of segments.slice(1).join(",").split(",")) {
8390
+ const seg = part.trim();
8378
8391
  const progressMatch = seg.match(/^(\d+)%$/);
8379
8392
  if (progressMatch) {
8380
8393
  progress = parseInt(progressMatch[1], 10);
@@ -8382,7 +8395,7 @@ function parseGantt(content, palette) {
8382
8395
  }
8383
8396
  if (metadata.lag || metadata.lead) {
8384
8397
  const key = metadata.lag ? "lag" : "lead";
8385
- fail(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
8398
+ softError(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
8386
8399
  }
8387
8400
  let taskOffset;
8388
8401
  if (metadata.offset) {
@@ -8469,7 +8482,7 @@ var init_parser10 = __esm({
8469
8482
  DEPENDENCY_RE = /^->\s*(.+)$/;
8470
8483
  COMMENT_RE = /^\/\//;
8471
8484
  ERA_RE = /^era\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*->\s*(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
8472
- MARKER_RE = /^marker\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
8485
+ MARKER_RE = /^marker:\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s+(.+)$/i;
8473
8486
  HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
8474
8487
  HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
8475
8488
  WORKWEEK_RE = /^workweek:\s*(.+)$/i;
@@ -18882,11 +18895,8 @@ function calculateSchedule(parsed) {
18882
18895
  for (const dep of task.dependencies) {
18883
18896
  const resolved = resolveTaskName(dep.targetName, allTasks);
18884
18897
  if (isResolverError(resolved)) {
18885
- if (resolved.kind === "ambiguous") {
18886
- return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18887
- } else {
18888
- return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18889
- }
18898
+ warn(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18899
+ continue;
18890
18900
  }
18891
18901
  const targetNode = taskMap.get(resolved.task.id);
18892
18902
  if (targetNode) {
@@ -18901,14 +18911,26 @@ function calculateSchedule(parsed) {
18901
18911
  }
18902
18912
  }
18903
18913
  }
18904
- const sortedIds = topologicalSort(taskMap);
18914
+ let sortedIds = topologicalSort(taskMap);
18905
18915
  if (!sortedIds) {
18906
18916
  const cycle = findCycle(taskMap);
18907
18917
  const cycleStr = cycle.map((id) => taskMap.get(id).task.label).join(" \u2192 ");
18908
- return fail(
18918
+ warn(
18909
18919
  taskMap.get(cycle[0]).task.lineNumber,
18910
- `Circular dependency detected: ${cycleStr}`
18920
+ `Circular dependency detected: ${cycleStr}. The cycle-creating dependency was dropped.`
18911
18921
  );
18922
+ breakCycle(cycle, taskMap, depOffsetMap);
18923
+ sortedIds = topologicalSort(taskMap);
18924
+ let safety = 10;
18925
+ while (!sortedIds && safety-- > 0) {
18926
+ const nextCycle = findCycle(taskMap);
18927
+ if (nextCycle.length === 0) break;
18928
+ breakCycle(nextCycle, taskMap, depOffsetMap);
18929
+ sortedIds = topologicalSort(taskMap);
18930
+ }
18931
+ if (!sortedIds) {
18932
+ sortedIds = [...taskMap.keys()];
18933
+ }
18912
18934
  }
18913
18935
  for (const taskId of sortedIds) {
18914
18936
  const node = taskMap.get(taskId);
@@ -19192,6 +19214,19 @@ function findCycle(taskMap) {
19192
19214
  return null;
19193
19215
  }
19194
19216
  }
19217
+ function breakCycle(cycle, taskMap, depOffsetMap) {
19218
+ if (cycle.length < 3) return;
19219
+ const fromId = cycle[cycle.length - 2];
19220
+ const toId = cycle[0];
19221
+ const toNode = taskMap.get(toId);
19222
+ if (toNode) {
19223
+ const idx = toNode.predecessors.indexOf(fromId);
19224
+ if (idx !== -1) {
19225
+ toNode.predecessors.splice(idx, 1);
19226
+ depOffsetMap.delete(`${fromId}->${toId}`);
19227
+ }
19228
+ }
19229
+ }
19195
19230
  function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
19196
19231
  if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
19197
19232
  const latestEnd = /* @__PURE__ */ new Map();
@@ -19280,10 +19315,10 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
19280
19315
  if (!resolved?.startDate || !resolved?.endDate) continue;
19281
19316
  if (resolved.startDate.getTime() < minStart) minStart = resolved.startDate.getTime();
19282
19317
  if (resolved.endDate.getTime() > maxEnd) maxEnd = resolved.endDate.getTime();
19318
+ const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
19319
+ totalDuration += dur;
19283
19320
  if (task.progress !== null) {
19284
- const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
19285
19321
  totalProgress += task.progress * dur;
19286
- totalDuration += dur;
19287
19322
  hasProgress = true;
19288
19323
  }
19289
19324
  }
@@ -19328,7 +19363,7 @@ import * as d3Scale from "d3-scale";
19328
19363
  import * as d3Selection10 from "d3-selection";
19329
19364
  function renderGantt(container, resolved, palette, isDark, options, exportDims) {
19330
19365
  container.innerHTML = "";
19331
- if (resolved.error || resolved.tasks.length === 0) return;
19366
+ if (resolved.tasks.length === 0) return;
19332
19367
  const onClickItem = options?.onClickItem;
19333
19368
  const collapsedGroups = options?.collapsedGroups;
19334
19369
  const onToggleGroup = options?.onToggleGroup;
@@ -19358,9 +19393,10 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19358
19393
  const titleHeight = title ? 50 : 20;
19359
19394
  const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
19360
19395
  const topDateLabelReserve = 22;
19396
+ const CONTENT_TOP_PAD = 16;
19361
19397
  const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve;
19362
19398
  const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
19363
- const innerHeight = contentH;
19399
+ const innerHeight = CONTENT_TOP_PAD + contentH;
19364
19400
  const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
19365
19401
  const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
19366
19402
  const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
@@ -19398,7 +19434,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19398
19434
  },
19399
19435
  currentSwimlaneGroup,
19400
19436
  onSwimlaneChange,
19401
- viewMode
19437
+ viewMode,
19438
+ resolved.tasks
19402
19439
  );
19403
19440
  }
19404
19441
  }
@@ -19444,7 +19481,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19444
19481
  }
19445
19482
  }
19446
19483
  }
19447
- let yOffset = 0;
19484
+ let yOffset = CONTENT_TOP_PAD;
19448
19485
  for (const row of rows) {
19449
19486
  if (row.type === "lane-header") {
19450
19487
  const laneColor = row.laneColor === "#999999" ? palette.textMuted : row.laneColor;
@@ -19493,7 +19530,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19493
19530
  const isCollapsed = collapsedGroups?.has(group.name) ?? false;
19494
19531
  const indent = " ".repeat(group.depth);
19495
19532
  const toggleIcon = isCollapsed ? "\u25BA" : "\u25BC";
19496
- const groupColor = group.color || palette.textMuted;
19533
+ const tagColor = resolveTagColor(group.metadata, resolved.tagGroups, currentActiveGroup, true);
19534
+ const groupColor = tagColor && tagColor !== "#999999" ? tagColor : group.color || palette.textMuted;
19497
19535
  const labelG = svg.append("g").attr("class", "gantt-group-label").attr("data-group", group.name).attr("data-line-number", String(group.lineNumber)).style("cursor", onToggleGroup ? "pointer" : "default").on("click", () => {
19498
19536
  if (onToggleGroup) onToggleGroup(group.name);
19499
19537
  }).on("mouseenter", () => {
@@ -19549,7 +19587,11 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19549
19587
  const taskLabel = svg.append("text").attr("class", "gantt-task-label").attr("x", taskLabelX).attr("y", marginTop + yOffset + BAR_H / 2).attr("dy", "0.35em").attr("text-anchor", "start").attr("font-size", "11px").attr("fill", palette.text).attr("data-line-number", String(task.lineNumber)).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").text(task.label).on("click", () => {
19550
19588
  if (onClickItem) onClickItem(task.lineNumber);
19551
19589
  }).on("mouseenter", () => {
19552
- highlightTask(g, svg, task.id);
19590
+ if (rt.isMilestone) {
19591
+ highlightMilestone(g, svg, task.id);
19592
+ } else {
19593
+ highlightTask(g, svg, task.id);
19594
+ }
19553
19595
  }).on("mouseleave", () => {
19554
19596
  resetHighlight(g, svg);
19555
19597
  });
@@ -19563,14 +19605,14 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19563
19605
  if (rt.isMilestone) {
19564
19606
  const mx = xScale(dateToFractionalYear(rt.startDate));
19565
19607
  const my = yOffset + BAR_H / 2;
19566
- g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
19608
+ g.append("polygon").attr("class", "gantt-milestone").attr("points", diamondPoints(mx, my, MILESTONE_SIZE)).attr("fill", barColor).attr("stroke", barColor).attr("stroke-width", 1.5).attr("data-line-number", String(task.lineNumber)).attr("data-task-name", task.label).attr("data-task-id", task.id).attr("data-group", topGroup).style("cursor", onClickItem ? "pointer" : "default").on("click", () => {
19567
19609
  if (onClickItem) onClickItem(task.lineNumber);
19568
19610
  }).on("mouseenter", () => {
19569
- highlightTaskLabel(svg, task.lineNumber);
19611
+ highlightMilestone(g, svg, task.id);
19570
19612
  showGanttDateIndicators(g, xScale, rt.startDate, null, innerHeight, barColor);
19571
19613
  g.append("text").attr("class", "gantt-milestone-hover-label").attr("x", mx - MILESTONE_SIZE - 4).attr("y", my).attr("dy", "0.35em").attr("text-anchor", "end").attr("font-size", "10px").attr("fill", barColor).attr("font-weight", "600").text(task.label);
19572
19614
  }).on("mouseleave", () => {
19573
- resetTaskLabels(svg);
19615
+ resetHighlight(g, svg);
19574
19616
  hideGanttDateIndicators(g);
19575
19617
  g.selectAll(".gantt-milestone-hover-label").remove();
19576
19618
  });
@@ -19831,7 +19873,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
19831
19873
  }
19832
19874
  return iconG;
19833
19875
  }
19834
- function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode) {
19876
+ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
19835
19877
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
19836
19878
  let visibleGroups;
19837
19879
  if (activeGroupName) {
@@ -19841,6 +19883,28 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19841
19883
  } else {
19842
19884
  visibleGroups = tagGroups;
19843
19885
  }
19886
+ const usedValues = /* @__PURE__ */ new Map();
19887
+ if (resolvedTasks) {
19888
+ for (const group of visibleGroups) {
19889
+ const key = group.name.toLowerCase();
19890
+ const used = /* @__PURE__ */ new Set();
19891
+ for (const rt of resolvedTasks) {
19892
+ const val = rt.effectiveMetadata[key];
19893
+ if (val) used.add(val.toLowerCase());
19894
+ }
19895
+ usedValues.set(key, used);
19896
+ }
19897
+ }
19898
+ const filteredEntries = /* @__PURE__ */ new Map();
19899
+ for (const group of visibleGroups) {
19900
+ const key = group.name.toLowerCase();
19901
+ const used = usedValues.get(key);
19902
+ if (used && used.size > 0) {
19903
+ filteredEntries.set(key, group.entries.filter((e) => used.has(e.value.toLowerCase())));
19904
+ } else {
19905
+ filteredEntries.set(key, group.entries);
19906
+ }
19907
+ }
19844
19908
  const groupWidths = [];
19845
19909
  let totalW = 0;
19846
19910
  for (const group of visibleGroups) {
@@ -19851,8 +19915,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19851
19915
  const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
19852
19916
  let groupW = pillW;
19853
19917
  if (isActive) {
19918
+ const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
19854
19919
  let entriesW = 0;
19855
- for (const entry of group.entries) {
19920
+ for (const entry of entries) {
19856
19921
  entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
19857
19922
  }
19858
19923
  groupW = LEGEND_CAPSULE_PAD * 2 + pillW + 4 + entriesW;
@@ -19911,8 +19976,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19911
19976
  }
19912
19977
  if (isActive) {
19913
19978
  const tagKey = group.name.toLowerCase();
19979
+ const entries = filteredEntries.get(tagKey) ?? group.entries;
19914
19980
  let ex = pillXOff + pillW + LEGEND_CAPSULE_PAD + 4;
19915
- for (const entry of group.entries) {
19981
+ for (const entry of entries) {
19916
19982
  const entryValue = entry.value.toLowerCase();
19917
19983
  const entryG = gEl.append("g").attr("class", "gantt-legend-entry").style("cursor", "pointer");
19918
19984
  entryG.append("circle").attr("cx", ex + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
@@ -19996,13 +20062,26 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
19996
20062
  const color = marker.color || palette.accent || "#d08770";
19997
20063
  const mx = xScale(parseDateToFractionalYear(marker.date));
19998
20064
  const markerDate = parseDateStringToDate(marker.date);
19999
- const markerG = g.append("g").attr("class", "gantt-marker-group");
20000
- markerG.append("line").attr("class", "gantt-marker").attr("x1", mx).attr("y1", 0).attr("x2", mx).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "6 3").attr("opacity", 0.5);
20001
- markerG.append("polygon").attr("points", diamondPoints(mx, 6, 8)).attr("fill", color).attr("opacity", 0.5);
20002
- markerG.append("text").attr("class", "gantt-marker-label").attr("x", mx + 8).attr("y", 10).attr("font-size", "9px").attr("fill", color).attr("opacity", 0.7).attr("pointer-events", "none").text(marker.label);
20065
+ const diamondSize = 5;
20066
+ const labelY = -24;
20067
+ const diamondY = labelY + 14;
20068
+ const markerG = g.append("g").attr("class", "gantt-marker-group").style("cursor", "pointer");
20069
+ markerG.append("rect").attr("x", mx - 40).attr("y", labelY - 12).attr("width", 80).attr("height", innerHeight - labelY + 12).attr("fill", "transparent").attr("pointer-events", "all");
20070
+ markerG.append("text").attr("class", "gantt-marker-label").attr("x", mx).attr("y", labelY).attr("text-anchor", "middle").attr("font-size", "11px").attr("font-weight", "600").attr("fill", color).text(marker.label);
20071
+ markerG.append("path").attr("d", `M${mx},${diamondY - diamondSize} l${diamondSize},${diamondSize} l-${diamondSize},${diamondSize} l-${diamondSize},-${diamondSize} Z`).attr("fill", color).attr("opacity", 0.9);
20072
+ markerG.append("line").attr("class", "gantt-marker").attr("x1", mx).attr("y1", diamondY + diamondSize).attr("x2", mx).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "6 4").attr("opacity", 0.5);
20073
+ const markerLine = markerG.select(".gantt-marker");
20074
+ const markerLabel = markerG.select(".gantt-marker-label");
20075
+ const markerDiamond = markerG.select("path");
20003
20076
  markerG.on("mouseenter", () => {
20077
+ markerLine.attr("opacity", 0);
20078
+ markerLabel.attr("opacity", 0);
20079
+ markerDiamond.attr("opacity", 0);
20004
20080
  showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color);
20005
20081
  }).on("mouseleave", () => {
20082
+ markerLine.attr("opacity", 0.5);
20083
+ markerLabel.attr("opacity", 1);
20084
+ markerDiamond.attr("opacity", 0.9);
20006
20085
  hideGanttDateIndicators(g);
20007
20086
  });
20008
20087
  }
@@ -20077,6 +20156,7 @@ function highlightGroup(g, svg, groupName) {
20077
20156
  });
20078
20157
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20079
20158
  g.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
20159
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20080
20160
  }
20081
20161
  function highlightLane(g, svg, tagKey, laneName) {
20082
20162
  const tagAttr = `data-tag-${tagKey}`;
@@ -20103,6 +20183,7 @@ function highlightLane(g, svg, tagKey, laneName) {
20103
20183
  });
20104
20184
  g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20105
20185
  svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20186
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20106
20187
  }
20107
20188
  function highlightTask(g, svg, taskId) {
20108
20189
  g.selectAll(".gantt-task").each(function() {
@@ -20119,6 +20200,24 @@ function highlightTask(g, svg, taskId) {
20119
20200
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20120
20201
  g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20121
20202
  g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20203
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20204
+ }
20205
+ function highlightMilestone(g, svg, taskId) {
20206
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
20207
+ g.selectAll(".gantt-milestone").each(function() {
20208
+ const el = d3Selection10.select(this);
20209
+ el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
20210
+ });
20211
+ svg.selectAll(".gantt-task-label").each(function() {
20212
+ const el = d3Selection10.select(this);
20213
+ el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
20214
+ });
20215
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20216
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20217
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20218
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20219
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20220
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20122
20221
  }
20123
20222
  function highlightTaskLabel(svg, lineNumber) {
20124
20223
  const ln = String(lineNumber);
@@ -20138,6 +20237,7 @@ function resetHighlight(g, svg) {
20138
20237
  svg.selectAll(".gantt-lane-header").attr("opacity", 1);
20139
20238
  g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", 1);
20140
20239
  g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
20240
+ g.selectAll(".gantt-marker-group").attr("opacity", 1);
20141
20241
  }
20142
20242
  function buildRowList(resolved, collapsedGroups) {
20143
20243
  const rows = [];
@@ -20216,9 +20316,9 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20216
20316
  for (const entry of tagGroup.entries) {
20217
20317
  const entryKey = entry.value.toLowerCase();
20218
20318
  const tasks = buckets.get(entryKey) ?? [];
20319
+ if (tasks.length === 0) continue;
20219
20320
  tasks.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
20220
- const progressValues = tasks.map((t) => t.task.progress).filter((p) => p !== null);
20221
- const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
20321
+ const aggregateProgress = durationWeightedProgress(tasks);
20222
20322
  const laneStartDate = tasks.length > 0 ? new Date(Math.min(...tasks.map((t) => t.startDate.getTime()))) : null;
20223
20323
  const laneEndDate = tasks.length > 0 ? new Date(Math.max(...tasks.map((t) => t.endDate.getTime()))) : null;
20224
20324
  const isCollapsed = collapsedLanes?.has(entry.value) ?? false;
@@ -20240,8 +20340,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20240
20340
  }
20241
20341
  if (unbucketed.length > 0) {
20242
20342
  unbucketed.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
20243
- const progressValues = unbucketed.map((t) => t.task.progress).filter((p) => p !== null);
20244
- const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
20343
+ const aggregateProgress = durationWeightedProgress(unbucketed);
20245
20344
  const noLaneStartDate = unbucketed.length > 0 ? new Date(Math.min(...unbucketed.map((t) => t.startDate.getTime()))) : null;
20246
20345
  const noLaneEndDate = unbucketed.length > 0 ? new Date(Math.max(...unbucketed.map((t) => t.endDate.getTime()))) : null;
20247
20346
  const noLaneName = `No ${tagGroup.name}`;
@@ -20264,6 +20363,20 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20264
20363
  }
20265
20364
  return rows;
20266
20365
  }
20366
+ function durationWeightedProgress(tasks) {
20367
+ let totalDuration = 0;
20368
+ let totalProgress = 0;
20369
+ let hasProgress = false;
20370
+ for (const rt of tasks) {
20371
+ const dur = rt.endDate.getTime() - rt.startDate.getTime();
20372
+ totalDuration += dur;
20373
+ if (rt.task.progress !== null) {
20374
+ totalProgress += rt.task.progress * dur;
20375
+ hasProgress = true;
20376
+ }
20377
+ }
20378
+ return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
20379
+ }
20267
20380
  function dateToFractionalYear(d) {
20268
20381
  const y = d.getFullYear();
20269
20382
  const startOfYear = new Date(y, 0, 1);
@@ -20290,9 +20403,28 @@ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, col
20290
20403
  if (endDate && endDate.getTime() !== startDate.getTime()) {
20291
20404
  const endPos = xScale(dateToFractionalYear(endDate));
20292
20405
  const endLabel = formatGanttDate(endDate);
20406
+ const minLabelGap = 90;
20407
+ const gap = endPos - startPos;
20408
+ let startLabelX = startPos;
20409
+ let endLabelX = endPos;
20410
+ let startAnchor = "middle";
20411
+ let endAnchor = "middle";
20412
+ if (gap < minLabelGap) {
20413
+ const mid = (startPos + endPos) / 2;
20414
+ startLabelX = mid - minLabelGap / 2;
20415
+ endLabelX = mid + minLabelGap / 2;
20416
+ startAnchor = "middle";
20417
+ endAnchor = "middle";
20418
+ }
20293
20419
  g.append("line").attr("class", "gantt-hover-date").attr("x1", endPos).attr("y1", -tickLen).attr("x2", endPos).attr("y2", innerHeight).attr("stroke", color).attr("stroke-width", 1.5).attr("stroke-dasharray", "4 4").attr("opacity", 0.6);
20294
- g.append("text").attr("class", "gantt-hover-date").attr("x", endPos).attr("y", -tickLen - 4).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
20295
- g.append("text").attr("class", "gantt-hover-date").attr("x", endPos).attr("y", innerHeight + tickLen + 12).attr("text-anchor", "middle").attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
20420
+ g.selectAll("text.gantt-hover-date").each(function() {
20421
+ const el = d3Selection10.select(this);
20422
+ if (el.text() === startLabel) {
20423
+ el.attr("x", startLabelX).attr("text-anchor", startAnchor);
20424
+ }
20425
+ });
20426
+ g.append("text").attr("class", "gantt-hover-date").attr("x", endLabelX).attr("y", -tickLen - 4).attr("text-anchor", endAnchor).attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
20427
+ g.append("text").attr("class", "gantt-hover-date").attr("x", endLabelX).attr("y", innerHeight + tickLen + 12).attr("text-anchor", endAnchor).attr("fill", color).attr("font-size", "10px").attr("font-weight", "600").text(endLabel);
20296
20428
  }
20297
20429
  }
20298
20430
  function hideGanttDateIndicators(g) {
@@ -22348,7 +22480,7 @@ function parseVisualization(content, palette) {
22348
22480
  continue;
22349
22481
  }
22350
22482
  const markerMatch = line10.match(
22351
- /^marker\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s*:\s*(.+?)(?:\s*\(([^)]+)\))?\s*$/
22483
+ /^marker:\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/
22352
22484
  );
22353
22485
  if (markerMatch) {
22354
22486
  const colorAnnotation = markerMatch[3]?.trim() || null;
@@ -22372,7 +22504,7 @@ function parseVisualization(content, palette) {
22372
22504
  const unit = durationMatch[3];
22373
22505
  const endDate = addDurationToDate(startDate, amount, unit);
22374
22506
  const segments = durationMatch[5].split("|");
22375
- const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
22507
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
22376
22508
  result.timelineEvents.push({
22377
22509
  date: startDate,
22378
22510
  endDate,
@@ -22389,7 +22521,7 @@ function parseVisualization(content, palette) {
22389
22521
  );
22390
22522
  if (rangeMatch) {
22391
22523
  const segments = rangeMatch[4].split("|");
22392
- const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
22524
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
22393
22525
  result.timelineEvents.push({
22394
22526
  date: rangeMatch[1],
22395
22527
  endDate: rangeMatch[2],
@@ -22406,7 +22538,7 @@ function parseVisualization(content, palette) {
22406
22538
  );
22407
22539
  if (pointMatch) {
22408
22540
  const segments = pointMatch[2].split("|");
22409
- const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
22541
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
22410
22542
  result.timelineEvents.push({
22411
22543
  date: pointMatch[1],
22412
22544
  endDate: null,
@@ -25358,9 +25490,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
25358
25490
  const { renderGantt: renderGantt2 } = await Promise.resolve().then(() => (init_renderer9(), renderer_exports9));
25359
25491
  const effectivePalette2 = await resolveExportPalette(theme, palette);
25360
25492
  const ganttParsed = parseGantt2(content, effectivePalette2);
25361
- if (ganttParsed.error) return "";
25362
25493
  const resolved = calculateSchedule2(ganttParsed);
25363
- if (resolved.error || resolved.tasks.length === 0) return "";
25494
+ if (resolved.tasks.length === 0) return "";
25364
25495
  const EXPORT_W = 1200;
25365
25496
  const EXPORT_H = 800;
25366
25497
  const container2 = createExportContainer(EXPORT_W, EXPORT_H);