2026-02-07 10:36:45 +00:00
|
|
|
<!DOCTYPE html>
|
|
|
|
|
<html><head>
|
|
|
|
|
<meta charset="utf-8">
|
|
|
|
|
<title>swactor — dependency analysis</title>
|
|
|
|
|
<style>
|
|
|
|
|
* { margin:0; padding:0; box-sizing:border-box; }
|
|
|
|
|
body { background:#1a1a2e; color:#e0e0e0; font-family:system-ui,-apple-system,sans-serif; overflow:hidden; }
|
|
|
|
|
|
|
|
|
|
/* ─── Tab bar ───────────────────────────────────────────────────────────── */
|
|
|
|
|
.tab-bar { display:flex; align-items:center; height:42px; background:#12122a;
|
|
|
|
|
border-bottom:1px solid #2a2a5a; padding:0 16px; gap:8px; }
|
|
|
|
|
.tab-bar .title { font-size:14px; font-weight:700; letter-spacing:0.5px; margin-right:18px;
|
|
|
|
|
color:#8ab4f8; white-space:nowrap; }
|
|
|
|
|
.tab { background:none; border:none; color:#888; font-size:13px; padding:8px 16px;
|
|
|
|
|
cursor:pointer; border-bottom:2px solid transparent; transition:color 0.15s; }
|
|
|
|
|
.tab:hover { color:#ccc; }
|
|
|
|
|
.tab.active { color:#e0e0e0; border-bottom-color:#4fc3f7; }
|
|
|
|
|
|
|
|
|
|
/* ─── Tab content ───────────────────────────────────────────────────────── */
|
|
|
|
|
.tab-content { display:none; }
|
|
|
|
|
.tab-content.active { display:block; }
|
|
|
|
|
|
|
|
|
|
/* ─── DAG tab ───────────────────────────────────────────────────────────── */
|
|
|
|
|
#tab-dag { height:calc(100vh - 42px); overflow:hidden; position:relative; }
|
|
|
|
|
#dag-viewport { width:100%; height:100%; cursor:grab; }
|
|
|
|
|
#dag-viewport:active { cursor:grabbing; }
|
|
|
|
|
#dag-viewport svg { display:block; }
|
|
|
|
|
#dag-controls { position:absolute; top:12px; left:12px; z-index:10;
|
|
|
|
|
background:rgba(30,30,60,0.9); border-radius:8px; padding:10px 14px;
|
|
|
|
|
color:#ccc; font-size:13px; backdrop-filter:blur(8px); }
|
|
|
|
|
#dag-controls button { background:#333; color:#fff; border:1px solid #555;
|
|
|
|
|
border-radius:4px; padding:4px 10px; cursor:pointer; margin:0 3px; }
|
|
|
|
|
#dag-controls button:hover { background:#555; }
|
|
|
|
|
#dag-loading { position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
|
|
|
|
|
color:#ccc; font-size:18px; }
|
|
|
|
|
|
|
|
|
|
/* ─── Spectral tab ──────────────────────────────────────────────────────── */
|
|
|
|
|
#tab-spectral { overflow-y:auto; max-height:calc(100vh - 42px); }
|
|
|
|
|
|
|
|
|
|
.grid { display:grid; grid-template-columns:1fr 1fr; grid-template-rows:auto auto;
|
|
|
|
|
gap:16px; padding:16px 20px 20px; max-width:1600px; margin:0 auto; }
|
|
|
|
|
|
|
|
|
|
.panel { background:#16213e; border-radius:10px; border:1px solid #2a2a5a;
|
|
|
|
|
padding:16px; position:relative; min-height:100px; }
|
|
|
|
|
.panel h2 { font-size:14px; font-weight:600; margin-bottom:10px; color:#8ab4f8;
|
|
|
|
|
display:flex; align-items:center; gap:8px; }
|
|
|
|
|
.panel h2 .icon { font-size:16px; }
|
|
|
|
|
.panel svg { width:100%; display:block; }
|
|
|
|
|
|
|
|
|
|
.tooltip { position:fixed; background:rgba(22,33,62,0.96); border:1px solid #4fc3f7;
|
|
|
|
|
border-radius:6px; padding:8px 12px; font-size:12px; pointer-events:none;
|
|
|
|
|
z-index:100; backdrop-filter:blur(8px); max-width:300px;
|
|
|
|
|
box-shadow:0 4px 20px rgba(0,0,0,0.4); display:none; }
|
|
|
|
|
.tooltip .tt-label { font-weight:600; color:#4fc3f7; }
|
|
|
|
|
.tooltip .tt-val { color:#e0e0e0; }
|
|
|
|
|
|
|
|
|
|
svg text { user-select:none; }
|
|
|
|
|
|
|
|
|
|
/* Metrics panel */
|
|
|
|
|
.metrics-grid { display:grid; grid-template-columns:1fr 1fr; gap:8px 20px; }
|
|
|
|
|
.metric-item { display:flex; justify-content:space-between; font-size:12px;
|
|
|
|
|
padding:4px 8px; border-radius:4px; }
|
|
|
|
|
.metric-item:hover { background:rgba(79,195,247,0.08); }
|
|
|
|
|
.metric-label { opacity:0.7; }
|
|
|
|
|
.metric-value { font-weight:600; font-family:'SF Mono',monospace; }
|
|
|
|
|
.cci-box { grid-column:1/-1; text-align:center; margin-top:10px; padding:14px;
|
|
|
|
|
border-radius:8px; background:rgba(0,0,0,0.25); border:1px solid #333; }
|
|
|
|
|
.cci-score { font-size:32px; font-weight:700; }
|
|
|
|
|
.cci-label { font-size:14px; margin-top:2px; }
|
|
|
|
|
.cci-desc { font-size:11px; opacity:0.6; margin-top:4px; }
|
|
|
|
|
|
|
|
|
|
.sub-header { font-size:11px; font-weight:600; text-transform:uppercase;
|
|
|
|
|
letter-spacing:1px; opacity:0.4; margin:8px 0 4px; grid-column:1/-1; }
|
|
|
|
|
|
|
|
|
|
/* Heatmap */
|
|
|
|
|
.hm-cell { cursor:pointer; transition:opacity 0.15s; }
|
|
|
|
|
.hm-cell:hover { opacity:0.8; stroke:#4fc3f7; stroke-width:2; }
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
/* Cohesion / heatmap bars */
|
2026-02-07 10:36:45 +00:00
|
|
|
.fi-bar { cursor:pointer; transition:opacity 0.15s; }
|
|
|
|
|
.fi-bar:hover { opacity:0.85; }
|
|
|
|
|
</style>
|
|
|
|
|
</head>
|
|
|
|
|
<body>
|
|
|
|
|
|
|
|
|
|
<div class="tab-bar">
|
|
|
|
|
<div class="title">swactor — dependency analysis</div>
|
2026-02-09 19:05:37 +00:00
|
|
|
<button class="tab active" data-tab="spectral">Spectral Analysis</button>
|
|
|
|
|
<button class="tab" data-tab="dag">Dependency DAG</button>
|
2026-02-07 10:36:45 +00:00
|
|
|
</div>
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
<div class="tab-content" id="tab-dag">
|
2026-02-07 10:36:45 +00:00
|
|
|
<div id="dag-controls">
|
|
|
|
|
<button onclick="zoomIn()">+</button>
|
|
|
|
|
<button onclick="zoomOut()">−</button>
|
|
|
|
|
<button onclick="resetView()">fit</button>
|
|
|
|
|
<span style="margin-left:8px;opacity:0.6">scroll to zoom · drag to pan · click node to focus</span>
|
|
|
|
|
</div>
|
|
|
|
|
<div id="dag-viewport"></div>
|
|
|
|
|
<div id="dag-loading">Loading Graphviz…</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
<div class="tab-content active" id="tab-spectral">
|
2026-02-07 10:36:45 +00:00
|
|
|
<div class="grid">
|
2026-02-09 19:05:37 +00:00
|
|
|
<div class="panel" id="panel-structural">
|
|
|
|
|
<h2><span class="icon">◉</span> Structural Properties</h2>
|
|
|
|
|
<div id="structural-content"></div>
|
2026-02-07 10:36:45 +00:00
|
|
|
</div>
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
<div class="panel" id="panel-cohesion">
|
|
|
|
|
<h2><span class="icon">▨</span> Module Cohesion</h2>
|
|
|
|
|
<svg id="svg-cohesion"></svg>
|
2026-02-07 10:36:45 +00:00
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="panel" id="panel-heatmap">
|
|
|
|
|
<h2><span class="icon">▦</span> Module Coupling (directed edge counts)</h2>
|
|
|
|
|
<svg id="svg-heatmap"></svg>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="panel" id="panel-metrics">
|
|
|
|
|
<h2><span class="icon">∑</span> Complexity Metrics</h2>
|
|
|
|
|
<div id="metrics-content"></div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
<div class="tooltip" id="tooltip"></div>
|
|
|
|
|
|
|
|
|
|
<!-- ─── Script 1: synchronous — data + tab switching + spectral panels ─── -->
|
|
|
|
|
<script>
|
|
|
|
|
// ─── Data ──────────────────────────────────────────────────────────────────
|
2026-02-09 19:05:37 +00:00
|
|
|
const DATA = {"structural": {"avg_degree": 2.49, "max_fan_in": 16, "max_fan_in_node": "ActorAddress", "max_fan_out": 18, "max_fan_out_node": "Runtime", "dag_depth": 7, "clustering_coeff": 0.2668, "avg_module_cohesion": 0.302, "avg_module_size": 4.3}, "cohesion": [{"module": "actor", "cohesion": 0.214, "size": 7}, {"module": "worker", "cohesion": 0.167, "size": 4}, {"module": "channel", "cohesion": 0.5, "size": 3}, {"module": "config", "cohesion": 0.5, "size": 2}, {"module": "delivery", "cohesion": 0.167, "size": 7}, {"module": "stats", "cohesion": 0.25, "size": 5}, {"module": "runtime", "cohesion": 0.5, "size": 3}, {"module": "transport", "cohesion": 0.119, "size": 7}], "coupling": {"modules": ["actor", "worker", "channel", "error", "config", "delivery", "stats", "runtime", "transport"], "matrix": [[9.0, 0.0, 0.0, 2.0, 0.0, 0.0, 0.0, 0.0, 0.0], [9.0, 2.0, 1.0, 1.0, 0.0, 5.0, 3.0, 0.0, 0.0], [0.0, 0.0, 3.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0], [0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0], [5.0, 0.0, 1.0, 2.0, 1.0, 7.0, 0.0, 0.0, 2.0], [2.0, 0.0, 0.0, 0.0, 0.0, 0.0, 5.0, 0.0, 0.0], [4.0, 1.0, 2.0, 1.0, 1.0, 5.0, 2.0, 3.0, 2.0], [3.0, 0.0, 2.0, 4.0, 0.0, 0.0, 0.0, 0.0, 5.0]]}, "metrics": {"n_nodes": 39, "n_edges": 97, "n_modules": 9, "connected_components": 2, "algebraic_connectivity": 0.0, "normalized_algebraic_connectivity": 0.0, "spectral_entropy": 4.7287, "normalized_spectral_entropy": 0.9077, "edge_density": 0.0655, "cross_module_ratio": 0.6392, "spectral_radius": 7.9636, "normalized_spectral_radius": 0.2096, "cci": 0.396, "cci_label": "MODERATE", "cci_color": "#ff9800", "cci_desc": "typical well-structured codebase"}, "module_colors": {"actor": "#1565c0", "worker": "#c62828", "channel": "#e65100", "error": "#7b1fa2", "config": "#2e7d32", "delivery": "#f9a825", "stats": "#00838f", "runtime": "#d84315", "transport": "#1565c0"}};
|
|
|
|
|
const { structural, cohesion, coupling, metrics, module_colors } = DATA;
|
2026-02-07 10:36:45 +00:00
|
|
|
|
|
|
|
|
// ─── Tab switching ─────────────────────────────────────────────────────────
|
|
|
|
|
document.querySelectorAll('.tab').forEach(btn => {
|
|
|
|
|
btn.addEventListener('click', () => {
|
|
|
|
|
document.querySelectorAll('.tab').forEach(b => b.classList.remove('active'));
|
|
|
|
|
document.querySelectorAll('.tab-content').forEach(c => c.classList.remove('active'));
|
|
|
|
|
btn.classList.add('active');
|
|
|
|
|
document.getElementById('tab-' + btn.dataset.tab).classList.add('active');
|
2026-02-09 19:05:37 +00:00
|
|
|
if (btn.dataset.tab === 'dag') {
|
|
|
|
|
window.dispatchEvent(new Event('dag-visible'));
|
|
|
|
|
}
|
2026-02-07 10:36:45 +00:00
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ─── Tooltip ───────────────────────────────────────────────────────────────
|
|
|
|
|
const TT = document.getElementById('tooltip');
|
|
|
|
|
function showTip(evt, html) {
|
|
|
|
|
TT.innerHTML = html;
|
|
|
|
|
TT.style.display = 'block';
|
|
|
|
|
const x = evt.clientX + 14, y = evt.clientY - 10;
|
|
|
|
|
TT.style.left = Math.min(x, window.innerWidth - TT.offsetWidth - 20) + 'px';
|
|
|
|
|
TT.style.top = Math.min(y, window.innerHeight - TT.offsetHeight - 20) + 'px';
|
|
|
|
|
}
|
|
|
|
|
function hideTip() { TT.style.display = 'none'; }
|
|
|
|
|
|
|
|
|
|
function modColor(mod) { return module_colors[mod] || '#9e9e9e'; }
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// ─── Structural Properties ────────────────────────────────────────────────
|
2026-02-07 10:36:45 +00:00
|
|
|
(function() {
|
2026-02-09 19:05:37 +00:00
|
|
|
const c = document.getElementById('structural-content');
|
|
|
|
|
const s = structural;
|
|
|
|
|
c.innerHTML = `
|
|
|
|
|
<div class="metrics-grid">
|
|
|
|
|
<div class="sub-header">Density & Depth</div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Edges/node (avg degree)</span><span class="metric-value">${s.avg_degree}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">DAG depth</span><span class="metric-value">${s.dag_depth}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Clustering coefficient</span><span class="metric-value">${s.clustering_coeff}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Avg module size</span><span class="metric-value">${s.avg_module_size}</span></div>
|
|
|
|
|
|
|
|
|
|
<div class="sub-header">Dependency Hotspots</div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Max fan-in</span><span class="metric-value">${s.max_fan_in} ← ${s.max_fan_in_node}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Max fan-out</span><span class="metric-value">${s.max_fan_out} → ${s.max_fan_out_node}</span></div>
|
|
|
|
|
|
|
|
|
|
<div class="sub-header">Cohesion</div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Avg module cohesion</span><span class="metric-value">${s.avg_module_cohesion}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Cross-module ratio</span><span class="metric-value">${(metrics.cross_module_ratio*100).toFixed(1)}%</span></div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
2026-02-07 10:36:45 +00:00
|
|
|
})();
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// ─── Module Cohesion ──────────────────────────────────────────────────────
|
2026-02-07 10:36:45 +00:00
|
|
|
(function() {
|
2026-02-09 19:05:37 +00:00
|
|
|
const svg = document.getElementById('svg-cohesion');
|
|
|
|
|
const n = cohesion.length;
|
|
|
|
|
if (n === 0) return;
|
|
|
|
|
const barH = Math.max(20, Math.min(36, 300/n));
|
|
|
|
|
const W = 560, H = Math.max(200, n*barH + 60), M = {t:10,r:30,b:30,l:120};
|
2026-02-07 10:36:45 +00:00
|
|
|
const w = W-M.l-M.r, h = H-M.t-M.b;
|
|
|
|
|
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
const xScale = v => M.l + v * w;
|
2026-02-07 10:36:45 +00:00
|
|
|
const yScale = i => M.t + (i/n) * h + barH/2;
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// Background grid
|
|
|
|
|
for (const tick of [0.25, 0.5, 0.75, 1.0]) {
|
|
|
|
|
const x = xScale(tick);
|
|
|
|
|
const line = document.createElementNS('http://www.w3.org/2000/svg','line');
|
|
|
|
|
Object.entries({x1:x,x2:x,y1:M.t,y2:M.t+h,stroke:'#2a2a5a','stroke-width':0.5}).forEach(([k,v])=>line.setAttribute(k,v));
|
|
|
|
|
svg.appendChild(line);
|
|
|
|
|
const txt = document.createElementNS('http://www.w3.org/2000/svg','text');
|
|
|
|
|
txt.setAttribute('x', x); txt.setAttribute('y', H-8);
|
|
|
|
|
txt.setAttribute('text-anchor','middle'); txt.setAttribute('fill','#666'); txt.setAttribute('font-size','10');
|
|
|
|
|
txt.textContent = (tick*100).toFixed(0) + '%';
|
|
|
|
|
svg.appendChild(txt);
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// Average line
|
|
|
|
|
const avgX = xScale(structural.avg_module_cohesion);
|
|
|
|
|
const avgLine = document.createElementNS('http://www.w3.org/2000/svg','line');
|
|
|
|
|
Object.entries({x1:avgX,x2:avgX,y1:M.t,y2:M.t+h,stroke:'#ff4444','stroke-width':1.5,'stroke-dasharray':'5,3','stroke-opacity':0.7}).forEach(([k,v])=>avgLine.setAttribute(k,v));
|
|
|
|
|
svg.appendChild(avgLine);
|
|
|
|
|
const avgLbl = document.createElementNS('http://www.w3.org/2000/svg','text');
|
|
|
|
|
avgLbl.setAttribute('x', avgX+4); avgLbl.setAttribute('y', M.t+10);
|
|
|
|
|
avgLbl.setAttribute('fill','#ff4444'); avgLbl.setAttribute('font-size','9'); avgLbl.setAttribute('opacity','0.8');
|
|
|
|
|
avgLbl.textContent = 'avg';
|
|
|
|
|
svg.appendChild(avgLbl);
|
|
|
|
|
|
|
|
|
|
cohesion.forEach((d, i) => {
|
|
|
|
|
const barW = Math.max(d.cohesion * w, 2);
|
|
|
|
|
const y = yScale(i) - barH*0.35;
|
2026-02-07 10:36:45 +00:00
|
|
|
const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
|
2026-02-09 19:05:37 +00:00
|
|
|
rect.setAttribute('x', M.l); rect.setAttribute('y', y);
|
|
|
|
|
rect.setAttribute('width', barW); rect.setAttribute('height', barH*0.7);
|
|
|
|
|
rect.setAttribute('rx', 3);
|
2026-02-07 10:36:45 +00:00
|
|
|
rect.setAttribute('fill', modColor(d.module));
|
|
|
|
|
rect.setAttribute('opacity', 0.85);
|
|
|
|
|
rect.classList.add('fi-bar');
|
|
|
|
|
rect.addEventListener('mousemove', e => showTip(e,
|
2026-02-09 19:05:37 +00:00
|
|
|
`<span class="tt-label">${d.module}</span><br>` +
|
|
|
|
|
`Types: <span class="tt-val">${d.size}</span><br>` +
|
|
|
|
|
`Cohesion: <span class="tt-val">${(d.cohesion*100).toFixed(1)}%</span>`
|
2026-02-07 10:36:45 +00:00
|
|
|
));
|
|
|
|
|
rect.addEventListener('mouseleave', hideTip);
|
|
|
|
|
svg.appendChild(rect);
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// Value label on bar
|
|
|
|
|
const valTxt = document.createElementNS('http://www.w3.org/2000/svg','text');
|
|
|
|
|
valTxt.setAttribute('x', M.l + barW + 6); valTxt.setAttribute('y', yScale(i)+4);
|
|
|
|
|
valTxt.setAttribute('fill','#ccc'); valTxt.setAttribute('font-size','10'); valTxt.setAttribute('font-weight','600');
|
|
|
|
|
valTxt.textContent = (d.cohesion*100).toFixed(0) + '%';
|
|
|
|
|
svg.appendChild(valTxt);
|
|
|
|
|
|
|
|
|
|
// Module label
|
2026-02-07 10:36:45 +00:00
|
|
|
const txt = document.createElementNS('http://www.w3.org/2000/svg','text');
|
2026-02-09 19:05:37 +00:00
|
|
|
txt.setAttribute('x', M.l-8); txt.setAttribute('y', yScale(i)+4);
|
|
|
|
|
txt.setAttribute('text-anchor','end'); txt.setAttribute('fill', modColor(d.module));
|
|
|
|
|
txt.setAttribute('font-size','11'); txt.setAttribute('font-weight','600');
|
|
|
|
|
txt.textContent = `${d.module} (${d.size})`;
|
2026-02-07 10:36:45 +00:00
|
|
|
svg.appendChild(txt);
|
|
|
|
|
});
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
// ─── Module Coupling Heatmap ───────────────────────────────────────────────
|
|
|
|
|
(function() {
|
|
|
|
|
const mods = coupling.modules;
|
|
|
|
|
const mat = coupling.matrix;
|
|
|
|
|
const n = mods.length;
|
|
|
|
|
const svg = document.getElementById('svg-heatmap');
|
|
|
|
|
const cellSz = Math.min(55, 400/n);
|
|
|
|
|
const M = {t:10,r:60,b:80,l:100};
|
|
|
|
|
const W = M.l + n*cellSz + M.r, H = M.t + n*cellSz + M.b;
|
|
|
|
|
svg.setAttribute('viewBox', `0 0 ${W} ${H}`);
|
|
|
|
|
|
|
|
|
|
const maxVal = Math.max(...mat.flat(), 1);
|
|
|
|
|
|
|
|
|
|
// Color scale: 0=transparent dark, max=deep red
|
|
|
|
|
function heatColor(v) {
|
|
|
|
|
if (v === 0) return '#1a1a2e';
|
|
|
|
|
const t = v / maxVal;
|
|
|
|
|
const r = Math.round(40 + 215*t);
|
|
|
|
|
const g = Math.round(30 + 40*(1-t));
|
|
|
|
|
const b = Math.round(50*(1-t));
|
|
|
|
|
return `rgb(${r},${g},${b})`;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for (let i = 0; i < n; i++) {
|
|
|
|
|
// Row labels
|
|
|
|
|
const rl = document.createElementNS('http://www.w3.org/2000/svg','text');
|
|
|
|
|
rl.setAttribute('x', M.l-8); rl.setAttribute('y', M.t + i*cellSz + cellSz/2 + 4);
|
|
|
|
|
rl.setAttribute('text-anchor','end'); rl.setAttribute('fill', modColor(mods[i]));
|
|
|
|
|
rl.setAttribute('font-size','11'); rl.setAttribute('font-weight','600');
|
|
|
|
|
rl.textContent = mods[i];
|
|
|
|
|
svg.appendChild(rl);
|
|
|
|
|
|
|
|
|
|
// Column labels
|
|
|
|
|
const cl = document.createElementNS('http://www.w3.org/2000/svg','text');
|
|
|
|
|
cl.setAttribute('x', M.l + i*cellSz + cellSz/2);
|
|
|
|
|
cl.setAttribute('y', M.t + n*cellSz + 16);
|
|
|
|
|
cl.setAttribute('text-anchor','end'); cl.setAttribute('fill', modColor(mods[i]));
|
|
|
|
|
cl.setAttribute('font-size','11'); cl.setAttribute('font-weight','600');
|
|
|
|
|
cl.setAttribute('transform', `rotate(-45, ${M.l + i*cellSz + cellSz/2}, ${M.t + n*cellSz + 16})`);
|
|
|
|
|
cl.textContent = mods[i];
|
|
|
|
|
svg.appendChild(cl);
|
|
|
|
|
|
|
|
|
|
for (let j = 0; j < n; j++) {
|
|
|
|
|
const v = mat[i][j];
|
|
|
|
|
const rect = document.createElementNS('http://www.w3.org/2000/svg','rect');
|
|
|
|
|
rect.setAttribute('x', M.l + j*cellSz + 1);
|
|
|
|
|
rect.setAttribute('y', M.t + i*cellSz + 1);
|
|
|
|
|
rect.setAttribute('width', cellSz-2); rect.setAttribute('height', cellSz-2);
|
|
|
|
|
rect.setAttribute('rx', 3);
|
|
|
|
|
rect.setAttribute('fill', heatColor(v));
|
|
|
|
|
rect.classList.add('hm-cell');
|
|
|
|
|
rect.addEventListener('mousemove', e => showTip(e,
|
|
|
|
|
`<span class="tt-label">${mods[i]} → ${mods[j]}</span><br>` +
|
|
|
|
|
`Edges: <span class="tt-val">${v}</span>` +
|
|
|
|
|
(i !== j ? '<br><span style="opacity:0.6">cross-module</span>' : '<br><span style="opacity:0.6">intra-module</span>')
|
|
|
|
|
));
|
|
|
|
|
rect.addEventListener('mouseleave', hideTip);
|
|
|
|
|
svg.appendChild(rect);
|
|
|
|
|
|
|
|
|
|
// Cell text
|
|
|
|
|
if (v > 0) {
|
|
|
|
|
const txt = document.createElementNS('http://www.w3.org/2000/svg','text');
|
|
|
|
|
txt.setAttribute('x', M.l + j*cellSz + cellSz/2);
|
|
|
|
|
txt.setAttribute('y', M.t + i*cellSz + cellSz/2 + 4);
|
|
|
|
|
txt.setAttribute('text-anchor','middle'); txt.setAttribute('font-size','11');
|
|
|
|
|
txt.setAttribute('font-weight','700'); txt.setAttribute('pointer-events','none');
|
|
|
|
|
txt.setAttribute('fill', v > maxVal*0.5 ? '#fff' : '#ccc');
|
|
|
|
|
txt.textContent = v;
|
|
|
|
|
svg.appendChild(txt);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Axis labels
|
|
|
|
|
const srcL = document.createElementNS('http://www.w3.org/2000/svg','text');
|
|
|
|
|
srcL.setAttribute('x', 10); srcL.setAttribute('y', M.t + n*cellSz/2);
|
|
|
|
|
srcL.setAttribute('text-anchor','middle'); srcL.setAttribute('fill','#666');
|
|
|
|
|
srcL.setAttribute('font-size','10');
|
|
|
|
|
srcL.setAttribute('transform', `rotate(-90,10,${M.t + n*cellSz/2})`);
|
|
|
|
|
srcL.textContent = 'source module';
|
|
|
|
|
svg.appendChild(srcL);
|
|
|
|
|
})();
|
|
|
|
|
|
|
|
|
|
// ─── Metrics Panel ─────────────────────────────────────────────────────────
|
|
|
|
|
(function() {
|
|
|
|
|
const c = document.getElementById('metrics-content');
|
|
|
|
|
const mm = metrics;
|
|
|
|
|
c.innerHTML = `
|
|
|
|
|
<div class="metrics-grid">
|
|
|
|
|
<div class="sub-header">Graph</div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Nodes</span><span class="metric-value">${mm.n_nodes}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Directed edges</span><span class="metric-value">${mm.n_edges}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Modules</span><span class="metric-value">${mm.n_modules}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Components</span><span class="metric-value">${mm.connected_components}</span></div>
|
|
|
|
|
|
|
|
|
|
<div class="sub-header">Spectral</div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">λ<sub>2</sub> (alg. connectivity)</span><span class="metric-value">${mm.algebraic_connectivity}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">λ<sub>2</sub>/n (normalized)</span><span class="metric-value">${mm.normalized_algebraic_connectivity}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Spectral entropy</span><span class="metric-value">${mm.spectral_entropy}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Norm. entropy</span><span class="metric-value">${mm.normalized_spectral_entropy}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Spectral radius</span><span class="metric-value">${mm.spectral_radius}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Norm. radius</span><span class="metric-value">${mm.normalized_spectral_radius}</span></div>
|
|
|
|
|
|
|
|
|
|
<div class="sub-header">Coupling</div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Edge density</span><span class="metric-value">${mm.edge_density}</span></div>
|
|
|
|
|
<div class="metric-item"><span class="metric-label">Cross-module ratio</span><span class="metric-value">${(mm.cross_module_ratio*100).toFixed(1)}%</span></div>
|
|
|
|
|
|
|
|
|
|
<div class="cci-box">
|
|
|
|
|
<div class="cci-score" style="color:${mm.cci_color}">CCI = ${mm.cci}</div>
|
|
|
|
|
<div class="cci-label" style="color:${mm.cci_color}">${mm.cci_label}</div>
|
|
|
|
|
<div class="cci-desc">${mm.cci_desc}</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
`;
|
|
|
|
|
})();
|
|
|
|
|
</script>
|
|
|
|
|
|
|
|
|
|
<!-- ─── Script 2: module — viz-js DAG rendering (async) ─────────────────── -->
|
|
|
|
|
<script type="module">
|
|
|
|
|
import { instance } from 'https://cdn.jsdelivr.net/npm/@viz-js/viz@3.11.0/lib/viz-standalone.mjs';
|
|
|
|
|
|
|
|
|
|
const DOT_SOURCE = `digraph swactor {
|
|
|
|
|
rankdir=LR;
|
|
|
|
|
fontname="Helvetica";
|
|
|
|
|
fontsize=14;
|
|
|
|
|
node [fontname="Helvetica", fontsize=11, style=filled, shape=record];
|
|
|
|
|
edge [fontname="Helvetica", fontsize=9];
|
|
|
|
|
label="swactor — internal dependency DAG";
|
|
|
|
|
labelloc=t;
|
|
|
|
|
compound=true;
|
|
|
|
|
newrank=true;
|
|
|
|
|
splines=ortho;
|
|
|
|
|
|
|
|
|
|
subgraph cluster_actor {
|
|
|
|
|
label="actor";
|
|
|
|
|
style="rounded,filled"; fillcolor="#e3f2fd"; color="#1565c0";
|
|
|
|
|
Message [label="{«trait» Message}", fillcolor="#bbdefb"];
|
|
|
|
|
ActorInterface [label="{«trait» ActorInterface|Incoming(Message)\\nResponse(Message)\\nhandle((&self, &Ctx, Self::Incoming))}", fillcolor="#bbdefb"];
|
|
|
|
|
ActorAddress [label="{ActorAddress|0: [u8; ..]}", fillcolor="#bbdefb"];
|
2026-02-09 19:05:37 +00:00
|
|
|
Actor [label="{Actor|0: A}", fillcolor="#bbdefb"];
|
2026-02-07 10:36:45 +00:00
|
|
|
AnyActor [label="{«trait» AnyActor|handle_any((&self, &Ctx, Box\\<dyn Any + Send\\>))}", fillcolor="#bbdefb"];
|
2026-02-09 19:05:37 +00:00
|
|
|
ContextInner [label="{«trait» ContextInner|send_any((&self, ActorAddress, Box\\<dyn Any + Send\\>) → Result\\<(), Error\\>)\\nspawn_any((&self, ActorAddress, Box\\<dyn AnyActor\\>) → Result\\<(), Error\\>)}", fillcolor="#bbdefb"];
|
2026-02-07 10:36:45 +00:00
|
|
|
Ctx [label="{Ctx|inner: &'a dyn ContextInner\\nself_addr: ActorAddress}", fillcolor="#bbdefb"];
|
|
|
|
|
}
|
2026-02-09 19:05:37 +00:00
|
|
|
subgraph cluster_worker {
|
|
|
|
|
label="worker";
|
|
|
|
|
style="rounded,filled"; fillcolor="#fce4ec"; color="#c62828";
|
|
|
|
|
Worker [label="{Worker|id: WorkerId\\npool: ActorPool\\ntransfer_rx: Receiver\\<Envelope\\>\\nspawn_rx: Receiver\\<(ActorAddress, Box\\<dyn AnyActor\\>)\\>\\nstats: Arc\\<WorkerStats\\>\\nmailbox_snapshot: Arc\\<std::sync::Mutex\\<Vec\\<(ActorAddress, usize)\\>\\>\\>}", fillcolor="#ffcdd2"];
|
|
|
|
|
WorkerContext [label="{WorkerContext|worker_id: WorkerId\\ntc: &'a TickContext\\<'a\\>\\npending_local: &'a RefCell\\<Vec\\<(ActorAddress, Box\\<dyn Any + Send\\>)\\>\\>\\nstats: &'a WorkerStats}", fillcolor="#ffcdd2"];
|
|
|
|
|
ActorSlot [label="{ActorSlot|mailbox: VecDeque\\<Box\\<dyn Any + Send\\>\\>\\nactor: Box\\<dyn AnyActor\\>}", fillcolor="#ffcdd2"];
|
|
|
|
|
ActorPool [label="{ActorPool|actors: HashMap\\<ActorAddress, ActorSlot\\>}", fillcolor="#ffcdd2"];
|
|
|
|
|
}
|
|
|
|
|
subgraph cluster_channel {
|
|
|
|
|
label="channel";
|
|
|
|
|
style="rounded,filled"; fillcolor="#fff3e0"; color="#e65100";
|
|
|
|
|
HybridChannel [label="{HybridChannel|ring: ArrayQueue\\<T\\>\\noverflow: SegQueue\\<T\\>}", fillcolor="#ffe0b2"];
|
|
|
|
|
Receiver [label="{Receiver|queue: Arc\\<HybridChannel\\<T\\>\\>}", fillcolor="#ffe0b2"];
|
|
|
|
|
Sender [label="{Sender|queue: Arc\\<HybridChannel\\<T\\>\\>}", fillcolor="#ffe0b2"];
|
|
|
|
|
}
|
|
|
|
|
subgraph cluster_error {
|
|
|
|
|
label="error";
|
2026-02-07 10:36:45 +00:00
|
|
|
style="rounded,filled"; fillcolor="#f3e5f5"; color="#7b1fa2";
|
2026-02-09 19:05:37 +00:00
|
|
|
Error [label="{Error|0: Box\\<dyn std :: error :: Error + Send + Sync + 'static\\>}", fillcolor="#e1bee7"];
|
|
|
|
|
}
|
|
|
|
|
subgraph cluster_config {
|
|
|
|
|
label="config";
|
|
|
|
|
style="rounded,filled"; fillcolor="#e8f5e9"; color="#2e7d32";
|
|
|
|
|
BackoffPolicy [label="{BackoffPolicy|spin_threshold: u32\\nyield_threshold: u32\\nsleep_increment_us: u64\\nsleep_max_us: u64}", fillcolor="#c8e6c9"];
|
|
|
|
|
RuntimeConfig [label="{RuntimeConfig|max_actors: usize\\nactor_max_messages: usize\\nnum_threads: usize\\nbackoff_policy: BackoffPolicy}", fillcolor="#c8e6c9"];
|
|
|
|
|
}
|
|
|
|
|
subgraph cluster_delivery {
|
|
|
|
|
label="delivery";
|
|
|
|
|
style="rounded,filled"; fillcolor="#fff9c4"; color="#f9a825";
|
|
|
|
|
WorkerId [label="{WorkerId|0: usize}", fillcolor="#fff59d"];
|
|
|
|
|
AddressMap [label="{AddressMap|inner: RwLock\\<HashMap\\<ActorAddress, WorkerId\\>\\>}", fillcolor="#fff59d"];
|
|
|
|
|
Placement [label="{Placement|next: AtomicUsize\\nnum_workers: usize}", fillcolor="#fff59d"];
|
|
|
|
|
Envelope [label="{Envelope|dest: ActorAddress\\npayload: Box\\<dyn Any + Send\\>}", fillcolor="#fff59d"];
|
|
|
|
|
SenderT [label="{«trait» SenderT|try_send_any((&self, Box\\<dyn Any + Send\\>))}", fillcolor="#fff59d"];
|
|
|
|
|
InboxRegistry [label="{InboxRegistry|senders: RwLock\\<HashMap\\<ActorAddress, Arc\\<dyn SenderT\\>\\>\\>}", fillcolor="#fff59d"];
|
|
|
|
|
TickContext [label="{TickContext|address_map: &'a AddressMap\\ntransfer_txs: &'a [Sender\\<Envelope\\>]\\nspawn_txs: &'a [Sender\\<(ActorAddress, Box\\<dyn AnyActor\\>)\\>]\\nplacement: &'a Placement\\ninbox_registry: &'a InboxRegistry\\nconfig: &'a RuntimeConfig\\ncodec_registry: Option\\<&'a crate::transport::CodecRegistry\\>\\ntransport_router: Option\\<&'a crate::transport::TransportRouter\\>}", fillcolor="#fff59d"];
|
|
|
|
|
}
|
|
|
|
|
subgraph cluster_stats {
|
|
|
|
|
label="stats";
|
|
|
|
|
style="rounded,filled"; fillcolor="#e0f7fa"; color="#00838f";
|
|
|
|
|
TickTiming [label="{TickTiming|phase_us: [u64; ..]\\nmessages_processed: usize\\ndid_work: bool}", fillcolor="#b2ebf2"];
|
|
|
|
|
WorkerStats [label="{WorkerStats|num_actors: AtomicUsize\\ntotal_mailbox_depth: AtomicUsize\\nmessages_processed: AtomicU64\\nlocal_sends: AtomicU64\\ncross_sends: AtomicU64\\ninbox_sends: AtomicU64\\ntype_mismatches: AtomicU64\\npanics: AtomicU64\\ntick_timings: std::sync::Mutex\\<VecDeque\\<TickTiming\\>\\>}", fillcolor="#b2ebf2"];
|
|
|
|
|
WorkerInfo [label="{WorkerInfo|id: usize\\nnum_actors: usize\\nmailbox_depth: usize\\nmessages_processed: u64\\nlocal_sends: u64\\ncross_sends: u64\\ninbox_sends: u64\\ntype_mismatches: u64\\npanics: u64}", fillcolor="#b2ebf2"];
|
|
|
|
|
ActorInfo [label="{ActorInfo|address: ActorAddress\\nworker_id: usize\\nmailbox_depth: usize}", fillcolor="#b2ebf2"];
|
|
|
|
|
RuntimeStats [label="{RuntimeStats|num_workers: usize\\nactors: Vec\\<(ActorAddress, usize)\\>\\nworkers: Vec\\<WorkerInfo\\>\\nactor_details: Vec\\<ActorInfo\\>\\ntick_timings: Vec\\<Vec\\<TickTiming\\>\\>}", fillcolor="#b2ebf2"];
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
|
|
|
|
subgraph cluster_runtime {
|
|
|
|
|
label="runtime";
|
2026-02-09 19:05:37 +00:00
|
|
|
style="rounded,filled"; fillcolor="#fbe9e7"; color="#d84315";
|
|
|
|
|
Inbox [label="{Inbox|addr: ActorAddress\\ninner: Receiver\\<M\\>}", fillcolor="#ffccbc"];
|
|
|
|
|
RuntimeHandle [label="{RuntimeHandle|runtime: Arc\\<Runtime\\>\\nthreads: Vec\\<JoinHandle\\<()\\>\\>}", fillcolor="#ffccbc"];
|
|
|
|
|
Runtime [label="{Runtime|config: RuntimeConfig\\naddress_map: Arc\\<AddressMap\\>\\ninbox_registry: Arc\\<InboxRegistry\\>\\ntransfer_txs: Vec\\<Sender\\<Envelope\\>\\>\\nspawn_txs: Vec\\<Sender\\<(ActorAddress, Box\\<dyn AnyActor\\>)\\>\\>\\nplacement: Placement\\nis_running: AtomicBool\\nworker_stats: Vec\\<Arc\\<WorkerStats\\>\\>\\nmailbox_snapshots: Vec\\<Arc\\<std::sync::Mutex\\<Vec\\<(ActorAddress, usize)\\>\\>\\>\\>\\ntick_workers: RefCell\\<Vec\\<Worker\\>\\>\\ncodec_registry: Option\\<Arc\\<crate::transport::CodecRegistry\\>\\>\\ntransport_router: Option\\<Arc\\<crate::transport::TransportRouter\\>\\>}", fillcolor="#ffccbc"];
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
2026-02-09 19:05:37 +00:00
|
|
|
subgraph cluster_transport {
|
|
|
|
|
label="transport (feature-gated)";
|
|
|
|
|
style="rounded,dashed,filled"; fillcolor="#e3f2fd"; color="#1565c0";
|
|
|
|
|
Codec [label="{«trait» Codec|encode((&self, &M) → Result\\<Vec\\<u8\\>, Error\\>)\\ndecode((&self, &[u8]) → Result\\<M, Error\\>)}", fillcolor="#bbdefb"];
|
|
|
|
|
NetworkMessage [label="{«trait» NetworkMessage|type_tag(() → &'static str)}", fillcolor="#bbdefb"];
|
|
|
|
|
WireEnvelope [label="{WireEnvelope|dest: ActorAddress\\ntype_tag: String\\npayload: Vec\\<u8\\>}", fillcolor="#bbdefb"];
|
|
|
|
|
Transport [label="{«trait» Transport|send((&self, WireEnvelope) → Result\\<(), Error\\>)}", fillcolor="#bbdefb"];
|
|
|
|
|
CodecRegistry [label="{CodecRegistry|encoders: HashMap\\<TypeId, EncodeFn\\>\\ndecoders: HashMap\\<String, DecodeFn\\>}", fillcolor="#bbdefb"];
|
|
|
|
|
TransportRouter [label="{TransportRouter|routes: RwLock\\<HashMap\\<ActorAddress, Arc\\<dyn Transport\\>\\>\\>}", fillcolor="#bbdefb"];
|
|
|
|
|
InMemoryTransport [label="{InMemoryTransport|tx: std::sync::Mutex\\<std::sync::mpsc::Sender\\<WireEnvelope\\>\\>}", fillcolor="#bbdefb"];
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
|
// INTRA-MODULE EDGES (within same cluster)
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
ActorInterface -> Message [label="Incoming", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
ActorInterface -> Ctx [label="handle", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
AnyActor -> Ctx [label="handle_any", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
ContextInner -> ActorAddress [label="send_any", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
ContextInner -> AnyActor [label="spawn_any", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
Ctx -> ContextInner [label="inner", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
Ctx -> ActorAddress [label="self_addr", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
Actor -> AnyActor [label="impl", style=dotted, color="#1565c0", penwidth=1];
|
|
|
|
|
Actor -> Ctx [label="handle_any() param", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
Worker -> ActorPool [label="pool", style=dashed, color="#c62828", penwidth=1];
|
|
|
|
|
ActorPool -> ActorSlot [label="actors", style=dashed, color="#c62828", penwidth=1];
|
|
|
|
|
Receiver -> HybridChannel [label="queue", style=dashed, color="#e65100", penwidth=1];
|
|
|
|
|
Sender -> HybridChannel [label="queue", style=dashed, color="#e65100", penwidth=1];
|
|
|
|
|
Receiver -> Sender [label="new_sender() param", style=dashed, color="#e65100", penwidth=1];
|
|
|
|
|
RuntimeConfig -> BackoffPolicy [label="backoff_policy", style=dashed, color="#2e7d32", penwidth=1];
|
|
|
|
|
AddressMap -> WorkerId [label="inner", style=dashed, color="#f9a825", penwidth=1];
|
|
|
|
|
InboxRegistry -> SenderT [label="senders", style=dashed, color="#f9a825", penwidth=1];
|
|
|
|
|
TickContext -> AddressMap [label="address_map", style=dashed, color="#f9a825", penwidth=1];
|
|
|
|
|
TickContext -> Envelope [label="transfer_txs", style=dashed, color="#f9a825", penwidth=1];
|
|
|
|
|
TickContext -> Placement [label="placement", style=dashed, color="#f9a825", penwidth=1];
|
|
|
|
|
TickContext -> InboxRegistry [label="inbox_registry", style=dashed, color="#f9a825", penwidth=1];
|
|
|
|
|
Placement -> WorkerId [label="next_worker() param", style=dashed, color="#f9a825", penwidth=1];
|
|
|
|
|
WorkerStats -> TickTiming [label="tick_timings", style=dashed, color="#00838f", penwidth=1];
|
|
|
|
|
RuntimeStats -> WorkerInfo [label="workers", style=dashed, color="#00838f", penwidth=1];
|
|
|
|
|
RuntimeStats -> ActorInfo [label="actor_details", style=dashed, color="#00838f", penwidth=1];
|
|
|
|
|
RuntimeStats -> TickTiming [label="tick_timings", style=dashed, color="#00838f", penwidth=1];
|
|
|
|
|
WorkerStats -> WorkerInfo [label="snapshot() param", style=dashed, color="#00838f", penwidth=1];
|
|
|
|
|
RuntimeHandle -> Runtime [label="runtime", style=dashed, color="#d84315", penwidth=1];
|
|
|
|
|
Runtime -> Inbox [label="new_inbox() param", style=dashed, color="#d84315", penwidth=1];
|
|
|
|
|
Runtime -> RuntimeHandle [label="run() param", style=dashed, color="#d84315", penwidth=1];
|
|
|
|
|
Transport -> WireEnvelope [label="send", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
TransportRouter -> Transport [label="routes", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
InMemoryTransport -> WireEnvelope [label="tx", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
CodecRegistry -> WireEnvelope [label="receive() param", style=dashed, color="#1565c0", penwidth=1];
|
|
|
|
|
InMemoryTransport -> Transport [label="impl", style=dotted, color="#1565c0", penwidth=1];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
|
// CROSS-MODULE EDGES (the real dependency DAG)
|
|
|
|
|
// ═══════════════════════════════════════════════════════════════════
|
|
|
|
|
|
|
|
|
|
// --- actor depends on error ---
|
|
|
|
|
ContextInner -> Error [label="send_any", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
Ctx -> Error [label="send() param", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- worker depends on actor ---
|
|
|
|
|
Worker -> ActorAddress [label="spawn_rx", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
Worker -> AnyActor [label="spawn_rx", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
WorkerContext -> ActorAddress [label="pending_local", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
ActorSlot -> AnyActor [label="actor", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
ActorPool -> ActorAddress [label="actors", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
WorkerContext -> ContextInner [label="impl", style=dotted, color="#c62828", penwidth=1.5];
|
|
|
|
|
WorkerContext -> AnyActor [label="spawn_any() param", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
ActorPool -> AnyActor [label="insert() param", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
ActorPool -> ContextInner [label="tick_all() param", style=solid, color="#c62828", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- worker depends on channel ---
|
|
|
|
|
Worker -> Receiver [label="transfer_rx", style=solid, color="#c62828", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- worker depends on error ---
|
|
|
|
|
WorkerContext -> Error [label="send_any() param", style=solid, color="#c62828", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- worker depends on delivery ---
|
|
|
|
|
Worker -> WorkerId [label="id", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
Worker -> Envelope [label="transfer_rx", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
WorkerContext -> WorkerId [label="worker_id", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
WorkerContext -> TickContext [label="tc", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
Worker -> TickContext [label="tick_once() param", style=solid, color="#c62828", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- worker depends on stats ---
|
|
|
|
|
Worker -> WorkerStats [label="stats", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
WorkerContext -> WorkerStats [label="stats", style=solid, color="#c62828", penwidth=1.5];
|
|
|
|
|
ActorPool -> WorkerStats [label="tick_all() param", style=solid, color="#c62828", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- channel depends on delivery ---
|
|
|
|
|
Sender -> SenderT [label="impl", style=dotted, color="#e65100", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- delivery depends on actor ---
|
|
|
|
|
AddressMap -> ActorAddress [label="inner", style=solid, color="#f9a825", penwidth=1.5];
|
|
|
|
|
Envelope -> ActorAddress [label="dest", style=solid, color="#f9a825", penwidth=1.5];
|
|
|
|
|
InboxRegistry -> ActorAddress [label="senders", style=solid, color="#f9a825", penwidth=1.5];
|
|
|
|
|
TickContext -> ActorAddress [label="spawn_txs", style=solid, color="#f9a825", penwidth=1.5];
|
|
|
|
|
TickContext -> AnyActor [label="spawn_txs", style=solid, color="#f9a825", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- delivery depends on channel ---
|
|
|
|
|
TickContext -> Sender [label="transfer_txs", style=solid, color="#f9a825", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- delivery depends on error ---
|
|
|
|
|
InboxRegistry -> Error [label="try_deliver() param", style=solid, color="#f9a825", penwidth=1.5];
|
|
|
|
|
TickContext -> Error [label="route_nonlocal() param", style=solid, color="#f9a825", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- delivery depends on config ---
|
|
|
|
|
TickContext -> RuntimeConfig [label="config", style=solid, color="#f9a825", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
|
2026-02-09 19:05:37 +00:00
|
|
|
// --- delivery depends on transport ---
|
|
|
|
|
TickContext -> CodecRegistry [label="codec_registry", style=solid, color="#f9a825", penwidth=1.5];
|
|
|
|
|
TickContext -> TransportRouter [label="transport_router", style=solid, color="#f9a825", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- stats depends on actor ---
|
|
|
|
|
ActorInfo -> ActorAddress [label="address", style=solid, color="#00838f", penwidth=1.5];
|
|
|
|
|
RuntimeStats -> ActorAddress [label="actors", style=solid, color="#00838f", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- runtime depends on actor ---
|
|
|
|
|
Inbox -> ActorAddress [label="addr", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> ActorAddress [label="spawn_txs", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> AnyActor [label="spawn_txs", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> ContextInner [label="impl", style=dotted, color="#d84315", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- runtime depends on worker ---
|
|
|
|
|
Runtime -> Worker [label="tick_workers", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- runtime depends on channel ---
|
|
|
|
|
Inbox -> Receiver [label="inner", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> Sender [label="transfer_txs", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- runtime depends on error ---
|
|
|
|
|
Runtime -> Error [label="spawn() param", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- runtime depends on config ---
|
|
|
|
|
Runtime -> RuntimeConfig [label="config", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- runtime depends on delivery ---
|
|
|
|
|
Runtime -> AddressMap [label="address_map", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> InboxRegistry [label="inbox_registry", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> Envelope [label="transfer_txs", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> Placement [label="placement", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> TickContext [label="make_tick_context() param", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- runtime depends on stats ---
|
|
|
|
|
Runtime -> WorkerStats [label="worker_stats", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> RuntimeStats [label="stats() param", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- runtime depends on transport ---
|
|
|
|
|
Runtime -> CodecRegistry [label="codec_registry", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
Runtime -> TransportRouter [label="transport_router", style=solid, color="#d84315", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- transport depends on actor ---
|
|
|
|
|
WireEnvelope -> ActorAddress [label="dest", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
TransportRouter -> ActorAddress [label="routes", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
CodecRegistry -> ActorAddress [label="receive() param", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- transport depends on channel ---
|
|
|
|
|
InMemoryTransport -> Sender [label="tx", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
InMemoryTransport -> Receiver [label="pair() param", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
|
|
|
|
|
// --- transport depends on error ---
|
|
|
|
|
Codec -> Error [label="encode", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
Transport -> Error [label="send", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
CodecRegistry -> Error [label="encode() param", style=solid, color="#1565c0", penwidth=1.5];
|
|
|
|
|
InMemoryTransport -> Error [label="send() param", style=solid, color="#1565c0", penwidth=1.5];
|
2026-02-07 10:36:45 +00:00
|
|
|
}
|
|
|
|
|
`;
|
|
|
|
|
|
|
|
|
|
const viz = await instance();
|
|
|
|
|
const svg = viz.renderSVGElement(DOT_SOURCE);
|
|
|
|
|
document.getElementById('dag-loading').remove();
|
|
|
|
|
|
|
|
|
|
const vp = document.getElementById('dag-viewport');
|
|
|
|
|
vp.appendChild(svg);
|
|
|
|
|
|
|
|
|
|
// ─── Dark-mode SVG recoloring ──────────────────────────────────────────────
|
|
|
|
|
svg.querySelectorAll('polygon[fill="white"]').forEach(el => el.setAttribute('fill','#1a1a2e'));
|
2026-02-09 19:05:37 +00:00
|
|
|
svg.querySelectorAll('.graph > text').forEach(el => el.setAttribute('fill','#e0e0e0'));
|
|
|
|
|
svg.querySelectorAll('.cluster > text').forEach(el => el.setAttribute('fill','#1a1a1a'));
|
|
|
|
|
svg.querySelectorAll('.edge text').forEach(el => el.setAttribute('fill','#ffb74d'));
|
2026-02-07 10:36:45 +00:00
|
|
|
svg.querySelectorAll('.node text').forEach(el => el.setAttribute('fill','#1a1a1a'));
|
|
|
|
|
|
|
|
|
|
// ─── Click-to-focus ────────────────────────────────────────────────────────
|
|
|
|
|
const edges = svg.querySelectorAll('.edge');
|
|
|
|
|
const nodes = svg.querySelectorAll('.node');
|
|
|
|
|
const clusterChrome = [];
|
|
|
|
|
svg.querySelectorAll('.cluster').forEach(c => {
|
|
|
|
|
c.querySelectorAll(':scope > path, :scope > polygon, :scope > text').forEach(el => clusterChrome.push(el));
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const nodeByTitle = new Map();
|
|
|
|
|
nodes.forEach(n => {
|
|
|
|
|
const t = n.querySelector('title');
|
|
|
|
|
if (t) nodeByTitle.set(t.textContent.trim(), n);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const nodeToClusterEls = new Map();
|
|
|
|
|
svg.querySelectorAll('.cluster').forEach(cluster => {
|
|
|
|
|
const chrome = [...cluster.querySelectorAll(':scope > path, :scope > polygon, :scope > text')];
|
|
|
|
|
cluster.querySelectorAll('.node title').forEach(t => {
|
|
|
|
|
nodeToClusterEls.set(t.textContent.trim(), chrome);
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const adj = new Map();
|
|
|
|
|
edges.forEach(edge => {
|
|
|
|
|
const t = edge.querySelector('title');
|
|
|
|
|
if (!t) return;
|
|
|
|
|
const parts = t.textContent.trim().split('->').map(s => s.trim());
|
|
|
|
|
if (parts.length !== 2) return;
|
|
|
|
|
const [src, dst] = parts;
|
|
|
|
|
if (!adj.has(src)) adj.set(src, { edges: [], neighbors: new Set() });
|
|
|
|
|
if (!adj.has(dst)) adj.set(dst, { edges: [], neighbors: new Set() });
|
|
|
|
|
adj.get(src).edges.push(edge);
|
|
|
|
|
adj.get(src).neighbors.add(dst);
|
|
|
|
|
adj.get(dst).edges.push(edge);
|
|
|
|
|
adj.get(dst).neighbors.add(src);
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
const DIM = 0.08;
|
|
|
|
|
let focused = null;
|
|
|
|
|
|
|
|
|
|
function clearFocus() {
|
|
|
|
|
focused = null;
|
|
|
|
|
nodes.forEach(n => n.style.opacity = '');
|
|
|
|
|
edges.forEach(e => e.style.opacity = '');
|
|
|
|
|
clusterChrome.forEach(el => el.style.opacity = '');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function focusNode(title) {
|
|
|
|
|
if (focused === title) { clearFocus(); return; }
|
|
|
|
|
focused = title;
|
|
|
|
|
const info = adj.get(title) || { edges: [], neighbors: new Set() };
|
|
|
|
|
const connected = new Set([title, ...info.neighbors]);
|
|
|
|
|
|
|
|
|
|
nodes.forEach(n => n.style.opacity = DIM);
|
|
|
|
|
edges.forEach(e => e.style.opacity = DIM);
|
|
|
|
|
clusterChrome.forEach(el => el.style.opacity = DIM);
|
|
|
|
|
|
|
|
|
|
connected.forEach(name => {
|
|
|
|
|
const el = nodeByTitle.get(name);
|
|
|
|
|
if (el) el.style.opacity = 1;
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
info.edges.forEach(e => e.style.opacity = 1);
|
|
|
|
|
|
|
|
|
|
const seen = new Set();
|
|
|
|
|
connected.forEach(name => {
|
|
|
|
|
const chrome = nodeToClusterEls.get(name);
|
|
|
|
|
if (chrome) chrome.forEach(el => {
|
|
|
|
|
if (!seen.has(el)) { seen.add(el); el.style.opacity = 1; }
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
nodes.forEach(node => {
|
|
|
|
|
node.style.cursor = 'pointer';
|
|
|
|
|
node.addEventListener('click', e => {
|
|
|
|
|
e.stopPropagation();
|
|
|
|
|
const t = node.querySelector('title');
|
|
|
|
|
if (t) focusNode(t.textContent.trim());
|
|
|
|
|
});
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ─── Pan & zoom ────────────────────────────────────────────────────────────
|
|
|
|
|
let scale = 1, tx = 0, ty = 0, dragging = false, didDrag = false, sx = 0, sy = 0;
|
|
|
|
|
function applyTransform() { svg.style.transform = `translate(${tx}px,${ty}px) scale(${scale})`; svg.style.transformOrigin = '0 0'; }
|
|
|
|
|
|
|
|
|
|
window.resetView = function() {
|
|
|
|
|
const vw = vp.clientWidth, vh = vp.clientHeight;
|
|
|
|
|
const bb = svg.getBBox();
|
|
|
|
|
scale = Math.min(vw / bb.width, vh / bb.height) * 0.92;
|
|
|
|
|
tx = (vw - bb.width * scale) / 2;
|
|
|
|
|
ty = (vh - bb.height * scale) / 2;
|
|
|
|
|
applyTransform();
|
|
|
|
|
};
|
2026-02-09 19:05:37 +00:00
|
|
|
let dagFitted = false;
|
|
|
|
|
window.addEventListener('dag-visible', () => {
|
|
|
|
|
if (!dagFitted) { dagFitted = true; requestAnimationFrame(resetView); }
|
|
|
|
|
});
|
2026-02-07 10:36:45 +00:00
|
|
|
|
|
|
|
|
window.zoomIn = function() { scale *= 1.3; applyTransform(); };
|
|
|
|
|
window.zoomOut = function() { scale *= 0.7; applyTransform(); };
|
|
|
|
|
|
|
|
|
|
vp.addEventListener('wheel', e => { e.preventDefault(); const f = e.deltaY < 0 ? 1.12 : 0.89; const rect = vp.getBoundingClientRect(); const mx = e.clientX - rect.left; const my = e.clientY - rect.top; tx = mx - f * (mx - tx); ty = my - f * (my - ty); scale *= f; applyTransform(); }, { passive:false });
|
|
|
|
|
vp.addEventListener('pointerdown', e => { dragging=true; didDrag=false; sx=e.clientX-tx; sy=e.clientY-ty; vp.setPointerCapture(e.pointerId); });
|
|
|
|
|
vp.addEventListener('pointermove', e => { if(!dragging) return; didDrag=true; tx=e.clientX-sx; ty=e.clientY-sy; applyTransform(); });
|
|
|
|
|
vp.addEventListener('pointerup', () => dragging=false);
|
|
|
|
|
vp.addEventListener('click', e => { if (!didDrag && !e.target.closest('.node')) clearFocus(); });
|
|
|
|
|
</script>
|
|
|
|
|
</body></html>
|