diff.added.forEach(id => {
const module = SNAPSHOTS_DATA.frames[index].modules.find(m => m.id === id);
if (module) {
html += `
${module.name}
`;
}
});
}
if (diff.removed.length > 0) {
html += 'π΄ Removed Modules ';
diff.removed.forEach(id => {
html += `${id.replace('mod:', '')}
`;
});
}
if (diff.changed.length > 0) {
html += 'π΅ Changed Modules ';
diff.changed.forEach(change => {
const tierInfo = change.tier_change ? `Tier: ${change.tier_change} ` : '';
const roleInfo = change.role_change ? `Role: ${change.role_change} ` : '';
const scoreInfo = Math.abs(change.score_delta) > 0.01 ?
`Score: ${change.score_delta > 0 ? '+' : ''}${change.score_delta.toFixed(3)} ` : '';
html += `
${change.id.replace('mod:', '')}${roleInfo}${tierInfo}${scoreInfo}
`;
});
}
if (diff.added.length === 0 && diff.removed.length === 0 && diff.changed.length === 0) {
html += 'No changes detected
';
}
document.getElementById('details-content').innerHTML = html;
}
// Render trends chart
function renderTrends() {
const option = {
title: {
text: 'Historical Trends (Click to view snapshot)',
left: 'center',
top: 10,
textStyle: { fontSize: 14, color: '#64748B' }
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
let result = params[0].name + ' ';
params.forEach(p => {
result += p.marker + p.seriesName + ': ' + p.value + ' ';
});
result += 'Click to view this snapshot ';
return result;
}
},
legend: { data: ['Files', 'Modules'], bottom: 10 },
grid: { left: 50, right: 50, top: 50, bottom: 50 },
xAxis: {
type: 'category',
data: TRENDS.dates.map((d, i) => {
// Format date display
return d.substring(0, 13).replace('T', ' ');
})
},
yAxis: { type: 'value' },
series: [
{
name: 'Files',
type: 'line',
data: TRENDS.file_counts,
smooth: true
},
{
name: 'Modules',
type: 'line',
data: TRENDS.module_counts,
smooth: true
}
]
};
trendsChart.setOption(option);
// Add click event
trendsChart.off('click');
trendsChart.on('click', function(params) {
if (params.componentType === 'series') {
const snapshotIndex = params.dataIndex;
if (SNAPSHOTS_DATA.frames[snapshotIndex]) {
renderSnapshot(snapshotIndex);
renderDiffSidebar(snapshotIndex);
}
}
});
}
// Show module details in sidebar
function showModuleDetails(moduleData) {
const moduleDeps = INDEX_DATA.module_dependencies || {};
const myDeps = moduleDeps[moduleData.id] || [];
// Get files for this module (handle both static and snapshot modes)
let moduleFiles = [];
if (moduleData.files && Array.isArray(moduleData.files)) {
// Static mode: files are embedded in node data
moduleFiles = moduleData.files;
} else {
// Snapshot mode: need to fetch from INDEX_DATA
const allFiles = INDEX_DATA.files || {};
const fileDeps = INDEX_DATA.file_dependencies || {};
for (const [filePath, fileInfo] of Object.entries(allFiles)) {
if (fileInfo.module === moduleData.id) {
moduleFiles.push({
path: filePath,
name: fileInfo.name || filePath.split('/').pop(),
desc: fileInfo.desc || '',
signatures: fileInfo.signatures || [],
dependencies: fileDeps[filePath] || []
});
}
}
}
// Breadcrumb navigation
let html = `
π Home
βΊ
${moduleData.name}
${moduleData.name}
${moduleData.desc || ''}
Role
${moduleData.role || moduleData.category}
Core Score
${moduleData.core_score.toFixed(3)}
Files
${moduleFiles.length}
`;
// Show module dependencies
if (myDeps.length > 0) {
html += `
Dependencies (${myDeps.length})
`;
myDeps.forEach(dep => {
const targetName = dep.target.replace('mod:', '');
html += `
β ${targetName}
(${dep.weight} imports)
`;
});
html += '
';
}
// Show files in module
html += `
Files (${moduleFiles.length})
`;
moduleFiles.forEach(file => {
const depCount = file.dependencies ? file.dependencies.length : 0;
const sigCount = file.signatures ? file.signatures.length : 0;
html += `
π ${file.name}
${file.desc || 'No description'}
${sigCount} symbols Β· ${depCount} dependencies
`;
});
html += '
';
document.getElementById('details-content').innerHTML = html;
// Add hover effect and click event listeners to file items
document.querySelectorAll('.file-item').forEach((item, index) => {
item.addEventListener('mouseenter', function() {
this.style.background = '#e0f2fe';
this.style.borderColor = '#0ea5e9';
});
item.addEventListener('mouseleave', function() {
this.style.background = '#f9fafb';
this.style.borderColor = '#e5e7eb';
});
item.addEventListener('click', function() {
const filePath = this.getAttribute('data-file-path');
const fileData = moduleFiles.find(f => f.path === filePath);
if (fileData) {
showFileDetails(fileData, moduleData.name);
}
});
});
}
// Show file details with code signatures
function showFileDetails(fileData, moduleName, options) {
var opts = options || {};
// Store current module in view stack for back navigation (skip when navigating back)
if (!opts.skipPush) {
viewStack.push({ type: 'module', name: moduleName });
}
// Breadcrumb navigation
let html = `
π Home
βΊ
${moduleName}
βΊ
${fileData.name}
π ${fileData.name}
${fileData.path}
π Focus Graph
${fileData.desc || 'No description'}
`;
// Show file dependencies
if (fileData.dependencies && fileData.dependencies.length > 0) {
html += `
Dependencies (${fileData.dependencies.length})
`;
fileData.dependencies.forEach(dep => {
const depName = dep.split('/').pop();
html += `
`;
});
html += '
';
}
// Show code signatures
if (fileData.signatures && fileData.signatures.length > 0) {
// Pre-build reverse index for incoming dependency lookup
const funcDepsMap = INDEX_DATA.function_dependencies || {};
const incomingTargets = new Set();
for (const deps of Object.values(funcDepsMap)) {
for (const d of deps) {
incomingTargets.add(d.target);
}
}
html += `
Symbols (${fileData.signatures.length})
`;
fileData.signatures.forEach((sig, sigIndex) => {
const kindColor = {
'class': '#3b82f6',
'function': '#8b5cf6',
'method': '#ec4899',
'variable': '#10b981'
}[sig.kind] || '#6b7280';
const isFunction = sig.kind === 'function' || sig.kind === 'method';
const funcKey = `${fileData.path}:${sig.name}`;
const hasOutgoing = isFunction && funcDepsMap[funcKey];
const hasIncoming = isFunction && incomingTargets.has(funcKey);
const hasDeps = hasOutgoing || hasIncoming;
const clickableClass = isFunction ? 'symbol-row-clickable' : '';
const clickableAttr = isFunction ? `data-sig-index="${sigIndex}"` : '';
html += `
${sig.kind}
${sig.name}
line ${sig.line}
${hasDeps ? `
πΈοΈ
` : ''}
${sig.desc ? `
${sig.desc}
` : ''}