# 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`, например: ```ts 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. 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](../../src/entities/portfolio/api/usePortfolio.ts) — entity query; [shared client](../../src/shared/api/client.ts) — transport/auth boundary. Изменяя header/retry/invalidation, проверяй наблюдаемое поведение через MSW и [Testing](testing.md); не обращайся к реальному T-Bank. `npm run openapi:check`, `npm run typecheck` и targeted tests проверяют разные части решения. Для поведения API-клиента используй сценарии MSW на публичной HTTP-границе; реализация сервера не является входом frontend-проверок.