Skip to content

Commit 7d7ec83

Browse files
vakovalskiiclaude
andauthored
feat: anonymous heartbeat + network stats on leaderboard (#114)
- Server sends anonymous heartbeat on startup (anonId, version, platform, agent counts) - Worker tracks active installs (48h TTL) and daily active users - Public leaderboard shows: on leaderboard / vibe coders online / active today - codedash UI shows network stats bar - Updated Worker HTML with tabs (Today/Week/All Time), GitHub links, agent badges - Week stats sent from client for weekly ranking - Export getOrCreateAnonId from data.js Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent a05f6bf commit 7d7ec83

4 files changed

Lines changed: 57 additions & 0 deletions

File tree

src/data.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2403,6 +2403,7 @@ module.exports = {
24032403
loadSessionDetail,
24042404
getProjectGitInfo,
24052405
getLeaderboardStats,
2406+
getOrCreateAnonId,
24062407
deleteSession,
24072408
getGitCommits,
24082409
exportSessionMarkdown,

src/frontend/app.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2729,6 +2729,14 @@ async function loadGlobalLeaderboard() {
27292729
try {
27302730
var resp = await fetch('/api/leaderboard/remote');
27312731
_lbRemoteData = await resp.json();
2732+
// Show network stats
2733+
var net = _lbRemoteData.network || {};
2734+
var netEl = document.getElementById('networkStats');
2735+
if (netEl) {
2736+
netEl.innerHTML = '<span>' + (_lbRemoteData.totalUsers || 0) + ' on leaderboard</span>' +
2737+
'<span>' + (net.totalInstalls || 0) + ' vibe coders online</span>' +
2738+
'<span>' + (net.todayActive || 0) + ' active today</span>';
2739+
}
27322740
renderGlobalBoard();
27332741
} catch { if (board) board.innerHTML = '<div style="text-align:center;padding:20px;color:var(--text-muted)">Could not load global leaderboard</div>'; }
27342742
}
@@ -2882,6 +2890,9 @@ async function renderLeaderboard(container) {
28822890
}
28832891

28842892
// Global leaderboard
2893+
// Network stats
2894+
html += '<div class="lb-network" id="networkStats"><span>Loading network...</span></div>';
2895+
28852896
// Global leaderboard with tabs
28862897
html += '<div class="lb-section-title">Global Leaderboard</div>';
28872898
html += '<div class="lb-tabs">';

src/frontend/styles.css

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2085,6 +2085,20 @@ body {
20852085
user-select: all;
20862086
}
20872087

2088+
.lb-network {
2089+
display: flex;
2090+
justify-content: center;
2091+
gap: 24px;
2092+
padding: 12px 16px;
2093+
background: linear-gradient(135deg, rgba(99,102,241,0.08), rgba(168,85,247,0.08));
2094+
border: 1px solid rgba(139,92,246,0.2);
2095+
border-radius: 10px;
2096+
margin: 16px 0;
2097+
font-size: 13px;
2098+
color: var(--text-muted);
2099+
}
2100+
.lb-network span { font-weight: 600; }
2101+
20882102
.lb-sync-bar {
20892103
text-align: center;
20902104
margin: 20px 0;

src/server.js

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -437,9 +437,40 @@ function startServer(host, port, openBrowser = true) {
437437
exec(`xdg-open ${browserUrl}`);
438438
}
439439
}
440+
441+
// Anonymous heartbeat — count active installs
442+
sendHeartbeat();
440443
});
441444
}
442445

446+
function sendHeartbeat() {
447+
try {
448+
const { getOrCreateAnonId } = require('./data');
449+
const anon = getOrCreateAnonId();
450+
const pkg = require('../package.json');
451+
const sessions = require('./data').loadSessions();
452+
const agentCounts = {};
453+
sessions.forEach(s => { agentCounts[s.tool] = (agentCounts[s.tool] || 0) + 1; });
454+
455+
const body = JSON.stringify({
456+
anonId: anon.id,
457+
version: pkg.version,
458+
platform: process.platform,
459+
agents: agentCounts,
460+
});
461+
462+
const req = https.request({
463+
hostname: 'codedash-leaderboard.valeriy.workers.dev',
464+
path: '/api/heartbeat', method: 'POST',
465+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(body) },
466+
timeout: 5000,
467+
});
468+
req.on('error', () => {}); // silent fail
469+
req.write(body);
470+
req.end();
471+
} catch {}
472+
}
473+
443474
// ── Helpers ─────────────────────────────────
444475
function json(res, data, status = 200) {
445476
res.writeHead(status, { 'Content-Type': 'application/json' });

0 commit comments

Comments
 (0)