A backend outage during deploy cleared the session and sent the user to login although the refresh cookie was still valid. Only a definitive 401 ends the session now; network errors and 5xx keep it and show a retry screen.
7.8 KiB
Frontend data
Выбор owner
Server state принадлежит TanStack Query, form state — React Hook Form, локальное
UI — React state. Не копируй успешный query result в useState/context для второго
кэша. Domain-specific queries, keys и policies принадлежат вызывающему slice;
общий transport — shared/api. Page VM выбирает inputs и собирает presentation data.
Используй apiClient/apiQueryClient из shared public API. Прямой fetch в
pages/features/entities и прямой T-Bank call запрещены. Сначала найди
существующий query того же endpoint; расширяй его owner, не создавай второй key
или глобальную query policy для одной страницы.
Типы и contract
Backend-owned OpenAPI contract поступает во frontend как предоставленный
актуальный snapshot; generated schema.d.ts производен от него. Тип
request/response бери из generated paths, например:
type User = paths["/api/v1/auth/me"]["get"]["responses"][200]["content"]["application/json"];
Domain/UI type допустим, когда он отличается по смыслу, а не копирует transport DTO.
Generated TS вручную не исправляется. Не добавляй параллельный fetch/axios
client, runtime schema, hash/checksum registry или дополнительный drift checker
без отдельной текущей потребности; existing OpenAPI flow уже владеет генерацией. Frontend-команды не читают соседний
backend checkout и не проверяют backend runtime OpenAPI; изменение wire
shape/status/route сохраняет
публичный контракт предоставленного snapshot.
Одна UI-модель не требует менять API. openapi:generate работает offline от snapshot;
openapi:check запускает native --check того же CLI и падает при устаревших
generated types, не меняя tracked files.
Auth и invalidation
Bearer header принадлежит shared authenticated fetch; query/mutation не задают
Authorization. Auth endpoints исключены из Bearer/retry middleware. Refresh
координируется single-flight; после 401 с UNAUTHORIZED возможен один retry.
Не добавляй собственный refresh/retry loop в caller.
Refresh, завершившийся 401, очищает session. Сетевая ошибка и 5xx
(например, деплой backend) не доказывают конец session: она сохраняется,
unavailable показывает retry-экран вместо login, а следующий 401 снова
запускает refresh. Retry/backoff внутри refresh не добавляется.
Cookie-writing requests упорядочены только в текущей вкладке. Cross-tab auth synchronization не требуется; допустим reload/re-login. Web Locks и отдельные token stores не добавляются.
Protected queries используют existing session-aware boundary; logout очищает
session-scoped cache. Access token хранится только в памяти, refresh передаётся
httpOnly cookie с credentials: include. Не сохраняй tokens в localStorage,
URL, логах или fixtures. Logout очищает локальную session/cache и при ошибке
сети; до окончания auth bootstrap показывай neutral state. Перед auth change
прочитай shared client, session owner и их tests.
Успешная connection mutation коммитит подтверждённый status и сбрасывает financial
GET subtree; ошибка mutation не меняет generation/cache. Это отдельная граница
от обычного фонового refetch. Даже повторное сохранение того же token начинает
новую generation; поздний ответ прежней generation не попадает в новый кэш.
Отмена/reset принадлежат TanStack Query и generated queryFn с AbortSignal,
без HTTP request registry, URL-wide latest-read counters и access epochs.
Успешный HTTP с data: null остаётся query success; readiness выбирается по
data/metadata, без подмены query status и отдельного transport state.
Status синхронизации содержит только состояние работы. Resources читаются
через sync=observe во время работы и после завершения, без hash comparison.
Согласованность income entries/groups обеспечивает backend; frontend не
дублирует её runtime validator.
Mutations используют существующий generated client и query-key owner; после успеха обновляй/invalidируй только связанные данные. Optimistic update нужен конкретному UX и обязан иметь rollback/error behavior.
Default focus refetch выключен; freshness и retry задаются существующими
query options у entity/feature owner. После staleTime повторное открытие
экрана может запустить background refetch; focus сам по себе его не запускает.
При обычном refetch сохраняй Client Cached Snapshot, при session/connection
смене не показывай данные прежнего владельца. UI presentation — в UI convention.
Errors
Извлекай code через existing apiErrorCode из shared/api. Backend Problem
Details code задаёт machine behavior; user-facing текст принадлежит i18n.
Network/unknown error получает безопасное локальное сообщение и retry,
не raw exception text. Input/domain failure сохраняет ввод и показывает
ошибку у ответственного form/section owner; не маскируй её успешным результатом.
Если одна группа API codes требует одинакового recovery в нескольких views,
сведи классификацию у ближайшего общего owner. Не размножай magic comparisons
и используй ApiErrorCode для известных значений: generated code остаётся string.
Неизвестный новый code должен сохранять fallback. Рефакторинг существующих
разных recovery paths требует behavior proof, а не общей обработки всех ошибок.
Примеры и проверка
usePortfolio — entity query;
shared client — transport/auth boundary.
Изменяя header/retry/invalidation, проверяй наблюдаемое поведение через MSW и
Testing; не обращайся к реальному T-Bank.
npm run openapi:check, npm run typecheck и targeted tests проверяют разные
части решения. Для поведения API-клиента используй сценарии MSW на публичной
HTTP-границе; реализация сервера не является входом frontend-проверок.