docs(ai): route frontend conventions by task
This commit is contained in:
@@ -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.
|
||||
@@ -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 по догадке.
|
||||
@@ -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 не доказывает
|
||||
правильный выбор слоя.
|
||||
@@ -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.
|
||||
@@ -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 не доказывает.
|
||||
@@ -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.
|
||||
@@ -1,39 +1,24 @@
|
||||
# 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),
|
||||
[`docs/STRUCTURE.md`](docs/STRUCTURE.md), [`docs/API-LAYER.md`](docs/API-LAYER.md),
|
||||
[`docs/UI-PATTERN.md`](docs/UI-PATTERN.md) и [`docs/TESTING.md`](docs/TESTING.md).
|
||||
Перед Node-командами: `nvm use --silent` по `.nvmrc`.
|
||||
После lockfile install `npm ci` полный gate — `npm run verify`.
|
||||
Он проверяет 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`; это выбирает
|
||||
Node 22. Не запускать отдельную проверку версии, кроме диагностики runtime issue.
|
||||
После lockfile install единственный completion gate:
|
||||
|
||||
```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.
|
||||
Code/style checks автоматические только в scope конфигурации. i18n, props,
|
||||
semantic placement и выбор полезных тестов обязательны при review.
|
||||
Storybook уже доступен как reference; ожидаемый UI и его состояния определяет
|
||||
задача, а визуальное соответствие проверяется отдельно от сборки.
|
||||
|
||||
@@ -10,6 +10,7 @@ backend; прямые вызовы T-Bank из frontend не допускают
|
||||
|
||||
```bash
|
||||
cp .env.example .env
|
||||
nvm use --silent
|
||||
npm ci
|
||||
npm run dev
|
||||
```
|
||||
@@ -25,23 +26,27 @@ npm run verify
|
||||
|
||||
Команда проверяет, что generated OpenAPI types соответствуют committed snapshot,
|
||||
затем запускает 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
|
||||
document и затем выполните `npm run openapi:generate`.
|
||||
|
||||
## Документация
|
||||
|
||||
- [Структура и FSD](docs/STRUCTURE.md)
|
||||
- [API layer и OpenAPI](docs/API-LAYER.md)
|
||||
- [UI-паттерны](docs/UI-PATTERN.md)
|
||||
- [Conventions](docs/CONVENTIONS.md) и [тестирование](docs/TESTING.md)
|
||||
- [Router conventions](.ai/CONVENTIONS.md) и [источники](.ai/SOURCES.md)
|
||||
- [Выбор owner/FSD](.ai/conventions/frontend-architecture.md)
|
||||
- [UI, state, формы и компоненты](.ai/conventions/frontend-ui.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)
|
||||
- [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)
|
||||
- [Remediation registry](../docs/development/audit-remediation.md)
|
||||
|
||||
Runtime OpenAPI backend сверяется с frontend snapshot отдельной read-only
|
||||
backend командой `npm run test:openapi-contract`.
|
||||
Полный multi-repository change проверяется из workspace командой
|
||||
native OpenSpec validation and the canonical gates of each affected repository.
|
||||
Разработка идёт напрямую по задаче пользователя. Каждый affected repo проходит
|
||||
свой verify; при изменении wire contract дополнительно выполняется contract check.
|
||||
Обязательного OpenSpec/SDD lifecycle нет.
|
||||
|
||||
@@ -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 по смыслу, а не повторяет его поля.
|
||||
@@ -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 для каждого компонента.
|
||||
@@ -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.
|
||||
@@ -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).
|
||||
@@ -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. Нельзя добавлять тест,
|
||||
который только повторяет декларативную структуру или внутренности зависимости,
|
||||
ради повышения процента.
|
||||
@@ -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.
|
||||
Reference in New Issue
Block a user