@principal-ade/code-quality-panels 0.1.0 → 0.1.2

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.
@@ -1,5 +1,6 @@
1
1
  import { jsxs, jsx, Fragment } from "react/jsx-runtime";
2
- import React2, { forwardRef, createElement, createContext, useState, useEffect, useContext } from "react";
2
+ import * as React2 from "react";
3
+ import React2__default, { forwardRef, createElement, createContext, useState, useEffect, useContext } from "react";
3
4
  /**
4
5
  * @license lucide-react v0.552.0 - ISC
5
6
  *
@@ -105,7 +106,34 @@ const createLucideIcon = (iconName, iconNode) => {
105
106
  * This source code is licensed under the ISC license.
106
107
  * See the LICENSE file in the root directory of this source tree.
107
108
  */
108
- const __iconNode = [
109
+ const __iconNode$4 = [["path", { d: "M20 6 9 17l-5-5", key: "1gmf2c" }]];
110
+ const Check = createLucideIcon("check", __iconNode$4);
111
+ /**
112
+ * @license lucide-react v0.552.0 - ISC
113
+ *
114
+ * This source code is licensed under the ISC license.
115
+ * See the LICENSE file in the root directory of this source tree.
116
+ */
117
+ const __iconNode$3 = [["path", { d: "m9 18 6-6-6-6", key: "mthhwq" }]];
118
+ const ChevronRight = createLucideIcon("chevron-right", __iconNode$3);
119
+ /**
120
+ * @license lucide-react v0.552.0 - ISC
121
+ *
122
+ * This source code is licensed under the ISC license.
123
+ * See the LICENSE file in the root directory of this source tree.
124
+ */
125
+ const __iconNode$2 = [
126
+ ["rect", { width: "14", height: "14", x: "8", y: "8", rx: "2", ry: "2", key: "17jyea" }],
127
+ ["path", { d: "M4 16c-1.1 0-2-.9-2-2V4c0-1.1.9-2 2-2h10c1.1 0 2 .9 2 2", key: "zix9uf" }]
128
+ ];
129
+ const Copy = createLucideIcon("copy", __iconNode$2);
130
+ /**
131
+ * @license lucide-react v0.552.0 - ISC
132
+ *
133
+ * This source code is licensed under the ISC license.
134
+ * See the LICENSE file in the root directory of this source tree.
135
+ */
136
+ const __iconNode$1 = [
109
137
  [
110
138
  "path",
111
139
  {
@@ -114,7 +142,18 @@ const __iconNode = [
114
142
  }
115
143
  ]
116
144
  ];
117
- const Hexagon = createLucideIcon("hexagon", __iconNode);
145
+ const Hexagon = createLucideIcon("hexagon", __iconNode$1);
146
+ /**
147
+ * @license lucide-react v0.552.0 - ISC
148
+ *
149
+ * This source code is licensed under the ISC license.
150
+ * See the LICENSE file in the root directory of this source tree.
151
+ */
152
+ const __iconNode = [
153
+ ["path", { d: "M12 19h8", key: "baeox8" }],
154
+ ["path", { d: "m4 17 6-6-6-6", key: "1yngyt" }]
155
+ ];
156
+ const Terminal = createLucideIcon("terminal", __iconNode);
118
157
  var terminalTheme = {
119
158
  space: [0, 4, 8, 16, 32, 64, 128, 256, 512],
120
159
  fonts: {
@@ -296,7 +335,7 @@ var ThemeProvider = ({
296
335
  initialMode
297
336
  }) => {
298
337
  const [mode, setMode] = useState(initialMode);
299
- const activeTheme = React2.useMemo(() => {
338
+ const activeTheme = React2__default.useMemo(() => {
300
339
  if (!mode || !customTheme.modes || !customTheme.modes[mode]) {
301
340
  return customTheme;
302
341
  }
@@ -325,7 +364,7 @@ var ThemeProvider = ({
325
364
  mode,
326
365
  setMode
327
366
  };
328
- return /* @__PURE__ */ React2.createElement(ThemeContextSingleton.Provider, {
367
+ return /* @__PURE__ */ React2__default.createElement(ThemeContextSingleton.Provider, {
329
368
  value
330
369
  }, children);
331
370
  };
@@ -404,7 +443,10 @@ function QualityHexagon({
404
443
  theme: theme2,
405
444
  showLabels = false,
406
445
  showValues = false,
407
- className
446
+ className,
447
+ onVertexHover,
448
+ onVertexLeave,
449
+ onVertexClick
408
450
  }) {
409
451
  const themeColors = getThemeColors(theme2);
410
452
  const colors = themeColors.tierColors[tier] ?? themeColors.tierColors.none;
@@ -484,38 +526,71 @@ function QualityHexagon({
484
526
  style: { transition: "all 0.5s ease" }
485
527
  }
486
528
  ),
487
- metricConfig.map(({ key, angle }) => {
488
- let value = metrics[key];
529
+ metricConfig.map(({ key, label, color, angle }) => {
530
+ const rawValue = metrics[key];
531
+ let value = rawValue;
489
532
  if (key === "deadCode") {
490
533
  value = 100 - value;
491
534
  }
492
535
  const point = calculateMetricPoint(center, radius, angle, 100);
493
536
  const dataPoint = calculateMetricPoint(center, radius, angle, value);
494
- return /* @__PURE__ */ jsxs("g", { children: [
495
- /* @__PURE__ */ jsx(
496
- "circle",
497
- {
498
- cx: point.x,
499
- cy: point.y,
500
- r: dotSize,
501
- fill: "white",
502
- stroke: colors.stroke,
503
- strokeWidth: 1.5
504
- }
505
- ),
506
- /* @__PURE__ */ jsx(
507
- "circle",
508
- {
509
- cx: dataPoint.x,
510
- cy: dataPoint.y,
511
- r: dotSize * 0.7,
512
- fill: colors.fill,
513
- stroke: colors.stroke,
514
- strokeWidth: 1,
515
- style: { opacity: 0.9 }
516
- }
517
- )
518
- ] }, key);
537
+ const vertexInfo = {
538
+ key,
539
+ label,
540
+ value: rawValue,
541
+ color
542
+ };
543
+ const handleMouseEnter = () => {
544
+ onVertexHover == null ? void 0 : onVertexHover(vertexInfo);
545
+ };
546
+ const handleClick = (e) => {
547
+ e.stopPropagation();
548
+ onVertexClick == null ? void 0 : onVertexClick(vertexInfo);
549
+ };
550
+ return /* @__PURE__ */ jsxs(
551
+ "g",
552
+ {
553
+ onMouseEnter: handleMouseEnter,
554
+ onMouseLeave: onVertexLeave,
555
+ onClick: handleClick,
556
+ style: { cursor: onVertexHover || onVertexClick ? "pointer" : "default" },
557
+ children: [
558
+ /* @__PURE__ */ jsx(
559
+ "circle",
560
+ {
561
+ cx: point.x,
562
+ cy: point.y,
563
+ r: dotSize * 2.5,
564
+ fill: "transparent"
565
+ }
566
+ ),
567
+ /* @__PURE__ */ jsx(
568
+ "circle",
569
+ {
570
+ cx: point.x,
571
+ cy: point.y,
572
+ r: dotSize,
573
+ fill: "white",
574
+ stroke: colors.stroke,
575
+ strokeWidth: 1.5
576
+ }
577
+ ),
578
+ /* @__PURE__ */ jsx(
579
+ "circle",
580
+ {
581
+ cx: dataPoint.x,
582
+ cy: dataPoint.y,
583
+ r: dotSize * 0.7,
584
+ fill: colors.fill,
585
+ stroke: colors.stroke,
586
+ strokeWidth: 1,
587
+ style: { opacity: 0.9 }
588
+ }
589
+ )
590
+ ]
591
+ },
592
+ key
593
+ );
519
594
  }),
520
595
  /* @__PURE__ */ jsx(
521
596
  "text",
@@ -726,6 +801,202 @@ function QualityHexagonDetailed({
726
801
  }
727
802
  );
728
803
  }
804
+ function QualityHexagonExpandable({
805
+ metrics,
806
+ tier,
807
+ theme: theme2,
808
+ className,
809
+ packageName,
810
+ packageVersion,
811
+ onRefresh,
812
+ isRefreshing = false,
813
+ defaultExpanded = false
814
+ }) {
815
+ const [expanded, setExpanded] = React2.useState(defaultExpanded);
816
+ const themeColors = getThemeColors(theme2);
817
+ const colors = themeColors.tierColors[tier] ?? themeColors.tierColors.none;
818
+ const metricConfig = getMetricConfig(themeColors);
819
+ const hasHeader = packageName || onRefresh;
820
+ return /* @__PURE__ */ jsxs(
821
+ "div",
822
+ {
823
+ className: cn(className),
824
+ style: {
825
+ display: "flex",
826
+ flexDirection: "column",
827
+ gap: 12,
828
+ padding: 16,
829
+ borderRadius: 8,
830
+ backgroundColor: colors.bg,
831
+ flex: "1 1 200px"
832
+ },
833
+ children: [
834
+ hasHeader && /* @__PURE__ */ jsxs("div", { style: {
835
+ display: "flex",
836
+ alignItems: "center",
837
+ justifyContent: "space-between",
838
+ gap: 12
839
+ }, children: [
840
+ packageName ? /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 2 }, children: [
841
+ packageName.startsWith("@") && packageName.includes("/") ? /* @__PURE__ */ jsxs(Fragment, { children: [
842
+ /* @__PURE__ */ jsx("span", { style: {
843
+ fontSize: 12,
844
+ color: theme2.colors.textMuted
845
+ }, children: packageName.split("/")[0] }),
846
+ /* @__PURE__ */ jsx("span", { style: {
847
+ fontSize: 14,
848
+ fontWeight: 500,
849
+ color: colors.stroke
850
+ }, children: packageName.split("/")[1] })
851
+ ] }) : /* @__PURE__ */ jsx("span", { style: {
852
+ fontSize: 14,
853
+ fontWeight: 500,
854
+ color: colors.stroke
855
+ }, children: packageName }),
856
+ packageVersion && /* @__PURE__ */ jsxs("span", { style: {
857
+ fontSize: 12,
858
+ color: theme2.colors.textMuted
859
+ }, children: [
860
+ "v",
861
+ packageVersion
862
+ ] })
863
+ ] }) : /* @__PURE__ */ jsx("span", {}),
864
+ onRefresh && /* @__PURE__ */ jsx(
865
+ "button",
866
+ {
867
+ onClick: onRefresh,
868
+ disabled: isRefreshing,
869
+ style: {
870
+ padding: 6,
871
+ display: "flex",
872
+ alignItems: "center",
873
+ justifyContent: "center",
874
+ border: `1px solid ${theme2.colors.border}`,
875
+ borderRadius: 4,
876
+ background: theme2.colors.surface,
877
+ color: theme2.colors.textMuted,
878
+ cursor: isRefreshing ? "not-allowed" : "pointer",
879
+ opacity: isRefreshing ? 0.6 : 1
880
+ },
881
+ title: "Refresh",
882
+ children: /* @__PURE__ */ jsxs(
883
+ "svg",
884
+ {
885
+ width: "14",
886
+ height: "14",
887
+ viewBox: "0 0 24 24",
888
+ fill: "none",
889
+ stroke: "currentColor",
890
+ strokeWidth: "2",
891
+ strokeLinecap: "round",
892
+ strokeLinejoin: "round",
893
+ style: {
894
+ animation: isRefreshing ? "spin 1s linear infinite" : "none"
895
+ },
896
+ children: [
897
+ /* @__PURE__ */ jsx("path", { d: "M21 12a9 9 0 0 0-9-9 9.75 9.75 0 0 0-6.74 2.74L3 8" }),
898
+ /* @__PURE__ */ jsx("path", { d: "M3 3v5h5" }),
899
+ /* @__PURE__ */ jsx("path", { d: "M3 12a9 9 0 0 0 9 9 9.75 9.75 0 0 0 6.74-2.74L21 16" }),
900
+ /* @__PURE__ */ jsx("path", { d: "M16 16h5v5" })
901
+ ]
902
+ }
903
+ )
904
+ }
905
+ )
906
+ ] }),
907
+ /* @__PURE__ */ jsx(
908
+ "div",
909
+ {
910
+ onClick: () => setExpanded(!expanded),
911
+ style: {
912
+ cursor: "pointer",
913
+ display: "flex",
914
+ justifyContent: "center",
915
+ alignItems: "center"
916
+ },
917
+ children: /* @__PURE__ */ jsx("div", { style: { width: 200, height: 200 }, children: /* @__PURE__ */ jsx(
918
+ QualityHexagon,
919
+ {
920
+ metrics,
921
+ tier,
922
+ theme: theme2,
923
+ showLabels: true,
924
+ showValues: false
925
+ }
926
+ ) })
927
+ }
928
+ ),
929
+ /* @__PURE__ */ jsx(
930
+ "div",
931
+ {
932
+ style: {
933
+ display: "grid",
934
+ gridTemplateRows: expanded ? "1fr" : "0fr",
935
+ transition: "grid-template-rows 0.3s ease"
936
+ },
937
+ children: /* @__PURE__ */ jsx("div", { style: { overflow: "hidden" }, children: /* @__PURE__ */ jsx("div", { style: {
938
+ display: "flex",
939
+ flexDirection: "column",
940
+ gap: 8,
941
+ padding: "8px 24px",
942
+ borderTop: `1px solid ${theme2.colors.border}`,
943
+ marginTop: 8
944
+ }, children: metricConfig.map(({ key, label, color }) => {
945
+ const value = metrics[key];
946
+ return /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12 }, children: [
947
+ /* @__PURE__ */ jsxs("span", { style: {
948
+ fontSize: 14,
949
+ color: theme2.colors.textMuted
950
+ }, children: [
951
+ label,
952
+ key === "deadCode" ? " ↓" : ""
953
+ ] }),
954
+ /* @__PURE__ */ jsxs("span", { style: {
955
+ fontSize: 14,
956
+ fontWeight: 500,
957
+ color
958
+ }, children: [
959
+ value,
960
+ "%"
961
+ ] })
962
+ ] }, key);
963
+ }) }) })
964
+ }
965
+ ),
966
+ /* @__PURE__ */ jsx(
967
+ "div",
968
+ {
969
+ onClick: () => setExpanded(!expanded),
970
+ style: {
971
+ display: "flex",
972
+ justifyContent: "center",
973
+ cursor: "pointer",
974
+ padding: 4
975
+ },
976
+ children: /* @__PURE__ */ jsx(
977
+ "svg",
978
+ {
979
+ width: "16",
980
+ height: "16",
981
+ viewBox: "0 0 24 24",
982
+ fill: "none",
983
+ stroke: theme2.colors.textMuted,
984
+ strokeWidth: "2",
985
+ strokeLinecap: "round",
986
+ strokeLinejoin: "round",
987
+ style: {
988
+ transform: expanded ? "rotate(180deg)" : "rotate(0deg)",
989
+ transition: "transform 0.3s ease"
990
+ },
991
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
992
+ }
993
+ )
994
+ }
995
+ )
996
+ ]
997
+ }
998
+ );
999
+ }
729
1000
  function calculateQualityTier(metrics) {
730
1001
  const metricsForAverage = { ...metrics };
731
1002
  metricsForAverage.deadCode = 100 - metricsForAverage.deadCode;
@@ -736,6 +1007,322 @@ function calculateQualityTier(metrics) {
736
1007
  if (average >= 40) return "bronze";
737
1008
  return "none";
738
1009
  }
1010
+ const WORKFLOW_FILE_PATH = ".github/workflows/quality-lens.yml";
1011
+ function checkFileExistsInTree(treeData, targetPath) {
1012
+ if (!(treeData == null ? void 0 : treeData.allFiles)) return false;
1013
+ const normalizedTarget = targetPath.replace(/^\//, "").toLowerCase();
1014
+ return treeData.allFiles.some((file) => {
1015
+ const filePath = (file.relativePath || file.path || "").toLowerCase();
1016
+ return filePath.endsWith(normalizedTarget) || filePath === normalizedTarget;
1017
+ });
1018
+ }
1019
+ const CommandLine = ({ command, theme: theme2 }) => {
1020
+ const [copied, setCopied] = React2__default.useState(false);
1021
+ const handleCopy = async () => {
1022
+ try {
1023
+ await navigator.clipboard.writeText(command);
1024
+ setCopied(true);
1025
+ setTimeout(() => setCopied(false), 2e3);
1026
+ } catch {
1027
+ console.log("Copy:", command);
1028
+ }
1029
+ };
1030
+ return /* @__PURE__ */ jsxs(
1031
+ "div",
1032
+ {
1033
+ style: {
1034
+ display: "flex",
1035
+ alignItems: "center",
1036
+ justifyContent: "space-between",
1037
+ gap: 12,
1038
+ padding: "10px 14px",
1039
+ borderRadius: 6,
1040
+ backgroundColor: theme2.colors.background,
1041
+ border: `1px solid ${theme2.colors.border}`,
1042
+ fontFamily: "monospace",
1043
+ fontSize: 13
1044
+ },
1045
+ children: [
1046
+ /* @__PURE__ */ jsx("code", { style: { color: theme2.colors.text }, children: command }),
1047
+ /* @__PURE__ */ jsx(
1048
+ "button",
1049
+ {
1050
+ onClick: handleCopy,
1051
+ style: {
1052
+ display: "flex",
1053
+ alignItems: "center",
1054
+ justifyContent: "center",
1055
+ padding: 4,
1056
+ border: "none",
1057
+ backgroundColor: "transparent",
1058
+ color: theme2.colors.textMuted,
1059
+ cursor: "pointer"
1060
+ },
1061
+ title: "Copy command",
1062
+ children: copied ? /* @__PURE__ */ jsx(Check, { size: 16, color: theme2.colors.success }) : /* @__PURE__ */ jsx(Copy, { size: 16 })
1063
+ }
1064
+ )
1065
+ ]
1066
+ }
1067
+ );
1068
+ };
1069
+ const QualityEmptyState = ({
1070
+ theme: theme2,
1071
+ hasWorkflow
1072
+ }) => {
1073
+ if (hasWorkflow) {
1074
+ return /* @__PURE__ */ jsxs(
1075
+ "div",
1076
+ {
1077
+ style: {
1078
+ display: "flex",
1079
+ flexDirection: "column",
1080
+ padding: "16px 0",
1081
+ gap: 16,
1082
+ width: "100%"
1083
+ },
1084
+ children: [
1085
+ /* @__PURE__ */ jsx(
1086
+ "p",
1087
+ {
1088
+ style: {
1089
+ margin: 0,
1090
+ fontSize: 14,
1091
+ color: theme2.colors.textMuted,
1092
+ lineHeight: 1.5
1093
+ },
1094
+ children: "Quality metrics will appear here after your next CI run completes."
1095
+ }
1096
+ ),
1097
+ /* @__PURE__ */ jsxs(
1098
+ "div",
1099
+ {
1100
+ style: {
1101
+ display: "flex",
1102
+ alignItems: "center",
1103
+ gap: 8,
1104
+ padding: "10px 14px",
1105
+ borderRadius: 6,
1106
+ backgroundColor: `${theme2.colors.success}15`,
1107
+ color: theme2.colors.success,
1108
+ fontSize: 13
1109
+ },
1110
+ children: [
1111
+ /* @__PURE__ */ jsx(Check, { size: 16 }),
1112
+ /* @__PURE__ */ jsxs("span", { children: [
1113
+ "Workflow detected at ",
1114
+ WORKFLOW_FILE_PATH
1115
+ ] })
1116
+ ]
1117
+ }
1118
+ )
1119
+ ]
1120
+ }
1121
+ );
1122
+ }
1123
+ return /* @__PURE__ */ jsxs(
1124
+ "div",
1125
+ {
1126
+ style: {
1127
+ display: "flex",
1128
+ flexDirection: "column",
1129
+ padding: "16px 0",
1130
+ gap: 16,
1131
+ width: "100%"
1132
+ },
1133
+ children: [
1134
+ /* @__PURE__ */ jsx(
1135
+ "p",
1136
+ {
1137
+ style: {
1138
+ margin: 0,
1139
+ fontSize: 14,
1140
+ color: theme2.colors.textMuted,
1141
+ lineHeight: 1.5
1142
+ },
1143
+ children: "Track your code quality with automated analysis of tests, linting, types, formatting, dead code, and documentation."
1144
+ }
1145
+ ),
1146
+ /* @__PURE__ */ jsxs(
1147
+ "div",
1148
+ {
1149
+ style: {
1150
+ display: "flex",
1151
+ flexDirection: "column",
1152
+ gap: 16,
1153
+ padding: 20,
1154
+ borderRadius: 8,
1155
+ backgroundColor: theme2.colors.surface,
1156
+ border: `1px solid ${theme2.colors.border}`
1157
+ },
1158
+ children: [
1159
+ /* @__PURE__ */ jsxs(
1160
+ "div",
1161
+ {
1162
+ style: {
1163
+ display: "flex",
1164
+ alignItems: "center",
1165
+ gap: 10,
1166
+ marginBottom: 4
1167
+ },
1168
+ children: [
1169
+ /* @__PURE__ */ jsx(Terminal, { size: 20, color: theme2.colors.text }),
1170
+ /* @__PURE__ */ jsx(
1171
+ "h4",
1172
+ {
1173
+ style: {
1174
+ margin: 0,
1175
+ fontSize: 15,
1176
+ fontWeight: 600,
1177
+ color: theme2.colors.text
1178
+ },
1179
+ children: "Get Started"
1180
+ }
1181
+ )
1182
+ ]
1183
+ }
1184
+ ),
1185
+ /* @__PURE__ */ jsxs("div", { children: [
1186
+ /* @__PURE__ */ jsxs(
1187
+ "div",
1188
+ {
1189
+ style: {
1190
+ display: "flex",
1191
+ alignItems: "center",
1192
+ gap: 8,
1193
+ marginBottom: 8,
1194
+ fontSize: 13,
1195
+ color: theme2.colors.textMuted
1196
+ },
1197
+ children: [
1198
+ /* @__PURE__ */ jsx(
1199
+ "span",
1200
+ {
1201
+ style: {
1202
+ display: "flex",
1203
+ alignItems: "center",
1204
+ justifyContent: "center",
1205
+ width: 20,
1206
+ height: 20,
1207
+ borderRadius: "50%",
1208
+ backgroundColor: theme2.colors.primary,
1209
+ color: theme2.colors.background,
1210
+ fontSize: 11,
1211
+ fontWeight: 600
1212
+ },
1213
+ children: "1"
1214
+ }
1215
+ ),
1216
+ /* @__PURE__ */ jsx("span", { children: "Install the CLI" })
1217
+ ]
1218
+ }
1219
+ ),
1220
+ /* @__PURE__ */ jsx(
1221
+ CommandLine,
1222
+ {
1223
+ command: "npm install -g @principal-ai/quality-lens-cli",
1224
+ theme: theme2
1225
+ }
1226
+ )
1227
+ ] }),
1228
+ /* @__PURE__ */ jsxs("div", { children: [
1229
+ /* @__PURE__ */ jsxs(
1230
+ "div",
1231
+ {
1232
+ style: {
1233
+ display: "flex",
1234
+ alignItems: "center",
1235
+ gap: 8,
1236
+ marginBottom: 8,
1237
+ fontSize: 13,
1238
+ color: theme2.colors.textMuted
1239
+ },
1240
+ children: [
1241
+ /* @__PURE__ */ jsx(
1242
+ "span",
1243
+ {
1244
+ style: {
1245
+ display: "flex",
1246
+ alignItems: "center",
1247
+ justifyContent: "center",
1248
+ width: 20,
1249
+ height: 20,
1250
+ borderRadius: "50%",
1251
+ backgroundColor: theme2.colors.primary,
1252
+ color: theme2.colors.background,
1253
+ fontSize: 11,
1254
+ fontWeight: 600
1255
+ },
1256
+ children: "2"
1257
+ }
1258
+ ),
1259
+ /* @__PURE__ */ jsx("span", { children: "Check what quality tools are available" })
1260
+ ]
1261
+ }
1262
+ ),
1263
+ /* @__PURE__ */ jsx(CommandLine, { command: "quality-lens list", theme: theme2 })
1264
+ ] }),
1265
+ /* @__PURE__ */ jsxs("div", { children: [
1266
+ /* @__PURE__ */ jsxs(
1267
+ "div",
1268
+ {
1269
+ style: {
1270
+ display: "flex",
1271
+ alignItems: "center",
1272
+ gap: 8,
1273
+ marginBottom: 8,
1274
+ fontSize: 13,
1275
+ color: theme2.colors.textMuted
1276
+ },
1277
+ children: [
1278
+ /* @__PURE__ */ jsx(
1279
+ "span",
1280
+ {
1281
+ style: {
1282
+ display: "flex",
1283
+ alignItems: "center",
1284
+ justifyContent: "center",
1285
+ width: 20,
1286
+ height: 20,
1287
+ borderRadius: "50%",
1288
+ backgroundColor: theme2.colors.primary,
1289
+ color: theme2.colors.background,
1290
+ fontSize: 11,
1291
+ fontWeight: 600
1292
+ },
1293
+ children: "3"
1294
+ }
1295
+ ),
1296
+ /* @__PURE__ */ jsx("span", { children: "Set up the GitHub Action" })
1297
+ ]
1298
+ }
1299
+ ),
1300
+ /* @__PURE__ */ jsx(CommandLine, { command: "quality-lens init", theme: theme2 })
1301
+ ] }),
1302
+ /* @__PURE__ */ jsxs(
1303
+ "div",
1304
+ {
1305
+ style: {
1306
+ display: "flex",
1307
+ alignItems: "center",
1308
+ gap: 6,
1309
+ paddingTop: 8,
1310
+ fontSize: 13,
1311
+ color: theme2.colors.textMuted
1312
+ },
1313
+ children: [
1314
+ /* @__PURE__ */ jsx(ChevronRight, { size: 14 }),
1315
+ /* @__PURE__ */ jsx("span", { children: "Then commit and push to start tracking quality" })
1316
+ ]
1317
+ }
1318
+ )
1319
+ ]
1320
+ }
1321
+ )
1322
+ ]
1323
+ }
1324
+ );
1325
+ };
739
1326
  const mockPackages = [
740
1327
  {
741
1328
  name: "@principal-ade/code-quality-panels",
@@ -756,11 +1343,15 @@ const QualityHexagonPanelContent = ({
756
1343
  }) => {
757
1344
  var _a;
758
1345
  const { theme: theme2 } = useTheme();
759
- const [refreshingPackages, setRefreshingPackages] = React2.useState(/* @__PURE__ */ new Set());
1346
+ const [refreshingPackages, setRefreshingPackages] = React2__default.useState(/* @__PURE__ */ new Set());
760
1347
  const qualitySlice = context.getSlice("quality");
761
1348
  const hasQualitySlice = context.hasSlice("quality");
762
1349
  const isLoading = (qualitySlice == null ? void 0 : qualitySlice.loading) ?? false;
763
- const packages = React2.useMemo(() => {
1350
+ const fileTreeSlice = context.getSlice("fileTree");
1351
+ const hasWorkflow = React2__default.useMemo(() => {
1352
+ return checkFileExistsInTree((fileTreeSlice == null ? void 0 : fileTreeSlice.data) ?? void 0, WORKFLOW_FILE_PATH);
1353
+ }, [fileTreeSlice == null ? void 0 : fileTreeSlice.data]);
1354
+ const packages = React2__default.useMemo(() => {
764
1355
  var _a2;
765
1356
  if ((_a2 = qualitySlice == null ? void 0 : qualitySlice.data) == null ? void 0 : _a2.packages) {
766
1357
  return qualitySlice.data.packages;
@@ -770,20 +1361,6 @@ const QualityHexagonPanelContent = ({
770
1361
  }
771
1362
  return mockPackages;
772
1363
  }, [(_a = qualitySlice == null ? void 0 : qualitySlice.data) == null ? void 0 : _a.packages, hasQualitySlice]);
773
- const handleRefreshPackage = async (packageName) => {
774
- setRefreshingPackages((prev) => new Set(prev).add(packageName));
775
- try {
776
- if (context.hasSlice("quality")) {
777
- await context.refresh("repository", "quality");
778
- }
779
- } finally {
780
- setRefreshingPackages((prev) => {
781
- const next = new Set(prev);
782
- next.delete(packageName);
783
- return next;
784
- });
785
- }
786
- };
787
1364
  const handleRefreshAll = async () => {
788
1365
  const allNames = packages.map((p) => p.name);
789
1366
  setRefreshingPackages(new Set(allNames));
@@ -795,7 +1372,7 @@ const QualityHexagonPanelContent = ({
795
1372
  setRefreshingPackages(/* @__PURE__ */ new Set());
796
1373
  }
797
1374
  };
798
- React2.useEffect(() => {
1375
+ React2__default.useEffect(() => {
799
1376
  const unsubscribers = [
800
1377
  events.on("principal-ade.quality-panel:refresh", async () => {
801
1378
  await handleRefreshAll();
@@ -820,93 +1397,679 @@ const QualityHexagonPanelContent = ({
820
1397
  documentation: acc.documentation + pkg.metrics.documentation / packages.length
821
1398
  }), { tests: 0, deadCode: 0, linting: 0, formatting: 0, types: 0, documentation: 0 })
822
1399
  ) : "none";
823
- return /* @__PURE__ */ jsxs(
1400
+ return /* @__PURE__ */ jsx(
824
1401
  "div",
825
1402
  {
826
1403
  style: {
827
- padding: 20,
828
1404
  fontFamily: theme2.fonts.body,
829
1405
  height: "100%",
830
- display: "flex",
831
- flexDirection: "column",
832
- gap: 16,
1406
+ minHeight: 0,
833
1407
  backgroundColor: theme2.colors.background,
834
1408
  color: theme2.colors.text,
835
1409
  overflowY: "auto",
836
1410
  boxSizing: "border-box"
837
1411
  },
1412
+ children: /* @__PURE__ */ jsxs(
1413
+ "div",
1414
+ {
1415
+ style: {
1416
+ padding: 20,
1417
+ display: "flex",
1418
+ flexDirection: "column",
1419
+ gap: 16
1420
+ },
1421
+ children: [
1422
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1423
+ /* @__PURE__ */ jsx(Hexagon, { size: 24, color: tierColors[overallTier] }),
1424
+ /* @__PURE__ */ jsx(
1425
+ "h2",
1426
+ {
1427
+ style: {
1428
+ margin: 0,
1429
+ fontSize: 20,
1430
+ fontWeight: 600,
1431
+ color: theme2.colors.text
1432
+ },
1433
+ children: "Code Quality"
1434
+ }
1435
+ ),
1436
+ /* @__PURE__ */ jsx(
1437
+ "span",
1438
+ {
1439
+ title: "Platinum: 90%+ avg | Gold: 75%+ | Silver: 60%+ | Bronze: 40%+",
1440
+ style: {
1441
+ display: "inline-flex",
1442
+ alignItems: "center",
1443
+ justifyContent: "center",
1444
+ width: 18,
1445
+ height: 18,
1446
+ borderRadius: "50%",
1447
+ border: `1px solid ${theme2.colors.border}`,
1448
+ fontSize: 12,
1449
+ color: theme2.colors.textMuted,
1450
+ cursor: "help"
1451
+ },
1452
+ children: "?"
1453
+ }
1454
+ ),
1455
+ packages.length > 1 && /* @__PURE__ */ jsxs("span", { style: {
1456
+ fontSize: 14,
1457
+ color: theme2.colors.textMuted
1458
+ }, children: [
1459
+ packages.length,
1460
+ " packages"
1461
+ ] })
1462
+ ] }),
1463
+ /* @__PURE__ */ jsx("div", { style: { display: "flex", flexWrap: "wrap", gap: 16, minHeight: 0 }, children: isLoading ? /* @__PURE__ */ jsx("div", { style: {
1464
+ padding: 40,
1465
+ textAlign: "center",
1466
+ color: theme2.colors.textMuted
1467
+ }, children: "Loading quality metrics..." }) : packages.length === 0 ? /* @__PURE__ */ jsx(
1468
+ QualityEmptyState,
1469
+ {
1470
+ theme: theme2,
1471
+ hasWorkflow
1472
+ }
1473
+ ) : packages.map((pkg) => {
1474
+ const tier = calculateQualityTier(pkg.metrics);
1475
+ return /* @__PURE__ */ jsx(
1476
+ QualityHexagonExpandable,
1477
+ {
1478
+ metrics: pkg.metrics,
1479
+ tier,
1480
+ theme: theme2,
1481
+ packageName: pkg.name,
1482
+ packageVersion: pkg.version
1483
+ },
1484
+ pkg.name
1485
+ );
1486
+ }) })
1487
+ ]
1488
+ }
1489
+ )
1490
+ }
1491
+ );
1492
+ };
1493
+ const QualityHexagonPanel = (props) => {
1494
+ return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(QualityHexagonPanelContent, { ...props }) });
1495
+ };
1496
+ const METRIC_OPTIONS = [
1497
+ { key: "types", label: "Types" },
1498
+ { key: "documentation", label: "Docs" },
1499
+ { key: "tests", label: "Tests" },
1500
+ { key: "deadCode", label: "Dead Code" },
1501
+ { key: "formatting", label: "Format" },
1502
+ { key: "linting", label: "Linting" }
1503
+ ];
1504
+ function flattenRepositories(repositories) {
1505
+ const items = [];
1506
+ for (const repo of repositories) {
1507
+ for (const pkg of repo.packages) {
1508
+ items.push({
1509
+ key: `${repo.id}:${pkg.name}`,
1510
+ repositoryId: repo.id,
1511
+ repositoryName: repo.name,
1512
+ packageName: pkg.name,
1513
+ version: pkg.version,
1514
+ metrics: pkg.metrics,
1515
+ tier: calculateQualityTier(pkg.metrics)
1516
+ });
1517
+ }
1518
+ }
1519
+ return items;
1520
+ }
1521
+ function calculateOverallTier(items) {
1522
+ if (items.length === 0) return "none";
1523
+ const avgMetrics = items.reduce(
1524
+ (acc, item) => ({
1525
+ tests: acc.tests + item.metrics.tests / items.length,
1526
+ deadCode: acc.deadCode + item.metrics.deadCode / items.length,
1527
+ linting: acc.linting + item.metrics.linting / items.length,
1528
+ formatting: acc.formatting + item.metrics.formatting / items.length,
1529
+ types: acc.types + item.metrics.types / items.length,
1530
+ documentation: acc.documentation + item.metrics.documentation / items.length
1531
+ }),
1532
+ { tests: 0, deadCode: 0, linting: 0, formatting: 0, types: 0, documentation: 0 }
1533
+ );
1534
+ return calculateQualityTier(avgMetrics);
1535
+ }
1536
+ function formatLabel(item, showRepositoryName, isSameAsRepo) {
1537
+ if (!showRepositoryName || isSameAsRepo) {
1538
+ return item.packageName;
1539
+ }
1540
+ return `${item.repositoryName} / ${item.packageName}`;
1541
+ }
1542
+ function getValueColor(value, key, theme2) {
1543
+ const effectiveValue = key === "deadCode" ? 100 - value : value;
1544
+ if (effectiveValue >= 80) return theme2.colors.success;
1545
+ if (effectiveValue >= 60) return theme2.colors.warning;
1546
+ return theme2.colors.error;
1547
+ }
1548
+ function RepositoryQualityGridItem({
1549
+ item,
1550
+ theme: theme2,
1551
+ onClick,
1552
+ onVertexClick,
1553
+ showRepositoryName = true,
1554
+ selectedMetric,
1555
+ className
1556
+ }) {
1557
+ const [hoveredVertex, setHoveredVertex] = React2.useState(null);
1558
+ const isSameAsRepo = item.packageName === item.repositoryName;
1559
+ const label = formatLabel(item, showRepositoryName, isSameAsRepo);
1560
+ const tierColors = {
1561
+ none: theme2.colors.muted,
1562
+ bronze: theme2.colors.warning,
1563
+ silver: theme2.colors.secondary,
1564
+ gold: theme2.colors.accent,
1565
+ platinum: theme2.colors.primary
1566
+ };
1567
+ const displayInfo = React2.useMemo(() => {
1568
+ if (selectedMetric) {
1569
+ const option = METRIC_OPTIONS.find((o) => o.key === selectedMetric);
1570
+ if (option) {
1571
+ const value = item.metrics[selectedMetric];
1572
+ return {
1573
+ label: option.label,
1574
+ value,
1575
+ valueColor: getValueColor(value, selectedMetric, theme2)
1576
+ };
1577
+ }
1578
+ }
1579
+ if (hoveredVertex) {
1580
+ return {
1581
+ label: hoveredVertex.label,
1582
+ value: hoveredVertex.value,
1583
+ valueColor: getValueColor(hoveredVertex.value, hoveredVertex.key, theme2)
1584
+ };
1585
+ }
1586
+ return null;
1587
+ }, [selectedMetric, hoveredVertex, item.metrics, theme2]);
1588
+ return /* @__PURE__ */ jsxs(
1589
+ "div",
1590
+ {
1591
+ className: cn(className),
1592
+ onClick,
1593
+ style: {
1594
+ display: "flex",
1595
+ flexDirection: "column",
1596
+ alignItems: "center",
1597
+ gap: 8,
1598
+ padding: 12,
1599
+ borderRadius: 8,
1600
+ backgroundColor: theme2.colors.surface,
1601
+ border: `1px solid ${theme2.colors.border}`,
1602
+ cursor: onClick ? "pointer" : "default",
1603
+ transition: "all 0.2s ease"
1604
+ },
1605
+ onMouseEnter: (e) => {
1606
+ if (onClick) {
1607
+ e.currentTarget.style.borderColor = tierColors[item.tier];
1608
+ e.currentTarget.style.transform = "translateY(-2px)";
1609
+ }
1610
+ },
1611
+ onMouseLeave: (e) => {
1612
+ e.currentTarget.style.borderColor = theme2.colors.border;
1613
+ e.currentTarget.style.transform = "translateY(0)";
1614
+ setHoveredVertex(null);
1615
+ },
838
1616
  children: [
839
- /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
840
- /* @__PURE__ */ jsx(Hexagon, { size: 24, color: tierColors[overallTier] }),
841
- /* @__PURE__ */ jsx(
842
- "h2",
843
- {
844
- style: {
845
- margin: 0,
846
- fontSize: 20,
847
- fontWeight: 600,
848
- color: theme2.colors.text
849
- },
850
- children: "Code Quality"
851
- }
852
- ),
853
- /* @__PURE__ */ jsx(
854
- "span",
855
- {
856
- title: "Platinum: 90%+ avg | Gold: 75%+ | Silver: 60%+ | Bronze: 40%+",
857
- style: {
858
- display: "inline-flex",
859
- alignItems: "center",
860
- justifyContent: "center",
861
- width: 18,
862
- height: 18,
863
- borderRadius: "50%",
864
- border: `1px solid ${theme2.colors.border}`,
865
- fontSize: 12,
866
- color: theme2.colors.textMuted,
867
- cursor: "help"
868
- },
869
- children: "?"
870
- }
871
- ),
872
- packages.length > 1 && /* @__PURE__ */ jsxs("span", { style: {
873
- fontSize: 14,
874
- color: theme2.colors.textMuted
875
- }, children: [
876
- packages.length,
877
- " packages"
878
- ] })
879
- ] }),
880
- /* @__PURE__ */ jsx("div", { style: { display: "flex", flexDirection: "column", gap: 16 }, children: isLoading ? /* @__PURE__ */ jsx("div", { style: {
881
- padding: 40,
882
- textAlign: "center",
883
- color: theme2.colors.textMuted
884
- }, children: "Loading quality metrics..." }) : packages.length === 0 ? /* @__PURE__ */ jsx("div", { style: {
1617
+ /* @__PURE__ */ jsx(
1618
+ "div",
1619
+ {
1620
+ style: {
1621
+ height: 24,
1622
+ display: "flex",
1623
+ alignItems: "center",
1624
+ justifyContent: "center",
1625
+ gap: 8,
1626
+ width: "100%",
1627
+ minHeight: 24
1628
+ },
1629
+ children: displayInfo ? /* @__PURE__ */ jsxs(Fragment, { children: [
1630
+ /* @__PURE__ */ jsx(
1631
+ "span",
1632
+ {
1633
+ style: {
1634
+ fontSize: 14,
1635
+ fontWeight: 500,
1636
+ color: theme2.colors.text
1637
+ },
1638
+ children: displayInfo.label
1639
+ }
1640
+ ),
1641
+ /* @__PURE__ */ jsxs(
1642
+ "span",
1643
+ {
1644
+ style: {
1645
+ fontSize: 14,
1646
+ fontWeight: 600,
1647
+ color: displayInfo.valueColor
1648
+ },
1649
+ children: [
1650
+ displayInfo.value,
1651
+ "%"
1652
+ ]
1653
+ }
1654
+ )
1655
+ ] }) : /* @__PURE__ */ jsx("span", { style: { fontSize: 12, color: theme2.colors.textMuted }, children: "Hover a corner" })
1656
+ }
1657
+ ),
1658
+ /* @__PURE__ */ jsx("div", { style: { width: 200, height: 200 }, children: /* @__PURE__ */ jsx(
1659
+ QualityHexagon,
1660
+ {
1661
+ metrics: item.metrics,
1662
+ tier: item.tier,
1663
+ theme: theme2,
1664
+ showLabels: false,
1665
+ showValues: false,
1666
+ onVertexHover: setHoveredVertex,
1667
+ onVertexLeave: () => setHoveredVertex(null),
1668
+ onVertexClick: onVertexClick ? (vertex) => onVertexClick(item, vertex) : void 0
1669
+ }
1670
+ ) }),
1671
+ /* @__PURE__ */ jsxs(
1672
+ "div",
1673
+ {
1674
+ style: {
1675
+ display: "flex",
1676
+ flexDirection: "column",
1677
+ alignItems: "center",
1678
+ gap: 2
1679
+ },
1680
+ children: [
1681
+ /* @__PURE__ */ jsx(
1682
+ "span",
1683
+ {
1684
+ style: {
1685
+ fontSize: 12,
1686
+ fontWeight: 500,
1687
+ color: theme2.colors.text,
1688
+ textAlign: "center"
1689
+ },
1690
+ children: label
1691
+ }
1692
+ ),
1693
+ item.version && /* @__PURE__ */ jsxs(
1694
+ "span",
1695
+ {
1696
+ style: {
1697
+ fontSize: 10,
1698
+ color: theme2.colors.textMuted
1699
+ },
1700
+ children: [
1701
+ "v",
1702
+ item.version
1703
+ ]
1704
+ }
1705
+ )
1706
+ ]
1707
+ }
1708
+ )
1709
+ ]
1710
+ }
1711
+ );
1712
+ }
1713
+ function calculateAverageScore(metrics) {
1714
+ const adjusted = { ...metrics };
1715
+ adjusted.deadCode = 100 - adjusted.deadCode;
1716
+ return Object.values(adjusted).reduce((a, b) => a + b, 0) / 6;
1717
+ }
1718
+ function RepositoryQualityGrid({
1719
+ repositories,
1720
+ theme: theme2,
1721
+ onItemClick,
1722
+ onVertexClick,
1723
+ className,
1724
+ showRepositoryName = true,
1725
+ showSummary = true
1726
+ }) {
1727
+ const [selectedMetric, setSelectedMetric] = React2.useState(null);
1728
+ const items = React2.useMemo(() => flattenRepositories(repositories), [repositories]);
1729
+ const overallTier = React2.useMemo(() => calculateOverallTier(items), [items]);
1730
+ const sortedItems = React2.useMemo(() => {
1731
+ return [...items].sort((a, b) => {
1732
+ if (selectedMetric) {
1733
+ const aVal = selectedMetric === "deadCode" ? 100 - a.metrics[selectedMetric] : a.metrics[selectedMetric];
1734
+ const bVal = selectedMetric === "deadCode" ? 100 - b.metrics[selectedMetric] : b.metrics[selectedMetric];
1735
+ return bVal - aVal;
1736
+ } else {
1737
+ return calculateAverageScore(b.metrics) - calculateAverageScore(a.metrics);
1738
+ }
1739
+ });
1740
+ }, [items, selectedMetric]);
1741
+ const tierColors = {
1742
+ none: theme2.colors.muted,
1743
+ bronze: theme2.colors.warning,
1744
+ silver: theme2.colors.secondary,
1745
+ gold: theme2.colors.accent,
1746
+ platinum: theme2.colors.primary
1747
+ };
1748
+ const tierLabels = {
1749
+ none: "No Data",
1750
+ bronze: "Bronze",
1751
+ silver: "Silver",
1752
+ gold: "Gold",
1753
+ platinum: "Platinum"
1754
+ };
1755
+ if (items.length === 0) {
1756
+ return /* @__PURE__ */ jsx(
1757
+ "div",
1758
+ {
1759
+ className: cn(className),
1760
+ style: {
885
1761
  padding: 40,
886
1762
  textAlign: "center",
887
- color: theme2.colors.textMuted
888
- }, children: "No packages found" }) : packages.map((pkg) => {
889
- const tier = calculateQualityTier(pkg.metrics);
890
- return /* @__PURE__ */ jsx(
891
- QualityHexagonDetailed,
892
- {
893
- metrics: pkg.metrics,
894
- tier,
895
- theme: theme2,
896
- packageName: pkg.name,
897
- packageVersion: pkg.version,
898
- onRefresh: () => handleRefreshPackage(pkg.name),
899
- isRefreshing: refreshingPackages.has(pkg.name)
1763
+ color: theme2.colors.textMuted,
1764
+ backgroundColor: theme2.colors.background,
1765
+ borderRadius: 8
1766
+ },
1767
+ children: "No repositories to display"
1768
+ }
1769
+ );
1770
+ }
1771
+ return /* @__PURE__ */ jsxs(
1772
+ "div",
1773
+ {
1774
+ className: cn(className),
1775
+ style: {
1776
+ display: "flex",
1777
+ flexDirection: "column",
1778
+ gap: 16,
1779
+ backgroundColor: theme2.colors.background,
1780
+ fontFamily: theme2.fonts.body
1781
+ },
1782
+ children: [
1783
+ showSummary && /* @__PURE__ */ jsxs(
1784
+ "div",
1785
+ {
1786
+ style: {
1787
+ display: "flex",
1788
+ alignItems: "center",
1789
+ justifyContent: "space-between",
1790
+ flexWrap: "wrap",
1791
+ gap: 12,
1792
+ padding: "12px 16px",
1793
+ backgroundColor: theme2.colors.surface,
1794
+ border: `1px solid ${theme2.colors.border}`
900
1795
  },
901
- pkg.name
902
- );
903
- }) })
1796
+ children: [
1797
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1798
+ /* @__PURE__ */ jsxs(
1799
+ "span",
1800
+ {
1801
+ style: {
1802
+ fontSize: 14,
1803
+ fontWeight: 500,
1804
+ color: theme2.colors.text
1805
+ },
1806
+ children: [
1807
+ items.length,
1808
+ " ",
1809
+ items.length === 1 ? "package" : "packages"
1810
+ ]
1811
+ }
1812
+ ),
1813
+ /* @__PURE__ */ jsx("span", { style: { color: theme2.colors.textMuted }, children: "•" }),
1814
+ /* @__PURE__ */ jsxs(
1815
+ "span",
1816
+ {
1817
+ style: {
1818
+ fontSize: 14,
1819
+ color: theme2.colors.textMuted
1820
+ },
1821
+ children: [
1822
+ repositories.length,
1823
+ " ",
1824
+ repositories.length === 1 ? "repository" : "repositories"
1825
+ ]
1826
+ }
1827
+ )
1828
+ ] }),
1829
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
1830
+ /* @__PURE__ */ jsxs(
1831
+ "select",
1832
+ {
1833
+ value: selectedMetric ?? "",
1834
+ onChange: (e) => setSelectedMetric(e.target.value ? e.target.value : null),
1835
+ style: {
1836
+ padding: "4px 8px",
1837
+ fontSize: 13,
1838
+ backgroundColor: theme2.colors.background,
1839
+ color: theme2.colors.text,
1840
+ border: `1px solid ${theme2.colors.border}`,
1841
+ borderRadius: 4,
1842
+ cursor: "pointer",
1843
+ outline: "none"
1844
+ },
1845
+ children: [
1846
+ /* @__PURE__ */ jsx("option", { value: "", children: "Select metric..." }),
1847
+ METRIC_OPTIONS.map((option) => /* @__PURE__ */ jsx("option", { value: option.key, children: option.label }, option.key))
1848
+ ]
1849
+ }
1850
+ ),
1851
+ /* @__PURE__ */ jsxs(
1852
+ "div",
1853
+ {
1854
+ style: {
1855
+ display: "flex",
1856
+ alignItems: "center",
1857
+ gap: 8,
1858
+ padding: "4px 12px",
1859
+ backgroundColor: theme2.colors.backgroundLight,
1860
+ borderRadius: 16,
1861
+ border: `1px solid ${tierColors[overallTier]}`
1862
+ },
1863
+ children: [
1864
+ /* @__PURE__ */ jsx(
1865
+ "span",
1866
+ {
1867
+ style: {
1868
+ width: 8,
1869
+ height: 8,
1870
+ borderRadius: "50%",
1871
+ backgroundColor: tierColors[overallTier]
1872
+ }
1873
+ }
1874
+ ),
1875
+ /* @__PURE__ */ jsx(
1876
+ "span",
1877
+ {
1878
+ style: {
1879
+ fontSize: 13,
1880
+ fontWeight: 500,
1881
+ color: tierColors[overallTier]
1882
+ },
1883
+ children: tierLabels[overallTier]
1884
+ }
1885
+ )
1886
+ ]
1887
+ }
1888
+ )
1889
+ ] })
1890
+ ]
1891
+ }
1892
+ ),
1893
+ /* @__PURE__ */ jsx(
1894
+ "div",
1895
+ {
1896
+ style: {
1897
+ display: "grid",
1898
+ gridTemplateColumns: "repeat(auto-fill, minmax(220px, 1fr))",
1899
+ gap: 12,
1900
+ padding: 16
1901
+ },
1902
+ children: sortedItems.map((item) => /* @__PURE__ */ jsx(
1903
+ RepositoryQualityGridItem,
1904
+ {
1905
+ item,
1906
+ theme: theme2,
1907
+ onClick: onItemClick ? () => onItemClick(item) : void 0,
1908
+ onVertexClick,
1909
+ showRepositoryName,
1910
+ selectedMetric
1911
+ },
1912
+ item.key
1913
+ ))
1914
+ }
1915
+ )
904
1916
  ]
905
1917
  }
906
1918
  );
1919
+ }
1920
+ const mockRepositories = [
1921
+ {
1922
+ id: "platform",
1923
+ name: "platform",
1924
+ packages: [
1925
+ {
1926
+ name: "@org/core",
1927
+ version: "2.0.0",
1928
+ metrics: { tests: 94, deadCode: 4, linting: 98, formatting: 100, types: 97, documentation: 90 }
1929
+ },
1930
+ {
1931
+ name: "@org/ui-components",
1932
+ version: "2.0.0",
1933
+ metrics: { tests: 85, deadCode: 8, linting: 95, formatting: 98, types: 92, documentation: 80 }
1934
+ },
1935
+ {
1936
+ name: "@org/hooks",
1937
+ version: "2.0.0",
1938
+ metrics: { tests: 88, deadCode: 6, linting: 96, formatting: 100, types: 94, documentation: 85 }
1939
+ }
1940
+ ]
1941
+ },
1942
+ {
1943
+ id: "backend",
1944
+ name: "backend-services",
1945
+ packages: [
1946
+ {
1947
+ name: "backend-services",
1948
+ version: "1.5.0",
1949
+ metrics: { tests: 80, deadCode: 15, linting: 90, formatting: 95, types: 85, documentation: 72 }
1950
+ }
1951
+ ]
1952
+ },
1953
+ {
1954
+ id: "docs",
1955
+ name: "documentation-site",
1956
+ packages: [
1957
+ {
1958
+ name: "documentation-site",
1959
+ version: "1.0.0",
1960
+ metrics: { tests: 45, deadCode: 25, linting: 75, formatting: 85, types: 60, documentation: 95 }
1961
+ }
1962
+ ]
1963
+ }
1964
+ ];
1965
+ const RepositoryQualityGridPanelContent = ({
1966
+ context,
1967
+ events
1968
+ }) => {
1969
+ var _a;
1970
+ const { theme: theme2 } = useTheme();
1971
+ const qualitySlice = context.getSlice("repositoriesQuality");
1972
+ const hasQualitySlice = context.hasSlice("repositoriesQuality");
1973
+ const isLoading = (qualitySlice == null ? void 0 : qualitySlice.loading) ?? false;
1974
+ const repositories = React2__default.useMemo(() => {
1975
+ var _a2;
1976
+ if ((_a2 = qualitySlice == null ? void 0 : qualitySlice.data) == null ? void 0 : _a2.repositories) {
1977
+ return qualitySlice.data.repositories;
1978
+ }
1979
+ if (hasQualitySlice) {
1980
+ return [];
1981
+ }
1982
+ return mockRepositories;
1983
+ }, [(_a = qualitySlice == null ? void 0 : qualitySlice.data) == null ? void 0 : _a.repositories, hasQualitySlice]);
1984
+ const handleItemClick = (item) => {
1985
+ events.emit({
1986
+ type: "principal-ade.repository-quality-grid:item:click",
1987
+ source: "principal-ade.repository-quality-grid-panel",
1988
+ timestamp: Date.now(),
1989
+ payload: {
1990
+ repositoryId: item.repositoryId,
1991
+ repositoryName: item.repositoryName,
1992
+ packageName: item.packageName,
1993
+ tier: item.tier
1994
+ }
1995
+ });
1996
+ };
1997
+ const handleVertexClick = (item, vertex) => {
1998
+ events.emit({
1999
+ type: "principal-ade.repository-quality-grid:vertex:click",
2000
+ source: "principal-ade.repository-quality-grid-panel",
2001
+ timestamp: Date.now(),
2002
+ payload: {
2003
+ repositoryId: item.repositoryId,
2004
+ repositoryName: item.repositoryName,
2005
+ packageName: item.packageName,
2006
+ metric: vertex.key,
2007
+ label: vertex.label,
2008
+ value: vertex.value
2009
+ }
2010
+ });
2011
+ };
2012
+ const handleRefresh = async () => {
2013
+ if (context.hasSlice("repositoriesQuality")) {
2014
+ await context.refresh("workspace", "repositoriesQuality");
2015
+ }
2016
+ };
2017
+ React2__default.useEffect(() => {
2018
+ const unsubscribers = [
2019
+ events.on("principal-ade.repository-quality-grid:refresh", async () => {
2020
+ await handleRefresh();
2021
+ })
2022
+ ];
2023
+ return () => unsubscribers.forEach((unsub) => unsub());
2024
+ }, [events, context]);
2025
+ return /* @__PURE__ */ jsx(
2026
+ "div",
2027
+ {
2028
+ style: {
2029
+ fontFamily: theme2.fonts.body,
2030
+ height: "100%",
2031
+ minHeight: 0,
2032
+ backgroundColor: theme2.colors.background,
2033
+ color: theme2.colors.text,
2034
+ overflowY: "auto",
2035
+ boxSizing: "border-box"
2036
+ },
2037
+ children: isLoading ? /* @__PURE__ */ jsx(
2038
+ "div",
2039
+ {
2040
+ style: {
2041
+ padding: 40,
2042
+ textAlign: "center",
2043
+ color: theme2.colors.textMuted
2044
+ },
2045
+ children: "Loading repository quality metrics..."
2046
+ }
2047
+ ) : repositories.length === 0 ? /* @__PURE__ */ jsx(
2048
+ "div",
2049
+ {
2050
+ style: {
2051
+ padding: 40,
2052
+ textAlign: "center",
2053
+ color: theme2.colors.textMuted
2054
+ },
2055
+ children: "No repositories with quality metrics found."
2056
+ }
2057
+ ) : /* @__PURE__ */ jsx(
2058
+ RepositoryQualityGrid,
2059
+ {
2060
+ repositories,
2061
+ theme: theme2,
2062
+ onItemClick: handleItemClick,
2063
+ onVertexClick: handleVertexClick,
2064
+ showRepositoryName: true,
2065
+ showSummary: true
2066
+ }
2067
+ )
2068
+ }
2069
+ );
907
2070
  };
908
- const QualityHexagonPanel = (props) => {
909
- return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(QualityHexagonPanelContent, { ...props }) });
2071
+ const RepositoryQualityGridPanel = (props) => {
2072
+ return /* @__PURE__ */ jsx(ThemeProvider, { children: /* @__PURE__ */ jsx(RepositoryQualityGridPanelContent, { ...props }) });
910
2073
  };
911
2074
  const panels = [
912
2075
  {
@@ -934,6 +2097,28 @@ const panels = [
934
2097
  onUnmount: async (_context) => {
935
2098
  console.log("Quality Hexagon Panel unmounting");
936
2099
  }
2100
+ },
2101
+ {
2102
+ metadata: {
2103
+ id: "principal-ade.repository-quality-grid-panel",
2104
+ name: "Repository Quality Grid",
2105
+ icon: "⬡",
2106
+ version: "0.1.0",
2107
+ author: "Principal ADE",
2108
+ description: "Display quality metrics for multiple repositories in a flat grid layout. Supports filtering by metric type, sorting, and comparing quality across projects.",
2109
+ slices: ["repositoriesQuality"],
2110
+ tools: []
2111
+ },
2112
+ component: RepositoryQualityGridPanel,
2113
+ onMount: async (context) => {
2114
+ console.log("Repository Quality Grid Panel mounted");
2115
+ if (context.hasSlice("repositoriesQuality") && !context.isSliceLoading("repositoriesQuality")) {
2116
+ await context.refresh("workspace", "repositoriesQuality");
2117
+ }
2118
+ },
2119
+ onUnmount: async (_context) => {
2120
+ console.log("Repository Quality Grid Panel unmounting");
2121
+ }
937
2122
  }
938
2123
  ];
939
2124
  const onPackageLoad = async () => {
@@ -946,7 +2131,11 @@ export {
946
2131
  QualityHexagon,
947
2132
  QualityHexagonCompact,
948
2133
  QualityHexagonDetailed,
2134
+ QualityHexagonExpandable,
949
2135
  QualityHexagonPanel,
2136
+ RepositoryQualityGrid,
2137
+ RepositoryQualityGridItem,
2138
+ RepositoryQualityGridPanel,
950
2139
  calculateQualityTier,
951
2140
  onPackageLoad,
952
2141
  onPackageUnload,