perf(analytics): project minimal columns in GetStats/GetAnalyticsOverview #3

Merged
cesnimda merged 13 commits from perf/wave1-perf into main 2026-07-05 21:18:56 +02:00
2 changed files with 18 additions and 1 deletions
Showing only changes of commit 490c5b803e - Show all commits
@@ -5,8 +5,19 @@
## Changes made (this pass) ## Changes made (this pass)
| Change | File | Effect | Verified | | Change | File | Effect | Verified |
|---|---|---|---| |---|---|---|---|
| **Stop the infinite `/auth/me` request loop** — make `clearAuthClientState` emit `auth-changed` only on a real signed-in→out transition | `job-tracker-ui/src/auth.ts` | Eliminates a runaway request storm (100+ `GET /auth/me` and climbing) that ran continuously whenever the user was logged out | **Runtime-confirmed** in a live stack: `/auth/me` count 100+ & growing → 0 and stable after fix |
| Revoke CV-preview blob URLs on unmount only (ref-based), not on every carousel change | `job-tracker-ui/src/pages/ProfilePage.tsx` | Fixes broken previews on multi-template decks; still frees URLs on unmount | `profile-page.test.tsx` 5/5 | | Revoke CV-preview blob URLs on unmount only (ref-based), not on every carousel change | `job-tracker-ui/src/pages/ProfilePage.tsx` | Fixes broken previews on multi-template decks; still frees URLs on unmount | `profile-page.test.tsx` 5/5 |
### Runtime finding — self-triggering auth loop (the most impactful issue found)
Only visible with a running backend (static analysis could not surface it). Sequence: the axios response
interceptor (`api.ts`) calls `clearAuthClientState()` on **every** 401; that dispatched `"auth-changed"`;
the `App` handler re-fetched `/auth/me`; that 401'd again → interceptor → `clearAuthClientState()`
`"auth-changed"` → … an unbounded loop that hammered the server and spun the client on the login page and
after any session expiry. Fix: `clearAuthClientState` now only emits when it actually removes a stored user
key (idempotent), so repeated 401s can't re-trigger the fetch. This is a CPU/network/battery drain and a
self-inflicted request flood, not a memory leak — but squarely in the Phase 3.5 "infinite polling / retry
loop / duplicate requests" scope, and the single highest-value fix from the whole investigation.
> Context: this was the only defect found in a full resource audit. The codebase already practises > Context: this was the only defect found in a full resource audit. The codebase already practises
> disciplined cleanup (timers cleared, listeners removed, object URLs revoked), so there was no leak to > disciplined cleanup (timers cleared, listeners removed, object URLs revoked), so there was no leak to
> fix — see the main report. > fix — see the main report.
+7 -1
View File
@@ -82,8 +82,14 @@ export function setAuthUserKey(value: string | null | undefined, emit = true) {
} }
export function clearAuthClientState(emit = true) { export function clearAuthClientState(emit = true) {
// Only emit "auth-changed" when this call actually transitions from
// "signed in" to "signed out". The response interceptor calls this on every
// 401; without this guard each 401 re-dispatches "auth-changed", which
// re-fetches /auth/me, which 401s again — an infinite request loop whenever
// the user is logged out (login page, expired session).
const had = safeGet(window.localStorage, AUTH_USER_KEY) != null;
safeRemove(window.localStorage, AUTH_USER_KEY); safeRemove(window.localStorage, AUTH_USER_KEY);
if (emit) emitAuthChanged(); if (emit && had) emitAuthChanged();
} }
export function getCsrfToken(): string | null { export function getCsrfToken(): string | null {