@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.cjs CHANGED
@@ -1517,24 +1517,26 @@ function parseSeriesNames(value, lines, lineIndex, palette) {
1517
1517
  }
1518
1518
  return { series, names, nameColors, newIndex };
1519
1519
  }
1520
- function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map()) {
1520
+ function parsePipeMetadata(segments, aliasMap = /* @__PURE__ */ new Map(), warnMultiplePipes) {
1521
+ if (segments.length > 2 && warnMultiplePipes) {
1522
+ warnMultiplePipes();
1523
+ }
1521
1524
  const metadata = {};
1522
- for (let j = 1; j < segments.length; j++) {
1523
- for (const part of segments[j].split(",")) {
1524
- const trimmedPart = part.trim();
1525
- if (!trimmedPart) continue;
1526
- const colonIdx = trimmedPart.indexOf(":");
1527
- if (colonIdx > 0) {
1528
- const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1529
- const key = aliasMap.get(rawKey) ?? rawKey;
1530
- const value = trimmedPart.substring(colonIdx + 1).trim();
1531
- metadata[key] = value;
1532
- }
1525
+ const raw = segments.slice(1).join(",");
1526
+ for (const part of raw.split(",")) {
1527
+ const trimmedPart = part.trim();
1528
+ if (!trimmedPart) continue;
1529
+ const colonIdx = trimmedPart.indexOf(":");
1530
+ if (colonIdx > 0) {
1531
+ const rawKey = trimmedPart.substring(0, colonIdx).trim().toLowerCase();
1532
+ const key = aliasMap.get(rawKey) ?? rawKey;
1533
+ const value = trimmedPart.substring(colonIdx + 1).trim();
1534
+ metadata[key] = value;
1533
1535
  }
1534
1536
  }
1535
1537
  return metadata;
1536
1538
  }
1537
- var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE;
1539
+ var COLOR_SUFFIX_RE, CHART_TYPE_RE, TITLE_RE, OPTION_RE, MULTIPLE_PIPE_WARNING;
1538
1540
  var init_parsing = __esm({
1539
1541
  "src/utils/parsing.ts"() {
1540
1542
  "use strict";
@@ -1543,6 +1545,7 @@ var init_parsing = __esm({
1543
1545
  CHART_TYPE_RE = /^chart\s*:\s*(.+)/i;
1544
1546
  TITLE_RE = /^title\s*:\s*(.+)/i;
1545
1547
  OPTION_RE = /^([a-z][a-z0-9-]*)\s*:\s*(.+)$/i;
1548
+ MULTIPLE_PIPE_WARNING = 'Use a single "|" to start metadata, then separate items with commas.';
1546
1549
  }
1547
1550
  });
1548
1551
 
@@ -2043,12 +2046,13 @@ function parseSequenceDgmo(content) {
2043
2046
  const participantGroupMap = /* @__PURE__ */ new Map();
2044
2047
  let currentTagGroup = null;
2045
2048
  const aliasMap = /* @__PURE__ */ new Map();
2046
- const splitPipe = (text) => {
2049
+ const splitPipe = (text, ln) => {
2047
2050
  const idx = text.indexOf("|");
2048
2051
  if (idx < 0) return { core: text };
2049
2052
  const core = text.substring(0, idx).trimEnd();
2050
2053
  const segments = text.substring(idx).split("|");
2051
- const meta = parsePipeMetadata(segments, aliasMap);
2054
+ const warnFn = ln != null ? () => pushWarning(ln, MULTIPLE_PIPE_WARNING) : void 0;
2055
+ const meta = parsePipeMetadata(segments, aliasMap, warnFn);
2052
2056
  return Object.keys(meta).length > 0 ? { core, meta } : { core };
2053
2057
  };
2054
2058
  const blockStack = [];
@@ -2077,7 +2081,7 @@ function parseSequenceDgmo(content) {
2077
2081
  if (gpipeIdx >= 0) {
2078
2082
  const nameAndColor = groupName.substring(0, gpipeIdx).trimEnd();
2079
2083
  const segments = groupName.substring(gpipeIdx).split("|");
2080
- const meta = parsePipeMetadata(segments, aliasMap);
2084
+ const meta = parsePipeMetadata(segments, aliasMap, () => pushWarning(lineNumber, MULTIPLE_PIPE_WARNING));
2081
2085
  if (Object.keys(meta).length > 0) groupMeta = meta;
2082
2086
  const colorSuffix = nameAndColor.match(/^(.+?)\(([^)]+)\)$/);
2083
2087
  if (colorSuffix) {
@@ -2200,7 +2204,7 @@ function parseSequenceDgmo(content) {
2200
2204
  continue;
2201
2205
  }
2202
2206
  }
2203
- const { core: isACore, meta: isAMeta } = splitPipe(trimmed);
2207
+ const { core: isACore, meta: isAMeta } = splitPipe(trimmed, lineNumber);
2204
2208
  const isAMatch = isACore.match(IS_A_PATTERN);
2205
2209
  if (isAMatch) {
2206
2210
  contentStarted = true;
@@ -2237,7 +2241,7 @@ function parseSequenceDgmo(content) {
2237
2241
  }
2238
2242
  continue;
2239
2243
  }
2240
- const { core: posCore, meta: posMeta } = splitPipe(trimmed);
2244
+ const { core: posCore, meta: posMeta } = splitPipe(trimmed, lineNumber);
2241
2245
  const posOnlyMatch = posCore.match(POSITION_ONLY_PATTERN);
2242
2246
  if (posOnlyMatch) {
2243
2247
  contentStarted = true;
@@ -2264,7 +2268,7 @@ function parseSequenceDgmo(content) {
2264
2268
  }
2265
2269
  continue;
2266
2270
  }
2267
- const { core: colorCore, meta: colorMeta } = splitPipe(trimmed);
2271
+ const { core: colorCore, meta: colorMeta } = splitPipe(trimmed, lineNumber);
2268
2272
  const coloredMatch = colorCore.match(COLORED_PARTICIPANT_PATTERN);
2269
2273
  if (coloredMatch && !ARROW_PATTERN.test(colorCore)) {
2270
2274
  const id = coloredMatch[1];
@@ -2292,7 +2296,7 @@ function parseSequenceDgmo(content) {
2292
2296
  continue;
2293
2297
  }
2294
2298
  {
2295
- const { core: bareCore, meta: bareMeta } = splitPipe(trimmed);
2299
+ const { core: bareCore, meta: bareMeta } = splitPipe(trimmed, lineNumber);
2296
2300
  const inGroup = activeGroup && measureIndent(raw) > 0;
2297
2301
  if (/^\S+$/.test(bareCore) && !ARROW_PATTERN.test(bareCore) && (inGroup || !contentStarted || bareMeta)) {
2298
2302
  contentStarted = true;
@@ -2328,7 +2332,7 @@ function parseSequenceDgmo(content) {
2328
2332
  }
2329
2333
  blockStack.pop();
2330
2334
  }
2331
- const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed);
2335
+ const { core: arrowCore, meta: arrowMeta } = splitPipe(trimmed, lineNumber);
2332
2336
  const asyncPrefixMatch = arrowCore.match(/^async\s+(.+)$/i);
2333
2337
  if (asyncPrefixMatch && ARROW_PATTERN.test(asyncPrefixMatch[1])) {
2334
2338
  pushError(lineNumber, "Use ~> for async messages: A ~> B: message");
@@ -3779,7 +3783,12 @@ function parseERDiagram(content, palette) {
3779
3783
  table.lineNumber = lineNumber;
3780
3784
  const pipeStr = tableDecl[3]?.trim();
3781
3785
  if (pipeStr) {
3782
- const meta = parsePipeMetadata(["", pipeStr], aliasMap);
3786
+ const pipeSegments = pipeStr.split("|");
3787
+ const meta = parsePipeMetadata(
3788
+ ["", ...pipeSegments],
3789
+ aliasMap,
3790
+ () => result.diagnostics.push(makeDgmoError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"))
3791
+ );
3783
3792
  Object.assign(table.metadata, meta);
3784
3793
  }
3785
3794
  currentTable = table;
@@ -5716,13 +5725,13 @@ function parseOrg(content, palette) {
5716
5725
  }
5717
5726
  } else if (metadataMatch && indentStack.length === 0) {
5718
5727
  if (indent === 0) {
5719
- const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
5728
+ const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
5720
5729
  attachNode(node, indent, indentStack, result);
5721
5730
  } else {
5722
5731
  pushError(lineNumber, "Metadata has no parent node");
5723
5732
  }
5724
5733
  } else {
5725
- const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap);
5734
+ const node = parseNodeLabel(trimmed, indent, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
5726
5735
  attachNode(node, indent, indentStack, result);
5727
5736
  }
5728
5737
  }
@@ -5744,11 +5753,11 @@ function parseOrg(content, palette) {
5744
5753
  }
5745
5754
  return result;
5746
5755
  }
5747
- function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
5756
+ function parseNodeLabel(trimmed, _indent, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
5748
5757
  const segments = trimmed.split("|").map((s) => s.trim());
5749
5758
  let rawLabel = segments[0];
5750
5759
  const { label, color } = extractColor(rawLabel, palette);
5751
- const metadata = parsePipeMetadata(segments, aliasMap);
5760
+ const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
5752
5761
  return {
5753
5762
  id: `node-${counter}`,
5754
5763
  label,
@@ -6290,7 +6299,7 @@ function parseC4(content, palette) {
6290
6299
  }
6291
6300
  const segments = trimmed.split("|").map((s) => s.trim());
6292
6301
  const nodeName = segments[0];
6293
- const metadata = parsePipeMetadata(segments, aliasMap);
6302
+ const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
6294
6303
  const shape = inferC4Shape(nodeName, metadata.tech ?? metadata.technology);
6295
6304
  const dNode = {
6296
6305
  name: nodeName,
@@ -6443,7 +6452,7 @@ function parseC4(content, palette) {
6443
6452
  }
6444
6453
  namePart = namePart.substring(0, isAMatch.index).trim();
6445
6454
  }
6446
- const metadata = parsePipeMetadata(segments, aliasMap);
6455
+ const metadata = parsePipeMetadata(segments, aliasMap, () => pushError(lineNumber, MULTIPLE_PIPE_WARNING, "warning"));
6447
6456
  const shape = explicitShape ?? inferC4Shape(namePart, metadata.tech ?? metadata.technology);
6448
6457
  const element = {
6449
6458
  name: namePart,
@@ -7230,14 +7239,14 @@ function parseSitemap(content, palette) {
7230
7239
  }
7231
7240
  } else if (metadataMatch && indentStack.length === 0) {
7232
7241
  if (indent === 0) {
7233
- const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
7242
+ const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
7234
7243
  attachNode2(node, indent, indentStack, result);
7235
7244
  labelToNode.set(node.label.toLowerCase(), node);
7236
7245
  } else {
7237
7246
  pushError(lineNumber, "Metadata has no parent node");
7238
7247
  }
7239
7248
  } else {
7240
- const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap);
7249
+ const node = parseNodeLabel2(trimmed, lineNumber, palette, ++nodeCounter, aliasMap, pushWarning);
7241
7250
  attachNode2(node, indent, indentStack, result);
7242
7251
  labelToNode.set(node.label.toLowerCase(), node);
7243
7252
  }
@@ -7279,11 +7288,11 @@ function parseSitemap(content, palette) {
7279
7288
  }
7280
7289
  return result;
7281
7290
  }
7282
- function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map()) {
7291
+ function parseNodeLabel2(trimmed, lineNumber, palette, counter, aliasMap = /* @__PURE__ */ new Map(), warnFn) {
7283
7292
  const segments = trimmed.split("|").map((s) => s.trim());
7284
7293
  const rawLabel = segments[0];
7285
7294
  const { label, color } = extractColor(rawLabel, palette);
7286
- const metadata = parsePipeMetadata(segments, aliasMap);
7295
+ const metadata = parsePipeMetadata(segments, aliasMap, warnFn ? () => warnFn(lineNumber, MULTIPLE_PIPE_WARNING) : void 0);
7287
7296
  return {
7288
7297
  id: `node-${counter}`,
7289
7298
  label,
@@ -7991,6 +8000,9 @@ function parseGantt(content, palette) {
7991
8000
  const warn = (line10, message) => {
7992
8001
  diagnostics.push(makeDgmoError(line10, message, "warning"));
7993
8002
  };
8003
+ const softError = (line10, message) => {
8004
+ diagnostics.push(makeDgmoError(line10, message, "error"));
8005
+ };
7994
8006
  const aliasMap = /* @__PURE__ */ new Map();
7995
8007
  const blockStack = [];
7996
8008
  const currentContainer = () => {
@@ -8124,10 +8136,10 @@ function parseGantt(content, palette) {
8124
8136
  const targetName = depParts[0].trim();
8125
8137
  let offset;
8126
8138
  if (depParts.length > 1) {
8127
- const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
8139
+ const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
8128
8140
  if (meta.lag || meta.lead) {
8129
8141
  const key = meta.lag ? "lag" : "lead";
8130
- return fail(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
8142
+ softError(lineNumber, `Unknown keyword "${key}". Use "offset: ${meta[key]}" instead.`);
8131
8143
  }
8132
8144
  if (meta.offset) {
8133
8145
  const raw = meta.offset;
@@ -8266,16 +8278,18 @@ function parseGantt(content, palette) {
8266
8278
  const groupMatch = line10.match(GROUP_RE2);
8267
8279
  if (groupMatch) {
8268
8280
  if (blockStack.length > 0 && blockStack[blockStack.length - 1].containerType === "task") {
8269
- return fail(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
8281
+ softError(lineNumber, `Cannot nest a group inside a task. Groups must be inside other groups or parallel blocks.`);
8282
+ continue;
8270
8283
  }
8271
8284
  const afterBrackets = groupMatch[2].trim();
8272
8285
  const segments = afterBrackets ? afterBrackets.split("|") : [];
8273
8286
  let metadata = {};
8274
8287
  let color = null;
8288
+ const pipeWarn = () => warn(lineNumber, MULTIPLE_PIPE_WARNING);
8275
8289
  if (segments.length > 0 && segments[0].trim()) {
8276
- metadata = parsePipeMetadata(["", ...segments], aliasMap);
8290
+ metadata = parsePipeMetadata(["", ...segments], aliasMap, pipeWarn);
8277
8291
  } else if (segments.length > 1) {
8278
- metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap);
8292
+ metadata = parsePipeMetadata(["", ...segments.slice(1)], aliasMap, pipeWarn);
8279
8293
  }
8280
8294
  const nameExtracted = extractColor(groupMatch[1], palette);
8281
8295
  if (nameExtracted.color) {
@@ -8306,7 +8320,6 @@ function parseGantt(content, palette) {
8306
8320
  const uncertain = !!timelineDurMatch[4];
8307
8321
  const labelRaw = timelineDurMatch[5];
8308
8322
  const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber, startDate);
8309
- if (result.error) return result;
8310
8323
  const taskNode = { kind: "task", ...task };
8311
8324
  currentContainer().push(taskNode);
8312
8325
  lastTaskNode = taskNode;
@@ -8320,7 +8333,6 @@ function parseGantt(content, palette) {
8320
8333
  const uncertain = !!durMatch[3];
8321
8334
  const labelRaw = durMatch[4];
8322
8335
  const task = makeTask(labelRaw, { amount, unit }, uncertain, lineNumber);
8323
- if (result.error) return result;
8324
8336
  const taskNode = { kind: "task", ...task };
8325
8337
  currentContainer().push(taskNode);
8326
8338
  lastTaskNode = taskNode;
@@ -8337,7 +8349,6 @@ function parseGantt(content, palette) {
8337
8349
  lineNumber,
8338
8350
  explicitDateMatch[1]
8339
8351
  );
8340
- if (result.error) return result;
8341
8352
  const taskNode = { kind: "task", ...task };
8342
8353
  currentContainer().push(taskNode);
8343
8354
  lastTaskNode = taskNode;
@@ -8347,13 +8358,14 @@ function parseGantt(content, palette) {
8347
8358
  const depMatch = line10.match(DEPENDENCY_RE);
8348
8359
  if (depMatch) {
8349
8360
  if (!lastTaskNode) {
8350
- return fail(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
8361
+ softError(lineNumber, `Dependency "-> ${depMatch[1]}" must be indented under a task.`);
8362
+ continue;
8351
8363
  }
8352
8364
  const depParts = depMatch[1].split("|");
8353
8365
  const targetName = depParts[0].trim();
8354
8366
  let offset;
8355
8367
  if (depParts.length > 1) {
8356
- const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap);
8368
+ const meta = parsePipeMetadata(["", ...depParts.slice(1)], aliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING));
8357
8369
  if (meta.lag || meta.lead) {
8358
8370
  const key = meta.lag ? "lag" : "lead";
8359
8371
  warn(lineNumber, `"${key}" is deprecated \u2014 use "offset: ${meta[key]}" instead.${key === "lead" ? ' Negate the value for lead behavior: "offset: -...".' : ""}`);
@@ -8373,7 +8385,8 @@ function parseGantt(content, palette) {
8373
8385
  lastTaskNode.dependencies.push({ targetName, offset, lineNumber });
8374
8386
  continue;
8375
8387
  }
8376
- return fail(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
8388
+ softError(lineNumber, `Expected duration (e.g., "10d: Task"), group brackets (e.g., "[Group]"), or keyword. Got: "${line10}"`);
8389
+ continue;
8377
8390
  }
8378
8391
  if (currentTagGroup) {
8379
8392
  result.tagGroups.push(currentTagGroup);
@@ -8387,16 +8400,16 @@ function parseGantt(content, palette) {
8387
8400
  const segments = labelRaw.split("|");
8388
8401
  const label = segments[0].trim();
8389
8402
  if (label.toLowerCase() === "parallel") {
8390
- fail(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
8403
+ softError(ln, `"parallel" is a reserved keyword and cannot be used as a task name.`);
8391
8404
  }
8392
- const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap) : {};
8405
+ const metadata = segments.length > 1 ? parsePipeMetadata(segments, aliasMap, () => warn(ln, MULTIPLE_PIPE_WARNING)) : {};
8393
8406
  let progress = null;
8394
8407
  if (metadata.progress) {
8395
8408
  progress = parseFloat(metadata.progress);
8396
8409
  delete metadata.progress;
8397
8410
  }
8398
- for (let j = 1; j < segments.length; j++) {
8399
- const seg = segments[j].trim();
8411
+ for (const part of segments.slice(1).join(",").split(",")) {
8412
+ const seg = part.trim();
8400
8413
  const progressMatch = seg.match(/^(\d+)%$/);
8401
8414
  if (progressMatch) {
8402
8415
  progress = parseInt(progressMatch[1], 10);
@@ -8404,7 +8417,7 @@ function parseGantt(content, palette) {
8404
8417
  }
8405
8418
  if (metadata.lag || metadata.lead) {
8406
8419
  const key = metadata.lag ? "lag" : "lead";
8407
- fail(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
8420
+ softError(ln, `Unknown keyword "${key}". Use "offset: ${metadata[key]}" instead.`);
8408
8421
  }
8409
8422
  let taskOffset;
8410
8423
  if (metadata.offset) {
@@ -8491,7 +8504,7 @@ var init_parser10 = __esm({
8491
8504
  DEPENDENCY_RE = /^->\s*(.+)$/;
8492
8505
  COMMENT_RE = /^\/\//;
8493
8506
  ERA_RE = /^era\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*->\s*(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
8494
- MARKER_RE = /^marker\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s*:\s*(.+)$/i;
8507
+ MARKER_RE = /^marker:\s+(\d{4}(?:-\d{2}(?:-\d{2})?)?)\s+(.+)$/i;
8495
8508
  HOLIDAY_DATE_RE = /^(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
8496
8509
  HOLIDAY_RANGE_RE = /^(\d{4}-\d{2}-\d{2})\s*->\s*(\d{4}-\d{2}-\d{2}):\s*(.+)$/;
8497
8510
  WORKWEEK_RE = /^workweek:\s*(.+)$/i;
@@ -18904,11 +18917,8 @@ function calculateSchedule(parsed) {
18904
18917
  for (const dep of task.dependencies) {
18905
18918
  const resolved = resolveTaskName(dep.targetName, allTasks);
18906
18919
  if (isResolverError(resolved)) {
18907
- if (resolved.kind === "ambiguous") {
18908
- return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18909
- } else {
18910
- return fail(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18911
- }
18920
+ warn(dep.lineNumber, `\`-> ${dep.targetName}\` \u2014 ${resolved.message}`);
18921
+ continue;
18912
18922
  }
18913
18923
  const targetNode = taskMap.get(resolved.task.id);
18914
18924
  if (targetNode) {
@@ -18923,14 +18933,26 @@ function calculateSchedule(parsed) {
18923
18933
  }
18924
18934
  }
18925
18935
  }
18926
- const sortedIds = topologicalSort(taskMap);
18936
+ let sortedIds = topologicalSort(taskMap);
18927
18937
  if (!sortedIds) {
18928
18938
  const cycle = findCycle(taskMap);
18929
18939
  const cycleStr = cycle.map((id) => taskMap.get(id).task.label).join(" \u2192 ");
18930
- return fail(
18940
+ warn(
18931
18941
  taskMap.get(cycle[0]).task.lineNumber,
18932
- `Circular dependency detected: ${cycleStr}`
18942
+ `Circular dependency detected: ${cycleStr}. The cycle-creating dependency was dropped.`
18933
18943
  );
18944
+ breakCycle(cycle, taskMap, depOffsetMap);
18945
+ sortedIds = topologicalSort(taskMap);
18946
+ let safety = 10;
18947
+ while (!sortedIds && safety-- > 0) {
18948
+ const nextCycle = findCycle(taskMap);
18949
+ if (nextCycle.length === 0) break;
18950
+ breakCycle(nextCycle, taskMap, depOffsetMap);
18951
+ sortedIds = topologicalSort(taskMap);
18952
+ }
18953
+ if (!sortedIds) {
18954
+ sortedIds = [...taskMap.keys()];
18955
+ }
18934
18956
  }
18935
18957
  for (const taskId of sortedIds) {
18936
18958
  const node = taskMap.get(taskId);
@@ -19214,6 +19236,19 @@ function findCycle(taskMap) {
19214
19236
  return null;
19215
19237
  }
19216
19238
  }
19239
+ function breakCycle(cycle, taskMap, depOffsetMap) {
19240
+ if (cycle.length < 3) return;
19241
+ const fromId = cycle[cycle.length - 2];
19242
+ const toId = cycle[0];
19243
+ const toNode = taskMap.get(toId);
19244
+ if (toNode) {
19245
+ const idx = toNode.predecessors.indexOf(fromId);
19246
+ if (idx !== -1) {
19247
+ toNode.predecessors.splice(idx, 1);
19248
+ depOffsetMap.delete(`${fromId}->${toId}`);
19249
+ }
19250
+ }
19251
+ }
19217
19252
  function computeCriticalPath(sortedIds, taskMap, depOffsetMap, holidays, holidaySet) {
19218
19253
  if (sortedIds.length === 0) return /* @__PURE__ */ new Set();
19219
19254
  const latestEnd = /* @__PURE__ */ new Map();
@@ -19302,10 +19337,10 @@ function buildResolvedGroups(nodes, taskMap, groups, depth) {
19302
19337
  if (!resolved?.startDate || !resolved?.endDate) continue;
19303
19338
  if (resolved.startDate.getTime() < minStart) minStart = resolved.startDate.getTime();
19304
19339
  if (resolved.endDate.getTime() > maxEnd) maxEnd = resolved.endDate.getTime();
19340
+ const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
19341
+ totalDuration += dur;
19305
19342
  if (task.progress !== null) {
19306
- const dur = resolved.endDate.getTime() - resolved.startDate.getTime();
19307
19343
  totalProgress += task.progress * dur;
19308
- totalDuration += dur;
19309
19344
  hasProgress = true;
19310
19345
  }
19311
19346
  }
@@ -19348,7 +19383,7 @@ __export(renderer_exports9, {
19348
19383
  });
19349
19384
  function renderGantt(container, resolved, palette, isDark, options, exportDims) {
19350
19385
  container.innerHTML = "";
19351
- if (resolved.error || resolved.tasks.length === 0) return;
19386
+ if (resolved.tasks.length === 0) return;
19352
19387
  const onClickItem = options?.onClickItem;
19353
19388
  const collapsedGroups = options?.collapsedGroups;
19354
19389
  const onToggleGroup = options?.onToggleGroup;
@@ -19378,9 +19413,10 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19378
19413
  const titleHeight = title ? 50 : 20;
19379
19414
  const tagLegendReserve = resolved.tagGroups.length > 0 ? LEGEND_HEIGHT + 8 : 0;
19380
19415
  const topDateLabelReserve = 22;
19416
+ const CONTENT_TOP_PAD = 16;
19381
19417
  const marginTop = titleHeight + tagLegendReserve + topDateLabelReserve;
19382
19418
  const contentH = isTagMode ? totalRows * (BAR_H + ROW_GAP) : totalRows * (BAR_H + ROW_GAP) + GROUP_GAP2 * resolved.groups.length;
19383
- const innerHeight = contentH;
19419
+ const innerHeight = CONTENT_TOP_PAD + contentH;
19384
19420
  const outerHeight = marginTop + innerHeight + BOTTOM_MARGIN;
19385
19421
  const containerWidth = exportDims?.width ?? (container.clientWidth || 800);
19386
19422
  const innerWidth = containerWidth - leftMargin - RIGHT_MARGIN;
@@ -19418,7 +19454,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19418
19454
  },
19419
19455
  currentSwimlaneGroup,
19420
19456
  onSwimlaneChange,
19421
- viewMode
19457
+ viewMode,
19458
+ resolved.tasks
19422
19459
  );
19423
19460
  }
19424
19461
  }
@@ -19464,7 +19501,7 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19464
19501
  }
19465
19502
  }
19466
19503
  }
19467
- let yOffset = 0;
19504
+ let yOffset = CONTENT_TOP_PAD;
19468
19505
  for (const row of rows) {
19469
19506
  if (row.type === "lane-header") {
19470
19507
  const laneColor = row.laneColor === "#999999" ? palette.textMuted : row.laneColor;
@@ -19513,7 +19550,8 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19513
19550
  const isCollapsed = collapsedGroups?.has(group.name) ?? false;
19514
19551
  const indent = " ".repeat(group.depth);
19515
19552
  const toggleIcon = isCollapsed ? "\u25BA" : "\u25BC";
19516
- const groupColor = group.color || palette.textMuted;
19553
+ const tagColor = resolveTagColor(group.metadata, resolved.tagGroups, currentActiveGroup, true);
19554
+ const groupColor = tagColor && tagColor !== "#999999" ? tagColor : group.color || palette.textMuted;
19517
19555
  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", () => {
19518
19556
  if (onToggleGroup) onToggleGroup(group.name);
19519
19557
  }).on("mouseenter", () => {
@@ -19569,7 +19607,11 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19569
19607
  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", () => {
19570
19608
  if (onClickItem) onClickItem(task.lineNumber);
19571
19609
  }).on("mouseenter", () => {
19572
- highlightTask(g, svg, task.id);
19610
+ if (rt.isMilestone) {
19611
+ highlightMilestone(g, svg, task.id);
19612
+ } else {
19613
+ highlightTask(g, svg, task.id);
19614
+ }
19573
19615
  }).on("mouseleave", () => {
19574
19616
  resetHighlight(g, svg);
19575
19617
  });
@@ -19583,14 +19625,14 @@ function renderGantt(container, resolved, palette, isDark, options, exportDims)
19583
19625
  if (rt.isMilestone) {
19584
19626
  const mx = xScale(dateToFractionalYear(rt.startDate));
19585
19627
  const my = yOffset + BAR_H / 2;
19586
- 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", () => {
19628
+ 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", () => {
19587
19629
  if (onClickItem) onClickItem(task.lineNumber);
19588
19630
  }).on("mouseenter", () => {
19589
- highlightTaskLabel(svg, task.lineNumber);
19631
+ highlightMilestone(g, svg, task.id);
19590
19632
  showGanttDateIndicators(g, xScale, rt.startDate, null, innerHeight, barColor);
19591
19633
  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);
19592
19634
  }).on("mouseleave", () => {
19593
- resetTaskLabels(svg);
19635
+ resetHighlight(g, svg);
19594
19636
  hideGanttDateIndicators(g);
19595
19637
  g.selectAll(".gantt-milestone-hover-label").remove();
19596
19638
  });
@@ -19851,7 +19893,7 @@ function drawSwimlaneIcon(parent, x, y, isActive, palette) {
19851
19893
  }
19852
19894
  return iconG;
19853
19895
  }
19854
- function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode) {
19896
+ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargin, chartInnerWidth, legendY, palette, isDark, hasCriticalPath, criticalPathActive, onToggle, onToggleCriticalPath, currentSwimlaneGroup, onSwimlaneChange, legendViewMode, resolvedTasks) {
19855
19897
  const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
19856
19898
  let visibleGroups;
19857
19899
  if (activeGroupName) {
@@ -19861,6 +19903,28 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19861
19903
  } else {
19862
19904
  visibleGroups = tagGroups;
19863
19905
  }
19906
+ const usedValues = /* @__PURE__ */ new Map();
19907
+ if (resolvedTasks) {
19908
+ for (const group of visibleGroups) {
19909
+ const key = group.name.toLowerCase();
19910
+ const used = /* @__PURE__ */ new Set();
19911
+ for (const rt of resolvedTasks) {
19912
+ const val = rt.effectiveMetadata[key];
19913
+ if (val) used.add(val.toLowerCase());
19914
+ }
19915
+ usedValues.set(key, used);
19916
+ }
19917
+ }
19918
+ const filteredEntries = /* @__PURE__ */ new Map();
19919
+ for (const group of visibleGroups) {
19920
+ const key = group.name.toLowerCase();
19921
+ const used = usedValues.get(key);
19922
+ if (used && used.size > 0) {
19923
+ filteredEntries.set(key, group.entries.filter((e) => used.has(e.value.toLowerCase())));
19924
+ } else {
19925
+ filteredEntries.set(key, group.entries);
19926
+ }
19927
+ }
19864
19928
  const groupWidths = [];
19865
19929
  let totalW = 0;
19866
19930
  for (const group of visibleGroups) {
@@ -19871,8 +19935,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19871
19935
  const pillW = group.name.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD + iconReserve;
19872
19936
  let groupW = pillW;
19873
19937
  if (isActive) {
19938
+ const entries = filteredEntries.get(group.name.toLowerCase()) ?? group.entries;
19874
19939
  let entriesW = 0;
19875
- for (const entry of group.entries) {
19940
+ for (const entry of entries) {
19876
19941
  entriesW += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + entry.value.length * LEGEND_ENTRY_FONT_W + LEGEND_ENTRY_TRAIL;
19877
19942
  }
19878
19943
  groupW = LEGEND_CAPSULE_PAD * 2 + pillW + 4 + entriesW;
@@ -19931,8 +19996,9 @@ function renderTagLegend(svg, chartG, tagGroups, activeGroupName, chartLeftMargi
19931
19996
  }
19932
19997
  if (isActive) {
19933
19998
  const tagKey = group.name.toLowerCase();
19999
+ const entries = filteredEntries.get(tagKey) ?? group.entries;
19934
20000
  let ex = pillXOff + pillW + LEGEND_CAPSULE_PAD + 4;
19935
- for (const entry of group.entries) {
20001
+ for (const entry of entries) {
19936
20002
  const entryValue = entry.value.toLowerCase();
19937
20003
  const entryG = gEl.append("g").attr("class", "gantt-legend-entry").style("cursor", "pointer");
19938
20004
  entryG.append("circle").attr("cx", ex + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", entry.color);
@@ -20016,13 +20082,26 @@ function renderErasAndMarkers(g, resolved, xScale, innerHeight, palette) {
20016
20082
  const color = marker.color || palette.accent || "#d08770";
20017
20083
  const mx = xScale(parseDateToFractionalYear(marker.date));
20018
20084
  const markerDate = parseDateStringToDate(marker.date);
20019
- const markerG = g.append("g").attr("class", "gantt-marker-group");
20020
- 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);
20021
- markerG.append("polygon").attr("points", diamondPoints(mx, 6, 8)).attr("fill", color).attr("opacity", 0.5);
20022
- 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);
20085
+ const diamondSize = 5;
20086
+ const labelY = -24;
20087
+ const diamondY = labelY + 14;
20088
+ const markerG = g.append("g").attr("class", "gantt-marker-group").style("cursor", "pointer");
20089
+ 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");
20090
+ 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);
20091
+ 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);
20092
+ 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);
20093
+ const markerLine = markerG.select(".gantt-marker");
20094
+ const markerLabel = markerG.select(".gantt-marker-label");
20095
+ const markerDiamond = markerG.select("path");
20023
20096
  markerG.on("mouseenter", () => {
20097
+ markerLine.attr("opacity", 0);
20098
+ markerLabel.attr("opacity", 0);
20099
+ markerDiamond.attr("opacity", 0);
20024
20100
  showGanttDateIndicators(g, xScale, markerDate, null, innerHeight, color);
20025
20101
  }).on("mouseleave", () => {
20102
+ markerLine.attr("opacity", 0.5);
20103
+ markerLabel.attr("opacity", 1);
20104
+ markerDiamond.attr("opacity", 0.9);
20026
20105
  hideGanttDateIndicators(g);
20027
20106
  });
20028
20107
  }
@@ -20097,6 +20176,7 @@ function highlightGroup(g, svg, groupName) {
20097
20176
  });
20098
20177
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20099
20178
  g.selectAll(".gantt-lane-band, .gantt-lane-accent").attr("opacity", FADE_OPACITY);
20179
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20100
20180
  }
20101
20181
  function highlightLane(g, svg, tagKey, laneName) {
20102
20182
  const tagAttr = `data-tag-${tagKey}`;
@@ -20123,6 +20203,7 @@ function highlightLane(g, svg, tagKey, laneName) {
20123
20203
  });
20124
20204
  g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20125
20205
  svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20206
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20126
20207
  }
20127
20208
  function highlightTask(g, svg, taskId) {
20128
20209
  g.selectAll(".gantt-task").each(function() {
@@ -20139,6 +20220,24 @@ function highlightTask(g, svg, taskId) {
20139
20220
  svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20140
20221
  g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20141
20222
  g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20223
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20224
+ }
20225
+ function highlightMilestone(g, svg, taskId) {
20226
+ g.selectAll(".gantt-task").attr("opacity", FADE_OPACITY);
20227
+ g.selectAll(".gantt-milestone").each(function() {
20228
+ const el = d3Selection10.select(this);
20229
+ el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
20230
+ });
20231
+ svg.selectAll(".gantt-task-label").each(function() {
20232
+ const el = d3Selection10.select(this);
20233
+ el.attr("opacity", el.attr("data-task-id") === taskId ? 1 : FADE_OPACITY);
20234
+ });
20235
+ g.selectAll(".gantt-group-bar, .gantt-group-summary").attr("opacity", FADE_OPACITY);
20236
+ svg.selectAll(".gantt-group-label").attr("opacity", FADE_OPACITY);
20237
+ svg.selectAll(".gantt-lane-header").attr("opacity", FADE_OPACITY);
20238
+ g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", FADE_OPACITY);
20239
+ g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", FADE_OPACITY);
20240
+ g.selectAll(".gantt-marker-group").attr("opacity", FADE_OPACITY);
20142
20241
  }
20143
20242
  function highlightTaskLabel(svg, lineNumber) {
20144
20243
  const ln = String(lineNumber);
@@ -20158,6 +20257,7 @@ function resetHighlight(g, svg) {
20158
20257
  svg.selectAll(".gantt-lane-header").attr("opacity", 1);
20159
20258
  g.selectAll(".gantt-lane-band, .gantt-lane-accent, .gantt-lane-band-group").attr("opacity", 1);
20160
20259
  g.selectAll(".gantt-dep-arrow, .gantt-dep-arrowhead").attr("opacity", 0.5);
20260
+ g.selectAll(".gantt-marker-group").attr("opacity", 1);
20161
20261
  }
20162
20262
  function buildRowList(resolved, collapsedGroups) {
20163
20263
  const rows = [];
@@ -20236,9 +20336,9 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20236
20336
  for (const entry of tagGroup.entries) {
20237
20337
  const entryKey = entry.value.toLowerCase();
20238
20338
  const tasks = buckets.get(entryKey) ?? [];
20339
+ if (tasks.length === 0) continue;
20239
20340
  tasks.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
20240
- const progressValues = tasks.map((t) => t.task.progress).filter((p) => p !== null);
20241
- const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
20341
+ const aggregateProgress = durationWeightedProgress(tasks);
20242
20342
  const laneStartDate = tasks.length > 0 ? new Date(Math.min(...tasks.map((t) => t.startDate.getTime()))) : null;
20243
20343
  const laneEndDate = tasks.length > 0 ? new Date(Math.max(...tasks.map((t) => t.endDate.getTime()))) : null;
20244
20344
  const isCollapsed = collapsedLanes?.has(entry.value) ?? false;
@@ -20260,8 +20360,7 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20260
20360
  }
20261
20361
  if (unbucketed.length > 0) {
20262
20362
  unbucketed.sort((a, b) => a.startDate.getTime() - b.startDate.getTime());
20263
- const progressValues = unbucketed.map((t) => t.task.progress).filter((p) => p !== null);
20264
- const aggregateProgress = progressValues.length > 0 ? progressValues.reduce((a, b) => a + b, 0) / progressValues.length : null;
20363
+ const aggregateProgress = durationWeightedProgress(unbucketed);
20265
20364
  const noLaneStartDate = unbucketed.length > 0 ? new Date(Math.min(...unbucketed.map((t) => t.startDate.getTime()))) : null;
20266
20365
  const noLaneEndDate = unbucketed.length > 0 ? new Date(Math.max(...unbucketed.map((t) => t.endDate.getTime()))) : null;
20267
20366
  const noLaneName = `No ${tagGroup.name}`;
@@ -20284,6 +20383,20 @@ function buildTagLaneRowList(resolved, swimlaneGroup, collapsedLanes) {
20284
20383
  }
20285
20384
  return rows;
20286
20385
  }
20386
+ function durationWeightedProgress(tasks) {
20387
+ let totalDuration = 0;
20388
+ let totalProgress = 0;
20389
+ let hasProgress = false;
20390
+ for (const rt of tasks) {
20391
+ const dur = rt.endDate.getTime() - rt.startDate.getTime();
20392
+ totalDuration += dur;
20393
+ if (rt.task.progress !== null) {
20394
+ totalProgress += rt.task.progress * dur;
20395
+ hasProgress = true;
20396
+ }
20397
+ }
20398
+ return hasProgress && totalDuration > 0 ? totalProgress / totalDuration : null;
20399
+ }
20287
20400
  function dateToFractionalYear(d) {
20288
20401
  const y = d.getFullYear();
20289
20402
  const startOfYear = new Date(y, 0, 1);
@@ -20310,9 +20423,28 @@ function showGanttDateIndicators(g, xScale, startDate, endDate, innerHeight, col
20310
20423
  if (endDate && endDate.getTime() !== startDate.getTime()) {
20311
20424
  const endPos = xScale(dateToFractionalYear(endDate));
20312
20425
  const endLabel = formatGanttDate(endDate);
20426
+ const minLabelGap = 90;
20427
+ const gap = endPos - startPos;
20428
+ let startLabelX = startPos;
20429
+ let endLabelX = endPos;
20430
+ let startAnchor = "middle";
20431
+ let endAnchor = "middle";
20432
+ if (gap < minLabelGap) {
20433
+ const mid = (startPos + endPos) / 2;
20434
+ startLabelX = mid - minLabelGap / 2;
20435
+ endLabelX = mid + minLabelGap / 2;
20436
+ startAnchor = "middle";
20437
+ endAnchor = "middle";
20438
+ }
20313
20439
  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);
20314
- 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);
20315
- 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);
20440
+ g.selectAll("text.gantt-hover-date").each(function() {
20441
+ const el = d3Selection10.select(this);
20442
+ if (el.text() === startLabel) {
20443
+ el.attr("x", startLabelX).attr("text-anchor", startAnchor);
20444
+ }
20445
+ });
20446
+ 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);
20447
+ 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);
20316
20448
  }
20317
20449
  }
20318
20450
  function hideGanttDateIndicators(g) {
@@ -22365,7 +22497,7 @@ function parseVisualization(content, palette) {
22365
22497
  continue;
22366
22498
  }
22367
22499
  const markerMatch = line10.match(
22368
- /^marker\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s*:\s*(.+?)(?:\s*\(([^)]+)\))?\s*$/
22500
+ /^marker:\s+(\d{4}(?:-\d{2})?(?:-\d{2})?)\s+(.+?)(?:\s*\(([^)]+)\))?\s*$/
22369
22501
  );
22370
22502
  if (markerMatch) {
22371
22503
  const colorAnnotation = markerMatch[3]?.trim() || null;
@@ -22389,7 +22521,7 @@ function parseVisualization(content, palette) {
22389
22521
  const unit = durationMatch[3];
22390
22522
  const endDate = addDurationToDate(startDate, amount, unit);
22391
22523
  const segments = durationMatch[5].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: startDate,
22395
22527
  endDate,
@@ -22406,7 +22538,7 @@ function parseVisualization(content, palette) {
22406
22538
  );
22407
22539
  if (rangeMatch) {
22408
22540
  const segments = rangeMatch[4].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: rangeMatch[1],
22412
22544
  endDate: rangeMatch[2],
@@ -22423,7 +22555,7 @@ function parseVisualization(content, palette) {
22423
22555
  );
22424
22556
  if (pointMatch) {
22425
22557
  const segments = pointMatch[2].split("|");
22426
- const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap) : {};
22558
+ const metadata = segments.length > 1 ? parsePipeMetadata(["", ...segments.slice(1)], timelineAliasMap, () => warn(lineNumber, MULTIPLE_PIPE_WARNING)) : {};
22427
22559
  result.timelineEvents.push({
22428
22560
  date: pointMatch[1],
22429
22561
  endDate: null,
@@ -25375,9 +25507,8 @@ async function renderForExport(content, theme, palette, orgExportState, options)
25375
25507
  const { renderGantt: renderGantt2 } = await Promise.resolve().then(() => (init_renderer9(), renderer_exports9));
25376
25508
  const effectivePalette2 = await resolveExportPalette(theme, palette);
25377
25509
  const ganttParsed = parseGantt2(content, effectivePalette2);
25378
- if (ganttParsed.error) return "";
25379
25510
  const resolved = calculateSchedule2(ganttParsed);
25380
- if (resolved.error || resolved.tasks.length === 0) return "";
25511
+ if (resolved.tasks.length === 0) return "";
25381
25512
  const EXPORT_W = 1200;
25382
25513
  const EXPORT_H = 800;
25383
25514
  const container2 = createExportContainer(EXPORT_W, EXPORT_H);