tech-debt-visualizer 0.2.3 → 0.2.4

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.
@@ -43,14 +43,14 @@ body.dashboard-page {
43
43
  padding-bottom: 2rem;
44
44
  }
45
45
 
46
- /* ——— Dashboard header (Grafana-style top bar) ——— */
46
+ /* ——— Dashboard header (Grafana top bar: title left, score right) ——— */
47
47
  .dashboard-header {
48
48
  display: flex;
49
- align-items: flex-start;
49
+ align-items: center;
50
50
  justify-content: space-between;
51
51
  flex-wrap: wrap;
52
- gap: 1rem;
53
- padding: 1rem 1.5rem;
52
+ gap: 0.75rem;
53
+ padding: 0.75rem 1.5rem;
54
54
  background: var(--bg-elevated);
55
55
  border-bottom: 1px solid var(--border);
56
56
  position: sticky;
@@ -60,95 +60,111 @@ body.dashboard-page {
60
60
 
61
61
  .dashboard-header-left {
62
62
  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 {
63
+ align-items: baseline;
64
+ gap: 0.75rem;
84
65
  min-width: 0;
85
66
  }
86
67
 
87
68
  .dashboard-title {
88
- margin: 0 0 0.25rem;
69
+ margin: 0;
89
70
  font-size: 1.125rem;
90
71
  font-weight: 600;
91
72
  letter-spacing: -0.01em;
92
73
  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;
74
+ white-space: nowrap;
75
+ overflow: hidden;
76
+ text-overflow: ellipsis;
101
77
  }
102
78
 
103
79
  .dashboard-meta {
104
80
  font-size: 12px;
105
81
  color: var(--text-muted);
82
+ white-space: nowrap;
83
+ overflow: hidden;
84
+ text-overflow: ellipsis;
106
85
  }
107
86
 
108
87
  .dashboard-header-right {
109
88
  display: flex;
110
- flex-direction: column;
111
- align-items: flex-end;
112
- gap: 0.25rem;
89
+ align-items: center;
90
+ gap: 1rem;
113
91
  flex-shrink: 0;
114
92
  }
115
93
 
116
- .dashboard-stats {
117
- font-size: 12px;
118
- color: var(--text-muted);
119
- white-space: nowrap;
94
+ .dashboard-score {
95
+ display: inline-flex;
96
+ align-items: baseline;
97
+ gap: 0.2rem;
98
+ padding: 0.35rem 0.65rem;
99
+ border-radius: var(--radius);
100
+ font-weight: 600;
120
101
  }
121
102
 
103
+ .dashboard-score-value { font-size: 1.25rem; line-height: 1; }
104
+ .dashboard-score-of { font-size: 0.85rem; font-weight: 500; color: var(--text-muted); }
105
+ .dashboard-score-label { font-size: 11px; font-weight: 500; text-transform: uppercase; letter-spacing: 0.04em; margin-left: 0.35rem; opacity: 0.95; }
106
+
107
+ .dashboard-score.tier-1 { background: rgba(204, 0, 0, 0.2); color: #f66; }
108
+ .dashboard-score.tier-2 { background: rgba(232, 93, 0, 0.2); color: #f90; }
109
+ .dashboard-score.tier-3 { background: rgba(184, 134, 11, 0.2); color: #db9; }
110
+ .dashboard-score.tier-4 { background: rgba(0, 102, 153, 0.2); color: #6cf; }
111
+ .dashboard-score.tier-5 { background: rgba(10, 107, 10, 0.2); color: #6c6; }
112
+
122
113
  .dashboard-date {
123
- font-size: 11px;
114
+ font-size: 12px;
124
115
  color: var(--text-muted);
125
- opacity: 0.9;
126
116
  }
127
117
 
128
- /* ——— Main content & grid ——— */
118
+ /* ——— Main content: 2×2 dashboard grid ——— */
129
119
  .dashboard-main {
130
- max-width: 1200px;
120
+ max-width: 1400px;
131
121
  margin: 0 auto;
132
- padding: 1.25rem 1.5rem;
122
+ padding: 1rem 1.5rem;
123
+ min-height: calc(100vh - 52px);
133
124
  }
134
125
 
135
- .dashboard-grid {
126
+ .dashboard-grid-2x2 {
136
127
  display: grid;
128
+ grid-template-columns: 1fr 1fr;
129
+ grid-template-rows: auto minmax(320px, 1fr);
137
130
  gap: 1rem;
138
- margin-bottom: 1rem;
131
+ align-items: start;
132
+ }
133
+
134
+ .dashboard-cell {
135
+ min-height: 0;
136
+ min-width: 0;
137
+ }
138
+
139
+ .dashboard-cell .panel {
140
+ height: 100%;
141
+ display: flex;
142
+ flex-direction: column;
139
143
  }
140
144
 
141
- .dashboard-grid-half {
142
- grid-template-columns: repeat(2, 1fr);
145
+ .dashboard-cell .panel-body {
146
+ flex: 1;
147
+ min-height: 0;
148
+ overflow: auto;
149
+ }
150
+
151
+ .dashboard-cell-score .panel-body-score { flex: 1 1 auto; overflow: visible; }
152
+
153
+ .dashboard-cell-heatmap .panel-body-heatmap,
154
+ .dashboard-cell-list .panel-body {
155
+ min-height: 280px;
143
156
  }
144
157
 
145
158
  @media (max-width: 900px) {
146
- .dashboard-grid-half { grid-template-columns: 1fr; }
159
+ .dashboard-grid-2x2 {
160
+ grid-template-columns: 1fr;
161
+ grid-template-rows: auto auto auto auto;
162
+ }
147
163
  }
148
164
 
149
165
  @media (max-width: 560px) {
150
- .dashboard-header { padding: 0.75rem 1rem; }
151
- .dashboard-main { padding: 1rem; }
166
+ .dashboard-header { padding: 0.6rem 1rem; }
167
+ .dashboard-main { padding: 0.75rem 1rem; }
152
168
  }
153
169
 
154
170
  /* ——— Panels (Grafana-style) ——— */
@@ -193,7 +209,97 @@ body.dashboard-page {
193
209
  text-align: center;
194
210
  }
195
211
 
196
- /* ——— Heatmap panel ——— */
212
+ /* ——— Score panel (top-left: shield + caption + stats) ——— */
213
+ .panel-score .panel-body-score {
214
+ text-align: center;
215
+ padding: 1.25rem 1rem;
216
+ }
217
+
218
+ .panel-score .score-badge {
219
+ display: inline-block;
220
+ margin-bottom: 0.75rem;
221
+ line-height: 0;
222
+ }
223
+
224
+ .panel-score .score-badge-svg {
225
+ display: block;
226
+ width: 100px;
227
+ height: auto;
228
+ margin: 0 auto;
229
+ }
230
+
231
+ .panel-score .score-badge-svg .score-badge-num {
232
+ font-size: 36px;
233
+ font-weight: 800;
234
+ letter-spacing: -0.02em;
235
+ }
236
+
237
+ .panel-score .score-badge-svg .score-badge-of {
238
+ font-size: 11px;
239
+ font-weight: 700;
240
+ letter-spacing: 0.08em;
241
+ text-transform: uppercase;
242
+ opacity: 0.95;
243
+ }
244
+
245
+ .panel-score .score-label {
246
+ margin: 0 0 0.25rem;
247
+ font-size: 1rem;
248
+ font-weight: 600;
249
+ color: var(--text);
250
+ }
251
+
252
+ .panel-score .score-desc {
253
+ margin: 0 0 0.5rem;
254
+ font-size: 13px;
255
+ color: var(--text-muted);
256
+ line-height: 1.45;
257
+ max-width: 280px;
258
+ margin-left: auto;
259
+ margin-right: auto;
260
+ }
261
+
262
+ .panel-score .score-stats {
263
+ margin: 0;
264
+ font-size: 11px;
265
+ color: var(--text-muted);
266
+ text-transform: uppercase;
267
+ letter-spacing: 0.03em;
268
+ }
269
+
270
+ /* ——— Description of problems panel (top-right) ——— */
271
+ .panel-empty {
272
+ margin: 0;
273
+ font-size: 13px;
274
+ color: var(--text-muted);
275
+ font-style: italic;
276
+ }
277
+
278
+ .priority-inline {
279
+ display: grid;
280
+ grid-template-columns: 1fr 1fr;
281
+ gap: 1rem;
282
+ margin-top: 1rem;
283
+ padding-top: 1rem;
284
+ border-top: 1px solid var(--border-subtle);
285
+ }
286
+
287
+ .priority-inline h4 {
288
+ margin: 0 0 0.5rem;
289
+ font-size: 11px;
290
+ font-weight: 600;
291
+ text-transform: uppercase;
292
+ letter-spacing: 0.04em;
293
+ color: var(--text-muted);
294
+ }
295
+
296
+ .priority-inline .priority-list { margin: 0; }
297
+
298
+ @media (max-width: 600px) {
299
+ .priority-inline { grid-template-columns: 1fr; }
300
+ }
301
+
302
+ /* ——— Heatmap panel (bottom-left) ——— */
197
303
  .panel-heatmap .panel-header-heatmap {
198
304
  display: flex;
199
305
  flex-wrap: wrap;
@@ -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,91 @@ 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 tier-${cleanliness.tier}">
117
+ <div class="panel-header">
118
+ <h2 class="panel-title">Technical Debt Cleanliness Score</h2>
119
+ </div>
120
+ <div class="panel-body panel-body-score">
121
+ <div class="score-badge" aria-hidden="true">${scoreBadgeSvg}</div>
122
+ <p class="score-label">${escapeHtml(cleanliness.label)}</p>
123
+ <p class="score-desc">${escapeHtml(cleanliness.description)}</p>
124
+ <p class="score-stats">${statsLine}</p>
125
+ </div>
141
126
  </div>
142
127
  </div>
143
- <div class="panel-body panel-body-heatmap">
144
- <div id="treemap"></div>
145
- </div>
146
- </div>
147
128
 
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>
129
+ <div class="dashboard-cell dashboard-cell-problems">
130
+ <div class="panel panel-llm">
131
+ <div class="panel-header">
132
+ <h2 class="panel-title">Description of problems</h2>
133
+ </div>
134
+ <div class="panel-body">
135
+ ${run.llmOverallAssessment || run.llmOverallRaw
136
+ ? `<div class="llm-output">${run.llmOverallAssessment
137
+ ? renderLlmOutputToHtml(run.llmOverallAssessment)
138
+ : '<div class="llm-prose">' +
139
+ escapeHtml(stripTrailingSeverityAndScore(run.llmOverallRaw ?? "")).replace(/\n/g, "<br>") +
140
+ "</div>"}</div>`
141
+ : '<p class="panel-empty">Run with LLM for an overall assessment of problems.</p>'}
142
+ <div class="priority-inline">
143
+ <div class="priority-inline-col">
144
+ <h4>High impact, easier</h4>
145
+ <ul id="q1" class="priority-list"></ul>
146
+ </div>
147
+ <div class="priority-inline-col">
148
+ <h4>High impact, harder</h4>
149
+ <ul id="q2" class="priority-list"></ul>
150
+ </div>
151
+ </div>
152
+ </div>
156
153
  </div>
157
154
  </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>
155
+
156
+ <div class="dashboard-cell dashboard-cell-heatmap">
157
+ <div class="panel panel-heatmap">
158
+ <div class="panel-header panel-header-heatmap">
159
+ <h2 class="panel-title">Files by debt</h2>
160
+ <p class="panel-desc">Size = complexity + churn. Color = severity. Click for details.</p>
161
+ <div class="legend legend-inline">
162
+ <span><span class="swatch swatch-crit"></span> Critical</span>
163
+ <span><span class="swatch swatch-high"></span> High</span>
164
+ <span><span class="swatch swatch-med"></span> Medium</span>
165
+ <span><span class="swatch swatch-low"></span> Low</span>
166
+ <span><span class="swatch swatch-none"></span> None</span>
167
+ </div>
168
+ </div>
169
+ <div class="panel-body panel-body-heatmap">
170
+ <div id="treemap"></div>
171
+ </div>
165
172
  </div>
166
173
  </div>
167
- </div>
168
174
 
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>
175
+ <div class="dashboard-cell dashboard-cell-list">
176
+ <div class="panel">
177
+ <div class="panel-header">
178
+ <h2 class="panel-title">Ratings & files</h2>
179
+ <p class="panel-desc">Files rated above none by static or LLM. Click for full details.</p>
180
+ </div>
181
+ <div class="panel-body">
182
+ <ul class="debt-list" id="debtList"></ul>
183
+ </div>
184
+ </div>
176
185
  </div>
177
186
  </div>
178
187
  </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.4",
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",