diff --git a/public/analytics.js b/public/analytics.js index c7e26b31..99007090 100644 --- a/public/analytics.js +++ b/public/analytics.js @@ -784,10 +784,15 @@ ${data.subpaths.map((s, i) => { const barW = Math.max(2, Math.round(s.count / maxCount * 100)); const hops = s.path.split(' → '); + const rawHops = s.rawHops || []; const hasSelfLoop = hops.some((h, j) => j > 0 && h === hops[j - 1]); + const routeDisplay = hops.map((h, j) => { + const prefix = rawHops[j] || ''; + return `${esc(h)} [${esc(prefix)}]`; + }).join(' → '); return ` ${i + 1} - ${esc(s.path)}${hasSelfLoop ? ' 🔄' : ''} + ${routeDisplay}${hasSelfLoop ? ' 🔄' : ''} ${s.count.toLocaleString()} ${s.pct}%
diff --git a/public/style.css b/public/style.css index 31fc9f4b..79dcd233 100644 --- a/public/style.css +++ b/public/style.css @@ -1150,3 +1150,6 @@ button.ch-item.ch-item-encrypted .ch-badge { filter: grayscale(0.6); } /* Self-loop subpath rows */ .subpath-selfloop { opacity: 0.6; } .subpath-selfloop td:first-child::after { content: ''; } + +/* Hop prefix in subpath routes */ +.hop-prefix { color: #9ca3af; font-size: 0.8em; } diff --git a/server.js b/server.js index d49c75c9..278bc485 100644 --- a/server.js +++ b/server.js @@ -1137,7 +1137,9 @@ app.get('/api/analytics/subpaths', (req, res) => { for (let len = minLen; len <= Math.min(maxLen, named.length); len++) { for (let start = 0; start <= named.length - len; start++) { const sub = named.slice(start, start + len).join(' → '); - subpathCounts[sub] = (subpathCounts[sub] || 0) + 1; + const raw = hops.slice(start, start + len).join(','); + if (!subpathCounts[sub]) subpathCounts[sub] = { count: 0, raw }; + subpathCounts[sub].count++; } } } @@ -1145,11 +1147,12 @@ app.get('/api/analytics/subpaths', (req, res) => { // Sort by frequency, return top results const limit = Number(req.query.limit) || 100; const ranked = Object.entries(subpathCounts) - .map(([path, count]) => ({ + .map(([path, data]) => ({ path, - count, + rawHops: data.raw.split(','), + count: data.count, hops: path.split(' → ').length, - pct: totalPaths > 0 ? Math.round(count / totalPaths * 1000) / 10 : 0 + pct: totalPaths > 0 ? Math.round(data.count / totalPaths * 1000) / 10 : 0 })) .sort((a, b) => b.count - a.count) .slice(0, limit);