diff --git a/tradein-mvp/frontend/src/app/providers.tsx b/tradein-mvp/frontend/src/app/providers.tsx index 38c3cbe9..47211624 100644 --- a/tradein-mvp/frontend/src/app/providers.tsx +++ b/tradein-mvp/frontend/src/app/providers.tsx @@ -11,13 +11,24 @@ export function Providers({ children }: { children: React.ReactNode }) { queries: { staleTime: 5 * 60_000, refetchOnWindowFocus: false, - // #770 finding #26: не ретраить 4xx (включая 429) — иначе на rate-limit - // дефолтные 3 ретрая превращаются в шторм. Ретраим только 5xx/сеть, ≤2 раза. + // #800: errored query + дефолтный refetchOnMount=true → каждый новый + // observer (Topbar/UserMenu/useBrand все дёргают useMe) ре-фетчит при + // mount → re-subscribe storm на стойком 401. Выключаем глобально: данные + // и так staleTime-кэшируются, а auth/quota не должны бить бэк на ремаунт. + refetchOnMount: false, + refetchOnReconnect: false, + // #770 finding #26 + #800: не ретраить 4xx (401/403/429) — иначе на + // rate-limit/протухшей сессии дефолтные 3 ретрая превращаются в шторм + // в собственный бэкенд. Ретраим только 5xx/сеть, ≤2 раза. retry: (failureCount, error) => { const status = (error as { status?: number } | null)?.status; if (typeof status === "number" && status >= 400 && status < 500) return false; return failureCount < 2; }, + // #800: экспоненциальный backoff на сетевых/5xx ретраях (1s, 2s, 4s… + // capped 30s) — никаких немедленных повторов, мягко к бэкенду. + retryDelay: (attemptIndex) => + Math.min(1000 * 2 ** attemptIndex, 30_000), }, mutations: { retry: false, diff --git a/tradein-mvp/frontend/src/lib/useQuota.ts b/tradein-mvp/frontend/src/lib/useQuota.ts index e5f60b7d..8545eec1 100644 --- a/tradein-mvp/frontend/src/lib/useQuota.ts +++ b/tradein-mvp/frontend/src/lib/useQuota.ts @@ -10,6 +10,11 @@ const BASE = "/api/v1/trade-in"; * GET /api/v1/trade-in/quota * Fetches the monthly evaluation quota status for the current session/account. * Fail-open: if the query errors, the UI must NOT block the user (treat as unlimited). + * + * #800: `retry:false` — на 401/протухшей сессии quota НЕ должна ре-стучаться в бэк. + * `refetchOnMount/Reconnect/WindowFocus:false` — иначе errored quota ре-фетчит на + * каждый ремаунт владельца → вклад в retry-storm. Освежается явным + * `invalidateQueries(["trade-in","quota"])` после успешной оценки (см. page.tsx). */ export function useQuota() { return useQuery({ @@ -17,5 +22,8 @@ export function useQuota() { queryFn: () => apiFetch(`${BASE}/quota`), staleTime: 30_000, retry: false, + refetchOnMount: false, + refetchOnReconnect: false, + refetchOnWindowFocus: false, }); }