@markdy/core 1.0.8 → 1.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  type LayoutDirection = "LR" | "RL" | "TB" | "BT";
6
6
  type EdgeKind = "request" | "response" | "event" | "dependency";
7
7
  type DiagramType = "architecture" | "flowchart" | "tree" | "state" | "sequence" | "constellation" | "loop" | "flywheel" | "medallion" | "quadrant" | "swimlane" | "pyramid" | "radar" | "timeline" | "gantt" | "venn" | "layers" | "nested";
8
- type NodeShape = "card" | "rounded" | "diamond" | "circle" | "pill" | "terminal";
8
+ type NodeShape = "card" | "rounded" | "diamond" | "circle" | "pill" | "terminal" | "container";
9
9
  type SceneMeta = {
10
10
  title?: string;
11
11
  width: number;
package/dist/index.js CHANGED
@@ -489,12 +489,12 @@ function canonicalNodeKind(kind) {
489
489
  }
490
490
 
491
491
  // src/compiler.ts
492
- var SAFE = 44;
493
- var TITLE_BAND = 76;
494
- var NODE_W = 168;
495
- var NODE_H = 72;
496
- var VENN_NODE_SIZE = 136;
497
- var GROUP_PAD = 24;
492
+ var SAFE = 48;
493
+ var TITLE_BAND = 84;
494
+ var NODE_W = 180;
495
+ var NODE_H = 76;
496
+ var VENN_NODE_SIZE = 220;
497
+ var GROUP_PAD = 28;
498
498
  var DEFAULTS = {
499
499
  show: 0.35,
500
500
  hide: 0.35,
@@ -594,7 +594,7 @@ function assignRanks(nodeIds, edges, direction) {
594
594
  return ranks;
595
595
  }
596
596
  function snapGrid(n) {
597
- return Math.round(n / 8) * 8;
597
+ return Math.round(n / 4) * 4;
598
598
  }
599
599
  function layoutRanked(ast, edges, opts) {
600
600
  const nodeIds = Object.keys(ast.nodes);
@@ -607,31 +607,106 @@ function layoutRanked(ast, edges, opts) {
607
607
  if (!byRank.has(r)) byRank.set(r, []);
608
608
  byRank.get(r).push(id);
609
609
  }
610
- for (const ids of byRank.values()) ids.sort();
610
+ const nodeGroupIndex = /* @__PURE__ */ new Map();
611
+ const groupIds = Object.keys(ast.groups);
612
+ groupIds.forEach((gId, gIdx) => {
613
+ for (const memberId of ast.groups[gId].members) {
614
+ nodeGroupIndex.set(memberId, gIdx);
615
+ }
616
+ });
617
+ for (const ids of byRank.values()) {
618
+ ids.sort((a, b) => {
619
+ const ga = nodeGroupIndex.has(a) ? nodeGroupIndex.get(a) : 9999;
620
+ const gb = nodeGroupIndex.has(b) ? nodeGroupIndex.get(b) : 9999;
621
+ if (ga !== gb) return ga - gb;
622
+ return a.localeCompare(b);
623
+ });
624
+ }
611
625
  const isVertical = direction === "TB" || direction === "BT";
612
626
  const contentW = ast.meta.width - SAFE * 2;
613
627
  const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
614
628
  const maxRank = Math.max(...byRank.keys(), 0);
615
629
  const rankCount = maxRank + 1;
630
+ const maxInRank = Math.max(...[...byRank.values()].map((v) => v.length), 1);
616
631
  const nodes = [];
632
+ if (opts.columnLayout) {
633
+ const count = nodeIds.length;
634
+ const colSpacing = Math.max(NODE_W + 32, contentW / Math.max(count, 1));
635
+ const totalW2 = (count - 1) * colSpacing + NODE_W;
636
+ const startX2 = SAFE + Math.max(0, (contentW - totalW2) / 2);
637
+ nodeIds.forEach((id, idx) => {
638
+ const decl = ast.nodes[id];
639
+ const role = nodeRole(decl.kind);
640
+ const x = startX2 + idx * colSpacing;
641
+ const y = TITLE_BAND + 32;
642
+ nodes.push({
643
+ id,
644
+ kind: decl.kind,
645
+ role,
646
+ label: decl.label,
647
+ x: snapGrid(x),
648
+ y: snapGrid(y),
649
+ width: NODE_W,
650
+ height: NODE_H,
651
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
652
+ props: decl.props,
653
+ opacity: 0,
654
+ shape: nodeShape(decl.kind, dtype),
655
+ focal: decl.props.focal === true || decl.props.accent === true,
656
+ column: idx
657
+ });
658
+ });
659
+ return nodes;
660
+ }
661
+ if (isVertical) {
662
+ const rowGap = Math.max(NODE_H + 40, Math.min(180, contentH / Math.max(rankCount, 1)));
663
+ const totalH = (rankCount - 1) * rowGap + NODE_H;
664
+ const startY = TITLE_BAND + Math.max(0, (contentH - totalH) / 2);
665
+ for (const [rank, ids] of [...byRank.entries()].sort((a, b) => a[0] - b[0])) {
666
+ const rowCount = ids.length;
667
+ const colSpacing = Math.max(NODE_W + 36, Math.min(260, contentW / Math.max(rowCount, 1)));
668
+ const rankW = (rowCount - 1) * colSpacing + NODE_W;
669
+ const rankStartX = SAFE + Math.max(0, (contentW - rankW) / 2);
670
+ const y = startY + rank * rowGap;
671
+ ids.forEach((id, idx) => {
672
+ const decl = ast.nodes[id];
673
+ const role = nodeRole(decl.kind);
674
+ const x = rankStartX + idx * colSpacing;
675
+ const focal = decl.props.focal === true || decl.props.accent === true;
676
+ nodes.push({
677
+ id,
678
+ kind: decl.kind,
679
+ role,
680
+ label: decl.label,
681
+ x: snapGrid(x),
682
+ y: snapGrid(y),
683
+ width: NODE_W,
684
+ height: NODE_H,
685
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
686
+ props: decl.props,
687
+ opacity: 0,
688
+ shape: nodeShape(decl.kind, dtype),
689
+ focal
690
+ });
691
+ });
692
+ }
693
+ return nodes;
694
+ }
695
+ const maxPossibleStep = rankCount > 1 ? (contentW - NODE_W) / (rankCount - 1) : 0;
696
+ const colGap = rankCount > 1 ? Math.min(220, Math.max(50, maxPossibleStep)) : 0;
697
+ const totalW = (rankCount - 1) * colGap + NODE_W;
698
+ const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
617
699
  for (const [rank, ids] of [...byRank.entries()].sort((a, b) => a[0] - b[0])) {
618
700
  const rowCount = ids.length;
619
- const orderedIds = opts.columnLayout ? nodeIds.filter((id) => ids.includes(id)) : [...ids].sort();
620
- orderedIds.forEach((id, idx) => {
701
+ const maxPossibleRowStep = rowCount > 1 ? (contentH - NODE_H) / (rowCount - 1) : 0;
702
+ const rowSpacing = rowCount > 1 ? Math.min(150, Math.max(36, maxPossibleRowStep)) : 0;
703
+ const colH = (rowCount - 1) * rowSpacing + NODE_H;
704
+ const colStartY = TITLE_BAND + Math.max(0, (contentH - colH) / 2);
705
+ const x = startX + rank * colGap;
706
+ ids.forEach((id, idx) => {
621
707
  const decl = ast.nodes[id];
622
708
  const role = nodeRole(decl.kind);
623
- let x;
624
- let y;
625
- if (opts.columnLayout) {
626
- x = SAFE + contentW / (rowCount + 1) * (idx + 1) - NODE_W / 2;
627
- y = TITLE_BAND + 48;
628
- } else if (isVertical) {
629
- x = SAFE + contentW / (rowCount + 1) * (idx + 1) - NODE_W / 2;
630
- y = TITLE_BAND + contentH / Math.max(rankCount, 1) * rank + (contentH / Math.max(rankCount, 1) - NODE_H) / 2;
631
- } else {
632
- x = SAFE + contentW / Math.max(rankCount, 1) * rank + (contentW / Math.max(rankCount, 1) - NODE_W) / 2;
633
- y = TITLE_BAND + contentH / (rowCount + 1) * (idx + 1) - NODE_H / 2;
634
- }
709
+ const y = colStartY + idx * rowSpacing;
635
710
  const focal = decl.props.focal === true || decl.props.accent === true;
636
711
  nodes.push({
637
712
  id,
@@ -646,8 +721,7 @@ function layoutRanked(ast, edges, opts) {
646
721
  props: decl.props,
647
722
  opacity: 0,
648
723
  shape: nodeShape(decl.kind, dtype),
649
- focal,
650
- column: opts.columnLayout ? idx : void 0
724
+ focal
651
725
  });
652
726
  });
653
727
  }
@@ -655,6 +729,7 @@ function layoutRanked(ast, edges, opts) {
655
729
  }
656
730
  function layoutTree(ast, edges) {
657
731
  const nodeIds = Object.keys(ast.nodes);
732
+ if (nodeIds.length === 0) return [];
658
733
  const children = /* @__PURE__ */ new Map();
659
734
  const parent = /* @__PURE__ */ new Map();
660
735
  for (const id of nodeIds) children.set(id, []);
@@ -665,49 +740,95 @@ function layoutTree(ast, edges) {
665
740
  parent.set(e.to, e.from);
666
741
  children.get(e.from).push(e.to);
667
742
  }
668
- const root = nodeIds.find((id) => !parent.has(id)) ?? nodeIds[0];
743
+ const roots = nodeIds.filter((id) => !parent.has(id));
744
+ if (roots.length === 0) roots.push(nodeIds[0]);
669
745
  const depth = /* @__PURE__ */ new Map();
670
- const queue = [root];
671
- depth.set(root, 0);
746
+ const queue = [...roots];
747
+ for (const r of roots) depth.set(r, 0);
672
748
  while (queue.length) {
673
749
  const id = queue.shift();
750
+ const d = depth.get(id);
674
751
  for (const child of children.get(id) ?? []) {
675
- if (depth.has(child)) continue;
676
- depth.set(child, (depth.get(id) ?? 0) + 1);
677
- queue.push(child);
752
+ if (!depth.has(child)) {
753
+ depth.set(child, d + 1);
754
+ queue.push(child);
755
+ }
678
756
  }
679
757
  }
680
758
  for (const id of nodeIds) if (!depth.has(id)) depth.set(id, 0);
681
- const byDepth = /* @__PURE__ */ new Map();
682
- for (const id of nodeIds) {
683
- const d = depth.get(id) ?? 0;
684
- if (!byDepth.has(d)) byDepth.set(d, []);
685
- byDepth.get(d).push(id);
759
+ const maxDepth = Math.max(...depth.values(), 0);
760
+ const subtreeSpan = /* @__PURE__ */ new Map();
761
+ const minLeafWidth = NODE_W + 48;
762
+ function measureSubtree(id) {
763
+ const kids = children.get(id) ?? [];
764
+ if (kids.length === 0) {
765
+ subtreeSpan.set(id, minLeafWidth);
766
+ return minLeafWidth;
767
+ }
768
+ let total = 0;
769
+ for (const kid of kids) {
770
+ total += measureSubtree(kid);
771
+ }
772
+ const span = Math.max(minLeafWidth, total);
773
+ subtreeSpan.set(id, span);
774
+ return span;
775
+ }
776
+ let totalRootsWidth = 0;
777
+ for (const r of roots) {
778
+ totalRootsWidth += measureSubtree(r);
686
779
  }
687
- for (const ids of byDepth.values()) ids.sort();
688
780
  const contentW = ast.meta.width - SAFE * 2;
689
781
  const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
690
- const maxDepth = Math.max(...byDepth.keys(), 0);
782
+ const treeStartX = SAFE + Math.max(0, (contentW - totalRootsWidth) / 2);
783
+ const levelHeight = Math.max(NODE_H + 48, Math.min(160, contentH / Math.max(maxDepth + 1, 1)));
784
+ const totalTreeHeight = maxDepth * levelHeight + NODE_H;
785
+ const treeStartY = TITLE_BAND + Math.max(16, (contentH - totalTreeHeight) / 2);
786
+ const nodePositions = /* @__PURE__ */ new Map();
787
+ function positionSubtree(id, leftX) {
788
+ const d = depth.get(id) ?? 0;
789
+ const y = treeStartY + d * levelHeight;
790
+ const kids = children.get(id) ?? [];
791
+ const span = subtreeSpan.get(id) ?? minLeafWidth;
792
+ if (kids.length === 0) {
793
+ const centerX = leftX + span / 2;
794
+ nodePositions.set(id, { x: centerX - NODE_W / 2, y });
795
+ return;
796
+ }
797
+ let curX = leftX;
798
+ for (const kid of kids) {
799
+ const kidSpan = subtreeSpan.get(kid) ?? minLeafWidth;
800
+ positionSubtree(kid, curX);
801
+ curX += kidSpan;
802
+ }
803
+ const firstChild = nodePositions.get(kids[0]);
804
+ const lastChild = nodePositions.get(kids[kids.length - 1]);
805
+ const parentCenterX = (firstChild.x + NODE_W / 2 + lastChild.x + NODE_W / 2) / 2;
806
+ nodePositions.set(id, { x: parentCenterX - NODE_W / 2, y });
807
+ }
808
+ let currentRootLeft = treeStartX;
809
+ for (const r of roots) {
810
+ const span = subtreeSpan.get(r) ?? minLeafWidth;
811
+ positionSubtree(r, currentRootLeft);
812
+ currentRootLeft += span;
813
+ }
691
814
  const nodes = [];
692
- for (const [d, ids] of [...byDepth.entries()].sort((a, b) => a[0] - b[0])) {
693
- ids.forEach((id, idx) => {
694
- const decl = ast.nodes[id];
695
- const x = SAFE + contentW / (ids.length + 1) * (idx + 1) - NODE_W / 2;
696
- const y = TITLE_BAND + contentH / Math.max(maxDepth + 1, 1) * d + 24;
697
- nodes.push({
698
- id,
699
- kind: decl.kind,
700
- role: nodeRole(decl.kind),
701
- label: decl.label,
702
- x: snapGrid(x),
703
- y: snapGrid(y),
704
- width: NODE_W,
705
- height: NODE_H,
706
- style: decl.style ? ast.styles[decl.style]?.props : void 0,
707
- props: decl.props,
708
- opacity: 0,
709
- shape: "card"
710
- });
815
+ for (const id of nodeIds) {
816
+ const decl = ast.nodes[id];
817
+ const pos = nodePositions.get(id) ?? { x: SAFE, y: TITLE_BAND };
818
+ nodes.push({
819
+ id,
820
+ kind: decl.kind,
821
+ role: nodeRole(decl.kind),
822
+ label: decl.label,
823
+ x: snapGrid(pos.x),
824
+ y: snapGrid(pos.y),
825
+ width: NODE_W,
826
+ height: NODE_H,
827
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
828
+ props: decl.props,
829
+ opacity: 0,
830
+ shape: "card",
831
+ focal: decl.props.focal === true || decl.props.accent === true
711
832
  });
712
833
  }
713
834
  return nodes;
@@ -792,8 +913,8 @@ function layoutLoop(ast) {
792
913
  const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
793
914
  const centerX = SAFE + contentW / 2;
794
915
  const centerY = TITLE_BAND + contentH / 2;
795
- const radiusX = Math.max(140, contentW / 2 - NODE_W / 2 - SAFE);
796
- const radiusY = Math.max(120, contentH / 2 - NODE_H / 2 - SAFE);
916
+ const radiusX = Math.min(contentW * 0.4, Math.max(220, (contentW - NODE_W) / 2 - 32));
917
+ const radiusY = Math.min(contentH * 0.38, Math.max(150, (contentH - NODE_H) / 2 - 32));
797
918
  const nodes = [];
798
919
  for (const id of nodeIds) {
799
920
  const decl = ast.nodes[id];
@@ -801,8 +922,8 @@ function layoutLoop(ast) {
801
922
  let x;
802
923
  let y;
803
924
  if (isHub) {
804
- x = centerX - NODE_W * 1.2 / 2;
805
- y = centerY - NODE_H * 1.1 / 2;
925
+ x = centerX - NODE_W * 1.25 / 2;
926
+ y = centerY - NODE_H * 1.15 / 2;
806
927
  } else {
807
928
  const idx = stationIds.indexOf(id);
808
929
  const angle = -Math.PI / 2 + idx * 2 * Math.PI / Math.max(stationIds.length, 1);
@@ -817,8 +938,8 @@ function layoutLoop(ast) {
817
938
  label: decl.label,
818
939
  x: snapGrid(x),
819
940
  y: snapGrid(y),
820
- width: isHub ? snapGrid(NODE_W * 1.2) : NODE_W,
821
- height: isHub ? snapGrid(NODE_H * 1.1) : NODE_H,
941
+ width: isHub ? snapGrid(NODE_W * 1.25) : NODE_W,
942
+ height: isHub ? snapGrid(NODE_H * 1.15) : NODE_H,
822
943
  style: decl.style ? ast.styles[decl.style]?.props : void 0,
823
944
  props: decl.props,
824
945
  opacity: 0,
@@ -850,13 +971,19 @@ function layoutMedallion(ast, edges) {
850
971
  const tierCount = activeTiers.length || 1;
851
972
  const contentW = ast.meta.width - SAFE * 2;
852
973
  const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
974
+ const colGap = Math.max(NODE_W + 48, contentW / (tierCount + 1));
975
+ const totalW = (tierCount - 1) * colGap + NODE_W;
976
+ const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
853
977
  const nodes = [];
854
978
  activeTiers.forEach(([_, ids], colIdx) => {
855
979
  const colCount = ids.length;
856
- const colX = SAFE + contentW / (tierCount + 1) * (colIdx + 1) - NODE_W / 2;
980
+ const colX = startX + colIdx * colGap;
981
+ const rowSpacing = Math.max(NODE_H + 32, contentH / (colCount + 1));
982
+ const colH = (colCount - 1) * rowSpacing + NODE_H;
983
+ const startY = TITLE_BAND + Math.max(0, (contentH - colH) / 2);
857
984
  ids.forEach((id, rowIdx) => {
858
985
  const decl = ast.nodes[id];
859
- const rowY = TITLE_BAND + contentH / (colCount + 1) * (rowIdx + 1) - NODE_H / 2;
986
+ const rowY = startY + rowIdx * rowSpacing;
860
987
  const focal = decl.props.focal === true || decl.props.accent === true;
861
988
  nodes.push({
862
989
  id,
@@ -916,7 +1043,7 @@ function layoutQuadrant(ast) {
916
1043
  ids.forEach((id, idx) => {
917
1044
  const decl = ast.nodes[id];
918
1045
  const offsetCount = ids.length;
919
- const rowOffset = (idx - (offsetCount - 1) / 2) * (NODE_H + 16);
1046
+ const rowOffset = (idx - (offsetCount - 1) / 2) * (NODE_H + 20);
920
1047
  const x = center.x - NODE_W / 2;
921
1048
  const y = center.y + rowOffset - NODE_H / 2;
922
1049
  const focal = decl.props.focal === true || decl.props.accent === true;
@@ -966,9 +1093,12 @@ function layoutSwimlane(ast, edges) {
966
1093
  activeLanes.forEach(([_, ids], laneIdx) => {
967
1094
  const laneY = TITLE_BAND + laneIdx * laneHeight + (laneHeight - NODE_H) / 2;
968
1095
  const count = ids.length;
1096
+ const colSpacing = Math.max(NODE_W + 48, Math.min(260, contentW / Math.max(count + 1, 2)));
1097
+ const totalW = (count - 1) * colSpacing + NODE_W;
1098
+ const startX = SAFE + Math.max(0, (contentW - totalW) / 2);
969
1099
  ids.forEach((id, colIdx) => {
970
1100
  const decl = ast.nodes[id];
971
- const colX = SAFE + contentW / (count + 1) * (colIdx + 1) - NODE_W / 2;
1101
+ const colX = startX + colIdx * colSpacing;
972
1102
  const focal = decl.props.focal === true || decl.props.accent === true;
973
1103
  nodes.push({
974
1104
  id,
@@ -1003,16 +1133,21 @@ function layoutPyramid(ast) {
1003
1133
  const tierCount = sortedTiers.length || 1;
1004
1134
  const contentW = ast.meta.width - SAFE * 2;
1005
1135
  const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1136
+ const centerX = SAFE + contentW / 2;
1137
+ const totalH = tierCount * NODE_H + (tierCount - 1) * 24;
1138
+ const startY = TITLE_BAND + Math.max(16, (contentH - totalH) / 2);
1006
1139
  const nodes = [];
1007
1140
  sortedTiers.forEach(([_, ids], tierIdx) => {
1008
- const tierY = TITLE_BAND + contentH / (tierCount + 1) * (tierIdx + 1) - NODE_H / 2;
1009
- const spreadFraction = 0.4 + 0.6 * tierIdx / Math.max(tierCount - 1, 1);
1010
- const tierWidth = contentW * spreadFraction;
1011
- const tierStartX = SAFE + (contentW - tierWidth) / 2;
1141
+ const tierY = startY + tierIdx * (NODE_H + 24);
1142
+ const baseFraction = 0.32 + 0.44 * tierIdx / Math.max(tierCount - 1, 1);
1143
+ const tierWidth = contentW * baseFraction;
1012
1144
  const count = ids.length;
1145
+ const nodeW = Math.max(NODE_W, Math.min(tierWidth / count - 12, 480));
1146
+ const totalTierW = count * nodeW + (count - 1) * 16;
1147
+ const tierStartX = centerX - totalTierW / 2;
1013
1148
  ids.forEach((id, idx) => {
1014
1149
  const decl = ast.nodes[id];
1015
- const x = tierStartX + tierWidth / (count + 1) * (idx + 1) - NODE_W / 2;
1150
+ const x = tierStartX + idx * (nodeW + 16);
1016
1151
  const focal = decl.props.focal === true || decl.props.accent === true;
1017
1152
  nodes.push({
1018
1153
  id,
@@ -1021,7 +1156,7 @@ function layoutPyramid(ast) {
1021
1156
  label: decl.label,
1022
1157
  x: snapGrid(x),
1023
1158
  y: snapGrid(tierY),
1024
- width: NODE_W,
1159
+ width: snapGrid(nodeW),
1025
1160
  height: NODE_H,
1026
1161
  style: decl.style ? ast.styles[decl.style]?.props : void 0,
1027
1162
  props: decl.props,
@@ -1069,8 +1204,12 @@ function layoutGantt(ast) {
1069
1204
  const nodeIds = Object.keys(ast.nodes);
1070
1205
  if (nodeIds.length === 0) return [];
1071
1206
  const contentW = ast.meta.width - SAFE * 2;
1072
- const rowH = 56;
1073
- const barH = 40;
1207
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1208
+ const N = nodeIds.length;
1209
+ const rowH = Math.min(68, Math.max(52, (contentH - 24) / Math.max(N, 1)));
1210
+ const barH = Math.min(44, rowH - 16);
1211
+ const totalH = N * rowH;
1212
+ const startY = TITLE_BAND + Math.max(16, (contentH - totalH) / 2);
1074
1213
  const nodes = [];
1075
1214
  nodeIds.forEach((id, idx) => {
1076
1215
  const decl = ast.nodes[id];
@@ -1084,7 +1223,7 @@ function layoutGantt(ast) {
1084
1223
  const unitW = contentW / totalPhases;
1085
1224
  const x = SAFE + phase * unitW;
1086
1225
  const w = Math.max(span * unitW - 8, NODE_W);
1087
- const y = TITLE_BAND + idx * rowH + (rowH - barH) / 2;
1226
+ const y = startY + idx * rowH + (rowH - barH) / 2;
1088
1227
  const focal = decl.props.focal === true || decl.props.accent === true;
1089
1228
  nodes.push({
1090
1229
  id,
@@ -1112,18 +1251,18 @@ function layoutVenn(ast) {
1112
1251
  const centerX = SAFE + contentW / 2;
1113
1252
  const centerY = TITLE_BAND + contentH / 2;
1114
1253
  const N = nodeIds.length;
1115
- const radius = Math.min(contentW, contentH) * 0.22;
1254
+ const radius = Math.min(contentW, contentH) * 0.24;
1116
1255
  const nodes = [];
1117
1256
  nodeIds.forEach((id, idx) => {
1118
1257
  const decl = ast.nodes[id];
1119
1258
  let x, y;
1120
1259
  if (N === 2) {
1121
- x = centerX + (idx === 0 ? -radius * 0.55 : radius * 0.55) - VENN_NODE_SIZE / 2;
1260
+ x = centerX + (idx === 0 ? -radius * 0.7 : radius * 0.7) - VENN_NODE_SIZE / 2;
1122
1261
  y = centerY - VENN_NODE_SIZE / 2;
1123
1262
  } else {
1124
1263
  const angle = -Math.PI / 2 + idx * 2 * Math.PI / N;
1125
- x = centerX + radius * 0.6 * Math.cos(angle) - VENN_NODE_SIZE / 2;
1126
- y = centerY + radius * 0.6 * Math.sin(angle) - VENN_NODE_SIZE / 2;
1264
+ x = centerX + radius * 0.85 * Math.cos(angle) - VENN_NODE_SIZE / 2;
1265
+ y = centerY + radius * 0.85 * Math.sin(angle) - VENN_NODE_SIZE / 2;
1127
1266
  }
1128
1267
  const focal = decl.props.focal === true || decl.props.accent === true;
1129
1268
  nodes.push({
@@ -1150,22 +1289,24 @@ function layoutLayers(ast) {
1150
1289
  const contentW = ast.meta.width - SAFE * 2;
1151
1290
  const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1152
1291
  const N = nodeIds.length;
1153
- const layerH = Math.min(Math.max((contentH - (N - 1) * 12) / N, 52), 76);
1154
- const totalH = N * layerH + (N - 1) * 12;
1292
+ const layerH = Math.min(Math.max((contentH - (N - 1) * 14) / N, 52), 68);
1293
+ const totalH = N * layerH + (N - 1) * 14;
1155
1294
  const startY = TITLE_BAND + (contentH - totalH) / 2;
1295
+ const layerW = Math.min(contentW, 880);
1296
+ const startX = SAFE + (contentW - layerW) / 2;
1156
1297
  const nodes = [];
1157
1298
  nodeIds.forEach((id, idx) => {
1158
1299
  const decl = ast.nodes[id];
1159
- const y = startY + idx * (layerH + 12);
1300
+ const y = startY + idx * (layerH + 14);
1160
1301
  const focal = decl.props.focal === true || decl.props.accent === true;
1161
1302
  nodes.push({
1162
1303
  id,
1163
1304
  kind: decl.kind,
1164
1305
  role: nodeRole(decl.kind),
1165
1306
  label: decl.label,
1166
- x: snapGrid(SAFE),
1307
+ x: snapGrid(startX),
1167
1308
  y: snapGrid(y),
1168
- width: snapGrid(contentW),
1309
+ width: snapGrid(layerW),
1169
1310
  height: snapGrid(layerH),
1170
1311
  style: decl.style ? ast.styles[decl.style]?.props : void 0,
1171
1312
  props: decl.props,
@@ -1181,17 +1322,28 @@ function layoutNested(ast) {
1181
1322
  if (nodeIds.length === 0) return [];
1182
1323
  const contentW = ast.meta.width - SAFE * 2;
1183
1324
  const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1325
+ const centerX = SAFE + contentW / 2;
1326
+ const centerY = TITLE_BAND + contentH / 2;
1184
1327
  const N = nodeIds.length;
1185
- const padX = Math.min(36, contentW * 0.4 / N);
1186
- const padY = Math.min(32, contentH * 0.4 / N);
1328
+ const padX = Math.min(54, contentW * 0.42 / Math.max(N, 1));
1329
+ const padY = Math.min(48, contentH * 0.42 / Math.max(N, 1));
1187
1330
  const nodes = [];
1188
1331
  nodeIds.forEach((id, idx) => {
1189
1332
  const decl = ast.nodes[id];
1190
- const x = SAFE + idx * padX;
1191
- const y = TITLE_BAND + idx * padY;
1192
- const w = contentW - idx * padX * 2;
1193
- const h = contentH - idx * padY * 2;
1194
- const focal = decl.props.focal === true || decl.props.accent === true || idx === N - 1;
1333
+ const isCore = idx === N - 1;
1334
+ let x, y, w, h;
1335
+ if (isCore) {
1336
+ w = Math.min(320, contentW - idx * padX * 2);
1337
+ h = 84;
1338
+ x = centerX - w / 2;
1339
+ y = centerY - h / 2 + (N > 1 ? 16 : 0);
1340
+ } else {
1341
+ x = SAFE + idx * padX;
1342
+ y = TITLE_BAND + idx * padY;
1343
+ w = contentW - idx * padX * 2;
1344
+ h = contentH - idx * padY * 2;
1345
+ }
1346
+ const focal = decl.props.focal === true || decl.props.accent === true || isCore;
1195
1347
  nodes.push({
1196
1348
  id,
1197
1349
  kind: decl.kind,
@@ -1204,7 +1356,7 @@ function layoutNested(ast) {
1204
1356
  style: decl.style ? ast.styles[decl.style]?.props : void 0,
1205
1357
  props: decl.props,
1206
1358
  opacity: 0,
1207
- shape: "rounded",
1359
+ shape: isCore ? "card" : "container",
1208
1360
  focal
1209
1361
  });
1210
1362
  });
@@ -1448,9 +1600,12 @@ function scheduleBeats(ast, edges) {
1448
1600
  function buildSequencePlan(ast, cues) {
1449
1601
  if (diagramType(ast) !== "sequence") return { messages: [], activations: [] };
1450
1602
  const flowCues = cues.filter((cue) => cue.kind === "flow" && cue.segments?.[0]);
1451
- const firstY = TITLE_BAND + NODE_H + 64;
1452
- const lastY = ast.meta.height - SAFE - 40;
1453
- const step = flowCues.length > 1 ? Math.max(36, Math.min(64, (lastY - firstY) / (flowCues.length - 1))) : 0;
1603
+ const firstY = TITLE_BAND + NODE_H + 56;
1604
+ const lastY = ast.meta.height - SAFE - 48;
1605
+ const availH = Math.max(120, lastY - firstY);
1606
+ const step = flowCues.length > 1 ? Math.max(52, Math.min(90, availH / flowCues.length)) : 0;
1607
+ const totalSeqH = (flowCues.length - 1) * step;
1608
+ const startY = flowCues.length > 1 ? firstY + Math.max(0, (availH - totalSeqH) / 2) : firstY + 40;
1454
1609
  const messages = [];
1455
1610
  const activations = [];
1456
1611
  flowCues.forEach((cue, index) => {
@@ -1462,7 +1617,7 @@ function buildSequencePlan(ast, cues) {
1462
1617
  to: segment.to,
1463
1618
  kind: segment.op,
1464
1619
  label: segment.label,
1465
- y: snapGrid(firstY + step * index),
1620
+ y: snapGrid(startY + step * index),
1466
1621
  start: cue.start,
1467
1622
  duration: cue.duration,
1468
1623
  beat: cue.beat
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markdy/core",
3
- "version": "1.0.8",
3
+ "version": "1.0.9",
4
4
  "description": "Diagram-native MarkdyScript parser and compiler (auto-layout, edge routing, cue scheduling) — zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "type": "module",