tech-debt-visualizer 0.2.3 → 0.2.5

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,4 +1,4 @@
1
- /* Technical Debt Report — Grafana-style dashboard */
1
+ /* Technical Debt Report — Colorful dashboard, clean fit, scroll in sections */
2
2
  :root {
3
3
  --bg: #0b0c0e;
4
4
  --bg-elevated: #111214;
@@ -9,8 +9,13 @@
9
9
  --text: #e8e9ea;
10
10
  --text-muted: #8b9199;
11
11
  --link: #5794f2;
12
- --radius: 4px;
13
- --shadow: 0 1px 2px rgba(0, 0, 0, 0.2);
12
+ --radius: 6px;
13
+ --shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
14
+ --tier-1: #e53935;
15
+ --tier-2: #f57c00;
16
+ --tier-3: #f9a825;
17
+ --tier-4: #29b6f6;
18
+ --tier-5: #43a047;
14
19
  }
15
20
 
16
21
  [data-theme="light"] {
@@ -24,6 +29,11 @@
24
29
  --text-muted: #6b7077;
25
30
  --link: #3274d9;
26
31
  --shadow: 0 1px 2px rgba(0, 0, 0, 0.06);
32
+ --tier-1: #c62828;
33
+ --tier-2: #ef6c00;
34
+ --tier-3: #f9a825;
35
+ --tier-4: #0288d1;
36
+ --tier-5: #2e7d32;
27
37
  }
28
38
 
29
39
  * { box-sizing: border-box; }
@@ -40,17 +50,21 @@ body {
40
50
  }
41
51
 
42
52
  body.dashboard-page {
43
- padding-bottom: 2rem;
53
+ padding-bottom: 0;
54
+ height: 100vh;
55
+ overflow: hidden;
56
+ display: flex;
57
+ flex-direction: column;
44
58
  }
45
59
 
