function renderKpiTiles(d) {
const container = document.getElementById('widget-kpi-tiles-body');
if (!container) return;
const tiles = [];
const hotspots = d.hotspots || [];
const codeHealth = d.code_health || [];
const coupling = d.coupling || [];
const knowledgeIslands = d.knowledge_islands || [];
const summaryByName = {};
for (var i = 0; i < (d.summary || []).length; i++) {
const s = d.summary[i];
summaryByName[s.metric] = s.value;
}
const fileCount = hotspots.length || codeHealth.length || 0;
tiles.push({
label: 'Files analyzed',
defKey: 'files_analyzed',
value: fmtInt(fileCount),
sub: fileCount === 1 ? 'one file' : 'live at HEAD',
});
const commits = summaryByName.commits || summaryByName['number-of-commits'] || 0;
tiles.push({
label: 'Commits',
defKey: 'commits',
value: fmtInt(commits),
sub: 'in the analysed history',
});
const authors = summaryByName['authors'] || summaryByName['number-of-authors'] || 0;
tiles.push({
label: 'Distinct authors',
defKey: 'authors',
value: fmtInt(authors),
sub: 'after mailmap resolution',
});
if (codeHealth.length) {
const healths = codeHealth.map(function (r) { return r.score; })
.filter(function (v) { return typeof v === 'number'; })
.sort(function (a, b) { return a - b; });
if (healths.length) {
const mid = Math.floor(healths.length / 2);
const median = (healths.length % 2) ? healths[mid] : (healths[mid - 1] + healths[mid]) / 2;
const healthBand = bandFor(median, data.options || {});
tiles.push({
label: 'Median code health',
defKey: 'median_code_health',
value: median.toFixed(1),
sub: 'band: ' + healthBand,
});
}
}
if (hotspots.length) {
const cogs = hotspots.map(function (r) { return r.cognitive; })
.filter(function (v) { return typeof v === 'number' && v > 0; })
.sort(function (a, b) { return a - b; });
if (cogs.length) {
const idx = Math.min(cogs.length - 1, Math.floor(cogs.length * 0.95));
tiles.push({
label: 'Cognitive p95',
defKey: 'cognitive_p95',
value: fmtInt(cogs[idx]),
sub: 'top-5% file complexity',
});
}
}
if (knowledgeIslands.length) {
tiles.push({
label: 'Knowledge islands',
defKey: 'knowledge_islands',
value: fmtInt(knowledgeIslands.length),
sub: 'departed-author files',
});
}
if (coupling.length) {
tiles.push({
label: 'Coupled file pairs',
defKey: 'coupling_pairs',
value: fmtInt(coupling.length),
sub: 'Fisher-significant',
});
}
if (typeof d.coupling_density === 'number' && isFinite(d.coupling_density)) {
tiles.push({
label: 'Coupling density',
defKey: 'coupling_density',
value: d.coupling_density.toFixed(4),
sub: 'graph |E|/(|V|·(|V|−1)/2)',
});
}
if (d.mi_rollup && typeof d.mi_rollup === 'object') {
const r = d.mi_rollup;
const known = (r.low || 0) + (r.moderate || 0) + (r.high || 0);
if (known > 0) {
tiles.push({
label: 'MI band breakdown',
defKey: 'mi_band',
value: '🟢 ' + fmtInt(r.high || 0) + ' / 🟡 ' + fmtInt(r.moderate || 0) + ' / 🔴 ' + fmtInt(r.low || 0),
sub: 'top / mid / bottom quartile',
});
}
}
var html = '';
for (var j = 0; j < tiles.length; j++) {
const t = tiles[j];
const tip = t.defKey ? buildTooltipHtml(t.defKey) : '';
html += '<div class="kpi-tile stat">' +
'<div class="kpi-label stat-title">' + escapeHtml(t.label) + tip + '</div>' +
'<div class="kpi-value stat-value">' + escapeHtml(t.value) + '</div>' +
'<div class="kpi-sub stat-desc">' + escapeHtml(t.sub) + '</div>' +
'</div>';
}
container.innerHTML = html;
}
function renderKnowledgeIslands(rows) {
const container = document.getElementById('widget-knowledge-islands-body');
if (!container) return;
if (!rows.length) {
container.innerHTML = '<div class="empty">No knowledge islands. ' +
'Either no contributors have departed past the threshold or the ' +
'analysis was not wired through.</div>';
return;
}
const sorted = rows.slice().sort(function (a, b) {
const oa = (typeof a.ownership_pct === 'number') ? a.ownership_pct : 0;
const ob = (typeof b.ownership_pct === 'number') ? b.ownership_pct : 0;
if (oa !== ob) return ob - oa;
const da = (typeof a.days_since_main_active === 'number') ? a.days_since_main_active : 0;
const db = (typeof b.days_since_main_active === 'number') ? b.days_since_main_active : 0;
return db - da;
});
var html = '<table><thead><tr>' +
'<th scope="col">Path</th>' +
'<th scope="col">Departed author</th>' +
'<th scope="col" class="num">Ownership %</th>' +
'<th scope="col" class="num">Days since active</th>' +
'<th scope="col" class="num">LOC</th>' +
'</tr></thead><tbody>';
for (var i = 0; i < sorted.length; i++) {
const r = sorted[i];
const filePath = r.path || r.entity || '';
html += '<tr data-path="' + escapeHtml(filePath) + '" class="ki-row">' +
'<td class="path">' + escapeHtml(filePath) + '</td>' +
'<td>' + escapeHtml(r.main_author || '') + '</td>' +
'<td class="num">' + fmtNumberFlex(r.ownership_pct, 1) + '</td>' +
'<td class="num">' + fmtInt(r.days_since_main_active) + '</td>' +
'<td class="num">' + fmtInt(r.total_loc) + '</td>' +
'</tr>';
}
html += '</tbody></table>';
container.innerHTML = html;
const trs = container.querySelectorAll('tr.ki-row');
for (var j = 0; j < trs.length; j++) {
trs[j].addEventListener('click', function (evt) {
const path = evt.currentTarget.getAttribute('data-path');
if (window._codeloreShowDetail) {
window._codeloreShowDetail(path);
} else {
showFileDetailDrawer(path, data);
}
});
trs[j].style.cursor = 'pointer';
wireRowKbActivation(trs[j]);
}
}
function renderImprovementsFeed(transitions) {
const container = document.getElementById('widget-improvements-feed-body');
if (!container) return;
if (!transitions || !transitions.length) {
container.innerHTML = '<div class="empty">No band transitions detected across the sampled history.</div>';
return;
}
function bandSeverity(b) {
return b === 'red' ? 3 : b === 'yellow' ? 2 : b === 'green' ? 1 : 0;
}
function transitionSeverity(r) {
return Math.max(bandSeverity(r.from_band), bandSeverity(r.to_band));
}
function topTransitions(direction) {
const bestByPath = {};
const order = [];
for (var i = 0; i < transitions.length; i++) {
const r = transitions[i];
if (r.direction !== direction) continue;
const prev = bestByPath[r.path];
if (!prev) {
bestByPath[r.path] = r;
order.push(r.path);
} else if (transitionSeverity(r) > transitionSeverity(prev)) {
bestByPath[r.path] = r;
}
}
return order
.map(function (p) { return bestByPath[p]; })
.sort(function (a, b) { return transitionSeverity(b) - transitionSeverity(a); })
.slice(0, 8);
}
const improved = topTransitions('improved');
const regressed = topTransitions('regressed');
function makeList(rows, label, icon) {
if (!rows.length) return '';
var html = '<h4 class="feed-section-title">' + icon + ' ' + escapeHtml(label) + '</h4><ul class="feed-list">';
for (var i = 0; i < rows.length; i++) {
const r = rows[i];
const shortPath = r.path.split('/').pop() || r.path;
html += '<li class="feed-row" data-path="' + escapeHtml(r.path) + '" tabindex="0" role="button">' +
'<code title="' + escapeHtml(r.path) + '">' + escapeHtml(shortPath) + '</code>' +
' <span class="feed-band">' + escapeHtml(r.from_band) + ' → ' + escapeHtml(r.to_band) + '</span>' +
' <span class="feed-date">' + escapeHtml(r.date) + '</span>' +
'</li>';
}
html += '</ul>';
return html;
}
container.innerHTML =
makeList(improved, 'Recent improvements', '↑') +
makeList(regressed, 'Regressions', '↓');
const rows = container.querySelectorAll('.feed-row');
for (var ri = 0; ri < rows.length; ri++) {
rows[ri].addEventListener('click', function (evt) {
const p = evt.currentTarget.getAttribute('data-path');
if (window._codeloreShowDetail) window._codeloreShowDetail(p);
});
rows[ri].style.cursor = 'pointer';
wireRowKbActivation(rows[ri]);
}
}
function renderFactorHeader(factors, opts) {
const container = document.getElementById('widget-factor-header-body');
if (!container) return;
if (!factors || !factors.length) {
container.innerHTML = '<div class="empty">No factor data — run with health-trend enabled.</div>';
return;
}
const FACTOR_TILE_TARGETS = {
Code: 'group-code-health',
Architecture: 'group-architecture',
Knowledge: 'group-knowledge',
Delivery: 'group-delivery',
};
const o = opts || {};
const greenMin = typeof o.health_green_min === 'number' ? o.health_green_min : 70;
const yellowMin = typeof o.health_yellow_min === 'number' ? o.health_yellow_min : 40;
function scoreColor(score) {
if (score === null || score === undefined) return token('--cl-health-yellow');
return score >= greenMin ? token('--cl-health-green')
: score >= yellowMin ? token('--cl-health-yellow')
: token('--cl-health-red');
}
function bulletBar(tile) {
const val = tile.headline !== null && tile.headline !== undefined ? tile.headline : 0;
const seriesMean = tile.series && tile.series.length
? tile.series.reduce(function (s, v) { return s + v; }, 0) / tile.series.length
: val;
const color = scoreColor(tile.headline);
return '<div class="factor-bullet-wrap" aria-label="' + fmtNumberFlex(val, 1) + ' / 100">' +
'<div class="factor-bullet-track">' +
'<div class="factor-bullet-fill" style="width:' + Math.min(100, Math.max(0, val)) + '%;background:' + color + ';"></div>' +
'<div class="factor-bullet-mean-tick" style="left:' + Math.min(100, Math.max(0, seriesMean)) + '%;"></div>' +
'</div>' +
'<span class="factor-bullet-label" style="color:' + color + ';">' + fmtNumberFlex(val, 1) + '</span>' +
'</div>';
}
var REWORK_BAND_COLORS = {
green: 'var(--cl-health-green, oklch(70% 0.18 145))',
yellow: 'var(--cl-health-yellow, oklch(80% 0.16 85))',
red: 'var(--cl-health-red, oklch(60% 0.20 25))',
};
function numbersList(tile) {
var reworkColor = REWORK_BAND_COLORS[tile.band] || '';
var html = '<div class="factor-numbers">';
var nums = tile.numbers || [];
for (var ni = 0; ni < nums.length; ni++) {
var pair = nums[ni];
var label = escapeHtml(pair[0] || '');
var value = escapeHtml(pair[1] || '');
var valStyle = (ni === 0 && reworkColor) ? ' style="color:' + reworkColor + ';"' : '';
html += '<div class="factor-number-row">' +
'<span class="factor-number-label">' + label + '</span>' +
'<span class="factor-number-value"' + valStyle + '>' + value + '</span>' +
'</div>';
}
html += '</div>';
return html;
}
var html = '<div class="factor-tiles">';
for (var i = 0; i < factors.length; i++) {
const t = factors[i];
const hasHeadline = t.headline !== null && t.headline !== undefined;
const headlineStr = hasHeadline ? fmtNumberFlex(t.headline, 1) : null;
const hasNumbers = t.numbers && t.numbers.length > 0;
const jumpTarget = FACTOR_TILE_TARGETS[t.name];
const jumpAttrs = jumpTarget
? ' data-target="' + jumpTarget + '" role="link" tabindex="0"'
: '';
html += '<div class="factor-tile" id="factor-tile-' + i + '"' + jumpAttrs + '>' +
'<div class="factor-name">' + escapeHtml(t.name) + '</div>' +
(t.attention ? '<span class="factor-attention-chip">Attention</span>' : '') +
(headlineStr !== null
? '<div class="factor-headline">' + headlineStr + '</div>' + bulletBar(t)
: (hasNumbers ? numbersList(t) : '<div class="factor-headline">—</div>')) +
(t.series && t.series.length ? '<div id="factor-sparkline-' + i + '" class="factor-sparkline"></div>' : '') +
'<div class="factor-detail">' + escapeHtml(t.detail || '') + '</div>' +
'</div>';
}
html += '</div>';
container.innerHTML = html;
const jumpTiles = container.querySelectorAll('.factor-tile[data-target]');
for (let ji = 0; ji < jumpTiles.length; ji++) {
const tileEl = jumpTiles[ji];
const target = tileEl.getAttribute('data-target');
tileEl.addEventListener('click', function () { scrollToDashSection(target); });
tileEl.addEventListener('keydown', function (evt) {
if (evt.key === 'Enter') {
evt.preventDefault();
scrollToDashSection(target);
}
});
}
for (var j = 0; j < factors.length; j++) {
(function (idx, tile) {
var el = document.getElementById('factor-sparkline-' + idx);
if (!el || !tile.series || !tile.series.length || typeof window.echarts === 'undefined') return;
try {
var chart = mountEcharts(el);
chart.setOption({
animation: false,
grid: { top: 2, bottom: 2, left: 2, right: 2 },
xAxis: { type: 'category', show: false, data: tile.series.map(function (_, k) { return k; }) },
yAxis: { type: 'value', show: false, min: 0, max: 100 },
series: [{
type: 'line',
data: tile.series,
smooth: true,
symbol: 'none',
lineStyle: { width: 1.5, color: scoreColor(tile.headline) },
}],
});
} catch (e) {
console.error('codelore: factor sparkline render failed for', tile.name, e);
}
})(j, factors[j]);
}
}