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);