@markdy/core 0.8.20 → 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.d.ts +199 -4
- package/dist/index.js +1623 -6
- package/package.json +1 -1
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",
|
|
@@ -747,6 +779,470 @@ function layoutConstellation(ast) {
|
|
|
747
779
|
}
|
|
748
780
|
return nodes;
|
|
749
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
|
+
}
|
|
750
1246
|
function layoutNodes(ast, edges) {
|
|
751
1247
|
const dtype = diagramType(ast);
|
|
752
1248
|
switch (dtype) {
|
|
@@ -758,6 +1254,29 @@ function layoutNodes(ast, edges) {
|
|
|
758
1254
|
return layoutRanked(ast, [], { columnLayout: true });
|
|
759
1255
|
case "constellation":
|
|
760
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);
|
|
761
1280
|
case "state":
|
|
762
1281
|
return layoutRanked(ast, cycleSafeEdges(Object.keys(ast.nodes), edges), { forceVertical: false });
|
|
763
1282
|
default:
|
|
@@ -1063,7 +1582,8 @@ var THEMES = {
|
|
|
1063
1582
|
shadow: "rgba(15, 23, 42, 0.14)",
|
|
1064
1583
|
labelPlate: "#ffffff",
|
|
1065
1584
|
roles: { ...ROLE_COLORS },
|
|
1066
|
-
edges: { ...EDGE_COLORS }
|
|
1585
|
+
edges: { ...EDGE_COLORS },
|
|
1586
|
+
series: ["#7c8f6f", "#5e7a9b", "#b8915a", "#9c6b50", "#6e6479"]
|
|
1067
1587
|
},
|
|
1068
1588
|
blueprint: {
|
|
1069
1589
|
name: "blueprint",
|
|
@@ -1143,7 +1663,8 @@ var THEMES = {
|
|
|
1143
1663
|
mono: "ui-monospace, SFMono-Regular, Menlo, monospace"
|
|
1144
1664
|
},
|
|
1145
1665
|
radiusMd: 6,
|
|
1146
|
-
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"]
|
|
1147
1668
|
},
|
|
1148
1669
|
nebula: {
|
|
1149
1670
|
name: "nebula",
|
|
@@ -1190,6 +1711,97 @@ var THEMES = {
|
|
|
1190
1711
|
},
|
|
1191
1712
|
radiusMd: 12,
|
|
1192
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 }
|
|
1193
1805
|
}
|
|
1194
1806
|
};
|
|
1195
1807
|
function resolveTheme(name) {
|
|
@@ -2117,25 +2729,1030 @@ function parseAndCompile(source) {
|
|
|
2117
2729
|
const plan = compile(ast);
|
|
2118
2730
|
return { ast, plan };
|
|
2119
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
|
+
}
|
|
2120
3722
|
export {
|
|
3723
|
+
ARCH_RULE_PRESETS,
|
|
2121
3724
|
BEAT_CUE_KEYWORDS,
|
|
2122
3725
|
CUE_ALIASES,
|
|
2123
3726
|
DIAGRAM_TYPES,
|
|
2124
3727
|
EDGE_OPERATORS,
|
|
2125
3728
|
NODE_ALIASES,
|
|
2126
3729
|
NODE_KINDS,
|
|
3730
|
+
OUTPUT_PRESETS,
|
|
2127
3731
|
ParseError,
|
|
2128
3732
|
SCENE_KEYS,
|
|
2129
3733
|
TECHNICAL_NODE_KINDS,
|
|
2130
3734
|
TECHNICAL_NODE_TYPES,
|
|
2131
3735
|
THEMES,
|
|
2132
3736
|
VISUAL_PRIMITIVE_TYPES,
|
|
3737
|
+
analyzeAndBuildRepairPrompt,
|
|
2133
3738
|
canonicalNodeKind,
|
|
3739
|
+
classifyTechnology,
|
|
2134
3740
|
compile,
|
|
2135
3741
|
compilePlan,
|
|
3742
|
+
compressMarkdyToUrlHash,
|
|
3743
|
+
decompressMarkdyFromUrlHash,
|
|
3744
|
+
diffDiagramASTs,
|
|
3745
|
+
generateThemeFromBrand,
|
|
3746
|
+
getBoxPortPosition,
|
|
2136
3747
|
humanizeId,
|
|
3748
|
+
listOutputPresets,
|
|
2137
3749
|
nodeRole,
|
|
2138
3750
|
parse,
|
|
2139
3751
|
parseAndCompile,
|
|
2140
|
-
|
|
3752
|
+
resolveArchitectureConfig,
|
|
3753
|
+
resolveOutputPreset,
|
|
3754
|
+
resolveTheme,
|
|
3755
|
+
routeOrthogonalEdge,
|
|
3756
|
+
selectOptimalPorts,
|
|
3757
|
+
validateArchitecture
|
|
2141
3758
|
};
|