@diagrammo/dgmo 0.6.1 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (52) hide show
  1. package/.claude/commands/dgmo.md +294 -0
  2. package/AGENTS.md +148 -0
  3. package/dist/cli.cjs +338 -163
  4. package/dist/index.cjs +1080 -319
  5. package/dist/index.cjs.map +1 -1
  6. package/dist/index.d.cts +28 -4
  7. package/dist/index.d.ts +28 -4
  8. package/dist/index.js +1078 -319
  9. package/dist/index.js.map +1 -1
  10. package/docs/ai-integration.md +33 -50
  11. package/package.json +8 -5
  12. package/src/c4/layout.ts +68 -10
  13. package/src/c4/parser.ts +0 -16
  14. package/src/c4/renderer.ts +1 -5
  15. package/src/class/layout.ts +0 -1
  16. package/src/class/parser.ts +28 -0
  17. package/src/class/renderer.ts +5 -26
  18. package/src/cli.ts +673 -2
  19. package/src/completion.ts +58 -0
  20. package/src/d3.ts +58 -106
  21. package/src/dgmo-router.ts +0 -57
  22. package/src/echarts.ts +96 -55
  23. package/src/er/classify.ts +206 -0
  24. package/src/er/layout.ts +259 -94
  25. package/src/er/parser.ts +30 -1
  26. package/src/er/renderer.ts +231 -18
  27. package/src/graph/flowchart-parser.ts +27 -4
  28. package/src/graph/flowchart-renderer.ts +1 -2
  29. package/src/graph/state-parser.ts +0 -1
  30. package/src/graph/state-renderer.ts +1 -3
  31. package/src/index.ts +10 -0
  32. package/src/infra/compute.ts +0 -7
  33. package/src/infra/layout.ts +60 -15
  34. package/src/infra/parser.ts +46 -4
  35. package/src/infra/renderer.ts +376 -47
  36. package/src/initiative-status/layout.ts +46 -30
  37. package/src/initiative-status/renderer.ts +5 -25
  38. package/src/kanban/parser.ts +0 -2
  39. package/src/org/layout.ts +0 -4
  40. package/src/org/renderer.ts +7 -28
  41. package/src/sequence/parser.ts +14 -11
  42. package/src/sequence/renderer.ts +0 -2
  43. package/src/sequence/tag-resolution.ts +0 -1
  44. package/src/sitemap/layout.ts +1 -14
  45. package/src/sitemap/parser.ts +1 -2
  46. package/src/sitemap/renderer.ts +0 -3
  47. package/src/utils/arrows.ts +7 -7
  48. package/src/utils/export-container.ts +40 -0
  49. package/.claude/skills/dgmo-chart/SKILL.md +0 -141
  50. package/.claude/skills/dgmo-flowchart/SKILL.md +0 -61
  51. package/.claude/skills/dgmo-generate/SKILL.md +0 -59
  52. package/.claude/skills/dgmo-sequence/SKILL.md +0 -104
package/dist/index.cjs CHANGED
@@ -1809,12 +1809,12 @@ var SYNC_LABELED_RE, ASYNC_LABELED_RE, RETURN_SYNC_LABELED_RE, RETURN_ASYNC_LABE
1809
1809
  var init_arrows = __esm({
1810
1810
  "src/utils/arrows.ts"() {
1811
1811
  "use strict";
1812
- SYNC_LABELED_RE = /^(\S+)\s+-(.+)->\s+(\S+)$/;
1813
- ASYNC_LABELED_RE = /^(\S+)\s+~(.+)~>\s+(\S+)$/;
1814
- RETURN_SYNC_LABELED_RE = /^(\S+)\s+<-(.+)-\s+(\S+)$/;
1815
- RETURN_ASYNC_LABELED_RE = /^(\S+)\s+<~(.+)~\s+(\S+)$/;
1816
- BIDI_SYNC_RE = /^(\S+)\s+<-(.+)->\s+(\S+)$/;
1817
- BIDI_ASYNC_RE = /^(\S+)\s+<~(.+)~>\s+(\S+)$/;
1812
+ SYNC_LABELED_RE = /^(.+?)\s+-(.+)->\s+(.+)$/;
1813
+ ASYNC_LABELED_RE = /^(.+?)\s+~(.+)~>\s+(.+)$/;
1814
+ RETURN_SYNC_LABELED_RE = /^(.+?)\s+<-(.+)-\s+(.+)$/;
1815
+ RETURN_ASYNC_LABELED_RE = /^(.+?)\s+<~(.+)~\s+(.+)$/;
1816
+ BIDI_SYNC_RE = /^(.+?)\s+<-(.+)->\s+(.+)$/;
1817
+ BIDI_ASYNC_RE = /^(.+?)\s+<~(.+)~>\s+(.+)$/;
1818
1818
  ARROW_CHARS = ["->", "~>"];
1819
1819
  }
1820
1820
  });
@@ -2223,7 +2223,7 @@ function parseSequenceDgmo(content) {
2223
2223
  continue;
2224
2224
  }
2225
2225
  const bidiPlainMatch = arrowCore.match(
2226
- /^(\S+)\s*(?:<->|<~>)\s*(\S+)/
2226
+ /^(.+?)\s*(?:<->|<~>)\s*(.+)/
2227
2227
  );
2228
2228
  if (bidiPlainMatch) {
2229
2229
  pushError(
@@ -2232,8 +2232,8 @@ function parseSequenceDgmo(content) {
2232
2232
  );
2233
2233
  continue;
2234
2234
  }
2235
- const bareReturnSync = arrowCore.match(/^(\S+)\s+<-\s+(\S+)$/);
2236
- const bareReturnAsync = arrowCore.match(/^(\S+)\s+<~\s+(\S+)$/);
2235
+ const bareReturnSync = arrowCore.match(/^(.+?)\s+<-\s+(.+)$/);
2236
+ const bareReturnAsync = arrowCore.match(/^(.+?)\s+<~\s+(.+)$/);
2237
2237
  const bareReturn = bareReturnSync || bareReturnAsync;
2238
2238
  if (bareReturn) {
2239
2239
  const to = bareReturn[1];
@@ -2244,8 +2244,8 @@ function parseSequenceDgmo(content) {
2244
2244
  );
2245
2245
  continue;
2246
2246
  }
2247
- const bareCallSync = arrowCore.match(/^(\S+)\s*->\s*(\S+)$/);
2248
- const bareCallAsync = arrowCore.match(/^(\S+)\s*~>\s*(\S+)$/);
2247
+ const bareCallSync = arrowCore.match(/^(.+?)\s*->\s*(.+)$/);
2248
+ const bareCallAsync = arrowCore.match(/^(.+?)\s*~>\s*(.+)$/);
2249
2249
  const bareCall = bareCallSync || bareCallAsync;
2250
2250
  if (bareCall) {
2251
2251
  contentStarted = true;
@@ -2497,21 +2497,22 @@ var init_parser = __esm({
2497
2497
  "networking",
2498
2498
  "frontend"
2499
2499
  ]);
2500
- IS_A_PATTERN = /^(\S+)\s+is\s+an?\s+(\w+)(?:\s+(.+))?$/i;
2501
- POSITION_ONLY_PATTERN = /^(\S+)\s+position\s+(-?\d+)$/i;
2500
+ IS_A_PATTERN = /^([^:]+?)\s+is\s+an?\s+(\w+)(?:\s+(.+))?$/i;
2501
+ POSITION_ONLY_PATTERN = /^([^:]+?)\s+position\s+(-?\d+)$/i;
2502
2502
  COLORED_PARTICIPANT_PATTERN = /^(\S+?)\(([^)]+)\)\s*$/;
2503
2503
  GROUP_HEADING_PATTERN = /^\[(.+?)(?:\(([^)]+)\))?\]\s*$/;
2504
2504
  LEGACY_GROUP_PATTERN = /^##\s+(.+?)(?:\(([^)]+)\))?\s*$/;
2505
2505
  SECTION_PATTERN = /^==\s+(.+?)(?:\s*==)?\s*$/;
2506
2506
  ARROW_PATTERN = /\S+\s*(?:<-\S+-|<~\S+~|-\S+->|~\S+~>|->|~>|<-|<~)\s*\S+/;
2507
- NOTE_SINGLE = /^note(?:\s+(right|left)\s+of\s+(\S+))?\s*:\s*(.+)$/i;
2508
- NOTE_MULTI = /^note(?:\s+(right|left)\s+of\s+([^\s:]+))?\s*:?\s*$/i;
2507
+ NOTE_SINGLE = /^note(?:\s+(right|left)\s+of\s+(.+?))?\s*:\s*(.+)$/i;
2508
+ NOTE_MULTI = /^note(?:\s+(right|left)\s+of\s+(.+?))?\s*:?\s*$/i;
2509
2509
  }
2510
2510
  });
2511
2511
 
2512
2512
  // src/graph/flowchart-parser.ts
2513
2513
  var flowchart_parser_exports = {};
2514
2514
  __export(flowchart_parser_exports, {
2515
+ extractSymbols: () => extractSymbols,
2515
2516
  looksLikeFlowchart: () => looksLikeFlowchart,
2516
2517
  parseFlowchart: () => parseFlowchart
2517
2518
  });
@@ -2555,7 +2556,6 @@ function parseNodeRef(text, palette) {
2555
2556
  }
