fix: speedrun scores, dashboard icons, N+1 queries, typeof guards

This commit is contained in:
Jeremy Brandenburger
2026-03-31 09:05:33 +02:00
parent 2d27d9fe4d
commit 8a5d08b586
3 changed files with 66 additions and 57 deletions
+33 -51
View File
@@ -364,11 +364,11 @@ async function loadDashboard() {
}
function renderDashboard({ stats, rank, elo, eloRank: rankLabel, online, recent, daily }) {
const r = eloRankFor(elo || 1000);
const myRank = eloRankFor(elo || 1000);
setText('dash-hero-name', state.user.user);
setText('dash-rank-label', rankLabel || r.label);
setText('dash-rank-label', rankLabel || myRank.label);
setText('dash-elo', elo || 1000);
setText('dash-rank-icon', r.icon);
setText('dash-rank-icon', myRank.icon);
setText('dash-total-score', stats.totalScore || 0);
setText('dash-rank', rank ? '#' + rank.position : 'Unranked');
@@ -389,14 +389,15 @@ function renderDashboard({ stats, rank, elo, eloRank: rankLabel, online, recent,
if (!recent?.length) {
tbody.innerHTML = '<tr><td colspan="4" class="muted">No sessions yet</td></tr>';
} else {
tbody.innerHTML = recent.map(r =>
`<tr>
<td><img class="stratagem-icon-sm" src="${esc(r.icon || '')}" alt="" ${r.icon ? '' : 'style="display:none"'}>${esc(r.stratagem)}</td>
tbody.innerHTML = recent.map(r => {
const icon = state.stratagems.find(s => s.name === r.stratagem)?.icon || '';
return `<tr>
<td><img class="stratagem-icon-sm" src="${esc(icon)}" alt="" ${icon ? '' : 'style="display:none"'}>${esc(r.stratagem)}</td>
<td><span class="badge">${esc(r.mode || 'timed')}</span></td>
<td>${r.score}</td>
<td>${(r.time_ms / 1000).toFixed(2)}s</td>
</tr>`
).join('');
</tr>`;
}).join('');
}
updateDashboardOnline(online);
@@ -412,23 +413,18 @@ function renderDailySequencePreview(sequence) {
function updateDashboardOnline(online) {
const el = document.getElementById('dash-online');
if (!el) return;
const players = (online || []).filter(u => {
const name = typeof u === 'object' ? u.name : u;
return name !== state.user?.user;
});
const players = (online || []).filter(u => u.name !== state.user?.user);
if (!players.length) {
el.innerHTML = '<span class="muted">No other Helldivers online</span>';
} else {
el.innerHTML = players.map(u => {
const name = typeof u === 'object' ? u.name : u;
const elo = typeof u === 'object' ? u.elo : '';
return `<div class="online-user">
el.innerHTML = players.map(u =>
`<div class="online-user">
<span class="online-dot"></span>
<span style="flex:1;font-family:var(--font-mono)">${esc(name)}</span>
${elo ? `<span class="player-elo">${elo}</span>` : ''}
<button class="btn btn-sm btn-accent" data-action="challenge" data-user="${esc(name)}">⚔ Challenge</button>
</div>`;
}).join('');
<span style="flex:1;font-family:var(--font-mono)">${esc(u.name)}</span>
${u.elo ? `<span class="player-elo">${u.elo}</span>` : ''}
<button class="btn btn-sm btn-accent" data-action="challenge" data-user="${esc(u.name)}">⚔ Challenge</button>
</div>`
).join('');
}
}
@@ -602,19 +598,13 @@ function nextStratagem() {
if (!p.speedrunPool.length) {
const totalMs = Date.now() - p.speedrunStart;
clearInterval(p.timerHandle);
api('POST', '/scores/practice', {
stratagem: '__speedrun__',
category: 'All',
time_ms: totalMs,
score: p.score,
mode: 'speedrun',
}).catch(() => {});
showToast(`Speedrun complete! ${(totalMs / 1000).toFixed(2)}s`);
openSessionSummary();
return;
}
strat = p.speedrunPool[0];
} else {
// timed & endless: pick random from pool
const pool = getPool();
if (!pool.length) { showPracticeIdle(); return; }
strat = pool[Math.floor(Math.random() * pool.length)];
@@ -836,15 +826,13 @@ function handlePracticeInput(dir) {
// Score popup
showScorePopup('+' + pts);
if (mode !== 'speedrun') {
api('POST', '/scores/practice', {
stratagem: p.current.name,
category: p.current.category,
time_ms: elapsed,
score: pts,
mode: mode,
}).catch(() => {});
}
api('POST', '/scores/practice', {
stratagem: p.current.name,
category: p.current.category,
time_ms: elapsed,
score: pts,
mode: mode,
}).catch(() => {});
if (mode === 'drill') {
p.drillPool.shift();
@@ -995,10 +983,7 @@ document.getElementById('btn-summary-restart')?.addEventListener('click', () =>
// ── Lobby ─────────────────────────────────────────────────────────────────────
function updateLobbyView() {
const others = state.lobby.online.filter(u => {
const name = typeof u === 'object' ? u.name : u;
return name !== state.user?.user;
});
const others = state.lobby.online.filter(u => u.name !== state.user?.user);
const el = document.getElementById('lobby-players');
if (!el) return;
@@ -1008,17 +993,14 @@ function updateLobbyView() {
<p>No other Helldivers online.<br>Waiting for reinforcements...</p>
</div>`;
} else {
el.innerHTML = others.map(u => {
const name = typeof u === 'object' ? u.name : u;
const elo = typeof u === 'object' ? u.elo : '';
const rank = typeof u === 'object' ? u.rank : '';
return `<div class="lobby-player">
el.innerHTML = others.map(u =>
`<div class="lobby-player">
<span class="online-dot"></span>
<span class="player-name">${esc(name)}</span>
${elo ? `<span class="player-elo">${esc(rank)} · ${elo}</span>` : ''}
<button class="btn btn-sm btn-accent" data-action="challenge" data-user="${esc(name)}">⚔ Challenge</button>
</div>`;
}).join('');
<span class="player-name">${esc(u.name)}</span>
${u.elo ? `<span class="player-elo">${esc(u.rank)} · ${u.elo}</span>` : ''}
<button class="btn btn-sm btn-accent" data-action="challenge" data-user="${esc(u.name)}">⚔ Challenge</button>
</div>`
).join('');
}
const challEl = document.getElementById('lobby-challenges');