Use Query cancellation and native mutation results instead of HTTP registries, access epochs and copied scope state. Keep local auth ordering and mutation identity fences. Allow staged generated files to remain excluded from ESLint. Backend contract: WealthLedger/backend@d698ed9 BREAKING CHANGE: drop investment stateVersion/progress/resources from the client contract and use data/metadata readiness instead of rewritten query status. Cross-tab auth may require re-login.
7.3 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 генерирует во временный каталог и сравнивает, не меняя 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 — 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-проверок.