docs(ai): route frontend conventions by task

This commit is contained in:
2026-09-09 16:22:47 +03:00
parent 26f229ba3e
commit bcc034f198
14 changed files with 333 additions and 210 deletions
+23
View File
@@ -0,0 +1,23 @@
# Frontend router
Читай строки, которые активирует задача, затронутый путь **или** обнаруженная
граница. Объединяй совпадения; якорь разрешает чтение только нужного раздела.
Не загружай все conventions из-за одного frontend-файла.
| Решение / сигнал | Читать до решения |
| --- | --- |
| Создать/перенести module/component/slice; выбрать page/entity/feature/shared; cross-slice imports | [Architecture](conventions/frontend-architecture.md) |
| Component/props, container/VM/view, form, local state/effect, loading/error/retry, Mantine/UI | [UI](conventions/frontend-ui.md) |
| Только продуктовая строка или Zod message | [Строки](conventions/frontend-ui.md#строки-и-валидация) |
| Только TS declaration/import/export | [Код](conventions/frontend-ui.md#код-и-components) |
| query/mutation, shared/api, server state/cache, generated types, Bearer/refresh | [Data](conventions/frontend-data.md) |
| Тесты или behavior с testing risk | [Testing](conventions/frontend-testing.md) |
| Новый abstraction/dependency, refactor или неоднозначный scope | [Общий выбор реализации](../../docs/development/engineering-governance.md#выбор-реализации) |
| Request/response field, route/status/error shape, OpenAPI snapshot/generated type | [Общий contract workflow](../../docs/development/runtime-openapi-verification.md) |
| Login/refresh/logout, token/cookie, auth ownership | Data + [security](../../backend/.ai/conventions/security-auth.md) + Testing |
| Storybook/theme/tokens/shared UI | UI + точные [источники](SOURCES.md) дизайн-системы/каталога |
UI-only props/presentation требуют UI, без API/structure/testing при отсутствии
их triggers. Показ query state не означает изменение data fetching.
Изменение тестов активирует Testing и без production diff. Нужные project facts
выбирай через SOURCES. Повтори routing при новой границе и по итоговому diff.
+18
View File
@@ -0,0 +1,18 @@
# Источники frontend
| Что нужно установить | Владелец |
| --- | --- |
| Какое implementation-решение принять | [CONVENTIONS](CONVENTIONS.md) |
| HTTP wire contract и направление генерации | Backend runtime OpenAPI → [snapshot/types workflow](../../docs/development/runtime-openapi-verification.md) |
| Error codes/validation/error envelope | [docs: HTTP API](../../docs/AI_INDEX.md#http-api) |
| Auth/session, token storage и refresh | [docs: Security](../../docs/AI_INDEX.md#security) |
| Значение Money, cached snapshot, account/position/income | [docs: Domain](../../docs/AI_INDEX.md#domain) |
| Mantine/theme/tokens и существующие stories | [docs: UI](../../docs/AI_INDEX.md#ui) |
| As-built behavior нужной feature | [docs: Features](../../docs/AI_INDEX.md#features) |
| Существующие behavioral requirements, если нужны по задаче | [specs index](../../specs/AI_INDEX.md) |
| Действующие versions/scripts/check scope | package.json, lockfile, Biome/TS/dependency configs |
Открывай точный source только по возникшему вопросу. Code examples показывают
pattern, но не переопределяют контракт. Specs не являются editable OpenAPI;
SDD-процедура для обычной работы не нужна. При конфликте источников сначала
установи факты, не меняй generated types или security policy по догадке.
+56
View File
@@ -0,0 +1,56 @@
# Frontend architecture
## Выбор владельца
Сначала найди владельца поведения и ближайший пример. Расширяй existing slice,
если ответственность остаётся связной. Размер файла или возможный будущий reuse
не создают новую архитектурную границу.
| Ответственность | Размещение |
| --- | --- |
| Bootstrap, глобальные providers, routes | `app` |
| Композиция маршрута и поведение, нужное только этому экрану | `pages/<page>` |
| Самостоятельная пользовательская возможность с собственным смыслом | `features/<capability>` |
| Представление/поведение бизнес-сущности | `entities/<entity>` |
| Переиспользуемый код без WealthLedger domain meaning | `shared` |
Локальный FSD profile: `app → pages → features → entities → shared`, без `widgets`.
Крупный уникальный блок остаётся в своей page. Form, mutation или click handler
сами по себе не требуют feature. Два callers не делают domain helper generic shared.
Slice именуется по ответственности, а не `helpers`, `hooks` или `types`.
**Безопасный путь:** при сомнении оставь код у ближайшего допустимого owner.
Извлекай его, когда появилась самостоятельная ответственность или реальный reuse.
Не вводи новый layer и не обходи public API, чтобы устранить неудобный import.
## Imports и public API
Cross-slice consumer использует `@/` и `index.ts` владельца, например
`@/entities/portfolio`. Alias Vite и TypeScript согласован. Другой slice не
импортирует внутренний файл напрямую. Направление зависимостей проверяет
`npm run deps`; Knip проверяет использование exports, а не смысл placement.
## UI foundation
Общий visual foundation принадлежит `shared/theme`. `tokens.json` — переносимый
WealthLedger v1 source; `theme.ts` адаптирует доверенный JSON в Mantine без runtime
schema. App и Storybook используют public API `shared/theme/index.ts`: theme,
designTokens, color roles, CSSVariablesResolver и base styles. Внешний consumer
читает JSON напрямую. Mantine defaults и локальные domain styles остаются у владельцев.
`shared/ui` содержит StateView/LoadingSkeleton. Existing route-aware AppShell также
лежит в shared/ui, но не является общим catalog primitive; его расположение не
служит основанием переносить новую domain/navigation логику в shared.
Изменяя foundation, прочитай [design system](../../../docs/development/design-system.md).
## Примеры
- Общая busy-область: существующий [LoadingSkeleton](../../src/shared/ui/LoadingSkeleton.tsx), а не domain widget в shared.
- Account-income state views: [страница](../../src/pages/account-income/ui/AccountIncomePage.tsx) и её flat `components/*.view.tsx`; новый page-local filter остаётся здесь.
- Portfolio query: [entity public API](../../src/entities/portfolio/index.ts); новая UI-сводка с бизнес-смыслом не становится shared только из-за нескольких экранов.
## Проверка
После placement/import change: `npm run deps`, `npm run typecheck`, `npm run knip`;
после собственного behavior — применимые tests. Финальный gate указан в AGENTS.
Review отдельно проверяет смысл owner: разрешённый dependency graph не доказывает
правильный выбор слоя.
+56
View File
@@ -0,0 +1,56 @@
# 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 runtime OpenAPI — source; committed 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 вручную не исправляется. Изменение wire shape/status/route проходит
[общий contract workflow](../../../docs/development/runtime-openapi-verification.md).
Одна 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.
Protected queries используют existing session-aware boundary; logout очищает
session-scoped cache. При auth/token change читай [security](../../../backend/.ai/conventions/security-auth.md)
и [authentication](../../../docs/features/authentication.md).
Успешная connection mutation коммитит подтверждённый status и сбрасывает financial
GET subtree; ошибка mutation не меняет generation/cache. Это отдельная граница
от обычного фонового refetch: [connection policy](../../../docs/features/tbank-connection.md#граница-финансового-кэша).
Default focus refetch выключен; freshness и retry определяются существующей
[query policy](../../../docs/adr/007-react-query-fetch-policy.md) и feature source.
При обычном refetch сохраняй Client Cached Snapshot, при session/connection
смене не показывай данные прежнего владельца. UI presentation — в UI convention.
## Примеры и проверка
[usePortfolio](../../src/entities/portfolio/api/usePortfolio.ts) — entity query;
[shared client](../../src/shared/api/client.ts) — transport/auth boundary.
Изменяя header/retry/invalidation, проверяй наблюдаемое поведение через MSW и
[Testing](frontend-testing.md); не обращайся к реальному T-Bank.
`npm run openapi:check`, `npm run typecheck` и targeted tests проверяют разные
части решения. Для API semantics нужны также exact docs и contract gate.
+50
View File
@@ -0,0 +1,50 @@
# Frontend testing
## Выбор проверки
Читай при изменении тестов или behavior с [testing risk](../../../docs/development/engineering-governance.md#risk-based-testing).
Выбирай boundary по наблюдаемому исходу, а не внутреннему helper/component state.
| Изменение | Дешёвая надёжная проверка |
| --- | --- |
| Чистое domain преобразование/денежный расчёт | Unit case с граничными значениями |
| Форма, route access, recovery, UI-state selection | RTL component/route test через видимое поведение |
| Headers, refresh/retry, query invalidation | MSW на transport boundary |
| Только declarative props/Storybook fixture | Type/build + semantic/visual review; unit test без собственной логики не нужен |
Tests colocated с source (`*.test.tsx` для UI, существующий `*.test.ts` для pure code).
Arrange/Act/Assert, semantic queries (`getByRole`, label); test id — когда
пользовательский selector не выражает нужный элемент. Не тестируй Mantine/React
internals, private calls или passthrough wrapper ради coverage.
## Инструменты и isolation
Используй `fireEvent` из объявленного `@testing-library/react` и ожидай async
outcome через `findBy*`/`waitFor`. `user-event` доступен транзитивно через Storybook,
но не принят как direct dependency; не добавляй его ради старого Markdown.
MSW запускается только Vitest setup в `src/test/`, без browser worker/mock dev mode.
Проверяй сеть MSW handlers, а не mock внутренностей query client или реальный backend.
Независимый case получает свой `createTestQueryClient`; rerender сохраняет
экземпляр и seeded cache. Providers выбирай явно. Existing teardown выполняет
RTL cleanup, auth reset и MSW reset: [test client](../../src/test/query.tsx).
## Важные случаи
Форма: field errors, отсутствие request при invalid input, submit blocking,
сохранение ввода при network error. Routes: доступ/redirect и neutral auth bootstrap.
Finance: loading/error/empty/loaded, cached snapshot при refetch failure, retry,
отсутствие старых данных после session/connection switch. ErrorBoundary: test-only
throwing route, безопасная ссылка и reset после navigation.
Примеры: [AuthForm test](../../src/features/auth/ui/AuthForm.test.tsx),
[client test](../../src/shared/api/client.test.ts),
[usePortfolio test](../../src/entities/portfolio/api/usePortfolio.test.tsx).
Не копируй fixtures/сценарии целиком, если задача затрагивает один исход.
## Команды
Итерация: `npm run test:run -- <path>` без coverage. Полный `npm run verify`
запускает Vitest один раз через `test:coverage`. Coverage floors — anti-regression
constraint, не повод тестировать декларации. После UI изменения отдельно
проверь доступность/визуальные состояния, которых JSDOM не доказывает.
+99
View File
@@ -0,0 +1,99 @@
# Frontend UI
## Выбор компонента и state
Используй существующий Mantine component и общую тему, если они выражают нужное
поведение. Wrapper нужен для проектной ответственности, а не ради нового имени
для Button. Локальный CSS module принадлежит component; общий token вводи для
реально общей роли через [design system](../../../docs/development/design-system.md).
| Состояние | Владелец |
| --- | --- |
| Ответ сервера / query lifecycle | TanStack Query, [data rules](frontend-data.md) |
| Ввод и ошибки формы | React Hook Form + Zod, container/VM |
| Параметр навигации, который должен переживать переход/ссылку | Router/URL |
| Локальное открытие, focus, rendering fallback | React UI state |
| Значение, вычисляемое из уже доступных inputs | Вычисление при render/в VM, без копирования через effect в state |
Effect нужен для синхронизации с внешней системой; действие пользователя
обрабатывай в handler. Не добавляй store, memoization или abstraction без текущей
потребности. Inspect ближайший component до создания нового pattern.
## Container, VM и view
Для data-driven finance page container вызывает один page VM и выбирает
Loading/Error/Empty/Loaded view. VM владеет data/auth/route inputs, state selection,
retry callbacks, domain grouping и derived presentation data. View получает props.
| Файл | Ответственность |
| --- | --- |
| `Component.tsx` | Container или публичный component |
| `Component.vm.ts` | Page VM, данные и действия |
| `Component.view.tsx` | Presentation |
| `Component.types.ts` | Переиспользуемые props/local types, когда отдельный owner полезен |
| `Component.module.css` | Локальные стили |
Во view нет data/auth/router/navigation/VM hooks и вычислений по domain collection.
Допустимы `useTranslation`, form-context hooks, UI-only state (например failed
image fallback) и formatter одного переданного значения. Группировка/доли
коллекции готовятся в VM. Перенос fetch во view ради меньшего container нарушает границу.
Отдельный state/section с самостоятельной responsibility выноси в flat
`components/*.view.tsx`. Небольшой tightly-coupled renderer оставляй рядом.
Не создавай folder/barrel/test/CSS для каждого view и не дроби по line count.
Пример: [AccountIncomePage](../../src/pages/account-income/ui/AccountIncomePage.tsx)
и [его VM](../../src/pages/account-income/ui/AccountIncomePage.vm.ts).
## Формы
Используй `react-hook-form`, `zodResolver`, `FormProvider`. Container/VM создаёт
form state, view получает `onSubmit`/`disabled`, fields используют `useFormContext`.
Во время submit блокируй все действия формы. Zod errors показывай у поля;
network error — отдельным Alert с сохранением ввода. Невалидная форма не отправляет request.
## Состояния и recovery
Без успешных данных выбирай loading → error → empty → loaded. При наличии query
данных сохраняй их во время refetch и его ошибки: progress, локальное предупреждение,
retry. Это Client Cached Snapshot, отдельный от server stale/fallback.
Known API errors принадлежат local view; непойманные route errors — route-keyed
ErrorBoundary. Сброс query cache после смены session/connection задают data rules.
Initial loading использует page-owned structural skeleton по semantic regions,
не одиночный spinner/StateView. Partial loading принадлежит section. Busy-область
доступна assistive technology, shapes декоративны, без данных, интерактивности
и focus. Route fallback сохраняет shell; до auth ready — neutral shell skeleton.
Button loading при mutation и компактный cached-snapshot progress остаются
контекстными indicators. Проверяй keyboard access к actions и безопасный retry.
## Код и components
Style/type automation задают configs. Используй `import type`; `enum` и
`namespace` в frontend не применяются. Named components — const-valued; Biome
проверяет declarations, no-default-export и exports-last в своём scope.
Обязательный review: `FC<Props>` и named props рядом/в types-файле; без inline
props typing и destructuring в параметрах. Принимай `props`, destructure в body.
Только children — `FC<PropsWithChildren>`; дополнительные props — named type
на основе `PropsWithChildren<{ ... }>`. Без props — `FC`.
Своди обычные exports к читаемому нижнему named block; barrel делает прямой
re-export без промежуточного import.
CSF `*.stories.tsx` сохраняют default meta и прямые story exports для indexer.
Vite/Vitest configs и `.storybook/main.ts`/`preview.tsx` имеют только
конфигурационное default-export exception. Scope исключений принадлежит biome.json.
## Строки и валидация
Продуктовый текст хранится в `src/shared/i18n/locales/ru.ts`, читается через
`useTranslation()`. В Zod используй `validationMessage(key)` из i18n/zod, без
пользовательских literal messages. Это mandatory semantic review: общего запрета
непереведённых JSX-строк в Biome/TypeScript нет. Synthetic story fixtures и
описания историй допустимы; они не заменяют продуктовую локализацию.
## Проверка
Type/style: `npm run typecheck`, `npm run lint`. Для risk behavior/изменения тестов
используй [Testing](frontend-testing.md). UI: keyboard, narrow viewport,
loading/error/empty/loaded и recovery по применимости. [Storybook](../../../docs/development/ui-catalog.md)
уже установлен; используй его как reference, но сверяй UI с задачей отдельно от build.
+18 -33
View File
@@ -1,39 +1,24 @@
# Frontend # Frontend
## Назначение и authority React/Vite-клиент; общие правила работы — в root `AGENTS.md`.
До frontend-решения прочитай [router](.ai/CONVENTIONS.md) и
[sources](.ai/SOURCES.md), затем только применимые документы/разделы.
React/Vite-клиент WealthLedger. Workspace layout и reference policy — ## Проверка
в root `AGENTS.md`. Для planning, implementation, validation и завершения
читай [canonical lifecycle](../docs/development/openspec-authority-and-lifecycle.md);
для recovery и выбора relevant evidence —
[context discipline](../docs/development/agent-token-efficiency.md).
Они доступны без optional agent skills или commands.
Frontend-specific conventions: [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md), Перед Node-командами: `nvm use --silent` по `.nvmrc`.
[`docs/STRUCTURE.md`](docs/STRUCTURE.md), [`docs/API-LAYER.md`](docs/API-LAYER.md), После lockfile install `npm ci` полный gate — `npm run verify`.
[`docs/UI-PATTERN.md`](docs/UI-PATTERN.md) и [`docs/TESTING.md`](docs/TESTING.md). Он проверяет OpenAPI drift, TypeScript, Biome, Vitest/coverage, Knip,
dependency boundaries и сборки приложения/Storybook без backend.
Точные scripts принадлежат `package.json`; targeted checks — у выбранного convention.
## Canonical gate HTTP contract changes дополнительно проходят общий
[contract workflow](../docs/development/runtime-openapi-verification.md).
Generated types редактируются через их source; T-Bank доступен только через backend.
Frontend dependencies используют exact versions. Pre-commit запускает staged
formatting и FSD checks; сообщения commits — Conventional Commits.
Перед Node-командами активировать `.nvmrc` через `nvm use --silent`; это выбирает Code/style checks автоматические только в scope конфигурации. i18n, props,
Node 22. Не запускать отдельную проверку версии, кроме диагностики runtime issue. semantic placement и выбор полезных тестов обязательны при review.
После lockfile install единственный completion gate: Storybook уже доступен как reference; ожидаемый UI и его состояния определяет
задача, а визуальное соответствие проверяется отдельно от сборки.
```bash
npm ci
npm run verify
```
Gate non-mutating и проверяет strict TypeScript, Biome, risk-based tests/coverage ratchet, Knip, dependency boundaries и build. Если backend API contract меняется, committed frontend snapshot `src/shared/api/spec/openapi.json` обновляется явным копированием backend OpenAPI document, затем `npm run openapi:generate` регенерирует committed types; обе стороны API contract сверяются отдельной backend командой `npm run test:openapi-contract`.
Актуальные scripts проверять в `package.json`/`npm run`.
## Enforceable boundaries
- Code style и i18n: [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md), исполняются Biome и TypeScript.
- React component declaration, props и export style: [`docs/CONVENTIONS.md`](docs/CONVENTIONS.md), часть исполняется Biome, часть является mandatory review convention.
- FSD и public API: [`docs/STRUCTURE.md`](docs/STRUCTURE.md), исполняются dependency-cruiser и Knip; направление `app → pages → features → entities → shared`.
- HTTP и types: [`docs/API-LAYER.md`](docs/API-LAYER.md); только typed client и generated OpenAPI `paths[...]`, без ручных transport DTO и прямых T-Bank calls.
- UI states и VM/view boundary: [`docs/UI-PATTERN.md`](docs/UI-PATTERN.md); container вызывает page VM hook, VM владеет data/state/derived presentation data, `*.view.tsx` получает props и рендерит. View files дробить по semantic responsibility, когда section/state можно назвать отдельно.
- Тесты: [`docs/TESTING.md`](docs/TESTING.md); новый тест обязателен по risk trigger, а не из-за появления файла или coverage процента.
Непроверяемые эстетические предпочтения считаются guidance. Не добавлять Storybook, Playwright, CI, browser MSW worker или mock development mode без утверждённого change.
+13 -8
View File
@@ -10,6 +10,7 @@ backend; прямые вызовы T-Bank из frontend не допускают
```bash ```bash
cp .env.example .env cp .env.example .env
nvm use --silent
npm ci npm ci
npm run dev npm run dev
``` ```
@@ -25,23 +26,27 @@ npm run verify
Команда проверяет, что generated OpenAPI types соответствуют committed snapshot, Команда проверяет, что generated OpenAPI types соответствуют committed snapshot,
затем запускает strict TypeScript, Biome, один coverage-backed запуск Vitest, затем запускает strict TypeScript, Biome, один coverage-backed запуск Vitest,
Knip, dependency rules и production build. Если backend-контракт изменился, Knip, dependency rules приложения и stories, сборки приложения и Storybook.
Если backend-контракт изменился,
обновите `src/shared/api/spec/openapi.json` копированием backend OpenAPI обновите `src/shared/api/spec/openapi.json` копированием backend OpenAPI
document и затем выполните `npm run openapi:generate`. document и затем выполните `npm run openapi:generate`.
## Документация ## Документация
- [Структура и FSD](docs/STRUCTURE.md) - [Router conventions](.ai/CONVENTIONS.md) и [источники](.ai/SOURCES.md)
- [API layer и OpenAPI](docs/API-LAYER.md) - [Выбор owner/FSD](.ai/conventions/frontend-architecture.md)
- [UI-паттерны](docs/UI-PATTERN.md) - [UI, state, формы и компоненты](.ai/conventions/frontend-ui.md)
- [Conventions](docs/CONVENTIONS.md) и [тестирование](docs/TESTING.md) - [Data/API/cache](.ai/conventions/frontend-data.md)
- [Testing](.ai/conventions/frontend-testing.md)
- [Поставленный UI-каталог Storybook](../docs/development/ui-catalog.md)
- [Обзор поставленной системы](../docs/architecture/system-overview.md) - [Обзор поставленной системы](../docs/architecture/system-overview.md)
- [Engineering governance и risk-based testing](../docs/development/engineering-governance.md) - [Engineering governance и risk-based testing](../docs/development/engineering-governance.md)
- [OpenSpec authority и lifecycle](../docs/development/openspec-authority-and-lifecycle.md) - [Карта проектных фактов](../docs/AI_INDEX.md)
- [Frontend workflow](../docs/development/frontend-workflow.md) - [Frontend workflow](../docs/development/frontend-workflow.md)
- [Remediation registry](../docs/development/audit-remediation.md) - [Remediation registry](../docs/development/audit-remediation.md)
Runtime OpenAPI backend сверяется с frontend snapshot отдельной read-only Runtime OpenAPI backend сверяется с frontend snapshot отдельной read-only
backend командой `npm run test:openapi-contract`. backend командой `npm run test:openapi-contract`.
Полный multi-repository change проверяется из workspace командой Разработка идёт напрямую по задаче пользователя. Каждый affected repo проходит
native OpenSpec validation and the canonical gates of each affected repository. свой verify; при изменении wire contract дополнительно выполняется contract check.
Обязательного OpenSpec/SDD lifecycle нет.
-19
View File
@@ -1,19 +0,0 @@
# API Layer
OpenAPI backend является источником истины. После изменения backend-контракта committed snapshot `src/shared/api/spec/openapi.json` обновляют простым копированием опубликованного документа backend, а затем регенерируют `src/shared/api/generated/schema.d.ts` командой `npm run openapi:generate`. Оба файла committed; generated файл не редактируется вручную.
`npm run openapi:generate` использует committed snapshot offline. `verify`
повторно генерирует types во временную директорию и сравнивает их с committed
`schema.d.ts`; backend для этого не требуется, а tracked files gate не меняет.
Все HTTP-вызовы используют `apiClient` и `apiQueryClient` из `src/shared/api`. Прямой `fetch` в features, pages и entities не используется. Query options берутся из `apiQueryClient`, а сохранённые cache keys и policies задаются вызывающим slice.
Bearer header принадлежит только shared authenticated fetch. Auth endpoints исключены; refresh сохраняет single-flight и один retry `UNAUTHORIZED`. Не передавайте `Authorization` из query или mutation.
Request и response types выводятся из generated `paths`, например:
```ts
type User = paths["/api/v1/auth/me"]["get"]["responses"][200]["content"]["application/json"];
```
Ручной domain/UI type допустим только если он отличается от transport DTO по смыслу, а не повторяет его поля.
-53
View File
@@ -1,53 +0,0 @@
# Conventions
## Must
- Использовать Biome: `npm run lint`; табы и двойные кавычки определяет `biome.json`.
- Для type-only импортов использовать `import type`; не использовать `enum` и `namespace`.
- UI-строки хранить в `src/shared/i18n/locales/ru.ts` и получать через `useTranslation()`.
- Zod-схемы не содержат пользовательских строк: использовать `validationMessage(key)` из `src/shared/i18n/zod.ts`.
- Публичный импорт слайса идёт через его `index.ts`.
- Тесты лежат рядом с исходником как `*.test.tsx`.
- Зависимости используют exact versions; не добавлять semver ranges.
- Полный локальный gate: `npm run verify`. Pre-commit запускает staged formatting
и FSD dependency checks; commit message должен следовать Conventional Commits.
## React component style
Biome-enforced guardrails:
- Named React components объявлять как const-valued components, а не function declarations.
- Не использовать default exports.
- Export statements держать после declarations тела module.
Storybook CSF-файлы `*.stories.tsx` используют штатный `export default meta`
и прямые `export const` для stories: статический indexer должен видеть их
метаданные и названия. Biome разрешает эти два исключения только для stories;
`.storybook/main.ts` и `preview.tsx` разрешают только обязательный default export.
Synthetic fixtures и Storybook descriptions являются данными/документацией
истории; продуктовые UI-строки по-прежнему принадлежат i18n.
Mandatory review conventions:
- React components типизировать через `FC`: `const Component: FC<Props> = (props) => { ... }`; если props нет — `const Component: FC = () => { ... }`.
- Props описывать named type рядом с component либо в `Component.types.ts`, если type переиспользуется или file становится крупным.
- Не использовать inline props typing в component parameters.
- Для components только с `children` использовать `FC<PropsWithChildren>` без отдельного props alias. Если кроме `children` есть другие props, использовать named props type на основе `PropsWithChildren<{ ... }>`.
- Не destructure props в component parameters; принимать `props` и destructure внутри component body.
- Обычный module export surface сводить к одному нижнему named export block, когда это выражается читаемо без потери public API.
- Чистые barrel files должны использовать прямой re-export без промежуточного import: `export { Component } from "./Component";`.
## File roles
- `Component.tsx` - контейнер или публичный компонент; для data-driven pages вызывает page VM hook и выбирает top-level state view.
- `Component.vm.ts` - hook с данными, действиями, state selection и derived presentation data.
- `Component.view.tsx` - чистое представление: получает props, рендерит JSX и не вызывает data/auth/router/navigation/VM hooks.
- `Component.types.ts` - props и локальные типы.
- `Component.module.css` - локальные стили.
Самостоятельный top-level state или именуемый section обычно выносится в
соседний `*.view.tsx`, когда у него есть отдельная responsibility, reuse либо
change cadence или он скрывает principal component. Небольшой tightly-coupled
renderer остаётся рядом с parent. Предпочтительный flat pattern —
`components/AccountOperationsPageLoaded.view.tsx` без отдельной директории,
barrel, test и stylesheet для каждого компонента.
-13
View File
@@ -1,13 +0,0 @@
# Frontend Documentation
Эти документы задают frontend-local conventions и дополняют `AGENTS.md`.
Общая engineering policy находится в
[`../../docs/development/engineering-governance.md`](../../docs/development/engineering-governance.md),
а фактические версии зависимостей и scripts всегда определяются конфигурацией
проекта.
- `CONVENTIONS.md` - обязательные правила кода и i18n.
- `API-LAYER.md` - OpenAPI, generated types и typed client.
- `UI-PATTERN.md` - формы и UI-состояния.
- `STRUCTURE.md` - FSD-слои, импорты и public API слайсов.
- `TESTING.md` - Vitest и Testing Library.
-24
View File
@@ -1,24 +0,0 @@
# Structure
Проект использует FSD-направление импортов: `app -> pages -> features -> entities -> shared`.
- `app` - providers и routes; общий визуальный фундамент принадлежит shared/theme.
- `pages` - страницы маршрутов.
- `features` - пользовательские сценарии, включая authentication.
- `entities` - доменные модели и их UI/API.
- `shared` - переиспользуемый код без доменной логики: API, i18n и UI.
Каждый слайс предоставляет наружу только public API через `index.ts`; внутренние файлы соседний слой не импортирует напрямую.
Для cross-slice imports используйте alias `@/` и public API, например `@/entities/portfolio`. Vite и TypeScript используют одинаковый alias.
`shared/theme/tokens.json` владеет опубликованными токенами и описаниями ролей
в переносимом формате WealthLedger v1. `theme.ts` напрямую адаптирует доверенный
локальный JSON в Mantine без runtime-схемы. `shared/theme/index.ts` экспортирует theme,
designTokens, семантические color roles и CSSVariablesResolver и подключает
базовые styles. App и Storybook подключают свои MantineProvider к этому public API.
Внешние системы читают raw JSON напрямую, без frontend barrel и его runtime.
Неопубликованные defaults остаются у Mantine, локальные domain styles — у владельцев.
`shared/ui` предоставляет
StateView и LoadingSkeleton; их каталог не включает route-aware AppShell.
Подробнее: [дизайн-система](../../docs/development/design-system.md).
-23
View File
@@ -1,23 +0,0 @@
# Testing
Canonical completion gate — `npm run verify`; он запускает Vitest один раз
через `npm run test:coverage`. `npm run test:run` остаётся короткой локальной
командой без coverage report.
Выбор нового теста определяется риском поведения, а не фактом изменения файла.
Автоматический тест обязателен для security/privacy, денежных расчётов,
целостности данных, concurrency, публичного контракта, критического journey и
регрессии. DTO без логики, декларативная конфигурация, тонкая orchestration и
поведение зависимости отдельного unit test не требуют, пока у них нет
собственного branching, transformation, invariant или regression. Полная
policy находится в [engineering governance](../../docs/development/engineering-governance.md).
MSW запускается только Vitest setup в `src/test/`; handlers покрывают текущий API. Не добавлять service worker, browser mocking или mock development mode. Для transport проверять заголовки и retry через MSW, не через реальный backend.
Проверяйте наблюдаемое поведение, а не детали реализации. UI-тест строится как Arrange, Act, Assert; для интерактивных действий предпочитайте `userEvent`. Форма должна проверять валидацию и отсутствие запроса при невалидных данных; маршруты - редиректы и доступность экрана.
Colocated tests finance views фиксируют выбор loading/error/empty/loaded и доступные recovery actions. Route boundary tests используют test-only throwing route и проверяют безопасную ссылку и reset после navigation.
Coverage floors остаются anti-regression constraint. Нельзя добавлять тест,
который только повторяет декларативную структуру или внутренности зависимости,
ради повышения процента.
-37
View File
@@ -1,37 +0,0 @@
# UI Pattern
Формы используют `react-hook-form`, `zodResolver` и `FormProvider`. Form state создаётся в контейнере или VM, чистое представление получает `onSubmit` и `disabled`, а поля подключает через `useFormContext`.
Во время submit все действия формы блокируются. Ошибки Zod отображаются у поля; ошибки сети сохраняют введённые значения и показываются отдельным `Alert`.
Запросные экраны без успешных данных выбирают состояние в порядке loading, error, empty, loaded. Если query уже содержит данные, они остаются видимыми при фоновом refetch и его ошибке: рядом показываются неблокирующий progress и локальное предупреждение с retry. Такое состояние называется Client Cached Snapshot; оно не заменяет доменное server-side stale/fallback состояние.
Initial loading без доступного результата использует page-owned structural skeleton, повторяющий semantic regions экрана (heading, cards, filters, summaries или rows), а не `StateView` или отдельный spinner. Partial loading принадлежит section view и занимает только ещё не загруженный блок. Skeleton region имеет доступное busy-обозначение, но его декоративные shapes не содержат данных, не интерактивны и не получают фокус. Route fallback сохраняет доступный application shell и skeleton-изирует content area; до auth `ready` public и protected guards показывают neutral shell skeleton. Button `loading` для mutation и compact progress при Client Cached Snapshot остаются контекстными indicators и не заменяются skeleton.
Для finance-экранов используется граница `container -> ViewModel -> view`.
`*.tsx` без суффикса — container: он вызывает единственный page VM hook и
выбирает top-level Loading/Error/Empty/Loaded view. `*.vm.ts` владеет data
fetching, auth/route inputs, state selection, retry callbacks, domain grouping
и derived presentation data. `*.view.tsx` и соседние presentation components
получают props и рендерят JSX.
View modules не вызывают data/auth/router/navigation/VM hooks: никаких
`useBrokerage*`, `usePortfolio`, `useAuth`, `useParams`, `useNavigate` или
page VM hooks внутри `*.view.tsx` и `components/*.view.tsx`. View modules также
не группируют domain collections и не считают derived collection values вроде
долей Portfolio Position. Эти значения готовит VM.
Допустимые hooks во view: `useTranslation()` для labels, form-context hooks в
form views и локальный UI-only state для rendering fallback, например failed
image state. Простое форматирование одного переданного значения через
существующий formatter остаётся presentation concern; вычисления из всей
коллекции остаются во VM.
View files дробятся по ownership и смысловой нагрузке, а не по количеству строк.
Самостоятельные top-level states и sections выносятся в flat `components/*.view.tsx`,
если это делает principal component и границы изменений яснее. Маленькие
tightly-coupled renderers остаются рядом с parent; механическое правило «один
component — один file» и line-count gate не используются.
Known API errors, retry и stale snapshot остаются local views, а непойманные
route errors обрабатывает общий route-keyed ErrorBoundary.