@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.js CHANGED
@@ -1787,12 +1787,12 @@ var SYNC_LABELED_RE, ASYNC_LABELED_RE, RETURN_SYNC_LABELED_RE, RETURN_ASYNC_LABE
1787
1787
  var init_arrows = __esm({
1788
1788
  "src/utils/arrows.ts"() {
1789
1789
  "use strict";
1790
- SYNC_LABELED_RE = /^(\S+)\s+-(.+)->\s+(\S+)$/;
1791
- ASYNC_LABELED_RE = /^(\S+)\s+~(.+)~>\s+(\S+)$/;
1792
- RETURN_SYNC_LABELED_RE = /^(\S+)\s+<-(.+)-\s+(\S+)$/;
1793
- RETURN_ASYNC_LABELED_RE = /^(\S+)\s+<~(.+)~\s+(\S+)$/;
1794
- BIDI_SYNC_RE = /^(\S+)\s+<-(.+)->\s+(\S+)$/;
1795
- BIDI_ASYNC_RE = /^(\S+)\s+<~(.+)~>\s+(\S+)$/;
1790
+ SYNC_LABELED_RE = /^(.+?)\s+-(.+)->\s+(.+)$/;
1791
+ ASYNC_LABELED_RE = /^(.+?)\s+~(.+)~>\s+(.+)$/;
1792
+ RETURN_SYNC_LABELED_RE = /^(.+?)\s+<-(.+)-\s+(.+)$/;
1793
+ RETURN_ASYNC_LABELED_RE = /^(.+?)\s+<~(.+)~\s+(.+)$/;
1794
+ BIDI_SYNC_RE = /^(.+?)\s+<-(.+)->\s+(.+)$/;
1795
+ BIDI_ASYNC_RE = /^(.+?)\s+<~(.+)~>\s+(.+)$/;
1796
1796
  ARROW_CHARS = ["->", "~>"];
1797
1797
  }
1798
1798
  });
@@ -2201,7 +2201,7 @@ function parseSequenceDgmo(content) {
2201
2201
  continue;
2202
2202
  }
2203
2203
  const bidiPlainMatch = arrowCore.match(
2204
- /^(\S+)\s*(?:<->|<~>)\s*(\S+)/
2204
+ /^(.+?)\s*(?:<->|<~>)\s*(.+)/
2205
2205
  );
2206
2206
  if (bidiPlainMatch) {
2207
2207
  pushError(
@@ -2210,8 +2210,8 @@ function parseSequenceDgmo(content) {
2210
2210
  );
2211
2211
  continue;
2212
2212
  }
2213
- const bareReturnSync = arrowCore.match(/^(\S+)\s+<-\s+(\S+)$/);
2214
- const bareReturnAsync = arrowCore.match(/^(\S+)\s+<~\s+(\S+)$/);
2213
+ const bareReturnSync = arrowCore.match(/^(.+?)\s+<-\s+(.+)$/);
2214
+ const bareReturnAsync = arrowCore.match(/^(.+?)\s+<~\s+(.+)$/);
2215
2215
  const bareReturn = bareReturnSync || bareReturnAsync;
2216
2216
  if (bareReturn) {
2217
2217
  const to = bareReturn[1];
@@ -2222,8 +2222,8 @@ function parseSequenceDgmo(content) {
2222
2222
  );
2223
2223
  continue;
2224
2224
  }
2225
- const bareCallSync = arrowCore.match(/^(\S+)\s*->\s*(\S+)$/);
2226
- const bareCallAsync = arrowCore.match(/^(\S+)\s*~>\s*(\S+)$/);
2225
+ const bareCallSync = arrowCore.match(/^(.+?)\s*->\s*(.+)$/);
2226
+ const bareCallAsync = arrowCore.match(/^(.+?)\s*~>\s*(.+)$/);
2227
2227
  const bareCall = bareCallSync || bareCallAsync;
2228
2228
  if (bareCall) {
2229
2229
  contentStarted = true;
@@ -2475,21 +2475,22 @@ var init_parser = __esm({
2475
2475
  "networking",
2476
2476
  "frontend"
2477
2477
  ]);
2478
- IS_A_PATTERN = /^(\S+)\s+is\s+an?\s+(\w+)(?:\s+(.+))?$/i;
2479
- POSITION_ONLY_PATTERN = /^(\S+)\s+position\s+(-?\d+)$/i;
2478
+ IS_A_PATTERN = /^([^:]+?)\s+is\s+an?\s+(\w+)(?:\s+(.+))?$/i;
2479
+ POSITION_ONLY_PATTERN = /^([^:]+?)\s+position\s+(-?\d+)$/i;
2480
2480
  COLORED_PARTICIPANT_PATTERN = /^(\S+?)\(([^)]+)\)\s*$/;
2481
2481
  GROUP_HEADING_PATTERN = /^\[(.+?)(?:\(([^)]+)\))?\]\s*$/;
2482
2482
  LEGACY_GROUP_PATTERN = /^##\s+(.+?)(?:\(([^)]+)\))?\s*$/;
2483
2483
  SECTION_PATTERN = /^==\s+(.+?)(?:\s*==)?\s*$/;
2484
2484
  ARROW_PATTERN = /\S+\s*(?:<-\S+-|<~\S+~|-\S+->|~\S+~>|->|~>|<-|<~)\s*\S+/;
2485
- NOTE_SINGLE = /^note(?:\s+(right|left)\s+of\s+(\S+))?\s*:\s*(.+)$/i;
2486
- NOTE_MULTI = /^note(?:\s+(right|left)\s+of\s+([^\s:]+))?\s*:?\s*$/i;
2485
+ NOTE_SINGLE = /^note(?:\s+(right|left)\s+of\s+(.+?))?\s*:\s*(.+)$/i;
2486
+ NOTE_MULTI = /^note(?:\s+(right|left)\s+of\s+(.+?))?\s*:?\s*$/i;
2487
2487
  }
2488
2488
  });
2489
2489
 
2490
2490
  // src/graph/flowchart-parser.ts
2491
2491
  var flowchart_parser_exports = {};
2492
2492
  __export(flowchart_parser_exports, {
2493
+ extractSymbols: () => extractSymbols,
2493
2494
  looksLikeFlowchart: () => looksLikeFlowchart,
2494
2495
  parseFlowchart: () => parseFlowchart
2495
2496
  });
@@ -2533,7 +2534,6 @@ function parseNodeRef(text, palette) {
2533
2534
  }
