Client-Side JavaScript Audit
F12 console error analysis — event listener leaks, timer leaks, null crashes, and View Transitions issues across all 122 page scripts
Client-Side JavaScript Audit
Full source code audit of all 122 .astro files with client-side <script> blocks, searching for patterns that produce browser DevTools (F12) console errors. Covers admin pages, playground, command center, dashboard, and public pages.
Findings Summary
| Issue | Count | Severity |
|---|---|---|
| Global event listener leaks | 11 files | Critical |
| Timer/interval leaks | 13 files | Critical |
| Missing null checks (querySelector) | 9+ files | Error |
| Non-null assertions (getElementById) | 10+ files (~80 instances) | Warning |
| fetch() without try/catch | 16 files (~25 calls) | Warning |
| No route guard on astro:page-load | 85 pages | Warning |
| DOMContentLoaded violations | 0 | Clear |
Event Listener Leaks
The Problem
document.addEventListener('keydown', handler) inside is:inline scripts re-executes on every View Transitions navigation. Each navigation adds another copy of the same handler. After 5 navigations, you have 5 duplicate keydown handlers all firing.
Similarly, handlers added inside astro:page-load callbacks accumulate because the callback fires on every navigation.
Affected Files
| File | Line(s) | Leak |
|---|---|---|
admin/edit.astro | 3159, 6173, 6185 | 3 keydown handlers per navigation |
admin/pipeline.astro | 1460 | Ctrl+S stacks |
admin/homelab.astro | 2155 | Escape stacks |
admin/mm-terminal.astro | 184 | Escape stacks |
admin/docs-hub.astro | 260 | Escape stacks |
admin/dashboard-profiles/edit/[id].astro | 876 | Escape stacks |
admin/chat.astro | 1532 | Ctrl+N persists globally |
admin/workbench.astro | 4033 | Ctrl+N persists globally |
admin/personal.astro | 1435 | Ctrl+N persists globally |
admin/probe-studio.astro | 1340 | Global click handler stacks |
admin/content-lab.astro | 658+ | querySelectorAll handlers stack |
Fix Pattern
Use a dedup flag or named function with cleanup:
// Option A: dedup flag
if (!window.__editKeydownBound) {
window.__editKeydownBound = true;
document.addEventListener('keydown', handleKeydown);
}
// Option B: cleanup in astro:before-swap
document.addEventListener('astro:before-swap', () => {
document.removeEventListener('keydown', handleKeydown);
}, { once: true });
Timer/Interval Leaks
The Problem
setInterval() or recursive setTimeout() that keeps running after the user navigates away. The old timer fires on a different page, trying to update DOM elements that don’t exist.
No Cleanup At All (7 pages)
| File | Intervals | Context |
|---|---|---|
404.astro | 2 | Animation timers |
playground/build-swarm.astro | 5 | Simulation timers |
playground/monitoring.astro | 2 | Demo timers |
playground/infrastructure.astro | 1 | Animation timer |
command/space.astro | 3 | Animation timers |
command/portal.astro | 1 | Animation timer |
projects/services.astro | 1 | Polling timer |
Partial Cleanup (6 pages)
These have astro:before-swap cleanup but miss some timers:
| File | Missed Timer | What’s Cleaned |
|---|---|---|
admin/build.astro | BS.pollTimer | Nothing |
admin/edit.astro | S.autoSaveTimer | Nothing |
admin/rag.astro | ollamaPollTimer | pollTimer |
admin/jobs.astro | retryTimer, local poll | pollInterval |
admin/servers/index.astro | anonymous interval | N/A (no VT layout) |
Fix Pattern
// Store all timers
let pollTimer, retryTimer;
// Cleanup ALL timers on navigation
document.addEventListener('astro:before-swap', () => {
clearInterval(pollTimer);
clearInterval(retryTimer);
}, { once: true });
Null Reference Crashes
querySelector Without Null Checks
Direct property access on querySelector() results — crashes with TypeError: Cannot read properties of null.
| File | Line | Code |
|---|---|---|
admin/index.astro | 1810 | .querySelector('.filter-btn.active').dataset.filter |
admin/index.astro | 1931 | .querySelector('.table-container').scrollIntoView(...) |
admin/pipeline.astro | 616, 621 | .querySelector('.pipe-stage').classList |
admin/network-map.astro | 514, 521, 535 | querySelector('input:checked').value |
admin/security.astro | 491-494 | .querySelector('.sec-stat-value').textContent |
getElementById Non-null Assertions
TypeScript ! assertions are erased at runtime — no actual null check:
admin/chat.astro— 15 instancesadmin/workbench.astro— 15+admin/settings.astro— 14admin/build-swarm-public-v3.astro— 10+
Fix Pattern
// Use optional chaining
document.querySelector('.filter-btn.active')?.dataset?.filter;
// Or assign with guard
const el = document.getElementById('myEl');
if (!el) return;
el.textContent = 'value';
Unhandled Fetch Errors
16 files have await fetch() without try/catch. Network errors produce unhandled promise rejections.
Files: admin/bland.astro, admin/build-swarm.astro, admin/cloudflare/dns.astro, admin/edit.astro, admin/elevenlabs.astro, admin/email.astro, admin/index.astro, admin/linkedin-studio.astro, admin/network-map.astro, admin/pipeline.astro, admin/servers/index.astro, admin/settings.astro, admin/twilio.astro, admin/vapi.astro, admin/workbench.astro, dashboard/index.astro
Missing Route Guards
85 pages have astro:page-load handlers with fetches or timers but no window.location.pathname check. Their initialization logic fires on every View Transitions navigation, even when the user is on a completely different page.
Top offenders:
| File | Actions Firing |
|---|---|
command/build.astro | 42 |
admin/workbench.astro | 28 |
admin/probe-studio.astro | 15 |
dashboard/index.astro | 14 |
playground/build-swarm.astro | 13 |
Fix Pattern
document.addEventListener('astro:page-load', () => {
if (!window.location.pathname.startsWith('/admin/health')) return;
// ... rest of init
});
Pages With Proper Cleanup
These pages correctly implement cleanup and serve as reference:
| Page | Pattern Used |
|---|---|
admin/deployments.astro | astro:before-swap + { once: true } |
admin/ollama.astro | Multiple timers all in cleanup |
admin/playground.astro | destroyed flag pattern |
admin/workbench.astro | Cleanup for timers + AbortController |
admin/health.astro | Timer cleanup |
admin/system-test.astro | Dedup guard + route check |
Fix Priority
- Timer leaks in admin pages — cause real network requests to wrong endpoints
- Global keydown leaks — cause duplicate keyboard shortcuts firing
- Null querySelector crashes — cause visible F12 errors
- Route guards — low effort, high impact
- Non-null assertions — lower priority (only crash if HTML changes)
- fetch error handling — lowest (only affects network failures)
Related
| Item | Value |
|---|---|
| Audit date | 2026-02-28 |
| Files scanned | 122 .astro files with script blocks |
| Method | Source code pattern analysis |
| Vault reference | infrastructure/24-CLIENT-SIDE-AUDIT-2026-02-28.md |
| Previous audits | Site Health Test, System Test |