@markdy/core 0.8.19 → 0.8.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -44,6 +44,7 @@ var TECHNICAL_NODE_TYPES = [
44
44
  "warehouse",
45
45
  "lake",
46
46
  "object_store",
47
+ "storage",
47
48
  "bucket",
48
49
  "blob",
49
50
  "volume",
@@ -117,6 +118,7 @@ var TECHNICAL_NODE_TYPES = [
117
118
  "secret",
118
119
  "key",
119
120
  "certificate",
121
+ "security",
120
122
  "repo",
121
123
  "branch",
122
124
  "commit",
@@ -147,6 +149,12 @@ var TECHNICAL_NODE_TYPES = [
147
149
  "loop",
148
150
  "sequence",
149
151
  "participant",
152
+ "hub",
153
+ "station",
154
+ "bronze",
155
+ "silver",
156
+ "gold",
157
+ "lane",
150
158
  "replica",
151
159
  "shard",
152
160
  "leader",
@@ -176,7 +184,9 @@ var VISUAL_PRIMITIVE_TYPES = [
176
184
  "token_strip",
177
185
  "chips",
178
186
  "glyph_card",
179
- "glyph"
187
+ "glyph",
188
+ "external",
189
+ "optional"
180
190
  ];
181
191
  var VISUAL_PRIMITIVE_KINDS = {
182
192
  panel: "flow",
@@ -193,7 +203,9 @@ var VISUAL_PRIMITIVE_KINDS = {
193
203
  token_strip: "flow",
194
204
  chips: "flow",
195
205
  glyph_card: "flow",
196
- glyph: "flow"
206
+ glyph: "flow",
207
+ external: "network",
208
+ optional: "flow"
197
209
  };
198
210
  var TECHNICAL_NODE_KINDS = {
199
211
  service: "compute",
@@ -246,6 +258,7 @@ var TECHNICAL_NODE_KINDS = {
246
258
  warehouse: "data",
247
259
  lake: "data",
248
260
  object_store: "data",
261
+ storage: "data",
249
262
  bucket: "data",
250
263
  blob: "data",
251
264
  volume: "data",
@@ -319,6 +332,7 @@ var TECHNICAL_NODE_KINDS = {
319
332
  secret: "security",
320
333
  key: "security",
321
334
  certificate: "security",
335
+ security: "security",
322
336
  repo: "delivery",
323
337
  branch: "delivery",
324
338
  commit: "delivery",
@@ -349,6 +363,12 @@ var TECHNICAL_NODE_KINDS = {
349
363
  loop: "flow",
350
364
  sequence: "flow",
351
365
  participant: "flow",
366
+ hub: "data",
367
+ station: "compute",
368
+ bronze: "data",
369
+ silver: "data",
370
+ gold: "data",
371
+ lane: "flow",
352
372
  replica: "distributed",
353
373
  shard: "distributed",
354
374
  leader: "distributed",
@@ -369,7 +389,19 @@ var DIAGRAM_TYPES = /* @__PURE__ */ new Set([
369
389
  "tree",
370
390
  "state",
371
391
  "sequence",
372
- "constellation"
392
+ "constellation",
393
+ "loop",
394
+ "flywheel",
395
+ "medallion",
396
+ "quadrant",
397
+ "swimlane",
398
+ "pyramid",
399
+ "radar",
400
+ "timeline",
401
+ "gantt",
402
+ "venn",
403
+ "layers",
404
+ "nested"
373
405
  ]);
374
406
  var EDGE_OPERATORS = {
375
407
  "->": "request",
@@ -400,7 +432,24 @@ var SCENE_KEYS = /* @__PURE__ */ new Set([
400
432
  "duration",
401
433
  "direction",
402
434
  "layout",
403
- "type"
435
+ "type",
436
+ "progressColor",
437
+ "progressBarColor",
438
+ "progress_color",
439
+ "progress_bar_color",
440
+ "progress",
441
+ "progressBar",
442
+ "sceneBoundaryProgress",
443
+ "controls",
444
+ "interactive",
445
+ "interactiveViewport",
446
+ "interactive_viewport",
447
+ "autoplay",
448
+ "loop",
449
+ "copyright",
450
+ "playbackRate",
451
+ "playback_rate",
452
+ "speed"
404
453
  ]);
405
454
  function nodeRole(kind) {
406
455
  const canonical = canonicalNodeKind(kind);
@@ -730,6 +779,470 @@ function layoutConstellation(ast) {
730
779
  }
731
780
  return nodes;
732
781
  }
782
+ function layoutLoop(ast) {
783
+ const nodeIds = Object.keys(ast.nodes);
784
+ if (nodeIds.length === 0) return [];
785
+ const hubId = nodeIds.find((id) => {
786
+ const decl = ast.nodes[id];
787
+ return decl.kind === "hub" || decl.props.hub === true || /^(hub|state|memory|shared_memory|core)$/i.test(id);
788
+ }) ?? nodeIds[0];
789
+ const stationIds = nodeIds.filter((id) => id !== hubId);
790
+ const contentW = ast.meta.width - SAFE * 2;
791
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
792
+ const centerX = SAFE + contentW / 2;
793
+ const centerY = TITLE_BAND + contentH / 2;
794
+ const radiusX = Math.max(140, contentW / 2 - NODE_W / 2 - SAFE);
795
+ const radiusY = Math.max(120, contentH / 2 - NODE_H / 2 - SAFE);
796
+ const nodes = [];
797
+ for (const id of nodeIds) {
798
+ const decl = ast.nodes[id];
799
+ const isHub = id === hubId;
800
+ let x;
801
+ let y;
802
+ if (isHub) {
803
+ x = centerX - NODE_W * 1.2 / 2;
804
+ y = centerY - NODE_H * 1.1 / 2;
805
+ } else {
806
+ const idx = stationIds.indexOf(id);
807
+ const angle = -Math.PI / 2 + idx * 2 * Math.PI / Math.max(stationIds.length, 1);
808
+ x = centerX + Math.cos(angle) * radiusX - NODE_W / 2;
809
+ y = centerY + Math.sin(angle) * radiusY - NODE_H / 2;
810
+ }
811
+ const focal = isHub || decl.props.focal === true || decl.props.accent === true;
812
+ nodes.push({
813
+ id,
814
+ kind: decl.kind,
815
+ role: nodeRole(decl.kind),
816
+ label: decl.label,
817
+ x: snapGrid(x),
818
+ y: snapGrid(y),
819
+ width: isHub ? snapGrid(NODE_W * 1.2) : NODE_W,
820
+ height: isHub ? snapGrid(NODE_H * 1.1) : NODE_H,
821
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
822
+ props: decl.props,
823
+ opacity: 0,
824
+ shape: isHub ? "pill" : "card",
825
+ focal
826
+ });
827
+ }
828
+ return nodes;
829
+ }
830
+ function layoutMedallion(ast, edges) {
831
+ const nodeIds = Object.keys(ast.nodes);
832
+ if (nodeIds.length === 0) return [];
833
+ function getMedallionTier(id, kind) {
834
+ const combined = `${id} ${kind}`.toLowerCase();
835
+ if (kind === "bronze" || combined.includes("bronze") || combined.includes("raw") || combined.includes("landing")) return 1;
836
+ if (kind === "silver" || combined.includes("silver") || combined.includes("clean") || combined.includes("curated") || combined.includes("conformed")) return 2;
837
+ if (kind === "gold" || combined.includes("gold") || combined.includes("agg") || combined.includes("mart") || combined.includes("analytics")) return 3;
838
+ if (combined.includes("bi") || combined.includes("dash") || combined.includes("model") || combined.includes("app") || combined.includes("consumer") || combined.includes("user") || combined.includes("client")) return 4;
839
+ return 0;
840
+ }
841
+ const tiers = /* @__PURE__ */ new Map();
842
+ for (let i = 0; i <= 4; i++) tiers.set(i, []);
843
+ for (const id of nodeIds) {
844
+ const decl = ast.nodes[id];
845
+ const tier = getMedallionTier(id, decl.kind);
846
+ tiers.get(tier).push(id);
847
+ }
848
+ const activeTiers = [...tiers.entries()].filter(([_, ids]) => ids.length > 0);
849
+ const tierCount = activeTiers.length || 1;
850
+ const contentW = ast.meta.width - SAFE * 2;
851
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
852
+ const nodes = [];
853
+ activeTiers.forEach(([_, ids], colIdx) => {
854
+ const colCount = ids.length;
855
+ const colX = SAFE + contentW / (tierCount + 1) * (colIdx + 1) - NODE_W / 2;
856
+ ids.forEach((id, rowIdx) => {
857
+ const decl = ast.nodes[id];
858
+ const rowY = TITLE_BAND + contentH / (colCount + 1) * (rowIdx + 1) - NODE_H / 2;
859
+ const focal = decl.props.focal === true || decl.props.accent === true;
860
+ nodes.push({
861
+ id,
862
+ kind: decl.kind,
863
+ role: nodeRole(decl.kind),
864
+ label: decl.label,
865
+ x: snapGrid(colX),
866
+ y: snapGrid(rowY),
867
+ width: NODE_W,
868
+ height: NODE_H,
869
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
870
+ props: decl.props,
871
+ opacity: 0,
872
+ shape: "card",
873
+ focal
874
+ });
875
+ });
876
+ });
877
+ return nodes;
878
+ }
879
+ function layoutQuadrant(ast) {
880
+ const nodeIds = Object.keys(ast.nodes);
881
+ if (nodeIds.length === 0) return [];
882
+ const contentW = ast.meta.width - SAFE * 2;
883
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
884
+ const centerX = SAFE + contentW / 2;
885
+ const centerY = TITLE_BAND + contentH / 2;
886
+ const quadNodes = /* @__PURE__ */ new Map([
887
+ [1, []],
888
+ [2, []],
889
+ [3, []],
890
+ [4, []]
891
+ ]);
892
+ nodeIds.forEach((id, idx) => {
893
+ const decl = ast.nodes[id];
894
+ let q = 1;
895
+ if (decl.props.quadrant) {
896
+ const qVal = String(decl.props.quadrant).toUpperCase();
897
+ if (qVal === "Q1" || qVal === "1" || qVal === "TOP_RIGHT") q = 1;
898
+ else if (qVal === "Q2" || qVal === "2" || qVal === "TOP_LEFT") q = 2;
899
+ else if (qVal === "Q3" || qVal === "3" || qVal === "BOTTOM_LEFT") q = 3;
900
+ else if (qVal === "Q4" || qVal === "4" || qVal === "BOTTOM_RIGHT") q = 4;
901
+ } else {
902
+ q = idx % 4 + 1;
903
+ }
904
+ quadNodes.get(q).push(id);
905
+ });
906
+ const nodes = [];
907
+ const quadCenters = {
908
+ 1: { x: centerX + contentW / 4, y: centerY - contentH / 4 },
909
+ 2: { x: centerX - contentW / 4, y: centerY - contentH / 4 },
910
+ 3: { x: centerX - contentW / 4, y: centerY + contentH / 4 },
911
+ 4: { x: centerX + contentW / 4, y: centerY + contentH / 4 }
912
+ };
913
+ for (const [qNum, ids] of quadNodes) {
914
+ const center = quadCenters[qNum];
915
+ ids.forEach((id, idx) => {
916
+ const decl = ast.nodes[id];
917
+ const offsetCount = ids.length;
918
+ const rowOffset = (idx - (offsetCount - 1) / 2) * (NODE_H + 16);
919
+ const x = center.x - NODE_W / 2;
920
+ const y = center.y + rowOffset - NODE_H / 2;
921
+ const focal = decl.props.focal === true || decl.props.accent === true;
922
+ nodes.push({
923
+ id,
924
+ kind: decl.kind,
925
+ role: nodeRole(decl.kind),
926
+ label: decl.label,
927
+ x: snapGrid(x),
928
+ y: snapGrid(y),
929
+ width: NODE_W,
930
+ height: NODE_H,
931
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
932
+ props: decl.props,
933
+ opacity: 0,
934
+ shape: "card",
935
+ focal
936
+ });
937
+ });
938
+ }
939
+ return nodes;
940
+ }
941
+ function layoutSwimlane(ast, edges) {
942
+ const nodeIds = Object.keys(ast.nodes);
943
+ if (nodeIds.length === 0) return [];
944
+ const lanes = /* @__PURE__ */ new Map();
945
+ const groupKeys = Object.keys(ast.groups);
946
+ if (groupKeys.length > 0) {
947
+ for (const gId of groupKeys) {
948
+ const members = ast.groups[gId].members.filter((id) => ast.nodes[id]);
949
+ if (members.length > 0) lanes.set(gId, members);
950
+ }
951
+ } else {
952
+ for (const id of nodeIds) {
953
+ const decl = ast.nodes[id];
954
+ const role = nodeRole(decl.kind);
955
+ if (!lanes.has(role)) lanes.set(role, []);
956
+ lanes.get(role).push(id);
957
+ }
958
+ }
959
+ const activeLanes = [...lanes.entries()].filter(([_, ids]) => ids.length > 0);
960
+ const laneCount = activeLanes.length || 1;
961
+ const contentW = ast.meta.width - SAFE * 2;
962
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
963
+ const laneHeight = contentH / laneCount;
964
+ const nodes = [];
965
+ activeLanes.forEach(([_, ids], laneIdx) => {
966
+ const laneY = TITLE_BAND + laneIdx * laneHeight + (laneHeight - NODE_H) / 2;
967
+ const count = ids.length;
968
+ ids.forEach((id, colIdx) => {
969
+ const decl = ast.nodes[id];
970
+ const colX = SAFE + contentW / (count + 1) * (colIdx + 1) - NODE_W / 2;
971
+ const focal = decl.props.focal === true || decl.props.accent === true;
972
+ nodes.push({
973
+ id,
974
+ kind: decl.kind,
975
+ role: nodeRole(decl.kind),
976
+ label: decl.label,
977
+ x: snapGrid(colX),
978
+ y: snapGrid(laneY),
979
+ width: NODE_W,
980
+ height: NODE_H,
981
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
982
+ props: decl.props,
983
+ opacity: 0,
984
+ shape: "card",
985
+ focal
986
+ });
987
+ });
988
+ });
989
+ return nodes;
990
+ }
991
+ function layoutPyramid(ast) {
992
+ const nodeIds = Object.keys(ast.nodes);
993
+ if (nodeIds.length === 0) return [];
994
+ const ranks = /* @__PURE__ */ new Map();
995
+ nodeIds.forEach((id, idx) => {
996
+ const decl = ast.nodes[id];
997
+ const tier = typeof decl.props.tier === "number" ? decl.props.tier : typeof decl.props.level === "number" ? decl.props.level : idx;
998
+ if (!ranks.has(tier)) ranks.set(tier, []);
999
+ ranks.get(tier).push(id);
1000
+ });
1001
+ const sortedTiers = [...ranks.entries()].sort((a, b) => a[0] - b[0]);
1002
+ const tierCount = sortedTiers.length || 1;
1003
+ const contentW = ast.meta.width - SAFE * 2;
1004
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1005
+ const nodes = [];
1006
+ sortedTiers.forEach(([_, ids], tierIdx) => {
1007
+ const tierY = TITLE_BAND + contentH / (tierCount + 1) * (tierIdx + 1) - NODE_H / 2;
1008
+ const spreadFraction = 0.4 + 0.6 * tierIdx / Math.max(tierCount - 1, 1);
1009
+ const tierWidth = contentW * spreadFraction;
1010
+ const tierStartX = SAFE + (contentW - tierWidth) / 2;
1011
+ const count = ids.length;
1012
+ ids.forEach((id, idx) => {
1013
+ const decl = ast.nodes[id];
1014
+ const x = tierStartX + tierWidth / (count + 1) * (idx + 1) - NODE_W / 2;
1015
+ const focal = decl.props.focal === true || decl.props.accent === true;
1016
+ nodes.push({
1017
+ id,
1018
+ kind: decl.kind,
1019
+ role: nodeRole(decl.kind),
1020
+ label: decl.label,
1021
+ x: snapGrid(x),
1022
+ y: snapGrid(tierY),
1023
+ width: NODE_W,
1024
+ height: NODE_H,
1025
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
1026
+ props: decl.props,
1027
+ opacity: 0,
1028
+ shape: "card",
1029
+ focal
1030
+ });
1031
+ });
1032
+ });
1033
+ return nodes;
1034
+ }
1035
+ function layoutTimeline(ast) {
1036
+ const nodeIds = Object.keys(ast.nodes);
1037
+ if (nodeIds.length === 0) return [];
1038
+ const contentW = ast.meta.width - SAFE * 2;
1039
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1040
+ const baselineY = TITLE_BAND + contentH / 2;
1041
+ const spacing = contentW / Math.max(nodeIds.length, 1);
1042
+ const nodes = [];
1043
+ nodeIds.forEach((id, idx) => {
1044
+ const decl = ast.nodes[id];
1045
+ const x = SAFE + spacing * idx + (spacing - NODE_W) / 2;
1046
+ const above = idx % 2 === 0;
1047
+ const y = above ? baselineY - NODE_H - 24 : baselineY + 24;
1048
+ const focal = decl.props.focal === true || decl.props.accent === true;
1049
+ nodes.push({
1050
+ id,
1051
+ kind: decl.kind,
1052
+ role: nodeRole(decl.kind),
1053
+ label: decl.label,
1054
+ x: snapGrid(x),
1055
+ y: snapGrid(y),
1056
+ width: NODE_W,
1057
+ height: NODE_H,
1058
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
1059
+ props: decl.props,
1060
+ opacity: 0,
1061
+ shape: "pill",
1062
+ focal
1063
+ });
1064
+ });
1065
+ return nodes;
1066
+ }
1067
+ function layoutGantt(ast) {
1068
+ const nodeIds = Object.keys(ast.nodes);
1069
+ if (nodeIds.length === 0) return [];
1070
+ const contentW = ast.meta.width - SAFE * 2;
1071
+ const rowH = 56;
1072
+ const barH = 40;
1073
+ const nodes = [];
1074
+ nodeIds.forEach((id, idx) => {
1075
+ const decl = ast.nodes[id];
1076
+ const phase = typeof decl.props.phase === "number" ? decl.props.phase : 0;
1077
+ const span = typeof decl.props.span === "number" ? decl.props.span : 1;
1078
+ const totalPhases = Math.max(...nodeIds.map((nid) => {
1079
+ const p = ast.nodes[nid].props.phase;
1080
+ const s = ast.nodes[nid].props.span;
1081
+ return (typeof p === "number" ? p : 0) + (typeof s === "number" ? s : 1);
1082
+ }), 1);
1083
+ const unitW = contentW / totalPhases;
1084
+ const x = SAFE + phase * unitW;
1085
+ const w = Math.max(span * unitW - 8, NODE_W);
1086
+ const y = TITLE_BAND + idx * rowH + (rowH - barH) / 2;
1087
+ const focal = decl.props.focal === true || decl.props.accent === true;
1088
+ nodes.push({
1089
+ id,
1090
+ kind: decl.kind,
1091
+ role: nodeRole(decl.kind),
1092
+ label: decl.label,
1093
+ x: snapGrid(x),
1094
+ y: snapGrid(y),
1095
+ width: snapGrid(w),
1096
+ height: barH,
1097
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
1098
+ props: decl.props,
1099
+ opacity: 0,
1100
+ shape: "pill",
1101
+ focal
1102
+ });
1103
+ });
1104
+ return nodes;
1105
+ }
1106
+ function layoutVenn(ast) {
1107
+ const nodeIds = Object.keys(ast.nodes);
1108
+ if (nodeIds.length === 0) return [];
1109
+ const contentW = ast.meta.width - SAFE * 2;
1110
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1111
+ const centerX = SAFE + contentW / 2;
1112
+ const centerY = TITLE_BAND + contentH / 2;
1113
+ const N = nodeIds.length;
1114
+ const radius = Math.min(contentW, contentH) * 0.22;
1115
+ const nodes = [];
1116
+ nodeIds.forEach((id, idx) => {
1117
+ const decl = ast.nodes[id];
1118
+ let x, y;
1119
+ if (N === 2) {
1120
+ x = centerX + (idx === 0 ? -radius * 0.55 : radius * 0.55) - NODE_W / 2;
1121
+ y = centerY - NODE_H / 2;
1122
+ } else {
1123
+ const angle = -Math.PI / 2 + idx * 2 * Math.PI / N;
1124
+ x = centerX + radius * 0.6 * Math.cos(angle) - NODE_W / 2;
1125
+ y = centerY + radius * 0.6 * Math.sin(angle) - NODE_H / 2;
1126
+ }
1127
+ const focal = decl.props.focal === true || decl.props.accent === true;
1128
+ nodes.push({
1129
+ id,
1130
+ kind: decl.kind,
1131
+ role: nodeRole(decl.kind),
1132
+ label: decl.label,
1133
+ x: snapGrid(x),
1134
+ y: snapGrid(y),
1135
+ width: NODE_W,
1136
+ height: NODE_H,
1137
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
1138
+ props: decl.props,
1139
+ opacity: 0,
1140
+ shape: "circle",
1141
+ focal
1142
+ });
1143
+ });
1144
+ return nodes;
1145
+ }
1146
+ function layoutLayers(ast) {
1147
+ const nodeIds = Object.keys(ast.nodes);
1148
+ if (nodeIds.length === 0) return [];
1149
+ const contentW = ast.meta.width - SAFE * 2;
1150
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1151
+ const N = nodeIds.length;
1152
+ const layerH = Math.min(Math.max((contentH - (N - 1) * 12) / N, 52), 76);
1153
+ const totalH = N * layerH + (N - 1) * 12;
1154
+ const startY = TITLE_BAND + (contentH - totalH) / 2;
1155
+ const nodes = [];
1156
+ nodeIds.forEach((id, idx) => {
1157
+ const decl = ast.nodes[id];
1158
+ const y = startY + idx * (layerH + 12);
1159
+ const focal = decl.props.focal === true || decl.props.accent === true;
1160
+ nodes.push({
1161
+ id,
1162
+ kind: decl.kind,
1163
+ role: nodeRole(decl.kind),
1164
+ label: decl.label,
1165
+ x: snapGrid(SAFE),
1166
+ y: snapGrid(y),
1167
+ width: snapGrid(contentW),
1168
+ height: snapGrid(layerH),
1169
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
1170
+ props: decl.props,
1171
+ opacity: 0,
1172
+ shape: "rounded",
1173
+ focal
1174
+ });
1175
+ });
1176
+ return nodes;
1177
+ }
1178
+ function layoutNested(ast) {
1179
+ const nodeIds = Object.keys(ast.nodes);
1180
+ if (nodeIds.length === 0) return [];
1181
+ const contentW = ast.meta.width - SAFE * 2;
1182
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1183
+ const N = nodeIds.length;
1184
+ const padX = Math.min(36, contentW * 0.4 / N);
1185
+ const padY = Math.min(32, contentH * 0.4 / N);
1186
+ const nodes = [];
1187
+ nodeIds.forEach((id, idx) => {
1188
+ const decl = ast.nodes[id];
1189
+ const x = SAFE + idx * padX;
1190
+ const y = TITLE_BAND + idx * padY;
1191
+ const w = contentW - idx * padX * 2;
1192
+ const h = contentH - idx * padY * 2;
1193
+ const focal = decl.props.focal === true || decl.props.accent === true || idx === N - 1;
1194
+ nodes.push({
1195
+ id,
1196
+ kind: decl.kind,
1197
+ role: nodeRole(decl.kind),
1198
+ label: decl.label,
1199
+ x: snapGrid(x),
1200
+ y: snapGrid(y),
1201
+ width: snapGrid(w),
1202
+ height: snapGrid(h),
1203
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
1204
+ props: decl.props,
1205
+ opacity: 0,
1206
+ shape: "rounded",
1207
+ focal
1208
+ });
1209
+ });
1210
+ return nodes;
1211
+ }
1212
+ function layoutRadar(ast) {
1213
+ const nodeIds = Object.keys(ast.nodes);
1214
+ if (nodeIds.length === 0) return [];
1215
+ const contentW = ast.meta.width - SAFE * 2;
1216
+ const contentH = ast.meta.height - SAFE - TITLE_BAND - SAFE;
1217
+ const centerX = SAFE + contentW / 2;
1218
+ const centerY = TITLE_BAND + contentH / 2;
1219
+ const radius = Math.min(contentW, contentH) * 0.38;
1220
+ const N = Math.max(nodeIds.length, 3);
1221
+ const nodes = [];
1222
+ nodeIds.forEach((id, idx) => {
1223
+ const decl = ast.nodes[id];
1224
+ const angle = -Math.PI / 2 + idx * 2 * Math.PI / N;
1225
+ const x = centerX + radius * Math.cos(angle) - NODE_W / 2;
1226
+ const y = centerY + radius * Math.sin(angle) - NODE_H / 2;
1227
+ const focal = decl.props.focal === true || decl.props.accent === true;
1228
+ nodes.push({
1229
+ id,
1230
+ kind: decl.kind,
1231
+ role: nodeRole(decl.kind),
1232
+ label: decl.label,
1233
+ x: snapGrid(x),
1234
+ y: snapGrid(y),
1235
+ width: NODE_W,
1236
+ height: NODE_H,
1237
+ style: decl.style ? ast.styles[decl.style]?.props : void 0,
1238
+ props: decl.props,
1239
+ opacity: 0,
1240
+ shape: "rounded",
1241
+ focal
1242
+ });
1243
+ });
1244
+ return nodes;
1245
+ }
733
1246
  function layoutNodes(ast, edges) {
734
1247
  const dtype = diagramType(ast);
735
1248
  switch (dtype) {
@@ -741,6 +1254,29 @@ function layoutNodes(ast, edges) {
741
1254
  return layoutRanked(ast, [], { columnLayout: true });
742
1255
  case "constellation":
743
1256
  return layoutConstellation(ast);
1257
+ case "loop":
1258
+ case "flywheel":
1259
+ return layoutLoop(ast);
1260
+ case "medallion":
1261
+ return layoutMedallion(ast, edges);
1262
+ case "quadrant":
1263
+ return layoutQuadrant(ast);
1264
+ case "swimlane":
1265
+ return layoutSwimlane(ast, edges);
1266
+ case "pyramid":
1267
+ return layoutPyramid(ast);
1268
+ case "radar":
1269
+ return layoutRadar(ast);
1270
+ case "timeline":
1271
+ return layoutTimeline(ast);
1272
+ case "gantt":
1273
+ return layoutGantt(ast);
1274
+ case "venn":
1275
+ return layoutVenn(ast);
1276
+ case "layers":
1277
+ return layoutLayers(ast);
1278
+ case "nested":
1279
+ return layoutNested(ast);
744
1280
  case "state":
745
1281
  return layoutRanked(ast, cycleSafeEdges(Object.keys(ast.nodes), edges), { forceVertical: false });
746
1282
  default:
@@ -1046,7 +1582,8 @@ var THEMES = {
1046
1582
  shadow: "rgba(15, 23, 42, 0.14)",
1047
1583
  labelPlate: "#ffffff",
1048
1584
  roles: { ...ROLE_COLORS },
1049
- edges: { ...EDGE_COLORS }
1585
+ edges: { ...EDGE_COLORS },
1586
+ series: ["#7c8f6f", "#5e7a9b", "#b8915a", "#9c6b50", "#6e6479"]
1050
1587
  },
1051
1588
  blueprint: {
1052
1589
  name: "blueprint",
@@ -1126,7 +1663,8 @@ var THEMES = {
1126
1663
  mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
1127
1664
  },
1128
1665
  radiusMd: 6,
1129
- spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
1666
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 },
1667
+ series: ["#7c8f6f", "#5e7a9b", "#b8915a", "#9c6b50", "#6e6479"]
1130
1668
  },
1131
1669
  nebula: {
1132
1670
  name: "nebula",
@@ -1173,6 +1711,97 @@ var THEMES = {
1173
1711
  },
1174
1712
  radiusMd: 12,
1175
1713
  spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
1714
+ },
1715
+ terminal: {
1716
+ name: "terminal",
1717
+ canvas: "#0a0a0a",
1718
+ surface: "#141414",
1719
+ surfaceRaised: "#1e1e1e",
1720
+ border: "#2b2b2b",
1721
+ text: "#f5f5f5",
1722
+ textMuted: "#9a9a9a",
1723
+ paper: "#141414",
1724
+ ink: "#f5f5f5",
1725
+ muted: "#9a9a9a",
1726
+ rule: "#2b2b2b",
1727
+ soft: "#5c5c5c",
1728
+ link: "#ff5a36",
1729
+ gridMinor: "rgba(255, 255, 255, 0.04)",
1730
+ gridMajor: "rgba(255, 255, 255, 0.08)",
1731
+ vignette: "rgba(0, 0, 0, 0.8)",
1732
+ accent: "#ff5a36",
1733
+ accentTint: "rgba(255, 90, 54, 0.15)",
1734
+ nodeSurface: "#141414",
1735
+ nodeSurfaceRaised: "#222222",
1736
+ hairline: "#333333",
1737
+ shadow: "rgba(0, 0, 0, 0.8)",
1738
+ labelPlate: "#141414",
1739
+ flatCards: true,
1740
+ roles: {
1741
+ ...ROLE_COLORS,
1742
+ compute: "#ff5a36",
1743
+ client: "#f5f5f5",
1744
+ data: "#9a9a9a",
1745
+ network: "#38bdf8"
1746
+ },
1747
+ edges: {
1748
+ request: "#ff5a36",
1749
+ response: "#9a9a9a",
1750
+ event: "#facc15",
1751
+ dependency: "#5c5c5c"
1752
+ },
1753
+ fonts: {
1754
+ title: "ui-monospace, SFMono-Regular, Menlo, monospace",
1755
+ nodeName: "ui-monospace, SFMono-Regular, Menlo, monospace",
1756
+ mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
1757
+ },
1758
+ radiusMd: 6,
1759
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
1760
+ },
1761
+ sketchy: {
1762
+ name: "sketchy",
1763
+ canvas: "#fbfaf8",
1764
+ surface: "#ffffff",
1765
+ surfaceRaised: "#f6f3eb",
1766
+ border: "#2d3142",
1767
+ text: "#2d3142",
1768
+ textMuted: "#4f5d75",
1769
+ paper: "#fbfaf8",
1770
+ ink: "#2d3142",
1771
+ muted: "#4f5d75",
1772
+ rule: "rgba(45, 49, 66, 0.18)",
1773
+ soft: "#7a8399",
1774
+ link: "#eb6c36",
1775
+ gridMinor: "rgba(45, 49, 66, 0.05)",
1776
+ gridMajor: "rgba(45, 49, 66, 0.08)",
1777
+ vignette: "rgba(0, 0, 0, 0.06)",
1778
+ accent: "#eb6c36",
1779
+ accentTint: "rgba(235, 108, 54, 0.10)",
1780
+ nodeSurface: "#ffffff",
1781
+ nodeSurfaceRaised: "#fbfaf8",
1782
+ hairline: "rgba(45, 49, 66, 0.35)",
1783
+ shadow: "rgba(45, 49, 66, 0.08)",
1784
+ labelPlate: "#ffffff",
1785
+ flatCards: true,
1786
+ roles: {
1787
+ ...ROLE_COLORS,
1788
+ compute: "#2d3142",
1789
+ client: "#4f5d75",
1790
+ data: "#4f5d75"
1791
+ },
1792
+ edges: {
1793
+ request: "#2d3142",
1794
+ response: "#4f5d75",
1795
+ event: "#eb6c36",
1796
+ dependency: "#7a8399"
1797
+ },
1798
+ fonts: {
1799
+ title: "Georgia, Times New Roman, serif",
1800
+ nodeName: "ui-sans-serif, system-ui, sans-serif",
1801
+ mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
1802
+ },
1803
+ radiusMd: 4,
1804
+ spacing: { xs: 4, sm: 8, md: 16, lg: 24, xl: 40 }
1176
1805
  }
1177
1806
  };
1178
1807
  function resolveTheme(name) {
@@ -1571,7 +2200,14 @@ function readIndentedBody(blocks, startIdx, parentIndent) {
1571
2200
  }
1572
2201
  return { body, nextIdx: i };
1573
2202
  }
1574
- var TOP_LEVEL_KEYWORDS_RE = /^(scene|layout|pattern|group|annotation|edge|beat|var|style)\b/;
2203
+ function parseBooleanToken(raw) {
2204
+ if (typeof raw === "boolean") return raw;
2205
+ const s = String(raw).trim().toLowerCase();
2206
+ if (["true", "on", "yes", "1"].includes(s)) return true;
2207
+ if (["false", "off", "no", "0"].includes(s)) return false;
2208
+ return void 0;
2209
+ }
2210
+ var TOP_LEVEL_KEYWORDS_RE = /^(scene|layout|pattern|group|annotation|edge|beat|var|style|controls|interactive|interactiveViewport|interactive_viewport|autoplay|loop|copyright|playbackRate|playback_rate|speed|progressColor|progressBarColor|progress_color|progress_bar_color|progress|progressBar)\b/;
1575
2211
  function isTopLevelStatement(line) {
1576
2212
  if (line === "}") return true;
1577
2213
  if (TOP_LEVEL_KEYWORDS_RE.test(line)) return true;
@@ -1849,7 +2485,29 @@ function parse(source, opts = {}) {
1849
2485
  else if (k === "duration") meta.duration = Number(v);
1850
2486
  else if (k === "theme") meta.theme = String(v);
1851
2487
  else if (k === "direction" || k === "layout") meta.direction = String(v).toUpperCase();
1852
- else if (k === "type") {
2488
+ else if (k === "progressColor" || k === "progressBarColor" || k === "progress_color" || k === "progress_bar_color" || k === "progress") {
2489
+ meta.progressColor = String(v);
2490
+ } else if (k === "progressBar" || k === "sceneBoundaryProgress") {
2491
+ const bool = parseBooleanToken(v);
2492
+ if (bool !== void 0) {
2493
+ if (!bool) meta.progressColor = "none";
2494
+ } else {
2495
+ meta.progressColor = String(v);
2496
+ }
2497
+ } else if (k === "controls") {
2498
+ meta.controls = parseBooleanToken(v) ?? true;
2499
+ } else if (k === "interactive" || k === "interactiveViewport" || k === "interactive_viewport") {
2500
+ meta.interactiveViewport = parseBooleanToken(v) ?? true;
2501
+ } else if (k === "autoplay") {
2502
+ meta.autoplay = parseBooleanToken(v) ?? true;
2503
+ } else if (k === "loop") {
2504
+ meta.loop = parseBooleanToken(v) ?? true;
2505
+ } else if (k === "copyright") {
2506
+ meta.copyright = parseBooleanToken(v) ?? true;
2507
+ } else if (k === "playbackRate" || k === "playback_rate" || k === "speed") {
2508
+ const r = Number(v);
2509
+ if (!Number.isNaN(r) && r > 0) meta.playbackRate = r;
2510
+ } else if (k === "type") {
1853
2511
  const t = String(v).toLowerCase();
1854
2512
  if (!DIAGRAM_TYPES.has(t)) {
1855
2513
  diagnostics.push({ severity: "warning", message: `unknown diagram type '${v}'`, line: lineNo });
@@ -1861,6 +2519,36 @@ function parse(source, opts = {}) {
1861
2519
  i++;
1862
2520
  continue;
1863
2521
  }
2522
+ const directiveMatch = line.match(
2523
+ /^(controls|interactive|interactiveViewport|interactive_viewport|autoplay|loop|copyright|playbackRate|playback_rate|speed|progressColor|progressBarColor|progress_color|progress_bar_color|progress|progressBar)\b(?:\s*[:=]?\s*(.+))?$/
2524
+ );
2525
+ if (directiveMatch) {
2526
+ const keyword = directiveMatch[1];
2527
+ const rawVal = (directiveMatch[2] ?? "").trim();
2528
+ const val = rawVal.startsWith('"') && rawVal.endsWith('"') || rawVal.startsWith("'") && rawVal.endsWith("'") ? rawVal.slice(1, -1) : rawVal;
2529
+ if (keyword === "controls") {
2530
+ meta.controls = val ? parseBooleanToken(val) ?? true : true;
2531
+ } else if (keyword === "interactive" || keyword === "interactiveViewport" || keyword === "interactive_viewport") {
2532
+ meta.interactiveViewport = val ? parseBooleanToken(val) ?? true : true;
2533
+ } else if (keyword === "autoplay") {
2534
+ meta.autoplay = val ? parseBooleanToken(val) ?? true : true;
2535
+ } else if (keyword === "loop") {
2536
+ meta.loop = val ? parseBooleanToken(val) ?? true : true;
2537
+ } else if (keyword === "copyright") {
2538
+ meta.copyright = val ? parseBooleanToken(val) ?? true : true;
2539
+ } else if (keyword === "playbackRate" || keyword === "playback_rate" || keyword === "speed") {
2540
+ const r = Number(val);
2541
+ if (!Number.isNaN(r) && r > 0) meta.playbackRate = r;
2542
+ } else if (keyword === "progressColor" || keyword === "progressBarColor" || keyword === "progress_color" || keyword === "progress_bar_color" || keyword === "progress") {
2543
+ if (val) meta.progressColor = val;
2544
+ } else if (keyword === "progressBar") {
2545
+ const bool = parseBooleanToken(val);
2546
+ if (bool === false) meta.progressColor = "none";
2547
+ else if (val) meta.progressColor = val;
2548
+ }
2549
+ i++;
2550
+ continue;
2551
+ }
1864
2552
  if (/^layout\s+(LR|RL|TB|BT)\b/i.test(line)) {
1865
2553
  meta.direction = line.split(/\s+/)[1].toUpperCase();
1866
2554
  i++;
@@ -2041,25 +2729,1030 @@ function parseAndCompile(source) {
2041
2729
  const plan = compile(ast);
2042
2730
  return { ast, plan };
2043
2731
  }
2732
+
2733
+ // src/theme-generator.ts
2734
+ function hexToRgb(hex) {
2735
+ let cleaned = hex.replace(/^#/, "").trim();
2736
+ if (cleaned.length === 3) {
2737
+ cleaned = cleaned.split("").map((c) => c + c).join("");
2738
+ }
2739
+ const num = parseInt(cleaned, 16);
2740
+ return {
2741
+ r: num >> 16 & 255,
2742
+ g: num >> 8 & 255,
2743
+ b: num & 255
2744
+ };
2745
+ }
2746
+ function rgbToHsl(r, g, b) {
2747
+ r /= 255;
2748
+ g /= 255;
2749
+ b /= 255;
2750
+ const max = Math.max(r, g, b);
2751
+ const min = Math.min(r, g, b);
2752
+ let h = 0;
2753
+ let s = 0;
2754
+ const l = (max + min) / 2;
2755
+ if (max !== min) {
2756
+ const d = max - min;
2757
+ s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
2758
+ switch (max) {
2759
+ case r:
2760
+ h = (g - b) / d + (g < b ? 6 : 0);
2761
+ break;
2762
+ case g:
2763
+ h = (b - r) / d + 2;
2764
+ break;
2765
+ case b:
2766
+ h = (r - g) / d + 4;
2767
+ break;
2768
+ }
2769
+ h /= 6;
2770
+ }
2771
+ return {
2772
+ h: Math.round(h * 360),
2773
+ s: Math.round(s * 100),
2774
+ l: Math.round(l * 100)
2775
+ };
2776
+ }
2777
+ function hslToHex(h, s, l) {
2778
+ h = (h % 360 + 360) % 360;
2779
+ s = Math.max(0, Math.min(100, s)) / 100;
2780
+ l = Math.max(0, Math.min(100, l)) / 100;
2781
+ const c = (1 - Math.abs(2 * l - 1)) * s;
2782
+ const x = c * (1 - Math.abs(h / 60 % 2 - 1));
2783
+ const m = l - c / 2;
2784
+ let r = 0, g = 0, b = 0;
2785
+ if (0 <= h && h < 60) {
2786
+ r = c;
2787
+ g = x;
2788
+ b = 0;
2789
+ } else if (60 <= h && h < 120) {
2790
+ r = x;
2791
+ g = c;
2792
+ b = 0;
2793
+ } else if (120 <= h && h < 180) {
2794
+ r = 0;
2795
+ g = c;
2796
+ b = x;
2797
+ } else if (180 <= h && h < 240) {
2798
+ r = 0;
2799
+ g = x;
2800
+ b = c;
2801
+ } else if (240 <= h && h < 300) {
2802
+ r = x;
2803
+ g = 0;
2804
+ b = c;
2805
+ } else if (300 <= h && h < 360) {
2806
+ r = c;
2807
+ g = 0;
2808
+ b = x;
2809
+ }
2810
+ const toHex = (n) => {
2811
+ const val = Math.round((n + m) * 255);
2812
+ return val.toString(16).padStart(2, "0");
2813
+ };
2814
+ return `#${toHex(r)}${toHex(g)}${toHex(b)}`;
2815
+ }
2816
+ function generateThemeFromBrand(opts) {
2817
+ const isDark = opts.mode === "dark";
2818
+ const { r, g, b } = hexToRgb(opts.accentHex);
2819
+ const hsl = rgbToHsl(r, g, b);
2820
+ const canvas = opts.canvasHex ?? (isDark ? hslToHex(hsl.h, 16, 8) : hslToHex(hsl.h, 18, 97));
2821
+ const surface = isDark ? hslToHex(hsl.h, 14, 13) : hslToHex(hsl.h, 20, 100);
2822
+ const surfaceRaised = isDark ? hslToHex(hsl.h, 14, 18) : hslToHex(hsl.h, 22, 94);
2823
+ const border = isDark ? hslToHex(hsl.h, 12, 24) : hslToHex(hsl.h, 14, 86);
2824
+ const text = opts.inkHex ?? (isDark ? hslToHex(hsl.h, 10, 94) : hslToHex(hsl.h, 24, 12));
2825
+ const textMuted = isDark ? hslToHex(hsl.h, 10, 62) : hslToHex(hsl.h, 14, 42);
2826
+ const gridMinor = isDark ? "rgba(255, 255, 255, 0.04)" : "rgba(0, 0, 0, 0.04)";
2827
+ const gridMajor = isDark ? "rgba(255, 255, 255, 0.08)" : "rgba(0, 0, 0, 0.08)";
2828
+ const vignette = isDark ? "rgba(0, 0, 0, 0.45)" : "rgba(0, 0, 0, 0.06)";
2829
+ const accent = opts.accentHex;
2830
+ const accentTint = isDark ? `rgba(${r}, ${g}, ${b}, 0.18)` : `rgba(${r}, ${g}, ${b}, 0.08)`;
2831
+ const soft = isDark ? hslToHex(hsl.h, 10, 48) : hslToHex(hsl.h, 12, 58);
2832
+ const rule = border;
2833
+ return {
2834
+ name: opts.name,
2835
+ canvas,
2836
+ surface,
2837
+ surfaceRaised,
2838
+ border,
2839
+ text,
2840
+ textMuted,
2841
+ gridMinor,
2842
+ gridMajor,
2843
+ vignette,
2844
+ accent,
2845
+ paper: canvas,
2846
+ ink: text,
2847
+ muted: textMuted,
2848
+ soft,
2849
+ rule,
2850
+ accentTint,
2851
+ nodeSurface: surface,
2852
+ nodeSurfaceRaised: surfaceRaised,
2853
+ hairline: border,
2854
+ shadow: isDark ? "rgba(0, 0, 0, 0.5)" : "rgba(15, 23, 42, 0.08)",
2855
+ labelPlate: isDark ? "rgba(15, 23, 42, 0.85)" : "rgba(255, 255, 255, 0.95)",
2856
+ roles: {
2857
+ client: hslToHex((hsl.h + 30) % 360, 70, isDark ? 65 : 45),
2858
+ compute: accent,
2859
+ data: hslToHex((hsl.h + 180) % 360, 65, isDark ? 60 : 42),
2860
+ messaging: hslToHex((hsl.h + 90) % 360, 75, isDark ? 68 : 46),
2861
+ network: hslToHex((hsl.h + 210) % 360, 60, isDark ? 62 : 44),
2862
+ platform: hslToHex((hsl.h + 270) % 360, 55, isDark ? 65 : 48),
2863
+ security: hslToHex(10, 80, isDark ? 65 : 50),
2864
+ delivery: hslToHex((hsl.h + 150) % 360, 65, isDark ? 62 : 42),
2865
+ observability: hslToHex((hsl.h + 45) % 360, 75, isDark ? 64 : 45),
2866
+ flow: textMuted,
2867
+ code: textMuted,
2868
+ distributed: hslToHex((hsl.h + 300) % 360, 60, isDark ? 65 : 45)
2869
+ },
2870
+ edges: {
2871
+ request: isDark ? "#38bdf8" : "#0284c7",
2872
+ response: isDark ? "#94a3b8" : "#64748b",
2873
+ event: isDark ? "#f59e0b" : "#d97706",
2874
+ dependency: isDark ? "#475569" : "#94a3b8"
2875
+ }
2876
+ };
2877
+ }
2878
+
2879
+ // src/arch-lint.ts
2880
+ function matchNode(node, selector) {
2881
+ if (!selector) return true;
2882
+ if (selector.id && node.id !== selector.id) return false;
2883
+ if (selector.kindEquals && node.kind.toLowerCase() !== selector.kindEquals.toLowerCase()) return false;
2884
+ if (selector.roleEquals) {
2885
+ const role = nodeRole(node.kind);
2886
+ if (role.toLowerCase() !== selector.roleEquals.toLowerCase()) return false;
2887
+ }
2888
+ if (selector.labelContains) {
2889
+ const target = (node.label || node.id).toLowerCase();
2890
+ if (!target.includes(selector.labelContains.toLowerCase())) return false;
2891
+ }
2892
+ return true;
2893
+ }
2894
+ function matchEdge(edge, selector) {
2895
+ if (!selector) return true;
2896
+ if (selector.kind && edge.kind !== selector.kind) return false;
2897
+ if (selector.labelContains) {
2898
+ const label = (edge.label || "").toLowerCase();
2899
+ if (!label.includes(selector.labelContains.toLowerCase())) return false;
2900
+ }
2901
+ return true;
2902
+ }
2903
+ function extractAllEdges(ast) {
2904
+ const edges = [];
2905
+ const seen = /* @__PURE__ */ new Set();
2906
+ for (const edge of ast.edges) {
2907
+ const key = `${edge.from}->${edge.to}:${edge.kind}:${edge.label ?? ""}`;
2908
+ if (!seen.has(key)) {
2909
+ seen.add(key);
2910
+ edges.push({
2911
+ from: edge.from,
2912
+ to: edge.to,
2913
+ kind: edge.kind,
2914
+ label: edge.label,
2915
+ line: edge.line
2916
+ });
2917
+ }
2918
+ }
2919
+ for (const beat of ast.beats) {
2920
+ for (const cue of beat.cues) {
2921
+ if (cue.kind === "flow") {
2922
+ for (const seg of cue.segments) {
2923
+ const key = `${seg.from}->${seg.to}:${seg.op}:${seg.label ?? ""}`;
2924
+ if (!seen.has(key)) {
2925
+ seen.add(key);
2926
+ edges.push({
2927
+ from: seg.from,
2928
+ to: seg.to,
2929
+ kind: seg.op,
2930
+ label: seg.label,
2931
+ line: cue.line
2932
+ });
2933
+ }
2934
+ }
2935
+ }
2936
+ }
2937
+ }
2938
+ return edges;
2939
+ }
2940
+ function detectCycleInGraph(nodes, edges, edgeFilter) {
2941
+ const candidateEdges = edges.filter((e) => matchEdge(e, edgeFilter));
2942
+ const adj = /* @__PURE__ */ new Map();
2943
+ for (const e of candidateEdges) {
2944
+ if (!adj.has(e.from)) adj.set(e.from, []);
2945
+ adj.get(e.from).push({ to: e.to, line: e.line });
2946
+ }
2947
+ const visited = /* @__PURE__ */ new Set();
2948
+ const onStack = /* @__PURE__ */ new Set();
2949
+ const parentMap = /* @__PURE__ */ new Map();
2950
+ let foundCycle = null;
2951
+ const dfs = (curr) => {
2952
+ visited.add(curr);
2953
+ onStack.add(curr);
2954
+ for (const next of adj.get(curr) ?? []) {
2955
+ if (foundCycle) return;
2956
+ if (!visited.has(next.to)) {
2957
+ parentMap.set(next.to, curr);
2958
+ dfs(next.to);
2959
+ } else if (onStack.has(next.to)) {
2960
+ const cycle = [next.to, curr];
2961
+ let p = curr;
2962
+ while (p !== next.to && parentMap.has(p)) {
2963
+ p = parentMap.get(p);
2964
+ cycle.push(p);
2965
+ }
2966
+ foundCycle = {
2967
+ path: cycle.reverse(),
2968
+ line: next.line
2969
+ };
2970
+ return;
2971
+ }
2972
+ }
2973
+ onStack.delete(curr);
2974
+ };
2975
+ for (const node of nodes) {
2976
+ if (!visited.has(node.id)) {
2977
+ dfs(node.id);
2978
+ if (foundCycle) return foundCycle;
2979
+ }
2980
+ }
2981
+ return null;
2982
+ }
2983
+ var ARCH_RULE_PRESETS = {
2984
+ cleanArchitecture: {
2985
+ id: "clean-architecture",
2986
+ name: "Clean / Layered Architecture",
2987
+ description: "Enforce strict dependency rules: Presentation cannot directly access Data Storage",
2988
+ rules: [
2989
+ {
2990
+ id: "no-presentation-to-database",
2991
+ name: "No Direct Client DB Access",
2992
+ description: "Presentation/Client components must communicate via backend services, never directly with databases.",
2993
+ severity: "error",
2994
+ type: "cannot-connect",
2995
+ from: { roleEquals: "client" },
2996
+ to: { roleEquals: "data" }
2997
+ },
2998
+ {
2999
+ id: "no-browser-to-internal-storage",
3000
+ name: "No Direct Browser Storage Access",
3001
+ description: "Browser nodes must not connect directly to private storage buckets.",
3002
+ severity: "error",
3003
+ type: "cannot-connect",
3004
+ from: { kindEquals: "browser" },
3005
+ to: { kindEquals: "storage" }
3006
+ }
3007
+ ]
3008
+ },
3009
+ microservicesGovernance: {
3010
+ id: "microservices-governance",
3011
+ name: "Microservices Governance",
3012
+ description: "Prevent synchronous request cycles and shared database anti-patterns",
3013
+ rules: [
3014
+ {
3015
+ id: "no-sync-request-cycles",
3016
+ name: "Forbidden Request Cycle",
3017
+ description: "Synchronous request flows (->) must not form cyclic dependencies between services.",
3018
+ severity: "error",
3019
+ type: "forbidden-cycle",
3020
+ edge: { kind: "request" }
3021
+ },
3022
+ {
3023
+ id: "gateway-enforcement",
3024
+ name: "API Gateway Required",
3025
+ description: "Architecture diagrams with 3 or more services should declare an API gateway.",
3026
+ severity: "warning",
3027
+ type: "must-have-role",
3028
+ from: { roleEquals: "gateway" },
3029
+ min: 1
3030
+ }
3031
+ ]
3032
+ },
3033
+ securityBoundaries: {
3034
+ id: "security-boundaries",
3035
+ name: "Zero-Trust Security Boundaries",
3036
+ description: "Ensure external clients pass through auth and edge gateway tiers",
3037
+ rules: [
3038
+ {
3039
+ id: "auth-service-presence",
3040
+ name: "Authentication Component Presence",
3041
+ description: "Public-facing architectures should explicitly define an Auth or Identity provider.",
3042
+ severity: "info",
3043
+ type: "must-have-role",
3044
+ from: { roleEquals: "security" }
3045
+ }
3046
+ ]
3047
+ }
3048
+ };
3049
+ function validateArchitecture(ast, rules = [
3050
+ ...ARCH_RULE_PRESETS.cleanArchitecture.rules,
3051
+ ...ARCH_RULE_PRESETS.microservicesGovernance.rules
3052
+ ]) {
3053
+ const violations = [];
3054
+ const nodes = Object.values(ast.nodes);
3055
+ const nodeMap = new Map(nodes.map((n) => [n.id, n]));
3056
+ const edges = extractAllEdges(ast);
3057
+ for (const rule of rules) {
3058
+ switch (rule.type) {
3059
+ case "cannot-connect": {
3060
+ for (const edge of edges) {
3061
+ const sourceNode = nodeMap.get(edge.from);
3062
+ const targetNode = nodeMap.get(edge.to);
3063
+ if (!sourceNode || !targetNode) continue;
3064
+ if (matchNode(sourceNode, rule.from) && matchNode(targetNode, rule.to) && matchEdge(edge, rule.edge)) {
3065
+ violations.push({
3066
+ ruleId: rule.id,
3067
+ ruleName: rule.name,
3068
+ message: rule.description || `Node "${sourceNode.id}" is forbidden from connecting to "${targetNode.id}"`,
3069
+ severity: rule.severity,
3070
+ nodeIds: [sourceNode.id, targetNode.id],
3071
+ edgeKeys: [`${edge.from}->${edge.to}`],
3072
+ line: edge.line
3073
+ });
3074
+ }
3075
+ }
3076
+ break;
3077
+ }
3078
+ case "must-connect": {
3079
+ const sourceNodes = nodes.filter((n) => matchNode(n, rule.from));
3080
+ for (const src of sourceNodes) {
3081
+ const hasMatchingConnection = edges.some((edge) => {
3082
+ if (edge.from !== src.id) return false;
3083
+ const targetNode = nodeMap.get(edge.to);
3084
+ return targetNode ? matchNode(targetNode, rule.to) && matchEdge(edge, rule.edge) : false;
3085
+ });
3086
+ if (!hasMatchingConnection) {
3087
+ violations.push({
3088
+ ruleId: rule.id,
3089
+ ruleName: rule.name,
3090
+ message: rule.description || `Node "${src.id}" must connect to a matching downstream component`,
3091
+ severity: rule.severity,
3092
+ nodeIds: [src.id],
3093
+ edgeKeys: [],
3094
+ line: src.line
3095
+ });
3096
+ }
3097
+ }
3098
+ break;
3099
+ }
3100
+ case "forbidden-cycle": {
3101
+ const cycleInfo = detectCycleInGraph(nodes, edges, rule.edge);
3102
+ if (cycleInfo) {
3103
+ violations.push({
3104
+ ruleId: rule.id,
3105
+ ruleName: rule.name,
3106
+ message: `${rule.description} (Cycle path: ${cycleInfo.path.join(" -> ")})`,
3107
+ severity: rule.severity,
3108
+ nodeIds: Array.from(new Set(cycleInfo.path)),
3109
+ edgeKeys: [],
3110
+ line: cycleInfo.line
3111
+ });
3112
+ }
3113
+ break;
3114
+ }
3115
+ case "must-have-role": {
3116
+ const matching = nodes.filter((n) => matchNode(n, rule.from));
3117
+ const min = rule.min ?? 1;
3118
+ if (matching.length < min) {
3119
+ violations.push({
3120
+ ruleId: rule.id,
3121
+ ruleName: rule.name,
3122
+ message: rule.description,
3123
+ severity: rule.severity,
3124
+ nodeIds: [],
3125
+ edgeKeys: [],
3126
+ line: 1
3127
+ });
3128
+ }
3129
+ break;
3130
+ }
3131
+ case "role-count-limit": {
3132
+ const matching = nodes.filter((n) => matchNode(n, rule.from));
3133
+ if (rule.max !== void 0 && matching.length > rule.max) {
3134
+ violations.push({
3135
+ ruleId: rule.id,
3136
+ ruleName: rule.name,
3137
+ message: `${rule.description} (Found ${matching.length}, maximum allowed is ${rule.max})`,
3138
+ severity: rule.severity,
3139
+ nodeIds: matching.map((n) => n.id),
3140
+ edgeKeys: [],
3141
+ line: matching[0]?.line ?? 1
3142
+ });
3143
+ }
3144
+ break;
3145
+ }
3146
+ }
3147
+ }
3148
+ return violations;
3149
+ }
3150
+ function resolveArchitectureConfig(config) {
3151
+ if (!config) {
3152
+ return ARCH_RULE_PRESETS.cleanArchitecture.rules;
3153
+ }
3154
+ const rulesMap = /* @__PURE__ */ new Map();
3155
+ if (config.extends) {
3156
+ for (const presetName of config.extends) {
3157
+ const preset = ARCH_RULE_PRESETS[presetName];
3158
+ if (preset) {
3159
+ for (const r of preset.rules) {
3160
+ rulesMap.set(r.id, { ...r });
3161
+ }
3162
+ }
3163
+ }
3164
+ }
3165
+ if (config.rules) {
3166
+ for (const ruleItem of config.rules) {
3167
+ if (typeof ruleItem === "string") {
3168
+ for (const preset of Object.values(ARCH_RULE_PRESETS)) {
3169
+ const found = preset.rules.find((r) => r.id === ruleItem);
3170
+ if (found) {
3171
+ rulesMap.set(found.id, { ...found });
3172
+ break;
3173
+ }
3174
+ }
3175
+ } else if (typeof ruleItem === "object" && ruleItem.id) {
3176
+ rulesMap.set(ruleItem.id, ruleItem);
3177
+ }
3178
+ }
3179
+ }
3180
+ if (config.severityOverrides) {
3181
+ for (const [ruleId, sev] of Object.entries(config.severityOverrides)) {
3182
+ const existing = rulesMap.get(ruleId);
3183
+ if (existing) {
3184
+ existing.severity = sev;
3185
+ }
3186
+ }
3187
+ }
3188
+ if (rulesMap.size === 0) {
3189
+ return ARCH_RULE_PRESETS.cleanArchitecture.rules;
3190
+ }
3191
+ return Array.from(rulesMap.values());
3192
+ }
3193
+
3194
+ // src/classifier.ts
3195
+ var TECH_CATALOG = [
3196
+ // ── DATABASES ───────────────────────────────────────────────────────
3197
+ {
3198
+ patterns: [/\b(postgres(ql)?|psql|cockroach(db)?|timescale)\b/i],
3199
+ kind: "database",
3200
+ role: "database",
3201
+ badge: "SQL"
3202
+ },
3203
+ {
3204
+ patterns: [/\b(mysql|mariadb|aurora|planetscale)\b/i],
3205
+ kind: "database",
3206
+ role: "database",
3207
+ badge: "MySQL"
3208
+ },
3209
+ {
3210
+ patterns: [/\b(mongodb|mongo|documentdb|couchdb)\b/i],
3211
+ kind: "database",
3212
+ role: "database",
3213
+ badge: "Document"
3214
+ },
3215
+ {
3216
+ patterns: [/\b(dynamodb|cassandra|scylla|hbase)\b/i],
3217
+ kind: "database",
3218
+ role: "database",
3219
+ badge: "NoSQL"
3220
+ },
3221
+ {
3222
+ patterns: [/\b(redis|memcached?|elasticache|dragonfly|valkey)\b/i],
3223
+ kind: "cache",
3224
+ role: "cache",
3225
+ badge: "Cache"
3226
+ },
3227
+ {
3228
+ patterns: [/\b(neo4j|memgraph|dgraph|graphdb)\b/i],
3229
+ kind: "database",
3230
+ role: "database",
3231
+ badge: "Graph"
3232
+ },
3233
+ {
3234
+ patterns: [/\b(clickhouse|snowflake|bigquery|redshift|duckdb)\b/i],
3235
+ kind: "database",
3236
+ role: "database",
3237
+ badge: "Analytics"
3238
+ },
3239
+ // ── MESSAGING & EVENT STREAMING ────────────────────────────────────
3240
+ {
3241
+ patterns: [/\b(kafka|confluent|redpanda)\b/i],
3242
+ kind: "queue",
3243
+ role: "event_stream",
3244
+ badge: "EventStream"
3245
+ },
3246
+ {
3247
+ patterns: [/\b(rabbitmq|sqs|activemq|pulsar|nats|eventgrid|servicebus)\b/i],
3248
+ kind: "queue",
3249
+ role: "queue",
3250
+ badge: "Queue"
3251
+ },
3252
+ // ── GATEWAYS & INGRESS ──────────────────────────────────────────────
3253
+ {
3254
+ patterns: [/\b(kong|envoy|nginx|traefik|caddy|haproxy|emissary)\b/i],
3255
+ kind: "api_gateway",
3256
+ role: "gateway",
3257
+ badge: "Gateway"
3258
+ },
3259
+ {
3260
+ patterns: [/\b(cloudfront|cloudflare|fastly|akamai|cdn)\b/i],
3261
+ kind: "cdn",
3262
+ role: "network",
3263
+ badge: "CDN"
3264
+ },
3265
+ {
3266
+ patterns: [/\b(alb|elb|nlb|load_?balancer|ingress)\b/i],
3267
+ kind: "load_balancer",
3268
+ role: "network",
3269
+ badge: "LB"
3270
+ },
3271
+ // ── CLIENT & PRESENTATION ──────────────────────────────────────────
3272
+ {
3273
+ patterns: [/\b(react|vue|angular|svelte|next(\.?js)?|nuxt|solid)\b/i],
3274
+ kind: "browser",
3275
+ role: "client",
3276
+ badge: "Web"
3277
+ },
3278
+ {
3279
+ patterns: [/\b(flutter|react_?native|ios|android|swift|kotlin|mobile)\b/i],
3280
+ kind: "browser",
3281
+ role: "client",
3282
+ badge: "Mobile"
3283
+ },
3284
+ // ── AI & LLM ────────────────────────────────────────────────────────
3285
+ {
3286
+ patterns: [/\b(gemini|gpt(-?[a-z0-9.]+)?|claude|openai|anthropic|bedrock|vertexai|mistral|ollama|deepseek)\b/i],
3287
+ kind: "service",
3288
+ role: "ai_model",
3289
+ badge: "AI/LLM"
3290
+ },
3291
+ {
3292
+ patterns: [/\b(pinecone|weaviate|qdrant|chroma|milvus)\b/i],
3293
+ kind: "database",
3294
+ role: "database",
3295
+ badge: "VectorDB"
3296
+ },
3297
+ // ── CLOUD & CONTAINER RUNTIMES ─────────────────────────────────────
3298
+ {
3299
+ patterns: [/\b(k8s|kubernetes|eks|gke|aks|helm|argocd)\b/i],
3300
+ kind: "cluster",
3301
+ role: "platform",
3302
+ badge: "K8s"
3303
+ },
3304
+ {
3305
+ patterns: [/\b(lambda|cloud_?functions?|azure_?functions?|serverless)\b/i],
3306
+ kind: "worker",
3307
+ role: "compute",
3308
+ badge: "Function"
3309
+ },
3310
+ {
3311
+ patterns: [/\b(docker|ecs|container|fargate|cloud_?run)\b/i],
3312
+ kind: "service",
3313
+ role: "compute",
3314
+ badge: "Container"
3315
+ },
3316
+ // ── STORAGE & BLOB ──────────────────────────────────────────────────
3317
+ {
3318
+ patterns: [/\b(s3|gcs|azure_?blob|minio|r2|ceph)\b/i],
3319
+ kind: "storage",
3320
+ role: "storage",
3321
+ badge: "ObjectStorage"
3322
+ },
3323
+ // ── SECURITY & AUTH ─────────────────────────────────────────────────
3324
+ {
3325
+ patterns: [/\b(auth0|clerk|keycloak|cognito|okta|vault|jwt|oauth|oidc)\b/i],
3326
+ kind: "service",
3327
+ role: "security",
3328
+ badge: "Security"
3329
+ }
3330
+ ];
3331
+ function classifyTechnology(id, label = "") {
3332
+ const combined = `${id} ${label}`.trim();
3333
+ for (const entry of TECH_CATALOG) {
3334
+ if (entry.patterns.some((p) => p.test(combined))) {
3335
+ return {
3336
+ kind: entry.kind,
3337
+ role: entry.role,
3338
+ suggestedTheme: "paper",
3339
+ badge: entry.badge
3340
+ };
3341
+ }
3342
+ }
3343
+ return {
3344
+ kind: "service",
3345
+ role: "compute",
3346
+ suggestedTheme: "paper"
3347
+ };
3348
+ }
3349
+
3350
+ // src/diff.ts
3351
+ function diffDiagramASTs(beforeAST, afterAST) {
3352
+ const nodeDiffs = [];
3353
+ const edgeDiffs = [];
3354
+ const beforeNodeIds = new Set(Object.keys(beforeAST.nodes));
3355
+ const afterNodeIds = new Set(Object.keys(afterAST.nodes));
3356
+ let addedNodesCount = 0;
3357
+ let removedNodesCount = 0;
3358
+ let modifiedNodesCount = 0;
3359
+ for (const [id, afterNode] of Object.entries(afterAST.nodes)) {
3360
+ if (!beforeNodeIds.has(id)) {
3361
+ nodeDiffs.push({ id, status: "added", after: afterNode, changes: ["Newly added node"] });
3362
+ addedNodesCount++;
3363
+ } else {
3364
+ const beforeNode = beforeAST.nodes[id];
3365
+ const changes = [];
3366
+ if (beforeNode.kind !== afterNode.kind) {
3367
+ changes.push(`Kind changed: ${beforeNode.kind} \u2192 ${afterNode.kind}`);
3368
+ }
3369
+ if (beforeNode.label !== afterNode.label) {
3370
+ changes.push(`Label changed: "${beforeNode.label}" \u2192 "${afterNode.label}"`);
3371
+ }
3372
+ if (changes.length > 0) {
3373
+ nodeDiffs.push({ id, status: "modified", before: beforeNode, after: afterNode, changes });
3374
+ modifiedNodesCount++;
3375
+ } else {
3376
+ nodeDiffs.push({ id, status: "unchanged", before: beforeNode, after: afterNode, changes: [] });
3377
+ }
3378
+ }
3379
+ }
3380
+ for (const [id, beforeNode] of Object.entries(beforeAST.nodes)) {
3381
+ if (!afterNodeIds.has(id)) {
3382
+ nodeDiffs.push({ id, status: "removed", before: beforeNode, changes: ["Removed node"] });
3383
+ removedNodesCount++;
3384
+ }
3385
+ }
3386
+ const edgeKey = (e) => `${e.from}->${e.to}:${e.kind}`;
3387
+ const beforeEdgeMap = new Map(beforeAST.edges.map((e) => [edgeKey(e), e]));
3388
+ const afterEdgeMap = new Map(afterAST.edges.map((e) => [edgeKey(e), e]));
3389
+ for (const [key, afterEdge] of afterEdgeMap) {
3390
+ if (!beforeEdgeMap.has(key)) {
3391
+ edgeDiffs.push({ key, status: "added", after: afterEdge });
3392
+ } else {
3393
+ edgeDiffs.push({ key, status: "unchanged", before: beforeEdgeMap.get(key), after: afterEdge });
3394
+ }
3395
+ }
3396
+ for (const [key, beforeEdge] of beforeEdgeMap) {
3397
+ if (!afterEdgeMap.has(key)) {
3398
+ edgeDiffs.push({ key, status: "removed", before: beforeEdge });
3399
+ }
3400
+ }
3401
+ const summaryLines = [
3402
+ "### \u{1F4CA} Markdy Architectural Diff Summary",
3403
+ "",
3404
+ `| Metric | Count |`,
3405
+ `|---|---|`,
3406
+ `| \u{1F7E2} Nodes Added | **${addedNodesCount}** |`,
3407
+ `| \u{1F534} Nodes Removed | **${removedNodesCount}** |`,
3408
+ `| \u{1F7E1} Nodes Modified | **${modifiedNodesCount}** |`,
3409
+ ""
3410
+ ];
3411
+ if (addedNodesCount > 0 || modifiedNodesCount > 0 || removedNodesCount > 0) {
3412
+ summaryLines.push("#### Changes Detail");
3413
+ for (const nd of nodeDiffs.filter((n) => n.status !== "unchanged")) {
3414
+ summaryLines.push(`- **${nd.id}** (${nd.status.toUpperCase()}): ${nd.changes.join(", ")}`);
3415
+ }
3416
+ summaryLines.push("");
3417
+ }
3418
+ const evolutionLines = [
3419
+ `scene "Architecture Evolution" theme=${afterAST.meta.theme || "paper"}`,
3420
+ `layout ${afterAST.meta.direction || "LR"}`,
3421
+ ""
3422
+ ];
3423
+ for (const [id, node] of Object.entries({ ...beforeAST.nodes, ...afterAST.nodes })) {
3424
+ evolutionLines.push(`${node.kind} ${id} "${node.label}"`);
3425
+ }
3426
+ evolutionLines.push("");
3427
+ evolutionLines.push('beat v1 "Baseline Architecture":');
3428
+ const v1NodeIds = Object.keys(beforeAST.nodes).join(" ");
3429
+ if (v1NodeIds) {
3430
+ evolutionLines.push(` show ${v1NodeIds}`);
3431
+ }
3432
+ evolutionLines.push("");
3433
+ evolutionLines.push('beat transition "Migrate to Target Architecture":');
3434
+ const addedIds = nodeDiffs.filter((n) => n.status === "added").map((n) => n.id);
3435
+ const removedIds = nodeDiffs.filter((n) => n.status === "removed").map((n) => n.id);
3436
+ if (removedIds.length > 0) {
3437
+ evolutionLines.push(` hide ${removedIds.join(" ")}`);
3438
+ }
3439
+ if (addedIds.length > 0) {
3440
+ evolutionLines.push(` show ${addedIds.join(" ")}`);
3441
+ evolutionLines.push(` glow ${addedIds.join(" ")} color="#10b981"`);
3442
+ }
3443
+ return {
3444
+ nodes: nodeDiffs,
3445
+ edges: edgeDiffs,
3446
+ addedNodesCount,
3447
+ removedNodesCount,
3448
+ modifiedNodesCount,
3449
+ summaryMarkdown: summaryLines.join("\n"),
3450
+ evolutionMarkdyScript: evolutionLines.join("\n")
3451
+ };
3452
+ }
3453
+
3454
+ // src/url-codec.ts
3455
+ var PREFIX = "~m";
3456
+ var B64_CHARS = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
3457
+ function bytesToBase64Url(bytes) {
3458
+ let result = "";
3459
+ const len = bytes.length;
3460
+ let i = 0;
3461
+ while (i < len) {
3462
+ const b0 = bytes[i++];
3463
+ const b1 = i < len ? bytes[i++] : NaN;
3464
+ const b2 = i < len ? bytes[i++] : NaN;
3465
+ const idx0 = b0 >> 2;
3466
+ const idx1 = (b0 & 3) << 4 | (isNaN(b1) ? 0 : b1 >> 4);
3467
+ result += B64_CHARS[idx0] + B64_CHARS[idx1];
3468
+ if (!isNaN(b1)) {
3469
+ const idx2 = (b1 & 15) << 2 | (isNaN(b2) ? 0 : b2 >> 6);
3470
+ result += B64_CHARS[idx2];
3471
+ }
3472
+ if (!isNaN(b2)) {
3473
+ const idx3 = b2 & 63;
3474
+ result += B64_CHARS[idx3];
3475
+ }
3476
+ }
3477
+ return result;
3478
+ }
3479
+ var B64_LOOKUP = new Uint8Array(256);
3480
+ for (let i = 0; i < B64_CHARS.length; i++) {
3481
+ B64_LOOKUP[B64_CHARS.charCodeAt(i)] = i;
3482
+ }
3483
+ B64_LOOKUP["+".charCodeAt(0)] = 62;
3484
+ B64_LOOKUP["/".charCodeAt(0)] = 63;
3485
+ function base64UrlToBytes(str) {
3486
+ const cleanStr = str.replace(/=/g, "");
3487
+ const len = cleanStr.length;
3488
+ const outLen = len * 3 >> 2;
3489
+ const bytes = new Uint8Array(outLen);
3490
+ let inIdx = 0;
3491
+ let outIdx = 0;
3492
+ while (inIdx < len) {
3493
+ const c0 = B64_LOOKUP[cleanStr.charCodeAt(inIdx++)];
3494
+ const c1 = B64_LOOKUP[cleanStr.charCodeAt(inIdx++)];
3495
+ const c2 = inIdx < len ? B64_LOOKUP[cleanStr.charCodeAt(inIdx++)] : 64;
3496
+ const c3 = inIdx < len ? B64_LOOKUP[cleanStr.charCodeAt(inIdx++)] : 64;
3497
+ bytes[outIdx++] = c0 << 2 | c1 >> 4;
3498
+ if (c2 !== 64 && outIdx < outLen) {
3499
+ bytes[outIdx++] = (c1 & 15) << 4 | c2 >> 2;
3500
+ }
3501
+ if (c3 !== 64 && outIdx < outLen) {
3502
+ bytes[outIdx++] = (c2 & 3) << 6 | c3;
3503
+ }
3504
+ }
3505
+ return bytes;
3506
+ }
3507
+ async function compressMarkdyToUrlHash(code) {
3508
+ const encoder = new TextEncoder();
3509
+ const inputBytes = encoder.encode(code);
3510
+ if (typeof CompressionStream !== "undefined") {
3511
+ const cs = new CompressionStream("deflate-raw");
3512
+ const writer = cs.writable.getWriter();
3513
+ writer.write(inputBytes);
3514
+ writer.close();
3515
+ const response = new Response(cs.readable);
3516
+ const compressedBuffer = await response.arrayBuffer();
3517
+ return PREFIX + bytesToBase64Url(new Uint8Array(compressedBuffer));
3518
+ }
3519
+ return PREFIX + bytesToBase64Url(inputBytes);
3520
+ }
3521
+ async function decompressMarkdyFromUrlHash(hash) {
3522
+ if (!hash.startsWith(PREFIX)) {
3523
+ throw new Error("Invalid Markdy compressed URL state prefix");
3524
+ }
3525
+ const rawPayload = hash.slice(PREFIX.length);
3526
+ const bytes = base64UrlToBytes(rawPayload);
3527
+ if (typeof DecompressionStream !== "undefined") {
3528
+ try {
3529
+ const ds = new DecompressionStream("deflate-raw");
3530
+ const writer = ds.writable.getWriter();
3531
+ writer.write(bytes);
3532
+ writer.close();
3533
+ const response = new Response(ds.readable);
3534
+ const decompressedBuffer = await response.arrayBuffer();
3535
+ return new TextDecoder().decode(decompressedBuffer);
3536
+ } catch {
3537
+ return new TextDecoder().decode(bytes);
3538
+ }
3539
+ }
3540
+ return new TextDecoder().decode(bytes);
3541
+ }
3542
+
3543
+ // src/router.ts
3544
+ function getBoxPortPosition(box, port) {
3545
+ switch (port) {
3546
+ case "left":
3547
+ return { x: box.x, y: box.y + box.height / 2 };
3548
+ case "right":
3549
+ return { x: box.x + box.width, y: box.y + box.height / 2 };
3550
+ case "top":
3551
+ return { x: box.x + box.width / 2, y: box.y };
3552
+ case "bottom":
3553
+ return { x: box.x + box.width / 2, y: box.y + box.height };
3554
+ }
3555
+ }
3556
+ function selectOptimalPorts(sourceBox, targetBox) {
3557
+ const srcCenter = { x: sourceBox.x + sourceBox.width / 2, y: sourceBox.y + sourceBox.height / 2 };
3558
+ const tgtCenter = { x: targetBox.x + targetBox.width / 2, y: targetBox.y + targetBox.height / 2 };
3559
+ const dx = tgtCenter.x - srcCenter.x;
3560
+ const dy = tgtCenter.y - srcCenter.y;
3561
+ if (Math.abs(dx) >= Math.abs(dy)) {
3562
+ return dx > 0 ? { sourcePort: "right", targetPort: "left" } : { sourcePort: "left", targetPort: "right" };
3563
+ } else {
3564
+ return dy > 0 ? { sourcePort: "bottom", targetPort: "top" } : { sourcePort: "top", targetPort: "bottom" };
3565
+ }
3566
+ }
3567
+ function routeOrthogonalEdge(sourceBox, targetBox) {
3568
+ const { sourcePort, targetPort } = selectOptimalPorts(sourceBox, targetBox);
3569
+ const start = getBoxPortPosition(sourceBox, sourcePort);
3570
+ const end = getBoxPortPosition(targetBox, targetPort);
3571
+ const waypoints = [];
3572
+ if (sourcePort === "right" && targetPort === "left") {
3573
+ const midX = (start.x + end.x) / 2;
3574
+ waypoints.push({ x: midX, y: start.y });
3575
+ waypoints.push({ x: midX, y: end.y });
3576
+ } else if (sourcePort === "bottom" && targetPort === "top") {
3577
+ const midY = (start.y + end.y) / 2;
3578
+ waypoints.push({ x: start.x, y: midY });
3579
+ waypoints.push({ x: end.x, y: midY });
3580
+ } else {
3581
+ waypoints.push({ x: end.x, y: start.y });
3582
+ }
3583
+ let svgPathData = `M ${start.x} ${start.y}`;
3584
+ for (const wp of waypoints) {
3585
+ svgPathData += ` L ${wp.x} ${wp.y}`;
3586
+ }
3587
+ svgPathData += ` L ${end.x} ${end.y}`;
3588
+ return {
3589
+ sourcePort,
3590
+ targetPort,
3591
+ startPoint: start,
3592
+ endPoint: end,
3593
+ waypoints,
3594
+ svgPathData
3595
+ };
3596
+ }
3597
+
3598
+ // src/ai-healing.ts
3599
+ function analyzeAndBuildRepairPrompt(sourceCode) {
3600
+ const syntaxErrors = [];
3601
+ let ast = null;
3602
+ try {
3603
+ ast = parse(sourceCode);
3604
+ } catch (err) {
3605
+ syntaxErrors.push(err instanceof Error ? err.message : String(err));
3606
+ }
3607
+ if (!ast) {
3608
+ return {
3609
+ isValid: false,
3610
+ syntaxErrors,
3611
+ archViolations: [],
3612
+ repairPrompt: [
3613
+ "The following MarkdyScript failed to parse with syntax errors:",
3614
+ ...syntaxErrors.map((e) => ` - ${e}`),
3615
+ "",
3616
+ "Please fix the code below to follow valid MarkdyScript syntax:",
3617
+ "```markdy",
3618
+ sourceCode,
3619
+ "```"
3620
+ ].join("\n")
3621
+ };
3622
+ }
3623
+ const archViolations = validateArchitecture(ast);
3624
+ if (ast.diagnostics.length === 0 && archViolations.length === 0) {
3625
+ return { isValid: true, syntaxErrors: [], archViolations: [] };
3626
+ }
3627
+ const promptSections = [
3628
+ "The MarkdyScript diagram has the following compiler diagnostics and architectural rule violations:",
3629
+ "",
3630
+ "### Diagnostics:",
3631
+ ...ast.diagnostics.map((d) => ` - Line ${d.line}: ${d.message}`),
3632
+ "",
3633
+ "### Architectural Violations:",
3634
+ ...archViolations.map((v) => ` - [${v.severity.toUpperCase()}] ${v.ruleName}: ${v.message}`),
3635
+ "",
3636
+ "Please revise the diagram code to resolve all issues while preserving semantic nodes and beats:",
3637
+ "```markdy",
3638
+ sourceCode,
3639
+ "```"
3640
+ ];
3641
+ return {
3642
+ isValid: false,
3643
+ syntaxErrors: [],
3644
+ archViolations,
3645
+ repairPrompt: promptSections.join("\n")
3646
+ };
3647
+ }
3648
+
3649
+ // src/output-presets.ts
3650
+ var OUTPUT_PRESETS = {
3651
+ "doc-inline": {
3652
+ name: "doc-inline",
3653
+ width: 960,
3654
+ height: 600,
3655
+ safeArea: 40,
3656
+ aspect: "8:5",
3657
+ context: "Inline documentation diagrams"
3658
+ },
3659
+ "doc-wide": {
3660
+ name: "doc-wide",
3661
+ width: 1280,
3662
+ height: 720,
3663
+ safeArea: 40,
3664
+ aspect: "16:9",
3665
+ context: "Wide documentation hero diagrams"
3666
+ },
3667
+ "slide-16x9": {
3668
+ name: "slide-16x9",
3669
+ width: 1280,
3670
+ height: 720,
3671
+ safeArea: 48,
3672
+ aspect: "16:9",
3673
+ context: "Presentation slides (Google Slides, Keynote, PowerPoint)"
3674
+ },
3675
+ "slide-4x3": {
3676
+ name: "slide-4x3",
3677
+ width: 1024,
3678
+ height: 768,
3679
+ safeArea: 48,
3680
+ aspect: "4:3",
3681
+ context: "Legacy presentation slides"
3682
+ },
3683
+ "social-og": {
3684
+ name: "social-og",
3685
+ width: 1200,
3686
+ height: 632,
3687
+ safeArea: 64,
3688
+ aspect: "~1.9:1",
3689
+ context: "Open Graph / Twitter Card / LinkedIn"
3690
+ },
3691
+ "social-square": {
3692
+ name: "social-square",
3693
+ width: 1080,
3694
+ height: 1080,
3695
+ safeArea: 64,
3696
+ aspect: "1:1",
3697
+ context: "Instagram / Square social posts"
3698
+ },
3699
+ "print-a4": {
3700
+ name: "print-a4",
3701
+ width: 1120,
3702
+ height: 792,
3703
+ safeArea: 40,
3704
+ aspect: "~1.41:1",
3705
+ context: "A4 landscape print"
3706
+ },
3707
+ "print-letter": {
3708
+ name: "print-letter",
3709
+ width: 1056,
3710
+ height: 816,
3711
+ safeArea: 40,
3712
+ aspect: "~1.29:1",
3713
+ context: "US Letter landscape print"
3714
+ }
3715
+ };
3716
+ function resolveOutputPreset(name) {
3717
+ return OUTPUT_PRESETS[name] ?? OUTPUT_PRESETS["doc-inline"];
3718
+ }
3719
+ function listOutputPresets() {
3720
+ return Object.keys(OUTPUT_PRESETS);
3721
+ }
2044
3722
  export {
3723
+ ARCH_RULE_PRESETS,
2045
3724
  BEAT_CUE_KEYWORDS,
2046
3725
  CUE_ALIASES,
2047
3726
  DIAGRAM_TYPES,
2048
3727
  EDGE_OPERATORS,
2049
3728
  NODE_ALIASES,
2050
3729
  NODE_KINDS,
3730
+ OUTPUT_PRESETS,
2051
3731
  ParseError,
2052
3732
  SCENE_KEYS,
2053
3733
  TECHNICAL_NODE_KINDS,
2054
3734
  TECHNICAL_NODE_TYPES,
2055
3735
  THEMES,
2056
3736
  VISUAL_PRIMITIVE_TYPES,
3737
+ analyzeAndBuildRepairPrompt,
2057
3738
  canonicalNodeKind,
3739
+ classifyTechnology,
2058
3740
  compile,
2059
3741
  compilePlan,
3742
+ compressMarkdyToUrlHash,
3743
+ decompressMarkdyFromUrlHash,
3744
+ diffDiagramASTs,
3745
+ generateThemeFromBrand,
3746
+ getBoxPortPosition,
2060
3747
  humanizeId,
3748
+ listOutputPresets,
2061
3749
  nodeRole,
2062
3750
  parse,
2063
3751
  parseAndCompile,
2064
- resolveTheme
3752
+ resolveArchitectureConfig,
3753
+ resolveOutputPreset,
3754
+ resolveTheme,
3755
+ routeOrthogonalEdge,
3756
+ selectOptimalPorts,
3757
+ validateArchitecture
2065
3758
  };