2556
2557
  function splitArrows(line10) {
2557
2558
  const segments = [];
2558
- const arrowRe = /(?:^|\s)-([^>\s(][^(>]*?)?\s*(?:\(([^)]+)\))?\s*->|(?:^|\s)->/g;
2559
2559
  let lastIndex = 0;
2560
2560
  const arrowPositions = [];
2561
2561
  let searchFrom = 0;
@@ -2796,7 +2796,20 @@ function looksLikeFlowchart(content) {
2796
2796
  /->[ \t]*[\[(<\/]/.test(content);
2797
2797
  return shapeNearArrow;
2798
2798
  }
2799
- var LEGACY_GROUP_RE;
2799
+ function extractSymbols(docText) {
2800
+ const entities = [];
2801
+ let inMetadata = true;
2802
+ for (const rawLine of docText.split("\n")) {
2803
+ const line10 = rawLine.trim();
2804
+ if (inMetadata && /^[a-z-]+\s*:/i.test(line10)) continue;
2805
+ inMetadata = false;
2806
+ if (line10.length === 0 || /^\s/.test(rawLine)) continue;
2807
+ const m = NODE_ID_RE.exec(line10);
2808
+ if (m && !entities.includes(m[1])) entities.push(m[1]);
2809
+ }
2810
+ return { kind: "flowchart", entities, keywords: [] };
2811
+ }
2812
+ var LEGACY_GROUP_RE, NODE_ID_RE;
2800
2813
  var init_flowchart_parser = __esm({
2801
2814
  "src/graph/flowchart-parser.ts"() {
2802
2815
  "use strict";
@@ -2804,6 +2817,7 @@ var init_flowchart_parser = __esm({
2804
2817
  init_diagnostics();
2805
2818
  init_parsing();
2806
2819
  LEGACY_GROUP_RE = /^##\s+/;
2820
+ NODE_ID_RE = /^([a-zA-Z_][\w-]*)[\s([</{]/;
2807
2821
  }
2808
2822
  });
2809
2823
 
@@ -3079,6 +3093,7 @@ var init_state_parser = __esm({
3079
3093
  // src/class/parser.ts
3080
3094
  var parser_exports2 = {};
3081
3095
  __export(parser_exports2, {
3096
+ extractSymbols: () => extractSymbols2,
3082
3097
  looksLikeClassDiagram: () => looksLikeClassDiagram,
3083
3098
  parseClassDiagram: () => parseClassDiagram
3084
3099
  });
@@ -3326,6 +3341,23 @@ function looksLikeClassDiagram(content) {
3326
3341
  if (hasRelationship && hasClassDecl && hasIndentedMember) return true;
3327
3342
  return false;
3328
3343
  }
3344
+ function extractSymbols2(docText) {
3345
+ const entities = [];
3346
+ let inMetadata = true;
3347
+ for (const rawLine of docText.split("\n")) {
3348
+ const line10 = rawLine.trim();
3349
+ if (inMetadata && /^[a-z-]+\s*:/i.test(line10)) continue;
3350
+ inMetadata = false;
3351
+ if (line10.length === 0 || /^\s/.test(rawLine)) continue;
3352
+ const m = CLASS_DECL_RE.exec(line10);
3353
+ if (m && !entities.includes(m[1])) entities.push(m[1]);
3354
+ }
3355
+ return {
3356
+ kind: "class",
3357
+ entities,
3358
+ keywords: ["extends", "implements", "abstract", "interface", "enum"]
3359
+ };
3360
+ }
3329
3361
  var CLASS_DECL_RE, REL_ARROW_RE, VISIBILITY_RE, STATIC_SUFFIX_RE, METHOD_RE, FIELD_RE, ARROW_TO_TYPE;
3330
3362
  var init_parser2 = __esm({
3331
3363
  "src/class/parser.ts"() {
@@ -3353,6 +3385,7 @@ var init_parser2 = __esm({
3353
3385
  // src/er/parser.ts
3354
3386
  var parser_exports3 = {};
3355
3387
  __export(parser_exports3, {
3388
+ extractSymbols: () => extractSymbols3,
3356
3389
  looksLikeERDiagram: () => looksLikeERDiagram,
3357
3390
  parseERDiagram: () => parseERDiagram
3358
3391
  });
@@ -3655,6 +3688,25 @@ function looksLikeERDiagram(content) {
3655
3688
  if (hasRelationship && hasTableDecl) return true;
3656
3689
  return false;
3657
3690
  }
3691
+ function extractSymbols3(docText) {
3692
+ const entities = [];
3693
+ let inMetadata = true;
3694
+ for (const rawLine of docText.split("\n")) {
3695
+ const line10 = rawLine.trim();
3696
+ if (inMetadata && /^chart\s*:/i.test(line10)) continue;
3697
+ if (inMetadata && /^[a-z-]+\s*:/i.test(line10)) continue;
3698
+ inMetadata = false;
3699
+ if (line10.length === 0) continue;
3700
+ if (/^\s/.test(rawLine)) continue;
3701
+ const m = TABLE_DECL_RE.exec(line10);
3702
+ if (m) entities.push(m[1]);
3703
+ }
3704
+ return {
3705
+ kind: "er",
3706
+ entities,
3707
+ keywords: ["pk", "fk", "unique", "nullable", "1", "*", "?"]
3708
+ };
3709
+ }
3658
3710
  var TABLE_DECL_RE, COLUMN_RE, INDENT_REL_RE, CONSTRAINT_MAP, REL_SYMBOLIC_RE, REL_KEYWORD_RE, KEYWORD_TO_SYMBOL;
3659
3711
  var init_parser3 = __esm({
3660
3712
  "src/er/parser.ts"() {
@@ -4136,10 +4188,12 @@ function buildExtendedChartOption(parsed, palette, isDark) {
4136
4188
  );
4137
4189
  }
4138
4190
  if (parsed.type === "chord") {
4191
+ const bg = isDark ? palette.surface : palette.bg;
4139
4192
  return buildChordOption(
4140
4193
  parsed,
4141
4194
  textColor,
4142
4195
  colors,
4196
+ bg,
4143
4197
  titleConfig,
4144
4198
  tooltipTheme
4145
4199
  );
@@ -4157,6 +4211,7 @@ function buildExtendedChartOption(parsed, palette, isDark) {
4157
4211
  );
4158
4212
  }
4159
4213
  if (parsed.type === "scatter") {
4214
+ const bg = isDark ? palette.surface : palette.bg;
4160
4215
  return buildScatterOption(
4161
4216
  parsed,
4162
4217
  palette,
@@ -4164,15 +4219,18 @@ function buildExtendedChartOption(parsed, palette, isDark) {
4164
4219
  axisLineColor,
4165
4220
  gridOpacity,
4166
4221
  colors,
4222
+ bg,
4167
4223
  titleConfig,
4168
4224
  tooltipTheme
4169
4225
  );
4170
4226
  }
4171
4227
  if (parsed.type === "funnel") {
4228
+ const bg = isDark ? palette.surface : palette.bg;
4172
4229
  return buildFunnelOption(
4173
4230
  parsed,
4174
4231
  textColor,
4175
4232
  colors,
4233
+ bg,
4176
4234
  titleConfig,
4177
4235
  tooltipTheme
4178
4236
  );
@@ -4180,6 +4238,7 @@ function buildExtendedChartOption(parsed, palette, isDark) {
4180
4238
  return buildHeatmapOption(
4181
4239
  parsed,
4182
4240
  palette,
4241
+ isDark,
4183
4242
  textColor,
4184
4243
  axisLineColor,
4185
4244
  titleConfig,
@@ -4236,7 +4295,7 @@ function buildSankeyOption(parsed, textColor, colors, titleConfig, tooltipTheme)
4236
4295
  ]
4237
4296
  };
4238
4297
  }
4239
- function buildChordOption(parsed, textColor, colors, titleConfig, tooltipTheme) {
4298
+ function buildChordOption(parsed, textColor, colors, bg, titleConfig, tooltipTheme) {
4240
4299
  const nodeSet = /* @__PURE__ */ new Set();
4241
4300
  if (parsed.links) {
4242
4301
  for (const link of parsed.links) {
@@ -4256,12 +4315,13 @@ function buildChordOption(parsed, textColor, colors, titleConfig, tooltipTheme)
4256
4315
  }
4257
4316
  }
4258
4317
  }
4259
- const categories = nodeNames.map((name, index) => ({
4260
- name,
4261
- itemStyle: {
4262
- color: colors[index % colors.length]
4263
- }
4264
- }));
4318
+ const categories = nodeNames.map((name, index) => {
4319
+ const stroke2 = colors[index % colors.length];
4320
+ return {
4321
+ name,
4322
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
4323
+ };
4324
+ });
4265
4325
  return {
4266
4326
  ...CHART_BASE,
4267
4327
  title: titleConfig,
@@ -4429,7 +4489,7 @@ function buildFunctionOption(parsed, palette, textColor, axisLineColor, gridOpac
4429
4489
  series
4430
4490
  };
4431
4491
  }
4432
- function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpacity, colors, titleConfig, tooltipTheme) {
4492
+ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme) {
4433
4493
  const points = parsed.scatterPoints ?? [];
4434
4494
  const defaultSize = 15;
4435
4495
  const hasCategories = points.some((p) => p.category !== void 0);
@@ -4461,27 +4521,30 @@ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpaci
4461
4521
  const data = categoryPoints.map((p) => ({
4462
4522
  name: p.name,
4463
4523
  value: hasSize ? [p.x, p.y, p.size ?? 0] : [p.x, p.y],
4464
- ...p.color && { itemStyle: { color: p.color } }
4524
+ ...p.color && {
4525
+ itemStyle: { color: mix(p.color, bg, 30), borderColor: p.color, borderWidth: CHART_BORDER_WIDTH }
4526
+ }
4465
4527
  }));
4466
4528
  return {
4467
4529
  name: category,
4468
4530
  type: "scatter",
4469
4531
  data,
4470
4532
  ...hasSize ? { symbolSize: (val) => val[2] } : { symbolSize: defaultSize },
4471
- itemStyle: { color: catColor },
4533
+ itemStyle: { color: mix(catColor, bg, 30), borderColor: catColor, borderWidth: CHART_BORDER_WIDTH },
4472
4534
  label: labelConfig,
4473
4535
  emphasis: emphasisConfig
4474
4536
  };
4475
4537
  });
4476
4538
  } else {
4477
- const data = points.map((p, index) => ({
4478
- name: p.name,
4479
- value: hasSize ? [p.x, p.y, p.size ?? 0] : [p.x, p.y],
4480
- ...hasSize ? { symbolSize: p.size ?? defaultSize } : { symbolSize: defaultSize },
4481
- itemStyle: {
4482
- color: p.color ?? colors[index % colors.length]
4483
- }
4484
- }));
4539
+ const data = points.map((p, index) => {
4540
+ const stroke2 = p.color ?? colors[index % colors.length];
4541
+ return {
4542
+ name: p.name,
4543
+ value: hasSize ? [p.x, p.y, p.size ?? 0] : [p.x, p.y],
4544
+ ...hasSize ? { symbolSize: p.size ?? defaultSize } : { symbolSize: defaultSize },
4545
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
4546
+ };
4547
+ });
4485
4548
  series = [
4486
4549
  {
4487
4550
  type: "scatter",
@@ -4584,7 +4647,8 @@ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpaci
4584
4647
  series
4585
4648
  };
4586
4649
  }
4587
- function buildHeatmapOption(parsed, palette, textColor, axisLineColor, titleConfig, tooltipTheme) {
4650
+ function buildHeatmapOption(parsed, palette, isDark, textColor, axisLineColor, titleConfig, tooltipTheme) {
4651
+ const bg = isDark ? palette.surface : palette.bg;
4588
4652
  const heatmapRows = parsed.heatmapRows ?? [];
4589
4653
  const columns = parsed.columns ?? [];
4590
4654
  const rowLabels = heatmapRows.map((r) => r.label);
@@ -4655,10 +4719,10 @@ function buildHeatmapOption(parsed, palette, textColor, axisLineColor, titleConf
4655
4719
  top: "center",
4656
4720
  inRange: {
4657
4721
  color: [
4658
- palette.primary,
4659
- palette.colors.cyan,
4660
- palette.colors.yellow,
4661
- palette.colors.orange
4722
+ mix(palette.primary, bg, 30),
4723
+ mix(palette.colors.cyan, bg, 30),
4724
+ mix(palette.colors.yellow, bg, 30),
4725
+ mix(palette.colors.orange, bg, 30)
4662
4726
  ]
4663
4727
  },
4664
4728
  textStyle: {
@@ -4669,9 +4733,13 @@ function buildHeatmapOption(parsed, palette, textColor, axisLineColor, titleConf
4669
4733
  {
4670
4734
  type: "heatmap",
4671
4735
  data,
4736
+ itemStyle: {
4737
+ borderWidth: 2,
4738
+ borderColor: bg
4739
+ },
4672
4740
  label: {
4673
4741
  show: true,
4674
- color: "#ffffff",
4742
+ color: textColor,
4675
4743
  fontSize: 14,
4676
4744
  fontWeight: "bold"
4677
4745
  },
@@ -4686,17 +4754,21 @@ function buildHeatmapOption(parsed, palette, textColor, axisLineColor, titleConf
4686
4754
  ]
4687
4755
  };
4688
4756
  }
4689
- function buildFunnelOption(parsed, textColor, colors, titleConfig, tooltipTheme) {
4757
+ function buildFunnelOption(parsed, textColor, colors, bg, titleConfig, tooltipTheme) {
4690
4758
  const sorted = [...parsed.data].sort((a, b) => b.value - a.value);
4691
4759
  const topValue = sorted.length > 0 ? sorted[0].value : 1;
4692
- const data = sorted.map((d) => ({
4693
- name: d.label,
4694
- value: d.value,
4695
- itemStyle: {
4696
- color: d.color ?? colors[parsed.data.indexOf(d) % colors.length],
4697
- borderWidth: 0
4698
- }
4699
- }));
4760
+ const data = sorted.map((d) => {
4761
+ const stroke2 = d.color ?? colors[parsed.data.indexOf(d) % colors.length];
4762
+ return {
4763
+ name: d.label,
4764
+ value: d.value,
4765
+ itemStyle: {
4766
+ color: mix(stroke2, bg, 30),
4767
+ borderColor: stroke2,
4768
+ borderWidth: CHART_BORDER_WIDTH
4769
+ }
4770
+ };
4771
+ });
4700
4772
  const prevValueMap = /* @__PURE__ */ new Map();
4701
4773
  for (let i = 0; i < sorted.length; i++) {
4702
4774
  prevValueMap.set(
@@ -4842,23 +4914,24 @@ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacit
4842
4914
  function buildSimpleChartOption(parsed, palette, isDark, chartWidth) {
4843
4915
  if (parsed.error) return {};
4844
4916
  const { textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme } = buildChartCommons(parsed, palette, isDark);
4917
+ const bg = isDark ? palette.surface : palette.bg;
4845
4918
  switch (parsed.type) {
4846
4919
  case "bar":
4847
- return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
4920
+ return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth);
4848
4921
  case "bar-stacked":
4849
- return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
4922
+ return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth);
4850
4923
  case "line":
4851
4924
  return parsed.seriesNames ? buildMultiLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) : buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
4852
4925
  case "area":
4853
4926
  return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
4854
4927
  case "pie":
4855
- return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, false);
4928
+ return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme, false);
4856
4929
  case "doughnut":
4857
- return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, true);
4930
+ return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme, true);
4858
4931
  case "radar":
4859
- return buildRadarOption(parsed, palette, textColor, gridOpacity, colors, titleConfig, tooltipTheme);
4932
+ return buildRadarOption(parsed, palette, isDark, textColor, gridOpacity, titleConfig, tooltipTheme);
4860
4933
  case "polar-area":
4861
- return buildPolarAreaOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme);
4934
+ return buildPolarAreaOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme);
4862
4935
  }
4863
4936
  }
4864
4937
  function makeChartGrid(options) {
@@ -4870,14 +4943,17 @@ function makeChartGrid(options) {
4870
4943
  containLabel: true
4871
4944
  };
4872
4945
  }
4873
- function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) {
4946
+ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth) {
4874
4947
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
4875
4948
  const isHorizontal = parsed.orientation === "horizontal";
4876
4949
  const labels = parsed.data.map((d) => d.label);
4877
- const data = parsed.data.map((d, i) => ({
4878
- value: d.value,
4879
- itemStyle: { color: d.color ?? colors[i % colors.length] }
4880
- }));
4950
+ const data = parsed.data.map((d, i) => {
4951
+ const stroke2 = d.color ?? colors[i % colors.length];
4952
+ return {
4953
+ value: d.value,
4954
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
4955
+ };
4956
+ });
4881
4957
  const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4882
4958
  const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap, !isHorizontal ? chartWidth : void 0);
4883
4959
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
@@ -5058,12 +5134,15 @@ function segmentLabelFormatter(mode) {
5058
5134
  return "{b} \u2014 {c} ({d}%)";
5059
5135
  }
5060
5136
  }
5061
- function buildPieOption(parsed, textColor, colors, titleConfig, tooltipTheme, isDoughnut) {
5062
- const data = parsed.data.map((d, i) => ({
5063
- name: d.label,
5064
- value: d.value,
5065
- itemStyle: { color: d.color ?? colors[i % colors.length] }
5066
- }));
5137
+ function buildPieOption(parsed, textColor, colors, bg, titleConfig, tooltipTheme, isDoughnut) {
5138
+ const data = parsed.data.map((d, i) => {
5139
+ const stroke2 = d.color ?? colors[i % colors.length];
5140
+ return {
5141
+ name: d.label,
5142
+ value: d.value,
5143
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
5144
+ };
5145
+ });
5067
5146
  return {
5068
5147
  ...CHART_BASE,
5069
5148
  title: titleConfig,
@@ -5088,7 +5167,8 @@ function buildPieOption(parsed, textColor, colors, titleConfig, tooltipTheme, is
5088
5167
  ]
5089
5168
  };
5090
5169
  }
5091
- function buildRadarOption(parsed, palette, textColor, gridOpacity, colors, titleConfig, tooltipTheme) {
5170
+ function buildRadarOption(parsed, palette, isDark, textColor, gridOpacity, titleConfig, tooltipTheme) {
5171
+ const bg = isDark ? palette.surface : palette.bg;
5092
5172
  const radarColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
5093
5173
  const values = parsed.data.map((d) => d.value);
5094
5174
  const maxValue = Math.max(...values) * 1.15;
@@ -5125,7 +5205,7 @@ function buildRadarOption(parsed, palette, textColor, gridOpacity, colors, title
5125
5205
  {
5126
5206
  value: values,
5127
5207
  name: parsed.series ?? "Value",
5128
- areaStyle: { color: radarColor, opacity: 0.25 },
5208
+ areaStyle: { color: mix(radarColor, bg, 30) },
5129
5209
  lineStyle: { color: radarColor },
5130
5210
  itemStyle: { color: radarColor },
5131
5211
  symbol: "circle",
@@ -5144,12 +5224,15 @@ function buildRadarOption(parsed, palette, textColor, gridOpacity, colors, title
5144
5224
  ]
5145
5225
  };
5146
5226
  }
5147
- function buildPolarAreaOption(parsed, textColor, colors, titleConfig, tooltipTheme) {
5148
- const data = parsed.data.map((d, i) => ({
5149
- name: d.label,
5150
- value: d.value,
5151
- itemStyle: { color: d.color ?? colors[i % colors.length] }
5152
- }));
5227
+ function buildPolarAreaOption(parsed, textColor, colors, bg, titleConfig, tooltipTheme) {
5228
+ const data = parsed.data.map((d, i) => {
5229
+ const stroke2 = d.color ?? colors[i % colors.length];
5230
+ return {
5231
+ name: d.label,
5232
+ value: d.value,
5233
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
5234
+ };
5235
+ });
5153
5236
  return {
5154
5237
  ...CHART_BASE,
5155
5238
  title: titleConfig,
@@ -5175,7 +5258,7 @@ function buildPolarAreaOption(parsed, textColor, colors, titleConfig, tooltipThe
5175
5258
  ]
5176
5259
  };
5177
5260
  }
5178
- function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) {
5261
+ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth) {
5179
5262
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
5180
5263
  const isHorizontal = parsed.orientation === "horizontal";
5181
5264
  const seriesNames = parsed.seriesNames ?? [];
@@ -5190,12 +5273,12 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
5190
5273
  type: "bar",
5191
5274
  stack: "total",
5192
5275
  data,
5193
- itemStyle: { color },
5276
+ itemStyle: { color: mix(color, bg, 30), borderColor: color, borderWidth: CHART_BORDER_WIDTH },
5194
5277
  label: {
5195
5278
  show: true,
5196
5279
  position: "inside",
5197
5280
  formatter: "{c}",
5198
- color: "#ffffff",
5281
+ color: textColor,
5199
5282
  fontSize: 14,
5200
5283
  fontWeight: "bold",
5201
5284
  fontFamily: FONT_FAMILY
@@ -5267,7 +5350,7 @@ async function renderExtendedChartForExport(content, theme, palette, options) {
5267
5350
  chart.dispose();
5268
5351
  }
5269
5352
  }
5270
- var echarts, EMPHASIS_SELF, CHART_BASE, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
5353
+ var echarts, EMPHASIS_SELF, CHART_BASE, CHART_BORDER_WIDTH, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
5271
5354
  var init_echarts = __esm({
5272
5355
  "src/echarts.ts"() {
5273
5356
  "use strict";
@@ -5275,12 +5358,14 @@ var init_echarts = __esm({
5275
5358
  init_fonts();
5276
5359
  init_branding();
5277
5360
  init_palettes();
5361
+ init_color_utils();
5278
5362
  init_chart();
5279
5363
  init_diagnostics();
5280
5364
  init_colors();
5281
5365
  init_parsing();
5282
5366
  EMPHASIS_SELF = { focus: "self", blurScope: "global" };
5283
5367
  CHART_BASE = { backgroundColor: "transparent", animation: false };
5368
+ CHART_BORDER_WIDTH = 2;
5284
5369
  ECHART_EXPORT_WIDTH = 1200;
5285
5370
  ECHART_EXPORT_HEIGHT = 800;
5286
5371
  STANDARD_CHART_TYPES = /* @__PURE__ */ new Set([
@@ -6995,6 +7080,7 @@ var init_types2 = __esm({
6995
7080
  // src/infra/parser.ts
6996
7081
  var parser_exports9 = {};
6997
7082
  __export(parser_exports9, {
7083
+ extractSymbols: () => extractSymbols4,
6998
7084
  parseInfra: () => parseInfra
6999
7085
  });
7000
7086
  function nodeId2(name) {
@@ -7037,7 +7123,6 @@ function parseInfra(content) {
7037
7123
  error: null
7038
7124
  };
7039
7125
  const nodeMap = /* @__PURE__ */ new Map();
7040
- const edgeNodeId = "edge";
7041
7126
  const setError = (line10, message) => {
7042
7127
  const diag = makeDgmoError(line10, message);
7043
7128
  result.diagnostics.push(diag);
@@ -7403,6 +7488,38 @@ function parseInfra(content) {
7403
7488
  }
7404
7489
  return result;
7405
7490
  }
7491
+ function extractSymbols4(docText) {
7492
+ const entities = [];
7493
+ let inMetadata = true;
7494
+ let inTagGroup = false;
7495
+ for (const rawLine of docText.split("\n")) {
7496
+ const line10 = rawLine.trim();
7497
+ if (line10.length === 0) continue;
7498
+ const indented = /^\s/.test(rawLine);
7499
+ if (inMetadata) {
7500
+ if (!indented && !/^[a-z-]+\s*:/i.test(line10)) inMetadata = false;
7501
+ else continue;
7502
+ }
7503
+ if (!indented) {
7504
+ if (/^tag\s*:/i.test(line10)) {
7505
+ inTagGroup = true;
7506
+ continue;
7507
+ }
7508
+ inTagGroup = false;
7509
+ if (/^\[/.test(line10)) continue;
7510
+ const m = COMPONENT_RE.exec(line10);
7511
+ if (m && !entities.includes(m[1])) entities.push(m[1]);
7512
+ } else {
7513
+ if (inTagGroup) continue;
7514
+ if (/^->/.test(line10)) continue;
7515
+ if (/^-[^>]+-?>/.test(line10)) continue;
7516
+ if (/^\w[\w-]*\s*:/.test(line10)) continue;
7517
+ const m = COMPONENT_RE.exec(line10);
7518
+ if (m && !entities.includes(m[1])) entities.push(m[1]);
7519
+ }
7520
+ }
7521
+ return { kind: "infra", entities, keywords: [] };
7522
+ }
7406
7523
  var CONNECTION_RE, SIMPLE_CONNECTION_RE, GROUP_RE, TAG_GROUP_RE, TAG_VALUE_RE, COMPONENT_RE, PIPE_META_RE, PROPERTY_RE, PERCENT_RE, RANGE_RE, EDGE_NODE_NAMES;
7407
7524
  var init_parser9 = __esm({
7408
7525
  "src/infra/parser.ts"() {
@@ -8265,7 +8382,6 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
8265
8382
  const visibleGroups = activeTagGroup != null ? legendGroups.filter((g) => g.name.toLowerCase() === activeTagGroup.toLowerCase()) : legendGroups;
8266
8383
  const allExpanded = expandAllLegend && activeTagGroup == null;
8267
8384
  const effectiveW = (g) => activeTagGroup != null || allExpanded ? g.width : g.minifiedWidth;
8268
- const effectiveH = (g) => activeTagGroup != null || allExpanded ? g.height : g.minifiedHeight;
8269
8385
  if (visibleGroups.length > 0) {
8270
8386
  if (legendPosition === "bottom") {
8271
8387
  const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP2;
@@ -8419,6 +8535,35 @@ var init_collapse = __esm({
8419
8535
  }
8420
8536
  });
8421
8537
 
8538
+ // src/utils/export-container.ts
8539
+ function runInExportContainer(width, height, fn) {
8540
+ const container = document.createElement("div");
8541
+ container.style.width = `${width}px`;
8542
+ container.style.height = `${height}px`;
8543
+ container.style.position = "absolute";
8544
+ container.style.left = "-9999px";
8545
+ document.body.appendChild(container);
8546
+ try {
8547
+ return fn(container);
8548
+ } finally {
8549
+ document.body.removeChild(container);
8550
+ }
8551
+ }
8552
+ function extractExportSvg(container, theme) {
8553
+ const svgEl = container.querySelector("svg");
8554
+ if (!svgEl) return "";
8555
+ if (theme === "transparent") svgEl.style.background = "none";
8556
+ svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
8557
+ svgEl.style.fontFamily = FONT_FAMILY;
8558
+ return svgEl.outerHTML;
8559
+ }
8560
+ var init_export_container = __esm({
8561
+ "src/utils/export-container.ts"() {
8562
+ "use strict";
8563
+ init_fonts();
8564
+ }
8565
+ });
8566
+
8422
8567
  // src/org/renderer.ts
8423
8568
  var renderer_exports = {};
8424
8569
  __export(renderer_exports, {
@@ -8673,31 +8818,16 @@ function renderOrgForExport(content, theme, palette) {
8673
8818
  const exportHidden = hideOption ? new Set(hideOption.split(",").map((s) => s.trim().toLowerCase())) : void 0;
8674
8819
  const layout = layoutOrg(parsed, void 0, void 0, exportHidden);
8675
8820
  const isDark = theme === "dark";
8676
- const container = document.createElement("div");
8677
8821
  const titleOffset = parsed.title ? TITLE_HEIGHT : 0;
8678
8822
  const exportWidth = layout.width + DIAGRAM_PADDING * 2;
8679
8823
  const exportHeight = layout.height + DIAGRAM_PADDING * 2 + titleOffset;
8680
- container.style.width = `${exportWidth}px`;
8681
- container.style.height = `${exportHeight}px`;
8682
- container.style.position = "absolute";
8683
- container.style.left = "-9999px";
8684
- document.body.appendChild(container);
8685
- try {
8824
+ return runInExportContainer(exportWidth, exportHeight, (container) => {
8686
8825
  renderOrg(container, parsed, layout, palette, isDark, void 0, {
8687
8826
  width: exportWidth,
8688
8827
  height: exportHeight
8689
8828
  });
8690
- const svgEl = container.querySelector("svg");
8691
- if (!svgEl) return "";
8692
- if (theme === "transparent") {
8693
- svgEl.style.background = "none";
8694
- }
8695
- svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
8696
- svgEl.style.fontFamily = FONT_FAMILY;
8697
- return svgEl.outerHTML;
8698
- } finally {
8699
- document.body.removeChild(container);
8700
- }
8829
+ return extractExportSvg(container, theme);
8830
+ });
8701
8831
  }
8702
8832
  var d3Selection, DIAGRAM_PADDING, MAX_SCALE, TITLE_HEIGHT, TITLE_FONT_SIZE, LABEL_FONT_SIZE, META_FONT_SIZE, META_LINE_HEIGHT2, HEADER_HEIGHT2, SEPARATOR_GAP2, EDGE_STROKE_WIDTH, NODE_STROKE_WIDTH, CARD_RADIUS, CONTAINER_RADIUS, CONTAINER_LABEL_FONT_SIZE, CONTAINER_META_FONT_SIZE, CONTAINER_META_LINE_HEIGHT2, CONTAINER_HEADER_HEIGHT, COLLAPSE_BAR_HEIGHT, COLLAPSE_BAR_INSET, LEGEND_FIXED_GAP;
8703
8833
  var init_renderer = __esm({
@@ -8705,6 +8835,7 @@ var init_renderer = __esm({
8705
8835
  "use strict";
8706
8836
  d3Selection = __toESM(require("d3-selection"), 1);
8707
8837
  init_fonts();
8838
+ init_export_container();
8708
8839
  init_color_utils();
8709
8840
  init_parser4();
8710
8841
  init_layout();
@@ -10290,7 +10421,6 @@ function renderClassDiagram(container, parsed, layout, palette, isDark, onClickI
10290
10421
  const scaleY = (availH - DIAGRAM_PADDING4 * 2) / diagramH;
10291
10422
  const scale = Math.min(MAX_SCALE3, scaleX, scaleY);
10292
10423
  const scaledW = diagramW * scale;
10293
- const scaledH = diagramH * scale;
10294
10424
  const offsetX = (width - scaledW) / 2;
10295
10425
  const offsetY = titleHeight + DIAGRAM_PADDING4;
10296
10426
  const svg = d3Selection4.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -10421,15 +10551,9 @@ function renderClassDiagramForExport(content, theme, palette) {
10421
10551
  if (parsed.error || parsed.classes.length === 0) return "";
10422
10552
  const layout = layoutClassDiagram(parsed);
10423
10553
  const isDark = theme === "dark";
10424
- const container = document.createElement("div");
10425
10554
  const exportWidth = layout.width + DIAGRAM_PADDING4 * 2;
10426
10555
  const exportHeight = layout.height + DIAGRAM_PADDING4 * 2 + (parsed.title ? 40 : 0);
10427
- container.style.width = `${exportWidth}px`;
10428
- container.style.height = `${exportHeight}px`;
10429
- container.style.position = "absolute";
10430
- container.style.left = "-9999px";
10431
- document.body.appendChild(container);
10432
- try {
10556
+ return runInExportContainer(exportWidth, exportHeight, (container) => {
10433
10557
  renderClassDiagram(
10434
10558
  container,
10435
10559
  parsed,
@@ -10439,17 +10563,8 @@ function renderClassDiagramForExport(content, theme, palette) {
10439
10563
  void 0,
10440
10564
  { width: exportWidth, height: exportHeight }
10441
10565
  );
10442
- const svgEl = container.querySelector("svg");
10443
- if (!svgEl) return "";
10444
- if (theme === "transparent") {
10445
- svgEl.style.background = "none";
10446
- }
10447
- svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
10448
- svgEl.style.fontFamily = FONT_FAMILY;
10449
- return svgEl.outerHTML;
10450
- } finally {
10451
- document.body.removeChild(container);
10452
- }
10566
+ return extractExportSvg(container, theme);
10567
+ });
10453
10568
  }
10454
10569
  var d3Selection4, d3Shape2, DIAGRAM_PADDING4, MAX_SCALE3, CLASS_FONT_SIZE, MEMBER_FONT_SIZE, EDGE_LABEL_FONT_SIZE2, EDGE_STROKE_WIDTH3, NODE_STROKE_WIDTH3, MEMBER_LINE_HEIGHT2, COMPARTMENT_PADDING_Y2, MEMBER_PADDING_X, lineGenerator2;
10455
10570
  var init_renderer4 = __esm({
@@ -10458,6 +10573,7 @@ var init_renderer4 = __esm({
10458
10573
  d3Selection4 = __toESM(require("d3-selection"), 1);
10459
10574
  d3Shape2 = __toESM(require("d3-shape"), 1);
10460
10575
  init_fonts();
10576
+ init_export_container();
10461
10577
  init_color_utils();
10462
10578
  init_parser2();
10463
10579
  init_layout3();
@@ -10497,110 +10613,211 @@ function computeNodeDimensions2(table) {
10497
10613
  const height = headerHeight + columnsHeight + (columnsHeight === 0 ? 4 : 0);
10498
10614
  return { width, height, headerHeight, columnsHeight };
10499
10615
  }
10616
+ function findConnectedComponents(tableIds, relationships) {
10617
+ const adj = /* @__PURE__ */ new Map();
10618
+ for (const id of tableIds) adj.set(id, /* @__PURE__ */ new Set());
10619
+ for (const rel of relationships) {
10620
+ adj.get(rel.source)?.add(rel.target);
10621
+ adj.get(rel.target)?.add(rel.source);
10622
+ }
10623
+ const visited = /* @__PURE__ */ new Set();
10624
+ const components = [];
10625
+ for (const id of tableIds) {
10626
+ if (visited.has(id)) continue;
10627
+ const comp = [];
10628
+ const queue = [id];
10629
+ while (queue.length > 0) {
10630
+ const cur = queue.shift();
10631
+ if (visited.has(cur)) continue;
10632
+ visited.add(cur);
10633
+ comp.push(cur);
10634
+ for (const nb of adj.get(cur) ?? []) {
10635
+ if (!visited.has(nb)) queue.push(nb);
10636
+ }
10637
+ }
10638
+ components.push(comp);
10639
+ }
10640
+ return components;
10641
+ }
10642
+ function layoutComponent(tables, rels, dimMap) {
10643
+ const nodePositions = /* @__PURE__ */ new Map();
10644
+ const edgePoints = /* @__PURE__ */ new Map();
10645
+ if (tables.length === 1) {
10646
+ const dims = dimMap.get(tables[0].id);
10647
+ nodePositions.set(tables[0].id, { x: dims.width / 2, y: dims.height / 2, ...dims });
10648
+ return { nodePositions, edgePoints, width: dims.width, height: dims.height };
10649
+ }
10650
+ const g = new import_dagre3.default.graphlib.Graph({ multigraph: true });
10651
+ g.setGraph({ rankdir: "LR", nodesep: 40, ranksep: 80, edgesep: 20 });
10652
+ g.setDefaultEdgeLabel(() => ({}));
10653
+ for (const table of tables) {
10654
+ const dims = dimMap.get(table.id);
10655
+ g.setNode(table.id, { width: dims.width, height: dims.height });
10656
+ }
10657
+ for (const rel of rels) {
10658
+ g.setEdge(rel.source, rel.target, { label: rel.label ?? "" }, String(rel.lineNumber));
10659
+ }
10660
+ import_dagre3.default.layout(g);
10661
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10662
+ for (const table of tables) {
10663
+ const pos = g.node(table.id);
10664
+ const dims = dimMap.get(table.id);
10665
+ minX = Math.min(minX, pos.x - dims.width / 2);
10666
+ minY = Math.min(minY, pos.y - dims.height / 2);
10667
+ maxX = Math.max(maxX, pos.x + dims.width / 2);
10668
+ maxY = Math.max(maxY, pos.y + dims.height / 2);
10669
+ }
10670
+ for (const rel of rels) {
10671
+ const ed = g.edge(rel.source, rel.target, String(rel.lineNumber));
10672
+ for (const pt of ed?.points ?? []) {
10673
+ minX = Math.min(minX, pt.x);
10674
+ minY = Math.min(minY, pt.y);
10675
+ maxX = Math.max(maxX, pt.x);
10676
+ maxY = Math.max(maxY, pt.y);
10677
+ }
10678
+ if (rel.label && (ed?.points ?? []).length > 0) {
10679
+ const pts = ed.points;
10680
+ const mid = pts[Math.floor(pts.length / 2)];
10681
+ const hw = (rel.label.length * 7 + 8) / 2;
10682
+ minX = Math.min(minX, mid.x - hw);
10683
+ maxX = Math.max(maxX, mid.x + hw);
10684
+ }
10685
+ }
10686
+ for (const table of tables) {
10687
+ const pos = g.node(table.id);
10688
+ const dims = dimMap.get(table.id);
10689
+ nodePositions.set(table.id, {
10690
+ x: pos.x - minX,
10691
+ y: pos.y - minY,
10692
+ ...dims
10693
+ });
10694
+ }
10695
+ for (const rel of rels) {
10696
+ const ed = g.edge(rel.source, rel.target, String(rel.lineNumber));
10697
+ edgePoints.set(
10698
+ rel.lineNumber,
10699
+ (ed?.points ?? []).map((pt) => ({ x: pt.x - minX, y: pt.y - minY }))
10700
+ );
10701
+ }
10702
+ return {
10703
+ nodePositions,
10704
+ edgePoints,
10705
+ width: Math.max(0, maxX - minX),
10706
+ height: Math.max(0, maxY - minY)
10707
+ };
10708
+ }
10709
+ function packComponents(items) {
10710
+ if (items.length === 0) return [];
10711
+ const sorted = [...items].sort((a, b) => {
10712
+ const aConnected = a.compIds.length > 1 ? 1 : 0;
10713
+ const bConnected = b.compIds.length > 1 ? 1 : 0;
10714
+ if (aConnected !== bConnected) return bConnected - aConnected;
10715
+ return b.compLayout.height - a.compLayout.height;
10716
+ });
10717
+ const totalArea = items.reduce(
10718
+ (s, c) => s + (c.compLayout.width || MIN_WIDTH2) * (c.compLayout.height || HEADER_BASE2),
10719
+ 0
10720
+ );
10721
+ const targetW = Math.max(
10722
+ Math.sqrt(totalArea) * 1.5,
10723
+ sorted[0].compLayout.width
10724
+ // at least as wide as the widest component
10725
+ );
10726
+ const placements = [];
10727
+ let curX = 0;
10728
+ let curY = 0;
10729
+ let rowH = 0;
10730
+ for (const item of sorted) {
10731
+ const w = item.compLayout.width || MIN_WIDTH2;
10732
+ const h = item.compLayout.height || HEADER_BASE2;
10733
+ if (curX > 0 && curX + w > targetW) {
10734
+ curY += rowH + COMP_GAP;
10735
+ curX = 0;
10736
+ rowH = 0;
10737
+ }
10738
+ placements.push({ compIds: item.compIds, compLayout: item.compLayout, offsetX: curX, offsetY: curY });
10739
+ curX += w + COMP_GAP;
10740
+ rowH = Math.max(rowH, h);
10741
+ }
10742
+ return placements;
10743
+ }
10500
10744
  function layoutERDiagram(parsed) {
10501
10745
  if (parsed.tables.length === 0) {
10502
10746
  return { nodes: [], edges: [], width: 0, height: 0 };
10503
10747
  }
10504
- const g = new import_dagre3.default.graphlib.Graph();
10505
- g.setGraph({
10506
- rankdir: "TB",
10507
- nodesep: 60,
10508
- ranksep: 80,
10509
- edgesep: 20
10510
- });
10511
- g.setDefaultEdgeLabel(() => ({}));
10512
10748
  const dimMap = /* @__PURE__ */ new Map();
10513
10749
  for (const table of parsed.tables) {
10514
- const dims = computeNodeDimensions2(table);
10515
- dimMap.set(table.id, dims);
10516
- g.setNode(table.id, {
10517
- label: table.name,
10518
- width: dims.width,
10519
- height: dims.height
10520
- });
10750
+ dimMap.set(table.id, computeNodeDimensions2(table));
10521
10751
  }
10522
- for (const rel of parsed.relationships) {
10523
- g.setEdge(rel.source, rel.target, { label: rel.label ?? "" });
10752
+ const compIdSets = findConnectedComponents(
10753
+ parsed.tables.map((t) => t.id),
10754
+ parsed.relationships
10755
+ );
10756
+ const tableById = new Map(parsed.tables.map((t) => [t.id, t]));
10757
+ const componentItems = compIdSets.map((ids) => {
10758
+ const tables = ids.map((id) => tableById.get(id));
10759
+ const rels = parsed.relationships.filter((r) => ids.includes(r.source));
10760
+ return { compIds: ids, compLayout: layoutComponent(tables, rels, dimMap) };
10761
+ });
10762
+ const packed = packComponents(componentItems);
10763
+ const placementByTableId = /* @__PURE__ */ new Map();
10764
+ for (const p of packed) {
10765
+ for (const id of p.compIds) placementByTableId.set(id, p);
10766
+ }
10767
+ const placementByRelLine = /* @__PURE__ */ new Map();
10768
+ for (const p of packed) {
10769
+ for (const lineNum of p.compLayout.edgePoints.keys()) {
10770
+ placementByRelLine.set(lineNum, p);
10771
+ }
10524
10772
  }
10525
- import_dagre3.default.layout(g);
10526
10773
  const layoutNodes = parsed.tables.map((table) => {
10527
- const pos = g.node(table.id);
10528
- const dims = dimMap.get(table.id);
10774
+ const p = placementByTableId.get(table.id);
10775
+ const pos = p.compLayout.nodePositions.get(table.id);
10529
10776
  return {
10530
10777
  ...table,
10531
- x: pos.x,
10532
- y: pos.y,
10533
- width: dims.width,
10534
- height: dims.height,
10535
- headerHeight: dims.headerHeight,
10536
- columnsHeight: dims.columnsHeight
10778
+ x: pos.x + p.offsetX + HALF_MARGIN,
10779
+ y: pos.y + p.offsetY + HALF_MARGIN,
10780
+ width: pos.width,
10781
+ height: pos.height,
10782
+ headerHeight: pos.headerHeight,
10783
+ columnsHeight: pos.columnsHeight
10537
10784
  };
10538
10785
  });
10539
10786
  const layoutEdges = parsed.relationships.map((rel) => {
10540
- const edgeData = g.edge(rel.source, rel.target);
10787
+ const p = placementByRelLine.get(rel.lineNumber);
10788
+ const pts = p?.compLayout.edgePoints.get(rel.lineNumber) ?? [];
10541
10789
  return {
10542
10790
  source: rel.source,
10543
10791
  target: rel.target,
10544
10792
  cardinality: rel.cardinality,
10545
- points: edgeData?.points ?? [],
10793
+ points: pts.map((pt) => ({
10794
+ x: pt.x + (p?.offsetX ?? 0) + HALF_MARGIN,
10795
+ y: pt.y + (p?.offsetY ?? 0) + HALF_MARGIN
10796
+ })),
10546
10797
  label: rel.label,
10547
10798
  lineNumber: rel.lineNumber
10548
10799
  };
10549
10800
  });
10550
- let minX = Infinity;
10551
- let minY = Infinity;
10552
10801
  let maxX = 0;
10553
10802
  let maxY = 0;
10554
10803
  for (const node of layoutNodes) {
10555
- const left = node.x - node.width / 2;
10556
- const right = node.x + node.width / 2;
10557
- const top = node.y - node.height / 2;
10558
- const bottom = node.y + node.height / 2;
10559
- if (left < minX) minX = left;
10560
- if (right > maxX) maxX = right;
10561
- if (top < minY) minY = top;
10562
- if (bottom > maxY) maxY = bottom;
10804
+ maxX = Math.max(maxX, node.x + node.width / 2);
10805
+ maxY = Math.max(maxY, node.y + node.height / 2);
10563
10806
  }
10564
10807
  for (const edge of layoutEdges) {
10565
10808
  for (const pt of edge.points) {
10566
- if (pt.x < minX) minX = pt.x;
10567
- if (pt.x > maxX) maxX = pt.x;
10568
- if (pt.y < minY) minY = pt.y;
10569
- if (pt.y > maxY) maxY = pt.y;
10570
- }
10571
- if (edge.label && edge.points.length > 0) {
10572
- const midPt = edge.points[Math.floor(edge.points.length / 2)];
10573
- const labelHalfW = (edge.label.length * 7 + 8) / 2;
10574
- if (midPt.x + labelHalfW > maxX) maxX = midPt.x + labelHalfW;
10575
- if (midPt.x - labelHalfW < minX) minX = midPt.x - labelHalfW;
10809
+ maxX = Math.max(maxX, pt.x);
10810
+ maxY = Math.max(maxY, pt.y);
10576
10811
  }
10577
10812
  }
10578
- const EDGE_MARGIN2 = 60;
10579
- const HALF_MARGIN = EDGE_MARGIN2 / 2;
10580
- const shiftX = -minX + HALF_MARGIN;
10581
- const shiftY = -minY + HALF_MARGIN;
10582
- for (const node of layoutNodes) {
10583
- node.x += shiftX;
10584
- node.y += shiftY;
10585
- }
10586
- for (const edge of layoutEdges) {
10587
- for (const pt of edge.points) {
10588
- pt.x += shiftX;
10589
- pt.y += shiftY;
10590
- }
10591
- }
10592
- maxX += shiftX;
10593
- maxY += shiftY;
10594
- const totalWidth = maxX + HALF_MARGIN;
10595
- const totalHeight = maxY + HALF_MARGIN;
10596
10813
  return {
10597
10814
  nodes: layoutNodes,
10598
10815
  edges: layoutEdges,
10599
- width: totalWidth,
10600
- height: totalHeight
10816
+ width: maxX + HALF_MARGIN,
10817
+ height: maxY + HALF_MARGIN
10601
10818
  };
10602
10819
  }
10603
- var import_dagre3, MIN_WIDTH2, CHAR_WIDTH4, PADDING_X2, HEADER_BASE2, MEMBER_LINE_HEIGHT3, COMPARTMENT_PADDING_Y3, SEPARATOR_HEIGHT2;
10820
+ var import_dagre3, MIN_WIDTH2, CHAR_WIDTH4, PADDING_X2, HEADER_BASE2, MEMBER_LINE_HEIGHT3, COMPARTMENT_PADDING_Y3, SEPARATOR_HEIGHT2, HALF_MARGIN, COMP_GAP;
10604
10821
  var init_layout4 = __esm({
10605
10822
  "src/er/layout.ts"() {
10606
10823
  "use strict";
@@ -10612,6 +10829,135 @@ var init_layout4 = __esm({
10612
10829
  MEMBER_LINE_HEIGHT3 = 18;
10613
10830
  COMPARTMENT_PADDING_Y3 = 8;
10614
10831
  SEPARATOR_HEIGHT2 = 1;
10832
+ HALF_MARGIN = 30;
10833
+ COMP_GAP = 60;
10834
+ }
10835
+ });
10836
+
10837
+ // src/er/classify.ts
10838
+ function classifyEREntities(tables, relationships) {
10839
+ const result = /* @__PURE__ */ new Map();
10840
+ if (tables.length === 0) return result;
10841
+ const indegreeMap = {};
10842
+ for (const t of tables) indegreeMap[t.id] = 0;
10843
+ for (const rel of relationships) {
10844
+ if (rel.source === rel.target) continue;
10845
+ if (rel.cardinality.from === "1" && rel.cardinality.to !== "1") {
10846
+ indegreeMap[rel.source] = (indegreeMap[rel.source] ?? 0) + 1;
10847
+ }
10848
+ if (rel.cardinality.to === "1" && rel.cardinality.from !== "1") {
10849
+ indegreeMap[rel.target] = (indegreeMap[rel.target] ?? 0) + 1;
10850
+ }
10851
+ }
10852
+ const tableStarNeighbors = /* @__PURE__ */ new Map();
10853
+ for (const rel of relationships) {
10854
+ if (rel.source === rel.target) continue;
10855
+ if (rel.cardinality.from === "*") {
10856
+ if (!tableStarNeighbors.has(rel.source)) tableStarNeighbors.set(rel.source, /* @__PURE__ */ new Set());
10857
+ tableStarNeighbors.get(rel.source).add(rel.target);
10858
+ }
10859
+ if (rel.cardinality.to === "*") {
10860
+ if (!tableStarNeighbors.has(rel.target)) tableStarNeighbors.set(rel.target, /* @__PURE__ */ new Set());
10861
+ tableStarNeighbors.get(rel.target).add(rel.source);
10862
+ }
10863
+ }
10864
+ const mmParticipants = /* @__PURE__ */ new Set();
10865
+ for (const [id, neighbors] of tableStarNeighbors) {
10866
+ if (neighbors.size >= 2) mmParticipants.add(id);
10867
+ }
10868
+ const indegreeValues = Object.values(indegreeMap);
10869
+ const mean = indegreeValues.reduce((a, b) => a + b, 0) / indegreeValues.length;
10870
+ const variance = indegreeValues.reduce((a, b) => a + (b - mean) ** 2, 0) / indegreeValues.length;
10871
+ const stddev = Math.sqrt(variance);
10872
+ const sorted = [...indegreeValues].sort((a, b) => a - b);
10873
+ const median = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
10874
+ for (const table of tables) {
10875
+ const id = table.id;
10876
+ const cols = table.columns;
10877
+ const fkCols = cols.filter((c) => c.constraints.includes("fk"));
10878
+ const pkFkCols = cols.filter(
10879
+ (c) => c.constraints.includes("pk") && c.constraints.includes("fk")
10880
+ );
10881
+ const fkCount = fkCols.length;
10882
+ const fkRatio = cols.length === 0 ? 0 : fkCount / cols.length;
10883
+ const indegree = indegreeMap[id] ?? 0;
10884
+ const nameLower = table.name.toLowerCase();
10885
+ const externalRels = relationships.filter(
10886
+ (r) => (r.source === id || r.target === id) && r.source !== r.target
10887
+ );
10888
+ const hasSelfRef = relationships.some((r) => r.source === id && r.target === id);
10889
+ const externalTargets = /* @__PURE__ */ new Set();
10890
+ for (const rel of externalRels) {
10891
+ externalTargets.add(rel.source === id ? rel.target : rel.source);
10892
+ }
10893
+ if (hasSelfRef && externalRels.length === 0) {
10894
+ result.set(id, "self-referential");
10895
+ continue;
10896
+ }
10897
+ const isInheritancePattern = pkFkCols.length >= 2 && externalTargets.size === 1;
10898
+ const junctionByRatio = fkRatio >= 0.6 && !isInheritancePattern;
10899
+ const junctionByCompositePk = pkFkCols.length >= 2 && externalTargets.size >= 2;
10900
+ const junctionByMm = mmParticipants.has(id);
10901
+ if (junctionByRatio || junctionByCompositePk || junctionByMm) {
10902
+ result.set(id, "junction");
10903
+ continue;
10904
+ }
10905
+ if (fkRatio >= 0.4 && fkRatio < 0.6 && pkFkCols.length < 2 && !mmParticipants.has(id)) {
10906
+ result.set(id, "ambiguous");
10907
+ continue;
10908
+ }
10909
+ const nameMatchesLookup = LOOKUP_NAME_SUFFIXES.some((s) => nameLower.endsWith(s));
10910
+ if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median) {
10911
+ result.set(id, "lookup");
10912
+ continue;
10913
+ }
10914
+ if (tables.length >= 6 && indegree > 0 && indegree > mean + 1.5 * stddev && indegree >= 2 * mean) {
10915
+ result.set(id, "hub");
10916
+ continue;
10917
+ }
10918
+ if (fkCount > 0) {
10919
+ result.set(id, "dependent");
10920
+ continue;
10921
+ }
10922
+ result.set(id, "core");
10923
+ }
10924
+ return result;
10925
+ }
10926
+ var ROLE_COLORS, ROLE_LABELS, ROLE_ORDER, LOOKUP_NAME_SUFFIXES;
10927
+ var init_classify = __esm({
10928
+ "src/er/classify.ts"() {
10929
+ "use strict";
10930
+ ROLE_COLORS = {
10931
+ core: "green",
10932
+ dependent: "blue",
10933
+ junction: "red",
10934
+ ambiguous: "purple",
10935
+ lookup: "yellow",
10936
+ hub: "orange",
10937
+ "self-referential": "teal",
10938
+ unclassified: "gray"
10939
+ };
10940
+ ROLE_LABELS = {
10941
+ core: "Core entity",
10942
+ dependent: "Dependent",
10943
+ junction: "Junction / M:M",
10944
+ ambiguous: "Bridge",
10945
+ lookup: "Lookup / Reference",
10946
+ hub: "Hub",
10947
+ "self-referential": "Self-referential",
10948
+ unclassified: "Unclassified"
10949
+ };
10950
+ ROLE_ORDER = [
10951
+ "core",
10952
+ "dependent",
10953
+ "junction",
10954
+ "ambiguous",
10955
+ "lookup",
10956
+ "hub",
10957
+ "self-referential",
10958
+ "unclassified"
10959
+ ];
10960
+ LOOKUP_NAME_SUFFIXES = ["_type", "_status", "_code", "_category"];
10615
10961
  }
10616
10962
  });
10617
10963
 
@@ -10681,25 +11027,41 @@ function drawCardinality(g, point, prevPoint, cardinality, color, useLabels) {
10681
11027
  g.append("line").attr("x1", bx + px * spread).attr("y1", by + py * spread).attr("x2", bx - px * spread).attr("y2", by - py * spread).attr("stroke", color).attr("stroke-width", sw);
10682
11028
  }
10683
11029
  }
10684
- function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup) {
11030
+ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup, semanticColorsActive) {
10685
11031
  d3Selection5.select(container).selectAll(":not([data-d3-tooltip])").remove();
10686
- const width = exportDims?.width ?? container.clientWidth;
10687
- const height = exportDims?.height ?? container.clientHeight;
10688
- if (width <= 0 || height <= 0) return;
11032
+ const useSemanticColors = parsed.tagGroups.length === 0 && layout.nodes.every((n) => !n.color);
11033
+ const legendReserveH = useSemanticColors ? LEGEND_HEIGHT + DIAGRAM_PADDING5 : 0;
10689
11034
  const titleHeight = parsed.title ? 40 : 0;
10690
11035
  const diagramW = layout.width;
10691
11036
  const diagramH = layout.height;
10692
- const availH = height - titleHeight;
10693
- const scaleX = (width - DIAGRAM_PADDING5 * 2) / diagramW;
10694
- const scaleY = (availH - DIAGRAM_PADDING5 * 2) / diagramH;
10695
- const scale = Math.min(MAX_SCALE4, scaleX, scaleY);
10696
- const scaledW = diagramW * scale;
10697
- const scaledH = diagramH * scale;
10698
- const offsetX = (width - scaledW) / 2;
10699
- const offsetY = titleHeight + DIAGRAM_PADDING5;
10700
- const svg = d3Selection5.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
11037
+ const naturalW = diagramW + DIAGRAM_PADDING5 * 2;
11038
+ const naturalH = diagramH + titleHeight + legendReserveH + DIAGRAM_PADDING5 * 2;
11039
+ let viewW;
11040
+ let viewH;
11041
+ let scale;
11042
+ let offsetX;
11043
+ let offsetY;
11044
+ if (exportDims) {
11045
+ viewW = exportDims.width ?? naturalW;
11046
+ viewH = exportDims.height ?? naturalH;
11047
+ const availH = viewH - titleHeight - legendReserveH;
11048
+ const scaleX = (viewW - DIAGRAM_PADDING5 * 2) / diagramW;
11049
+ const scaleY = (availH - DIAGRAM_PADDING5 * 2) / diagramH;
11050
+ scale = Math.min(MAX_SCALE4, scaleX, scaleY);
11051
+ const scaledW = diagramW * scale;
11052
+ offsetX = (viewW - scaledW) / 2;
11053
+ offsetY = titleHeight + DIAGRAM_PADDING5;
11054
+ } else {
11055
+ viewW = naturalW;
11056
+ viewH = naturalH;
11057
+ scale = 1;
11058
+ offsetX = DIAGRAM_PADDING5;
11059
+ offsetY = titleHeight + DIAGRAM_PADDING5;
11060
+ }
11061
+ if (viewW <= 0 || viewH <= 0) return;
11062
+ const svg = d3Selection5.select(container).append("svg").attr("width", exportDims ? viewW : "100%").attr("height", exportDims ? viewH : "100%").attr("viewBox", `0 0 ${viewW} ${viewH}`).attr("preserveAspectRatio", "xMidYMid meet").style("font-family", FONT_FAMILY);
10701
11063
  if (parsed.title) {
10702
- const titleEl = svg.append("text").attr("class", "chart-title").attr("x", width / 2).attr("y", 30).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", "20px").attr("font-weight", "700").style("cursor", onClickItem && parsed.titleLineNumber ? "pointer" : "default").text(parsed.title);
11064
+ const titleEl = svg.append("text").attr("class", "chart-title").attr("x", viewW / 2).attr("y", 30).attr("text-anchor", "middle").attr("fill", palette.text).attr("font-size", "20px").attr("font-weight", "700").style("cursor", onClickItem && parsed.titleLineNumber ? "pointer" : "default").text(parsed.title);
10703
11065
  if (parsed.titleLineNumber) {
10704
11066
  titleEl.attr("data-line-number", parsed.titleLineNumber);
10705
11067
  if (onClickItem) {
@@ -10713,6 +11075,8 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10713
11075
  }
10714
11076
  const contentG = svg.append("g").attr("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
10715
11077
  const seriesColors2 = getSeriesColors(palette);
11078
+ const semanticRoles = useSemanticColors ? classifyEREntities(parsed.tables, parsed.relationships) : null;
11079
+ const semanticActive = semanticRoles !== null && (semanticColorsActive ?? true);
10716
11080
  const useLabels = parsed.options.notation === "labels";
10717
11081
  for (const edge of layout.edges) {
10718
11082
  if (edge.points.length < 2) continue;
@@ -10752,7 +11116,8 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10752
11116
  for (let ni = 0; ni < layout.nodes.length; ni++) {
10753
11117
  const node = layout.nodes[ni];
10754
11118
  const tagColor = resolveTagColor(node.metadata, parsed.tagGroups, activeTagGroup ?? null);
10755
- const nodeColor2 = node.color ?? tagColor ?? seriesColors2[ni % seriesColors2.length];
11119
+ const semanticColor = semanticActive ? palette.colors[ROLE_COLORS[semanticRoles.get(node.id) ?? "unclassified"]] : semanticRoles ? palette.primary : void 0;
11120
+ const nodeColor2 = node.color ?? tagColor ?? semanticColor ?? seriesColors2[ni % seriesColors2.length];
10756
11121
  const nodeG = contentG.append("g").attr("transform", `translate(${node.x}, ${node.y})`).attr("class", "er-table").attr("data-line-number", String(node.lineNumber)).attr("data-node-id", node.id);
10757
11122
  if (activeTagGroup) {
10758
11123
  const tagKey = activeTagGroup.toLowerCase();
@@ -10761,6 +11126,10 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10761
11126
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
10762
11127
  }
10763
11128
  }
11129
+ if (semanticRoles) {
11130
+ const role = semanticRoles.get(node.id);
11131
+ if (role) nodeG.attr("data-er-role", role);
11132
+ }
10764
11133
  if (onClickItem) {
10765
11134
  nodeG.style("cursor", "pointer").on("click", () => {
10766
11135
  onClickItem(node.lineNumber);
@@ -10801,7 +11170,7 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10801
11170
  legendG.attr("data-legend-active", activeTagGroup.toLowerCase());
10802
11171
  }
10803
11172
  let legendX = DIAGRAM_PADDING5;
10804
- let legendY = height - DIAGRAM_PADDING5;
11173
+ let legendY = viewH - DIAGRAM_PADDING5;
10805
11174
  for (const group of parsed.tagGroups) {
10806
11175
  const groupG = legendG.append("g").attr("data-legend-group", group.name.toLowerCase());
10807
11176
  const labelText = groupG.append("text").attr("x", legendX).attr("y", legendY + LEGEND_PILL_H / 2).attr("dominant-baseline", "central").attr("fill", palette.textMuted).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-family", FONT_FAMILY).text(`${group.name}:`);
@@ -10820,6 +11189,62 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10820
11189
  legendX += LEGEND_GROUP_GAP;
10821
11190
  }
10822
11191
  }
11192
+ if (semanticRoles) {
11193
+ const presentRoles = ROLE_ORDER.filter((role) => {
11194
+ for (const r of semanticRoles.values()) {
11195
+ if (r === role) return true;
11196
+ }
11197
+ return false;
11198
+ });
11199
+ if (presentRoles.length > 0) {
11200
+ const measureLabelW = (text, fontSize) => {
11201
+ const dummy = svg.append("text").attr("font-size", fontSize).attr("font-family", FONT_FAMILY).attr("visibility", "hidden").text(text);
11202
+ const measured = dummy.node()?.getComputedTextLength?.() ?? 0;
11203
+ dummy.remove();
11204
+ return measured > 0 ? measured : text.length * fontSize * 0.6;
11205
+ };
11206
+ const labelWidths = /* @__PURE__ */ new Map();
11207
+ for (const role of presentRoles) {
11208
+ labelWidths.set(role, measureLabelW(ROLE_LABELS[role], LEGEND_ENTRY_FONT_SIZE));
11209
+ }
11210
+ const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
11211
+ const groupName = "Role";
11212
+ const pillWidth = groupName.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
11213
+ const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
11214
+ let totalWidth;
11215
+ let entriesWidth = 0;
11216
+ if (semanticActive) {
11217
+ for (const role of presentRoles) {
11218
+ entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + labelWidths.get(role) + LEGEND_ENTRY_TRAIL;
11219
+ }
11220
+ totalWidth = LEGEND_CAPSULE_PAD * 2 + pillWidth + LEGEND_ENTRY_TRAIL + entriesWidth;
11221
+ } else {
11222
+ totalWidth = pillWidth;
11223
+ }
11224
+ const legendX = (viewW - totalWidth) / 2;
11225
+ const legendY = viewH - DIAGRAM_PADDING5 - LEGEND_HEIGHT;
11226
+ const semanticLegendG = svg.append("g").attr("class", "er-semantic-legend").attr("data-legend-group", "role").attr("transform", `translate(${legendX}, ${legendY})`).style("cursor", "pointer");
11227
+ if (semanticActive) {
11228
+ semanticLegendG.append("rect").attr("width", totalWidth).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
11229
+ semanticLegendG.append("rect").attr("x", LEGEND_CAPSULE_PAD).attr("y", LEGEND_CAPSULE_PAD).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", palette.bg);
11230
+ semanticLegendG.append("rect").attr("x", LEGEND_CAPSULE_PAD).attr("y", LEGEND_CAPSULE_PAD).attr("width", pillWidth).attr("height", pillH).attr("rx", pillH / 2).attr("fill", "none").attr("stroke", mix(palette.textMuted, palette.bg, 50)).attr("stroke-width", 0.75);
11231
+ semanticLegendG.append("text").attr("x", LEGEND_CAPSULE_PAD + pillWidth / 2).attr("y", LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", palette.text).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).text(groupName);
11232
+ let entryX = LEGEND_CAPSULE_PAD + pillWidth + LEGEND_ENTRY_TRAIL;
11233
+ for (const role of presentRoles) {
11234
+ const label = ROLE_LABELS[role];
11235
+ const roleColor = palette.colors[ROLE_COLORS[role]];
11236
+ const entryG = semanticLegendG.append("g").attr("data-legend-entry", role);
11237
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", roleColor);
11238
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
11239
+ entryG.append("text").attr("x", textX).attr("y", LEGEND_HEIGHT / 2 + LEGEND_ENTRY_FONT_SIZE / 2 - 1).attr("font-size", LEGEND_ENTRY_FONT_SIZE).attr("fill", palette.textMuted).attr("font-family", FONT_FAMILY).text(label);
11240
+ entryX = textX + labelWidths.get(role) + LEGEND_ENTRY_TRAIL;
11241
+ }
11242
+ } else {
11243
+ semanticLegendG.append("rect").attr("width", pillWidth).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
11244
+ semanticLegendG.append("text").attr("x", pillWidth / 2).attr("y", LEGEND_HEIGHT / 2 + LEGEND_PILL_FONT_SIZE / 2 - 2).attr("font-size", LEGEND_PILL_FONT_SIZE).attr("font-weight", "500").attr("fill", palette.textMuted).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).text(groupName);
11245
+ }
11246
+ }
11247
+ }
10823
11248
  }
10824
11249
  function renderERDiagramForExport(content, theme, palette) {
10825
11250
  const parsed = parseERDiagram(content, palette);
@@ -10869,6 +11294,7 @@ var init_renderer5 = __esm({
10869
11294
  init_legend_constants();
10870
11295
  init_parser3();
10871
11296
  init_layout4();
11297
+ init_classify();
10872
11298
  DIAGRAM_PADDING5 = 20;
10873
11299
  MAX_SCALE4 = 3;
10874
11300
  TABLE_FONT_SIZE = 13;
@@ -10996,9 +11422,10 @@ function layoutInitiativeStatus(parsed, collapseResult) {
10996
11422
  const dagreEdge = g.edge(edge.source, edge.target, `e${i}`);
10997
11423
  const dagrePoints = dagreEdge?.points ?? [];
10998
11424
  const hasIntermediateRank = allNodeX.some((x) => x > src.x + 20 && x < tgt.x - 20);
10999
- const step = Math.min((enterX - exitX) * 0.15, 20);
11425
+ const step = Math.max(0, Math.min((enterX - exitX) * 0.15, 20));
11000
11426
  const isBackEdge = tgt.x < src.x - 5;
11001
- const isYDisplaced = !isBackEdge && Math.abs(tgt.y - src.y) > NODESEP;
11427
+ const isTopExit = !isBackEdge && tgt.x > src.x && !hasIntermediateRank && tgt.y < src.y - NODESEP;
11428
+ const isBottomExit = !isBackEdge && tgt.x > src.x && !hasIntermediateRank && tgt.y > src.y + NODESEP;
11002
11429
  let points;
11003
11430
  if (isBackEdge) {
11004
11431
  const routeAbove = Math.min(src.y, tgt.y) > avgNodeY;
@@ -11008,31 +11435,44 @@ function layoutInitiativeStatus(parsed, collapseResult) {
11008
11435
  const spreadDir = avgNodeX < rawMidX ? 1 : -1;
11009
11436
  const unclamped = Math.abs(src.x - tgt.x) < NODE_WIDTH ? rawMidX + spreadDir * BACK_EDGE_MIN_SPREAD : rawMidX;
11010
11437
  const midX = Math.min(src.x, Math.max(tgt.x, unclamped));
11438
+ const srcDepart = Math.max(midX + 1, src.x - TOP_EXIT_STEP);
11439
+ const tgtApproach = Math.min(midX - 1, tgt.x + TOP_EXIT_STEP);
11011
11440
  if (routeAbove) {
11012
11441
  const arcY = Math.min(src.y - srcHalfH, tgt.y - tgtHalfH) - BACK_EDGE_MARGIN;
11013
11442
  points = [
11014
11443
  { x: src.x, y: src.y - srcHalfH },
11444
+ { x: srcDepart, y: src.y - srcHalfH - TOP_EXIT_STEP },
11015
11445
  { x: midX, y: arcY },
11446
+ { x: tgtApproach, y: tgt.y - tgtHalfH - TOP_EXIT_STEP },
11016
11447
  { x: tgt.x, y: tgt.y - tgtHalfH }
11017
11448
  ];
11018
11449
  } else {
11019
11450
  const arcY = Math.max(src.y + srcHalfH, tgt.y + tgtHalfH) + BACK_EDGE_MARGIN;
11020
11451
  points = [
11021
11452
  { x: src.x, y: src.y + srcHalfH },
11453
+ { x: srcDepart, y: src.y + srcHalfH + TOP_EXIT_STEP },
11022
11454
  { x: midX, y: arcY },
11455
+ { x: tgtApproach, y: tgt.y + tgtHalfH + TOP_EXIT_STEP },
11023
11456
  { x: tgt.x, y: tgt.y + tgtHalfH }
11024
11457
  ];
11025
11458
  }
11026
- } else if (isYDisplaced) {
11027
- const exitY = tgt.y > src.y + NODESEP ? src.y + src.height / 2 : src.y - src.height / 2;
11028
- const spreadExitX = src.x + yOffset;
11029
- const spreadEntryY = tgt.y + yOffset;
11030
- const midX = (spreadExitX + enterX) / 2;
11031
- const midY = (exitY + spreadEntryY) / 2;
11459
+ } else if (isTopExit) {
11460
+ const exitY = src.y - src.height / 2;
11461
+ const p1x = Math.min(Math.max(src.x, src.x + yOffset + TOP_EXIT_STEP), (src.x + enterX) / 2 - 1);
11032
11462
  points = [
11033
- { x: spreadExitX, y: exitY },
11034
- { x: midX, y: midY },
11035
- { x: enterX, y: spreadEntryY }
11463
+ { x: src.x, y: exitY },
11464
+ { x: p1x, y: exitY - TOP_EXIT_STEP },
11465
+ { x: enterX - step, y: tgt.y + yOffset },
11466
+ { x: enterX, y: tgt.y }
11467
+ ];
11468
+ } else if (isBottomExit) {
11469
+ const exitY = src.y + src.height / 2;
11470
+ const p1x = Math.min(Math.max(src.x, src.x + yOffset + TOP_EXIT_STEP), (src.x + enterX) / 2 - 1);
11471
+ points = [
11472
+ { x: src.x, y: exitY },
11473
+ { x: p1x, y: exitY + TOP_EXIT_STEP },
11474
+ { x: enterX - step, y: tgt.y + yOffset },
11475
+ { x: enterX, y: tgt.y }
11036
11476
  ];
11037
11477
  } else if (tgt.x > src.x && !hasIntermediateRank) {
11038
11478
  points = [
@@ -11129,7 +11569,7 @@ function layoutInitiativeStatus(parsed, collapseResult) {
11129
11569
  totalHeight += 40;
11130
11570
  return { nodes: layoutNodes, edges: layoutEdges, groups: layoutGroups, width: totalWidth, height: totalHeight };
11131
11571
  }
11132
- var import_dagre4, STATUS_PRIORITY, PHI, NODE_HEIGHT, NODE_WIDTH, GROUP_PADDING, NODESEP, RANKSEP, PARALLEL_SPACING, PARALLEL_EDGE_MARGIN, MAX_PARALLEL_EDGES, BACK_EDGE_MARGIN, BACK_EDGE_MIN_SPREAD, CHAR_WIDTH_RATIO, NODE_FONT_SIZE, NODE_TEXT_PADDING;
11572
+ var import_dagre4, STATUS_PRIORITY, PHI, NODE_HEIGHT, NODE_WIDTH, GROUP_PADDING, NODESEP, RANKSEP, PARALLEL_SPACING, PARALLEL_EDGE_MARGIN, MAX_PARALLEL_EDGES, BACK_EDGE_MARGIN, BACK_EDGE_MIN_SPREAD, TOP_EXIT_STEP, CHAR_WIDTH_RATIO, NODE_FONT_SIZE, NODE_TEXT_PADDING;
11133
11573
  var init_layout5 = __esm({
11134
11574
  "src/initiative-status/layout.ts"() {
11135
11575
  "use strict";
@@ -11146,6 +11586,7 @@ var init_layout5 = __esm({
11146
11586
  MAX_PARALLEL_EDGES = 5;
11147
11587
  BACK_EDGE_MARGIN = 40;
11148
11588
  BACK_EDGE_MIN_SPREAD = Math.round(NODE_WIDTH * 0.75);
11589
+ TOP_EXIT_STEP = 10;
11149
11590
  CHAR_WIDTH_RATIO = 0.6;
11150
11591
  NODE_FONT_SIZE = 13;
11151
11592
  NODE_TEXT_PADDING = 12;
@@ -11392,7 +11833,6 @@ function renderInitiativeStatus(container, parsed, layout, palette, isDark, onCl
11392
11833
  const scaleY = (availH - DIAGRAM_PADDING6 * 2) / diagramH;
11393
11834
  const scale = Math.min(MAX_SCALE5, scaleX, scaleY);
11394
11835
  const scaledW = diagramW * scale;
11395
- const scaledH = diagramH * scale;
11396
11836
  const offsetX = (width - scaledW) / 2;
11397
11837
  const offsetY = titleHeight + DIAGRAM_PADDING6;
11398
11838
  const svg = d3Selection6.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -11575,13 +12015,7 @@ function renderInitiativeStatusForExport(content, theme, palette) {
11575
12015
  const titleOffset = parsed.title ? 40 : 0;
11576
12016
  const exportWidth = layout.width + DIAGRAM_PADDING6 * 2;
11577
12017
  const exportHeight = layout.height + DIAGRAM_PADDING6 * 2 + titleOffset;
11578
- const container = document.createElement("div");
11579
- container.style.width = `${exportWidth}px`;
11580
- container.style.height = `${exportHeight}px`;
11581
- container.style.position = "absolute";
11582
- container.style.left = "-9999px";
11583
- document.body.appendChild(container);
11584
- try {
12018
+ return runInExportContainer(exportWidth, exportHeight, (container) => {
11585
12019
  renderInitiativeStatus(
11586
12020
  container,
11587
12021
  parsed,
@@ -11591,17 +12025,8 @@ function renderInitiativeStatusForExport(content, theme, palette) {
11591
12025
  void 0,
11592
12026
  { width: exportWidth, height: exportHeight }
11593
12027
  );
11594
- const svgEl = container.querySelector("svg");
11595
- if (!svgEl) return "";
11596
- if (theme === "transparent") {
11597
- svgEl.style.background = "none";
11598
- }
11599
- svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
11600
- svgEl.style.fontFamily = FONT_FAMILY;
11601
- return svgEl.outerHTML;
11602
- } finally {
11603
- document.body.removeChild(container);
11604
- }
12028
+ return extractExportSvg(container, theme);
12029
+ });
11605
12030
  }
11606
12031
  var d3Selection6, d3Shape4, DIAGRAM_PADDING6, MAX_SCALE5, NODE_FONT_SIZE2, MIN_NODE_FONT_SIZE, EDGE_LABEL_FONT_SIZE4, EDGE_STROKE_WIDTH5, NODE_STROKE_WIDTH5, NODE_RX, ARROWHEAD_W2, ARROWHEAD_H2, CHAR_WIDTH_RATIO2, NODE_TEXT_PADDING2, SERVICE_RX, GROUP_EXTRA_PADDING, GROUP_LABEL_FONT_SIZE, COLLAPSE_BAR_HEIGHT3, lineGenerator4;
11607
12032
  var init_renderer6 = __esm({
@@ -11610,6 +12035,7 @@ var init_renderer6 = __esm({
11610
12035
  d3Selection6 = __toESM(require("d3-selection"), 1);
11611
12036
  d3Shape4 = __toESM(require("d3-shape"), 1);
11612
12037
  init_fonts();
12038
+ init_export_container();
11613
12039
  init_color_utils();
11614
12040
  init_parser7();
11615
12041
  init_layout5();
@@ -11644,7 +12070,7 @@ __export(layout_exports6, {
11644
12070
  layoutC4Deployment: () => layoutC4Deployment,
11645
12071
  rollUpContextRelationships: () => rollUpContextRelationships
11646
12072
  });
11647
- function computeEdgePenalty(edgeList, nodePositions, degrees) {
12073
+ function computeEdgePenalty(edgeList, nodePositions, degrees, nodeGeometry) {
11648
12074
  let penalty = 0;
11649
12075
  for (const edge of edgeList) {
11650
12076
  const sx = nodePositions.get(edge.source);
@@ -11654,6 +12080,32 @@ function computeEdgePenalty(edgeList, nodePositions, degrees) {
11654
12080
  const weight = Math.min(degrees.get(edge.source) ?? 1, degrees.get(edge.target) ?? 1);
11655
12081
  penalty += dist * weight;
11656
12082
  }
12083
+ if (nodeGeometry) {
12084
+ for (const edge of edgeList) {
12085
+ const geomA = nodeGeometry.get(edge.source);
12086
+ const geomB = nodeGeometry.get(edge.target);
12087
+ if (!geomA || !geomB) continue;
12088
+ const ax = nodePositions.get(edge.source) ?? 0;
12089
+ const bx = nodePositions.get(edge.target) ?? 0;
12090
+ const ay = geomA.y;
12091
+ const by = geomB.y;
12092
+ if (ay === by) continue;
12093
+ const edgeMinX = Math.min(ax, bx);
12094
+ const edgeMaxX = Math.max(ax, bx);
12095
+ const edgeMinY = Math.min(ay, by);
12096
+ const edgeMaxY = Math.max(ay, by);
12097
+ for (const [name, geomC] of nodeGeometry) {
12098
+ if (name === edge.source || name === edge.target) continue;
12099
+ const cx = nodePositions.get(name) ?? 0;
12100
+ const cy = geomC.y;
12101
+ const hw = geomC.width / 2;
12102
+ const hh = geomC.height / 2;
12103
+ if (cx + hw > edgeMinX && cx - hw < edgeMaxX && cy + hh > edgeMinY && cy - hh < edgeMaxY) {
12104
+ penalty += EDGE_NODE_COLLISION_WEIGHT;
12105
+ }
12106
+ }
12107
+ }
12108
+ }
11657
12109
  return penalty;
11658
12110
  }
11659
12111
  function reduceCrossings(g, edgeList, nodeGroupMap) {
@@ -11663,6 +12115,11 @@ function reduceCrossings(g, edgeList, nodeGroupMap) {
11663
12115
  degrees.set(edge.source, (degrees.get(edge.source) ?? 0) + 1);
11664
12116
  degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + 1);
11665
12117
  }
12118
+ const nodeGeometry = /* @__PURE__ */ new Map();
12119
+ for (const name of g.nodes()) {
12120
+ const pos = g.node(name);
12121
+ if (pos) nodeGeometry.set(name, { y: pos.y, width: pos.width, height: pos.height });
12122
+ }
11666
12123
  const rankMap = /* @__PURE__ */ new Map();
11667
12124
  for (const name of g.nodes()) {
11668
12125
  const pos = g.node(name);
@@ -11705,7 +12162,7 @@ function reduceCrossings(g, edgeList, nodeGroupMap) {
11705
12162
  const pos = g.node(name);
11706
12163
  if (pos) basePositions.set(name, pos.x);
11707
12164
  }
11708
- const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees);
12165
+ const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees, nodeGeometry);
11709
12166
  let bestPerm = [...partition];
11710
12167
  let bestPenalty = currentPenalty;
11711
12168
  if (partition.length <= 8) {
@@ -11715,7 +12172,7 @@ function reduceCrossings(g, edgeList, nodeGroupMap) {
11715
12172
  for (let i = 0; i < perm.length; i++) {
11716
12173
  testPositions.set(perm[i], xSlots[i]);
11717
12174
  }
11718
- const penalty = computeEdgePenalty(edgeList, testPositions, degrees);
12175
+ const penalty = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
11719
12176
  if (penalty < bestPenalty) {
11720
12177
  bestPenalty = penalty;
11721
12178
  bestPerm = [...perm];
@@ -11733,13 +12190,13 @@ function reduceCrossings(g, edgeList, nodeGroupMap) {
11733
12190
  for (let k = 0; k < workingOrder.length; k++) {
11734
12191
  testPositions.set(workingOrder[k], xSlots[k]);
11735
12192
  }
11736
- const before = computeEdgePenalty(edgeList, testPositions, degrees);
12193
+ const before = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
11737
12194
  [workingOrder[i], workingOrder[i + 1]] = [workingOrder[i + 1], workingOrder[i]];
11738
12195
  const testPositions2 = new Map(basePositions);
11739
12196
  for (let k = 0; k < workingOrder.length; k++) {
11740
12197
  testPositions2.set(workingOrder[k], xSlots[k]);
11741
12198
  }
11742
- const after = computeEdgePenalty(edgeList, testPositions2, degrees);
12199
+ const after = computeEdgePenalty(edgeList, testPositions2, degrees, nodeGeometry);
11743
12200
  if (after < before) {
11744
12201
  improved = true;
11745
12202
  if (after < bestPenalty) {
@@ -11846,8 +12303,6 @@ function collectAllRelationships(elements, ownerMap) {
11846
12303
  function rollUpContextRelationships(parsed) {
11847
12304
  const ownerMap = buildOwnershipMap(parsed.elements);
11848
12305
  const allRels = collectAllRelationships(parsed.elements, ownerMap);
11849
- for (const rel of parsed.relationships) {
11850
- }
11851
12306
  const topLevelNames = new Set(parsed.elements.map((e) => e.name));
11852
12307
  const explicitKeys = /* @__PURE__ */ new Set();
11853
12308
  const explicit = [];
@@ -13059,7 +13514,7 @@ function layoutC4Deployment(parsed, activeTagGroup) {
13059
13514
  }
13060
13515
  return { nodes, edges, legend: legendGroups, groupBoundaries, width: totalWidth, height: totalHeight };
13061
13516
  }
13062
- var import_dagre5, CHAR_WIDTH5, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, DESC_LINE_HEIGHT, DESC_CHAR_WIDTH, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT5, META_CHAR_WIDTH, MARGIN3, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, LEGEND_HEIGHT4, LEGEND_PILL_FONT_SIZE2, LEGEND_PILL_FONT_W4, LEGEND_PILL_PAD4, LEGEND_DOT_R4, LEGEND_ENTRY_FONT_SIZE2, LEGEND_ENTRY_FONT_W4, LEGEND_ENTRY_DOT_GAP4, LEGEND_ENTRY_TRAIL4, LEGEND_CAPSULE_PAD4, META_EXCLUDE_KEYS;
13517
+ var import_dagre5, CHAR_WIDTH5, MIN_NODE_WIDTH, MAX_NODE_WIDTH, TYPE_LABEL_HEIGHT, DIVIDER_GAP, NAME_HEIGHT, DESC_LINE_HEIGHT, DESC_CHAR_WIDTH, CARD_V_PAD3, CARD_H_PAD3, META_LINE_HEIGHT5, META_CHAR_WIDTH, MARGIN3, BOUNDARY_PAD, GROUP_BOUNDARY_PAD, LEGEND_HEIGHT4, LEGEND_PILL_FONT_SIZE2, LEGEND_PILL_FONT_W4, LEGEND_PILL_PAD4, LEGEND_DOT_R4, LEGEND_ENTRY_FONT_SIZE2, LEGEND_ENTRY_FONT_W4, LEGEND_ENTRY_DOT_GAP4, LEGEND_ENTRY_TRAIL4, LEGEND_CAPSULE_PAD4, EDGE_NODE_COLLISION_WEIGHT, META_EXCLUDE_KEYS;
13063
13518
  var init_layout6 = __esm({
13064
13519
  "src/c4/layout.ts"() {
13065
13520
  "use strict";
@@ -13089,6 +13544,7 @@ var init_layout6 = __esm({
13089
13544
  LEGEND_ENTRY_DOT_GAP4 = 4;
13090
13545
  LEGEND_ENTRY_TRAIL4 = 8;
13091
13546
  LEGEND_CAPSULE_PAD4 = 4;
13547
+ EDGE_NODE_COLLISION_WEIGHT = 5e3;
13092
13548
  META_EXCLUDE_KEYS = /* @__PURE__ */ new Set(["description", "tech", "technology", "is a"]);
13093
13549
  }
13094
13550
  });
@@ -13176,7 +13632,6 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
13176
13632
  const scaleY = (availH - DIAGRAM_PADDING7 * 2) / diagramH;
13177
13633
  const scale = Math.min(MAX_SCALE6, scaleX, scaleY);
13178
13634
  const scaledW = diagramW * scale;
13179
- const scaledH = diagramH * scale;
13180
13635
  const offsetX = (width - scaledW) / 2;
13181
13636
  const offsetY = titleHeight + DIAGRAM_PADDING7;
13182
13637
  const svg = d3Selection7.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -13666,7 +14121,6 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
13666
14121
  const scaleY = (availH - DIAGRAM_PADDING7 * 2) / diagramH;
13667
14122
  const scale = Math.min(MAX_SCALE6, scaleX, scaleY);
13668
14123
  const scaledW = diagramW * scale;
13669
- const scaledH = diagramH * scale;
13670
14124
  const offsetX = (width - scaledW) / 2;
13671
14125
  const offsetY = titleHeight + DIAGRAM_PADDING7;
13672
14126
  const svg = d3Selection7.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -14279,7 +14733,6 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
14279
14733
  const scaleY = (availH - DIAGRAM_PADDING8 * 2) / diagramH;
14280
14734
  const scale = Math.min(MAX_SCALE7, scaleX, scaleY);
14281
14735
  const scaledW = diagramW * scale;
14282
- const scaledH = diagramH * scale;
14283
14736
  const offsetX = (width - scaledW) / 2;
14284
14737
  const offsetY = titleHeight + DIAGRAM_PADDING8;
14285
14738
  const svg = d3Selection8.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -15273,6 +15726,7 @@ var init_compute = __esm({
15273
15726
  // src/infra/layout.ts
15274
15727
  var layout_exports8 = {};
15275
15728
  __export(layout_exports8, {
15729
+ fixEdgeWaypoints: () => fixEdgeWaypoints,
15276
15730
  layoutInfra: () => layoutInfra,
15277
15731
  separateGroups: () => separateGroups
15278
15732
  });
@@ -15456,6 +15910,8 @@ function formatUptime(fraction) {
15456
15910
  return `${pct.toFixed(1)}%`;
15457
15911
  }
15458
15912
  function separateGroups(groups, nodes, isLR, maxIterations = 20) {
15913
+ const groupDeltas = /* @__PURE__ */ new Map();
15914
+ let converged = false;
15459
15915
  for (let iter = 0; iter < maxIterations; iter++) {
15460
15916
  let anyOverlap = false;
15461
15917
  for (let i = 0; i < groups.length; i++) {
@@ -15473,6 +15929,9 @@ function separateGroups(groups, nodes, isLR, maxIterations = 20) {
15473
15929
  const groupToShift = aCenter <= bCenter ? gb : ga;
15474
15930
  if (isLR) groupToShift.y += shift;
15475
15931
  else groupToShift.x += shift;
15932
+ const prev = groupDeltas.get(groupToShift.id) ?? { dx: 0, dy: 0 };
15933
+ if (isLR) groupDeltas.set(groupToShift.id, { dx: prev.dx, dy: prev.dy + shift });
15934
+ else groupDeltas.set(groupToShift.id, { dx: prev.dx + shift, dy: prev.dy });
15476
15935
  for (const node of nodes) {
15477
15936
  if (node.groupId === groupToShift.id) {
15478
15937
  if (isLR) node.y += shift;
@@ -15481,19 +15940,48 @@ function separateGroups(groups, nodes, isLR, maxIterations = 20) {
15481
15940
  }
15482
15941
  }
15483
15942
  }
15484
- if (!anyOverlap) break;
15943
+ if (!anyOverlap) {
15944
+ converged = true;
15945
+ break;
15946
+ }
15947
+ }
15948
+ if (!converged && maxIterations > 0) {
15949
+ console.warn(`separateGroups: hit maxIterations (${maxIterations}) without fully resolving all group overlaps`);
15950
+ }
15951
+ return groupDeltas;
15952
+ }
15953
+ function fixEdgeWaypoints(edges, nodes, groupDeltas) {
15954
+ if (groupDeltas.size === 0) return;
15955
+ const nodeToGroup = /* @__PURE__ */ new Map();
15956
+ for (const node of nodes) nodeToGroup.set(node.id, node.groupId);
15957
+ for (const edge of edges) {
15958
+ const srcGroup = nodeToGroup.get(edge.sourceId) ?? null;
15959
+ const tgtGroup = nodeToGroup.get(edge.targetId) ?? null;
15960
+ const srcDelta = srcGroup ? groupDeltas.get(srcGroup) : void 0;
15961
+ const tgtDelta = tgtGroup ? groupDeltas.get(tgtGroup) : void 0;
15962
+ if (!srcDelta && !tgtDelta) continue;
15963
+ if (srcDelta && tgtDelta && srcGroup !== tgtGroup) {
15964
+ edge.points = [];
15965
+ continue;
15966
+ }
15967
+ const delta = srcDelta ?? tgtDelta;
15968
+ for (const pt of edge.points) {
15969
+ pt.x += delta.dx;
15970
+ pt.y += delta.dy;
15971
+ }
15485
15972
  }
15486
15973
  }
15487
15974
  function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
15488
15975
  if (computed.nodes.length === 0) {
15489
- return { nodes: [], edges: [], groups: [], options: {}, width: 0, height: 0 };
15976
+ return { nodes: [], edges: [], groups: [], options: {}, direction: computed.direction, width: 0, height: 0 };
15490
15977
  }
15978
+ const isLR = computed.direction !== "TB";
15491
15979
  const g = new import_dagre7.default.graphlib.Graph();
15492
15980
  g.setGraph({
15493
15981
  rankdir: computed.direction === "TB" ? "TB" : "LR",
15494
- nodesep: 50,
15495
- ranksep: 100,
15496
- edgesep: 20
15982
+ nodesep: isLR ? 70 : 60,
15983
+ ranksep: isLR ? 150 : 120,
15984
+ edgesep: 30
15497
15985
  });
15498
15986
  g.setDefaultEdgeLabel(() => ({}));
15499
15987
  const groupedNodeIds = /* @__PURE__ */ new Set();
@@ -15501,7 +15989,6 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
15501
15989
  if (node.groupId) groupedNodeIds.add(node.id);
15502
15990
  }
15503
15991
  const GROUP_INFLATE = GROUP_PADDING3 * 2 + GROUP_HEADER_HEIGHT;
15504
- const isLR = computed.direction !== "TB";
15505
15992
  const widthMap = /* @__PURE__ */ new Map();
15506
15993
  const heightMap = /* @__PURE__ */ new Map();
15507
15994
  for (const node of computed.nodes) {
@@ -15636,7 +16123,8 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
15636
16123
  lineNumber: group.lineNumber
15637
16124
  };
15638
16125
  });
15639
- separateGroups(layoutGroups, layoutNodes, isLR);
16126
+ const groupDeltas = separateGroups(layoutGroups, layoutNodes, isLR);
16127
+ fixEdgeWaypoints(layoutEdges, layoutNodes, groupDeltas);
15640
16128
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
15641
16129
  for (const node of layoutNodes) {
15642
16130
  const left = node.x - node.width / 2;
@@ -15694,6 +16182,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
15694
16182
  edges: layoutEdges,
15695
16183
  groups: layoutGroups,
15696
16184
  options: computed.options,
16185
+ direction: computed.direction,
15697
16186
  width: totalWidth,
15698
16187
  height: totalHeight
15699
16188
  };
@@ -15736,23 +16225,23 @@ var init_layout8 = __esm({
15736
16225
  ]);
15737
16226
  DISPLAY_NAMES = {
15738
16227
  "cache-hit": "cache hit",
15739
- "firewall-block": "fw block",
16228
+ "firewall-block": "firewall block",
15740
16229
  "ratelimit-rps": "rate limit RPS",
15741
16230
  "latency-ms": "latency",
15742
16231
  "uptime": "uptime",
15743
16232
  "instances": "instances",
15744
16233
  "max-rps": "max RPS",
15745
- "cb-error-threshold": "CB error",
15746
- "cb-latency-threshold-ms": "CB latency",
16234
+ "cb-error-threshold": "CB error threshold",
16235
+ "cb-latency-threshold-ms": "CB latency threshold",
15747
16236
  "concurrency": "concurrency",
15748
16237
  "duration-ms": "duration",
15749
16238
  "cold-start-ms": "cold start",
15750
16239
  "buffer": "buffer",
15751
- "drain-rate": "drain",
16240
+ "drain-rate": "drain rate",
15752
16241
  "retention-hours": "retention",
15753
16242
  "partitions": "partitions"
15754
16243
  };
15755
- GROUP_GAP = 24;
16244
+ GROUP_GAP = GROUP_PADDING3 * 2 + GROUP_HEADER_HEIGHT;
15756
16245
  }
15757
16246
  });
15758
16247
 
@@ -15825,6 +16314,236 @@ function resolveNodeSlo(node, diagramOptions) {
15825
16314
  if (availThreshold == null && latencyP90 == null) return null;
15826
16315
  return { availThreshold, latencyP90, warningMargin };
15827
16316
  }
16317
+ function buildPathD(pts, direction) {
16318
+ const gen = d3Shape7.line().x((d) => d.x).y((d) => d.y);
16319
+ if (pts.length <= 2) {
16320
+ gen.curve(direction === "TB" ? d3Shape7.curveBumpY : d3Shape7.curveBumpX);
16321
+ } else {
16322
+ gen.curve(d3Shape7.curveCatmullRom.alpha(0.5));
16323
+ }
16324
+ return gen(pts) ?? "";
16325
+ }
16326
+ function computePortPts(edges, nodeMap, direction) {
16327
+ const srcPts = /* @__PURE__ */ new Map();
16328
+ const tgtPts = /* @__PURE__ */ new Map();
16329
+ const PAD = 0.1;
16330
+ const activeEdges = edges.filter((e) => e.points.length > 0);
16331
+ const bySource = /* @__PURE__ */ new Map();
16332
+ for (const e of activeEdges) {
16333
+ if (!bySource.has(e.sourceId)) bySource.set(e.sourceId, []);
16334
+ bySource.get(e.sourceId).push(e);
16335
+ }
16336
+ for (const [sourceId, es] of bySource) {
16337
+ if (es.length < 2) continue;
16338
+ const source = nodeMap.get(sourceId);
16339
+ if (!source) continue;
16340
+ const sorted = es.map((e) => ({ e, t: nodeMap.get(e.targetId) })).filter((x) => x.t != null).sort((a, b) => direction === "LR" ? a.t.y - b.t.y : a.t.x - b.t.x);
16341
+ const n = sorted.length;
16342
+ for (let i = 0; i < n; i++) {
16343
+ const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
16344
+ const { e, t } = sorted[i];
16345
+ const isBackward = direction === "LR" ? t.x < source.x : t.y < source.y;
16346
+ if (direction === "LR") {
16347
+ srcPts.set(`${e.sourceId}:${e.targetId}`, {
16348
+ x: isBackward ? source.x - source.width / 2 : source.x + source.width / 2,
16349
+ y: source.y - source.height / 2 + frac * source.height
16350
+ });
16351
+ } else {
16352
+ srcPts.set(`${e.sourceId}:${e.targetId}`, {
16353
+ x: source.x - source.width / 2 + frac * source.width,
16354
+ y: isBackward ? source.y - source.height / 2 : source.y + source.height / 2
16355
+ });
16356
+ }
16357
+ }
16358
+ }
16359
+ const byTarget = /* @__PURE__ */ new Map();
16360
+ for (const e of activeEdges) {
16361
+ if (!byTarget.has(e.targetId)) byTarget.set(e.targetId, []);
16362
+ byTarget.get(e.targetId).push(e);
16363
+ }
16364
+ for (const [targetId, es] of byTarget) {
16365
+ if (es.length < 2) continue;
16366
+ const target = nodeMap.get(targetId);
16367
+ if (!target) continue;
16368
+ const sorted = es.map((e) => ({ e, s: nodeMap.get(e.sourceId) })).filter((x) => x.s != null).sort((a, b) => direction === "LR" ? a.s.y - b.s.y : a.s.x - b.s.x);
16369
+ const n = sorted.length;
16370
+ for (let i = 0; i < n; i++) {
16371
+ const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
16372
+ const { e, s } = sorted[i];
16373
+ const isBackward = direction === "LR" ? target.x < s.x : target.y < s.y;
16374
+ if (direction === "LR") {
16375
+ tgtPts.set(`${e.sourceId}:${e.targetId}`, {
16376
+ x: isBackward ? target.x + target.width / 2 : target.x - target.width / 2,
16377
+ y: target.y - target.height / 2 + frac * target.height
16378
+ });
16379
+ } else {
16380
+ tgtPts.set(`${e.sourceId}:${e.targetId}`, {
16381
+ x: target.x - target.width / 2 + frac * target.width,
16382
+ y: isBackward ? target.y + target.height / 2 : target.y - target.height / 2
16383
+ });
16384
+ }
16385
+ }
16386
+ }
16387
+ return { srcPts, tgtPts };
16388
+ }
16389
+ function findRoutingLane(blocking, targetY, margin) {
16390
+ const MERGE_SLOP = 4;
16391
+ const sorted = [...blocking].sort((a, b) => a.y + a.height / 2 - (b.y + b.height / 2));
16392
+ const merged = [];
16393
+ for (const r of sorted) {
16394
+ const lo = r.y - MERGE_SLOP;
16395
+ const hi = r.y + r.height + MERGE_SLOP;
16396
+ if (merged.length && lo <= merged[merged.length - 1][1]) {
16397
+ merged[merged.length - 1][1] = Math.max(merged[merged.length - 1][1], hi);
16398
+ } else {
16399
+ merged.push([lo, hi]);
16400
+ }
16401
+ }
16402
+ if (merged.length === 0) return targetY;
16403
+ const MIN_GAP = 10;
16404
+ const candidates = [
16405
+ merged[0][0] - margin,
16406
+ // above all blocking rects
16407
+ merged[merged.length - 1][1] + margin
16408
+ // below all blocking rects
16409
+ ];
16410
+ for (let i = 0; i < merged.length - 1; i++) {
16411
+ const gapLo = merged[i][1];
16412
+ const gapHi = merged[i + 1][0];
16413
+ if (gapHi - gapLo >= MIN_GAP) {
16414
+ candidates.push((gapLo + gapHi) / 2);
16415
+ }
16416
+ }
16417
+ return candidates.reduce(
16418
+ (best, c) => Math.abs(c - targetY) < Math.abs(best - targetY) ? c : best,
16419
+ candidates[0]
16420
+ );
16421
+ }
16422
+ function segmentIntersectsRect(p1, p2, rect) {
16423
+ const { x: rx, y: ry, width: rw, height: rh } = rect;
16424
+ const rr = rx + rw;
16425
+ const rb = ry + rh;
16426
+ const inRect = (p) => p.x >= rx && p.x <= rr && p.y >= ry && p.y <= rb;
16427
+ if (inRect(p1) || inRect(p2)) return true;
16428
+ if (Math.max(p1.x, p2.x) < rx || Math.min(p1.x, p2.x) > rr) return false;
16429
+ if (Math.max(p1.y, p2.y) < ry || Math.min(p1.y, p2.y) > rb) return false;
16430
+ const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
16431
+ const crosses = (a, b) => {
16432
+ const d1 = cross(a, b, p1);
16433
+ const d2 = cross(a, b, p2);
16434
+ const d3 = cross(p1, p2, a);
16435
+ const d4 = cross(p1, p2, b);
16436
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
16437
+ };
16438
+ const tl = { x: rx, y: ry };
16439
+ const tr = { x: rr, y: ry };
16440
+ const br = { x: rr, y: rb };
16441
+ const bl = { x: rx, y: rb };
16442
+ return crosses(tl, tr) || crosses(tr, br) || crosses(br, bl) || crosses(bl, tl);
16443
+ }
16444
+ function curveIntersectsRect(sc, tc, rect, direction) {
16445
+ if (direction === "LR") {
16446
+ const midX = (sc.x + tc.x) / 2;
16447
+ const m1 = { x: midX, y: sc.y };
16448
+ const m2 = { x: midX, y: tc.y };
16449
+ return segmentIntersectsRect(sc, m1, rect) || segmentIntersectsRect(m1, m2, rect) || segmentIntersectsRect(m2, tc, rect);
16450
+ } else {
16451
+ const midY = (sc.y + tc.y) / 2;
16452
+ const m1 = { x: sc.x, y: midY };
16453
+ const m2 = { x: tc.x, y: midY };
16454
+ return segmentIntersectsRect(sc, m1, rect) || segmentIntersectsRect(m1, m2, rect) || segmentIntersectsRect(m2, tc, rect);
16455
+ }
16456
+ }
16457
+ function edgeWaypoints(source, target, groups, nodes, direction, margin = 30, srcExitPt, tgtEnterPt) {
16458
+ const sc = { x: source.x, y: source.y };
16459
+ const tc = { x: target.x, y: target.y };
16460
+ const isBackward = direction === "LR" ? tc.x < sc.x : tc.y < sc.y;
16461
+ if (isBackward) {
16462
+ if (direction === "LR") {
16463
+ const xBandObs = [];
16464
+ for (const g of groups) {
16465
+ if (g.x + g.width < tc.x - margin || g.x > sc.x + margin) continue;
16466
+ xBandObs.push({ x: g.x, y: g.y, width: g.width, height: g.height });
16467
+ }
16468
+ for (const n of nodes) {
16469
+ if (n.id === source.id || n.id === target.id) continue;
16470
+ const nLeft = n.x - n.width / 2;
16471
+ const nRight = n.x + n.width / 2;
16472
+ if (nRight < tc.x - margin || nLeft > sc.x + margin) continue;
16473
+ xBandObs.push({ x: nLeft, y: n.y - n.height / 2, width: n.width, height: n.height });
16474
+ }
16475
+ const midY = (sc.y + tc.y) / 2;
16476
+ const routeY2 = xBandObs.length > 0 ? findRoutingLane(xBandObs, midY, margin) : midY;
16477
+ const exitBorder = srcExitPt ?? nodeBorderPoint(source, { x: sc.x, y: routeY2 });
16478
+ const exitPt2 = { x: exitBorder.x, y: routeY2 };
16479
+ const enterPt2 = { x: tc.x, y: routeY2 };
16480
+ const tp2 = tgtEnterPt ?? nodeBorderPoint(target, enterPt2);
16481
+ return srcExitPt ? [srcExitPt, exitPt2, enterPt2, tp2] : [exitBorder, exitPt2, enterPt2, tp2];
16482
+ } else {
16483
+ const yBandObs = [];
16484
+ for (const g of groups) {
16485
+ if (g.y + g.height < tc.y - margin || g.y > sc.y + margin) continue;
16486
+ yBandObs.push({ x: g.x, y: g.y, width: g.width, height: g.height });
16487
+ }
16488
+ for (const n of nodes) {
16489
+ if (n.id === source.id || n.id === target.id) continue;
16490
+ const nTop = n.y - n.height / 2;
16491
+ const nBot = n.y + n.height / 2;
16492
+ if (nBot < tc.y - margin || nTop > sc.y + margin) continue;
16493
+ yBandObs.push({ x: n.x - n.width / 2, y: nTop, width: n.width, height: n.height });
16494
+ }
16495
+ const rotated = yBandObs.map((r) => ({ x: r.y, y: r.x, width: r.height, height: r.width }));
16496
+ const midX = (sc.x + tc.x) / 2;
16497
+ const routeX = rotated.length > 0 ? findRoutingLane(rotated, midX, margin) : midX;
16498
+ const exitPt2 = srcExitPt ?? { x: routeX, y: sc.y };
16499
+ const enterPt2 = { x: routeX, y: tc.y };
16500
+ return [
16501
+ srcExitPt ?? nodeBorderPoint(source, exitPt2),
16502
+ exitPt2,
16503
+ enterPt2,
16504
+ tgtEnterPt ?? nodeBorderPoint(target, enterPt2)
16505
+ ];
16506
+ }
16507
+ }
16508
+ const blocking = [];
16509
+ const blockingGroupIds = /* @__PURE__ */ new Set();
16510
+ const pathSrc = srcExitPt ?? sc;
16511
+ const pathTgt = tgtEnterPt ?? tc;
16512
+ for (const g of groups) {
16513
+ if (g.id === source.groupId || g.id === target.groupId) continue;
16514
+ const gRect = { x: g.x, y: g.y, width: g.width, height: g.height };
16515
+ if (curveIntersectsRect(pathSrc, pathTgt, gRect, direction)) {
16516
+ blocking.push(gRect);
16517
+ blockingGroupIds.add(g.id);
16518
+ }
16519
+ }
16520
+ for (const n of nodes) {
16521
+ if (n.id === source.id || n.id === target.id) continue;
16522
+ if (n.groupId && (n.groupId === source.groupId || n.groupId === target.groupId)) continue;
16523
+ if (n.groupId && blockingGroupIds.has(n.groupId)) continue;
16524
+ const nodeRect = { x: n.x - n.width / 2, y: n.y - n.height / 2, width: n.width, height: n.height };
16525
+ if (curveIntersectsRect(pathSrc, pathTgt, nodeRect, direction)) {
16526
+ blocking.push(nodeRect);
16527
+ }
16528
+ }
16529
+ if (blocking.length === 0) {
16530
+ const sp = srcExitPt ?? nodeBorderPoint(source, tc);
16531
+ const tp2 = tgtEnterPt ?? nodeBorderPoint(target, sp);
16532
+ return [sp, tp2];
16533
+ }
16534
+ const obsLeft = Math.min(...blocking.map((o) => o.x));
16535
+ const obsRight = Math.max(...blocking.map((o) => o.x + o.width));
16536
+ const routeY = findRoutingLane(blocking, tc.y, margin);
16537
+ const exitX = direction === "LR" ? Math.max(sc.x, obsLeft - margin) : obsLeft - margin;
16538
+ const enterX = direction === "LR" ? Math.min(tc.x, obsRight + margin) : obsRight + margin;
16539
+ const exitPt = { x: exitX, y: routeY };
16540
+ const enterPt = { x: enterX, y: routeY };
16541
+ const tp = tgtEnterPt ?? nodeBorderPoint(target, enterPt);
16542
+ if (srcExitPt) {
16543
+ return [srcExitPt, exitPt, enterPt, tp];
16544
+ }
16545
+ return [nodeBorderPoint(source, exitPt), exitPt, enterPt, tp];
16546
+ }
15828
16547
  function nodeBorderPoint(node, target) {
15829
16548
  const hw = node.width / 2;
15830
16549
  const hh = node.height / 2;
@@ -16108,33 +16827,29 @@ function renderGroups(svg, groups, palette, isDark) {
16108
16827
  }
16109
16828
  }
16110
16829
  }
16111
- function renderEdgePaths(svg, edges, nodes, palette, isDark, animate) {
16830
+ function renderEdgePaths(svg, edges, nodes, groups, palette, isDark, animate, direction) {
16112
16831
  const nodeMap = new Map(nodes.map((n) => [n.id, n]));
16113
16832
  const maxRps = Math.max(...edges.map((e) => e.computedRps), 1);
16833
+ const { srcPts, tgtPts } = computePortPts(edges, nodeMap, direction);
16114
16834
  for (const edge of edges) {
16115
16835
  if (edge.points.length === 0) continue;
16116
16836
  const targetNode = nodeMap.get(edge.targetId);
16117
16837
  const sourceNode = nodeMap.get(edge.sourceId);
16118
16838
  const color = edgeColor(edge, palette);
16119
16839
  const strokeW = edgeWidth();
16120
- let pts = edge.points;
16121
- if (sourceNode && targetNode && pts.length >= 2) {
16122
- const first = pts[0];
16123
- const distFirstToSource = (first.x - sourceNode.x) ** 2 + (first.y - sourceNode.y) ** 2;
16124
- const distFirstToTarget = (first.x - targetNode.x) ** 2 + (first.y - targetNode.y) ** 2;
16125
- if (distFirstToTarget < distFirstToSource) {
16126
- pts = [...pts].reverse();
16127
- }
16128
- }
16129
- if (sourceNode && pts.length > 0) {
16130
- const bp = nodeBorderPoint(sourceNode, pts[0]);
16131
- pts = [bp, ...pts];
16132
- }
16133
- if (targetNode && pts.length > 0) {
16134
- const bp = nodeBorderPoint(targetNode, pts[pts.length - 1]);
16135
- pts = [...pts, bp];
16136
- }
16137
- const pathD = lineGenerator7(pts) ?? "";
16840
+ if (!sourceNode || !targetNode) continue;
16841
+ const key = `${edge.sourceId}:${edge.targetId}`;
16842
+ const pts = edgeWaypoints(
16843
+ sourceNode,
16844
+ targetNode,
16845
+ groups,
16846
+ nodes,
16847
+ direction,
16848
+ 30,
16849
+ srcPts.get(key),
16850
+ tgtPts.get(key)
16851
+ );
16852
+ const pathD = buildPathD(pts, direction);
16138
16853
  const edgeG = svg.append("g").attr("class", "infra-edge").attr("data-line-number", edge.lineNumber);
16139
16854
  edgeG.append("path").attr("d", pathD).attr("fill", "none").attr("stroke", color).attr("stroke-width", strokeW);
16140
16855
  if (animate && edge.computedRps > 0) {
@@ -16149,19 +16864,34 @@ function renderEdgePaths(svg, edges, nodes, palette, isDark, animate) {
16149
16864
  }
16150
16865
  }
16151
16866
  }
16152
- function renderEdgeLabels(svg, edges, palette, isDark, animate) {
16867
+ function renderEdgeLabels(svg, edges, nodes, groups, palette, isDark, animate, direction) {
16868
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
16869
+ const { srcPts, tgtPts } = computePortPts(edges, nodeMap, direction);
16153
16870
  for (const edge of edges) {
16154
16871
  if (edge.points.length === 0) continue;
16155
16872
  if (!edge.label) continue;
16156
- const midIdx = Math.floor(edge.points.length / 2);
16157
- const midPt = edge.points[midIdx];
16873
+ const sourceNode = nodeMap.get(edge.sourceId);
16874
+ const targetNode = nodeMap.get(edge.targetId);
16875
+ if (!sourceNode || !targetNode) continue;
16876
+ const key = `${edge.sourceId}:${edge.targetId}`;
16877
+ const wps = edgeWaypoints(
16878
+ sourceNode,
16879
+ targetNode,
16880
+ groups,
16881
+ nodes,
16882
+ direction,
16883
+ 30,
16884
+ srcPts.get(key),
16885
+ tgtPts.get(key)
16886
+ );
16887
+ const midPt = wps[Math.floor(wps.length / 2)];
16158
16888
  const labelText = edge.label;
16159
16889
  const g = svg.append("g").attr("class", animate ? "infra-edge-label" : "");
16160
16890
  const textWidth = labelText.length * 6.5 + 8;
16161
16891
  g.append("rect").attr("x", midPt.x - textWidth / 2).attr("y", midPt.y - 8).attr("width", textWidth).attr("height", 16).attr("rx", 3).attr("fill", palette.bg).attr("opacity", 0.9);
16162
16892
  g.append("text").attr("x", midPt.x).attr("y", midPt.y + 4).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", EDGE_LABEL_FONT_SIZE7).attr("fill", palette.textMuted).text(labelText);
16163
16893
  if (animate) {
16164
- const pathD = lineGenerator7(edge.points) ?? "";
16894
+ const pathD = buildPathD(wps, direction);
16165
16895
  g.insert("path", ":first-child").attr("d", pathD).attr("fill", "none").attr("stroke", "transparent").attr("stroke-width", 20);
16166
16896
  }
16167
16897
  }
@@ -16571,7 +17301,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
16571
17301
  rootSvg.append("text").attr("class", "chart-title").attr("x", totalWidth / 2).attr("y", 28).attr("text-anchor", "middle").attr("font-family", FONT_FAMILY).attr("font-size", 18).attr("font-weight", "700").attr("fill", palette.text).attr("data-line-number", titleLineNumber != null ? titleLineNumber : "").text(title);
16572
17302
  }
16573
17303
  renderGroups(svg, layout.groups, palette, isDark);
16574
- renderEdgePaths(svg, layout.edges, layout.nodes, palette, isDark, shouldAnimate);
17304
+ renderEdgePaths(svg, layout.edges, layout.nodes, layout.groups, palette, isDark, shouldAnimate, layout.direction);
16575
17305
  const fanoutSourceIds = collectFanoutSourceIds(layout.edges);
16576
17306
  const scaledGroupIds = new Set(
16577
17307
  layout.groups.filter((g) => {
@@ -16583,7 +17313,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
16583
17313
  if (shouldAnimate) {
16584
17314
  renderRejectParticles(svg, layout.nodes);
16585
17315
  }
16586
- renderEdgeLabels(svg, layout.edges, palette, isDark, shouldAnimate);
17316
+ renderEdgeLabels(svg, layout.edges, layout.nodes, layout.groups, palette, isDark, shouldAnimate, layout.direction);
16587
17317
  if (hasLegend) {
16588
17318
  if (fixedLegend) {
16589
17319
  const containerWidth = container.clientWidth || totalWidth;
@@ -16601,7 +17331,7 @@ function parseAndLayoutInfra(content) {
16601
17331
  const layout = layoutInfra(computed);
16602
17332
  return { parsed, computed, layout };
16603
17333
  }
16604
- var d3Selection9, d3Shape7, NODE_FONT_SIZE4, META_FONT_SIZE4, META_LINE_HEIGHT8, EDGE_LABEL_FONT_SIZE7, GROUP_LABEL_FONT_SIZE2, NODE_BORDER_RADIUS, EDGE_STROKE_WIDTH8, NODE_STROKE_WIDTH8, OVERLOAD_STROKE_WIDTH, ROLE_DOT_RADIUS, NODE_HEADER_HEIGHT2, NODE_SEPARATOR_GAP2, NODE_PAD_BOTTOM2, COLLAPSE_BAR_HEIGHT5, COLLAPSE_BAR_INSET2, LEGEND_FIXED_GAP3, COLOR_HEALTHY, COLOR_WARNING, COLOR_OVERLOADED, FLOW_SPEED_MIN, FLOW_SPEED_MAX, PARTICLE_R, PARTICLE_COUNT_MIN, PARTICLE_COUNT_MAX, NODE_PULSE_SPEED, NODE_PULSE_OVERLOAD, REJECT_PARTICLE_R, REJECT_DROP_DISTANCE, REJECT_DURATION_MIN, REJECT_DURATION_MAX, REJECT_COUNT_MIN, REJECT_COUNT_MAX, lineGenerator7, PROP_DISPLAY, DESC_MAX_CHARS, RPS_FORMAT_KEYS, MS_FORMAT_KEYS, PCT_FORMAT_KEYS;
17334
+ var d3Selection9, d3Shape7, NODE_FONT_SIZE4, META_FONT_SIZE4, META_LINE_HEIGHT8, EDGE_LABEL_FONT_SIZE7, GROUP_LABEL_FONT_SIZE2, NODE_BORDER_RADIUS, EDGE_STROKE_WIDTH8, NODE_STROKE_WIDTH8, OVERLOAD_STROKE_WIDTH, ROLE_DOT_RADIUS, NODE_HEADER_HEIGHT2, NODE_SEPARATOR_GAP2, NODE_PAD_BOTTOM2, COLLAPSE_BAR_HEIGHT5, COLLAPSE_BAR_INSET2, LEGEND_FIXED_GAP3, COLOR_HEALTHY, COLOR_WARNING, COLOR_OVERLOADED, FLOW_SPEED_MIN, FLOW_SPEED_MAX, PARTICLE_R, PARTICLE_COUNT_MIN, PARTICLE_COUNT_MAX, NODE_PULSE_SPEED, NODE_PULSE_OVERLOAD, REJECT_PARTICLE_R, REJECT_DROP_DISTANCE, REJECT_DURATION_MIN, REJECT_DURATION_MAX, REJECT_COUNT_MIN, REJECT_COUNT_MAX, PROP_DISPLAY, DESC_MAX_CHARS, RPS_FORMAT_KEYS, MS_FORMAT_KEYS, PCT_FORMAT_KEYS;
16605
17335
  var init_renderer8 = __esm({
16606
17336
  "src/infra/renderer.ts"() {
16607
17337
  "use strict";
@@ -16647,7 +17377,6 @@ var init_renderer8 = __esm({
16647
17377
  REJECT_DURATION_MAX = 3;
16648
17378
  REJECT_COUNT_MIN = 1;
16649
17379
  REJECT_COUNT_MAX = 3;
16650
- lineGenerator7 = d3Shape7.line().x((d) => d.x).y((d) => d.y).curve(d3Shape7.curveBasis);
16651
17380
  PROP_DISPLAY = {
16652
17381
  "cache-hit": "cache hit",
16653
17382
  "firewall-block": "firewall block",
@@ -16830,7 +17559,7 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
16830
17559
  }
16831
17560
  }
16832
17561
  } else if (edge.points.length >= 2) {
16833
- const pathD = lineGenerator8(edge.points);
17562
+ const pathD = lineGenerator7(edge.points);
16834
17563
  if (pathD) {
16835
17564
  edgeG.append("path").attr("d", pathD).attr("fill", "none").attr("stroke", edgeColor2).attr("stroke-width", EDGE_STROKE_WIDTH9).attr("marker-end", `url(#${markerId})`).attr("class", "st-edge");
16836
17565
  }
@@ -16894,7 +17623,7 @@ function renderStateForExport(content, theme, palette) {
16894
17623
  document.body.removeChild(container);
16895
17624
  }
16896
17625
  }
16897
- var d3Selection10, d3Shape8, DIAGRAM_PADDING9, MAX_SCALE8, NODE_FONT_SIZE5, EDGE_LABEL_FONT_SIZE8, GROUP_LABEL_FONT_SIZE3, EDGE_STROKE_WIDTH9, NODE_STROKE_WIDTH9, ARROWHEAD_W4, ARROWHEAD_H4, PSEUDOSTATE_RADIUS, STATE_CORNER_RADIUS, GROUP_EXTRA_PADDING2, lineGenerator8;
17626
+ var d3Selection10, d3Shape8, DIAGRAM_PADDING9, MAX_SCALE8, NODE_FONT_SIZE5, EDGE_LABEL_FONT_SIZE8, GROUP_LABEL_FONT_SIZE3, EDGE_STROKE_WIDTH9, NODE_STROKE_WIDTH9, ARROWHEAD_W4, ARROWHEAD_H4, PSEUDOSTATE_RADIUS, STATE_CORNER_RADIUS, GROUP_EXTRA_PADDING2, lineGenerator7;
16898
17627
  var init_state_renderer = __esm({
16899
17628
  "src/graph/state-renderer.ts"() {
16900
17629
  "use strict";
@@ -16916,7 +17645,7 @@ var init_state_renderer = __esm({
16916
17645
  PSEUDOSTATE_RADIUS = 10;
16917
17646
  STATE_CORNER_RADIUS = 10;
16918
17647
  GROUP_EXTRA_PADDING2 = 12;
16919
- lineGenerator8 = d3Shape8.line().x((d) => d.x).y((d) => d.y).curve(d3Shape8.curveBasis);
17648
+ lineGenerator7 = d3Shape8.line().x((d) => d.x).y((d) => d.y).curve(d3Shape8.curveBasis);
16920
17649
  }
16921
17650
  });
16922
17651
 
@@ -18078,7 +18807,6 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
18078
18807
  if (secY === void 0) continue;
18079
18808
  const isCollapsed = collapsedSections?.has(sec.lineNumber) ?? false;
18080
18809
  const lineColor = palette.textMuted;
18081
- const HIT_AREA_HEIGHT = 36;
18082
18810
  const sectionG = svg.append("g").attr("data-section-toggle", "").attr("data-line-number", String(sec.lineNumber)).attr("data-section", "").attr("tabindex", "0").attr("role", "button").attr("aria-expanded", String(!isCollapsed));
18083
18811
  const BAND_HEIGHT = 22;
18084
18812
  const bandX = sectionLineX1 - 10;
@@ -19467,7 +20195,6 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
19467
20195
  const positions = groupNodes.map((n) => yScale(n));
19468
20196
  const minY = Math.min(...positions) - bandPad;
19469
20197
  const maxY = Math.max(...positions) + bandPad;
19470
- const bandColor = group.color ?? mutedColor;
19471
20198
  g.append("rect").attr("class", "arc-group-band").attr("data-group", group.name).attr("data-line-number", String(group.lineNumber)).attr("x", baseX - bandHalfW).attr("y", minY).attr("width", bandHalfW * 2).attr("height", maxY - minY).attr("rx", 4).attr("fill", textColor).attr("fill-opacity", 0.06).style("cursor", "pointer").on("mouseenter", () => handleGroupEnter(group.name)).on("mouseleave", handleMouseLeave).on("click", () => {
19472
20199
  if (onClickItem) onClickItem(group.lineNumber);
19473
20200
  });
@@ -19511,7 +20238,6 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
19511
20238
  const positions = groupNodes.map((n) => xScale(n));
19512
20239
  const minX = Math.min(...positions) - bandPad;
19513
20240
  const maxX = Math.max(...positions) + bandPad;
19514
- const bandColor = group.color ?? mutedColor;
19515
20241
  g.append("rect").attr("class", "arc-group-band").attr("data-group", group.name).attr("data-line-number", String(group.lineNumber)).attr("x", minX).attr("y", baseY - bandHalfH).attr("width", maxX - minX).attr("height", bandHalfH * 2).attr("rx", 4).attr("fill", textColor).attr("fill-opacity", 0.06).style("cursor", "pointer").on("mouseenter", () => handleGroupEnter(group.name)).on("mouseleave", handleMouseLeave).on("click", () => {
19516
20242
  if (onClickItem) onClickItem(group.lineNumber);
19517
20243
  });
@@ -19829,6 +20555,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19829
20555
  const textColor = palette.text;
19830
20556
  const mutedColor = palette.border;
19831
20557
  const bgColor = palette.bg;
20558
+ const bg = isDark ? palette.surface : palette.bg;
19832
20559
  const colors = getSeriesColors(palette);
19833
20560
  const groupColorMap = /* @__PURE__ */ new Map();
19834
20561
  timelineGroups.forEach((grp, i) => {
@@ -20090,7 +20817,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20090
20817
  if (ev.endDate) {
20091
20818
  const y2 = yScale(parseTimelineDate(ev.endDate));
20092
20819
  const rectH = Math.max(y2 - y, 4);
20093
- let fill2 = evColor;
20820
+ let fill2 = mix(evColor, bg, 30);
20094
20821
  if (ev.uncertain) {
20095
20822
  const gradientId = `uncertain-vg-${ev.lineNumber}`;
20096
20823
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -20098,13 +20825,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20098
20825
  { offset: "0%", opacity: 1 },
20099
20826
  { offset: "80%", opacity: 1 },
20100
20827
  { offset: "100%", opacity: 0 }
20101
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", laneColor).attr("stop-opacity", (d) => d.opacity);
20828
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", mix(laneColor, bg, 30)).attr("stop-opacity", (d) => d.opacity);
20102
20829
  fill2 = `url(#${gradientId})`;
20103
20830
  }
20104
- evG.append("rect").attr("x", laneCenter - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2);
20831
+ evG.append("rect").attr("x", laneCenter - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2).attr("stroke", evColor).attr("stroke-width", 2);
20105
20832
  evG.append("text").attr("x", laneCenter + 14).attr("y", y + rectH / 2).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "10px").text(ev.label);
20106
20833
  } else {
20107
- evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
20834
+ evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", mix(evColor, bg, 30)).attr("stroke", evColor).attr("stroke-width", 2);
20108
20835
  evG.append("text").attr("x", laneCenter + 10).attr("y", y).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "10px").text(ev.label);
20109
20836
  }
20110
20837
  }
@@ -20197,7 +20924,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20197
20924
  if (ev.endDate) {
20198
20925
  const y2 = yScale(parseTimelineDate(ev.endDate));
20199
20926
  const rectH = Math.max(y2 - y, 4);
20200
- let fill2 = color;
20927
+ let fill2 = mix(color, bg, 30);
20201
20928
  if (ev.uncertain) {
20202
20929
  const gradientId = `uncertain-v-${ev.lineNumber}`;
20203
20930
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -20205,13 +20932,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20205
20932
  { offset: "0%", opacity: 1 },
20206
20933
  { offset: "80%", opacity: 1 },
20207
20934
  { offset: "100%", opacity: 0 }
20208
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", color).attr("stop-opacity", (d) => d.opacity);
20935
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", mix(color, bg, 30)).attr("stop-opacity", (d) => d.opacity);
20209
20936
  fill2 = `url(#${gradientId})`;
20210
20937
  }
20211
- evG.append("rect").attr("x", axisX - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2);
20938
+ evG.append("rect").attr("x", axisX - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2).attr("stroke", color).attr("stroke-width", 2);
20212
20939
  evG.append("text").attr("x", axisX + 16).attr("y", y + rectH / 2).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "11px").text(ev.label);
20213
20940
  } else {
20214
- evG.append("circle").attr("cx", axisX).attr("cy", y).attr("r", 4).attr("fill", color).attr("stroke", bgColor).attr("stroke-width", 1.5);
20941
+ evG.append("circle").attr("cx", axisX).attr("cy", y).attr("r", 4).attr("fill", mix(color, bg, 30)).attr("stroke", color).attr("stroke-width", 2);
20215
20942
  evG.append("text").attr("x", axisX + 16).attr("y", y).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "11px").text(ev.label);
20216
20943
  }
20217
20944
  evG.append("text").attr("x", axisX - 14).attr(
@@ -20362,7 +21089,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20362
21089
  const rectW = Math.max(x2 - x, 4);
20363
21090
  const estLabelWidth = ev.label.length * 7 + 16;
20364
21091
  const labelFitsInside = rectW >= estLabelWidth;
20365
- let fill2 = evColor;
21092
+ let fill2 = mix(evColor, bg, 30);
20366
21093
  if (ev.uncertain) {
20367
21094
  const gradientId = `uncertain-${ev.lineNumber}`;
20368
21095
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -20370,12 +21097,12 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20370
21097
  { offset: "0%", opacity: 1 },
20371
21098
  { offset: "80%", opacity: 1 },
20372
21099
  { offset: "100%", opacity: 0 }
20373
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", evColor).attr("stop-opacity", (d) => d.opacity);
21100
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", mix(evColor, bg, 30)).attr("stop-opacity", (d) => d.opacity);
20374
21101
  fill2 = `url(#${gradientId})`;
20375
21102
  }
20376
- evG.append("rect").attr("x", x).attr("y", y - BAR_H / 2).attr("width", rectW).attr("height", BAR_H).attr("rx", 4).attr("fill", fill2);
21103
+ evG.append("rect").attr("x", x).attr("y", y - BAR_H / 2).attr("width", rectW).attr("height", BAR_H).attr("rx", 4).attr("fill", fill2).attr("stroke", evColor).attr("stroke-width", 2);
20377
21104
  if (labelFitsInside) {
20378
- evG.append("text").attr("x", x + 8).attr("y", y).attr("dy", "0.35em").attr("text-anchor", "start").attr("fill", "#ffffff").attr("font-size", "14px").attr("font-weight", "700").text(ev.label);
21105
+ evG.append("text").attr("x", x + 8).attr("y", y).attr("dy", "0.35em").attr("text-anchor", "start").attr("fill", textColor).attr("font-size", "14px").attr("font-weight", "700").text(ev.label);
20379
21106
  } else {
20380
21107
  const wouldFlipLeft = x + rectW > innerWidth * 0.6;
20381
21108
  const labelFitsLeft = x - 6 - estLabelWidth > 0;
@@ -20387,7 +21114,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20387
21114
  const wouldFlipLeft = x > innerWidth * 0.6;
20388
21115
  const labelFitsLeft = x - 10 - estLabelWidth > 0;
20389
21116
  const flipLeft = wouldFlipLeft && labelFitsLeft;
20390
- evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
21117
+ evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", mix(evColor, bg, 30)).attr("stroke", evColor).attr("stroke-width", 2);
20391
21118
  evG.append("text").attr("x", flipLeft ? x - 10 : x + 10).attr("y", y).attr("dy", "0.35em").attr("text-anchor", flipLeft ? "end" : "start").attr("fill", textColor).attr("font-size", "12px").text(ev.label);
20392
21119
  }
20393
21120
  });
@@ -20500,7 +21227,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20500
21227
  const rectW = Math.max(x2 - x, 4);
20501
21228
  const estLabelWidth = ev.label.length * 7 + 16;
20502
21229
  const labelFitsInside = rectW >= estLabelWidth;
20503
- let fill2 = color;
21230
+ let fill2 = mix(color, bg, 30);
20504
21231
  if (ev.uncertain) {
20505
21232
  const gradientId = `uncertain-ts-${ev.lineNumber}`;
20506
21233
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -20508,12 +21235,12 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20508
21235
  { offset: "0%", opacity: 1 },
20509
21236
  { offset: "80%", opacity: 1 },
20510
21237
  { offset: "100%", opacity: 0 }
20511
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", color).attr("stop-opacity", (d) => d.opacity);
21238
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", mix(color, bg, 30)).attr("stop-opacity", (d) => d.opacity);
20512
21239
  fill2 = `url(#${gradientId})`;
20513
21240
  }
20514
- evG.append("rect").attr("x", x).attr("y", y - BAR_H / 2).attr("width", rectW).attr("height", BAR_H).attr("rx", 4).attr("fill", fill2);
21241
+ evG.append("rect").attr("x", x).attr("y", y - BAR_H / 2).attr("width", rectW).attr("height", BAR_H).attr("rx", 4).attr("fill", fill2).attr("stroke", color).attr("stroke-width", 2);
20515
21242
  if (labelFitsInside) {
20516
- evG.append("text").attr("x", x + 8).attr("y", y).attr("dy", "0.35em").attr("text-anchor", "start").attr("fill", "#ffffff").attr("font-size", "14px").attr("font-weight", "700").text(ev.label);
21243
+ evG.append("text").attr("x", x + 8).attr("y", y).attr("dy", "0.35em").attr("text-anchor", "start").attr("fill", textColor).attr("font-size", "14px").attr("font-weight", "700").text(ev.label);
20517
21244
  } else {
20518
21245
  const wouldFlipLeft = x + rectW > innerWidth * 0.6;
20519
21246
  const labelFitsLeft = x - 6 - estLabelWidth > 0;
@@ -20525,7 +21252,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20525
21252
  const wouldFlipLeft = x > innerWidth * 0.6;
20526
21253
  const labelFitsLeft = x - 10 - estLabelWidth > 0;
20527
21254
  const flipLeft = wouldFlipLeft && labelFitsLeft;
20528
- evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", color).attr("stroke", bgColor).attr("stroke-width", 1.5);
21255
+ evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", mix(color, bg, 30)).attr("stroke", color).attr("stroke-width", 2);
20529
21256
  evG.append("text").attr("x", flipLeft ? x - 10 : x + 10).attr("y", y).attr("dy", "0.35em").attr("text-anchor", flipLeft ? "end" : "start").attr("fill", textColor).attr("font-size", "12px").text(ev.label);
20530
21257
  }
20531
21258
  });
@@ -20680,8 +21407,8 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20680
21407
  } else {
20681
21408
  color = ev.group && groupColorMap.has(ev.group) ? groupColorMap.get(ev.group) : textColor;
20682
21409
  }
20683
- el.selectAll("rect").attr("fill", color);
20684
- el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", color);
21410
+ el.selectAll("rect").attr("fill", mix(color, bg, 30)).attr("stroke", color);
21411
+ el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", mix(color, bg, 30)).attr("stroke", color);
20685
21412
  });
20686
21413
  };
20687
21414
  var drawSwimlaneIcon = drawSwimlaneIcon2, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
@@ -21146,7 +21873,6 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
21146
21873
  const init2 = initD3Chart(container, palette, exportDims);
21147
21874
  if (!init2) return;
21148
21875
  const { svg, width, height, textColor } = init2;
21149
- const mutedColor = palette.textMuted;
21150
21876
  const borderColor = palette.border;
21151
21877
  const defaultColors = [
21152
21878
  palette.colors.blue,
@@ -21170,13 +21896,17 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
21170
21896
  const f = r.length === 3 ? r[0] + r[0] + r[1] + r[1] + r[2] + r[2] : r;
21171
21897
  return [parseInt(f.substring(0, 2), 16), parseInt(f.substring(2, 4), 16), parseInt(f.substring(4, 6), 16)];
21172
21898
  };
21173
- const [ar, ag, ab] = parse(a), [br, bg, bb] = parse(b), t = pct / 100;
21899
+ const [ar, ag, ab] = parse(a), [br, bg2, bb] = parse(b), t = pct / 100;
21174
21900
  const c = (x, y) => Math.round(x * t + y * (1 - t)).toString(16).padStart(2, "0");
21175
- return `#${c(ar, br)}${c(ag, bg)}${c(ab, bb)}`;
21901
+ return `#${c(ar, br)}${c(ag, bg2)}${c(ab, bb)}`;
21176
21902
  };
21177
- const getQuadrantFill = (label, defaultIdx) => {
21903
+ const bg = isDark ? palette.surface : palette.bg;
21904
+ const getQuadrantColor = (label, defaultIdx) => {
21178
21905
  return label?.color ?? defaultColors[defaultIdx % defaultColors.length];
21179
21906
  };
21907
+ const getQuadrantFill = (label, defaultIdx) => {
21908
+ return mixHex(getQuadrantColor(label, defaultIdx), bg, 30);
21909
+ };
21180
21910
  const quadrantDefs = [
21181
21911
  {
21182
21912
  position: "top-left",
@@ -21227,12 +21957,11 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
21227
21957
  // purple
21228
21958
  }
21229
21959
  ];
21230
- const quadrantRects = chartG.selectAll("rect.quadrant").data(quadrantDefs).enter().append("rect").attr("class", "quadrant").attr("x", (d) => d.x).attr("y", (d) => d.y).attr("width", (d) => d.w).attr("height", (d) => d.h).attr("fill", (d) => getQuadrantFill(d.label, d.colorIdx)).attr("stroke", borderColor).attr("stroke-width", 0.5);
21231
- const contrastColor = "#ffffff";
21960
+ const quadrantRects = chartG.selectAll("rect.quadrant").data(quadrantDefs).enter().append("rect").attr("class", "quadrant").attr("x", (d) => d.x).attr("y", (d) => d.y).attr("width", (d) => d.w).attr("height", (d) => d.h).attr("fill", (d) => getQuadrantFill(d.label, d.colorIdx)).attr("stroke", (d) => getQuadrantColor(d.label, d.colorIdx)).attr("stroke-width", 2);
21232
21961
  const shadowColor = "rgba(0,0,0,0.4)";
21233
21962
  const getQuadrantLabelColor = (d) => {
21234
- const fill2 = getQuadrantFill(d.label, d.colorIdx);
21235
- return mixHex("#000000", fill2, 40);
21963
+ const color = getQuadrantColor(d.label, d.colorIdx);
21964
+ return mixHex("#000000", color, 40);
21236
21965
  };
21237
21966
  const LABEL_MAX_FONT = 48;
21238
21967
  const LABEL_MIN_FONT = 14;
@@ -21373,7 +22102,7 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
21373
22102
  const pointColor = quadDef?.label?.color ?? defaultColors[quadDef?.colorIdx ?? 0];
21374
22103
  const pointG = pointsG.append("g").attr("class", "point-group").attr("data-line-number", String(point.lineNumber));
21375
22104
  pointG.append("circle").attr("cx", cx).attr("cy", cy).attr("r", 6).attr("fill", "#ffffff").attr("stroke", pointColor).attr("stroke-width", 2);
21376
- pointG.append("text").attr("x", cx).attr("y", cy - 10).attr("text-anchor", "middle").attr("fill", contrastColor).attr("font-size", "12px").attr("font-weight", "700").style("text-shadow", `0 1px 2px ${shadowColor}`).text(point.label);
22105
+ pointG.append("text").attr("x", cx).attr("y", cy - 10).attr("text-anchor", "middle").attr("fill", textColor).attr("font-size", "12px").attr("font-weight", "700").style("text-shadow", `0 1px 2px ${shadowColor}`).text(point.label);
21377
22106
  const tipHtml = `<strong>${point.label}</strong><br>x: ${point.x.toFixed(2)}, y: ${point.y.toFixed(2)}`;
21378
22107
  pointG.style("cursor", onClickItem ? "pointer" : "default").on("mouseenter", (event) => {
21379
22108
  showTooltip(tooltip, tipHtml, event);
@@ -21874,6 +22603,7 @@ __export(index_exports, {
21874
22603
  contrastText: () => contrastText,
21875
22604
  decodeDiagramUrl: () => decodeDiagramUrl,
21876
22605
  encodeDiagramUrl: () => encodeDiagramUrl,
22606
+ extractDiagramSymbols: () => extractDiagramSymbols,
21877
22607
  formatDateLabel: () => formatDateLabel,
21878
22608
  formatDgmoError: () => formatDgmoError,
21879
22609
  getAvailablePalettes: () => getAvailablePalettes,
@@ -21937,6 +22667,7 @@ __export(index_exports, {
21937
22667
  parseState: () => parseState,
21938
22668
  parseTimelineDate: () => parseTimelineDate,
21939
22669
  parseVisualization: () => parseVisualization,
22670
+ registerExtractor: () => registerExtractor,
21940
22671
  registerPalette: () => registerPalette,
21941
22672
  render: () => render,
21942
22673
  renderArcDiagram: () => renderArcDiagram,
@@ -22790,6 +23521,34 @@ function decodeDiagramUrl(hash) {
22790
23521
  }
22791
23522
  }
22792
23523
 
23524
+ // src/completion.ts
23525
+ init_parser3();
23526
+ init_flowchart_parser();
23527
+ init_parser9();
23528
+ init_parser2();
23529
+ var registry = /* @__PURE__ */ new Map();
23530
+ function registerExtractor(kind, fn) {
23531
+ registry.set(kind, fn);
23532
+ }
23533
+ function extractDiagramSymbols(docText) {
23534
+ let chartType = null;
23535
+ for (const line10 of docText.split("\n")) {
23536
+ const m = line10.match(/^\s*chart\s*:\s*(.+)/i);
23537
+ if (m) {
23538
+ chartType = m[1].trim().toLowerCase();
23539
+ break;
23540
+ }
23541
+ }
23542
+ if (!chartType) return null;
23543
+ const fn = registry.get(chartType);
23544
+ if (!fn) return null;
23545
+ return fn(docText);
23546
+ }
23547
+ registerExtractor("er", extractSymbols3);
23548
+ registerExtractor("flowchart", extractSymbols);
23549
+ registerExtractor("infra", extractSymbols4);
23550
+ registerExtractor("class", extractSymbols2);
23551
+
22793
23552
  // src/index.ts
22794
23553
  init_branding();
22795
23554
  // Annotate the CommonJS export names for ESM import in node:
@@ -22822,6 +23581,7 @@ init_branding();
22822
23581
  contrastText,
22823
23582
  decodeDiagramUrl,
22824
23583
  encodeDiagramUrl,
23584
+ extractDiagramSymbols,
22825
23585
  formatDateLabel,
22826
23586
  formatDgmoError,
22827
23587
  getAvailablePalettes,
@@ -22885,6 +23645,7 @@ init_branding();
22885
23645
  parseState,
22886
23646
  parseTimelineDate,
22887
23647
  parseVisualization,
23648
+ registerExtractor,
22888
23649
  registerPalette,
22889
23650
  render,
22890
23651
  renderArcDiagram,