Skip to content

Commit eb1b285

Browse files
ozgesolidkeyclaude
andcommitted
Add minimap density heatmap by log level
- Analyzer collects per-bucket density during the existing scan pass (error/warning/info counts in 200 byte-position buckets) - Minimap renders the density as a colored background layer: red=errors, amber=warnings, blue=info, intensity by frequency - Zero performance cost: one extra increment per analyzed line, stored in compact Uint32Array; bucket count is fixed regardless of file size Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent d03ef0d commit eb1b285

File tree

3 files changed

+64
-3
lines changed

3 files changed

+64
-3
lines changed

src/main/analyzers/columnAwareAnalyzer.ts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,13 @@ export class ColumnAwareAnalyzer implements LogAnalyzer {
6262
error: 0, warning: 0, info: 0, debug: 0, trace: 0
6363
};
6464

65+
// Density buckets — 200 buckets keyed by byte position
66+
// Used by the minimap to draw a heat map by log level
67+
const DENSITY_BUCKETS = 200;
68+
const densityError = new Uint32Array(DENSITY_BUCKETS);
69+
const densityWarning = new Uint32Array(DENSITY_BUCKETS);
70+
const densityInfo = new Uint32Array(DENSITY_BUCKETS);
71+
6572
// Crash tracking
6673
const crashes: CrashEntry[] = [];
6774

@@ -116,7 +123,14 @@ export class ColumnAwareAnalyzer implements LogAnalyzer {
116123
} else {
117124
level = this.detectLevelFromText(line) || undefined;
118125
}
119-
if (level) levelCounts[level]++;
126+
if (level) {
127+
levelCounts[level]++;
128+
// Update density bucket based on byte position
129+
const bucket = Math.min(DENSITY_BUCKETS - 1, Math.floor((bytesRead / fileSize) * DENSITY_BUCKETS));
130+
if (level === 'error') densityError[bucket]++;
131+
else if (level === 'warning') densityWarning[bucket]++;
132+
else if (level === 'info') densityInfo[bucket]++;
133+
}
120134

121135
// Extract message text
122136
let message = '';
@@ -239,7 +253,13 @@ export class ColumnAwareAnalyzer implements LogAnalyzer {
239253
: undefined,
240254
analyzerName: this.name,
241255
analyzedAt: Date.now(),
242-
insights
256+
insights,
257+
density: {
258+
buckets: DENSITY_BUCKETS,
259+
error: Array.from(densityError),
260+
warning: Array.from(densityWarning),
261+
info: Array.from(densityInfo),
262+
}
243263
};
244264

245265
} catch (error) {

src/main/analyzers/types.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,6 +59,13 @@ export interface AnalysisResult {
5959
analyzerName: string;
6060
analyzedAt: number;
6161
insights: AnalysisInsights;
62+
// Density buckets for minimap heat map (indexed by file byte position)
63+
density?: {
64+
buckets: number;
65+
error: number[];
66+
warning: number[];
67+
info: number[];
68+
};
6269
}
6370

6471
// Base interface - all analyzers must implement this

src/renderer/renderer.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3389,14 +3389,48 @@ function renderMinimapMarkers(): void {
33893389
if (!minimapElement) return;
33903390

33913391
// Remove existing markers
3392-
minimapElement.querySelectorAll('.minimap-bookmark, .minimap-search-marker, .minimap-notes-marker, .minimap-sc-marker, .minimap-annotation-marker').forEach(el => el.remove());
3392+
minimapElement.querySelectorAll('.minimap-bookmark, .minimap-search-marker, .minimap-notes-marker, .minimap-sc-marker, .minimap-annotation-marker, .minimap-density-bucket').forEach(el => el.remove());
33933393

33943394
const totalLines = getTotalLines();
33953395
if (totalLines === 0) return;
33963396

33973397
const minimapHeight = minimapElement.clientHeight;
33983398
const fragment = document.createDocumentFragment();
33993399

3400+
// Density heat map (background layer) — render first so markers appear on top
3401+
const density = (state.analysisResult as any)?.density;
3402+
if (density && density.buckets > 0) {
3403+
const buckets = density.buckets;
3404+
const bucketHeight = minimapHeight / buckets;
3405+
// Find max for normalization (to prevent over-saturation)
3406+
let maxVal = 1;
3407+
for (let i = 0; i < buckets; i++) {
3408+
const total = density.error[i] + density.warning[i] + density.info[i];
3409+
if (total > maxVal) maxVal = total;
3410+
}
3411+
for (let i = 0; i < buckets; i++) {
3412+
const errCount = density.error[i] || 0;
3413+
const warnCount = density.warning[i] || 0;
3414+
const infoCount = density.info[i] || 0;
3415+
const total = errCount + warnCount + infoCount;
3416+
if (total === 0) continue;
3417+
// Mix color based on dominant level
3418+
const intensity = Math.min(1, total / maxVal);
3419+
let color: string;
3420+
if (errCount > warnCount && errCount > infoCount) {
3421+
color = `rgba(255, 80, 80, ${intensity * 0.6})`;
3422+
} else if (warnCount > infoCount) {
3423+
color = `rgba(255, 180, 40, ${intensity * 0.5})`;
3424+
} else {
3425+
color = `rgba(100, 150, 255, ${intensity * 0.35})`;
3426+
}
3427+
const marker = document.createElement('div');
3428+
marker.className = 'minimap-density-bucket';
3429+
marker.style.cssText = `position:absolute;left:0;right:0;top:${i * bucketHeight}px;height:${Math.ceil(bucketHeight) + 1}px;background-color:${color};pointer-events:none;`;
3430+
fragment.appendChild(marker);
3431+
}
3432+
}
3433+
34003434
// Add saved notes range markers (drawn first, behind other markers)
34013435
for (const range of state.savedRanges) {
34023436
const marker = document.createElement('div');

0 commit comments

Comments
 (0)