2534
2535
  function splitArrows(line10) {
2535
2536
  const segments = [];
2536
- const arrowRe = /(?:^|\s)-([^>\s(][^(>]*?)?\s*(?:\(([^)]+)\))?\s*->|(?:^|\s)->/g;
2537
2537
  let lastIndex = 0;
2538
2538
  const arrowPositions = [];
2539
2539
  let searchFrom = 0;
@@ -2774,7 +2774,20 @@ function looksLikeFlowchart(content) {
2774
2774
  /->[ \t]*[\[(<\/]/.test(content);
2775
2775
  return shapeNearArrow;
2776
2776
  }
2777
- var LEGACY_GROUP_RE;
2777
+ function extractSymbols(docText) {
2778
+ const entities = [];
2779
+ let inMetadata = true;
2780
+ for (const rawLine of docText.split("\n")) {
2781
+ const line10 = rawLine.trim();
2782
+ if (inMetadata && /^[a-z-]+\s*:/i.test(line10)) continue;
2783
+ inMetadata = false;
2784
+ if (line10.length === 0 || /^\s/.test(rawLine)) continue;
2785
+ const m = NODE_ID_RE.exec(line10);
2786
+ if (m && !entities.includes(m[1])) entities.push(m[1]);
2787
+ }
2788
+ return { kind: "flowchart", entities, keywords: [] };
2789
+ }
2790
+ var LEGACY_GROUP_RE, NODE_ID_RE;
2778
2791
  var init_flowchart_parser = __esm({
2779
2792
  "src/graph/flowchart-parser.ts"() {
2780
2793
  "use strict";
@@ -2782,6 +2795,7 @@ var init_flowchart_parser = __esm({
2782
2795
  init_diagnostics();
2783
2796
  init_parsing();
2784
2797
  LEGACY_GROUP_RE = /^##\s+/;
2798
+ NODE_ID_RE = /^([a-zA-Z_][\w-]*)[\s([</{]/;
2785
2799
  }
2786
2800
  });
2787
2801
 
@@ -3057,6 +3071,7 @@ var init_state_parser = __esm({
3057
3071
  // src/class/parser.ts
3058
3072
  var parser_exports2 = {};
3059
3073
  __export(parser_exports2, {
3074
+ extractSymbols: () => extractSymbols2,
3060
3075
  looksLikeClassDiagram: () => looksLikeClassDiagram,
3061
3076
  parseClassDiagram: () => parseClassDiagram
3062
3077
  });
@@ -3304,6 +3319,23 @@ function looksLikeClassDiagram(content) {
3304
3319
  if (hasRelationship && hasClassDecl && hasIndentedMember) return true;
3305
3320
  return false;
3306
3321
  }
3322
+ function extractSymbols2(docText) {
3323
+ const entities = [];
3324
+ let inMetadata = true;
3325
+ for (const rawLine of docText.split("\n")) {
3326
+ const line10 = rawLine.trim();
3327
+ if (inMetadata && /^[a-z-]+\s*:/i.test(line10)) continue;
3328
+ inMetadata = false;
3329
+ if (line10.length === 0 || /^\s/.test(rawLine)) continue;
3330
+ const m = CLASS_DECL_RE.exec(line10);
3331
+ if (m && !entities.includes(m[1])) entities.push(m[1]);
3332
+ }
3333
+ return {
3334
+ kind: "class",
3335
+ entities,
3336
+ keywords: ["extends", "implements", "abstract", "interface", "enum"]
3337
+ };
3338
+ }
3307
3339
  var CLASS_DECL_RE, REL_ARROW_RE, VISIBILITY_RE, STATIC_SUFFIX_RE, METHOD_RE, FIELD_RE, ARROW_TO_TYPE;
3308
3340
  var init_parser2 = __esm({
3309
3341
  "src/class/parser.ts"() {
@@ -3331,6 +3363,7 @@ var init_parser2 = __esm({
3331
3363
  // src/er/parser.ts
3332
3364
  var parser_exports3 = {};
3333
3365
  __export(parser_exports3, {
3366
+ extractSymbols: () => extractSymbols3,
3334
3367
  looksLikeERDiagram: () => looksLikeERDiagram,
3335
3368
  parseERDiagram: () => parseERDiagram
3336
3369
  });
@@ -3633,6 +3666,25 @@ function looksLikeERDiagram(content) {
3633
3666
  if (hasRelationship && hasTableDecl) return true;
3634
3667
  return false;
3635
3668
  }
3669
+ function extractSymbols3(docText) {
3670
+ const entities = [];
3671
+ let inMetadata = true;
3672
+ for (const rawLine of docText.split("\n")) {
3673
+ const line10 = rawLine.trim();
3674
+ if (inMetadata && /^chart\s*:/i.test(line10)) continue;
3675
+ if (inMetadata && /^[a-z-]+\s*:/i.test(line10)) continue;
3676
+ inMetadata = false;
3677
+ if (line10.length === 0) continue;
3678
+ if (/^\s/.test(rawLine)) continue;
3679
+ const m = TABLE_DECL_RE.exec(line10);
3680
+ if (m) entities.push(m[1]);
3681
+ }
3682
+ return {
3683
+ kind: "er",
3684
+ entities,
3685
+ keywords: ["pk", "fk", "unique", "nullable", "1", "*", "?"]
3686
+ };
3687
+ }
3636
3688
  var TABLE_DECL_RE, COLUMN_RE, INDENT_REL_RE, CONSTRAINT_MAP, REL_SYMBOLIC_RE, REL_KEYWORD_RE, KEYWORD_TO_SYMBOL;
3637
3689
  var init_parser3 = __esm({
3638
3690
  "src/er/parser.ts"() {
@@ -4115,10 +4167,12 @@ function buildExtendedChartOption(parsed, palette, isDark) {
4115
4167
  );
4116
4168
  }
4117
4169
  if (parsed.type === "chord") {
4170
+ const bg = isDark ? palette.surface : palette.bg;
4118
4171
  return buildChordOption(
4119
4172
  parsed,
4120
4173
  textColor,
4121
4174
  colors,
4175
+ bg,
4122
4176
  titleConfig,
4123
4177
  tooltipTheme
4124
4178
  );
@@ -4136,6 +4190,7 @@ function buildExtendedChartOption(parsed, palette, isDark) {
4136
4190
  );
4137
4191
  }
4138
4192
  if (parsed.type === "scatter") {
4193
+ const bg = isDark ? palette.surface : palette.bg;
4139
4194
  return buildScatterOption(
4140
4195
  parsed,
4141
4196
  palette,
@@ -4143,15 +4198,18 @@ function buildExtendedChartOption(parsed, palette, isDark) {
4143
4198
  axisLineColor,
4144
4199
  gridOpacity,
4145
4200
  colors,
4201
+ bg,
4146
4202
  titleConfig,
4147
4203
  tooltipTheme
4148
4204
  );
4149
4205
  }
4150
4206
  if (parsed.type === "funnel") {
4207
+ const bg = isDark ? palette.surface : palette.bg;
4151
4208
  return buildFunnelOption(
4152
4209
  parsed,
4153
4210
  textColor,
4154
4211
  colors,
4212
+ bg,
4155
4213
  titleConfig,
4156
4214
  tooltipTheme
4157
4215
  );
@@ -4159,6 +4217,7 @@ function buildExtendedChartOption(parsed, palette, isDark) {
4159
4217
  return buildHeatmapOption(
4160
4218
  parsed,
4161
4219
  palette,
4220
+ isDark,
4162
4221
  textColor,
4163
4222
  axisLineColor,
4164
4223
  titleConfig,
@@ -4215,7 +4274,7 @@ function buildSankeyOption(parsed, textColor, colors, titleConfig, tooltipTheme)
4215
4274
  ]
4216
4275
  };
4217
4276
  }
4218
- function buildChordOption(parsed, textColor, colors, titleConfig, tooltipTheme) {
4277
+ function buildChordOption(parsed, textColor, colors, bg, titleConfig, tooltipTheme) {
4219
4278
  const nodeSet = /* @__PURE__ */ new Set();
4220
4279
  if (parsed.links) {
4221
4280
  for (const link of parsed.links) {
@@ -4235,12 +4294,13 @@ function buildChordOption(parsed, textColor, colors, titleConfig, tooltipTheme)
4235
4294
  }
4236
4295
  }
4237
4296
  }
4238
- const categories = nodeNames.map((name, index) => ({
4239
- name,
4240
- itemStyle: {
4241
- color: colors[index % colors.length]
4242
- }
4243
- }));
4297
+ const categories = nodeNames.map((name, index) => {
4298
+ const stroke2 = colors[index % colors.length];
4299
+ return {
4300
+ name,
4301
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
4302
+ };
4303
+ });
4244
4304
  return {
4245
4305
  ...CHART_BASE,
4246
4306
  title: titleConfig,
@@ -4408,7 +4468,7 @@ function buildFunctionOption(parsed, palette, textColor, axisLineColor, gridOpac
4408
4468
  series
4409
4469
  };
4410
4470
  }
4411
- function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpacity, colors, titleConfig, tooltipTheme) {
4471
+ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme) {
4412
4472
  const points = parsed.scatterPoints ?? [];
4413
4473
  const defaultSize = 15;
4414
4474
  const hasCategories = points.some((p) => p.category !== void 0);
@@ -4440,27 +4500,30 @@ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpaci
4440
4500
  const data = categoryPoints.map((p) => ({
4441
4501
  name: p.name,
4442
4502
  value: hasSize ? [p.x, p.y, p.size ?? 0] : [p.x, p.y],
4443
- ...p.color && { itemStyle: { color: p.color } }
4503
+ ...p.color && {
4504
+ itemStyle: { color: mix(p.color, bg, 30), borderColor: p.color, borderWidth: CHART_BORDER_WIDTH }
4505
+ }
4444
4506
  }));
4445
4507
  return {
4446
4508
  name: category,
4447
4509
  type: "scatter",
4448
4510
  data,
4449
4511
  ...hasSize ? { symbolSize: (val) => val[2] } : { symbolSize: defaultSize },
4450
- itemStyle: { color: catColor },
4512
+ itemStyle: { color: mix(catColor, bg, 30), borderColor: catColor, borderWidth: CHART_BORDER_WIDTH },
4451
4513
  label: labelConfig,
4452
4514
  emphasis: emphasisConfig
4453
4515
  };
4454
4516
  });
4455
4517
  } else {
4456
- const data = points.map((p, index) => ({
4457
- name: p.name,
4458
- value: hasSize ? [p.x, p.y, p.size ?? 0] : [p.x, p.y],
4459
- ...hasSize ? { symbolSize: p.size ?? defaultSize } : { symbolSize: defaultSize },
4460
- itemStyle: {
4461
- color: p.color ?? colors[index % colors.length]
4462
- }
4463
- }));
4518
+ const data = points.map((p, index) => {
4519
+ const stroke2 = p.color ?? colors[index % colors.length];
4520
+ return {
4521
+ name: p.name,
4522
+ value: hasSize ? [p.x, p.y, p.size ?? 0] : [p.x, p.y],
4523
+ ...hasSize ? { symbolSize: p.size ?? defaultSize } : { symbolSize: defaultSize },
4524
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
4525
+ };
4526
+ });
4464
4527
  series = [
4465
4528
  {
4466
4529
  type: "scatter",
@@ -4563,7 +4626,8 @@ function buildScatterOption(parsed, palette, textColor, axisLineColor, gridOpaci
4563
4626
  series
4564
4627
  };
4565
4628
  }
4566
- function buildHeatmapOption(parsed, palette, textColor, axisLineColor, titleConfig, tooltipTheme) {
4629
+ function buildHeatmapOption(parsed, palette, isDark, textColor, axisLineColor, titleConfig, tooltipTheme) {
4630
+ const bg = isDark ? palette.surface : palette.bg;
4567
4631
  const heatmapRows = parsed.heatmapRows ?? [];
4568
4632
  const columns = parsed.columns ?? [];
4569
4633
  const rowLabels = heatmapRows.map((r) => r.label);
@@ -4634,10 +4698,10 @@ function buildHeatmapOption(parsed, palette, textColor, axisLineColor, titleConf
4634
4698
  top: "center",
4635
4699
  inRange: {
4636
4700
  color: [
4637
- palette.primary,
4638
- palette.colors.cyan,
4639
- palette.colors.yellow,
4640
- palette.colors.orange
4701
+ mix(palette.primary, bg, 30),
4702
+ mix(palette.colors.cyan, bg, 30),
4703
+ mix(palette.colors.yellow, bg, 30),
4704
+ mix(palette.colors.orange, bg, 30)
4641
4705
  ]
4642
4706
  },
4643
4707
  textStyle: {
@@ -4648,9 +4712,13 @@ function buildHeatmapOption(parsed, palette, textColor, axisLineColor, titleConf
4648
4712
  {
4649
4713
  type: "heatmap",
4650
4714
  data,
4715
+ itemStyle: {
4716
+ borderWidth: 2,
4717
+ borderColor: bg
4718
+ },
4651
4719
  label: {
4652
4720
  show: true,
4653
- color: "#ffffff",
4721
+ color: textColor,
4654
4722
  fontSize: 14,
4655
4723
  fontWeight: "bold"
4656
4724
  },
@@ -4665,17 +4733,21 @@ function buildHeatmapOption(parsed, palette, textColor, axisLineColor, titleConf
4665
4733
  ]
4666
4734
  };
4667
4735
  }
4668
- function buildFunnelOption(parsed, textColor, colors, titleConfig, tooltipTheme) {
4736
+ function buildFunnelOption(parsed, textColor, colors, bg, titleConfig, tooltipTheme) {
4669
4737
  const sorted = [...parsed.data].sort((a, b) => b.value - a.value);
4670
4738
  const topValue = sorted.length > 0 ? sorted[0].value : 1;
4671
- const data = sorted.map((d) => ({
4672
- name: d.label,
4673
- value: d.value,
4674
- itemStyle: {
4675
- color: d.color ?? colors[parsed.data.indexOf(d) % colors.length],
4676
- borderWidth: 0
4677
- }
4678
- }));
4739
+ const data = sorted.map((d) => {
4740
+ const stroke2 = d.color ?? colors[parsed.data.indexOf(d) % colors.length];
4741
+ return {
4742
+ name: d.label,
4743
+ value: d.value,
4744
+ itemStyle: {
4745
+ color: mix(stroke2, bg, 30),
4746
+ borderColor: stroke2,
4747
+ borderWidth: CHART_BORDER_WIDTH
4748
+ }
4749
+ };
4750
+ });
4679
4751
  const prevValueMap = /* @__PURE__ */ new Map();
4680
4752
  for (let i = 0; i < sorted.length; i++) {
4681
4753
  prevValueMap.set(
@@ -4821,23 +4893,24 @@ function makeGridAxis(type, textColor, axisLineColor, splitLineColor, gridOpacit
4821
4893
  function buildSimpleChartOption(parsed, palette, isDark, chartWidth) {
4822
4894
  if (parsed.error) return {};
4823
4895
  const { textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme } = buildChartCommons(parsed, palette, isDark);
4896
+ const bg = isDark ? palette.surface : palette.bg;
4824
4897
  switch (parsed.type) {
4825
4898
  case "bar":
4826
- return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
4899
+ return buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth);
4827
4900
  case "bar-stacked":
4828
- return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth);
4901
+ return buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth);
4829
4902
  case "line":
4830
4903
  return parsed.seriesNames ? buildMultiLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) : buildLineOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
4831
4904
  case "area":
4832
4905
  return buildAreaOption(parsed, palette, textColor, axisLineColor, splitLineColor, gridOpacity, titleConfig, tooltipTheme, chartWidth);
4833
4906
  case "pie":
4834
- return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, false);
4907
+ return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme, false);
4835
4908
  case "doughnut":
4836
- return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme, true);
4909
+ return buildPieOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme, true);
4837
4910
  case "radar":
4838
- return buildRadarOption(parsed, palette, textColor, gridOpacity, colors, titleConfig, tooltipTheme);
4911
+ return buildRadarOption(parsed, palette, isDark, textColor, gridOpacity, titleConfig, tooltipTheme);
4839
4912
  case "polar-area":
4840
- return buildPolarAreaOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), titleConfig, tooltipTheme);
4913
+ return buildPolarAreaOption(parsed, textColor, getSegmentColors(palette, parsed.data.length), bg, titleConfig, tooltipTheme);
4841
4914
  }
4842
4915
  }
4843
4916
  function makeChartGrid(options) {
@@ -4849,14 +4922,17 @@ function makeChartGrid(options) {
4849
4922
  containLabel: true
4850
4923
  };
4851
4924
  }
4852
- function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) {
4925
+ function buildBarOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth) {
4853
4926
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
4854
4927
  const isHorizontal = parsed.orientation === "horizontal";
4855
4928
  const labels = parsed.data.map((d) => d.label);
4856
- const data = parsed.data.map((d, i) => ({
4857
- value: d.value,
4858
- itemStyle: { color: d.color ?? colors[i % colors.length] }
4859
- }));
4929
+ const data = parsed.data.map((d, i) => {
4930
+ const stroke2 = d.color ?? colors[i % colors.length];
4931
+ return {
4932
+ value: d.value,
4933
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
4934
+ };
4935
+ });
4860
4936
  const hCatGap = isHorizontal && yLabel ? Math.max(40, Math.max(...labels.map((l) => l.length)) * 8 + 16) : void 0;
4861
4937
  const categoryAxis = makeGridAxis("category", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? yLabel : xLabel, labels, hCatGap, !isHorizontal ? chartWidth : void 0);
4862
4938
  const valueAxis = makeGridAxis("value", textColor, axisLineColor, splitLineColor, gridOpacity, isHorizontal ? xLabel : yLabel);
@@ -5037,12 +5113,15 @@ function segmentLabelFormatter(mode) {
5037
5113
  return "{b} \u2014 {c} ({d}%)";
5038
5114
  }
5039
5115
  }
5040
- function buildPieOption(parsed, textColor, colors, titleConfig, tooltipTheme, isDoughnut) {
5041
- const data = parsed.data.map((d, i) => ({
5042
- name: d.label,
5043
- value: d.value,
5044
- itemStyle: { color: d.color ?? colors[i % colors.length] }
5045
- }));
5116
+ function buildPieOption(parsed, textColor, colors, bg, titleConfig, tooltipTheme, isDoughnut) {
5117
+ const data = parsed.data.map((d, i) => {
5118
+ const stroke2 = d.color ?? colors[i % colors.length];
5119
+ return {
5120
+ name: d.label,
5121
+ value: d.value,
5122
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
5123
+ };
5124
+ });
5046
5125
  return {
5047
5126
  ...CHART_BASE,
5048
5127
  title: titleConfig,
@@ -5067,7 +5146,8 @@ function buildPieOption(parsed, textColor, colors, titleConfig, tooltipTheme, is
5067
5146
  ]
5068
5147
  };
5069
5148
  }
5070
- function buildRadarOption(parsed, palette, textColor, gridOpacity, colors, titleConfig, tooltipTheme) {
5149
+ function buildRadarOption(parsed, palette, isDark, textColor, gridOpacity, titleConfig, tooltipTheme) {
5150
+ const bg = isDark ? palette.surface : palette.bg;
5071
5151
  const radarColor = parsed.color ?? parsed.seriesNameColors?.[0] ?? palette.primary;
5072
5152
  const values = parsed.data.map((d) => d.value);
5073
5153
  const maxValue = Math.max(...values) * 1.15;
@@ -5104,7 +5184,7 @@ function buildRadarOption(parsed, palette, textColor, gridOpacity, colors, title
5104
5184
  {
5105
5185
  value: values,
5106
5186
  name: parsed.series ?? "Value",
5107
- areaStyle: { color: radarColor, opacity: 0.25 },
5187
+ areaStyle: { color: mix(radarColor, bg, 30) },
5108
5188
  lineStyle: { color: radarColor },
5109
5189
  itemStyle: { color: radarColor },
5110
5190
  symbol: "circle",
@@ -5123,12 +5203,15 @@ function buildRadarOption(parsed, palette, textColor, gridOpacity, colors, title
5123
5203
  ]
5124
5204
  };
5125
5205
  }
5126
- function buildPolarAreaOption(parsed, textColor, colors, titleConfig, tooltipTheme) {
5127
- const data = parsed.data.map((d, i) => ({
5128
- name: d.label,
5129
- value: d.value,
5130
- itemStyle: { color: d.color ?? colors[i % colors.length] }
5131
- }));
5206
+ function buildPolarAreaOption(parsed, textColor, colors, bg, titleConfig, tooltipTheme) {
5207
+ const data = parsed.data.map((d, i) => {
5208
+ const stroke2 = d.color ?? colors[i % colors.length];
5209
+ return {
5210
+ name: d.label,
5211
+ value: d.value,
5212
+ itemStyle: { color: mix(stroke2, bg, 30), borderColor: stroke2, borderWidth: CHART_BORDER_WIDTH }
5213
+ };
5214
+ });
5132
5215
  return {
5133
5216
  ...CHART_BASE,
5134
5217
  title: titleConfig,
@@ -5154,7 +5237,7 @@ function buildPolarAreaOption(parsed, textColor, colors, titleConfig, tooltipThe
5154
5237
  ]
5155
5238
  };
5156
5239
  }
5157
- function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, titleConfig, tooltipTheme, chartWidth) {
5240
+ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor, gridOpacity, colors, bg, titleConfig, tooltipTheme, chartWidth) {
5158
5241
  const { xLabel, yLabel } = resolveAxisLabels(parsed);
5159
5242
  const isHorizontal = parsed.orientation === "horizontal";
5160
5243
  const seriesNames = parsed.seriesNames ?? [];
@@ -5169,12 +5252,12 @@ function buildBarStackedOption(parsed, textColor, axisLineColor, splitLineColor,
5169
5252
  type: "bar",
5170
5253
  stack: "total",
5171
5254
  data,
5172
- itemStyle: { color },
5255
+ itemStyle: { color: mix(color, bg, 30), borderColor: color, borderWidth: CHART_BORDER_WIDTH },
5173
5256
  label: {
5174
5257
  show: true,
5175
5258
  position: "inside",
5176
5259
  formatter: "{c}",
5177
- color: "#ffffff",
5260
+ color: textColor,
5178
5261
  fontSize: 14,
5179
5262
  fontWeight: "bold",
5180
5263
  fontFamily: FONT_FAMILY
@@ -5246,19 +5329,21 @@ async function renderExtendedChartForExport(content, theme, palette, options) {
5246
5329
  chart.dispose();
5247
5330
  }
5248
5331
  }
5249
- var EMPHASIS_SELF, CHART_BASE, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
5332
+ var EMPHASIS_SELF, CHART_BASE, CHART_BORDER_WIDTH, ECHART_EXPORT_WIDTH, ECHART_EXPORT_HEIGHT, STANDARD_CHART_TYPES;
5250
5333
  var init_echarts = __esm({
5251
5334
  "src/echarts.ts"() {
5252
5335
  "use strict";
5253
5336
  init_fonts();
5254
5337
  init_branding();
5255
5338
  init_palettes();
5339
+ init_color_utils();
5256
5340
  init_chart();
5257
5341
  init_diagnostics();
5258
5342
  init_colors();
5259
5343
  init_parsing();
5260
5344
  EMPHASIS_SELF = { focus: "self", blurScope: "global" };
5261
5345
  CHART_BASE = { backgroundColor: "transparent", animation: false };
5346
+ CHART_BORDER_WIDTH = 2;
5262
5347
  ECHART_EXPORT_WIDTH = 1200;
5263
5348
  ECHART_EXPORT_HEIGHT = 800;
5264
5349
  STANDARD_CHART_TYPES = /* @__PURE__ */ new Set([
@@ -6973,6 +7058,7 @@ var init_types2 = __esm({
6973
7058
  // src/infra/parser.ts
6974
7059
  var parser_exports9 = {};
6975
7060
  __export(parser_exports9, {
7061
+ extractSymbols: () => extractSymbols4,
6976
7062
  parseInfra: () => parseInfra
6977
7063
  });
6978
7064
  function nodeId2(name) {
@@ -7015,7 +7101,6 @@ function parseInfra(content) {
7015
7101
  error: null
7016
7102
  };
7017
7103
  const nodeMap = /* @__PURE__ */ new Map();
7018
- const edgeNodeId = "edge";
7019
7104
  const setError = (line10, message) => {
7020
7105
  const diag = makeDgmoError(line10, message);
7021
7106
  result.diagnostics.push(diag);
@@ -7381,6 +7466,38 @@ function parseInfra(content) {
7381
7466
  }
7382
7467
  return result;
7383
7468
  }
7469
+ function extractSymbols4(docText) {
7470
+ const entities = [];
7471
+ let inMetadata = true;
7472
+ let inTagGroup = false;
7473
+ for (const rawLine of docText.split("\n")) {
7474
+ const line10 = rawLine.trim();
7475
+ if (line10.length === 0) continue;
7476
+ const indented = /^\s/.test(rawLine);
7477
+ if (inMetadata) {
7478
+ if (!indented && !/^[a-z-]+\s*:/i.test(line10)) inMetadata = false;
7479
+ else continue;
7480
+ }
7481
+ if (!indented) {
7482
+ if (/^tag\s*:/i.test(line10)) {
7483
+ inTagGroup = true;
7484
+ continue;
7485
+ }
7486
+ inTagGroup = false;
7487
+ if (/^\[/.test(line10)) continue;
7488
+ const m = COMPONENT_RE.exec(line10);
7489
+ if (m && !entities.includes(m[1])) entities.push(m[1]);
7490
+ } else {
7491
+ if (inTagGroup) continue;
7492
+ if (/^->/.test(line10)) continue;
7493
+ if (/^-[^>]+-?>/.test(line10)) continue;
7494
+ if (/^\w[\w-]*\s*:/.test(line10)) continue;
7495
+ const m = COMPONENT_RE.exec(line10);
7496
+ if (m && !entities.includes(m[1])) entities.push(m[1]);
7497
+ }
7498
+ }
7499
+ return { kind: "infra", entities, keywords: [] };
7500
+ }
7384
7501
  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;
7385
7502
  var init_parser9 = __esm({
7386
7503
  "src/infra/parser.ts"() {
@@ -8244,7 +8361,6 @@ function layoutOrg(parsed, hiddenCounts, activeTagGroup, hiddenAttributes, expan
8244
8361
  const visibleGroups = activeTagGroup != null ? legendGroups.filter((g) => g.name.toLowerCase() === activeTagGroup.toLowerCase()) : legendGroups;
8245
8362
  const allExpanded = expandAllLegend && activeTagGroup == null;
8246
8363
  const effectiveW = (g) => activeTagGroup != null || allExpanded ? g.width : g.minifiedWidth;
8247
- const effectiveH = (g) => activeTagGroup != null || allExpanded ? g.height : g.minifiedHeight;
8248
8364
  if (visibleGroups.length > 0) {
8249
8365
  if (legendPosition === "bottom") {
8250
8366
  const totalGroupsWidth = visibleGroups.reduce((s, g) => s + effectiveW(g), 0) + (visibleGroups.length - 1) * LEGEND_GROUP_GAP2;
@@ -8397,6 +8513,35 @@ var init_collapse = __esm({
8397
8513
  }
8398
8514
  });
8399
8515
 
8516
+ // src/utils/export-container.ts
8517
+ function runInExportContainer(width, height, fn) {
8518
+ const container = document.createElement("div");
8519
+ container.style.width = `${width}px`;
8520
+ container.style.height = `${height}px`;
8521
+ container.style.position = "absolute";
8522
+ container.style.left = "-9999px";
8523
+ document.body.appendChild(container);
8524
+ try {
8525
+ return fn(container);
8526
+ } finally {
8527
+ document.body.removeChild(container);
8528
+ }
8529
+ }
8530
+ function extractExportSvg(container, theme) {
8531
+ const svgEl = container.querySelector("svg");
8532
+ if (!svgEl) return "";
8533
+ if (theme === "transparent") svgEl.style.background = "none";
8534
+ svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
8535
+ svgEl.style.fontFamily = FONT_FAMILY;
8536
+ return svgEl.outerHTML;
8537
+ }
8538
+ var init_export_container = __esm({
8539
+ "src/utils/export-container.ts"() {
8540
+ "use strict";
8541
+ init_fonts();
8542
+ }
8543
+ });
8544
+
8400
8545
  // src/org/renderer.ts
8401
8546
  var renderer_exports = {};
8402
8547
  __export(renderer_exports, {
@@ -8652,37 +8797,23 @@ function renderOrgForExport(content, theme, palette) {
8652
8797
  const exportHidden = hideOption ? new Set(hideOption.split(",").map((s) => s.trim().toLowerCase())) : void 0;
8653
8798
  const layout = layoutOrg(parsed, void 0, void 0, exportHidden);
8654
8799
  const isDark = theme === "dark";
8655
- const container = document.createElement("div");
8656
8800
  const titleOffset = parsed.title ? TITLE_HEIGHT : 0;
8657
8801
  const exportWidth = layout.width + DIAGRAM_PADDING * 2;
8658
8802
  const exportHeight = layout.height + DIAGRAM_PADDING * 2 + titleOffset;
8659
- container.style.width = `${exportWidth}px`;
8660
- container.style.height = `${exportHeight}px`;
8661
- container.style.position = "absolute";
8662
- container.style.left = "-9999px";
8663
- document.body.appendChild(container);
8664
- try {
8803
+ return runInExportContainer(exportWidth, exportHeight, (container) => {
8665
8804
  renderOrg(container, parsed, layout, palette, isDark, void 0, {
8666
8805
  width: exportWidth,
8667
8806
  height: exportHeight
8668
8807
  });
8669
- const svgEl = container.querySelector("svg");
8670
- if (!svgEl) return "";
8671
- if (theme === "transparent") {
8672
- svgEl.style.background = "none";
8673
- }
8674
- svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
8675
- svgEl.style.fontFamily = FONT_FAMILY;
8676
- return svgEl.outerHTML;
8677
- } finally {
8678
- document.body.removeChild(container);
8679
- }
8808
+ return extractExportSvg(container, theme);
8809
+ });
8680
8810
  }
8681
8811
  var 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;
8682
8812
  var init_renderer = __esm({
8683
8813
  "src/org/renderer.ts"() {
8684
8814
  "use strict";
8685
8815
  init_fonts();
8816
+ init_export_container();
8686
8817
  init_color_utils();
8687
8818
  init_parser4();
8688
8819
  init_layout();
@@ -10270,7 +10401,6 @@ function renderClassDiagram(container, parsed, layout, palette, isDark, onClickI
10270
10401
  const scaleY = (availH - DIAGRAM_PADDING4 * 2) / diagramH;
10271
10402
  const scale = Math.min(MAX_SCALE3, scaleX, scaleY);
10272
10403
  const scaledW = diagramW * scale;
10273
- const scaledH = diagramH * scale;
10274
10404
  const offsetX = (width - scaledW) / 2;
10275
10405
  const offsetY = titleHeight + DIAGRAM_PADDING4;
10276
10406
  const svg = d3Selection4.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -10401,15 +10531,9 @@ function renderClassDiagramForExport(content, theme, palette) {
10401
10531
  if (parsed.error || parsed.classes.length === 0) return "";
10402
10532
  const layout = layoutClassDiagram(parsed);
10403
10533
  const isDark = theme === "dark";
10404
- const container = document.createElement("div");
10405
10534
  const exportWidth = layout.width + DIAGRAM_PADDING4 * 2;
10406
10535
  const exportHeight = layout.height + DIAGRAM_PADDING4 * 2 + (parsed.title ? 40 : 0);
10407
- container.style.width = `${exportWidth}px`;
10408
- container.style.height = `${exportHeight}px`;
10409
- container.style.position = "absolute";
10410
- container.style.left = "-9999px";
10411
- document.body.appendChild(container);
10412
- try {
10536
+ return runInExportContainer(exportWidth, exportHeight, (container) => {
10413
10537
  renderClassDiagram(
10414
10538
  container,
10415
10539
  parsed,
@@ -10419,23 +10543,15 @@ function renderClassDiagramForExport(content, theme, palette) {
10419
10543
  void 0,
10420
10544
  { width: exportWidth, height: exportHeight }
10421
10545
  );
10422
- const svgEl = container.querySelector("svg");
10423
- if (!svgEl) return "";
10424
- if (theme === "transparent") {
10425
- svgEl.style.background = "none";
10426
- }
10427
- svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
10428
- svgEl.style.fontFamily = FONT_FAMILY;
10429
- return svgEl.outerHTML;
10430
- } finally {
10431
- document.body.removeChild(container);
10432
- }
10546
+ return extractExportSvg(container, theme);
10547
+ });
10433
10548
  }
10434
10549
  var 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;
10435
10550
  var init_renderer4 = __esm({
10436
10551
  "src/class/renderer.ts"() {
10437
10552
  "use strict";
10438
10553
  init_fonts();
10554
+ init_export_container();
10439
10555
  init_color_utils();
10440
10556
  init_parser2();
10441
10557
  init_layout3();
@@ -10476,110 +10592,211 @@ function computeNodeDimensions2(table) {
10476
10592
  const height = headerHeight + columnsHeight + (columnsHeight === 0 ? 4 : 0);
10477
10593
  return { width, height, headerHeight, columnsHeight };
10478
10594
  }
10595
+ function findConnectedComponents(tableIds, relationships) {
10596
+ const adj = /* @__PURE__ */ new Map();
10597
+ for (const id of tableIds) adj.set(id, /* @__PURE__ */ new Set());
10598
+ for (const rel of relationships) {
10599
+ adj.get(rel.source)?.add(rel.target);
10600
+ adj.get(rel.target)?.add(rel.source);
10601
+ }
10602
+ const visited = /* @__PURE__ */ new Set();
10603
+ const components = [];
10604
+ for (const id of tableIds) {
10605
+ if (visited.has(id)) continue;
10606
+ const comp = [];
10607
+ const queue = [id];
10608
+ while (queue.length > 0) {
10609
+ const cur = queue.shift();
10610
+ if (visited.has(cur)) continue;
10611
+ visited.add(cur);
10612
+ comp.push(cur);
10613
+ for (const nb of adj.get(cur) ?? []) {
10614
+ if (!visited.has(nb)) queue.push(nb);
10615
+ }
10616
+ }
10617
+ components.push(comp);
10618
+ }
10619
+ return components;
10620
+ }
10621
+ function layoutComponent(tables, rels, dimMap) {
10622
+ const nodePositions = /* @__PURE__ */ new Map();
10623
+ const edgePoints = /* @__PURE__ */ new Map();
10624
+ if (tables.length === 1) {
10625
+ const dims = dimMap.get(tables[0].id);
10626
+ nodePositions.set(tables[0].id, { x: dims.width / 2, y: dims.height / 2, ...dims });
10627
+ return { nodePositions, edgePoints, width: dims.width, height: dims.height };
10628
+ }
10629
+ const g = new dagre3.graphlib.Graph({ multigraph: true });
10630
+ g.setGraph({ rankdir: "LR", nodesep: 40, ranksep: 80, edgesep: 20 });
10631
+ g.setDefaultEdgeLabel(() => ({}));
10632
+ for (const table of tables) {
10633
+ const dims = dimMap.get(table.id);
10634
+ g.setNode(table.id, { width: dims.width, height: dims.height });
10635
+ }
10636
+ for (const rel of rels) {
10637
+ g.setEdge(rel.source, rel.target, { label: rel.label ?? "" }, String(rel.lineNumber));
10638
+ }
10639
+ dagre3.layout(g);
10640
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
10641
+ for (const table of tables) {
10642
+ const pos = g.node(table.id);
10643
+ const dims = dimMap.get(table.id);
10644
+ minX = Math.min(minX, pos.x - dims.width / 2);
10645
+ minY = Math.min(minY, pos.y - dims.height / 2);
10646
+ maxX = Math.max(maxX, pos.x + dims.width / 2);
10647
+ maxY = Math.max(maxY, pos.y + dims.height / 2);
10648
+ }
10649
+ for (const rel of rels) {
10650
+ const ed = g.edge(rel.source, rel.target, String(rel.lineNumber));
10651
+ for (const pt of ed?.points ?? []) {
10652
+ minX = Math.min(minX, pt.x);
10653
+ minY = Math.min(minY, pt.y);
10654
+ maxX = Math.max(maxX, pt.x);
10655
+ maxY = Math.max(maxY, pt.y);
10656
+ }
10657
+ if (rel.label && (ed?.points ?? []).length > 0) {
10658
+ const pts = ed.points;
10659
+ const mid = pts[Math.floor(pts.length / 2)];
10660
+ const hw = (rel.label.length * 7 + 8) / 2;
10661
+ minX = Math.min(minX, mid.x - hw);
10662
+ maxX = Math.max(maxX, mid.x + hw);
10663
+ }
10664
+ }
10665
+ for (const table of tables) {
10666
+ const pos = g.node(table.id);
10667
+ const dims = dimMap.get(table.id);
10668
+ nodePositions.set(table.id, {
10669
+ x: pos.x - minX,
10670
+ y: pos.y - minY,
10671
+ ...dims
10672
+ });
10673
+ }
10674
+ for (const rel of rels) {
10675
+ const ed = g.edge(rel.source, rel.target, String(rel.lineNumber));
10676
+ edgePoints.set(
10677
+ rel.lineNumber,
10678
+ (ed?.points ?? []).map((pt) => ({ x: pt.x - minX, y: pt.y - minY }))
10679
+ );
10680
+ }
10681
+ return {
10682
+ nodePositions,
10683
+ edgePoints,
10684
+ width: Math.max(0, maxX - minX),
10685
+ height: Math.max(0, maxY - minY)
10686
+ };
10687
+ }
10688
+ function packComponents(items) {
10689
+ if (items.length === 0) return [];
10690
+ const sorted = [...items].sort((a, b) => {
10691
+ const aConnected = a.compIds.length > 1 ? 1 : 0;
10692
+ const bConnected = b.compIds.length > 1 ? 1 : 0;
10693
+ if (aConnected !== bConnected) return bConnected - aConnected;
10694
+ return b.compLayout.height - a.compLayout.height;
10695
+ });
10696
+ const totalArea = items.reduce(
10697
+ (s, c) => s + (c.compLayout.width || MIN_WIDTH2) * (c.compLayout.height || HEADER_BASE2),
10698
+ 0
10699
+ );
10700
+ const targetW = Math.max(
10701
+ Math.sqrt(totalArea) * 1.5,
10702
+ sorted[0].compLayout.width
10703
+ // at least as wide as the widest component
10704
+ );
10705
+ const placements = [];
10706
+ let curX = 0;
10707
+ let curY = 0;
10708
+ let rowH = 0;
10709
+ for (const item of sorted) {
10710
+ const w = item.compLayout.width || MIN_WIDTH2;
10711
+ const h = item.compLayout.height || HEADER_BASE2;
10712
+ if (curX > 0 && curX + w > targetW) {
10713
+ curY += rowH + COMP_GAP;
10714
+ curX = 0;
10715
+ rowH = 0;
10716
+ }
10717
+ placements.push({ compIds: item.compIds, compLayout: item.compLayout, offsetX: curX, offsetY: curY });
10718
+ curX += w + COMP_GAP;
10719
+ rowH = Math.max(rowH, h);
10720
+ }
10721
+ return placements;
10722
+ }
10479
10723
  function layoutERDiagram(parsed) {
10480
10724
  if (parsed.tables.length === 0) {
10481
10725
  return { nodes: [], edges: [], width: 0, height: 0 };
10482
10726
  }
10483
- const g = new dagre3.graphlib.Graph();
10484
- g.setGraph({
10485
- rankdir: "TB",
10486
- nodesep: 60,
10487
- ranksep: 80,
10488
- edgesep: 20
10489
- });
10490
- g.setDefaultEdgeLabel(() => ({}));
10491
10727
  const dimMap = /* @__PURE__ */ new Map();
10492
10728
  for (const table of parsed.tables) {
10493
- const dims = computeNodeDimensions2(table);
10494
- dimMap.set(table.id, dims);
10495
- g.setNode(table.id, {
10496
- label: table.name,
10497
- width: dims.width,
10498
- height: dims.height
10499
- });
10729
+ dimMap.set(table.id, computeNodeDimensions2(table));
10500
10730
  }
10501
- for (const rel of parsed.relationships) {
10502
- g.setEdge(rel.source, rel.target, { label: rel.label ?? "" });
10731
+ const compIdSets = findConnectedComponents(
10732
+ parsed.tables.map((t) => t.id),
10733
+ parsed.relationships
10734
+ );
10735
+ const tableById = new Map(parsed.tables.map((t) => [t.id, t]));
10736
+ const componentItems = compIdSets.map((ids) => {
10737
+ const tables = ids.map((id) => tableById.get(id));
10738
+ const rels = parsed.relationships.filter((r) => ids.includes(r.source));
10739
+ return { compIds: ids, compLayout: layoutComponent(tables, rels, dimMap) };
10740
+ });
10741
+ const packed = packComponents(componentItems);
10742
+ const placementByTableId = /* @__PURE__ */ new Map();
10743
+ for (const p of packed) {
10744
+ for (const id of p.compIds) placementByTableId.set(id, p);
10745
+ }
10746
+ const placementByRelLine = /* @__PURE__ */ new Map();
10747
+ for (const p of packed) {
10748
+ for (const lineNum of p.compLayout.edgePoints.keys()) {
10749
+ placementByRelLine.set(lineNum, p);
10750
+ }
10503
10751
  }
10504
- dagre3.layout(g);
10505
10752
  const layoutNodes = parsed.tables.map((table) => {
10506
- const pos = g.node(table.id);
10507
- const dims = dimMap.get(table.id);
10753
+ const p = placementByTableId.get(table.id);
10754
+ const pos = p.compLayout.nodePositions.get(table.id);
10508
10755
  return {
10509
10756
  ...table,
10510
- x: pos.x,
10511
- y: pos.y,
10512
- width: dims.width,
10513
- height: dims.height,
10514
- headerHeight: dims.headerHeight,
10515
- columnsHeight: dims.columnsHeight
10757
+ x: pos.x + p.offsetX + HALF_MARGIN,
10758
+ y: pos.y + p.offsetY + HALF_MARGIN,
10759
+ width: pos.width,
10760
+ height: pos.height,
10761
+ headerHeight: pos.headerHeight,
10762
+ columnsHeight: pos.columnsHeight
10516
10763
  };
10517
10764
  });
10518
10765
  const layoutEdges = parsed.relationships.map((rel) => {
10519
- const edgeData = g.edge(rel.source, rel.target);
10766
+ const p = placementByRelLine.get(rel.lineNumber);
10767
+ const pts = p?.compLayout.edgePoints.get(rel.lineNumber) ?? [];
10520
10768
  return {
10521
10769
  source: rel.source,
10522
10770
  target: rel.target,
10523
10771
  cardinality: rel.cardinality,
10524
- points: edgeData?.points ?? [],
10772
+ points: pts.map((pt) => ({
10773
+ x: pt.x + (p?.offsetX ?? 0) + HALF_MARGIN,
10774
+ y: pt.y + (p?.offsetY ?? 0) + HALF_MARGIN
10775
+ })),
10525
10776
  label: rel.label,
10526
10777
  lineNumber: rel.lineNumber
10527
10778
  };
10528
10779
  });
10529
- let minX = Infinity;
10530
- let minY = Infinity;
10531
10780
  let maxX = 0;
10532
10781
  let maxY = 0;
10533
10782
  for (const node of layoutNodes) {
10534
- const left = node.x - node.width / 2;
10535
- const right = node.x + node.width / 2;
10536
- const top = node.y - node.height / 2;
10537
- const bottom = node.y + node.height / 2;
10538
- if (left < minX) minX = left;
10539
- if (right > maxX) maxX = right;
10540
- if (top < minY) minY = top;
10541
- if (bottom > maxY) maxY = bottom;
10783
+ maxX = Math.max(maxX, node.x + node.width / 2);
10784
+ maxY = Math.max(maxY, node.y + node.height / 2);
10542
10785
  }
10543
10786
  for (const edge of layoutEdges) {
10544
10787
  for (const pt of edge.points) {
10545
- if (pt.x < minX) minX = pt.x;
10546
- if (pt.x > maxX) maxX = pt.x;
10547
- if (pt.y < minY) minY = pt.y;
10548
- if (pt.y > maxY) maxY = pt.y;
10549
- }
10550
- if (edge.label && edge.points.length > 0) {
10551
- const midPt = edge.points[Math.floor(edge.points.length / 2)];
10552
- const labelHalfW = (edge.label.length * 7 + 8) / 2;
10553
- if (midPt.x + labelHalfW > maxX) maxX = midPt.x + labelHalfW;
10554
- if (midPt.x - labelHalfW < minX) minX = midPt.x - labelHalfW;
10788
+ maxX = Math.max(maxX, pt.x);
10789
+ maxY = Math.max(maxY, pt.y);
10555
10790
  }
10556
10791
  }
10557
- const EDGE_MARGIN2 = 60;
10558
- const HALF_MARGIN = EDGE_MARGIN2 / 2;
10559
- const shiftX = -minX + HALF_MARGIN;
10560
- const shiftY = -minY + HALF_MARGIN;
10561
- for (const node of layoutNodes) {
10562
- node.x += shiftX;
10563
- node.y += shiftY;
10564
- }
10565
- for (const edge of layoutEdges) {
10566
- for (const pt of edge.points) {
10567
- pt.x += shiftX;
10568
- pt.y += shiftY;
10569
- }
10570
- }
10571
- maxX += shiftX;
10572
- maxY += shiftY;
10573
- const totalWidth = maxX + HALF_MARGIN;
10574
- const totalHeight = maxY + HALF_MARGIN;
10575
10792
  return {
10576
10793
  nodes: layoutNodes,
10577
10794
  edges: layoutEdges,
10578
- width: totalWidth,
10579
- height: totalHeight
10795
+ width: maxX + HALF_MARGIN,
10796
+ height: maxY + HALF_MARGIN
10580
10797
  };
10581
10798
  }
10582
- var MIN_WIDTH2, CHAR_WIDTH4, PADDING_X2, HEADER_BASE2, MEMBER_LINE_HEIGHT3, COMPARTMENT_PADDING_Y3, SEPARATOR_HEIGHT2;
10799
+ var MIN_WIDTH2, CHAR_WIDTH4, PADDING_X2, HEADER_BASE2, MEMBER_LINE_HEIGHT3, COMPARTMENT_PADDING_Y3, SEPARATOR_HEIGHT2, HALF_MARGIN, COMP_GAP;
10583
10800
  var init_layout4 = __esm({
10584
10801
  "src/er/layout.ts"() {
10585
10802
  "use strict";
@@ -10590,6 +10807,135 @@ var init_layout4 = __esm({
10590
10807
  MEMBER_LINE_HEIGHT3 = 18;
10591
10808
  COMPARTMENT_PADDING_Y3 = 8;
10592
10809
  SEPARATOR_HEIGHT2 = 1;
10810
+ HALF_MARGIN = 30;
10811
+ COMP_GAP = 60;
10812
+ }
10813
+ });
10814
+
10815
+ // src/er/classify.ts
10816
+ function classifyEREntities(tables, relationships) {
10817
+ const result = /* @__PURE__ */ new Map();
10818
+ if (tables.length === 0) return result;
10819
+ const indegreeMap = {};
10820
+ for (const t of tables) indegreeMap[t.id] = 0;
10821
+ for (const rel of relationships) {
10822
+ if (rel.source === rel.target) continue;
10823
+ if (rel.cardinality.from === "1" && rel.cardinality.to !== "1") {
10824
+ indegreeMap[rel.source] = (indegreeMap[rel.source] ?? 0) + 1;
10825
+ }
10826
+ if (rel.cardinality.to === "1" && rel.cardinality.from !== "1") {
10827
+ indegreeMap[rel.target] = (indegreeMap[rel.target] ?? 0) + 1;
10828
+ }
10829
+ }
10830
+ const tableStarNeighbors = /* @__PURE__ */ new Map();
10831
+ for (const rel of relationships) {
10832
+ if (rel.source === rel.target) continue;
10833
+ if (rel.cardinality.from === "*") {
10834
+ if (!tableStarNeighbors.has(rel.source)) tableStarNeighbors.set(rel.source, /* @__PURE__ */ new Set());
10835
+ tableStarNeighbors.get(rel.source).add(rel.target);
10836
+ }
10837
+ if (rel.cardinality.to === "*") {
10838
+ if (!tableStarNeighbors.has(rel.target)) tableStarNeighbors.set(rel.target, /* @__PURE__ */ new Set());
10839
+ tableStarNeighbors.get(rel.target).add(rel.source);
10840
+ }
10841
+ }
10842
+ const mmParticipants = /* @__PURE__ */ new Set();
10843
+ for (const [id, neighbors] of tableStarNeighbors) {
10844
+ if (neighbors.size >= 2) mmParticipants.add(id);
10845
+ }
10846
+ const indegreeValues = Object.values(indegreeMap);
10847
+ const mean = indegreeValues.reduce((a, b) => a + b, 0) / indegreeValues.length;
10848
+ const variance = indegreeValues.reduce((a, b) => a + (b - mean) ** 2, 0) / indegreeValues.length;
10849
+ const stddev = Math.sqrt(variance);
10850
+ const sorted = [...indegreeValues].sort((a, b) => a - b);
10851
+ const median = sorted.length % 2 === 0 ? (sorted[sorted.length / 2 - 1] + sorted[sorted.length / 2]) / 2 : sorted[Math.floor(sorted.length / 2)];
10852
+ for (const table of tables) {
10853
+ const id = table.id;
10854
+ const cols = table.columns;
10855
+ const fkCols = cols.filter((c) => c.constraints.includes("fk"));
10856
+ const pkFkCols = cols.filter(
10857
+ (c) => c.constraints.includes("pk") && c.constraints.includes("fk")
10858
+ );
10859
+ const fkCount = fkCols.length;
10860
+ const fkRatio = cols.length === 0 ? 0 : fkCount / cols.length;
10861
+ const indegree = indegreeMap[id] ?? 0;
10862
+ const nameLower = table.name.toLowerCase();
10863
+ const externalRels = relationships.filter(
10864
+ (r) => (r.source === id || r.target === id) && r.source !== r.target
10865
+ );
10866
+ const hasSelfRef = relationships.some((r) => r.source === id && r.target === id);
10867
+ const externalTargets = /* @__PURE__ */ new Set();
10868
+ for (const rel of externalRels) {
10869
+ externalTargets.add(rel.source === id ? rel.target : rel.source);
10870
+ }
10871
+ if (hasSelfRef && externalRels.length === 0) {
10872
+ result.set(id, "self-referential");
10873
+ continue;
10874
+ }
10875
+ const isInheritancePattern = pkFkCols.length >= 2 && externalTargets.size === 1;
10876
+ const junctionByRatio = fkRatio >= 0.6 && !isInheritancePattern;
10877
+ const junctionByCompositePk = pkFkCols.length >= 2 && externalTargets.size >= 2;
10878
+ const junctionByMm = mmParticipants.has(id);
10879
+ if (junctionByRatio || junctionByCompositePk || junctionByMm) {
10880
+ result.set(id, "junction");
10881
+ continue;
10882
+ }
10883
+ if (fkRatio >= 0.4 && fkRatio < 0.6 && pkFkCols.length < 2 && !mmParticipants.has(id)) {
10884
+ result.set(id, "ambiguous");
10885
+ continue;
10886
+ }
10887
+ const nameMatchesLookup = LOOKUP_NAME_SUFFIXES.some((s) => nameLower.endsWith(s));
10888
+ if (nameMatchesLookup && cols.length <= 6 && fkCount <= 1 && indegree > median) {
10889
+ result.set(id, "lookup");
10890
+ continue;
10891
+ }
10892
+ if (tables.length >= 6 && indegree > 0 && indegree > mean + 1.5 * stddev && indegree >= 2 * mean) {
10893
+ result.set(id, "hub");
10894
+ continue;
10895
+ }
10896
+ if (fkCount > 0) {
10897
+ result.set(id, "dependent");
10898
+ continue;
10899
+ }
10900
+ result.set(id, "core");
10901
+ }
10902
+ return result;
10903
+ }
10904
+ var ROLE_COLORS, ROLE_LABELS, ROLE_ORDER, LOOKUP_NAME_SUFFIXES;
10905
+ var init_classify = __esm({
10906
+ "src/er/classify.ts"() {
10907
+ "use strict";
10908
+ ROLE_COLORS = {
10909
+ core: "green",
10910
+ dependent: "blue",
10911
+ junction: "red",
10912
+ ambiguous: "purple",
10913
+ lookup: "yellow",
10914
+ hub: "orange",
10915
+ "self-referential": "teal",
10916
+ unclassified: "gray"
10917
+ };
10918
+ ROLE_LABELS = {
10919
+ core: "Core entity",
10920
+ dependent: "Dependent",
10921
+ junction: "Junction / M:M",
10922
+ ambiguous: "Bridge",
10923
+ lookup: "Lookup / Reference",
10924
+ hub: "Hub",
10925
+ "self-referential": "Self-referential",
10926
+ unclassified: "Unclassified"
10927
+ };
10928
+ ROLE_ORDER = [
10929
+ "core",
10930
+ "dependent",
10931
+ "junction",
10932
+ "ambiguous",
10933
+ "lookup",
10934
+ "hub",
10935
+ "self-referential",
10936
+ "unclassified"
10937
+ ];
10938
+ LOOKUP_NAME_SUFFIXES = ["_type", "_status", "_code", "_category"];
10593
10939
  }
10594
10940
  });
10595
10941
 
@@ -10661,25 +11007,41 @@ function drawCardinality(g, point, prevPoint, cardinality, color, useLabels) {
10661
11007
  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);
10662
11008
  }
10663
11009
  }
10664
- function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup) {
11010
+ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem, exportDims, activeTagGroup, semanticColorsActive) {
10665
11011
  d3Selection5.select(container).selectAll(":not([data-d3-tooltip])").remove();
10666
- const width = exportDims?.width ?? container.clientWidth;
10667
- const height = exportDims?.height ?? container.clientHeight;
10668
- if (width <= 0 || height <= 0) return;
11012
+ const useSemanticColors = parsed.tagGroups.length === 0 && layout.nodes.every((n) => !n.color);
11013
+ const legendReserveH = useSemanticColors ? LEGEND_HEIGHT + DIAGRAM_PADDING5 : 0;
10669
11014
  const titleHeight = parsed.title ? 40 : 0;
10670
11015
  const diagramW = layout.width;
10671
11016
  const diagramH = layout.height;
10672
- const availH = height - titleHeight;
10673
- const scaleX = (width - DIAGRAM_PADDING5 * 2) / diagramW;
10674
- const scaleY = (availH - DIAGRAM_PADDING5 * 2) / diagramH;
10675
- const scale = Math.min(MAX_SCALE4, scaleX, scaleY);
10676
- const scaledW = diagramW * scale;
10677
- const scaledH = diagramH * scale;
10678
- const offsetX = (width - scaledW) / 2;
10679
- const offsetY = titleHeight + DIAGRAM_PADDING5;
10680
- const svg = d3Selection5.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
11017
+ const naturalW = diagramW + DIAGRAM_PADDING5 * 2;
11018
+ const naturalH = diagramH + titleHeight + legendReserveH + DIAGRAM_PADDING5 * 2;
11019
+ let viewW;
11020
+ let viewH;
11021
+ let scale;
11022
+ let offsetX;
11023
+ let offsetY;
11024
+ if (exportDims) {
11025
+ viewW = exportDims.width ?? naturalW;
11026
+ viewH = exportDims.height ?? naturalH;
11027
+ const availH = viewH - titleHeight - legendReserveH;
11028
+ const scaleX = (viewW - DIAGRAM_PADDING5 * 2) / diagramW;
11029
+ const scaleY = (availH - DIAGRAM_PADDING5 * 2) / diagramH;
11030
+ scale = Math.min(MAX_SCALE4, scaleX, scaleY);
11031
+ const scaledW = diagramW * scale;
11032
+ offsetX = (viewW - scaledW) / 2;
11033
+ offsetY = titleHeight + DIAGRAM_PADDING5;
11034
+ } else {
11035
+ viewW = naturalW;
11036
+ viewH = naturalH;
11037
+ scale = 1;
11038
+ offsetX = DIAGRAM_PADDING5;
11039
+ offsetY = titleHeight + DIAGRAM_PADDING5;
11040
+ }
11041
+ if (viewW <= 0 || viewH <= 0) return;
11042
+ 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);
10681
11043
  if (parsed.title) {
10682
- 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);
11044
+ 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);
10683
11045
  if (parsed.titleLineNumber) {
10684
11046
  titleEl.attr("data-line-number", parsed.titleLineNumber);
10685
11047
  if (onClickItem) {
@@ -10693,6 +11055,8 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10693
11055
  }
10694
11056
  const contentG = svg.append("g").attr("transform", `translate(${offsetX}, ${offsetY}) scale(${scale})`);
10695
11057
  const seriesColors2 = getSeriesColors(palette);
11058
+ const semanticRoles = useSemanticColors ? classifyEREntities(parsed.tables, parsed.relationships) : null;
11059
+ const semanticActive = semanticRoles !== null && (semanticColorsActive ?? true);
10696
11060
  const useLabels = parsed.options.notation === "labels";
10697
11061
  for (const edge of layout.edges) {
10698
11062
  if (edge.points.length < 2) continue;
@@ -10732,7 +11096,8 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10732
11096
  for (let ni = 0; ni < layout.nodes.length; ni++) {
10733
11097
  const node = layout.nodes[ni];
10734
11098
  const tagColor = resolveTagColor(node.metadata, parsed.tagGroups, activeTagGroup ?? null);
10735
- const nodeColor2 = node.color ?? tagColor ?? seriesColors2[ni % seriesColors2.length];
11099
+ const semanticColor = semanticActive ? palette.colors[ROLE_COLORS[semanticRoles.get(node.id) ?? "unclassified"]] : semanticRoles ? palette.primary : void 0;
11100
+ const nodeColor2 = node.color ?? tagColor ?? semanticColor ?? seriesColors2[ni % seriesColors2.length];
10736
11101
  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);
10737
11102
  if (activeTagGroup) {
10738
11103
  const tagKey = activeTagGroup.toLowerCase();
@@ -10741,6 +11106,10 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10741
11106
  nodeG.attr(`data-tag-${tagKey}`, tagValue.toLowerCase());
10742
11107
  }
10743
11108
  }
11109
+ if (semanticRoles) {
11110
+ const role = semanticRoles.get(node.id);
11111
+ if (role) nodeG.attr("data-er-role", role);
11112
+ }
10744
11113
  if (onClickItem) {
10745
11114
  nodeG.style("cursor", "pointer").on("click", () => {
10746
11115
  onClickItem(node.lineNumber);
@@ -10781,7 +11150,7 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10781
11150
  legendG.attr("data-legend-active", activeTagGroup.toLowerCase());
10782
11151
  }
10783
11152
  let legendX = DIAGRAM_PADDING5;
10784
- let legendY = height - DIAGRAM_PADDING5;
11153
+ let legendY = viewH - DIAGRAM_PADDING5;
10785
11154
  for (const group of parsed.tagGroups) {
10786
11155
  const groupG = legendG.append("g").attr("data-legend-group", group.name.toLowerCase());
10787
11156
  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}:`);
@@ -10800,6 +11169,62 @@ function renderERDiagram(container, parsed, layout, palette, isDark, onClickItem
10800
11169
  legendX += LEGEND_GROUP_GAP;
10801
11170
  }
10802
11171
  }
11172
+ if (semanticRoles) {
11173
+ const presentRoles = ROLE_ORDER.filter((role) => {
11174
+ for (const r of semanticRoles.values()) {
11175
+ if (r === role) return true;
11176
+ }
11177
+ return false;
11178
+ });
11179
+ if (presentRoles.length > 0) {
11180
+ const measureLabelW = (text, fontSize) => {
11181
+ const dummy = svg.append("text").attr("font-size", fontSize).attr("font-family", FONT_FAMILY).attr("visibility", "hidden").text(text);
11182
+ const measured = dummy.node()?.getComputedTextLength?.() ?? 0;
11183
+ dummy.remove();
11184
+ return measured > 0 ? measured : text.length * fontSize * 0.6;
11185
+ };
11186
+ const labelWidths = /* @__PURE__ */ new Map();
11187
+ for (const role of presentRoles) {
11188
+ labelWidths.set(role, measureLabelW(ROLE_LABELS[role], LEGEND_ENTRY_FONT_SIZE));
11189
+ }
11190
+ const groupBg = isDark ? mix(palette.surface, palette.bg, 50) : mix(palette.surface, palette.bg, 30);
11191
+ const groupName = "Role";
11192
+ const pillWidth = groupName.length * LEGEND_PILL_FONT_W + LEGEND_PILL_PAD;
11193
+ const pillH = LEGEND_HEIGHT - LEGEND_CAPSULE_PAD * 2;
11194
+ let totalWidth;
11195
+ let entriesWidth = 0;
11196
+ if (semanticActive) {
11197
+ for (const role of presentRoles) {
11198
+ entriesWidth += LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP + labelWidths.get(role) + LEGEND_ENTRY_TRAIL;
11199
+ }
11200
+ totalWidth = LEGEND_CAPSULE_PAD * 2 + pillWidth + LEGEND_ENTRY_TRAIL + entriesWidth;
11201
+ } else {
11202
+ totalWidth = pillWidth;
11203
+ }
11204
+ const legendX = (viewW - totalWidth) / 2;
11205
+ const legendY = viewH - DIAGRAM_PADDING5 - LEGEND_HEIGHT;
11206
+ const semanticLegendG = svg.append("g").attr("class", "er-semantic-legend").attr("data-legend-group", "role").attr("transform", `translate(${legendX}, ${legendY})`).style("cursor", "pointer");
11207
+ if (semanticActive) {
11208
+ semanticLegendG.append("rect").attr("width", totalWidth).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
11209
+ 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);
11210
+ 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);
11211
+ 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);
11212
+ let entryX = LEGEND_CAPSULE_PAD + pillWidth + LEGEND_ENTRY_TRAIL;
11213
+ for (const role of presentRoles) {
11214
+ const label = ROLE_LABELS[role];
11215
+ const roleColor = palette.colors[ROLE_COLORS[role]];
11216
+ const entryG = semanticLegendG.append("g").attr("data-legend-entry", role);
11217
+ entryG.append("circle").attr("cx", entryX + LEGEND_DOT_R).attr("cy", LEGEND_HEIGHT / 2).attr("r", LEGEND_DOT_R).attr("fill", roleColor);
11218
+ const textX = entryX + LEGEND_DOT_R * 2 + LEGEND_ENTRY_DOT_GAP;
11219
+ 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);
11220
+ entryX = textX + labelWidths.get(role) + LEGEND_ENTRY_TRAIL;
11221
+ }
11222
+ } else {
11223
+ semanticLegendG.append("rect").attr("width", pillWidth).attr("height", LEGEND_HEIGHT).attr("rx", LEGEND_HEIGHT / 2).attr("fill", groupBg);
11224
+ 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);
11225
+ }
11226
+ }
11227
+ }
10803
11228
  }
10804
11229
  function renderERDiagramForExport(content, theme, palette) {
10805
11230
  const parsed = parseERDiagram(content, palette);
@@ -10847,6 +11272,7 @@ var init_renderer5 = __esm({
10847
11272
  init_legend_constants();
10848
11273
  init_parser3();
10849
11274
  init_layout4();
11275
+ init_classify();
10850
11276
  DIAGRAM_PADDING5 = 20;
10851
11277
  MAX_SCALE4 = 3;
10852
11278
  TABLE_FONT_SIZE = 13;
@@ -10975,9 +11401,10 @@ function layoutInitiativeStatus(parsed, collapseResult) {
10975
11401
  const dagreEdge = g.edge(edge.source, edge.target, `e${i}`);
10976
11402
  const dagrePoints = dagreEdge?.points ?? [];
10977
11403
  const hasIntermediateRank = allNodeX.some((x) => x > src.x + 20 && x < tgt.x - 20);
10978
- const step = Math.min((enterX - exitX) * 0.15, 20);
11404
+ const step = Math.max(0, Math.min((enterX - exitX) * 0.15, 20));
10979
11405
  const isBackEdge = tgt.x < src.x - 5;
10980
- const isYDisplaced = !isBackEdge && Math.abs(tgt.y - src.y) > NODESEP;
11406
+ const isTopExit = !isBackEdge && tgt.x > src.x && !hasIntermediateRank && tgt.y < src.y - NODESEP;
11407
+ const isBottomExit = !isBackEdge && tgt.x > src.x && !hasIntermediateRank && tgt.y > src.y + NODESEP;
10981
11408
  let points;
10982
11409
  if (isBackEdge) {
10983
11410
  const routeAbove = Math.min(src.y, tgt.y) > avgNodeY;
@@ -10987,31 +11414,44 @@ function layoutInitiativeStatus(parsed, collapseResult) {
10987
11414
  const spreadDir = avgNodeX < rawMidX ? 1 : -1;
10988
11415
  const unclamped = Math.abs(src.x - tgt.x) < NODE_WIDTH ? rawMidX + spreadDir * BACK_EDGE_MIN_SPREAD : rawMidX;
10989
11416
  const midX = Math.min(src.x, Math.max(tgt.x, unclamped));
11417
+ const srcDepart = Math.max(midX + 1, src.x - TOP_EXIT_STEP);
11418
+ const tgtApproach = Math.min(midX - 1, tgt.x + TOP_EXIT_STEP);
10990
11419
  if (routeAbove) {
10991
11420
  const arcY = Math.min(src.y - srcHalfH, tgt.y - tgtHalfH) - BACK_EDGE_MARGIN;
10992
11421
  points = [
10993
11422
  { x: src.x, y: src.y - srcHalfH },
11423
+ { x: srcDepart, y: src.y - srcHalfH - TOP_EXIT_STEP },
10994
11424
  { x: midX, y: arcY },
11425
+ { x: tgtApproach, y: tgt.y - tgtHalfH - TOP_EXIT_STEP },
10995
11426
  { x: tgt.x, y: tgt.y - tgtHalfH }
10996
11427
  ];
10997
11428
  } else {
10998
11429
  const arcY = Math.max(src.y + srcHalfH, tgt.y + tgtHalfH) + BACK_EDGE_MARGIN;
10999
11430
  points = [
11000
11431
  { x: src.x, y: src.y + srcHalfH },
11432
+ { x: srcDepart, y: src.y + srcHalfH + TOP_EXIT_STEP },
11001
11433
  { x: midX, y: arcY },
11434
+ { x: tgtApproach, y: tgt.y + tgtHalfH + TOP_EXIT_STEP },
11002
11435
  { x: tgt.x, y: tgt.y + tgtHalfH }
11003
11436
  ];
11004
11437
  }
11005
- } else if (isYDisplaced) {
11006
- const exitY = tgt.y > src.y + NODESEP ? src.y + src.height / 2 : src.y - src.height / 2;
11007
- const spreadExitX = src.x + yOffset;
11008
- const spreadEntryY = tgt.y + yOffset;
11009
- const midX = (spreadExitX + enterX) / 2;
11010
- const midY = (exitY + spreadEntryY) / 2;
11438
+ } else if (isTopExit) {
11439
+ const exitY = src.y - src.height / 2;
11440
+ const p1x = Math.min(Math.max(src.x, src.x + yOffset + TOP_EXIT_STEP), (src.x + enterX) / 2 - 1);
11011
11441
  points = [
11012
- { x: spreadExitX, y: exitY },
11013
- { x: midX, y: midY },
11014
- { x: enterX, y: spreadEntryY }
11442
+ { x: src.x, y: exitY },
11443
+ { x: p1x, y: exitY - TOP_EXIT_STEP },
11444
+ { x: enterX - step, y: tgt.y + yOffset },
11445
+ { x: enterX, y: tgt.y }
11446
+ ];
11447
+ } else if (isBottomExit) {
11448
+ const exitY = src.y + src.height / 2;
11449
+ const p1x = Math.min(Math.max(src.x, src.x + yOffset + TOP_EXIT_STEP), (src.x + enterX) / 2 - 1);
11450
+ points = [
11451
+ { x: src.x, y: exitY },
11452
+ { x: p1x, y: exitY + TOP_EXIT_STEP },
11453
+ { x: enterX - step, y: tgt.y + yOffset },
11454
+ { x: enterX, y: tgt.y }
11015
11455
  ];
11016
11456
  } else if (tgt.x > src.x && !hasIntermediateRank) {
11017
11457
  points = [
@@ -11108,7 +11548,7 @@ function layoutInitiativeStatus(parsed, collapseResult) {
11108
11548
  totalHeight += 40;
11109
11549
  return { nodes: layoutNodes, edges: layoutEdges, groups: layoutGroups, width: totalWidth, height: totalHeight };
11110
11550
  }
11111
- var 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;
11551
+ var 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;
11112
11552
  var init_layout5 = __esm({
11113
11553
  "src/initiative-status/layout.ts"() {
11114
11554
  "use strict";
@@ -11124,6 +11564,7 @@ var init_layout5 = __esm({
11124
11564
  MAX_PARALLEL_EDGES = 5;
11125
11565
  BACK_EDGE_MARGIN = 40;
11126
11566
  BACK_EDGE_MIN_SPREAD = Math.round(NODE_WIDTH * 0.75);
11567
+ TOP_EXIT_STEP = 10;
11127
11568
  CHAR_WIDTH_RATIO = 0.6;
11128
11569
  NODE_FONT_SIZE = 13;
11129
11570
  NODE_TEXT_PADDING = 12;
@@ -11372,7 +11813,6 @@ function renderInitiativeStatus(container, parsed, layout, palette, isDark, onCl
11372
11813
  const scaleY = (availH - DIAGRAM_PADDING6 * 2) / diagramH;
11373
11814
  const scale = Math.min(MAX_SCALE5, scaleX, scaleY);
11374
11815
  const scaledW = diagramW * scale;
11375
- const scaledH = diagramH * scale;
11376
11816
  const offsetX = (width - scaledW) / 2;
11377
11817
  const offsetY = titleHeight + DIAGRAM_PADDING6;
11378
11818
  const svg = d3Selection6.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -11555,13 +11995,7 @@ function renderInitiativeStatusForExport(content, theme, palette) {
11555
11995
  const titleOffset = parsed.title ? 40 : 0;
11556
11996
  const exportWidth = layout.width + DIAGRAM_PADDING6 * 2;
11557
11997
  const exportHeight = layout.height + DIAGRAM_PADDING6 * 2 + titleOffset;
11558
- const container = document.createElement("div");
11559
- container.style.width = `${exportWidth}px`;
11560
- container.style.height = `${exportHeight}px`;
11561
- container.style.position = "absolute";
11562
- container.style.left = "-9999px";
11563
- document.body.appendChild(container);
11564
- try {
11998
+ return runInExportContainer(exportWidth, exportHeight, (container) => {
11565
11999
  renderInitiativeStatus(
11566
12000
  container,
11567
12001
  parsed,
@@ -11571,23 +12005,15 @@ function renderInitiativeStatusForExport(content, theme, palette) {
11571
12005
  void 0,
11572
12006
  { width: exportWidth, height: exportHeight }
11573
12007
  );
11574
- const svgEl = container.querySelector("svg");
11575
- if (!svgEl) return "";
11576
- if (theme === "transparent") {
11577
- svgEl.style.background = "none";
11578
- }
11579
- svgEl.setAttribute("xmlns", "http://www.w3.org/2000/svg");
11580
- svgEl.style.fontFamily = FONT_FAMILY;
11581
- return svgEl.outerHTML;
11582
- } finally {
11583
- document.body.removeChild(container);
11584
- }
12008
+ return extractExportSvg(container, theme);
12009
+ });
11585
12010
  }
11586
12011
  var 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;
11587
12012
  var init_renderer6 = __esm({
11588
12013
  "src/initiative-status/renderer.ts"() {
11589
12014
  "use strict";
11590
12015
  init_fonts();
12016
+ init_export_container();
11591
12017
  init_color_utils();
11592
12018
  init_parser7();
11593
12019
  init_layout5();
@@ -11623,7 +12049,7 @@ __export(layout_exports6, {
11623
12049
  rollUpContextRelationships: () => rollUpContextRelationships
11624
12050
  });
11625
12051
  import dagre5 from "@dagrejs/dagre";
11626
- function computeEdgePenalty(edgeList, nodePositions, degrees) {
12052
+ function computeEdgePenalty(edgeList, nodePositions, degrees, nodeGeometry) {
11627
12053
  let penalty = 0;
11628
12054
  for (const edge of edgeList) {
11629
12055
  const sx = nodePositions.get(edge.source);
@@ -11633,6 +12059,32 @@ function computeEdgePenalty(edgeList, nodePositions, degrees) {
11633
12059
  const weight = Math.min(degrees.get(edge.source) ?? 1, degrees.get(edge.target) ?? 1);
11634
12060
  penalty += dist * weight;
11635
12061
  }
12062
+ if (nodeGeometry) {
12063
+ for (const edge of edgeList) {
12064
+ const geomA = nodeGeometry.get(edge.source);
12065
+ const geomB = nodeGeometry.get(edge.target);
12066
+ if (!geomA || !geomB) continue;
12067
+ const ax = nodePositions.get(edge.source) ?? 0;
12068
+ const bx = nodePositions.get(edge.target) ?? 0;
12069
+ const ay = geomA.y;
12070
+ const by = geomB.y;
12071
+ if (ay === by) continue;
12072
+ const edgeMinX = Math.min(ax, bx);
12073
+ const edgeMaxX = Math.max(ax, bx);
12074
+ const edgeMinY = Math.min(ay, by);
12075
+ const edgeMaxY = Math.max(ay, by);
12076
+ for (const [name, geomC] of nodeGeometry) {
12077
+ if (name === edge.source || name === edge.target) continue;
12078
+ const cx = nodePositions.get(name) ?? 0;
12079
+ const cy = geomC.y;
12080
+ const hw = geomC.width / 2;
12081
+ const hh = geomC.height / 2;
12082
+ if (cx + hw > edgeMinX && cx - hw < edgeMaxX && cy + hh > edgeMinY && cy - hh < edgeMaxY) {
12083
+ penalty += EDGE_NODE_COLLISION_WEIGHT;
12084
+ }
12085
+ }
12086
+ }
12087
+ }
11636
12088
  return penalty;
11637
12089
  }
11638
12090
  function reduceCrossings(g, edgeList, nodeGroupMap) {
@@ -11642,6 +12094,11 @@ function reduceCrossings(g, edgeList, nodeGroupMap) {
11642
12094
  degrees.set(edge.source, (degrees.get(edge.source) ?? 0) + 1);
11643
12095
  degrees.set(edge.target, (degrees.get(edge.target) ?? 0) + 1);
11644
12096
  }
12097
+ const nodeGeometry = /* @__PURE__ */ new Map();
12098
+ for (const name of g.nodes()) {
12099
+ const pos = g.node(name);
12100
+ if (pos) nodeGeometry.set(name, { y: pos.y, width: pos.width, height: pos.height });
12101
+ }
11645
12102
  const rankMap = /* @__PURE__ */ new Map();
11646
12103
  for (const name of g.nodes()) {
11647
12104
  const pos = g.node(name);
@@ -11684,7 +12141,7 @@ function reduceCrossings(g, edgeList, nodeGroupMap) {
11684
12141
  const pos = g.node(name);
11685
12142
  if (pos) basePositions.set(name, pos.x);
11686
12143
  }
11687
- const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees);
12144
+ const currentPenalty = computeEdgePenalty(edgeList, basePositions, degrees, nodeGeometry);
11688
12145
  let bestPerm = [...partition];
11689
12146
  let bestPenalty = currentPenalty;
11690
12147
  if (partition.length <= 8) {
@@ -11694,7 +12151,7 @@ function reduceCrossings(g, edgeList, nodeGroupMap) {
11694
12151
  for (let i = 0; i < perm.length; i++) {
11695
12152
  testPositions.set(perm[i], xSlots[i]);
11696
12153
  }
11697
- const penalty = computeEdgePenalty(edgeList, testPositions, degrees);
12154
+ const penalty = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
11698
12155
  if (penalty < bestPenalty) {
11699
12156
  bestPenalty = penalty;
11700
12157
  bestPerm = [...perm];
@@ -11712,13 +12169,13 @@ function reduceCrossings(g, edgeList, nodeGroupMap) {
11712
12169
  for (let k = 0; k < workingOrder.length; k++) {
11713
12170
  testPositions.set(workingOrder[k], xSlots[k]);
11714
12171
  }
11715
- const before = computeEdgePenalty(edgeList, testPositions, degrees);
12172
+ const before = computeEdgePenalty(edgeList, testPositions, degrees, nodeGeometry);
11716
12173
  [workingOrder[i], workingOrder[i + 1]] = [workingOrder[i + 1], workingOrder[i]];
11717
12174
  const testPositions2 = new Map(basePositions);
11718
12175
  for (let k = 0; k < workingOrder.length; k++) {
11719
12176
  testPositions2.set(workingOrder[k], xSlots[k]);
11720
12177
  }
11721
- const after = computeEdgePenalty(edgeList, testPositions2, degrees);
12178
+ const after = computeEdgePenalty(edgeList, testPositions2, degrees, nodeGeometry);
11722
12179
  if (after < before) {
11723
12180
  improved = true;
11724
12181
  if (after < bestPenalty) {
@@ -11825,8 +12282,6 @@ function collectAllRelationships(elements, ownerMap) {
11825
12282
  function rollUpContextRelationships(parsed) {
11826
12283
  const ownerMap = buildOwnershipMap(parsed.elements);
11827
12284
  const allRels = collectAllRelationships(parsed.elements, ownerMap);
11828
- for (const rel of parsed.relationships) {
11829
- }
11830
12285
  const topLevelNames = new Set(parsed.elements.map((e) => e.name));
11831
12286
  const explicitKeys = /* @__PURE__ */ new Set();
11832
12287
  const explicit = [];
@@ -13038,7 +13493,7 @@ function layoutC4Deployment(parsed, activeTagGroup) {
13038
13493
  }
13039
13494
  return { nodes, edges, legend: legendGroups, groupBoundaries, width: totalWidth, height: totalHeight };
13040
13495
  }
13041
- var 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;
13496
+ var 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;
13042
13497
  var init_layout6 = __esm({
13043
13498
  "src/c4/layout.ts"() {
13044
13499
  "use strict";
@@ -13067,6 +13522,7 @@ var init_layout6 = __esm({
13067
13522
  LEGEND_ENTRY_DOT_GAP4 = 4;
13068
13523
  LEGEND_ENTRY_TRAIL4 = 8;
13069
13524
  LEGEND_CAPSULE_PAD4 = 4;
13525
+ EDGE_NODE_COLLISION_WEIGHT = 5e3;
13070
13526
  META_EXCLUDE_KEYS = /* @__PURE__ */ new Set(["description", "tech", "technology", "is a"]);
13071
13527
  }
13072
13528
  });
@@ -13156,7 +13612,6 @@ function renderC4Context(container, parsed, layout, palette, isDark, onClickItem
13156
13612
  const scaleY = (availH - DIAGRAM_PADDING7 * 2) / diagramH;
13157
13613
  const scale = Math.min(MAX_SCALE6, scaleX, scaleY);
13158
13614
  const scaledW = diagramW * scale;
13159
- const scaledH = diagramH * scale;
13160
13615
  const offsetX = (width - scaledW) / 2;
13161
13616
  const offsetY = titleHeight + DIAGRAM_PADDING7;
13162
13617
  const svg = d3Selection7.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -13646,7 +14101,6 @@ function renderC4Containers(container, parsed, layout, palette, isDark, onClickI
13646
14101
  const scaleY = (availH - DIAGRAM_PADDING7 * 2) / diagramH;
13647
14102
  const scale = Math.min(MAX_SCALE6, scaleX, scaleY);
13648
14103
  const scaledW = diagramW * scale;
13649
- const scaledH = diagramH * scale;
13650
14104
  const offsetX = (width - scaledW) / 2;
13651
14105
  const offsetY = titleHeight + DIAGRAM_PADDING7;
13652
14106
  const svg = d3Selection7.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -14259,7 +14713,6 @@ function renderFlowchart(container, graph, layout, palette, isDark, onClickItem,
14259
14713
  const scaleY = (availH - DIAGRAM_PADDING8 * 2) / diagramH;
14260
14714
  const scale = Math.min(MAX_SCALE7, scaleX, scaleY);
14261
14715
  const scaledW = diagramW * scale;
14262
- const scaledH = diagramH * scale;
14263
14716
  const offsetX = (width - scaledW) / 2;
14264
14717
  const offsetY = titleHeight + DIAGRAM_PADDING8;
14265
14718
  const svg = d3Selection8.select(container).append("svg").attr("width", width).attr("height", height).style("font-family", FONT_FAMILY);
@@ -15251,6 +15704,7 @@ var init_compute = __esm({
15251
15704
  // src/infra/layout.ts
15252
15705
  var layout_exports8 = {};
15253
15706
  __export(layout_exports8, {
15707
+ fixEdgeWaypoints: () => fixEdgeWaypoints,
15254
15708
  layoutInfra: () => layoutInfra,
15255
15709
  separateGroups: () => separateGroups
15256
15710
  });
@@ -15435,6 +15889,8 @@ function formatUptime(fraction) {
15435
15889
  return `${pct.toFixed(1)}%`;
15436
15890
  }
15437
15891
  function separateGroups(groups, nodes, isLR, maxIterations = 20) {
15892
+ const groupDeltas = /* @__PURE__ */ new Map();
15893
+ let converged = false;
15438
15894
  for (let iter = 0; iter < maxIterations; iter++) {
15439
15895
  let anyOverlap = false;
15440
15896
  for (let i = 0; i < groups.length; i++) {
@@ -15452,6 +15908,9 @@ function separateGroups(groups, nodes, isLR, maxIterations = 20) {
15452
15908
  const groupToShift = aCenter <= bCenter ? gb : ga;
15453
15909
  if (isLR) groupToShift.y += shift;
15454
15910
  else groupToShift.x += shift;
15911
+ const prev = groupDeltas.get(groupToShift.id) ?? { dx: 0, dy: 0 };
15912
+ if (isLR) groupDeltas.set(groupToShift.id, { dx: prev.dx, dy: prev.dy + shift });
15913
+ else groupDeltas.set(groupToShift.id, { dx: prev.dx + shift, dy: prev.dy });
15455
15914
  for (const node of nodes) {
15456
15915
  if (node.groupId === groupToShift.id) {
15457
15916
  if (isLR) node.y += shift;
@@ -15460,19 +15919,48 @@ function separateGroups(groups, nodes, isLR, maxIterations = 20) {
15460
15919
  }
15461
15920
  }
15462
15921
  }
15463
- if (!anyOverlap) break;
15922
+ if (!anyOverlap) {
15923
+ converged = true;
15924
+ break;
15925
+ }
15926
+ }
15927
+ if (!converged && maxIterations > 0) {
15928
+ console.warn(`separateGroups: hit maxIterations (${maxIterations}) without fully resolving all group overlaps`);
15929
+ }
15930
+ return groupDeltas;
15931
+ }
15932
+ function fixEdgeWaypoints(edges, nodes, groupDeltas) {
15933
+ if (groupDeltas.size === 0) return;
15934
+ const nodeToGroup = /* @__PURE__ */ new Map();
15935
+ for (const node of nodes) nodeToGroup.set(node.id, node.groupId);
15936
+ for (const edge of edges) {
15937
+ const srcGroup = nodeToGroup.get(edge.sourceId) ?? null;
15938
+ const tgtGroup = nodeToGroup.get(edge.targetId) ?? null;
15939
+ const srcDelta = srcGroup ? groupDeltas.get(srcGroup) : void 0;
15940
+ const tgtDelta = tgtGroup ? groupDeltas.get(tgtGroup) : void 0;
15941
+ if (!srcDelta && !tgtDelta) continue;
15942
+ if (srcDelta && tgtDelta && srcGroup !== tgtGroup) {
15943
+ edge.points = [];
15944
+ continue;
15945
+ }
15946
+ const delta = srcDelta ?? tgtDelta;
15947
+ for (const pt of edge.points) {
15948
+ pt.x += delta.dx;
15949
+ pt.y += delta.dy;
15950
+ }
15464
15951
  }
15465
15952
  }
15466
15953
  function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
15467
15954
  if (computed.nodes.length === 0) {
15468
- return { nodes: [], edges: [], groups: [], options: {}, width: 0, height: 0 };
15955
+ return { nodes: [], edges: [], groups: [], options: {}, direction: computed.direction, width: 0, height: 0 };
15469
15956
  }
15957
+ const isLR = computed.direction !== "TB";
15470
15958
  const g = new dagre7.graphlib.Graph();
15471
15959
  g.setGraph({
15472
15960
  rankdir: computed.direction === "TB" ? "TB" : "LR",
15473
- nodesep: 50,
15474
- ranksep: 100,
15475
- edgesep: 20
15961
+ nodesep: isLR ? 70 : 60,
15962
+ ranksep: isLR ? 150 : 120,
15963
+ edgesep: 30
15476
15964
  });
15477
15965
  g.setDefaultEdgeLabel(() => ({}));
15478
15966
  const groupedNodeIds = /* @__PURE__ */ new Set();
@@ -15480,7 +15968,6 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
15480
15968
  if (node.groupId) groupedNodeIds.add(node.id);
15481
15969
  }
15482
15970
  const GROUP_INFLATE = GROUP_PADDING3 * 2 + GROUP_HEADER_HEIGHT;
15483
- const isLR = computed.direction !== "TB";
15484
15971
  const widthMap = /* @__PURE__ */ new Map();
15485
15972
  const heightMap = /* @__PURE__ */ new Map();
15486
15973
  for (const node of computed.nodes) {
@@ -15615,7 +16102,8 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
15615
16102
  lineNumber: group.lineNumber
15616
16103
  };
15617
16104
  });
15618
- separateGroups(layoutGroups, layoutNodes, isLR);
16105
+ const groupDeltas = separateGroups(layoutGroups, layoutNodes, isLR);
16106
+ fixEdgeWaypoints(layoutEdges, layoutNodes, groupDeltas);
15619
16107
  let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
15620
16108
  for (const node of layoutNodes) {
15621
16109
  const left = node.x - node.width / 2;
@@ -15673,6 +16161,7 @@ function layoutInfra(computed, expandedNodeIds, collapsedNodes) {
15673
16161
  edges: layoutEdges,
15674
16162
  groups: layoutGroups,
15675
16163
  options: computed.options,
16164
+ direction: computed.direction,
15676
16165
  width: totalWidth,
15677
16166
  height: totalHeight
15678
16167
  };
@@ -15714,23 +16203,23 @@ var init_layout8 = __esm({
15714
16203
  ]);
15715
16204
  DISPLAY_NAMES = {
15716
16205
  "cache-hit": "cache hit",
15717
- "firewall-block": "fw block",
16206
+ "firewall-block": "firewall block",
15718
16207
  "ratelimit-rps": "rate limit RPS",
15719
16208
  "latency-ms": "latency",
15720
16209
  "uptime": "uptime",
15721
16210
  "instances": "instances",
15722
16211
  "max-rps": "max RPS",
15723
- "cb-error-threshold": "CB error",
15724
- "cb-latency-threshold-ms": "CB latency",
16212
+ "cb-error-threshold": "CB error threshold",
16213
+ "cb-latency-threshold-ms": "CB latency threshold",
15725
16214
  "concurrency": "concurrency",
15726
16215
  "duration-ms": "duration",
15727
16216
  "cold-start-ms": "cold start",
15728
16217
  "buffer": "buffer",
15729
- "drain-rate": "drain",
16218
+ "drain-rate": "drain rate",
15730
16219
  "retention-hours": "retention",
15731
16220
  "partitions": "partitions"
15732
16221
  };
15733
- GROUP_GAP = 24;
16222
+ GROUP_GAP = GROUP_PADDING3 * 2 + GROUP_HEADER_HEIGHT;
15734
16223
  }
15735
16224
  });
15736
16225
 
@@ -15805,6 +16294,236 @@ function resolveNodeSlo(node, diagramOptions) {
15805
16294
  if (availThreshold == null && latencyP90 == null) return null;
15806
16295
  return { availThreshold, latencyP90, warningMargin };
15807
16296
  }
16297
+ function buildPathD(pts, direction) {
16298
+ const gen = d3Shape7.line().x((d) => d.x).y((d) => d.y);
16299
+ if (pts.length <= 2) {
16300
+ gen.curve(direction === "TB" ? d3Shape7.curveBumpY : d3Shape7.curveBumpX);
16301
+ } else {
16302
+ gen.curve(d3Shape7.curveCatmullRom.alpha(0.5));
16303
+ }
16304
+ return gen(pts) ?? "";
16305
+ }
16306
+ function computePortPts(edges, nodeMap, direction) {
16307
+ const srcPts = /* @__PURE__ */ new Map();
16308
+ const tgtPts = /* @__PURE__ */ new Map();
16309
+ const PAD = 0.1;
16310
+ const activeEdges = edges.filter((e) => e.points.length > 0);
16311
+ const bySource = /* @__PURE__ */ new Map();
16312
+ for (const e of activeEdges) {
16313
+ if (!bySource.has(e.sourceId)) bySource.set(e.sourceId, []);
16314
+ bySource.get(e.sourceId).push(e);
16315
+ }
16316
+ for (const [sourceId, es] of bySource) {
16317
+ if (es.length < 2) continue;
16318
+ const source = nodeMap.get(sourceId);
16319
+ if (!source) continue;
16320
+ 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);
16321
+ const n = sorted.length;
16322
+ for (let i = 0; i < n; i++) {
16323
+ const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
16324
+ const { e, t } = sorted[i];
16325
+ const isBackward = direction === "LR" ? t.x < source.x : t.y < source.y;
16326
+ if (direction === "LR") {
16327
+ srcPts.set(`${e.sourceId}:${e.targetId}`, {
16328
+ x: isBackward ? source.x - source.width / 2 : source.x + source.width / 2,
16329
+ y: source.y - source.height / 2 + frac * source.height
16330
+ });
16331
+ } else {
16332
+ srcPts.set(`${e.sourceId}:${e.targetId}`, {
16333
+ x: source.x - source.width / 2 + frac * source.width,
16334
+ y: isBackward ? source.y - source.height / 2 : source.y + source.height / 2
16335
+ });
16336
+ }
16337
+ }
16338
+ }
16339
+ const byTarget = /* @__PURE__ */ new Map();
16340
+ for (const e of activeEdges) {
16341
+ if (!byTarget.has(e.targetId)) byTarget.set(e.targetId, []);
16342
+ byTarget.get(e.targetId).push(e);
16343
+ }
16344
+ for (const [targetId, es] of byTarget) {
16345
+ if (es.length < 2) continue;
16346
+ const target = nodeMap.get(targetId);
16347
+ if (!target) continue;
16348
+ 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);
16349
+ const n = sorted.length;
16350
+ for (let i = 0; i < n; i++) {
16351
+ const frac = n === 1 ? 0.5 : PAD + (1 - 2 * PAD) * i / (n - 1);
16352
+ const { e, s } = sorted[i];
16353
+ const isBackward = direction === "LR" ? target.x < s.x : target.y < s.y;
16354
+ if (direction === "LR") {
16355
+ tgtPts.set(`${e.sourceId}:${e.targetId}`, {
16356
+ x: isBackward ? target.x + target.width / 2 : target.x - target.width / 2,
16357
+ y: target.y - target.height / 2 + frac * target.height
16358
+ });
16359
+ } else {
16360
+ tgtPts.set(`${e.sourceId}:${e.targetId}`, {
16361
+ x: target.x - target.width / 2 + frac * target.width,
16362
+ y: isBackward ? target.y + target.height / 2 : target.y - target.height / 2
16363
+ });
16364
+ }
16365
+ }
16366
+ }
16367
+ return { srcPts, tgtPts };
16368
+ }
16369
+ function findRoutingLane(blocking, targetY, margin) {
16370
+ const MERGE_SLOP = 4;
16371
+ const sorted = [...blocking].sort((a, b) => a.y + a.height / 2 - (b.y + b.height / 2));
16372
+ const merged = [];
16373
+ for (const r of sorted) {
16374
+ const lo = r.y - MERGE_SLOP;
16375
+ const hi = r.y + r.height + MERGE_SLOP;
16376
+ if (merged.length && lo <= merged[merged.length - 1][1]) {
16377
+ merged[merged.length - 1][1] = Math.max(merged[merged.length - 1][1], hi);
16378
+ } else {
16379
+ merged.push([lo, hi]);
16380
+ }
16381
+ }
16382
+ if (merged.length === 0) return targetY;
16383
+ const MIN_GAP = 10;
16384
+ const candidates = [
16385
+ merged[0][0] - margin,
16386
+ // above all blocking rects
16387
+ merged[merged.length - 1][1] + margin
16388
+ // below all blocking rects
16389
+ ];
16390
+ for (let i = 0; i < merged.length - 1; i++) {
16391
+ const gapLo = merged[i][1];
16392
+ const gapHi = merged[i + 1][0];
16393
+ if (gapHi - gapLo >= MIN_GAP) {
16394
+ candidates.push((gapLo + gapHi) / 2);
16395
+ }
16396
+ }
16397
+ return candidates.reduce(
16398
+ (best, c) => Math.abs(c - targetY) < Math.abs(best - targetY) ? c : best,
16399
+ candidates[0]
16400
+ );
16401
+ }
16402
+ function segmentIntersectsRect(p1, p2, rect) {
16403
+ const { x: rx, y: ry, width: rw, height: rh } = rect;
16404
+ const rr = rx + rw;
16405
+ const rb = ry + rh;
16406
+ const inRect = (p) => p.x >= rx && p.x <= rr && p.y >= ry && p.y <= rb;
16407
+ if (inRect(p1) || inRect(p2)) return true;
16408
+ if (Math.max(p1.x, p2.x) < rx || Math.min(p1.x, p2.x) > rr) return false;
16409
+ if (Math.max(p1.y, p2.y) < ry || Math.min(p1.y, p2.y) > rb) return false;
16410
+ const cross = (o, a, b) => (a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
16411
+ const crosses = (a, b) => {
16412
+ const d1 = cross(a, b, p1);
16413
+ const d2 = cross(a, b, p2);
16414
+ const d3 = cross(p1, p2, a);
16415
+ const d4 = cross(p1, p2, b);
16416
+ return (d1 > 0 && d2 < 0 || d1 < 0 && d2 > 0) && (d3 > 0 && d4 < 0 || d3 < 0 && d4 > 0);
16417
+ };
16418
+ const tl = { x: rx, y: ry };
16419
+ const tr = { x: rr, y: ry };
16420
+ const br = { x: rr, y: rb };
16421
+ const bl = { x: rx, y: rb };
16422
+ return crosses(tl, tr) || crosses(tr, br) || crosses(br, bl) || crosses(bl, tl);
16423
+ }
16424
+ function curveIntersectsRect(sc, tc, rect, direction) {
16425
+ if (direction === "LR") {
16426
+ const midX = (sc.x + tc.x) / 2;
16427
+ const m1 = { x: midX, y: sc.y };
16428
+ const m2 = { x: midX, y: tc.y };
16429
+ return segmentIntersectsRect(sc, m1, rect) || segmentIntersectsRect(m1, m2, rect) || segmentIntersectsRect(m2, tc, rect);
16430
+ } else {
16431
+ const midY = (sc.y + tc.y) / 2;
16432
+ const m1 = { x: sc.x, y: midY };
16433
+ const m2 = { x: tc.x, y: midY };
16434
+ return segmentIntersectsRect(sc, m1, rect) || segmentIntersectsRect(m1, m2, rect) || segmentIntersectsRect(m2, tc, rect);
16435
+ }
16436
+ }
16437
+ function edgeWaypoints(source, target, groups, nodes, direction, margin = 30, srcExitPt, tgtEnterPt) {
16438
+ const sc = { x: source.x, y: source.y };
16439
+ const tc = { x: target.x, y: target.y };
16440
+ const isBackward = direction === "LR" ? tc.x < sc.x : tc.y < sc.y;
16441
+ if (isBackward) {
16442
+ if (direction === "LR") {
16443
+ const xBandObs = [];
16444
+ for (const g of groups) {
16445
+ if (g.x + g.width < tc.x - margin || g.x > sc.x + margin) continue;
16446
+ xBandObs.push({ x: g.x, y: g.y, width: g.width, height: g.height });
16447
+ }
16448
+ for (const n of nodes) {
16449
+ if (n.id === source.id || n.id === target.id) continue;
16450
+ const nLeft = n.x - n.width / 2;
16451
+ const nRight = n.x + n.width / 2;
16452
+ if (nRight < tc.x - margin || nLeft > sc.x + margin) continue;
16453
+ xBandObs.push({ x: nLeft, y: n.y - n.height / 2, width: n.width, height: n.height });
16454
+ }
16455
+ const midY = (sc.y + tc.y) / 2;
16456
+ const routeY2 = xBandObs.length > 0 ? findRoutingLane(xBandObs, midY, margin) : midY;
16457
+ const exitBorder = srcExitPt ?? nodeBorderPoint(source, { x: sc.x, y: routeY2 });
16458
+ const exitPt2 = { x: exitBorder.x, y: routeY2 };
16459
+ const enterPt2 = { x: tc.x, y: routeY2 };
16460
+ const tp2 = tgtEnterPt ?? nodeBorderPoint(target, enterPt2);
16461
+ return srcExitPt ? [srcExitPt, exitPt2, enterPt2, tp2] : [exitBorder, exitPt2, enterPt2, tp2];
16462
+ } else {
16463
+ const yBandObs = [];
16464
+ for (const g of groups) {
16465
+ if (g.y + g.height < tc.y - margin || g.y > sc.y + margin) continue;
16466
+ yBandObs.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 nTop = n.y - n.height / 2;
16471
+ const nBot = n.y + n.height / 2;
16472
+ if (nBot < tc.y - margin || nTop > sc.y + margin) continue;
16473
+ yBandObs.push({ x: n.x - n.width / 2, y: nTop, width: n.width, height: n.height });
16474
+ }
16475
+ const rotated = yBandObs.map((r) => ({ x: r.y, y: r.x, width: r.height, height: r.width }));
16476
+ const midX = (sc.x + tc.x) / 2;
16477
+ const routeX = rotated.length > 0 ? findRoutingLane(rotated, midX, margin) : midX;
16478
+ const exitPt2 = srcExitPt ?? { x: routeX, y: sc.y };
16479
+ const enterPt2 = { x: routeX, y: tc.y };
16480
+ return [
16481
+ srcExitPt ?? nodeBorderPoint(source, exitPt2),
16482
+ exitPt2,
16483
+ enterPt2,
16484
+ tgtEnterPt ?? nodeBorderPoint(target, enterPt2)
16485
+ ];
16486
+ }
16487
+ }
16488
+ const blocking = [];
16489
+ const blockingGroupIds = /* @__PURE__ */ new Set();
16490
+ const pathSrc = srcExitPt ?? sc;
16491
+ const pathTgt = tgtEnterPt ?? tc;
16492
+ for (const g of groups) {
16493
+ if (g.id === source.groupId || g.id === target.groupId) continue;
16494
+ const gRect = { x: g.x, y: g.y, width: g.width, height: g.height };
16495
+ if (curveIntersectsRect(pathSrc, pathTgt, gRect, direction)) {
16496
+ blocking.push(gRect);
16497
+ blockingGroupIds.add(g.id);
16498
+ }
16499
+ }
16500
+ for (const n of nodes) {
16501
+ if (n.id === source.id || n.id === target.id) continue;
16502
+ if (n.groupId && (n.groupId === source.groupId || n.groupId === target.groupId)) continue;
16503
+ if (n.groupId && blockingGroupIds.has(n.groupId)) continue;
16504
+ const nodeRect = { x: n.x - n.width / 2, y: n.y - n.height / 2, width: n.width, height: n.height };
16505
+ if (curveIntersectsRect(pathSrc, pathTgt, nodeRect, direction)) {
16506
+ blocking.push(nodeRect);
16507
+ }
16508
+ }
16509
+ if (blocking.length === 0) {
16510
+ const sp = srcExitPt ?? nodeBorderPoint(source, tc);
16511
+ const tp2 = tgtEnterPt ?? nodeBorderPoint(target, sp);
16512
+ return [sp, tp2];
16513
+ }
16514
+ const obsLeft = Math.min(...blocking.map((o) => o.x));
16515
+ const obsRight = Math.max(...blocking.map((o) => o.x + o.width));
16516
+ const routeY = findRoutingLane(blocking, tc.y, margin);
16517
+ const exitX = direction === "LR" ? Math.max(sc.x, obsLeft - margin) : obsLeft - margin;
16518
+ const enterX = direction === "LR" ? Math.min(tc.x, obsRight + margin) : obsRight + margin;
16519
+ const exitPt = { x: exitX, y: routeY };
16520
+ const enterPt = { x: enterX, y: routeY };
16521
+ const tp = tgtEnterPt ?? nodeBorderPoint(target, enterPt);
16522
+ if (srcExitPt) {
16523
+ return [srcExitPt, exitPt, enterPt, tp];
16524
+ }
16525
+ return [nodeBorderPoint(source, exitPt), exitPt, enterPt, tp];
16526
+ }
15808
16527
  function nodeBorderPoint(node, target) {
15809
16528
  const hw = node.width / 2;
15810
16529
  const hh = node.height / 2;
@@ -16088,33 +16807,29 @@ function renderGroups(svg, groups, palette, isDark) {
16088
16807
  }
16089
16808
  }
16090
16809
  }
16091
- function renderEdgePaths(svg, edges, nodes, palette, isDark, animate) {
16810
+ function renderEdgePaths(svg, edges, nodes, groups, palette, isDark, animate, direction) {
16092
16811
  const nodeMap = new Map(nodes.map((n) => [n.id, n]));
16093
16812
  const maxRps = Math.max(...edges.map((e) => e.computedRps), 1);
16813
+ const { srcPts, tgtPts } = computePortPts(edges, nodeMap, direction);
16094
16814
  for (const edge of edges) {
16095
16815
  if (edge.points.length === 0) continue;
16096
16816
  const targetNode = nodeMap.get(edge.targetId);
16097
16817
  const sourceNode = nodeMap.get(edge.sourceId);
16098
16818
  const color = edgeColor(edge, palette);
16099
16819
  const strokeW = edgeWidth();
16100
- let pts = edge.points;
16101
- if (sourceNode && targetNode && pts.length >= 2) {
16102
- const first = pts[0];
16103
- const distFirstToSource = (first.x - sourceNode.x) ** 2 + (first.y - sourceNode.y) ** 2;
16104
- const distFirstToTarget = (first.x - targetNode.x) ** 2 + (first.y - targetNode.y) ** 2;
16105
- if (distFirstToTarget < distFirstToSource) {
16106
- pts = [...pts].reverse();
16107
- }
16108
- }
16109
- if (sourceNode && pts.length > 0) {
16110
- const bp = nodeBorderPoint(sourceNode, pts[0]);
16111
- pts = [bp, ...pts];
16112
- }
16113
- if (targetNode && pts.length > 0) {
16114
- const bp = nodeBorderPoint(targetNode, pts[pts.length - 1]);
16115
- pts = [...pts, bp];
16116
- }
16117
- const pathD = lineGenerator7(pts) ?? "";
16820
+ if (!sourceNode || !targetNode) continue;
16821
+ const key = `${edge.sourceId}:${edge.targetId}`;
16822
+ const pts = edgeWaypoints(
16823
+ sourceNode,
16824
+ targetNode,
16825
+ groups,
16826
+ nodes,
16827
+ direction,
16828
+ 30,
16829
+ srcPts.get(key),
16830
+ tgtPts.get(key)
16831
+ );
16832
+ const pathD = buildPathD(pts, direction);
16118
16833
  const edgeG = svg.append("g").attr("class", "infra-edge").attr("data-line-number", edge.lineNumber);
16119
16834
  edgeG.append("path").attr("d", pathD).attr("fill", "none").attr("stroke", color).attr("stroke-width", strokeW);
16120
16835
  if (animate && edge.computedRps > 0) {
@@ -16129,19 +16844,34 @@ function renderEdgePaths(svg, edges, nodes, palette, isDark, animate) {
16129
16844
  }
16130
16845
  }
16131
16846
  }
16132
- function renderEdgeLabels(svg, edges, palette, isDark, animate) {
16847
+ function renderEdgeLabels(svg, edges, nodes, groups, palette, isDark, animate, direction) {
16848
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
16849
+ const { srcPts, tgtPts } = computePortPts(edges, nodeMap, direction);
16133
16850
  for (const edge of edges) {
16134
16851
  if (edge.points.length === 0) continue;
16135
16852
  if (!edge.label) continue;
16136
- const midIdx = Math.floor(edge.points.length / 2);
16137
- const midPt = edge.points[midIdx];
16853
+ const sourceNode = nodeMap.get(edge.sourceId);
16854
+ const targetNode = nodeMap.get(edge.targetId);
16855
+ if (!sourceNode || !targetNode) continue;
16856
+ const key = `${edge.sourceId}:${edge.targetId}`;
16857
+ const wps = edgeWaypoints(
16858
+ sourceNode,
16859
+ targetNode,
16860
+ groups,
16861
+ nodes,
16862
+ direction,
16863
+ 30,
16864
+ srcPts.get(key),
16865
+ tgtPts.get(key)
16866
+ );
16867
+ const midPt = wps[Math.floor(wps.length / 2)];
16138
16868
  const labelText = edge.label;
16139
16869
  const g = svg.append("g").attr("class", animate ? "infra-edge-label" : "");
16140
16870
  const textWidth = labelText.length * 6.5 + 8;
16141
16871
  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);
16142
16872
  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);
16143
16873
  if (animate) {
16144
- const pathD = lineGenerator7(edge.points) ?? "";
16874
+ const pathD = buildPathD(wps, direction);
16145
16875
  g.insert("path", ":first-child").attr("d", pathD).attr("fill", "none").attr("stroke", "transparent").attr("stroke-width", 20);
16146
16876
  }
16147
16877
  }
@@ -16551,7 +17281,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
16551
17281
  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);
16552
17282
  }
16553
17283
  renderGroups(svg, layout.groups, palette, isDark);
16554
- renderEdgePaths(svg, layout.edges, layout.nodes, palette, isDark, shouldAnimate);
17284
+ renderEdgePaths(svg, layout.edges, layout.nodes, layout.groups, palette, isDark, shouldAnimate, layout.direction);
16555
17285
  const fanoutSourceIds = collectFanoutSourceIds(layout.edges);
16556
17286
  const scaledGroupIds = new Set(
16557
17287
  layout.groups.filter((g) => {
@@ -16563,7 +17293,7 @@ function renderInfra(container, layout, palette, isDark, title, titleLineNumber,
16563
17293
  if (shouldAnimate) {
16564
17294
  renderRejectParticles(svg, layout.nodes);
16565
17295
  }
16566
- renderEdgeLabels(svg, layout.edges, palette, isDark, shouldAnimate);
17296
+ renderEdgeLabels(svg, layout.edges, layout.nodes, layout.groups, palette, isDark, shouldAnimate, layout.direction);
16567
17297
  if (hasLegend) {
16568
17298
  if (fixedLegend) {
16569
17299
  const containerWidth = container.clientWidth || totalWidth;
@@ -16581,7 +17311,7 @@ function parseAndLayoutInfra(content) {
16581
17311
  const layout = layoutInfra(computed);
16582
17312
  return { parsed, computed, layout };
16583
17313
  }
16584
- var 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;
17314
+ var 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;
16585
17315
  var init_renderer8 = __esm({
16586
17316
  "src/infra/renderer.ts"() {
16587
17317
  "use strict";
@@ -16625,7 +17355,6 @@ var init_renderer8 = __esm({
16625
17355
  REJECT_DURATION_MAX = 3;
16626
17356
  REJECT_COUNT_MIN = 1;
16627
17357
  REJECT_COUNT_MAX = 3;
16628
- lineGenerator7 = d3Shape7.line().x((d) => d.x).y((d) => d.y).curve(d3Shape7.curveBasis);
16629
17358
  PROP_DISPLAY = {
16630
17359
  "cache-hit": "cache hit",
16631
17360
  "firewall-block": "firewall block",
@@ -16810,7 +17539,7 @@ function renderState(container, graph, layout, palette, isDark, onClickItem, exp
16810
17539
  }
16811
17540
  }
16812
17541
  } else if (edge.points.length >= 2) {
16813
- const pathD = lineGenerator8(edge.points);
17542
+ const pathD = lineGenerator7(edge.points);
16814
17543
  if (pathD) {
16815
17544
  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");
16816
17545
  }
@@ -16874,7 +17603,7 @@ function renderStateForExport(content, theme, palette) {
16874
17603
  document.body.removeChild(container);
16875
17604
  }
16876
17605
  }
16877
- var 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;
17606
+ var 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;
16878
17607
  var init_state_renderer = __esm({
16879
17608
  "src/graph/state-renderer.ts"() {
16880
17609
  "use strict";
@@ -16894,7 +17623,7 @@ var init_state_renderer = __esm({
16894
17623
  PSEUDOSTATE_RADIUS = 10;
16895
17624
  STATE_CORNER_RADIUS = 10;
16896
17625
  GROUP_EXTRA_PADDING2 = 12;
16897
- lineGenerator8 = d3Shape8.line().x((d) => d.x).y((d) => d.y).curve(d3Shape8.curveBasis);
17626
+ lineGenerator7 = d3Shape8.line().x((d) => d.x).y((d) => d.y).curve(d3Shape8.curveBasis);
16898
17627
  }
16899
17628
  });
16900
17629
 
@@ -18057,7 +18786,6 @@ function renderSequenceDiagram(container, parsed, palette, isDark, _onNavigateTo
18057
18786
  if (secY === void 0) continue;
18058
18787
  const isCollapsed = collapsedSections?.has(sec.lineNumber) ?? false;
18059
18788
  const lineColor = palette.textMuted;
18060
- const HIT_AREA_HEIGHT = 36;
18061
18789
  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));
18062
18790
  const BAND_HEIGHT = 22;
18063
18791
  const bandX = sectionLineX1 - 10;
@@ -19450,7 +20178,6 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
19450
20178
  const positions = groupNodes.map((n) => yScale(n));
19451
20179
  const minY = Math.min(...positions) - bandPad;
19452
20180
  const maxY = Math.max(...positions) + bandPad;
19453
- const bandColor = group.color ?? mutedColor;
19454
20181
  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", () => {
19455
20182
  if (onClickItem) onClickItem(group.lineNumber);
19456
20183
  });
@@ -19494,7 +20221,6 @@ function renderArcDiagram(container, parsed, palette, _isDark, onClickItem, expo
19494
20221
  const positions = groupNodes.map((n) => xScale(n));
19495
20222
  const minX = Math.min(...positions) - bandPad;
19496
20223
  const maxX = Math.max(...positions) + bandPad;
19497
- const bandColor = group.color ?? mutedColor;
19498
20224
  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", () => {
19499
20225
  if (onClickItem) onClickItem(group.lineNumber);
19500
20226
  });
@@ -19812,6 +20538,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
19812
20538
  const textColor = palette.text;
19813
20539
  const mutedColor = palette.border;
19814
20540
  const bgColor = palette.bg;
20541
+ const bg = isDark ? palette.surface : palette.bg;
19815
20542
  const colors = getSeriesColors(palette);
19816
20543
  const groupColorMap = /* @__PURE__ */ new Map();
19817
20544
  timelineGroups.forEach((grp, i) => {
@@ -20073,7 +20800,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20073
20800
  if (ev.endDate) {
20074
20801
  const y2 = yScale(parseTimelineDate(ev.endDate));
20075
20802
  const rectH = Math.max(y2 - y, 4);
20076
- let fill2 = evColor;
20803
+ let fill2 = mix(evColor, bg, 30);
20077
20804
  if (ev.uncertain) {
20078
20805
  const gradientId = `uncertain-vg-${ev.lineNumber}`;
20079
20806
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -20081,13 +20808,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20081
20808
  { offset: "0%", opacity: 1 },
20082
20809
  { offset: "80%", opacity: 1 },
20083
20810
  { offset: "100%", opacity: 0 }
20084
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", laneColor).attr("stop-opacity", (d) => d.opacity);
20811
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", mix(laneColor, bg, 30)).attr("stop-opacity", (d) => d.opacity);
20085
20812
  fill2 = `url(#${gradientId})`;
20086
20813
  }
20087
- evG.append("rect").attr("x", laneCenter - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2);
20814
+ 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);
20088
20815
  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);
20089
20816
  } else {
20090
- evG.append("circle").attr("cx", laneCenter).attr("cy", y).attr("r", 4).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
20817
+ 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);
20091
20818
  evG.append("text").attr("x", laneCenter + 10).attr("y", y).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "10px").text(ev.label);
20092
20819
  }
20093
20820
  }
@@ -20180,7 +20907,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20180
20907
  if (ev.endDate) {
20181
20908
  const y2 = yScale(parseTimelineDate(ev.endDate));
20182
20909
  const rectH = Math.max(y2 - y, 4);
20183
- let fill2 = color;
20910
+ let fill2 = mix(color, bg, 30);
20184
20911
  if (ev.uncertain) {
20185
20912
  const gradientId = `uncertain-v-${ev.lineNumber}`;
20186
20913
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -20188,13 +20915,13 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20188
20915
  { offset: "0%", opacity: 1 },
20189
20916
  { offset: "80%", opacity: 1 },
20190
20917
  { offset: "100%", opacity: 0 }
20191
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", color).attr("stop-opacity", (d) => d.opacity);
20918
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", mix(color, bg, 30)).attr("stop-opacity", (d) => d.opacity);
20192
20919
  fill2 = `url(#${gradientId})`;
20193
20920
  }
20194
- evG.append("rect").attr("x", axisX - 6).attr("y", y).attr("width", 12).attr("height", rectH).attr("rx", 4).attr("fill", fill2);
20921
+ 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);
20195
20922
  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);
20196
20923
  } else {
20197
- evG.append("circle").attr("cx", axisX).attr("cy", y).attr("r", 4).attr("fill", color).attr("stroke", bgColor).attr("stroke-width", 1.5);
20924
+ 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);
20198
20925
  evG.append("text").attr("x", axisX + 16).attr("y", y).attr("dy", "0.35em").attr("fill", textColor).attr("font-size", "11px").text(ev.label);
20199
20926
  }
20200
20927
  evG.append("text").attr("x", axisX - 14).attr(
@@ -20345,7 +21072,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20345
21072
  const rectW = Math.max(x2 - x, 4);
20346
21073
  const estLabelWidth = ev.label.length * 7 + 16;
20347
21074
  const labelFitsInside = rectW >= estLabelWidth;
20348
- let fill2 = evColor;
21075
+ let fill2 = mix(evColor, bg, 30);
20349
21076
  if (ev.uncertain) {
20350
21077
  const gradientId = `uncertain-${ev.lineNumber}`;
20351
21078
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -20353,12 +21080,12 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20353
21080
  { offset: "0%", opacity: 1 },
20354
21081
  { offset: "80%", opacity: 1 },
20355
21082
  { offset: "100%", opacity: 0 }
20356
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", evColor).attr("stop-opacity", (d) => d.opacity);
21083
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", mix(evColor, bg, 30)).attr("stop-opacity", (d) => d.opacity);
20357
21084
  fill2 = `url(#${gradientId})`;
20358
21085
  }
20359
- 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);
21086
+ 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);
20360
21087
  if (labelFitsInside) {
20361
- 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);
21088
+ 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);
20362
21089
  } else {
20363
21090
  const wouldFlipLeft = x + rectW > innerWidth * 0.6;
20364
21091
  const labelFitsLeft = x - 6 - estLabelWidth > 0;
@@ -20370,7 +21097,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20370
21097
  const wouldFlipLeft = x > innerWidth * 0.6;
20371
21098
  const labelFitsLeft = x - 10 - estLabelWidth > 0;
20372
21099
  const flipLeft = wouldFlipLeft && labelFitsLeft;
20373
- evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", evColor).attr("stroke", bgColor).attr("stroke-width", 1.5);
21100
+ 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);
20374
21101
  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);
20375
21102
  }
20376
21103
  });
@@ -20483,7 +21210,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20483
21210
  const rectW = Math.max(x2 - x, 4);
20484
21211
  const estLabelWidth = ev.label.length * 7 + 16;
20485
21212
  const labelFitsInside = rectW >= estLabelWidth;
20486
- let fill2 = color;
21213
+ let fill2 = mix(color, bg, 30);
20487
21214
  if (ev.uncertain) {
20488
21215
  const gradientId = `uncertain-ts-${ev.lineNumber}`;
20489
21216
  const defs = svg.select("defs").node() || svg.append("defs").node();
@@ -20491,12 +21218,12 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20491
21218
  { offset: "0%", opacity: 1 },
20492
21219
  { offset: "80%", opacity: 1 },
20493
21220
  { offset: "100%", opacity: 0 }
20494
- ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", color).attr("stop-opacity", (d) => d.opacity);
21221
+ ]).enter().append("stop").attr("offset", (d) => d.offset).attr("stop-color", mix(color, bg, 30)).attr("stop-opacity", (d) => d.opacity);
20495
21222
  fill2 = `url(#${gradientId})`;
20496
21223
  }
20497
- 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);
21224
+ 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);
20498
21225
  if (labelFitsInside) {
20499
- 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);
21226
+ 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);
20500
21227
  } else {
20501
21228
  const wouldFlipLeft = x + rectW > innerWidth * 0.6;
20502
21229
  const labelFitsLeft = x - 6 - estLabelWidth > 0;
@@ -20508,7 +21235,7 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20508
21235
  const wouldFlipLeft = x > innerWidth * 0.6;
20509
21236
  const labelFitsLeft = x - 10 - estLabelWidth > 0;
20510
21237
  const flipLeft = wouldFlipLeft && labelFitsLeft;
20511
- evG.append("circle").attr("cx", x).attr("cy", y).attr("r", 5).attr("fill", color).attr("stroke", bgColor).attr("stroke-width", 1.5);
21238
+ 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);
20512
21239
  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);
20513
21240
  }
20514
21241
  });
@@ -20663,8 +21390,8 @@ function renderTimeline(container, parsed, palette, isDark, onClickItem, exportD
20663
21390
  } else {
20664
21391
  color = ev.group && groupColorMap.has(ev.group) ? groupColorMap.get(ev.group) : textColor;
20665
21392
  }
20666
- el.selectAll("rect").attr("fill", color);
20667
- el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", color);
21393
+ el.selectAll("rect").attr("fill", mix(color, bg, 30)).attr("stroke", color);
21394
+ el.selectAll("circle:not(.tl-event-point-outline)").attr("fill", mix(color, bg, 30)).attr("stroke", color);
20668
21395
  });
20669
21396
  };
20670
21397
  var drawSwimlaneIcon = drawSwimlaneIcon2, relayout = relayout2, drawLegend = drawLegend2, recolorEvents = recolorEvents2;
@@ -21129,7 +21856,6 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
21129
21856
  const init2 = initD3Chart(container, palette, exportDims);
21130
21857
  if (!init2) return;
21131
21858
  const { svg, width, height, textColor } = init2;
21132
- const mutedColor = palette.textMuted;
21133
21859
  const borderColor = palette.border;
21134
21860
  const defaultColors = [
21135
21861
  palette.colors.blue,
@@ -21153,13 +21879,17 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
21153
21879
  const f = r.length === 3 ? r[0] + r[0] + r[1] + r[1] + r[2] + r[2] : r;
21154
21880
  return [parseInt(f.substring(0, 2), 16), parseInt(f.substring(2, 4), 16), parseInt(f.substring(4, 6), 16)];
21155
21881
  };
21156
- const [ar, ag, ab] = parse(a), [br, bg, bb] = parse(b), t = pct / 100;
21882
+ const [ar, ag, ab] = parse(a), [br, bg2, bb] = parse(b), t = pct / 100;
21157
21883
  const c = (x, y) => Math.round(x * t + y * (1 - t)).toString(16).padStart(2, "0");
21158
- return `#${c(ar, br)}${c(ag, bg)}${c(ab, bb)}`;
21884
+ return `#${c(ar, br)}${c(ag, bg2)}${c(ab, bb)}`;
21159
21885
  };
21160
- const getQuadrantFill = (label, defaultIdx) => {
21886
+ const bg = isDark ? palette.surface : palette.bg;
21887
+ const getQuadrantColor = (label, defaultIdx) => {
21161
21888
  return label?.color ?? defaultColors[defaultIdx % defaultColors.length];
21162
21889
  };
21890
+ const getQuadrantFill = (label, defaultIdx) => {
21891
+ return mixHex(getQuadrantColor(label, defaultIdx), bg, 30);
21892
+ };
21163
21893
  const quadrantDefs = [
21164
21894
  {
21165
21895
  position: "top-left",
@@ -21210,12 +21940,11 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
21210
21940
  // purple
21211
21941
  }
21212
21942
  ];
21213
- 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);
21214
- const contrastColor = "#ffffff";
21943
+ 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);
21215
21944
  const shadowColor = "rgba(0,0,0,0.4)";
21216
21945
  const getQuadrantLabelColor = (d) => {
21217
- const fill2 = getQuadrantFill(d.label, d.colorIdx);
21218
- return mixHex("#000000", fill2, 40);
21946
+ const color = getQuadrantColor(d.label, d.colorIdx);
21947
+ return mixHex("#000000", color, 40);
21219
21948
  };
21220
21949
  const LABEL_MAX_FONT = 48;
21221
21950
  const LABEL_MIN_FONT = 14;
@@ -21356,7 +22085,7 @@ function renderQuadrant(container, parsed, palette, isDark, onClickItem, exportD
21356
22085
  const pointColor = quadDef?.label?.color ?? defaultColors[quadDef?.colorIdx ?? 0];
21357
22086
  const pointG = pointsG.append("g").attr("class", "point-group").attr("data-line-number", String(point.lineNumber));
21358
22087
  pointG.append("circle").attr("cx", cx).attr("cy", cy).attr("r", 6).attr("fill", "#ffffff").attr("stroke", pointColor).attr("stroke-width", 2);
21359
- 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);
22088
+ 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);
21360
22089
  const tipHtml = `<strong>${point.label}</strong><br>x: ${point.x.toFixed(2)}, y: ${point.y.toFixed(2)}`;
21361
22090
  pointG.style("cursor", onClickItem ? "pointer" : "default").on("mouseenter", (event) => {
21362
22091
  showTooltip(tooltip, tipHtml, event);
@@ -22629,6 +23358,34 @@ function decodeDiagramUrl(hash) {
22629
23358
  }
22630
23359
  }
22631
23360
 
23361
+ // src/completion.ts
23362
+ init_parser3();
23363
+ init_flowchart_parser();
23364
+ init_parser9();
23365
+ init_parser2();
23366
+ var registry = /* @__PURE__ */ new Map();
23367
+ function registerExtractor(kind, fn) {
23368
+ registry.set(kind, fn);
23369
+ }
23370
+ function extractDiagramSymbols(docText) {
23371
+ let chartType = null;
23372
+ for (const line10 of docText.split("\n")) {
23373
+ const m = line10.match(/^\s*chart\s*:\s*(.+)/i);
23374
+ if (m) {
23375
+ chartType = m[1].trim().toLowerCase();
23376
+ break;
23377
+ }
23378
+ }
23379
+ if (!chartType) return null;
23380
+ const fn = registry.get(chartType);
23381
+ if (!fn) return null;
23382
+ return fn(docText);
23383
+ }
23384
+ registerExtractor("er", extractSymbols3);
23385
+ registerExtractor("flowchart", extractSymbols);
23386
+ registerExtractor("infra", extractSymbols4);
23387
+ registerExtractor("class", extractSymbols2);
23388
+
22632
23389
  // src/index.ts
22633
23390
  init_branding();
22634
23391
  export {
@@ -22660,6 +23417,7 @@ export {
22660
23417
  contrastText,
22661
23418
  decodeDiagramUrl,
22662
23419
  encodeDiagramUrl,
23420
+ extractDiagramSymbols,
22663
23421
  formatDateLabel,
22664
23422
  formatDgmoError,
22665
23423
  getAvailablePalettes,
@@ -22723,6 +23481,7 @@ export {
22723
23481
  parseState,
22724
23482
  parseTimelineDate,
22725
23483
  parseVisualization,
23484
+ registerExtractor,
22726
23485
  registerPalette,
22727
23486
  render,
22728
23487
  renderArcDiagram,