function renderShareBars(rows, opts) {
var mount = document.getElementById('widget-share-bars-body');
if (!mount) return;
if (!rows || !rows.length) {
mount.innerHTML =
'<p class="text-base-content/50 text-sm">No effort-exposure data available.</p>';
return;
}
var BAND_ORDER = ['red', 'yellow', 'green'];
var BAND_LABEL = { red: 'Red', yellow: 'Yellow', green: 'Green' };
var byBand = {};
for (var i = 0; i < rows.length; i++) {
byBand[rows[i].band] = rows[i];
}
function buildBar(axisLabel, valueFn, ariaDesc) {
var total = 0;
for (var b = 0; b < BAND_ORDER.length; b++) {
var r = byBand[BAND_ORDER[b]];
total += r ? (valueFn(r) || 0) : 0;
}
var segments = '';
for (var bi = 0; bi < BAND_ORDER.length; bi++) {
var band = BAND_ORDER[bi];
var row = byBand[band];
var raw = row ? (valueFn(row) || 0) : 0;
var pct = total > 0 ? (raw / total * 100) : 0;
if (pct < 0.5) continue;
var pctStr = pct.toFixed(1);
segments +=
'<div class="share-bar-segment"' +
' style="width:' + pct.toFixed(2) + '%;background:' + bandColor(band) + ';"' +
' title="' + BAND_LABEL[band] + ': ' + pctStr + '%">' +
(pct >= 8
? '<span class="share-bar-label">' + BAND_LABEL[band] + ' ' + pctStr + '%</span>'
: '') +
'</div>';
}
return (
'<div class="share-bar-row">' +
'<span class="share-bar-axis-label">' + axisLabel + '</span>' +
'<div class="share-bar-track" role="img" aria-label="' + ariaDesc + '">' +
segments +
'</div>' +
'</div>'
);
}
var locBar = buildBar(
'LOC',
function (r) { return r.loc_share_pct; },
'Source lines of code share per health band: ' +
BAND_ORDER.map(function (b) {
var r = byBand[b]; return BAND_LABEL[b] + ' ' + (r ? r.loc_share_pct.toFixed(1) : '0') + '%';
}).join(', ')
);
var churnBar = buildBar(
'Churn',
function (r) { return r.churn_share_pct; },
'Churn share per health band in the trailing window: ' +
BAND_ORDER.map(function (b) {
var r = byBand[b]; return BAND_LABEL[b] + ' ' + (r ? r.churn_share_pct.toFixed(1) : '0') + '%';
}).join(', ')
);
var captionHtml = '';
var redRow = byBand['red'];
if (redRow) {
var redChurn = (redRow.churn_share_pct || 0).toFixed(1);
var ciLo = ((redRow.commit_share_ci_low || 0) * 100).toFixed(1);
var ciHi = ((redRow.commit_share_ci_high || 0) * 100).toFixed(1);
var windowStr = (opts && opts.window_days)
? 'the last ' + opts.window_days + ' days’'
: 'recent';
captionHtml =
'<p class="share-bars-caption"' +
' title="Wilson 95 % CI on commit share: [' + ciLo + ' %, ' + ciHi + ' %]">' +
'<strong>' + redChurn + '%</strong> of ' + windowStr + ' changes landed in red code' +
'</p>';
}
var dotCounts = {};
var dotSum = 0;
var maxBand = null;
var maxShare = -1;
for (var di = 0; di < BAND_ORDER.length; di++) {
var db = BAND_ORDER[di];
var dr = byBand[db];
var share = dr ? (dr.churn_share_pct || 0) : 0;
var rounded = Math.round(share / 5);
dotCounts[db] = rounded;
dotSum += rounded;
if (share > maxShare) { maxShare = share; maxBand = db; }
}
var remainder = 20 - dotSum;
if (remainder !== 0 && maxBand !== null) {
dotCounts[maxBand] = (dotCounts[maxBand] || 0) + remainder;
}
var dots = '';
for (var dbi = 0; dbi < BAND_ORDER.length; dbi++) {
var dotBand = BAND_ORDER[dbi];
var count = Math.max(0, dotCounts[dotBand] || 0);
for (var k = 0; k < count; k++) {
dots +=
'<span class="effort-dot"' +
' style="background:' + bandColor(dotBand) + ';"' +
' title="' + BAND_LABEL[dotBand] + ' band (5% churn per dot)">' +
'</span>';
}
}
var dotAriaLabel =
(dotCounts['red'] || 0) + ' red, ' +
(dotCounts['yellow'] || 0) + ' yellow, ' +
(dotCounts['green'] || 0) + ' green — each dot = 5% of window churn';
var dotStrip =
'<div class="effort-dot-strip-wrap">' +
'<span class="share-bar-axis-label">Effort</span>' +
'<div class="effort-dot-strip" role="img" aria-label="' + dotAriaLabel + '">' +
dots +
'</div>' +
'</div>';
mount.innerHTML =
'<div class="share-bars-container">' +
locBar +
churnBar +
dotStrip +
captionHtml +
'</div>';
}
function renderKnowledgeSurfaces(famRows, teamRows, coordRows) {
var mount = document.getElementById('widget-knowledge-surfaces-body');
if (!mount) return;
var html = '';
var fam = famRows && famRows.length ? famRows[0] : null;
if (fam) {
var famPct = typeof fam.familiarity_pct === 'number' ? fam.familiarity_pct : 0;
var islPct = typeof fam.islands_pct === 'number' ? fam.islands_pct : 0;
var activeAuthors = typeof fam.active_authors === 'number' ? fam.active_authors : '—';
var famColor = bandColor(famPct >= 70 ? 'green' : famPct >= 40 ? 'yellow' : 'red');
var islColor = bandColor(islPct <= 20 ? 'green' : islPct <= 40 ? 'yellow' : 'red');
html +=
'<div class="knowledge-familiarity-bars">' +
'<div class="share-bar-row" title="Mean team familiarity with active files (' + fmtNumberFlex(famPct, 1) + '%)">' +
'<span class="share-bar-axis-label">Familiarity</span>' +
'<div class="share-bar-track">' +
'<div class="share-bar-fill" style="width:' + Math.min(100, famPct) + '%;background:' + famColor + ';"></div>' +
'</div>' +
'<span class="share-bar-value">' + fmtNumberFlex(famPct, 1) + '%</span>' +
'</div>' +
'<div class="share-bar-row" title="Knowledge islands: files with no active owner (' + fmtNumberFlex(islPct, 1) + '% of files)">' +
'<span class="share-bar-axis-label">Islands</span>' +
'<div class="share-bar-track">' +
'<div class="share-bar-fill" style="width:' + Math.min(100, islPct) + '%;background:' + islColor + ';"></div>' +
'</div>' +
'<span class="share-bar-value">' + fmtNumberFlex(islPct, 1) + '%</span>' +
'</div>' +
'<p class="knowledge-caption">Active authors: <strong>' + activeAuthors + '</strong>' +
(fam.verdict ? ' — <em>' + escapeHtml(fam.verdict) + '</em>' : '') +
'</p>' +
'</div>';
}
if (teamRows && teamRows.length) {
var bucketColors = {
onboarded: 'var(--color-info, oklch(0.623 0.214 259.532))',
experienced:'var(--color-success, oklch(0.753 0.152 163.216))',
veteran: 'var(--color-primary, oklch(0.491 0.270 282.717))',
};
var BUCKET_ORDER = ['onboarded', 'experienced', 'veteran'];
var bucketCounts = { onboarded: 0, experienced: 0, veteran: 0 };
var realAuthors = 0;
for (var ti = 0; ti < teamRows.length; ti++) {
var tr = teamRows[ti];
if (tr.author === '__summary__') continue;
if (Object.prototype.hasOwnProperty.call(bucketCounts, tr.bucket)) {
bucketCounts[tr.bucket] += 1;
realAuthors += 1;
}
}
if (realAuthors > 0) {
var segments = '';
var legend = '';
for (var bi = 0; bi < BUCKET_ORDER.length; bi++) {
var bucket = BUCKET_ORDER[bi];
var count = bucketCounts[bucket];
if (count === 0) continue;
var share = (count / realAuthors) * 100;
var color = bucketColors[bucket];
segments +=
'<div class="team-bar-segment" style="width:' + fmtNumberFlex(share, 1) + '%;background:' + color + ';"' +
' title="' + escapeHtml(bucket) + ': ' + fmtNumberFlex(share, 1) + '% of authors, ' + count + ' author(s)">' +
'</div>';
legend +=
'<span class="team-bar-key" style="color:' + color + ';">' + escapeHtml(bucket) + '</span> ' +
fmtNumberFlex(share, 1) + '% (' + count + ') ';
}
html +=
'<div class="team-composition-bar">' +
'<div class="team-bar-track" role="img" aria-label="Team tenure distribution">' + segments + '</div>' +
'<p class="knowledge-caption">' + legend.trim() + '</p>' +
'</div>';
}
}
if (coordRows && coordRows.length) {
var tierBadge = function (t) {
var colors = { high: 'badge-error', medium: 'badge-warning', low: 'badge-info', single: 'badge-ghost' };
return '<span class="badge badge-sm ' + (colors[t] || 'badge-ghost') + '">' + escapeHtml(t) + '</span>';
};
var rows = '';
for (var ci = 0; ci < coordRows.length; ci++) {
var cr = coordRows[ci];
var name = (cr.path || '').split('/').pop();
rows +=
'<tr class="hover" style="cursor:pointer;" onclick="window._codeloreShowDetail && window._codeloreShowDetail(' + JSON.stringify(cr.path) + ')">' +
'<td class="coord-path" title="' + escapeHtml(cr.path || '') + '">' + escapeHtml(name) + '</td>' +
'<td>' + tierBadge(cr.tier || 'single') + '</td>' +
'<td>' + fmtNumberFlex(cr.fragmentation, 2) + '</td>' +
'<td>' + fmtNumberFlex(cr.cochange_entropy, 3) + '</td>' +
'</tr>';
}
html +=
'<div class="table-container coordination-table">' +
'<table class="table table-xs">' +
'<thead><tr>' +
'<th>File</th><th>Tier</th><th>Fragmentation</th><th>Entropy</th>' +
'</tr></thead>' +
'<tbody>' + rows + '</tbody>' +
'</table>' +
'</div>';
}
if (!html) {
html = '<p class="muted-hint">No knowledge data — run with source files present to populate.</p>';
}
mount.innerHTML = html;
}