tech-debt-visualizer 0.2.2 → 0.2.3

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.
@@ -46,11 +46,11 @@ body.dashboard-page {
46
46
  /* ——— Dashboard header (Grafana-style top bar) ——— */
47
47
  .dashboard-header {
48
48
  display: flex;
49
- align-items: center;
49
+ align-items: flex-start;
50
50
  justify-content: space-between;
51
51
  flex-wrap: wrap;
52
- gap: 0.75rem;
53
- padding: 0.75rem 1.5rem;
52
+ gap: 1rem;
53
+ padding: 1rem 1.5rem;
54
54
  background: var(--bg-elevated);
55
55
  border-bottom: 1px solid var(--border);
56
56
  position: sticky;
@@ -60,75 +60,69 @@ body.dashboard-page {
60
60
 
61
61
  .dashboard-header-left {
62
62
  display: flex;
63
- align-items: baseline;
64
- gap: 0.75rem;
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 {
65
84
  min-width: 0;
66
85
  }
67
86
 
68
87
  .dashboard-title {
69
- margin: 0;
88
+ margin: 0 0 0.25rem;
70
89
  font-size: 1.125rem;
71
90
  font-weight: 600;
72
91
  letter-spacing: -0.01em;
73
92
  color: var(--text);
74
- white-space: nowrap;
75
- overflow: hidden;
76
- text-overflow: ellipsis;
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;
77
101
  }
78
102
 
79
103
  .dashboard-meta {
80
104
  font-size: 12px;
81
105
  color: var(--text-muted);
82
- white-space: nowrap;
83
- overflow: hidden;
84
- text-overflow: ellipsis;
85
106
  }
86
107
 
87
108
  .dashboard-header-right {
88
109
  display: flex;
89
- align-items: center;
90
- gap: 1rem;
110
+ flex-direction: column;
111
+ align-items: flex-end;
112
+ gap: 0.25rem;
91
113
  flex-shrink: 0;
92
114
  }
93
115
 
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;
101
- }
102
-
103
- .dashboard-score-value {
104
- font-size: 1.25rem;
105
- line-height: 1;
106
- }
107
-
108
- .dashboard-score-of {
109
- font-size: 0.85rem;
110
- font-weight: 500;
116
+ .dashboard-stats {
117
+ font-size: 12px;
111
118
  color: var(--text-muted);
119
+ white-space: nowrap;
112
120
  }
113
121
 
114
- .dashboard-score-label {
115
- font-size: 11px;
116
- font-weight: 500;
117
- text-transform: uppercase;
118
- letter-spacing: 0.04em;
119
- margin-left: 0.35rem;
120
- opacity: 0.95;
121
- }
122
-
123
- .dashboard-score.tier-1 { background: rgba(204, 0, 0, 0.2); color: #f66; }
124
- .dashboard-score.tier-2 { background: rgba(232, 93, 0, 0.2); color: #f90; }
125
- .dashboard-score.tier-3 { background: rgba(184, 134, 11, 0.2); color: #db9; }
126
- .dashboard-score.tier-4 { background: rgba(0, 102, 153, 0.2); color: #6cf; }
127
- .dashboard-score.tier-5 { background: rgba(10, 107, 10, 0.2); color: #6c6; }
128
-
129
122
  .dashboard-date {
130
- font-size: 12px;
123
+ font-size: 11px;
131
124
  color: var(--text-muted);
125
+ opacity: 0.9;
132
126
  }
133
127
 
134
128
  /* ——— Main content & grid ——— */
@@ -144,23 +138,17 @@ body.dashboard-page {
144
138
  margin-bottom: 1rem;
145
139
  }
146
140
 
147
- .dashboard-grid-stats {
148
- grid-template-columns: repeat(4, 1fr);
149
- }
150
-
151
141
  .dashboard-grid-half {
152
142
  grid-template-columns: repeat(2, 1fr);
153
143
  }
154
144
 
155
145
  @media (max-width: 900px) {
156
- .dashboard-grid-stats { grid-template-columns: repeat(2, 1fr); }
157
146
  .dashboard-grid-half { grid-template-columns: 1fr; }
158
147
  }
159
148
 
160
149
  @media (max-width: 560px) {
161
- .dashboard-header { padding: 0.6rem 1rem; }
150
+ .dashboard-header { padding: 0.75rem 1rem; }
162
151
  .dashboard-main { padding: 1rem; }
163
- .dashboard-grid-stats { grid-template-columns: 1fr; }
164
152
  }
165
153
 
166
154
  /* ——— Panels (Grafana-style) ——— */
@@ -205,69 +193,29 @@ body.dashboard-page {
205
193
  text-align: center;
206
194
  }
207
195
 
208
- /* Stat panels (KPI row) */
209
- .stat-panel .panel-body {
210
- padding: 1rem 1.25rem;
211
- }
212
-
213
- .stat-value {
214
- font-size: 1.75rem;
215
- font-weight: 700;
216
- line-height: 1.2;
217
- color: var(--text);
218
- letter-spacing: -0.02em;
219
- }
220
-
221
- .stat-label {
222
- font-size: 11px;
223
- font-weight: 500;
224
- text-transform: uppercase;
225
- letter-spacing: 0.04em;
226
- color: var(--text-muted);
227
- margin-top: 0.25rem;
228
- }
229
-
230
- .stat-panel-warn .stat-value { color: #f44; }
231
-
232
- /* Score panel (cleanliness gauge) */
233
- .panel-score .panel-body-center {
234
- padding: 1.25rem 1rem;
235
- }
236
-
237
- .panel-score .score-badge {
238
- display: inline-block;
239
- margin-bottom: 0.75rem;
240
- line-height: 0;
196
+ /* ——— Heatmap panel ——— */
197
+ .panel-heatmap .panel-header-heatmap {
198
+ display: flex;
199
+ flex-wrap: wrap;
200
+ align-items: baseline;
201
+ gap: 0.75rem 1.25rem;
241
202
  }
242
203
 
243
- .panel-score .score-badge-svg {
244
- display: block;
245
- width: 120px;
246
- height: auto;
247
- }
204
+ .panel-heatmap .panel-title { margin-right: 0.5rem; }
205
+ .panel-heatmap .panel-desc { margin: 0.15rem 0 0; flex-basis: 100%; }
248
206
 
249
- .panel-score .score-badge-svg .score-badge-num {
250
- font-size: 42px;
251
- font-weight: 800;
252
- letter-spacing: -0.02em;
207
+ .legend-inline {
208
+ margin: 0;
209
+ margin-left: auto;
253
210
  }
254
211
 
255
- .panel-score .score-badge-svg .score-badge-of {
256
- font-size: 12px;
257
- font-weight: 700;
258
- letter-spacing: 0.08em;
259
- text-transform: uppercase;
260
- opacity: 0.95;
212
+ .panel-body-heatmap {
213
+ padding: 1rem;
214
+ padding-top: 0.5rem;
261
215
  }
262
216
 
263
- .panel-score .score-desc {
264
- font-size: 13px;
265
- color: var(--text-muted);
266
- margin: 0;
267
- max-width: 320px;
268
- margin-left: auto;
269
- margin-right: auto;
270
- line-height: 1.45;
217
+ .panel-body-heatmap #treemap {
218
+ border-radius: var(--radius);
271
219
  }
272
220
 
273
221
  /* LLM panel accent */
@@ -379,8 +327,18 @@ body.dashboard-page {
379
327
 
380
328
  .debt-list .title { font-weight: 600; margin-bottom: 0.2rem; }
381
329
  .debt-list .meta { font-size: 12px; color: var(--text-muted); display: block; margin-top: 0.25rem; font-family: ui-monospace, monospace; }
330
+ .debt-list-explanation {
331
+ font-size: 12px;
332
+ color: var(--text-muted);
333
+ margin: 0.35rem 0 0;
334
+ line-height: 1.4;
335
+ display: -webkit-box;
336
+ -webkit-line-clamp: 2;
337
+ -webkit-box-orient: vertical;
338
+ overflow: hidden;
339
+ }
382
340
  .debt-list .insight { font-size: 13px; color: var(--text-muted); margin-top: 0.25rem; line-height: 1.4; white-space: pre-wrap; word-break: break-word; }
383
- .debt-list-ratings { display: flex; align-items: center; gap: 1rem; margin: 0.25rem 0; flex-wrap: wrap; }
341
+ .debt-list-ratings { display: flex; align-items: center; gap: 1rem; margin: 0.25rem 0 0; flex-wrap: wrap; }
384
342
  .debt-list-rating { display: inline-flex; align-items: center; gap: 0.35rem; }
385
343
  .debt-list-rating-label { font-size: 10px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.03em; color: var(--text-muted); }
386
344
  .debt-list-llm-none { color: var(--text-muted); font-size: 12px; }
@@ -166,44 +166,81 @@ document.getElementById("q2").innerHTML = highImpact
166
166
  .map(function (d) { return '<li style="font-size:0.8rem">' + escapeHtml(d.file) + "</li>"; })
167
167
  .join("");
168
168
 
169
- // Debt list: one row per file; static = worst severity across that file's issues, LLM = file's rating
170
- var sev = { critical: 4, high: 3, medium: 2, low: 1 };
169
+ // Debt list: every file rated above none by static OR LLM; show both ratings and short explanation
170
+ var sev = { critical: 4, high: 3, medium: 2, low: 1, none: 0 };
171
171
  var list = document.getElementById("debtList");
172
- var filesWithDebt = Array.from(debtByFile.keys()).sort(function (fa, fb) {
173
- var itemsA = debtByFile.get(fa);
174
- var itemsB = debtByFile.get(fb);
175
- var worstA = itemsA.length ? Math.max.apply(null, itemsA.map(function (d) { return sev[d.severity] || 0; })) : 0;
176
- var worstB = itemsB.length ? Math.max.apply(null, itemsB.map(function (d) { return sev[d.severity] || 0; })) : 0;
172
+
173
+ function severityNum(s) { return sev[s] || 0; }
174
+ function fileWorstStatic(items) {
175
+ if (!items || !items.length) return 0;
176
+ return Math.max.apply(null, items.map(function (d) { return severityNum(d.severity); }));
177
+ }
178
+ function fileWorstLlm(metric) {
179
+ if (!metric || !metric.llmSeverity || metric.llmSeverity === "none") return 0;
180
+ return severityNum(metric.llmSeverity);
181
+ }
182
+
183
+ var filesFromStatic = new Set(debtByFile.keys());
184
+ var filesFromLlm = new Set();
185
+ DATA.fileMetrics.forEach(function (m) {
186
+ if (m.llmSeverity && m.llmSeverity !== "none") filesFromLlm.add(m.file);
187
+ });
188
+ var fileSet = new Set([].concat(Array.from(filesFromStatic), Array.from(filesFromLlm)));
189
+
190
+ var filesWithDebt = Array.from(fileSet).sort(function (fa, fb) {
191
+ var itemsA = debtByFile.get(fa) || [];
192
+ var itemsB = debtByFile.get(fb) || [];
193
+ var metricA = DATA.fileMetrics.find(function (m) { return m.file === fa; });
194
+ var metricB = DATA.fileMetrics.find(function (m) { return m.file === fb; });
195
+ var worstA = Math.max(fileWorstStatic(itemsA), fileWorstLlm(metricA));
196
+ var worstB = Math.max(fileWorstStatic(itemsB), fileWorstLlm(metricB));
177
197
  if (worstB !== worstA) return worstB - worstA;
178
198
  return fa.localeCompare(fb);
179
199
  });
200
+
201
+ function firstLine(text) {
202
+ if (!text || !String(text).trim()) return "";
203
+ return String(text).trim().split(/\n/)[0].trim().slice(0, 120);
204
+ }
205
+
180
206
  filesWithDebt.forEach(function (file) {
181
- var items = debtByFile.get(file);
207
+ var items = debtByFile.get(file) || [];
208
+ var fileM = DATA.fileMetrics.find(function (m) { return m.file === file; });
182
209
  var worstSeverityVal = items.length ? items.reduce(function (best, d) {
183
- return (sev[d.severity] || 0) > (sev[best.severity] || 0) ? d : best;
210
+ return severityNum(d.severity) > severityNum(best.severity) ? d : best;
184
211
  }, items[0]) : null;
185
- var staticSeverity = worstSeverityVal ? worstSeverityVal.severity : "low";
186
- var fileM = DATA.fileMetrics.find(function (m) { return m.file === file; });
187
- var fileLlmSeverity = fileM && fileM.llmSeverity ? fileM.llmSeverity : null;
188
- var staticBadge = '<span class="badge badge-' + staticSeverity + '" title="Static analysis (worst of ' + items.length + ' issue(s))">' + staticSeverity + "</span>";
189
- var llmRating = fileLlmSeverity
190
- ? '<span class="badge badge-' + fileLlmSeverity + '" title="LLM rating for this file">' + fileLlmSeverity + "</span>"
212
+ var staticSeverity = worstSeverityVal ? worstSeverityVal.severity : null;
213
+ var fileLlmSeverity = fileM && fileM.llmSeverity && fileM.llmSeverity !== "none" ? fileM.llmSeverity : null;
214
+
215
+ var staticBadge = staticSeverity
216
+ ? '<span class="badge badge-' + staticSeverity + '" title="Static (worst of ' + items.length + ')">' + staticSeverity + "</span>"
191
217
  : '<span class="debt-list-llm-none">—</span>';
192
- var titleText = items.length === 1
193
- ? items[0].title
194
- : worstSeverityVal.title + " (+" + (items.length - 1) + " more)";
218
+ var llmBadge = fileLlmSeverity
219
+ ? '<span class="badge badge-' + fileLlmSeverity + '" title="LLM">' + fileLlmSeverity + "</span>"
220
+ : '<span class="debt-list-llm-none">—</span>';
221
+
222
+ var explanation = "";
223
+ if (items.length && worstSeverityVal) {
224
+ explanation = escapeHtml(firstLine(worstSeverityVal.title || worstSeverityVal.description || ""));
225
+ if (items.length > 1) explanation += " (+" + (items.length - 1) + " more)";
226
+ }
227
+ if (fileM && (fileM.llmAssessment || fileM.llmRawAssessment)) {
228
+ var llmBlurb = firstLine(stripTrailingSeverityAndScore(fileM.llmRawAssessment || fileM.llmAssessment || ""));
229
+ if (llmBlurb) explanation = explanation ? explanation + " · " + escapeHtml(llmBlurb) : escapeHtml(llmBlurb);
230
+ }
231
+ if (!explanation) explanation = "Rated by static or LLM.";
232
+
195
233
  var ratingsRow =
196
234
  '<div class="debt-list-ratings">' +
197
235
  '<span class="debt-list-rating"><span class="debt-list-rating-label">Static</span> ' + staticBadge + "</span>" +
198
- '<span class="debt-list-rating"><span class="debt-list-rating-label">LLM</span> ' + llmRating + "</span>" +
236
+ '<span class="debt-list-rating"><span class="debt-list-rating-label">LLM</span> ' + llmBadge + "</span>" +
199
237
  "</div>";
200
238
  var li = document.createElement("li");
201
239
  li.innerHTML =
202
- '<span class="title">' +
203
- escapeHtml(titleText) +
204
- "</span> " +
240
+ '<span class="title">' + escapeHtml(file.split("/").pop() || file) + "</span> " +
205
241
  ratingsRow +
206
- '<span class="meta">' + escapeHtml(file) + "</span>";
242
+ '<span class="meta">' + escapeHtml(file) + "</span>" +
243
+ '<p class="debt-list-explanation">' + explanation + "</p>";
207
244
  li.addEventListener("click", function () { showDetail(file, items); });
208
245
  list.appendChild(li);
209
246
  });
@@ -212,35 +249,44 @@ function showDetail(file, items) {
212
249
  var panel = document.getElementById("detail");
213
250
  var fileMetric = DATA.fileMetrics.find(function (m) { return m.file === file; });
214
251
  var worstItem = items.length ? items.reduce(function (best, d) {
215
- return (sev[d.severity] || 0) > (sev[best.severity] || 0) ? d : best;
252
+ return severityNum(d.severity) > severityNum(best.severity) ? d : best;
216
253
  }, items[0]) : null;
217
- var staticSeverity = worstItem ? worstItem.severity : "low";
254
+ var staticSeverity = worstItem ? worstItem.severity : null;
218
255
 
219
- document.getElementById("detailTitle").textContent = items.length === 1
256
+ var titleText = items.length === 1
220
257
  ? (items[0].title || "Debt item")
221
- : items.length + " static issues";
258
+ : items.length > 1
259
+ ? items.length + " static issues"
260
+ : (fileMetric && fileMetric.llmSeverity ? "LLM assessment" : "File details");
261
+ document.getElementById("detailTitle").textContent = titleText;
222
262
  document.getElementById("detailFile").textContent = file;
223
263
 
224
264
  var explanationEl = document.getElementById("detailExplanation");
225
265
  var parts = [];
226
266
  parts.push(
227
267
  '<div class="detail-severities">' +
228
- '<span class="detail-sev"><strong>Static</strong> <span class="badge badge-' + staticSeverity + '">' + staticSeverity + "</span> (worst of " + items.length + ")</span> " +
268
+ '<span class="detail-sev"><strong>Static</strong> ' +
269
+ (staticSeverity ? '<span class="badge badge-' + staticSeverity + '">' + staticSeverity + "</span> (worst of " + items.length + ")" : "<span class=\"debt-list-llm-none\">\u2014</span>") +
270
+ "</span> " +
229
271
  '<span class="detail-sev"><strong>LLM</strong> ' +
230
- (fileMetric && fileMetric.llmSeverity ? '<span class="badge badge-' + fileMetric.llmSeverity + '">' + fileMetric.llmSeverity + "</span>" : "<span class=\"debt-list-llm-none\">—</span>") +
272
+ (fileMetric && fileMetric.llmSeverity && fileMetric.llmSeverity !== "none" ? '<span class="badge badge-' + fileMetric.llmSeverity + '">' + fileMetric.llmSeverity + "</span>" : "<span class=\"debt-list-llm-none\">\u2014</span>") +
231
273
  "</span></div>"
232
274
  );
233
275
  parts.push('<div class="detail-static-desc"><strong>Static issues</strong><ul class="detail-issues-list">');
234
- items.forEach(function (item) {
235
- parts.push(
236
- '<li><span class="badge badge-' + item.severity + '">' + item.severity + "</span> " +
237
- escapeHtml(item.title || "Issue") +
238
- (item.line ? " <span class=\"detail-line\">line " + item.line + "</span>" : "")
239
- );
240
- if (item.description)
241
- parts.push('<div class="detail-issue-desc">' + escapeHtml(item.description).replace(/\n/g, "<br>") + "</div>");
242
- parts.push("</li>");
243
- });
276
+ if (items.length) {
277
+ items.forEach(function (item) {
278
+ parts.push(
279
+ '<li><span class="badge badge-' + item.severity + '">' + item.severity + "</span> " +
280
+ escapeHtml(item.title || "Issue") +
281
+ (item.line ? " <span class=\"detail-line\">line " + item.line + "</span>" : "")
282
+ );
283
+ if (item.description)
284
+ parts.push('<div class="detail-issue-desc">' + escapeHtml(item.description).replace(/\n/g, "<br>") + "</div>");
285
+ parts.push("</li>");
286
+ });
287
+ } else {
288
+ parts.push("<li class=\"detail-no-llm\">No static issues for this file.</li>");
289
+ }
244
290
  parts.push("</ul></div>");
245
291
  parts.push('<div class="detail-llm-label"><strong>LLM assessment</strong></div>');
246
292
  if (fileMetric && (fileMetric.llmRawAssessment || fileMetric.llmAssessment)) {
@@ -93,6 +93,7 @@ function buildHtml(run, title, darkMode, css, script) {
93
93
  </div>
94
94
  </div>`
95
95
  : "";
96
+ const statsLine = `${run.fileMetrics.length} files · ${run.debtItems.length} items · ${highCriticalCount} high/crit · ${hotspotCount} hotspots`;
96
97
  return `<!DOCTYPE html>
97
98
  <html lang="en" data-theme="${theme}">
98
99
  <head>
@@ -111,74 +112,35 @@ function buildHtml(run, title, darkMode, css, script) {
111
112
  ${!hasLlm ? `<div class="no-llm-banner"><p class="no-llm-cta">Analysis run without LLM — for full results, run with LLM</p></div>` : ""}
112
113
  <header class="dashboard-header">
113
114
  <div class="dashboard-header-left">
114
- <h1 class="dashboard-title">${escapeHtml(title)}</h1>
115
- <span class="dashboard-meta">${escapeHtml(run.repoPath)}</span>
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>
116
121
  </div>
117
122
  <div class="dashboard-header-right">
118
- <div class="dashboard-score tier-${cleanliness.tier}" aria-label="Score ${cleanliness.tier} of 5">
119
- <span class="dashboard-score-value">${cleanliness.tier}</span>
120
- <span class="dashboard-score-of">/ 5</span>
121
- <span class="dashboard-score-label">${escapeHtml(cleanliness.label)}</span>
122
- </div>
123
+ <span class="dashboard-stats">${statsLine}</span>
123
124
  <span class="dashboard-date">${run.completedAt ?? run.startedAt}</span>
124
125
  </div>
125
126
  </header>
126
127
 
127
128
  <main class="dashboard-main">
128
- <div class="dashboard-grid dashboard-grid-stats">
129
- <div class="panel stat-panel">
130
- <div class="panel-body">
131
- <div class="stat-value">${run.fileMetrics.length}</div>
132
- <div class="stat-label">Files analyzed</div>
133
- </div>
134
- </div>
135
- <div class="panel stat-panel">
136
- <div class="panel-body">
137
- <div class="stat-value">${run.debtItems.length}</div>
138
- <div class="stat-label">Debt items</div>
139
- </div>
140
- </div>
141
- <div class="panel stat-panel stat-panel-warn">
142
- <div class="panel-body">
143
- <div class="stat-value">${highCriticalCount}</div>
144
- <div class="stat-label">High / Critical</div>
145
- </div>
146
- </div>
147
- <div class="panel stat-panel">
148
- <div class="panel-body">
149
- <div class="stat-value">${hotspotCount}</div>
150
- <div class="stat-label">Hotspots</div>
151
- </div>
152
- </div>
153
- </div>
154
-
155
- <div class="dashboard-grid">
156
- <div class="panel panel-score tier-${cleanliness.tier}">
157
- <div class="panel-header">
158
- <h2 class="panel-title">Cleanliness score</h2>
159
- </div>
160
- <div class="panel-body panel-body-center">
161
- <div class="score-badge" aria-hidden="true">${scoreBadgeSvg}</div>
162
- <p class="score-desc">${escapeHtml(cleanliness.description)}</p>
163
- </div>
164
- </div>
165
- </div>
166
-
167
129
  ${llmPanelHtml}
168
130
 
169
- <div class="panel">
170
- <div class="panel-header">
131
+ <div class="panel panel-heatmap">
132
+ <div class="panel-header panel-header-heatmap">
171
133
  <h2 class="panel-title">Files by debt</h2>
172
- <p class="panel-desc">Size = complexity + churn. Color = LLM severity. Click for details.</p>
173
- </div>
174
- <div class="panel-body">
175
- <div class="legend">
134
+ <p class="panel-desc">Size = complexity + churn. Color = severity (static or LLM). Click for details.</p>
135
+ <div class="legend legend-inline">
176
136
  <span><span class="swatch swatch-crit"></span> Critical</span>
177
137
  <span><span class="swatch swatch-high"></span> High</span>
178
138
  <span><span class="swatch swatch-med"></span> Medium</span>
179
139
  <span><span class="swatch swatch-low"></span> Low</span>
180
- <span><span class="swatch swatch-none"></span> No debt</span>
140
+ <span><span class="swatch swatch-none"></span> None</span>
181
141
  </div>
142
+ </div>
143
+ <div class="panel-body panel-body-heatmap">
182
144
  <div id="treemap"></div>
183
145
  </div>
184
146
  </div>
@@ -206,8 +168,8 @@ function buildHtml(run, title, darkMode, css, script) {
206
168
 
207
169
  <div class="panel">
208
170
  <div class="panel-header">
209
- <h2 class="panel-title">All debt items</h2>
210
- <p class="panel-desc">Static and LLM ratings. Click a row for details.</p>
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>
211
173
  </div>
212
174
  <div class="panel-body">
213
175
  <ul class="debt-list" id="debtList"></ul>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tech-debt-visualizer",
3
- "version": "0.2.2",
3
+ "version": "0.2.3",
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",