46
- /* ——— Dashboard header (Grafana-style top bar) ——— */
60
+ /* ——— Dashboard header (Grafana top bar: title left, score right) ——— */
47
61
  .dashboard-header {
48
62
  display: flex;
49
- align-items: flex-start;
63
+ align-items: center;
50
64
  justify-content: space-between;
51
65
  flex-wrap: wrap;
52
- gap: 1rem;
53
- padding: 1rem 1.5rem;
66
+ gap: 0.75rem;
67
+ padding: 0.75rem 1.5rem;
54
68
  background: var(--bg-elevated);
55
69
  border-bottom: 1px solid var(--border);
56
70
  position: sticky;
@@ -60,98 +74,126 @@ body.dashboard-page {
60
74
 
61
75
  .dashboard-header-left {
62
76
  display: flex;
63
- align-items: flex-start;
64
- gap: 1rem;
65
- min-width: 0;
66
- flex: 1;
67
- }
68
-
69
- .dashboard-score-badge {
70
- flex-shrink: 0;
71
- line-height: 0;
72
- }
73
-
74
- .dashboard-score-badge .score-badge-svg {
75
- width: 56px;
76
- height: auto;
77
- display: block;
78
- }
79
-
80
- .dashboard-score-badge .score-badge-num { font-size: 22px; }
81
- .dashboard-score-badge .score-badge-of { font-size: 9px; }
82
-
83
- .dashboard-hero {
77
+ align-items: baseline;
78
+ gap: 0.75rem;
84
79
  min-width: 0;
85
80
  }
86
81
 
87
82
  .dashboard-title {
88
- margin: 0 0 0.25rem;
83
+ margin: 0;
89
84
  font-size: 1.125rem;
90
85
  font-weight: 600;
91
86
  letter-spacing: -0.01em;
92
87
  color: var(--text);
93
- }
94
-
95
- .dashboard-blurb {
96
- margin: 0 0 0.35rem;
97
- font-size: 14px;
98
- line-height: 1.45;
99
- color: var(--text);
100
- max-width: 42em;
88
+ white-space: nowrap;
89
+ overflow: hidden;
90
+ text-overflow: ellipsis;
101
91
  }
102
92
 
103
93
  .dashboard-meta {
104
94
  font-size: 12px;
105
95
  color: var(--text-muted);
96
+ white-space: nowrap;
97
+ overflow: hidden;
98
+ text-overflow: ellipsis;
106
99
  }
107
100
 
108
101
  .dashboard-header-right {
109
102
  display: flex;
110
- flex-direction: column;
111
- align-items: flex-end;
112
- gap: 0.25rem;
103
+ align-items: center;
104
+ gap: 1rem;
113
105
  flex-shrink: 0;
114
106
  }
115
107
 
116
- .dashboard-stats {
117
- font-size: 12px;
118
- color: var(--text-muted);
119
- white-space: nowrap;
108
+ .dashboard-score {
109
+ display: inline-flex;
110
+ align-items: baseline;
111
+ gap: 0.2rem;
112
+ padding: 0.35rem 0.65rem;
113
+ border-radius: var(--radius);
114
+ font-weight: 600;
120
115
  }
121
116
 
117
+ .dashboard-score-value { font-size: 1.25rem; line-height: 1; }
118
+ .dashboard-score-of { font-size: 0.85rem; font-weight: 500; color: var(--text-muted); }
119
+ .dashboard-score-label { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; margin-left: 0.35rem; opacity: 0.95; }
120
+
121
+ .dashboard-score.tier-1 { background: rgba(229, 57, 53, 0.25); color: #ff8a80; font-weight: 700; }
122
+ .dashboard-score.tier-2 { background: rgba(245, 124, 0, 0.25); color: #ffb74d; font-weight: 700; }
123
+ .dashboard-score.tier-3 { background: rgba(249, 168, 37, 0.25); color: #ffd54f; font-weight: 700; }
124
+ .dashboard-score.tier-4 { background: rgba(41, 182, 246, 0.25); color: #82b1ff; font-weight: 700; }
125
+ .dashboard-score.tier-5 { background: rgba(67, 160, 71, 0.25); color: #81c784; font-weight: 700; }
126
+
122
127
  .dashboard-date {
123
- font-size: 11px;
128
+ font-size: 12px;
124
129
  color: var(--text-muted);
125
- opacity: 0.9;
126
130
  }
127
131
 
128
- /* ——— Main content & grid ——— */
132
+ /* ——— Main content: 2×2 grid, fills viewport, scroll inside cells ——— */
129
133
  .dashboard-main {
130
- max-width: 1200px;
134
+ flex: 1;
135
+ min-height: 0;
136
+ max-width: 1600px;
131
137
  margin: 0 auto;
132
- padding: 1.25rem 1.5rem;
138
+ padding: 0.75rem 1rem;
139
+ width: 100%;
140
+ overflow: hidden;
133
141
  }
134
142
 
135
- .dashboard-grid {
143
+ .dashboard-grid-2x2 {
136
144
  display: grid;
137
- gap: 1rem;
138
- margin-bottom: 1rem;
145
+ grid-template-columns: 1fr 1fr;
146
+ grid-template-rows: auto 1fr;
147
+ gap: 0.75rem;
148
+ height: 100%;
149
+ min-height: 0;
150
+ align-items: stretch;
139
151
  }
140
152
 
141
- .dashboard-grid-half {
142
- grid-template-columns: repeat(2, 1fr);
153
+ .dashboard-cell {
154
+ min-height: 0;
155
+ min-width: 0;
156
+ overflow: hidden;
157
+ }
158
+
159
+ .dashboard-cell .panel {
160
+ height: 100%;
161
+ display: flex;
162
+ flex-direction: column;
163
+ min-height: 0;
164
+ }
165
+
166
+ .dashboard-cell .panel-body {
167
+ flex: 1;
168
+ min-height: 0;
169
+ overflow: auto;
170
+ -webkit-overflow-scrolling: touch;
171
+ }
172
+
173
+ .dashboard-cell-score .panel-body-score { flex: 0 1 auto; overflow: visible; min-height: 0; }
174
+
175
+ .dashboard-cell-heatmap .panel-body-heatmap,
176
+ .dashboard-cell-list .panel-body {
177
+ min-height: 0;
143
178
  }
144
179
 
145
180
  @media (max-width: 900px) {
146
- .dashboard-grid-half { grid-template-columns: 1fr; }
181
+ body.dashboard-page { height: auto; overflow: auto; }
182
+ .dashboard-grid-2x2 {
183
+ grid-template-columns: 1fr;
184
+ grid-template-rows: auto auto auto auto;
185
+ height: auto;
186
+ }
187
+ .dashboard-cell-heatmap .panel-body-heatmap { min-height: 240px; }
188
+ .dashboard-cell-list .panel-body { min-height: 240px; }
147
189
  }
148
190
 
149
191
  @media (max-width: 560px) {
150
- .dashboard-header { padding: 0.75rem 1rem; }
151
- .dashboard-main { padding: 1rem; }
192
+ .dashboard-header { padding: 0.5rem 0.75rem; }
193
+ .dashboard-main { padding: 0.5rem 0.75rem; }
152
194
  }
153
195
 
154
- /* ——— Panels (Grafana-style) ——— */
196
+ /* ——— Panels: borders, colored accents ——— */
155
197
  .panel {
156
198
  background: var(--surface);
157
199
  border: 1px solid var(--border);
@@ -161,39 +203,189 @@ body.dashboard-page {
161
203
  }
162
204
 
163
205
  .panel-header {
164
- padding: 0.75rem 1rem;
206
+ padding: 0.6rem 0.85rem;
165
207
  border-bottom: 1px solid var(--border-subtle);
166
- background: rgba(0, 0, 0, 0.03);
208
+ background: rgba(0, 0, 0, 0.04);
209
+ flex-shrink: 0;
167
210
  }
168
211
 
212
+ .panel-header-accent { border-left: 3px solid var(--tier-4); }
213
+ .panel-header-accent-orange { border-left: 3px solid var(--tier-2); }
214
+ .panel-header-accent-blue { border-left: 3px solid var(--link); }
215
+
216
+ .panel-score-tier-1 .panel-header-accent { border-left-color: var(--tier-1); }
217
+ .panel-score-tier-2 .panel-header-accent { border-left-color: var(--tier-2); }
218
+ .panel-score-tier-3 .panel-header-accent { border-left-color: var(--tier-3); }
219
+ .panel-score-tier-4 .panel-header-accent { border-left-color: var(--tier-4); }
220
+ .panel-score-tier-5 .panel-header-accent { border-left-color: var(--tier-5); }
221
+
169
222
  [data-theme="light"] .panel-header {
170
223
  background: rgba(0, 0, 0, 0.02);
171
224
  }
172
225
 
173
226
  .panel-title {
174
227
  margin: 0;
175
- font-size: 13px;
176
- font-weight: 600;
228
+ font-size: 12px;
229
+ font-weight: 700;
177
230
  color: var(--text);
178
- letter-spacing: -0.01em;
231
+ letter-spacing: 0.02em;
232
+ text-transform: uppercase;
179
233
  }
180
234
 
181
235
  .panel-desc {
182
- margin: 0.25rem 0 0;
183
- font-size: 12px;
236
+ margin: 0.2rem 0 0;
237
+ font-size: 11px;
184
238
  color: var(--text-muted);
185
- line-height: 1.4;
239
+ line-height: 1.35;
186
240
  }
187
241
 
188
242
  .panel-body {
189
- padding: 1rem;
243
+ padding: 0.85rem 1rem;
190
244
  }
191
245
 
192
246
  .panel-body-center {
193
247
  text-align: center;
194
248
  }
195
249
 
196
- /* ——— Heatmap panel ——— */
250
+ /* ——— Score panel: shield + big number + tier color ——— */
251
+ .panel-score {
252
+ border-top: 2px solid transparent;
253
+ }
254
+ .panel-score-tier-1 { border-top-color: var(--tier-1); }
255
+ .panel-score-tier-2 { border-top-color: var(--tier-2); }
256
+ .panel-score-tier-3 { border-top-color: var(--tier-3); }
257
+ .panel-score-tier-4 { border-top-color: var(--tier-4); }
258
+ .panel-score-tier-5 { border-top-color: var(--tier-5); }
259
+
260
+ .panel-score .panel-body-score {
261
+ text-align: center;
262
+ padding: 1rem 0.85rem;
263
+ }
264
+
265
+ .score-display {
266
+ display: flex;
267
+ align-items: center;
268
+ justify-content: center;
269
+ gap: 1rem;
270
+ margin-bottom: 0.6rem;
271
+ flex-wrap: wrap;
272
+ }
273
+
274
+ .score-shield-wrap {
275
+ line-height: 0;
276
+ flex-shrink: 0;
277
+ }
278
+
279
+ .panel-score .score-badge-svg {
280
+ display: block;
281
+ width: 72px;
282
+ height: auto;
283
+ }
284
+
285
+ .panel-score .score-badge-svg .score-badge-num {
286
+ font-size: 28px;
287
+ font-weight: 900;
288
+ letter-spacing: -0.03em;
289
+ fill: #fff;
290
+ }
291
+
292
+ .panel-score .score-badge-svg .score-badge-of {
293
+ font-size: 10px;
294
+ font-weight: 700;
295
+ letter-spacing: 0.1em;
296
+ text-transform: uppercase;
297
+ fill: rgba(255,255,255,0.95);
298
+ }
299
+
300
+ .score-numeric {
301
+ display: flex;
302
+ align-items: baseline;
303
+ gap: 0.2rem;
304
+ }
305
+
306
+ .score-num {
307
+ font-size: 2.25rem;
308
+ font-weight: 800;
309
+ line-height: 1;
310
+ letter-spacing: -0.04em;
311
+ }
312
+
313
+ .panel-score-tier-1 .score-num { color: var(--tier-1); }
314
+ .panel-score-tier-2 .score-num { color: var(--tier-2); }
315
+ .panel-score-tier-3 .score-num { color: var(--tier-3); }
316
+ .panel-score-tier-4 .score-num { color: var(--tier-4); }
317
+ .panel-score-tier-5 .score-num { color: var(--tier-5); }
318
+
319
+ .score-of {
320
+ font-size: 0.95rem;
321
+ font-weight: 600;
322
+ color: var(--text-muted);
323
+ }
324
+
325
+ .panel-score .score-label {
326
+ margin: 0 0 0.2rem;
327
+ font-size: 0.95rem;
328
+ font-weight: 700;
329
+ }
330
+
331
+ .panel-score-tier-1 .score-label { color: var(--tier-1); }
332
+ .panel-score-tier-2 .score-label { color: var(--tier-2); }
333
+ .panel-score-tier-3 .score-label { color: var(--tier-3); }
334
+ .panel-score-tier-4 .score-label { color: var(--tier-4); }
335
+ .panel-score-tier-5 .score-label { color: var(--tier-5); }
336
+
337
+ .panel-score .score-desc {
338
+ margin: 0 0 0.4rem;
339
+ font-size: 12px;
340
+ color: var(--text-muted);
341
+ line-height: 1.4;
342
+ max-width: 260px;
343
+ margin-left: auto;
344
+ margin-right: auto;
345
+ }
346
+
347
+ .panel-score .score-stats {
348
+ margin: 0;
349
+ font-size: 10px;
350
+ color: var(--text-muted);
351
+ text-transform: uppercase;
352
+ letter-spacing: 0.04em;
353
+ font-weight: 600;
354
+ }
355
+
356
+ /* ——— Description of problems panel (top-right) ——— */
357
+ .panel-empty {
358
+ margin: 0;
359
+ font-size: 13px;
360
+ color: var(--text-muted);
361
+ font-style: italic;
362
+ }
363
+
364
+ .priority-inline {
365
+ display: grid;
366
+ grid-template-columns: 1fr 1fr;
367
+ gap: 0.75rem;
368
+ margin-top: 0.75rem;
369
+ padding-top: 0.75rem;
370
+ border-top: 1px solid var(--border-subtle);
371
+ }
372
+
373
+ .priority-inline h4 {
374
+ margin: 0 0 0.35rem;
375
+ font-size: 10px;
376
+ font-weight: 700;
377
+ text-transform: uppercase;
378
+ letter-spacing: 0.05em;
379
+ color: var(--tier-2);
380
+ }
381
+
382
+ .priority-inline .priority-list { margin: 0; }
383
+
384
+ @media (max-width: 600px) {
385
+ .priority-inline { grid-template-columns: 1fr; }
386
+ }
387
+
388
+ /* ——— Heatmap panel (bottom-left) ——— */
197
389
  .panel-heatmap .panel-header-heatmap {
198
390
  display: flex;
199
391
  flex-wrap: wrap;
@@ -218,10 +410,11 @@ body.dashboard-page {
218
410
  border-radius: var(--radius);
219
411
  }
220
412
 
221
- /* LLM panel accent */
413
+ /* Problems panel: blue accent */
222
414
  .panel-llm {
223
415
  border-left: 3px solid var(--link);
224
416
  }
417
+ .panel-llm .panel-header { border-left: none; }
225
418
 
226
419
  /* ——— Banner ——— */
227
420
  .no-llm-banner {
@@ -267,11 +460,11 @@ body.dashboard-page {
267
460
  outline-offset: 1px;
268
461
  }
269
462
 
270
- .treemap-cell[data-severity="critical"] { background: #c00; color: #fff; border-color: #900; }
271
- .treemap-cell[data-severity="high"] { background: #e85d00; color: #fff; border-color: #b84a00; }
272
- .treemap-cell[data-severity="medium"] { background: #b8860b; color: #fff; border-color: #8b6914; }
273
- .treemap-cell[data-severity="low"] { background: #0a6b0a; color: #fff; border-color: #064906; }
274
- .treemap-cell[data-severity="none"] { background: var(--border); color: var(--text-muted); border-color: var(--border); }
463
+ .treemap-cell[data-severity="critical"] { background: var(--tier-1); color: #fff; border-color: rgba(0,0,0,0.2); }
464
+ .treemap-cell[data-severity="high"] { background: var(--tier-2); color: #fff; border-color: rgba(0,0,0,0.15); }
465
+ .treemap-cell[data-severity="medium"] { background: var(--tier-3); color: #1a1a1a; border-color: rgba(0,0,0,0.1); }
466
+ .treemap-cell[data-severity="low"] { background: var(--tier-5); color: #fff; border-color: rgba(0,0,0,0.1); }
467
+ .treemap-cell[data-severity="none"] { background: var(--border); color: var(--text-muted); border-color: var(--border-subtle); }
275
468
 
276
469
  .legend {
277
470
  display: flex;
@@ -283,12 +476,12 @@ body.dashboard-page {
283
476
  color: var(--text-muted);
284
477
  }
285
478
 
286
- .legend span { display: inline-flex; align-items: center; gap: 0.35rem; }
287
- .legend .swatch { width: 10px; height: 10px; border-radius: 2px; }
288
- .legend .swatch-crit { background: #c00; }
289
- .legend .swatch-high { background: #e85d00; }
290
- .legend .swatch-med { background: #b8860b; }
291
- .legend .swatch-low { background: #0a6b0a; }
479
+ .legend span { display: inline-flex; align-items: center; gap: 0.4rem; font-size: 11px; font-weight: 600; }
480
+ .legend .swatch { width: 12px; height: 12px; border-radius: 3px; }
481
+ .legend .swatch-crit { background: var(--tier-1); }
482
+ .legend .swatch-high { background: var(--tier-2); }
483
+ .legend .swatch-med { background: var(--tier-3); }
484
+ .legend .swatch-low { background: var(--tier-5); }
292
485
  .legend .swatch-none { background: var(--border); }
293
486
 
294
487
  /* ——— Priority lists ——— */
@@ -354,12 +547,12 @@ body.dashboard-page {
354
547
  border-radius: 3px;
355
548
  }
356
549
 
357
- .badge-critical { background: #c00; color: #fff; }
358
- .badge-high { background: #e85d00; color: #fff; }
359
- .badge-medium { background: #b8860b; color: #fff; }
360
- .badge-low { background: #0a6b0a; color: #fff; }
550
+ .badge-critical { background: var(--tier-1); color: #fff; font-weight: 700; }
551
+ .badge-high { background: var(--tier-2); color: #fff; font-weight: 700; }
552
+ .badge-medium { background: var(--tier-3); color: #1a1a1a; font-weight: 700; }
553
+ .badge-low { background: var(--tier-5); color: #fff; font-weight: 700; }
361
554
  .badge-none { background: var(--border); color: var(--text-muted); }
362
- .badge-llm { background: var(--link); color: #fff; }
555
+ .badge-llm { background: var(--link); color: #fff; font-weight: 700; }
363
556
 
364
557
  /* ——— Detail modal ——— */
365
558
  #detail {
@@ -14,25 +14,25 @@ export async function generateHtmlReport(run, options) {
14
14
  const html = buildHtml(run, title, darkMode, css, script);
15
15
  await writeFile(outputPath, html, "utf-8");
16
16
  }
17
- /** Inline SVG badge: shield shape with tier number and "of 5". */
17
+ /** Inline SVG badge: shield shape with tier number and "of 5" — clean, bold, tier-colored. */
18
18
  function buildScoreBadgeSvg(tier, fillColor) {
19
- const lighter = adjustHexBrightness(fillColor, 1.3);
19
+ const lighter = adjustHexBrightness(fillColor, 1.25);
20
20
  const shadowId = "badge-shadow-" + tier;
21
21
  const gradientId = "badge-shine-" + tier;
22
22
  return `<svg class="score-badge-svg" viewBox="0 0 140 168" xmlns="http://www.w3.org/2000/svg" role="img">
23
23
  <defs>
24
24
  <linearGradient id="${gradientId}" x1="0%" y1="0%" x2="0%" y2="100%">
25
- <stop offset="0%" style="stop-color:${lighter};stop-opacity:0.5" />
25
+ <stop offset="0%" style="stop-color:${lighter};stop-opacity:0.6" />
26
26
  <stop offset="100%" style="stop-color:${fillColor};stop-opacity:1" />
27
27
  </linearGradient>
28
- <filter id="${shadowId}" x="-30%" y="-20%" width="160%" height="150%">
29
- <feDropShadow dx="0" dy="6" stdDeviation="8" flood-opacity="0.35"/>
28
+ <filter id="${shadowId}" x="-40%" y="-30%" width="180%" height="180%">
29
+ <feDropShadow dx="0" dy="4" stdDeviation="6" flood-color="${fillColor}" flood-opacity="0.4"/>
30
30
  </filter>
31
31
  </defs>
32
32
  <path fill="url(#${gradientId})" filter="url(#${shadowId})" d="M70 12 C108 12 128 38 128 72 C128 106 70 156 70 156 C70 156 12 106 12 72 C12 38 32 12 70 12 Z"/>
33
- <path fill="none" stroke="rgba(255,255,255,0.4)" stroke-width="2.5" stroke-linejoin="round" d="M70 12 C108 12 128 38 128 72 C128 106 70 156 70 156 C70 156 12 106 12 72 C12 38 32 12 70 12 Z"/>
34
- <text x="70" y="78" text-anchor="middle" class="score-badge-num" fill="white">${tier}</text>
35
- <text x="70" y="100" text-anchor="middle" class="score-badge-of" fill="white">of 5</text>
33
+ <path fill="none" stroke="rgba(255,255,255,0.5)" stroke-width="2" stroke-linejoin="round" d="M70 12 C108 12 128 38 128 72 C128 106 70 156 70 156 C70 156 12 106 12 72 C12 38 32 12 70 12 Z"/>
34
+ <text x="70" y="80" text-anchor="middle" class="score-badge-num" fill="white">${tier}</text>
35
+ <text x="70" y="102" text-anchor="middle" class="score-badge-of" fill="rgba(255,255,255,0.95)">of 5</text>
36
36
  </svg>`;
37
37
  }
38
38
  function adjustHexBrightness(hex, factor) {
@@ -78,21 +78,6 @@ function buildHtml(run, title, darkMode, css, script) {
78
78
  };
79
79
  const tierColor = tierColors[cleanliness.tier] ?? "#666";
80
80
  const scoreBadgeSvg = buildScoreBadgeSvg(cleanliness.tier, tierColor);
81
- const llmPanelHtml = run.llmOverallAssessment || run.llmOverallRaw
82
- ? `
83
- <div class="panel panel-llm">
84
- <div class="panel-header">
85
- <h2 class="panel-title">LLM overall assessment</h2>
86
- </div>
87
- <div class="panel-body">
88
- <div class="llm-output">${run.llmOverallAssessment
89
- ? renderLlmOutputToHtml(run.llmOverallAssessment)
90
- : '<div class="llm-prose">' +
91
- escapeHtml(stripTrailingSeverityAndScore(run.llmOverallRaw ?? "")).replace(/\n/g, "<br>") +
92
- "</div>"}</div>
93
- </div>
94
- </div>`
95
- : "";
96
81
  const statsLine = `${run.fileMetrics.length} files · ${run.debtItems.length} items · ${highCriticalCount} high/crit · ${hotspotCount} hotspots`;
97
82
  return `<!DOCTYPE html>
98
83
  <html lang="en" data-theme="${theme}">
@@ -112,67 +97,97 @@ function buildHtml(run, title, darkMode, css, script) {
112
97
  ${!hasLlm ? `<div class="no-llm-banner"><p class="no-llm-cta">Analysis run without LLM — for full results, run with LLM</p></div>` : ""}
113
98
  <header class="dashboard-header">
114
99
  <div class="dashboard-header-left">
115
- <div class="dashboard-score-badge tier-${cleanliness.tier}" aria-label="Score ${cleanliness.tier} of 5">${scoreBadgeSvg}</div>
116
- <div class="dashboard-hero">
117
- <h1 class="dashboard-title">${escapeHtml(title)}</h1>
118
- <p class="dashboard-blurb">${escapeHtml(cleanliness.description)}</p>
119
- <span class="dashboard-meta">${escapeHtml(run.repoPath)}</span>
120
- </div>
100
+ <h1 class="dashboard-title">${escapeHtml(title)}</h1>
101
+ <span class="dashboard-meta">${escapeHtml(run.repoPath)}</span>
121
102
  </div>
122
103
  <div class="dashboard-header-right">
123
- <span class="dashboard-stats">${statsLine}</span>
104
+ <div class="dashboard-score tier-${cleanliness.tier}" aria-label="Score ${cleanliness.tier} of 5">
105
+ <span class="dashboard-score-value">${cleanliness.tier}</span>
106
+ <span class="dashboard-score-of">/ 5</span>
107
+ <span class="dashboard-score-label">${escapeHtml(cleanliness.label)}</span>
108
+ </div>
124
109
  <span class="dashboard-date">${run.completedAt ?? run.startedAt}</span>
125
110
  </div>
126
111
  </header>
127
112
 
128
113
  <main class="dashboard-main">
129
- ${llmPanelHtml}
130
-
131
- <div class="panel panel-heatmap">
132
- <div class="panel-header panel-header-heatmap">
133
- <h2 class="panel-title">Files by debt</h2>
134
- <p class="panel-desc">Size = complexity + churn. Color = severity (static or LLM). Click for details.</p>
135
- <div class="legend legend-inline">
136
- <span><span class="swatch swatch-crit"></span> Critical</span>
137
- <span><span class="swatch swatch-high"></span> High</span>
138
- <span><span class="swatch swatch-med"></span> Medium</span>
139
- <span><span class="swatch swatch-low"></span> Low</span>
140
- <span><span class="swatch swatch-none"></span> None</span>
114
+ <div class="dashboard-grid-2x2">
115
+ <div class="dashboard-cell dashboard-cell-score">
116
+ <div class="panel panel-score panel-score-tier-${cleanliness.tier}">
117
+ <div class="panel-header panel-header-accent">
118
+ <h2 class="panel-title">Technical Debt Cleanliness Score</h2>
119
+ </div>
120
+ <div class="panel-body panel-body-score">
121
+ <div class="score-display">
122
+ <div class="score-shield-wrap" aria-hidden="true">${scoreBadgeSvg}</div>
123
+ <div class="score-numeric">
124
+ <span class="score-num">${cleanliness.tier}</span>
125
+ <span class="score-of">of 5</span>
126
+ </div>
127
+ </div>
128
+ <p class="score-label">${escapeHtml(cleanliness.label)}</p>
129
+ <p class="score-desc">${escapeHtml(cleanliness.description)}</p>
130
+ <p class="score-stats">${statsLine}</p>
131
+ </div>
141
132
  </div>
142
133
  </div>
143
- <div class="panel-body panel-body-heatmap">
144
- <div id="treemap"></div>
145
- </div>
146
- </div>
147
134
 
148
- <div class="dashboard-grid dashboard-grid-half">
149
- <div class="panel">
150
- <div class="panel-header">
151
- <h2 class="panel-title">High impact, easier</h2>
152
- <p class="panel-desc">High severity in smaller files.</p>
153
- </div>
154
- <div class="panel-body">
155
- <ul id="q1" class="priority-list"></ul>
135
+ <div class="dashboard-cell dashboard-cell-problems">
136
+ <div class="panel panel-llm">
137
+ <div class="panel-header">
138
+ <h2 class="panel-title">Description of problems</h2>
139
+ </div>
140
+ <div class="panel-body">
141
+ ${run.llmOverallAssessment || run.llmOverallRaw
142
+ ? `<div class="llm-output">${run.llmOverallAssessment
143
+ ? renderLlmOutputToHtml(run.llmOverallAssessment)
144
+ : '<div class="llm-prose">' +
145
+ escapeHtml(stripTrailingSeverityAndScore(run.llmOverallRaw ?? "")).replace(/\n/g, "<br>") +
146
+ "</div>"}</div>`
147
+ : '<p class="panel-empty">Run with LLM for an overall assessment of problems.</p>'}
148
+ <div class="priority-inline">
149
+ <div class="priority-inline-col">
150
+ <h4>High impact, easier</h4>
151
+ <ul id="q1" class="priority-list"></ul>
152
+ </div>
153
+ <div class="priority-inline-col">
154
+ <h4>High impact, harder</h4>
155
+ <ul id="q2" class="priority-list"></ul>
156
+ </div>
157
+ </div>
158
+ </div>
156
159
  </div>
157
160
  </div>
158
- <div class="panel">
159
- <div class="panel-header">
160
- <h2 class="panel-title">High impact, harder</h2>
161
- <p class="panel-desc">Critical or hotspot files.</p>
162
- </div>
163
- <div class="panel-body">
164
- <ul id="q2" class="priority-list"></ul>
161
+
162
+ <div class="dashboard-cell dashboard-cell-heatmap">
163
+ <div class="panel panel-heatmap panel-header-accent-orange">
164
+ <div class="panel-header panel-header-heatmap">
165
+ <h2 class="panel-title">Files by debt</h2>
166
+ <p class="panel-desc">Size = complexity + churn. Color = severity. Click for details.</p>
167
+ <div class="legend legend-inline">
168
+ <span><span class="swatch swatch-crit"></span> Critical</span>
169
+ <span><span class="swatch swatch-high"></span> High</span>
170
+ <span><span class="swatch swatch-med"></span> Medium</span>
171
+ <span><span class="swatch swatch-low"></span> Low</span>
172
+ <span><span class="swatch swatch-none"></span> None</span>
173
+ </div>
174
+ </div>
175
+ <div class="panel-body panel-body-heatmap">
176
+ <div id="treemap"></div>
177
+ </div>
165
178
  </div>
166
179
  </div>
167
- </div>
168
180
 
169
- <div class="panel">
170
- <div class="panel-header">
171
- <h2 class="panel-title">Files with debt (static or LLM)</h2>
172
- <p class="panel-desc">Every file rated above none by static analysis or LLM. Click a row for full ratings and explanations.</p>
173
- </div>
174
- <div class="panel-body">
175
- <ul class="debt-list" id="debtList"></ul>
181
+ <div class="dashboard-cell dashboard-cell-list">
182
+ <div class="panel panel-header-accent-blue">
183
+ <div class="panel-header">
184
+ <h2 class="panel-title">Ratings & files</h2>
185
+ <p class="panel-desc">Files rated above none by static or LLM. Click for full details.</p>
186
+ </div>
187
+ <div class="panel-body">
188
+ <ul class="debt-list" id="debtList"></ul>
189
+ </div>
190
+ </div>
176
191
  </div>
177
192
  </div>
178
193
  </main>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tech-debt-visualizer",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Language-agnostic CLI that analyzes repos and generates interactive technical debt visualizations with AI-powered insights",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",